[go: up one dir, main page]

tera 0.10.3

Template engine based on Jinja2/Django templates
Documentation
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
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
# Tera

[![Build Status](https://travis-ci.org/Keats/tera.svg)](https://travis-ci.org/Keats/tera)
[![Build status](https://ci.appveyor.com/api/projects/status/omd2auu2e9qc8ukd?svg=true)](https://ci.appveyor.com/project/Keats/tera)

Current release API docs are available on [docs.rs](https://docs.rs/tera).
This project follows semver only for the public API, public API here meaning functions appearing in the docs.
Some features, like accessing the AST, are also available but breaking changes in them can happen in minor versions.

## Introduction
Tera is a template engine based on [Jinja2](http://jinja.pocoo.org/) and the [Django template language](https://docs.djangoproject.com/en/1.9/topics/templates/).

While Tera is inspired by the engines above, it doesn't aim to be a complete port of one of the other.

Example of a simple template file:

```jinja
<html>
  <head>
    <title>{{ product.name }}</title>
  </head>
  <body>
    <h1>{{ product.name | upper }} - {{ 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 or 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>
```

## Usage
The primary method of using Tera will load and parse all the templates in the given glob.

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 = compile_templates!("templates/**/*");
```

The `compile_templates!` macro will try to parse all files found in the glob. If errors are encountered, it will print them and exit the process.

If you don't want to exit the process on errors, you can call the `Tera::new` method and handle errors directly.
Compiling templates is a step is also meant to only be ran once: use something like [lazy_static](https://crates.io/crates/lazy_static) 
to have the `tera` variable as a global static in your app. See `examples/basic.rs` for an example.

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.
`Context` takes any primitive value or a struct that implements the `Serialize` trait from `serde_json`. You can also merge 2 
`Context` by using the `Context::extend` method.

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.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.
 

Want to render a single template? For example a user given one? Tera provides the `one_off` function for that.

```rust
// The last parameter is whether we want to autoescape the template or not.
// Should be true in 99% of the cases for HTML
let context = Context::new()
// add stuff to context
let result = Tera::one_off(user_tpl, &context, true);
// Or use a struct
let result = Tera::one_off(user_tpl, &user, true);
```

### Extending another instance of Tera
Quite often, you will want to extend some third party templates - think Django admin for example.
Tera allows you to do that quite easily:

```rust
// I get my templates first
let mut tera = compile_templates!("templates/**/*");
// And then I extend my instance with the templates from the framework
tera.extend(&SOME_FRAMEWORK_TERA)?;
```
Note that if a template with the same name exists in `SOME_FRAMEWORK_TERA` and your `Tera` instance,
it will keep your template.

Filters and testers will also automatically be copied over using the same logic.

### Reloading
If you are working with templates, getting instant feedback is important and you probably don't want to wait
ages to fix a typo. Tera comes with `Tera::full_reload` that can be used in conjunction of watching file changes.

`Tera::full_reload` is only available if you are using a glob to load templates and will keep the templates that were extended
around, unless you overwrite them.

### Autoescaping
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. Suffixes don't have to be extensions.

```rust
let mut tera = compile_templates!("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![]);`.


## Template writer documentation
### Variables
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.

A magical variable exists if you want to print the current context: `__tera_context`.

### If
Conditionals are fully supported and are identical to the ones in Python.

```jinja2
{% if price < 10 or always_show %}
   Price is {{ price }}.
{% elif price > 1000 and not rich %}
   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:

```jinja2
{% if my_var %}
    {{ my_var }}
{% else %}
    Sorry, my_var isn't defined.
{% endif %}
```
Every `if` statement has to end with an `endif` tag.

### For
Loop over items in a array:
```jinja2
{% for product in products %}
  {{loop.index}}. {{product.name}}
{% endfor %}
```
A few special variables are available inside for loops:

- `loop.index`: current iteration 1-indexed
- `loop.index0`: current iteration 0-indexed
- `loop.first`: whether this is the first iteration
- `loop.last`: whether this is the last iteration

Every `for` statement has to end with an `endfor` tag.

You can also loop on maps and structs using the following syntax:
```jinja2
{% for key, value in products %}
  {{loop.index}}. {{product.name}}
{% endfor %}
```
`key` and `value` can be named however you want, they just need to be separated with a comma.

If you are iterating on an array, you can also apply filters to the container:

```jinja2
{% for product in products | reverse %}
  {{loop.index}}. {{product.name}}
{% endfor %}
```

### Raw
Tera will consider all text inside the `raw` block as a string and won't try to
render what's inside. Useful if you have text that contains Tera delimiters.
```jinja2
{% raw %}
  Hello {{ name }}
{% endraw %}
```
would be rendered:
```jinja2
Hello {{ name }}
```

### Inheritance
Tera uses the same kind of inheritance as Jinja2 and Django templates: 
you define a base template and extends it in child templates through blocks.
There can be multiple levels of inheritance (i.e. A extends B that extends C).

#### Base template
A base template typically contains the basic document structure as well as 
several `blocks` that can have content.

For example, here's a `base.html` almost copied from the jinja documentation:

```jinja2
<!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 %}
        &copy; Copyright 2008 by <a href="http://domain.invalid/">you</a>.
        {% endblock footer %}
    </div>
</body>
</html>
```
The only difference with Jinja2 is that the `endblock` tags have to be named.
This `base.html` template defines 4 `block` tag that child templates can override. 
The `head` and `footer` block have some content already which will be rendered if they are not overridden.

#### Child template
Again, straight from Jinja2 docs:

```jinja2
{% 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 as the first thing in the file followed by the name of the template you want
to extend.
The `{{ super() }}` variable call tells Tera to render the parent block there.

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 `ending` is nested in the `hey` block. Rendering the `child` template will do the following:

- Find the first base template: `grandparent`
- See `hey` block in it and checks if it is in `child` and `parent` template
- It is in `child` 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 well
- See `ending` 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".

#### Include
You can include a template to be rendered using the current context with the `include` tag.

```jinja
{% include "included.html" %}
```

Tera doesn't offer passing a custom context to the `include` tag. If you want to do that, use macros.

### Macros
Macros are a simple way to reuse template bits. Think of them as functions that you can call and return some text.
Macros need to be defined in a separate file and imported to be useable.

They are defined as follows:

```jinja2
{% macro input(label, type) %}
    <label>
        {{ label }}
        <input type="{{type}}" />
    </label>
{% endmacro hello_world %}
```

In order to use them, you need to import the file containing the macros:

```jinja2
{% import "macros.html" as macros %}
```
You can name that file namespace (`macros` in the example) anything you want.
A macro is called like this:

```jinja2
// namespace::macro_name(**kwargs)
{{ macros::input(label="Name", type="text") }}
```
Do note that macros, like filters, require keyword arguments.
If you are trying to call a macro defined in the same file or itself, you will need to use the `self` namespace.
The `self` namespace can only be used in macros.
Macros can be called recursively but there is no limit to recursion so make sure you macro ends.

Here's an example of a recursive macro:

```jinja2
{% macro factorial(n) %}
  {% if n > 1 %}{{ n }} - {{ self::factorial(n=n-1) }}{% else %}1{% endif %}
{% endmacro factorial %}
```

Macros body can contain all normal Tera syntax with the exception of macros definition, `block` and `extends`.

### Tests

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:

```jinja2
{% 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:

```jinja2
{% if my_number + 1 is odd %}
 blabla
{% endif %}
```

Tests are functions with the `fn(Option<Value>, Vec<Value>) -> Result<bool>` type and custom ones can be
registered like so:

```rust
tera.register_tester("odd", testers::odd);
```

Here are the currently built-in testers:

#### defined
Returns true if the given variable is defined.

#### undefined
Returns true if the given variable is undefined.

#### odd
Returns true if the given variable is an odd number.

#### even
Returns true if the given variable is an even number.

#### string
Returns true if the given variable is a string.

#### number
Returns true if the given variable is a number.

#### divisibleby
Returns true if the given expression is divisible by the arg given.

Example:
```jinja2
{% if rating is divisibleby 2 %}
    Divisible
{% endif %}
```

#### iterable
Returns true if the given variable can be iterated over in Tera (ie is an array/tuple).

### Filters
Variables can be modified by filters before being rendered. 
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`, 
make it lowercase and then replace instances of `doctor` by `Dr.`. 
It is equivalent to `replace(lower(name), from="doctor", to="Dr.")` if we were to look at it as functions.

Note that calling filters on a incorrect type like trying to capitalize an array will result in a error.

Filters are functions with the `fn(Value, HashMap<String, Value>) -> Result<Value>` type and custom ones can be added
like so:

```rust
tera.register_filter("upper", string::upper);
```

Tera has currently the following filters built-in:

#### lower
Lowercase a string

#### wordcount
Returns number of words in a string

#### capitalize
Returns the string with all its character lowercased apart from the first char which is uppercased.

#### replace
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")}}`

#### addslashes
Adds slashes before quotes.

Example: `{{ value | addslashes }}` 

If value is "I'm using Tera", the output will be "I\'m using Tera".

#### slugify
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".

#### title
Capitalizes each word inside a sentence.

Example: `{{ value | title}}`

If value is "foo  bar", the output will be "Foo  Bar".

#### striptags
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 "<b>Joel</b>", the output will be "Joel".

#### first
Returns the first element of an array.
If the array is empty, returns empty string.

#### last
Returns the last element of an array.
If the array is empty, returns empty string.

#### join
Joins an array with a string.

Example: `{{ value| join(sep=" // ") }}`

If value is the array `['a', 'b', 'c']`, the output will be the string "a // b // c".

#### length
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?

#### reverse
Returns a reversed string or array.

#### urlencode
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="") }}`. 

#### pluralize
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 num_messages is 1, the output will be You have 1 message. If num_messages 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") }}`

#### round
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.
Another optional argument, `precision`, is available to select the precision of the rounding. It defaults to `0`, which will
round to the nearest integer for the given method.

Example: `{{ num | round }} {{ num | round(method="ceil", precision=2) }}`

#### filesizeformat
Returns a human-readable file size (i.e. '110 MB') from an integer.

Example: `{{ num | filesizeformat }}`

#### date
Parse a timestamp into a date(time) string. Defaults to `YYYY-MM-DD` format.
Time formatting syntax is inspired from strftime and a full reference is available 
on [chrono docs](https://lifthrasiir.github.io/rust-chrono/chrono/format/strftime/index.html).

Example: `{{ ts | date }} {{ ts | date(format="%Y-%m-%d %H:%M") }}`

#### escape
Escapes a string's HTML. Specifically, it makes these replacements:

- & is converted to `&amp;`
- < is converted to `&lt;`
- > is converted to `&gt;`
- " (double quote) is converted to `&quot;`
- ' (single quote) is converted to `&#x27;`
- / is converted to `&#x27;`
- `` ` `` is converted to `&#96;`

#### safe
Mark a variable as safe: HTML will not be escaped anymore.
Currently the position of the safe filter does not matter, e.g.
`{{ content | safe | replace(from="Robert", to="Bob") }}` and `{{ content | replace(from="Robert", to="Bob") | safe }}` will output the same thing.

#### get
Access a value from an object when the key is not a Tera identifier.
Example: `{{ sections | get(key="posts/content") }}`


### Filter sections
Whole sections can also be processed by filters if they are encapsulated in `{% filter name %}` and `{% endfilter %}`
tags where `name` is the name of the filter.

Example:
```jinja2
{% filter upper %}
    Hello
{% endfilter %}
```

This example transforms the text `Hello` in all upper-case (`HELLO`).

Note that calling filters on an incorrect type like trying to capitalize an array will result in a error.

If the return type of the filter is not a string it will be converted to a string using the JSON format.

Filters are functions with the `fn(Value, HashMap<String, Value>) -> Result<Value>` type and custom ones can be added
like so:

```rust
tera.register_filter("upper", string::upper);
```
Filter functions for regular filters can also be used for filter sections.

### Global functions
You can also pass global functions to the Tera instance. 
Global functions are Rust code that return a `Result<Value>` from the given params.

Quite often, global functions will need to capture some external variables, such as a `url_for` global function needing
the list of URLs present for example. Therefore, the type of `GlobalFn` is a boxed closure: `Box<Fn(HashMap<String, Value>) -> Result<Value> + Sync>`.

Here's an example on how to implement a very basic global function:

```rust
fn make_url_for(urls: BTreeMap<String, String>) -> GlobalFn {
    // args is a HashMap<String, Value>
    Box::new(move |args| -> Result<Value> {
        match args.get("name") {
            Some(val) => match from_value::<String>(val.clone()) {
                Ok(v) =>  Ok(to_value(urls.get(&v).unwrap()).unwrap()),
                Err(_) => Err("oops".into()),
            },
            None => Err("oops".into()),
        }
    })
}
```
You then need to add it to Tera:

```rust
tera.register_global_function("url_for", make_url_for(urls));
```

And you can now call it from a template:

```jinja2
{{ url_for(name="home") }}
```

Currently global functions can be called in two places in templates:

- variable block: `{{ url_for(name="home") }}`
- for loop container: `{% for i in range(end=5) %}`

Tera comes with some built-in global functions.

#### range
Returns an array of integers created using the arguments given. 
There are 3 arguments, all integers:

- `end`: where to stop, mandatory
- `start`: where to start from, defaults to `0`
- `step_by`: with what number do we increment, defaults to `1`

## Accessing the AST
Tera gives access to the AST of each template but the functions required is hidden
from the docs at the current time.
See `examples/ast.rs` for an example on how to get the AST for a given template.

The AST is not considered public and breaking changes could happen in minor versions.

### Assignments
You can assign values to variables. Assignments in for loops and macros are scoped to their context but
assignments outside of those will be set in the global context.

```jinja2
{% set my_var = "hello" %}
{% set my_var = 1 + 4 %}
{% set my_var = some_var %}
{% set my_var = macros::some_macro() %}
{% set my_var = global_fn() %}
```