tests-always-included / mo

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

Nicer Iterators #29

Closed fidian closed 4 months ago

fidian commented 5 years ago

Split from #28 by @sc0ttj:

First, here is the custom iterator I am using for hashes in arrays:

# ITEMS():  A custom mustache iterator called ITEMS:
#           Uses special keywords ITEMS and ITEM.
#
# Usage:
#
# Set an array of hashes, which must be called ITEMS:
#
#   declare -A archive=([url]="archive.html" [name]="Archive page")
#   declare -A contact=([url]="contact.html" [name]="Contact page")
#   ITEMS=(archive contact)
#
# You can then use nested array data in your mustache templates like so:
#
#   {{#ITEMS}}
#     {{ITEM.name}} is at {{ITEM.url}}
#   {{/ITEMS}}
#
# Finally, build the output like so:
#
#   cat path/to/some-file.mustache | mo > some-file.html
#
function ITEMS {
  # The block contents come in through standard input. Capture it here.
  local content="$(cat)"
  local length=${#ITEMS[@]}
  local i=0
  # Get list of items
  for ITEM in "${ITEMS[@]}"; do
    # String replace ITEM_ with the name
    # This changes everything in the content block of the template.
    # It rewrites {{ITEM.name}} into {{foo.name}}, for example - where
    # 'foo' is a hash with they key 'name'.
    # You can prefix your environment variables and do other things as well.
    echo -ne "$content" | sed "s/{{ITEM/{{${ITEM}/g"
    i=$(($i + 1))
    # if not last item in array, add comma
    if [ "$AUTO_APPEND" != '' ];then
      [ "$i" != "$length" ] && echo "$AUTO_APPEND" || echo ''
    fi
  done
}
# export the function so `mo` can use it
export -f ITEMS

I'd love to be able to define the array and variable names used by my custom iterator as a param for that iterator, so that I can make my code nicer... I'd like to rename my ITEMS iterator to {{#foreach}}, and use it like so:

{{#foreach link in links}}
  <a href="{{link.url}}">{{link.name}}</a>
{{/foreach}}

^ where links is the array of hashes I want to iterate over.

..I've been unable to update my ITEMS function to achieve this, would really, really love some help.

I'd also love for this iterator to provide various vars "for free" when inside the iteration body, such as {{#odd}}, {{#even}}, {{#first}}, {{#last}}, {{index}}, which could be used like so:

{{#foreach link in links}}
  <a href="{{link.url}}">{{link.name}}</a>{{^last}},{{/last}}
{{/foreach}}

^ obviously, this would append a comma after each link, except the last one...

..a 'custom' iterator that worked in that way would be nice enough to include in mo by default, as a {{#foreach}} operator/iterator.

The benefit of this is obviously that users of mo will no longer need to laboriously scaffold out some helper function to do thier looping stuff.. They simply define the array (or array of hashes) and pass it in as a paramater... Soooo much easier.

fidian commented 5 years ago

This appears to be the same as #31 and #27, where you want to have parameters passed to arbitrary things, which is difficult. If parameters were able to be passed to partials and functions, would that give you everything you need?

sc0ttj commented 5 years ago

Sorry to be a pain, but I don't think this is the same as the others - although yeah, it's totally related ..and somewhat similar in terms of implementation, and even usage...

But I think this is a little different as this would be a set of features that live inside mo itself... rather than mo handling the passing of params to external funcs (or files/partials).. The reason I think this iterator issue is different is that it wouldn't be up to users to define any custom functions, as the foreach iterator would be "built in" ..

Also, unlike custom functions defined by the user, it would have a fixed api - only take $1 as $3 as params, and fail if $2 is not "as" and "$3" is not an array .. It would also provide various features "for free" inside the iterator body, such as {{#even}}, {{^even}}, {{#first}}, {{^first}}, {{#last}}, {{^last}} (and so on)..

So, I don't think implementing the other issues would give me all I need - I (and all other users) would still need to implement the iterator, even if the {{foreach foo as bar}} syntax was recognised and parsed (as fixing the others might achieve)..

All that being said, I think this one is probably outside the objectives and spec of mo, but would still be very nice.. I will work on implementing this on my project, and get back to you if I have something nice.

fidian commented 5 years ago

I assure you that you're certainly not a pain. :-) I value your feedback and I have been thinking about this problem for most of the week. It's a great idea and one I think should get implemented somewhere.

Thank you for explaining how I missed the point of the request. To summarize, there's multiple associative arrays and a list of associative array names. What's desired is an easier way to iterate through that data structure. Now that I get it, I see how the associative-arrays and function-for-advanced-looping examples don't fit these needs, but somewhat get close. One loops through the items in an associative array. The second is much closer but a shorthand or syntactic sugar is desired to make this really make sense within the templates.

You predicted correctly that I'm against adding more functions to mo for the lame reason that they are not part of the spec. This can still be done, but it will remain outside the mo executable. Two key things need to be done.

  1. mo must be called with --allow-function-arguments or set the MO_ALLOW_FUNCTION_ARGUMENTS environment variable. This could open up security holes, so make sure you trust the machine.
  2. A foreach function needs to be added to the environment. To make things easier, I've done this very thing in commit 9f6d3bc. You could extend it to add the even, first, last and other properties.

There's always the possibility of extending mo with a library ... mo-extra or something. This function would certainly qualify as being useful enough.

sc0ttj commented 5 years ago

The example you added looks great, will be just what I need.. Thanks.. Will defo have a play with it later :)

Cheers