use crate::delimiters::Delimiters;
use crate::errors::{Error, ErrorKind, TeraResult};
use crate::parsing::ast::ComponentDefinition;
use crate::parsing::{Chunk, Compiler};
use crate::tera::Tera;
use crate::utils::Span;
use crate::{HashMap, Parser};
use std::collections::HashSet;
#[derive(Debug, PartialEq, Clone)]
pub struct Template {
pub name: String,
pub(crate) source: String,
pub(crate) path: Option<String>,
pub(crate) chunk: Chunk,
pub(crate) blocks: HashMap<String, Chunk>,
pub(crate) block_name_spans: HashMap<String, Span>,
pub(crate) components: HashMap<String, (ComponentDefinition, Chunk)>,
pub(crate) component_calls: HashMap<String, Vec<Span>>,
pub(crate) filter_calls: HashMap<String, Vec<Span>>,
pub(crate) test_calls: HashMap<String, Vec<Span>>,
pub(crate) function_calls: HashMap<String, Vec<Span>>,
pub(crate) include_calls: HashMap<String, Vec<Span>>,
pub(crate) raw_content_num_bytes: usize,
pub(crate) parents: Vec<String>,
pub(crate) block_lineage: HashMap<String, Vec<Chunk>>,
pub(crate) autoescape_enabled: bool,
pub(crate) top_level_variables: HashSet<String>,
}
impl Template {
pub(crate) fn new(
tpl_name: &str,
source: &str,
path: Option<String>,
delimiters: Delimiters,
) -> TeraResult<Self> {
let parser = Parser::new(source, delimiters);
let parser_output = match parser.parse() {
Ok(p) => p,
Err(e) => match e.kind {
ErrorKind::SyntaxError(mut s) => {
s.set_source(tpl_name, source);
return Err(Error {
kind: ErrorKind::SyntaxError(s),
source: None,
});
}
_ => unreachable!("Parser got something other than a SyntaxError: {e}"),
},
};
let parents = if let Some(p) = parser_output.parent {
vec![p]
} else {
vec![]
};
let mut body_compiler = Compiler::new(tpl_name);
body_compiler.compile(parser_output.nodes);
let mut chunk = body_compiler.chunk;
chunk.optimize();
let blocks: HashMap<String, Chunk> = body_compiler
.blocks
.into_iter()
.map(|(name, mut chunk)| {
chunk.optimize();
(name, chunk)
})
.collect();
let raw_content_num_bytes = body_compiler.raw_content_num_bytes;
let mut filter_calls = body_compiler.filter_calls;
let mut test_calls = body_compiler.test_calls;
let mut function_calls = body_compiler.function_calls;
let mut include_calls = body_compiler.include_calls;
let top_level_variables = body_compiler.top_level_variables;
let components = parser_output
.component_definitions
.into_iter()
.map(|c| {
let mut compiler = Compiler::new(tpl_name);
compiler.compile(c.body.clone());
for (name, spans) in compiler.filter_calls {
filter_calls.entry(name).or_default().extend(spans);
}
for (name, spans) in compiler.test_calls {
test_calls.entry(name).or_default().extend(spans);
}
for (name, spans) in compiler.function_calls {
function_calls.entry(name).or_default().extend(spans);
}
for (name, spans) in compiler.include_calls {
include_calls.entry(name).or_default().extend(spans);
}
let mut chunk = compiler.chunk;
chunk.optimize();
(c.name.clone(), (c, chunk))
})
.collect();
let component_calls = body_compiler.component_calls;
let block_name_spans = body_compiler.block_name_spans;
Ok(Self {
name: tpl_name.to_string(),
source: source.to_string(),
path,
blocks,
block_name_spans,
raw_content_num_bytes,
chunk,
parents,
components,
component_calls,
filter_calls,
test_calls,
function_calls,
include_calls,
top_level_variables,
block_lineage: HashMap::new(),
autoescape_enabled: true,
})
}
pub(crate) fn size_hint(&self) -> usize {
(self.raw_content_num_bytes * 2).next_power_of_two()
}
}
pub(crate) fn check_include_cycles(tera: &Tera, start: &Template) -> Result<(), Error> {
let mut stack: Vec<String> = vec![start.name.clone()];
fn walk(tera: &Tera, current: &Template, stack: &mut Vec<String>) -> Result<(), Error> {
let mut names: Vec<&String> = current.include_calls.keys().collect();
names.sort();
for include_name in names {
let Some(resolved) = tera.resolve_template_name(include_name) else {
continue;
};
if stack.iter().any(|s| s == resolved) {
let mut chain = stack.clone();
chain.push(resolved.to_string());
return Err(Error::circular_include(resolved, chain));
}
stack.push(resolved.to_string());
walk(tera, &tera.templates[resolved], stack)?;
stack.pop();
}
Ok(())
}
walk(tera, start, &mut stack)
}
pub(crate) fn find_parents(
tera: &Tera,
start: &Template,
template: &Template,
mut parents: Vec<String>,
) -> Result<Vec<String>, Error> {
if !parents.is_empty() && start.name == template.name {
return Err(Error::circular_extend(&start.name, parents));
}
match template.parents.last() {
Some(ref p) => match tera.resolve_template_name(p) {
Some(resolved) => {
let parent = &tera.templates[resolved];
parents.push(parent.name.clone());
find_parents(tera, start, parent, parents)
}
None => Err(Error::missing_parent(&template.name, p)),
},
None => {
parents.reverse();
Ok(parents)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn can_find_parents() {
let mut tera = Tera::default();
tera.add_raw_templates(vec![
("a", ""),
("b", "{% extends 'a' %}"),
("c", "{% extends 'b' %}"),
])
.unwrap();
let parents_a =
find_parents(&tera, &tera.templates["a"], &tera.templates["a"], vec![]).unwrap();
assert!(parents_a.is_empty());
let parents_b =
find_parents(&tera, &tera.templates["b"], &tera.templates["b"], vec![]).unwrap();
assert_eq!(parents_b, vec!["a".to_string()]);
let parents_c =
find_parents(&tera, &tera.templates["c"], &tera.templates["c"], vec![]).unwrap();
assert_eq!(parents_c, vec!["a".to_string(), "b".to_string()]);
}
#[test]
fn nested_blocks_are_tracked() {
let tpl = Template::new(
"mid",
r#"{% block hey %}hi {% block ending %}sincerely{% endblock ending %}{% endblock hey %}"#,
None,
Delimiters::default(),
)
.unwrap();
assert!(tpl.blocks.contains_key("hey"));
assert!(tpl.blocks.contains_key("ending"));
assert!(tpl.block_name_spans.contains_key("hey"));
assert!(
!tpl.block_name_spans.contains_key("ending"),
"nested blocks should not be in block_name_spans"
);
}
}