tera/renderer/
macros.rs

1use crate::errors::{Error, Result};
2use crate::parser::ast::MacroDefinition;
3use crate::template::Template;
4use crate::tera::Tera;
5use std::collections::HashMap;
6
7// Types around Macros get complicated, simplify it a bit by using aliases
8
9/// Maps { macro => macro_definition }
10pub type MacroDefinitionMap = HashMap<String, MacroDefinition>;
11/// Maps { namespace => ( macro_template, { macro => macro_definition }) }
12pub type MacroNamespaceMap<'a> = HashMap<&'a str, (&'a str, &'a MacroDefinitionMap)>;
13/// Maps { template => { namespace => ( macro_template, { macro => macro_definition }) }
14pub type MacroTemplateMap<'a> = HashMap<&'a str, MacroNamespaceMap<'a>>;
15
16/// Collection of all macro templates by file
17#[derive(Clone, Debug, Default)]
18pub struct MacroCollection<'a> {
19    macros: MacroTemplateMap<'a>,
20}
21
22impl<'a> MacroCollection<'a> {
23    pub fn from_original_template(tpl: &'a Template, tera: &'a Tera) -> MacroCollection<'a> {
24        let mut macro_collection = MacroCollection { macros: MacroTemplateMap::new() };
25
26        macro_collection
27            .add_macros_from_template(tera, tpl)
28            .expect("Couldn't load macros from base template");
29
30        macro_collection
31    }
32
33    /// Add macros from parsed template to `MacroCollection`
34    ///
35    /// Macro templates can import other macro templates so the macro loading needs to
36    /// happen recursively. We need all of the macros loaded in one go to be in the same
37    /// HashMap for easy popping as well, otherwise there could be stray macro
38    /// definitions remaining
39    pub fn add_macros_from_template(
40        &mut self,
41        tera: &'a Tera,
42        template: &'a Template,
43    ) -> Result<()> {
44        let template_name = &template.name[..];
45        if self.macros.contains_key(template_name) {
46            return Ok(());
47        }
48
49        let mut macro_namespace_map = MacroNamespaceMap::new();
50
51        if !template.macros.is_empty() {
52            macro_namespace_map.insert("self", (template_name, &template.macros));
53        }
54
55        for (filename, namespace) in &template.imported_macro_files {
56            let macro_tpl = tera.get_template(filename)?;
57            macro_namespace_map.insert(namespace, (filename, &macro_tpl.macros));
58            self.add_macros_from_template(tera, macro_tpl)?;
59
60            // We need to load the macros loaded in our macros in our namespace as well, unless we override it
61            for (namespace, m) in &self.macros[&macro_tpl.name.as_ref()].clone() {
62                if macro_namespace_map.contains_key(namespace) {
63                    continue;
64                }
65                // We inserted before so we're safe
66                macro_namespace_map.insert(namespace, *m);
67            }
68        }
69
70        self.macros.insert(template_name, macro_namespace_map);
71
72        for parent in &template.parents {
73            let parent = &parent[..];
74            let parent_template = tera.get_template(parent)?;
75            self.add_macros_from_template(tera, parent_template)?;
76
77            // We need to load the parent macros in our namespace as well, unless we override it
78            for (namespace, m) in &self.macros[parent].clone() {
79                if self.macros[template_name].contains_key(namespace) {
80                    continue;
81                }
82                // We inserted before so we're safe
83                self.macros.get_mut(template_name).unwrap().insert(namespace, *m);
84            }
85        }
86
87        Ok(())
88    }
89
90    pub fn lookup_macro(
91        &self,
92        template_name: &'a str,
93        macro_namespace: &'a str,
94        macro_name: &'a str,
95    ) -> Result<(&'a str, &'a MacroDefinition)> {
96        let namespace = self
97            .macros
98            .get(template_name)
99            .and_then(|namespace_map| namespace_map.get(macro_namespace));
100
101        if let Some(n) = namespace {
102            let &(macro_template, macro_definition_map) = n;
103
104            if let Some(m) = macro_definition_map.get(macro_name).map(|md| (macro_template, md)) {
105                Ok(m)
106            } else {
107                Err(Error::msg(format!(
108                    "Macro `{}::{}` not found in template `{}`",
109                    macro_namespace, macro_name, template_name
110                )))
111            }
112        } else {
113            Err(Error::msg(format!(
114                "Macro namespace `{}` was not found in template `{}`. Have you maybe forgotten to import it, or misspelled it?",
115                macro_namespace, template_name
116            )))
117        }
118    }
119}