PageRenderTime 52ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

/Articles/WhyMustacheFilters.md

http://github.com/groue/GRMustache
Markdown | 205 lines | 125 code | 80 blank | 0 comment | 0 complexity | 7d9c6f8488fcbac2f3fdff84750251c7 MD5 | raw file
  1. # Mustache support for "Filters"
  2. Here are a few arguments for the introduction of "filters" in Mustache, and a description of what they should be, as a contribution to the [open discussion](http://github.com/mustache/spec/issues/41) on the mustache/spec repository.
  3. GRMustache provides an implementation of [filters](../Guides/filters.md) that fully cover all the points described here.
  4. 1. Why filters are good for Mustache
  5. 2. Why Mustache tags should contain expressions, not statements
  6. 3. Parsing GRMustache expressions
  7. 4. The details
  8. ## 1. Why filters are good for Mustache
  9. ### History of user-provided code: lambdas
  10. Mustache users today have a single way to have their own code executed while rendering a template: "Mustache lambdas".
  11. Lambdas operate at the *template canvas* level: they can alter raw portions of a template, insert and process raw text, add and remove mustache tags, and their output is then processed by the Mustache engine which renders it.
  12. One can for instance write a lambda that turns `{{#link}}{{name}}{{/link}}` into `<a href="{{url}}">{{name}}</a>`, which is later rendered as `<a href="/stuff/1">blah</a>`.
  13. However, lambdas do not have access to the *view model* level. They can not, for instance, render the uppercase version of a value.
  14. > Precisely: should a lambda evaluate the inner rendering of a section, turn it into uppercase, and provide the result to the Mustache engine, there is the possibility that the view model data would contain mustache tags that would be then processed by the Mustache engine. An application user could "attack" the rendering engine by setting his name to `{{pwned}}`, for instance.
  15. ### The consequences of a drastic interpretation of "logiclessness"
  16. The inability for library user's to provide code that operates on the view model level has until now be considered positive and "pure", because of the "logiclessness" of Mustache. Yes, there is no logic code in the template itself, no "if", no "while", no operators, etc. Actually, there is no code at all in a Mustache template.
  17. However, the interpretation of "logiclessness" becomes uselessly drastic, and painful to the library user when the view model is made 100% responsible for the rendering of value tags and the control of section tags. The problem arises at the the *view model preparation phase*, when the library user has to prepare all the values that will be interpreted by the Mustache engine. The preparation phase becomes a chore when the user has to process many values in the same way.
  18. For instance, a model may hold a dozen named numerical values, that should be rendered in a formatted way. It thus has to be turned into a view model holding a dozen named formatted values, with the necessity of duplicated code. I, as a Mustache implementor, have received many feature requests on this topic. There is more evidence that this is a recurrent issue with Mustache at: [mustache/spec/issues/41](https://github.com/mustache/spec/issues/41) and [bobthecow/mustache.php/pull/102](https://github.com/bobthecow/mustache.php/pull/102).
  19. Another common chore is preparing the input in order to test if a collection is empty or not. See [mustache/spec/issues/23](https://github.com/mustache/spec/pull/23), and [defunkt/mustache/issues/4](https://github.com/defunkt/mustache/issues/4).
  20. Another chore is processing model arrays so that the view model contains arrays whose items know about their index in the array. Again, if many model arrays should be processed this way, we again have a duplicated code problem. Evidence can be found at [janl/mustache.js/pull/205](https://github.com/janl/mustache.js/pull/205), [groue/GRMustache/issues/14](https://github.com/groue/GRMustache/issues/14), [groue/GRMustache/issues/18](https://github.com/groue/GRMustache/issues/18), and the language extension implemented by [samskivert/jmustache](https://github.com/samskivert/jmustache) and [christophercotton/GRMustache](https://github.com/christophercotton/GRMustache).
  21. Some would say: "use your language features, and dynamically add the needed properties to your objects". This argument is invalid for many reasons, and primarily because Mustache is a language-agnostic template language, and some host languages do not sport any dynamic features.
  22. Some readers might be interested by a [more general rebuttal of the drastic interpretation of Mustache "logiclessness"](TheNatureOfLogicLessTemplates.md).
  23. ### Filters empower the library user, and Mustache itself
  24. This is why Mustache should provide a way to let the library user provide code that processes the view model values before they enter the rendering engine, and express directly in the template how the view model values should be processed.
  25. These code chunks would be called *filters*, because they are functions that take a mustache-interpretable value as an input, and return an other mustache-interpretable value. In the template itself, tags would contain *filtered expressions* that would tell the rendering engine which filters should be applied to the raw view model values.
  26. Since the role of filters is to relieve view models from providing "final" values, filters do not conceptually belong to them. They instead belong the template: for instance, a template would provide a filter for rendering uppercase values. Now all the view models are relieved from the burden of computing those. Another template would provide a filter for rendering array indexes. View models would then provide raw arrays, and the template would be able to render item indexes. (For real examples, check [number formatting](../Guides/sample_code/number_formatting.md) and [indexes](../Guides/sample_code/indexes.md) sample code).
  27. Since filters belong to the templates, Mustache can provide a *standard library* of filters, that would be pre-baked into all Mustache templates.
  28. Since filters are not tied to the view model, they are *reusable*.
  29. ## 2. Why Mustache tags should contain expressions, not statements
  30. ### Composition
  31. There are major differences between *expressions* and *statements*. Statements chain, one after the other, independently, and can not provide any value. Statements *perform* and return nothing. Expressions are a different kind of beast: by essence, they provide *values*, and can be *composed* from other expressions.
  32. Obviously, Mustache needs values: variable tags need a value that they can render, section tags need a value that they can test, loop, or make enter the context stack. Since only expressions provide with values, they are what Mustache need.
  33. Mustache already has two kinds of expressions: keys and key paths. `name` is a key. `person.name` is a key path. Both expressions evaluate in a different manner. The key expression looks in the context stack for an object that would provide the "name" key. The key path expression looks in the context stack for an object that would provide the "person" key, and then extract the "name" key right from this person. The latter behavior is called a "scoped lookup".
  34. Let filters enter, and turn them into expressions:
  35. Library users should be able to build filter expressions with other expressions. One should be able to filter `person.name` with the filter `uppercase`.
  36. Composition goes further: library users should be able to perform a "scoped" lookup out of a filtered expression.
  37. The latter point is important: there is no good reason to prevent the library user to perform a scoped lookup out of a filtered expression.
  38. ### A syntax that fulfills those properties
  39. GRMustache implements filters with a good old function call syntax: `f(x)`.
  40. Just like `x`, `f(x)` is an expression that has a value. The GRMustache expression syntax let the user write `f(*)` and `*(x)` anywhere he can write `*`:
  41. - One can render `{{ f(x) }}` instead of `{{ x }}`.
  42. - One can render `{{ f(x.y) }}` instead of `{{ x.y }}`.
  43. - One can render `{{ f(g(x)) }}` instead of `{{ g(x) }}`.
  44. - One can render `{{ f(x)(y) }}` instead of `{{ f(x) }}` (`f` is a meta-filter: a filter that returns a filter).
  45. This fits pretty well with the "scoped" Mustache expression: the regular Mustache syntax lets the user write `*.y` anywhere he can write `*`:
  46. - One can render `{{ x.y }}` instead of `{{ x }}`.
  47. - One can render `{{ f(x).y }}` instead of `{{ f(x) }}`.
  48. - One can render `{{ f.g(x) }}` instead of `{{ f(x) }}`.
  49. A contrieved user could write `{{a.b(c.d(e.f).g.h).i.j(k.l)}}`. Whether this is sane or not is not the business of a library that embraces userland code.
  50. Last point: white space is irrelevant. `f(x)` is the same as `f ( x )`.
  51. You'll find below a grammar and a state machine that implement the parsing of those expressions.
  52. ### A syntax that does not fullfill those properties
  53. The only other syntax that I'm aware of is the one of bobthecow's [mustache.php](https://github.com/bobthecow/mustache.php/pull/102), which is not yet merged in the released branch of his library.
  54. {{ created_at | date.iso8601 }}
  55. Pipes have great ascendants (unix shell, Liquid filters), and this syntax sports a genuine relevance for its purpose. Pipable unix commands such as sort, uniq, etc. have a great deal in common with template filters.
  56. However, it fails on the composition part, since pipes build *statements*, not expressions.
  57. For example, how would pipes handle cases like `f(x).y` without the introduction of parenthesis in a fashion that is not common to pipes?
  58. {{ (x | f).y }} vs. {{ f(x).y }}
  59. {{ (x | f).y | g }} vs. {{ g(f(x).y) }}
  60. More, how would pipes handle meta-filters like `f(x)(y)` ?
  61. {{ y | (x | f) }} vs. {{ f(x)(y) }}
  62. The `f(x)` notation has here an advantage, which is its pervasiveness if many widely adopted languages that also use the dot as a property accessor.
  63. ### Filters can't load from the "implicit iterator"
  64. We've said above that filters should not come from the view model provided by the user, but instead be tied to a template. This allows a template to provide filters as services, including a standard library of filters.
  65. As a consequence, the `.(x)` syntax is forbidden. In Mustache, `.` aka the "implicit iterator", represents the currently rendered object from the view model. It thus can not provide any filter. Identically, the `.a(x)` syntax is invalid as well (it would mean "perform a scoped lookup for `a` in the view model, and apply the result as a filter").
  66. ## 3. Parsing GRMustache expressions
  67. Here is a state machine that describes GRMustache expressions. It reads one character
  68. after the other, until it reaches the *VALID*, *EMPTY*, or *INVALID* state:
  69. # ID stands for "identifier character"
  70. # WS stands for "white space character"
  71. # EOF stands for "end of input"
  72. # All non explicited transitions end up in the INVALID state.
  73. -> parenthesisLevel=0, INITIAL
  74. INITIAL -> WS -> INITIAL
  75. INITIAL -> ID -> scopable=YES, IDENTIFIER
  76. INITIAL -> '.' -> scopable=NO, LEADING_DOT
  77. INITIAL && parenthesisLevel==0 -> EOF -> EMPTY
  78. LEADING_DOT -> WS -> IDENTIFIER_DONE
  79. LEADING_DOT -> ID -> IDENTIFIER
  80. LEADING_DOT && parenthesisLevel>0 -> ')' -> --parenthesisLevel, FILTER_DONE
  81. LEADING_DOT && parenthesisLevel==0 -> EOF -> VALID
  82. IDENTIFIER -> WS -> IDENTIFIER_DONE
  83. IDENTIFIER -> ID -> IDENTIFIER
  84. IDENTIFIER -> '.' -> WAITING_FOR_IDENTIFIER
  85. IDENTIFIER && scopable -> '(' -> ++parenthesisLevel, INITIAL
  86. IDENTIFIER && parenthesisLevel>0 -> ')' -> --parenthesisLevel, FILTER_DONE
  87. IDENTIFIER && parenthesisLevel==0 -> EOF -> VALID
  88. WAITING_FOR_IDENTIFIER -> ID -> IDENTIFIER
  89. IDENTIFIER_DONE -> WS -> IDENTIFIER_DONE
  90. IDENTIFIER_DONE && scopable -> '(' -> ++parenthesisLevel, INITIAL
  91. IDENTIFIER_DONE && parenthesisLevel==0 -> EOF -> VALID
  92. FILTER_DONE -> WS -> FILTER_DONE
  93. FILTER_DONE -> '.' -> WAITING_FOR_IDENTIFIER
  94. FILTER_DONE -> '(' -> ++parenthesisLevel, INITIAL
  95. FILTER_DONE && parenthesisLevel>0 -> ')' -> --parenthesisLevel, FILTER_DONE
  96. FILTER_DONE && parenthesisLevel==0 -> EOF -> VALID
  97. ## 4. The details
  98. ### Filtered variables, filtered sections
  99. Expressions as a way for the library user to build values that would be rendered by Mustache. Now those values are actually rendered by variable tags, or section tags.
  100. The only argument so far I've read against filtered sections is: "I see no compelling use case that need this feature".
  101. This argument fails for two reasons. First it only shows the lack of imagination of the one expressing it. Second, it artificially limits the empowerment of the library user, who deserves more respect. If Mustache allows the library user to inject code, there is no point nannying him and preventing him from injecting his code where he thinks it is relevant. This only makes Mustache painful to use, without any benefit for anybody.
  102. Here is a nice section filter, for the unimaginative ones:
  103. ```js
  104. with_index = function(array) {
  105. for (i=0; i<array.length; ++i) {
  106. var object = array[i];
  107. object.index = i;
  108. object.even = (i % 2 == 0);
  109. object.first = (i == 0);
  110. object.last = (i == array.length - 1);
  111. }
  112. return array;
  113. };
  114. ```
  115. Yes, this filters allows to render collections with the `index`, `even`, `first`, and `last` keys usable in the template, as requested by at least five github issues (the ones linked above).
  116. ### Empty closing section tags
  117. Mustache sections are implemented by a pair of symetric tags: `{{#name}}...{{/name}}`, and `{{^name}}...{{/name}}`.
  118. When we introduce filters, this symetry becomes quite verbose: `{{^ isEmpty(people) }}...{{/ isEmpty(people) }}`. Although this point is not required, GRMustache allows for empty closing tags: `{{^ isEmpty(people) }}...{{/}}`
  119. ### Missing or invalid filters
  120. In order to perform, filters must be fetched by name, as written in the template, and apply to a value. Both operations may fail.
  121. The failure must not be silent, returning nil/null/undefined. This would prevent debugging, and mislead chained filters such as `f(g(x))` in unpredictable (presumably hilarious) ways.
  122. GRMustache raises an exception in those cases.
  123. ### Filter namespaces
  124. Just as Mustache users can extract a value with a scoped expression as `person.pet.name`, filters should be addressable in the same way.
  125. This would allow the definition of filter namespaces, so that the user can define `math.abs`, `javascript.escape`, or load a third-party filter library that would not clash with his own filters.