//! ## TinyTemplate //! //! TinyTemplate is a minimal templating library originally designed for use in [Criterion.rs]. //! It deliberately does not provide all of the features of a full-power template engine, but in //! return it provides a simple API, clear templating syntax, decent performance and very few //! dependencies. //! //! ## Features //! //! The most important features are as follows (see the [syntax](syntax/index.html) module for full //! details on the template syntax): //! //! * Rendering values - `{ myvalue }` //! * Conditionals - `{{ if foo }}Foo is true{{ else }}Foo is false{{ endif }}` //! * Loops - `{{ for value in row }}{value}{{ endfor }}` //! * Customizable value formatters `{ value | my_formatter }` //! * Macros `{{ call my_template with foo }}` //! //! ## Restrictions //! //! TinyTemplate was designed with the assumption that the templates are available as static strings, //! either using string literals or the `include_str!` macro. Thus, it borrows `&str` slices from the //! template text itself and uses them during the rendering process. Although it is possible to use //! TinyTemplate with template strings loaded at runtime, this is not recommended. //! //! Additionally, TinyTemplate can only render templates into Strings. If you need to render a //! template directly to a socket or file, TinyTemplate may not be right for you. //! //! ## Example //! //! ``` //! #[macro_use] //! extern crate serde_derive; //! extern crate tinytemplate; //! //! use tinytemplate::TinyTemplate; //! use std::error::Error; //! //! #[derive(Serialize)] //! struct Context { //! name: String, //! } //! //! static TEMPLATE : &'static str = "Hello {name}!"; //! //! pub fn main() -> Result<(), Box> { //! let mut tt = TinyTemplate::new(); //! tt.add_template("hello", TEMPLATE)?; //! //! let context = Context { //! name: "World".to_string(), //! }; //! //! let rendered = tt.render("hello", &context)?; //! # assert_eq!("Hello World!", &rendered); //! println!("{}", rendered); //! //! Ok(()) //! } //! ``` //! //! [Criterion.rs]: https://github.com/bheisler/criterion.rs //! extern crate serde; extern crate serde_json; #[cfg(test)] #[cfg_attr(test, macro_use)] extern crate serde_derive; mod compiler; pub mod error; mod instruction; pub mod syntax; mod template; use error::*; use serde::Serialize; use serde_json::Value; use std::collections::HashMap; use std::fmt::Write; use template::Template; /// Type alias for closures which can be used as value formatters. pub type ValueFormatter = dyn Fn(&Value, &mut String) -> Result<()>; /// Appends `value` to `output`, performing HTML-escaping in the process. pub fn escape(value: &str, output: &mut String) { // Algorithm taken from the rustdoc source code. let value_str = value; let mut last_emitted = 0; for (i, ch) in value.bytes().enumerate() { match ch as char { '<' | '>' | '&' | '\'' | '"' => { output.push_str(&value_str[last_emitted..i]); let s = match ch as char { '>' => ">", '<' => "<", '&' => "&", '\'' => "'", '"' => """, _ => unreachable!(), }; output.push_str(s); last_emitted = i + 1; } _ => {} } } if last_emitted < value_str.len() { output.push_str(&value_str[last_emitted..]); } } /// The format function is used as the default value formatter for all values unless the user /// specifies another. It is provided publicly so that it can be called as part of custom formatters. /// Values are formatted as follows: /// /// * `Value::Null` => the empty string /// * `Value::Bool` => true|false /// * `Value::Number` => the number, as formatted by `serde_json`. /// * `Value::String` => the string, HTML-escaped /// /// Arrays and objects are not formatted, and attempting to do so will result in a rendering error. pub fn format(value: &Value, output: &mut String) -> Result<()> { match value { Value::Null => Ok(()), Value::Bool(b) => { write!(output, "{}", b)?; Ok(()) } Value::Number(n) => { write!(output, "{}", n)?; Ok(()) } Value::String(s) => { escape(s, output); Ok(()) } _ => Err(unprintable_error()), } } /// Identical to [`format`](fn.format.html) except that this does not perform HTML escaping. pub fn format_unescaped(value: &Value, output: &mut String) -> Result<()> { match value { Value::Null => Ok(()), Value::Bool(b) => { write!(output, "{}", b)?; Ok(()) } Value::Number(n) => { write!(output, "{}", n)?; Ok(()) } Value::String(s) => { output.push_str(s); Ok(()) } _ => Err(unprintable_error()), } } /// The TinyTemplate struct is the entry point for the TinyTemplate library. It contains the /// template and formatter registries and provides functions to render templates as well as to /// register templates and formatters. pub struct TinyTemplate<'template> { templates: HashMap<&'template str, Template<'template>>, formatters: HashMap<&'template str, Box>, default_formatter: &'template ValueFormatter, } impl<'template> TinyTemplate<'template> { /// Create a new TinyTemplate registry. The returned registry contains no templates, and has /// [`format_unescaped`](fn.format_unescaped.html) registered as a formatter named "unescaped". pub fn new() -> TinyTemplate<'template> { let mut tt = TinyTemplate { templates: HashMap::default(), formatters: HashMap::default(), default_formatter: &format, }; tt.add_formatter("unescaped", format_unescaped); tt } /// Parse and compile the given template, then register it under the given name. pub fn add_template(&mut self, name: &'template str, text: &'template str) -> Result<()> { let template = Template::compile(text)?; self.templates.insert(name, template); Ok(()) } /// Changes the default formatter from [`format`](fn.format.html) to `formatter`. Usefull in combination with [`format_unescaped`](fn.format_unescaped.html) to deactivate HTML-escaping pub fn set_default_formatter(&mut self, formatter: &'template F) where F: 'static + Fn(&Value, &mut String) -> Result<()>, { self.default_formatter = formatter; } /// Register the given formatter function under the given name. pub fn add_formatter(&mut self, name: &'template str, formatter: F) where F: 'static + Fn(&Value, &mut String) -> Result<()>, { self.formatters.insert(name, Box::new(formatter)); } /// Render the template with the given name using the given context object. The context /// object must implement `serde::Serialize` as it will be converted to `serde_json::Value`. pub fn render(&self, template: &str, context: &C) -> Result where C: Serialize, { let value = serde_json::to_value(context)?; match self.templates.get(template) { Some(tmpl) => tmpl.render( &value, &self.templates, &self.formatters, self.default_formatter, ), None => Err(Error::GenericError { msg: format!("Unknown template '{}'", template), }), } } } impl<'template> Default for TinyTemplate<'template> { fn default() -> TinyTemplate<'template> { TinyTemplate::new() } } #[cfg(test)] mod test { use super::*; #[derive(Serialize)] struct Context { name: String, } static TEMPLATE: &'static str = "Hello {name}!"; #[test] pub fn test_set_default_formatter() { let mut tt = TinyTemplate::new(); tt.add_template("hello", TEMPLATE).unwrap(); tt.set_default_formatter(&format_unescaped); let context = Context { name: "".to_string(), }; let rendered = tt.render("hello", &context).unwrap(); assert_eq!(rendered, "Hello !") } }