tests-always-included / mo

Mustache templates in pure bash
Other
563 stars 67 forks source link

Looping: Pass current element to function calls #55

Closed Swivelgames closed 1 year ago

Swivelgames commented 1 year ago

It would be a huge help if it was possible to pass in the current element to a function call when looping. This would greatly simplify things when looping.

I spent quite a bit of time studying mo, and went ahead and implemented the idea in #54, along with tests and a demo. Thoughts?

Here's an example:

#!/usr/bin/env bash
# Example for how #54 can get implemented.

cd "$(dirname "$0")" # Go to the script's directory

# The links are associative arrays
declare -a links
declare -A names urls

links+=(resque)
names[resque]=Resque
urls[resque]=http://example.com/resque

links+=(hub)
names[hub]=Hub
urls[hub]=http://example.com/hub

links+=(rip)
names[rip]=Rip
urls[rip]=http://example.com/rip

# helper functions
link_name() {
    # Trying to use unique names
    local key
    key=$(cat)
    echo ${names[$key]}
}

link_url() {
    # Trying to use unique names
    local key
    key=$(cat)
    echo ${urls[$key]}
}

# Source mo in order to work with arrays
. ../mo

# Process the template
cat <<EOF | mo --allow-function-arguments
Here are your links:
{{#links}}
 * [{{link_name}}]({{link_url}})
{{/links}}

EOF
Swivelgames commented 1 year ago

I'm actually working on an alternative to this that might make it obsolete. Trying to implement something that allows for a bit broader of a solution, including passing keys and values of associative arrays, and nested loops.

I'm not sure if that is desired, but let me know either way!

If it is, we probably don't want to merge #54.

Swivelgames commented 1 year ago

Updated #54! Here's the rundown on the changes:

Click the arrows below to see examples.


Output Key of Array

Added `{{$}}` syntax to output the key of the element. Useful for Associative Arrays: #### Example: ```bash declare -A myArray myArray[foo]=bar {{#myArray}} - {{$}}: {{.}} {{/myArray}} ``` #### Outputs: ```markdown - foo: bar ```

Cross-reference Associative Arrays:

Reference a sister-array using the key of the first array: #### Example: ```bash declare -A titles links titles[foo]="Foo" links[foo]="https://www.example.com/foo" {{#links}} - ({{titles[$]}})[{{.}}] {{/links}} ``` #### Outputs: ```markdown - [Foo](https://www.example.com/foo) ```
Reference a sister-array using the _value_ of the first array: #### Example: ```bash declare -A titles links titles=(foo bar) links[foo]="https://www.example.com/foo" links[bar]="https://www.example.com/bar" {{#titles}} - ({{.}})[{{links[.]}}] {{/titles}} ``` #### Outputs: ```markdown - [foo](https://www.example.com/foo) - [bar](https://www.example.com/bar) ```

Expand Array of Associative Arrays to Scope

Use an associative array's keys as first-class variables #### Example: ```bash declare -a navItems declare -A homeLink otherLink homeLink[title]="Home" homeLink[url]="https://www.example.com/home" otherLink[title]="Other Link" otherLink[url]="https://www.example.com/other-page" navItems=(homeLink otherLink) {{#navItems}} - [{{title}}]({{url}}) {{/navItems}} ``` #### Outputs: ```markdown - [Home](https://www.example.com/home) - [Other Link](https://www.example.com/other-page) ```

Expanded Associative Arrays can Reference Other Arrays

Reference other arrays within expanded associative arrays #### Using `declare -a` for sub-elements > By declaring the key of an element as an array, `mo` will now treat it as an array: > ```bash > declare -a links > homeSections[links]="resqueLink hubLink" # <- Treated as an array > ``` #### Example: ```bash # Declare links declare -A resqueLink hubLink ripLink resqueLink[title]="Resque" resqueLink[url]="http://example.com/resque" hubLink[title]="Hub" hubLink[url]="http://example.com/hub" ripLink[title]="Rip" ripLink[url]="http://example.com/rip" # Declare sections declare -a sections declare -A homeSection otherSection declare -a links # Declare [links] as array references homeSection[name]="Home Section" homeSection[links]="resqueLink hubLink" otherSection[name]="Other Section" otherSection[links]="ripLink" sections=(homeSection otherSection) {{#sections}} # {{name}} {{#links}} - [{{title}}]({{url}}) {{/links}} {{/sections}} ``` #### Outputs: ```markdown # Home Section - [Resque](http://example.com/resque) - [Hub](http://example.com/hub) # Other Section ripLink - [Rip](http://example.com/rip) ```

Functions called within loops get the key and value that they're looping inside of as arguments

With `--allow-function-arguments`, the last two arguments passed to a function are the key and value respectively. #### Example: ```bash func1() { echo "- $2" } func2() { echo "- [$1 ~ $3]($2)" } declare -a arr arr=(foo bar) declare -A assoc assoc[foo]="hello" assoc[bar]="world" {{#arr}} {{func1}} {{/arr}} {{#assoc}} {{func2 Arrrgh}} {{/assoc}] ``` #### Outputs: ```markdown - foo - bar - [Arrrgh - hello](foo) - [Arrrgh - world](bar) ```
MO_LOOP and MO_LOOP_KEYS provide the key and value for the current element #### Example: > MO_LOOP and MO_LOOP_KEYS work like a stack, in that the zeroeth element is the _current_ value and key (respectively), and each element after that represents a level above it in the loop. ```bash declare -A assoc assoc[strawberry]="red" assoc[watermelon]="green" uppercaseKey() { echo "${MO_LOOP_KEYS[0]^^}" } uppercaseValue() { echo "${MO_LOOP[0]^^}" } {{#assoc}} - a {{uppercaseKey}} is {{uppercaseValue}} {{/assoc}} ``` #### Outputs: ``` - a STRAWBERRY is RED - a WATERMELON is GREEN ```
MO_LOOP and MO_LOOP_KEYS provide a context stack for nested loops #### Example: > MO_LOOP and MO_LOOP_KEYS work like a stack, in that the zeroeth element is the _current_ value and key (respectively), and each element after that represents a level above it in the loop. ```bash declare -A assoc assoc[strawberry]="hello" assoc[watermelon]="world" declare -a arr arr=(foo bar) func() { echo "Key Stack: ${MO_LOOP_KEYS[@]}" echo "Value Stack: ${MO_LOOP[@]}" } {{#assoc}} {{#arr}} {{func}} {{/arr}} {{/assoc}} ``` #### Outputs: ``` Key Stack: 0 strawberry Value Stack: foo hello Key Stack: 1 strawberry Value Stack: foo hello Key Stack: 0 watermelon Value Stack: bar world Key Stack: 1 watermelon Value Stack: bar world ```
fidian commented 1 year ago

I think it might be better to split this to separate issues to keep the discussions isolated.

{{$}} syntax seems different from any other mustache implementation and doesn't really fit Bash because $$ is the process ID. Handlebars.js uses @key, which would work.

mo supports Bash 3.2 for Apple, which does not support associative arrays. Have you tested the referencing of a sister array with older versions of Bash? If this uses the same syntax as the first issue ($ vs @key) and doesn't complain in Bash 3.2 (I don't expect it to work, just not complain), then I see no issues.

Array of associate arrays - how will mo differentiate between an array of associative arrays and an array that happens to contain array variable names?

When expanding associative arrays, how will you determine if I am referencing arrays versus having words in a list?

Functions called within loops should not automatically get parameters. Imagine a function with an optional argument, then how do you determine if the optional argument was passed? If you want the function to get the value, you should use {{func2 @key .}} or similar.

MO_LOOP and MO_LOOP_KEYS: I'm against adding these non-standard iteration values to Mustache because they rely heavily on the nesting of the templates. What use case do you have for these?

It looks like you're asking a lot out of logic-less templates, to the point that this appears to be adding logic into templates. If you would like to continue the discussion, please create issues that individually span smaller scopes.

Swivelgames commented 1 year ago

That's kind of the response I was expecting. I'll work to isolate these changes into separate PRs and create separate issues to discuss them at their merit.

Most of this is not related to adding logic per se, but more to bring it in-line with other Mustache implementations. But I'll elaborate in the other threads! I'll go ahead and close this issue in favor of those so we don't continue to branch this issue thread.

And, sincerely, thanks a lot for the response!