mirror of
https://github.com/tests-always-included/mo.git
synced 2024-12-18 16:27:52 +00:00
698 lines
21 KiB
Bash
Executable File
698 lines
21 KiB
Bash
Executable File
#!/bin/bash
|
|
#
|
|
# Mo is a mustache template rendering software written in bash. It inserts
|
|
# environment variables into templates.
|
|
#
|
|
# Learn more about mustache templates at https://mustache.github.io/
|
|
#
|
|
# 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
|
|
|
|
|
|
# Scan content until the right end tag is found. Returns an array with the
|
|
# following members:
|
|
# [0] = Content before end tag
|
|
# [1] = End tag (complete tag)
|
|
# [2] = Content after end tag
|
|
#
|
|
# Everything using this function uses the "standalone tags" logic.
|
|
#
|
|
# Parameters:
|
|
# $1: Where to store the array
|
|
# $2: Content
|
|
# $3: Name of end tag
|
|
# $4: If -z, do standalone tag processing before finishing
|
|
mustache-find-end-tag() {
|
|
local CONTENT SCANNED
|
|
|
|
# Find open tags
|
|
SCANNED=""
|
|
mustache-split CONTENT "$2" '{{' '}}'
|
|
|
|
while [[ "${#CONTENT[@]}" -gt 1 ]]; do
|
|
mustache-trim-whitespace TAG "${CONTENT[1]}"
|
|
|
|
# Restore CONTENT[1] before we start using it
|
|
CONTENT[1]='{{'"${CONTENT[1]}"'}}'
|
|
|
|
case $TAG in
|
|
'#'* | '^'*)
|
|
# Start another block
|
|
SCANNED="${SCANNED}${CONTENT[0]}${CONTENT[1]}"
|
|
mustache-trim-whitespace TAG "${TAG:1}"
|
|
mustache-find-end-tag CONTENT "${CONTENT[2]}" "$TAG" "loop"
|
|
SCANNED="${SCANNED}${CONTENT[0]}${CONTENT[1]}"
|
|
CONTENT=${CONTENT[2]}
|
|
;;
|
|
|
|
'/'*)
|
|
# End a block - could be ours
|
|
mustache-trim-whitespace TAG "${TAG:1}"
|
|
SCANNED="$SCANNED${CONTENT[0]}"
|
|
|
|
if [[ "$TAG" == "$3" ]]; then
|
|
# Found our end tag
|
|
if [[ -z "$4" ]] && mustache-is-standalone STANDALONE_BYTES "$SCANNED" "${CONTENT[2]}" true; then
|
|
# This is also a standalone tag - clean up whitespace
|
|
STANDALONE_BYTES=( $STANDALONE_BYTES )
|
|
SCANNED="${SCANNED:0:${STANDALONE_BYTES[0]}}"
|
|
CONTENT[2]="${CONTENT[2]:${STANDALONE_BYTES[1]}}"
|
|
fi
|
|
|
|
local "$1" && mustache-indirect-array "$1" "$SCANNED" "${CONTENT[1]}" "${CONTENT[2]}"
|
|
return 0
|
|
fi
|
|
|
|
SCANNED="$SCANNED${CONTENT[1]}"
|
|
CONTENT=${CONTENT[2]}
|
|
;;
|
|
|
|
*)
|
|
# Ignore all other tags
|
|
SCANNED="${SCANNED}${CONTENT[0]}${CONTENT[1]}"
|
|
CONTENT=${CONTENT[2]}
|
|
;;
|
|
esac
|
|
|
|
mustache-split CONTENT "$CONTENT" '{{' '}}'
|
|
done
|
|
|
|
# Did not find our closing tag
|
|
local "$1" && mustache-indirect-array "$1" "${SCANNED}" "" ""
|
|
}
|
|
|
|
|
|
# Find the first index of a substring
|
|
#
|
|
# Parameters:
|
|
# $1: Destination variable
|
|
# $2: Haystack
|
|
# $3: Needle
|
|
mustache-find-string() {
|
|
local POS STRING
|
|
|
|
STRING=${2%%$3*}
|
|
[[ "$STRING" == "$2" ]] && POS=-1 || POS=${#STRING}
|
|
local "$1" && mustache-indirect "$1" $POS
|
|
}
|
|
|
|
|
|
# Return a dotted name based on current context and target name
|
|
#
|
|
# Parameters:
|
|
# $1: Target variable to store results
|
|
# $2: Context name
|
|
# $3: Desired variable name
|
|
mustache-full-tag-name() {
|
|
if [[ -z "$2" ]]; then
|
|
local "$1" && mustache-indirect "$1" "$3"
|
|
else
|
|
local "$1" && mustache-indirect "$1" "${2}.${3}"
|
|
fi
|
|
}
|
|
|
|
|
|
# Return the content to parse. Can be a list of partials for files or
|
|
# the content from stdin.
|
|
#
|
|
# Parameters:
|
|
# $1: Variable name to assign this content back as
|
|
# $2-*: File names (optional)
|
|
mustache-get-content() {
|
|
local CONTENT FILENAME TARGET
|
|
|
|
TARGET=$1
|
|
shift
|
|
if [[ "${#@}" -gt 0 ]]; then
|
|
CONTENT=""
|
|
|
|
for FILENAME in ${1+"$@"}; do
|
|
# This is so relative paths work from inside template files
|
|
CONTENT="$CONTENT"'{{>'"$FILENAME"'}}'
|
|
done
|
|
else
|
|
mustache-load-file CONTENT /dev/stdin
|
|
fi
|
|
|
|
local "$TARGET" && mustache-indirect "$TARGET" "$CONTENT"
|
|
}
|
|
|
|
|
|
# Indent a string, placing the indent at the beginning of every
|
|
# line that has any content.
|
|
#
|
|
# Parameters:
|
|
# $1: Name of destination variable to get an array of lines
|
|
# $2: The indent string
|
|
# $3: The string to reindent
|
|
mustache-indent-lines() {
|
|
local CONTENT FRAGMENT POS_N POS_R RESULT TRIMMED
|
|
|
|
RESULT=""
|
|
CONTENT="${3:0: -1}" # Remove newline and dot from workaround - in mustache-partial
|
|
|
|
if [ -z "$2" ]; then
|
|
local "$1" && mustache-indirect "$1" "$CONTENT"
|
|
return 0
|
|
fi
|
|
|
|
mustache-find-string POS_N "$CONTENT" $'\n'
|
|
mustache-find-string POS_R "$CONTENT" $'\r'
|
|
|
|
while [[ "$POS_N" -gt -1 ]] || [[ "$POS_R" -gt -1 ]]; do
|
|
if [[ "$POS_N" -gt -1 ]]; then
|
|
FRAGMENT="${CONTENT:0:$POS_N + 1}"
|
|
CONTENT=${CONTENT:$POS_N + 1}
|
|
else
|
|
FRAGMENT="${CONTENT:0:$POS_R + 1}"
|
|
CONTENT=${CONTENT:$POS_R + 1}
|
|
fi
|
|
|
|
mustache-trim-chars TRIMMED "$FRAGMENT" false true " " "$'\t'" "$'\n'" "$'\r'"
|
|
|
|
if [ ! -z "$TRIMMED" ]; then
|
|
FRAGMENT="$2$FRAGMENT"
|
|
fi
|
|
|
|
RESULT="$RESULT$FRAGMENT"
|
|
mustache-find-string POS_N "$CONTENT" $'\n'
|
|
mustache-find-string POS_R "$CONTENT" $'\r'
|
|
done
|
|
|
|
mustache-trim-chars TRIMMED "$CONTENT" false true " " "$'\t'"
|
|
|
|
if [ ! -z "$TRIMMED" ]; then
|
|
CONTENT="$2$CONTENT"
|
|
fi
|
|
|
|
RESULT="$RESULT$CONTENT"
|
|
|
|
local "$1" && mustache-indirect "$1" "$RESULT"
|
|
}
|
|
|
|
|
|
# Send a variable up to caller of a function
|
|
#
|
|
# Parameters:
|
|
# $1: Variable name
|
|
# $2: Value
|
|
mustache-indirect() {
|
|
unset -v "$1"
|
|
printf -v "$1" '%s' "$2"
|
|
}
|
|
|
|
|
|
# Send an array up to caller of a function
|
|
#
|
|
# Parameters:
|
|
# $1: Variable name
|
|
# $2-*: Array elements
|
|
mustache-indirect-array() {
|
|
unset -v "$1"
|
|
eval $1=\(\"\${@:2}\"\)
|
|
}
|
|
|
|
|
|
# Determine if a given environment variable exists and if it is an array.
|
|
#
|
|
# Parameters:
|
|
# $1: Name of environment variable
|
|
#
|
|
# Return code:
|
|
# 0 if the name is not empty, 1 otherwise
|
|
mustache-is-array() {
|
|
local MUSTACHE_TEST
|
|
|
|
MUSTACHE_TEST=$(declare -p "$1" 2>/dev/null) || return 1
|
|
[[ "${MUSTACHE_TEST:0:10}" == "declare -a" ]] && return 0
|
|
[[ "${MUSTACHE_TEST:0:10}" == "declare -A" ]] && return 0
|
|
|
|
return 1
|
|
}
|
|
|
|
|
|
# Return 0 if the passed name is a function.
|
|
#
|
|
# Parameters:
|
|
# $1: Name to check if it's a function
|
|
#
|
|
# Return code:
|
|
# 0 if the name is a function, 1 otherwise
|
|
mustache-is-function() {
|
|
local FUNCTIONS NAME
|
|
|
|
FUNCTIONS=$(declare -F)
|
|
FUNCTIONS=( ${FUNCTIONS//declare -f /} )
|
|
|
|
for NAME in ${FUNCTIONS[@]}; do
|
|
if [[ "$NAME" == "$1" ]]; then
|
|
return 0
|
|
fi
|
|
done
|
|
|
|
return 1
|
|
}
|
|
|
|
|
|
# Determine if the tag is a standalone tag based on whitespace before and
|
|
# after the tag.
|
|
#
|
|
# Passes back a string containing two numbers in the format "BEFORE AFTER"
|
|
# like "27 10". It indicates the number of bytes remaining in the "before"
|
|
# string (27) and the number of bytes to trim in the "after" string (10).
|
|
# Useful for string manipulation:
|
|
#
|
|
# mustache-is-standalone RESULT "$before" "$after" false || return 0
|
|
# RESULT_ARRAY=( $RESULT )
|
|
# echo "${before:0:${RESULT_ARRAY[0]}}...${after:${RESULT_ARRAY[1]}}"
|
|
#
|
|
# Parameters:
|
|
# $1: Variable to pass data back
|
|
# $2: Content before the tag
|
|
# $3: Content after the tag
|
|
# $4: true/false: is this the beginning of the content?
|
|
mustache-is-standalone() {
|
|
local AFTER_TRIMMED BEFORE_TRIMMED CHAR
|
|
|
|
mustache-trim-chars BEFORE_TRIMMED "$2" false true " " "$'\t'"
|
|
mustache-trim-chars AFTER_TRIMMED "$3" true false " " "$'\t'"
|
|
CHAR=${BEFORE_TRIMMED: -1}
|
|
|
|
if [[ "$CHAR" != $'\n' ]] && [[ "$CHAR" != $'\r' ]]; then
|
|
if [[ ! -z "$CHAR" ]] || ! $4; then
|
|
return 1;
|
|
fi
|
|
fi
|
|
|
|
CHAR=${AFTER_TRIMMED:0:1}
|
|
|
|
if [[ "$CHAR" != $'\n' ]] && [[ "$CHAR" != $'\r' ]] && [[ ! -z "$CHAR" ]]; then
|
|
return 2;
|
|
fi
|
|
|
|
if [[ "$CHAR" == $'\r' ]] && [[ "${AFTER_TRIMMED:1:1}" == $'\n' ]]; then
|
|
CHAR="$CHAR"$'\n'
|
|
fi
|
|
|
|
local "$1" && mustache-indirect "$1" "$((${#BEFORE_TRIMMED})) $((${#3} + ${#CHAR} - ${#AFTER_TRIMMED}))"
|
|
}
|
|
|
|
|
|
# Read a file
|
|
#
|
|
# Parameters:
|
|
# $1: Variable name to receive the file's content
|
|
# $2: Filename to load
|
|
mustache-load-file() {
|
|
local CONTENT
|
|
|
|
# The subshell removes any trailing newlines. We forcibly add
|
|
# a dot to the content to preserve all newlines.
|
|
# TODO: remove cat and replace with read loop?
|
|
CONTENT=$(cat $2; echo '.')
|
|
CONTENT=${CONTENT:0: -1} # Remove last dot
|
|
|
|
local "$1" && mustache-indirect "$1" "$CONTENT"
|
|
}
|
|
|
|
|
|
# Process a chunk of content some number of times.
|
|
#
|
|
# Parameters:
|
|
# $1: Content to parse and reparse and reparse
|
|
# $2: Tag prefix (context name)
|
|
# $3-*: Names to insert into the parsed content
|
|
mustache-loop() {
|
|
local CONTENT CONTEXT CONTEXT_BASE IGNORE
|
|
|
|
CONTENT=$1
|
|
CONTEXT_BASE=$2
|
|
shift 2
|
|
|
|
while [[ "${#@}" -gt 0 ]]; do
|
|
mustache-full-tag-name CONTEXT "$CONTEXT_BASE" "$1"
|
|
mustache-parse IGNORE "$CONTENT" "$CONTEXT" false
|
|
shift
|
|
done
|
|
}
|
|
|
|
|
|
# Parse a block of text
|
|
#
|
|
# Parameters:
|
|
# $1: Where to store content left after parsing
|
|
# $2: Block of text to change
|
|
# $3: Current name (the variable NAME for what {{.}} means)
|
|
# $4: true when no content before this, false otherwise
|
|
mustache-parse() {
|
|
# Keep naming variables MUSTACHE_* here to not overwrite needed variables
|
|
# used in the string replacements
|
|
local MUSTACHE_BLOCK MUSTACHE_CONTENT MUSTACHE_CURRENT MUSTACHE_IS_BEGINNING MUSTACHE_TAG
|
|
|
|
MUSTACHE_CURRENT=$3
|
|
MUSTACHE_IS_BEGINNING=$4
|
|
|
|
# Find open tags
|
|
mustache-split MUSTACHE_CONTENT "$2" '{{' '}}'
|
|
|
|
while [[ "${#MUSTACHE_CONTENT[@]}" -gt 1 ]]; do
|
|
mustache-trim-whitespace MUSTACHE_TAG "${MUSTACHE_CONTENT[1]}"
|
|
|
|
case $MUSTACHE_TAG in
|
|
'#'*)
|
|
# Loop, if/then, or pass content through function
|
|
# Sets context
|
|
mustache-standalone-allowed MUSTACHE_CONTENT "${MUSTACHE_CONTENT[@]}" $MUSTACHE_IS_BEGINNING
|
|
mustache-trim-whitespace MUSTACHE_TAG "${MUSTACHE_TAG:1}"
|
|
mustache-find-end-tag MUSTACHE_BLOCK "$MUSTACHE_CONTENT" "$MUSTACHE_TAG"
|
|
mustache-full-tag-name MUSTACHE_TAG "$MUSTACHE_CURRENT" "$MUSTACHE_TAG"
|
|
|
|
if mustache-test "$MUSTACHE_TAG"; then
|
|
# Show / loop / pass through function
|
|
if mustache-is-function "$MUSTACHE_TAG"; then
|
|
# TODO: Consider piping the output to
|
|
# mustache-get-content so the lambda does not
|
|
# execute in a subshell?
|
|
MUSTACHE_CONTENT=$($MUSTACHE_TAG "${MUSTACHE_BLOCK[0]}")
|
|
mustache-parse MUSTACHE_CONTENT "$MUSTACHE_CONTENT" "$MUSTACHE_CURRENT" false
|
|
MUSTACHE_CONTENT="${MUSTACHE_BLOCK[2]}"
|
|
elif mustache-is-array "$MUSTACHE_TAG"; then
|
|
eval 'mustache-loop "${MUSTACHE_BLOCK[0]}" "$MUSTACHE_TAG" "${!'"$MUSTACHE_TAG"'[@]}"'
|
|
else
|
|
mustache-parse MUSTACHE_CONTENT "${MUSTACHE_BLOCK[0]}" "$MUSTACHE_CURRENT" false
|
|
fi
|
|
fi
|
|
|
|
MUSTACHE_CONTENT="${MUSTACHE_BLOCK[2]}"
|
|
;;
|
|
|
|
'>'*)
|
|
# Load partial - get name of file relative to cwd
|
|
mustache-partial MUSTACHE_CONTENT "${MUSTACHE_CONTENT[@]}" $MUSTACHE_IS_BEGINNING "$MUSTACHE_CURRENT"
|
|
;;
|
|
|
|
'/'*)
|
|
# Closing tag - If hit in this loop, we simply ignore
|
|
# Matching tags are found in mustache-find-end-tag
|
|
mustache-standalone-allowed MUSTACHE_CONTENT "${MUSTACHE_CONTENT[@]}" $MUSTACHE_IS_BEGINNING
|
|
;;
|
|
|
|
'^'*)
|
|
# Display section if named thing does not exist
|
|
mustache-standalone-allowed MUSTACHE_CONTENT "${MUSTACHE_CONTENT[@]}" $MUSTACHE_IS_BEGINNING
|
|
mustache-trim-whitespace MUSTACHE_TAG "${MUSTACHE_TAG:1}"
|
|
mustache-find-end-tag MUSTACHE_BLOCK "$MUSTACHE_CONTENT" "$MUSTACHE_TAG"
|
|
mustache-full-tag-name MUSTACHE_TAG "$MUSTACHE_CURRENT" "$MUSTACHE_TAG"
|
|
|
|
if ! mustache-test "$MUSTACHE_TAG"; then
|
|
mustache-parse MUSTACHE_CONTENT "${MUSTACHE_BLOCK[0]}" "$MUSTACHE_CURRENT" false "$MUSTACHE_CURRENT"
|
|
fi
|
|
|
|
MUSTACHE_CONTENT="${MUSTACHE_BLOCK[2]}"
|
|
;;
|
|
|
|
'!'*)
|
|
# Comment - ignore the tag content entirely
|
|
# Trim spaces/tabs before the comment
|
|
mustache-standalone-allowed MUSTACHE_CONTENT "${MUSTACHE_CONTENT[@]}" $MUSTACHE_IS_BEGINNING
|
|
;;
|
|
|
|
.)
|
|
# Current content (environment variable or function)
|
|
mustache-standalone-denied MUSTACHE_CONTENT "${MUSTACHE_CONTENT[@]}"
|
|
mustache-show "$MUSTACHE_CURRENT" "$MUSTACHE_CURRENT"
|
|
;;
|
|
|
|
'=')
|
|
# Change delimiters
|
|
# Any two non-whitespace sequences separated by whitespace.
|
|
# TODO
|
|
mustache-standalone-allowed MUSTACHE_CONTENT "${MUSTACHE_CONTENT[@]}" $MUSTACHE_IS_BEGINNING
|
|
;;
|
|
|
|
'{'*)
|
|
# Unescaped - split on }}} not }}
|
|
mustache-standalone-denied MUSTACHE_CONTENT "${MUSTACHE_CONTENT[@]}"
|
|
MUSTACHE_CONTENT="${MUSTACHE_TAG:1}"'}}'"$MUSTACHE_CONTENT"
|
|
mustache-split MUSTACHE_CONTENT "$MUSTACHE_CONTENT" '}}}'
|
|
mustache-trim-whitespace MUSTACHE_TAG "${MUSTACHE_CONTENT[0]}"
|
|
mustache-full-tag-name MUSTACHE_TAG "$MUSTACHE_CURRENT" "$MUSTACHE_TAG"
|
|
MUSTACHE_CONTENT=${MUSTACHE_CONTENT[1]}
|
|
|
|
# Now show the value
|
|
mustache-show "$MUSTACHE_TAG" "$MUSTACHE_CURRENT"
|
|
;;
|
|
|
|
'&'*)
|
|
# Unescaped
|
|
mustache-standalone-denied MUSTACHE_CONTENT "${MUSTACHE_CONTENT[@]}"
|
|
mustache-trim-whitespace MUSTACHE_TAG "${MUSTACHE_TAG:1}"
|
|
mustache-full-tag-name MUSTACHE_TAG "$MUSTACHE_CURRENT" "$MUSTACHE_TAG"
|
|
mustache-show "$MUSTACHE_TAG" "$MUSTACHE_CURRENT"
|
|
;;
|
|
|
|
*)
|
|
# Normal environment variable or function call
|
|
mustache-standalone-denied MUSTACHE_CONTENT "${MUSTACHE_CONTENT[@]}"
|
|
mustache-full-tag-name MUSTACHE_TAG "$MUSTACHE_CURRENT" "$MUSTACHE_TAG"
|
|
mustache-show "$MUSTACHE_TAG" "$MUSTACHE_CURRENT"
|
|
;;
|
|
esac
|
|
|
|
MUSTACHE_IS_BEGINNING=false
|
|
mustache-split MUSTACHE_CONTENT "$MUSTACHE_CONTENT" '{{' '}}'
|
|
done
|
|
|
|
echo -n "${MUSTACHE_CONTENT[0]}"
|
|
local "$1" && mustache-indirect "$1" ""
|
|
}
|
|
|
|
|
|
# Process a partial
|
|
#
|
|
# Indentation should be applied to the entire partial
|
|
#
|
|
# Prefix all variables
|
|
#
|
|
# Parameters:
|
|
# $1: Name of destination "content" variable.
|
|
# $2: Content before the tag that was not yet written
|
|
# $3: Tag content
|
|
# $4: Content after the tag
|
|
# $5: true/false: is this the beginning of the content?
|
|
# $6: Current context name
|
|
mustache-partial() {
|
|
local MUSTACHE_CONTENT MUSTACHE_FILENAME MUSTACHE_INDENT MUSTACHE_LINE MUSTACHE_PARTIAL MUSTACHE_STANDALONE
|
|
|
|
if mustache-is-standalone MUSTACHE_STANDALONE "$2" "$4" $5; then
|
|
MUSTACHE_STANDALONE=( $MUSTACHE_STANDALONE )
|
|
echo -n "${2:0:${MUSTACHE_STANDALONE[0]}}"
|
|
MUSTACHE_INDENT=${2:${MUSTACHE_STANDALONE[0]}}
|
|
MUSTACHE_CONTENT=${4:${MUSTACHE_STANDALONE[1]}}
|
|
else
|
|
MUSTACHE_INDENT=""
|
|
echo -n "$2"
|
|
MUSTACHE_CONTENT=$4
|
|
fi
|
|
|
|
mustache-trim-whitespace MUSTACHE_FILENAME "${3:1}"
|
|
|
|
# Execute in subshell to preserve current cwd and environment
|
|
(
|
|
# TODO: Remove dirname and use a function instead
|
|
cd "$(dirname "$MUSTACHE_FILENAME")"
|
|
mustache-indent-lines MUSTACHE_PARTIAL "$MUSTACHE_INDENT" "$(
|
|
mustache-load-file MUSTACHE_PARTIAL "${MUSTACHE_FILENAME##*/}"
|
|
|
|
# Fix bash handling of subshells
|
|
# The extra dot is removed in mustache-indent-lines
|
|
echo -n "${MUSTACHE_PARTIAL}."
|
|
)"
|
|
mustache-parse MUSTACHE_PARTIAL "$MUSTACHE_PARTIAL" "$6" true
|
|
echo -n "$MUSTACHE_PARTIAL"
|
|
)
|
|
|
|
local "$1" && mustache-indirect "$1" "$MUSTACHE_CONTENT"
|
|
}
|
|
|
|
|
|
# Show an environment variable or the output of a function.
|
|
#
|
|
# Limit/prefix any variables used
|
|
#
|
|
# Parameters:
|
|
# $1: Name of environment variable or function
|
|
# $2: Current context
|
|
mustache-show() {
|
|
local CONTENT MUSTACHE_NAME_PARTS
|
|
|
|
if mustache-is-function "$1"; then
|
|
CONTENT=$($1 "")
|
|
mustache-parse CONTENT "$CONTENT" "$2" false
|
|
return 0
|
|
fi
|
|
|
|
mustache-split MUSTACHE_NAME_PARTS "$1" "."
|
|
|
|
if [[ -z "${MUSTACHE_NAME_PARTS[1]}" ]]; then
|
|
echo -n "${!1}"
|
|
else
|
|
# Further subindexes are disallowed
|
|
eval 'echo -n "${'"${MUSTACHE_NAME_PARTS[0]}"'['"${MUSTACHE_NAME_PARTS[1]%%.*}"']}"'
|
|
fi
|
|
}
|
|
|
|
|
|
# Split a larger string into an array
|
|
#
|
|
# Parameters:
|
|
# $1: Destination variable
|
|
# $2: String to split
|
|
# $3: Starting delimiter
|
|
# $4: Ending delimiter (optional)
|
|
mustache-split() {
|
|
local POS RESULT
|
|
|
|
RESULT=( "$2" )
|
|
mustache-find-string POS "${RESULT[0]}" "$3"
|
|
|
|
if [[ "$POS" -ne -1 ]]; then
|
|
# The first delimiter was found
|
|
RESULT[1]=${RESULT[0]:$POS + ${#3}}
|
|
RESULT[0]=${RESULT[0]:0:$POS}
|
|
|
|
if [[ ! -z "$4" ]]; then
|
|
mustache-find-string POS "${RESULT[1]}" "$4"
|
|
|
|
if [[ "$POS" -ne -1 ]]; then
|
|
# The second delimiter was found
|
|
RESULT[2]="${RESULT[1]:$POS + ${#4}}"
|
|
RESULT[1]="${RESULT[1]:0:$POS}"
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
local "$1" && mustache-indirect-array "$1" "${RESULT[@]}"
|
|
}
|
|
|
|
|
|
# Handle the content for a standalone tag. This means removing whitespace
|
|
# (not newlines) before a tag and whitespace and a newline after a tag.
|
|
# That is, assuming, that the line is otherwise empty.
|
|
#
|
|
# Parameters:
|
|
# $1: Name of destination "content" variable.
|
|
# $2: Content before the tag that was not yet written
|
|
# $3: Tag content (not used)
|
|
# $4: Content after the tag
|
|
# $5: true/false: is this the beginning of the content?
|
|
mustache-standalone-allowed() {
|
|
local STANDALONE_BYTES
|
|
|
|
if mustache-is-standalone STANDALONE_BYTES "$2" "$4" $5; then
|
|
STANDALONE_BYTES=( $STANDALONE_BYTES )
|
|
echo -n "${2:0:${STANDALONE_BYTES[0]}}"
|
|
local "$1" && mustache-indirect "$1" "${4:${STANDALONE_BYTES[1]}}"
|
|
else
|
|
echo -n "$2"
|
|
local "$1" && mustache-indirect "$1" "$4"
|
|
fi
|
|
}
|
|
|
|
|
|
# Handle the content for a tag that is never "standalone". No adjustments
|
|
# are made for newlines and whitespace.
|
|
#
|
|
# Parameters:
|
|
# $1: Name of destination "content" variable.
|
|
# $2: Content before the tag that was not yet written
|
|
# $3: Tag content (not used)
|
|
# $4: Content after the tag
|
|
mustache-standalone-denied() {
|
|
echo -n "$2"
|
|
local "$1" && mustache-indirect "$1" "$4"
|
|
}
|
|
|
|
|
|
# Returns 0 (success) if the named thing is a function or if it is a non-empty
|
|
# environment variable.
|
|
#
|
|
# Do not use unprefixed variables here if possible as this needs to check
|
|
# if any name exists in the environment
|
|
#
|
|
# Parameters:
|
|
# $1: Name of environment variable or function
|
|
# $2: Current value (our context)
|
|
#
|
|
# Return code:
|
|
# 0 if the name is not empty, 1 otherwise
|
|
mustache-test() {
|
|
# Test for functions
|
|
mustache-is-function "$1" && return 0
|
|
|
|
if mustache-is-array "$1"; then
|
|
# Arrays must have at least 1 element
|
|
eval '[[ "${#'"$1"'}" -gt 0 ]]' && return 0
|
|
else
|
|
# Environment variables must not be empty
|
|
[[ ! -z "${!1}" ]] && return 0
|
|
fi
|
|
|
|
return 1
|
|
}
|
|
|
|
|
|
# Trim the leading whitespace only
|
|
#
|
|
# Parameters:
|
|
# $1: Name of destination variable
|
|
# $2: The string
|
|
# $3: true/false - trim front?
|
|
# $4: true/false - trim end?
|
|
# $5-*: Characters to trim
|
|
mustache-trim-chars() {
|
|
local BACK CURRENT FRONT LAST TARGET VAR
|
|
|
|
TARGET=$1
|
|
CURRENT=$2
|
|
FRONT=$3
|
|
BACK=$4
|
|
LAST=""
|
|
shift # Remove target
|
|
shift # Remove string
|
|
shift # Remove trim front flag
|
|
shift # Remove trim end flag
|
|
|
|
while [[ "$CURRENT" != "$LAST" ]]; do
|
|
LAST=$CURRENT
|
|
|
|
for VAR in "$@"; do
|
|
$FRONT && CURRENT="${CURRENT/#$VAR}"
|
|
$BACK && CURRENT="${CURRENT/%$VAR}"
|
|
done
|
|
done
|
|
|
|
local "$TARGET" && mustache-indirect "$TARGET" "$CURRENT"
|
|
}
|
|
|
|
|
|
# Trim leading and trailing whitespace from a string
|
|
#
|
|
# Parameters:
|
|
# $1: Name of variable to store trimmed string
|
|
# $2: The string
|
|
mustache-trim-whitespace() {
|
|
local RESULT
|
|
|
|
mustache-trim-chars RESULT "$2" true true "$'\r'" "$'\n'" "$'\t'" " "
|
|
local "$1" && mustache-indirect "$1" "$RESULT"
|
|
}
|
|
|
|
|
|
mustache-get-content MUSTACHE_CONTENT ${1+"$@"}
|
|
mustache-parse MUSTACHE_CONTENT "$MUSTACHE_CONTENT" "" true
|