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 for rendering values and
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 are available everywhere in templates and they can be emitted by
wrapping them in {{ ... }}
.
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
,\\
,\"
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.
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.
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.
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.
Hello {{ user.name }} {{ user?.surname }}!
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.
{{ 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.
{{ page.path | append: ".html" }}
See the filters
module documentation for more
information on filters.
Blocks are marked with an opening {% ... %}
and a closing {% ... %}
.
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. 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.
{% 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 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 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.
{% for user in users %}
<p>{{ user.name }}</p>
{% endfor %}
Here is an example where users
is a map.
{% 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 iterableloop.first
:true
if this is the first iteration of the looploop.last
:true
if this is the last iteration of the loop
<ul>
{% for user in users %}
<li>{{ loop.index }}. {{ user.name }}</li>
{% endfor %}
</ul>
“With” blocks can be used to create a variable from an expression. The variable is only valid within the block and it shadows any outer variables with the same name.
{% with user.names | join: " " as fullname %}
Hello {{ fullname }}!
{% endwith %}
“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.
<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.
<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
.
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.
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:
Hello,
and welcome,
John!
We can add whitespace trimming like this.
Hello,
{%- if user.is_welcome %} and welcome, {% endif -%}
{{ user.name }}!
Now it will be rendered without the newlines or extra spaces.
Hello, and welcome, John!