From 0bd99b09201976b2274f5558567605658379e072 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Sun, 28 Jul 2024 15:47:32 +0200 Subject: [PATCH] Remove expansion chapter from rinja book --- book/src/SUMMARY.md | 1 - book/src/template_expansion.md | 485 --------------------------------- 2 files changed, 486 deletions(-) delete mode 100644 book/src/template_expansion.md diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index 92c110f4..7899831a 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -9,4 +9,3 @@ - [Filters](./filters.md) - [Integrations](./integrations.md) - [Performance](./performance.md) -- [Template expansion](./template_expansion.md) diff --git a/book/src/template_expansion.md b/book/src/template_expansion.md deleted file mode 100644 index 7dc201b0..00000000 --- a/book/src/template_expansion.md +++ /dev/null @@ -1,485 +0,0 @@ -# Template Expansion - -This chapter will explain how the different parts of the templates are -translated into Rust code. - -⚠️ Please note that the generated code might change in the future so the -following examples might not be up-to-date. - -## Basic explanations - -When you add `#[derive(Template)]` and `#[template(...)]` on your type, the -`Template` derive proc-macro will then generate an implementation of the -`rinja::Template` trait which will be a Rust version of the template. - -It will also implement the `std::fmt::Display` trait on your type which will -internally call the `rinja::Template` trait. - -Let's take a small example: - -```rust -#[derive(Template)] -#[template(source = "{% set x = 12 %}", ext = "html")] -struct Mine; -``` - -will generate: - -```rust -impl ::rinja::Template for YourType { - fn render_into( - &self, - writer: &mut (impl ::std::fmt::Write + ?Sized), - ) -> ::rinja::Result<()> { - let x = 12; - ::rinja::Result::Ok(()) - } - const EXTENSION: ::std::option::Option<&'static ::std::primitive::str> = Some( - "html", - ); - const SIZE_HINT: ::std::primitive::usize = 0; - const MIME_TYPE: &'static ::std::primitive::str = "text/html; charset=utf-8"; -} - -impl ::std::fmt::Display for YourType { - #[inline] - fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { - ::rinja::Template::render_into(self, f).map_err(|_| ::std::fmt::Error {}) - } -} -``` - -For simplicity, we will only keep the content of the `rinja::Template::render_into` -function from now on. - -## Text content - -If you have "text content" (for example HTML) in your template: - -```html -

{{ title }}

-``` - -It will generate it like this: - -```rust -writer - .write_fmt( - format_args!( - "

{0}

", - &::rinja::MarkupDisplay::new_unsafe(&(self.title), ::rinja::Html), - ), - )?; -::rinja::Result::Ok(()) -``` - -About `MarkupDisplay`: we need to use this type in order to prevent generating -invalid HTML. Let's take an example: if `title` is `""` and we display it as -is, in the generated HTML, you won't see `` but instead a new HTML element -will be created. To prevent this, we need to escape some characters. - -In this example, `` will become `<a>`. And this is why there is the -`safe` builtin filter, in case you want it to be displayed as is. - -## Variables - -### Variables creation - -If you create a variable in your template, it will be created in the generated -Rust code as well. For example: - -```jinja -{% set x = 12 %} -{% let y = x + 1 %} -``` - -will generate: - -```rust -let x = 12; -let y = x + 1; -::rinja::Result::Ok(()) -``` - -### Variables usage - -By default, variables will reference a field from the type on which the `rinja::Template` -trait is implemented: - -```jinja -{{ y }} -``` - -This template will expand as follows: - -```rust -writer - .write_fmt( - format_args!( - "{0}", - &::rinja::MarkupDisplay::new_unsafe(&(self.y), ::rinja::Html), - ), - )?; -::rinja::Result::Ok(()) -``` - -This is why if the variable is undefined, it won't work with Rinja and why -we can't check if a variable is defined or not. - -You can still access constants and statics by using paths. Let's say you have in -your Rust code: - -```rust -const FOO: u32 = 0; -``` - -Then you can use them in your template by referring to them with a path: - -```jinja -{{ crate::FOO }}{{ super::FOO }}{{ self::FOO }} -``` - -It will generate: - -```rust -writer - .write_fmt( - format_args!( - "{0}{1}{2}", - &::rinja::MarkupDisplay::new_unsafe(&(crate::FOO), ::rinja::Html), - &::rinja::MarkupDisplay::new_unsafe(&(super::FOO), ::rinja::Html), - &::rinja::MarkupDisplay::new_unsafe(&(self::FOO), ::rinja::Html), - ), - )?; -::rinja::Result::Ok(()) -``` - -(Note: `crate::` is to get an item at the root level of the crate, `super::` is -to get an item in the parent module and `self::` is to get an item in the -current module.) - -You can also access items from the type that implements `Template` as well using -as `Self::`, it'll use the same logic. - -## Control blocks - -### if/else - -The generated code can be more complex than expected, as seen with `if`/`else` -conditions: - -```jinja -{% if x == "a" %} -gateau -{% else %} -tarte -{% endif %} -``` - -It will generate: - -```rust -if *(&(self.x == "a") as &bool) { - writer.write_str("gateau")?; -} else { - writer.write_str("tarte")?; -} -::rinja::Result::Ok(()) -``` - -Very much as expected except for the `&(self.x == "a") as &bool`. Now about why -the `as &bool` is needed: - -The following syntax `*(&(...) as &bool)` is used to trigger Rust's automatic -dereferencing, to coerce e.g. `&&&&&bool` to `bool`. First `&(...) as &bool` -coerces e.g. `&&&bool` to `&bool`. Then `*(&bool)` finally dereferences it to -`bool`. - -In short, it allows to fallback to a boolean as much as possible, but it also -explains why you can't do: - -```jinja -{% set x = "a" %} -{% if x %} - {{ x }} -{% endif %} -``` - -Because it fail to compile because: - -```console -error[E0605]: non-primitive cast: `&&str` as `&bool` -``` - -### if let - -```jinja -{% if let Some(x) = x %} - {{ x }} -{% endif %} -``` - -will generate: - -```rust -if let Some(x) = &(self.x) { - writer - .write_fmt( - format_args!( - "{0}", - &::rinja::MarkupDisplay::new_unsafe(&(x), ::rinja::Html), - ), - )?; -} -``` - -### Loops - -```html -{% for user in users %} - {{ user }} -{% endfor %} -``` - -will generate: - -```rust -{ - let _iter = (&self.users).into_iter(); - for (user, _loop_item) in ::rinja::helpers::TemplateLoop::new(_iter) { - writer - .write_fmt( - format_args!( - "\n {0}\n", - &::rinja::MarkupDisplay::new_unsafe(&(user), ::rinja::Html), - ), - )?; - } -} -::rinja::Result::Ok(()) -``` - -Now let's see what happens if you add an `else` condition: - -```jinja -{% for user in x if x.len() > 2 %} - {{ user }} -{% else %} - {{ x }} -{% endfor %} -``` - -Which generates: - -```rust -{ - let mut _did_loop = false; - let _iter = (&self.users).into_iter(); - for (user, _loop_item) in ::rinja::helpers::TemplateLoop::new(_iter) { - _did_loop = true; - writer - .write_fmt( - format_args!( - "\n {0}\n", - &::rinja::MarkupDisplay::new_unsafe(&(user), ::rinja::Html), - ), - )?; - } - if !_did_loop { - writer - .write_fmt( - format_args!( - "\n {0}\n", - &::rinja::MarkupDisplay::new_unsafe( - &(self.x), - ::rinja::Html, - ), - ), - )?; - } -} -::rinja::Result::Ok(()) -``` - -It creates a `_did_loop` variable which will check if we entered the loop. If -we didn't (because the iterator didn't return any value), it will enter the -`else` condition by checking `if !_did_loop {`. - -We can extend it even further if we add an `if` condition on our loop: - -```jinja -{% for user in users if users.len() > 2 %} - {{ user }} -{% else %} - {{ x }} -{% endfor %} -``` - -which generates: - -```rust -{ - let mut _did_loop = false; - let _iter = (&self.users).into_iter(); - let _iter = _iter.filter(|user| -> bool { self.users.len() > 2 }); - for (user, _loop_item) in ::rinja::helpers::TemplateLoop::new(_iter) { - _did_loop = true; - writer - .write_fmt( - format_args!( - "\n {0}\n", - &::rinja::MarkupDisplay::new_unsafe(&(user), ::rinja::Html), - ), - )?; - } - if !_did_loop { - writer - .write_fmt( - format_args!( - "\n {0}\n", - &::rinja::MarkupDisplay::new_unsafe( - &(self.x), - ::rinja::Html, - ), - ), - )?; - } -} -::rinja::Result::Ok(()) -``` - -It generates an iterator but filters it based on the `if` condition (`users.len() > 2`). -So once again, if the iterator doesn't return any value, we enter the `else` -condition. - -Of course, if you only have a `if` and no `else`, the generated code is much -shorter: - -```jinja -{% for user in users if users.len() > 2 %} - {{ user }} -{% endfor %} -``` - -Which generates: - -```rust -{ - let _iter = (&self.users).into_iter(); - let _iter = _iter.filter(|user| -> bool { self.users.len() > 2 }); - for (user, _loop_item) in ::rinja::helpers::TemplateLoop::new(_iter) { - writer - .write_fmt( - format_args!( - "\n {0}\n", - &::rinja::MarkupDisplay::new_unsafe(&(user), ::rinja::Html), - ), - )?; - } -} -::rinja::Result::Ok(()) -``` - -## Filters - -Example of using the `abs` built-in filter: - -```jinja -{{ -2|abs }} -``` - -Which generates: - -```rust -writer - .write_fmt( - format_args!( - "{0}", - &::rinja::MarkupDisplay::new_unsafe( - &(::rinja::filters::abs(-2)?), - ::rinja::Html, - ), - ), - )?; -::rinja::Result::Ok(()) -``` - -The filter is called with `-2` as first argument. You can add further arguments -to the call like this: - -```jinja -{{ "a"|indent(4) }} -``` - -Which generates: - -```rust -writer - .write_fmt( - format_args!( - "{0}", - &::rinja::MarkupDisplay::new_unsafe( - &(::rinja::filters::indent("a", 4)?), - ::rinja::Html, - ), - ), - )?; -::rinja::Result::Ok(()) -``` - -No surprise there, `4` is added after `"a"`. Now let's check when we chain the filters: - -```jinja -{{ "a"|indent(4)|capitalize }} -``` - -Which generates: - -```rust -writer - .write_fmt( - format_args!( - "{0}", - &::rinja::MarkupDisplay::new_unsafe( - &(::rinja::filters::capitalize( - &(::rinja::filters::indent("a", 4)?), - )?), - ::rinja::Html, - ), - ), - )?; -::rinja::Result::Ok(()) -``` - -As expected, `capitalize`'s first argument is the value returned by the `indent` call. - -## Macros - -This code: - -```html -{% macro heading(arg) %} -

{{arg}}

-{% endmacro %} - -{% call heading("title") %} -``` - -generates: - -```rust -{ - let (arg) = (("title")); - writer - .write_fmt( - format_args!( - "\n

{0}

\n", - &::rinja::MarkupDisplay::new_unsafe(&(arg), ::rinja::Html), - ), - )?; -} -::rinja::Result::Ok(()) -``` - -As you can see, the macro itself isn't present in the generated code, only its -internal code is generated as well as its arguments.