Pass shellcheck, more specs are handled, preserve function whitespace

This closes #49.
This commit is contained in:
Tyler Akins 2023-04-10 08:10:14 -05:00
parent e0e9189355
commit 7604ce3054
No known key found for this signature in database
GPG Key ID: 8F3B8C432F4393BD
46 changed files with 308 additions and 273 deletions

223
mo
View File

@ -15,26 +15,59 @@
#/ #/
#/ Options: #/ Options:
#/ #/
#/ --allow-function-arguments
#/ Permit functions to be called with additional arguments. Otherwise,
#/ the only way to get access to the arguments is to use the
#/ MO_FUNCTION_ARGS environment variable.
#/ -d, --debug
#/ Enable debug logging to stderr.
#/ -u, --fail-not-set #/ -u, --fail-not-set
#/ Fail upon expansion of an unset variable. #/ Fail upon expansion of an unset variable. Will silently ignore by
#/ default. Alternately, set MO_FAIL_ON_UNSET to a non-empty value.
#/ -x, --fail-on-function #/ -x, --fail-on-function
#/ Fail when a function returns a non-zero status code. #/ Fail when a function returns a non-zero status code instead of
#/ silently ignoring it. Alternately, set MO_FAIL_ON_FUNCTION to a
#/ non-empty value.
#/ -f, --fail-on-file
#/ Fail when a file (from command-line or partial) does not exist.
#/ Alternately, set MO_FAIL_ON_FILE to a non-empty value.
#/ -e, --false #/ -e, --false
#/ Treat the string "false" as empty for conditionals. #/ Treat the string "false" as empty for conditionals. Alternately,
#/ set MO_FALSE_IS_EMPTY to a non-empty value.
#/ -h, --help #/ -h, --help
#/ This message. #/ This message.
#/ -s=FILE, --source=FILE #/ -s=FILE, --source=FILE
#/ Load FILE into the environment before processing templates. #/ Load FILE into the environment before processing templates.
#/ Can be used multiple times. #/ Can be used multiple times.
#/ -d, --debug #/ -- Indicate the end of options. All arguments after this will be
#/ Enable debug logging to stderr. #/ treated as filenames only. Use when filenames may start with
# #/ hyphens.
# Mo is under a MIT style licence with an additional non-advertising clause. #/
# See LICENSE.md for the full text. #/ Mo uses the following environment variables:
# #/
# This is open source! Please feel free to contribute. #/ MO_ALLOW_FUNCTION_ARGUMENTS - When set to a non-empty value, this allows
# #/ functions referenced in templates to receive additional options and
# https://github.com/tests-always-included/mo #/ arguments.
#/ MO_DEBUG - When set to a non-empty value, additional debug information is
#/ written to stderr.
#/ MO_FUNCTION_ARGS - Arguments passed to the function.
#/ MO_FAIL_ON_FILE - If a filename from the command-line is missing or a
#/ partial does not exist, abort with an error.
#/ MO_FAIL_ON_FUNCTION - If a function returns a non-zero status code, abort
#/ with an error.
#/ MO_FAIL_ON_UNSET - When set to a non-empty value, expansion of an unset env
#/ variable will be aborted with an error.
#/ MO_FALSE_IS_EMPTY - When set to a non-empty value, the string "false" will
#/ be treated as an empty value for the purposes of conditionals.
#/ MO_ORIGINAL_COMMAND - Used to find the `mo` program in order to generate a
#/ help message.
#/
#/ Mo is under a MIT style licence with an additional non-advertising clause.
#/ See LICENSE.md for the full text.
#/
#/ This is open source! Please feel free to contribute.
#/
#/ https://github.com/tests-always-included/mo
# Public: Template parser function. Writes templates to stdout. # Public: Template parser function. Writes templates to stdout.
@ -42,63 +75,7 @@
# $0 - Name of the mo file, used for getting the help message. # $0 - Name of the mo file, used for getting the help message.
# $@ - Filenames to parse. # $@ - Filenames to parse.
# #
# Options: # See the comment above for details.
#
# --allow-function-arguments
#
# Permit functions in templates to be called with additional arguments. This
# puts template data directly in to the path of an eval statement. Use with
# caution. Not listed in the help because it only makes sense when mo is
# sourced.
#
# -u, --fail-not-set
#
# Fail upon expansion of an unset variable. Default behavior is to silently
# ignore and expand into empty string.
#
# -x, --fail-on-function
#
# Fail when a function used by a template returns an error status code.
# Alternately, ou may set the MO_FAIL_ON_FUNCTION environment variable to a
# non-empty value to enable this behavior.
#
# -e, --false
#
# Treat "false" as an empty value. You may set the MO_FALSE_IS_EMPTY
# environment variable instead to a non-empty value to enable this behavior.
#
# -h, --help
#
# Display a help message.
#
# -s=FILE, --source=FILE
#
# Source a file into the environment before processing template files.
# This can be used multiple times.
#
# --
#
# Used to indicate the end of options. You may optionally use this when
# filenames may start with two hyphens.
#
# Mo uses the following environment variables:
#
# MO_ALLOW_FUNCTION_ARGUMENTS - When set to a non-empty value, this allows
# functions referenced in templates to receive additional
# options and arguments. This puts the content from the
# template directly into an eval statement. Use with extreme
# care.
# MO_DEBUG - When set to a non-empty value, additional debug information is
# written to stderr.
# MO_FUNCTION_ARGS - Arguments passed to the function.
# MO_FAIL_ON_FUNCTION - If a function returns a non-zero status code, abort
# with an error.
# MO_FAIL_ON_UNSET - When set to a non-empty value, expansion of an unset env
# variable will be aborted with an error.
# MO_FALSE_IS_EMPTY - When set to a non-empty value, the string "false" will be
# treated as an empty value for the purposes of conditionals.
# MO_ORIGINAL_COMMAND - Used to find the `mo` program in order to generate a
# help message.
# #
# Returns nothing. # Returns nothing.
mo() ( mo() (
@ -140,6 +117,11 @@ mo() (
MO_FAIL_ON_FUNCTION=true MO_FAIL_ON_FUNCTION=true
;; ;;
-p | --fail-on-file)
# shellcheck disable=SC2030
MO_FAIL_ON_FILE=true
;;
-e | --false) -e | --false)
# shellcheck disable=SC2030 # shellcheck disable=SC2030
MO_FALSE_IS_EMPTY=true MO_FALSE_IS_EMPTY=true
@ -162,6 +144,7 @@ mo() (
;; ;;
-d | --debug) -d | --debug)
# shellcheck disable=SC2030
MO_DEBUG=true MO_DEBUG=true
;; ;;
@ -170,6 +153,10 @@ mo() (
moDoubleHyphens=true moDoubleHyphens=true
;; ;;
-*)
mo::error "Unknown option: $arg (See --help for options)"
;;
*) *)
#: Every arg that is not a flag or a option should be a file #: Every arg that is not a flag or a option should be a file
moFiles=(${moFiles[@]+"${moFiles[@]}"} "$arg") moFiles=(${moFiles[@]+"${moFiles[@]}"} "$arg")
@ -192,6 +179,7 @@ mo() (
# #
# Returns nothing. # Returns nothing.
mo::debug() { mo::debug() {
# shellcheck disable=SC2031
if [[ -n "${MO_DEBUG:-}" ]]; then if [[ -n "${MO_DEBUG:-}" ]]; then
echo "DEBUG: $1" >&2 echo "DEBUG: $1" >&2
fi fi
@ -205,7 +193,7 @@ mo::debug() {
# Returns nothing. Exits the program. # Returns nothing. Exits the program.
mo::error() { mo::error() {
echo "ERROR: $1" >&2 echo "ERROR: $1" >&2
exit ${2:-1} exit "${2:-1}"
} }
@ -264,17 +252,26 @@ mo::content() {
# #
# Returns nothing. # Returns nothing.
mo::contentFile() { mo::contentFile() {
local moContent moLen local moContent moFile
# The subshell removes any trailing newlines. We forcibly add # The subshell removes any trailing newlines. We forcibly add
# a dot to the content to preserve all newlines. # a dot to the content to preserve all newlines.
# As a future optimization, it would be worth considering removing # As a future optimization, it would be worth considering removing
# cat and replacing this with a read loop. # cat and replacing this with a read loop.
mo::debug "Loading content: ${2:-/dev/stdin}" moFile=${2:-/dev/stdin}
moContent=$(cat -- "${2:-/dev/stdin}" && echo '.') || return 1
moLen=$((${#moContent} - 1)) # shellcheck disable=SC2031
moContent=${moContent:0:$moLen} # Remove last dot if [[ -e "$moFile" ]]; then
mo::debug "Loading content: $moFile"
moContent=$(cat -- "$moFile" && echo '.') || return 1
moContent=${moContent%.} # Remove last dot
elif [[ -n "${MO_FAIL_ON_FILE-}" ]]; then
mo::error "No such file: $moFile"
else
mo::debug "File does not exist: $moFile"
moContent=""
fi
local "$1" && mo::indirect "$1" "$moContent" local "$1" && mo::indirect "$1" "$moContent"
} }
@ -384,9 +381,9 @@ mo::trim() {
while [[ "$moContent" != "$moLast" ]]; do while [[ "$moContent" != "$moLast" ]]; do
moLast=$moContent moLast=$moContent
moContent=${moContent# } moContent=${moContent# }
moContent=${moContent#$moR} moContent=${moContent#"$moR"}
moContent=${moContent#$moN} moContent=${moContent#"$moN"}
moContent=${moContent#$moT} moContent=${moContent#"$moT"}
done done
local "$1" && mo::indirect "$1" "$moContent" local "$1" && mo::indirect "$1" "$moContent"
@ -406,9 +403,9 @@ mo::chomp() {
moN=$'\n' moN=$'\n'
moT=$'\t' moT=$'\t'
moTemp=${2%% *} moTemp=${2%% *}
moTemp=${moTemp%%$moR*} moTemp=${moTemp%%"$moR"*}
moTemp=${moTemp%%$moN*} moTemp=${moTemp%%"$moN"*}
moTemp=${moTemp%%$moT*} moTemp=${moTemp%%"$moT"*}
local "$1" && mo::indirect "$1" "$moTemp" local "$1" && mo::indirect "$1" "$moTemp"
} }
@ -440,7 +437,7 @@ mo::chomp() {
# #
# Returns nothing. # Returns nothing.
mo::parse() { mo::parse() {
local moContent moCurrent moOpenDelimiter moCloseDelimieter moResult moSplit moParseChunk moFastMode moStandaloneContent moRemainder local moContent moCurrent moOpenDelimiter moCloseDelimiter moResult moSplit moParseChunk moFastMode moStandaloneContent moRemainder
moContent=$2 moContent=$2
moCurrent=$3 moCurrent=$3
moCurrentBlock=$4 moCurrentBlock=$4
@ -777,15 +774,15 @@ mo::parsePartial() {
moCloseDelimiter=$5 moCloseDelimiter=$5
moFastMode=$6 moFastMode=$6
moStandaloneContent=$7 moStandaloneContent=$7
mo::chomp moFilename "${moContent%%$moCloseDelimiter*}" mo::chomp moFilename "${moContent%%"$moCloseDelimiter"*}"
moContent="${moContent#*$moCloseDelimiter}" moContent="${moContent#*"$moCloseDelimiter"}"
moIndentation="" moIndentation=""
if mo::standaloneCheck "$moStandaloneContent" "$moContent"; then if mo::standaloneCheck "$moStandaloneContent" "$moContent"; then
moN=$'\n' moN=$'\n'
moR=$'\r' moR=$'\r'
moIndentation="$moN${moPrevious//$moR/$moN}" moIndentation="$moN${moPrevious//"$moR"/"$moN"}"
moIndentation=${moIndentation##*$moN} moIndentation=${moIndentation##*"$moN"}
mo::debug "Adding indentation to partial: '$moIndentation'" mo::debug "Adding indentation to partial: '$moIndentation'"
mo::standaloneProcessBefore moPrevious "$moPrevious" mo::standaloneProcessBefore moPrevious "$moPrevious"
mo::standaloneProcessAfter moContent "$moContent" mo::standaloneProcessAfter moContent "$moContent"
@ -796,7 +793,6 @@ mo::parsePartial() {
if [[ -n "$moFastMode" ]]; then if [[ -n "$moFastMode" ]]; then
moResult="" moResult=""
moLen=0
else else
mo::debug "Parsing partial: $moFilename" mo::debug "Parsing partial: $moFilename"
@ -901,7 +897,7 @@ mo::parseComment() {
moContent=$3 moContent=$3
moCloseDelimiter=$4 moCloseDelimiter=$4
moStandaloneContent=$5 moStandaloneContent=$5
moContent=${moContent#*$moCloseDelimiter} moContent=${moContent#*"$moCloseDelimiter"}
mo::debug "Parsing comment" mo::debug "Parsing comment"
if mo::standaloneCheck "$moStandaloneContent" "$moContent"; then if mo::standaloneCheck "$moStandaloneContent" "$moContent"; then
@ -942,8 +938,8 @@ mo::parseDelimiter() {
mo::chomp moOpen "$moContent" mo::chomp moOpen "$moContent"
moContent=${moContent:${#moOpen}} moContent=${moContent:${#moOpen}}
mo::trim moContent "$moContent" mo::trim moContent "$moContent"
moClose="${moContent%%=$moCloseDelimiter*}" moClose="${moContent%%="$moCloseDelimiter"*}"
moContent=${moContent#*=$moCloseDelimiter} moContent=${moContent#*="$moCloseDelimiter"}
mo::debug "Parsing delimiters: $moOpen $moClose" mo::debug "Parsing delimiters: $moOpen $moClose"
if mo::standaloneCheck "$moStandaloneContent" "$moContent"; then if mo::standaloneCheck "$moStandaloneContent" "$moContent"; then
@ -983,7 +979,7 @@ mo::parseValue() {
moOpenDelimiter=$5 moOpenDelimiter=$5
moCloseDelimiter=$6 moCloseDelimiter=$6
moFastMode=$7 moFastMode=$7
mo::trim moContent "${moContentOriginal#$moOpenDelimiter}" mo::trim moContent "${moContentOriginal#"$moOpenDelimiter"}"
mo::parseValueInner moArgs "$moContent" "$moCurrent" "$moCloseDelimiter" mo::parseValueInner moArgs "$moContent" "$moCurrent" "$moCloseDelimiter"
moContent=${moArgs[0]} moContent=${moArgs[0]}
@ -1252,7 +1248,7 @@ mo::getArgumentDefault() {
local moTemp moContent local moTemp moContent
moTemp=$2 moTemp=$2
mo::chomp moTemp "${moTemp%%$3*}" mo::chomp moTemp "${moTemp%%"$3"*}"
moTemp=${moTemp%%)*} moTemp=${moTemp%%)*}
moTemp=${moTemp%%\}*} moTemp=${moTemp%%\}*}
moContent=${2:${#moTemp}} moContent=${2:${#moTemp}}
@ -1384,6 +1380,7 @@ mo::isTruthy() {
moTruthy=true moTruthy=true
# shellcheck disable=SC2031
if [[ -z "${1-}" ]]; then if [[ -z "${1-}" ]]; then
moTruthy=false moTruthy=false
elif [[ -n "${MO_FALSE_IS_EMPTY-}" ]] && [[ "${1-}" == "false" ]]; then elif [[ -n "${MO_FALSE_IS_EMPTY-}" ]] && [[ "${1-}" == "false" ]]; then
@ -1533,7 +1530,7 @@ mo::evaluateKey() {
# #
# Returns nothing. # Returns nothing.
mo::evaluateVariable() { mo::evaluateVariable() {
local moResult moCurrent moArg moNameParts moJoined moKey moValue local moResult moCurrent moArg moNameParts
moArg=$2 moArg=$2
moCurrent=$3 moCurrent=$3
@ -1543,7 +1540,7 @@ mo::evaluateVariable() {
if [[ -z "${moNameParts[1]}" ]]; then if [[ -z "${moNameParts[1]}" ]]; then
if mo::isArray "$moArg"; then if mo::isArray "$moArg"; then
eval mo::join moResult "," "\${$moArg[@]}" eval mo::join moResult "," "\${${moArg}[@]}"
else else
# shellcheck disable=SC2031 # shellcheck disable=SC2031
if mo::isVarSet "$moArg"; then if mo::isVarSet "$moArg"; then
@ -1560,7 +1557,7 @@ mo::evaluateVariable() {
fi fi
fi fi
local $1 && mo::indirect "$1" "$moResult" local "$1" && mo::indirect "$1" "$moResult"
} }
@ -1648,7 +1645,7 @@ moJoin() {
# #
# Returns nothing. # Returns nothing.
mo::evaluateFunction() { mo::evaluateFunction() {
local moArgs moContent moFunctionArgs moFunctionResult moTarget moFunction moTemp moFunctionCall local moArgs moContent moFunctionResult moTarget moFunction moTemp moFunctionCall
moTarget=$1 moTarget=$1
moContent=$2 moContent=$2
@ -1675,16 +1672,18 @@ mo::evaluateFunction() {
fi fi
mo::debug "Calling function: $moFunctionCall" mo::debug "Calling function: $moFunctionCall"
moContent=$(export MO_FUNCTION_ARGS=("${moArgs[@]}"); echo -n "$moContent" | eval "$moFunctionCall") || {
# Call the function in a subshell for safety. Employ the trick to preserve
# whitespace at the end of the output.
moContent=$(export MO_FUNCTION_ARGS=("${moArgs[@]}"); echo -n "$moContent" | eval "$moFunctionCall ; moFunctionResult=\$? ; echo -n '.' ; exit \"\$moFunctionResult\"") || {
moFunctionResult=$? moFunctionResult=$?
# shellcheck disable=SC2031 # shellcheck disable=SC2031
if [[ -n "${MO_FAIL_ON_FUNCTION-}" && "$moFunctionResult" != 0 ]]; then if [[ -n "${MO_FAIL_ON_FUNCTION-}" && "$moFunctionResult" != 0 ]]; then
mo::error "Function '$moFunction' with args (${moArgs[@]+"${moArgs[@]}"}) failed with status code $moFunctionResult" "$moFunctionResult" mo::error "Function failed with status code $moFunctionResult: $moFunctionCall" "$moFunctionResult"
fi fi
} }
# shellcheck disable=SC2031 local "$moTarget" && mo::indirect "$moTarget" "${moContent%.}"
local "$moTarget" && mo::indirect "$moTarget" "$moContent"
} }
@ -1704,7 +1703,7 @@ mo::standaloneCheck() {
moT=$'\t' moT=$'\t'
# Check the content before # Check the content before
moContent=${1//$moR/$moN} moContent=${1//"$moR"/"$moN"}
if [[ "$moContent" != *"$moN"* ]]; then if [[ "$moContent" != *"$moN"* ]]; then
mo::debug "Not a standalone tag - no newline before" mo::debug "Not a standalone tag - no newline before"
@ -1712,8 +1711,8 @@ mo::standaloneCheck() {
return 1 return 1
fi fi
moContent=${moContent##*$moN} moContent=${moContent##*"$moN"}
moContent=${moContent//$moT/} moContent=${moContent//"$moT"/}
moContent=${moContent// /} moContent=${moContent// /}
if [[ -n "$moContent" ]]; then if [[ -n "$moContent" ]]; then
@ -1723,9 +1722,9 @@ mo::standaloneCheck() {
fi fi
# Check the content after # Check the content after
moContent=${2//$moR/$moN} moContent=${2//"$moR"/"$moN"}
moContent=${moContent%%$moN*} moContent=${moContent%%"$moN"*}
moContent=${moContent//$moT/} moContent=${moContent//"$moT"/}
moContent=${moContent// /} moContent=${moContent// /}
if [[ -n "$moContent" ]]; then if [[ -n "$moContent" ]]; then
@ -1756,7 +1755,7 @@ mo::standaloneProcessBefore() {
while [[ "$moLast" != "$moContent" ]]; do while [[ "$moLast" != "$moContent" ]]; do
moLast=$moContent moLast=$moContent
moContent=${moContent% } moContent=${moContent% }
moContent=${moContent%$moT} moContent=${moContent%"$moT"}
done done
local "$1" && mo::indirect "$1" "$moContent" local "$1" && mo::indirect "$1" "$moContent"
@ -1783,11 +1782,11 @@ mo::standaloneProcessAfter() {
while [[ "$moLast" != "$moContent" ]]; do while [[ "$moLast" != "$moContent" ]]; do
moLast=$moContent moLast=$moContent
moContent=${moContent# } moContent=${moContent# }
moContent=${moContent#$moT} moContent=${moContent#"$moT"}
done done
moContent=${moContent#$moR} moContent=${moContent#"$moR"}
moContent=${moContent#$moN} moContent=${moContent#"$moN"}
local "$1" && mo::indirect "$1" "$moContent" local "$1" && mo::indirect "$1" "$moContent"
} }
@ -1816,8 +1815,8 @@ mo::indentLines() {
mo::debug "Applying indentation: '${moIndentation}'" mo::debug "Applying indentation: '${moIndentation}'"
while [[ -n "$moContent" ]]; do while [[ -n "$moContent" ]]; do
moChunk=${moContent%%$moN*} moChunk=${moContent%%"$moN"*}
moChunk=${moChunk%%$moR*} moChunk=${moChunk%%"$moR"*}
moContent=${moContent:${#moChunk}} moContent=${moContent:${#moChunk}}
if [[ -n "$moChunk" ]]; then if [[ -n "$moChunk" ]]; then

View File

@ -11,6 +11,12 @@ const fsPromises = require("fs").promises;
// //
// To override any test property, just define that property. // To override any test property, just define that property.
const testOverrides = { const testOverrides = {
"Comments -> Variable Name Collision": {
// Can't use variables with exclamation points easily
data: {
comment: 4
}
},
"Interpolation -> HTML Escaping": { "Interpolation -> HTML Escaping": {
skip: "HTML escaping is not supported" skip: "HTML escaping is not supported"
}, },
@ -20,6 +26,9 @@ const testOverrides = {
"Lambdas -> Escaping": { "Lambdas -> Escaping": {
skip: "HTML escaping is not supported" skip: "HTML escaping is not supported"
}, },
"Partials -> Recursion": {
skip: "Complex objects are not supported and context is reset to the global level, so the recursion will loop forever"
},
"Sections -> Deeply Nested Contexts": { "Sections -> Deeply Nested Contexts": {
skip: "Nested objects are not supported" skip: "Nested objects are not supported"
}, },
@ -268,6 +277,7 @@ function applyTestOverrides(test) {
test[key] = value; test[key] = value;
} }
test.overridesApplied = true;
test.valuesBeforeOverride = originals; test.valuesBeforeOverride = originals;
} }
@ -310,6 +320,7 @@ function processSpecFile(filename) {
testSet.pass = 0; testSet.pass = 0;
testSet.fail = 0; testSet.fail = 0;
testSet.skip = 0; testSet.skip = 0;
testSet.passOverride = 0;
for (const test of testSet.tests) { for (const test of testSet.tests) {
if (test.isFailure) { if (test.isFailure) {
@ -318,10 +329,14 @@ function processSpecFile(filename) {
testSet.skip += 1; testSet.skip += 1;
} else { } else {
testSet.pass += 1; testSet.pass += 1;
if (test.overridesApplied) {
testSet.passOverride += 1;
}
} }
} }
console.log( console.log(
`### ${testSet.name} Results = ${testSet.pass} passed, ${testSet.fail} failed, ${testSet.skip} skipped` `### ${testSet.name} Results = ${testSet.pass} passed (with ${testSet.passOverride} overridden), ${testSet.fail} failed, ${testSet.skip} skipped`
); );
return testSet; return testSet;
@ -344,16 +359,18 @@ processArraySequentially(process.argv.slice(2), processSpecFile).then(
let pass = 0, let pass = 0,
fail = 0, fail = 0,
skip = 0, skip = 0,
total = 0; total = 0,
passOverride = 0;
for (const testSet of result) { for (const testSet of result) {
pass += testSet.pass; pass += testSet.pass;
fail += testSet.fail; fail += testSet.fail;
skip += testSet.skip; skip += testSet.skip;
total += testSet.tests.length; total += testSet.tests.length;
passOverride += testSet.passOverride;
console.log( console.log(
`* ${testSet.name}: ${testSet.tests.length} total, ${testSet.pass} pass, ${testSet.fail} fail, ${testSet.skip} skip` `* ${testSet.name}: ${testSet.tests.length} total, ${testSet.pass} pass (with ${passOverride} overridden), ${testSet.fail} fail, ${testSet.skip} skip`
); );
for (const test of testSet.tests) { for (const test of testSet.tests) {
@ -365,7 +382,7 @@ processArraySequentially(process.argv.slice(2), processSpecFile).then(
console.log(""); console.log("");
console.log( console.log(
`Final result: ${total} total, ${pass} pass, ${fail} fail, ${skip} skip` `Final result: ${total} total, ${pass} pass (with ${passOverride} overridden), ${fail} fail, ${skip} skip`
); );
if (fail) { if (fail) {

View File

@ -52,6 +52,8 @@ runTest() (
if [[ -n "${MO_DEBUG_TEST-}" ]]; then if [[ -n "${MO_DEBUG_TEST-}" ]]; then
declare -p testExpected declare -p testExpected
# Align the two declare outputs
echo -n " "
declare -p testActual declare -p testActual
fi fi

View File

@ -2,8 +2,8 @@
cd "${0%/*}" || exit 1 cd "${0%/*}" || exit 1
. ../run-tests . ../run-tests
thing="Works" export thing="Works"
template="{{&thing}}" export template="{{&thing}}"
expected="Works" export expected="Works"
runTest runTest

View File

@ -2,7 +2,7 @@
cd "${0%/*}" || exit 1 cd "${0%/*}" || exit 1
. ../run-tests . ../run-tests
repo=( "resque" "hub" "rip" ) export repo=( "resque" "hub" "rip" )
template() { template() {
cat <<EOF cat <<EOF
{{#repo}} {{#repo}}

View File

@ -6,6 +6,7 @@ declare -A repo
repo[resque]="Resque" repo[resque]="Resque"
repo[hub]="Hub" repo[hub]="Hub"
repo[rip]="Rip" repo[rip]="Rip"
export repo
template() { template() {
cat <<EOF cat <<EOF
{{#repo}} {{#repo}}

View File

@ -2,7 +2,7 @@
cd "${0%/*}" || exit 1 cd "${0%/*}" || exit 1
. ../run-tests . ../run-tests
template="Wor{{!comment}}ks" export template="Wor{{!comment}}ks"
expected="Works" export expected="Works"
runTest runTest

View File

@ -10,10 +10,6 @@ run through multiple
lines}}.</h1> lines}}.</h1>
EOF EOF
} }
expected() { export expected=$'<h1>Today.</h1>\n'
cat <<EOF
<h1>Today.</h1>
EOF
}
runTest runTest

View File

@ -2,7 +2,7 @@
cd "${0%/*}" || exit 1 cd "${0%/*}" || exit 1
. ../run-tests . ../run-tests
template="Wor{{! comment }}ks" export template="Wor{{! comment }}ks"
expected="Works" export expected="Works"
runTest runTest

View File

@ -2,9 +2,9 @@
cd "${0%/*}" || exit 1 cd "${0%/*}" || exit 1
. ../run-tests . ../run-tests
thing="Wor" export thing="Wor"
thing2="ks" export thing2="ks"
template="{{thing thing2}}" export template="{{thing thing2}}"
expected="Works" export expected="Works"
runTest runTest

View File

@ -2,8 +2,8 @@
cd "${0%/*}" || exit 1 cd "${0%/*}" || exit 1
. ../run-tests . ../run-tests
thing="Works" export thing="Works"
template="{{=| |=}}|thing|" export template="{{=| |=}}|thing|"
expected="Works" export expected="Works"
runTest runTest

View File

@ -2,9 +2,9 @@
cd "${0%/*}" || exit 1 cd "${0%/*}" || exit 1
. ../run-tests . ../run-tests
arguments=(-- --help) export arguments=(--fail-on-file -- --help)
returnCode=1 export returnCode=1
template="" export template=""
expected="cat: --help: No such file or directory"$'\n' export expected=$'ERROR: No such file: --help\n'
runTest runTest

View File

@ -2,7 +2,7 @@
cd "${0%/*}" || exit 1 cd "${0%/*}" || exit 1
. ../run-tests . ../run-tests
template='{{"Works"}}' export template='{{"Works"}}'
expected="Works" export expected="Works"
runTest runTest

View File

@ -3,10 +3,10 @@ cd "${0%/*}" || exit 1
. ../run-tests . ../run-tests
unset __NO_SUCH_VAR unset __NO_SUCH_VAR
POPULATED="words" export POPULATED="words"
EMPTY="" export EMPTY=""
arguments=(--fail-not-set) export arguments=(--fail-not-set)
returnCode=1 export returnCode=1
template() { template() {
cat <<EOF cat <<EOF
@ -15,10 +15,6 @@ Empty: {{EMPTY}};
Unset: {{__NO_SUCH_VAR}}; Unset: {{__NO_SUCH_VAR}};
EOF EOF
} }
expected() { export expected=$'ERROR: Environment variable not set: __NO_SUCH_VAR\n'
cat <<EOF
ERROR: Environment variable not set: __NO_SUCH_VAR
EOF
}
runTest runTest

View File

@ -5,14 +5,9 @@ cd "${0%/*}" || exit 1
failFunction() { failFunction() {
false false
} }
arguments=(--fail-on-function) export arguments=(--fail-on-function)
returnCode=1 export returnCode=1
export template="Fail on function? {{failFunction}}"
template="Fail on function? {{failFunction}}" export expected=$'ERROR: Function failed with status code 1: "failFunction"\n'
expected() {
cat <<EOF
ERROR: Function 'failFunction' with args () failed with status code 1
EOF
}
runTest runTest

View File

@ -2,9 +2,9 @@
cd "${0%/*}" || exit 1 cd "${0%/*}" || exit 1
. ../run-tests . ../run-tests
USER=j.doe export USER=j.doe
ADMIN=false export ADMIN=false
arguments=(--false) export arguments=(--false)
template() { template() {
cat <<EOF cat <<EOF
The user {{USER}} exists. The user {{USER}} exists.
@ -13,10 +13,6 @@ WRONG - should not be an admin.
{{/ADMIN}} {{/ADMIN}}
EOF EOF
} }
expected() { export expected=$'The user j.doe exists.\n'
cat <<EOF
The user j.doe exists.
EOF
}
runTest runTest

View File

@ -2,8 +2,8 @@
cd "${0%/*}" || exit 1 cd "${0%/*}" || exit 1
. ../run-tests . ../run-tests
USER=j.doe export USER=j.doe
ADMIN=false export ADMIN=false
MO_FALSE_IS_EMPTY=yeppers MO_FALSE_IS_EMPTY=yeppers
template() { template() {
cat <<EOF cat <<EOF
@ -13,10 +13,6 @@ WRONG - should not be an admin.
{{/ADMIN}} {{/ADMIN}}
EOF EOF
} }
expected() { export expected=$'The user j.doe exists.\n'
cat <<EOF
The user j.doe exists.
EOF
}
runTest runTest

View File

@ -2,7 +2,7 @@
cd "${0%/*}" || exit 1 cd "${0%/*}" || exit 1
. ../run-tests . ../run-tests
person="" export person=""
template() { template() {
cat <<EOF cat <<EOF
Shown. Shown.
@ -11,10 +11,6 @@ Shown.
{{/person}} {{/person}}
EOF EOF
} }
expected() { export expected=$'Shown.\n'
cat <<EOF
Shown.
EOF
}
runTest runTest

View File

@ -2,9 +2,10 @@
cd "${0%/*}" || exit 1 cd "${0%/*}" || exit 1
. ../run-tests . ../run-tests
name=Willy export name=Willy
wrapped() { wrapped() {
# This eats the newline in the content # Wrapping 'cat' in a subshell eats the trailing whitespace
# The echo adds a newline, which is preserved.
echo "<b>$(cat)</b>" echo "<b>$(cat)</b>"
} }
template() { template() {
@ -15,10 +16,6 @@ template() {
... this is the last line. ... this is the last line.
EOF EOF
} }
expected() { export expected=$'<b> Willy is awesome.</b>\n... this is the last line.\n'
cat <<EOF
<b> Willy is awesome.</b>... this is the last line.
EOF
}
runTest runTest

View File

@ -2,7 +2,7 @@
cd "${0%/*}" || exit 1 cd "${0%/*}" || exit 1
. ../run-tests . ../run-tests
name=Willy export name=Willy
MO_ALLOW_FUNCTION_ARGUMENTS=true MO_ALLOW_FUNCTION_ARGUMENTS=true
pipeTo() { pipeTo() {

View File

@ -3,8 +3,10 @@ cd "${0%/*}" || exit 1
. ../run-tests . ../run-tests
testArgs() { testArgs() {
local args=$(declare -p MO_FUNCTION_ARGS) local args
echo "${args#*=}" # shellcheck disable=SC2031
args=$(declare -p MO_FUNCTION_ARGS)
echo -n "${args#*=}"
} }
template() { template() {
cat <<EOF cat <<EOF

View File

@ -2,8 +2,8 @@
cd "${0%/*}" || exit 1 cd "${0%/*}" || exit 1
. ../run-tests . ../run-tests
STR=abc export STR=abc
DATA=(111 222) export DATA=(111 222)
template() { template() {
cat <<EOF cat <<EOF
Issue #7 Issue #7

View File

@ -2,10 +2,9 @@
cd "${0%/*}" || exit 1 cd "${0%/*}" || exit 1
. ../run-tests . ../run-tests
template="" export arguments=(--help)
arguments=(--help)
expected() { expected() {
cat <<EOF cat <<'EOF'
Mo is a mustache template rendering software written in bash. It inserts Mo is a mustache template rendering software written in bash. It inserts
environment variables into templates. environment variables into templates.
@ -21,19 +20,59 @@ Simple usage:
Options: Options:
--allow-function-arguments
Permit functions to be called with additional arguments. Otherwise,
the only way to get access to the arguments is to use the
MO_FUNCTION_ARGS environment variable.
-d, --debug
Enable debug logging to stderr.
-u, --fail-not-set -u, --fail-not-set
Fail upon expansion of an unset variable. Fail upon expansion of an unset variable. Will silently ignore by
default. Alternately, set MO_FAIL_ON_UNSET to a non-empty value.
-x, --fail-on-function -x, --fail-on-function
Fail when a function returns a non-zero status code. Fail when a function returns a non-zero status code instead of
silently ignoring it. Alternately, set MO_FAIL_ON_FUNCTION to a
non-empty value.
-f, --fail-on-file
Fail when a file (from command-line or partial) does not exist.
Alternately, set MO_FAIL_ON_FILE to a non-empty value.
-e, --false -e, --false
Treat the string "false" as empty for conditionals. Treat the string "false" as empty for conditionals. Alternately,
set MO_FALSE_IS_EMPTY to a non-empty value.
-h, --help -h, --help
This message. This message.
-s=FILE, --source=FILE -s=FILE, --source=FILE
Load FILE into the environment before processing templates. Load FILE into the environment before processing templates.
Can be used multiple times. Can be used multiple times.
-d, --debug -- Indicate the end of options. All arguments after this will be
Enable debug logging to stderr. treated as filenames only. Use when filenames may start with
hyphens.
Mo uses the following environment variables:
MO_ALLOW_FUNCTION_ARGUMENTS - When set to a non-empty value, this allows
functions referenced in templates to receive additional options and
arguments.
MO_DEBUG - When set to a non-empty value, additional debug information is
written to stderr.
MO_FUNCTION_ARGS - Arguments passed to the function.
MO_FAIL_ON_FILE - If a filename from the command-line is missing or a
partial does not exist, abort with an error.
MO_FAIL_ON_FUNCTION - If a function returns a non-zero status code, abort
with an error.
MO_FAIL_ON_UNSET - When set to a non-empty value, expansion of an unset env
variable will be aborted with an error.
MO_FALSE_IS_EMPTY - When set to a non-empty value, the string "false" will
be treated as an empty value for the purposes of conditionals.
MO_ORIGINAL_COMMAND - Used to find the `mo` program in order to generate a
help message.
Mo is under a MIT style licence with an additional non-advertising clause.
See LICENSE.md for the full text.
This is open source! Please feel free to contribute.
https://github.com/tests-always-included/mo
MO_VERSION=3.0.0 MO_VERSION=3.0.0
EOF EOF

View File

@ -2,7 +2,7 @@
cd "${0%/*}" || exit 1 cd "${0%/*}" || exit 1
. ../run-tests . ../run-tests
thisIsTrue=true export thisIsTrue=true
template() { template() {
cat <<EOF cat <<EOF
With spacing With spacing

View File

@ -2,8 +2,8 @@
cd "${0%/*}" || exit 1 cd "${0%/*}" || exit 1
. ../run-tests . ../run-tests
boolean=true export boolean=true
template=$' | {{#boolean}} {{! Important Whitespace }}\n {{/boolean}} | \n' export template=$' | {{#boolean}} {{! Important Whitespace }}\n {{/boolean}} | \n'
expected=$' | \n | \n' export expected=$' | \n | \n'
runTest runTest

View File

@ -2,14 +2,10 @@
cd "${0%/*}" || exit 1 cd "${0%/*}" || exit 1
. ../run-tests . ../run-tests
person="" export person=""
template="" export template=""
returnCode=1 export returnCode=1
arguments=(--something) export arguments=(--something)
expected() { export expected=$'ERROR: Unknown option: --something (See --help for options)\n'
cat <<EOF
cat: --something: No such file or directory
EOF
}
runTest runTest

View File

@ -2,7 +2,7 @@
cd "${0%/*}" || exit 1 cd "${0%/*}" || exit 1
. ../run-tests . ../run-tests
repo=() export repo=()
template() { template() {
cat <<EOF cat <<EOF
{{#repo}} {{#repo}}
@ -13,10 +13,6 @@ template() {
{{/repo}} {{/repo}}
EOF EOF
} }
expected() { export expected=$' No repos :(\n'
cat <<EOF
No repos :(
EOF
}
runTest runTest

View File

@ -2,13 +2,15 @@
cd "${0%/*}" || exit 1 cd "${0%/*}" || exit 1
. ../run-tests . ../run-tests
a=foo export a=foo
b=wrong export b=wrong
declare -A sec declare -A sec
sec=([b]="bar") sec=([b]="bar")
export sec
declare -A c declare -A c
c=([d]="baz") c=([d]="baz")
template="{{#sec}}{{a}} {{b}} {{c.d}}{{/sec}}" export c
expected="foo bar baz" export template="{{#sec}}{{a}} {{b}} {{c.d}}{{/sec}}"
export expected="foo bar baz"
runTest runTest

View File

@ -2,8 +2,8 @@
cd "${0%/*}" || exit 1 cd "${0%/*}" || exit 1
. ../run-tests . ../run-tests
name="Chris" export name="Chris"
company="<b>GitHub</b>" export company="<b>GitHub</b>"
template() { template() {
cat <<EOF cat <<EOF
* .{{name}}. * .{{name}}.

View File

@ -2,7 +2,7 @@
cd "${0%/*}" || exit 1 cd "${0%/*}" || exit 1
. ../run-tests . ../run-tests
multilineData=$'line 1\nline 2' export multilineData=$'line 1\nline 2'
template() { template() {
cat <<EOF cat <<EOF
Partial: Partial:
@ -26,7 +26,10 @@ Indented:
line 1 line 1
line 2 line 2
EOF EOF
# This one looks odd, but if you check the spec spec/specs/partials.yaml, name "Standalone Indentation" (mirrors "standalone-indentation" in tests/), then the spec clearly shows that the indentation is applied before rendering. # This one looks odd, but if you check the spec spec/specs/partials.yaml,
# name "Standalone Indentation" (mirrors "standalone-indentation" in
# tests/), then the spec clearly shows that the indentation is applied
# before rendering.
} }
runTest runTest

View File

@ -2,12 +2,12 @@
cd "${0%/*}" || exit 1 cd "${0%/*}" || exit 1
. ../run-tests . ../run-tests
USER=jwerle export USER=jwerle
GENDER=male export GENDER=male
THING=apple export THING=apple
COLOR=red export COLOR=red
PERSON=tobi export PERSON=tobi
ADJECTIVE=cool export ADJECTIVE=cool
template() { template() {
cat <<EOF cat <<EOF
{{! this is a comment }} {{! this is a comment }}

View File

@ -2,7 +2,7 @@
cd "${0%/*}" || exit 1 cd "${0%/*}" || exit 1
. ../run-tests . ../run-tests
template="Works" export template="Works"
expected="Works" export expected="Works"
runTest runTest

View File

@ -2,7 +2,7 @@
cd "${0%/*}" || exit 1 cd "${0%/*}" || exit 1
. ../run-tests . ../run-tests
names=( "Tyler" ) export names=( "Tyler" )
template() { template() {
cat <<EOF cat <<EOF
<h2>Names</h2> <h2>Names</h2>

9
tests/partial-bad-file Executable file
View File

@ -0,0 +1,9 @@
#!/usr/bin/env bash
cd "${0%/*}" || exit 1
. ../run-tests
# This file intentionally does not exist
export template="{{>fixtures/partial-bad-file.partial}}"
export expected=""
runTest

View File

@ -2,8 +2,9 @@
cd "${0%/*}" || exit 1 cd "${0%/*}" || exit 1
. ../run-tests . ../run-tests
returnCode=1 export returnCode=1
person="" export arguments=(--fail-on-file)
export person=""
template() { template() {
cat <<EOF cat <<EOF
Won't be there: {{> fixtures/partial-missing.partial}} Won't be there: {{> fixtures/partial-missing.partial}}
@ -11,7 +12,7 @@ EOF
} }
expected() { expected() {
cat <<EOF cat <<EOF
cat: partial-missing.partial: No such file or directory ERROR: No such file: partial-missing.partial
EOF EOF
} }

View File

@ -2,7 +2,7 @@
cd "${0%/*}" || exit 1 cd "${0%/*}" || exit 1
. ../run-tests . ../run-tests
template="{{'Works'}}" export template="{{'Works'}}"
expected="Works" export expected="Works"
runTest runTest

View File

@ -2,8 +2,8 @@
cd "${0%/*}" || exit 1 cd "${0%/*}" || exit 1
. ../run-tests . ../run-tests
thing="Works" export thing="Works"
template="{{thing}}" export template="{{thing}}"
expected="Works" export expected="Works"
runTest runTest

View File

@ -2,7 +2,7 @@
cd "${0%/*}" || exit 1 cd "${0%/*}" || exit 1
. ../run-tests . ../run-tests
arguments=(--source=fixtures/source.vars) export arguments=(--source=fixtures/source.vars)
template() { template() {
cat <<EOF cat <<EOF
{{VAR}} {{VAR}}

View File

@ -2,13 +2,9 @@
cd "${0%/*}" || exit 1 cd "${0%/*}" || exit 1
. ../run-tests . ../run-tests
arguments=(--source=invalid) export arguments=(--source=invalid)
returnCode=1 export returnCode=1
template="Do not display this" export template="Do not display this"
expected() { export expected=$'No such file: invalid\n'
cat <<EOF
No such file: invalid
EOF
}
runTest runTest

View File

@ -2,7 +2,7 @@
cd "${0%/*}" || exit 1 cd "${0%/*}" || exit 1
. ../run-tests . ../run-tests
arguments=(--source=fixtures/source-multiple-1.vars --source=fixtures/source-multiple-2.vars) export arguments=(--source=fixtures/source-multiple-1.vars --source=fixtures/source-multiple-2.vars)
template() { template() {
cat <<EOF cat <<EOF
A: {{A}} A: {{A}}

View File

@ -2,9 +2,9 @@
cd "${0%/*}" || exit 1 cd "${0%/*}" || exit 1
. ../run-tests . ../run-tests
arguments=(--source=) export arguments=(--source=)
returnCode=1 export returnCode=1
template="Do not display this" export template="Do not display this"
expected=$'No such file: \n' export expected=$'No such file: \n'
runTest runTest

View File

@ -2,7 +2,7 @@
cd "${0%/*}" || exit 1 cd "${0%/*}" || exit 1
. ../run-tests . ../run-tests
content=$'<\n->' export content=$'<\n->'
template() { template() {
cat <<EOF cat <<EOF
\ \

View File

@ -2,8 +2,8 @@
cd "${0%/*}" || exit 1 cd "${0%/*}" || exit 1
. ../run-tests . ../run-tests
thing="Works" export thing="Works"
template="{{{thing}}}" export template="{{{thing}}}"
expected="Works" export expected="Works"
runTest runTest

View File

@ -2,10 +2,10 @@
cd "${0%/*}" || exit 1 cd "${0%/*}" || exit 1
. ../run-tests . ../run-tests
NAME="Chris" export NAME="Chris"
VALUE=10000 export VALUE=10000
TAXED_VALUE=6000 export TAXED_VALUE=6000
IN_CA=true export IN_CA=true
template() { template() {
cat <<EOF cat <<EOF
Hello {{NAME}} Hello {{NAME}}

View File

@ -3,8 +3,8 @@ cd "${0%/*}" || exit 1
. ../run-tests . ../run-tests
declare -A a declare -A a
a=() export a=()
template="o{{#a.b}}WRONG{{/a.b}}k" export template="o{{#a.b}}WRONG{{/a.b}}k"
expected="ok" export expected="ok"
runTest runTest

View File

@ -2,8 +2,8 @@
cd "${0%/*}" || exit 1 cd "${0%/*}" || exit 1
. ../run-tests . ../run-tests
foo=bar export foo=bar
template="{{#foo}}{{.}} is {{foo}}{{/foo}}" export template="{{#foo}}{{.}} is {{foo}}{{/foo}}"
expected="bar is bar" export expected="bar is bar"
runTest runTest