mirror of
https://github.com/tests-always-included/mo.git
synced 2024-12-18 16:27:52 +00:00
Releasing 3.0.0
This commit is contained in:
parent
0617b2ed35
commit
0ce41dedb1
781
API.md
781
API.md
@ -12,132 +12,87 @@ 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.
|
||||||
* $@ - Filenames to parse.
|
* $@ - Filenames to parse.
|
||||||
|
|
||||||
Options:
|
Returns nothing.
|
||||||
|
|
||||||
--allow-function-arguments
|
|
||||||
|
|
||||||
Permit functions in templates to be called with additional arguments. This puts template data directly in to the path of an eval statement. Use with caution. Not listed in the help because it only makes sense when mo is sourced.
|
`mo::debug()`
|
||||||
|
-------------
|
||||||
|
|
||||||
-u, --fail-not-set
|
Internal: Show a debug message
|
||||||
|
|
||||||
Fail upon expansion of an unset variable. Default behavior is to silently ignore and expand into empty string.
|
* $1 - The debug message to show
|
||||||
|
|
||||||
-x, --fail-on-function
|
|
||||||
|
|
||||||
Fail when a function used by a template returns an error status code. Alternately, ou may set the MO_FAIL_ON_FUNCTION environment variable to a non-empty value to enable this behavior.
|
|
||||||
|
|
||||||
-e, --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.
|
|
||||||
|
|
||||||
-h, --help
|
|
||||||
|
|
||||||
Display a help message.
|
|
||||||
|
|
||||||
-s=FILE, --source=FILE
|
|
||||||
|
|
||||||
Source a file into the environment before processing template files. This can be used multiple times.
|
|
||||||
|
|
||||||
--
|
|
||||||
|
|
||||||
Used to indicate the end of options. You may optionally use this when filenames may start with two hyphens.
|
|
||||||
|
|
||||||
Mo uses the following environment variables:
|
|
||||||
|
|
||||||
* MO_ALLOW_FUNCTION_ARGUMENTS - When set to a non-empty value, this allows functions referenced in templates to receive additional options and arguments. This puts the content from the template directly into an eval statement. Use with extreme care.
|
|
||||||
* MO_FUNCTION_ARGS - Arguments passed to the function
|
|
||||||
* MO_FAIL_ON_FUNCTION - If a function returns a non-zero status code, abort with an error.
|
|
||||||
* 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_ORIGINAL_COMMAND - Used to find the `mo` program in order to generate a help message.
|
|
||||||
|
|
||||||
Returns nothing.
|
Returns nothing.
|
||||||
|
|
||||||
|
|
||||||
`moCallFunction()`
|
`mo::debugShowState()`
|
||||||
------------------
|
----------------------
|
||||||
|
|
||||||
Internal: Call a function.
|
Internal: Show a debug message and internal state information
|
||||||
|
|
||||||
* $1 - Variable for output
|
No arguments
|
||||||
* $2 - Function to call
|
|
||||||
* $3 - Content to pass
|
|
||||||
* $4 - Additional arguments as a single string
|
|
||||||
|
|
||||||
This can be dangerous, especially if you are using tags like {{someFunction ; rm -rf / }}
|
|
||||||
|
|
||||||
Returns nothing.
|
Returns nothing.
|
||||||
|
|
||||||
|
|
||||||
`moFindEndTag()`
|
`mo::error()`
|
||||||
----------------
|
-------------
|
||||||
|
|
||||||
Internal: Scan content until the right end tag is found. Creates an array with the following members:
|
Internal: Show an error message and exit
|
||||||
|
|
||||||
[0] = Content before end tag
|
* $1 - The error message to show
|
||||||
[1] = End tag (complete tag)
|
* $2 - Error code
|
||||||
[2] = Content after end tag
|
|
||||||
|
|
||||||
Everything using this function uses the "standalone tags" logic.
|
Returns nothing. Exits the program.
|
||||||
|
|
||||||
* $1 - Name of variable for the array
|
|
||||||
* $2 - Content
|
|
||||||
* $3 - Name of end tag
|
|
||||||
* $4 - If -z, do standalone tag processing before finishing
|
|
||||||
|
|
||||||
Returns nothing.
|
|
||||||
|
|
||||||
|
|
||||||
`moFindString()`
|
`mo::errorNear()`
|
||||||
----------------
|
|
||||||
|
|
||||||
Internal: Find the first index of a substring. If not found, sets the index to -1.
|
|
||||||
|
|
||||||
* $1 - Destination variable for the index
|
|
||||||
* $2 - Haystack
|
|
||||||
* $3 - Needle
|
|
||||||
|
|
||||||
Returns nothing.
|
|
||||||
|
|
||||||
|
|
||||||
`moFullTagName()`
|
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
Internal: Generate a dotted name based on current context and target name.
|
Internal: Show an error message with a snippet of context and exit
|
||||||
|
|
||||||
* $1 - Target variable to store results
|
* $1 - The error message to show
|
||||||
* $2 - Context name
|
* $2 - The starting point
|
||||||
* $3 - Desired variable name
|
* $3 - Error code
|
||||||
|
|
||||||
|
Returns nothing. Exits the program.
|
||||||
|
|
||||||
|
|
||||||
|
`mo::usage()`
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Internal: Displays the usage for mo. Pulls this from the file that contained the `mo` function. Can only work when the right filename comes is the one argument, and that only happens when `mo` is called with `$0` set to this file.
|
||||||
|
|
||||||
|
* $1 - Filename that has the help message
|
||||||
|
|
||||||
Returns nothing.
|
Returns nothing.
|
||||||
|
|
||||||
|
|
||||||
`moGetContent()`
|
`mo::content()`
|
||||||
|
---------------
|
||||||
|
|
||||||
|
Internal: Fetches the content to parse into MO_UNPARSED. Can be a list of partials for files or the content from stdin.
|
||||||
|
|
||||||
|
* $1 - Destination variable name
|
||||||
|
* $2-@ - File names (optional), read from stdin otherwise
|
||||||
|
|
||||||
|
Returns nothing.
|
||||||
|
|
||||||
|
|
||||||
|
`mo::contentFile()`
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
Internal: Read a file into MO_UNPARSED.
|
||||||
|
|
||||||
|
* $1 - Destination variable name.
|
||||||
|
* $2 - Filename to load - if empty, defaults to /dev/stdin
|
||||||
|
|
||||||
|
Returns nothing.
|
||||||
|
|
||||||
|
|
||||||
|
`mo::indirect()`
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
Internal: Fetches the content to parse into a variable. Can be a list of partials for files or the content from stdin.
|
|
||||||
|
|
||||||
* $1 - Variable name to assign this content back as
|
|
||||||
* $2-@ - File names (optional)
|
|
||||||
|
|
||||||
Returns nothing.
|
|
||||||
|
|
||||||
|
|
||||||
`moIndentLines()`
|
|
||||||
-----------------
|
|
||||||
|
|
||||||
Internal: Indent a string, placing the indent at the beginning of every line that has any content.
|
|
||||||
|
|
||||||
* $1 - Name of destination variable to get an array of lines
|
|
||||||
* $2 - The indent string
|
|
||||||
* $3 - The string to reindent
|
|
||||||
|
|
||||||
Returns nothing.
|
|
||||||
|
|
||||||
|
|
||||||
`moIndirect()`
|
|
||||||
--------------
|
|
||||||
|
|
||||||
Internal: Send a variable up to the parent of the caller of this function.
|
Internal: Send a variable up to the parent of the caller of this function.
|
||||||
|
|
||||||
* $1 - Variable name
|
* $1 - Variable name
|
||||||
@ -146,7 +101,7 @@ Internal: Send a variable up to the parent of the caller of this function.
|
|||||||
Examples
|
Examples
|
||||||
|
|
||||||
callFunc () {
|
callFunc () {
|
||||||
local "$1" && moIndirect "$1" "the value"
|
local "$1" && mo::indirect "$1" "the value"
|
||||||
}
|
}
|
||||||
callFunc dest
|
callFunc dest
|
||||||
echo "$dest" # writes "the value"
|
echo "$dest" # writes "the value"
|
||||||
@ -154,8 +109,8 @@ Examples
|
|||||||
Returns nothing.
|
Returns nothing.
|
||||||
|
|
||||||
|
|
||||||
`moIndirectArray()`
|
`mo::indirectArray()`
|
||||||
-------------------
|
---------------------
|
||||||
|
|
||||||
Internal: Send an array as a variable up to caller of a function
|
Internal: Send an array as a variable up to caller of a function
|
||||||
|
|
||||||
@ -166,7 +121,7 @@ Examples
|
|||||||
|
|
||||||
callFunc () {
|
callFunc () {
|
||||||
local myArray=(one two three)
|
local myArray=(one two three)
|
||||||
local "$1" && moIndirectArray "$1" "${myArray[@]}"
|
local "$1" && mo::indirectArray "$1" "${myArray[@]}"
|
||||||
}
|
}
|
||||||
callFunc dest
|
callFunc dest
|
||||||
echo "${dest[@]}" # writes "one two three"
|
echo "${dest[@]}" # writes "one two three"
|
||||||
@ -174,9 +129,156 @@ Examples
|
|||||||
Returns nothing.
|
Returns nothing.
|
||||||
|
|
||||||
|
|
||||||
`moIsArray()`
|
`mo::trimUnparsed()`
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
Internal: Trim leading characters from MO_UNPARSED
|
||||||
|
|
||||||
|
Returns nothing.
|
||||||
|
|
||||||
|
|
||||||
|
`mo::chomp()`
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
|
Internal: Remove whitespace and content after whitespace
|
||||||
|
|
||||||
|
* $1 - Name of the destination variable
|
||||||
|
* $2 - The string to chomp
|
||||||
|
|
||||||
|
Returns nothing.
|
||||||
|
|
||||||
|
|
||||||
|
`mo::parse()`
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Public: Parses text, interpolates mustache tags. Utilizes the current value of MO_OPEN_DELIMITER, MO_CLOSE_DELIMITER, and MO_STANDALONE_CONTENT. Those three variables shouldn't be changed by user-defined functions.
|
||||||
|
|
||||||
|
* $1 - Destination variable name - where to store the finished content
|
||||||
|
* $2 - Content to parse
|
||||||
|
* $3 - Preserve standalone status/content - truthy if not empty. When set to a value, that becomes the standalone content value
|
||||||
|
|
||||||
|
Returns nothing.
|
||||||
|
|
||||||
|
|
||||||
|
`mo::parseInternal()`
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
Internal: Parse MO_UNPARSED, writing content to MO_PARSED. Interpolates mustache tags.
|
||||||
|
|
||||||
|
No arguments
|
||||||
|
|
||||||
|
Returns nothing.
|
||||||
|
|
||||||
|
|
||||||
|
`mo::parseBlock()`
|
||||||
|
------------------
|
||||||
|
|
||||||
|
Internal: Handle parsing a block
|
||||||
|
|
||||||
|
* $1 - Invert condition ("true" or "false")
|
||||||
|
|
||||||
|
Returns nothing
|
||||||
|
|
||||||
|
|
||||||
|
`mo::parseBlockFunction()`
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
Internal: Handle parsing a block whose first argument is a function
|
||||||
|
|
||||||
|
* $1 - Invert condition ("true" or "false")
|
||||||
|
* $2-@ - The parsed tokens from inside the block tags
|
||||||
|
|
||||||
|
Returns nothing
|
||||||
|
|
||||||
|
|
||||||
|
`mo::parseBlockArray()`
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
Internal: Handle parsing a block whose first argument is an array
|
||||||
|
|
||||||
|
* $1 - Invert condition ("true" or "false")
|
||||||
|
* $2-@ - The parsed tokens from inside the block tags
|
||||||
|
|
||||||
|
Returns nothing
|
||||||
|
|
||||||
|
|
||||||
|
`mo::parseBlockValue()`
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
Internal: Handle parsing a block whose first argument is a value
|
||||||
|
|
||||||
|
* $1 - Invert condition ("true" or "false")
|
||||||
|
* $2-@ - The parsed tokens from inside the block tags
|
||||||
|
|
||||||
|
Returns nothing
|
||||||
|
|
||||||
|
|
||||||
|
`mo::parsePartial()`
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
Internal: Handle parsing a partial
|
||||||
|
|
||||||
|
No arguments.
|
||||||
|
|
||||||
|
Indentation will be applied to the entire partial's contents before parsing. This indentation is based on the whitespace that ends the previously parsed content.
|
||||||
|
|
||||||
|
Returns nothing
|
||||||
|
|
||||||
|
|
||||||
|
`mo::parseComment()`
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
Internal: Handle parsing a comment
|
||||||
|
|
||||||
|
No arguments.
|
||||||
|
|
||||||
|
Returns nothing
|
||||||
|
|
||||||
|
|
||||||
|
`mo::parseDelimiter()`
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
Internal: Handle parsing the change of delimiters
|
||||||
|
|
||||||
|
No arguments.
|
||||||
|
|
||||||
|
Returns nothing
|
||||||
|
|
||||||
|
|
||||||
|
`mo::parseValue()`
|
||||||
|
------------------
|
||||||
|
|
||||||
|
Internal: Handle parsing value or function call
|
||||||
|
|
||||||
|
No arguments.
|
||||||
|
|
||||||
|
Returns nothing
|
||||||
|
|
||||||
|
|
||||||
|
`mo::isFunction()`
|
||||||
|
------------------
|
||||||
|
|
||||||
|
Internal: Determine if the given name is a defined function.
|
||||||
|
|
||||||
|
* $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
|
||||||
|
|
||||||
|
moo () {
|
||||||
|
echo "This is a function"
|
||||||
|
}
|
||||||
|
if mo::isFunction moo; then
|
||||||
|
echo "moo is a defined function"
|
||||||
|
fi
|
||||||
|
|
||||||
|
Returns 0 if the name is a function, 1 otherwise.
|
||||||
|
|
||||||
|
|
||||||
|
`mo::isArray()`
|
||||||
|
---------------
|
||||||
|
|
||||||
Internal: Determine if a given environment variable exists and if it is an array.
|
Internal: Determine if a given environment variable exists and if it is an array.
|
||||||
|
|
||||||
* $1 - Name of environment variable
|
* $1 - Name of environment variable
|
||||||
@ -194,231 +296,312 @@ Examples
|
|||||||
Returns 0 if the name is not empty, 1 otherwise.
|
Returns 0 if the name is not empty, 1 otherwise.
|
||||||
|
|
||||||
|
|
||||||
`moIsFunction()`
|
`mo::isArrayIndexValid()`
|
||||||
----------------
|
-------------------------
|
||||||
|
|
||||||
Internal: Determine if the given name is a defined function.
|
Internal: Determine if an array index exists.
|
||||||
|
|
||||||
* $1 - Function name to check
|
* $1 - Variable name to check
|
||||||
|
* $2 - The index 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.
|
Has to check if the variable is an array and if the index is valid for that type of array.
|
||||||
|
|
||||||
Examples
|
Returns true (0) if everything was ok, 1 if there's any condition that fails.
|
||||||
|
|
||||||
moo () {
|
|
||||||
echo "This is a function"
|
|
||||||
}
|
|
||||||
if moIsFunction moo; then
|
|
||||||
echo "moo is a defined function"
|
|
||||||
fi
|
|
||||||
|
|
||||||
Returns 0 if the name is a function, 1 otherwise.
|
`mo::isVarSet()`
|
||||||
|
|
||||||
|
|
||||||
`moIsStandalone()`
|
|
||||||
------------------
|
|
||||||
|
|
||||||
Internal: 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:
|
|
||||||
|
|
||||||
* $1 - Variable to set for passing data back
|
|
||||||
* $2 - Content before the tag
|
|
||||||
* $3 - Content after the tag
|
|
||||||
* $4 - true/false: is this the beginning of the content?
|
|
||||||
|
|
||||||
Examples
|
|
||||||
|
|
||||||
moIsStandalone RESULT "$before" "$after" false || return 0
|
|
||||||
RESULT_ARRAY=( $RESULT )
|
|
||||||
echo "${before:0:${RESULT_ARRAY[0]}}...${after:${RESULT_ARRAY[1]}}"
|
|
||||||
|
|
||||||
Returns nothing.
|
|
||||||
|
|
||||||
|
|
||||||
`moJoin()`
|
|
||||||
----------
|
|
||||||
|
|
||||||
Internal: Join / implode an array
|
|
||||||
|
|
||||||
* $1 - Variable name to receive the joined content
|
|
||||||
* $2 - Joiner
|
|
||||||
* $3-$* - Elements to join
|
|
||||||
|
|
||||||
Returns nothing.
|
|
||||||
|
|
||||||
|
|
||||||
`moLoadFile()`
|
|
||||||
--------------
|
|
||||||
|
|
||||||
Internal: Read a file into a variable.
|
|
||||||
|
|
||||||
* $1 - Variable name to receive the file's content
|
|
||||||
* $2 - Filename to load - if empty, defaults to /dev/stdin
|
|
||||||
|
|
||||||
Returns nothing.
|
|
||||||
|
|
||||||
|
|
||||||
`moLoop()`
|
|
||||||
----------
|
|
||||||
|
|
||||||
Internal: Process a chunk of content some number of times. Writes output to stdout.
|
|
||||||
|
|
||||||
* $1 - Content to parse repeatedly
|
|
||||||
* $2 - Tag prefix (context name)
|
|
||||||
* $3-@ - Names to insert into the parsed content
|
|
||||||
|
|
||||||
Returns nothing.
|
|
||||||
|
|
||||||
|
|
||||||
`moParse()`
|
|
||||||
-----------
|
|
||||||
|
|
||||||
Internal: Parse a block of text, writing the result to stdout.
|
|
||||||
|
|
||||||
* $1 - Block of text to change
|
|
||||||
* $2 - Current name (the variable NAME for what {{.}} means)
|
|
||||||
* $3 - true when no content before this, false otherwise
|
|
||||||
|
|
||||||
Returns nothing.
|
|
||||||
|
|
||||||
|
|
||||||
`moArgs`
|
|
||||||
--------
|
|
||||||
|
|
||||||
Split arguments from the tag name. Arguments are passed to functions.
|
|
||||||
|
|
||||||
|
|
||||||
`moPartial()`
|
|
||||||
-------------
|
|
||||||
|
|
||||||
Internal: Process a 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.
|
|
||||||
|
|
||||||
* $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
|
|
||||||
* $3 - Tag content
|
|
||||||
* $4 - Content after the tag
|
|
||||||
* $5 - true/false: is this the beginning of the content?
|
|
||||||
* $6 - Current context name
|
|
||||||
|
|
||||||
Returns nothing.
|
|
||||||
|
|
||||||
|
|
||||||
`moShow()`
|
|
||||||
----------
|
|
||||||
|
|
||||||
Internal: Show an environment variable or the output of a function to stdout.
|
|
||||||
|
|
||||||
Limit/prefix any variables used.
|
|
||||||
|
|
||||||
* $1 - Name of environment variable or function
|
|
||||||
* $2 - Current context
|
|
||||||
* $3 - Arguments string if $1 is a function
|
|
||||||
|
|
||||||
Returns nothing.
|
|
||||||
|
|
||||||
|
|
||||||
`moSplit()`
|
|
||||||
-----------
|
|
||||||
|
|
||||||
Internal: Split a larger string into an array.
|
|
||||||
|
|
||||||
* $1 - Destination variable
|
|
||||||
* $2 - String to split
|
|
||||||
* $3 - Starting delimiter
|
|
||||||
* $4 - Ending delimiter (optional)
|
|
||||||
|
|
||||||
Returns nothing.
|
|
||||||
|
|
||||||
|
|
||||||
`moStandaloneAllowed()`
|
|
||||||
-----------------------
|
|
||||||
|
|
||||||
Internal: 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.
|
|
||||||
|
|
||||||
* $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?
|
|
||||||
|
|
||||||
Returns nothing.
|
|
||||||
|
|
||||||
|
|
||||||
`moStandaloneDenied()`
|
|
||||||
----------------------
|
|
||||||
|
|
||||||
Internal: Handle the content for a tag that is never "standalone". No adjustments are made for newlines and whitespace.
|
|
||||||
|
|
||||||
* $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
|
|
||||||
|
|
||||||
Returns nothing.
|
|
||||||
|
|
||||||
|
|
||||||
`moTest()`
|
|
||||||
----------
|
|
||||||
|
|
||||||
Internal: Determines if the named thing is a function or if it is a non-empty environment variable. When MO_FALSE_IS_EMPTY is set to a non-empty value, then "false" is also treated is an empty value.
|
|
||||||
|
|
||||||
Do not use variables without prefixes here if possible as this needs to check if any name exists in the environment
|
|
||||||
|
|
||||||
* $1 - Name of environment variable or function
|
|
||||||
* $2 - Current value (our context)
|
|
||||||
* MO_FALSE_IS_EMPTY - When set to a non-empty value, this will say the string value "false" is empty.
|
|
||||||
|
|
||||||
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.
|
Internal: Determine if a variable is assigned, even if it is assigned an empty value.
|
||||||
|
|
||||||
* $1 - Variable name to check.
|
* $1 - Variable name to check.
|
||||||
|
|
||||||
|
Can not use logic like this in case invalid variable names are passed. [[ "${!1-a}" == "${!1-b}" ]]
|
||||||
|
|
||||||
Returns true (0) if the variable is set, 1 if the variable is unset.
|
Returns true (0) if the variable is set, 1 if the variable is unset.
|
||||||
|
|
||||||
|
|
||||||
`moTrimChars()`
|
`mo::isTruthy()`
|
||||||
---------------
|
----------------
|
||||||
|
|
||||||
Internal: Trim the leading whitespace only.
|
Internal: Determine if a value is considered truthy.
|
||||||
|
|
||||||
* $1 - Name of destination variable
|
* $1 - The value to test
|
||||||
* $2 - The string
|
* $2 - Invert the value, either "true" or "false"
|
||||||
* $3 - true/false - trim front?
|
|
||||||
* $4 - true/false - trim end?
|
Returns true (0) if truthy, 1 otherwise.
|
||||||
* $5-@ - Characters to trim
|
|
||||||
|
|
||||||
|
`mo::evaluate()`
|
||||||
|
----------------
|
||||||
|
|
||||||
|
Internal: Convert token list to values
|
||||||
|
|
||||||
|
* $1 - Destination variable name
|
||||||
|
* $2-@ - Tokens to convert
|
||||||
|
|
||||||
|
Sample call:
|
||||||
|
|
||||||
|
mo::evaluate dest NAME username VALUE abc123 PAREN 2
|
||||||
|
|
||||||
Returns nothing.
|
Returns nothing.
|
||||||
|
|
||||||
|
|
||||||
`moTrimWhitespace()`
|
`mo::evaluateListOfSingles()`
|
||||||
--------------------
|
-----------------------------
|
||||||
|
|
||||||
Internal: Trim leading and trailing whitespace from a string.
|
Internal: Convert an argument list to individual values.
|
||||||
|
|
||||||
* $1 - Name of variable to store trimmed string
|
* $1 - Destination variable name
|
||||||
* $2 - The string
|
* $2-@ - A list of argument types and argument name/value.
|
||||||
|
|
||||||
|
This assumes each value is separate from the rest. In contrast, mo::evaluate will pass all arguments to a function if the first value is a function.
|
||||||
|
|
||||||
|
Sample call:
|
||||||
|
|
||||||
|
mo::evaluateListOfSingles dest NAME username VALUE abc123
|
||||||
|
|
||||||
Returns nothing.
|
Returns nothing.
|
||||||
|
|
||||||
|
|
||||||
`moUsage()`
|
`mo::evaluateSingle()`
|
||||||
-----------
|
----------------------
|
||||||
|
|
||||||
Internal: Displays the usage for mo. Pulls this from the file that contained the `mo` function. Can only work when the right filename comes is the one argument, and that only happens when `mo` is called with `$0` set to this file.
|
Internal: Evaluate a single argument
|
||||||
|
|
||||||
* $1 - Filename that has the help message
|
* $1 - Name of variable for result
|
||||||
|
* $2 - Type of argument, either NAME or VALUE
|
||||||
|
* $3 - Argument
|
||||||
|
|
||||||
|
Returns nothing
|
||||||
|
|
||||||
|
|
||||||
|
`mo::evaluateKey()`
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
Internal: Return the value for @key based on current's name
|
||||||
|
|
||||||
|
* $1 - Name of variable for result
|
||||||
|
|
||||||
|
Returns nothing
|
||||||
|
|
||||||
|
|
||||||
|
`mo::evaluateVariable()`
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
Internal: Handle a variable name
|
||||||
|
|
||||||
|
* $1 - Destination variable name
|
||||||
|
* $2 - Variable name
|
||||||
|
|
||||||
|
Returns nothing.
|
||||||
|
|
||||||
|
|
||||||
|
`mo::findVariableName()`
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
Internal: Find the name of a variable to use
|
||||||
|
|
||||||
|
* $1 - Destination variable name, receives an array
|
||||||
|
* $2 - Variable name from the template
|
||||||
|
|
||||||
|
The array contains the following values
|
||||||
|
* [0] - Variable name
|
||||||
|
* [1] - Array index, or empty string
|
||||||
|
|
||||||
|
Example variables a="a"
|
||||||
|
b="b"
|
||||||
|
c=("c.0" "c.1")
|
||||||
|
d=([b]="d.b" [d]="d.d")
|
||||||
|
|
||||||
|
Given these inputs (function input, current value), produce these outputs a c => a
|
||||||
|
a c.0 => a
|
||||||
|
b d => d.b
|
||||||
|
b d.d => d.b
|
||||||
|
a d => d.a
|
||||||
|
a d.d => d.a
|
||||||
|
c.0 d => c.0
|
||||||
|
d.b d => d.b
|
||||||
|
'' c => c
|
||||||
|
'' c.0 => c.0
|
||||||
|
Returns nothing.
|
||||||
|
|
||||||
|
|
||||||
|
`mo::join()`
|
||||||
|
------------
|
||||||
|
|
||||||
|
Internal: Join / implode an array
|
||||||
|
|
||||||
|
* $1 - Variable name to receive the joined content
|
||||||
|
* $2 - Joiner
|
||||||
|
* $3-@ - Elements to join
|
||||||
|
|
||||||
|
Returns nothing.
|
||||||
|
|
||||||
|
|
||||||
|
`mo::evaluateFunction()`
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
Internal: Call a function.
|
||||||
|
|
||||||
|
* $1 - Variable for output
|
||||||
|
* $2 - Content to pass
|
||||||
|
* $3 - Function to call
|
||||||
|
* $4-@ - Additional arguments as list of type, value/name
|
||||||
|
|
||||||
|
Returns nothing.
|
||||||
|
|
||||||
|
|
||||||
|
`mo::standaloneCheck()`
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
Internal: Check if a tag appears to have only whitespace before it and after it on a line. There must be a new line before and there must be a newline after or the end of a string
|
||||||
|
|
||||||
|
No arguments.
|
||||||
|
|
||||||
|
Returns 0 if this is a standalone tag, 1 otherwise.
|
||||||
|
|
||||||
|
|
||||||
|
`mo::standaloneProcess()`
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
Internal: Process content before and after a tag. Remove prior whitespace up to the previous newline. Remove following whitespace up to and including the next newline.
|
||||||
|
|
||||||
|
No arguments.
|
||||||
|
|
||||||
|
Returns nothing.
|
||||||
|
|
||||||
|
|
||||||
|
`mo::indentLines()`
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
Internal: Apply indentation before any line that has content in MO_UNPARSED.
|
||||||
|
|
||||||
|
* $1 - Destination variable name.
|
||||||
|
* $2 - The indentation string.
|
||||||
|
* $3 - The content that needs the indentation string prepended on each line.
|
||||||
|
|
||||||
|
Returns nothing.
|
||||||
|
|
||||||
|
|
||||||
|
`mo::escape()`
|
||||||
|
--------------
|
||||||
|
|
||||||
|
Internal: Escape a value
|
||||||
|
|
||||||
|
* $1 - Destination variable name
|
||||||
|
* $2 - Value to escape
|
||||||
|
|
||||||
|
Returns nothing
|
||||||
|
|
||||||
|
|
||||||
|
`mo::getContentUntilClose()`
|
||||||
|
----------------------------
|
||||||
|
|
||||||
|
Internal: Get the content up to the end of the block by minimally parsing and balancing blocks. Returns the content before the end tag to the caller and removes the content + the end tag from MO_UNPARSED. This can change the delimiters, adjusting MO_OPEN_DELIMITER and MO_CLOSE_DELIMITER.
|
||||||
|
|
||||||
|
* $1 - Destination variable name
|
||||||
|
* $2 - Token string to match for a closing tag
|
||||||
|
|
||||||
|
Returns nothing.
|
||||||
|
|
||||||
|
|
||||||
|
`mo::tokensToString()`
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
Internal: Convert a list of tokens to a string
|
||||||
|
|
||||||
|
* $1 - Destination variable for the string
|
||||||
|
* $2-$@ - Token list
|
||||||
|
|
||||||
|
Returns nothing.
|
||||||
|
|
||||||
|
|
||||||
|
`mo::getContentTrim()`
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
Internal: Trims content from MO_UNPARSED, returns trimmed content.
|
||||||
|
|
||||||
|
* $1 - Destination variable
|
||||||
|
|
||||||
|
Returns nothing.
|
||||||
|
|
||||||
|
|
||||||
|
`mo::getContentComment()`
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
Get the content up to and including a close tag
|
||||||
|
|
||||||
|
* $1 - Destination variable
|
||||||
|
|
||||||
|
Returns nothing.
|
||||||
|
|
||||||
|
|
||||||
|
`mo::getContentDelimiter()`
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
Get the content up to and including a close tag. First two non-whitespace tokens become the new open and close tag.
|
||||||
|
|
||||||
|
* $1 - Destination variable
|
||||||
|
|
||||||
|
Returns nothing.
|
||||||
|
|
||||||
|
|
||||||
|
`mo::getContentWithinTag()`
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
Get the content up to and including a close tag. First two non-whitespace tokens become the new open and close tag.
|
||||||
|
|
||||||
|
* $1 - Destination variable, an array
|
||||||
|
* $2 - Terminator string
|
||||||
|
|
||||||
|
The array contents: [0] The raw content within the tag
|
||||||
|
[1] The parsed tokens as a single string
|
||||||
|
|
||||||
|
Returns nothing.
|
||||||
|
|
||||||
|
|
||||||
|
`mo::tokenizeTagContents()`
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
Internal: Parse MO_UNPARSED and retrieve the content within the tag delimiters. Converts everything into an array of string values.
|
||||||
|
|
||||||
|
* $1 - Destination variable for the array of contents.
|
||||||
|
* $2 - Stop processing when this content is found.
|
||||||
|
|
||||||
|
The list of tokens are in RPN form. The first item in the resulting array is the number of actual tokens (after combining command tokens) in the list.
|
||||||
|
|
||||||
|
Given: a 'bc' "de\"\n" (f {g 'h'}) Result: ([0]=4 [1]=NAME [2]=a [3]=VALUE [4]=bc [5]=VALUE [6]=$'de\"\n' [7]=NAME [8]=f [9]=NAME [10]=g [11]=VALUE [12]=h [13]=BRACE [14]=2 [15]=PAREN [16]=2
|
||||||
|
|
||||||
|
Returns nothing
|
||||||
|
|
||||||
|
|
||||||
|
`mo::tokenizeTagContentsName()`
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
Internal: Get the contents of a variable name.
|
||||||
|
|
||||||
|
* $1 - Destination variable name for the token list (array of strings)
|
||||||
|
|
||||||
|
Returns nothing
|
||||||
|
|
||||||
|
|
||||||
|
`mo::tokenizeTagContentsDoubleQuote()`
|
||||||
|
--------------------------------------
|
||||||
|
|
||||||
|
Internal: Get the contents of a tag in double quotes. Parses the backslash sequences.
|
||||||
|
|
||||||
|
* $1 - Destination variable name for the token list (array of strings)
|
||||||
|
|
||||||
|
Returns nothing.
|
||||||
|
|
||||||
|
|
||||||
|
`mo::tokenizeTagContentsSingleQuote()`
|
||||||
|
--------------------------------------
|
||||||
|
|
||||||
|
Internal: Get the contents of a tag in single quotes. Only gets the raw value.
|
||||||
|
|
||||||
|
* $1 - Destination variable name for the token list (array of strings)
|
||||||
|
|
||||||
Returns nothing.
|
Returns nothing.
|
||||||
|
|
||||||
|
84
README.md
84
README.md
@ -188,20 +188,67 @@ myfunc() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Triple Mustache, Parenthesis, and Quotes
|
||||||
|
|
||||||
|
Normally, triple mustache syntax, such as `{{{var}}}` will avoid HTML escaping of the variable. Because HTML escaping is not supported in `mo`, this is now used differently. Anything within braces will be looked up and the values will be concatenated together and the result will be treated as a value. Anything in parenthesis will be looked up, concatenated, and treated as a name. Also, anything in single quotes is passed as a value; double quoted things first are unescaped and then passed as a value.
|
||||||
|
|
||||||
|
```
|
||||||
|
# Example input
|
||||||
|
var=abc
|
||||||
|
user=admin
|
||||||
|
admin=Administrator
|
||||||
|
u=user
|
||||||
|
abc=([0]=zero [1]=one [2]=two)
|
||||||
|
```
|
||||||
|
|
||||||
|
| Mustache syntax | Resulting output | Notes |
|
||||||
|
|-----------------|------------------|-------|
|
||||||
|
| `{{var}}` | `abc` | Normal behavior |
|
||||||
|
| `{{var us}}` | `abcus` | Concatenation |
|
||||||
|
| `{{'var'}}` | `var` | Passing as a value |
|
||||||
|
| `{{"a\tb"}}` | `a b` | There was an escaped tab in the value |
|
||||||
|
| `{{u}}` | `user` | Normal behavior |
|
||||||
|
| `{{{u}}}` | `user` | Look up "$u", treat as the value `{{'user'}}` |
|
||||||
|
| `{{(u)}}` | `admin` | Look up "$u", treat as the name `{{user}}` |
|
||||||
|
| `{{var user}}` | `abcuser` | Concatenation |
|
||||||
|
| `{{(var '.1')}}` | `one` | Look up "$var", treat as "abc", then concatenate ".1" and look up `{{abc.1}}` |
|
||||||
|
|
||||||
|
In double-quoted strings, the following escape sequences are defined.
|
||||||
|
|
||||||
|
* `\"` - Quote
|
||||||
|
* `\b` - Bell
|
||||||
|
* `\e` - Escape (note that Bash typically uses $'\E' for the same thing)
|
||||||
|
* `\f` - Form feed
|
||||||
|
* `\n` - Newline
|
||||||
|
* `\r` - Carriage return
|
||||||
|
* `\t` - Tab
|
||||||
|
* `\v` - Vertical tab
|
||||||
|
* Anything else will skip the `\` and place the next character. However, this implementation is allowed to change in the future if a different escape character mapping becomes commonplace.
|
||||||
|
|
||||||
|
|
||||||
Environment Variables and Functions
|
Environment Variables and Functions
|
||||||
-----------------------------------
|
-----------------------------------
|
||||||
|
|
||||||
There are several functions and variables used to process templates. `mo` reserves variables that start with `MO_` for variables exposing data or configuration, functions starting with `mo::`, and local variables starting with `mo[A-Z]`. You are welcome to use internal functions, though only ones that are marked as "Public" should not change their interface. Scripts may also read any of the variables.
|
There are several functions and variables used to process templates. `mo` reserves variables that start with `MO_` for variables exposing data or configuration, functions starting with `mo::`, and local variables starting with `mo[A-Z]`. You are welcome to use internal functions, though only ones that are marked as "Public" should not change their interface. Scripts may also read any of the variables.
|
||||||
|
|
||||||
* `MO_ALLOW_FUNCTION_ARGUMENTS` - When set to a non-empty value, this allows functions referenced in templates to receive additional options and arguments. This puts the content from the template directly into an eval statement. Use with extreme care.
|
Functions are all executed in a subshell, with another subshell for lambdas. Thus, your lambda can't affect the parsing of a template. There's more information about lambdas when talking about tests that fail.
|
||||||
|
|
||||||
|
* `MO_ALLOW_FUNCTION_ARGUMENTS` - When set to a non-empty value, this allows functions referenced in templates to receive additional options and arguments.
|
||||||
|
* `MO_CLOSE_DELIMITER` - The string used when closing a tag. Defaults to "}}". Used internally.
|
||||||
|
* `MO_CLOSE_DELIMITER_DEFAULT` - The default value of `MO_CLOSE_DELIMITER`. Used when resetting the close delimiter, such as when parsing a partial.
|
||||||
|
* `MO_CURRENT` - Variable name to use for ".".
|
||||||
* `MO_DEBUG` - When set to a non-empty value, additional debug information is written to stderr.
|
* `MO_DEBUG` - When set to a non-empty value, additional debug information is written to stderr.
|
||||||
* `MO_FUNCTION_ARGS` - Arguments passed to the function.
|
* `MO_FUNCTION_ARGS` - Arguments passed to the function.
|
||||||
|
* `MO_FAIL_ON_FILE` - If a filename from the command-line is missing or a partial does not exist, abort with an error.
|
||||||
* `MO_FAIL_ON_FUNCTION` - If a function returns a non-zero status code, abort with an error.
|
* `MO_FAIL_ON_FUNCTION` - If a function returns a non-zero status code, abort with an error.
|
||||||
* `MO_FAIL_ON_UNSET` - When set to a non-empty value, expansion of an unset env variable will be aborted with an error.
|
* `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_OPEN_DELIMITER` - The string used when opening a tag. Defaults to "{{". Used internally.
|
||||||
|
* `MO_OPEN_DELIMITER_DEFAULT` - The default value of MO_OPEN_DELIMITER. Used when resetting the open delimiter, such as when parsing a partial.
|
||||||
* `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.
|
||||||
* `MO_VERSION` - Version of `mo`.
|
* `MO_PARSED` - Content that has made it through the template engine.
|
||||||
|
* `MO_STANDALONE_CONTENT` - The unparsed content that preceeded the current tag. When a standalone tag is encountered, this is checked to see if it only contains whitespace. If this and the whitespace condition after a tag is met, then this will be reset to $'\n'.
|
||||||
|
* `MO_UNPARSED` - Template content yet to make it through the parser.
|
||||||
|
|
||||||
|
|
||||||
Concessions
|
Concessions
|
||||||
@ -238,16 +285,41 @@ When submitting patches, make sure to run them past [ShellCheck] and ensure no p
|
|||||||
|
|
||||||
### Porting and Backporting
|
### Porting and Backporting
|
||||||
|
|
||||||
There is a diagnostic script that is aimed to help at making sure the internal functions all work when testing the script out on a version of bash or in an environment that is causing issues. When adding new functions, please make sure this script gets updated to properly test your code.
|
In case of problems, setting MO_DEBUG to a non-empty value will give you LOTS of output.
|
||||||
|
|
||||||
|
MO_DEBUG=1 ./mo my-template
|
||||||
|
|
||||||
|
|
||||||
### Failed Specs
|
### Failed Specs
|
||||||
|
|
||||||
It is acceptable for some of the official spec tests to fail. Anything dealing with multiple levels of objects (eg. `{{a.b.c}}`) and changing the delimiters (`{{= | | =}}`) will fail. Other than that, this bash implementation of the mustache spec should pass tests.
|
It is acceptable for some of the official spec tests to fail. The spec runner has specific exclusions and overrides to test similar functionality that avoid the following issues.
|
||||||
|
|
||||||
Specific issues:
|
* Using `{{.}}` outside of a loop - In order to access any variable, you must use its name. In a loop, `{{.}}` will refer to the current value, but outside the loop you are unable to use this dot notation because there is no current value.
|
||||||
* Interpolation - Multiple Calls: This fails because lambdas execute in a subshell so their output can be captured. This is flagged as a TODO in the code.
|
* Deeply nested data - Bash doesn't support complex data structure. Basically, just strings and arrays of strings.
|
||||||
|
* Interpolation; Multiple Calls: This fails because lambdas execute in a subshell so their output can be captured. If you want state to be preserved, you will need to write it outside of the current environment and load it again later.
|
||||||
* HTML Escaping - Since bash is not often executed in a web server context, it makes no sense to have the output escaped as HTML. Performing shell escaping of variables may be an option in the future if there's a demand.
|
* HTML Escaping - Since bash is not often executed in a web server context, it makes no sense to have the output escaped as HTML. Performing shell escaping of variables may be an option in the future if there's a demand.
|
||||||
|
* Lambdas - Function results are *not* automatically interpreted again. If you want to parse the results as Mustache content, use `mo::parse`. When they use `mo::parse`, it will use the current delimiters.
|
||||||
|
|
||||||
|
For lambdas, these examples may help.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Retrieve content into a variable.
|
||||||
|
content=$(cat)
|
||||||
|
|
||||||
|
# Retrieve all content and do not trim newlines at the end.
|
||||||
|
content=$(cat; echo -n '.')
|
||||||
|
content=${content%.}
|
||||||
|
|
||||||
|
# Parse content using the current delimiters
|
||||||
|
mo::parse results "This is my content. Hello, {{username}}"
|
||||||
|
echo -n "$results"
|
||||||
|
|
||||||
|
# Parse content using the default delimiters
|
||||||
|
MO_OPEN_DELIMITER=$MO_OPEN_DELIMITER_DEFAULT
|
||||||
|
MO_CLOSE_DELIMITER=$MO_CLOSE_DELIMITER_DEFAULT
|
||||||
|
mo::parse results "This is my content. Hello, {{username}}"
|
||||||
|
echo -n "$results"
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
### Future Enhancements
|
### Future Enhancements
|
||||||
|
@ -24,8 +24,8 @@ TITLE() {
|
|||||||
|
|
||||||
cat <<EOF | mo -u
|
cat <<EOF | mo -u
|
||||||
{{TITLE 'Part 1'}}
|
{{TITLE 'Part 1'}}
|
||||||
{{INCLUDE function-args-part1}}
|
{{INCLUDE 'function-args-part1'}}
|
||||||
|
|
||||||
{{TITLE 'Part 2'}}
|
{{TITLE 'Part 2'}}
|
||||||
{{INCLUDE function-args-part2}}
|
{{INCLUDE 'function-args-part2'}}
|
||||||
EOF
|
EOF
|
||||||
|
@ -9,6 +9,8 @@ foreach() {
|
|||||||
|
|
||||||
foreachContent=$(cat)
|
foreachContent=$(cat)
|
||||||
|
|
||||||
|
local x
|
||||||
|
x=("${@}")
|
||||||
if [[ "$2" != "as" && "$2" != "in" ]]; then
|
if [[ "$2" != "as" && "$2" != "in" ]]; then
|
||||||
echo "Invalid foreach - bad format."
|
echo "Invalid foreach - bad format."
|
||||||
elif [[ "$(declare -p "$1")" != "declare -"[aA]* ]]; then
|
elif [[ "$(declare -p "$1")" != "declare -"[aA]* ]]; then
|
||||||
@ -41,8 +43,8 @@ links=(resque hub rip)
|
|||||||
# Process the template
|
# Process the template
|
||||||
cat <<EOF | mo --allow-function-arguments
|
cat <<EOF | mo --allow-function-arguments
|
||||||
Here are your links:
|
Here are your links:
|
||||||
{{#foreach links as link}}
|
{{#foreach 'links' 'as' 'link'}}
|
||||||
* [{{link.name}}]({{link.url}})
|
* [{{link.name}}]({{link.url}})
|
||||||
{{/foreach}}
|
{{/foreach 'links' 'as' 'link'}}
|
||||||
|
|
||||||
EOF
|
EOF
|
||||||
|
393
diagnostic
393
diagnostic
@ -1,393 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
#
|
|
||||||
# Test individual functions in mo and make sure they are performing their
|
|
||||||
# tasks correctly. This can be used to help determine what needs to get
|
|
||||||
# fixed when making mo work on another version of bash, or another shell
|
|
||||||
# entirely.
|
|
||||||
#
|
|
||||||
# Functions are tested from the most primitive to the most advanced.
|
|
||||||
# Errors, once found, halt the program. This way we can more easily
|
|
||||||
# diagnose what's not working and fix those low-level functions first.
|
|
||||||
|
|
||||||
PARENT_PID=$$
|
|
||||||
cd "$(dirname "$0")" || exit 1
|
|
||||||
rm -f diagnostic.test
|
|
||||||
rm -f diagnostic.partial
|
|
||||||
|
|
||||||
# Load mo's functions
|
|
||||||
# shellcheck disable=SC1091
|
|
||||||
. ./mo
|
|
||||||
|
|
||||||
fail() {
|
|
||||||
echo "$1"
|
|
||||||
kill $PARENT_PID
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# No dependencies
|
|
||||||
|
|
||||||
echo -n "moIndirect ... "
|
|
||||||
(
|
|
||||||
a() {
|
|
||||||
local V
|
|
||||||
V="a"
|
|
||||||
b
|
|
||||||
echo -n "$V"
|
|
||||||
}
|
|
||||||
|
|
||||||
b() {
|
|
||||||
local V
|
|
||||||
V="b"
|
|
||||||
c V
|
|
||||||
echo -n "$V"
|
|
||||||
}
|
|
||||||
|
|
||||||
c() {
|
|
||||||
local "$1" && moIndirect "$1" c
|
|
||||||
}
|
|
||||||
|
|
||||||
[[ "$(a)" != "ca" ]] && fail "Did not assign or scope bled '$RESULT'"
|
|
||||||
)
|
|
||||||
echo "ok"
|
|
||||||
|
|
||||||
echo -n "moIndirectArray ... "
|
|
||||||
(
|
|
||||||
a() {
|
|
||||||
local V
|
|
||||||
V=( "a" )
|
|
||||||
b
|
|
||||||
echo -n "${#V[@]}"
|
|
||||||
}
|
|
||||||
|
|
||||||
b() {
|
|
||||||
local V
|
|
||||||
V=( "b" "b" )
|
|
||||||
c V
|
|
||||||
echo -n "${#V[@]}"
|
|
||||||
}
|
|
||||||
|
|
||||||
c() {
|
|
||||||
local "$1" && moIndirectArray "$1" c c c
|
|
||||||
}
|
|
||||||
|
|
||||||
[[ "$(a)" != "31" ]] && fail "Did not assign or scope bled '$RESULT'"
|
|
||||||
)
|
|
||||||
echo "ok"
|
|
||||||
|
|
||||||
echo -n "moIsArray ... "
|
|
||||||
(
|
|
||||||
export TEST_NUM=1
|
|
||||||
moIsArray TEST_NUM && fail "Wrongly said number was an array"
|
|
||||||
)
|
|
||||||
(
|
|
||||||
export TEST_ARR=()
|
|
||||||
moIsArray TEST_ARR || fail "Wrongly said array was not an array"
|
|
||||||
)
|
|
||||||
(
|
|
||||||
export TEST_EMPTY=""
|
|
||||||
moIsArray TEST_EMPTY && fail "Wrongly said string was an array"
|
|
||||||
)
|
|
||||||
(
|
|
||||||
unset TEST_UNSET
|
|
||||||
moIsArray TEST_UNSET && fail "Wrongly said undefined was an array"
|
|
||||||
)
|
|
||||||
echo "ok"
|
|
||||||
|
|
||||||
echo -n "moIsFunction ... "
|
|
||||||
(
|
|
||||||
aa() { echo "hi"; }
|
|
||||||
|
|
||||||
moIsFunction aa || fail "Did not find a function"
|
|
||||||
|
|
||||||
moIsFunction dd && fail "Wrongly found a function"
|
|
||||||
)
|
|
||||||
echo "ok"
|
|
||||||
|
|
||||||
echo -n "moFindString ... "
|
|
||||||
(
|
|
||||||
moFindString POS "abcdefg" "ab"
|
|
||||||
[[ "$POS" == "0" ]] || fail "Did not find at beginning of string"
|
|
||||||
|
|
||||||
moFindString POS "abcdefg" "fg"
|
|
||||||
[[ "$POS" == "5" ]] || fail "Did not find at end of string"
|
|
||||||
|
|
||||||
moFindString POS "abcdefg" "de"
|
|
||||||
[[ "$POS" == "3" ]] || fail "Did not find at middle of string"
|
|
||||||
|
|
||||||
moFindString POS "abcdefg" "CD"
|
|
||||||
[[ "$POS" == "-1" ]] || fail "Did not correctly return a miss"
|
|
||||||
)
|
|
||||||
echo "ok"
|
|
||||||
|
|
||||||
echo -n "moFullTagName ... "
|
|
||||||
(
|
|
||||||
moFullTagName TAG "abc" "def"
|
|
||||||
[[ "$TAG" == "abc.def" ]] || fail "Did not work with a context"
|
|
||||||
|
|
||||||
moFullTagName TAG "" "one"
|
|
||||||
[[ "$TAG" == "one" ]] || fail "Did not work without a context"
|
|
||||||
)
|
|
||||||
echo "ok"
|
|
||||||
|
|
||||||
echo -n "moLoadFile ... "
|
|
||||||
(
|
|
||||||
# TODO - find a way to test reading from stdin that is not painful.
|
|
||||||
|
|
||||||
touch diagnostic.test
|
|
||||||
moLoadFile RESULT diagnostic.test
|
|
||||||
[[ "${#RESULT}" == "0" ]] || fail "Did not read from empty file '$RESULT'"
|
|
||||||
|
|
||||||
echo "abc" >> diagnostic.test
|
|
||||||
moLoadFile RESULT diagnostic.test
|
|
||||||
[[ "${#RESULT}" == "4" ]] || fail "Did not read from file '$RESULT'"
|
|
||||||
|
|
||||||
echo "" >> diagnostic.test
|
|
||||||
moLoadFile RESULT diagnostic.test
|
|
||||||
[[ "${#RESULT}" == "5" ]] || fail "Trimmed newline from file '$RESULT'"
|
|
||||||
|
|
||||||
rm diagnostic.test
|
|
||||||
)
|
|
||||||
echo "ok"
|
|
||||||
|
|
||||||
echo -n "moStandaloneDenied ... "
|
|
||||||
(
|
|
||||||
moStandaloneDenied RESULT before tag after > diagnostic.test
|
|
||||||
[[ "$(cat diagnostic.test)" == "before" ]] || fail "Did not output content before tag '$(cat diagnostic.test)'"
|
|
||||||
[[ "$RESULT" == "after" ]] || fail "Did not set the remaining content '$RESULT'"
|
|
||||||
rm diagnostic.test
|
|
||||||
)
|
|
||||||
echo "ok"
|
|
||||||
|
|
||||||
echo -n "moTest ... "
|
|
||||||
# shellcheck disable=SC2034
|
|
||||||
(
|
|
||||||
aa() { echo "hi"; }
|
|
||||||
bb="bb"
|
|
||||||
cc=3
|
|
||||||
dd=( dd )
|
|
||||||
xx=()
|
|
||||||
unset yy
|
|
||||||
zz=""
|
|
||||||
|
|
||||||
moTest aa || fail "Did not detect a function"
|
|
||||||
moTest bb || fail "Did not detect a non-empty string"
|
|
||||||
moTest cc || fail "Did not detect a number"
|
|
||||||
moTest dd || fail "Did not detect a populated array"
|
|
||||||
moTest xx && fail "Erroneously flagged an empty array"
|
|
||||||
moTest yy && fail "Erroneously flagged an undefined value"
|
|
||||||
moTest zz && fail "Erroneously flagged an empty string"
|
|
||||||
MO_FALSE_IF_EMPTY=true moTest zz && fail "Erroneously flagged an empty value as non-false when empty should be false"
|
|
||||||
)
|
|
||||||
echo "ok"
|
|
||||||
|
|
||||||
echo -n "moTrimChars ... "
|
|
||||||
(
|
|
||||||
moTrimChars RESULT "abcdabc" true true a b c
|
|
||||||
[[ "$RESULT" == "d" ]] || fail "Did not trim multiple characters '$RESULT'"
|
|
||||||
|
|
||||||
moTrimChars RESULT "abc" true false a c
|
|
||||||
[[ "$RESULT" == "bc" ]] || fail "Did not trim only from the front '$RESULT'"
|
|
||||||
|
|
||||||
moTrimChars RESULT "abc" false true a c
|
|
||||||
[[ "$RESULT" == "ab" ]] || fail "Did not trim only from the end '$RESULT'"
|
|
||||||
)
|
|
||||||
echo "ok"
|
|
||||||
|
|
||||||
echo -n "moGetContent ... "
|
|
||||||
(
|
|
||||||
# TODO - find a way to test reading from stdin that is not painful.
|
|
||||||
# Until then, mock it
|
|
||||||
|
|
||||||
moLoadFile() { local "$1" && moIndirect "$1" "STDIN"; }
|
|
||||||
|
|
||||||
moGetContent RESULT a
|
|
||||||
[[ "$RESULT" == "{{>a}}" ]] || fail "Did not construct 1 partial correctly '$RESULT'"
|
|
||||||
|
|
||||||
moGetContent RESULT a b c
|
|
||||||
[[ "$RESULT" == "{{>a}}{{>b}}{{>c}}" ]] || fail "Did not construct 3 partials correctly '$RESULT'"
|
|
||||||
|
|
||||||
moGetContent RESULT
|
|
||||||
[[ "$RESULT" == "STDIN" ]] || fail "Did not call moLoadFile correctly"
|
|
||||||
)
|
|
||||||
echo "ok"
|
|
||||||
|
|
||||||
echo -n "moIndentLines ... "
|
|
||||||
(
|
|
||||||
CR=$'\r'
|
|
||||||
LF=$'\n'
|
|
||||||
# shellcheck disable=SC2034
|
|
||||||
CRLF="$CR$LF"
|
|
||||||
|
|
||||||
# CAUTION
|
|
||||||
# This must have a dot at the end of the input string
|
|
||||||
# That is part of how moPartial calls this function
|
|
||||||
for NEWLINE in "CR" "LF" "CRLF"; do
|
|
||||||
NL="${!NEWLINE}"
|
|
||||||
|
|
||||||
moIndentLines RESULT "" "has${NL}${NEWLINE}${NL}."
|
|
||||||
printf -v QUOTED '%q' "$RESULT"
|
|
||||||
[[ "$RESULT" == "has${NL}${NEWLINE}${NL}" ]] || fail "Should not have changed string $QUOTED"
|
|
||||||
|
|
||||||
moIndentLines RESULT "" "without${NL}trailing${NL}${NEWLINE}."
|
|
||||||
printf -v QUOTED '%q' "$RESULT"
|
|
||||||
[[ "$RESULT" == "without${NL}trailing${NL}${NEWLINE}" ]] || fail "Should not have changed string $QUOTED"
|
|
||||||
|
|
||||||
moIndentLines RESULT "_-_" "has${NL}${NL}${NEWLINE}${NL}."
|
|
||||||
printf -v QUOTED '%q' "$RESULT"
|
|
||||||
[[ "$RESULT" == "_-_has${NL}${NL}_-_${NEWLINE}${NL}" ]] || fail "Should have indented $QUOTED"
|
|
||||||
|
|
||||||
moIndentLines RESULT "_-_" "without${NL}${NL}trailing${NL}${NEWLINE}."
|
|
||||||
printf -v QUOTED '%q' "$RESULT"
|
|
||||||
[[ "$RESULT" == "_-_without${NL}${NL}_-_trailing${NL}_-_${NEWLINE}" ]] || fail "Should have indented $QUOTED"
|
|
||||||
done
|
|
||||||
)
|
|
||||||
echo "ok"
|
|
||||||
|
|
||||||
echo -n "moIsStandalone ... "
|
|
||||||
(
|
|
||||||
CR=$'\r'
|
|
||||||
LF=$'\n'
|
|
||||||
TAB=$'\t'
|
|
||||||
|
|
||||||
moIsStandalone RESULT "" "" false && fail "Needs newline before or beginning of content flag"
|
|
||||||
moIsStandalone RESULT "" "" true || fail "Has beginning of content flag and no other content"
|
|
||||||
[[ "$RESULT" == "0 0" ]] || fail "Wrong returned value for no content '$RESULT'"
|
|
||||||
moIsStandalone RESULT "moo" "cow" false && fail "Needs newlines when there is content"
|
|
||||||
moIsStandalone RESULT "moo$CR$LF$TAB $TAB " " $TAB $TAB$CR${LF}pasture" false || fail "Has newlines and content but did not flag"
|
|
||||||
[[ "$RESULT" == "5 6" ]] || fail "Wrong returned value when there was whitespace '$RESULT'"
|
|
||||||
)
|
|
||||||
echo "ok"
|
|
||||||
|
|
||||||
echo -n "moSplit ... "
|
|
||||||
(
|
|
||||||
moSplit RESULT "abc-def-ghi" "-"
|
|
||||||
[[ "${#RESULT[@]}" == "2" ]] || fail "Returned wrong number of elements with one delimiter ${#RESULT[@]}"
|
|
||||||
[[ "${RESULT[0]}" == "abc" ]] || fail "Returned wrong left hand string with one delimiter '${RESULT[0]}'"
|
|
||||||
[[ "${RESULT[1]}" == "def-ghi" ]] || fail "Returned wrong right hand string with one delimiter '${RESULT[1]}'"
|
|
||||||
|
|
||||||
moSplit RESULT "abc-def-ghi" "-" "g"
|
|
||||||
[[ "${#RESULT[@]}" == "3" ]] || fail "Returned wrong number of elements with two delimiters ${#RESULT[@]}"
|
|
||||||
[[ "${RESULT[0]}" == "abc" ]] || fail "Returned wrong left hand string with two delimiters '${RESULT[0]}'"
|
|
||||||
[[ "${RESULT[1]}" == "def-" ]] || fail "Returned wrong middle string with two delimiters '${RESULT[1]}'"
|
|
||||||
[[ "${RESULT[2]}" == "hi" ]] || fail "Returned wrong right hand string with two delimiters '${RESULT[2]}'"
|
|
||||||
)
|
|
||||||
echo "ok"
|
|
||||||
|
|
||||||
echo -n "moTrimWhitespace ... "
|
|
||||||
(
|
|
||||||
CR=$'\r'
|
|
||||||
LF=$'\n'
|
|
||||||
TAB=$'\t'
|
|
||||||
|
|
||||||
moTrimWhitespace RESULT "ab cd"
|
|
||||||
printf -v QUOTED '%q' "$RESULT"
|
|
||||||
[[ "${RESULT}" == "ab cd" ]] || fail "Trimmed a string that did not need trimming $QUOTED"
|
|
||||||
|
|
||||||
moTrimWhitespace RESULT "$CR$LF$TAB ab $CR$LF$TAB cd $CR$LF$TAB $CR$LF$TAB"
|
|
||||||
printf -v QUOTED '%q' "$RESULT"
|
|
||||||
[[ "${RESULT}" == "ab $CR$LF$TAB cd" ]] || fail "Did not fully trim a string $QUOTED"
|
|
||||||
)
|
|
||||||
echo "ok"
|
|
||||||
|
|
||||||
echo -n "moStandaloneAllowed ... "
|
|
||||||
(
|
|
||||||
# Mock moIsStandalone to make things simpler
|
|
||||||
|
|
||||||
moIsStandalone() { return 1; }
|
|
||||||
|
|
||||||
moStandaloneAllowed RESULT before tag after > diagnostic.test
|
|
||||||
[[ "$(cat diagnostic.test)" == "before" ]] || fail "Did not output content before tag when not standalone '$(cat diagnostic.test)'"
|
|
||||||
[[ "$RESULT" == "after" ]] || fail "Did not set the remaining content when not standalone '$RESULT'"
|
|
||||||
|
|
||||||
moIsStandalone() { local "$1" && moIndirect "$1" "3 5"; return 0; }
|
|
||||||
|
|
||||||
moStandaloneAllowed RESULT before tag afterwards > diagnostic.test
|
|
||||||
[[ "$(cat diagnostic.test)" == "bef" ]] || fail "Did not output content before tag when standalone '$(cat diagnostic.test)'"
|
|
||||||
[[ "$RESULT" == "wards" ]] || fail "Did not set the remaining content when standalone '$RESULT'"
|
|
||||||
|
|
||||||
rm diagnostic.test
|
|
||||||
)
|
|
||||||
echo "ok"
|
|
||||||
|
|
||||||
echo -n "moFindEndTag ... "
|
|
||||||
(
|
|
||||||
LF=$'\n'
|
|
||||||
|
|
||||||
moFindEndTag RESULT "moo{{ / cow }}pasture" "cow"
|
|
||||||
[[ "${#RESULT[@]}" == "3" ]] || fail "(simple) Wrong number of elements in the array ${#RESULT[@]}"
|
|
||||||
[[ "${RESULT[0]}" == "moo" ]] || fail "(simple) Wrong left-hand '${RESULT[0]}'"
|
|
||||||
[[ "${RESULT[1]}" == "{{ / cow }}" ]] || fail "(simple) Wrong middle '${RESULT[1]}'"
|
|
||||||
[[ "${RESULT[2]}" == "pasture" ]] || fail "(simple) Wrong right-hand '${RESULT[2]}'"
|
|
||||||
|
|
||||||
moFindEndTag RESULT "moo$LF {{/cow}} $LF pasture" "cow"
|
|
||||||
[[ "${#RESULT[@]}" == "3" ]] || fail "(standalone) Wrong number of elements in the array ${#RESULT[@]}"
|
|
||||||
[[ "${RESULT[0]}" == "moo$LF" ]] || fail "(standalone) Wrong left-hand '${RESULT[0]}'"
|
|
||||||
[[ "${RESULT[1]}" == " {{/cow}} $LF" ]] || fail "(standalone) Wrong middle '${RESULT[1]}'"
|
|
||||||
[[ "${RESULT[2]}" == " pasture" ]] || fail "(standalone) Wrong right-hand '${RESULT[2]}'"
|
|
||||||
|
|
||||||
moFindEndTag RESULT "aa{{#bb}}cc{{/bb}}dd{{^bb}}ee{{/bb}}ff{{/bb}}gg" "bb"
|
|
||||||
[[ "${#RESULT[@]}" == "3" ]] || fail "(recursive) Wrong number of elements in the array ${#RESULT[@]}"
|
|
||||||
[[ "${RESULT[0]}" == "aa{{#bb}}cc{{/bb}}dd{{^bb}}ee{{/bb}}ff" ]] || fail "(recursive) Wrong left-hand '${RESULT[0]}'"
|
|
||||||
[[ "${RESULT[1]}" == "{{/bb}}" ]] || fail "(recursive) Wrong middle '${RESULT[1]}'"
|
|
||||||
[[ "${RESULT[2]}" == "gg" ]] || fail "(recursive) Wrong right-hand '${RESULT[2]}'"
|
|
||||||
|
|
||||||
moFindEndTag RESULT "aa{{#bb}}cc{{/dd}}ee" "dd"
|
|
||||||
[[ "${#RESULT[@]}" == "3" ]] || fail "(unbalanced) Wrong number of elements in the array ${#RESULT[@]}"
|
|
||||||
[[ "${RESULT[0]}" == "aa{{#bb}}cc{{/dd}}ee" ]] || fail "(unbalanced) Wrong left-hand '${RESULT[0]}'"
|
|
||||||
[[ "${RESULT[1]}" == "" ]] || fail "(unbalanced) Wrong middle '${RESULT[1]}'"
|
|
||||||
[[ "${RESULT[2]}" == "" ]] || fail "(unbalanced) Wrong right-hand '${RESULT[2]}'"
|
|
||||||
)
|
|
||||||
echo "ok"
|
|
||||||
|
|
||||||
echo -n "moLoop ... "
|
|
||||||
(
|
|
||||||
moParse() { echo "parse[$1] context[$2] flag[$3]"; }
|
|
||||||
|
|
||||||
moLoop "content" "prefix" a b c > diagnostic.test
|
|
||||||
(
|
|
||||||
echo "parse[content] context[prefix.a] flag[false]"
|
|
||||||
echo "parse[content] context[prefix.b] flag[false]"
|
|
||||||
echo "parse[content] context[prefix.c] flag[false]"
|
|
||||||
) | diff -q diagnostic.test - || fail "Result was not as expected '$(cat diagnostic.test)'"
|
|
||||||
|
|
||||||
rm diagnostic.test
|
|
||||||
)
|
|
||||||
echo "ok"
|
|
||||||
|
|
||||||
echo -n "moPartial ... "
|
|
||||||
(
|
|
||||||
NL=$'\n'
|
|
||||||
moParse() { echo "parse[$1] context[$2] flag[$3]"; }
|
|
||||||
echo "partial" > diagnostic.partial
|
|
||||||
|
|
||||||
moPartial RESULT "line$NL" ">diagnostic.partial" " ${NL}line2" false "moo" > diagnostic.test
|
|
||||||
[[ "$RESULT" == "line2" ]] || fail "Did not consume newline for standalone '$RESULT'"
|
|
||||||
printf -v QUOTED '%q' "$(cat diagnostic.test)"
|
|
||||||
[[ "$(cat diagnostic.test)" == "line${NL}parse[partial${NL}] context[moo] flag[true]" ]] || fail "Did not parse right standalone content $QUOTED"
|
|
||||||
|
|
||||||
moPartial RESULT "line" ">diagnostic.partial" "line2" false "moo" > diagnostic.test
|
|
||||||
[[ "$RESULT" == "line2" ]] || fail "Did not preserve content for non-standalone '$RESULT'"
|
|
||||||
printf -v QUOTED '%q' "$(cat diagnostic.test)"
|
|
||||||
[[ "$(cat diagnostic.test)" == "lineparse[partial${NL}] context[moo] flag[true]" ]] || fail "Did not parse right non-standalone content $QUOTED"
|
|
||||||
|
|
||||||
rm diagnostic.test diagnostic.partial
|
|
||||||
)
|
|
||||||
echo "ok"
|
|
||||||
|
|
||||||
echo -n "moShow ... "
|
|
||||||
# shellcheck disable=SC2034
|
|
||||||
(
|
|
||||||
aa() { echo "this is aa"; }
|
|
||||||
bb="BB"
|
|
||||||
cc=( zero one two )
|
|
||||||
|
|
||||||
[[ "$(moShow aa)" == "this is aa" ]] || fail "Did not load function"
|
|
||||||
[[ "$(moShow bb)" == "BB" ]] || fail "Did not show variable"
|
|
||||||
[[ "$(moShow cc.1)" == "one" ]] || fail "Did not show value from indexed array"
|
|
||||||
)
|
|
||||||
echo "ok"
|
|
||||||
|
|
||||||
echo -n "moParse ... skipped (tested by specs)"
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "All diagnostic tests pass"
|
|
286
mo
286
mo
@ -39,6 +39,10 @@
|
|||||||
#/ -s=FILE, --source=FILE
|
#/ -s=FILE, --source=FILE
|
||||||
#/ Load FILE into the environment before processing templates.
|
#/ Load FILE into the environment before processing templates.
|
||||||
#/ Can be used multiple times.
|
#/ Can be used multiple times.
|
||||||
|
#/ -o=DELIM, --open=DELIM
|
||||||
|
#/ Set the opening delimiter. Default is "{{".
|
||||||
|
#/ -c=DELIM, --close=DELIM
|
||||||
|
#/ Set the closing delimiter. Default is "}}".
|
||||||
#/ -- Indicate the end of options. All arguments after this will be
|
#/ -- Indicate the end of options. All arguments after this will be
|
||||||
#/ treated as filenames only. Use when filenames may start with
|
#/ treated as filenames only. Use when filenames may start with
|
||||||
#/ hyphens.
|
#/ hyphens.
|
||||||
@ -49,6 +53,9 @@
|
|||||||
#/ functions referenced in templates to receive additional options and
|
#/ functions referenced in templates to receive additional options and
|
||||||
#/ arguments.
|
#/ arguments.
|
||||||
#/ MO_CLOSE_DELIMITER - The string used when closing a tag. Defaults to "}}".
|
#/ MO_CLOSE_DELIMITER - The string used when closing a tag. Defaults to "}}".
|
||||||
|
#/ Used internally.
|
||||||
|
#/ MO_CLOSE_DELIMITER_DEFAULT - The default value of MO_CLOSE_DELIMITER. Used
|
||||||
|
#/ when resetting the close delimiter, such as when parsing a partial.
|
||||||
#/ MO_CURRENT - Variable name to use for ".".
|
#/ MO_CURRENT - Variable name to use for ".".
|
||||||
#/ MO_DEBUG - When set to a non-empty value, additional debug information is
|
#/ MO_DEBUG - When set to a non-empty value, additional debug information is
|
||||||
#/ written to stderr.
|
#/ written to stderr.
|
||||||
@ -62,11 +69,14 @@
|
|||||||
#/ MO_FALSE_IS_EMPTY - When set to a non-empty value, the string "false" will
|
#/ 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.
|
#/ be treated as an empty value for the purposes of conditionals.
|
||||||
#/ MO_OPEN_DELIMITER - The string used when opening a tag. Defaults to "{{".
|
#/ MO_OPEN_DELIMITER - The string used when opening a tag. Defaults to "{{".
|
||||||
|
#/ Used internally.
|
||||||
|
#/ MO_OPEN_DELIMITER_DEFAULT - The default value of MO_OPEN_DELIMITER. Used
|
||||||
|
#/ when resetting the open delimiter, such as when parsing a partial.
|
||||||
#/ MO_ORIGINAL_COMMAND - Used to find the `mo` program in order to generate a
|
#/ MO_ORIGINAL_COMMAND - Used to find the `mo` program in order to generate a
|
||||||
#/ help message.
|
#/ help message.
|
||||||
#/ MO_PARSED - Content that has made it through the template engine.
|
#/ MO_PARSED - Content that has made it through the template engine.
|
||||||
#/ MO_STANDALONE_CONTENT - The content that preceeded the current tag. When a
|
#/ MO_STANDALONE_CONTENT - The unparsed content that preceeded the current tag.
|
||||||
#/ standalone tag is encountered, this is checked to see if it only
|
#/ When a standalone tag is encountered, this is checked to see if it only
|
||||||
#/ contains whitespace. If this and the whitespace condition after a tag is
|
#/ contains whitespace. If this and the whitespace condition after a tag is
|
||||||
#/ met, then this will be reset to $'\n'.
|
#/ met, then this will be reset to $'\n'.
|
||||||
#/ MO_UNPARSED - Template content yet to make it through the parser.
|
#/ MO_UNPARSED - Template content yet to make it through the parser.
|
||||||
@ -78,12 +88,12 @@
|
|||||||
#/
|
#/
|
||||||
#/ https://github.com/tests-always-included/mo
|
#/ https://github.com/tests-always-included/mo
|
||||||
|
|
||||||
# Disable these warnings for the entire file
|
#: Disable these warnings for the entire file
|
||||||
#
|
#:
|
||||||
# VAR_NAME was modified in a subshell. That change might be lost.
|
#: VAR_NAME was modified in a subshell. That change might be lost.
|
||||||
# shellcheck disable=SC2031
|
# shellcheck disable=SC2031
|
||||||
#
|
#:
|
||||||
# Modification of VAR_NAME is local (to subshell caused by (..) group).
|
#: Modification of VAR_NAME is local (to subshell caused by (..) group).
|
||||||
# shellcheck disable=SC2030
|
# shellcheck disable=SC2030
|
||||||
|
|
||||||
# Public: Template parser function. Writes templates to stdout.
|
# Public: Template parser function. Writes templates to stdout.
|
||||||
@ -95,13 +105,15 @@
|
|||||||
mo() (
|
mo() (
|
||||||
local moSource moFiles moDoubleHyphens moParsed moContent
|
local moSource moFiles moDoubleHyphens moParsed moContent
|
||||||
|
|
||||||
# This function executes in a subshell; IFS is reset at the end.
|
#: This function executes in a subshell; IFS is reset at the end.
|
||||||
IFS=$' \n\t'
|
IFS=$' \n\t'
|
||||||
|
|
||||||
# Enable a strict mode. This is also reset at the end.
|
#: Enable a strict mode. This is also reset at the end.
|
||||||
set -eEu -o pipefail
|
set -eEu -o pipefail
|
||||||
moFiles=()
|
moFiles=()
|
||||||
moDoubleHyphens=false
|
moDoubleHyphens=false
|
||||||
|
MO_OPEN_DELIMITER_DEFAULT="{{"
|
||||||
|
MO_CLOSE_DELIMITER_DEFAULT="}}"
|
||||||
|
|
||||||
if [[ $# -gt 0 ]]; then
|
if [[ $# -gt 0 ]]; then
|
||||||
for arg in "$@"; do
|
for arg in "$@"; do
|
||||||
@ -152,6 +164,22 @@ mo() (
|
|||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
|
|
||||||
|
-o=* | --open=*)
|
||||||
|
if [[ "$arg" == --open=* ]]; then
|
||||||
|
MO_OPEN_DELIMITER_DEFAULT="${arg#--open=}"
|
||||||
|
else
|
||||||
|
MO_OPEN_DELIMITER_DEFAULT="${arg#-o=}"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
|
||||||
|
-c=* | --close=*)
|
||||||
|
if [[ "$arg" == --close=* ]]; then
|
||||||
|
MO_CLOSE_DELIMITER_DEFAULT="${arg#--close=}"
|
||||||
|
else
|
||||||
|
MO_CLOSE_DELIMITER_DEFAULT="${arg#-c=}"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
|
||||||
-d | --debug)
|
-d | --debug)
|
||||||
MO_DEBUG=true
|
MO_DEBUG=true
|
||||||
;;
|
;;
|
||||||
@ -175,18 +203,8 @@ mo() (
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
mo::debug "Debug enabled"
|
mo::debug "Debug enabled"
|
||||||
MO_OPEN_DELIMITER="${MO_OPEN_DELIMITER:-"{{"}"
|
MO_OPEN_DELIMITER="$MO_OPEN_DELIMITER_DEFAULT"
|
||||||
MO_CLOSE_DELIMITER="${MO_CLOSE_DELIMITER:-"}}"}"
|
MO_CLOSE_DELIMITER="$MO_CLOSE_DELIMITER_DEFAULT"
|
||||||
|
|
||||||
# The standalone content is a trick to make the standalone tag detection
|
|
||||||
# possible. When it's set to content with a newline and if the tag supports
|
|
||||||
# it, the standalone content check happens. This check ensures only
|
|
||||||
# whitespace is after the last newline up to the tag, and only whitespace
|
|
||||||
# is after the tag up to the next newline. If that is the case, remove
|
|
||||||
# whitespace and the trailing newline. By setting this to $'\n', we're
|
|
||||||
# saying we are at the beginning of content.
|
|
||||||
MO_STANDALONE_CONTENT=$'\n'
|
|
||||||
MO_CURRENT=
|
|
||||||
mo::content moContent "${moFiles[@]}" || return 1
|
mo::content moContent "${moFiles[@]}" || return 1
|
||||||
mo::parse moParsed "$moContent"
|
mo::parse moParsed "$moContent"
|
||||||
echo -n "$moParsed"
|
echo -n "$moParsed"
|
||||||
@ -205,6 +223,49 @@ mo::debug() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Internal: Show a debug message and internal state information
|
||||||
|
#
|
||||||
|
# No arguments
|
||||||
|
#
|
||||||
|
# Returns nothing.
|
||||||
|
mo::debugShowState() {
|
||||||
|
if [[ -z "${MO_DEBUG:-}" ]]; then
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
local moState moTemp moIndex moDots
|
||||||
|
|
||||||
|
mo::escape moTemp "$MO_OPEN_DELIMITER"
|
||||||
|
moState="open: $moTemp"
|
||||||
|
mo::escape moTemp "$MO_CLOSE_DELIMITER"
|
||||||
|
moState="$moState close: $moTemp"
|
||||||
|
mo::escape moTemp "$MO_STANDALONE_CONTENT"
|
||||||
|
moState="$moState standalone: $moTemp"
|
||||||
|
mo::escape moTemp "$MO_CURRENT"
|
||||||
|
moState="$moState current: $moTemp"
|
||||||
|
moIndex=$((${#MO_PARSED} - 20))
|
||||||
|
moDots=...
|
||||||
|
|
||||||
|
if [[ "$moIndex" -lt 0 ]]; then
|
||||||
|
moIndex=0
|
||||||
|
moDots=
|
||||||
|
fi
|
||||||
|
|
||||||
|
mo::escape moTemp "${MO_PARSED:$moIndex}"
|
||||||
|
moState="$moState parsed: $moDots$moTemp"
|
||||||
|
|
||||||
|
moDots=...
|
||||||
|
|
||||||
|
if [[ "${#MO_UNPARSED}" -le 20 ]]; then
|
||||||
|
moDots=
|
||||||
|
fi
|
||||||
|
|
||||||
|
mo::escape moTemp "${MO_UNPARSED:0:20}$moDots"
|
||||||
|
moState="$moState unparsed: $moTemp"
|
||||||
|
|
||||||
|
echo "DEBUG ${FUNCNAME[1]:-?} - $moState" >&2
|
||||||
|
}
|
||||||
|
|
||||||
# Internal: Show an error message and exit
|
# Internal: Show an error message and exit
|
||||||
#
|
#
|
||||||
# $1 - The error message to show
|
# $1 - The error message to show
|
||||||
@ -291,10 +352,10 @@ mo::content() {
|
|||||||
mo::contentFile() {
|
mo::contentFile() {
|
||||||
local moFile moResult moContent
|
local moFile moResult moContent
|
||||||
|
|
||||||
# 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. Reading from
|
#: a dot to the content to preserve all newlines. Reading from
|
||||||
# stdin with a `read` loop does not work as expected, so `cat`
|
#: stdin with a `read` loop does not work as expected, so `cat`
|
||||||
# needs to stay.
|
#: needs to stay.
|
||||||
moFile=${2:-/dev/stdin}
|
moFile=${2:-/dev/stdin}
|
||||||
|
|
||||||
if [[ -e "$moFile" ]]; then
|
if [[ -e "$moFile" ]]; then
|
||||||
@ -306,7 +367,7 @@ mo::contentFile() {
|
|||||||
echo -n '.'
|
echo -n '.'
|
||||||
exit "$moResult"
|
exit "$moResult"
|
||||||
) || return 1
|
) || return 1
|
||||||
moContent=${moContent%.} # Remove last dot
|
moContent=${moContent%.} #: Remove last dot
|
||||||
elif [[ -n "${MO_FAIL_ON_FILE-}" ]]; then
|
elif [[ -n "${MO_FAIL_ON_FILE-}" ]]; then
|
||||||
mo::error "No such file: $moFile"
|
mo::error "No such file: $moFile"
|
||||||
else
|
else
|
||||||
@ -356,10 +417,10 @@ mo::indirect() {
|
|||||||
mo::indirectArray() {
|
mo::indirectArray() {
|
||||||
unset -v "$1"
|
unset -v "$1"
|
||||||
|
|
||||||
# IFS must be set to a string containing space or unset in order for
|
#: IFS must be set to a string containing space or unset in order for
|
||||||
# the array slicing to work regardless of the current IFS setting on
|
#: the array slicing to work regardless of the current IFS setting on
|
||||||
# bash 3. This is detailed further at
|
#: bash 3. This is detailed further at
|
||||||
# https://github.com/fidian/gg-core/pull/7
|
#: https://github.com/fidian/gg-core/pull/7
|
||||||
eval "$(printf "IFS= %s=(\"\${@:2}\") IFS=%q" "$1" "$IFS")"
|
eval "$(printf "IFS= %s=(\"\${@:2}\") IFS=%q" "$1" "$IFS")"
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -412,21 +473,43 @@ mo::chomp() {
|
|||||||
#
|
#
|
||||||
# $1 - Destination variable name - where to store the finished content
|
# $1 - Destination variable name - where to store the finished content
|
||||||
# $2 - Content to parse
|
# $2 - Content to parse
|
||||||
|
# $3 - Preserve standalone status/content - truthy if not empty. When set to a
|
||||||
|
# value, that becomes the standalone content value
|
||||||
#
|
#
|
||||||
# Returns nothing.
|
# Returns nothing.
|
||||||
mo::parse() {
|
mo::parse() {
|
||||||
local moOldParsed moOldUnparsed moResult
|
local moOldParsed moOldStandaloneContent moOldUnparsed moResult
|
||||||
|
|
||||||
|
#: The standalone content is a trick to make the standalone tag detection
|
||||||
|
#: possible. When it's set to content with a newline and if the tag supports
|
||||||
|
#: it, the standalone content check happens. This check ensures only
|
||||||
|
#: whitespace is after the last newline up to the tag, and only whitespace
|
||||||
|
#: is after the tag up to the next newline. If that is the case, remove
|
||||||
|
#: whitespace and the trailing newline. By setting this to $'\n', we're
|
||||||
|
#: saying we are at the beginning of content.
|
||||||
mo::debug "Starting parse of ${#2} bytes"
|
mo::debug "Starting parse of ${#2} bytes"
|
||||||
moOldParsed=${MO_PARSED:-}
|
moOldParsed=${MO_PARSED:-}
|
||||||
moOldUnparsed=${MO_UNPARSED:-}
|
moOldUnparsed=${MO_UNPARSED:-}
|
||||||
MO_PARSED=""
|
MO_PARSED=""
|
||||||
MO_UNPARSED="$2"
|
MO_UNPARSED="$2"
|
||||||
|
|
||||||
|
if [[ -z "${3:-}" ]]; then
|
||||||
|
moOldStandaloneContent=${MO_STANDALONE_CONTENT:-}
|
||||||
|
MO_STANDALONE_CONTENT=$'\n'
|
||||||
|
else
|
||||||
|
MO_STANDALONE_CONTENT=$3
|
||||||
|
fi
|
||||||
|
|
||||||
|
MO_CURRENT=${MO_CURRENT:-}
|
||||||
mo::parseInternal
|
mo::parseInternal
|
||||||
moResult="$MO_PARSED$MO_UNPARSED"
|
moResult="$MO_PARSED$MO_UNPARSED"
|
||||||
MO_PARSED=$moOldParsed
|
MO_PARSED=$moOldParsed
|
||||||
MO_UNPARSED=$moOldUnparsed
|
MO_UNPARSED=$moOldUnparsed
|
||||||
|
|
||||||
|
if [[ -z "${3:-}" ]]; then
|
||||||
|
MO_STANDALONE_CONTENT=$moOldStandaloneContent
|
||||||
|
fi
|
||||||
|
|
||||||
local "$1" && mo::indirect "$1" "$moResult"
|
local "$1" && mo::indirect "$1" "$moResult"
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -440,9 +523,10 @@ mo::parse() {
|
|||||||
mo::parseInternal() {
|
mo::parseInternal() {
|
||||||
local moChunk
|
local moChunk
|
||||||
|
|
||||||
mo::debug "Starting parse, current: $MO_CURRENT"
|
mo::debug "Starting parse"
|
||||||
|
|
||||||
while [[ -n "$MO_UNPARSED" ]]; do
|
while [[ -n "$MO_UNPARSED" ]]; do
|
||||||
|
mo::debugShowState
|
||||||
moChunk=${MO_UNPARSED%%"$MO_OPEN_DELIMITER"*}
|
moChunk=${MO_UNPARSED%%"$MO_OPEN_DELIMITER"*}
|
||||||
MO_PARSED="$MO_PARSED$moChunk"
|
MO_PARSED="$MO_PARSED$moChunk"
|
||||||
MO_STANDALONE_CONTENT="$MO_STANDALONE_CONTENT$moChunk"
|
MO_STANDALONE_CONTENT="$MO_STANDALONE_CONTENT$moChunk"
|
||||||
@ -454,46 +538,46 @@ mo::parseInternal() {
|
|||||||
|
|
||||||
case "$MO_UNPARSED" in
|
case "$MO_UNPARSED" in
|
||||||
'#'*)
|
'#'*)
|
||||||
# Loop, if/then, or pass content through function
|
#: Loop, if/then, or pass content through function
|
||||||
mo::parseBlock false
|
mo::parseBlock false
|
||||||
;;
|
;;
|
||||||
|
|
||||||
'^'*)
|
'^'*)
|
||||||
# Display section if named thing does not exist
|
#: Display section if named thing does not exist
|
||||||
mo::parseBlock true
|
mo::parseBlock true
|
||||||
;;
|
;;
|
||||||
|
|
||||||
'>'*)
|
'>'*)
|
||||||
# Load partial - get name of file relative to cwd
|
#: Load partial - get name of file relative to cwd
|
||||||
mo::parsePartial
|
mo::parsePartial
|
||||||
;;
|
;;
|
||||||
|
|
||||||
'/'*)
|
'/'*)
|
||||||
# Closing tag
|
#: Closing tag
|
||||||
mo::errorNear "Unbalanced close tag" "$MO_UNPARSED"
|
mo::errorNear "Unbalanced close tag" "$MO_UNPARSED"
|
||||||
;;
|
;;
|
||||||
|
|
||||||
'!'*)
|
'!'*)
|
||||||
# Comment - ignore the tag content entirely
|
#: Comment - ignore the tag content entirely
|
||||||
mo::parseComment
|
mo::parseComment
|
||||||
;;
|
;;
|
||||||
|
|
||||||
'='*)
|
'='*)
|
||||||
# Change delimiters
|
#: Change delimiters
|
||||||
# Any two non-whitespace sequences separated by whitespace.
|
#: Any two non-whitespace sequences separated by whitespace.
|
||||||
mo::parseDelimiter
|
mo::parseDelimiter
|
||||||
;;
|
;;
|
||||||
|
|
||||||
'&'*)
|
'&'*)
|
||||||
# Unescaped - mo doesn't escape/unescape
|
#: Unescaped - mo doesn't escape/unescape
|
||||||
MO_UNPARSED=${MO_UNPARSED#&}
|
MO_UNPARSED=${MO_UNPARSED#&}
|
||||||
mo::trimUnparsed
|
mo::trimUnparsed
|
||||||
mo::parseValue
|
mo::parseValue
|
||||||
;;
|
;;
|
||||||
|
|
||||||
*)
|
*)
|
||||||
# Normal environment variable, string, subexpression,
|
#: Normal environment variable, string, subexpression,
|
||||||
# current value, key, or function call
|
#: current value, key, or function call
|
||||||
mo::parseValue
|
mo::parseValue
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
@ -546,8 +630,8 @@ mo::parseBlockFunction() {
|
|||||||
moTokens=(${@+"$@"})
|
moTokens=(${@+"$@"})
|
||||||
mo::debug "Parsing block function: $moTokensString"
|
mo::debug "Parsing block function: $moTokensString"
|
||||||
mo::getContentUntilClose moTemp "$moTokensString"
|
mo::getContentUntilClose moTemp "$moTokensString"
|
||||||
# Pass unparsed content to the function.
|
#: Pass unparsed content to the function.
|
||||||
# Keep the updated delimiters if they changed.
|
#: Keep the updated delimiters if they changed.
|
||||||
|
|
||||||
if [[ "$moInvertBlock" != "true" ]]; then
|
if [[ "$moInvertBlock" != "true" ]]; then
|
||||||
mo::evaluateFunction moResult "$moTemp" "${moTokens[@]:1}"
|
mo::evaluateFunction moResult "$moTemp" "${moTokens[@]:1}"
|
||||||
@ -581,30 +665,30 @@ mo::parseBlockArray() {
|
|||||||
eval "moArrayIndexes=(\"\${!${moArrayName}[@]}\")"
|
eval "moArrayIndexes=(\"\${!${moArrayName}[@]}\")"
|
||||||
|
|
||||||
if [[ "${#moArrayIndexes[@]}" -lt 1 ]]; then
|
if [[ "${#moArrayIndexes[@]}" -lt 1 ]]; then
|
||||||
# No elements
|
#: No elements
|
||||||
if [[ "$moInvertBlock" == "true" ]]; then
|
if [[ "$moInvertBlock" == "true" ]]; then
|
||||||
# Restore the delimiter before parsing
|
#: Restore the delimiter before parsing
|
||||||
MO_OPEN_DELIMITER=$moOpenDelimiterBefore
|
MO_OPEN_DELIMITER=$moOpenDelimiterBefore
|
||||||
MO_CLOSE_DELIMITER=$moCloseDelimiterBefore
|
MO_CLOSE_DELIMITER=$moCloseDelimiterBefore
|
||||||
moCurrent=$MO_CURRENT
|
moCurrent=$MO_CURRENT
|
||||||
MO_CURRENT=$moArrayName
|
MO_CURRENT=$moArrayName
|
||||||
mo::parse moParsed "$moTemp"
|
mo::parse moParsed "$moTemp" "blockArrayInvert$MO_STANDALONE_CONTENT"
|
||||||
MO_CURRENT=$moCurrent
|
MO_CURRENT=$moCurrent
|
||||||
MO_PARSED="$MO_PARSED$moParsed"
|
MO_PARSED="$MO_PARSED$moParsed"
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
if [[ "$moInvertBlock" != "true" ]]; then
|
if [[ "$moInvertBlock" != "true" ]]; then
|
||||||
# Process for each element in the array
|
#: Process for each element in the array
|
||||||
moUnparsed=$MO_UNPARSED
|
moUnparsed=$MO_UNPARSED
|
||||||
|
|
||||||
for moArrayIndex in "${moArrayIndexes[@]}"; do
|
for moArrayIndex in "${moArrayIndexes[@]}"; do
|
||||||
# Restore the delimiter before parsing
|
#: Restore the delimiter before parsing
|
||||||
MO_OPEN_DELIMITER=$moOpenDelimiterBefore
|
MO_OPEN_DELIMITER=$moOpenDelimiterBefore
|
||||||
MO_CLOSE_DELIMITER=$moCloseDelimiterBefore
|
MO_CLOSE_DELIMITER=$moCloseDelimiterBefore
|
||||||
moCurrent=$MO_CURRENT
|
moCurrent=$MO_CURRENT
|
||||||
MO_CURRENT=$moArrayName.$moArrayIndex
|
MO_CURRENT=$moArrayName.$moArrayIndex
|
||||||
mo::debug "Iterate over array using element: $MO_CURRENT"
|
mo::debug "Iterate over array using element: $MO_CURRENT"
|
||||||
mo::parse moParsed "$moTemp" "$moArrayName"
|
mo::parse moParsed "$moTemp" "blockArray$MO_STANDALONE_CONTENT"
|
||||||
MO_CURRENT=$moCurrent
|
MO_CURRENT=$moCurrent
|
||||||
MO_PARSED="$MO_PARSED$moParsed"
|
MO_PARSED="$MO_PARSED$moParsed"
|
||||||
done
|
done
|
||||||
@ -639,17 +723,17 @@ mo::parseBlockValue() {
|
|||||||
moOpenDelimiterAfter=$MO_OPEN_DELIMITER
|
moOpenDelimiterAfter=$MO_OPEN_DELIMITER
|
||||||
moCloseDelimiterAfter=$MO_CLOSE_DELIMITER
|
moCloseDelimiterAfter=$MO_CLOSE_DELIMITER
|
||||||
|
|
||||||
# Variable, value, or list of mixed things
|
#: Variable, value, or list of mixed things
|
||||||
mo::evaluateListOfSingles moResult "${moTokens[@]}"
|
mo::evaluateListOfSingles moResult "${moTokens[@]}"
|
||||||
|
|
||||||
if mo::isTruthy "$moResult" "$moInvertBlock"; then
|
if mo::isTruthy "$moResult" "$moInvertBlock"; then
|
||||||
mo::debug "Block is truthy: $moResult"
|
mo::debug "Block is truthy: $moResult"
|
||||||
# Restore the delimiter before parsing
|
#: Restore the delimiter before parsing
|
||||||
MO_OPEN_DELIMITER=$moOpenDelimiterBefore
|
MO_OPEN_DELIMITER=$moOpenDelimiterBefore
|
||||||
MO_CLOSE_DELIMITER=$moCloseDelimiterBefore
|
MO_CLOSE_DELIMITER=$moCloseDelimiterBefore
|
||||||
moCurrent=$MO_CURRENT
|
moCurrent=$MO_CURRENT
|
||||||
MO_CURRENT=${moTokens[1]}
|
MO_CURRENT=${moTokens[1]}
|
||||||
mo::parse moParsed "$moTemp"
|
mo::parse moParsed "$moTemp" "blockValue$MO_STANDALONE_CONTENT"
|
||||||
MO_PARSED="$MO_PARSED$moParsed"
|
MO_PARSED="$MO_PARSED$moParsed"
|
||||||
MO_CURRENT=$moCurrent
|
MO_CURRENT=$moCurrent
|
||||||
fi
|
fi
|
||||||
@ -697,10 +781,10 @@ mo::parsePartial() {
|
|||||||
|
|
||||||
mo::debug "Parsing partial: $moFilename"
|
mo::debug "Parsing partial: $moFilename"
|
||||||
|
|
||||||
# Execute in subshell to preserve current cwd and environment
|
#: Execute in subshell to preserve current cwd and environment
|
||||||
moResult=$(
|
moResult=$(
|
||||||
# It would be nice to remove `dirname` and use a function instead,
|
#: It would be nice to remove `dirname` and use a function instead,
|
||||||
# but that is difficult when only given filenames.
|
#: but that is difficult when only given filenames.
|
||||||
cd "$(dirname -- "$moFilename")" || exit 1
|
cd "$(dirname -- "$moFilename")" || exit 1
|
||||||
echo "$(
|
echo "$(
|
||||||
local moPartialContent moPartialParsed
|
local moPartialContent moPartialParsed
|
||||||
@ -709,15 +793,13 @@ mo::parsePartial() {
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
#: Reset delimiters before parsing
|
||||||
mo::indentLines moPartialContent "$moIndentation" "$moPartialContent"
|
mo::indentLines moPartialContent "$moIndentation" "$moPartialContent"
|
||||||
|
MO_OPEN_DELIMITER="$MO_OPEN_DELIMITER_DEFAULT"
|
||||||
# Delimiters are reset when loading a new partial
|
MO_CLOSE_DELIMITER="$MO_CLOSE_DELIMITER_DEFAULT"
|
||||||
MO_OPEN_DELIMITER="{{"
|
|
||||||
MO_CLOSE_DELIMITER="}}"
|
|
||||||
MO_STANDALONE_CONTENT=$'\n'
|
|
||||||
mo::parse moPartialParsed "$moPartialContent"
|
mo::parse moPartialParsed "$moPartialContent"
|
||||||
|
|
||||||
# Fix bash handling of subshells and keep trailing whitespace.
|
#: Fix bash handling of subshells and keep trailing whitespace.
|
||||||
echo -n "$moPartialParsed."
|
echo -n "$moPartialParsed."
|
||||||
)" || exit 1
|
)" || exit 1
|
||||||
) || exit 1
|
) || exit 1
|
||||||
@ -733,6 +815,8 @@ mo::parsePartial() {
|
|||||||
|
|
||||||
# Internal: Handle parsing a comment
|
# Internal: Handle parsing a comment
|
||||||
#
|
#
|
||||||
|
# No arguments.
|
||||||
|
#
|
||||||
# Returns nothing
|
# Returns nothing
|
||||||
mo::parseComment() {
|
mo::parseComment() {
|
||||||
local moContent moContent
|
local moContent moContent
|
||||||
@ -748,6 +832,8 @@ mo::parseComment() {
|
|||||||
|
|
||||||
# Internal: Handle parsing the change of delimiters
|
# Internal: Handle parsing the change of delimiters
|
||||||
#
|
#
|
||||||
|
# No arguments.
|
||||||
|
#
|
||||||
# Returns nothing
|
# Returns nothing
|
||||||
mo::parseDelimiter() {
|
mo::parseDelimiter() {
|
||||||
local moContent moOpen moClose
|
local moContent moOpen moClose
|
||||||
@ -787,6 +873,10 @@ mo::parseValue() {
|
|||||||
mo::errorNear "Did not find closing tag" "$moUnparsedOriginal"
|
mo::errorNear "Did not find closing tag" "$moUnparsedOriginal"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if mo::standaloneCheck; then
|
||||||
|
mo::standaloneProcess
|
||||||
|
fi
|
||||||
|
|
||||||
MO_UNPARSED=${MO_UNPARSED:${#MO_CLOSE_DELIMITER}}
|
MO_UNPARSED=${MO_UNPARSED:${#MO_CLOSE_DELIMITER}}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -837,7 +927,7 @@ mo::isFunction() {
|
|||||||
#
|
#
|
||||||
# Returns 0 if the name is not empty, 1 otherwise.
|
# Returns 0 if the name is not empty, 1 otherwise.
|
||||||
mo::isArray() {
|
mo::isArray() {
|
||||||
# Namespace this variable so we don't conflict with what we're testing.
|
#: Namespace this variable so we don't conflict with what we're testing.
|
||||||
local moTestResult
|
local moTestResult
|
||||||
|
|
||||||
moTestResult=$(declare -p "$1" 2>/dev/null) || return 1
|
moTestResult=$(declare -p "$1" 2>/dev/null) || return 1
|
||||||
@ -864,15 +954,15 @@ mo::isArrayIndexValid() {
|
|||||||
moTest=""
|
moTest=""
|
||||||
|
|
||||||
if [[ "${moDeclare:0:10}" == "declare -a" ]]; then
|
if [[ "${moDeclare:0:10}" == "declare -a" ]]; then
|
||||||
# Numerically indexed array - must check if the index looks like a
|
#: Numerically indexed array - must check if the index looks like a
|
||||||
# number because using a string to index a numerically indexed array
|
#: number because using a string to index a numerically indexed array
|
||||||
# will appear like it worked.
|
#: will appear like it worked.
|
||||||
if [[ "$2" == "0" ]] || [[ "$2" =~ ^[1-9][0-9]*$ ]]; then
|
if [[ "$2" == "0" ]] || [[ "$2" =~ ^[1-9][0-9]*$ ]]; then
|
||||||
# Index looks like a number
|
#: Index looks like a number
|
||||||
eval "moTest=\"\${$1[$2]+ok}\""
|
eval "moTest=\"\${$1[$2]+ok}\""
|
||||||
fi
|
fi
|
||||||
elif [[ "${moDeclare:0:10}" == "declare -A" ]]; then
|
elif [[ "${moDeclare:0:10}" == "declare -A" ]]; then
|
||||||
# Associative array
|
#: Associative array
|
||||||
eval "moTest=\"\${$1[$2]+ok}\""
|
eval "moTest=\"\${$1[$2]+ok}\""
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@ -919,12 +1009,12 @@ mo::isTruthy() {
|
|||||||
moTruthy=false
|
moTruthy=false
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# XOR the results
|
#: XOR the results
|
||||||
# moTruthy inverse desiredResult
|
#: moTruthy inverse desiredResult
|
||||||
# true false true
|
#: true false true
|
||||||
# true true false
|
#: true true false
|
||||||
# false false false
|
#: false false false
|
||||||
# false true true
|
#: false true true
|
||||||
if [[ "$moTruthy" == "$2" ]]; then
|
if [[ "$moTruthy" == "$2" ]]; then
|
||||||
mo::debug "Value is falsy, test result: $moTruthy inverse: $2"
|
mo::debug "Value is falsy, test result: $moTruthy inverse: $2"
|
||||||
return 1
|
return 1
|
||||||
@ -951,7 +1041,7 @@ mo::evaluate() {
|
|||||||
moTarget=$1
|
moTarget=$1
|
||||||
shift
|
shift
|
||||||
|
|
||||||
# Phase 1 - remove all command tokens (PAREN, BRACE)
|
#: Phase 1 - remove all command tokens (PAREN, BRACE)
|
||||||
moStack=()
|
moStack=()
|
||||||
|
|
||||||
while [[ $# -gt 0 ]]; do
|
while [[ $# -gt 0 ]]; do
|
||||||
@ -978,14 +1068,14 @@ mo::evaluate() {
|
|||||||
shift 2
|
shift 2
|
||||||
done
|
done
|
||||||
|
|
||||||
# Phase 2 - check if this is a function or if we should just concatenate values
|
#: Phase 2 - check if this is a function or if we should just concatenate values
|
||||||
if [[ "${moStack[0]:-}" == "NAME" ]] && mo::isFunction "${moStack[1]}"; then
|
if [[ "${moStack[0]:-}" == "NAME" ]] && mo::isFunction "${moStack[1]}"; then
|
||||||
# Special case - if the first argument is a function, then the rest are
|
#: Special case - if the first argument is a function, then the rest are
|
||||||
# passed to the function.
|
#: passed to the function.
|
||||||
mo::debug "Evaluating function: ${moStack[1]}"
|
mo::debug "Evaluating function: ${moStack[1]}"
|
||||||
mo::evaluateFunction moResult "" "${moStack[@]:1}"
|
mo::evaluateFunction moResult "" "${moStack[@]:1}"
|
||||||
else
|
else
|
||||||
# Concatenate
|
#: Concatenate
|
||||||
mo::debug "Concatenating ${#moStack[@]} stack items"
|
mo::debug "Concatenating ${#moStack[@]} stack items"
|
||||||
mo::evaluateListOfSingles moResult "${moStack[@]}"
|
mo::evaluateListOfSingles moResult "${moStack[@]}"
|
||||||
fi
|
fi
|
||||||
@ -1228,8 +1318,8 @@ mo::evaluateFunction() {
|
|||||||
|
|
||||||
mo::debug "Calling function: $moFunctionCall"
|
mo::debug "Calling function: $moFunctionCall"
|
||||||
|
|
||||||
# Call the function in a subshell for safety. Employ the trick to preserve
|
#: Call the function in a subshell for safety. Employ the trick to preserve
|
||||||
# whitespace at the end of the output.
|
#: whitespace at the end of the output.
|
||||||
moContent=$(
|
moContent=$(
|
||||||
export MO_FUNCTION_ARGS=("${moArgs[@]}")
|
export MO_FUNCTION_ARGS=("${moArgs[@]}")
|
||||||
echo -n "$moContent" | eval "$moFunctionCall ; moFunctionResult=\$? ; echo -n '.' ; exit \"\$moFunctionResult\""
|
echo -n "$moContent" | eval "$moFunctionCall ; moFunctionResult=\$? ; echo -n '.' ; exit \"\$moFunctionResult\""
|
||||||
@ -1258,10 +1348,10 @@ mo::standaloneCheck() {
|
|||||||
moR=$'\r'
|
moR=$'\r'
|
||||||
moT=$'\t'
|
moT=$'\t'
|
||||||
|
|
||||||
# Check the content before
|
#: Check the content before
|
||||||
moContent=${MO_STANDALONE_CONTENT//"$moR"/"$moN"}
|
moContent=${MO_STANDALONE_CONTENT//"$moR"/"$moN"}
|
||||||
|
|
||||||
# By default, signal to the next check that this one failed
|
#: By default, signal to the next check that this one failed
|
||||||
MO_STANDALONE_CONTENT=""
|
MO_STANDALONE_CONTENT=""
|
||||||
|
|
||||||
if [[ "$moContent" != *"$moN"* ]]; then
|
if [[ "$moContent" != *"$moN"* ]]; then
|
||||||
@ -1280,7 +1370,7 @@ mo::standaloneCheck() {
|
|||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check the content after
|
#: Check the content after
|
||||||
moContent=${MO_UNPARSED//"$moR"/"$moN"}
|
moContent=${MO_UNPARSED//"$moR"/"$moN"}
|
||||||
moContent=${moContent%%"$moN"*}
|
moContent=${moContent%%"$moN"*}
|
||||||
moContent=${moContent//"$moT"/}
|
moContent=${moContent//"$moT"/}
|
||||||
@ -1292,7 +1382,7 @@ mo::standaloneCheck() {
|
|||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Signal to the next check that this tag removed content
|
#: Signal to the next check that this tag removed content
|
||||||
MO_STANDALONE_CONTENT=$'\n'
|
MO_STANDALONE_CONTENT=$'\n'
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
@ -1412,7 +1502,7 @@ mo::escape() {
|
|||||||
#
|
#
|
||||||
# Returns nothing.
|
# Returns nothing.
|
||||||
mo::getContentUntilClose() {
|
mo::getContentUntilClose() {
|
||||||
local moChunk moResult moTemp moTokensString moTokens moTarget moTagStack moResultTemp moStandaloneTemp
|
local moChunk moResult moTemp moTokensString moTokens moTarget moTagStack moResultTemp
|
||||||
|
|
||||||
moTarget=$1
|
moTarget=$1
|
||||||
moTagStack=("$2")
|
moTagStack=("$2")
|
||||||
@ -1433,7 +1523,7 @@ mo::getContentUntilClose() {
|
|||||||
|
|
||||||
case "$MO_UNPARSED" in
|
case "$MO_UNPARSED" in
|
||||||
'#'*)
|
'#'*)
|
||||||
# Increase block
|
#: Increase block
|
||||||
moResultTemp="$moResultTemp${MO_UNPARSED:0:1}"
|
moResultTemp="$moResultTemp${MO_UNPARSED:0:1}"
|
||||||
MO_UNPARSED=${MO_UNPARSED:1}
|
MO_UNPARSED=${MO_UNPARSED:1}
|
||||||
mo::getContentTrim moTemp
|
mo::getContentTrim moTemp
|
||||||
@ -1443,7 +1533,7 @@ mo::getContentUntilClose() {
|
|||||||
;;
|
;;
|
||||||
|
|
||||||
'^'*)
|
'^'*)
|
||||||
# Increase block
|
#: Increase block
|
||||||
moResultTemp="$moResultTemp${MO_UNPARSED:0:1}"
|
moResultTemp="$moResultTemp${MO_UNPARSED:0:1}"
|
||||||
MO_UNPARSED=${MO_UNPARSED:1}
|
MO_UNPARSED=${MO_UNPARSED:1}
|
||||||
mo::getContentTrim moTemp
|
mo::getContentTrim moTemp
|
||||||
@ -1453,7 +1543,7 @@ mo::getContentUntilClose() {
|
|||||||
;;
|
;;
|
||||||
|
|
||||||
'>'*)
|
'>'*)
|
||||||
# Partial - ignore
|
#: Partial - ignore
|
||||||
moResultTemp="$moResultTemp${MO_UNPARSED:0:1}"
|
moResultTemp="$moResultTemp${MO_UNPARSED:0:1}"
|
||||||
MO_UNPARSED=${MO_UNPARSED:1}
|
MO_UNPARSED=${MO_UNPARSED:1}
|
||||||
mo::getContentTrim moTemp
|
mo::getContentTrim moTemp
|
||||||
@ -1462,7 +1552,7 @@ mo::getContentUntilClose() {
|
|||||||
;;
|
;;
|
||||||
|
|
||||||
'/'*)
|
'/'*)
|
||||||
# Decrease block
|
#: Decrease block
|
||||||
moResultTemp="$moResultTemp${MO_UNPARSED:0:1}"
|
moResultTemp="$moResultTemp${MO_UNPARSED:0:1}"
|
||||||
MO_UNPARSED=${MO_UNPARSED:1}
|
MO_UNPARSED=${MO_UNPARSED:1}
|
||||||
mo::getContentTrim moTemp
|
mo::getContentTrim moTemp
|
||||||
@ -1473,7 +1563,7 @@ mo::getContentUntilClose() {
|
|||||||
moTagStack=("${moTagStack[@]:1}")
|
moTagStack=("${moTagStack[@]:1}")
|
||||||
|
|
||||||
if [[ "${#moTagStack[@]}" -eq 0 ]]; then
|
if [[ "${#moTagStack[@]}" -eq 0 ]]; then
|
||||||
# Erase all portions of the close tag
|
#: Erase all portions of the close tag
|
||||||
moResultTemp=""
|
moResultTemp=""
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
@ -1482,19 +1572,19 @@ mo::getContentUntilClose() {
|
|||||||
;;
|
;;
|
||||||
|
|
||||||
'!'*)
|
'!'*)
|
||||||
# Comment - ignore
|
#: Comment - ignore
|
||||||
mo::getContentComment moTemp
|
mo::getContentComment moTemp
|
||||||
moResultTemp="$moResultTemp$moTemp"
|
moResultTemp="$moResultTemp$moTemp"
|
||||||
;;
|
;;
|
||||||
|
|
||||||
'='*)
|
'='*)
|
||||||
# Change delimiters
|
#: Change delimiters
|
||||||
mo::getContentDelimiter moTemp
|
mo::getContentDelimiter moTemp
|
||||||
moResultTemp="$moResultTemp$moTemp"
|
moResultTemp="$moResultTemp$moTemp"
|
||||||
;;
|
;;
|
||||||
|
|
||||||
'&'*)
|
'&'*)
|
||||||
# Unescaped - bypass one then ignore
|
#: Unescaped - bypass one then ignore
|
||||||
moResultTemp="$moResultTemp${MO_UNPARSED:0:1}"
|
moResultTemp="$moResultTemp${MO_UNPARSED:0:1}"
|
||||||
MO_UNPARSED=${MO_UNPARSED:1}
|
MO_UNPARSED=${MO_UNPARSED:1}
|
||||||
mo::getContentTrim moTemp
|
mo::getContentTrim moTemp
|
||||||
@ -1504,7 +1594,7 @@ mo::getContentUntilClose() {
|
|||||||
;;
|
;;
|
||||||
|
|
||||||
*)
|
*)
|
||||||
# Normal variable - ignore
|
#: Normal variable - ignore
|
||||||
mo::getContentWithinTag moTemp "$MO_CLOSE_DELIMITER"
|
mo::getContentWithinTag moTemp "$MO_CLOSE_DELIMITER"
|
||||||
moResultTemp="$moResultTemp${moTemp[0]}"
|
moResultTemp="$moResultTemp${moTemp[0]}"
|
||||||
;;
|
;;
|
||||||
@ -1682,7 +1772,7 @@ mo::tokenizeTagContents() {
|
|||||||
;;
|
;;
|
||||||
|
|
||||||
'('*)
|
'('*)
|
||||||
# Do not tokenize the open paren - treat this as RPL
|
#: Do not tokenize the open paren - treat this as RPL
|
||||||
MO_UNPARSED=${MO_UNPARSED:1}
|
MO_UNPARSED=${MO_UNPARSED:1}
|
||||||
mo::tokenizeTagContents moTemp ')'
|
mo::tokenizeTagContents moTemp ')'
|
||||||
moResult=("${moResult[@]}" "${moTemp[@]:1}" PAREN "${moTemp[0]}")
|
moResult=("${moResult[@]}" "${moTemp[@]:1}" PAREN "${moTemp[0]}")
|
||||||
@ -1690,7 +1780,7 @@ mo::tokenizeTagContents() {
|
|||||||
;;
|
;;
|
||||||
|
|
||||||
'{'*)
|
'{'*)
|
||||||
# Do not tokenize the open brace - treat this as RPL
|
#: Do not tokenize the open brace - treat this as RPL
|
||||||
MO_UNPARSED=${MO_UNPARSED:1}
|
MO_UNPARSED=${MO_UNPARSED:1}
|
||||||
mo::tokenizeTagContents moTemp '}'
|
mo::tokenizeTagContents moTemp '}'
|
||||||
moResult=("${moResult[@]}" "${moTemp[@]:1}" BRACE "${moTemp[0]}")
|
moResult=("${moResult[@]}" "${moTemp[@]:1}" BRACE "${moTemp[0]}")
|
||||||
@ -1776,7 +1866,7 @@ mo::tokenizeTagContentsDoubleQuote() {
|
|||||||
;;
|
;;
|
||||||
|
|
||||||
\\e*)
|
\\e*)
|
||||||
# Note, \e is ESC, but in Bash $'\E' is ESC.
|
#: Note, \e is ESC, but in Bash $'\E' is ESC.
|
||||||
moResult="$moResult"$'\E'
|
moResult="$moResult"$'\E'
|
||||||
MO_UNPARSED=${MO_UNPARSED:2}
|
MO_UNPARSED=${MO_UNPARSED:2}
|
||||||
;;
|
;;
|
||||||
|
96
run-spec.js
96
run-spec.js
@ -9,7 +9,8 @@ const fsPromises = require("fs").promises;
|
|||||||
// To skip a test, define a "skip" property and explain why the test is
|
// To skip a test, define a "skip" property and explain why the test is
|
||||||
// skipped.
|
// skipped.
|
||||||
//
|
//
|
||||||
// To override any test property, just define that property.
|
// To override any test property, just define that property. It replaces the
|
||||||
|
// original property, not augmenting it.
|
||||||
const testOverrides = {
|
const testOverrides = {
|
||||||
"Comments -> Variable Name Collision": {
|
"Comments -> Variable Name Collision": {
|
||||||
// Can't use variables with exclamation points easily
|
// Can't use variables with exclamation points easily
|
||||||
@ -17,15 +18,67 @@ const testOverrides = {
|
|||||||
comment: 4
|
comment: 4
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Interpolation -> Dotted Names - Arbitrary Depth": {
|
||||||
|
skip: "Not able to use more than one level of depth"
|
||||||
|
},
|
||||||
|
"Interpolation -> Dotted Names - Broken Chain Resolution": {
|
||||||
|
data: {
|
||||||
|
a: {
|
||||||
|
b: "wrong"
|
||||||
|
},
|
||||||
|
name: "Jim"
|
||||||
|
},
|
||||||
|
template: '"{{a.name}}" == ""'
|
||||||
|
},
|
||||||
|
"Interpolation -> Dotted Names - Initial Resolution": {
|
||||||
|
data: {
|
||||||
|
a: {
|
||||||
|
name: "Phil"
|
||||||
|
},
|
||||||
|
name: "Wrong"
|
||||||
|
},
|
||||||
|
template: "\"{{#a}}{{name}}{{/a}}\" == \"Phil\""
|
||||||
|
},
|
||||||
|
"Interpolation -> Implicit Iterators - Ampersand": {
|
||||||
|
skip: "HTML escaping is not supported"
|
||||||
|
},
|
||||||
|
"Interpolation -> Implicit Iterators - Basic Interpolation": {
|
||||||
|
skip: "Can not use {{.}} outside of a loop. Need to use a variable name."
|
||||||
|
},
|
||||||
|
"Interpolation -> Implicit Iterators - Basic Integer Interpolation": {
|
||||||
|
skip: "Can not use {{.}} outside of a loop. Need to use a variable name."
|
||||||
|
},
|
||||||
|
"Interpolation -> Implicit Iterators - Triple Mustache": {
|
||||||
|
skip: "Can not use {{.}} outside of a loop. Need to use a variable name."
|
||||||
|
},
|
||||||
"Interpolation -> HTML Escaping": {
|
"Interpolation -> HTML Escaping": {
|
||||||
skip: "HTML escaping is not supported"
|
skip: "HTML escaping is not supported"
|
||||||
},
|
},
|
||||||
"Interpolation -> Implicit Iterators - HTML Escaping": {
|
"Interpolation -> Implicit Iterators - HTML Escaping": {
|
||||||
skip: "HTML escaping is not supported"
|
skip: "HTML escaping is not supported"
|
||||||
},
|
},
|
||||||
|
"Inverted -> Dotted Names - Falsey": {
|
||||||
|
data: {
|
||||||
|
a: {
|
||||||
|
b: ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
template: '"{{^a.b}}Not Here{{/a.b}}" == "Not Here"'
|
||||||
|
},
|
||||||
|
"Inverted -> Dotted Names - Truthy": {
|
||||||
|
data: {
|
||||||
|
a: {
|
||||||
|
b: "1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
template: '"{{^a.b}}Not Here{{/a.b}}" == ""'
|
||||||
|
},
|
||||||
"Lambdas -> Escaping": {
|
"Lambdas -> Escaping": {
|
||||||
skip: "HTML escaping is not supported"
|
skip: "HTML escaping is not supported"
|
||||||
},
|
},
|
||||||
|
"Lambdas -> Interpolation - Alternate Delimiters": {
|
||||||
|
skip: "There is no difference between a lamba used as a value and a lambda used as a block. Both will parse using the current delimiters."
|
||||||
|
},
|
||||||
"Lambdas -> Inverted Section": {
|
"Lambdas -> Inverted Section": {
|
||||||
// This one passed mostly by accident. Correcting so the test still
|
// This one passed mostly by accident. Correcting so the test still
|
||||||
// tests what is was designed to illustrate.
|
// tests what is was designed to illustrate.
|
||||||
@ -37,12 +90,51 @@ const testOverrides = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Lambdas -> Interpolation": {
|
||||||
|
data: {
|
||||||
|
lambda: {
|
||||||
|
__tag__: 'code',
|
||||||
|
bash: 'echo -n "world"'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Lambdas -> Interpolation - Expansion": {
|
||||||
|
data: {
|
||||||
|
lambda: {
|
||||||
|
__tag__: 'code',
|
||||||
|
bash: 'mo::parse result "{{planet}}"; echo -n "$result"'
|
||||||
|
},
|
||||||
|
planet: 'world'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Lambdas -> Interpolation - Multiple Calls": {
|
||||||
|
skip: "Calls are not cached, but they run in isolated environments, so saving a global variable does not work."
|
||||||
|
},
|
||||||
|
"Lambdas -> Section": {
|
||||||
|
data: {
|
||||||
|
lambda: {
|
||||||
|
__tag__: 'code',
|
||||||
|
bash: 'if [[ "$(cat)" == "{{x}}" ]]; then echo -n yes; else echo -n no; fi'
|
||||||
|
},
|
||||||
|
x: "Error!"
|
||||||
|
}
|
||||||
|
},
|
||||||
"Lambdas -> Section - Alternate Delimiters": {
|
"Lambdas -> Section - Alternate Delimiters": {
|
||||||
data: {
|
data: {
|
||||||
lambda: {
|
lambda: {
|
||||||
__tag__: 'code',
|
__tag__: 'code',
|
||||||
bash: 'content=$(cat); echo -n "$content{{planet}}=>|planet|$content"'
|
bash: 'local content=$(cat); mo::parse content "$content{{planet}} => |planet|$content"; echo -n "$content"'
|
||||||
|
},
|
||||||
|
planet: 'Earth'
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"Lambdas -> Section - Expansion": {
|
||||||
|
data: {
|
||||||
|
lambda: {
|
||||||
|
__tag__: 'code',
|
||||||
|
bash: 'local content=$(cat); mo::parse content "$content{{planet}}$content"; echo -n "$content"'
|
||||||
|
},
|
||||||
|
planet: "Earth"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Lambdas -> Section - Multiple Calls": {
|
"Lambdas -> Section - Multiple Calls": {
|
||||||
|
2
tests/fixtures/inline-indentation
vendored
Normal file
2
tests/fixtures/inline-indentation
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
>
|
||||||
|
>
|
14
tests/help
14
tests/help
@ -44,6 +44,10 @@ Options:
|
|||||||
-s=FILE, --source=FILE
|
-s=FILE, --source=FILE
|
||||||
Load FILE into the environment before processing templates.
|
Load FILE into the environment before processing templates.
|
||||||
Can be used multiple times.
|
Can be used multiple times.
|
||||||
|
-o=DELIM, --open=DELIM
|
||||||
|
Set the opening delimiter. Default is "{{".
|
||||||
|
-c=DELIM, --close=DELIM
|
||||||
|
Set the closing delimiter. Default is "}}".
|
||||||
-- Indicate the end of options. All arguments after this will be
|
-- Indicate the end of options. All arguments after this will be
|
||||||
treated as filenames only. Use when filenames may start with
|
treated as filenames only. Use when filenames may start with
|
||||||
hyphens.
|
hyphens.
|
||||||
@ -54,6 +58,9 @@ MO_ALLOW_FUNCTION_ARGUMENTS - When set to a non-empty value, this allows
|
|||||||
functions referenced in templates to receive additional options and
|
functions referenced in templates to receive additional options and
|
||||||
arguments.
|
arguments.
|
||||||
MO_CLOSE_DELIMITER - The string used when closing a tag. Defaults to "}}".
|
MO_CLOSE_DELIMITER - The string used when closing a tag. Defaults to "}}".
|
||||||
|
Used internally.
|
||||||
|
MO_CLOSE_DELIMITER_DEFAULT - The default value of MO_CLOSE_DELIMITER. Used
|
||||||
|
when resetting the close delimiter, such as when parsing a partial.
|
||||||
MO_CURRENT - Variable name to use for ".".
|
MO_CURRENT - Variable name to use for ".".
|
||||||
MO_DEBUG - When set to a non-empty value, additional debug information is
|
MO_DEBUG - When set to a non-empty value, additional debug information is
|
||||||
written to stderr.
|
written to stderr.
|
||||||
@ -67,11 +74,14 @@ MO_FAIL_ON_UNSET - When set to a non-empty value, expansion of an unset env
|
|||||||
MO_FALSE_IS_EMPTY - When set to a non-empty value, the string "false" will
|
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.
|
be treated as an empty value for the purposes of conditionals.
|
||||||
MO_OPEN_DELIMITER - The string used when opening a tag. Defaults to "{{".
|
MO_OPEN_DELIMITER - The string used when opening a tag. Defaults to "{{".
|
||||||
|
Used internally.
|
||||||
|
MO_OPEN_DELIMITER_DEFAULT - The default value of MO_OPEN_DELIMITER. Used
|
||||||
|
when resetting the open delimiter, such as when parsing a partial.
|
||||||
MO_ORIGINAL_COMMAND - Used to find the `mo` program in order to generate a
|
MO_ORIGINAL_COMMAND - Used to find the `mo` program in order to generate a
|
||||||
help message.
|
help message.
|
||||||
MO_PARSED - Content that has made it through the template engine.
|
MO_PARSED - Content that has made it through the template engine.
|
||||||
MO_STANDALONE_CONTENT - The content that preceeded the current tag. When a
|
MO_STANDALONE_CONTENT - The unparsed content that preceeded the current tag.
|
||||||
standalone tag is encountered, this is checked to see if it only
|
When a standalone tag is encountered, this is checked to see if it only
|
||||||
contains whitespace. If this and the whitespace condition after a tag is
|
contains whitespace. If this and the whitespace condition after a tag is
|
||||||
met, then this will be reset to $'\n'.
|
met, then this will be reset to $'\n'.
|
||||||
MO_UNPARSED - Template content yet to make it through the parser.
|
MO_UNPARSED - Template content yet to make it through the parser.
|
||||||
|
19
tests/inline-indentation
Executable file
19
tests/inline-indentation
Executable file
@ -0,0 +1,19 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
cd "${0%/*}" || exit 1
|
||||||
|
. ../run-tests
|
||||||
|
|
||||||
|
export data="|"
|
||||||
|
template() {
|
||||||
|
cat <<EOF
|
||||||
|
{{data}} {{> fixtures/inline-indentation}}
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
expected() {
|
||||||
|
cat <<EOF
|
||||||
|
| >
|
||||||
|
>
|
||||||
|
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
runTest
|
@ -2,7 +2,7 @@
|
|||||||
cd "${0%/*}" || exit 1
|
cd "${0%/*}" || exit 1
|
||||||
. ../run-tests
|
. ../run-tests
|
||||||
|
|
||||||
export names=( "Tyler" )
|
export names=( "Tyler" "Abc" )
|
||||||
template() {
|
template() {
|
||||||
cat <<EOF
|
cat <<EOF
|
||||||
<h2>Names</h2>
|
<h2>Names</h2>
|
||||||
@ -15,6 +15,7 @@ expected() {
|
|||||||
cat <<EOF
|
cat <<EOF
|
||||||
<h2>Names</h2>
|
<h2>Names</h2>
|
||||||
<strong>Tyler</strong>
|
<strong>Tyler</strong>
|
||||||
|
<strong>Abc</strong>
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user