forked from SquareBracketAssociates/EnterprisePharo
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Mustache.pillar
498 lines (393 loc) · 15.3 KB
/
Mustache.pillar
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
!Mustache Templates for Pharo
@cha:mustache
Mustache is a framework-agnostic, logic-free templating format that "emphasizes separating logic from presentation: it is impossible to embed application logic in this template language". It is supported in many programming languages, as shown at *http://mustache.github.io*. The full syntax documentation is available online at *http://mustache.github.io/mustache.5.html*.
Mustache is simple and versatile, its syntax is small and covers a
wide range of use cases. Although it was designed to be a templating
engine for HTML pages it is useful in different areas.
Norbert Hartl developed a Mustache package for Pharo. This chapter is an introduction to this package and a mini tutorial to get started with Mustache. This text is based on the original blog entries written by Norbert and extended to offer a larger covering of the Mustache features.
!! Getting started
To install Mustache, execute the following expression in a workspace:
[[[
Gofer it
smalltalkhubUser: 'NorbertHartl' project: 'Mustache';
configuration;
loadStable
]]]
A Mustache expression takes two arguments as input:
(1) a template and (2) a context object (which is a list of bindings). The latter is called a hash in Mustache jargon.
Consider a simple Mustache template (taken literally from the documentation):
[[[
templateString := 'Hello {{ name }}
You have just won ${{value}}!
{{#in_ca}}
Well, ${{taxed_value}}, after taxes.
{{/in_ca}}'.
]]]
The expression =={{}}== delimits a ''tag'' or variable inside a template.
Here =={{ name }}== represents the variable ==name==. When the template is evaluated with a context, variables are replaced by their values given by a context. A possible context for the template above is the following:
[[[
context := {
'name' -> 'Chris'.
'value' -> 10000.
'taxed_value' -> (10000 - (10000 * 0.4)).
'in_ca' -> true } asDictionary
]]]
Given a context object with some bindings, we can evaluate a template in two different ways:
[[[
(MustacheTemplate on: templateString) value: context
templateString asMustacheTemplate value: context
]]]
For the above example, we get the following output in both cases:
[[[
'Hello Chris
You have just won $10000!
Well, $6000.0, after taxes.
'
]]]
As context object we can use Dictionaries and Objects. Dictionaries need to have a key that is used in the template and Objects need a selector with the same
name. In this chapter we use Dictionaries for brevity.
!! Tags as Variables
Tags can be of different types: The first and simplest type is ''variable''.
We explain its working with an example: a =={{age}}== tag in a basic template tries to find the
==age== key in the current context. If there is no ==age== key, the parent contexts are looked up recursively. If the top context is reached and the ==age==
key is still not found, nothing will be rendered. For example, In the following example ==age== is not defined, therefore nothing is present in the output (included below after the ==-->==).
[[[
'* {{name}}
* {{age}}
* {{company}}' asMustacheTemplate value:
{
'name' -> 'Chris' .
'company' -> '<b>GitHub</b>'
} asDictionary
-->
'* Chris
*
* <b>GitHub</b>
]]]
The last line above shows that all variables are HTML escaped by default, e.g. if a binding for that variable contains a ==<== character it will be converted to ==<== . To return unescaped HTML, the
triple mustache expression must be used: ==\{{{name\}}}==. Also, the character ==&== can be used to unescape a variable as in =={{& name}}==. This can be useful when changing delimiters, which is discussed later in this chapter.
The template below shows the different ways in which ==company== is escaped in the output (included below after the ==-->==).
[[[
'* {{name}}
* {{age}}
* {{&company}}
* {{company}}
* {{{company}}}' asMustacheTemplate value:
{
'age' -> 33 .
'name' -> 'Chris' .
'company' -> '<b>GitHub</b>'
} asDictionary
-->
'* Chris
* 33
* <b>GitHub</b>
* <b>GitHub</b>
* <b>GitHub</b>'
]]]
!! Sections
Sections render blocks of text a number of times if their key is present in the context. A section is delimited by a hash sign and a slash. For example, =={{#number}}== begins a section for the ''number'' variable while =={{/number}}== ends it.
When a variable is not present in the context the section is not present in the output:
[[[
| templateString context |
templateString := 'Shown.
{{#number}}
Shown too!
{{/number}}'.
context := { 'foo' -> 'true' } asDictionary.
(MustacheTemplate on: templateString) value: context
-->
'Shown.
'
]]]
When the variable is set, the output depends on the contents of the variable and there are three distinct cases, as we discuss next.
!!! With the variable value being a 'simple' object
For values that are not collections nor blocks, the output will be present once.
For example, below =='number'== is bound to ==true== which causes the =='Shown too'== text to be shown. The same will happen when ==number== is bound to another value, e.g. ==42==.
[[[
| templateString context |
templateString := 'Shown.
{{#number}}
Shown too!
{{/number}}'.
context := { 'number' -> true } asDictionary.
(MustacheTemplate on: templateString) value: context
-->
'Shown.
Shown too!
'
]]]
There is one exception to this rule: when the variable is bound to ==false== the section is not present in the output:
[[[
| templateString context |
templateString := 'Shown.
{{#number}}
Shown too!
{{/number}}'.
context := { 'number' -> false } asDictionary.
(MustacheTemplate on: templateString) value: context
-->
'Shown.
'
]]]
!!! With the variable value being a collection
We can use collections to create loop constructs in templates. If a
section key is present in the context and it has a collection as a
value, the text of the section is present as many times as there are
items in the collection. Keep in mind that Strings are collections of characters.
[[[
| templateString context |
templateString := 'Shown.
{{#list}}
Shown too!
{{/list}}'.
context := { 'list' -> #(1 2 3) } asDictionary.
(MustacheTemplate on: templateString) value: context
-->
'Shown.
Shown too!
Shown too!
Shown too!
'
]]]
@@note Consequently, if the collection is empty (or if it is the empty string), the output is present 0 times, i.e. it is absent.
When processing collections, Mustache iterates over them and for each element of the collection the context of the section is set to the current item. This allows a section to use variables that are contained in the elements of the collection. For example, below we define a ''list'' binding that contains multiple ''number'' bindings. The section ''list'' is evaluated for each of the its ''number'' bindings.
[[[
| templateString context |
templateString := 'A {{ label }} list of numbers
{{# list }}
Number: {{ number }}
{{/ list }}'.
context := {
'label' -> 'fine'.
'list' -> {
{ 'number' -> 1 } asDictionary.
{ 'number' -> 2 } asDictionary.
}
} asDictionary.
(MustacheTemplate on: templateString) value: context
-->
'A fine list of numbers
Number: 1
Number: 2
'
]]]
With such behavior we can easily generate menus and lists in html, for example:
[[[
'<ul>
{{#entries}}<li class="menuEntry{{#active}} active{{/active}}">{{label}}</li>
{{/entries}}
</ul>' asMustacheTemplate
value: { 'entries' -> {
{ 'label' -> 'first' } asDictionary.
{ 'label' -> 'second' . 'active' -> true } asDictionary.
{ 'label' -> 'third' } asDictionary } } asDictionary.
-->
'<ul>
<li class="menuEntry">first</li>
<li class="menuEntry active">second</li>
<li class="menuEntry">third</li>
</ul>'
]]]
[[[
'{{#coolBooks}}
<b>{{name}}</b>
{{/coolBooks}}' asMustacheTemplate
value: {'coolBooks' -> {
{ 'name' -> 'Pharo By Example' } asDictionary.
{ 'name' -> 'Deep Into Pharo' } asDictionary.
{ 'name' -> 'Fun Wih Pharo' } asDictionary }
} asDictionary
-->
'
<b>Pharo By Example</b>
<b>Deep Into Pharo</b>
<b>Fun Wih Pharo</b>
'
]]]
%JF this section brings nothing new to the table
% !!!! Dictionaries with repeated values
% When the value of a variable is a dictionary with a single value it will be only used once as context, for example:
% [[[
% | template result |
% template := MustacheTemplate on: '{{#person?}} Hi {{name}}! {{/person?}}' .
% result := template value: { 'person?' -> { 'name' -> 'Jon' } asDictionary } asDictionary.
% -->
% ' Hi Jon! '
% ]]]
% Here giving twice the same variable does not work because a dictionary cannot have a variable defined twice.
% [[[
% | template result |
% template := MustacheTemplate on: '{{#person?}} Hi {{name}}! {{/person?}}' .
% result := template value: { 'person?' -> { 'name' -> 'Jon' . 'name' -> 'Smith' } asDictionary } asDictionary.
% -->
% ' Hi Jon! '
% ]]]
% To be able to loop over values as shown previously we should define multiple dictionaries as follows:
% [[[
% | template result |
% template := MustacheTemplate on: '{{#person?}} Hi {{name}}! {{/person?}}' .
% result := template
% value: { 'person?' ->
% {
% {'name' -> 'Jon' } asDictionary .
% {'name' -> 'Pilou'} asDictionary}
% } asDictionary
% -> ' Hi Jon! Hi Pilou! '
% ]]]
!!! With the variable value being a block
We can use Pharo blocks in context objects as well. They are evaluated at the time the template is filled out. For example,
[[[
'The alphabet: {{ alphabet }}' asMustacheTemplate
value: { 'alphabet' -> [ Character alphabet ] } asDictionary
-->
The alphabet: abcdefghijklmnopqrstuvwxyz
]]]
Now there is another interesting way of using blocks. With a block we can get access to the
value of a section and perform some operations on it.
This example shows that we can access the value of a section. Here the block is expecting one argument:
the value of this argument will be the section with subsituted variables.
[[[
'{{#wrapped}} {{name}} is awesome {{/wrapped}}' asMustacheTemplate
value: {
'name' -> 'Willy'.
'wrapped' -> [ :render | '<b>', render value, '</b>' ] } asDictionary.
-->
'<b> Willy is awesome </b>'.
]]]
!!! Inverted Sections
A last use of sections is inverted sections. These begin with a caret
and ends with a slash, for example: =={{^list}}== begins a ''list'' inverted section and =={{/list}}== ends it. While
sections are used to render text one or more times based on the value of the key, inverted sections may render text once based on the ''inverse'' value of the key.
That is, they will be rendered if the key doesn't exist, is false, or is an empty collection:
[[[
'list{{^ list }} is {{/ list}}displayed' asMustacheTemplate
value: { 'list' -> false } asDictionary.
-->
'list is displayed'
]]]
[[[
'list{{^ list }} is {{/ list}}displayed' asMustacheTemplate
value: { 'list' -> { } } asDictionary.
-->
'list is displayed'
]]]
[[[
'list{{^ list }} is {{/ list}}displayed' asMustacheTemplate
value: { 'list' -> { 1 } } asDictionary.
-->
'listdisplayed'
]]]
%JF: already said in 'Tags as Variables'
% !! UnescapedToken
% Variable values can contain characters that will be escaped when producing the result.
% For example, when we set the value of variable to ==\&== it is escaped into ==&==.
% [[[
% | template |
% template := MustacheTemplate on: 'This is a test for {{ name }}.'.
% template value: { 'name' -> '&' } asDictionary .
% -->
% 'This is a test for &.'
% ]]]
% If the variable explicitly manipulates the variable as an unescaped variable i.e., using ==& name==.
% In the following example the outputs is ==&== since the variable was specified not to be unescaped.
% [[[
% | template result |
% template := MustacheTemplate on: 'This is a test for {{& name }}.'.
% template value: { 'name' -> '&' } asDictionary .
% -->
% 'This is a test for &.'
% ]]]
!! Partial templates
Mustache templates have a notion of sub-templates that are called partials. Partial templates are useful for reusing and
composing templates out of simpler ones. Partials begin with a greater than sign, such as =={{> user}}==.
The following example shows that the partial is replaced by its definition which is then expanded.
[[[
| template |
template := 'This is a test for {{> partial }} .' asMustacheTemplate.
template
value: { 'name' -> 'partial template' } asDictionary
partials: { 'partial' -> '{{name}} rendering' } asDictionary.
-->
'This is a test for partial template rendering .'
]]]
The following example shows that partials work similarly with lists.
[[[
| template |
template := MustacheTemplate on: 'We can have a list ({{# list}} [ {{> partial }} ] {{/ list}}) .'.
template
value: { 'list' -> {
{ 'name' -> 'first AAA' } asDictionary.
{ 'name' -> 'last BBB' } asDictionary } } asDictionary
partials:
(Dictionary new
at: 'partial' put: (MustacheTemplate on: 'including {{name}} item');
yourself).
-->
'We can have a list ( [ including first AAA item ] [ including last BBB item ] ) .'
]]]
The values of dictionary used as template are supposed to be MustacheTemplates. However, when strings are used instead of MustacheTemplates, strings are transparently
converted, as is the case for =='<strong>{{name}}</strong>'== below:
[[[
| template |
template := '<h2>Names</h2>
{{# names }}
{{> user }}
{{/ names }}' asMustacheTemplate.
template
value: {
'names' -> {
{ 'name' -> 'Username' } asDictionary } } asDictionary
partials: {'user' -> '<strong>{{name}}</strong>'} asDictionary
-->
'<h2>Names</h2>
<strong>Username</strong>
'
]]]
!! Miscellaneous
!!! Changing Set Delimiters
When you want to use Mustache to generate LaTeX you face the problem
that LaTeX may need to contain =={{== and ==}}==, which conflicts
with the Mustache set delimiters. To avoid such conflicts the
delimiters can be changed using the ==\= == characters separated by a space.
For example, =={{=<% %>=}}== defines ==<%== and ==%>== as new delimiters. To replace the default separators, we can simply use the previously defined ones: ==<%={{ }}=%>==:
[[[
'{{ number }}
{{=<% %>=}}
<% number %>
<%={{ }}=%>
{{ number }}' asMustacheTemplate
value: { 'number' -> 42 } asDictionary
-->
'42
42
42'
]]]
!!! How about JSON?
JSON is really easy to apply to the templates once NeoJSON is
installed (see Chapter *NeoJSON>../NeoJSON/NeoJSON.pillar@cha:JSON*.)
After that it is just as simple as in the following example:
[[[
'I can use {{name}} easily with {{format}}' asMustacheTemplate
value: (NeoJSONReader fromString: '{ "name" : "Mustache", "format" : "JSON" }')
-->
'I can use Mustache easily with JSON'
]]]
% JF: this does not add important material
% !! Example
% Here is a schematic way how Mustache is used by pillar.
% [[[
% pillarString := '! Some text
% -item1
% -item2'.
% pillarDocument := PRDocumentParser parse: pillarString.
% htmlString := PRHTMLWriter write: pillarDocument.
% 'main.template.html' asFileReference mustacheTemplateDuring: [ :template |
% 'result.html' asFileReference writeStreamDo: [ :outStream |
% outStream nextPutAll: (template value: { 'contents' -> htmlString} asDictionary )
% ]]
% ]]]
!! Templates made easy
Mustache can make template dependent tasks very easy from a simple token replacement up to nested structures to create HTML pages. We use them e.g., for
generating SOAP templates. The strength of Mustache lays in the syntax and the combination of context objects. So, there is more for you to find what can be
done with it. Happy templating !