Expand description
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
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
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
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
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. 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
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:trueif this is the first iteration of the looploop.last:trueif this is the last iteration of the loop
<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. 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
“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.
§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.
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!