Added assoc array expansion, new key var, and array key/value interpolation

This commit is contained in:
Joseph Dalrymple 2023-04-02 23:39:10 -05:00
parent 73ea191a99
commit 5603ad4178
8 changed files with 242 additions and 100 deletions

View File

@ -14,6 +14,6 @@ Accessing data directly:
Things in DATA:
{{#DATA}}
Item: {{.}}
{{$}}: {{.}}
{{/DATA}}
EOF

28
demo/associative-cross-arrays Executable file
View File

@ -0,0 +1,28 @@
#!/bin/bash
cd "$(dirname "$0")" # Go to the script's directory
declare -A DATA OTHER_DATA NUM_DATA
DATA=([one]=111 [two]=222)
# Lookup another array using same keys
OTHER_DATA=([one]="!!!" [two]="@@@")
# Lookup another array using value as key
NUM_DATA=([111]="One" [222]="Two")
. ../mo
cat <<EOF | mo
Accessing data directly:
DATA: {{DATA}}
One: {{DATA.one}}
Two: {{DATA.two}}
Things in DATA:
{{#DATA}}
DATA: {{$}}: {{.}}
OTHER_DATA: {{$}}: {{OTHER_DATA[$]}}
NUM_DATA: {{.}}: {{NUM_DATA[.]}}
{{/DATA}}
EOF

View File

@ -1,50 +1,34 @@
#!/usr/bin/env bash
# Example for how #54 can get implemented.
#
# This shows the key and value of an array element being appended
# to a function call's arguments list
cd "$(dirname "$0")" # Go to the script's directory
MO_ALLOW_FUNCTION_ARGUMENTS=true
# The links are associative arrays
#declare -a links
declare -A names urls links pewpew
pewpew[test1]=test1.1
pewpew[test2]=test2.2
pewpew[test3]=test3.3
links[foo]=resque
names[resque]=Resque
urls[resque]=http://example.com/resque
links[bar]=hub
names[hub]=Hub
urls[hub]=http://example.com/hub
links[quux]=rip
names[rip]=Rip
urls[rip]=http://example.com/rip
# helper functions
link_name() {
echo "$@" >&2
echo ${names[$3]}
}
link_url() {
echo "$@" >&2
echo ${urls[$2]}
}
# Source mo in order to work with arrays
declare -A DATA
DATA=([one]=111 [two]=222)
. ../mo
# Process the template
cat <<EOF | mo --allow-function-arguments
Here are your links:
{{#pewpew}}{{#links}}
* [{{link_name test}}]({{link_url}})
{{/links}}{{/pewpew}}
declare -a OTHER_DATA
OTHER_DATA=(foo bar)
LIST_ITEM() {
echo "- $1: $2"
}
SPECIAL_LIST_ITEM() {
echo "- [$1](./?item=$2): $3"
}
cat <<EOF | mo --allow-function-arguments
Accessing data directly:
DATA: {{DATA}}
One: {{DATA.one}}
Two: {{DATA.two}}
Things in DATA:
{{#DATA}}
{{LIST_ITEM}}
{{SPECIAL_LIST_ITEM Click}}
{{/DATA}}
EOF

50
demo/using-deep-arrays Executable file
View File

@ -0,0 +1,50 @@
#!/usr/bin/env bash
# Example for how #54 can get implemented.
cd "$(dirname "$0")" # Go to the script's directory
MO_ALLOW_FUNCTION_ARGUMENTS=true
# The links are associative arrays
declare -a sections
declare -A homeSection otherSection
declare -A resqueLink hubLink ripLink
resqueLink[title]="Resque"
resqueLink[url]="http://example.com/resque"
hubLink[title]="Hub"
hubLink[url]="http://example.com/hub"
ripLink[title]="Rip"
ripLink[url]="http://example.com/rip"
# By declaring `links` as an array
# the `links` sub-elements will be
# treated as arrays instead of strings
declare -a links
homeSection[name]="Home Section"
homeSection[links]="resqueLink hubLink"
otherSection[name]="Other Section"
otherSection[links]="ripLink"
sections=(homeSection otherSection)
# Source mo in order to work with arrays
. ../mo
# Process the template
cat <<EOF | mo --allow-function-arguments
{{#sections}}
# {{name}}
{{links}}
{{#links}}
- [{{title}}]({{url}})
{{/links}}
{{/sections}}
EOF

150
mo
View File

@ -187,19 +187,12 @@ mo() (
#
# Returns nothing.
moCallFunction() {
local moArgs moContent moBlock moLoopKeys moLoopValues moFunctionArgs moFunctionResult
local moArgs moContent moBlock moFunctionResult
local MO_LOOP_KEYS MO_LOOP MO_FUNCTION_ARGS
moArgs=()
moTrimWhitespace moFunctionArgs "$5"
# shellcheck disable=SC2031
if [[ -n "${MO_ALLOW_FUNCTION_ARGUMENTS-}" ]]; then
# Intentionally bad behavior
# shellcheck disable=SC2206
moArgs=($5)
fi
declare -a moLoopKeys moLoopValues
moTrimWhitespace MO_FUNCTION_ARGS "$5"
declare -a MO_LOOP_KEYS MO_LOOP
moBlock=$3
if [[ $4 == true ]]; then
@ -212,22 +205,24 @@ moCallFunction() {
for f in "${moFields[@]}"; do
local moField
moField=(${f//$moKeySep/ })
moLoopKeys+=(${moField[0]})
moLoopValues+=(${moField[1]})
MO_LOOP_KEYS+=(${moField[0]})
MO_LOOP+=(${moField[1]})
done;
moBlock=""
moArgs+=(${moLoopKeys[0]})
moArgs+=(${moLoopValues[0]})
fi
moContent=$(\
echo -n "$moBlock" | \
MO_LOOP_KEYS="${moLoopKeys[@]}" \
MO_LOOP="${moLoopValues[@]}" \
MO_FUNCTION_ARGS="$moFunctionArgs" \
eval "$2" "${moArgs[@]}"\
) || {
# shellcheck disable=SC2031
if [[ -n "${MO_ALLOW_FUNCTION_ARGUMENTS-}" ]]; then
# Intentionally bad behavior
# shellcheck disable=SC2206
moArgs=($5)
moArgs+=(${MO_LOOP_KEYS[0]})
moArgs+=(${MO_LOOP[0]})
fi
moContent=$(echo -n "$moBlock" | eval "$2" "${moArgs[@]}") || {
moFunctionResult=$?
# shellcheck disable=SC2031
if [[ -n "${MO_FAIL_ON_FUNCTION-}" && "$moFunctionResult" != 0 ]]; then
@ -345,7 +340,13 @@ moFindString() {
#
# Returns nothing.
moFullTagName() {
if [[ -z "${2-}" ]] || [[ "$2" == *.* ]]; then
if [[ "$3" =~ "[\$]" ]] && [[ "$2" == *.* ]]; then
local "$1" && moIndirect "$1" "${3%\[*}.${2#*.}"
elif [[ "$3" =~ "[.]" ]] && [[ "$2" == *.* ]]; then
local moValue
moValue=$(moShow "$2" "$2")
local "$1" && moIndirect "$1" "${3%\[*}.$moValue"
elif [[ -z "${2-}" ]] || [[ "$2" == *.* ]]; then
local "$1" && moIndirect "$1" "$3"
else
local "$1" && moIndirect "$1" "${2}.${3}"
@ -473,6 +474,44 @@ moIndirect() {
}
# Internal: Expand an array to local variables
#
# $1 - Array name
#
# If an associative array's key is also declared an array
# then its value will be treated as an array
#
# Returns nothing.
moExpandAssoc() {
local moKeys moValue moValueArr
moKeys=($(eval 'echo "${!'$1'[@]}"'))
for k in "${moKeys[@]}"; do
if moIsArray "$k"; then
#if moIsArrayList "${moValueArr[@]}"; then
moValueArr=($(eval 'echo "${'$1'['$k']}"'))
eval "$k=(${moValueArr[@]})"
else
moValue=$(eval 'echo "${'$1'['$k']}"')
local "$k" && moIndirect "$k" "$moValue"
fi
done
}
# Internal: Scans a string to determine if all elements are declared arrays
#
# $1-@ - Array elements
#
# Returns 0 if all elements match, otherwise Returns 1
moIsArrayList() {
for var in "$@"; do
if ! moIsArray "$var"; then
return 1
fi
done;
return 0
}
# Internal: Send an array as a variable up to caller of a function
#
# $1 - Variable name
@ -529,6 +568,36 @@ moIsArray() {
}
# Internal: Determine if a given environment variable exists and if it is
# an associative array.
#
# $1 - Name of environment variable
#
# Be extremely careful. Even if strict mode is enabled, it is not honored
# in newer versions of Bash. Any errors that crop up here will not be
# caught automatically.
#
# Examples
#
# declare -A var
# var[foo]="bar"
# if moIsArray var; then
# echo "This is an array"
# echo "Make sure you don't accidentally use \$var"
# fi
#
# Returns 0 if the name is not empty, 1 otherwise.
moIsAssocArray() {
# Namespace this variable so we don't conflict with what we're testing.
local moTestResult
moTestResult=$(declare -p "$1" 2>/dev/null) || return 1
[[ "${moTestResult:0:10}" == "declare -A" ]] && return 0
return 1
}
# Internal: Determine if the given name is a defined function.
#
# $1 - Function name to check
@ -734,16 +803,22 @@ moParse() {
moContent="${moBlock[2]}"
elif moIsArray "$moTag"; then
local moFullKey moValue
declare -a moKeys
moKeys=($(eval "echo \"\${!${moTag}[@]}\""))
for k in "${moKeys[@]}"; do
moFullTagName moFullKey "$moTag" "$k"
moValue=$(moShow "$moFullKey" "$moFullKey")
if moIsAssocArray "$moValue"; then
moExpandAssoc "$moValue"
fi
moCurContext=$(moAppendContext "$moContext" "$k" "$moValue")
moParse "${moBlock[0]}" "$moFullKey" false "$moCurContext"
done;
#eval "moLoop \"\${moBlock[0]}\" \"$moTag\" \"\${!${moTag}[@]}\""
else
moParse "${moBlock[0]}" "$moCurrent" true "$moContext"
fi
@ -791,6 +866,16 @@ moParse() {
moShow "$moCurrent" "$moCurrent"
;;
'$')
moStandaloneDenied moContent "${moContent[@]}"
# Current content (environment variable or function)
if [[ "$moCurrent" == *.* ]]; then
echo -n "${moCurrent#*.}"
else
echo -n "$moCurrent"
fi
;;
'=')
# Change delimiters
# Any two non-whitespace sequences separated by whitespace.
@ -811,15 +896,9 @@ moParse() {
moFullTagName moTag "$moCurrent" "$moTag"
moContent=${moContent[1]}
moCurContext="$moContext"
if [[ ! -z "$moCurrent" ]]; then
moBlock=$(moShow "$moCurrent" "$moCurrent")
moCurContext=$(moAppendContext "$moContext" "$k" "$moValue")
fi
# Now show the value
# Quote moArgs here, do not quote it later.
moShow "$moTag" "$moCurrent" "$moArgs" "$moCurContext"
moShow "$moTag" "$moCurrent" "$moArgs" "$moContext"
;;
'&'*)
@ -839,14 +918,8 @@ moParse() {
moArgs=${moArgs:${#moTag}}
moFullTagName moTag "$moCurrent" "$moTag"
moCurContext="$moContext"
if [[ ! -z "$moCurrent" ]]; then
moBlock=$(moShow "$moCurrent" "$moCurrent")
moCurContext=$(moAppendContext "$moContext" "$k" "$moValue")
fi
# Quote moArgs here, do not quote it later.
moShow "$moTag" "$moCurrent" "$moArgs" "$moCurContext"
moShow "$moTag" "$moCurrent" "$moArgs" "$moContext"
;;
esac
@ -992,6 +1065,7 @@ moShow() {
fi
}
# Internal: Split a larger string into an array.
#
# $1 - Destination variable

View File

@ -1,9 +1,11 @@
arr=(
"test1"
"test2"
arr1=(
"foo1"
)
arr2=(
"foo2"
"bar2"
)
testArgs() {
value=$(cat)
echo "$value - $MO_FUNCTION_ARGS"
echo "$MO_FUNCTION_ARGS ${MO_LOOP_KEYS[0]} ${MO_LOOP[0]}"
}

View File

@ -1,8 +1,10 @@
No args: [test1 - ] - done
One arg: [test1 - one] - done
Multiple arguments: [test1 - aa bb cc 'x' " ! {[_.|] - done
Evil: [test1 - bla; cat /etc/issue] - done
No args: [test2 - ] - done
One arg: [test2 - one] - done
Multiple arguments: [test2 - aa bb cc 'x' " ! {[_.|] - done
Evil: [test2 - bla; cat /etc/issue] - done
No args: 0: foo2 [ 0 foo2] - done
One arg: 0: foo2 [one 0 foo2] - done
Multiple arguments: 0: foo2 [aa bb cc 'x' " ! {[_.| 0 foo2] - done
Evil: 0: foo2 [bla; cat /etc/issue 0 foo2] - done
No args: 1: bar2 [ 1 bar2] - done
One arg: 1: bar2 [one 1 bar2] - done
Multiple arguments: 1: bar2 [aa bb cc 'x' " ! {[_.| 1 bar2] - done
Evil: 1: bar2 [bla; cat /etc/issue 1 bar2] - done

View File

@ -1,6 +1,8 @@
{{#arr}}
No args: [{{testArgs}}] - done
One arg: [{{testArgs one}}] - done
Multiple arguments: [{{testArgs aa bb cc 'x' " ! {[_.| }}] - done
Evil: [{{testArgs bla; cat /etc/issue}}] - done
{{/arr}}
{{#arr1}}
{{#arr2}}
No args: {{$}}: {{.}} [{{testArgs}}] - done
One arg: {{$}}: {{.}} [{{testArgs one}}] - done
Multiple arguments: {{$}}: {{.}} [{{testArgs aa bb cc 'x' " ! {[_.| }}] - done
Evil: {{$}}: {{.}} [{{testArgs bla; cat /etc/issue}}] - done
{{/arr2}}
{{/arr1}}