From 3248ad939a4b331472dc0fe985546da5a6641204 Mon Sep 17 00:00:00 2001 From: cetra3 Date: Mon, 29 Jun 2020 09:40:01 +0000 Subject: Initial Askama Book (#332) --- book/.gitignore | 1 + book/book.toml | 6 + book/src/SUMMARY.md | 10 ++ book/src/askama.md | 56 +++++++ book/src/configuration.md | 67 ++++++++ book/src/creating_templates.md | 93 +++++++++++ book/src/debugging.md | 49 ++++++ book/src/filters.md | 254 ++++++++++++++++++++++++++++ book/src/getting_started.md | 37 +++++ book/src/integrations.md | 75 +++++++++ book/src/template_syntax.md | 370 +++++++++++++++++++++++++++++++++++++++++ 11 files changed, 1018 insertions(+) create mode 100644 book/.gitignore create mode 100644 book/book.toml create mode 100644 book/src/SUMMARY.md create mode 100644 book/src/askama.md create mode 100644 book/src/configuration.md create mode 100644 book/src/creating_templates.md create mode 100644 book/src/debugging.md create mode 100644 book/src/filters.md create mode 100644 book/src/getting_started.md create mode 100644 book/src/integrations.md create mode 100644 book/src/template_syntax.md diff --git a/book/.gitignore b/book/.gitignore new file mode 100644 index 0000000..7585238 --- /dev/null +++ b/book/.gitignore @@ -0,0 +1 @@ +book diff --git a/book/book.toml b/book/book.toml new file mode 100644 index 0000000..b46541a --- /dev/null +++ b/book/book.toml @@ -0,0 +1,6 @@ +[book] +authors = ["cetra3"] +language = "en" +multilingual = false +src = "src" +title = "Askama" diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md new file mode 100644 index 0000000..2994524 --- /dev/null +++ b/book/src/SUMMARY.md @@ -0,0 +1,10 @@ +# Summary + +- [Askama](./askama.md) +- [Getting Started](./getting_started.md) +- [Creating Templates](./creating_templates.md) +- [Debugging](./debugging.md) +- [Configuration](./configuration.md) +- [Template Syntax](./template_syntax.md) +- [Filters](./filters.md) +- [Integrations](./integrations.md) diff --git a/book/src/askama.md b/book/src/askama.md new file mode 100644 index 0000000..739f6d2 --- /dev/null +++ b/book/src/askama.md @@ -0,0 +1,56 @@ +# Askama + +[![Documentation](https://docs.rs/askama/badge.svg)](https://docs.rs/askama/) +[![Latest version](https://img.shields.io/crates/v/askama.svg)](https://crates.io/crates/askama) +[![Build Status](https://github.com/djc/askama/workflows/CI/badge.svg)](https://github.com/djc/askama/actions?query=workflow%3ACI) +[![Chat](https://badges.gitter.im/gitterHQ/gitter.svg)](https://gitter.im/djc/askama) + +Askama implements a template rendering engine based on Jinja. +It generates Rust code from your templates at compile time +based on a user-defined `struct` to hold the template's context. +See below for an example, or read [the documentation][docs]. + +**"I use Askama for actix's TechEmpower benchmarks."** -- +[Nikolay Kim][fafhrd91], creator of actix-web + +**"Pretty exciting. I would love to use this already."** -- +[Armin Ronacher][mitsuhiko], creator of Jinja + +All feedback welcome. Feel free to file bugs, requests for documentation and +any other feedback to the [issue tracker][issues] or [tweet me][twitter]. +Many thanks to [David Tolnay][dtolnay] for his support in improving Askama. + +Askama was created by and is maintained by Dirkjan Ochtman. If you are in a +position to support ongoing maintenance and further development or use it +in a for-profit context, please consider supporting my open source work on +[Patreon][patreon]. + +### Feature highlights + +* Construct templates using a familiar, easy-to-use syntax +* Template code is compiled into your crate for [optimal performance][benchmarks] +* Benefit from the safety provided by Rust's type system +* Optional built-in support for Actix, Gotham, Iron, Rocket and warp web frameworks +* Debugging features to assist you in template development +* Templates must be valid UTF-8 and produce UTF-8 when rendered +* Works on stable Rust + +### Supported in templates + +* Template inheritance +* Loops, if/else statements and include support +* Macro support +* Variables (no mutability allowed) +* Some built-in filters, and the ability to use your own +* Whitespace suppressing with '-' markers +* Opt-out HTML escaping +* Syntax customization + +[docs]: https://docs.rs/askama +[fafhrd91]: https://github.com/fafhrd91 +[mitsuhiko]: http://lucumr.pocoo.org/ +[issues]: https://github.com/djc/askama/issues +[twitter]: https://twitter.com/djco/ +[dtolnay]: https://github.com/dtolnay +[patreon]: https://www.patreon.com/dochtman +[benchmarks]: https://github.com/djc/template-benchmarks-rs diff --git a/book/src/configuration.md b/book/src/configuration.md new file mode 100644 index 0000000..7c68724 --- /dev/null +++ b/book/src/configuration.md @@ -0,0 +1,67 @@ +# Configuration + +At compile time, Askama will read optional configuration values from +`askama.toml` in the crate root (the directory where `Cargo.toml` can +be found). Currently, this covers the directories to search for templates, +custom syntax configuration and escaper configuration. + +This example file demonstrates the default configuration: + +```toml +[general] +# Directories to search for templates, relative to the crate root. +dirs = ["templates"] +``` + +Here is an example that defines two custom syntaxes: + +```toml +[general] +default_syntax = "foo" + +[[syntax]] +name = "foo" +block_start = "%{" +comment_start = "#{" +expr_end = "^^" + +[[syntax]] +name = "bar" +block_start = "%%" +block_end = "%%" +comment_start = "%#" +expr_start = "%{" +``` + +A syntax block consists of at least the attribute `name` which uniquely +names this syntax in the project. + +The following keys can currently be used to customize template syntax: + +* `block_start`, defaults to `{%` +* `block_end`, defaults to `%}` +* `comment_start`, defaults to `{#` +* `comment_end`, defaults to `#}` +* `expr_start`, defaults to `{{` +* `expr_end`, defaults to `}}` + +Values must be 2 characters long and start delimiters must all start with the same +character. If a key is omitted, the value from the default syntax is used. + +Here is an example of a custom escaper: + +```toml +[[escaper]] +path = "::tex_escape::Tex" +extensions = ["tex"] +``` + +An escaper block consists of the attributes `path` and `name`. `path` +contains a Rust identifier that must be in scope for templates using this +escaper. `extensions` defines a list of file extensions that will trigger +the use of that escaper. Extensions are matched in order, starting with the +first escaper configured and ending with the default escapers for HTML +(extensions `html`, `htm`, `xml`, `j2`, `jinja`, `jinja2`) and plain text +(no escaping; `md`, `yml`, `none`, `txt`, and the empty string). Note that +this means you can also define other escapers that match different extensions +to the same escaper. \ No newline at end of file diff --git a/book/src/creating_templates.md b/book/src/creating_templates.md new file mode 100644 index 0000000..406a825 --- /dev/null +++ b/book/src/creating_templates.md @@ -0,0 +1,93 @@ +# Creating Templates + +An Askama template is a `struct` definition which provides the template +context combined with a UTF-8 encoded text file (or inline source, see +below). Askama can be used to generate any kind of text-based format. +The template file's extension may be used to provide content type hints. + +A template consists of **text contents**, which are passed through as-is, +**expressions**, which get replaced with content while being rendered, and +**tags**, which control the template's logic. +The [template syntax](template_syntax.md) is very similar to [Jinja](http://jinja.pocoo.org/), +as well as Jinja-derivatives like [Twig](http://twig.sensiolabs.org/) or +[Tera](https://github.com/Keats/tera). + +```rust +#[derive(Template)] // this will generate the code... +#[template(path = "hello.html")] // using the template in this path, relative + // to the `templates` dir in the crate root +struct HelloTemplate<'a> { // the name of the struct can be anything + name: &'a str, // the field name should match the variable name + // in your template +} +``` + +## The `template()` attribute + +Askama works by generating one or more trait implementations for any +`struct` type decorated with the `#[derive(Template)]` attribute. The +code generation process takes some options that can be specified through +the `template()` attribute. The following sub-attributes are currently +recognized: + +* `path` (as `path = "foo.html"`): sets the path to the template file. The + path is interpreted as relative to the configured template directories + (by default, this is a `templates` directory next to your `Cargo.toml`). + The file name extension is used to infer an escape mode (see below). In + web framework integrations, the path's extension may also be used to + infer the content type of the resulting response. + Cannot be used together with `source`. + ```rust + #[derive(Template)] + #[template(path = "hello.html")] + struct HelloTemplate<'a> { ... } + ``` + +* `source` (as `source = "{{ foo }}"`): directly sets the template source. + This can be useful for test cases or short templates. The generated path + is undefined, which generally makes it impossible to refer to this + template from other templates. If `source` is specified, `ext` must also + be specified (see below). Cannot be used together with `path`. + ```rust + #[derive(Template)] + #[template(source = "Hello {{ name }}")] + struct HelloTemplate<'a> { + name: &'a str, + } + ``` +* `ext` (as `ext = "txt"`): lets you specify the content type as a file + extension. This is used to infer an escape mode (see below), and some + web framework integrations use it to determine the content type. + Cannot be used together with `path`. + ```rust + #[derive(Template)] + #[template(source = "Hello {{ name }}", ext = "txt")] + struct HelloTemplate<'a> { + name: &'a str, + } + ``` +* `print` (as `print = "code"`): enable debugging by printing nothing + (`none`), the parsed syntax tree (`ast`), the generated code (`code`) + or `all` for both. The requested data will be printed to stdout at + compile time. + ```rust + #[derive(Template)] + #[template(path = "hello.html", print = "all")] + struct HelloTemplate<'a> { ... } + ``` +* `escape` (as `escape = "none"`): override the template's extension used for + the purpose of determining the escaper for this template. See the section + on configuring custom escapers for more information. + ```rust + #[derive(Template)] + #[template(path = "hello.html", escape = "none")] + struct HelloTemplate<'a> { ... } + ``` +* `syntax` (as `syntax = "foo"`): set the syntax name for a parser defined + in the configuration file. The default syntax , "default", is the one + provided by Askama. + ```rust + #[derive(Template)] + #[template(path = "hello.html", syntax = "foo")] + struct HelloTemplate<'a> { ... } + ``` \ No newline at end of file diff --git a/book/src/debugging.md b/book/src/debugging.md new file mode 100644 index 0000000..8995438 --- /dev/null +++ b/book/src/debugging.md @@ -0,0 +1,49 @@ +# Debugging and Troubleshooting + +You can view the parse tree for a template as well as the generated code by +changing the `template` attribute item list for the template struct: + +```rust +#[derive(Template)] +#[template(path = "hello.html", print = "all")] +struct HelloTemplate<'a> { ... } +``` + +The `print` key can take one of four values: + +* `none` (the default value) +* `ast` (print the parse tree) +* `code` (print the generated code) +* `all` (print both parse tree and code) + +The resulting output will be printed to `stderr` during the compilation process. + +The parse tree looks like this for the example template: + +``` +[Lit("", "Hello,", " "), Expr(WS(false, false), Var("name")), +Lit("", "!", "\n")] +``` + +The generated code looks like this: + +```rust +impl < 'a > ::askama::Template for HelloTemplate< 'a > { + fn render_into(&self, writer: &mut ::std::fmt::Write) -> ::askama::Result<()> { + write!( + writer, + "Hello, {expr0}!", + expr0 = &::askama::MarkupDisplay::from(&self.name), + )?; + Ok(()) + } + fn extension() -> Option<&'static str> { + Some("html") + } +} +impl < 'a > ::std::fmt::Display for HelloTemplate< 'a > { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + ::askama::Template::render_into(self, f).map_err(|_| ::std::fmt::Error {}) + } +} +``` diff --git a/book/src/filters.md b/book/src/filters.md new file mode 100644 index 0000000..35cb557 --- /dev/null +++ b/book/src/filters.md @@ -0,0 +1,254 @@ +# Filters + +Values such as those obtained from variables can be post-processed +using **filters**. +Filters are applied to values using the pipe symbol (`|`) and may +have optional extra arguments in parentheses. +Filters can be chained, in which case the output from one filter +is passed to the next. + +``` +{{"HELLO" | lower}} +``` + +Askama has a collection of built-in filters, documented below, but can also include custom filters. + +## Built-In Filters + +### capitalize + +Capitalize a value. The first character will be uppercase, all others lowercase: + +``` +{{ "hello" | capitalize}} +``` + +Output: + +``` +Hello +``` + +### center + +Centers the value in a field of a given width: + +``` +-{{ "a" | center(5)}}- +``` + +Output: +``` +- a - +``` + +### escape | e + +Escapes html characters in strings: + +``` +{{ "Escape <>&" | e}} +``` + +Output: + +``` +Escape <>& +``` + +### filesizeformat + +Returns adequate string representation (in KB, ..) of number of bytes: + +``` +{{ 1000 | filesizeformat }} +``` + +Output: +``` +1 KB +``` + +### format + +Formats arguments according to the specified format + +The first argument to this filter must be a string literal (as in normal Rust). + +All arguments are passed through to the format!() macro by the Askama code generator. + +``` +{{ "{:?}"|format(var) }} +``` + +### indent + +Indent newlines with width spaces + +``` +{{ "hello\nfoo\nbar" | indent(4) }} +``` + +Output: + +``` +hello + foo + bar +``` + +### join + +Joins iterable into a string separated by provided argument + +``` +array = &["foo", "bar", "bazz"] +``` + +``` +{{ array | join(", ")}} +``` + +Output: + +``` +foo, bar, bazz +``` + +### linebreaks + +Replaces line breaks in plain text with appropriate HTML + +A single newline becomes an HTML line break
and a new line followed by a blank line becomes a paragraph break

. + +``` +{{ "hello\nworld\n\nfrom\naskama" | linebreaks }} +``` + +Output: + +``` +

hello
world

from
askama

+``` + +### linebreaksbr + +Converts all newlines in a piece of plain text to HTML line breaks + +``` +{{ "hello\nworld\n\nfrom\naskama" | linebreaks }} +``` + +Output: + +``` +hello
world

from
askama +``` + +### lower | lowercase + +Converts to lowercase + +``` +{{ "HELLO" | lower }} +``` + +Output: + +``` +hello +``` + +### safe + +Marks a string (or other Display type) as safe. By default all strings are escaped according to the format + +``` +{{ "

I'm Safe

" | safe}} +``` + +Output: + +``` +

I'm Safe

+``` + +### trim + +Strip leading and trailing whitespace + +``` +{{ " hello " | trim}} +``` + +Output: + +``` +hello +``` + +### truncate + +Limit string length, appends '...' if truncated + + +``` +{{ "hello" | truncate(2) }} +``` + +Output: + +``` +he... +``` + +### upper | uppercase + +Converts to uppercase + +``` +{{ "hello" | upper}} +``` + +Output: + +``` +HELLO +``` + +### wordcount + +Count the words in that string + +``` +{{ "askama is sort of cool" | wordcount}} +``` + +``` +5 +``` + +## Custom Filters + +To define your own filters, simply have a module named filters in scope of the context deriving a Template impl. + +Note that in case of name collision, the built in filters take precedence. + +```rust +#[derive(Template)] +#[template(source = "{{ s|myfilter }}", ext = "txt")] +struct MyFilterTemplate<'a> { + s: &'a str, +} + +mod filters { + pub fn myfilter(s: &str) -> ::askama::Result { + Ok(s.replace("oo", "aa")) + } +} + +fn main() { + let t = MyFilterTemplate { s: "foo" }; + assert_eq!(t.render().unwrap(), "faa"); +} +``` \ No newline at end of file diff --git a/book/src/getting_started.md b/book/src/getting_started.md new file mode 100644 index 0000000..4b7fb5f --- /dev/null +++ b/book/src/getting_started.md @@ -0,0 +1,37 @@ +# Getting Started + +First, add the following to your crate's `Cargo.toml`: + +```toml +# in section [dependencies] +askama = "0.8" + +``` + +Now create a directory called `templates` in your crate root. +In it, create a file called `hello.html`, containing the following: + +``` +Hello, {{ name }}! +``` + +In any Rust file inside your crate, add the following: + +```rust +use askama::Template; // bring trait in scope + +#[derive(Template)] // this will generate the code... +#[template(path = "hello.html")] // using the template in this path, relative + // to the `templates` dir in the crate root +struct HelloTemplate<'a> { // the name of the struct can be anything + name: &'a str, // the field name should match the variable name + // in your template +} + +fn main() { + let hello = HelloTemplate { name: "world" }; // instantiate your struct + println!("{}", hello.render().unwrap()); // then render it. +} +``` + +You should now be able to compile and run this code. diff --git a/book/src/integrations.md b/book/src/integrations.md new file mode 100644 index 0000000..ed0fec0 --- /dev/null +++ b/book/src/integrations.md @@ -0,0 +1,75 @@ +# Integrations + +## Rocket integration + +Enabling the `with-rocket` feature appends an implementation of Rocket's +`Responder` trait for each template type. This makes it easy to trivially +return a value of that type in a Rocket handler. See +[the example](https://github.com/djc/askama/blob/master/askama_rocket/tests/basic.rs) +from the Askama test suite for more on how to integrate. + +In case a run-time error occurs during templating, a `500 Internal Server +Error` `Status` value will be returned, so that this can be further +handled by your error catcher. + +## Iron integration + +Enabling the `with-iron` feature appends an implementation of Iron's +`Modifier` trait for each template type. This makes it easy to +trivially return a value of that type in an Iron handler. See +[the example](https://github.com/djc/askama/blob/master/askama_iron/tests/basic.rs) +from the Askama test suite for more on how to integrate. + +Note that Askama's generated `Modifier` implementation currently +unwraps any run-time errors from the template. If you have a better +suggestion, please [file an issue](https://github.com/djc/askama/issues/new). + +## Actix-web integration + +Enabling the `with-actix-web` feature appends an implementation of Actix-web's +`Responder` trait for each template type. This makes it easy to trivially return +a value of that type in an Actix-web handler. See +[the example](https://github.com/djc/askama/blob/master/askama_actix/tests/basic.rs) +from the Askama test suite for more on how to integrate. + +## Gotham integration + +Enabling the `with-gotham` feature appends an implementation of Gotham's +`IntoResponse` trait for each template type. This makes it easy to trivially +return a value of that type in a Gotham handler. See +[the example](https://github.com/djc/askama/blob/master/askama_gotham/tests/basic.rs) +from the Askama test suite for more on how to integrate. + +In case of a run-time error occurring during templating, the response will be of the same +signature, with a status code of `500 Internal Server Error`, mime `*/*`, and an empty `Body`. +This preserves the response chain if any custom error handling needs to occur. + +## Warp integration + +Enabling the `with-warp` feature appends an implementation of Warp's `Reply` +trait for each template type. This makes it simple to return a template from +a Warp filter. See [the example](https://github.com/djc/askama/blob/master/askama_warp/tests/warp.rs) +from the Askama test suite for more on how to integrate. + +## The `json` filter + +Enabling the `serde-json` filter will enable the use of the `json` filter. +This will output formatted JSON for any value that implements the required +`Serialize` trait. + +``` +{ + "foo": "{{ foo }}", + "bar": {{ bar|json }} +} +``` + +## The `yaml` filter + +Enabling the `serde-yaml` filter will enable the use of the `yaml` filter. +This will output formatted JSON for any value that implements the required +`Serialize` trait. + +``` +{{ foo|yaml }} +``` \ No newline at end of file diff --git a/book/src/template_syntax.md b/book/src/template_syntax.md new file mode 100644 index 0000000..6b98d99 --- /dev/null +++ b/book/src/template_syntax.md @@ -0,0 +1,370 @@ +# Template Syntax + +## Variables + +Top-level template variables are defined by the template's context type. +You can use a dot (`.`) to access variable's attributes or methods. +Reading from variables is subject to the usual borrowing policies. +For example, `{{ name }}` will get the ``name`` field from the template +context, +while `{{ user.name }}` will get the ``name`` field of the ``user`` +field from the template context. + +## Assignments + +Inside code blocks, you can also declare variables or assign values +to variables. +Assignments can't be imported by other templates. + +Assignments use the let tag: + +```text +{% let name = user.name %} +{% let len = name.len() %} + +{% let val -%} +{% if len == 0 -%} + {% let val = "foo" -%} +{% else -%} + {% let val = name -%} +{% endif -%} +{{ val }} +``` + +## Filters + +Values such as those obtained from variables can be post-processed +using **filters**. +Filters are applied to values using the pipe symbol (`|`) and may +have optional extra arguments in parentheses. +Filters can be chained, in which case the output from one filter +is passed to the next. + +For example, `{{ "{:?}"|format(name|escape) }}` will escape HTML +characters from the value obtained by accessing the `name` field, +and print the resulting string as a Rust literal. + +The built-in filters are documented as part of the +[filters documentation](filters.md). + +To define your own filters, simply have a module named `filters` in +scope of the context deriving a `Template` `impl`. Note that in case of +name collision, the built in filters take precedence. + +## Whitespace control + +Askama considers all tabs, spaces, newlines and carriage returns to be +whitespace. By default, it preserves all whitespace in template code, +except that a single trailing newline character is suppressed. +However, whitespace before and after expression and block delimiters +can be suppressed by writing a minus sign directly following a +start delimiter or leading into an end delimiter. + +Here is an example: + +```text +{% if foo %} + {{- bar -}} +{% else if -%} + nothing +{%- endif %} +``` + +This discards all whitespace inside the if/else block. If a literal +(any part of the template not surrounded by `{% %}` or `{{ }}`) +includes only whitespace, whitespace suppression on either side will +completely suppress that literal content. + +## Template inheritance + +Template inheritance allows you to build a base template with common +elements that can be shared by all inheriting templates. +A base template defines **blocks** that child templates can override. + +### Base template + +```html + + + + {% block title %}{{ title }} - My Site{% endblock %} + {% block head %}{% endblock %} + + +
+ {% block content %}{% endblock %} +
+ + +``` + +The `block` tags define three blocks that can be filled in by child +templates. The base template defines a default version of the block. +A base template must define one or more blocks in order to enable +inheritance. Blocks can only be specified at the top level of a template +or inside other blocks, not inside `if`/`else` branches or in `for`-loop +bodies. + +### Child template + +Here's an example child template: + +```html +{% extends "base.html" %} + +{% block title %}Index{% endblock %} + +{% block head %} + +{% endblock %} + +{% block content %} +

Index

+

Hello, world!

+{% endblock %} +``` + +The `extends` tag tells the code generator that this template inherits +from another template. It will search for the base template relative to +itself before looking relative to the template base directory. It will +render the top-level content from the base template, and substitute +blocks from the base template with those from the child template. Inside +a block in a child template, the `super()` macro can be called to render +the parent block's contents. + +## HTML escaping + +Askama by default escapes variables if it thinks it is rendering HTML +content. It infers the escaping context from the extension of template +filenames, escaping by default if the extension is one of `html`, `htm`, +or `xml`. When specifying a template as `source` in an attribute, the +`ext` attribute parameter must be used to specify a type. Additionally, +you can specify an escape mode explicitly for your template by setting +the `escape` attribute parameter value (to `none` or `html`). + +Askama escapes `<`, `>`, `&`, `"`, `'`, `\` and `/`, according to the +[OWASP escaping recommendations][owasp]. Use the `safe` filter to +prevent escaping for a single expression, or the `escape` (or `e`) +filter to escape a single expression in an unescaped context. + +[owasp]: https://www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet#RULE_.231_-_HTML_Escape_Before_Inserting_Untrusted_Data_into_HTML_Element_Content + +```rust +#[derive(Template)] +#[template(source = "{{strvar}}")] +struct TestTemplate { + strvar: String, +} + +fn main() { + let s = TestTemplate { + strvar: "// my is \"unsafe\" & should be 'escaped'".to_string(), + }; + assert_eq!( + s.render().unwrap(), + "// my <html> is "unsafe" & \ + should be 'escaped'" + ); +} +``` + +## Control structures + +### For + +Loop over each item in an iterator. For example: + +```html +

Users

+
    +{% for user in users %} +
  • {{ user.name|e }}
  • +{% endfor %} +
+``` + +Inside for-loop blocks, some useful variables are accessible: + +* *loop.index*: current loop iteration (starting from 1) +* *loop.index0*: current loop iteration (starting from 0) +* *loop.first*: whether this is the first iteration of the loop +* *loop.last*: whether this is the last iteration of the loop + + +```html +

Users

+
    +{% for user in users %} + {% if loop.first %} +
  • First: {{user.name}}
  • + {% else %} +
  • User#{{loop.index}}: {{user.name}}
  • + {% endif %} +{% endfor %} +
+``` + +### If + +The *if* statement is used as you might expect: + +```text +{% if users.len() == 0 %} + No users +{% else if users.len() == 1 %} + 1 user +{% else %} + {{ users.len() }} users +{% endif %} +``` + +### Match + +In order to deal with Rust `enum`s in a type-safe way, templates support +match blocks from version 0.6. Here is a simple example showing how to +expand an `Option`: + +```text +{% match item %} + {% when Some with ("foo") %} + Found literal foo + {% when Some with (val) %} + Found {{ val }} + {% when None %} +{% endmatch %} +``` + +That is, a `match` block can optionally contain some whitespace (but +no other literal content), followed by a number of `when` blocks +and an optional `else` block. Each `when` block must name a list of +matches (`(val)`), optionally introduced with a variant name. The +`else` block is equivalent to matching on `_` (matching anything). + +Struct-like enum variants are supported from version 0.8, with the list +of matches surrounded by curly braces instead (`{ field }`). New names +for the fields can be specified after a colon in the list of matches +(`{ field: val }`). + +### Include + +The *include* statement lets you split large or repetitive blocks into +separate template files. Included templates get full access to the context +in which they're used, including local variables like those from loops: + +```text +{% for i in iter %} + {% include "item.html" %} +{% endfor %} +``` + +```text +* Item: {{ i }} +``` + +The path to include must be a string literal, so that it is known at +compile time. Askama will try to find the specified template relative +to the including template's path before falling back to the absolute +template path. Use `include` within the branches of an `if`/`else` +block to use includes more dynamically. + +## Expressions + +Askama supports string literals (`"foo"`) and integer literals (`1`). +It supports almost all binary operators that Rust supports, +including arithmetic, comparison and logic operators. +The parser applies the same precedence order as the Rust compiler. +Expressions can be grouped using parentheses. +The HTML special characters `&`, `<` and `>` will be replaced with their +character entities unless the `escape` mode is disabled for a template. +Methods can be called on variables that are in scope, including `self`. + +``` +{{ 3 * 4 / 2 }} +{{ 26 / 2 % 7 }} +{{ 3 % 2 * 6 }} +{{ 1 * 2 + 4 }} +{{ 11 - 15 / 3 }} +{{ 4 + 5 % 3 }} +{{ 4 | 2 + 5 & 2 }} +``` + +**Warning**: if the result of an expression (a `{{ }}` block) is +equivalent to `self`, this can result in a stack overflow from infinite +recursion. This is because the `Display` implementation for that expression +will in turn evaluate the expression and yield `self` again. + + +## Templates in templates + +Using expressions, it is possible to delegate rendering part of a template to another template. +This makes it possible to inject modular template sections into other templates and facilitates +testing and reuse. + +```rust +use askama::Template; +#[derive(Template)] +#[template(source = "Section 1: {{ s1.render().unwrap() }}", ext = "txt")] +struct RenderInPlace<'a> { + s1: SectionOne<'a> +} + +#[derive(Template)] +#[template(source = "A={{ a }}\nB={{ b }}", ext = "txt")] +struct SectionOne<'a> { + a: &'a str, + b: &'a str, +} +let t = RenderInPlace { s1: SectionOne { a: "a", b: "b" } }; +assert_eq!(t.render().unwrap(), "Section 1: A=a\nB=b") +``` + +See the example +[render in place](https://github.com/djc/askama/blob/master/testing/tests/render_in_place.rs) +using a vector of templates in a for block. + +## Comments + +Askama supports block comments delimited by `{#` and `#}`. + +``` +{# A Comment #} +``` + +## Recursive Structures + +Recursive implementations should preferably use a custom iterator and +use a plain loop. If that is not doable, call `.render()` +directly by using an expression as shown below. +Including self does not work, see #105 and #220 . + +```rust +use askama::Template; + +#[derive(Template)] +#[template(source = r#" +//! {% for item in children %} + {{ item.render().unwrap() }} +{% endfor %} +"#, ext = "html", escape = "none")] +struct Item<'a> { + name: &'a str, + children: &'a [Item<'a>], +} +``` + +## Macros + +You can define macros within your template by using `{% macro name(args) %}`, ending with `{% endmacro %}` + +You can then call it later with `{% call name(args) %}` + +``` +{% macro heading(arg) %} + +

{{arg}}

+ +{% endmacro %} + +{% call heading(s) %} +``` -- cgit