From 85244ecdcb86e203b9775e69c0fc66c401afc132 Mon Sep 17 00:00:00 2001 From: Chris Fiege <chris@tinyhost.de> Date: Tue, 5 Jun 2018 19:28:47 +0200 Subject: [PATCH] Add mo to repo to make it self contained Signed-off-by: Chris Fiege <chris@tinyhost.de> --- .gitignore | 2 +- generate.sh | 2 +- mo | 726 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 728 insertions(+), 2 deletions(-) create mode 100755 mo diff --git a/.gitignore b/.gitignore index a585b7a..4f5d5e0 100644 --- a/.gitignore +++ b/.gitignore @@ -4,8 +4,8 @@ anleitung-backbone.html anleitung-business.html anleitung-starter.html -auswahl.html eintragen.html +event.html faq.html impressum.html index.html diff --git a/generate.sh b/generate.sh index f43b3c6..3f2b85a 100755 --- a/generate.sh +++ b/generate.sh @@ -7,7 +7,7 @@ cp .gitignore.template .gitignore (echo "# ignoring from *.template generated files" for file in $(ls *.template); do new=$(echo $file | sed 's/template/html/') - /home/kasalehlia/mo $file > $new + ./mo $file > $new echo "$new" done) >> .gitignore diff --git a/mo b/mo new file mode 100755 index 0000000..07da321 --- /dev/null +++ b/mo @@ -0,0 +1,726 @@ +#!/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 + # and move those whitespace bytes to the "tag" element + STANDALONE_BYTES=( $STANDALONE_BYTES ) + CONTENT[1]="${SCANNED:${STANDALONE_BYTES[0]}}${CONTENT[1]}${CONTENT[2]:0:${STANDALONE_BYTES[1]}}" + 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 + SCANNED="$SCANNED${CONTENT[0]}" + 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 "$@"; 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 LEN POS_N POS_R RESULT TRIMMED + + RESULT="" + LEN=$((${#3} - 1)) + CONTENT="${3:0:$LEN}" # 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)) + CHAR=${BEFORE_TRIMMED:$CHAR} + + 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}))" +} + + +# Join / implode an array +# +# Parameters: +# $1: Variable name to receive the joined content +# $2: Joiner +# $3-$*: Elements to join +mustache-join() { + local JOINER PART RESULT TARGET + + TARGET=$1 + JOINER=$2 + RESULT=$3 + shift 3 + + for PART in "$@"; do + RESULT="$RESULT$JOINER$PART" + done + + local "$TARGET" && mustache-indirect "$TARGET" "$RESULT" +} + +# Read a file +# +# Parameters: +# $1: Variable name to receive the file's content +# $2: Filename to load +mustache-load-file() { + local CONTENT LEN + + # 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 '.') + LEN=$((${#CONTENT} - 1)) + CONTENT=${CONTENT:0:$LEN} # 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 "$CONTENT" "$CONTEXT" false + shift + done +} + + +# Parse a block of text +# +# Parameters: +# $1: Block of text to change +# $2: Current name (the variable NAME for what {{.}} means) +# $3: 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=$2 + MUSTACHE_IS_BEGINNING=$3 + + # Find open tags + mustache-split MUSTACHE_CONTENT "$1" '{{' '}}' + + 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_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_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_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]}" +} + + +# 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" "$6" true + ) + + 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 JOINED MUSTACHE_NAME_PARTS + + if mustache-is-function "$1"; then + CONTENT=$($1 "") + mustache-parse "$CONTENT" "$2" false + return 0 + fi + + mustache-split MUSTACHE_NAME_PARTS "$1" "." + + if [[ -z "${MUSTACHE_NAME_PARTS[1]}" ]]; then + if mustache-is-array "$1"; then + eval mustache-join JOINED "," "\${$1[@]}" + echo -n "$JOINED" + else + echo -n "${!1}" + fi + 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 "$@" +mustache-parse "$MUSTACHE_CONTENT" "" true -- GitLab