Expand description
The hairy crate provides text templates, not unlike mustache and handlebars (and the earlier ctemplate original), but a bit different, focussing on error handling.
Scoping is different: variables must specify explicitly which scope they use (by using a.b syntax). This is to avoid implicit behaviour when variables are (accidentally) overwritten.
To catch errors early on, optionally types can be added, which are checked compile time.
All errors are treated as hard errors and are reported (including a stack trace).
So missing values and typing errors are not silently ignored.
Also, the templates support evaluation: {{=expression}}.
The supported expressions are from the expry crate, which allows executing expression on binary encoded JSON-like values (with support for defining custom functions).
Auto-escaping is applied to the output to avoid security issues (such as letting user input be exected as javascript).
§Syntax
In short the features and syntax:
- Variables can be declared at top of an input file with
type key: exprypairs (JSON is a valid expry).typecan be eitherinline(value is replaced during compilation of the template) ordefault(which is a default value that is added to the evaluation time value if the field does not exists). 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 thenullvalue is considered a nop (without generating an error). Other values are considered an error, except when thesjs(script-js) orjsescape modes are used. - Boolean conditionals with
{{if expr}}contents{{end}}. Contents is displayed if value evaluates totrue. Keywordselseandelseifare also supported:{{if expr1}}a{{elseif expr2}}b{{else}}c{{endif}}. Ifexpristruethe then branch is outputted, ifnullorfalsethe else branch is taken, otherwise an error is generated. - Let conditionals with
{{if let x = expr}}a{{end}}. Ifexpris nonnullcontents is displayed, otherwise (on non error) the else branch is displayed.elseandelseifare supported. - Iterators with
{{for variable in name}}content{{endfor}}, which can be used to iterate over (arrays of) any type. The contents of the array is available in the loop body under keyvariable. Ifvariableis of the form(i,v), then the current index number is stored in the variablei. - Template definition with
{{define name}}content{{enddefine}}. Templates can have optional default values (in an object) with{{define name defaults object}}content{{enddefine}}. 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}}.namecan 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 thewithsyntax is used, only the data specified invalueis 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: missing fields are always considered an error.
Errors can be suppressed with the
expr ??? alternativetry syntax (on error inexpr,alternativewill be executed). Shorthand forexpr ??? nullisexpr ???, which can be used in loops with{{for i in someField???}}, which ignores errors if the field is not found. Same for conditionals.
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 hairy
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.
Note that if extra variables are introduced with a loop or a if let, they do not overwrite
the field with the same name in the this object. The original value can be retrieved by using
this.name if name was given another value.
§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. hairy 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 expry_macros::*;
use std::io::Write;
let template = r#"foobar = {{=this.foovar .. this.barvar}}"#;
let mut options = HairyOptions::new();
let value = value!({
"foovar": "foo",
"barvar": "bar",
}).encode_to_vec();
options.set_named_dynamic_values(&[("this",value.to_ref())]);
let result = hairy_compile_html(template, "test.tpl", &(&options).try_into().unwrap());
match result {
Ok(parsed) => {
match hairy_eval_html(parsed.to_ref(), &(&options).try_into().unwrap()) {
Ok(output) => { std::io::stdout().write_all(&output); },
Err(err) => { eprintln!("{}", err); },
}
},
Err(mut err) => {
eprintln!("{}", err);
panic!(); // 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))()}}.
use hairy::*;
use expry::*;
use expry_macros::*;
use std::io::Write;
let main_template = r#"<html><title>{{=value.title}}</title><body>{{call ((value.body))({value.title,value.foobarvar})}}</body></html>"#;
let mut options = HairyCompileOptions::new();
options.set_dynamic_value_name_and_types(&[("value",expry_type!("{title:string,body:string,foobarvar:string}"))]);
let main = hairy_compile_html(main_template, "main.tpl", &options).unwrap();
let child_template = r#"<p>title of this page = {{=this.title}}</p><p>foobar = {{=this.foobarvar}}"#;
options.set_dynamic_value_name_and_types(&[("this", expry_type!("{title:string,foobarvar:string}"))]);
let child = hairy_compile_html(child_template, "child.tpl", &options).unwrap();
let value = value!({
"body": child,
"foobarvar": "foobar",
"title": "my title",
}).encode_to_vec();
let mut options = HairyEvalOptions::new();
options.values = vec![value.to_ref()];
match hairy_eval_html(main.to_ref(), &options) {
Ok(output) => { std::io::stderr().write_all(&output).unwrap(); },
Err(mut err) => {
eprintln!("{}", err);
panic!(); // to check this example
},
}Structs§
- Bytecode
Ref - Reference to expry expression bytecode.
- Bytecode
Vec - Self-contained expry expression bytecode.
- Default
Escaper - Default escaper. Defaults to escaping in
htmlmode. Currently supportshtml,url, andnone(throws error on other escape modes). - Encoded
Value Ref - Reference to a expry value.
- Encoded
Value Vec - Self-contained expry value (in encoded form).
- Expry
Spanner - Recognizes expressions by counting the
{,},[,],(,), and". Does not support advanced string literals such as raw strings (e.g.r##" " "##). Stops at first]or}or)encountered that is not started inside the expression. - Hairy
Compile Error - Hairy
Compile Options - Hairy
Error - Hairy errors, including a stack trace so debugging is easier.
- Hairy
Eval Options - Hairy
Options - Hairy
Stack Entry - Used for stack traces, contains the details of a call site.
- NoCustom
Funcs - An provided implementation of the custom function handler, that directly throws an error when invoked.
- Template
TagContent Spanner - This spanner will try to recognize the content of a Hairy 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.
Enums§
- Escape
Mode - Hairy
Eval Error - Hairy
Location Type - Used for stack traces, to make distinction between call sites.
- Hairy
Parser Error - The errors that can occur during Hairy 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 Hairy 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§
- hairy_
check_ types - hairy_
compile - Converts a Hairy template (in
reader) to bytecode that can be evaluation later. To help debugging during later evaluation, afilenamecan 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). - hairy_
compile_ error_ format_ console - hairy_
compile_ error_ format_ html - hairy_
compile_ html - 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_compilefunction. - hairy_
compile_ html_ scope - hairy_
eval - Evaluate Hairy bytecode to generate output, using a
valueencoded as aexpryobject. - hairy_
eval_ html - 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_evalfunction. - hairy_
extract_ subtemplates - hairy_
templates_ to_ types