mirror of
https://github.com/tests-always-included/mo.git
synced 2025-04-08 09:44:12 +00:00
Compare commits
10 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
7e86c1a5f5 | ||
|
b595ad26b7 | ||
|
5a49fe9900 | ||
|
8056ee6961 | ||
|
5db34e55d3 | ||
|
26ca5059d8 | ||
|
6e57510ba9 | ||
|
54b2184b70 | ||
|
84d17268c9 | ||
|
68306c4c6d |
@ -117,9 +117,11 @@ There are more scripts available in the [demos directory](demo/) that could help
|
||||
|
||||
There are additional features that the program supports. Try using `mo --help` to see what is available.
|
||||
|
||||
Please note that this command is written in Bash and pulls data from either the environment or (when using `--source`) from a text file that will be sourced and loaded into the environment, which means you will need to have Bash-style variables defined. Please see the examples in `demo/` for different ways you can use `mo`.
|
||||
|
||||
|
||||
Enhancements
|
||||
-----------
|
||||
------------
|
||||
|
||||
In addition to many of the features built-in to Mustache, `mo` includes a number of unique features that make it a bit more powerful.
|
||||
|
||||
@ -263,6 +265,7 @@ Pull requests to solve the following issues would be helpful.
|
||||
|
||||
* Dotted names are supported but only for associative arrays (Bash 4). See [`demo/associative-arrays`](demo/associative-arrays) for an example.
|
||||
* There's no "top level" object, so `echo '{{.}}' | ./mo` does not do anything useful. In other languages you can say the data for the template is a string and in `mo` the data is always the environment. Luckily this type of usage is rare and `{{.}}` works great when iterating over an array.
|
||||
* [Parents](https://mustache.github.io/mustache.5.html#Parents), where a template can override chunks of a partial, are not supported.
|
||||
* HTML encoding is not built into `mo`. `{{{var}}}`, `{{&var}}` and `{{var}}` all do the same thing. `echo '{{TEST}}' | TEST='<b>' mo` will give you "`<b>`" instead of "`>b<`".
|
||||
|
||||
|
||||
|
135
mo
135
mo
@ -38,7 +38,8 @@
|
||||
#/ This message.
|
||||
#/ -s=FILE, --source=FILE
|
||||
#/ Load FILE into the environment before processing templates.
|
||||
#/ Can be used multiple times.
|
||||
#/ Can be used multiple times. The file must be a valid shell script
|
||||
#/ and should only contain variable assignments.
|
||||
#/ -o=DELIM, --open=DELIM
|
||||
#/ Set the opening delimiter. Default is "{{".
|
||||
#/ -c=DELIM, --close=DELIM
|
||||
@ -114,6 +115,8 @@ mo() (
|
||||
moDoubleHyphens=false
|
||||
MO_OPEN_DELIMITER_DEFAULT="{{"
|
||||
MO_CLOSE_DELIMITER_DEFAULT="}}"
|
||||
MO_FUNCTION_CACHE_HIT=()
|
||||
MO_FUNCTION_CACHE_MISS=()
|
||||
|
||||
if [[ $# -gt 0 ]]; then
|
||||
for arg in "$@"; do
|
||||
@ -155,7 +158,7 @@ mo() (
|
||||
moSource="${arg#-s=}"
|
||||
fi
|
||||
|
||||
if [[ -f "$moSource" ]]; then
|
||||
if [[ -e "$moSource" ]]; then
|
||||
# shellcheck disable=SC1090
|
||||
. "$moSource"
|
||||
else
|
||||
@ -429,20 +432,19 @@ mo::indirectArray() {
|
||||
#
|
||||
# Returns nothing.
|
||||
mo::trimUnparsed() {
|
||||
local moLast moR moN moT
|
||||
local moI moC
|
||||
|
||||
moLast=""
|
||||
moR=$'\r'
|
||||
moN=$'\n'
|
||||
moT=$'\t'
|
||||
moI=0
|
||||
moC=${MO_UNPARSED:0:1}
|
||||
|
||||
while [[ "$MO_UNPARSED" != "$moLast" ]]; do
|
||||
moLast=$MO_UNPARSED
|
||||
MO_UNPARSED=${MO_UNPARSED# }
|
||||
MO_UNPARSED=${MO_UNPARSED#"$moR"}
|
||||
MO_UNPARSED=${MO_UNPARSED#"$moN"}
|
||||
MO_UNPARSED=${MO_UNPARSED#"$moT"}
|
||||
while [[ "$moC" == " " || "$moC" == $'\r' || "$moC" == $'\n' || "$moC" == $'\t' ]]; do
|
||||
moI=$((moI + 1))
|
||||
moC=${MO_UNPARSED:$moI:1}
|
||||
done
|
||||
|
||||
if [[ "$moI" != 0 ]]; then
|
||||
MO_UNPARSED=${MO_UNPARSED:$moI}
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
@ -900,10 +902,28 @@ mo::parseValue() {
|
||||
#
|
||||
# Returns 0 if the name is a function, 1 otherwise.
|
||||
mo::isFunction() {
|
||||
local moFunctionName
|
||||
|
||||
for moFunctionName in "${MO_FUNCTION_CACHE_HIT[@]}"; do
|
||||
if [[ "$moFunctionName" == "$1" ]]; then
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
for moFunctionName in "${MO_FUNCTION_CACHE_MISS[@]}"; do
|
||||
if [[ "$moFunctionName" == "$1" ]]; then
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
|
||||
if declare -F "$1" &> /dev/null; then
|
||||
MO_FUNCTION_CACHE_HIT=( ${MO_FUNCTION_CACHE_HIT[@]+"${MO_FUNCTION_CACHE_HIT[@]}"} "$1" )
|
||||
|
||||
return 0
|
||||
fi
|
||||
|
||||
MO_FUNCTION_CACHE_MISS=( ${MO_FUNCTION_CACHE_MISS[@]+"${MO_FUNCTION_CACHE_MISS[@]}"} "$1" )
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
@ -982,13 +1002,24 @@ mo::isArrayIndexValid() {
|
||||
# Can not use logic like this in case invalid variable names are passed.
|
||||
# [[ "${!1-a}" == "${!1-b}" ]]
|
||||
#
|
||||
# Using logic like this gives false positives.
|
||||
# [[ -v "$a" ]]
|
||||
#
|
||||
# Declaring a variable is not the same as assigning the variable.
|
||||
# export x
|
||||
# declare -p x # Output: declare -x x
|
||||
# export y=""
|
||||
# declare -p y # Output: declare -x y=""
|
||||
# unset z
|
||||
# declare -p z # Error code 1 and output: bash: declare: z: not found
|
||||
#
|
||||
# Returns true (0) if the variable is set, 1 if the variable is unset.
|
||||
mo::isVarSet() {
|
||||
if ! declare -p "$1" &> /dev/null; then
|
||||
return 1
|
||||
if declare -p "$1" &> /dev/null && [[ -v "$1" ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
return 0
|
||||
return 1
|
||||
}
|
||||
|
||||
|
||||
@ -1061,7 +1092,7 @@ mo::evaluate() {
|
||||
;;
|
||||
|
||||
*)
|
||||
moStack=("${moStack[@]}" "$1" "$2")
|
||||
moStack=(${moStack[@]+"${moStack[@]}"} "$1" "$2")
|
||||
;;
|
||||
esac
|
||||
|
||||
@ -1077,7 +1108,7 @@ mo::evaluate() {
|
||||
else
|
||||
#: Concatenate
|
||||
mo::debug "Concatenating ${#moStack[@]} stack items"
|
||||
mo::evaluateListOfSingles moResult "${moStack[@]}"
|
||||
mo::evaluateListOfSingles moResult ${moStack[@]+"${moStack[@]}"}
|
||||
fi
|
||||
|
||||
local "$moTarget" && mo::indirect "$moTarget" "$moResult"
|
||||
@ -1310,10 +1341,12 @@ mo::evaluateFunction() {
|
||||
if [[ -n "${MO_ALLOW_FUNCTION_ARGUMENTS-}" ]]; then
|
||||
mo::debug "Function arguments are allowed"
|
||||
|
||||
for moTemp in "${moArgs[@]}"; do
|
||||
mo::escape moTemp "$moTemp"
|
||||
moFunctionCall="$moFunctionCall $moTemp"
|
||||
done
|
||||
if [[ ${#moArgs[@]} -gt 0 ]]; then
|
||||
for moTemp in "${moArgs[@]}"; do
|
||||
mo::escape moTemp "$moTemp"
|
||||
moFunctionCall="$moFunctionCall $moTemp"
|
||||
done
|
||||
fi
|
||||
fi
|
||||
|
||||
mo::debug "Calling function: $moFunctionCall"
|
||||
@ -1321,7 +1354,7 @@ mo::evaluateFunction() {
|
||||
#: 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[@]}")
|
||||
export MO_FUNCTION_ARGS=(${moArgs[@]+"${moArgs[@]}"})
|
||||
echo -n "$moContent" | eval "$moFunctionCall ; moFunctionResult=\$? ; echo -n '.' ; exit \"\$moFunctionResult\""
|
||||
) || {
|
||||
moFunctionResult=$?
|
||||
@ -1397,31 +1430,39 @@ mo::standaloneCheck() {
|
||||
#
|
||||
# Returns nothing.
|
||||
mo::standaloneProcess() {
|
||||
local moContent moLast moT moR moN
|
||||
|
||||
moT=$'\t'
|
||||
moR=$'\r'
|
||||
moN=$'\n'
|
||||
moLast=
|
||||
local moI moTemp
|
||||
|
||||
mo::debug "Standalone tag - processing content before and after tag"
|
||||
moI=$((${#MO_PARSED} - 1))
|
||||
mo::debug "zero done ${#MO_PARSED}"
|
||||
mo::escape moTemp "$MO_PARSED"
|
||||
mo::debug "$moTemp"
|
||||
|
||||
while [[ "$moLast" != "$MO_PARSED" ]]; do
|
||||
moLast=$MO_PARSED
|
||||
MO_PARSED=${MO_PARSED% }
|
||||
MO_PARSED=${MO_PARSED%"$moT"}
|
||||
while [[ "${MO_PARSED:$moI:1}" == " " || "${MO_PARSED:$moI:1}" == $'\t' ]]; do
|
||||
moI=$((moI - 1))
|
||||
done
|
||||
|
||||
moLast=
|
||||
if [[ $((moI + 1)) != "${#MO_PARSED}" ]]; then
|
||||
MO_PARSED="${MO_PARSED:0:${moI}+1}"
|
||||
fi
|
||||
|
||||
while [[ "$moLast" != "$MO_UNPARSED" ]]; do
|
||||
moLast=$MO_UNPARSED
|
||||
MO_UNPARSED=${MO_UNPARSED# }
|
||||
MO_UNPARSED=${MO_UNPARSED#"$moT"}
|
||||
moI=0
|
||||
|
||||
while [[ "${MO_UNPARSED:${moI}:1}" == " " || "${MO_UNPARSED:${moI}:1}" == $'\t' ]]; do
|
||||
moI=$((moI + 1))
|
||||
done
|
||||
|
||||
MO_UNPARSED=${MO_UNPARSED#"$moR"}
|
||||
MO_UNPARSED=${MO_UNPARSED#"$moN"}
|
||||
if [[ "${MO_UNPARSED:${moI}:1}" == $'\r' ]]; then
|
||||
moI=$((moI + 1))
|
||||
fi
|
||||
|
||||
if [[ "${MO_UNPARSED:${moI}:1}" == $'\n' ]]; then
|
||||
moI=$((moI + 1))
|
||||
fi
|
||||
|
||||
if [[ "$moI" != 0 ]]; then
|
||||
MO_UNPARSED=${MO_UNPARSED:${moI}}
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
@ -1767,7 +1808,7 @@ mo::tokenizeTagContents() {
|
||||
|
||||
"$moTerminator"*)
|
||||
mo::debug "Found terminator"
|
||||
local "$1" && mo::indirectArray "$1" "$moTokenCount" "${moResult[@]}"
|
||||
local "$1" && mo::indirectArray "$1" "$moTokenCount" ${moResult[@]+"${moResult[@]}"}
|
||||
return
|
||||
;;
|
||||
|
||||
@ -1775,7 +1816,7 @@ mo::tokenizeTagContents() {
|
||||
#: Do not tokenize the open paren - treat this as RPL
|
||||
MO_UNPARSED=${MO_UNPARSED:1}
|
||||
mo::tokenizeTagContents moTemp ')'
|
||||
moResult=("${moResult[@]}" "${moTemp[@]:1}" PAREN "${moTemp[0]}")
|
||||
moResult=(${moResult[@]+"${moResult[@]}"} "${moTemp[@]:1}" PAREN "${moTemp[0]}")
|
||||
MO_UNPARSED=${MO_UNPARSED:1}
|
||||
;;
|
||||
|
||||
@ -1783,7 +1824,7 @@ mo::tokenizeTagContents() {
|
||||
#: Do not tokenize the open brace - treat this as RPL
|
||||
MO_UNPARSED=${MO_UNPARSED:1}
|
||||
mo::tokenizeTagContents moTemp '}'
|
||||
moResult=("${moResult[@]}" "${moTemp[@]:1}" BRACE "${moTemp[0]}")
|
||||
moResult=(${moResult[@]+"${moResult[@]}"} "${moTemp[@]:1}" BRACE "${moTemp[0]}")
|
||||
MO_UNPARSED=${MO_UNPARSED:1}
|
||||
;;
|
||||
|
||||
@ -1793,17 +1834,17 @@ mo::tokenizeTagContents() {
|
||||
|
||||
"'"*)
|
||||
mo::tokenizeTagContentsSingleQuote moTemp
|
||||
moResult=("${moResult[@]}" "${moTemp[@]}")
|
||||
moResult=(${moResult[@]+"${moResult[@]}"} "${moTemp[@]}")
|
||||
;;
|
||||
|
||||
'"'*)
|
||||
mo::tokenizeTagContentsDoubleQuote moTemp
|
||||
moResult=("${moResult[@]}" "${moTemp[@]}")
|
||||
moResult=(${moResult[@]+"${moResult[@]}"} "${moTemp[@]}")
|
||||
;;
|
||||
|
||||
*)
|
||||
mo::tokenizeTagContentsName moTemp
|
||||
moResult=("${moResult[@]}" "${moTemp[@]}")
|
||||
moResult=(${moResult[@]+"${moResult[@]}"} "${moTemp[@]}")
|
||||
;;
|
||||
esac
|
||||
|
||||
@ -1947,7 +1988,7 @@ mo::tokenizeTagContentsSingleQuote() {
|
||||
|
||||
# Save the original command's path for usage later
|
||||
MO_ORIGINAL_COMMAND="$(cd "${BASH_SOURCE[0]%/*}" || exit 1; pwd)/${BASH_SOURCE[0]##*/}"
|
||||
MO_VERSION="3.0.1"
|
||||
MO_VERSION="3.0.7"
|
||||
|
||||
# If sourced, load all functions.
|
||||
# If executed, perform the actions as expected.
|
||||
|
@ -3,7 +3,8 @@ cd "${0%/*}" || exit 1
|
||||
. ../run-tests
|
||||
|
||||
declare -A repo
|
||||
repo[resque]="Resque"
|
||||
# The order of the array elements can be shuffled depending on the version of
|
||||
# Bash. Keeping this to a minimal set and alphabetized seems to help.
|
||||
repo[hub]="Hub"
|
||||
repo[rip]="Rip"
|
||||
export repo
|
||||
@ -18,7 +19,6 @@ expected() {
|
||||
cat <<EOF
|
||||
<b>hub - Hub</b>
|
||||
<b>rip - Rip</b>
|
||||
<b>resque - Resque</b>
|
||||
EOF
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,21 @@ testArgs() {
|
||||
local args
|
||||
# shellcheck disable=SC2031
|
||||
args=$(declare -p MO_FUNCTION_ARGS)
|
||||
echo -n "${args#*=}"
|
||||
|
||||
# The output from declare -p could look like these
|
||||
# declare -a MO_FUNCTION_ARGS=([0]="one")
|
||||
# declare -ax MO_FUNCTION_ARGS='([0]="one")'
|
||||
# Trim leading declare statement and variable name
|
||||
args="${args#*=}"
|
||||
|
||||
# If there are any quotes, remove them. The function arguments will always
|
||||
# be an array.
|
||||
if [[ "${args:0:1}" == "'" ]]; then
|
||||
args=${args#\'}
|
||||
args=${args%\'}
|
||||
fi
|
||||
|
||||
echo -n "$args"
|
||||
}
|
||||
template() {
|
||||
cat <<EOF
|
||||
|
@ -43,7 +43,8 @@ Options:
|
||||
This message.
|
||||
-s=FILE, --source=FILE
|
||||
Load FILE into the environment before processing templates.
|
||||
Can be used multiple times.
|
||||
Can be used multiple times. The file must be a valid shell script
|
||||
and should only contain variable assignments.
|
||||
-o=DELIM, --open=DELIM
|
||||
Set the opening delimiter. Default is "{{".
|
||||
-c=DELIM, --close=DELIM
|
||||
@ -93,7 +94,7 @@ This is open source! Please feel free to contribute.
|
||||
|
||||
https://github.com/tests-always-included/mo
|
||||
|
||||
MO_VERSION=3.0.1
|
||||
MO_VERSION=3.0.7
|
||||
EOF
|
||||
}
|
||||
|
||||
|
9
tests/issue-75
Executable file
9
tests/issue-75
Executable file
@ -0,0 +1,9 @@
|
||||
#!/usr/bin/env bash
|
||||
cd "${0%/*}" || exit 1
|
||||
. ../run-tests
|
||||
|
||||
export uv
|
||||
export template='{{^uv}}OK{{/uv}}{{#uv}}FAIL{{/uv}}'
|
||||
export expected='OK'
|
||||
|
||||
runTest
|
Loading…
x
Reference in New Issue
Block a user