mirror of
https://github.com/tests-always-included/mo.git
synced 2025-04-07 01:16:38 +00:00
Added assoc array expansion, new key var, and array key/value interpolation
This commit is contained in:
parent
73ea191a99
commit
5603ad4178
@ -14,6 +14,6 @@ Accessing data directly:
|
||||
|
||||
Things in DATA:
|
||||
{{#DATA}}
|
||||
Item: {{.}}
|
||||
{{$}}: {{.}}
|
||||
{{/DATA}}
|
||||
EOF
|
||||
|
28
demo/associative-cross-arrays
Executable file
28
demo/associative-cross-arrays
Executable 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
|
@ -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
50
demo/using-deep-arrays
Executable 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
150
mo
@ -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
|
||||
|
@ -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]}"
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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}}
|
||||
|
Loading…
x
Reference in New Issue
Block a user