tinytemplate/
template.rs

1//! This module implements the bytecode interpreter that actually renders the templates.
2
3use compiler::TemplateCompiler;
4use error::Error::*;
5use error::*;
6use instruction::{Instruction, PathSlice};
7use serde_json::Value;
8use std::collections::HashMap;
9use std::fmt::Write;
10use std::slice;
11use ValueFormatter;
12
13/// Enum defining the different kinds of records on the context stack.
14enum ContextElement<'render, 'template> {
15    /// Object contexts shadow everything below them on the stack, because every name is looked up
16    /// in this object.
17    Object(&'render Value),
18    /// Named contexts shadow only one name. Any path that starts with that name is looked up in
19    /// this object, and all others are passed on down the stack.
20    Named(&'template str, &'render Value),
21    /// Iteration contexts shadow one name with the current value of the iteration. They also
22    /// store the iteration state. The two usizes are the index of the current value and the length
23    /// of the array that we're iterating over.
24    Iteration(
25        &'template str,
26        &'render Value,
27        usize,
28        usize,
29        slice::Iter<'render, Value>,
30    ),
31}
32
33/// Helper struct which mostly exists so that I have somewhere to put functions that access the
34/// rendering context stack.
35struct RenderContext<'render, 'template> {
36    original_text: &'template str,
37    context_stack: Vec<ContextElement<'render, 'template>>,
38}
39impl<'render, 'template> RenderContext<'render, 'template> {
40    /// Look up the given path in the context stack and return the value (if found) or an error (if
41    /// not)
42    fn lookup(&self, path: PathSlice) -> Result<&'render Value> {
43        for stack_layer in self.context_stack.iter().rev() {
44            match stack_layer {
45                ContextElement::Object(obj) => return self.lookup_in(path, obj),
46                ContextElement::Named(name, obj) => {
47                    if *name == path[0] {
48                        return self.lookup_in(&path[1..], obj);
49                    }
50                }
51                ContextElement::Iteration(name, obj, _, _, _) => {
52                    if *name == path[0] {
53                        return self.lookup_in(&path[1..], obj);
54                    }
55                }
56            }
57        }
58        panic!("Attempted to do a lookup with an empty context stack. That shouldn't be possible.")
59    }
60
61    /// Look up a path within a given value object and return the resulting value (if found) or
62    /// an error (if not)
63    fn lookup_in(&self, path: PathSlice, object: &'render Value) -> Result<&'render Value> {
64        let mut current = object;
65        for step in path.iter() {
66            match current.get(step) {
67                Some(next) => current = next,
68                None => return Err(lookup_error(self.original_text, step, path, current)),
69            }
70        }
71        Ok(current)
72    }
73
74    /// Look up the index and length values for the top iteration context on the stack.
75    fn lookup_index(&self) -> Result<(usize, usize)> {
76        for stack_layer in self.context_stack.iter().rev() {
77            match stack_layer {
78                ContextElement::Iteration(_, _, index, length, _) => return Ok((*index, *length)),
79                _ => continue,
80            }
81        }
82        Err(GenericError {
83            msg: "Used @index outside of a foreach block.".to_string(),
84        })
85    }
86
87    /// Look up the root context object
88    fn lookup_root(&self) -> Result<&'render Value> {
89        match self.context_stack.get(0) {
90            Some(ContextElement::Object(obj)) => Ok(obj),
91            Some(_) => {
92                panic!("Expected Object value at root of context stack, but was something else.")
93            }
94            None => panic!(
95                "Attempted to do a lookup with an empty context stack. That shouldn't be possible."
96            ),
97        }
98    }
99}
100
101/// Structure representing a parsed template. It holds the bytecode program for rendering the
102/// template as well as the length of the original template string, which is used as a guess to
103/// pre-size the output string buffer.
104pub(crate) struct Template<'template> {
105    original_text: &'template str,
106    instructions: Vec<Instruction<'template>>,
107    template_len: usize,
108}
109impl<'template> Template<'template> {
110    /// Create a Template from the given template string.
111    pub fn compile(text: &'template str) -> Result<Template> {
112        Ok(Template {
113            original_text: text,
114            template_len: text.len(),
115            instructions: TemplateCompiler::new(text).compile()?,
116        })
117    }
118
119    /// Render this template into a string and return it (or any error if one is encountered).
120    pub fn render(
121        &self,
122        context: &Value,
123        template_registry: &HashMap<&str, Template>,
124        formatter_registry: &HashMap<&str, Box<ValueFormatter>>,
125        default_formatter: &ValueFormatter,
126    ) -> Result<String> {
127        // The length of the original template seems like a reasonable guess at the length of the
128        // output.
129        let mut output = String::with_capacity(self.template_len);
130        self.render_into(
131            context,
132            template_registry,
133            formatter_registry,
134            default_formatter,
135            &mut output,
136        )?;
137        Ok(output)
138    }
139
140    /// Render this template into a given string. Used for calling other templates.
141    pub fn render_into(
142        &self,
143        context: &Value,
144        template_registry: &HashMap<&str, Template>,
145        formatter_registry: &HashMap<&str, Box<ValueFormatter>>,
146        default_formatter: &ValueFormatter,
147        output: &mut String,
148    ) -> Result<()> {
149        let mut program_counter = 0;
150        let mut render_context = RenderContext {
151            original_text: self.original_text,
152            context_stack: vec![ContextElement::Object(context)],
153        };
154
155        while program_counter < self.instructions.len() {
156            match &self.instructions[program_counter] {
157                Instruction::Literal(text) => {
158                    output.push_str(text);
159                    program_counter += 1;
160                }
161                Instruction::Value(path) => {
162                    let first = *path.first().unwrap();
163                    if first.starts_with('@') {
164                        // Currently we just hard-code the special @-keywords and have special
165                        // lookup functions to use them because there are lifetime complexities with
166                        // looking up values that don't live for as long as the given context object.
167                        match first {
168                            "@index" => {
169                                write!(output, "{}", render_context.lookup_index()?.0).unwrap()
170                            }
171                            "@first" => {
172                                write!(output, "{}", render_context.lookup_index()?.0 == 0).unwrap()
173                            }
174                            "@last" => {
175                                let (index, length) = render_context.lookup_index()?;
176                                write!(output, "{}", index == length - 1).unwrap()
177                            }
178                            "@root" => {
179                                let value_to_render = render_context.lookup_root()?;
180                                default_formatter(value_to_render, output)?;
181                            }
182                            _ => panic!(), // This should have been caught by the parser.
183                        }
184                    } else {
185                        let value_to_render = render_context.lookup(path)?;
186                        default_formatter(value_to_render, output)?;
187                    }
188                    program_counter += 1;
189                }
190                Instruction::FormattedValue(path, name) => {
191                    // The @ keywords aren't supported for formatted values. Should they be?
192                    let value_to_render = render_context.lookup(path)?;
193                    match formatter_registry.get(name) {
194                        Some(formatter) => {
195                            let formatter_result = formatter(value_to_render, output);
196                            if let Err(err) = formatter_result {
197                                return Err(called_formatter_error(self.original_text, name, err));
198                            }
199                        }
200                        None => return Err(unknown_formatter(self.original_text, name)),
201                    }
202                    program_counter += 1;
203                }
204                Instruction::Branch(path, negate, target) => {
205                    let first = *path.first().unwrap();
206                    let mut truthy = if first.starts_with('@') {
207                        match first {
208                            "@index" => render_context.lookup_index()?.0 != 0,
209                            "@first" => render_context.lookup_index()?.0 == 0,
210                            "@last" => {
211                                let (index, length) = render_context.lookup_index()?;
212                                index == (length - 1)
213                            }
214                            "@root" => self.value_is_truthy(render_context.lookup_root()?, path)?,
215                            other => panic!("Unknown keyword {}", other), // This should have been caught by the parser.
216                        }
217                    } else {
218                        let value_to_render = render_context.lookup(path)?;
219                        self.value_is_truthy(value_to_render, path)?
220                    };
221                    if *negate {
222                        truthy = !truthy;
223                    }
224
225                    if truthy {
226                        program_counter = *target;
227                    } else {
228                        program_counter += 1;
229                    }
230                }
231                Instruction::PushNamedContext(path, name) => {
232                    let context_value = render_context.lookup(path)?;
233                    render_context
234                        .context_stack
235                        .push(ContextElement::Named(name, context_value));
236                    program_counter += 1;
237                }
238                Instruction::PushIterationContext(path, name) => {
239                    // We push a context with an invalid index and no value and then wait for the
240                    // following Iterate instruction to set the index and value properly.
241                    let first = *path.first().unwrap();
242                    let context_value = match first {
243                        "@root" => render_context.lookup_root()?,
244                        other if other.starts_with('@') => {
245                            return Err(not_iterable_error(self.original_text, path))
246                        }
247                        _ => render_context.lookup(path)?,
248                    };
249                    match context_value {
250                        Value::Array(ref arr) => {
251                            render_context.context_stack.push(ContextElement::Iteration(
252                                name,
253                                &Value::Null,
254                                ::std::usize::MAX,
255                                arr.len(),
256                                arr.iter(),
257                            ))
258                        }
259                        _ => return Err(not_iterable_error(self.original_text, path)),
260                    };
261                    program_counter += 1;
262                }
263                Instruction::PopContext => {
264                    render_context.context_stack.pop();
265                    program_counter += 1;
266                }
267                Instruction::Goto(target) => {
268                    program_counter = *target;
269                }
270                Instruction::Iterate(target) => {
271                    match render_context.context_stack.last_mut() {
272                        Some(ContextElement::Iteration(_, val, index, _, iter)) => {
273                            match iter.next() {
274                                Some(new_val) => {
275                                    *val = new_val;
276                                    // On the first iteration, this will be usize::MAX so it will
277                                    // wrap around to zero.
278                                    *index = index.wrapping_add(1);
279                                    program_counter += 1;
280                                }
281                                None => {
282                                    program_counter = *target;
283                                }
284                            }
285                        }
286                        _ => panic!("Malformed program."),
287                    };
288                }
289                Instruction::Call(template_name, path) => {
290                    let context_value = render_context.lookup(path)?;
291                    match template_registry.get(template_name) {
292                        Some(templ) => {
293                            let called_templ_result = templ.render_into(
294                                context_value,
295                                template_registry,
296                                formatter_registry,
297                                default_formatter,
298                                output,
299                            );
300                            if let Err(err) = called_templ_result {
301                                return Err(called_template_error(
302                                    self.original_text,
303                                    template_name,
304                                    err,
305                                ));
306                            }
307                        }
308                        None => return Err(unknown_template(self.original_text, template_name)),
309                    }
310                    program_counter += 1;
311                }
312            }
313        }
314        Ok(())
315    }
316
317    fn value_is_truthy(&self, value: &Value, path: &[&str]) -> Result<bool> {
318        let truthy = match value {
319            Value::Null => false,
320            Value::Bool(b) => *b,
321            Value::Number(n) => match n.as_f64() {
322                Some(float) => float == 0.0,
323                None => {
324                    return Err(truthiness_error(self.original_text, path));
325                }
326            },
327            Value::String(s) => !s.is_empty(),
328            Value::Array(arr) => !arr.is_empty(),
329            Value::Object(_) => true,
330        };
331        Ok(truthy)
332    }
333}
334
335#[cfg(test)]
336mod test {
337    use super::*;
338    use compiler::TemplateCompiler;
339
340    fn compile(text: &'static str) -> Template<'static> {
341        Template {
342            original_text: text,
343            template_len: text.len(),
344            instructions: TemplateCompiler::new(text).compile().unwrap(),
345        }
346    }
347
348    #[derive(Serialize)]
349    struct NestedContext {
350        value: usize,
351    }
352
353    #[derive(Serialize)]
354    struct TestContext {
355        number: usize,
356        string: &'static str,
357        boolean: bool,
358        null: Option<usize>,
359        array: Vec<usize>,
360        nested: NestedContext,
361        escapes: &'static str,
362    }
363
364    fn context() -> Value {
365        let ctx = TestContext {
366            number: 5,
367            string: "test",
368            boolean: true,
369            null: None,
370            array: vec![1, 2, 3],
371            nested: NestedContext { value: 10 },
372            escapes: "1:< 2:> 3:& 4:' 5:\"",
373        };
374        ::serde_json::to_value(&ctx).unwrap()
375    }
376
377    fn other_templates() -> HashMap<&'static str, Template<'static>> {
378        let mut map = HashMap::new();
379        map.insert("my_macro", compile("{value}"));
380        map
381    }
382
383    fn format(value: &Value, output: &mut String) -> Result<()> {
384        output.push_str("{");
385        ::format(value, output)?;
386        output.push_str("}");
387        Ok(())
388    }
389
390    fn formatters() -> HashMap<&'static str, Box<ValueFormatter>> {
391        let mut map = HashMap::<&'static str, Box<ValueFormatter>>::new();
392        map.insert("my_formatter", Box::new(format));
393        map
394    }
395
396    pub fn default_formatter() -> &'static ValueFormatter {
397        &::format
398    }
399
400    #[test]
401    fn test_literal() {
402        let template = compile("Hello!");
403        let context = context();
404        let template_registry = other_templates();
405        let formatter_registry = formatters();
406        let string = template
407            .render(
408                &context,
409                &template_registry,
410                &formatter_registry,
411                &default_formatter(),
412            )
413            .unwrap();
414        assert_eq!("Hello!", &string);
415    }
416
417    #[test]
418    fn test_value() {
419        let template = compile("{ number }");
420        let context = context();
421        let template_registry = other_templates();
422        let formatter_registry = formatters();
423        let string = template
424            .render(
425                &context,
426                &template_registry,
427                &formatter_registry,
428                &default_formatter(),
429            )
430            .unwrap();
431        assert_eq!("5", &string);
432    }
433
434    #[test]
435    fn test_path() {
436        let template = compile("The number of the day is { nested.value }.");
437        let context = context();
438        let template_registry = other_templates();
439        let formatter_registry = formatters();
440        let string = template
441            .render(
442                &context,
443                &template_registry,
444                &formatter_registry,
445                &default_formatter(),
446            )
447            .unwrap();
448        assert_eq!("The number of the day is 10.", &string);
449    }
450
451    #[test]
452    fn test_if_taken() {
453        let template = compile("{{ if boolean }}Hello!{{ endif }}");
454        let context = context();
455        let template_registry = other_templates();
456        let formatter_registry = formatters();
457        let string = template
458            .render(
459                &context,
460                &template_registry,
461                &formatter_registry,
462                &default_formatter(),
463            )
464            .unwrap();
465        assert_eq!("Hello!", &string);
466    }
467
468    #[test]
469    fn test_if_untaken() {
470        let template = compile("{{ if null }}Hello!{{ endif }}");
471        let context = context();
472        let template_registry = other_templates();
473        let formatter_registry = formatters();
474        let string = template
475            .render(
476                &context,
477                &template_registry,
478                &formatter_registry,
479                &default_formatter(),
480            )
481            .unwrap();
482        assert_eq!("", &string);
483    }
484
485    #[test]
486    fn test_if_else_taken() {
487        let template = compile("{{ if boolean }}Hello!{{ else }}Goodbye!{{ endif }}");
488        let context = context();
489        let template_registry = other_templates();
490        let formatter_registry = formatters();
491        let string = template
492            .render(
493                &context,
494                &template_registry,
495                &formatter_registry,
496                &default_formatter(),
497            )
498            .unwrap();
499        assert_eq!("Hello!", &string);
500    }
501
502    #[test]
503    fn test_if_else_untaken() {
504        let template = compile("{{ if null }}Hello!{{ else }}Goodbye!{{ endif }}");
505        let context = context();
506        let template_registry = other_templates();
507        let formatter_registry = formatters();
508        let string = template
509            .render(
510                &context,
511                &template_registry,
512                &formatter_registry,
513                &default_formatter(),
514            )
515            .unwrap();
516        assert_eq!("Goodbye!", &string);
517    }
518
519    #[test]
520    fn test_ifnot_taken() {
521        let template = compile("{{ if not boolean }}Hello!{{ endif }}");
522        let context = context();
523        let template_registry = other_templates();
524        let formatter_registry = formatters();
525        let string = template
526            .render(
527                &context,
528                &template_registry,
529                &formatter_registry,
530                &default_formatter(),
531            )
532            .unwrap();
533        assert_eq!("", &string);
534    }
535
536    #[test]
537    fn test_ifnot_untaken() {
538        let template = compile("{{ if not null }}Hello!{{ endif }}");
539        let context = context();
540        let template_registry = other_templates();
541        let formatter_registry = formatters();
542        let string = template
543            .render(
544                &context,
545                &template_registry,
546                &formatter_registry,
547                &default_formatter(),
548            )
549            .unwrap();
550        assert_eq!("Hello!", &string);
551    }
552
553    #[test]
554    fn test_ifnot_else_taken() {
555        let template = compile("{{ if not boolean }}Hello!{{ else }}Goodbye!{{ endif }}");
556        let context = context();
557        let template_registry = other_templates();
558        let formatter_registry = formatters();
559        let string = template
560            .render(
561                &context,
562                &template_registry,
563                &formatter_registry,
564                &default_formatter(),
565            )
566            .unwrap();
567        assert_eq!("Goodbye!", &string);
568    }
569
570    #[test]
571    fn test_ifnot_else_untaken() {
572        let template = compile("{{ if not null }}Hello!{{ else }}Goodbye!{{ endif }}");
573        let context = context();
574        let template_registry = other_templates();
575        let formatter_registry = formatters();
576        let string = template
577            .render(
578                &context,
579                &template_registry,
580                &formatter_registry,
581                &default_formatter(),
582            )
583            .unwrap();
584        assert_eq!("Hello!", &string);
585    }
586
587    #[test]
588    fn test_nested_ifs() {
589        let template = compile(
590            "{{ if boolean }}Hi, {{ if null }}there!{{ else }}Hello!{{ endif }}{{ endif }}",
591        );
592        let context = context();
593        let template_registry = other_templates();
594        let formatter_registry = formatters();
595        let string = template
596            .render(
597                &context,
598                &template_registry,
599                &formatter_registry,
600                &default_formatter(),
601            )
602            .unwrap();
603        assert_eq!("Hi, Hello!", &string);
604    }
605
606    #[test]
607    fn test_with() {
608        let template = compile("{{ with nested as n }}{ n.value } { number }{{endwith}}");
609        let context = context();
610        let template_registry = other_templates();
611        let formatter_registry = formatters();
612        let string = template
613            .render(
614                &context,
615                &template_registry,
616                &formatter_registry,
617                &default_formatter(),
618            )
619            .unwrap();
620        assert_eq!("10 5", &string);
621    }
622
623    #[test]
624    fn test_for_loop() {
625        let template = compile("{{ for a in array }}{ a }{{ endfor }}");
626        let context = context();
627        let template_registry = other_templates();
628        let formatter_registry = formatters();
629        let string = template
630            .render(
631                &context,
632                &template_registry,
633                &formatter_registry,
634                &default_formatter(),
635            )
636            .unwrap();
637        assert_eq!("123", &string);
638    }
639
640    #[test]
641    fn test_for_loop_index() {
642        let template = compile("{{ for a in array }}{ @index }{{ endfor }}");
643        let context = context();
644        let template_registry = other_templates();
645        let formatter_registry = formatters();
646        let string = template
647            .render(
648                &context,
649                &template_registry,
650                &formatter_registry,
651                &default_formatter(),
652            )
653            .unwrap();
654        assert_eq!("012", &string);
655    }
656
657    #[test]
658    fn test_for_loop_first() {
659        let template =
660            compile("{{ for a in array }}{{if @first }}{ @index }{{ endif }}{{ endfor }}");
661        let context = context();
662        let template_registry = other_templates();
663        let formatter_registry = formatters();
664        let string = template
665            .render(
666                &context,
667                &template_registry,
668                &formatter_registry,
669                &default_formatter(),
670            )
671            .unwrap();
672        assert_eq!("0", &string);
673    }
674
675    #[test]
676    fn test_for_loop_last() {
677        let template =
678            compile("{{ for a in array }}{{ if @last}}{ @index }{{ endif }}{{ endfor }}");
679        let context = context();
680        let template_registry = other_templates();
681        let formatter_registry = formatters();
682        let string = template
683            .render(
684                &context,
685                &template_registry,
686                &formatter_registry,
687                &default_formatter(),
688            )
689            .unwrap();
690        assert_eq!("2", &string);
691    }
692
693    #[test]
694    fn test_whitespace_stripping_value() {
695        let template = compile("1  \n\t   {- number -}  \n   1");
696        let context = context();
697        let template_registry = other_templates();
698        let formatter_registry = formatters();
699        let string = template
700            .render(
701                &context,
702                &template_registry,
703                &formatter_registry,
704                &default_formatter(),
705            )
706            .unwrap();
707        assert_eq!("151", &string);
708    }
709
710    #[test]
711    fn test_call() {
712        let template = compile("{{ call my_macro with nested }}");
713        let context = context();
714        let template_registry = other_templates();
715        let formatter_registry = formatters();
716        let string = template
717            .render(
718                &context,
719                &template_registry,
720                &formatter_registry,
721                &default_formatter(),
722            )
723            .unwrap();
724        assert_eq!("10", &string);
725    }
726
727    #[test]
728    fn test_formatter() {
729        let template = compile("{ nested.value | my_formatter }");
730        let context = context();
731        let template_registry = other_templates();
732        let formatter_registry = formatters();
733        let string = template
734            .render(
735                &context,
736                &template_registry,
737                &formatter_registry,
738                &default_formatter(),
739            )
740            .unwrap();
741        assert_eq!("{10}", &string);
742    }
743
744    #[test]
745    fn test_unknown() {
746        let template = compile("{ foobar }");
747        let context = context();
748        let template_registry = other_templates();
749        let formatter_registry = formatters();
750        template
751            .render(
752                &context,
753                &template_registry,
754                &formatter_registry,
755                &default_formatter(),
756            )
757            .unwrap_err();
758    }
759
760    #[test]
761    fn test_escaping() {
762        let template = compile("{ escapes }");
763        let context = context();
764        let template_registry = other_templates();
765        let formatter_registry = formatters();
766        let string = template
767            .render(
768                &context,
769                &template_registry,
770                &formatter_registry,
771                &default_formatter(),
772            )
773            .unwrap();
774        assert_eq!("1:&lt; 2:&gt; 3:&amp; 4:&#39; 5:&quot;", &string);
775    }
776
777    #[test]
778    fn test_unescaped() {
779        let template = compile("{ escapes | unescaped }");
780        let context = context();
781        let template_registry = other_templates();
782        let mut formatter_registry = formatters();
783        formatter_registry.insert("unescaped", Box::new(::format_unescaped));
784        let string = template
785            .render(
786                &context,
787                &template_registry,
788                &formatter_registry,
789                &default_formatter(),
790            )
791            .unwrap();
792        assert_eq!("1:< 2:> 3:& 4:' 5:\"", &string);
793    }
794
795    #[test]
796    fn test_root_print() {
797        let template = compile("{ @root }");
798        let context = "Hello World!";
799        let context = ::serde_json::to_value(&context).unwrap();
800        let template_registry = other_templates();
801        let formatter_registry = formatters();
802        let string = template
803            .render(
804                &context,
805                &template_registry,
806                &formatter_registry,
807                &default_formatter(),
808            )
809            .unwrap();
810        assert_eq!("Hello World!", &string);
811    }
812
813    #[test]
814    fn test_root_branch() {
815        let template = compile("{{ if @root }}Hello World!{{ endif }}");
816        let context = true;
817        let context = ::serde_json::to_value(&context).unwrap();
818        let template_registry = other_templates();
819        let formatter_registry = formatters();
820        let string = template
821            .render(
822                &context,
823                &template_registry,
824                &formatter_registry,
825                &default_formatter(),
826            )
827            .unwrap();
828        assert_eq!("Hello World!", &string);
829    }
830
831    #[test]
832    fn test_root_iterate() {
833        let template = compile("{{ for a in @root }}{ a }{{ endfor }}");
834        let context = vec!["foo", "bar"];
835        let context = ::serde_json::to_value(&context).unwrap();
836        let template_registry = other_templates();
837        let formatter_registry = formatters();
838        let string = template
839            .render(
840                &context,
841                &template_registry,
842                &formatter_registry,
843                &default_formatter(),
844            )
845            .unwrap();
846        assert_eq!("foobar", &string);
847    }
848}