This is my attempt at writing a HTML template system for Rust. Some inspiration comes from the scala template system used in play 2, as well as plain old jsp.
Display
trait should be outputable.This is currently more of a proof of concept that anyting ready for actual production use. That said, it actually does work; templates can be transpiled to rust functions, which are then compiled and can be called from rust code. The template syntax is not stable yet, but some examples in the current format can be seen below, and in examples/simple/templates.
A template consists of three basic parts:
First a preamle of use
statements, each prepended by an @ sign.
Secondly a declaration of the parameters the template takes.
And third, the template body.
``` @use any::rust::Type;
@(name: &str, items: Vec
... ```
A parameter can be used in an expression preceded by an @ sign.
```
```
If a parameter is a struct or a trait object, its fields or methods can be used, and if it is a callable, it can be called.
```
The user @user.name has email @user.email.
```
Standard function and macros can also be used, e.g. for specific formatting needs:
```
The value is @format!("{:.1}", float_value).
```
Rust-like loops are supported like this:
```
```
Note that the thing to loop over (items, in the example) is a rust expression, while the contents of the block is template code.
Rust-like conditionals are supported in a style similar to the loops:
@if items.is_empty() {
<p>There are no items.</p>
}
Pattern matching let expressions are also supported, as well as an optional else part.
@if let Some(foo) = foo {
<p>Foo is @foo.</p>
} else {
<p>There is no foo.</p>
}
While rust methods can be called as a simple expression, there is a
special syntax for calling other templates:
@:template_name(template_arguments)
.
Also, before calling a template, it has to be imported by a use
statement.
Templates are declared in a templates
module.
So, given something like this in header.rs.html
:
``` @(title: &str)
It can be used like this:
``
@use templates::header
@()
@:header("Example")
Ructe compiles your templates to rust code that should be compiled with
your other rust code, so it needs to be called before compiling.
Assuming you use cargo, it can be done like
this:
First, specify a build script and ructe as a build dependency in
Cargo.toml
:
```toml build = "src/build.rs"
[build-dependencies] ructe = { git = "https://github.com/kaj/ructe" } ```
Then, in the build script, compile all templates found in the templates directory and put the output where cargo tells it to:
```rust extern crate ructe;
use ructe::compile_templates; use std::env; use std::path::PathBuf;
fn main() { let outdir = PathBuf::from(env::var("OUTDIR").unwrap()); let indir = PathBuf::from(env::var("CARGOMANIFESTDIR").unwrap()) .join("templates"); compiletemplates(&indir, &outdir).expect("foo"); } ```
And finally, include and use the generated code in your code.
The file templates.rs
will contain mod templates { ... }
,
so I just include it in my main.rs
:
rust
include!(concat!(env!("OUT_DIR"), "/templates.rs"));
When calling a template, the arguments declared in the template will be
prepended by an argument that is the std::io::Write
to write the
output to.
It can be a Vec<u8>
as a buffer or for testing, or an actual output
destination.
The return value of a template is std::io::Result<()>
, which should be
Ok(())
unless writing to the destination fails.
```
fn testhello() { let mut buf = Vec::new(); templates::hello(&mut buf, "World").unwrap(); asserteq!(from_utf8(&buf).unwrap(), "
When I use ructe with nickel, I use a rendering function that looks like this:
fn render<'mw, F>(res: Response<'mw>, do_render: F)
->MiddlewareResult<'mw>
where F: FnOnce(&mut Write) -> io::Result<()>
{
let mut stream = try!(res.start());
match do_render(&mut stream) {
Ok(()) => Ok(Halt(stream)),
Err(e) => stream.bail(format!("Problem rendering template: {:?}", e))
}
}
Which I call like this:
render(res, |o| templates::foo(o, other, arguments))