tests-always-included / mo

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

(Close Pending #62) Parity: Looping a List of Associative Arrays #58

Closed Swivelgames closed 4 months ago

Swivelgames commented 1 year ago

Feature

In order to bring parity with mo and traditional mustache implementations, this feature will extract the keys of an associative array when looping. This feature alone will enable mo to work with meaningful data structures and elevates it to a proper Mustache implementation.

Specifically, derived from the official documentation (See "Non-Empty Lists"):

declare -a navItems nonFunctional
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) # <- List of arrays
stringArray=("homeLink" "otherLink") # <- List of strings

Template:

# Links
{{#navItems}}
- [{{title}}]({{url}})
{{/navItems}}

# Standard List
{{#stringArray}}
- {{.}}
{{/stringArray}}

Output:

# Links
- [Home](https://www.example.com/home)
- [Other Link](https://www.example.com/other-page)

# Standard List
- homeLink
- otherLink

Justificaton

Implementation Considerations

Bash has supported nested arrays since Bash 4.3. Because of the way that Bash is implemented, this addition can leverage built-in loops to iterate over Lists and extract fields from their Associative Array members.

Swivelgames commented 1 year ago

UPDATE Just tested this with Bash 3.1 and Bash 3.2 and mo executed successfully!

Just like the other Associative Array features already in mo, these features are compatible with Bash 4.0-5.1.

This was implemented successfully in #59

fidian commented 1 year ago

The issue I have is that navItems and stringArray are both simply arrays of strings and there's no way to really tell that one's values are names of other arrays and the other is just normal strings.

Sample environment variables - add these two lines to the bottom

title="This is a title"
url="URL"

Add these lines to the bottom of your template

# Standard list again but now I can't reference `$title` nor `$url` that I defined.
{{#stringArray}}
- [{{title}}]({{url}})
{{/stringArray}}

mo does not really have scoping of variables because Bash itself doesn't natively support data structures this complex. What I think you want is a way to do an indirect lookup of a variable. Something like how bash uses ${!variable}, you might want the equivalent of Mustache's lookup, which I would be very interested in seeing.

If I merge this pull request as-is, it will be breaking change because the current behavior doesn't allow for looking up values automatically and could easily cause problems with people feeding in lots of data to their templates and iterating across arrays whose values match variable names.

fidian commented 1 year ago

I'd like to propose some use cases to use as examples. For both of these I included {{INDIRECT variableName [key]}} indicating I wanted to get a variable or I wanted to get a specific key from a variable. Syntax here isn't important - just the idea that the information is needed.

. ./mo

# Use case 1: look up values in an associative array
declare -A employee
employee[123]="Tyler"
employee[456]="Joseph"
employees=(123 456)

cat <<EOF | mo
Employee list:
{{#employees}}
    - {{INDIRECT employee .}}
{{/employees}}
EOF

# Use case 2: look up another variable for internationalization
welcomeMessage="Gruß dich!"
farewellMessage="Bis spater"
messages=(welcomeMessage farewellMessage)

cat <<EOF | mo
List of messages:
{{#messages}}
* {{INDIRECT .}}
{{/messages}}
EOF

# Use case 3, getting related information
declare -A pages urls
pages["home"]="Home page"
urls["home"]="http://localhost/"
pages["search"]="Search for content"
urls["search"]="http://localhost/search/"

cat <<EOF | mo
Pages on the site:
{{#pages}}
* [{{.}}]({{INDIRECT urls @key}})
{{/pages}}
EOF

Hopefully this explains some of the ways I've seen the templating system get used. Normally a function is created to do the work, but having a general purpose mechanism in place would be ideal. Getting function arguments parsed and supporting some sort of generic indirect syntax (perhaps steal from C's use of *variable or PHP's use of $$variable.

fidian commented 1 year ago

I think that #62 solved this problem, though in a different way. Would you concur?