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