#templating #web #compile-time #template-engine #name #mustache #bart

macro bart_derive

#[derive(BartDisplay)] implementation for supporting the bart package

8 releases

0.1.6 Jun 21, 2023
0.1.5 Jan 5, 2023
0.1.4 Jul 14, 2017
0.1.1 Jun 20, 2017
0.0.1 Jan 7, 2017

#21 in #mustache

28 downloads per month
Used in 5 crates (3 directly)

MIT license

37KB
1K SLoC

Build Status

Bart is a compile time templating language for Rust inspired by Mustache. It plays to Rust's strengths by statically compiling the template into efficient code and performing full variable resolution and type checking at compile time.

Cargo dependencies

To use Bart, add these dependencies to your Cargo.toml:

[dependencies]
bart = "0.1.4"
bart_derive = "0.1.4"

Example

Given the template file hello_world.html:

Hello {{name}}

We can write the following program:

#[derive(bart_derive::BartDisplay)]
#[template = "hello_world.html"]
struct HelloWorld<'a> {
    name: &'a str,
}

fn main() {
    print!("{}", &HelloWorld { name: "World" });
}

To compile this example program, you need to add both bart and bart_derive as dependencies in your Cargo.toml.

Running this program will output

Hello World

You can run this example by cloning this repository and executing cargo run --example hello_world.

Line by line

#[derive(BartDisplay)]

The programmer interface to Bart is the procedural macro defined in the bart_derive crate, which implements support for #[derive(bart_derive::BartDisplay)]. It must be added as a dependency in your Cargo.toml. bart_derive generates code which is dependent on the bart crate, so you also need to pull this in as a dependency.

Use bart_derive::BartDisplay to generate an impl of the Display trait based on the template and struct below.

#[template = "hello_world.html"]

bart_derive will read hello_world.html and use it to generate the template rendering code. The given file name is relative to your crate root, so, for example, you have to specify #[template = "src/hello_world.html"] if you want your template to reside in the src/ directory.

It is also possible to specify the template inline with template_string: #[template_string = "Hello {{name}}"].

struct HelloWorld<'a> {
    name: &'a str,
}

Values to be interpolated in the template will be resolved from the given struct. In this case {{name}} would be resolved to the name field of this struct. Fields to be interpolated must implement the Display trait.

fn main() {
    print!("{}", &HelloWorld { name: "World" });
}

As noted above, bart_derive has now generated an impl of Display for HelloWorld. This means we can pass instances of HelloWorld to print!, write!, format! and so on. The template is rendered with the supplied data, generating Hello World to standard output.

Language reference

The Bart templating language is inspired by Mustache. (Bart is the Norwegian word for Mustache.)

The input is reproduced verbatim except for tags. Tags start with {{ and end with }}.

Interpolation

The simplest tag is the interpolation tag, which contains a data reference. For the template Hello {{name}}, {{name}} is recognized as an interpolation tag and name is resolved as a field on the given struct. This field must implement the Display trait. It is possible to use . to refer to fields in nested structs; {{name.surname}}.

Interpolation tags are HTML escaped, so for the template Hello {{name}}, if {{name}} is Bobby <tags>, the output will be Hello Bobby &lt;tags>.

Verbatim/unescaped interpolation

It is also useful to be able to deliberately include HTML content unescaped. Use triple-tags, {{{}}}, for this: Hello {{{name}}} would render Hello Bobby <tags> if name were Bobby <tags>.

Iteration

It is possible to iterate over anything that implements IntoIterator:

<ul>
{{#values}}
    <li>{{.}}</li>
{{/values}}
</ul>

Use {{.}} to refer to the current value. For example, if values were a Vec<i32>, {{.}} would refer to each of the contained i32 values in turn. When iterating over a set of structures, use a . prefix to refer to members:

<ul>
{{#people}}
    <li>{{.name}} ({{.age}})</li>
{{/people}}
</ul>

It can be useful to take advantage of the IntoIterator implementations on Option and Result to use them in Bart iterations.

Scoping

Similar to iteration, it is possible to enter a scope for a variable, by specifying a trailing dot:

{{#person.}}
    {{.name}} ({{.age}})
{{/person}}

It is also possible to fully qualify each reference:

{{person.name}} ({{person.age}})

When in a nested scope, use multiple leading dots to step out:

{{#department.}}
    {{#head.}}
        {{.name}}, head of the {{..name}} department.
    {{/head}}
{{/department}}

Unqualified names, that is, names without leading dots, will always be resolved in the topmost scope.

The same scoping rules apply to iteration scopes.

Dependencies

~2.5MB
~55K SLoC