Fixing indentation of multiple partials

This fixes #16.
This commit is contained in:
Tyler Akins 2017-06-20 14:19:44 -05:00
parent e6787e71d5
commit 6376a9b817
No known key found for this signature in database
GPG Key ID: 8F3B8C432F4393BD
6 changed files with 101 additions and 18 deletions

40
API.md
View File

@ -9,15 +9,17 @@ mo()
Public: Template parser function. Writes templates to stdout. Public: Template parser function. Writes templates to stdout.
* $0 - Name of the mo file, used for getting the help message. * $0 - Name of the mo file, used for getting the help message.
* --false - Treat "false" as an empty value. You may set the MO_FALSE_IS_EMPTY environment variable instead to a non-empty value to enable this behavior. * --fail-not-set - Fail upon expansion of an unset variable. Default behavior is to silently ignore and expand into empty string.
* --help - Display a help message. * --false - Treat "false" as an empty value. You may set the MO_FALSE_IS_EMPTY environment variable instead to a non-empty value to enable this behavior.
* --source=FILE - Source a file into the environment before processint template files. * --help - Display a help message.
* -- - Used to indicate the end of options. You may optionally use this when filenames may start with two hyphens. * --source=FILE - Source a file into the environment before processint template files.
* $@ - Filenames to parse. * -- - Used to indicate the end of options. You may optionally use this when filenames may start with two hyphens.
* $@ - Filenames to parse.
Mo uses the following environment variables: Mo uses the following environment variables:
* MO_FAIL_ON_UNSET - When set to a non-empty value, expansion of an unset env variable will be aborted with an error.
* MO_FALSE_IS_EMPTY - When set to a non-empty value, the string "false" will be treated as an empty value for the purposes of conditionals. * MO_FALSE_IS_EMPTY - When set to a non-empty value, the string "false" will be treated as an empty value for the purposes of conditionals.
* MO_ORIGINAL_COMMAND - Used to find the `mo` program in order to generate a help message. * MO_ORIGINAL_COMMAND - Used to find the `mo` program in order to generate a help message.
@ -30,6 +32,12 @@ files
After we encounter two hyphens together, all the rest of the arguments are files. After we encounter two hyphens together, all the rest of the arguments are files.
MO_FAIL_ON_UNSET
----------------
shellcheck disable=SC2030
MO_FALSE_IS_EMPTY MO_FALSE_IS_EMPTY
----------------- -----------------
@ -160,6 +168,8 @@ Internal: Determine if a given environment variable exists and if it is an array
* $1 - Name of environment variable * $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 Examples
var=(abc) var=(abc)
@ -178,6 +188,8 @@ Internal: Determine if the given name is a defined function.
* $1 - Function name to check * $1 - Function name to check
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 Examples
moo () { moo () {
@ -263,11 +275,13 @@ moPartial()
Internal: Process a partial. Internal: Process a partial.
Indentation should be applied to the entire partial Indentation should be applied to the entire partial.
This sends back the "is beginning" flag because the newline after a standalone partial is consumed. That newline is very important in the middle of content. We send back this flag to reset the processing loop's `moIsBeginning` variable, so the software thinks we are back at the beginning of a file and standalone processing continues to work.
Prefix all variables. Prefix all variables.
* $1 - Name of destination "content" variable. * $1 - Name of destination variable. Element [0] is the content, [1] is the true/false flag indicating if we are at the beginning of content.
* $2 - Content before the tag that was not yet written * $2 - Content before the tag that was not yet written
* $3 - Tag content * $3 - Tag content
* $4 - Content after the tag * $4 - Content after the tag
@ -344,6 +358,16 @@ Do not use variables without prefixes here if possible as this needs to check if
Returns 0 if the name is not empty, 1 otherwise. When MO_FALSE_IS_EMPTY is set, this returns 1 if the name is "false". Returns 0 if the name is not empty, 1 otherwise. When MO_FALSE_IS_EMPTY is set, this returns 1 if the name is "false".
moTestVarSet()
--------------
Internal: Determine if a variable is assigned, even if it is assigned an empty value.
* $1 - Variable name to check.
Returns true (0) if the variable is set, 1 if the variable is unset.
moTrimChars() moTrimChars()
------------- -------------

45
mo
View File

@ -464,15 +464,20 @@ moIsStandalone() {
char=$((${#beforeTrimmed} - 1)) char=$((${#beforeTrimmed} - 1))
char=${beforeTrimmed:$char} char=${beforeTrimmed:$char}
# If the content before didn't end in a newline
if [[ "$char" != $'\n' ]] && [[ "$char" != $'\r' ]]; then if [[ "$char" != $'\n' ]] && [[ "$char" != $'\r' ]]; then
# and there was content or this didn't start the file
if [[ -n "$char" ]] || ! $4; then if [[ -n "$char" ]] || ! $4; then
# then this is not a standalone tag.
return 1 return 1
fi fi
fi fi
char=${afterTrimmed:0:1} char=${afterTrimmed:0:1}
# If the content after doesn't start with a newline and it is something
if [[ "$char" != $'\n' ]] && [[ "$char" != $'\r' ]] && [[ -n "$char" ]]; then if [[ "$char" != $'\n' ]] && [[ "$char" != $'\r' ]] && [[ -n "$char" ]]; then
# then this is not a standalone tag.
return 2 return 2
fi fi
@ -518,7 +523,8 @@ moLoadFile() {
# The subshell removes any trailing newlines. We forcibly add # The subshell removes any trailing newlines. We forcibly add
# a dot to the content to preserve all newlines. # a dot to the content to preserve all newlines.
# TODO: remove cat and replace with read loop? # As a future optimization, it would be worth considering removing
# cat and replacing this with a read loop.
content=$(cat -- "$2"; echo '.') content=$(cat -- "$2"; echo '.')
len=$((${#content} - 1)) len=$((${#content} - 1))
@ -561,7 +567,7 @@ moLoop() {
moParse() { moParse() {
# Keep naming variables mo* here to not overwrite needed variables # Keep naming variables mo* here to not overwrite needed variables
# used in the string replacements # used in the string replacements
local moBlock moContent moCurrent moIsBeginning moTag local moBlock moContent moCurrent moIsBeginning moNextIsBeginning moTag
moCurrent=$2 moCurrent=$2
moIsBeginning=$3 moIsBeginning=$3
@ -571,6 +577,7 @@ moParse() {
while [[ "${#moContent[@]}" -gt 1 ]]; do while [[ "${#moContent[@]}" -gt 1 ]]; do
moTrimWhitespace moTag "${moContent[1]}" moTrimWhitespace moTag "${moContent[1]}"
moNextIsBeginning=false
case $moTag in case $moTag in
'#'*) '#'*)
@ -584,7 +591,7 @@ moParse() {
if moTest "$moTag"; then if moTest "$moTag"; then
# Show / loop / pass through function # Show / loop / pass through function
if moIsFunction "$moTag"; then if moIsFunction "$moTag"; then
#: TODO: Consider piping the output to moGetContent #: Consider piping the output to moGetContent
#: so the lambda does not execute in a subshell? #: so the lambda does not execute in a subshell?
moContent=$($moTag "${moBlock[0]}") moContent=$($moTag "${moBlock[0]}")
moParse "$moContent" "$moCurrent" false moParse "$moContent" "$moCurrent" false
@ -602,6 +609,8 @@ moParse() {
'>'*) '>'*)
# Load partial - get name of file relative to cwd # Load partial - get name of file relative to cwd
moPartial moContent "${moContent[@]}" "$moIsBeginning" "$moCurrent" moPartial moContent "${moContent[@]}" "$moIsBeginning" "$moCurrent"
moNextIsBeginning=${moContent[1]}
moContent=${moContent[0]}
;; ;;
'/'*) '/'*)
@ -639,7 +648,7 @@ moParse() {
'=') '=')
# Change delimiters # Change delimiters
# Any two non-whitespace sequences separated by whitespace. # Any two non-whitespace sequences separated by whitespace.
# TODO # This tag is ignored.
moStandaloneAllowed moContent "${moContent[@]}" "$moIsBeginning" moStandaloneAllowed moContent "${moContent[@]}" "$moIsBeginning"
;; ;;
@ -672,7 +681,7 @@ moParse() {
;; ;;
esac esac
moIsBeginning=false moIsBeginning=$moNextIsBeginning
moSplit moContent "$moContent" '{{' '}}' moSplit moContent "$moContent" '{{' '}}'
done done
@ -682,11 +691,18 @@ moParse() {
# Internal: Process a partial. # Internal: Process a partial.
# #
# Indentation should be applied to the entire partial # Indentation should be applied to the entire partial.
#
# This sends back the "is beginning" flag because the newline after a
# standalone partial is consumed. That newline is very important in the middle
# of content. We send back this flag to reset the processing loop's
# `moIsBeginning` variable, so the software thinks we are back at the
# beginning of a file and standalone processing continues to work.
# #
# Prefix all variables. # Prefix all variables.
# #
# $1 - Name of destination "content" variable. # $1 - Name of destination variable. Element [0] is the content, [1] is the
# true/false flag indicating if we are at the beginning of content.
# $2 - Content before the tag that was not yet written # $2 - Content before the tag that was not yet written
# $3 - Tag content # $3 - Tag content
# $4 - Content after the tag # $4 - Content after the tag
@ -696,24 +712,27 @@ moParse() {
# Returns nothing. # Returns nothing.
moPartial() { moPartial() {
# Namespace variables here to prevent conflicts. # Namespace variables here to prevent conflicts.
local moContent moFilename moIndent moPartial moStandalone moUnindented local moContent moFilename moIndent moIsBeginning moPartial moStandalone moUnindented
if moIsStandalone moStandalone "$2" "$4" "$5"; then if moIsStandalone moStandalone "$2" "$4" "$5"; then
moStandalone=( $moStandalone ) moStandalone=( $moStandalone )
echo -n "${2:0:${moStandalone[0]}}" echo -n "${2:0:${moStandalone[0]}}"
moIndent=${2:${moStandalone[0]}} moIndent=${2:${moStandalone[0]}}
moContent=${4:${moStandalone[1]}} moContent=${4:${moStandalone[1]}}
moIsBeginning=true
else else
moIndent="" moIndent=""
echo -n "$2" echo -n "$2"
moContent=$4 moContent=$4
moIsBeginning=$5
fi fi
moTrimWhitespace moFilename "${3:1}" moTrimWhitespace moFilename "${3:1}"
# Execute in subshell to preserve current cwd and environment # Execute in subshell to preserve current cwd and environment
( (
# TODO: Remove dirname and use a function instead # It would be nice to remove `dirname` and use a function instead,
# but that's difficult when you're only given filenames.
cd "$(dirname -- "$moFilename")" || exit 1 cd "$(dirname -- "$moFilename")" || exit 1
moUnindented="$( moUnindented="$(
moLoadFile moPartial "${moFilename##*/}" moLoadFile moPartial "${moFilename##*/}"
@ -727,7 +746,13 @@ moPartial() {
echo -n "$moPartial" echo -n "$moPartial"
) || exit 1 ) || exit 1
local "$1" && moIndirect "$1" "$moContent" # If this is a standalone tag, the trailing newline after the tag is
# removed and the contents of the partial are added, which typically
# contain a newline. We need to send a signal back to the processing
# loop that the moIsBeginning flag needs to be turned on again.
#
# [0] is the content, [1] is that flag.
local "$1" && moIndirectArray "$1" "$moContent" "$moIsBeginning"
} }

View File

View File

@ -0,0 +1,19 @@
With spacing
first line
second line
first line
second line
Without spacing
first line
second line
first line
second line
With text
first line
second line
text
first line
second line

View File

@ -0,0 +1,2 @@
first line
second line

View File

@ -0,0 +1,13 @@
With spacing
{{> indented-partials.partial}}
{{> indented-partials.partial}}
Without spacing
{{> indented-partials.partial}}
{{> indented-partials.partial}}
With text
{{> indented-partials.partial}}
text
{{> indented-partials.partial}}