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
//! Documents the template syntax.
//!
//! An `upon` template is simply a piece of UTF-8 text. It can be embedded in
//! the binary or provided at runtime (e.g. read from a file). A template
//! contains [**expressions**](#expressions) for rendering values and
//! [**blocks**](#blocks) for controlling logic. These require you to use
//! specific syntax delimiters in the template. Because `upon` allows you to
//! configure these delimiters, this document will only refer to the **default**
//! configuration.
//!
//! # Expressions
//!
//! Expressions are available everywhere in templates and they can be emitted by
//! wrapping them in `{{ ... }}`.
//!
//! ## Literals
//!
//! The simplest form of expressions are literals. The following types are
//! available in templates.
//!
//! - Booleans: `true`, `false`
//! - Integers: `42`, `0o52`, `-0x2a`
//! - Floats: `0.123`, `-3.14`, `5.23e10`
//! - Strings: `"Hello World!"`, escape characters are supported: `\r`, `\n`,
//! `\t`, `\\`, `\"`
//! - Lists: `[1, 2, 3]`, `[value, "string", 3.14]`
//! - Maps: `{"a": value, "b": "string", "c": 3.14}`, literal map keys are
//! always constant strings
//!
//! Both lists and maps can contain any type of value including literals and
//! [values](#values).
//!
//! ## Values
//!
//! You can lookup up existing values in the current scope by name. The
//! following would lookup the field "name" in the current scope and insert it
//! into the rendered output.
//!
//! ```text
//! Hello {{ name }}!
//! ```
//!
//! You can access nested fields using a dotted path. The following would first
//! lookup the field "user" and then lookup the field "name" within it.
//!
//! ```text
//! Hello {{ user.name }}!
//! ```
//!
//! You can also use this syntax to lookup a particular index of a list. For
//! each of the expressions in the following code, first the field "users" is
//! looked up, then a particular user is selected from the list by index.
//! Finally, the field "name" is looked up from the selected user.
//!
//! ```text
//! Hello {{ users.0.name }}!
//! And hello {{ users.1.name }}!
//! And also hello {{ users.2.name }}!
//! ```
//!
//! The dotted path syntax will raise an error when the field or index is not
//! found. If you want to try lookup a field and return [`Value::None`] when it
//! is not found then you can use the optional dotted path syntax (`?.`). The
//! following would try lookup the field "surname" from "user" and return
//! [`Value::None`] if it is not found.
//!
//! ```text
//! Hello {{ user.name }} {{ user?.surname }}!
//! ```
//!
//! [`Value::None`]: crate::Value::None
//!
//! ## Filters
//!
//! Filters can be applied to existing expressions using the `|` (pipe)
//! operator. The simplest filters take no extra arguments and are just
//! specified by name. For example, assuming a filter called `lower` is
//! registered in the engine the following would produce an expression with the
//! `user.name` value transformed to lowercase.
//!
//! ```html
//! {{ user.name | lower }}
//! ```
//!
//! Filters can also take arguments which must be a sequence of comma separated
//! values or literals. In the following we lookup the value `page.path` and
//! append a suffix to it.
//!
//! ```html
//! {{ page.path | append: ".html" }}
//! ```
//!
//! See the [`filters`][crate::filters] module documentation for more
//! information on filters.
//!
//! # Blocks
//!
//! Blocks are marked with an opening `{% ... %}` and a closing `{% ... %}`.
//!
//! ## Conditionals
//!
//! Conditionals are marked using an opening `if` block and a closing `endif`
//! block. It can also have zero or more optional `else if` clauses and an
//! optional `else` clause. A conditional renders the contents of the block
//! based on the specified condition which can be any
//! [**expression**](#expressions). An expression can be negated by applying the
//! prefix `not`. The conditional evaluates the expression based on it's
//! truthiness. The following values are considered falsy, every other value is
//! truthy and will pass the condition:
//! - `None`
//! - Boolean `false`
//! - An integer with value `0`
//! - A float with value `0.0`
//! - An empty string
//! - An empty list
//! - An empty map
//!
//! Consider the following template. If the nested field `user.is_enabled` is
//! returns `false` then the first paragraph would be rendered. Otherwise if
//! `user.has_permission` returns true then the second paragraph would be
//! rendered. If neither condition is satisfied then the HTML table would be
//! rendered.
//!
//! ```html
//! {% if not user.is_enabled %}
//! <p>User is disabled</p>
//! {% else if user.has_permission %}
//! <p>User has insufficient permissions</p>
//! {% else %}
//! <table>...</table>
//! {% endif %}
//! ```
//!
//! ## Loops
//!
//! Loops are marked using an opening `for` block and a closing `endfor` block.
//! A loop renders the contents of the block once for each item in the specified
//! sequence. This is done by unpacking each item into one or two variables.
//! These variables are added to the scope within the loop block and shadow any
//! variables with the same name in the outer scope. The specified sequence can
//! be any [**expression**](#expressions) but it must resolve to a list or map.
//! Additionally, for lists there must a single loop variable and for maps there
//! must be key and value loop variables.
//!
//! Consider the following template. This would render an HTML paragraph for
//! each user in the list.
//!
//! ```html
//! {% for user in users %}
//! <p>{{ user.name }}</p>
//! {% endfor %}
//! ```
//!
//! Here is an example where `users` is a map.
//!
//! ```html
//! {% for id, user in users %}
//! <div>
//! <p>ID: {{ id }}</p>
//! <p>Name: {{ user.name }}</p>
//! </div>
//! {% endfor %}
//! ```
//!
//! Additionally, there are three special values available within loops.
//!
//! - `loop.index`: a zero-based index of the current value in the iterable
//! - `loop.first`: `true` if this is the first iteration of the loop
//! - `loop.last`: `true` if this is the last iteration of the loop
//!
//! ```html
//! <ul>
//! {% for user in users %}
//! <li>{{ loop.index }}. {{ user.name }}</li>
//! {% endfor %}
//! </ul>
//! ```
//!
//! ## With
//!
//! "With" blocks can be used to create a variable from an
//! [**expression**](#expressions). The variable is only valid within the block
//! and it shadows any outer variables with the same name.
//!
//! ```html
//! {% with user.names | join: " " as fullname %}
//! Hello {{ fullname }}!
//! {% endwith %}
//! ```
//!
//! ## Include
//!
//! "Include" blocks can be used to render nested templates. The nested template
//! must have been registered in the engine before rendering. For example,
//! assuming a template "footer" has been registered in the engine the following
//! would render the template "footer" in the place of the include block. All
//! variables in the current template will be available to the nested template.
//!
//! ```html
//! <body>
//! ...
//!
//! {% include "footer" %}
//!
//! </body>
//! ```
//!
//! You can also include the nested using a specific context. In this case the
//! nested template would not have any access to the current template's
//! variables and `path.to.footer.info` would form the global context for the
//! nested template.
//!
//! ```html
//! <body>
//! ...
//!
//! {% include "footer" with path.to.footer.info %}
//!
//! </body>
//! ```
//!
//! Self-referential templates and include cycles are allowed but the maximum
//! include depth is restricted by the engine setting
//! [`set_max_include_depth`][crate::Engine::set_max_include_depth].
//!
//! # Whitespace control
//!
//! If an expression or block includes a hyphen `-` character, like `{{-`,
//! `-}}`, `{%-`, and `-%}` then any whitespace in the template adjacent to the
//! tag will be skipped when the template is rendered.
//!
//! Consider the following template.
//!
//! ```text
//! Hello,
//! {% if user.is_welcome %} and welcome, {% endif %}
//! {{ user.name }}!
//! ```
//!
//! This doesn't use any whitespace trimming so it would be rendered something
//! like this:
//! ```text
//! Hello,
//! and welcome,
//! John!
//! ```
//!
//! We can add whitespace trimming like this.
//! ```text
//! Hello,
//! {%- if user.is_welcome %} and welcome, {% endif -%}
//! {{ user.name }}!
//! ```
//!
//! Now it will be rendered without the newlines or extra spaces.
//! ```text
//! Hello, and welcome, John!
//! ```