]> git.proxmox.com Git - rustc.git/blob - vendor/tera/src/template.rs
New upstream version 1.55.0+dfsg1
[rustc.git] / vendor / tera / src / template.rs
1 use std::collections::HashMap;
2
3 use crate::errors::{Error, Result};
4 use crate::parser::ast::{Block, MacroDefinition, Node};
5 use crate::parser::{parse, remove_whitespace};
6
7 /// This is the parsed equivalent of a template file.
8 /// It also does some pre-processing to ensure it does as little as possible at runtime
9 /// Not meant to be used directly.
10 #[derive(Debug, Clone)]
11 pub struct Template {
12 /// Name of the template, usually very similar to the path
13 pub name: String,
14 /// Original path of the file. A template doesn't necessarily have
15 /// a file associated with it though so it's optional.
16 pub path: Option<String>,
17 /// Parsed AST, after whitespace removal
18 pub ast: Vec<Node>,
19 /// Whether this template came from a call to `Tera::extend`, so we do
20 /// not remove it when we are doing a template reload
21 pub from_extend: bool,
22
23 /// Macros defined in that file: name -> definition ast
24 pub macros: HashMap<String, MacroDefinition>,
25 /// (filename, namespace) for the macros imported in that file
26 pub imported_macro_files: Vec<(String, String)>,
27
28 /// Only used during initial parsing. Rendering will use `self.parents`
29 pub parent: Option<String>,
30 /// Only used during initial parsing. Rendering will use `self.blocks_definitions`
31 pub blocks: HashMap<String, Block>,
32
33 // Below are filled when all templates have been parsed so we know the full hierarchy of templates
34 /// The full list of parent templates
35 pub parents: Vec<String>,
36 /// The definition of all the blocks for the current template and the definition of those blocks
37 /// in parent templates if there are some.
38 /// Needed for super() to work without having to find them each time.
39 /// The type corresponds to the following `block_name -> [(template name, definition)]`
40 /// The order of the Vec is from the first in hierarchy to the current template and the template
41 /// name is needed in order to load its macros if necessary.
42 pub blocks_definitions: HashMap<String, Vec<(String, Block)>>,
43 }
44
45 impl Template {
46 /// Parse the template string given
47 pub fn new(tpl_name: &str, tpl_path: Option<String>, input: &str) -> Result<Template> {
48 let ast = remove_whitespace(parse(input)?, None);
49
50 // First we want all the blocks used in that template
51 // This is recursive as we can have blocks inside blocks
52 let mut blocks = HashMap::new();
53 fn find_blocks(ast: &[Node], blocks: &mut HashMap<String, Block>) -> Result<()> {
54 for node in ast {
55 match *node {
56 Node::Block(_, ref block, _) => {
57 if blocks.contains_key(&block.name) {
58 return Err(Error::msg(format!(
59 "Block `{}` is duplicated",
60 block.name
61 )));
62 }
63
64 blocks.insert(block.name.to_string(), block.clone());
65 find_blocks(&block.body, blocks)?;
66 }
67 _ => continue,
68 };
69 }
70
71 Ok(())
72 }
73 find_blocks(&ast, &mut blocks)?;
74
75 // And now we find the potential parent and everything macro related (definition, import)
76 let mut macros = HashMap::new();
77 let mut imported_macro_files = vec![];
78 let mut parent = None;
79
80 for node in &ast {
81 match *node {
82 Node::Extends(_, ref name) => parent = Some(name.to_string()),
83 Node::MacroDefinition(_, ref macro_def, _) => {
84 if macros.contains_key(&macro_def.name) {
85 return Err(Error::msg(format!(
86 "Macro `{}` is duplicated",
87 macro_def.name
88 )));
89 }
90 macros.insert(macro_def.name.clone(), macro_def.clone());
91 }
92 Node::ImportMacro(_, ref tpl_name, ref namespace) => {
93 imported_macro_files.push((tpl_name.to_string(), namespace.to_string()));
94 }
95 _ => continue,
96 }
97 }
98
99 Ok(Template {
100 name: tpl_name.to_string(),
101 path: tpl_path,
102 ast,
103 parent,
104 blocks,
105 macros,
106 imported_macro_files,
107 parents: vec![],
108 blocks_definitions: HashMap::new(),
109 from_extend: false,
110 })
111 }
112 }
113
114 #[cfg(test)]
115 mod tests {
116 use super::Template;
117
118 #[test]
119 fn can_parse_ok_template() {
120 Template::new("hello", None, "Hello {{ world }}.").unwrap();
121 }
122
123 #[test]
124 fn can_find_parent_template() {
125 let tpl = Template::new("hello", None, "{% extends \"base.html\" %}").unwrap();
126
127 assert_eq!(tpl.parent.unwrap(), "base.html".to_string());
128 }
129
130 #[test]
131 fn can_find_blocks() {
132 let tpl = Template::new(
133 "hello",
134 None,
135 "{% extends \"base.html\" %}{% block hey %}{% endblock hey %}",
136 )
137 .unwrap();
138
139 assert_eq!(tpl.parent.unwrap(), "base.html".to_string());
140 assert_eq!(tpl.blocks.contains_key("hey"), true);
141 }
142
143 #[test]
144 fn can_find_nested_blocks() {
145 let tpl = Template::new(
146 "hello",
147 None,
148 "{% extends \"base.html\" %}{% block hey %}{% block extrahey %}{% endblock extrahey %}{% endblock hey %}",
149 ).unwrap();
150
151 assert_eq!(tpl.parent.unwrap(), "base.html".to_string());
152 assert_eq!(tpl.blocks.contains_key("hey"), true);
153 assert_eq!(tpl.blocks.contains_key("extrahey"), true);
154 }
155
156 #[test]
157 fn can_find_macros() {
158 let tpl = Template::new("hello", None, "{% macro hey() %}{% endmacro hey %}").unwrap();
159 assert_eq!(tpl.macros.contains_key("hey"), true);
160 }
161
162 #[test]
163 fn can_find_imported_macros() {
164 let tpl = Template::new("hello", None, "{% import \"macros.html\" as macros %}").unwrap();
165 assert_eq!(
166 tpl.imported_macro_files,
167 vec![("macros.html".to_string(), "macros".to_string())]
168 );
169 }
170 }