tera_v1/
tera.rs

1use std::collections::HashMap;
2use std::fmt;
3use std::fs::File;
4use std::io::prelude::*;
5use std::path::Path;
6use std::sync::Arc;
7
8use globwalk::glob;
9use serde::Serialize;
10use serde_json::value::to_value;
11
12use crate::builtins::filters::{array, common, number, object, string, Filter};
13use crate::builtins::functions::{self, Function};
14use crate::builtins::testers::{self, Test};
15use crate::context::Context;
16use crate::errors::{Error, Result};
17use crate::renderer::Renderer;
18use crate::template::Template;
19use crate::utils::escape_html;
20
21/// The escape function type definition
22pub type EscapeFn = fn(&str) -> String;
23
24/// The main point of interaction in this library.
25pub struct Tera {
26    // The glob used in `Tera::new`, None if Tera was instantiated differently
27    #[doc(hidden)]
28    glob: Option<String>,
29    #[doc(hidden)]
30    pub templates: HashMap<String, Template>,
31    #[doc(hidden)]
32    pub filters: HashMap<String, Arc<dyn Filter>>,
33    #[doc(hidden)]
34    pub testers: HashMap<String, Arc<dyn Test>>,
35    #[doc(hidden)]
36    pub functions: HashMap<String, Arc<dyn Function>>,
37    // Which extensions does Tera automatically autoescape on.
38    // Defaults to [".html", ".htm", ".xml"]
39    #[doc(hidden)]
40    pub autoescape_suffixes: Vec<&'static str>,
41    #[doc(hidden)]
42    escape_fn: EscapeFn,
43}
44
45impl Tera {
46    fn create(dir: &str, parse_only: bool) -> Result<Tera> {
47        if dir.find('*').is_none() {
48            return Err(Error::msg(format!(
49                "Tera expects a glob as input, no * were found in `{}`",
50                dir
51            )));
52        }
53
54        let mut tera = Tera {
55            glob: Some(dir.to_string()),
56            templates: HashMap::new(),
57            filters: HashMap::new(),
58            functions: HashMap::new(),
59            testers: HashMap::new(),
60            autoescape_suffixes: vec![".html", ".htm", ".xml"],
61            escape_fn: escape_html,
62        };
63
64        tera.load_from_glob()?;
65        if !parse_only {
66            tera.build_inheritance_chains()?;
67            tera.check_macro_files()?;
68        }
69        tera.register_tera_filters();
70        tera.register_tera_testers();
71        tera.register_tera_functions();
72        Ok(tera)
73    }
74
75    /// Create a new instance of Tera, containing all the parsed templates found in the `dir` glob
76    ///
77    /// The example below is what the [compile_templates](macro.compile_templates.html) macros expands to.
78    ///
79    ///```ignore
80    ///match Tera::new("templates/**/*") {
81    ///    Ok(t) => t,
82    ///    Err(e) => {
83    ///        println!("Parsing error(s): {}", e);
84    ///        ::std::process::exit(1);
85    ///    }
86    ///}
87    ///```
88    pub fn new(dir: &str) -> Result<Tera> {
89        Self::create(dir, false)
90    }
91
92    /// Create a new instance of Tera, containing all the parsed templates found in the `dir` glob
93    /// The difference with `Tera::new` is that it won't build the inheritance chains automatically
94    /// so you are free to modify the templates if you need to.
95    /// You will NOT get a working Tera instance using `Tera::parse`, you will need to call
96    /// `tera.build_inheritance_chains()` to make it usable
97    ///
98    ///```ignore
99    ///let mut tera = match Tera::parse("templates/**/*") {
100    ///    Ok(t) => t,
101    ///    Err(e) => {
102    ///        println!("Parsing error(s): {}", e);
103    ///        ::std::process::exit(1);
104    ///    }
105    ///};
106    ///tera.build_inheritance_chains()?;
107    ///```
108    pub fn parse(dir: &str) -> Result<Tera> {
109        Self::create(dir, true)
110    }
111
112    /// Loads all the templates found in the glob that was given to Tera::new
113    fn load_from_glob(&mut self) -> Result<()> {
114        if self.glob.is_none() {
115            return Err(Error::msg("Tera can only load from glob if a glob is provided"));
116        }
117        // We want to preserve templates that have been added through
118        // Tera::extend so we only keep those
119        self.templates = self
120            .templates
121            .iter()
122            .filter(|&(_, t)| t.from_extend)
123            .map(|(n, t)| (n.clone(), t.clone())) // TODO: avoid that clone
124            .collect();
125
126        let mut errors = String::new();
127
128        let dir = self.glob.clone().unwrap();
129        // We clean the filename by removing the dir given
130        // to Tera so users don't have to prefix everytime
131        let mut parent_dir = dir.split_at(dir.find('*').unwrap()).0;
132        // Remove `./` from the glob if used as it would cause an error in strip_prefix
133        if parent_dir.starts_with("./") {
134            parent_dir = &parent_dir[2..];
135        }
136
137        // We are parsing all the templates on instantiation
138        for entry in glob(&dir).unwrap().filter_map(|e| e.ok()) {
139            let mut path = entry.into_path();
140            // We only care about actual files
141            if path.is_file() {
142                if path.starts_with("./") {
143                    path = path.strip_prefix("./").unwrap().to_path_buf();
144                }
145
146                let filepath = path
147                    .strip_prefix(&parent_dir)
148                    .unwrap()
149                    .to_string_lossy()
150                    // unify on forward slash
151                    .replace("\\", "/");
152
153                if let Err(e) = self.add_file(Some(&filepath), path) {
154                    use std::error::Error;
155
156                    errors += &format!("\n* {}", e);
157                    let mut cause = e.source();
158                    while let Some(e) = cause {
159                        errors += &format!("\n{}", e);
160                        cause = e.source();
161                    }
162                }
163            }
164        }
165
166        if !errors.is_empty() {
167            return Err(Error::msg(errors));
168        }
169
170        Ok(())
171    }
172
173    // Add a template from a path: reads the file and parses it.
174    // This will return an error if the template is invalid and doesn't check the validity of
175    // inheritance chains.
176    fn add_file<P: AsRef<Path>>(&mut self, name: Option<&str>, path: P) -> Result<()> {
177        let path = path.as_ref();
178        let tpl_name = name.unwrap_or_else(|| path.to_str().unwrap());
179
180        let mut f = File::open(path)
181            .map_err(|e| Error::chain(format!("Couldn't open template '{:?}'", path), e))?;
182
183        let mut input = String::new();
184        f.read_to_string(&mut input)
185            .map_err(|e| Error::chain(format!("Failed to read template '{:?}'", path), e))?;
186
187        let tpl = Template::new(tpl_name, Some(path.to_str().unwrap().to_string()), &input)
188            .map_err(|e| Error::chain(format!("Failed to parse {:?}", path), e))?;
189
190        self.templates.insert(tpl_name.to_string(), tpl);
191        Ok(())
192    }
193
194    /// We need to know the hierarchy of templates to be able to render multiple extends level
195    /// This happens at compile-time to avoid checking it every time we want to render a template
196    /// This also checks for soundness issues in the inheritance chains, such as missing template or
197    /// circular extends.
198    /// It also builds the block inheritance chain and detects when super() is called in a place
199    /// where it can't possibly work
200    ///
201    /// You generally don't need to call that yourself, unless you used `Tera::parse`
202    pub fn build_inheritance_chains(&mut self) -> Result<()> {
203        // Recursive fn that finds all the parents and put them in an ordered Vec from closest to first parent
204        // parent template
205        fn build_chain(
206            templates: &HashMap<String, Template>,
207            start: &Template,
208            template: &Template,
209            mut parents: Vec<String>,
210        ) -> Result<Vec<String>> {
211            if !parents.is_empty() && start.name == template.name {
212                return Err(Error::circular_extend(&start.name, parents));
213            }
214
215            match template.parent {
216                Some(ref p) => match templates.get(p) {
217                    Some(parent) => {
218                        parents.push(parent.name.clone());
219                        build_chain(templates, start, parent, parents)
220                    }
221                    None => Err(Error::missing_parent(&template.name, &p)),
222                },
223                None => Ok(parents),
224            }
225        }
226
227        // TODO: if we can rewrite the 2 loops below to be only one loop, that'd be great
228        let mut tpl_parents = HashMap::new();
229        let mut tpl_block_definitions = HashMap::new();
230        for (name, template) in &self.templates {
231            if template.parent.is_none() && template.blocks.is_empty() {
232                continue;
233            }
234
235            let parents = build_chain(&self.templates, template, template, vec![])?;
236
237            let mut blocks_definitions = HashMap::new();
238            for (block_name, def) in &template.blocks {
239                // push our own block first
240                let mut definitions = vec![(template.name.clone(), def.clone())];
241
242                // and then see if our parents have it
243                for parent in &parents {
244                    let t = self.get_template(parent)?;
245
246                    if let Some(b) = t.blocks.get(block_name) {
247                        definitions.push((t.name.clone(), b.clone()));
248                    }
249                }
250                blocks_definitions.insert(block_name.clone(), definitions);
251            }
252            tpl_parents.insert(name.clone(), parents);
253            tpl_block_definitions.insert(name.clone(), blocks_definitions);
254        }
255
256        for template in self.templates.values_mut() {
257            // Simple template: no inheritance or blocks -> nothing to do
258            if template.parent.is_none() && template.blocks.is_empty() {
259                continue;
260            }
261
262            template.parents = match tpl_parents.remove(&template.name) {
263                Some(parents) => parents,
264                None => vec![],
265            };
266            template.blocks_definitions = match tpl_block_definitions.remove(&template.name) {
267                Some(blocks) => blocks,
268                None => HashMap::new(),
269            };
270        }
271
272        Ok(())
273    }
274
275    /// We keep track of macro files loaded in each Template so we can know whether one or them
276    /// is missing and error accordingly before the user tries to render a template.
277    ///
278    /// As with `self::build_inheritance_chains`, you don't usually need to call that yourself.
279    pub fn check_macro_files(&self) -> Result<()> {
280        for template in self.templates.values() {
281            for &(ref tpl_name, _) in &template.imported_macro_files {
282                if !self.templates.contains_key(tpl_name) {
283                    return Err(Error::msg(format!(
284                        "Template `{}` loads macros from `{}` which isn't present in Tera",
285                        template.name, tpl_name
286                    )));
287                }
288            }
289        }
290
291        Ok(())
292    }
293
294    /// Renders a Tera template given a `tera::Context`,
295    ///
296    /// To render a template with an empty context, simply pass a new `tera::Context` object
297    ///
298    /// ```rust,ignore
299    /// // Rendering a template with a normal context
300    /// let mut context = Context::new();
301    /// context.insert("age", 18);
302    /// tera.render("hello.html", context);
303    /// // Rendering a template with an empty context
304    /// tera.render("hello.html", Context::new());
305    /// ```
306    pub fn render(&self, template_name: &str, context: Context) -> Result<String> {
307        let template = self.get_template(template_name)?;
308        let renderer = Renderer::new(template, self, context.into_json());
309        renderer.render()
310    }
311
312    /// Renders a Tera template given an object that implements `Serialize`.
313    ///
314    /// If `data` is serializing to an object, an error will be returned.
315    ///
316    /// ```rust,ignore
317    /// // Rendering a template with a struct that impl `Serialize`
318    /// tera.render_value("hello.html", &product);
319    /// ```
320    pub fn render_value<T: Serialize>(&self, template_name: &str, data: &T) -> Result<String> {
321        let value = to_value(data).map_err(Error::json)?;
322        if !value.is_object() {
323            return Err(Error::msg(format!(
324                "Failed to render '{}': context isn't a JSON object. \
325                The value passed needs to be a key-value object: context, struct, hashmap for example.",
326                template_name
327            )));
328        }
329
330        let template = self.get_template(template_name)?;
331        let renderer = Renderer::new(template, self, value);
332        renderer.render()
333    }
334
335    /// Renders a one off template (for example a template coming from a user input) given a `Context`
336    ///
337    /// This creates a separate instance of Tera with no possibilities of adding custom filters
338    /// or testers, parses the template and render it immediately.
339    /// Any errors will mention the `one_off` template: this is the name given to the template by
340    /// Tera
341    ///
342    /// ```rust,ignore
343    /// let mut context = Context::new();
344    /// context.insert("greeting", &"hello");
345    /// Tera::one_off("{{ greeting }} world", context, true);
346    /// ```
347    pub fn one_off(input: &str, context: Context, autoescape: bool) -> Result<String> {
348        let mut tera = Tera::default();
349        tera.add_raw_template("one_off", input)?;
350        if autoescape {
351            tera.autoescape_on(vec!["one_off"]);
352        }
353
354        tera.render("one_off", context)
355    }
356
357    /// Renders a one off template (for example a template coming from a user input) given an object
358    /// that implements `Serialize`.
359    ///
360    /// This creates a separate instance of Tera with no possibilities of adding custom filters
361    /// or testers, parses the template and render it immediately.
362    /// Any errors will mention the `one_off` template: this is the name given to the template by
363    /// Tera
364    ///
365    /// ```rust,ignore
366    /// // With a struct that impl Serialize
367    /// Tera::one_off("{{ greeting }} world", &user, true);
368    /// ```
369    pub fn one_off_value<T: Serialize>(input: &str, data: &T, autoescape: bool) -> Result<String> {
370        let mut tera = Tera::default();
371        tera.add_raw_template("one_off", input)?;
372        if autoescape {
373            tera.autoescape_on(vec!["one_off"]);
374        }
375
376        tera.render_value("one_off", data)
377    }
378    #[doc(hidden)]
379    #[inline]
380    pub fn get_template(&self, template_name: &str) -> Result<&Template> {
381        match self.templates.get(template_name) {
382            Some(tpl) => Ok(tpl),
383            None => Err(Error::template_not_found(template_name)),
384        }
385    }
386
387    /// Add a single template to the Tera instance
388    ///
389    /// This will error if the inheritance chain can't be built, such as adding a child
390    /// template without the parent one.
391    /// If you want to add several templates, use [Tera::add_templates](struct.Tera.html#method.add_templates)
392    ///
393    /// ```rust,ignore
394    /// tera.add_template("new.html", "Blabla");
395    /// ```
396    pub fn add_raw_template(&mut self, name: &str, content: &str) -> Result<()> {
397        let tpl = Template::new(name, None, content)
398            .map_err(|e| Error::chain(format!("Failed to parse '{}'", name), e))?;
399        self.templates.insert(name.to_string(), tpl);
400        self.build_inheritance_chains()?;
401        self.check_macro_files()?;
402        Ok(())
403    }
404
405    /// Add all the templates given to the Tera instance
406    ///
407    /// This will error if the inheritance chain can't be built, such as adding a child
408    /// template without the parent one.
409    ///
410    /// ```rust,ignore
411    /// tera.add_raw_templates(vec![
412    ///     ("new.html", "blabla"),
413    ///     ("new2.html", "hello"),
414    /// ]);
415    /// ```
416    pub fn add_raw_templates(&mut self, templates: Vec<(&str, &str)>) -> Result<()> {
417        for (name, content) in templates {
418            let tpl = Template::new(name, None, content)
419                .map_err(|e| Error::chain(format!("Failed to parse '{}'", name), e))?;
420            self.templates.insert(name.to_string(), tpl);
421        }
422        self.build_inheritance_chains()?;
423        self.check_macro_files()?;
424        Ok(())
425    }
426
427    /// Add a single template from a path to the Tera instance. The default name for the template is
428    /// the path given, but this can be renamed with the `name` parameter
429    ///
430    /// This will error if the inheritance chain can't be built, such as adding a child
431    /// template without the parent one.
432    /// If you want to add several file, use [Tera::add_template_files](struct.Tera.html#method.add_template_files)
433    ///
434    /// ```rust,ignore
435    /// // Use path as name
436    /// tera.add_template_file(path, None);
437    /// // Rename
438    /// tera.add_template_file(path, Some("index");
439    /// ```
440    pub fn add_template_file<P: AsRef<Path>>(&mut self, path: P, name: Option<&str>) -> Result<()> {
441        self.add_file(name, path)?;
442        self.build_inheritance_chains()?;
443        self.check_macro_files()?;
444        Ok(())
445    }
446
447    /// Add several templates from paths to the Tera instance. The default name for the template is
448    /// the path given, but this can be renamed with the second parameter of the tuple
449    ///
450    /// This will error if the inheritance chain can't be built, such as adding a child
451    /// template without the parent one.
452    ///
453    /// ```rust,ignore
454    /// tera.add_template_files(vec![
455    ///     (path1, None), // this template will have the value of path1 as name
456    ///     (path2, Some("hey")), // this template will have `hey` as name
457    /// ]);
458    /// ```
459    pub fn add_template_files<P: AsRef<Path>>(
460        &mut self,
461        files: Vec<(P, Option<&str>)>,
462    ) -> Result<()> {
463        for (path, name) in files {
464            self.add_file(name, path)?;
465        }
466        self.build_inheritance_chains()?;
467        self.check_macro_files()?;
468        Ok(())
469    }
470
471    #[doc(hidden)]
472    #[inline]
473    pub fn get_filter(&self, filter_name: &str) -> Result<&dyn Filter> {
474        match self.filters.get(filter_name) {
475            Some(fil) => Ok(&**fil),
476            None => Err(Error::filter_not_found(filter_name)),
477        }
478    }
479
480    /// Register a filter with Tera.
481    ///
482    /// If a filter with that name already exists, it will be overwritten
483    ///
484    /// ```rust,ignore
485    /// tera.register_filter("upper", &make_filter_fn(string::upper);
486    /// ```
487    pub fn register_filter<F: Filter + 'static>(&mut self, name: &str, filter: F) {
488        self.filters.insert(name.to_string(), Arc::new(filter));
489    }
490
491    #[doc(hidden)]
492    #[inline]
493    pub fn get_tester(&self, tester_name: &str) -> Result<&dyn Test> {
494        match self.testers.get(tester_name) {
495            Some(t) => Ok(&**t),
496            None => Err(Error::test_not_found(tester_name)),
497        }
498    }
499
500    /// Register a tester with Tera.
501    ///
502    /// If a tester with that name already exists, it will be overwritten
503    ///
504    /// ```rust,ignore
505    /// tera.register_tester("odd", testers::odd);
506    /// ```
507    pub fn register_tester<T: Test + 'static>(&mut self, name: &str, tester: T) {
508        self.testers.insert(name.to_string(), Arc::new(tester));
509    }
510
511    #[doc(hidden)]
512    #[inline]
513    pub fn get_function(&self, fn_name: &str) -> Result<&dyn Function> {
514        match self.functions.get(fn_name) {
515            Some(t) => Ok(&**t),
516            None => Err(Error::function_not_found(fn_name)),
517        }
518    }
519
520    /// Register a function with Tera.
521    ///
522    /// If a function with that name already exists, it will be overwritten
523    ///
524    /// ```rust,ignore
525    /// tera.register_function("range", range);
526    /// ```
527    pub fn register_function<F: Function + 'static>(&mut self, name: &str, function: F) {
528        self.functions.insert(name.to_string(), Arc::new(function));
529    }
530
531    fn register_tera_filters(&mut self) {
532        self.register_filter("upper", string::upper);
533        self.register_filter("lower", string::lower);
534        self.register_filter("trim", string::trim);
535        self.register_filter("truncate", string::truncate);
536        self.register_filter("wordcount", string::wordcount);
537        self.register_filter("replace", string::replace);
538        self.register_filter("capitalize", string::capitalize);
539        self.register_filter("title", string::title);
540        self.register_filter("striptags", string::striptags);
541        self.register_filter("urlencode", string::urlencode);
542        self.register_filter("escape", string::escape_html);
543        self.register_filter("slugify", string::slugify);
544        self.register_filter("addslashes", string::addslashes);
545        self.register_filter("split", string::split);
546
547        self.register_filter("first", array::first);
548        self.register_filter("last", array::last);
549        self.register_filter("nth", array::nth);
550        self.register_filter("join", array::join);
551        self.register_filter("sort", array::sort);
552        self.register_filter("slice", array::slice);
553        self.register_filter("group_by", array::group_by);
554        self.register_filter("filter", array::filter);
555        self.register_filter("concat", array::concat);
556
557        self.register_filter("pluralize", number::pluralize);
558        self.register_filter("round", number::round);
559        self.register_filter("filesizeformat", number::filesizeformat);
560
561        self.register_filter("length", common::length);
562        self.register_filter("reverse", common::reverse);
563        self.register_filter("date", common::date);
564        self.register_filter("json_encode", common::json_encode);
565        self.register_filter("as_str", common::as_str);
566
567        self.register_filter("get", object::get);
568    }
569
570    fn register_tera_testers(&mut self) {
571        self.register_tester("defined", testers::defined);
572        self.register_tester("undefined", testers::undefined);
573        self.register_tester("odd", testers::odd);
574        self.register_tester("even", testers::even);
575        self.register_tester("string", testers::string);
576        self.register_tester("number", testers::number);
577        self.register_tester("divisibleby", testers::divisible_by);
578        self.register_tester("iterable", testers::iterable);
579        self.register_tester("starting_with", testers::starting_with);
580        self.register_tester("ending_with", testers::ending_with);
581        self.register_tester("containing", testers::containing);
582        self.register_tester("matching", testers::matching);
583    }
584
585    fn register_tera_functions(&mut self) {
586        self.register_function("range", functions::range);
587        self.register_function("now", functions::now);
588        self.register_function("throw", functions::throw);
589    }
590
591    /// Select which suffix(es) to automatically do HTML escaping on,
592    ///`[".html", ".htm", ".xml"]` by default.
593    ///
594    /// Only call this function if you wish to change the defaults.
595    ///
596    ///
597    ///```ignore
598    /// // escape only files ending with `.php.html`
599    /// tera.autoescape_on(vec![".php.html"]);
600    /// // disable autoescaping completely
601    /// tera.autoescape_on(vec![]);
602    ///```
603    pub fn autoescape_on(&mut self, suffixes: Vec<&'static str>) {
604        self.autoescape_suffixes = suffixes;
605    }
606
607    #[doc(hidden)]
608    #[inline]
609    pub fn get_escape_fn(&self) -> &EscapeFn {
610        &self.escape_fn
611    }
612
613    /// Set user-defined function which applied to a rendered content.
614    ///
615    ///```rust,ignore
616    /// fn escape_c_string(input: &str) -> String { ... }
617    ///
618    /// // make valid C string literal
619    /// tera.set_escape_fn(escape_c_string);
620    /// tera.add_raw_template("foo", "\"{{ content }}\"").unwrap();
621    /// tera.autoescape_on(vec!["foo"]);
622    /// let mut context = Context::new();
623    /// context.insert("content", &"Hello\n\'world\"!");
624    /// let result = tera.render("foo", context).unwrap();
625    /// assert_eq!(result, r#""Hello\n\'world\"!""#);
626    ///```
627    pub fn set_escape_fn(&mut self, function: EscapeFn) {
628        self.escape_fn = function;
629    }
630
631    /// Reset escape function to default `tera::escape_html`.
632    pub fn reset_escape_fn(&mut self) {
633        self.escape_fn = escape_html;
634    }
635
636    /// Re-parse all templates found in the glob given to Tera
637    /// Use this when you are watching a directory and want to reload everything,
638    /// for example when a file is added.
639    ///
640    /// If you are adding templates without using a glob, we can't know when a template
641    /// is deleted, which would result in an error if we are trying to reload that file
642    pub fn full_reload(&mut self) -> Result<()> {
643        if self.glob.is_some() {
644            self.load_from_glob()?;
645        } else {
646            return Err(Error::msg("Reloading is only available if you are using a glob"));
647        }
648
649        self.build_inheritance_chains()?;
650        self.check_macro_files()
651    }
652
653    /// Use that method when you want to add a given Tera instance templates/filters/testers
654    /// to your own. If a template/filter/tester with the same name already exists in your instance,
655    /// it will not be overwritten.
656    ///
657    ///```rust,ignore
658    /// // add all the templates from FRAMEWORK_TERA
659    /// // except the ones that have an identical name to the ones in `my_tera`
660    /// my_tera.extend(&FRAMEWORK_TERA);
661    ///```
662    pub fn extend(&mut self, other: &Tera) -> Result<()> {
663        for (name, template) in &other.templates {
664            if !self.templates.contains_key(name) {
665                let mut tpl = template.clone();
666                tpl.from_extend = true;
667                self.templates.insert(name.to_string(), tpl);
668            }
669        }
670
671        for (name, filter) in &other.filters {
672            if !self.filters.contains_key(name) {
673                self.filters.insert(name.to_string(), filter.clone());
674            }
675        }
676
677        for (name, tester) in &other.testers {
678            if !self.testers.contains_key(name) {
679                self.testers.insert(name.to_string(), tester.clone());
680            }
681        }
682
683        self.build_inheritance_chains()?;
684        self.check_macro_files()
685    }
686}
687
688impl Default for Tera {
689    fn default() -> Tera {
690        let mut tera = Tera {
691            glob: None,
692            templates: HashMap::new(),
693            filters: HashMap::new(),
694            testers: HashMap::new(),
695            functions: HashMap::new(),
696            autoescape_suffixes: vec![".html", ".htm", ".xml"],
697            escape_fn: escape_html,
698        };
699
700        tera.register_tera_filters();
701        tera.register_tera_testers();
702        tera.register_tera_functions();
703        tera
704    }
705}
706
707// Needs a manual implementation since borrows in Fn's don't implement Debug.
708impl fmt::Debug for Tera {
709    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
710        write!(f, "Tera {{")?;
711        writeln!(f, "\n\ttemplates: [")?;
712
713        for template in self.templates.keys() {
714            writeln!(f, "\t\t{},", template)?;
715        }
716        write!(f, "\t]")?;
717        writeln!(f, "\n\tfilters: [")?;
718
719        for filter in self.filters.keys() {
720            writeln!(f, "\t\t{},", filter)?;
721        }
722        write!(f, "\t]")?;
723        writeln!(f, "\n\ttesters: [")?;
724
725        for tester in self.testers.keys() {
726            writeln!(f, "\t\t{},", tester)?;
727        }
728        writeln!(f, "\t]")?;
729
730        writeln!(f, "}}")
731    }
732}
733
734#[cfg(test)]
735mod tests {
736    use tempfile::tempdir;
737
738    use std::collections::HashMap;
739    use std::fs::File;
740
741    use super::Tera;
742    use crate::context::Context;
743    use serde_json::{Map as JsonObject, Value as JsonValue};
744
745    #[test]
746    fn test_get_inheritance_chain() {
747        let mut tera = Tera::default();
748        tera.add_raw_templates(vec![
749            ("a", "{% extends \"b\" %}"),
750            ("b", "{% extends \"c\" %}"),
751            ("c", "{% extends \"d\" %}"),
752            ("d", ""),
753        ])
754        .unwrap();
755
756        assert_eq!(
757            tera.get_template("a").unwrap().parents,
758            vec!["b".to_string(), "c".to_string(), "d".to_string()]
759        );
760
761        assert_eq!(tera.get_template("b").unwrap().parents, vec!["c".to_string(), "d".to_string()]);
762
763        assert_eq!(tera.get_template("c").unwrap().parents, vec!["d".to_string()]);
764
765        assert_eq!(tera.get_template("d").unwrap().parents.len(), 0);
766    }
767
768    #[test]
769    fn test_missing_parent_template() {
770        let mut tera = Tera::default();
771        assert_eq!(
772            tera.add_raw_template("a", "{% extends \"b\" %}").unwrap_err().to_string(),
773            "Template \'a\' is inheriting from \'b\', which doesn\'t exist or isn\'t loaded."
774        );
775    }
776
777    #[test]
778    fn test_circular_extends() {
779        let mut tera = Tera::default();
780        let err = tera
781            .add_raw_templates(vec![("a", "{% extends \"b\" %}"), ("b", "{% extends \"a\" %}")])
782            .unwrap_err();
783
784        assert!(err.to_string().contains("Circular extend detected for template"));
785    }
786
787    #[test]
788    fn test_get_parent_blocks_definition() {
789        let mut tera = Tera::default();
790        tera.add_raw_templates(vec![
791            (
792                "grandparent",
793                "{% block hey %}hello{% endblock hey %} {% block ending %}sincerely{% endblock ending %}",
794            ),
795            (
796                "parent",
797                "{% extends \"grandparent\" %}{% block hey %}hi and grandma says {{ super() }}{% endblock hey %}",
798            ),
799            (
800                "child",
801                "{% extends \"parent\" %}{% block hey %}dad says {{ super() }}{% endblock hey %}{% block ending %}{{ super() }} with love{% endblock ending %}",
802            ),
803        ]).unwrap();
804
805        let hey_definitions =
806            tera.get_template("child").unwrap().blocks_definitions.get("hey").unwrap();
807        assert_eq!(hey_definitions.len(), 3);
808
809        let ending_definitions =
810            tera.get_template("child").unwrap().blocks_definitions.get("ending").unwrap();
811        assert_eq!(ending_definitions.len(), 2);
812    }
813
814    #[test]
815    fn test_get_parent_blocks_definition_nested_block() {
816        let mut tera = Tera::default();
817        tera.add_raw_templates(vec![
818            ("grandparent", "{% block hey %}hello{% endblock hey %}"),
819            (
820                "parent",
821                "{% extends \"grandparent\" %}{% block hey %}hi and grandma says {{ super() }} {% block ending %}sincerely{% endblock ending %}{% endblock hey %}",
822            ),
823            (
824                "child",
825                "{% extends \"parent\" %}{% block hey %}dad says {{ super() }}{% endblock hey %}{% block ending %}{{ super() }} with love{% endblock ending %}",
826            ),
827        ]).unwrap();
828
829        let hey_definitions =
830            tera.get_template("child").unwrap().blocks_definitions.get("hey").unwrap();
831        assert_eq!(hey_definitions.len(), 3);
832
833        let ending_definitions =
834            tera.get_template("parent").unwrap().blocks_definitions.get("ending").unwrap();
835        assert_eq!(ending_definitions.len(), 1);
836    }
837
838    #[test]
839    fn test_can_autoescape_one_off_template() {
840        let mut context = Context::new();
841        context.insert("greeting", &"<p>");
842        let result = Tera::one_off("{{ greeting }} world", context, true).unwrap();
843
844        assert_eq!(result, "&lt;p&gt; world");
845    }
846
847    #[test]
848    fn test_can_disable_autoescape_one_off_template() {
849        let mut context = Context::new();
850        context.insert("greeting", &"<p>");
851        let result = Tera::one_off("{{ greeting }} world", context, false).unwrap();
852
853        assert_eq!(result, "<p> world");
854    }
855
856    #[test]
857    fn test_set_escape_function() {
858        let escape_c_string: super::EscapeFn = |input| {
859            let mut output = String::with_capacity(input.len() * 2);
860            for c in input.chars() {
861                match c {
862                    '\'' => output.push_str("\\'"),
863                    '\"' => output.push_str("\\\""),
864                    '\\' => output.push_str("\\\\"),
865                    '\n' => output.push_str("\\n"),
866                    '\r' => output.push_str("\\r"),
867                    '\t' => output.push_str("\\t"),
868                    _ => output.push(c),
869                }
870            }
871            output
872        };
873        let mut tera = Tera::default();
874        tera.add_raw_template("foo", "\"{{ content }}\"").unwrap();
875        tera.autoescape_on(vec!["foo"]);
876        tera.set_escape_fn(escape_c_string);
877        let mut context = Context::new();
878        context.insert("content", &"Hello\n\'world\"!");
879        let result = tera.render("foo", context).unwrap();
880        assert_eq!(result, r#""Hello\n\'world\"!""#);
881    }
882
883    #[test]
884    fn test_reset_escape_function() {
885        let no_escape: super::EscapeFn = |input| input.to_string();
886        let mut tera = Tera::default();
887        tera.add_raw_template("foo", "{{ content }}").unwrap();
888        tera.autoescape_on(vec!["foo"]);
889        tera.set_escape_fn(no_escape);
890        tera.reset_escape_fn();
891        let mut context = Context::new();
892        context.insert("content", &"Hello\n\'world\"!");
893        let result = tera.render("foo", context).unwrap();
894        assert_eq!(result, "Hello\n&#x27;world&quot;!");
895    }
896
897    #[test]
898    fn test_value_one_off_template() {
899        let mut context = JsonObject::new();
900        context.insert("greeting".to_string(), JsonValue::String("Good morning".to_string()));
901        let result = Tera::one_off_value("{{ greeting }} world", &context, true).unwrap();
902
903        assert_eq!(result, "Good morning world");
904    }
905
906    #[test]
907    fn test_extend_no_overlap() {
908        let mut my_tera = Tera::default();
909        my_tera
910            .add_raw_templates(vec![
911                ("one", "{% block hey %}1{% endblock hey %}"),
912                ("two", "{% block hey %}2{% endblock hey %}"),
913                ("three", "{% block hey %}3{% endblock hey %}"),
914            ])
915            .unwrap();
916
917        let mut framework_tera = Tera::default();
918        framework_tera.add_raw_templates(vec![("four", "Framework X")]).unwrap();
919
920        my_tera.extend(&framework_tera).unwrap();
921        assert_eq!(my_tera.templates.len(), 4);
922        let result = my_tera.render("four", Context::default()).unwrap();
923        assert_eq!(result, "Framework X");
924    }
925
926    #[test]
927    fn test_extend_with_overlap() {
928        let mut my_tera = Tera::default();
929        my_tera
930            .add_raw_templates(vec![
931                ("one", "MINE"),
932                ("two", "{% block hey %}2{% endblock hey %}"),
933                ("three", "{% block hey %}3{% endblock hey %}"),
934            ])
935            .unwrap();
936
937        let mut framework_tera = Tera::default();
938        framework_tera
939            .add_raw_templates(vec![("one", "FRAMEWORK"), ("four", "Framework X")])
940            .unwrap();
941
942        my_tera.extend(&framework_tera).unwrap();
943        assert_eq!(my_tera.templates.len(), 4);
944        let result = my_tera.render("one", Context::default()).unwrap();
945        assert_eq!(result, "MINE");
946    }
947
948    #[test]
949    fn test_extend_new_filter() {
950        let mut my_tera = Tera::default();
951        let mut framework_tera = Tera::default();
952        framework_tera.register_filter("hello", |_: &JsonValue, _: &HashMap<String, JsonValue>| {
953            Ok(JsonValue::Number(10.into()))
954        });
955        my_tera.extend(&framework_tera).unwrap();
956        assert!(my_tera.filters.contains_key("hello"));
957    }
958
959    #[test]
960    fn test_extend_new_tester() {
961        let mut my_tera = Tera::default();
962        let mut framework_tera = Tera::default();
963        framework_tera.register_tester("hello", |_: Option<&JsonValue>, _: &[JsonValue]| Ok(true));
964        my_tera.extend(&framework_tera).unwrap();
965        assert!(my_tera.testers.contains_key("hello"));
966    }
967
968    #[test]
969    fn can_load_from_glob() {
970        let tera = Tera::new("examples/basic/templates/**/*").unwrap();
971        assert!(tera.get_template("base.html").is_ok());
972    }
973
974    #[test]
975    fn can_load_from_glob_with_patterns() {
976        let tera = Tera::new("examples/basic/templates/**/*.{html, xml}").unwrap();
977        assert!(tera.get_template("base.html").is_ok());
978    }
979
980    #[test]
981    fn full_reload_with_glob() {
982        let mut tera = Tera::new("examples/basic/templates/**/*").unwrap();
983        tera.full_reload().unwrap();
984
985        assert!(tera.get_template("base.html").is_ok());
986    }
987
988    #[test]
989    fn full_reload_with_glob_after_extending() {
990        let mut tera = Tera::new("examples/basic/templates/**/*").unwrap();
991        let mut framework_tera = Tera::default();
992        framework_tera
993            .add_raw_templates(vec![("one", "FRAMEWORK"), ("four", "Framework X")])
994            .unwrap();
995        tera.extend(&framework_tera).unwrap();
996        tera.full_reload().unwrap();
997
998        assert!(tera.get_template("base.html").is_ok());
999        assert!(tera.get_template("one").is_ok());
1000    }
1001
1002    #[should_panic]
1003    #[test]
1004    fn test_can_only_parse_templates() {
1005        let mut tera = Tera::parse("examples/basic/templates/**/*").unwrap();
1006        for tpl in tera.templates.values_mut() {
1007            tpl.name = format!("a-theme/templates/{}", tpl.name);
1008            if let Some(ref parent) = tpl.parent.clone() {
1009                tpl.parent = Some(format!("a-theme/templates/{}", parent));
1010            }
1011        }
1012        // Will panic here as we changed the parent and it won't be able
1013        // to build the inheritance chain in this case
1014        tera.build_inheritance_chains().unwrap();
1015    }
1016
1017    // https://github.com/Keats/tera/issues/380
1018    #[test]
1019    fn glob_work_with_absolute_paths() {
1020        let tmp_dir = tempdir().expect("create temp dir");
1021        let cwd = tmp_dir.path().canonicalize().unwrap();
1022        File::create(cwd.join("hey.html")).expect("Failed to create a test file");
1023        File::create(cwd.join("ho.html")).expect("Failed to create a test file");
1024        let glob = cwd.join("*.html").into_os_string().into_string().unwrap();
1025        let tera = Tera::new(&glob).expect("Couldn't build Tera instance");
1026        assert_eq!(tera.templates.len(), 2);
1027    }
1028}