From 8ee909f8def8181172f81941e41d8b61b83d3c3b Mon Sep 17 00:00:00 2001 From: Tyler Akins Date: Mon, 26 Jan 2015 03:31:21 +0000 Subject: [PATCH] Adding some support for standalone tags Failing test count is down about 5. Currently at 57 / 122 failures --- mo | 208 ++++++++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 164 insertions(+), 44 deletions(-) diff --git a/mo b/mo index 77f66fe..e1da1ce 100755 --- a/mo +++ b/mo @@ -39,7 +39,7 @@ mustache-loop() { # This MUST loop at least once or assignment back to ${!DEST_NAME} # will not work. while [[ ${#@} -gt 0 ]]; do - mustache-parse MODIFIED_CONTENT "$CONTENT" "$END_TAG" "$1" + mustache-parse MODIFIED_CONTENT "$CONTENT" "$END_TAG" "$1" false shift done @@ -54,27 +54,28 @@ mustache-loop() { # $2: Block of text to change # $3: Stop at this closing tag (eg "/NAME") # $4: Current value (what {{.}} will mean) +# $5: 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_CONTENT MUSTACHE_CURRENT MUSTACHE_END_TAG MUSTACHE_TAG + local MUSTACHE_CONTENT MUSTACHE_CURRENT MUSTACHE_END_TAG MUSTACHE_IS_BEGINNING MUSTACHE_TAG MUSTACHE_END_TAG="$3" MUSTACHE_CURRENT="$4" + MUSTACHE_IS_BEGINNING="$5" # Find open tags mustache-split MUSTACHE_CONTENT "$2" '{{' '}}' while [[ ${#MUSTACHE_CONTENT[@]} -gt 1 ]]; do - echo -n "${MUSTACHE_CONTENT[0]}" - mustache-trim MUSTACHE_TAG "${MUSTACHE_CONTENT[1]}" - MUSTACHE_CONTENT="${MUSTACHE_CONTENT[2]}" + mustache-trim-whitespace MUSTACHE_TAG "${MUSTACHE_CONTENT[1]}" case "$MUSTACHE_TAG" in '#'*) # Loop, if/then, or pass content through function # Sets context - mustache-trim MUSTACHE_TAG "${MUSTACHE_TAG:1}" + mustache-standalone-allowed MUSTACHE_CONTENT "${MUSTACHE_CONTENT[@]}" $MUSTACHE_IS_BEGINNING + mustache-trim-whitespace MUSTACHE_TAG "${MUSTACHE_TAG:1}" if mustache-test "$MUSTACHE_TAG"; then # Show / loop / pass through function @@ -82,37 +83,41 @@ mustache-parse() { # This is slower - need to parse twice to avoid # subshells. First, pass content to function but # the updated MUSTACHE_CONTENT is lost due to subshell. - $MUSTACHE_TAG "$(mustache-parse MUSTACHE_CONTENT "$MUSTACHE_CONTENT" "$MUSTACHE_TAG")" + $MUSTACHE_TAG "$(mustache-parse MUSTACHE_CONTENT "$MUSTACHE_CONTENT" "$MUSTACHE_TAG" "" false)" # Secondly, update MUSTACHE_CONTENT but do not output. - mustache-parse MUSTACHE_CONTENT "$MUSTACHE_CONTENT" "$MUSTACHE_TAG" > /dev/null 2>&1 + mustache-parse MUSTACHE_CONTENT "$MUSTACHE_CONTENT" "$MUSTACHE_TAG" "" false > /dev/null 2>&1 elif mustache-is-array "$MUSTACHE_TAG"; then eval 'mustache-loop MUSTACHE_CONTENT "$MUSTACHE_CONTENT" "$MUSTACHE_TAG" "${'"$MUSTACHE_TAG"'[@]}"' else - mustache-parse MUSTACHE_CONTENT "$MUSTACHE_CONTENT" "$MUSTACHE_TAG" "$(mustache-show "$MUSTACHE_TAG")" + mustache-parse MUSTACHE_CONTENT "$MUSTACHE_CONTENT" "$MUSTACHE_TAG" "$(mustache-show "$MUSTACHE_TAG")" false fi else # Do not show - mustache-parse MUSTACHE_CONTENT "$MUSTACHE_CONTENT" "$MUSTACHE_TAG" > /dev/null + mustache-parse MUSTACHE_CONTENT "$MUSTACHE_CONTENT" "$MUSTACHE_TAG" false > /dev/null fi ;; '>'*) # Load partial - get name of file relative to cwd - mustache-trim MUSTACHE_TAG "${MUSTACHE_TAG:1}" + # TODO: Should be somewhat standalone. Indentation should + # be applied to entire partial + mustache-standalone-denied MUSTACHE_CONTENT "${MUSTACHE_CONTENT[@]}" + mustache-trim-whitespace MUSTACHE_TAG "${MUSTACHE_TAG:1}" # Execute in subshell to preserve current cwd ( cd "$(dirname "$MUSTACHE_TAG")" mustache-load-file MUSTACHE_TAG "${MUSTACHE_TAG##*/}" - mustache-parse MUSTACHE_TAG "$MUSTACHE_TAG" "" "$MUSTACHE_CURRENT" + mustache-parse MUSTACHE_TAG "$MUSTACHE_TAG" "" "$MUSTACHE_CURRENT" false echo -n $MUSTACHE_TAG ) ;; '/'*) # Closing tag - If we hit MUSTACHE_END_TAG, we're done. - mustache-trim MUSTACHE_TAG "${MUSTACHE_TAG:1}" + mustache-standalone-denied MUSTACHE_CONTENT "${MUSTACHE_CONTENT[@]}" + mustache-trim-whitespace MUSTACHE_TAG "${MUSTACHE_TAG:1}" if [[ "$MUSTACHE_TAG" == "$MUSTACHE_END_TAG" ]]; then # Tag hit - done @@ -125,31 +130,43 @@ mustache-parse() { '^'*) # Display section if named thing does not exist - mustache-trim MUSTACHE_TAG "${MUSTACHE_TAG:1}" + mustache-standalone-allowed MUSTACHE_CONTENT "${MUSTACHE_CONTENT[@]}" $MUSTACHE_IS_BEGINNING + mustache-trim-whitespace MUSTACHE_TAG "${MUSTACHE_TAG:1}" if mustache-test "$MUSTACHE_TAG"; then # Do not show - mustache-parse MUSTACHE_CONTENT "$MUSTACHE_CONTENT" "$MUSTACHE_TAG" > /dev/null 2>&1 + mustache-parse MUSTACHE_CONTENT "$MUSTACHE_CONTENT" "$MUSTACHE_TAG" "" false > /dev/null 2>&1 else # Show - mustache-parse MUSTACHE_CONTENT "$MUSTACHE_CONTENT" "$MUSTACHE_TAG" + mustache-parse MUSTACHE_CONTENT "$MUSTACHE_CONTENT" "$MUSTACHE_TAG" "" false fi ;; '!'*) - # Comment - ignore the tag entirely + # 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[@]}" echo -n "$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 MUSTACHE_TAG "${MUSTACHE_CONTENT[0]}" + mustache-trim-whitespace MUSTACHE_TAG "${MUSTACHE_CONTENT[0]}" MUSTACHE_CONTENT="${MUSTACHE_CONTENT[1]}" # Now show the value @@ -158,16 +175,19 @@ mustache-parse() { '&'*) # Unescaped - mustache-trim MUSTACHE_TAG "${MUSTACHE_TAG:1}" + mustache-standalone-denied MUSTACHE_CONTENT "${MUSTACHE_CONTENT[@]}" + mustache-trim-whitespace MUSTACHE_TAG "${MUSTACHE_TAG:1}" mustache-show "$MUSTACHE_TAG" ;; *) # Normal environment variable or function call + mustache-standalone-denied MUSTACHE_CONTENT "${MUSTACHE_CONTENT[@]}" mustache-show "$MUSTACHE_TAG" ;; esac + MUSTACHE_IS_BEGINNING=false mustache-split MUSTACHE_CONTENT "$MUSTACHE_CONTENT" '{{' '}}' done @@ -176,6 +196,88 @@ mustache-parse() { } +# 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 +} + + +# 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 + + # TODO - peek ahead and see if the newline (if ! -z $CHAR) is CR + LF + # TODO - fix returned value if $CHAR is empty string + + local "$1" && mustache-indirect "$1" "$((${#BEFORE_TRIMMED})) $((${#3} + 1 - ${#AFTER_TRIMMED}))" +} + + +# 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() { + # TODO + echo -n "$2" + local "$1" && mustache-indirect "$1" "$4" +} + + # Show an environment variable or the output of a function. # # Parameters: @@ -231,32 +333,50 @@ mustache-is-array() { } +# 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() { - local CR CURRENT MODIFIED NEEDLE NL TAB SPACE VAR +mustache-trim-whitespace() { + local RESULT - CR="$'\r'" - NL="$'\n'" - TAB="$'\t'" - SPACE=" " - CURRENT="$2" - LAST="" - - while [[ "$CURRENT" != "$LAST" ]]; do - LAST="$CURRENT" - - for VAR in CR NL TAB SPACE; do - NEEDLE="${!VAR}" - CURRENT="${CURRENT/#$NEEDLE}" - CURRENT="${CURRENT/%$NEEDLE}" - done - done - - local "$1" && mustache-indirect "$1" "$CURRENT" + mustache-trim-chars RESULT "$2" true true "$'\r'" "$'\n'" "$'\t'" " " + local "$1" && mustache-indirect "$1" "$RESULT" } @@ -265,8 +385,8 @@ mustache-trim() { # Parameters: # $1: Destination variable # $2: String to split -# $3: Starting delimeter -# $4: Ending delimeter (optional) +# $3: Starting delimiter +# $4: Ending delimiter (optional) mustache-split() { local POS RESULT @@ -274,7 +394,7 @@ mustache-split() { mustache-find-string POS "${RESULT[0]}" "$3" if [[ $POS -ne -1 ]]; then - # The first delimeter was found + # The first delimiter was found RESULT[1]="${RESULT[0]:$POS + ${#3}}" RESULT[0]="${RESULT[0]:0:$POS}" @@ -282,7 +402,7 @@ mustache-split() { mustache-find-string POS "${RESULT[1]}" "$4" if [[ $POS -ne -1 ]]; then - # The second delimeter was found + # The second delimiter was found RESULT[2]="${RESULT[1]:$POS + ${#4}}" RESULT[1]="${RESULT[1]:0:$POS}" fi @@ -376,4 +496,4 @@ mustache-load-file() { MUSTACHE_FUNCTIONS=$(declare -F) MUSTACHE_FUNCTIONS=( ${MUSTACHE_FUNCTIONS//declare -f /} ) mustache-get-content MUSTACHE_CONTENT ${1+"$@"} -mustache-parse MUSTACHE_CONTENT "$MUSTACHE_CONTENT" +mustache-parse MUSTACHE_CONTENT "$MUSTACHE_CONTENT" "" "" true