GRMustache ships with a library of built-in goodies available for your templates.
GRMustache provides built-in support for Foundation's Formatter and its subclasses such as NumberFormatter and DateFormatter.
let percentFormatter = NumberFormatter()
percentFormatter.numberStyle = .percent
let template = try! Template(string: "{{ percent(x) }}")
template.register(percentFormatter, forKey: "percent")
// Rendering: 50%
let data = ["x": 0.5]
let rendering = try! template.render(data)
Formatters are able to format all variable tags inside the section:
Document.mustache
:
{{# percent }}
hourly: {{ hourly }}
daily: {{ daily }}
weekly: {{ weekly }}
{{/ percent }}
Rendering code:
let percentFormatter = NumberFormatter()
percentFormatter.numberStyle = .percent
let template = try! Template(named: "Document")
template.register(percentFormatter, forKey: "percent")
// Rendering:
//
// hourly: 10%
// daily: 150%
// weekly: 400%
let data = [
"hourly": 0.1,
"daily": 1.5,
"weekly": 4,
]
let rendering = try! template.render(data)
Variable tags buried inside inner sections are escaped as well, so that you can render loop and conditional sections. However, values that can't be formatted are left untouched:
Document.mustache
:
{{# percent }}
{{# ingredients }}
- {{ name }}: {{ proportion }} {{! name is intact, proportion is formatted. }}
{{/ ingredients }}
{{/ percent }}
Would render:
- bread: 50%
- ham: 22%
- butter: 43%
Precisely speaking, "values that can't be formatted" are the ones that have the string(for:)
method return nil, as stated by NSFormatter documentation.
Typically, NumberFormatter only formats numbers, and DateFormatter, dates: you can safely mix various data types in a section controlled by those well-behaved formatters.
Support for Formatter is written using public APIs. You can check the source for inspiration.
Usage:
let template = ...
template.register(StandardLibrary.HTMLEscape, forKey: "HTMLEscape")
As a filter, HTMLEscape
returns its argument, HTML-escaped.
<pre>
{{ HTMLEscape(content) }}
</pre>
When used in a section, HTMLEscape
escapes all inner variable tags in a section:
{{# HTMLEscape }}
{{ firstName }}
{{ lastName }}
{{/ HTMLEscape }}
Variable tags buried inside inner sections are escaped as well, so that you can render loop and conditional sections:
{{# HTMLEscape }}
{{# items }}
{{ name }}
{{/}}
{{/ HTMLEscape }}
StandardLibrary.HTMLEscape is written using public APIs. You can check the source for inspiration.
See also javascriptEscape, URLEscape
Usage:
let template = ...
template.register(StandardLibrary.javascriptEscape, forKey: "javascriptEscape")
As a filter, javascriptEscape
outputs a Javascript and JSON-savvy string:
<script type="text/javascript">
var name = "{{ javascriptEscape(name) }}";
</script>
When used in a section, javascriptEscape
escapes all inner variable tags in a section:
<script type="text/javascript">
{{# javascriptEscape }}
var firstName = "{{ firstName }}";
var lastName = "{{ lastName }}";
{{/ javascriptEscape }}
</script>
Variable tags buried inside inner sections are escaped as well, so that you can render loop and conditional sections:
<script type="text/javascript">
{{# javascriptEscape }}
var firstName = {{# firstName }}"{{ firstName }}"{{^}}null{{/}};
var lastName = {{# lastName }}"{{ lastName }}"{{^}}null{{/}};
{{/ javascriptEscape }}
</script>
StandardLibrary.javascriptEscape is written using public APIs. You can check the source for inspiration.
See also HTMLEscape, URLEscape
Usage:
let template = ...
template.register(StandardLibrary.URLEscape, forKey: "URLEscape")
As a filter, URLEscape
returns its argument, percent-escaped.
<a href="http://google.com?q={{ URLEscape(query) }}">...</a>
When used in a section, URLEscape
escapes all inner variable tags in a section:
{{# URLEscape }}
<a href="http://google.com?q={{query}}&hl={{language}}">...</a>
{{/ URLEscape }}
Variable tags buried inside inner sections are escaped as well, so that you can render loop and conditional sections:
{{# URLEscape }}
<a href="http://google.com?q={{query}}{{#language}}&hl={{language}}{{/language}}">...</a>
{{/ URLEscape }}
StandardLibrary.URLEscape is written using public APIs. You can check the source for inspiration.
See also HTMLEscape, javascriptEscape
Usage:
let template = ...
template.register(StandardLibrary.each, forKey: "each")
Iteration is natural to Mustache templates: {{# users }}{{ name }}, {{/ users }}
renders "Alice, Bob, etc." when the users
key is given a list of users.
The each
filter is there to give you some extra keys:
@index
contains the 0-based index of the item (0, 1, 2, etc.)@indexPlusOne
contains the 1-based index of the item (1, 2, 3, etc.)@indexIsEven
is true if the 0-based index is even.@first
is true for the first item only.@last
is true for the last item only.
Users with their positions:
{{# each(users) }}
- {{ @indexPlusOne }}: {{ name }}
{{/}}
Comma-separated user names:
{{# each(users) }}{{ name }}{{^ @last }}, {{/}}{{/}}.
Users with their positions:
- 1: Alice
- 2: Bob
- 3: Craig
Comma-separated user names: Alice, Bob, Craig.
When provided with a dictionary, each
iterates each key/value pair of the dictionary, stores the key in @key
, and sets the value as the current context:
{{# each(items) }}
- key: {{ @key }}, value: {{.}}
{{/}}
- key: name, value: Alice
- key: score, value: 200
- key: level, value: 5
The other positional keys @index
, @first
, etc. are still available when iterating dictionaries.
The each
filter is written using public APIs. You can check the source for inspiration.
Usage:
let template = ...
template.register(StandardLibrary.zip, forKey: "zip")
The zip filter iterates several lists all at once. On each step, one object from each input list enters the rendering context, and makes its own keys available for rendering.
Document.mustache
:
{{# zip(users, teams, scores) }}
- {{ name }} ({{ team }}): {{ score }} points
{{/}}
Data:
[
"users": [
[ "name": "Alice" ],
[ "name": "Bob" ],
],
"teams": [
[ "team": "iOS" ],
[ "team": "Android" ],
],
"scores": [
[ "score": 100 ],
[ "score": 200 ],
]
]
Rendering:
- Alice (iOS): 100 points
- Bob (Android): 200 points
In the example above, the first step has consumed (Alice, iOS and 100), and the second one (Bob, Android and 200).
The zip filter renders a section as many times as there are elements in the longest of its argument: exhausted lists simply do not add anything to the rendering context.
The zip
filter is written using public APIs. You can check the source for inspiration.
Usage:
let template = ...
let localizer = StandardLibrary.Localizer(bundle: nil, table: nil)
template.register(localizer, forKey: "localize")
As a filter, localize
outputs a localized string:
{{ localize(greeting) }}
This would render Bonjour
, given Hello
as a greeting, and a French localization for Hello
.
When used in a section, localize
outputs the localization of a full section:
{{# localize }}Hello{{/ localize }}
This would render Bonjour
, given a French localization for Hello
.
When looking for the localized string, GRMustache replaces all variable tags with "%@":
{{# localize }}Hello {{name}}{{/ localize }}
This would render Bonjour Arthur
, given a French localization for Hello %@
. String(format:)
is used for the final interpolation.
You can embed conditional sections inside:
{{# localize }}Hello {{#name}}{{name}}{{^}}you{{/}}{{/ localize }}
Depending on the name, this would render Bonjour Arthur
or Bonjour toi
, given French localizations for both Hello %@
and Hello you
.
StandardLibrary.Localizer filter is written using public APIs. You can check the source for inspiration.
Usage:
let template = ...
let logger = StandardLibrary.Logger { print($0) }
template.extendBaseContext(logger)
Logger
is a tool intended for debugging templates.
It logs the rendering of variable and section tags such as {{name}}
and
{{#name}}...{{/name}}
.
To activate logging, add a Logger to the base context of a template:
let template = try! Template(string: "{{name}} died at {{age}}.")
// Logs all tag renderings with NSLog():
let logger = StandardLibrary.Logger()
template.extendBaseContext(logger)
// Render
let data = ["name": "Freddy Mercury", "age": 45]
let rendering = try! template.render(data)
In the log:
{{name}} at line 1 did render "Freddy Mercury" as "Freddy Mercury"
{{age}} at line 1 did render 45 as "45"