12 Commits

Author SHA1 Message Date
Tyler Akins
becc97c677 Updating to use a newer cache 2026-01-19 17:33:05 -06:00
Tyler Akins
edfce66305 Finally accessed a Mac to replicate the issue 2026-01-19 17:29:28 -06:00
Tyler Akins
e0b3b95eba Possible fix for Bash 3.2 2024-10-23 18:38:59 -05:00
Tyler Akins
7e86c1a5f5 Detect when variables are declared and not set
It is possible to declare a variable but not assign a value to it using
`export x`. When you do this, `declare -p x` shows the variable but does
not show an "=" nor any value afterwards.

When running another command, this variable will not be added to the
environment variables even though it is flagged as exported. Most likely
it's because the value is not a string and can't be easily converted to
a string; this is the same behavior as arrays.

Using `[[ -v x ]]` is also inadequate and I believe its because there
are false positives when trying to access data, which goes on to break
tests and the new braces and parenthesis indirection. Perhaps it could
be reviewed and made to work.

The best solution so far is to combine `declare -p` with `[[ -v` to see
if the variable is declared and if a value is set.

Closes #75.
2024-07-24 12:22:03 -05:00
Tyler Akins
b595ad26b7 Documenting that parents are not supported 2024-06-21 20:44:16 -05:00
Tyler Akins
5a49fe9900 Releasing 3.0.6 2024-06-16 21:13:43 -05:00
Tyler Akins
8056ee6961 Only cut strings once
It's faster to loop through the string and check the character at an
index than it is to trim single characters from the end in a loop.
Trimming multiple characters in the loop is surprisingly slower than
trimming one.

Addresses more of the speed problem reported in #73.
2024-06-16 21:11:45 -05:00
Tyler Akins
5db34e55d3 Caching function name lookups
Faster than dumping functions each time.
Related to #73.
2024-06-16 21:11:02 -05:00
Tyler Akins
26ca5059d8 Fix slowness with larger templates
Closes #69 and #71
Released 3.0.5
2024-03-27 21:57:11 -05:00
Tyler Akins
6e57510ba9 Making it more clear that sourced files are shell
Addresses part of the concerns from #69
2023-11-16 16:04:52 -06:00
Tyler Akins
54b2184b70 Bumping version 2023-09-10 08:11:54 -05:00
Tyler Akins
84d17268c9 Work with symbolic links 2023-09-10 08:10:51 -05:00
5 changed files with 130 additions and 42 deletions

View File

@@ -20,7 +20,7 @@ jobs:
uses: docker/setup-buildx-action@v1 uses: docker/setup-buildx-action@v1
- name: Cache Docker layers - name: Cache Docker layers
if: startsWith(github.ref, 'refs/tags/') if: startsWith(github.ref, 'refs/tags/')
uses: actions/cache@v2 uses: actions/cache@v4
with: with:
path: /tmp/.buildx-cache path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-${{ github.sha }} key: ${{ runner.os }}-buildx-${{ github.sha }}

View File

@@ -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. 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 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. 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. * 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. * 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 "`&gt;b&lt;`". * 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 "`&gt;b&lt;`".

147
mo
View File

@@ -38,7 +38,8 @@
#/ 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. The file must be a valid shell script
#/ and should only contain variable assignments.
#/ -o=DELIM, --open=DELIM #/ -o=DELIM, --open=DELIM
#/ Set the opening delimiter. Default is "{{". #/ Set the opening delimiter. Default is "{{".
#/ -c=DELIM, --close=DELIM #/ -c=DELIM, --close=DELIM
@@ -114,6 +115,8 @@ mo() (
moDoubleHyphens=false moDoubleHyphens=false
MO_OPEN_DELIMITER_DEFAULT="{{" MO_OPEN_DELIMITER_DEFAULT="{{"
MO_CLOSE_DELIMITER_DEFAULT="}}" MO_CLOSE_DELIMITER_DEFAULT="}}"
MO_FUNCTION_CACHE_HIT=()
MO_FUNCTION_CACHE_MISS=()
if [[ $# -gt 0 ]]; then if [[ $# -gt 0 ]]; then
for arg in "$@"; do for arg in "$@"; do
@@ -155,7 +158,7 @@ mo() (
moSource="${arg#-s=}" moSource="${arg#-s=}"
fi fi
if [[ -f "$moSource" ]]; then if [[ -e "$moSource" ]]; then
# shellcheck disable=SC1090 # shellcheck disable=SC1090
. "$moSource" . "$moSource"
else else
@@ -429,20 +432,19 @@ mo::indirectArray() {
# #
# Returns nothing. # Returns nothing.
mo::trimUnparsed() { mo::trimUnparsed() {
local moLast moR moN moT local moI moC
moLast="" moI=0
moR=$'\r' moC=${MO_UNPARSED:0:1}
moN=$'\n'
moT=$'\t'
while [[ "$MO_UNPARSED" != "$moLast" ]]; do while [[ "$moC" == " " || "$moC" == $'\r' || "$moC" == $'\n' || "$moC" == $'\t' ]]; do
moLast=$MO_UNPARSED moI=$((moI + 1))
MO_UNPARSED=${MO_UNPARSED# } moC=${MO_UNPARSED:$moI:1}
MO_UNPARSED=${MO_UNPARSED#"$moR"}
MO_UNPARSED=${MO_UNPARSED#"$moN"}
MO_UNPARSED=${MO_UNPARSED#"$moT"}
done done
if [[ "$moI" != 0 ]]; then
MO_UNPARSED=${MO_UNPARSED:$moI}
fi
} }
@@ -900,9 +902,41 @@ mo::parseValue() {
# #
# Returns 0 if the name is a function, 1 otherwise. # Returns 0 if the name is a function, 1 otherwise.
mo::isFunction() { mo::isFunction() {
if declare -F "$1" &> /dev/null; then local moFunctionName
# Need to test for the array length, otherwise Mac will report an
# unbound variable
if [[ "${#MO_FUNCTION_CACHE_HIT[@]}" -gt 0 ]]; then
for moFunctionName in "${MO_FUNCTION_CACHE_HIT[@]}"; do
if [[ "$moFunctionName" == "$1" ]]; then
return 0 return 0
fi fi
done
fi
if [[ "${#MO_FUNCTION_CACHE_MISS[@]}" -gt 0 ]]; then
for moFunctionName in "${MO_FUNCTION_CACHE_MISS[@]}"; do
if [[ "$moFunctionName" == "$1" ]]; then
return 1
fi
done
fi
if declare -F "$1" &> /dev/null; then
if [[ "${#MO_FUNCTION_CACHE_HIT[@]}" -gt 0 ]]; then
MO_FUNCTION_CACHE_HIT=( ${MO_FUNCTION_CACHE_HIT[@]+"${MO_FUNCTION_CACHE_HIT[@]}"} "$1" )
else
MO_FUNCTION_CACHE_HIT=( "$1" )
fi
return 0
fi
if [[ "${#MO_FUNCTION_CACHE_MISS[@]}" -gt 0 ]]; then
MO_FUNCTION_CACHE_MISS=( ${MO_FUNCTION_CACHE_MISS[@]+"${MO_FUNCTION_CACHE_MISS[@]}"} "$1" )
else
MO_FUNCTION_CACHE_MISS=( "$1" )
fi
return 1 return 1
} }
@@ -982,14 +1016,46 @@ mo::isArrayIndexValid() {
# Can not use logic like this in case invalid variable names are passed. # Can not use logic like this in case invalid variable names are passed.
# [[ "${!1-a}" == "${!1-b}" ]] # [[ "${!1-a}" == "${!1-b}" ]]
# #
# Using logic like this gives false positives. Also, this is not supported on
# Bash 3.2 and the script parsing will error before any commands are executed.
# [[ -v "$a" ]]
#
# Declaring a variable is not the same as assigning the variable.
# export x
# declare -p x # Output: declare -x x
# # Bash 3.2 returns error code 1 and outputs:
# # bash: declare: x: not found
# 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. # Returns true (0) if the variable is set, 1 if the variable is unset.
mo::isVarSet() { MO_VAR_TEST="ok"
if ! declare -p "$1" &> /dev/null; then if test -v "MO_VAR_TEST" &> /dev/null; then
return 1 mo::debug "Using declare -p and [[ -v ]] for variable checks"
# More recent Bash
mo::isVarSet() {
# Do not convert this to [[, otherwise Bash 3.2 will fail to parse the
# script.
if declare -p "$1" &> /dev/null && test -v "$1"; then
return 0
fi fi
return 1
}
else
mo::debug "Using declare -p for variable checks"
# Bash 3.2
mo::isVarSet() {
if declare -p "$1" &> /dev/null; then
return 0 return 0
} fi
return 1
}
fi
unset MO_VAR_TEST
# Internal: Determine if a value is considered truthy. # Internal: Determine if a value is considered truthy.
@@ -1399,31 +1465,40 @@ mo::standaloneCheck() {
# #
# Returns nothing. # Returns nothing.
mo::standaloneProcess() { mo::standaloneProcess() {
local moContent moLast moT moR moN local moI moTemp
moT=$'\t'
moR=$'\r'
moN=$'\n'
moLast=
mo::debug "Standalone tag - processing content before and after tag" 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 # Mac appears to allow getting characters from before the start of the string
moLast=$MO_PARSED while [[ "$moI" -ge 0 ]] && [[ "${MO_PARSED:$moI:1}" == " " || "${MO_PARSED:$moI:1}" == $'\t' ]]; do
MO_PARSED=${MO_PARSED% } moI=$((moI - 1))
MO_PARSED=${MO_PARSED%"$moT"}
done done
moLast= if [[ $((moI + 1)) != "${#MO_PARSED}" ]]; then
MO_PARSED="${MO_PARSED:0:${moI}+1}"
fi
while [[ "$moLast" != "$MO_UNPARSED" ]]; do moI=0
moLast=$MO_UNPARSED
MO_UNPARSED=${MO_UNPARSED# } while [[ "${MO_UNPARSED:${moI}:1}" == " " || "${MO_UNPARSED:${moI}:1}" == $'\t' ]]; do
MO_UNPARSED=${MO_UNPARSED#"$moT"} moI=$((moI + 1))
done done
MO_UNPARSED=${MO_UNPARSED#"$moR"} if [[ "${MO_UNPARSED:${moI}:1}" == $'\r' ]]; then
MO_UNPARSED=${MO_UNPARSED#"$moN"} 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
} }
@@ -1949,7 +2024,7 @@ mo::tokenizeTagContentsSingleQuote() {
# Save the original command's path for usage later # Save the original command's path for usage later
MO_ORIGINAL_COMMAND="$(cd "${BASH_SOURCE[0]%/*}" || exit 1; pwd)/${BASH_SOURCE[0]##*/}" MO_ORIGINAL_COMMAND="$(cd "${BASH_SOURCE[0]%/*}" || exit 1; pwd)/${BASH_SOURCE[0]##*/}"
MO_VERSION="3.0.2" MO_VERSION="3.0.7"
# If sourced, load all functions. # If sourced, load all functions.
# If executed, perform the actions as expected. # If executed, perform the actions as expected.

View File

@@ -43,7 +43,8 @@ Options:
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. The file must be a valid shell script
and should only contain variable assignments.
-o=DELIM, --open=DELIM -o=DELIM, --open=DELIM
Set the opening delimiter. Default is "{{". Set the opening delimiter. Default is "{{".
-c=DELIM, --close=DELIM -c=DELIM, --close=DELIM
@@ -93,7 +94,7 @@ This is open source! Please feel free to contribute.
https://github.com/tests-always-included/mo https://github.com/tests-always-included/mo
MO_VERSION=3.0.2 MO_VERSION=3.0.7
EOF EOF
} }

9
tests/issue-75 Executable file
View 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