Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 206 lines (125 sloc) 14.494 kb
77e0009 @groue More filters documentation, and articles.
authored
1 # Mustache support for "Filters"
2
a702a66 @groue More WhyMustacheFilters
authored
3 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.
77e0009 @groue More filters documentation, and articles.
authored
4
a702a66 @groue More WhyMustacheFilters
authored
5 GRMustache provides an implementation of [filters](../Guides/filters.md) that fully cover all the points described here.
77e0009 @groue More filters documentation, and articles.
authored
6
7 1. Why filters are good for Mustache
8 2. Why Mustache tags should contain expressions, not statements
caeab91 @groue More WhyMustacheFilters
authored
9 3. Parsing GRMustache expressions
77e0009 @groue More filters documentation, and articles.
authored
10 4. The details
11
12 ## 1. Why filters are good for Mustache
13
14 ### History of user-provided code: lambdas
15
16 Mustache users today have a single way to have their own code executed while rendering a template: "Mustache lambdas".
17
18 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.
19
20 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>`.
21
22 However, lambdas do not have access to the *view model* level. They can not, for instance, render the uppercase version of a value.
23
24 > 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.
25
26 ### The consequences of a drastic interpretation of "logiclessness"
27
28 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.
29
30 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.
31
32 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).
33
34 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).
35
36 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).
37
38 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.
39
40 Some readers might be interested by a [more general rebuttal of the drastic interpretation of Mustache "logiclessness"](TheNatureOfLogicLessTemplates.md).
41
42 ### Filters empower the library user, and Mustache itself
43
44 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.
45
46 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.
47
4c26aab @groue More WhyMustacheFilters
authored
48 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).
77e0009 @groue More filters documentation, and articles.
authored
49
50 Since filters belong to the templates, Mustache can provide a *standard library* of filters, that would be pre-baked into all Mustache templates.
51
52 Since filters are not tied to the view model, they are *reusable*.
53
54
55 ## 2. Why Mustache tags should contain expressions, not statements
56
57 ### Composition
58
59 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.
60
ed26c85 @groue v5.3.0
authored
61 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.
77e0009 @groue More filters documentation, and articles.
authored
62
63 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".
64
65 Let filters enter, and turn them into expressions:
66
67 Library users should be able to build filter expressions with other expressions. One should be able to filter `person.name` with the filter `uppercase`.
68
69 Composition goes further: library users should be able to perform a "scoped" lookup out of a filtered expression.
70
71 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.
72
a702a66 @groue More WhyMustacheFilters
authored
73 ### A syntax that fulfills those properties
77e0009 @groue More filters documentation, and articles.
authored
74
75 GRMustache implements filters with a good old function call syntax: `f(x)`.
76
3f9834c @groue More criticism of the pipe filters
authored
77 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 `*`:
77e0009 @groue More filters documentation, and articles.
authored
78
79 - One can render `{{ f(x) }}` instead of `{{ x }}`.
648f451 @groue More WhyMustacheFilters
authored
80 - One can render `{{ f(x.y) }}` instead of `{{ x.y }}`.
3f9834c @groue More criticism of the pipe filters
authored
81 - One can render `{{ f(g(x)) }}` instead of `{{ g(x) }}`.
5a02653 @groue Typo
authored
82 - One can render `{{ f(x)(y) }}` instead of `{{ f(x) }}` (`f` is a meta-filter: a filter that returns a filter).
77e0009 @groue More filters documentation, and articles.
authored
83
648f451 @groue More WhyMustacheFilters
authored
84 This fits pretty well with the "scoped" Mustache expression: the regular Mustache syntax lets the user write `*.y` anywhere he can write `*`:
77e0009 @groue More filters documentation, and articles.
authored
85
86 - One can render `{{ x.y }}` instead of `{{ x }}`.
87 - One can render `{{ f(x).y }}` instead of `{{ f(x) }}`.
648f451 @groue More WhyMustacheFilters
authored
88 - One can render `{{ f.g(x) }}` instead of `{{ f(x) }}`.
77e0009 @groue More filters documentation, and articles.
authored
89
3f9834c @groue More criticism of the pipe filters
authored
90 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.
77e0009 @groue More filters documentation, and articles.
authored
91
92 Last point: white space is irrelevant. `f(x)` is the same as `f ( x )`.
93
a702a66 @groue More WhyMustacheFilters
authored
94 You'll find below a grammar and a state machine that implement the parsing of those expressions.
95
96 ### A syntax that does not fullfill those properties
97
35fdf88 @groue More WhyMustacheFilters
authored
98 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.
a702a66 @groue More WhyMustacheFilters
authored
99
100 {{ created_at | date.iso8601 }}
101
d1d4d09 @groue More WhyMustacheFilters
authored
102 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.
a702a66 @groue More WhyMustacheFilters
authored
103
104 However, it fails on the composition part, since pipes build *statements*, not expressions.
105
3f9834c @groue More criticism of the pipe filters
authored
106 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?
a702a66 @groue More WhyMustacheFilters
authored
107
108 {{ (x | f).y }} vs. {{ f(x).y }}
109 {{ (x | f).y | g }} vs. {{ g(f(x).y) }}
110
3f9834c @groue More criticism of the pipe filters
authored
111 More, how would pipes handle meta-filters like `f(x)(y)` ?
112
113 {{ y | (x | f) }} vs. {{ f(x)(y) }}
114
a702a66 @groue More WhyMustacheFilters
authored
115 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.
77e0009 @groue More filters documentation, and articles.
authored
116
d1d4d09 @groue More WhyMustacheFilters
authored
117
77e0009 @groue More filters documentation, and articles.
authored
118 ### Filters can't load from the "implicit iterator"
119
a702a66 @groue More WhyMustacheFilters
authored
120 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.
77e0009 @groue More filters documentation, and articles.
authored
121
122 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").
123
a702a66 @groue More WhyMustacheFilters
authored
124
caeab91 @groue More WhyMustacheFilters
authored
125 ## 3. Parsing GRMustache expressions
77e0009 @groue More filters documentation, and articles.
authored
126
3f9834c @groue More criticism of the pipe filters
authored
127 Here is a state machine that describes GRMustache expressions. It reads one character
77e0009 @groue More filters documentation, and articles.
authored
128 after the other, until it reaches the *VALID*, *EMPTY*, or *INVALID* state:
129
130 # ID stands for "identifier character"
131 # WS stands for "white space character"
132 # EOF stands for "end of input"
133 # All non explicited transitions end up in the INVALID state.
134 -> parenthesisLevel=0, INITIAL
135 INITIAL -> WS -> INITIAL
136 INITIAL -> ID -> scopable=YES, IDENTIFIER
137 INITIAL -> '.' -> scopable=NO, LEADING_DOT
138 INITIAL && parenthesisLevel==0 -> EOF -> EMPTY
139 LEADING_DOT -> WS -> IDENTIFIER_DONE
140 LEADING_DOT -> ID -> IDENTIFIER
141 LEADING_DOT && parenthesisLevel>0 -> ')' -> --parenthesisLevel, FILTER_DONE
142 LEADING_DOT && parenthesisLevel==0 -> EOF -> VALID
143 IDENTIFIER -> WS -> IDENTIFIER_DONE
144 IDENTIFIER -> ID -> IDENTIFIER
145 IDENTIFIER -> '.' -> WAITING_FOR_IDENTIFIER
146 IDENTIFIER && scopable -> '(' -> ++parenthesisLevel, INITIAL
147 IDENTIFIER && parenthesisLevel>0 -> ')' -> --parenthesisLevel, FILTER_DONE
148 IDENTIFIER && parenthesisLevel==0 -> EOF -> VALID
149 WAITING_FOR_IDENTIFIER -> ID -> IDENTIFIER
150 IDENTIFIER_DONE -> WS -> IDENTIFIER_DONE
151 IDENTIFIER_DONE && scopable -> '(' -> ++parenthesisLevel, INITIAL
152 IDENTIFIER_DONE && parenthesisLevel==0 -> EOF -> VALID
153 FILTER_DONE -> WS -> FILTER_DONE
154 FILTER_DONE -> '.' -> WAITING_FOR_IDENTIFIER
3f9834c @groue More criticism of the pipe filters
authored
155 FILTER_DONE -> '(' -> ++parenthesisLevel, INITIAL
77e0009 @groue More filters documentation, and articles.
authored
156 FILTER_DONE && parenthesisLevel>0 -> ')' -> --parenthesisLevel, FILTER_DONE
157 FILTER_DONE && parenthesisLevel==0 -> EOF -> VALID
158
159
160 ## 4. The details
161
162 ### Filtered variables, filtered sections
163
ed26c85 @groue v5.3.0
authored
164 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.
77e0009 @groue More filters documentation, and articles.
authored
165
166 The only argument so far I've read against filtered sections is: "I see no compelling use case that need this feature".
167
168 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.
169
170 Here is a nice section filter, for the unimaginative ones:
171
172 ```js
173 with_index = function(array) {
174 for (i=0; i<array.length; ++i) {
175 var object = array[i];
176 object.index = i;
177 object.even = (i % 2 == 0);
178 object.first = (i == 0);
179 object.last = (i == array.length - 1);
180 }
181 return array;
182 };
183 ```
184
185 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).
186
187 ### Empty closing section tags
188
189 Mustache sections are implemented by a pair of symetric tags: `{{#name}}...{{/name}}`, and `{{^name}}...{{/name}}`.
190
191 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) }}...{{/}}`
192
193 ### Missing or invalid filters
194
195 In order to perform, filters must be fetched by name, as written in the template, and apply to a value. Both operations may fail.
196
197 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.
198
199 GRMustache raises an exception in those cases.
200
201 ### Filter namespaces
202
203 Just as Mustache users can extract a value with a scoped expression as `person.pet.name`, filters should be addressable in the same way.
204
205 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.
Something went wrong with that request. Please try again.