upon/syntax.rs
1//! Documents the template syntax.
2//!
3//! An `upon` template is simply a piece of UTF-8 text. It can be embedded in
4//! the binary or provided at runtime (e.g. read from a file). A template
5//! contains [**expressions**](#expressions) for rendering values and
6//! [**blocks**](#blocks) for controlling logic. These require you to use
7//! specific syntax delimiters in the template. Because `upon` allows you to
8//! configure these delimiters, this document will only refer to the **default**
9//! configuration.
10//!
11//! # Expressions
12//!
13//! Expressions are available everywhere in templates and they can be emitted by
14//! wrapping them in `{{ ... }}`.
15//!
16//! ## Literals
17//!
18//! The simplest form of expressions are literals. The following types are
19//! available in templates.
20//!
21//! - Booleans: `true`, `false`
22//! - Integers: `42`, `0o52`, `-0x2a`
23//! - Floats: `0.123`, `-3.14`, `5.23e10`
24//! - Strings: `"Hello World!"`, escape characters are supported: `\r`, `\n`,
25//! `\t`, `\\`, `\"`
26//! - Lists: `[1, 2, 3]`, `[value, "string", 3.14]`
27//! - Maps: `{"a": value, "b": "string", "c": 3.14}`, literal map keys are
28//! always constant strings
29//!
30//! Both lists and maps can contain any type of value including literals and
31//! [values](#values).
32//!
33//! ## Values
34//!
35//! You can lookup up existing values in the current scope by name. The
36//! following would lookup the field "name" in the current scope and insert it
37//! into the rendered output.
38//!
39//! ```text
40//! Hello {{ .name }}!
41//! ```
42//!
43//! You can access nested fields using a dotted path. The following would first
44//! lookup the field "user" and then lookup the field "name" within it.
45//!
46//! ```text
47//! Hello {{ .user.name }}!
48//! ```
49//!
50//! For convenience, you can also omit the leading dot (`.`) in the first path
51//! member. The following is equivalent to the previous example.
52//! ```text
53//! Hello {{ user.name }}!
54//! ```
55//!
56//! You can also use this syntax to lookup a particular index of a list. For
57//! each of the expressions in the following code, first the field "users" is
58//! looked up, then a particular user is selected from the list by index.
59//! Finally, the field "name" is looked up from the selected user.
60//!
61//! ```text
62//! Hello {{ users.0.name }}!
63//! And hello {{ users.1.name }}!
64//! And also hello {{ users.2.name }}!
65//! ```
66//!
67//! The dotted path syntax will raise an error when the field or index is not
68//! found. If you want to try lookup a field and return [`Value::None`] when it
69//! is not found then you can use the optional dotted path syntax (`?.`). The
70//! following would try lookup the field "surname" from "user" and return
71//! [`Value::None`] if it is not found.
72//!
73//! ```text
74//! Hello {{ user.name }} {{ user?.surname }}!
75//! ```
76//!
77//! This is useful when checking if the first member of a path might not be
78//! defined.
79//! ```text
80//! {% if ?.user %} ... {% endif %}
81//! ```
82//!
83//! [`Value::None`]: crate::Value::None
84//!
85//! ## Filters
86//!
87//! Filters are functions that can be applied to existing expressions using the
88//! `|` (pipe) operator. The simplest filters take no extra arguments and are
89//! just specified by name. For example, assuming a function called `lower` is
90//! registered in the engine the following would produce an expression with the
91//! `user.name` value transformed to lowercase.
92//!
93//! ```html
94//! {{ user.name | lower }}
95//! ```
96//!
97//! Filters can also take arguments which must be a sequence of comma separated
98//! values or literals. In the following we lookup the value `page.path` and
99//! append a suffix to it.
100//!
101//! ```html
102//! {{ page.path | append: ".html" }}
103//! ```
104//!
105//! See the [`functions`][crate::functions] module documentation for more
106//! information on filters.
107//!
108//! ## Functions
109//!
110//! Functions can also be called with the `name(args...)` syntax. This allows
111//! you to use functions in other contexts like arguments to other functions or
112//! at the start of an expression. For example, assuming a function called
113//! `now` is registered in the engine, the following would produce an expression
114//! with the current date and time.
115//!
116//! ```html
117//! {{ now() }}
118//! ```
119//!
120//! Functions can also take arguments which must be a sequence of comma
121//! separated values, literals, or function calls. In the following we call a
122//! function `add` with two arguments.
123//!
124//! ```html
125//! {{ add(1, 2) }}
126//! ```
127//!
128//! See the [`functions`][crate::functions] module documentation for more
129//! information on functions.
130//!
131//! # Blocks
132//!
133//! Blocks are marked with an opening `{% ... %}` and a closing `{% ... %}`.
134//!
135//! ## Conditionals
136//!
137//! Conditionals are marked using an opening `if` block and a closing `endif`
138//! block. It can also have zero or more optional `else if` clauses and an
139//! optional `else` clause. A conditional renders the contents of the block
140//! based on the specified condition which can be any
141//! [**expression**](#expressions). An expression can be negated by applying the
142//! prefix `not`. The conditional evaluates the expression based on it's
143//! truthiness. The following values are considered falsy, every other value is
144//! truthy and will pass the condition:
145//! - `None`
146//! - Boolean `false`
147//! - An integer with value `0`
148//! - A float with value `0.0`
149//! - An empty string
150//! - An empty list
151//! - An empty map
152//!
153//! Consider the following template. If the nested field `user.is_enabled` is
154//! returns `false` then the first paragraph would be rendered. Otherwise if
155//! `user.has_permission` returns true then the second paragraph would be
156//! rendered. If neither condition is satisfied then the HTML table would be
157//! rendered.
158//!
159//! ```html
160//! {% if not user.is_enabled %}
161//! <p>User is disabled</p>
162//! {% else if user.has_permission %}
163//! <p>User has insufficient permissions</p>
164//! {% else %}
165//! <table>...</table>
166//! {% endif %}
167//! ```
168//!
169//! ## Loops
170//!
171//! Loops are marked using an opening `for` block and a closing `endfor` block.
172//! A loop renders the contents of the block once for each item in the specified
173//! sequence. This is done by unpacking each item into one or two variables.
174//! These variables are added to the scope within the loop block and shadow any
175//! variables with the same name in the outer scope. The specified sequence can
176//! be any [**expression**](#expressions) but it must resolve to a list or map.
177//! Additionally, for lists there must a single loop variable and for maps there
178//! must be key and value loop variables.
179//!
180//! Consider the following template. This would render an HTML paragraph for
181//! each user in the list.
182//!
183//! ```html
184//! {% for user in users %}
185//! <p>{{ user.name }}</p>
186//! {% endfor %}
187//! ```
188//!
189//! Here is an example where `users` is a map.
190//!
191//! ```html
192//! {% for id, user in users %}
193//! <div>
194//! <p>ID: {{ id }}</p>
195//! <p>Name: {{ user.name }}</p>
196//! </div>
197//! {% endfor %}
198//! ```
199//!
200//! Additionally, there are three special values available within loops.
201//!
202//! - `loop.index`: a zero-based index of the current value in the iterable
203//! - `loop.first`: `true` if this is the first iteration of the loop
204//! - `loop.last`: `true` if this is the last iteration of the loop
205//!
206//! ```html
207//! <ul>
208//! {% for user in users %}
209//! <li>{{ loop.index }}. {{ user.name }}</li>
210//! {% endfor %}
211//! </ul>
212//! ```
213//!
214//! ## With
215//!
216//! "With" blocks can be used to create a variable from an
217//! [**expression**](#expressions). The variable is only valid within the block
218//! and it shadows any outer variables with the same name.
219//!
220//! ```html
221//! {% with user.names | join: " " as fullname %}
222//! Hello {{ fullname }}!
223//! {% endwith %}
224//! ```
225//!
226//! ## Include
227//!
228//! "Include" blocks can be used to render nested templates. The nested template
229//! must have been registered in the engine before rendering. For example,
230//! assuming a template "footer" has been registered in the engine the following
231//! would render the template "footer" in the place of the include block. All
232//! variables in the current template will be available to the nested template.
233//!
234//! ```html
235//! <body>
236//! ...
237//!
238//! {% include "footer" %}
239//!
240//! </body>
241//! ```
242//!
243//! You can also include the nested using a specific context. In this case the
244//! nested template would not have any access to the current template's
245//! variables and `path.to.footer.info` would form the global context for the
246//! nested template.
247//!
248//! ```html
249//! <body>
250//! ...
251//!
252//! {% include "footer" with path.to.footer.info %}
253//!
254//! </body>
255//! ```
256//!
257//! Self-referential templates and include cycles are allowed but the maximum
258//! include depth is restricted by the engine setting
259//! [`set_max_include_depth`][crate::Engine::set_max_include_depth].
260//!
261//! # Whitespace control
262//!
263//! If an expression or block includes a hyphen `-` character, like `{{-`,
264//! `-}}`, `{%-`, and `-%}` then any whitespace in the template adjacent to the
265//! tag will be skipped when the template is rendered.
266//!
267//! Consider the following template.
268//!
269//! ```text
270//! Hello,
271//! {% if user.is_welcome %} and welcome, {% endif %}
272//! {{ user.name }}!
273//! ```
274//!
275//! This doesn't use any whitespace trimming so it would be rendered something
276//! like this:
277//! ```text
278//! Hello,
279//! and welcome,
280//! John!
281//! ```
282//!
283//! We can add whitespace trimming like this.
284//! ```text
285//! Hello,
286//! {%- if user.is_welcome %} and welcome, {% endif -%}
287//! {{ user.name }}!
288//! ```
289//!
290//! Now it will be rendered without the newlines or extra spaces.
291//! ```text
292//! Hello, and welcome, John!
293//! ```