Tera is a template engine based on Jinja2 and the Django template language.
It is subject to lots of API changes as users provide feedback.
While Tera is inspired by the engines above, it doesn't have the backward compatibility to maintain and we can improve on those if possible. One of the goal is to avoid putting too much logic in the templates so it's closer to the Django ones in that respect, except it has math operations built-in.
Example of a template file:
jinja
<html>
<head>
<title>{{ product.name }}</title>
</head>
<body>
<h1>{{ product.name }} - {{ product.manufacturer }}</h1>
<p>{{ product.summary }}</p>
<p>£{{ product.price * 1.20 }} (VAT inc.)</p>
{% if friend_reviewed %}
<p>Look at reviews from your friends {{ username }}</p>
{% if number_reviews > 10 || show_more %}
<p>All reviews</p>
{% for review in reviews %}
<h3>{{review.title}}</h3>
{% for paragraph in review.paragraphs %}
<p>{{ paragraph }}</p>
{% endfor %}
{% endfor %}
{% elif number_reviews == 1 %}
<p>Only one review</p>
{% endif %}
{% else %}
<p>None of your friend reviewed this product</p>
{% endif %}
<button>Buy!</button>
</body>
</html>
Tera will load and parse all the templates in the given directory.
Let's take the following directory as example.
bash
templates/
hello.html
index.html
products/
product.html
price.html
Assuming the rust file is at the same level as the templates
folder, we would parse the templates that way:
```rust use tera::Tera;
// Use globbing let tera = Tera::new("templates/*/"); ```
Tera will panic on invalid templates which means you should add template compilation as a build step when compiling. Have a look at that page to learn more about build script.
This step is also meant to only be ran once, so you can use something like lazy_static to have the tera
variable as a global static in your app.
If no errors happened while parsing any of the files, you can now render a template like so:
```rust use tera::Context;
let mut context = Context::new(); context.add("product", &product); context.add("vat_rate", &0.20);
tera.render("products/product.html", context);
``
Notice that the name of the template is based on the root of the template directory given to the Tera instance.
Contexttakes any primitive value or a struct that implements the
Serializetrait from
serde_json`.
If the data you want to render implements the Serialize
trait, you can bypass the context and render the value directly:
``rust
// product here is a struct with a
name` field
tera.value_render("products/product.html", &product);
// in product.html {{ name }} ``` Note that this method only works for objects that would be converted to JSON objects, like structs and maps.
By default, autoescaping is turned on for files ending in .html
, .htm
and .xml
.
You can change that by calling Tera::autoescape_on
with a Vec of suffixes.
rust
let mut tera = Tera::new("templates/**/*");
tera.autoescape_on(vec!["email.j2", ".sql"]);
Note that calling autoescape_on
will remove the defaults. If you want to completely disable autoescaping, simply
call tera.autoescape_on(vec![]);
.
Another thing to note is that you can pass file suffixes and not only extensions to that method.
You can access variables of the context by using the {{ my_variable_name }}
construct.
You can access attributes by using the dot (.
) like {{ product.name }}
.
You can access specific members of an array or tuple by using the .i
notation where i
is a zero-based index.
You can also do some maths: {{ product.price + 10 }}
. If product.price
is not a number type, the render
method will return an error.
Similar to the if in Rust, you can have several conditions and also use elif
and else
:
jinja
{% if price < 10 || always_show %}
Price is {{ price }}.
{% elif price > 1000 %}
That's expensive!
{% else %}
N/A
{% endif %}
Undefined variables are considered falsy. This means that you can test for the presence of a variable in the current context by writing:
jinja
{% if my_var %}
{{ my_var }}
{% else %}
Sorry, my_var isn't defined.
{% endif %}
If my_var
is defined, the if
branch will be rendered. Otherwise, the else
branch will be rendered.
Every if
statement has to end with an endif
tag.
Loop over items in a array:
jinja
{% for product in products %}
{{loop.index}}. {{product.name}}
{% endfor %}
A few special variables are available inside for loops like in jinja2:
loop.index
: current iteration 1-indexedloop.index0
: current iteration 0-indexedloop.first
: whether this is the first iterationloop.last
: whether this is the last iterationThe for
statement has to end with a endfor
tag.
Allow you to ignore texts that Tera would try to render otherwise.
jinja
{% raw %}
Hello {{ name }}
{% endraw %}
would be rendered:
jinja
Hello {{ name }}
Tera uses the same kind of inheritance as Jinja2 and django templates: you define a base template and extends it in child templates. There can be multiple levels of inheritance (i.e. A extends B that extends C).
A base template typically contains the basic html structure as well as several blocks
that can contain placeholders.
For example, here's a base.html
almost copied from the jinja documentation:
jinja
<!DOCTYPE html>
<html lang="en">
<head>
{% block head %}
<link rel="stylesheet" href="style.css" />
<title>{% block title %}{% endblock title %} - My Webpage</title>
{% endblock head %}
</head>
<body>
<div id="content">{% block content %}{% endblock content %}</div>
<div id="footer">
{% block footer %}
© Copyright 2008 by <a href="http://domain.invalid/">you</a>.
{% endblock footer %}
</div>
</body>
</html>
The difference with Jinja being that endblock
tags must be named.
This defines 4 block
tag that child templates can override.
The head
and footer
block contains some html already which will be rendered if they are not overridden.
Again, straight from jinja2 docs:
jinja
{% extends "base.html" %}
{% block title %}Index{% endblock title %}
{% block head %}
{{ super() }}
<style type="text/css">
.important { color: #336699; }
</style>
{% endblock head %}
{% block content %}
<h1>Index</h1>
<p class="important">
Welcome to my awesome homepage.
</p>
{% endblock content %}
To indicate inheritance, you have use the extends
tag at the top of a file followed by the name of the template you want
to extend.
The {{ super() }}
variable call is a magic variable in Tera that means: render the parent block there.
Note that nested blocks are valid in Tera, consider the following templates:
```jinja2 // grandparent {% block hey %}hello{% endblock hey %}
// parent {% extends "grandparent" %} {% block hey %}hi and grandma says {{ super() }} {% block ending %}sincerely{% endblock ending %}{% endblock hey %}
// child
{% extends "parent" %}
{% block hey %}dad says {{ super() }}{% endblock hey %}
{% block ending %}{{ super() }} with love{% endblock ending %}
``
The block
endingis nested in the
heyblock, which means rendering the
child` template will do the following:
grandparent
hey
block in it and checks if it is in child
and parent
templatechild
so we render it, it contains a super()
call so we render the hey
block from parent
, which also contains a super()
so we render the hey
block of the grandparent
template as wellending
block in child
, render it and also renders the ending
block of parent
as there is a super()
The end result of that rendering (not counting whitespace) will be: "dad says hi and grandma says hello sincerely with love".
You can include a template to be rendered using the current context with the include
tag.
jinja
{% include "included.html" %}
Tests can be used against a variable to check some condition on the variable.
Perhaps the most common use of variable tests is to check if a variable is
defined before its use to prevent run-time errors. Tests are made against
variables in if
blocks using the is
keyword. For example, to test if user
is defined, you would write:
{% if user is defined %}
... do something with user ...
{% else %}
... don't use user here ...
{% end %}
Note that testers allow expressions, so the following is a valid test as well:
{% if my_number + 1 is odd %}
blabla
{% endif %}
Here are the currently implemented testers:
Returns true if the given variable is defined.
Returns true if the given variable is undefined.
Returns true if the given variable is an odd number.
Returns true if the given variable is an even number.
Returns true if the given variable is a string.
Returns true if the given variable is a number.
Variables can be modified by filters.
Filters are separated from the variable by a pipe symbol (|
) and may have named arguments in parentheses. Multiple filters can be chained: the output of one filter is applied to the next.
For example, {{ name | lower | replace(from="doctor", to="Dr.") }}
will take a variable called name
and make it lowercase and then replace instances of doctor
by Dr.
. It's equivalent to replace(lower(name), from="doctor", to="Dr.")
as a function.
Note that calling filters on a incorrect type like trying to capitalize an array will result in a error.
Lowercase a string
Returns number of words in a string
Returns the string with all its character lowercased apart from the first char which is uppercased.
Takes 2 mandatory string named arguments: from
and to
. It will return a string with all instances of
the from
string with the to
string.
Example: {{ name | replace(from="Robert", to="Bob")}}
Adds slashes before quotes.
Example: {{ value | addslashes }}
If value is "I'm using Tera", the output will be "I\'m using Tera"
Transform a string into ASCII, lowercase it, trim it, converts spaces to hyphens and remove all characters that are not numbers, lowercase letters or hyphens.
Example: {{ value | slugify}}
If value is "-Hello world! ", the output will be "hello-world".
Capitalizes each word inside a sentence.
Example: {{ value | title}}
If value is "foo bar", the output will be "Foo Bar".
Tries to remove HTML tags from input. Does not guarantee well formed output if input is not valid HTML.
Example: {{ value | striptags}}
If value is "Joel", the output will be "Joel"
Returns the first element of an array. If the array is empty, returns empty string;
Returns the last element of an array. If the array is empty, returns empty string;
Joins an array with a string.
Example: {{ value|join:" // " }}
If value is the array ['a', 'b', 'c'], the output will be the string "a // b // c".
Returns the length of an array or a string, 0 if the value is not an array. // TODO: return an error instead to be consistent?
Returns a reversed string or array
Percent-encodes a string.
Example: {{ value | urlencode }}
If value is "/foo?a=b&c=d", the output will be "/foo%3Fa%3Db%26c%3Dd".
Takes an optional argument of characters that shouldn't be percent-encoded (/
by default). So, to encode slashes as well, you can do {{ value | urlencode(safe: "") }}
.
Returns a suffix if the value is greater or equal than 2. Suffix defaults to s
Example: You have {{ num_messages }} message{{ num_messages|pluralize }}
If nummessages is 1, the output will be You have 1 message. If nummessages is 2 the output will be You have 2 messages.
You can specify the suffix as an argument that way: {{ num_messages|pluralize(suffix="es") }}
Returns a number rounded following the method given. Default method is common
which will round to the nearest integer.
ceil
and floor
are available as alternative methods.
Example: {{ num | round }} {{ num | round(method="ceil") }}
Returns a human-readable file size (i.e. '110 MB') from an integer.
Example: {{ num | filesizeformat }}
Escapes a string's HTML. Specifically, it makes these replacements:
Macros are a simple way to reuse template bits. Think of them as functions that you can call that return some text.
Macros are defined as follows:
jinja2
{% macro input(label, type) %}
<label>
{{ label }}
<input type="{{type}}" />
</label>
{% endmacro hello_world %}
You need to import the file containing the macros in order to use them:
jinja2
{% import "macros.html" as macros %}
You can name that file namespace (macros
in the example) anything you want.
You can call a macro the following way:
jinja2
{{ macros::input(label="Name", type="text") }}
Do note that macros, like other functions in Tera, require keyword arguments.
If you are trying to call a macro defined in the same file, you will need to use the `{{ self::my_macro() }}`` syntax to call it the file its defined in.
Macros can be called recursively but there is no limit to recursion so make sure you macro ends.
There are a few restrictions to the content inside macros: no macros definitions or blocks are allowed.