1 use std
::collections
::HashMap
;
3 use crate::errors
::{Error, Result}
;
4 use crate::parser
::ast
::{Block, MacroDefinition, Node}
;
5 use crate::parser
::{parse, remove_whitespace}
;
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)]
12 /// Name of the template, usually very similar to the path
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
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
,
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
)>,
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
>,
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
)>>,
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
);
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
<()> {
56 Node
::Block(_
, ref block
, _
) => {
57 if blocks
.contains_key(&block
.name
) {
58 return Err(Error
::msg(format
!(
59 "Block `{}` is duplicated",
64 blocks
.insert(block
.name
.to_string(), block
.clone());
65 find_blocks(&block
.body
, blocks
)?
;
73 find_blocks(&ast
, &mut blocks
)?
;
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
;
82 Node
::Extends(_
, ref name
) => parent
= Some(name
.to_string()),
83 Node
::MacroDefinition(_
, ref macro_def
, _
) => {
84 if macros
.contains_key(¯o_def
.name
) {
85 return Err(Error
::msg(format
!(
86 "Macro `{}` is duplicated",
90 macros
.insert(macro_def
.name
.clone(), macro_def
.clone());
92 Node
::ImportMacro(_
, ref tpl_name
, ref namespace
) => {
93 imported_macro_files
.push((tpl_name
.to_string(), namespace
.to_string()));
100 name
: tpl_name
.to_string(),
106 imported_macro_files
,
108 blocks_definitions
: HashMap
::new(),
119 fn can_parse_ok_template() {
120 Template
::new("hello", None
, "Hello {{ world }}.").unwrap();
124 fn can_find_parent_template() {
125 let tpl
= Template
::new("hello", None
, "{% extends \"base.html\" %}").unwrap();
127 assert_eq
!(tpl
.parent
.unwrap(), "base.html".to_string());
131 fn can_find_blocks() {
132 let tpl
= Template
::new(
135 "{% extends \"base.html\" %}{% block hey %}{% endblock hey %}",
139 assert_eq
!(tpl
.parent
.unwrap(), "base.html".to_string());
140 assert_eq
!(tpl
.blocks
.contains_key("hey"), true);
144 fn can_find_nested_blocks() {
145 let tpl
= Template
::new(
148 "{% extends \"base.html\" %}{% block hey %}{% block extrahey %}{% endblock extrahey %}{% endblock hey %}",
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);
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);
163 fn can_find_imported_macros() {
164 let tpl
= Template
::new("hello", None
, "{% import \"macros.html\" as macros %}").unwrap();
166 tpl
.imported_macro_files
,
167 vec
![("macros.html".to_string(), "macros".to_string())]