summaryrefslogtreecommitdiffstats
path: root/vendor/tinytemplate/src
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-18 02:49:50 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-18 02:49:50 +0000
commit9835e2ae736235810b4ea1c162ca5e65c547e770 (patch)
tree3fcebf40ed70e581d776a8a4c65923e8ec20e026 /vendor/tinytemplate/src
parentReleasing progress-linux version 1.70.0+dfsg2-1~progress7.99u1. (diff)
downloadrustc-9835e2ae736235810b4ea1c162ca5e65c547e770.tar.xz
rustc-9835e2ae736235810b4ea1c162ca5e65c547e770.zip
Merging upstream version 1.71.1+dfsg1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/tinytemplate/src')
-rwxr-xr-xvendor/tinytemplate/src/compiler.rs698
-rwxr-xr-xvendor/tinytemplate/src/error.rs246
-rwxr-xr-xvendor/tinytemplate/src/instruction.rs85
-rwxr-xr-xvendor/tinytemplate/src/lib.rs260
-rwxr-xr-xvendor/tinytemplate/src/syntax.rs184
-rwxr-xr-xvendor/tinytemplate/src/template.rs944
6 files changed, 2417 insertions, 0 deletions
diff --git a/vendor/tinytemplate/src/compiler.rs b/vendor/tinytemplate/src/compiler.rs
new file mode 100755
index 000000000..df37947df
--- /dev/null
+++ b/vendor/tinytemplate/src/compiler.rs
@@ -0,0 +1,698 @@
+#![allow(deprecated)]
+
+/// The compiler module houses the code which parses and compiles templates. TinyTemplate implements
+/// a simple bytecode interpreter (see the [instruction] module for more details) to render templates.
+/// The [`TemplateCompiler`](struct.TemplateCompiler.html) struct is responsible for parsing the
+/// template strings and generating the appropriate bytecode instructions.
+use error::Error::*;
+use error::{get_offset, Error, Result};
+use instruction::{Instruction, Path, PathStep};
+
+/// The end point of a branch or goto instruction is not known.
+const UNKNOWN: usize = ::std::usize::MAX;
+
+/// The compiler keeps a stack of the open blocks so that it can ensure that blocks are closed in
+/// the right order. The Block type is a simple enumeration of the kinds of blocks that could be
+/// open. It may contain the instruction index corresponding to the start of the block.
+enum Block {
+ Branch(usize),
+ For(usize),
+ With,
+}
+
+/// List of the known @-keywords so that we can error if the user spells them wrong.
+static KNOWN_KEYWORDS: [&str; 4] = ["@index", "@first", "@last", "@root"];
+
+/// The TemplateCompiler struct is responsible for parsing a template string and generating bytecode
+/// instructions based on it. The parser is a simple hand-written pattern-matching parser with no
+/// recursion, which makes it relatively easy to read.
+pub(crate) struct TemplateCompiler<'template> {
+ original_text: &'template str,
+ remaining_text: &'template str,
+ instructions: Vec<Instruction<'template>>,
+ block_stack: Vec<(&'template str, Block)>,
+
+ /// When we see a `{foo -}` or similar, we need to remember to left-trim the next text block we
+ /// encounter.
+ trim_next: bool,
+}
+impl<'template> TemplateCompiler<'template> {
+ /// Create a new template compiler to parse and compile the given template.
+ pub fn new(text: &'template str) -> TemplateCompiler<'template> {
+ TemplateCompiler {
+ original_text: text,
+ remaining_text: text,
+ instructions: vec![],
+ block_stack: vec![],
+ trim_next: false,
+ }
+ }
+
+ /// Consume the template compiler to parse the template and return the generated bytecode.
+ pub fn compile(mut self) -> Result<Vec<Instruction<'template>>> {
+ while !self.remaining_text.is_empty() {
+ // Comment, denoted by {# comment text #}
+ if self.remaining_text.starts_with("{#") {
+ self.trim_next = false;
+
+ let tag = self.consume_tag("#}")?;
+ let comment = tag[2..(tag.len() - 2)].trim();
+ if comment.starts_with('-') {
+ self.trim_last_whitespace();
+ }
+ if comment.ends_with('-') {
+ self.trim_next_whitespace();
+ }
+ // Block tag. Block tags are wrapped in {{ }} and always have one word at the start
+ // to identify which kind of tag it is. Depending on the tag type there may be more.
+ } else if self.remaining_text.starts_with("{{") {
+ self.trim_next = false;
+
+ let (discriminant, rest) = self.consume_block()?;
+ match discriminant {
+ "if" => {
+ let (path, negated) = if rest.starts_with("not") {
+ (self.parse_path(&rest[4..])?, true)
+ } else {
+ (self.parse_path(rest)?, false)
+ };
+ self.block_stack
+ .push((discriminant, Block::Branch(self.instructions.len())));
+ self.instructions
+ .push(Instruction::Branch(path, !negated, UNKNOWN));
+ }
+ "else" => {
+ self.expect_empty(rest)?;
+ let num_instructions = self.instructions.len() + 1;
+ self.close_branch(num_instructions, discriminant)?;
+ self.block_stack
+ .push((discriminant, Block::Branch(self.instructions.len())));
+ self.instructions.push(Instruction::Goto(UNKNOWN))
+ }
+ "endif" => {
+ self.expect_empty(rest)?;
+ let num_instructions = self.instructions.len();
+ self.close_branch(num_instructions, discriminant)?;
+ }
+ "with" => {
+ let (path, name) = self.parse_with(rest)?;
+ let instruction = Instruction::PushNamedContext(path, name);
+ self.instructions.push(instruction);
+ self.block_stack.push((discriminant, Block::With));
+ }
+ "endwith" => {
+ self.expect_empty(rest)?;
+ if let Some((_, Block::With)) = self.block_stack.pop() {
+ self.instructions.push(Instruction::PopContext)
+ } else {
+ return Err(self.parse_error(
+ discriminant,
+ "Found a closing endwith that doesn't match with a preceeding with.".to_string()
+ ));
+ }
+ }
+ "for" => {
+ let (path, name) = self.parse_for(rest)?;
+ self.instructions
+ .push(Instruction::PushIterationContext(path, name));
+ self.block_stack
+ .push((discriminant, Block::For(self.instructions.len())));
+ self.instructions.push(Instruction::Iterate(UNKNOWN));
+ }
+ "endfor" => {
+ self.expect_empty(rest)?;
+ let num_instructions = self.instructions.len() + 1;
+ let goto_target = self.close_for(num_instructions, discriminant)?;
+ self.instructions.push(Instruction::Goto(goto_target));
+ self.instructions.push(Instruction::PopContext);
+ }
+ "call" => {
+ let (name, path) = self.parse_call(rest)?;
+ self.instructions.push(Instruction::Call(name, path));
+ }
+ _ => {
+ return Err(self.parse_error(
+ discriminant,
+ format!("Unknown block type '{}'", discriminant),
+ ));
+ }
+ }
+ // Values, of the form { dotted.path.to.value.in.context }
+ // Note that it is not (currently) possible to escape curly braces in the templates to
+ // prevent them from being interpreted as values.
+ } else if self.remaining_text.starts_with('{') {
+ self.trim_next = false;
+
+ let (path, name) = self.consume_value()?;
+ let instruction = match name {
+ Some(name) => Instruction::FormattedValue(path, name),
+ None => Instruction::Value(path),
+ };
+ self.instructions.push(instruction);
+ // All other text - just consume characters until we see a {
+ } else {
+ let mut escaped = false;
+ loop {
+ let mut text = self.consume_text(escaped);
+ if self.trim_next {
+ text = text.trim_left();
+ self.trim_next = false;
+ }
+ escaped = text.ends_with('\\');
+ if escaped {
+ text = &text[0..(text.len() - 1)];
+ }
+ self.instructions.push(Instruction::Literal(text));
+
+ if !escaped {
+ break;
+ }
+ }
+ }
+ }
+
+ if let Some((text, _)) = self.block_stack.pop() {
+ return Err(self.parse_error(
+ text,
+ "Expected block-closing tag, but reached the end of input.".to_string(),
+ ));
+ }
+
+ Ok(self.instructions)
+ }
+
+ /// Splits a string into a list of named segments which can later be used to look up values in the
+ /// context.
+ fn parse_path(&self, text: &'template str) -> Result<Path<'template>> {
+ if !text.starts_with('@') {
+ Ok(text
+ .split('.')
+ .map(|s| match s.parse::<usize>() {
+ Ok(n) => PathStep::Index(s, n),
+ Err(_) => PathStep::Name(s),
+ })
+ .collect::<Vec<_>>())
+ } else if KNOWN_KEYWORDS.iter().any(|k| *k == text) {
+ Ok(vec![PathStep::Name(text)])
+ } else {
+ Err(self.parse_error(text, format!("Invalid keyword name '{}'", text)))
+ }
+ }
+
+ /// Finds the line number and column where an error occurred. Location is the substring of
+ /// self.original_text where the error was found, and msg is the error message.
+ fn parse_error(&self, location: &str, msg: String) -> Error {
+ let (line, column) = get_offset(self.original_text, location);
+ ParseError { msg, line, column }
+ }
+
+ /// Tags which should have no text after the discriminant use this to raise an error if
+ /// text is found.
+ fn expect_empty(&self, text: &str) -> Result<()> {
+ if text.is_empty() {
+ Ok(())
+ } else {
+ Err(self.parse_error(text, format!("Unexpected text '{}'", text)))
+ }
+ }
+
+ /// Close the branch that is on top of the block stack by setting its target instruction
+ /// and popping it from the stack. Returns an error if the top of the block stack is not a
+ /// branch.
+ fn close_branch(&mut self, new_target: usize, discriminant: &str) -> Result<()> {
+ let branch_block = self.block_stack.pop();
+ if let Some((_, Block::Branch(index))) = branch_block {
+ match &mut self.instructions[index] {
+ Instruction::Branch(_, _, target) => {
+ *target = new_target;
+ Ok(())
+ }
+ Instruction::Goto(target) => {
+ *target = new_target;
+ Ok(())
+ }
+ _ => panic!(),
+ }
+ } else {
+ Err(self.parse_error(
+ discriminant,
+ "Found a closing endif or else which doesn't match with a preceding if."
+ .to_string(),
+ ))
+ }
+ }
+
+ /// Close the for loop that is on top of the block stack by setting its target instruction and
+ /// popping it from the stack. Returns an error if the top of the stack is not a for loop.
+ /// Returns the index of the loop's Iterate instruction for further processing.
+ fn close_for(&mut self, new_target: usize, discriminant: &str) -> Result<usize> {
+ let branch_block = self.block_stack.pop();
+ if let Some((_, Block::For(index))) = branch_block {
+ match &mut self.instructions[index] {
+ Instruction::Iterate(target) => {
+ *target = new_target;
+ Ok(index)
+ }
+ _ => panic!(),
+ }
+ } else {
+ Err(self.parse_error(
+ discriminant,
+ "Found a closing endfor which doesn't match with a preceding for.".to_string(),
+ ))
+ }
+ }
+
+ /// Advance the cursor to the next { and return the consumed text. If `escaped` is true, skips
+ /// a { at the start of the text.
+ fn consume_text(&mut self, escaped: bool) -> &'template str {
+ let search_substr = if escaped {
+ &self.remaining_text[1..]
+ } else {
+ self.remaining_text
+ };
+
+ let mut position = search_substr
+ .find('{')
+ .unwrap_or_else(|| search_substr.len());
+ if escaped {
+ position += 1;
+ }
+
+ let (text, remaining) = self.remaining_text.split_at(position);
+ self.remaining_text = remaining;
+ text
+ }
+
+ /// Advance the cursor to the end of the value tag and return the value's path and optional
+ /// formatter name.
+ fn consume_value(&mut self) -> Result<(Path<'template>, Option<&'template str>)> {
+ let tag = self.consume_tag("}")?;
+ let mut tag = tag[1..(tag.len() - 1)].trim();
+ if tag.starts_with('-') {
+ tag = tag[1..].trim();
+ self.trim_last_whitespace();
+ }
+ if tag.ends_with('-') {
+ tag = tag[0..tag.len() - 1].trim();
+ self.trim_next_whitespace();
+ }
+
+ if let Some(index) = tag.find('|') {
+ let (path_str, name_str) = tag.split_at(index);
+ let name = name_str[1..].trim();
+ let path = self.parse_path(path_str.trim())?;
+ Ok((path, Some(name)))
+ } else {
+ Ok((self.parse_path(tag)?, None))
+ }
+ }
+
+ /// Right-trim whitespace from the last text block we parsed.
+ fn trim_last_whitespace(&mut self) {
+ if let Some(Instruction::Literal(text)) = self.instructions.last_mut() {
+ *text = text.trim_right();
+ }
+ }
+
+ /// Make a note to left-trim whitespace from the next text block we parse.
+ fn trim_next_whitespace(&mut self) {
+ self.trim_next = true;
+ }
+
+ /// Advance the cursor to the end of the current block tag and return the discriminant substring
+ /// and the rest of the text in the tag. Also handles trimming whitespace where needed.
+ fn consume_block(&mut self) -> Result<(&'template str, &'template str)> {
+ let tag = self.consume_tag("}}")?;
+ let mut block = tag[2..(tag.len() - 2)].trim();
+ if block.starts_with('-') {
+ block = block[1..].trim();
+ self.trim_last_whitespace();
+ }
+ if block.ends_with('-') {
+ block = block[0..block.len() - 1].trim();
+ self.trim_next_whitespace();
+ }
+ let discriminant = block.split_whitespace().next().unwrap_or(block);
+ let rest = block[discriminant.len()..].trim();
+ Ok((discriminant, rest))
+ }
+
+ /// Advance the cursor to after the given expected_close string and return the text in between
+ /// (including the expected_close characters), or return an error message if we reach the end
+ /// of a line of text without finding it.
+ fn consume_tag(&mut self, expected_close: &str) -> Result<&'template str> {
+ if let Some(line) = self.remaining_text.lines().next() {
+ if let Some(pos) = line.find(expected_close) {
+ let (tag, remaining) = self.remaining_text.split_at(pos + expected_close.len());
+ self.remaining_text = remaining;
+ Ok(tag)
+ } else {
+ Err(self.parse_error(
+ line,
+ format!(
+ "Expected a closing '{}' but found end-of-line instead.",
+ expected_close
+ ),
+ ))
+ }
+ } else {
+ Err(self.parse_error(
+ self.remaining_text,
+ format!(
+ "Expected a closing '{}' but found end-of-text instead.",
+ expected_close
+ ),
+ ))
+ }
+ }
+
+ /// Parse a with tag to separate the value path from the (optional) name.
+ fn parse_with(&self, with_text: &'template str) -> Result<(Path<'template>, &'template str)> {
+ if let Some(index) = with_text.find(" as ") {
+ let (path_str, name_str) = with_text.split_at(index);
+ let path = self.parse_path(path_str.trim())?;
+ let name = name_str[" as ".len()..].trim();
+ Ok((path, name))
+ } else {
+ Err(self.parse_error(
+ with_text,
+ format!(
+ "Expected 'as <path>' in with block, but found \"{}\" instead",
+ with_text
+ ),
+ ))
+ }
+ }
+
+ /// Parse a for tag to separate the value path from the name.
+ fn parse_for(&self, for_text: &'template str) -> Result<(Path<'template>, &'template str)> {
+ if let Some(index) = for_text.find(" in ") {
+ let (name_str, path_str) = for_text.split_at(index);
+ let name = name_str.trim();
+ let path = self.parse_path(path_str[" in ".len()..].trim())?;
+ Ok((path, name))
+ } else {
+ Err(self.parse_error(
+ for_text,
+ format!("Unable to parse for block text '{}'", for_text),
+ ))
+ }
+ }
+
+ /// Parse a call tag to separate the template name and context value.
+ fn parse_call(&self, call_text: &'template str) -> Result<(&'template str, Path<'template>)> {
+ if let Some(index) = call_text.find(" with ") {
+ let (name_str, path_str) = call_text.split_at(index);
+ let name = name_str.trim();
+ let path = self.parse_path(path_str[" with ".len()..].trim())?;
+ Ok((name, path))
+ } else {
+ Err(self.parse_error(
+ call_text,
+ format!("Unable to parse call block text '{}'", call_text),
+ ))
+ }
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use instruction::Instruction::*;
+
+ fn compile(text: &'static str) -> Result<Vec<Instruction<'static>>> {
+ TemplateCompiler::new(text).compile()
+ }
+
+ #[test]
+ fn test_compile_literal() {
+ let text = "Test String";
+ let instructions = compile(text).unwrap();
+ assert_eq!(1, instructions.len());
+ assert_eq!(&Literal(text), &instructions[0]);
+ }
+
+ #[test]
+ fn test_compile_value() {
+ let text = "{ foobar }";
+ let instructions = compile(text).unwrap();
+ assert_eq!(1, instructions.len());
+ assert_eq!(&Value(vec![PathStep::Name("foobar")]), &instructions[0]);
+ }
+
+ #[test]
+ fn test_compile_value_with_formatter() {
+ let text = "{ foobar | my_formatter }";
+ let instructions = compile(text).unwrap();
+ assert_eq!(1, instructions.len());
+ assert_eq!(
+ &FormattedValue(vec![PathStep::Name("foobar")], "my_formatter"),
+ &instructions[0]
+ );
+ }
+
+ #[test]
+ fn test_dotted_path() {
+ let text = "{ foo.bar }";
+ let instructions = compile(text).unwrap();
+ assert_eq!(1, instructions.len());
+ assert_eq!(
+ &Value(vec![PathStep::Name("foo"), PathStep::Name("bar")]),
+ &instructions[0]
+ );
+ }
+
+ #[test]
+ fn test_indexed_path() {
+ let text = "{ foo.0.bar }";
+ let instructions = compile(text).unwrap();
+ assert_eq!(1, instructions.len());
+ assert_eq!(
+ &Value(vec![
+ PathStep::Name("foo"),
+ PathStep::Index("0", 0),
+ PathStep::Name("bar")
+ ]),
+ &instructions[0]
+ );
+ }
+
+ #[test]
+ fn test_mixture() {
+ let text = "Hello { name }, how are you?";
+ let instructions = compile(text).unwrap();
+ assert_eq!(3, instructions.len());
+ assert_eq!(&Literal("Hello "), &instructions[0]);
+ assert_eq!(&Value(vec![PathStep::Name("name")]), &instructions[1]);
+ assert_eq!(&Literal(", how are you?"), &instructions[2]);
+ }
+
+ #[test]
+ fn test_if_endif() {
+ let text = "{{ if foo }}Hello!{{ endif }}";
+ let instructions = compile(text).unwrap();
+ assert_eq!(2, instructions.len());
+ assert_eq!(
+ &Branch(vec![PathStep::Name("foo")], true, 2),
+ &instructions[0]
+ );
+ assert_eq!(&Literal("Hello!"), &instructions[1]);
+ }
+
+ #[test]
+ fn test_if_not_endif() {
+ let text = "{{ if not foo }}Hello!{{ endif }}";
+ let instructions = compile(text).unwrap();
+ assert_eq!(2, instructions.len());
+ assert_eq!(
+ &Branch(vec![PathStep::Name("foo")], false, 2),
+ &instructions[0]
+ );
+ assert_eq!(&Literal("Hello!"), &instructions[1]);
+ }
+
+ #[test]
+ fn test_if_else_endif() {
+ let text = "{{ if foo }}Hello!{{ else }}Goodbye!{{ endif }}";
+ let instructions = compile(text).unwrap();
+ assert_eq!(4, instructions.len());
+ assert_eq!(
+ &Branch(vec![PathStep::Name("foo")], true, 3),
+ &instructions[0]
+ );
+ assert_eq!(&Literal("Hello!"), &instructions[1]);
+ assert_eq!(&Goto(4), &instructions[2]);
+ assert_eq!(&Literal("Goodbye!"), &instructions[3]);
+ }
+
+ #[test]
+ fn test_with() {
+ let text = "{{ with foo as bar }}Hello!{{ endwith }}";
+ let instructions = compile(text).unwrap();
+ assert_eq!(3, instructions.len());
+ assert_eq!(
+ &PushNamedContext(vec![PathStep::Name("foo")], "bar"),
+ &instructions[0]
+ );
+ assert_eq!(&Literal("Hello!"), &instructions[1]);
+ assert_eq!(&PopContext, &instructions[2]);
+ }
+
+ #[test]
+ fn test_foreach() {
+ let text = "{{ for foo in bar.baz }}{ foo }{{ endfor }}";
+ let instructions = compile(text).unwrap();
+ assert_eq!(5, instructions.len());
+ assert_eq!(
+ &PushIterationContext(vec![PathStep::Name("bar"), PathStep::Name("baz")], "foo"),
+ &instructions[0]
+ );
+ assert_eq!(&Iterate(4), &instructions[1]);
+ assert_eq!(&Value(vec![PathStep::Name("foo")]), &instructions[2]);
+ assert_eq!(&Goto(1), &instructions[3]);
+ assert_eq!(&PopContext, &instructions[4]);
+ }
+
+ #[test]
+ fn test_strip_whitespace_value() {
+ let text = "Hello, {- name -} , how are you?";
+ let instructions = compile(text).unwrap();
+ assert_eq!(3, instructions.len());
+ assert_eq!(&Literal("Hello,"), &instructions[0]);
+ assert_eq!(&Value(vec![PathStep::Name("name")]), &instructions[1]);
+ assert_eq!(&Literal(", how are you?"), &instructions[2]);
+ }
+
+ #[test]
+ fn test_strip_whitespace_block() {
+ let text = "Hello, {{- if name -}} {name} {{- endif -}} , how are you?";
+ let instructions = compile(text).unwrap();
+ assert_eq!(6, instructions.len());
+ assert_eq!(&Literal("Hello,"), &instructions[0]);
+ assert_eq!(
+ &Branch(vec![PathStep::Name("name")], true, 5),
+ &instructions[1]
+ );
+ assert_eq!(&Literal(""), &instructions[2]);
+ assert_eq!(&Value(vec![PathStep::Name("name")]), &instructions[3]);
+ assert_eq!(&Literal(""), &instructions[4]);
+ assert_eq!(&Literal(", how are you?"), &instructions[5]);
+ }
+
+ #[test]
+ fn test_comment() {
+ let text = "Hello, {# foo bar baz #} there!";
+ let instructions = compile(text).unwrap();
+ assert_eq!(2, instructions.len());
+ assert_eq!(&Literal("Hello, "), &instructions[0]);
+ assert_eq!(&Literal(" there!"), &instructions[1]);
+ }
+
+ #[test]
+ fn test_strip_whitespace_comment() {
+ let text = "Hello, \t\n {#- foo bar baz -#} \t there!";
+ let instructions = compile(text).unwrap();
+ assert_eq!(2, instructions.len());
+ assert_eq!(&Literal("Hello,"), &instructions[0]);
+ assert_eq!(&Literal("there!"), &instructions[1]);
+ }
+
+ #[test]
+ fn test_strip_whitespace_followed_by_another_tag() {
+ let text = "{value -}{value} Hello";
+ let instructions = compile(text).unwrap();
+ assert_eq!(3, instructions.len());
+ assert_eq!(&Value(vec![PathStep::Name("value")]), &instructions[0]);
+ assert_eq!(&Value(vec![PathStep::Name("value")]), &instructions[1]);
+ assert_eq!(&Literal(" Hello"), &instructions[2]);
+ }
+
+ #[test]
+ fn test_call() {
+ let text = "{{ call my_macro with foo.bar }}";
+ let instructions = compile(text).unwrap();
+ assert_eq!(1, instructions.len());
+ assert_eq!(
+ &Call(
+ "my_macro",
+ vec![PathStep::Name("foo"), PathStep::Name("bar")]
+ ),
+ &instructions[0]
+ );
+ }
+
+ #[test]
+ fn test_curly_brace_escaping() {
+ let text = "body \\{ \nfont-size: {fontsize} \n}";
+ let instructions = compile(text).unwrap();
+ assert_eq!(4, instructions.len());
+ assert_eq!(&Literal("body "), &instructions[0]);
+ assert_eq!(&Literal("{ \nfont-size: "), &instructions[1]);
+ assert_eq!(&Value(vec![PathStep::Name("fontsize")]), &instructions[2]);
+ assert_eq!(&Literal(" \n}"), &instructions[3]);
+ }
+
+ #[test]
+ fn test_unclosed_tags() {
+ let tags = vec![
+ "{",
+ "{ foo.bar",
+ "{ foo.bar\n }",
+ "{{",
+ "{{ if foo.bar",
+ "{{ if foo.bar \n}}",
+ "{#",
+ "{# if foo.bar",
+ "{# if foo.bar \n#}",
+ ];
+ for tag in tags {
+ compile(tag).unwrap_err();
+ }
+ }
+
+ #[test]
+ fn test_mismatched_blocks() {
+ let text = "{{ if foo }}{{ with bar }}{{ endif }} {{ endwith }}";
+ compile(text).unwrap_err();
+ }
+
+ #[test]
+ fn test_disallows_invalid_keywords() {
+ let text = "{ @foo }";
+ compile(text).unwrap_err();
+ }
+
+ #[test]
+ fn test_diallows_unknown_block_type() {
+ let text = "{{ foobar }}";
+ compile(text).unwrap_err();
+ }
+
+ #[test]
+ fn test_parse_error_line_column_num() {
+ let text = "\n\n\n{{ foobar }}";
+ let err = compile(text).unwrap_err();
+ if let ParseError { line, column, .. } = err {
+ assert_eq!(4, line);
+ assert_eq!(3, column);
+ } else {
+ panic!("Should have returned a parse error");
+ }
+ }
+
+ #[test]
+ fn test_parse_error_on_unclosed_if() {
+ let text = "{{ if foo }}";
+ compile(text).unwrap_err();
+ }
+
+ #[test]
+ fn test_parse_escaped_open_curly_brace() {
+ let text: &str = r"hello \{world}";
+ let instructions = compile(text).unwrap();
+ assert_eq!(2, instructions.len());
+ assert_eq!(&Literal("hello "), &instructions[0]);
+ assert_eq!(&Literal("{world}"), &instructions[1]);
+ }
+}
diff --git a/vendor/tinytemplate/src/error.rs b/vendor/tinytemplate/src/error.rs
new file mode 100755
index 000000000..730c64891
--- /dev/null
+++ b/vendor/tinytemplate/src/error.rs
@@ -0,0 +1,246 @@
+//! Module containing the error type returned by TinyTemplate if an error occurs.
+
+use instruction::{path_to_str, PathSlice};
+use serde_json::Error as SerdeJsonError;
+use serde_json::Value;
+use std::error::Error as StdError;
+use std::fmt;
+
+/// Enum representing the potential errors that TinyTemplate can encounter.
+#[derive(Debug)]
+pub enum Error {
+ ParseError {
+ msg: String,
+ line: usize,
+ column: usize,
+ },
+ RenderError {
+ msg: String,
+ line: usize,
+ column: usize,
+ },
+ SerdeError {
+ err: SerdeJsonError,
+ },
+ GenericError {
+ msg: String,
+ },
+ StdFormatError {
+ err: fmt::Error,
+ },
+ CalledTemplateError {
+ name: String,
+ err: Box<Error>,
+ line: usize,
+ column: usize,
+ },
+ CalledFormatterError {
+ name: String,
+ err: Box<Error>,
+ line: usize,
+ column: usize,
+ },
+
+ #[doc(hidden)]
+ __NonExhaustive,
+}
+impl From<SerdeJsonError> for Error {
+ fn from(err: SerdeJsonError) -> Error {
+ Error::SerdeError { err }
+ }
+}
+impl From<fmt::Error> for Error {
+ fn from(err: fmt::Error) -> Error {
+ Error::StdFormatError { err }
+ }
+}
+impl fmt::Display for Error {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ Error::ParseError { msg, line, column } => write!(
+ f,
+ "Failed to parse the template (line {}, column {}). Reason: {}",
+ line, column, msg
+ ),
+ Error::RenderError { msg, line, column } => {
+ write!(
+ f,
+ "Encountered rendering error on line {}, column {}. Reason: {}",
+ line, column, msg
+ )
+ }
+ Error::SerdeError { err } => {
+ write!(f, "Unexpected serde error while converting the context to a serde_json::Value. Error: {}", err)
+ }
+ Error::GenericError { msg } => {
+ write!(f, "{}", msg)
+ }
+ Error::StdFormatError { err } => {
+ write!(f, "Unexpected formatting error: {}", err)
+ }
+ Error::CalledTemplateError {
+ name,
+ err,
+ line,
+ column,
+ } => {
+ write!(
+ f,
+ "Call to sub-template \"{}\" on line {}, column {} failed. Reason: {}",
+ name, line, column, err
+ )
+ }
+ Error::CalledFormatterError {
+ name,
+ err,
+ line,
+ column,
+ } => {
+ write!(
+ f,
+ "Call to value formatter \"{}\" on line {}, column {} failed. Reason: {}",
+ name, line, column, err
+ )
+ }
+ Error::__NonExhaustive => unreachable!(),
+ }
+ }
+}
+impl StdError for Error {
+ fn description(&self) -> &str {
+ match self {
+ Error::ParseError { .. } => "ParseError",
+ Error::RenderError { .. } => "RenderError",
+ Error::SerdeError { .. } => "SerdeError",
+ Error::GenericError { msg } => &msg,
+ Error::StdFormatError { .. } => "StdFormatError",
+ Error::CalledTemplateError { .. } => "CalledTemplateError",
+ Error::CalledFormatterError { .. } => "CalledFormatterError",
+ Error::__NonExhaustive => unreachable!(),
+ }
+ }
+}
+
+pub type Result<T> = ::std::result::Result<T, Error>;
+
+pub(crate) fn lookup_error(source: &str, step: &str, path: PathSlice, current: &Value) -> Error {
+ let avail_str = if let Value::Object(object_map) = current {
+ let mut avail_str = " Available values at this level are ".to_string();
+ for (i, key) in object_map.keys().enumerate() {
+ if i > 0 {
+ avail_str.push_str(", ");
+ }
+ avail_str.push('\'');
+ avail_str.push_str(key);
+ avail_str.push('\'');
+ }
+ avail_str
+ } else {
+ "".to_string()
+ };
+
+ let (line, column) = get_offset(source, step);
+
+ Error::RenderError {
+ msg: format!(
+ "Failed to find value '{}' from path '{}'.{}",
+ step,
+ path_to_str(path),
+ avail_str
+ ),
+ line,
+ column,
+ }
+}
+
+pub(crate) fn truthiness_error(source: &str, path: PathSlice) -> Error {
+ let (line, column) = get_offset(source, path.last().unwrap());
+ Error::RenderError {
+ msg: format!(
+ "Path '{}' produced a value which could not be checked for truthiness.",
+ path_to_str(path)
+ ),
+ line,
+ column,
+ }
+}
+
+pub(crate) fn unprintable_error() -> Error {
+ Error::GenericError {
+ msg: "Expected a printable value but found array or object.".to_string(),
+ }
+}
+
+pub(crate) fn not_iterable_error(source: &str, path: PathSlice) -> Error {
+ let (line, column) = get_offset(source, path.last().unwrap());
+ Error::RenderError {
+ msg: format!(
+ "Expected an array for path '{}' but found a non-iterable value.",
+ path_to_str(path)
+ ),
+ line,
+ column,
+ }
+}
+
+pub(crate) fn unknown_template(source: &str, name: &str) -> Error {
+ let (line, column) = get_offset(source, name);
+ Error::RenderError {
+ msg: format!("Tried to call an unknown template '{}'", name),
+ line,
+ column,
+ }
+}
+
+pub(crate) fn unknown_formatter(source: &str, name: &str) -> Error {
+ let (line, column) = get_offset(source, name);
+ Error::RenderError {
+ msg: format!("Tried to call an unknown formatter '{}'", name),
+ line,
+ column,
+ }
+}
+
+pub(crate) fn called_template_error(source: &str, template_name: &str, err: Error) -> Error {
+ let (line, column) = get_offset(source, template_name);
+ Error::CalledTemplateError {
+ name: template_name.to_string(),
+ err: Box::new(err),
+ line,
+ column,
+ }
+}
+
+pub(crate) fn called_formatter_error(source: &str, formatter_name: &str, err: Error) -> Error {
+ let (line, column) = get_offset(source, formatter_name);
+ Error::CalledFormatterError {
+ name: formatter_name.to_string(),
+ err: Box::new(err),
+ line,
+ column,
+ }
+}
+
+/// Find the line number and column of the target string within the source string. Will panic if
+/// target is not a substring of source.
+pub(crate) fn get_offset(source: &str, target: &str) -> (usize, usize) {
+ let offset = target.as_ptr() as isize - source.as_ptr() as isize;
+ let to_scan = &source[0..(offset as usize)];
+
+ let mut line = 1;
+ let mut column = 0;
+
+ for byte in to_scan.bytes() {
+ match byte as char {
+ '\n' => {
+ line += 1;
+ column = 0;
+ }
+ _ => {
+ column += 1;
+ }
+ }
+ }
+
+ (line, column)
+}
diff --git a/vendor/tinytemplate/src/instruction.rs b/vendor/tinytemplate/src/instruction.rs
new file mode 100755
index 000000000..0e1981465
--- /dev/null
+++ b/vendor/tinytemplate/src/instruction.rs
@@ -0,0 +1,85 @@
+use std::ops::Deref;
+
+/// TinyTemplate implements a simple bytecode interpreter for its template engine. Instructions
+/// for this interpreter are represented by the Instruction enum and typically contain various
+/// parameters such as the path to context values or name strings.
+///
+/// In TinyTemplate, the template string itself is assumed to be statically available (or at least
+/// longer-lived than the TinyTemplate instance) so paths and instructions simply borrow string
+/// slices from the template text. These string slices can then be appended directly to the output
+/// string.
+
+/// Enum for a step in a path which optionally contains a parsed index.
+#[derive(Eq, PartialEq, Debug, Clone)]
+pub(crate) enum PathStep<'template> {
+ Name(&'template str),
+ Index(&'template str, usize),
+}
+impl<'template> Deref for PathStep<'template> {
+ type Target = str;
+
+ fn deref(&self) -> &Self::Target {
+ match self {
+ PathStep::Name(s) => s,
+ PathStep::Index(s, _) => s,
+ }
+ }
+}
+
+/// Sequence of named steps used for looking up values in the context
+pub(crate) type Path<'template> = Vec<PathStep<'template>>;
+
+/// Path, but as a slice.
+pub(crate) type PathSlice<'a, 'template> = &'a [PathStep<'template>];
+
+/// Enum representing the bytecode instructions.
+#[derive(Eq, PartialEq, Debug, Clone)]
+pub(crate) enum Instruction<'template> {
+ /// Emit a literal string into the output buffer
+ Literal(&'template str),
+
+ /// Look up the value for the given path and render it into the output buffer using the default
+ /// formatter
+ Value(Path<'template>),
+
+ /// Look up the value for the given path and pass it to the formatter with the given name
+ FormattedValue(Path<'template>, &'template str),
+
+ /// Look up the value at the given path and jump to the given instruction index if that value
+ /// is truthy (if the boolean is true) or falsy (if the boolean is false)
+ Branch(Path<'template>, bool, usize),
+
+ /// Push a named context on the stack, shadowing only that name.
+ PushNamedContext(Path<'template>, &'template str),
+
+ /// Push an iteration context on the stack, shadowing the given name with the current value from
+ /// the vec pointed to by the path. The current value will be updated by the Iterate instruction.
+ /// This is always generated before an Iterate instruction which actually starts the iterator.
+ PushIterationContext(Path<'template>, &'template str),
+
+ /// Pop a context off the stack
+ PopContext,
+
+ /// Advance the topmost iterator on the context stack by one and update that context. If the
+ /// iterator is empty, jump to the given instruction.
+ Iterate(usize),
+
+ /// Unconditionally jump to the given instruction. Used to skip else blocks and repeat loops.
+ Goto(usize),
+
+ /// Look up the named template and render it into the output buffer with the value pointed to
+ /// by the path as its context.
+ Call(&'template str, Path<'template>),
+}
+
+/// Convert a path back into a dotted string.
+pub(crate) fn path_to_str(path: PathSlice) -> String {
+ let mut path_str = "".to_string();
+ for (i, step) in path.iter().enumerate() {
+ if i > 0 {
+ path_str.push('.');
+ }
+ path_str.push_str(step);
+ }
+ path_str
+}
diff --git a/vendor/tinytemplate/src/lib.rs b/vendor/tinytemplate/src/lib.rs
new file mode 100755
index 000000000..396be217c
--- /dev/null
+++ b/vendor/tinytemplate/src/lib.rs
@@ -0,0 +1,260 @@
+//! ## 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<Error>> {
+//! 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 {
+ '>' => "&gt;",
+ '<' => "&lt;",
+ '&' => "&amp;",
+ '\'' => "&#39;",
+ '"' => "&quot;",
+ _ => 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<ValueFormatter>>,
+ 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<F>(&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<F>(&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<C>(&self, template: &str, context: &C) -> Result<String>
+ 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: "<World>".to_string(),
+ };
+
+ let rendered = tt.render("hello", &context).unwrap();
+ assert_eq!(rendered, "Hello <World>!")
+ }
+}
diff --git a/vendor/tinytemplate/src/syntax.rs b/vendor/tinytemplate/src/syntax.rs
new file mode 100755
index 000000000..2e8eedd1e
--- /dev/null
+++ b/vendor/tinytemplate/src/syntax.rs
@@ -0,0 +1,184 @@
+//! Documentation of TinyTemplate's template syntax.
+//!
+//! ### Context Types
+//!
+//! TinyTemplate uses `serde_json`'s Value structure to represent the context. Therefore, any
+//! `Serializable` structure can be used as a context. All values in such structures are mapped to
+//! their JSON representations - booleans, numbers, strings, arrays, objects and nulls.
+//!
+//! ### Values
+//!
+//! Template values are marked with `{...}`. For example, this will look up the "name" field in
+//! the context structure and insert it into the rendered string:
+//!
+//! ```text
+//! Hello, {name}, how are you?
+//! ```
+//!
+//! Optionally, a value formatter may be provided. One formatter, "unescaped", is provided by
+//! default. Any other formatters must be registered with the
+//! [`TinyTemplate.add_formatter`](../struct.TinyTemplate.html#method.add_formatter)
+//! function prior to rendering or an error will be generated. This will call the formatter function
+//! registered as "percent_formatter" with the value of the "percentage" field:
+//!
+//! ```text
+//! Give it {percentage | percent_formatter}!
+//! ```
+//!
+//! The value may be a dotted path through a hierarchy of context objects. This will look up the
+//! "friend" field in the context structure, then substitute the "name" field from the "friend"
+//! object.
+//!
+//! ```text
+//! And hello to {friend.name} as well!
+//! ```
+//!
+//! Additionally, you may use the `@root` keyword to refer to the root object of your context.
+//! Since TinyTemplate can't normally print complex context objects, this is only useful if the
+//! context is a simple object like an integer or string.
+//!
+//! ### Conditionals
+//!
+//! TinyTemplate blocks are marked with `{{...}}` - double-braces where values are single-braces.
+//!
+//! Conditionals are denoted by "{{ if path }}...{{ else }}...{{ endif }}". The Else block is
+//! optional. Else-if is not currently supported. If "path" evaluates to a truthy expression
+//! (true if boolean, non-zero if numeric, non-empty for strings and arrays, and non-null for
+//! objects) then the section of the template between "if" and "else" is evaluated, otherwise the
+//! section between "else" and "endif" (if present) is evaluated.
+//!
+//! ```text
+//! {{ if user.is_birthday }}
+//! Happy Birthday!
+//! {{ else }}
+//! Have a nice day!
+//! {{ endif }}
+//! ```
+//!
+//! The condition can be negated by using "{{ if not path }}":
+//!
+//! ```text
+//! {{ if not user.is_birthday }}
+//! Have a nice day!
+//! {{ else }}
+//! Happy Birthday!
+//! {{ endif }}
+//! ```
+//!
+//! If desired, the `@root` keyword can be used to branch on the root context object.
+//!
+//! ### Loops
+//!
+//! TinyTemplate supports iterating over the values of arrays. Only arrays are supported. Loops
+//! are denoted by "{{ for value_name in value.path }}...{{ endfor }}". The section of the template between
+//! the two tags will be executed once for each value in the array denoted by "value.path".
+//!
+//! ```text
+//! Hello to {{ for name in guests }}
+//! {name}
+//! {{ endfor }}
+//! ```
+//!
+//! If the iteration value chosen in the "for" tag is the same as that of a regular context value,
+//! the name in the tag will shadow the context value for the scope of the loop. For nested loops,
+//! inner loops will shadow the values of outer loops.
+//!
+//! ```text
+//! {{ for person in guests }}
+//! Hello to {person}{{ for person in person.friends }} and your friend {person}{{ endfor }}
+//! {{ endfor }}
+//! ```
+//!
+//! There are three special values which are available within a loop:
+//!
+//! * `@index` - zero-based index of the current value within the array.
+//! * `@first` - true if this is the first iteration of the loop, otherwise false.
+//! * `@last` - true if this is the last iteration of the loop, otherwise false.
+//!
+//! ```text
+//! Hello to {{ for name in guests -}}
+//! { @index }. {name},
+//! {{- endfor }}
+//! ```
+//!
+//!
+//! In case of nested loops, these values refer to the innermost loop which contains them.
+//!
+//! If the root context object is an array, the `@root` keyword can be used to iterate over the
+//! root object.
+//!
+//! ### With Blocks
+//!
+//! Templates can use with blocks to partially shadows the outer context, the same way that
+//! for-loops do. These are formed like so:
+//!
+//! "{{ with path.to.value as name }}..{{ endwith }}""
+//!
+//! For example:
+//!
+//! ```text
+//! {{ with person.spouse as s }}
+//! Hello { s.name }!
+//! {{ endwith }}
+//! ```
+//!
+//! This looks up "person.spouse" and adds that to the context as "s" within the block. Only the
+//! name "s" is shadowed within the with block and otherwise the outer context is still accessible.
+//!
+//! ### Trimming Whitespace
+//!
+//! If a block tag, comment or value tag includes a "-" character at the start, the trailing
+//! whitespace of the previous text section will be skipped in the output. Likewise, if the tag
+//! ends with a "-", the leading whitespace of the following text will be skipped.
+//!
+//! ```text
+//! Hello { friend.name -}
+//! , how are you?
+//!
+//! {{- if status.good }} I am fine. {{- endif }}
+//! ```
+//!
+//! This will print "Hello friend, how are you? I am fine." without the newlines or extra spaces.
+//!
+//! ### Calling other Templates
+//!
+//! Templates may call other templates by name. The other template must have been registered using
+//! the [`TinyTemplate.add_template`](../struct.TinyTemplate.html#method.add_template) function
+//! before rendering or an error will be generated. This is done with the "call" tag:
+//!
+//! "{{ call template_name with path.to.context }}"
+//!
+//! The call tag has no closing tag. This will look up the "path.to.context" path in the current
+//! context, then render the "template_name" template using the value at that path as the context
+//! for the other template. The string produced by the called template is then inserted into the
+//! output from the calling template. This can be used for a limited form of template code reuse.
+//!
+//! ### Comments
+//!
+//! Comments in the templates are denoted by "{# comment text #}". Comments will be skipped when
+//! rendering the template, though whitespace adjacent to comments will not be stripped unless the
+//! "-" is added. For example:
+//!
+//! ```text
+//! Hello
+//!
+//! {#- This is a comment #} world!
+//! ```
+//!
+//! This will print "Hello world!".
+//!
+//! ### Escaping Curly Braces
+//!
+//! If your template contains opening curly-braces (`{`), they must be escaped using a leading `\`
+//! character. For example:
+//!
+//! ```text
+//! h2 \{
+//! font-size: {fontsize};
+//! }
+//! ```
+//!
+//! If using a string literal in rust source code, the `\` itself must be escaped, producing `\\{`.
+//!
+
+// There's nothing here, this module is solely for documentation.
diff --git a/vendor/tinytemplate/src/template.rs b/vendor/tinytemplate/src/template.rs
new file mode 100755
index 000000000..6f0162d22
--- /dev/null
+++ b/vendor/tinytemplate/src/template.rs
@@ -0,0 +1,944 @@
+//! This module implements the bytecode interpreter that actually renders the templates.
+
+use compiler::TemplateCompiler;
+use error::Error::*;
+use error::*;
+use instruction::{Instruction, PathSlice, PathStep};
+use serde_json::Value;
+use std::collections::HashMap;
+use std::fmt::Write;
+use std::slice;
+use ValueFormatter;
+
+/// Enum defining the different kinds of records on the context stack.
+enum ContextElement<'render, 'template> {
+ /// Object contexts shadow everything below them on the stack, because every name is looked up
+ /// in this object.
+ Object(&'render Value),
+ /// Named contexts shadow only one name. Any path that starts with that name is looked up in
+ /// this object, and all others are passed on down the stack.
+ Named(&'template str, &'render Value),
+ /// Iteration contexts shadow one name with the current value of the iteration. They also
+ /// store the iteration state. The two usizes are the index of the current value and the length
+ /// of the array that we're iterating over.
+ Iteration(
+ &'template str,
+ &'render Value,
+ usize,
+ usize,
+ slice::Iter<'render, Value>,
+ ),
+}
+
+/// Helper struct which mostly exists so that I have somewhere to put functions that access the
+/// rendering context stack.
+struct RenderContext<'render, 'template> {
+ original_text: &'template str,
+ context_stack: Vec<ContextElement<'render, 'template>>,
+}
+impl<'render, 'template> RenderContext<'render, 'template> {
+ /// Look up the given path in the context stack and return the value (if found) or an error (if
+ /// not)
+ fn lookup(&self, path: PathSlice) -> Result<&'render Value> {
+ for stack_layer in self.context_stack.iter().rev() {
+ match stack_layer {
+ ContextElement::Object(obj) => return self.lookup_in(path, obj),
+ ContextElement::Named(name, obj) => {
+ if *name == &*path[0] {
+ return self.lookup_in(&path[1..], obj);
+ }
+ }
+ ContextElement::Iteration(name, obj, _, _, _) => {
+ if *name == &*path[0] {
+ return self.lookup_in(&path[1..], obj);
+ }
+ }
+ }
+ }
+ panic!("Attempted to do a lookup with an empty context stack. That shouldn't be possible.")
+ }
+
+ /// Look up a path within a given value object and return the resulting value (if found) or
+ /// an error (if not)
+ fn lookup_in(&self, path: PathSlice, object: &'render Value) -> Result<&'render Value> {
+ let mut current = object;
+ for step in path.iter() {
+ if let PathStep::Index(_, n) = step {
+ if let Some(next) = current.get(n) {
+ current = next;
+ continue;
+ }
+ }
+
+ let step: &str = &*step;
+
+ match current.get(step) {
+ Some(next) => current = next,
+ None => return Err(lookup_error(self.original_text, step, path, current)),
+ }
+ }
+ Ok(current)
+ }
+
+ /// Look up the index and length values for the top iteration context on the stack.
+ fn lookup_index(&self) -> Result<(usize, usize)> {
+ for stack_layer in self.context_stack.iter().rev() {
+ match stack_layer {
+ ContextElement::Iteration(_, _, index, length, _) => return Ok((*index, *length)),
+ _ => continue,
+ }
+ }
+ Err(GenericError {
+ msg: "Used @index outside of a foreach block.".to_string(),
+ })
+ }
+
+ /// Look up the root context object
+ fn lookup_root(&self) -> Result<&'render Value> {
+ match self.context_stack.get(0) {
+ Some(ContextElement::Object(obj)) => Ok(obj),
+ Some(_) => {
+ panic!("Expected Object value at root of context stack, but was something else.")
+ }
+ None => panic!(
+ "Attempted to do a lookup with an empty context stack. That shouldn't be possible."
+ ),
+ }
+ }
+}
+
+/// Structure representing a parsed template. It holds the bytecode program for rendering the
+/// template as well as the length of the original template string, which is used as a guess to
+/// pre-size the output string buffer.
+pub(crate) struct Template<'template> {
+ original_text: &'template str,
+ instructions: Vec<Instruction<'template>>,
+ template_len: usize,
+}
+impl<'template> Template<'template> {
+ /// Create a Template from the given template string.
+ pub fn compile(text: &'template str) -> Result<Template> {
+ Ok(Template {
+ original_text: text,
+ template_len: text.len(),
+ instructions: TemplateCompiler::new(text).compile()?,
+ })
+ }
+
+ /// Render this template into a string and return it (or any error if one is encountered).
+ pub fn render(
+ &self,
+ context: &Value,
+ template_registry: &HashMap<&str, Template>,
+ formatter_registry: &HashMap<&str, Box<ValueFormatter>>,
+ default_formatter: &ValueFormatter,
+ ) -> Result<String> {
+ // The length of the original template seems like a reasonable guess at the length of the
+ // output.
+ let mut output = String::with_capacity(self.template_len);
+ self.render_into(
+ context,
+ template_registry,
+ formatter_registry,
+ default_formatter,
+ &mut output,
+ )?;
+ Ok(output)
+ }
+
+ /// Render this template into a given string. Used for calling other templates.
+ pub fn render_into(
+ &self,
+ context: &Value,
+ template_registry: &HashMap<&str, Template>,
+ formatter_registry: &HashMap<&str, Box<ValueFormatter>>,
+ default_formatter: &ValueFormatter,
+ output: &mut String,
+ ) -> Result<()> {
+ let mut program_counter = 0;
+ let mut render_context = RenderContext {
+ original_text: self.original_text,
+ context_stack: vec![ContextElement::Object(context)],
+ };
+
+ while program_counter < self.instructions.len() {
+ match &self.instructions[program_counter] {
+ Instruction::Literal(text) => {
+ output.push_str(text);
+ program_counter += 1;
+ }
+ Instruction::Value(path) => {
+ let first = path.first().unwrap();
+ if first.starts_with('@') {
+ // Currently we just hard-code the special @-keywords and have special
+ // lookup functions to use them because there are lifetime complexities with
+ // looking up values that don't live for as long as the given context object.
+ let first: &str = &*first;
+ match first {
+ "@index" => {
+ write!(output, "{}", render_context.lookup_index()?.0).unwrap()
+ }
+ "@first" => {
+ write!(output, "{}", render_context.lookup_index()?.0 == 0).unwrap()
+ }
+ "@last" => {
+ let (index, length) = render_context.lookup_index()?;
+ write!(output, "{}", index == length - 1).unwrap()
+ }
+ "@root" => {
+ let value_to_render = render_context.lookup_root()?;
+ default_formatter(value_to_render, output)?;
+ }
+ _ => panic!(), // This should have been caught by the parser.
+ }
+ } else {
+ let value_to_render = render_context.lookup(path)?;
+ default_formatter(value_to_render, output)?;
+ }
+ program_counter += 1;
+ }
+ Instruction::FormattedValue(path, name) => {
+ // The @ keywords aren't supported for formatted values. Should they be?
+ let value_to_render = render_context.lookup(path)?;
+ match formatter_registry.get(name) {
+ Some(formatter) => {
+ let formatter_result = formatter(value_to_render, output);
+ if let Err(err) = formatter_result {
+ return Err(called_formatter_error(self.original_text, name, err));
+ }
+ }
+ None => return Err(unknown_formatter(self.original_text, name)),
+ }
+ program_counter += 1;
+ }
+ Instruction::Branch(path, negate, target) => {
+ let first = path.first().unwrap();
+ let mut truthy = if first.starts_with('@') {
+ let first: &str = &*first;
+ match &*first {
+ "@index" => render_context.lookup_index()?.0 != 0,
+ "@first" => render_context.lookup_index()?.0 == 0,
+ "@last" => {
+ let (index, length) = render_context.lookup_index()?;
+ index == (length - 1)
+ }
+ "@root" => self.value_is_truthy(render_context.lookup_root()?, path)?,
+ other => panic!("Unknown keyword {}", other), // This should have been caught by the parser.
+ }
+ } else {
+ let value_to_render = render_context.lookup(path)?;
+ self.value_is_truthy(value_to_render, path)?
+ };
+ if *negate {
+ truthy = !truthy;
+ }
+
+ if truthy {
+ program_counter = *target;
+ } else {
+ program_counter += 1;
+ }
+ }
+ Instruction::PushNamedContext(path, name) => {
+ let context_value = render_context.lookup(path)?;
+ render_context
+ .context_stack
+ .push(ContextElement::Named(name, context_value));
+ program_counter += 1;
+ }
+ Instruction::PushIterationContext(path, name) => {
+ // We push a context with an invalid index and no value and then wait for the
+ // following Iterate instruction to set the index and value properly.
+ let first = path.first().unwrap();
+ let context_value = match first {
+ PathStep::Name("@root") => render_context.lookup_root()?,
+ PathStep::Name(other) if other.starts_with('@') => {
+ return Err(not_iterable_error(self.original_text, path))
+ }
+ _ => render_context.lookup(path)?,
+ };
+ match context_value {
+ Value::Array(ref arr) => {
+ render_context.context_stack.push(ContextElement::Iteration(
+ name,
+ &Value::Null,
+ ::std::usize::MAX,
+ arr.len(),
+ arr.iter(),
+ ))
+ }
+ _ => return Err(not_iterable_error(self.original_text, path)),
+ };
+ program_counter += 1;
+ }
+ Instruction::PopContext => {
+ render_context.context_stack.pop();
+ program_counter += 1;
+ }
+ Instruction::Goto(target) => {
+ program_counter = *target;
+ }
+ Instruction::Iterate(target) => {
+ match render_context.context_stack.last_mut() {
+ Some(ContextElement::Iteration(_, val, index, _, iter)) => {
+ match iter.next() {
+ Some(new_val) => {
+ *val = new_val;
+ // On the first iteration, this will be usize::MAX so it will
+ // wrap around to zero.
+ *index = index.wrapping_add(1);
+ program_counter += 1;
+ }
+ None => {
+ program_counter = *target;
+ }
+ }
+ }
+ _ => panic!("Malformed program."),
+ };
+ }
+ Instruction::Call(template_name, path) => {
+ let context_value = render_context.lookup(path)?;
+ match template_registry.get(template_name) {
+ Some(templ) => {
+ let called_templ_result = templ.render_into(
+ context_value,
+ template_registry,
+ formatter_registry,
+ default_formatter,
+ output,
+ );
+ if let Err(err) = called_templ_result {
+ return Err(called_template_error(
+ self.original_text,
+ template_name,
+ err,
+ ));
+ }
+ }
+ None => return Err(unknown_template(self.original_text, template_name)),
+ }
+ program_counter += 1;
+ }
+ }
+ }
+ Ok(())
+ }
+
+ fn value_is_truthy(&self, value: &Value, path: PathSlice) -> Result<bool> {
+ let truthy = match value {
+ Value::Null => false,
+ Value::Bool(b) => *b,
+ Value::Number(n) => match n.as_f64() {
+ Some(float) => float != 0.0,
+ None => {
+ return Err(truthiness_error(self.original_text, path));
+ }
+ },
+ Value::String(s) => !s.is_empty(),
+ Value::Array(arr) => !arr.is_empty(),
+ Value::Object(_) => true,
+ };
+ Ok(truthy)
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use compiler::TemplateCompiler;
+
+ fn compile(text: &'static str) -> Template<'static> {
+ Template {
+ original_text: text,
+ template_len: text.len(),
+ instructions: TemplateCompiler::new(text).compile().unwrap(),
+ }
+ }
+
+ #[derive(Serialize)]
+ struct NestedContext {
+ value: usize,
+ }
+
+ #[derive(Serialize)]
+ struct TestContext {
+ number: usize,
+ string: &'static str,
+ boolean: bool,
+ null: Option<usize>,
+ array: Vec<usize>,
+ nested: NestedContext,
+ escapes: &'static str,
+ }
+
+ fn context() -> Value {
+ let ctx = TestContext {
+ number: 5,
+ string: "test",
+ boolean: true,
+ null: None,
+ array: vec![1, 2, 3],
+ nested: NestedContext { value: 10 },
+ escapes: "1:< 2:> 3:& 4:' 5:\"",
+ };
+ ::serde_json::to_value(&ctx).unwrap()
+ }
+
+ fn other_templates() -> HashMap<&'static str, Template<'static>> {
+ let mut map = HashMap::new();
+ map.insert("my_macro", compile("{value}"));
+ map
+ }
+
+ fn format(value: &Value, output: &mut String) -> Result<()> {
+ output.push_str("{");
+ ::format(value, output)?;
+ output.push_str("}");
+ Ok(())
+ }
+
+ fn formatters() -> HashMap<&'static str, Box<ValueFormatter>> {
+ let mut map = HashMap::<&'static str, Box<ValueFormatter>>::new();
+ map.insert("my_formatter", Box::new(format));
+ map
+ }
+
+ pub fn default_formatter() -> &'static ValueFormatter {
+ &::format
+ }
+
+ #[test]
+ fn test_literal() {
+ let template = compile("Hello!");
+ let context = context();
+ let template_registry = other_templates();
+ let formatter_registry = formatters();
+ let string = template
+ .render(
+ &context,
+ &template_registry,
+ &formatter_registry,
+ &default_formatter(),
+ )
+ .unwrap();
+ assert_eq!("Hello!", &string);
+ }
+
+ #[test]
+ fn test_value() {
+ let template = compile("{ number }");
+ let context = context();
+ let template_registry = other_templates();
+ let formatter_registry = formatters();
+ let string = template
+ .render(
+ &context,
+ &template_registry,
+ &formatter_registry,
+ &default_formatter(),
+ )
+ .unwrap();
+ assert_eq!("5", &string);
+ }
+
+ #[test]
+ fn test_path() {
+ let template = compile("The number of the day is { nested.value }.");
+ let context = context();
+ let template_registry = other_templates();
+ let formatter_registry = formatters();
+ let string = template
+ .render(
+ &context,
+ &template_registry,
+ &formatter_registry,
+ &default_formatter(),
+ )
+ .unwrap();
+ assert_eq!("The number of the day is 10.", &string);
+ }
+
+ #[test]
+ fn test_if_taken() {
+ let template = compile("{{ if boolean }}Hello!{{ endif }}");
+ let context = context();
+ let template_registry = other_templates();
+ let formatter_registry = formatters();
+ let string = template
+ .render(
+ &context,
+ &template_registry,
+ &formatter_registry,
+ &default_formatter(),
+ )
+ .unwrap();
+ assert_eq!("Hello!", &string);
+ }
+
+ #[test]
+ fn test_if_untaken() {
+ let template = compile("{{ if null }}Hello!{{ endif }}");
+ let context = context();
+ let template_registry = other_templates();
+ let formatter_registry = formatters();
+ let string = template
+ .render(
+ &context,
+ &template_registry,
+ &formatter_registry,
+ &default_formatter(),
+ )
+ .unwrap();
+ assert_eq!("", &string);
+ }
+
+ #[test]
+ fn test_if_else_taken() {
+ let template = compile("{{ if boolean }}Hello!{{ else }}Goodbye!{{ endif }}");
+ let context = context();
+ let template_registry = other_templates();
+ let formatter_registry = formatters();
+ let string = template
+ .render(
+ &context,
+ &template_registry,
+ &formatter_registry,
+ &default_formatter(),
+ )
+ .unwrap();
+ assert_eq!("Hello!", &string);
+ }
+
+ #[test]
+ fn test_if_else_untaken() {
+ let template = compile("{{ if null }}Hello!{{ else }}Goodbye!{{ endif }}");
+ let context = context();
+ let template_registry = other_templates();
+ let formatter_registry = formatters();
+ let string = template
+ .render(
+ &context,
+ &template_registry,
+ &formatter_registry,
+ &default_formatter(),
+ )
+ .unwrap();
+ assert_eq!("Goodbye!", &string);
+ }
+
+ #[test]
+ fn test_ifnot_taken() {
+ let template = compile("{{ if not boolean }}Hello!{{ endif }}");
+ let context = context();
+ let template_registry = other_templates();
+ let formatter_registry = formatters();
+ let string = template
+ .render(
+ &context,
+ &template_registry,
+ &formatter_registry,
+ &default_formatter(),
+ )
+ .unwrap();
+ assert_eq!("", &string);
+ }
+
+ #[test]
+ fn test_ifnot_untaken() {
+ let template = compile("{{ if not null }}Hello!{{ endif }}");
+ let context = context();
+ let template_registry = other_templates();
+ let formatter_registry = formatters();
+ let string = template
+ .render(
+ &context,
+ &template_registry,
+ &formatter_registry,
+ &default_formatter(),
+ )
+ .unwrap();
+ assert_eq!("Hello!", &string);
+ }
+
+ #[test]
+ fn test_ifnot_else_taken() {
+ let template = compile("{{ if not boolean }}Hello!{{ else }}Goodbye!{{ endif }}");
+ let context = context();
+ let template_registry = other_templates();
+ let formatter_registry = formatters();
+ let string = template
+ .render(
+ &context,
+ &template_registry,
+ &formatter_registry,
+ &default_formatter(),
+ )
+ .unwrap();
+ assert_eq!("Goodbye!", &string);
+ }
+
+ #[test]
+ fn test_ifnot_else_untaken() {
+ let template = compile("{{ if not null }}Hello!{{ else }}Goodbye!{{ endif }}");
+ let context = context();
+ let template_registry = other_templates();
+ let formatter_registry = formatters();
+ let string = template
+ .render(
+ &context,
+ &template_registry,
+ &formatter_registry,
+ &default_formatter(),
+ )
+ .unwrap();
+ assert_eq!("Hello!", &string);
+ }
+
+ #[test]
+ fn test_nested_ifs() {
+ let template = compile(
+ "{{ if boolean }}Hi, {{ if null }}there!{{ else }}Hello!{{ endif }}{{ endif }}",
+ );
+ let context = context();
+ let template_registry = other_templates();
+ let formatter_registry = formatters();
+ let string = template
+ .render(
+ &context,
+ &template_registry,
+ &formatter_registry,
+ &default_formatter(),
+ )
+ .unwrap();
+ assert_eq!("Hi, Hello!", &string);
+ }
+
+ #[test]
+ fn test_with() {
+ let template = compile("{{ with nested as n }}{ n.value } { number }{{endwith}}");
+ let context = context();
+ let template_registry = other_templates();
+ let formatter_registry = formatters();
+ let string = template
+ .render(
+ &context,
+ &template_registry,
+ &formatter_registry,
+ &default_formatter(),
+ )
+ .unwrap();
+ assert_eq!("10 5", &string);
+ }
+
+ #[test]
+ fn test_for_loop() {
+ let template = compile("{{ for a in array }}{ a }{{ endfor }}");
+ let context = context();
+ let template_registry = other_templates();
+ let formatter_registry = formatters();
+ let string = template
+ .render(
+ &context,
+ &template_registry,
+ &formatter_registry,
+ &default_formatter(),
+ )
+ .unwrap();
+ assert_eq!("123", &string);
+ }
+
+ #[test]
+ fn test_for_loop_index() {
+ let template = compile("{{ for a in array }}{ @index }{{ endfor }}");
+ let context = context();
+ let template_registry = other_templates();
+ let formatter_registry = formatters();
+ let string = template
+ .render(
+ &context,
+ &template_registry,
+ &formatter_registry,
+ &default_formatter(),
+ )
+ .unwrap();
+ assert_eq!("012", &string);
+ }
+
+ #[test]
+ fn test_for_loop_first() {
+ let template =
+ compile("{{ for a in array }}{{if @first }}{ @index }{{ endif }}{{ endfor }}");
+ let context = context();
+ let template_registry = other_templates();
+ let formatter_registry = formatters();
+ let string = template
+ .render(
+ &context,
+ &template_registry,
+ &formatter_registry,
+ &default_formatter(),
+ )
+ .unwrap();
+ assert_eq!("0", &string);
+ }
+
+ #[test]
+ fn test_for_loop_last() {
+ let template =
+ compile("{{ for a in array }}{{ if @last}}{ @index }{{ endif }}{{ endfor }}");
+ let context = context();
+ let template_registry = other_templates();
+ let formatter_registry = formatters();
+ let string = template
+ .render(
+ &context,
+ &template_registry,
+ &formatter_registry,
+ &default_formatter(),
+ )
+ .unwrap();
+ assert_eq!("2", &string);
+ }
+
+ #[test]
+ fn test_whitespace_stripping_value() {
+ let template = compile("1 \n\t {- number -} \n 1");
+ let context = context();
+ let template_registry = other_templates();
+ let formatter_registry = formatters();
+ let string = template
+ .render(
+ &context,
+ &template_registry,
+ &formatter_registry,
+ &default_formatter(),
+ )
+ .unwrap();
+ assert_eq!("151", &string);
+ }
+
+ #[test]
+ fn test_call() {
+ let template = compile("{{ call my_macro with nested }}");
+ let context = context();
+ let template_registry = other_templates();
+ let formatter_registry = formatters();
+ let string = template
+ .render(
+ &context,
+ &template_registry,
+ &formatter_registry,
+ &default_formatter(),
+ )
+ .unwrap();
+ assert_eq!("10", &string);
+ }
+
+ #[test]
+ fn test_formatter() {
+ let template = compile("{ nested.value | my_formatter }");
+ let context = context();
+ let template_registry = other_templates();
+ let formatter_registry = formatters();
+ let string = template
+ .render(
+ &context,
+ &template_registry,
+ &formatter_registry,
+ &default_formatter(),
+ )
+ .unwrap();
+ assert_eq!("{10}", &string);
+ }
+
+ #[test]
+ fn test_unknown() {
+ let template = compile("{ foobar }");
+ let context = context();
+ let template_registry = other_templates();
+ let formatter_registry = formatters();
+ template
+ .render(
+ &context,
+ &template_registry,
+ &formatter_registry,
+ &default_formatter(),
+ )
+ .unwrap_err();
+ }
+
+ #[test]
+ fn test_escaping() {
+ let template = compile("{ escapes }");
+ let context = context();
+ let template_registry = other_templates();
+ let formatter_registry = formatters();
+ let string = template
+ .render(
+ &context,
+ &template_registry,
+ &formatter_registry,
+ &default_formatter(),
+ )
+ .unwrap();
+ assert_eq!("1:&lt; 2:&gt; 3:&amp; 4:&#39; 5:&quot;", &string);
+ }
+
+ #[test]
+ fn test_unescaped() {
+ let template = compile("{ escapes | unescaped }");
+ let context = context();
+ let template_registry = other_templates();
+ let mut formatter_registry = formatters();
+ formatter_registry.insert("unescaped", Box::new(::format_unescaped));
+ let string = template
+ .render(
+ &context,
+ &template_registry,
+ &formatter_registry,
+ &default_formatter(),
+ )
+ .unwrap();
+ assert_eq!("1:< 2:> 3:& 4:' 5:\"", &string);
+ }
+
+ #[test]
+ fn test_root_print() {
+ let template = compile("{ @root }");
+ let context = "Hello World!";
+ let context = ::serde_json::to_value(&context).unwrap();
+ let template_registry = other_templates();
+ let formatter_registry = formatters();
+ let string = template
+ .render(
+ &context,
+ &template_registry,
+ &formatter_registry,
+ &default_formatter(),
+ )
+ .unwrap();
+ assert_eq!("Hello World!", &string);
+ }
+
+ #[test]
+ fn test_root_branch() {
+ let template = compile("{{ if @root }}Hello World!{{ endif }}");
+ let context = true;
+ let context = ::serde_json::to_value(&context).unwrap();
+ let template_registry = other_templates();
+ let formatter_registry = formatters();
+ let string = template
+ .render(
+ &context,
+ &template_registry,
+ &formatter_registry,
+ &default_formatter(),
+ )
+ .unwrap();
+ assert_eq!("Hello World!", &string);
+ }
+
+ #[test]
+ fn test_root_iterate() {
+ let template = compile("{{ for a in @root }}{ a }{{ endfor }}");
+ let context = vec!["foo", "bar"];
+ let context = ::serde_json::to_value(&context).unwrap();
+ let template_registry = other_templates();
+ let formatter_registry = formatters();
+ let string = template
+ .render(
+ &context,
+ &template_registry,
+ &formatter_registry,
+ &default_formatter(),
+ )
+ .unwrap();
+ assert_eq!("foobar", &string);
+ }
+
+ #[test]
+ fn test_number_truthiness_zero() {
+ let template = compile("{{ if @root }}truthy{{else}}not truthy{{ endif }}");
+ let context = 0;
+ let context = ::serde_json::to_value(&context).unwrap();
+ let template_registry = other_templates();
+ let formatter_registry = formatters();
+ let string = template
+ .render(
+ &context,
+ &template_registry,
+ &formatter_registry,
+ &default_formatter(),
+ )
+ .unwrap();
+ assert_eq!("not truthy", &string);
+ }
+
+ #[test]
+ fn test_number_truthiness_one() {
+ let template = compile("{{ if @root }}truthy{{else}}not truthy{{ endif }}");
+ let context = 1;
+ let context = ::serde_json::to_value(&context).unwrap();
+ let template_registry = other_templates();
+ let formatter_registry = formatters();
+ let string = template
+ .render(
+ &context,
+ &template_registry,
+ &formatter_registry,
+ &default_formatter(),
+ )
+ .unwrap();
+ assert_eq!("truthy", &string);
+ }
+
+ #[test]
+ fn test_indexed_paths() {
+ #[derive(Serialize)]
+ struct Context {
+ foo: (usize, usize),
+ }
+
+ let template = compile("{ foo.1 }{ foo.0 }");
+ let context = Context { foo: (123, 456) };
+ let context = ::serde_json::to_value(&context).unwrap();
+ let template_registry = other_templates();
+ let formatter_registry = formatters();
+ let string = template
+ .render(
+ &context,
+ &template_registry,
+ &formatter_registry,
+ &default_formatter(),
+ )
+ .unwrap();
+ assert_eq!("456123", &string);
+ }
+
+ #[test]
+ fn test_indexed_paths_fall_back_to_string_lookup() {
+ #[derive(Serialize)]
+ struct Context {
+ foo: HashMap<&'static str, usize>,
+ }
+
+ let template = compile("{ foo.1 }{ foo.0 }");
+ let mut foo = HashMap::new();
+ foo.insert("0", 123);
+ foo.insert("1", 456);
+ let context = Context { foo };
+ let context = ::serde_json::to_value(&context).unwrap();
+ let template_registry = other_templates();
+ let formatter_registry = formatters();
+ let string = template
+ .render(
+ &context,
+ &template_registry,
+ &formatter_registry,
+ &default_formatter(),
+ )
+ .unwrap();
+ assert_eq!("456123", &string);
+ }
+}