Crate pochoir_template_engine

Crate pochoir_template_engine 

Source
Expand description

A mustache template engine supporting complex expressions and statements.

use pochoir_lang::Context;
use pochoir_template_engine::process_template;

let mut context = Context::new();
context.insert("name", "world");

assert_eq!(
    process_template("index.html", "Hello {{ name }}!", &mut context, 0),
    Ok("Hello world!".to_string()),
);

§Variables and expressions

Variables needs to be defined in a Context. They can be inserted using Context::insert but if you want child components to also inherit the data (like a global variable), you should use Context::insert_inherited. Both functions take a key as first argument which is the name of the variable usable in expressions and a value implementing the IntoValue trait (implemented for a lot of default types and structures). The value will then be cloned and transformed in each expression. If you want to pass enumerations or structures, you should implement IntoValue on them using the IntoValue derive macro. The variable keys must follow some rules to be valid: they must only contain lowercase and uppercase Latin letters, underscores and digits (but the first character must not be a digit).

When data needs to be inserted inside a page, you need to use an expression. Expressions are tiny groups of variables and operators written in their own custom language that manipulate data. They are written in a pair of curly brackets and can be used everywhere you write text. The resulting value of expressions is escaped to prevent XSS attacks, but it is possible to opt out of auto-escaping by replacing the inner curly brackets by exclamation marks.

§Example
use pochoir_lang::Context;
use pochoir_template_engine::process_template;

let mut context = Context::new();
context.insert("date", "August 26, 2023");
context.insert("html", "<b>some bold HTML</b>");

let source = "
Today it is {{ date }}.
Unescaped HTML can be inserted: {! html !}.";

let expected = "
Today it is August 26, 2023.
Unescaped HTML can be inserted: <b>some bold HTML</b>.";

assert_eq!(
    process_template(
        "index.html",
        source,
        &mut context,
        0,
    ),
    Ok(expected.to_string()),
);

§Statements

Common statements can be used in templates. They are all written in curly brackets with inner percentages.

  • if/elif/else conditional statements are used to check if an expression equals true. If it does, the inner content will be included, if not it won’t. One additional feature is that you can use the if let (and elif let) syntax to check if a value is not null. It can be combined with an assignment to replicate the if let Some(_) = _ syntax of Rust: “unwrap” the value if it is not null or don’t execute a block content at all if the value is null. You need to note that assignments return the assigned value (like in Javascript) which enables the if let syntax to do that.
  • for loop statements are also supported. They are mostly used to iterate lists so they are written using the for ... in ... syntax. If you want to just get some ordered numbers, you would need to use ranges like for ... in 3..12. Destructuring objects and arrays is also supported, so if you iterate an array of objects and they all share the same structure you can bind their the fields to comma-separated variables instead of indexing them later. The same thing is supported for arrays, except that you can name the keys as you want, just the order in which they are defined is important. If a value cannot be destructured (because the field does not exist), the value will simply be null.
  • Finally, let statements can be used to assign a value to a variable and comments can be added with curly brackets with inner #s.
§Example
use pochoir_lang::{object, Context};
use pochoir_template_engine::process_template;

let mut context = Context::new();
context.insert("date", "August 26, 2023");
context.insert("weather", "cloudy");
context.insert("users", vec![
    object! {
        "name" => "John",
        "job" => "Football player"
    },
    object! {
        "name" => "Jane",
        "job" => "Designer",
    }
]);

let source = r#"
{% if date == "August 26, 2023" %}
It is today!
{% elif date == "August 25, 2023" %}
It is yesterday!
{% else %}
Oh welcome to the future.
{% endif %}

{# Note that a single `=` is used here because it is an assignment. If `weather` is null, the
assignment will return `null` and the `if` block content will never run (because `if let`
checks for `null`ity) but if the value is something other than `null`, `current_weather` will
be different than `null` and the inner block will be run #}

{% if let current_weather = weather %}
Today it is {{ current_weather }}.
{% endif %}

{# We now define a variable in the template and iterate it in a for loop #}

{% let alphabet = ["a", "b", "c", "d"] %}

{% for letter in alphabet %}"{{ letter }}" then {% endfor %}"z"

{# Objects are destructured here: the `name` and `job` fields will be extracted from the
objects to avoid having to later index them #}

<ul>
  {% for name, job in users %}
  <li>{{ name }} is a {{ job }}</li>
  {% endfor %}
</ul>"#;

let expected = r#"

It is today!





Today it is cloudy.






"a" then "b" then "c" then "d" then "z"



<ul>
  
  <li>John is a Football player</li>
  
  <li>Jane is a Designer</li>
  
</ul>"#;

assert_eq!(
    process_template(
        "index.html",
        source,
        &mut context,
        0,
    ),
    Ok(expected.to_string()),
);

§Custom parsing

To extend the behavior of parsing template expressions and statements, it is possible to use the TemplateCustomParsing trait which will let you customize the behavior of the parser for each character encountered. It is especially used in pochoir-parser to evaluate expressions in element attributes where it is useful to stop the parsing when " is found.

§Quirks

Nested objects defined in template expressions need to have spaces between their curly braces to differentiate them from template expression delimiters.

Bad:

{{ {a: {b: "letters"}} }}

The error will be:

error: unterminated object
./index.html:1:7

1 | {{ {a:{b: "letters"}} }}

Fixed:

{{ {a: {b: "letters"} } }}

Re-exports§

pub use escaping::Escaping;

Modules§

escaping

Enums§

Error
TemplateBlock

Traits§

TemplateCustomParsing

Functions§

parse_template
Parse a template to a list of TemplateBlocks along with their span.
process_template
Parse and render a template string.
render_template
Render a previously-parsed template to a string.
stream_parse_template
Parse a template to a list of TemplateBlocks along with their span from a pre-built StreamParser.

Type Aliases§

BlockContext
Result