README.md

Cuach, a HTML template system for Rust

Cuach defines a single trait Render, usable to produce HTML pages from templates and Rust structs. Cuach compiles on stable Rust and uses procedural macros to produce efficient static code.

Cuach requires a folder called templates at the root of a repository (alongside Cargo.toml). Then, if that folder contains a file called index.html, we can use it like so:

#[macro_use]
extern crate cuach;
use cuach::*;

#[template(path="index.html")]
struct A<'a> {
    field: &'a str,
    other_field: usize
}

fn main() {
   println!("{}, (A { field: "blah", other_field: 0 }).render().unwrap())
}

The template can be as simple as an empty file, or contain more elaborate things, such as:

<html>
  <body>
  field is equal to <strong>{ self.field }</strong>, and other_field to { self.other_field }.
  </body>
</html>

Rules

All the variables and functions of the module where A is defined are in scope in the template. There is also an extra variable in scope, w, of type &mut std::fmt::Write. For any Rust expression e, { e } in the template gets translated to (e).render_into(w).

Moreover, more complex Rust expressions can be used using HTML comments. For instance, the following produces ten HTML paragraphs, with 0, 1, …, 9 as their contents. Anything inside HTML comments is parsed as Rust.

<!-- for i in 0..10 { -->
        <p>{ i }</p>
<!-- } -->

This means in particular that any expression of the form { e } in the template is strictly equivalent to

(e).render_into(w)?;

Whitespace and escaping

Any amount of whitespace between tags or comments is ignored if it contains at least one newline character, and is output as is else. One way to force whitespace on an otherwise blank line is to add whitespace between two HTMLcomments, like so:

<!--  -->    <!--  -->

Comments can be used in the templates, but they have to be Rust comments inside HTML comments:

<!-- // this is a comment -->
<!-- /* this is
another,
multiline,
comment */ -->

The parsing of templates is done using an XML parsing library, xml-rs. Therefore, standard XML escaping works and can be used, but it sometimes conflicts with Rust borrows. Cuach handles that situation by replacing, before parsing, all & that are not recognised by regular expression &[a-z]+; with &amp;. After parsing, all instances of &amp; are converted back to &.

Including other files

There is nothing special about including other files in Cuach, as Rust provides that ability already, by doing something like { include_str!("../templates/included.html") }. Unfortunately, the parsing libraries used by Cuach (such as proc-macro2) do not yet allow Cuach to use filepaths relative to the current file.

Conflict with JS

The curly braces might sometimes conflict with JS embedded in HTML. The way to deal with this is that Cuach only considers curly braces on the same line: in order to be understood as a Rust expression, the code between { and } must not contain the character \n.

Here is a valid example of JS templated by Cuach, embedded in HTML:

<script>my_function("{self.argument}")</script>

And the following is a valid example of passing an object to a function in JS, not transformed by Cuach:

<script>
my_function({
  "arg": 0
})
</script>