45 Commits
2.0.2 ... 2.4.1

Author SHA1 Message Date
Tyler Akins
8e9fd680d4 Fixing a test, release 2.4.1 2023-04-05 21:14:19 -05:00
Tyler Akins
24e80aedaa Bumping version
2.3.0 was already tagged
2023-04-05 20:55:16 -05:00
Tyler Akins
4311f29879 Fixing the build, some doc cleanup, update version 2023-04-05 20:54:27 -05:00
Tyler Akins
d6794de1e2 Merge pull request #57 from Swivelgames/loop-accessors
Feature: Key and Index Accessors Inside Loops
2023-04-05 20:51:13 -05:00
Joseph Dalrymple
b22baa9776 Added Functions/Helpers to README 2023-04-05 20:19:42 -05:00
Joseph Dalrymple
c28bffe708 Added @key loop accessor 2023-04-05 20:19:29 -05:00
Tyler Akins
b01fc43580 Merge pull request #60 from Swivelgames/portability
Improved portability by using /usr/bin/env pragma
2023-04-04 09:50:21 -05:00
Joseph Dalrymple
53e3208ba0 Improved portability by using /usr/bin/env pragma 2023-04-04 02:00:31 -05:00
Tyler Akins
6dc284f05a Updating URLs
Closes #53
2023-04-03 12:57:14 -05:00
Tyler Akins
a62541fc98 Adding missing character, reported by brainchild0 2022-01-26 19:07:12 -06:00
Tyler Akins
b31a97cfb1 Merge pull request #51 from brainchild0/docker
improve docker image
2022-01-26 10:00:28 -06:00
Eric Levy
71f85fa4f2 Use latest Alpine image as Docker base 2022-01-25 17:34:56 -05:00
Eric Levy
14003ba24a Introduce general improvements for Docker image 2022-01-24 20:51:27 -05:00
Tyler Akins
54195a6c6e Fixing test script to return 0 on success 2021-11-25 16:02:21 -06:00
Tyler Akins
81f9ec326d Leaning away from committing package.json
Thank you yutachaos!
2021-11-25 15:59:30 -06:00
Tyler Akins
dae1c66f8f Merge branch 'feature/added_github_action' of https://github.com/yutachaos/mo into yutachaos-feature/added_github_action 2021-11-25 15:57:52 -06:00
Tyler Akins
08576fca7b Adding script to run against official specs 2021-11-25 15:40:13 -06:00
Tyler Akins
3aa5c462f8 Quoting variable
This suggestion is brought to you by neema80 - thank you!

This closes #48.
2021-11-25 15:36:47 -06:00
yutachaos
a28ed0ccd5 Added docker image push action 2021-04-23 13:01:42 +09:00
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
Tyler Akins
9f6d3bcdab Adding an example
Shows how one can solve #29
2019-07-19 20:22:27 -05:00
Tyler Akins
eac2685632 Adding an example for adding a comma
This closes #26.
2019-04-14 06:25:06 -05:00
Tyler Akins
505570a57b Adding short options
Feature request #25
2019-03-13 20:38:00 -05:00
Tyler Akins
aabc62f1d6 Merge pull request #22 from mamercad/mamercad-curl-follow-redirects
curl needs to follow redirects
2018-03-13 07:25:13 -05:00
Mark Mercado
e672bda163 curl needs to follow redirects 2018-03-13 08:11:43 -04:00
Tyler Akins
dbefade193 Adding a version number
It is shown with --help. It's also available as $MO_VERSION when mo is
sourced.
2017-11-30 05:43:10 -06:00
Tyler Akins
57a8d41394 Merge pull request #21 from jas99/patch-1
Fix: Intall issue
2017-11-20 07:22:43 -06:00
Jaspreet Singh
5739ccd705 Fix: Intall issue
Added -L flag to curl; so as to allow following redirect.
2017-11-19 15:53:12 +05:30
Tyler Akins
f889c37316 Found another issue with strict mode 2017-11-13 13:53:38 -06:00
52 changed files with 667 additions and 220 deletions

13
.github/workflows/ci.yaml vendored Normal file
View File

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

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

2
.gitignore vendored
View File

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

194
API.md
View File

@@ -4,21 +4,49 @@ API / Function Documentation
This documentation is generated automatically from the source of [mo] thanks to [tomdoc.sh]. This documentation is generated automatically from the source of [mo] thanks to [tomdoc.sh].
mo() `mo()`
---- ------
Public: Template parser function. Writes templates to stdout. Public: Template parser function. Writes templates to stdout.
* $0 - Name of the mo file, used for getting the help message. * $0 - Name of the mo file, used for getting the help message.
* --fail-not-set - Fail upon expansion of an unset variable. Default behavior is to silently ignore and expand into empty string.
* --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.
* --help - Display a help message.
* --source=FILE - Source a file into the environment before processint template files.
* -- - Used to indicate the end of options. You may optionally use this when filenames may start with two 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_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.
@@ -26,39 +54,24 @@ Mo uses the following environment variables:
Returns nothing. Returns nothing.
files `moCallFunction()`
----- ------------------
After we encounter two hyphens together, all the rest of the arguments are files. Internal: Call a function.
* $1 - Variable for output
* $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.
MO_FAIL_ON_UNSET `moFindEndTag()`
---------------- ----------------
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
moFindEndTag()
--------------
Internal: Scan content until the right end tag is found. Creates an array with the following members: Internal: Scan content until the right end tag is found. Creates an array with the following members:
[0] = Content before end tag [0] = Content before end tag
@@ -75,8 +88,8 @@ Everything using this function uses the "standalone tags" logic.
Returns nothing. Returns nothing.
moFindString() `moFindString()`
-------------- ----------------
Internal: Find the first index of a substring. If not found, sets the index to -1. Internal: Find the first index of a substring. If not found, sets the index to -1.
@@ -87,8 +100,8 @@ Internal: Find the first index of a substring. If not found, sets the index to
Returns nothing. Returns nothing.
moFullTagName() `moFullTagName()`
--------------- -----------------
Internal: Generate a dotted name based on current context and target name. Internal: Generate a dotted name based on current context and target name.
@@ -99,8 +112,8 @@ Internal: Generate a dotted name based on current context and target name.
Returns nothing. Returns nothing.
moGetContent() `moGetContent()`
-------------- ----------------
Internal: Fetches the content to parse into a variable. Can be a list of partials for files or the content from stdin. Internal: Fetches the content to parse into a variable. Can be a list of partials for files or the content from stdin.
@@ -110,8 +123,8 @@ Internal: Fetches the content to parse into a variable. Can be a list of partia
Returns nothing. Returns nothing.
moIndentLines() `moIndentLines()`
--------------- -----------------
Internal: Indent a string, placing the indent at the beginning of every line that has any content. Internal: Indent a string, placing the indent at the beginning of every line that has any content.
@@ -122,8 +135,8 @@ Internal: Indent a string, placing the indent at the beginning of every line tha
Returns nothing. Returns nothing.
moIndirect() `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.
@@ -141,8 +154,8 @@ Examples
Returns nothing. Returns nothing.
moIndirectArray() `moIndirectArray()`
----------------- -------------------
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
@@ -161,8 +174,8 @@ Examples
Returns nothing. Returns nothing.
moIsArray() `moIsArray()`
----------- -------------
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.
@@ -173,16 +186,16 @@ Be extremely careful. Even if strict mode is enabled, it is not honored in newe
Examples Examples
var=(abc) var=(abc)
if moIsArray var; the if moIsArray var; then
echo "This is an array" echo "This is an array"
echo "Make sure you don't accidentally use $var" echo "Make sure you don't accidentally use \$var"
fi fi
Returns 0 if the name is not empty, 1 otherwise. Returns 0 if the name is not empty, 1 otherwise.
moIsFunction() `moIsFunction()`
-------------- ----------------
Internal: Determine if the given name is a defined function. Internal: Determine if the given name is a defined function.
@@ -202,8 +215,8 @@ Examples
Returns 0 if the name is a function, 1 otherwise. Returns 0 if the name is a function, 1 otherwise.
moIsStandalone() `moIsStandalone()`
---------------- ------------------
Internal: Determine if the tag is a standalone tag based on whitespace before and after the tag. Internal: Determine if the tag is a standalone tag based on whitespace before and after the tag.
@@ -223,8 +236,8 @@ Examples
Returns nothing. Returns nothing.
moJoin() `moJoin()`
-------- ----------
Internal: Join / implode an array Internal: Join / implode an array
@@ -235,19 +248,19 @@ Internal: Join / implode an array
Returns nothing. Returns nothing.
moLoadFile() `moLoadFile()`
------------ --------------
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.
moLoop() `moLoop()`
-------- ----------
Internal: Process a chunk of content some number of times. Writes output to stdout. Internal: Process a chunk of content some number of times. Writes output to stdout.
@@ -258,8 +271,8 @@ Internal: Process a chunk of content some number of times. Writes output to std
Returns nothing. Returns nothing.
moParse() `moParse()`
--------- -----------
Internal: Parse a block of text, writing the result to stdout. Internal: Parse a block of text, writing the result to stdout.
@@ -270,8 +283,14 @@ Internal: Parse a block of text, writing the result to stdout.
Returns nothing. Returns nothing.
moPartial() `moArgs`
----------- --------
Split arguments from the tag name. Arguments are passed to functions.
`moPartial()`
-------------
Internal: Process a partial. Internal: Process a partial.
@@ -291,8 +310,8 @@ Prefix all variables.
Returns nothing. Returns nothing.
moShow() `moShow()`
-------- ----------
Internal: Show an environment variable or the output of a function to stdout. Internal: Show an environment variable or the output of a function to stdout.
@@ -300,12 +319,13 @@ Limit/prefix any variables used.
* $1 - Name of environment variable or function * $1 - Name of environment variable or function
* $2 - Current context * $2 - Current context
* $3 - Arguments string if $1 is a function
Returns nothing. Returns nothing.
moSplit() `moSplit()`
--------- -----------
Internal: Split a larger string into an array. Internal: Split a larger string into an array.
@@ -317,8 +337,8 @@ Internal: Split a larger string into an array.
Returns nothing. Returns nothing.
moStandaloneAllowed() `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. 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.
@@ -331,8 +351,8 @@ Internal: Handle the content for a standalone tag. This means removing whitespa
Returns nothing. Returns nothing.
moStandaloneDenied() `moStandaloneDenied()`
-------------------- ----------------------
Internal: Handle the content for a tag that is never "standalone". No adjustments are made for newlines and whitespace. Internal: Handle the content for a tag that is never "standalone". No adjustments are made for newlines and whitespace.
@@ -344,8 +364,8 @@ Internal: Handle the content for a tag that is never "standalone". No adjustmen
Returns nothing. Returns nothing.
moTest() `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. 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.
@@ -358,8 +378,8 @@ Do not use variables without prefixes here if possible as this needs to check if
Returns 0 if the name is not empty, 1 otherwise. When MO_FALSE_IS_EMPTY is set, this returns 1 if the name is "false". Returns 0 if the name is not empty, 1 otherwise. When MO_FALSE_IS_EMPTY is set, this returns 1 if the name is "false".
moTestVarSet() `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.
@@ -368,8 +388,8 @@ Internal: Determine if a variable is assigned, even if it is assigned an empty v
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() `moTrimChars()`
------------- ---------------
Internal: Trim the leading whitespace only. Internal: Trim the leading whitespace only.
@@ -382,8 +402,8 @@ Internal: Trim the leading whitespace only.
Returns nothing. Returns nothing.
moTrimWhitespace() `moTrimWhitespace()`
------------------ --------------------
Internal: Trim leading and trailing whitespace from a string. Internal: Trim leading and trailing whitespace from a string.
@@ -393,8 +413,8 @@ Internal: Trim leading and trailing whitespace from a string.
Returns nothing. Returns nothing.
moUsage() `moUsage()`
--------- -----------
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: 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.
@@ -403,11 +423,11 @@ Internal: Displays the usage for mo. Pulls this from the file that contained th
Returns nothing. Returns nothing.
MO_ORIGINAL_COMMAND `MO_ORIGINAL_COMMAND`
------------------- ---------------------
Save the original command's path for usage later 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

7
Dockerfile Normal file
View File

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

@@ -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. 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 # Download
curl -sS https://git.io/get-mo -o mo curl -sSL https://raw.githubusercontent.com/tests-always-included/mo/master/mo -o mo
# Make executable # Make executable
chmod +x mo 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/`. 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 # Download
curl -sS https://git.io/get-mo -o mo curl -sSL https://raw.githubusercontent.com/tests-always-included/mo/master/mo -o mo
# Make executable # Make executable
chmod +x mo 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. 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 # Download
curl -sS https://git.io/get-mo -o mo curl -sSL https://raw.githubusercontent.com/tests-always-included/mo/master/mo -o mo
# Move into your project folder # Move into your project folder
mv mo ~/projects/amazing-things/lib/ 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). 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).
#!/bin/bash #!/usr/bin/env bash
cd "$(dirname "$0")" # Go to the script's directory cd "$(dirname "$0")" # Go to the script's directory
export TEST="This is a test" export TEST="This is a test"
echo "Your message: {{TEST}}" | ../mo 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). Using arrays adds a slight level of complexity. *You must source `mo`.* Look at [`demo/using-arrays`](demo/using-arrays).
#!/bin/bash #!/usr/bin/env bash
cd "$(dirname "$0")" # Go to the script's directory cd "$(dirname "$0")" # Go to the script's directory
export ARRAY=( one two "three three three" four five ) export ARRAY=( one two "three three three" four five )
. ../mo # This loads the "mo" function . ../mo # This loads the "mo" function
@@ -118,6 +118,77 @@ 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. 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 Concessions
----------- -----------

View File

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

View File

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

29
demo/function-args Executable file
View File

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

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,4 +1,4 @@
#!/bin/bash #!/usr/bin/env bash
cd "$(dirname "$0")" # Go to the script's directory cd "$(dirname "$0")" # Go to the script's directory

39
demo/function-for-building-json Executable file
View File

@@ -0,0 +1,39 @@
#!/usr/bin/env bash
cd "$(dirname "$0")" # Go to the script's directory
# Detect if this is the first item and write a comma if it is.
# Normally, I would track this using a variable, like so:
#
# COMMA_IF_NOT_FIRST_FLAG=false
# COMMA_IF_NOT_FIRST() {
# $COMMA_IF_NOT_FIRST || echo ","
# COMMA_IF_NOT_FIRST_FLAG=true
# }
#
# Since this function executes in a subshell, that approach will not work.
# Instead, we peek inside mo and see what is being processed. If the variable
# name in moParse() changes, this will need to get updated as well. An
# alternate variable that is usable is context, but that is in moLoop() and is
# two levels levels deep instead of just one.
COMMA_IF_NOT_FIRST() {
[[ "${moCurrent#*.}" != "0" ]] && echo ","
}
# Create an array that will be embedded into the JSON. If you are manipulating
# JSON, might I suggest you look at using jq? It's really good at processing
# JSON.
items=(
'{"position":"one","url":"1"}'
'{"position":"two","url":"2"}'
'{"position":"three","url":"3"}'
)
. ../mo
cat <<EOF | mo
{
{{#items}}
{{COMMA_IF_NOT_FIRST}}
{{.}}
{{/items}}
}
EOF

48
demo/function-for-foreach Executable file
View File

@@ -0,0 +1,48 @@
#!/usr/bin/env bash
# Example for how #29 can get implemented.
cd "$(dirname "$0")" # Go to the script's directory
foreach() {
# Trying to use unique names
local foreachSourceName foreachIterator foreachEvalString foreachContent
foreachContent=$(cat)
if [[ "$2" != "as" && "$2" != "in" ]]; then
echo "Invalid foreach - bad format."
elif [[ "$(declare -p "$1")" != "declare -"[aA]* ]]; then
echo "$1 is not an array"
else
foreachSourceName="${1}[@]"
for foreachIterator in "${!foreachSourceName}"; do
foreachEvalString=$(declare -p "$foreachIterator")
foreachEvalString="declare -A $3=${foreachEvalString#*=}"
eval "$foreachEvalString"
echo "$foreachContent" | mo
done
fi
}
# The links are associative arrays
declare -A resque hub rip
resque=([name]=Resque [url]=http://example.com/resque)
hub=([name]=Hub [url]=http://example.com/hub)
rip=([name]=Rip [url]=http://example.com/rip)
# This is a list of the link arrays
links=(resque hub rip)
# Source mo in order to work with arrays
. ../mo
# Process the template
cat <<EOF | mo --allow-function-arguments
Here are your links:
{{#foreach links as link}}
* [{{link.name}}]({{link.url}})
{{/foreach}}
EOF

View File

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

View File

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

View File

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

View File

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

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 #!/usr/bin/env 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

198
mo
View File

@@ -11,12 +11,21 @@
#/ #/
#/ Simple usage: #/ Simple usage:
#/ #/
#/ mo [--false] [--help] [--source=FILE] filenames... #/ mo [OPTIONS] filenames...
#/ #/
#/ --fail-not-set - Fail upon expansion of an unset variable. #/ Options:
#/ --false - Treat the string "false" as empty for conditionals. #/
#/ --help - This message. #/ -u, --fail-not-set
#/ --source=FILE - Load FILE into the environment before processing templates. #/ Fail upon expansion of an unset variable.
#/ -x, --fail-on-function
#/ Fail when a function returns a non-zero status code.
#/ -e, --false
#/ Treat the string "false" as empty for conditionals.
#/ -h, --help
#/ This message.
#/ -s=FILE, --source=FILE
#/ Load FILE into the environment before processing templates.
#/ Can be used multiple times.
# #
# 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.
@@ -29,37 +38,63 @@
# 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.
# --fail-not-set - Fail upon expansion of an unset variable. Default behavior
# is to silently ignore and expand into empty string.
# --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.
# --help - Display a help message.
# --source=FILE - Source a file into the environment before processint
# template files.
# -- - Used to indicate the end of options. You may optionally
# use this when filenames may start with two 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 # MO_ALLOW_FUNCTION_ARGUMENTS - When set to a non-empty value, this allows
# - When set to a non-empty value, this allows functions # functions referenced in templates to receive additional
# referenced in templates to receive additional
# options and arguments. This puts the content from the # options and arguments. This puts the content from the
# template directly into an eval statement. Use with # template directly into an eval statement. Use with extreme
# extreme care. # care.
# MO_FAIL_ON_UNSET - When set to a non-empty value, expansion of an unset # MO_FUNCTION_ARGS - Arguments passed to the function
# env variable will be aborted with an error. # MO_FAIL_ON_FUNCTION - If a function returns a non-zero status code, abort
# MO_FALSE_IS_EMPTY - When set to a non-empty value, the string "false" # with an error.
# will be treated as an empty value for the purposes # MO_FAIL_ON_UNSET - When set to a non-empty value, expansion of an unset env
# of conditionals. # variable will be aborted with an error.
# MO_ORIGINAL_COMMAND - Used to find the `mo` program in order to generate # MO_FALSE_IS_EMPTY - When set to a non-empty value, the string "false" will be
# a help message. # 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.
mo() ( mo() (
@@ -74,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
@@ -89,18 +124,27 @@ mo() (
MO_ALLOW_FUNCTION_ARGUMENTS=true MO_ALLOW_FUNCTION_ARGUMENTS=true
;; ;;
--fail-not-set) -u | --fail-not-set)
# shellcheck disable=SC2030 # shellcheck disable=SC2030
MO_FAIL_ON_UNSET=true MO_FAIL_ON_UNSET=true
;; ;;
--false) -x | --fail-on-function)
# shellcheck disable=SC2030
MO_FAIL_ON_FUNCTION=true
;;
-e | --false)
# shellcheck disable=SC2030 # shellcheck disable=SC2030
MO_FALSE_IS_EMPTY=true MO_FALSE_IS_EMPTY=true
;; ;;
--source=*) -s=* | --source=*)
if [[ "$arg" == --source=* ]]; then
f2source="${arg#--source=}" f2source="${arg#--source=}"
else
f2source="${arg#-s=}"
fi
if [[ -f "$f2source" ]]; then if [[ -f "$f2source" ]]; then
# shellcheck disable=SC1090 # shellcheck disable=SC1090
@@ -112,12 +156,12 @@ mo() (
;; ;;
--) --)
# 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
@@ -132,25 +176,39 @@ mo() (
# 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"
} }
@@ -202,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]}}"
@@ -243,7 +302,7 @@ moFindEndTag() {
moFindString() { moFindString() {
local pos string local pos string
string=${2%%$3*} string=${2%%"$3"*}
[[ "$string" == "$2" ]] && pos=-1 || pos=${#string} [[ "$string" == "$2" ]] && pos=-1 || pos=${#string}
local "$1" && moIndirect "$1" "$pos" local "$1" && moIndirect "$1" "$pos"
} }
@@ -273,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"
} }
@@ -463,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
@@ -554,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() {
@@ -565,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
@@ -606,7 +666,7 @@ moLoop() {
moParse() { moParse() {
# Keep naming variables mo* here to not overwrite needed variables # Keep naming variables mo* here to not overwrite needed variables
# used in the string replacements # used in the string replacements
local moArgs moBlock moContent moCurrent moIsBeginning moNextIsBeginning moTag local moArgs moBlock moContent moCurrent moIsBeginning moNextIsBeginning moTag moKey
moCurrent=$2 moCurrent=$2
moIsBeginning=$3 moIsBeginning=$3
@@ -637,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
@@ -724,6 +782,17 @@ moParse() {
moShow "$moTag" "$moCurrent" 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 # Normal environment variable or function call
moStandaloneDenied moContent "${moContent[@]}" moStandaloneDenied moContent "${moContent[@]}"
@@ -772,6 +841,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]}}
@@ -825,11 +895,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
@@ -904,6 +974,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]}}"
@@ -1030,11 +1101,14 @@ moTrimWhitespace() {
# Returns nothing. # Returns nothing.
moUsage() { moUsage() {
grep '^#/' "${MO_ORIGINAL_COMMAND}" | cut -c 4- grep '^#/' "${MO_ORIGINAL_COMMAND}" | cut -c 4-
echo ""
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.4.1"
# 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,10 +0,0 @@
{
"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"
}
}

20
run-spec Executable file
View File

@@ -0,0 +1,20 @@
#!/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; var output, partials, script;
script = [ script = [
'#!/bin/bash' '#!/usr/bin/env bash'
]; ];
partials = test.partials || {}; partials = test.partials || {};

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"
@@ -36,4 +40,6 @@ done
echo "" echo ""
echo "Pass: $PASS" echo "Pass: $PASS"
echo "Fail: $FAIL" echo "Fail: $FAIL"
[[ $FAIL -gt 0 ]] && exit 1 if [[ $FAIL -gt 0 ]]; then
exit 1
fi

View File

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

View File

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

4
tests/assoc-array.env Normal file
View File

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

View File

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

View File

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

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 -- --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

@@ -9,9 +9,20 @@ Learn more about mustache templates at https://mustache.github.io/
Simple usage: Simple usage:
mo [--false] [--help] [--source=FILE] filenames... mo [OPTIONS] filenames...
--fail-not-set - Fail upon expansion of an unset variable. Options:
--false - Treat the string "false" as empty for conditionals.
--help - This message. -u, --fail-not-set
--source=FILE - Load FILE into the environment before processing templates. Fail upon expansion of an unset variable.
-x, --fail-on-function
Fail when a function returns a non-zero status code.
-e, --false
Treat the string "false" as empty for conditionals.
-h, --help
This message.
-s=FILE, --source=FILE
Load FILE into the environment before processing templates.
Can be used multiple times.
MO_VERSION=2.4.1

View File

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

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,6 +1,6 @@
#!/usr/bin/env bash #!/usr/bin/env bash
cd "${0%/*}" cd "${0%/*}" || exit 1
../mo partial-missing.template 2>&1 ../mo partial-missing.template 2>&1
if [[ $? -ne 1 ]]; then if [[ $? -ne 1 ]]; then

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,6 +1,6 @@
#!/usr/bin/env bash #!/usr/bin/env bash
cd "${0%/*}" cd "${0%/*}" || exit 1
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
export ASSOC_ARR=([a]=AAA [b]=BBB) # Can not export associative arrays, otherwise they turn into indexed arrays
ASSOC_ARR=([a]=AAA [b]=BBB)