17 Commits

Author SHA1 Message Date
Tyler Akins
c86fd9a89b Merge pull request #45 from yutachaos/feature/added_docker_release
Added dockerfile and release GitHub Action
2021-04-17 11:02:12 -05:00
yutachaos
03eb3925ac Added Dockerfile 2021-04-09 14:37:51 +09:00
yutachaos
8e3e08a42b Added docker image push action 2021-04-09 14:37:45 +09:00
Tyler Akins
4c332c9188 Updating test and documentation 2020-10-01 07:42:57 -05:00
Tyler Akins
67ba8bae2c Calling out that the argument can be used multiple times 2020-10-01 07:39:02 -05:00
Tyler Akins
1d2617dde1 Confirming multiple --source arguments work 2020-10-01 07:37:48 -05:00
Tyler Akins
dcd9d7738b Adding tests, shellcheck cleanup, update docs, release 2.2.0 2020-08-05 15:44:04 -05:00
Tyler Akins
65f12277e6 Merge pull request #40 from ynqa/show-latest-version
fix: show latest version for usage of mo
2020-08-05 11:37:38 -08:00
Tyler Akins
8cd67fb908 Merge pull request #39 from felipecassiors/felipecassiors/fail-on-function
Add fail on function
2020-08-05 11:27:50 -08:00
ynqa
bdb795bddf show 2.1.0 for mo usage 2020-08-06 00:01:09 +09:00
Felipe Santos
1f39b0f568 Add fail on function 2020-07-29 20:19:32 +00:00
Tyler Akins
0e6247e9e9 Merge pull request #37 from l2dy/whitespace
Remove trailing whitespace
2020-02-26 07:55:43 -06:00
Zero King
891b6a5de2 Remove trailing whitespace 2020-02-26 05:54:17 +00:00
Tyler Akins
3828588512 Showing how to write output that includes braces 2019-08-22 15:59:05 -05:00
Tyler Akins
929ffc5b88 Merge pull request #32 from andreax79/master
Pass arguments to function as environment variable MO_ALLOW_FUNCTION_ARGUMENTS
2019-08-08 07:47:44 -05:00
Andrea Bonomi
5b8cf24068 Pass arguments to function as environment variable MO_ALLOW_FUNCTION_ARGUMENTS 2019-08-08 11:43:50 +02:00
Andrea Bonomi
fcedd32155 Pass arguments to function as environment variable MO_ARGS 2019-08-07 15:01:51 +02:00
40 changed files with 348 additions and 243 deletions

45
.github/workflows/release.yaml vendored Normal file
View File

@@ -0,0 +1,45 @@
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

105
API.md
View File

@@ -10,70 +10,48 @@ This documentation is generated automatically from the source of [mo] thanks to
Public: Template parser function. Writes templates to stdout. Public: Template parser function. Writes templates to stdout.
* $0 - Name of the mo file, used for getting the help message. * $0 - Name of the mo file, used for getting the help message.
* --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. * $@ - 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 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_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_FAIL_ON_UNSET - When set to a non-empty value, expansion of an unset env variable will be aborted with an error.
* MO_FALSE_IS_EMPTY - When set to a non-empty value, the string "false" will be treated as an empty value for the purposes of conditionals. * MO_FALSE_IS_EMPTY - When set to a non-empty value, the string "false" will be treated as an empty value for the purposes of conditionals.
* MO_ORIGINAL_COMMAND - Used to find the `mo` program in order to generate a help message. * MO_ORIGINAL_COMMAND - Used to find the `mo` program in order to generate a help message.
* MO_SEARCH_PATH - Colon-separated list of folders to search for templates. They are relative to where `mo` was executed. Returns nothing.
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()` `moCallFunction()`
@@ -81,9 +59,10 @@ Internal: Change relative paths into absolute paths
Internal: Call a function. Internal: Call a function.
* $1 - Function to call * $1 - Variable for output
* $2 - Content to pass * $2 - Function to call
* $3 - Additional arguments as a single string * $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 / }} This can be dangerous, especially if you are using tags like {{someFunction ; rm -rf / }}
@@ -275,7 +254,7 @@ Returns nothing.
Internal: Read a file into a variable. Internal: Read a file into a variable.
* $1 - Variable name to receive the file's content * $1 - Variable name to receive the file's content
* $2 - Filename to load * $2 - Filename to load - if empty, defaults to /dev/stdin
Returns nothing. Returns nothing.
@@ -331,12 +310,6 @@ Prefix all variables.
Returns nothing. Returns nothing.
`IFS`
-----
Search the path for the file
`moShow()` `moShow()`
---------- ----------
@@ -457,4 +430,4 @@ Save the original command's path for usage later
[mo]: ./mo [mo]: ./mo
[tomdoc.sh]: https://github.com/mlafeldt/tomdoc.sh [tomdoc.sh]: https://github.com/tests-always-included/tomdoc.sh

11
Dockerfile Normal file
View File

@@ -0,0 +1,11 @@
FROM alpine:3.13.4
COPY ./mo .
RUN apk add --update bash && \
chmod +x mo &&\
mv mo /usr/local/bin/mo && \
rm -rf /var/cache/apk/*
WORKDIR /opt
ENTRYPOINT ["/usr/local/bin/mo"]

View File

@@ -27,7 +27,7 @@ Requirements
* The "coreutils" package (`basename` and `cat`) * The "coreutils" package (`basename` and `cat`)
* ... that's it. Why? Because bash **can**! * ... 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 Installation

29
demo/function-args Executable file
View File

@@ -0,0 +1,29 @@
#!/bin/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

1
demo/function-args-part1 Normal file
View File

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

3
demo/function-args-part2 Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

12
demo/writing-out-braces Executable file
View File

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

View File

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

224
mo
View File

@@ -16,15 +16,16 @@
#/ Options: #/ Options:
#/ #/
#/ -u, --fail-not-set #/ -u, --fail-not-set
#/ - Fail upon expansion of an unset variable. #/ Fail upon expansion of an unset variable.
#/ -x, --fail-on-function
#/ Fail when a function returns a non-zero status code.
#/ -e, --false #/ -e, --false
#/ - Treat the string "false" as empty for conditionals. #/ Treat the string "false" as empty for conditionals.
#/ -h, --help #/ -h, --help
#/ - This message. #/ This message.
#/ -s=FILE, --source=FILE #/ -s=FILE, --source=FILE
#/ - Load FILE into the environment before processing templates. #/ Load FILE into the environment before processing templates.
#/ -p=PATH, --path=PATH #/ Can be used multiple times.
#/ - Set a colon-delimited list of folders to search for templates.
# #
# Mo is under a MIT style licence with an additional non-advertising clause. # Mo is under a MIT style licence with an additional non-advertising clause.
# See LICENSE.md for the full text. # See LICENSE.md for the full text.
@@ -37,50 +38,69 @@
# Public: Template parser function. Writes templates to stdout. # Public: Template parser function. Writes templates to stdout.
# #
# $0 - Name of the mo file, used for getting the help message. # $0 - Name of the mo file, used for getting the help message.
# --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. # $@ - 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 uses the following environment variables:
# #
# MO_ALLOW_FUNCTION_ARGUMENTS - When set to a non-empty value, this allows # 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
# arguments. This puts the content from the template directly into an # options and arguments. This puts the content from the
# eval statement. Use with extreme care. # 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 # MO_FAIL_ON_UNSET - When set to a non-empty value, expansion of an unset env
# variable will be aborted with an error. # variable will be aborted with an error.
#
# MO_FALSE_IS_EMPTY - When set to a non-empty value, the string "false" will be # 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. # treated as an empty value for the purposes of conditionals.
#
# 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_SEARCH_PATH - Colon-separated list of folders to search for templates. # Returns nothing.
# 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() ( mo() (
# This function executes in a subshell so IFS is reset. # This function executes in a subshell so IFS is reset.
# Namespace this variable so we don't conflict with desired values. # Namespace this variable so we don't conflict with desired values.
local moContent f2source files doubleHyphens paths local moContent f2source files doubleHyphens
IFS=$' \n\t' IFS=$' \n\t'
files=() files=()
@@ -89,8 +109,8 @@ mo() (
if [[ $# -gt 0 ]]; then if [[ $# -gt 0 ]]; then
for arg in "$@"; do for arg in "$@"; do
if $doubleHyphens; then if $doubleHyphens; then
# After we encounter two hyphens together, all the rest #: After we encounter two hyphens together, all the rest
# of the arguments are files. #: of the arguments are files.
files=("${files[@]}" "$arg") files=("${files[@]}" "$arg")
else else
case "$arg" in case "$arg" in
@@ -109,13 +129,22 @@ mo() (
MO_FAIL_ON_UNSET=true MO_FAIL_ON_UNSET=true
;; ;;
-x | --fail-on-function)
# shellcheck disable=SC2030
MO_FAIL_ON_FUNCTION=true
;;
-e | --false) -e | --false)
# shellcheck disable=SC2030 # shellcheck disable=SC2030
MO_FALSE_IS_EMPTY=true MO_FALSE_IS_EMPTY=true
;; ;;
-s=* | --source=*) -s=* | --source=*)
f2source="${arg#*=}" if [[ "$arg" == --source=* ]]; then
f2source="${arg#--source=}"
else
f2source="${arg#-s=}"
fi
if [[ -f "$f2source" ]]; then if [[ -f "$f2source" ]]; then
# shellcheck disable=SC1090 # shellcheck disable=SC1090
@@ -126,17 +155,13 @@ mo() (
fi 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 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") files=(${files[@]+"${files[@]}"} "$arg")
;; ;;
esac esac
@@ -149,42 +174,41 @@ 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. # Internal: Call a function.
# #
# $1 - Function to call # $1 - Variable for output
# $2 - Content to pass # $2 - Function to call
# $3 - Additional arguments as a single string # $3 - Content to pass
# $4 - Additional arguments as a single string
# #
# This can be dangerous, especially if you are using tags like # This can be dangerous, especially if you are using tags like
# {{someFunction ; rm -rf / }} # {{someFunction ; rm -rf / }}
# #
# Returns nothing. # Returns nothing.
moCallFunction() { moCallFunction() {
local moArgs local moArgs moContent moFunctionArgs moFunctionResult
moArgs=() moArgs=()
moTrimWhitespace moFunctionArgs "$4"
# shellcheck disable=SC2031 # shellcheck disable=SC2031
if [[ -n "${MO_ALLOW_FUNCTION_ARGUMENTS-}" ]]; then if [[ -n "${MO_ALLOW_FUNCTION_ARGUMENTS-}" ]]; then
moArgs=$3 # Intentionally bad behavior
# shellcheck disable=SC2206
moArgs=($4)
fi fi
echo -n "$2" | eval "$1" "$moArgs" 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"
} }
@@ -236,6 +260,7 @@ moFindEndTag() {
if [[ -z "${4-}" ]] && moIsStandalone standaloneBytes "$scanned" "${content[2]}" true; then if [[ -z "${4-}" ]] && moIsStandalone standaloneBytes "$scanned" "${content[2]}" true; then
#: This is also a standalone tag - clean up whitespace #: This is also a standalone tag - clean up whitespace
#: and move those whitespace bytes to the "tag" element #: and move those whitespace bytes to the "tag" element
# shellcheck disable=SC2206
standaloneBytes=( $standaloneBytes ) standaloneBytes=( $standaloneBytes )
content[1]="${scanned:${standaloneBytes[0]}}${content[1]}${content[2]:0:${standaloneBytes[1]}}" content[1]="${scanned:${standaloneBytes[0]}}${content[1]}${content[2]:0:${standaloneBytes[1]}}"
scanned="${scanned:0:${standaloneBytes[0]}}" scanned="${scanned:0:${standaloneBytes[0]}}"
@@ -307,22 +332,22 @@ moFullTagName() {
# #
# Returns nothing. # Returns nothing.
moGetContent() { moGetContent() {
local content filename target local moContent moFilename moTarget
target=$1 moTarget=$1
shift shift
if [[ "${#@}" -gt 0 ]]; then if [[ "${#@}" -gt 0 ]]; then
content="" moContent=""
for filename in "$@"; do for moFilename in "$@"; do
#: This is so relative paths work from inside template files #: This is so relative paths work from inside template files
content="$content"'{{>'"$filename"'}}' moContent="$moContent"'{{>'"$moFilename"'}}'
done done
else else
moLoadFile content /dev/stdin || return 1 moLoadFile moContent || return 1
fi fi
local "$target" && moIndirect "$target" "$content" local "$moTarget" && moIndirect "$moTarget" "$moContent"
} }
@@ -497,6 +522,7 @@ moIsFunction() {
local functionList functionName local functionList functionName
functionList=$(declare -F) functionList=$(declare -F)
# shellcheck disable=SC2206
functionList=( ${functionList//declare -f /} ) functionList=( ${functionList//declare -f /} )
for functionName in "${functionList[@]}"; do for functionName in "${functionList[@]}"; do
@@ -588,7 +614,7 @@ moJoin() {
# Internal: Read a file into a variable. # Internal: Read a file into a variable.
# #
# $1 - Variable name to receive the file's content # $1 - Variable name to receive the file's content
# $2 - Filename to load # $2 - Filename to load - if empty, defaults to /dev/stdin
# #
# Returns nothing. # Returns nothing.
moLoadFile() { moLoadFile() {
@@ -599,7 +625,7 @@ moLoadFile() {
# As a future optimization, it would be worth considering removing # As a future optimization, it would be worth considering removing
# cat and replacing this with a read loop. # cat and replacing this with a read loop.
content=$(cat -- "$2" && echo '.') || return 1 content=$(cat -- "${2:-/dev/stdin}" && echo '.') || return 1
len=$((${#content} - 1)) len=$((${#content} - 1))
content=${content:0:$len} # Remove last dot content=${content:0:$len} # Remove last dot
@@ -671,9 +697,7 @@ moParse() {
if moTest "$moTag"; then if moTest "$moTag"; then
# Show / loop / pass through function # Show / loop / pass through function
if moIsFunction "$moTag"; then if moIsFunction "$moTag"; then
#: Consider piping the output to moGetContent moCallFunction moContent "$moTag" "${moBlock[0]}" "$moArgs"
#: so the lambda does not execute in a subshell?
moContent=$(moCallFunction "$moTag" "${moBlock[0]}" "$moArgs")
moParse "$moContent" "$moCurrent" false moParse "$moContent" "$moCurrent" false
moContent="${moBlock[2]}" moContent="${moBlock[2]}"
elif moIsArray "$moTag"; then elif moIsArray "$moTag"; then
@@ -806,6 +830,7 @@ moPartial() {
local moContent moFilename moIndent moIsBeginning moPartial moStandalone moUnindented local moContent moFilename moIndent moIsBeginning moPartial moStandalone moUnindented
if moIsStandalone moStandalone "$2" "$4" "$5"; then if moIsStandalone moStandalone "$2" "$4" "$5"; then
# shellcheck disable=SC2206
moStandalone=( $moStandalone ) moStandalone=( $moStandalone )
echo -n "${2:0:${moStandalone[0]}}" echo -n "${2:0:${moStandalone[0]}}"
moIndent=${2:${moStandalone[0]}} moIndent=${2:${moStandalone[0]}}
@@ -824,39 +849,7 @@ moPartial() {
( (
# 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's difficult when you're only given filenames. # but that's difficult when you're only given filenames.
if ! cd "$(dirname -- "$moFilename")"; then cd "$(dirname -- "$moFilename")" || exit 1
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="$( moUnindented="$(
moLoadFile moPartial "${moFilename##*/}" || exit 1 moLoadFile moPartial "${moFilename##*/}" || exit 1
moParse "${moPartial}" "$6" true moParse "${moPartial}" "$6" true
@@ -891,11 +884,11 @@ moPartial() {
# Returns nothing. # Returns nothing.
moShow() { moShow() {
# Namespace these variables # Namespace these variables
local moJoined moNameParts local moJoined moNameParts moContent
if moIsFunction "$1"; then if moIsFunction "$1"; then
CONTENT=$(moCallFunction "$1" "" "$3") moCallFunction moContent "$1" "" "$3"
moParse "$CONTENT" "$2" false moParse "$moContent" "$2" false
return 0 return 0
fi fi
@@ -970,6 +963,7 @@ moStandaloneAllowed() {
local bytes local bytes
if moIsStandalone bytes "$2" "$4" "$5"; then if moIsStandalone bytes "$2" "$4" "$5"; then
# shellcheck disable=SC2206
bytes=( $bytes ) bytes=( $bytes )
echo -n "${2:0:${bytes[0]}}" echo -n "${2:0:${bytes[0]}}"
local "$1" && moIndirect "$1" "${4:${bytes[1]}}" local "$1" && moIndirect "$1" "${4:${bytes[1]}}"
@@ -1097,13 +1091,13 @@ moTrimWhitespace() {
moUsage() { moUsage() {
grep '^#/' "${MO_ORIGINAL_COMMAND}" | cut -c 4- grep '^#/' "${MO_ORIGINAL_COMMAND}" | cut -c 4-
echo "" echo ""
set | grep ^MO_VERSION= echo "MO_VERSION=$MO_VERSION"
} }
# Save the original command's path for usage later # Save the original command's path for usage later
MO_ORIGINAL_COMMAND="$(cd "${BASH_SOURCE[0]%/*}" || exit 1; pwd)/${BASH_SOURCE[0]##*/}" MO_ORIGINAL_COMMAND="$(cd "${BASH_SOURCE[0]%/*}" || exit 1; pwd)/${BASH_SOURCE[0]##*/}"
MO_VERSION="2.0.4" MO_VERSION="2.2.0"
# If sourced, load all functions. # If sourced, load all functions.
# If executed, perform the actions as expected. # If executed, perform the actions as expected.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

18
tests/fail-on-function.sh Executable file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

8
tests/source-multiple.sh Executable file
View File

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

View File

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

View File

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