Expand description
The hair
crate provides text templates, not unlike mustache and handlebars (and the earlier ctemplate original), but a bit different.
All errors are treated as hard errors and are reported.
So missing values are not silently ignored.
Also, the templates support evaluation: {{=expression}}
.
The supported expressions are from the expry
crate, which provided executing expression on binary encoded JSON-like values.
Syntax
In short the features and syntax:
- Variables can be declared at top of an input file with
type key: expry
pairs (JSON is a valid expry).type
can be eitherinline
(value is replaced during compilation of the template) ordefault
(which is a default value that is added to the evaluation time value). The first non-parsable line signals the end of the variables. - Values can be outputted with
{{=value}}
(supports expressions, see below). Escape mode can be specified with{{=value:escape_mode}}
. Values are processed by a user-definable escaper that can take the escape mode into account. Normally only strings and numbers are displayed, and thenull
value is considered a nop. Other values are considered an error, except when thesjs
(script-js) orjs
escape mode is used. - Conditionals with
{{if value}}contents{{end}}
. Contents is displayed if value evaluates totrue
. - Iterators with
{{for variable in name}}content{{end}}
, which can be used to iterate over (arrays of) any type. The contents of the array is available in the loop body under keyvariable
; - Template definition with
{{define name}}content{{end}}
. Templates can have optional default values (in an object) with{{define name defaults object}}content{{end}}
. Defaults are resolved at template compile time, using the global context given to the compile function; - Template instantiation with
{{call name}}
or{{call name with value}}
.name
can be an expression. If the name starts with a*
, name can be an expression that resolves to a string (which is treated as a template name). If the name starts with**
, name should be an expression that resolves to a binary form template code (as produced by the compile functions). If thewith
syntax is used, only the data specified invalue
is passed along (so the current context is not automatically transfered/merged, to do that use for value{...this, key: value}
). This is done to avoid errors. - Error handling is different from normal Mustache: missing fields is always considered an error. Errors can be suppressed with the
expr ??? alternative
try syntax (on error inexpr
,alternative
will be executed). Shorthand forexpr ??? null
isexpr ???
(which can be used in loops with{{for i in someField???}}
, which ignores errors if the field is not found).
Some other template systems have the ability to iterate without variables, in effect making
the object fields available in the current context. Although this is at first appealing,
this makes these templates error-prone. Types can differ, depending on the presence of fields
in the object iterated over (which can change for each iteration). One of the design goals of hair
is error handling. To fall back to the value in the global context on error such as field not
found, the following expression can be used: {{iteration_variable.foo???foo}}
. This makes the
whole expression explicit in its intent.
Escaping
Normally, all strings that are dynamically outputted (using an evaluating {{=..}}
statement) are automatically ‘escaped’ using the escaper as given to an argument to the
hairy_eval
function. However, often you will want different escape modes depending on the
input context. For example in HTML, output may not normally contain <
or >
, but output
inside attributes in HTML tags are normally escaped like URLs. Although users can specify for
every output the escape mode by appending a :html
or :url
escaping mode to an expression,
this is error-prone. Auto escaping is therefore a safer alternative, by automatically deciding
the escape mode from the input. The general advise is to escape defensively. hair
functions
support an argument that is used to look up the escaping mode for a certain position in the input.
Easy interface
The ‘easy’ interface if you just want to quickly use an HTML template, with auto escaping of the
input, and returning a nicely formatted error that can be presented to the user. To evaluate, use hairy_eval_html
, so the proper escaper is used.
Note that although hairy_compile_html
and [hairy_eval_html]
are easier to use, they are
somewhat slower. For top performance please use the hairy_compile
and the hairy_eval
functions.
Example using the simple interface
use hairy::*;
use expry::*;
use std::io::Write;
let template = r#"foobar = {{=foovar .. barvar}}"#;
let result = hairy_compile_html(template, "test.tpl", None, 0);
match result {
Ok(parsed) => {
let value = value!({
"foovar": "foo",
"barvar": "bar",
}).to_vec(false);
match hairy_eval_html(parsed.to_ref(), value.to_ref()) {
Ok(output) => { std::io::stdout().write_all(&output); },
Err(err) => { eprintln!("{}", err); },
}
},
Err(err) => {
eprintln!("{}", err);
assert!(false); // to check this example
}
}
Enclosing other template files
The value can contain other template compiled with hairy_compile_html
. So if value body
contains the bytecode of such a template, that template can be invoked from the ‘main’ template
by using the template expression {{call **body with argument}}
.
use hairy::*;
use expry::*;
use std::io::Write;
let main_template = r#"<html><title>{{=title}}</title><body>{{call **body with {title,foobarvar}}}</body></html>"#;
let main = hairy_compile_html(main_template, "main.tpl", None, 0).unwrap();
let child_template = r#"<p>title of this page = {{=title}}</p><p>foobar = {{=foobarvar}}"#;
let child = hairy_compile_html(child_template, "child.tpl", None, 0).unwrap();
let value = value!({
"body": child,
"foobarvar": "foobar",
"title": "my title",
}).to_vec(false);
match hairy_eval_html(main.to_ref(), value.to_ref()) {
Ok(output) => { std::io::stderr().write_all(&output).unwrap(); },
Err(err) => {
eprintln!("{}", err);
assert!(false); // to check this example
},
}
Structs
Reference to expry expression bytecode.
Self-contained expry expression bytecode.
Default escaper. Defaults to escaping in html
mode. Currently supports html
, url
, and none
(throws error on other escape modes).
Recognizes expressions by counting the {
, }
, [
, ]
, and "
. Does not support advanced
string literals such as raw strings (e.g. r##" " "##
). Stops at first ]
or }
encountered
that is not started inside the expression.
Hair errors, including a stack trace so debugging is easier.
Internal error of the Hair lexer. Contains the source code line number.
Used for stack traces, contains the details of a call site.
An provided implementation of the custom function handler, that directly throws an error when invoked.
This spanner will try to recognize the content of a Hair template ({{..}}
). It counts the number of
nested {
and }
. It does have support for recognizing embedded strings, so {{foo != "}"}}
will work. Raw strings or the like are not supported, instead you can use {foo != r#" \" "#}
. Stops at first }
encountered that is not started inside the expression.
Reference to a expry value.
Self-contained expry value (in encoded form).
Enums
Used for stack traces, to make distinction between call sites.
The errors that can occur during Hair template parsing. It can either be a lexer error (so a part of
the input can not be translated to a token), or a parser error (a different token expected).
As Hair templates can contain expressions, these expressions can also trigger errors. These
can either be in the parsing of the expressions, or during optimizing (which causes
EvalErrors). Other errors are reporting using Other
.
Traits
Functions
Converts a Hair template (in reader
) to bytecode that can be evaluation later. To help debugging during later evaluation, a
filename
can be added to the bytecode. A MemoryScope is used to reduce the number of
allocated strings. The result stays valid as long as the MemoryScope is in the scope (forced by
the lifetime system of Rust). The globals and custom functions are used when evaluation the
defaults (for template calls and such).
The ‘easy’ interface if you just want to quickly use a HTML template, with auto escaping of the
input, and returning a nicely formatted error that can be presented to the user. For more
options, see the hairy_compile
function.
Evaluate Hair bytecode to generate output, using a value
encoded as a expry
object.
The ‘easy’ interface if you just want to quickly use a HTML template, with auto escaping of the
input, and returning a nicely formatted error that can be presented to the user. For more
options, see the hairy_eval
function.
The ‘easy’ interface (with custom functions) if you just want to quickly use a HTML template, with auto escaping of the
input, and returning a nicely formatted error that can be presented to the user. For more
options, see the hairy_eval
function.