Wednesday, May 6, 2020

mustache syntax is too simple for it's own good


After trying to improve some DocFx mustache templates to generate API documentation, it is my opinion that the `mustache` template syntax, in the interest of being overly simple, causes anything more than trivial templates to be over-complicated, and the possible formats you can achieve to be overly limited. (details below)

The first problem with `mustache` is that there are no conditionals. As a result, the `mustache` loop construct is constantly bastardized to be a conditional, by looping over a non-list. Any non-trivial template requires conditionals, and the documentation templates are absolutely full of these fake-loop conditionals.

given js data:

{elements = [ 0 = {name:"fred"}, 1 ={name:"barney"}};

Here is code to create a table, only if there are elements:

{{#elements.0}}     
   <table>
{{/elements.0}}
       {{#elements}}
           <tr><td>{{name}} </td></tr>
       {{/elements}}
{{#elements.0}}
   <table>
{{/elements.0}}

The second problem with `mustache` is that there is no loop context variable name. Instead, there is a "context", and when looping over a subset, the context is set to the element contents. The problem is, names that appear in the nested scope become inaccessible in the outer scope.

This happens all over certain types of templates, such as in the DocFx documentation generator, because the dataset reuses tax names such as `children` and `name` and `id` in every nesting context.

Consider the following javascript dataset:


{ name = "Papa James", children = [
    {  name = "Fred",  
          children = [ name = "Baby Louise" ]}   
    {  name = "Barney", 
          children = [ name = "Baby Anne"] }
   ]}

Here is the content we wish to produce:

Baby Louise has a grandpa named Papa James.
Baby Anne has a grandpa named Papa James.

As far as I can see, it's not possible to create this content with mustache, and this example explains why...

# context is top
{{#children}}    # loop through Papa James's children
  {{#children}}    # loop through next level children
     {{name}} has a grandpa named {{name????}}   
  {{/children}}
{{/children}}


There is no way to access the outer grandpa name, because the inner "name" tag shadows the outer "name" tag, making it inaccessible. The only way to fix this is to redesign the data-hierarchy to never repeat names, which ends up making the data-hierarchy inflexible. Either way you lose.

Essentially every template system other than `mustache` is free of this problem . For example, a DocFx Liquid template for this trivial example would be:


{% for level0 in children %}
  {% for level1 in x.children %}  
     {% level1.name %} has a grandpa named {% name %}
  {% endfor %}
{% endfor %}

While there are certainly simple templates where mustache's syntax is sufficient, I believe their overly rigid simplicity is simply idiotic. 

The lack of conditionals isn't stopping users from making loops into conditionals, and it is confusing and contorting the meaning and readability of the template code in the process, because when you see {{#foo}}, it is very hard to tell if this is a single element conditional, or a multi-element loop with a context. 

{{?isClass}}
   conditional occurs once
   {{#stuffInClass}}
      loop occurs once for each subelement
   {{/stuffInClass}}
{{/isClass}}

Further, it would be easy to support either accessing parent scopes...

{{#children}}
  {{#children}}
    {{name}} has a grandpa named {{~.~.name}}
  {{/children}}
{{/children}}

...or naming loop scopes (or both):

{{#children as level0}}
  {{#level0.children as level1}}
     {{level1.name}} has a grandpa named {{name}}
  {{/level0.children}}
{{/children}}









1 comment: