#template #html #html-templating #no-std #io-write

no-std nate

Not a Template Engine. Derive Display using a template.

20 releases

0.4.0 Apr 14, 2023
0.3.3 Dec 1, 2022
0.3.2 Jul 28, 2022
0.2.0 Mar 22, 2022
0.1.1 Jul 30, 2021

#111 in Template engine

Download history 52/week @ 2024-02-16 8/week @ 2024-02-23 10/week @ 2024-03-01 4/week @ 2024-03-08 1/week @ 2024-03-15 147/week @ 2024-03-29

152 downloads per month

Apache-2.0 WITH LLVM-exception

42KB
876 lines

NaTE — Not a Template Engine

GitHub Workflow Status Crates.io Minimum supported Rust version License

This is not a template engine, but sugar to implicitly call write!(…) like in PHP. The only difference is that the output gets XML escaped automatically unless opted-out explicitly.

Unlike other template engines like Askama, Handlebars, Liquid, Tera, or Tide, you don't have to learn a new language. If you know Rust and HTML, you already know how to implement templates with NaTE!

E.g.

  • templates/greeting.html:

    <h1>Hello, {{user}}!</h1>
    

    The path is relative to the cargo manifest dir (where you find Cargo.toml) of the project.

  • src/main.rs:

    use nate::Nate;
    
    #[derive(Nate)]
    #[template(path = "templates/greeting.html")]
    struct Greetings<'a> {
        user: &'a str,
    }
    
    fn main() {
        let mut output = String::new();
        let tmpl = Greetings { user: "<World>" };
        write!(output, "{}", tmpl).unwrap();
        println!("{}", output);
    }
    
  • Output:

    <h1>Hello, &#60;World&#62;!</h1>
    

No new traits are needed, instead #[derive(Nate)] primarily works by implementing fmt::Display. This also makes nesting of NaTE templates possible.

A more complex example would be:

  • src/main.rs:

    use nate::Nate;
    
    #[derive(Nate)]
    #[template(path = "templates/99-bottles.html")]
    struct Template {
        limit: usize,
    }
    
    #[test]
    fn ninetynine_bottles_of_beer() {
        print!("{}", Template { limit: 99 });
    }
    
  • templates/99-bottles.txt:

    {%-
        for i in (1..=self.limit).rev() {
            if i == 1 {
    -%}
    1 bottle of beer on the wall.
    1 bottle of beer.
    Take one down, pass it around.
    {%-
            } else {
    -%}
    {{i}} bottles of beer on the wall.
    {{i}} bottles of beer.
    Take one down, pass it around.
    
    {%
            }
        }
    -%}
    

Inside of a {% code block %} you can write any and all rust code.

Values in {{ value blocks }} are printed XML escaped.

Values in {{{ raw blocks }}} are printed verbatim.

For values in {{{{ debug blocks }}}} their debug message is printed as in "{:?}".

For values in {{{{{ verbose blocks }}}}} their debug message is printed verbose as in "{:#?}".

With {< include >} blocks you can include a template file. It then behaves like it was copy-pasted into the current file. If the path starts with "." or "..", the file is searched relative to the current file. Otherwise it is search in the project root.

Using hyphens - at the start/end of a block, whitespaces before/after the block are trimmed.

Data blocks {{}} to {{{{{}}}}} and includes {<>} must not be empty. Code {%%} and comment {##} blocks may be empty.

Blocks don't need to be closed at the end of the file.

To debug any errors you can add an argument as in #[template(generated = "some/path/generated.rs")]. The generated code is stored in there even if there were parsing errors in the Rust code. The path is relative to the project root (where your Cargo.toml lives).

Feature flags

  • std [enabled by default] — enable features found in std crate, e.g. printing the value of a MutexGuard

  • alloc [enabled by default, enabled by std] — enable features found in the alloc crate, e.g. io::Write

Dependencies

~1.9–2.6MB
~55K SLoC