2 Commits

Author SHA1 Message Date
Tyler Akins
9b2f6cea6c Removing debugging line 2019-07-19 21:47:06 -05:00
Tyler Akins
7a8d1d260e Implementing a search path
Closes #30
2019-07-19 21:43:42 -05:00
58 changed files with 278 additions and 498 deletions

View File

@@ -1,13 +0,0 @@
name: CI
on: [push]
jobs:
test:
name: Test
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v1
- name: Run tests
run: ./run-tests
- name: Run against spec
run: ./run-spec

View File

@@ -1,45 +0,0 @@
name: docker push
on: [push]
jobs:
push_to_registry:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- name: Docker meta
if: startsWith(github.ref, 'refs/tags/')
id: docker_meta
uses: crazy-max/ghaction-docker-meta@v1
with:
images: ghcr.io/${{ github.repository }}
tag-match: v(.*)
- name: Set up QEMU
if: startsWith(github.ref, 'refs/tags/')
uses: docker/setup-qemu-action@v1
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v1
- name: Cache Docker layers
if: startsWith(github.ref, 'refs/tags/')
uses: actions/cache@v2
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-${{ github.sha }}
restore-keys: |
${{ runner.os }}-buildx-
- name: Login to GitHub Container Registry
if: startsWith(github.ref, 'refs/tags/')
uses: docker/login-action@v1
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v2
if: startsWith(github.ref, 'refs/tags/')
with:
builder: ${{ steps.buildx.outputs.name }}
platforms: linux/amd64,linux/arm64
tags: ${{ steps.docker_meta.outputs.tags }}
cache-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,dest=/tmp/.buildx-cache
push: true

2
.gitignore vendored
View File

@@ -5,5 +5,3 @@ tests/*.diff
spec/
spec-runner/
node_modules/
package.json
package-lock.json

105
API.md
View File

@@ -10,48 +10,70 @@ This documentation is generated automatically from the source of [mo] thanks to
Public: Template parser function. Writes templates to stdout.
* $0 - Name of the mo file, used for getting the help message.
* --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.
* --fail-not-set - (`-u`) Fail upon expansion of an unset variable. Default behavior is to silently ignore and expand into empty string.
* --false - (`-e`) 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.
* --help - (`-h)` Display a help message.
* --source=FILE - (`-s=FILE`) Source a file into the environment before processing template files.
* --path=PATH - (`-p=PATH`) Colon-separated list of paths to search for templates. They are relative to where `mo` was executed.
* -- - Used to indicate the end of options. You may use this when filenames start with hyphens.
* $@ - Filenames to parse.
Options:
--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.
-u, --fail-not-set
Fail upon expansion of an unset variable. Default behavior is to silently ignore and expand into empty string.
-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.
* MO_SEARCH_PATH - Colon-separated list of folders to search for templates. They are relative to where `mo` was executed.
Returns true (0) when there are no errors. Sometimes returns (1) when there are errors and sometimes those errors are consumed. It greatly depends on the error and your options.
`files`
-------
After we encounter two hyphens together, all the rest of the arguments are files.
`MO_ALLOW_FUNCTION_ARGUMENTS`
-----------------------------
shellcheck disable=SC2030
`MO_FAIL_ON_UNSET`
------------------
shellcheck disable=SC2030
`MO_FALSE_IS_EMPTY`
-------------------
shellcheck disable=SC2030
`doubleHyphens`
---------------
Set a flag indicating we've encountered double hyphens
`files`
-------
Every arg that is not a flag or a option should be a file
`moProcessSearchPath()`
-----------------------
Internal: Change relative paths into absolute paths
`moCallFunction()`
@@ -59,10 +81,9 @@ Returns nothing.
Internal: Call a function.
* $1 - Variable for output
* $2 - Function to call
* $3 - Content to pass
* $4 - Additional arguments as a single string
* $1 - Function to call
* $2 - Content to pass
* $3 - Additional arguments as a single string
This can be dangerous, especially if you are using tags like {{someFunction ; rm -rf / }}
@@ -254,7 +275,7 @@ Returns nothing.
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
* $2 - Filename to load
Returns nothing.
@@ -310,6 +331,12 @@ Prefix all variables.
Returns nothing.
`IFS`
-----
Search the path for the file
`moShow()`
----------
@@ -430,4 +457,4 @@ Save the original command's path for usage later
[mo]: ./mo
[tomdoc.sh]: https://github.com/tests-always-included/tomdoc.sh
[tomdoc.sh]: https://github.com/mlafeldt/tomdoc.sh

View File

@@ -1,7 +0,0 @@
FROM alpine
RUN apk add --no-cache bash
ADD mo /usr/local/bin/mo
RUN chmod +x /usr/local/bin/mo
ENTRYPOINT /usr/local/bin/mo

View File

@@ -27,7 +27,7 @@ Requirements
* The "coreutils" package (`basename` and `cat`)
* ... that's it. Why? Because bash **can**!
If you intend to develop this and run the official specs, you also need node.js.
If you intend to develop this and run the official specs, you also need Node.js.
Installation
@@ -41,7 +41,7 @@ There are a few ways you can install this tool. How you install it depends on h
You can install this file in `/usr/local/bin/` or `/usr/bin/` by simply downloading it, changing the permissions, then moving it to the right location. Double check that your system's PATH includes the destination folder, otherwise users may have a hard time starting the command.
# Download
curl -sSL https://raw.githubusercontent.com/tests-always-included/mo/master/mo -o mo
curl -sSL https://git.io/get-mo -o mo
# Make executable
chmod +x mo
@@ -58,7 +58,7 @@ You can install this file in `/usr/local/bin/` or `/usr/bin/` by simply download
This is very similar to installing it globally but it does not require root privileges. It is very important that your PATH includes the destination folder otherwise it won't work. Some local folders that are typically used are `~/bin/` and `~/.local/bin/`.
# Download
curl -sSL https://raw.githubusercontent.com/tests-always-included/mo/master/mo -o mo
curl -sSL https://git.io/get-mo -o mo
# Make executable
chmod +x mo
@@ -78,7 +78,7 @@ This is very similar to installing it globally but it does not require root priv
Bash scripts can source `mo` to include the functionality in their own routines. This usage typically would have `mo` saved to a `lib/` folder in an application and your other scripts would use `. lib/mo` to bring it into your project.
# Download
curl -sSL https://raw.githubusercontent.com/tests-always-included/mo/master/mo -o mo
curl -sSL https://git.io/get-mo -o mo
# Move into your project folder
mv mo ~/projects/amazing-things/lib/
@@ -91,7 +91,7 @@ How to Use
If you only plan using strings and numbers, nothing could be simpler. In your shell script you can choose to export the variables. The below script is [`demo/using-strings`](demo/using-strings).
#!/usr/bin/env bash
#!/bin/bash
cd "$(dirname "$0")" # Go to the script's directory
export TEST="This is a test"
echo "Your message: {{TEST}}" | ../mo
@@ -100,7 +100,7 @@ The result? "Your message: This is a test".
Using arrays adds a slight level of complexity. *You must source `mo`.* Look at [`demo/using-arrays`](demo/using-arrays).
#!/usr/bin/env bash
#!/bin/bash
cd "$(dirname "$0")" # Go to the script's directory
export ARRAY=( one two "three three three" four five )
. ../mo # This loads the "mo" function
@@ -118,77 +118,6 @@ There are more scripts available in the [demos directory](demo/) that could help
There are additional features that the program supports. Try using `mo --help` to see what is available.
Enhancements
-----------
In addition to many of the features built-in to Mustache, `mo` includes a number of unique features that make it a bit more powerful.
### Loop @key
`mo` implements Handlebar's `@key` references for outputting the key inside of a loop:
Env:
```bash
myarr=( foo bar )
# Bash v4+
declare -A myassoc
myassoc[hello]="mo"
myassoc[world]="is great"
```
Template:
```handlebars
{{#myarr}}
- {{@key}} {{.}}
{{/myarr}}
{{#myassoc}}
* {{@key}} {{.}}
{{/myassoc}}
```
Output:
```markdown
- 0 foo
- 1 bar
* hello mo
* world is great
```
### Helpers / Function Arguments
Function Arguments are not a part of the official Mustache implementation, and are more often associated with Handlebar's Helper functionality.
`mo` allows for passing strings to functions.
```handlebars
{{myfunc foo bar}}
```
For security reasons, these arguments are not immediately available to function calls without a flag.
#### with `--allow-function-arguments`
```bash
myfunc() {
# Outputs "foo, bar"
echo "$1, $2";
}
```
#### Using `$MO_FUNCTION_ARGS`
```bash
myfunc() {
# Outputs "foo, bar"
echo "${MO_FUNCTION_ARGS[0]}, ${MO_FUNCTION_ARGS[1]}";
}
```
Concessions
-----------

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env bash
#!/bin/bash
cd "$(dirname "$0")" # Go to the script's directory

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env bash
#!/bin/bash
#
# This embeds a template in the script without using strange `cat` syntax.

View File

@@ -1,29 +0,0 @@
#!/usr/bin/env bash
#
# This sources a simple script with the env. variables needed for the template.
cd "$(dirname "$0")" # Go to the script's directory
source ../mo
export NAME="Alex"
export ARRAY=( AAA BBB CCC )
# Include an external template
INCLUDE() {
cat "$MO_FUNCTION_ARGS"
}
# Print section title
TITLE() {
echo "+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+"
echo "$MO_FUNCTION_ARGS"
echo "+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+"
}
cat <<EOF | mo -u
{{TITLE Part 1}}
{{INCLUDE function-args-part1}}
{{TITLE Part 2}}
{{INCLUDE function-args-part2}}
EOF

View File

@@ -1 +0,0 @@
Hello, my name is {{NAME}}.

View File

@@ -1,3 +0,0 @@
{{#ARRAY}}
* {{.}}
{{/ARRAY}}

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env bash
#!/bin/bash
cd "$(dirname "$0")" # Go to the script's directory

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env bash
#!/bin/bash
cd "$(dirname "$0")" # Go to the script's directory

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env bash
#!/bin/bash
cd "$(dirname "$0")"/..

View File

@@ -0,0 +1,3 @@
* Child1
{{> deeper/child2 }}
{{> ../relative-templates/deeper/child2 }}

View File

@@ -0,0 +1 @@
* Child3

View File

@@ -0,0 +1,2 @@
* Child2
{{> ../child3 }}

View File

@@ -0,0 +1,7 @@
This is "file"
Child1 in different ways
{{> child1 }}
{{> ./child1 }}
{{> ../relative-templates/child1 }}
{{> deeper/../child1 }}

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env bash
#!/bin/bash
#
# This sources a simple script with the env. variables needed for the template.

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env bash
#!/bin/bash
cd "$(dirname "$0")" # Go to the script's directory
export ARRAY=( one two "three three three" four five )
. ../mo

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env bash
#!/bin/bash
#
# This example does not source `mo` and is intentionally restricted to
# variables that are not arrays.

View File

@@ -1,12 +0,0 @@
#!/usr/bin/env bash
cd "$(dirname "$0")" # Go to the script's directory
export OPEN="{{"
export CLOSE="}}"
cat <<'EOF' | mo
You can use environment variables to write output that has double braces.
{{OPEN}}sampleTag{{CLOSE}}
EOF

View File

@@ -10,12 +10,11 @@
# diagnose what's not working and fix those low-level functions first.
PARENT_PID=$$
cd "$(dirname "$0")" || exit 1
cd "$(dirname "$0")"
rm -f diagnostic.test
rm -f diagnostic.partial
# Load mo's functions
# shellcheck disable=SC1091
. ./mo
fail() {
@@ -76,20 +75,21 @@ echo "ok"
echo -n "moIsArray ... "
(
export TEST_NUM=1
moIsArray TEST_NUM && fail "Wrongly said number was an array"
TEST=1
moIsArray TEST && fail "Wrongly said number was an array"
)
(
export TEST_ARR=()
moIsArray TEST_ARR || fail "Wrongly said array was not an array"
TEST=()
moIsArray TEST || fail "Wrongly said array was not an array"
)
(
export TEST_EMPTY=""
moIsArray TEST_EMPTY && fail "Wrongly said string was an array"
# shellcheck disable=SC2034
TEST=""
moIsArray TEST && fail "Wrongly said string was an array"
)
(
unset TEST_UNSET
moIsArray TEST_UNSET && fail "Wrongly said undefined was an array"
unset TEST
moIsArray TEST && fail "Wrongly said undefined was an array"
)
echo "ok"

View File

@@ -1,9 +1,9 @@
#!/usr/bin/env bash
#!/bin/bash
#
# This requires tomdoc.sh to be in your PATH.
# https://github.com/tests-always-included/tomdoc.sh
# https://github.com/mlafeldt/tomdoc.sh
cd "${0%/*}" || exit 1
cd "${0%/*}"
cat <<'EOF'
API / Function Documentation
@@ -13,8 +13,8 @@ This documentation is generated automatically from the source of [mo] thanks to
EOF
sed 's/# shellcheck.*//' mo | tomdoc.sh -m
tomdoc.sh -m mo
cat <<'EOF'
[mo]: ./mo
[tomdoc.sh]: https://github.com/tests-always-included/tomdoc.sh
[tomdoc.sh]: https://github.com/mlafeldt/tomdoc.sh
EOF

251
mo
View File

@@ -16,16 +16,15 @@
#/ Options:
#/
#/ -u, --fail-not-set
#/ Fail upon expansion of an unset variable.
#/ -x, --fail-on-function
#/ Fail when a function returns a non-zero status code.
#/ - Fail upon expansion of an unset variable.
#/ -e, --false
#/ Treat the string "false" as empty for conditionals.
#/ - Treat the string "false" as empty for conditionals.
#/ -h, --help
#/ This message.
#/ - This message.
#/ -s=FILE, --source=FILE
#/ Load FILE into the environment before processing templates.
#/ Can be used multiple times.
#/ - Load FILE into the environment before processing templates.
#/ -p=PATH, --path=PATH
#/ - Set a colon-delimited list of folders to search for templates.
#
# Mo is under a MIT style licence with an additional non-advertising clause.
# See LICENSE.md for the full text.
@@ -38,69 +37,50 @@
# Public: Template parser function. Writes templates to stdout.
#
# $0 - Name of the mo file, used for getting the help message.
# --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.
# --fail-not-set - (`-u`) Fail upon expansion of an unset variable. Default
# behavior is to silently ignore and expand into empty string.
# --false - (`-e`) 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.
# --help - (`-h)` Display a help message.
# --source=FILE - (`-s=FILE`) Source a file into the environment before processing
# template files.
# --path=PATH - (`-p=PATH`) Colon-separated list of paths to search for templates.
# They are relative to where `mo` was executed.
# -- - Used to indicate the end of options. You may use this when filenames
# start with hyphens.
# $@ - Filenames to parse.
#
# Options:
#
# --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.
#
# -u, --fail-not-set
#
# Fail upon expansion of an unset variable. Default behavior is to silently
# ignore and expand into empty string.
#
# -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.
# 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.
#
# Returns nothing.
# 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.
#
# MO_SEARCH_PATH - Colon-separated list of folders to search for templates.
# They are relative to where `mo` was executed.
#
# Returns true (0) when there are no errors. Sometimes returns (1) when there
# are errors and sometimes those errors are consumed. It greatly depends on the
# error and your options.
mo() (
# This function executes in a subshell so IFS is reset.
# Namespace this variable so we don't conflict with desired values.
local moContent f2source files doubleHyphens
local moContent f2source files doubleHyphens paths
IFS=$' \n\t'
files=()
@@ -109,8 +89,8 @@ mo() (
if [[ $# -gt 0 ]]; then
for arg in "$@"; do
if $doubleHyphens; then
#: After we encounter two hyphens together, all the rest
#: of the arguments are files.
# After we encounter two hyphens together, all the rest
# of the arguments are files.
files=("${files[@]}" "$arg")
else
case "$arg" in
@@ -129,22 +109,13 @@ mo() (
MO_FAIL_ON_UNSET=true
;;
-x | --fail-on-function)
# shellcheck disable=SC2030
MO_FAIL_ON_FUNCTION=true
;;
-e | --false)
# shellcheck disable=SC2030
MO_FALSE_IS_EMPTY=true
;;
-s=* | --source=*)
if [[ "$arg" == --source=* ]]; then
f2source="${arg#--source=}"
else
f2source="${arg#-s=}"
fi
f2source="${arg#*=}"
if [[ -f "$f2source" ]]; then
# shellcheck disable=SC1090
@@ -155,13 +126,17 @@ mo() (
fi
;;
-p=* | --path=*)
MO_SEARCH_PATH="$(moProcessSearchPath "${arg#*=}")"
;;
--)
#: Set a flag indicating we've encountered double hyphens
# Set a flag indicating we've encountered double hyphens
doubleHyphens=true
;;
*)
#: Every arg that is not a flag or a option should be a file
# Every arg that is not a flag or a option should be a file
files=(${files[@]+"${files[@]}"} "$arg")
;;
esac
@@ -174,41 +149,42 @@ mo() (
)
# Internal: Change relative paths into absolute paths
moProcessSearchPath() {
local in out path startingPwd IFS
IFS=:
startingPwd=$PWD
for path in $1; do
cd "$startingPwd" && cd "$path" 2>/dev/null && out="$out:$PWD"
done
echo "${out:1}"
}
# Internal: Call a function.
#
# $1 - Variable for output
# $2 - Function to call
# $3 - Content to pass
# $4 - Additional arguments as a single string
# $1 - Function to call
# $2 - Content to pass
# $3 - Additional arguments as a single string
#
# This can be dangerous, especially if you are using tags like
# {{someFunction ; rm -rf / }}
#
# Returns nothing.
moCallFunction() {
local moArgs moContent moFunctionArgs moFunctionResult
local moArgs
moArgs=()
moTrimWhitespace moFunctionArgs "$4"
# shellcheck disable=SC2031
if [[ -n "${MO_ALLOW_FUNCTION_ARGUMENTS-}" ]]; then
# Intentionally bad behavior
# shellcheck disable=SC2206
moArgs=($4)
moArgs=$3
fi
moContent=$(echo -n "$3" | MO_FUNCTION_ARGS="$moFunctionArgs" eval "$2" "${moArgs[@]}") || {
moFunctionResult=$?
# shellcheck disable=SC2031
if [[ -n "${MO_FAIL_ON_FUNCTION-}" && "$moFunctionResult" != 0 ]]; then
echo "Function '$2' with args (${moArgs[*]+"${moArgs[@]}"}) failed with status code $moFunctionResult"
exit "$moFunctionResult"
fi
}
# shellcheck disable=SC2031
local "$1" && moIndirect "$1" "$moContent"
echo -n "$2" | eval "$1" "$moArgs"
}
@@ -260,7 +236,6 @@ moFindEndTag() {
if [[ -z "${4-}" ]] && moIsStandalone standaloneBytes "$scanned" "${content[2]}" true; then
#: This is also a standalone tag - clean up whitespace
#: and move those whitespace bytes to the "tag" element
# shellcheck disable=SC2206
standaloneBytes=( $standaloneBytes )
content[1]="${scanned:${standaloneBytes[0]}}${content[1]}${content[2]:0:${standaloneBytes[1]}}"
scanned="${scanned:0:${standaloneBytes[0]}}"
@@ -302,7 +277,7 @@ moFindEndTag() {
moFindString() {
local pos string
string=${2%%"$3"*}
string=${2%%$3*}
[[ "$string" == "$2" ]] && pos=-1 || pos=${#string}
local "$1" && moIndirect "$1" "$pos"
}
@@ -332,22 +307,22 @@ moFullTagName() {
#
# Returns nothing.
moGetContent() {
local moContent moFilename moTarget
local content filename target
moTarget=$1
target=$1
shift
if [[ "${#@}" -gt 0 ]]; then
moContent=""
content=""
for moFilename in "$@"; do
for filename in "$@"; do
#: This is so relative paths work from inside template files
moContent="$moContent"'{{>'"$moFilename"'}}'
content="$content"'{{>'"$filename"'}}'
done
else
moLoadFile moContent || return 1
moLoadFile content /dev/stdin || return 1
fi
local "$moTarget" && moIndirect "$moTarget" "$moContent"
local "$target" && moIndirect "$target" "$content"
}
@@ -522,7 +497,6 @@ moIsFunction() {
local functionList functionName
functionList=$(declare -F)
# shellcheck disable=SC2206
functionList=( ${functionList//declare -f /} )
for functionName in "${functionList[@]}"; do
@@ -614,7 +588,7 @@ moJoin() {
# 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
# $2 - Filename to load
#
# Returns nothing.
moLoadFile() {
@@ -625,7 +599,7 @@ moLoadFile() {
# As a future optimization, it would be worth considering removing
# cat and replacing this with a read loop.
content=$(cat -- "${2:-/dev/stdin}" && echo '.') || return 1
content=$(cat -- "$2" && echo '.') || return 1
len=$((${#content} - 1))
content=${content:0:$len} # Remove last dot
@@ -666,7 +640,7 @@ moLoop() {
moParse() {
# Keep naming variables mo* here to not overwrite needed variables
# used in the string replacements
local moArgs moBlock moContent moCurrent moIsBeginning moNextIsBeginning moTag moKey
local moArgs moBlock moContent moCurrent moIsBeginning moNextIsBeginning moTag
moCurrent=$2
moIsBeginning=$3
@@ -697,7 +671,9 @@ moParse() {
if moTest "$moTag"; then
# Show / loop / pass through function
if moIsFunction "$moTag"; then
moCallFunction moContent "$moTag" "${moBlock[0]}" "$moArgs"
#: Consider piping the output to moGetContent
#: so the lambda does not execute in a subshell?
moContent=$(moCallFunction "$moTag" "${moBlock[0]}" "$moArgs")
moParse "$moContent" "$moCurrent" false
moContent="${moBlock[2]}"
elif moIsArray "$moTag"; then
@@ -782,17 +758,6 @@ moParse() {
moShow "$moTag" "$moCurrent"
;;
'@key')
# Special vars
moStandaloneDenied moContent "${moContent[@]}"
# Current content (environment variable or function)
if [[ "$moCurrent" == *.* ]]; then
echo -n "${moCurrent#*.}"
else
echo -n "$moCurrent"
fi
;;
*)
# Normal environment variable or function call
moStandaloneDenied moContent "${moContent[@]}"
@@ -841,7 +806,6 @@ moPartial() {
local moContent moFilename moIndent moIsBeginning moPartial moStandalone moUnindented
if moIsStandalone moStandalone "$2" "$4" "$5"; then
# shellcheck disable=SC2206
moStandalone=( $moStandalone )
echo -n "${2:0:${moStandalone[0]}}"
moIndent=${2:${moStandalone[0]}}
@@ -860,7 +824,39 @@ moPartial() {
(
# It would be nice to remove `dirname` and use a function instead,
# but that's difficult when you're only given filenames.
cd "$(dirname -- "$moFilename")" || exit 1
if ! cd "$(dirname -- "$moFilename")"; then
if [[ -n "${MO_FAIL_ON_UNSET-}" ]]; then
echo "Error changing to directory: $(dirname -- "$moFilename")" >&2
exit 1
fi
# Mustache likes to be silent when there are errors.
exit 0
fi
if [[ "$moFilename" != */* ]] && [[ ! -f "$moFilename" ]] && [[ -n "$MO_SEARCH_PATH" ]]; then
# Search the path for the file
IFS=:
for moSearchPath in $MO_SEARCH_PATH; do
if [[ ! -f "$moFilename" ]]; then
cd "$moSearchPath"
fi
done
IFS=$' \n\t'
fi
if [[ ! -f "${moFilename##*/}" ]]; then
if [[ -n "${MO_FAIL_ON_UNSET-}" ]]; then
echo "File does not exist: $PWD/${moFilename##*/}" >&2
exit 1
fi
# Mustache likes to be silent when there are errors.
exit 0
fi
moUnindented="$(
moLoadFile moPartial "${moFilename##*/}" || exit 1
moParse "${moPartial}" "$6" true
@@ -895,11 +891,11 @@ moPartial() {
# Returns nothing.
moShow() {
# Namespace these variables
local moJoined moNameParts moContent
local moJoined moNameParts
if moIsFunction "$1"; then
moCallFunction moContent "$1" "" "$3"
moParse "$moContent" "$2" false
CONTENT=$(moCallFunction "$1" "" "$3")
moParse "$CONTENT" "$2" false
return 0
fi
@@ -974,7 +970,6 @@ moStandaloneAllowed() {
local bytes
if moIsStandalone bytes "$2" "$4" "$5"; then
# shellcheck disable=SC2206
bytes=( $bytes )
echo -n "${2:0:${bytes[0]}}"
local "$1" && moIndirect "$1" "${4:${bytes[1]}}"
@@ -1102,13 +1097,13 @@ moTrimWhitespace() {
moUsage() {
grep '^#/' "${MO_ORIGINAL_COMMAND}" | cut -c 4-
echo ""
echo "MO_VERSION=$MO_VERSION"
set | grep ^MO_VERSION=
}
# Save the original command's path for usage later
MO_ORIGINAL_COMMAND="$(cd "${BASH_SOURCE[0]%/*}" || exit 1; pwd)/${BASH_SOURCE[0]##*/}"
MO_VERSION="2.4.1"
MO_VERSION="2.0.4"
# If sourced, load all functions.
# If executed, perform the actions as expected.

10
package.json Normal file
View File

@@ -0,0 +1,10 @@
{
"dependencies": {
"async": "*"
},
"scripts": {
"clean": "rm -rf package-lock.json node_modules/ spec/",
"install-tests": "npm install; git clone https://github.com/mustache/spec.git spec",
"test": "node run-spec.js spec/specs/*.json"
}
}

View File

@@ -1,20 +0,0 @@
#!/usr/bin/env bash
# Create a package.json so the dependency package is installed in the local
# directory
echo '{"private":true, "dependencies":{"async": "*"}}' > package.json
npm install
# Install or update the specs
if [[ ! -d spec ]]; then
git clone https://github.com/mustache/spec.git spec
else
(
cd spec;
git pull
)
fi
# Actually run the specs
node run-spec.js spec/specs/*.json

View File

@@ -64,7 +64,7 @@ function runTest(test, done) {
var output, partials, script;
script = [
'#!/usr/bin/env bash'
'#!/bin/bash'
];
partials = test.partials || {};

View File

@@ -1,8 +1,7 @@
#!/usr/bin/env bash
cd "${0%/*}" || exit 1
cd "${0%/*}"
# shellcheck disable=SC1091
. ./mo
PASS=0
FAIL=0
@@ -19,16 +18,13 @@ for TEST in tests/*.expected; do
"${BASE}.sh"
else
# Fall back to using .env and .template
# shellcheck disable=SC1090
. "${BASE}.env"
echo "Do not read this input" | mo "${BASE}.template"
fi
) | diff -U5 - "${TEST}" > "${BASE}.diff"
statusCode=$?
if [[ $statusCode -ne 0 ]]; then
echo "FAIL (status code $statusCode)"
if [[ $? -ne 0 ]]; then
echo "FAIL"
FAIL=$(( FAIL + 1 ))
else
echo "ok"
@@ -40,6 +36,4 @@ done
echo ""
echo "Pass: $PASS"
echo "Fail: $FAIL"
if [[ $FAIL -gt 0 ]]; then
exit 1
fi
[[ $FAIL -gt 0 ]] && exit 1

View File

@@ -1,3 +1,3 @@
<b>0 - resque</b>
<b>1 - hub</b>
<b>2 - rip</b>
<b>resque</b>
<b>hub</b>
<b>rip</b>

View File

@@ -1,3 +1,3 @@
{{#repo}}
<b>{{@key}} - {{.}}</b>
<b>{{.}}</b>
{{/repo}}

View File

@@ -1,4 +0,0 @@
declare -A repo
repo[resque]="Resque"
repo[hub]="Hub"
repo[rip]="Rip"

View File

@@ -1,3 +0,0 @@
<b>hub - Hub</b>
<b>rip - Rip</b>
<b>resque - Resque</b>

View File

@@ -1,3 +0,0 @@
{{#repo}}
<b>{{@key}} - {{.}}</b>
{{/repo}}

View File

@@ -1 +1 @@
cat: --help: No such file or directory
File does not exist: /home/fidian/repo/mo/tests/--help

View File

@@ -1,5 +1,5 @@
#!/usr/bin/env bash
# This should display a message indicating that the file --help
# could not be found. It should not display a help messsage.
cd "${0%/*}" || exit 1
../mo -- --help 2>&1
cd "${0%/*}"
../mo -u -- --help 2>&1

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env bash
cd "${0%/*}" || exit 1
cd "${0%/*}"
unset __NO_SUCH_VAR
POPULATED="words" EMPTY="" ../mo --fail-not-set ./fail-not-set-file.template 2>&1

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env bash
cd "${0%/*}" || exit 1
cd "${0%/*}"
unset __NO_SUCH_VAR
POPULATED="words" EMPTY="" ../mo --fail-not-set 2>&1 <<EOF
Populated: {{POPULATED}};

View File

@@ -1 +0,0 @@
Fail on function? Function 'failFunction' with args () failed with status code 1

View File

@@ -1,18 +0,0 @@
#!/usr/bin/env bash
cd "${0%/*}" || exit 1
failFunction() {
false
}
# Must be sourced to use functions
# shellcheck disable=SC1091
. ../mo
mo --fail-on-function 2>&1 <<EOF
Fail on function? {{failFunction}}
EOF
if [[ $? -ne 1 ]]; then
echo "Did not return 1"
fi

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env bash
cd "${0%/*}" || exit 1
cd "${0%/*}"
USER=j.doe ADMIN=false ../mo --false false-is-empty-arg.template

View File

@@ -1,3 +0,0 @@
testArgs() {
echo "$MO_FUNCTION_ARGS"
}

View File

@@ -1,4 +0,0 @@
No args: [] - done
One arg: [one] - done
Multiple arguments: [aa bb cc 'x' " ! {[_.|] - done
Evil: [bla; cat /etc/issue] - done

View File

@@ -1,4 +0,0 @@
No args: [{{testArgs}}] - done
One arg: [{{testArgs one}}] - done
Multiple arguments: [{{testArgs aa bb cc 'x' " ! {[_.| }}] - done
Evil: [{{testArgs bla; cat /etc/issue}}] - done

View File

@@ -14,15 +14,14 @@ Simple usage:
Options:
-u, --fail-not-set
Fail upon expansion of an unset variable.
-x, --fail-on-function
Fail when a function returns a non-zero status code.
- Fail upon expansion of an unset variable.
-e, --false
Treat the string "false" as empty for conditionals.
- Treat the string "false" as empty for conditionals.
-h, --help
This message.
- This message.
-s=FILE, --source=FILE
Load FILE into the environment before processing templates.
Can be used multiple times.
- Load FILE into the environment before processing templates.
-p=PATH, --path=PATH
- Set a colon-delimited list of folders to search for templates.
MO_VERSION=2.4.1
MO_VERSION=2.0.4

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env bash
cd "${0%/*}" || exit 1
../mo --help
cd "${0%/*}"
../mo -u --help

View File

@@ -1 +0,0 @@
cat: --something: No such file or directory

View File

@@ -1,5 +1,5 @@
#!/usr/bin/env bash
# This should display a message indicating that the file --something
# could not be found.
cd "${0%/*}" || exit 1
cd "${0%/*}"
../mo --something 2>&1

View File

@@ -1 +1 @@
cat: partial-missing.partial: No such file or directory
File does not exist: /home/fidian/repo/mo/tests/partial-missing.partial

View File

@@ -1,8 +1,9 @@
#!/usr/bin/env bash
cd "${0%/*}" || exit 1
../mo partial-missing.template 2>&1
cd "${0%/*}"
../mo -u partial-missing.template 2>&1
returned=$?
if [[ $? -ne 1 ]]; then
echo "Did not return 1"
if [[ $returned -ne 1 ]]; then
echo "Did not return 1. Instead, returned $returned."
fi

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env bash
cd "${0%/*}" || exit 1
cd "${0%/*}"
echo "Do not display this" | ../mo --source=invalid 2>&1
if [[ $? -ne 1 ]]; then

View File

@@ -1,2 +0,0 @@
export A=from1
export B=from1

View File

@@ -1,2 +0,0 @@
export B=from2
export C=from2

View File

@@ -1,3 +0,0 @@
A: from1
B: from2
C: from2

View File

@@ -1,8 +0,0 @@
#!/usr/bin/env bash
cd "${0%/*}" || exit 1
cat <<EOF | ../mo --source=source-multiple-1.vars --source=source-multiple-2.vars
A: {{A}}
B: {{B}}
C: {{C}}
EOF

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env bash
cd "${0%/*}" || exit 1
cd "${0%/*}"
echo "Do not display this" | ../mo --source= 2>&1
if [[ $? -ne 1 ]]; then

View File

@@ -1,7 +1,8 @@
#!/usr/bin/env bash
cd "${0%/*}" || exit 1
cat <<EOF | ../mo --source=source.vars
cd "${0%/*}"
. ../mo
cat <<EOF | mo --source=source.vars
{{VAR}}
{{#ARR}}
* {{.}}

View File

@@ -1,5 +1,4 @@
export VAR=value
export ARR=(1 2 3)
declare -A ASSOC_ARR
# Can not export associative arrays, otherwise they turn into indexed arrays
ASSOC_ARR=([a]=AAA [b]=BBB)