Skip to main content

tera/
tera.rs

1use std::borrow::Cow;
2use std::collections::HashSet;
3use std::fmt;
4use std::fs::File;
5use std::io::{Read, Write};
6use std::path::Path;
7
8use crate::args::ArgFromValue;
9use crate::errors::{Error, ReportError, TeraResult};
10use crate::filters::{Filter, StoredFilter};
11use crate::functions::{Function, StoredFunction};
12use crate::template::{Template, check_include_cycles, find_parents};
13use crate::tests::{StoredTest, Test, TestResult};
14use crate::value::FunctionResult;
15use crate::value::Value;
16use crate::vm::interpreter::VirtualMachine;
17use crate::vm::state::State;
18use crate::{ComponentInfo, Context, HashMap, escape_html};
19
20use crate::delimiters::Delimiters;
21#[cfg(feature = "glob_fs")]
22use crate::globbing::load_from_glob;
23use crate::parsing::Chunk;
24use crate::parsing::ast::ComponentDefinition;
25
26/// Default template name used for `Tera::render_str` and `Tera::one_off`.
27const ONE_OFF_TEMPLATE_NAME: &str = "__tera_one_off";
28
29/// The escape function type definition
30pub type EscapeFn = fn(&str, &mut dyn Write) -> std::io::Result<()>;
31
32/// Main point of interaction in this library.
33///
34/// The [`Tera`] struct is the primary interface for working with the Tera template engine. It contains parsed templates, registered filters (which can filter
35/// data), functions, and testers. It also contains some configuration options, such as a list of
36/// suffixes for files that have autoescaping turned on.
37///
38/// It is responsible for:
39///
40/// - Loading and managing templates from files or strings
41/// - Parsing templates and checking for syntax errors
42/// - Maintaining a cache of compiled templates for efficient rendering
43/// - Providing an interface for rendering templates with given contexts
44/// - Managing template inheritance and includes
45/// - Handling custom filters and functions
46/// - Overriding settings, such as autoescape rules
47///
48/// # Example
49///
50/// Basic usage:
51///
52/// ```
53/// use tera::Tera;
54///
55/// let mut tera = Tera::default();
56/// tera.add_raw_template("hello", "Hello, {{ name }}!").unwrap();
57///
58/// // Prepare the context with some data
59/// let mut context = tera::Context::new();
60/// context.insert("name", "World");
61///
62/// // Render the template with the given context
63/// let rendered = tera.render("hello", &context).unwrap();
64/// assert_eq!(rendered, "Hello, World!");
65/// ```
66#[derive(Clone)]
67pub struct Tera {
68    /// The glob used to load templates if there was one.
69    /// Only used if the `glob_fs` feature is turned on
70    #[allow(dead_code)]
71    pub(crate) glob: Option<String>,
72    pub(crate) templates: HashMap<String, Template>,
73    /// Which extensions does Tera automatically autoescape on.
74    /// Defaults to [".html", ".htm", ".xml"]
75    pub(crate) autoescape_suffixes: Vec<Cow<'static, str>>,
76    #[doc(hidden)]
77    pub(crate) escape_fn: EscapeFn,
78    global_context: Context,
79    pub(crate) filters: HashMap<Cow<'static, str>, StoredFilter>,
80    pub(crate) tests: HashMap<Cow<'static, str>, StoredTest>,
81    pub(crate) functions: HashMap<Cow<'static, str>, StoredFunction>,
82    pub(crate) components: HashMap<String, (ComponentDefinition, Chunk)>,
83    /// Custom delimiters for template syntax
84    delimiters: Delimiters,
85    /// Fallback prefixes to try when a template is not found by exact name.
86    fallback_prefixes: Vec<Cow<'static, str>>,
87}
88
89impl Tera {
90    /// Create a new instance of Tera. Equivalent of `Tera::default()`.
91    pub fn new() -> Self {
92        Self::default()
93    }
94
95    /// Loads all the parsed templates found in the `dir` glob.
96    ///
97    /// A glob is a pattern for matching multiple file paths, employing special characters such as
98    /// the single asterisk (`*`) to match any sequence of characters within a single directory
99    /// level, and the double asterisk (`**`) to match any sequence of characters across multiple
100    /// directory levels, thereby providing a flexible and concise way to select files based on
101    /// their names, extensions, or hierarchical relationships. For example, the glob pattern
102    /// `templates/*.html` will match all files with the `.html` extension located directly inside
103    /// the `templates` folder, while the glob pattern `templates/**/*.html` will match all files
104    /// with the `.html` extension directly inside or in a subdirectory of `templates`.
105    ///
106    /// # Examples
107    ///
108    /// Basic usage:
109    ///
110    /// ```
111    /// # use tera::Tera;
112    /// let mut tera = Tera::default();
113    /// tera.load_from_glob("examples/basic/templates/**/*").unwrap();
114    /// ```
115    #[cfg(feature = "glob_fs")]
116    pub fn load_from_glob(&mut self, glob: &str) -> TeraResult<()> {
117        let prev_templates = std::mem::take(&mut self.templates);
118        let prev_glob = self.glob.replace(glob.to_string());
119
120        // we keep manually-added templates
121        self.templates = prev_templates
122            .iter()
123            .filter(|(_, tpl)| !tpl.from_glob)
124            .map(|(name, tpl)| (name.clone(), tpl.clone()))
125            .collect();
126
127        let result = match load_from_glob(glob) {
128            Ok(entries) => {
129                let mut errors = Vec::new();
130                for (path, name) in entries {
131                    match self.add_file(&path, Some(&name)) {
132                        Ok((key, _)) => {
133                            if let Some(tpl) = self.templates.get_mut(&key) {
134                                tpl.from_glob = true;
135                            }
136                        }
137                        Err(e) => errors.push(format!("Failed to load {}: {e}", path.display())),
138                    }
139                }
140                if !errors.is_empty() {
141                    Err(Error::message(errors.join("\n")))
142                } else {
143                    self.finalize_templates()
144                }
145            }
146            Err(e) => Err(e),
147        };
148
149        // Reset to what was there before
150        if result.is_err() {
151            self.templates = prev_templates;
152            self.glob = prev_glob;
153        }
154        result
155    }
156
157    /// Re-parse all templates found in the glob given to Tera.
158    ///
159    /// Use this when you are watching a directory and want to reload everything,
160    /// for example when a file is added.
161    ///
162    /// If you are adding templates without using a glob, we can't know when a template
163    /// is deleted, which would result in an error if we are trying to reload that file.
164    /// Templates added manually are preserved.
165    #[cfg(feature = "glob_fs")]
166    pub fn full_reload(&mut self) -> TeraResult<()> {
167        if let Some(glob) = self.glob.clone().as_ref() {
168            self.load_from_glob(glob)
169        } else {
170            Err(Error::message(
171                "Reloading is only available if you are using a glob",
172            ))
173        }
174    }
175
176    fn set_templates_auto_escape(&mut self) {
177        for (tpl_name, tpl) in self.templates.iter_mut() {
178            tpl.autoescape_enabled = self
179                .autoescape_suffixes
180                .iter()
181                .any(|s| tpl_name.ends_with(s.as_ref()));
182        }
183    }
184
185    /// Select which suffix(es) to automatically do HTML escaping on.
186    ///
187    /// By default, autoescaping is performed on `.html`, `.htm` and `.xml` template files. Only
188    /// call this function if you wish to change the defaults.
189    ///
190    /// # Examples
191    ///
192    /// Basic usage:
193    ///
194    /// ```
195    /// # use tera::Tera;
196    /// let mut tera = Tera::default();
197    /// // escape only files ending with `.php.html`
198    /// tera.autoescape_on([".php.html"]);
199    /// // disable autoescaping completely
200    /// tera.autoescape_on(Vec::<&str>::new());
201    /// ```
202    pub fn autoescape_on(
203        &mut self,
204        suffixes: impl IntoIterator<Item = impl Into<Cow<'static, str>>>,
205    ) {
206        self.autoescape_suffixes = suffixes.into_iter().map(Into::into).collect();
207        self.set_templates_auto_escape();
208    }
209
210    /// Set custom delimiters for template syntax.
211    ///
212    /// This must be called before adding any templates.
213    /// Returns an error if any delimiter is empty, if start delimiters conflict or if there are
214    /// already templates added to the Tera instance.
215    ///
216    /// # Example
217    /// ```
218    /// use tera::{Tera, Delimiters};
219    ///
220    /// let mut tera = Tera::new();
221    /// tera.set_delimiters(Delimiters {
222    ///     block_start: "<%".into(),
223    ///     block_end: "%>".into(),
224    ///     variable_start: "<<".into(),
225    ///     variable_end: ">>".into(),
226    ///     comment_start: "<#".into(),
227    ///     comment_end: "#>".into(),
228    /// }).unwrap();
229    /// tera.add_raw_template("example", "<< name >>").unwrap();
230    /// ```
231    pub fn set_delimiters(&mut self, delimiters: Delimiters) -> TeraResult<()> {
232        if !self.templates.is_empty() {
233            return Err(Error::message(
234                "Delimiters cannot be modified if templates have already been added",
235            ));
236        }
237        delimiters.validate()?;
238        self.delimiters = delimiters;
239        Ok(())
240    }
241
242    /// Set user-defined function that is used to escape content.
243    ///
244    /// Often times, arbitrary data needs to be injected into a template without allowing injection
245    /// attacks. For this reason, typically escaping is performed on all input. By default, the
246    /// escaping function will produce HTML escapes, but it can be overridden to produce escapes
247    /// more appropriate to the language being used.
248    ///
249    /// Inside templates, escaping can be turned off for specific content using the `safe` filter.
250    /// For example, the string `{{ data }}` inside a template will escape data, while `{{ data |
251    /// safe }}` will not.
252    ///
253    /// # Examples
254    ///
255    /// Basic usage:
256    ///
257    /// ```
258    /// # use tera::{Tera, Context};
259    /// # use std::io::Write;
260    /// // Create new Tera instance
261    /// let mut tera = Tera::default();
262    ///
263    /// // Override escape function to escape the letter A, why not
264    /// tera.set_escape_fn(|input: &str, output: &mut dyn Write| {
265    ///     for byte in input.bytes() {
266    ///         match byte {
267    ///             b'a' => output.write_all(b"?")?,
268    ///             _ => output.write_all(&[byte])?,
269    ///         }
270    ///     }
271    ///     Ok(())
272    /// });
273    ///
274    /// // Create template and enable autoescape
275    /// tera.add_raw_template("hello.js", "const data = \"{{ content }}\";").unwrap();
276    /// tera.autoescape_on(vec!["js"]);
277    ///
278    /// // Create context with some data
279    /// let mut context = Context::new();
280    /// context.insert("content", &r#"Hello tera"#);
281    ///
282    /// // Render template
283    /// let result = tera.render("hello.js", &context).unwrap();
284    /// assert_eq!(result, r#"const data = "Hello ter?";"#);
285    /// ```
286    pub fn set_escape_fn(&mut self, function: EscapeFn) {
287        self.escape_fn = function;
288    }
289
290    /// Reset escape function to default [`escape_html()`].
291    pub fn reset_escape_fn(&mut self) {
292        self.escape_fn = escape_html;
293    }
294
295    /// Register a filter with Tera.
296    ///
297    /// If a filter with that name already exists, it will be overwritten.
298    /// If your filter is returning a String, make sure you check [crate::StringInput] to handle
299    /// string safety correctly if relevant.
300    ///
301    /// ```
302    /// # use tera::{Tera, Kwargs, State};
303    /// let mut tera = Tera::default();
304    /// tera.register_filter("double", |x: i64, _: Kwargs, _: &State| x * 2);
305    /// ```
306    pub fn register_filter<Func, Arg, Res>(
307        &mut self,
308        name: impl Into<Cow<'static, str>>,
309        filter: Func,
310    ) where
311        Func: Filter<Arg, Res> + for<'a> Filter<<Arg as ArgFromValue<'a>>::Output, Res>,
312        Arg: for<'a> ArgFromValue<'a>,
313        Res: FunctionResult,
314    {
315        self.filters
316            .insert(name.into(), StoredFilter::new::<_, Arg, _>(filter));
317    }
318
319    /// Register a test with Tera.
320    ///
321    /// If a test with that name already exists, it will be overwritten
322    ///
323    /// ```
324    /// # use tera::{Tera, Kwargs, State};
325    /// let mut tera = Tera::default();
326    /// tera.register_test("odd", |x: i64, _: Kwargs, _: &State| x % 2 != 0);
327    /// ```
328    pub fn register_test<Func, Arg, Res>(&mut self, name: impl Into<Cow<'static, str>>, test: Func)
329    where
330        Func: Test<Arg, Res> + for<'a> Test<<Arg as ArgFromValue<'a>>::Output, Res>,
331        Arg: for<'a> ArgFromValue<'a>,
332        Res: TestResult,
333    {
334        self.tests
335            .insert(name.into(), StoredTest::new::<_, Arg, _>(test));
336    }
337
338    /// Register a function with Tera.
339    ///
340    /// If a function with that name already exists, it will be overwritten
341    pub fn register_function<Func, Res>(&mut self, name: impl Into<Cow<'static, str>>, func: Func)
342    where
343        Func: Function<Res>,
344        Res: FunctionResult,
345    {
346        self.functions
347            .insert(name.into(), StoredFunction::new(func));
348    }
349
350    /// Register filters, tests, and functions from another [`Tera`] instance.
351    ///
352    /// If a filter/test/function with the same name already exists in this instance,
353    /// it will not be overwritten.
354    pub fn register_from(&mut self, other: &Tera) {
355        for (name, filter) in &other.filters {
356            if !self.filters.contains_key(name) {
357                self.filters.insert(name.clone(), filter.clone());
358            }
359        }
360
361        for (name, test) in &other.tests {
362            if !self.tests.contains_key(name) {
363                self.tests.insert(name.clone(), test.clone());
364            }
365        }
366
367        for (name, function) in &other.functions {
368            if !self.functions.contains_key(name) {
369                self.functions.insert(name.clone(), function.clone());
370            }
371        }
372    }
373
374    /// Does a best-effort to find the top level variables that might be needed to be provided
375    /// to render the template.
376    ///
377    /// This doesn't do a full analysis and just reports all top level variables that were
378    /// found. It doesn't care about if statements etc.
379    pub fn get_template_variables(&self, template_name: &str) -> TeraResult<HashSet<&str>> {
380        let template = self.must_get_template(template_name)?;
381        let mut vars: HashSet<&str> = HashSet::new();
382        let mut visited_templates: HashSet<&str> = HashSet::new();
383        let mut templates_to_visit: Vec<&Template> = vec![template];
384
385        for parent_name in &template.parents {
386            let parent = self.must_get_template(parent_name)?;
387            templates_to_visit.push(parent);
388        }
389
390        while let Some(current_template) = templates_to_visit.pop() {
391            if !visited_templates.insert(current_template.name.as_str()) {
392                continue;
393            }
394
395            vars.extend(
396                current_template
397                    .top_level_variables
398                    .iter()
399                    .map(|s| s.as_str()),
400            );
401
402            for include_name in current_template.include_calls.keys() {
403                let included = self.must_get_template(include_name)?;
404                templates_to_visit.push(included);
405            }
406        }
407
408        Ok(vars)
409    }
410
411    /// Returns information about a registered component definition.
412    ///
413    /// Returns `None` if no component with the given name is found.
414    ///
415    /// # Examples
416    ///
417    /// ```
418    /// # use tera::Tera;
419    /// let mut tera = Tera::default();
420    /// tera.add_raw_template(
421    ///     "components.html",
422    ///     r#"{% component Button(label: String, variant="primary") %}<button>{{ label }}</button>{% endcomponent Button %}"#,
423    /// ).unwrap();
424    ///
425    /// let info = tera.get_component_definition("Button").unwrap();
426    /// assert_eq!(info.name(), "Button");
427    /// assert_eq!(info.args().len(), 2);
428    /// ```
429    pub fn get_component_definition(&self, name: &str) -> Option<ComponentInfo> {
430        self.components
431            .get(name)
432            .map(|(def, _)| ComponentInfo::from(def))
433    }
434
435    /// Lookups a component by name, returning whether it's found or not
436    /// Returns `false` if no component with the given name is found.
437    ///
438    /// # Examples
439    ///
440    /// ```
441    /// # use tera::Tera;
442    /// let mut tera = Tera::default();
443    /// tera.add_raw_template(
444    ///     "components.html",
445    ///     r#"{% component Button(label: String, variant="primary") %}<button>{{ label }}</button>{% endcomponent Button %}"#,
446    /// ).unwrap();
447    ///
448    /// assert!(tera.contains_component("Button"));
449    /// ```
450    pub fn contains_component(&self, component_name: &str) -> bool {
451        self.get_component_definition(component_name).is_some()
452    }
453
454    /// Returns an iterator over the names of all registered components in an
455    /// unspecified order.
456    ///
457    /// # Example
458    ///
459    /// ```rust
460    /// use tera::Tera;
461    ///
462    /// let mut tera = Tera::default();
463    /// tera.add_raw_template("foo", "{% component hello(name) %}{{ name }}{% endcomponent %}");
464    ///
465    /// let names: Vec<_> = tera.get_component_names().collect();
466    /// assert_eq!(names.len(), 1);
467    /// assert!(names.contains(&"hello"));
468    /// ```
469    pub fn get_component_names(&self) -> impl Iterator<Item = &str> {
470        self.components.keys().map(|s| s.as_str())
471    }
472
473    fn register_builtin_filters(&mut self) {
474        self.register_filter("safe", crate::filters::safe);
475        self.register_filter("default", crate::filters::default);
476        self.register_filter("upper", crate::filters::upper);
477        self.register_filter("lower", crate::filters::lower);
478        self.register_filter("wordcount", crate::filters::wordcount);
479        self.register_filter("escape", crate::filters::escape);
480        self.register_filter("escape_html", crate::filters::escape_html);
481        self.register_filter("escape_xml", crate::filters::escape_xml);
482        self.register_filter("newlines_to_br", crate::filters::newlines_to_br);
483        self.register_filter("pluralize", crate::filters::pluralize);
484        self.register_filter("trim", crate::filters::trim);
485        self.register_filter("trim_start", crate::filters::trim_start);
486        self.register_filter("trim_end", crate::filters::trim_end);
487        self.register_filter("replace", crate::filters::replace);
488        self.register_filter("capitalize", crate::filters::capitalize);
489        self.register_filter("title", crate::filters::title);
490        self.register_filter("truncate", crate::filters::truncate);
491        self.register_filter("indent", crate::filters::indent);
492        self.register_filter("str", crate::filters::as_str);
493        self.register_filter("int", crate::filters::int);
494        self.register_filter("float", crate::filters::float);
495        self.register_filter("length", crate::filters::length);
496        self.register_filter("reverse", crate::filters::reverse);
497        self.register_filter("split", crate::filters::split);
498        self.register_filter("abs", crate::filters::abs);
499        self.register_filter("round", crate::filters::round);
500        self.register_filter("first", crate::filters::first);
501        self.register_filter("last", crate::filters::last);
502        self.register_filter("nth", crate::filters::nth);
503        self.register_filter("join", crate::filters::join);
504        self.register_filter("sort", crate::filters::sort);
505        self.register_filter("unique", crate::filters::unique);
506        self.register_filter("get", crate::filters::get);
507        self.register_filter("values", crate::filters::values);
508        self.register_filter("keys", crate::filters::keys);
509        self.register_filter("pairs", crate::filters::pairs);
510        self.register_filter("group_by", crate::filters::group_by);
511    }
512
513    fn register_builtin_tests(&mut self) {
514        self.register_test("string", crate::tests::is_string);
515        self.register_test("number", crate::tests::is_number);
516        self.register_test("map", crate::tests::is_map);
517        self.register_test("bool", crate::tests::is_bool);
518        self.register_test("array", crate::tests::is_array);
519        self.register_test("integer", crate::tests::is_integer);
520        self.register_test("float", crate::tests::is_float);
521        self.register_test("none", crate::tests::is_none);
522        self.register_test("iterable", crate::tests::is_iterable);
523        self.register_test("defined", crate::tests::is_defined);
524        self.register_test("undefined", crate::tests::is_undefined);
525        self.register_test("odd", crate::tests::is_odd);
526        self.register_test("even", crate::tests::is_even);
527        self.register_test("divisible_by", crate::tests::is_divisible_by);
528        self.register_test("starting_with", crate::tests::is_starting_with);
529        self.register_test("ending_with", crate::tests::is_ending_with);
530        self.register_test("containing", crate::tests::is_containing);
531    }
532
533    fn register_builtin_functions(&mut self) {
534        self.register_function("range", crate::functions::range);
535        self.register_function("throw", crate::functions::throw);
536    }
537
538    /// Validates that all filters/tests/functions/components/includes referenced by a template exist.
539    /// Returns a vec of (source_position, error_report) for any missing references.
540    fn validate_template_references(
541        &self,
542        tpl: &Template,
543        is_known_component: impl Fn(&str) -> bool,
544    ) -> Vec<(usize, String)> {
545        let mut errors = Vec::new();
546
547        for (filter, spans) in &tpl.filter_calls {
548            if !self.filters.contains_key(filter.as_str()) {
549                for span in spans {
550                    let err = ReportError::new(
551                        format!("Unknown filter `{filter}`"),
552                        &tpl.name,
553                        &tpl.source,
554                        span,
555                    );
556                    errors.push((span.range.start, err.generate_report()));
557                }
558            }
559        }
560
561        for (test, spans) in &tpl.test_calls {
562            if !self.tests.contains_key(test.as_str()) {
563                for span in spans {
564                    let err = ReportError::new(
565                        format!("Unknown test `{test}`"),
566                        &tpl.name,
567                        &tpl.source,
568                        span,
569                    );
570                    errors.push((span.range.start, err.generate_report()));
571                }
572            }
573        }
574
575        for (func, spans) in &tpl.function_calls {
576            if func != "super" && !self.functions.contains_key(func.as_str()) {
577                for span in spans {
578                    let err = ReportError::new(
579                        format!("Unknown function `{func}`"),
580                        &tpl.name,
581                        &tpl.source,
582                        span,
583                    );
584                    errors.push((span.range.start, err.generate_report()));
585                }
586            }
587        }
588
589        for (component, spans) in &tpl.component_calls {
590            if !is_known_component(component.as_str()) {
591                for span in spans {
592                    let err = ReportError::new(
593                        format!("Unknown component `{component}`"),
594                        &tpl.name,
595                        &tpl.source,
596                        span,
597                    );
598                    errors.push((span.range.start, err.generate_report()));
599                }
600            }
601        }
602
603        for (include_name, spans) in &tpl.include_calls {
604            if self.resolve_template_name(include_name).is_none() {
605                for span in spans {
606                    let err = ReportError::new(
607                        format!("Unknown template `{include_name}`"),
608                        &tpl.name,
609                        &tpl.source,
610                        span,
611                    );
612                    errors.push((span.range.start, err.generate_report()));
613                }
614            }
615        }
616
617        errors
618    }
619
620    /// Optimizes the templates when possible and doing some light
621    /// checks like whether blocks/macros/templates all exist when they are used
622    fn finalize_templates(&mut self) -> TeraResult<()> {
623        let mut tpl_parents: HashMap<String, Vec<String>> =
624            HashMap::with_capacity(self.templates.len());
625        let mut tpl_size_hint: HashMap<String, usize> =
626            HashMap::with_capacity(self.templates.len());
627        // Track which template defined each component: component_name -> (tpl_name, priority)
628        let mut component_sources: HashMap<&str, (&str, usize)> = HashMap::new();
629
630        // 1st loop: find parents of each template and check for duplicate components
631        // Sort so error messages (circular include chains, etc.) are deterministic
632        let mut ordered_names: Vec<&String> = self.templates.keys().collect();
633        ordered_names.sort();
634        for name in ordered_names {
635            let tpl = &self.templates[name];
636            let parents = find_parents(self, tpl, tpl, vec![])?;
637            check_include_cycles(self, tpl)?;
638            for component_name in tpl.components.keys() {
639                let current_priority = self.get_template_priority(&tpl.name);
640
641                match component_sources.get(component_name.as_str()) {
642                    Some(&(existing_name, existing_priority)) => {
643                        if current_priority < existing_priority {
644                            // Current has higher priority (lower number), override
645                            component_sources.insert(component_name, (&tpl.name, current_priority));
646                        } else if current_priority > existing_priority {
647                            // Existing has higher priority, keep it
648                        } else {
649                            // Same priority = duplicate error
650                            let mut names = [existing_name, tpl.name.as_str()];
651                            names.sort_unstable();
652                            return Err(Error::message(format!(
653                                "Component `{component_name}` is defined in both `{}` and `{}`",
654                                names[0], names[1]
655                            )));
656                        }
657                    }
658                    None => {
659                        component_sources.insert(component_name, (&tpl.name, current_priority));
660                    }
661                }
662            }
663
664            // This will include the Tera expr etc but it's ok, it's just a hint
665            let mut size_hint = tpl.source.len();
666            for parent in &parents {
667                size_hint += self.templates[parent].source.len();
668            }
669
670            tpl_parents.insert(name.clone(), parents);
671            tpl_size_hint.insert(name.clone(), size_hint);
672        }
673
674        // Build components map from component_sources (needed for validation)
675        let components: HashMap<String, (ComponentDefinition, Chunk)> = component_sources
676            .iter()
677            .map(|(component_name, (tpl_name, _))| {
678                let tpl = &self.templates[*tpl_name];
679                let data = tpl.components[*component_name].clone();
680                (component_name.to_string(), data)
681            })
682            .collect();
683
684        // 2nd loop: we check whether all called components/filters/tests/functions are defined
685        // as well as finding each block lineage
686        let mut tpl_blocks: HashMap<String, HashMap<String, Vec<Chunk>>> =
687            HashMap::with_capacity(self.templates.len());
688        // Collect errors with their location for stable sorting
689        let mut errors: Vec<(&str, usize, String)> = Vec::new();
690
691        for (name, tpl) in &self.templates {
692            // Validate filter/test/function/component/include references
693            for (pos, report) in
694                self.validate_template_references(tpl, |c| components.contains_key(c))
695            {
696                errors.push((&tpl.name, pos, report));
697            }
698
699            // Check that blocks in child templates exist in at least one parent
700            let parents = &tpl_parents[name];
701            if !parents.is_empty() {
702                for (block_name, span) in &tpl.block_name_spans {
703                    let exists_in_parent = parents.iter().any(|parent_name| {
704                        self.templates
705                            .get(parent_name)
706                            .map(|p| p.blocks.contains_key(block_name))
707                            .unwrap_or(false)
708                    });
709                    if !exists_in_parent {
710                        let err = ReportError::new(
711                            format!("Block `{block_name}` is not defined in any parent template"),
712                            &tpl.name,
713                            &tpl.source,
714                            span,
715                        );
716                        errors.push((&tpl.name, span.range.start, err.generate_report()));
717                    }
718                }
719            }
720
721            let mut blocks = HashMap::with_capacity(tpl.blocks.len());
722            for (block_name, chunk) in &tpl.blocks {
723                let mut all_blocks = vec![chunk.clone()];
724                if chunk.is_calling_function("super") {
725                    for parent_tpl_name in tpl_parents[name].iter().rev() {
726                        let parent_tpl = self.must_get_template(parent_tpl_name)?;
727                        if let Some(parent_chunk) = parent_tpl.blocks.get(block_name) {
728                            all_blocks.push(parent_chunk.clone());
729                            if !parent_chunk.is_calling_function("super") {
730                                break;
731                            }
732                        }
733                    }
734                }
735                blocks.insert(block_name.clone(), all_blocks);
736            }
737            tpl_blocks.insert(name.clone(), blocks);
738        }
739
740        // Add inherited blocks from parents that aren't overridden in child templates
741        for (name, parents) in &tpl_parents {
742            for parent_name in parents.iter().rev() {
743                if let Some(parent_blocks) = tpl_blocks.get(parent_name).cloned() {
744                    let child_blocks = tpl_blocks.get_mut(name).unwrap();
745                    for (block_name, lineage) in parent_blocks {
746                        child_blocks.entry(block_name).or_insert(lineage);
747                    }
748                }
749            }
750        }
751
752        if !errors.is_empty() {
753            // Sort by template name, then by position in source
754            errors.sort_by(|a, b| a.0.cmp(b.0).then(a.1.cmp(&b.1)));
755            let reports: Vec<String> = errors.into_iter().map(|(_, _, report)| report).collect();
756            return Err(Error::message(reports.join("\n\n")));
757        }
758
759        // 3rd loop: we actually set everything we've done on the templates objects
760        for (name, tpl) in self.templates.iter_mut() {
761            tpl.total_content_num_bytes = tpl_size_hint.remove(name.as_str()).unwrap();
762            tpl.parents = tpl_parents.remove(name.as_str()).unwrap();
763            tpl.block_lineage = tpl_blocks.remove(name.as_str()).unwrap();
764        }
765
766        self.components = components;
767        self.set_templates_auto_escape();
768        Ok(())
769    }
770
771    /// Add a single template to the Tera instance.
772    ///
773    /// This will error if there are errors in the inheritance, such as adding a child
774    /// template without the parent one.
775    ///
776    /// # Bulk loading
777    ///
778    /// If you want to add several templates, use
779    /// [`add_raw_templates()`](Tera::add_raw_templates).
780    ///
781    /// # Examples
782    ///
783    /// Basic usage:
784    ///
785    /// ```
786    /// # use tera::Tera;
787    /// let mut tera = Tera::default();
788    /// tera.add_raw_template("new.html", "Blabla").unwrap();
789    /// ```
790    pub fn add_raw_template(&mut self, name: &str, content: &str) -> TeraResult<()> {
791        self.add_raw_templates(std::iter::once((name, content)))
792    }
793
794    /// Add all the templates given to the Tera instance
795    ///
796    /// This will error if there are errors in the inheritance, such as adding a child
797    /// template without the parent one.
798    ///
799    /// ```
800    /// # use tera::Tera;
801    /// let mut tera = Tera::default();
802    /// tera.add_raw_templates(vec![
803    ///     ("new.html", "blabla"),
804    ///     ("new2.html", "hello"),
805    /// ]).unwrap();
806    /// ```
807    pub fn add_raw_templates<I, N, C>(&mut self, templates: I) -> TeraResult<()>
808    where
809        I: IntoIterator<Item = (N, C)>,
810        N: AsRef<str>,
811        C: AsRef<str>,
812    {
813        let mut inserted: Vec<(String, Option<Template>)> = Vec::new();
814        let result = (|| -> TeraResult<()> {
815            for (name, content) in templates {
816                let template = Template::new(
817                    name.as_ref(),
818                    content.as_ref(),
819                    None,
820                    self.delimiters.clone(),
821                )?;
822                let key = name.as_ref().to_string();
823                let previous = self.templates.insert(key.clone(), template);
824                inserted.push((key, previous));
825            }
826            self.finalize_templates()
827        })();
828
829        if result.is_err() {
830            // Undo in reverse so duplicate names within the batch restore correctly.
831            for (key, previous) in inserted.into_iter().rev() {
832                match previous {
833                    Some(old) => {
834                        self.templates.insert(key, old);
835                    }
836                    None => {
837                        self.templates.remove(&key);
838                    }
839                }
840            }
841        }
842        result
843    }
844
845    /// Add a template from a path: reads the file and parses it.
846    /// This will return an error if the template is invalid and doesn't check the validity of
847    /// the new set of templates.
848    fn add_file<P: AsRef<Path>>(
849        &mut self,
850        path: P,
851        name: Option<&str>,
852    ) -> TeraResult<(String, Option<Template>)> {
853        let path = path.as_ref();
854        let path_str = path.to_str().ok_or_else(|| {
855            Error::message(format!("Template path is not valid UTF-8: {:?}", path))
856        })?;
857        let tpl_name = name.unwrap_or(path_str);
858
859        let mut f = File::open(path)
860            .map_err(|e| Error::chain(format!("Couldn't open template '{:?}'", path), e))?;
861
862        let mut content = String::new();
863        f.read_to_string(&mut content)
864            .map_err(|e| Error::chain(format!("Failed to read template '{:?}'", path), e))?;
865
866        let template = Template::new(
867            tpl_name,
868            &content,
869            Some(path_str.to_string()),
870            self.delimiters.clone(),
871        )?;
872
873        let key = tpl_name.to_string();
874        let previous = self.templates.insert(key.clone(), template);
875        Ok((key, previous))
876    }
877
878    /// Add a single template from a path to the Tera instance. The default name for the template is
879    /// the path given, but this can be renamed with the `name` parameter
880    ///
881    /// This will error if the inheritance chain can't be built, such as adding a child
882    /// template without the parent one.
883    /// If you want to add several file, use [Tera::add_template_files](struct.Tera.html#method.add_template_files)
884    ///
885    /// ```no_run
886    /// # use tera::Tera;
887    /// let mut tera = Tera::default();
888    /// // Rename template with custom name
889    /// tera.add_template_file("path/to/template.html", Some("template.html")).unwrap();
890    /// // Use path as name
891    /// tera.add_template_file("path/to/other.html", None).unwrap();
892    /// ```
893    pub fn add_template_file<P: AsRef<Path>>(
894        &mut self,
895        path: P,
896        name: Option<&str>,
897    ) -> TeraResult<()> {
898        self.add_template_files(std::iter::once((path, name)))
899    }
900
901    /// Add several templates from paths to the Tera instance.
902    ///
903    /// The default name for the template is the path given, but this can be renamed with the
904    /// second parameter of the tuple
905    ///
906    /// This will error if the inheritance chain can't be built, such as adding a child
907    /// template without the parent one.
908    ///
909    /// ```no_run
910    /// # use tera::Tera;
911    /// let mut tera = Tera::default();
912    /// tera.add_template_files(vec![
913    ///     ("./path/to/template.tera", None), // this template will have the value of path1 as name
914    ///     ("./path/to/other.tera", Some("hey")), // this template will have `hey` as name
915    /// ]);
916    /// ```
917    pub fn add_template_files<I, P, N>(&mut self, files: I) -> TeraResult<()>
918    where
919        I: IntoIterator<Item = (P, Option<N>)>,
920        P: AsRef<Path>,
921        N: AsRef<str>,
922    {
923        let mut inserted: Vec<(String, Option<Template>)> = Vec::new();
924        let result = (|| -> TeraResult<()> {
925            for (path, name) in files {
926                let (key, previous) = self.add_file(path, name.as_ref().map(AsRef::as_ref))?;
927                inserted.push((key, previous));
928            }
929            self.finalize_templates()
930        })();
931
932        if result.is_err() {
933            for (key, previous) in inserted.into_iter().rev() {
934                match previous {
935                    Some(old) => {
936                        self.templates.insert(key, old);
937                    }
938                    None => {
939                        self.templates.remove(&key);
940                    }
941                }
942            }
943        }
944        result
945    }
946
947    /// Set fallback prefixes to try when a template is not found by exact name. This needs to be
948    /// called before adding templates, it will error otherwise.
949    ///
950    /// When a template is requested (via render, extends, or include) and the exact name
951    /// is not found, these prefixes are tried in order. The first prefix that produces
952    /// a match is used.
953    ///
954    /// Prefixes should include any path separator (e.g., `"themes/cool/"` not `"themes/cool"`).
955    ///
956    /// # Example
957    ///
958    /// ```
959    /// # use tera::Tera;
960    /// let mut tera = Tera::default();
961    /// // Templates in "themes/cool/" can be referenced without the prefix
962    /// tera.set_fallback_prefixes(["themes/cool/"]).unwrap();
963    /// ```
964    pub fn set_fallback_prefixes(
965        &mut self,
966        prefixes: impl IntoIterator<Item = impl Into<Cow<'static, str>>>,
967    ) -> TeraResult<()> {
968        if !self.templates.is_empty() {
969            return Err(Error::message(
970                "set_fallback_prefixes must be called before adding templates",
971            ));
972        }
973        self.fallback_prefixes = prefixes.into_iter().map(Into::into).collect();
974        Ok(())
975    }
976
977    /// Returns the priority level for a template based on fallback_prefixes.
978    /// 0 = highest priority (no prefix match), higher numbers = lower priority.
979    fn get_template_priority(&self, name: &str) -> usize {
980        for (i, prefix) in self.fallback_prefixes.iter().enumerate() {
981            if name.starts_with(prefix.as_ref()) {
982                return i + 1;
983            }
984        }
985        0
986    }
987
988    /// Resolves a template name, trying exact match first, then fallback prefixes.
989    /// Returns the actual template name if found, or None.
990    pub(crate) fn resolve_template_name(&self, name: &str) -> Option<&str> {
991        if let Some((resolved, _)) = self.templates.get_key_value(name) {
992            return Some(resolved.as_str());
993        }
994        for prefix in &self.fallback_prefixes {
995            let prefixed = format!("{}{}", prefix, name);
996            if let Some((resolved, _)) = self.templates.get_key_value(&prefixed) {
997                return Some(resolved.as_str());
998            }
999        }
1000        None
1001    }
1002
1003    /// Get a template by name, resolving fallback prefixes if needed.
1004    pub(crate) fn get_template(&self, template_name: &str) -> Option<&Template> {
1005        self.resolve_template_name(template_name)
1006            .map(|resolved| &self.templates[resolved])
1007    }
1008
1009    /// Lookups a template by name, resolving fallback prefixes if needed, returning whether it's
1010    /// found or not
1011    pub fn contains_template(&self, template_name: &str) -> bool {
1012        self.resolve_template_name(template_name).is_some()
1013    }
1014
1015    /// Returns an iterator over the names of all registered templates in an
1016    /// unspecified order.
1017    ///
1018    /// # Example
1019    ///
1020    /// ```rust
1021    /// use tera::Tera;
1022    ///
1023    /// let mut tera = Tera::default();
1024    /// tera.add_raw_template("foo", "{{ hello }}");
1025    /// tera.add_raw_template("another-one.html", "contents go here");
1026    ///
1027    /// let names: Vec<_> = tera.get_template_names().collect();
1028    /// assert_eq!(names.len(), 2);
1029    /// assert!(names.contains(&"foo"));
1030    /// assert!(names.contains(&"another-one.html"));
1031    /// ```
1032    pub fn get_template_names(&self) -> impl Iterator<Item = &str> {
1033        self.templates.keys().map(|s| s.as_str())
1034    }
1035
1036    /// Get a template by name, returning an error if not found. Used internally.
1037    #[inline]
1038    pub(crate) fn must_get_template(&self, template_name: &str) -> TeraResult<&Template> {
1039        self.get_template(template_name)
1040            .ok_or_else(|| Error::template_not_found(template_name))
1041    }
1042
1043    /// Renders a Tera template given a [`Context`].
1044    ///
1045    /// # Examples
1046    ///
1047    /// Basic usage:
1048    ///
1049    /// ```
1050    /// # use tera::{Tera, Context};
1051    /// // Create new tera instance with sample template
1052    /// let mut tera = Tera::default();
1053    /// tera.add_raw_template("info", "My age is {{ age }}.");
1054    ///
1055    /// // Create new context
1056    /// let mut context = Context::new();
1057    /// context.insert("age", &18);
1058    ///
1059    /// // Render template using the context
1060    /// let output = tera.render("info", &context).unwrap();
1061    /// assert_eq!(output, "My age is 18.");
1062    /// ```
1063    ///
1064    /// To render a template with no context, simply pass a [`Context::new()`] object.
1065    ///
1066    /// ```
1067    /// # use tera::{Tera, Context};
1068    /// // Create new tera instance with demo template
1069    /// let mut tera = Tera::default();
1070    /// tera.add_raw_template("hello.html", "<h1>Hello</h1>");
1071    ///
1072    /// // Render a template with an empty context
1073    /// let output = tera.render("hello.html", &Context::new()).unwrap();
1074    /// assert_eq!(output, "<h1>Hello</h1>");
1075    /// ```
1076    pub fn render(&self, template_name: &str, context: &Context) -> TeraResult<String> {
1077        let template = self.must_get_template(template_name)?;
1078        let mut vm = VirtualMachine::new(self, template);
1079        vm.render(context, &self.global_context)
1080    }
1081
1082    /// Renders a Tera template given a [`Context`] to something that implements [`Write`].
1083    ///
1084    /// The only difference from [`render()`](Self::render) is that this version doesn't convert
1085    /// buffer to a String, allowing to render directly to anything that implements [`Write`]. For
1086    /// example, this could be used to write directly to a [`File`](std::fs::File).
1087    ///
1088    /// Any I/O error will be reported in the result.
1089    ///
1090    /// # Examples
1091    ///
1092    /// Rendering into a `Vec<u8>`:
1093    ///
1094    /// ```
1095    /// # use tera::{Context, Tera};
1096    /// let mut tera = Tera::default();
1097    /// tera.add_raw_template("index.html", "<p>{{ name }}</p>");
1098    ///
1099    /// // Rendering a template to an internal buffer
1100    /// let mut buffer = Vec::new();
1101    /// let mut context = Context::new();
1102    /// context.insert("name", "John Wick");
1103    /// tera.render_to("index.html", &context, &mut buffer).unwrap();
1104    /// assert_eq!(buffer, b"<p>John Wick</p>");
1105    /// ```
1106    pub fn render_to(
1107        &self,
1108        template_name: &str,
1109        context: &Context,
1110        write: impl Write,
1111    ) -> TeraResult<()> {
1112        let template = self.must_get_template(template_name)?;
1113        let mut vm = VirtualMachine::new(self, template);
1114        vm.render_to(None, context, &self.global_context, write)
1115    }
1116
1117    /// Returns the global context, allowing modifications to it
1118    ///
1119    /// The global context is automatically included into every template,
1120    /// which is useful for sharing common data.
1121    ///
1122    /// The global context is *not* passed if you call `render_component`.
1123    ///
1124    /// ```
1125    /// # use tera::{Tera, Context, context};
1126    /// let mut tera = Tera::new();
1127    /// tera.global_context().insert("name", "John Doe");
1128    ///
1129    /// let content = tera
1130    ///     .render_str("Hello, {{ name }}!", &Context::new(), false)
1131    ///     .unwrap();
1132    /// assert_eq!(content, "Hello, John Doe!".to_string());
1133    ///
1134    /// let content2 = tera
1135    ///     .render_str(
1136    ///         "UserID: {{ id }}, Username: {{ name }}",
1137    ///         &context! { id => &7489 },
1138    ///         false,
1139    ///     )
1140    ///     .unwrap();
1141    /// assert_eq!(content2, "UserID: 7489, Username: John Doe");
1142    /// ```
1143    pub fn global_context(&mut self) -> &mut Context {
1144        &mut self.global_context
1145    }
1146
1147    /// Renders a one-off template (for example a template coming from a user input)
1148    /// given a `Context` and using this Tera instance's filters, tests, functions and components.
1149    ///
1150    /// The only limitation is that it cannot use `{% extends %}` and therefore blocks.
1151    ///
1152    /// Any errors will mention the `__tera_one_off` template: this is the name
1153    /// given to the template by Tera.
1154    ///
1155    /// ```
1156    /// # use tera::{Tera, Context, context};
1157    /// let tera = Tera::new();
1158    /// let result = tera.render_str(
1159    ///     "Hello {{ name }}!",
1160    ///     &context! { name => "world" },
1161    ///     false,
1162    /// ).unwrap();
1163    /// assert_eq!(result, "Hello world!");
1164    /// ```
1165    pub fn render_str(
1166        &self,
1167        input: &str,
1168        context: &Context,
1169        autoescape: bool,
1170    ) -> TeraResult<String> {
1171        let mut output = Vec::new();
1172        self.render_str_to(input, context, autoescape, &mut output)?;
1173        Ok(String::from_utf8(output)?)
1174    }
1175
1176    /// Renders a one-off template to a writer.
1177    ///
1178    /// Same as [`render_str`](Self::render_str) but writes to a [`Write`] implementor.
1179    pub fn render_str_to(
1180        &self,
1181        input: &str,
1182        context: &Context,
1183        autoescape: bool,
1184        write: impl Write,
1185    ) -> TeraResult<()> {
1186        let mut template =
1187            Template::new(ONE_OFF_TEMPLATE_NAME, input, None, self.delimiters.clone())?;
1188
1189        if template.extends.is_some() {
1190            return Err(Error::message(
1191                "Template inheritance ({% extends %}) is not supported in render_str.",
1192            ));
1193        }
1194        if !template.blocks.is_empty() {
1195            return Err(Error::message("Blocks not supported in render_str."));
1196        }
1197
1198        template.autoescape_enabled = autoescape;
1199
1200        // Validate template references
1201        let errors = self.validate_template_references(&template, |c| {
1202            self.components.contains_key(c) || template.components.contains_key(c)
1203        });
1204        if !errors.is_empty() {
1205            let reports: Vec<String> = errors.into_iter().map(|(_, report)| report).collect();
1206            return Err(Error::message(reports.join("\n\n")));
1207        }
1208
1209        let mut vm = VirtualMachine::new(self, &template);
1210        vm.render_to(None, context, &self.global_context, write)
1211    }
1212
1213    /// Renders a one off template (for example a template coming from a user input) given a `Context`
1214    ///
1215    /// This creates a separate instance of Tera with no possibilities of adding custom filters
1216    /// or testers, parses the template and renders it immediately.
1217    /// Any errors will mention the `__tera_one_off` template: this is the name given to the template by
1218    /// Tera
1219    ///
1220    /// ```
1221    /// # use tera::{Context, Tera};
1222    /// let mut context = Context::new();
1223    /// context.insert("greeting", &"hello");
1224    /// let result = Tera::one_off("{{ greeting }} world", &context, true).unwrap();
1225    /// assert_eq!(result, "hello world");
1226    /// ```
1227    pub fn one_off(input: &str, context: &Context, autoescape: bool) -> TeraResult<String> {
1228        let tera = Tera::default();
1229        tera.render_str(input, context, autoescape)
1230    }
1231
1232    /// Renders a component by name with the given context and optional body content.
1233    ///
1234    /// The context should contain the component's arguments as key-value pairs.
1235    ///
1236    /// # Examples
1237    ///
1238    /// ```
1239    /// # use tera::{Tera, Context, context};
1240    /// let mut tera = Tera::default();
1241    /// tera.add_raw_template(
1242    ///     "components.html",
1243    ///     r#"{% component Button(label) %}<button>{{ label }}</button>{% endcomponent Button %}
1244    /// {% component Card(title) %}<div><h1>{{ title }}</h1>{{ body }}</div>{% endcomponent Card %}"#,
1245    /// ).unwrap();
1246    ///
1247    /// // Render a component with arguments
1248    /// let html = tera.render_component(
1249    ///     "Button",
1250    ///     &context! { label => "Click me" },
1251    ///     None,
1252    ///     true,
1253    /// ).unwrap();
1254    /// assert_eq!(html, "<button>Click me</button>");
1255    ///
1256    /// // Render a component with body content
1257    /// let html = tera.render_component(
1258    ///     "Card",
1259    ///     &context! { title => "My Card" },
1260    ///     Some("<p>Card content here</p>"),
1261    ///     true,
1262    /// ).unwrap();
1263    /// assert_eq!(html, "<div><h1>My Card</h1><p>Card content here</p></div>");
1264    /// ```
1265    pub fn render_component(
1266        &self,
1267        component_name: &str,
1268        context: &Context,
1269        body: Option<&str>,
1270        autoescape: bool,
1271    ) -> TeraResult<String> {
1272        let mut output = Vec::new();
1273        self.render_component_to(component_name, context, body, autoescape, &mut output)?;
1274        Ok(String::from_utf8(output)?)
1275    }
1276
1277    /// Renders a component by name to something that implements [`Write`].
1278    ///
1279    /// Same as [`render_component`](Self::render_component) but writes to a [`Write`] implementor
1280    /// instead of returning a String.
1281    ///
1282    /// # Examples
1283    ///
1284    /// ```
1285    /// # use tera::{Tera, Context, context};
1286    /// let mut tera = Tera::default();
1287    /// tera.add_raw_template(
1288    ///     "components.html",
1289    ///     r#"{% component Button(label) %}<button>{{ label }}</button>{% endcomponent Button %}"#,
1290    /// ).unwrap();
1291    ///
1292    /// let mut buffer = Vec::new();
1293    /// tera.render_component_to(
1294    ///     "Button",
1295    ///     &context! { label => "Click me" },
1296    ///     None,
1297    ///     true,
1298    ///     &mut buffer,
1299    /// ).unwrap();
1300    /// assert_eq!(buffer, b"<button>Click me</button>");
1301    /// ```
1302    pub fn render_component_to(
1303        &self,
1304        component_name: &str,
1305        context: &Context,
1306        body: Option<&str>,
1307        autoescape: bool,
1308        mut write: impl Write,
1309    ) -> TeraResult<()> {
1310        let (component_def, chunk) = self
1311            .components
1312            .get(component_name)
1313            .ok_or_else(|| Error::component_not_found(component_name))?;
1314
1315        // Get the source template, we'll need it for the VM
1316        let template = self
1317            .templates
1318            .get(&chunk.name)
1319            .expect("Component source template must exist");
1320
1321        // Build the component context by validating and applying defaults
1322        let body_value = body.map(Value::safe_string);
1323        let component_context = component_def
1324            .build_context(
1325                context.data.keys().map(|k| k.as_ref()),
1326                |key| context.get(key).cloned(),
1327                body_value,
1328            )
1329            .map_err(Error::message)?;
1330
1331        let vm = VirtualMachine::new_with_autoescape(self, template, autoescape);
1332        let mut state = State::new_with_chunk(self, &component_context, chunk, autoescape);
1333        vm.interpret(&mut state, &mut write)?;
1334
1335        Ok(())
1336    }
1337
1338    /// Renders a block by name with the given context.
1339    ///
1340    /// # Examples
1341    ///
1342    /// ```
1343    /// # use tera::{Tera, Context};
1344    /// // Create new tera instance with demo template
1345    /// let mut tera = Tera::default();
1346    /// tera.add_raw_template("hello.html", "<h1>Hello</h1>{% block content %}in block{% endblock %}");
1347    ///
1348    /// // Render a template with an empty context
1349    /// let output = tera.render_block("hello.html", "content", &Context::new()).unwrap();
1350    /// assert_eq!(output, "in block");
1351    /// ```
1352    pub fn render_block(
1353        &self,
1354        template_name: &str,
1355        block_name: &str,
1356        context: &Context,
1357    ) -> TeraResult<String> {
1358        let template = self.must_get_template(template_name)?;
1359        if !template.block_lineage.contains_key(block_name) {
1360            return Err(Error::message(format!(
1361                "Block `{block_name}` not found in template `{template_name}`",
1362            )));
1363        }
1364        let mut vm = VirtualMachine::new(self, template);
1365        vm.render_block(block_name, context, &self.global_context)
1366    }
1367
1368    /// Renders a block by name with the given context to something that implements [`Write`].
1369    ///
1370    /// # Examples
1371    ///
1372    /// ```
1373    /// # use tera::{Tera, Context};
1374    /// let mut tera = Tera::default();
1375    /// tera.add_raw_template("hello.html", "<h1>Hello</h1>{% block content %}in block{% endblock %}");
1376    ///
1377    /// let mut buffer = Vec::new();
1378    /// tera.render_block_to("hello.html", "content", &Context::new(), &mut buffer).unwrap();
1379    /// assert_eq!(buffer, b"in block");
1380    /// ```
1381    pub fn render_block_to(
1382        &self,
1383        template_name: &str,
1384        block_name: &str,
1385        context: &Context,
1386        write: impl Write,
1387    ) -> TeraResult<()> {
1388        let template = self.must_get_template(template_name)?;
1389        if !template.block_lineage.contains_key(block_name) {
1390            return Err(Error::message(format!(
1391                "Block `{block_name}` not found in template `{template_name}`",
1392            )));
1393        }
1394        let mut vm = VirtualMachine::new(self, template);
1395        vm.render_to(Some(block_name), context, &self.global_context, write)
1396    }
1397}
1398
1399impl Default for Tera {
1400    fn default() -> Self {
1401        let mut tera = Self {
1402            glob: None,
1403            templates: HashMap::new(),
1404            autoescape_suffixes: vec![
1405                Cow::Borrowed(".html"),
1406                Cow::Borrowed(".htm"),
1407                Cow::Borrowed(".xml"),
1408            ],
1409            escape_fn: escape_html,
1410            global_context: Context::new(),
1411            filters: HashMap::new(),
1412            tests: HashMap::new(),
1413            functions: HashMap::new(),
1414            components: HashMap::new(),
1415            delimiters: Delimiters::default(),
1416            fallback_prefixes: Vec::new(),
1417        };
1418        tera.register_builtin_filters();
1419        tera.register_builtin_tests();
1420        tera.register_builtin_functions();
1421        tera
1422    }
1423}
1424
1425impl fmt::Debug for Tera {
1426    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1427        f.debug_struct("Tera")
1428            .field("glob", &self.glob)
1429            .field("templates", &self.templates.len())
1430            .field("autoescape_suffixes", &self.autoescape_suffixes)
1431            .field("filters", &self.filters.len())
1432            .field("tests", &self.tests.len())
1433            .field("functions", &self.functions.len())
1434            .field("components", &self.components.len())
1435            .field("delimiters", &self.delimiters)
1436            .finish_non_exhaustive()
1437    }
1438}
1439
1440#[cfg(test)]
1441mod tests {
1442    use crate::{Kwargs, context};
1443
1444    use super::*;
1445
1446    #[test]
1447    fn global_context() {
1448        let mut tera = Tera::new();
1449        tera.global_context().insert("name", "John Doe");
1450
1451        let content = tera
1452            .render_str("Hello, {{ name }}!", &Context::new(), false)
1453            .unwrap();
1454        assert_eq!(content, "Hello, John Doe!".to_string());
1455
1456        let content2 = tera
1457            .render_str(
1458                "UserID: {{ id }}, Username: {{ name }}",
1459                &context! { id => &7489 },
1460                false,
1461            )
1462            .unwrap();
1463        assert_eq!(content2, "UserID: 7489, Username: John Doe");
1464    }
1465
1466    #[cfg(feature = "glob_fs")]
1467    #[test]
1468    fn can_full_reload() {
1469        let mut tera = Tera::default();
1470        tera.load_from_glob("examples/basic/templates/**/*")
1471            .unwrap();
1472        tera.full_reload().unwrap();
1473
1474        assert!(tera.get_template("base.html").is_some());
1475    }
1476
1477    #[cfg(feature = "glob_fs")]
1478    #[test]
1479    fn error_on_malformed_template_in_glob() {
1480        let dir = tempfile::tempdir().unwrap();
1481        std::fs::write(dir.path().join("bad.html"), "{% if foo %}oops").unwrap();
1482        let glob = dir.path().join("**/*").to_string_lossy().to_string();
1483        let mut tera = Tera::default();
1484        let result = tera.load_from_glob(&glob);
1485        assert!(result.is_err());
1486    }
1487
1488    #[cfg(feature = "glob_fs")]
1489    #[test]
1490    fn failed_full_reload_preserves_existing_templates() {
1491        let dir = tempfile::tempdir().unwrap();
1492        let good_path = dir.path().join("good.html");
1493        std::fs::write(&good_path, "Hello {{ name }}").unwrap();
1494        let glob = dir.path().join("**/*").to_string_lossy().to_string();
1495
1496        let mut tera = Tera::default();
1497        tera.load_from_glob(&glob).unwrap();
1498        let mut ctx = Context::new();
1499        ctx.insert("name", &"world");
1500        assert_eq!(tera.render("good.html", &ctx).unwrap(), "Hello world");
1501
1502        std::fs::write(&good_path, "{% if x %}oops").unwrap();
1503        let result = tera.full_reload();
1504        assert!(result.is_err());
1505        assert_eq!(tera.render("good.html", &ctx).unwrap(), "Hello world");
1506    }
1507
1508    #[cfg(feature = "glob_fs")]
1509    #[test]
1510    fn load_from_glob_preserves_manual_templates() {
1511        let dir = tempfile::tempdir().unwrap();
1512        let page = dir.path().join("page.html");
1513        std::fs::write(&page, "page").unwrap();
1514        let glob = dir.path().join("**/*").to_string_lossy().to_string();
1515
1516        let mut tera = Tera::default();
1517        tera.add_raw_template("main.html", "main").unwrap();
1518        tera.load_from_glob(&glob).unwrap();
1519
1520        let ctx = Context::new();
1521        assert_eq!(tera.render("main.html", &ctx).unwrap(), "main");
1522        assert_eq!(tera.render("page.html", &ctx).unwrap(), "page");
1523
1524        std::fs::remove_file(&page).unwrap();
1525        tera.full_reload().unwrap();
1526        assert_eq!(tera.render("main.html", &ctx).unwrap(), "main");
1527        assert!(tera.get_template("page.html").is_none());
1528    }
1529
1530    #[test]
1531    fn add_raw_template_failure_preserves_existing() {
1532        let mut tera = Tera::default();
1533        tera.add_raw_template("good.html", "Hello {{ name }}")
1534            .unwrap();
1535
1536        // Parses fine but finalize rejects (unknown filter).
1537        let err = tera.add_raw_template("bad.html", "{{ name | no_such_filter }}");
1538        assert!(err.is_err());
1539
1540        assert!(tera.get_template("good.html").is_some());
1541        assert!(tera.get_template("bad.html").is_none());
1542    }
1543
1544    #[test]
1545    fn rendering_invalid_utf8_bytes_does_not_panic() {
1546        let mut tera = Tera::default();
1547        tera.add_raw_template("page.html", "{{ data }}").unwrap();
1548
1549        let mut ctx = Context::new();
1550        ctx.insert_value(
1551            "data",
1552            Value {
1553                inner: crate::value::ValueInner::Bytes(std::sync::Arc::new(vec![0xFF, 0xFE, 0xFD])),
1554            },
1555        );
1556        let out = tera.render("page.html", &ctx).unwrap();
1557        assert_eq!(out, "\u{FFFD}\u{FFFD}\u{FFFD}");
1558    }
1559
1560    #[test]
1561    fn test_render_component() {
1562        let mut tera = Tera::default();
1563        tera.add_raw_template(
1564            "components.html",
1565            r#"{% component Button(label, variant="primary") %}<button class="{{ variant }}">{{ label }}</button>{% endcomponent Button %}
1566{% component Card(title) %}<div><h1>{{ title }}</h1>{{ body }}</div>{% endcomponent Card %}
1567{% component Display(content) %}{{ content }}{% endcomponent Display %}"#,
1568        )
1569        .unwrap();
1570        tera.add_raw_template(
1571            "components.txt",
1572            "{% component Raw(content) %}{{ content }}{% endcomponent Raw %}",
1573        )
1574        .unwrap();
1575
1576        // Basic + defaults
1577        insta::assert_snapshot!(
1578            tera.render_component("Button", &context! { label => "Click" }, None, true).unwrap(),
1579            @r#"<button class="primary">Click</button>"#
1580        );
1581        // Override default
1582        insta::assert_snapshot!(
1583            tera.render_component("Button", &context! { label => "X", variant => "secondary" }, None, true).unwrap(),
1584            @r#"<button class="secondary">X</button>"#
1585        );
1586        // With body
1587        insta::assert_snapshot!(
1588            tera.render_component("Card", &context! { title => "T" }, Some("<p>body</p>"), true).unwrap(),
1589            @"<div><h1>T</h1><p>body</p></div>"
1590        );
1591        // Autoescape on
1592        insta::assert_snapshot!(
1593            tera.render_component("Display", &context! { content => "<script>" }, None, true).unwrap(),
1594            @"&lt;script&gt;"
1595        );
1596        // Autoescape off
1597        insta::assert_snapshot!(
1598            tera.render_component("Raw", &context! { content => "<script>" }, None, false).unwrap(),
1599            @"<script>"
1600        );
1601        // render_component_to variant
1602        let mut buffer = Vec::new();
1603        tera.render_component_to(
1604            "Button",
1605            &context! { label => "Y" },
1606            None,
1607            true,
1608            &mut buffer,
1609        )
1610        .unwrap();
1611        insta::assert_snapshot!(String::from_utf8(buffer).unwrap(), @r#"<button class="primary">Y</button>"#);
1612
1613        // Errors
1614        assert!(
1615            tera.render_component("Nope", &Context::new(), None, true)
1616                .is_err()
1617        );
1618        assert!(
1619            tera.render_component("Button", &Context::new(), None, true)
1620                .is_err()
1621        );
1622        assert!(
1623            tera.render_component("Button", &context! { label => "x", bad => "y" }, None, true)
1624                .is_err()
1625        );
1626    }
1627
1628    #[cfg(unix)]
1629    #[test]
1630    fn add_template_file_errors_on_non_utf8_path() {
1631        use std::ffi::OsStr;
1632        use std::os::unix::ffi::OsStrExt;
1633        use std::path::PathBuf;
1634
1635        let bad = PathBuf::from(OsStr::from_bytes(b"/tmp/\xff\xfe.html"));
1636        let mut tera = Tera::default();
1637        let err = tera.add_template_file(&bad, None).unwrap_err();
1638        assert!(format!("{err}").contains("not valid UTF-8"));
1639    }
1640
1641    #[test]
1642    fn custom_delimiters() {
1643        let mut tera = Tera::new();
1644        tera.set_delimiters(Delimiters {
1645            block_start: "<%".into(),
1646            block_end: "%>".into(),
1647            variable_start: "<<".into(),
1648            variable_end: ">>".into(),
1649            comment_start: "<#".into(),
1650            comment_end: "#>".into(),
1651        })
1652        .unwrap();
1653
1654        tera.add_raw_template(
1655            "test",
1656            "Hello, <# This is a comment #><% if show %><< name >>!<% endif %>",
1657        )
1658        .unwrap();
1659        let result = tera
1660            .render("test", &context! { name => "World", show => &true })
1661            .unwrap();
1662        insta::assert_snapshot!(result, @"Hello, World!");
1663    }
1664
1665    #[test]
1666    fn fallback_prefixes_resolve_templates() {
1667        let mut tera = Tera::default();
1668        tera.set_fallback_prefixes(vec!["themes/cool/".to_string()])
1669            .unwrap();
1670        tera.add_raw_templates(vec![
1671            (
1672                "themes/cool/base.html",
1673                "{% block content %}default{% endblock %}",
1674            ),
1675            ("themes/cool/partial.html", "partial"),
1676            (
1677                "child.html",
1678                "{% extends \"base.html\" %}{% block content %}child-{% include \"partial.html\" %}{% endblock %}",
1679            ),
1680        ])
1681        .unwrap();
1682
1683        let result = tera.render("child.html", &Context::new()).unwrap();
1684        assert_eq!(result, "child-partial");
1685    }
1686
1687    #[test]
1688    fn fallback_prefix_exact_match_takes_priority() {
1689        let mut tera = Tera::default();
1690        tera.set_fallback_prefixes(vec!["themes/cool/".to_string()])
1691            .unwrap();
1692        tera.add_raw_templates(vec![
1693            ("base.html", "exact"),
1694            ("themes/cool/base.html", "fallback"),
1695        ])
1696        .unwrap();
1697
1698        let result = tera.render("base.html", &Context::new()).unwrap();
1699        assert_eq!(result, "exact");
1700    }
1701
1702    #[test]
1703    fn adding_template_re_resolves_lineage_properly() {
1704        let mut tera = Tera::default();
1705        tera.set_fallback_prefixes(vec!["themes/cool/".to_string()])
1706            .unwrap();
1707        tera.add_raw_template("themes/cool/base.html", "fallback")
1708            .unwrap();
1709        tera.add_raw_template("child.html", r#"{% extends "base.html" %}"#)
1710            .unwrap();
1711        assert_eq!(
1712            tera.render("child.html", &Context::new()).unwrap(),
1713            "fallback"
1714        );
1715
1716        tera.add_raw_template("base.html", "main").unwrap();
1717        assert_eq!(tera.render("child.html", &Context::new()).unwrap(), "main");
1718    }
1719
1720    #[test]
1721    fn test_get_template_priority() {
1722        let mut tera = Tera::default();
1723        tera.set_fallback_prefixes(vec![
1724            "themes/child/".to_string(),
1725            "themes/parent/".to_string(),
1726        ])
1727        .unwrap();
1728
1729        assert_eq!(tera.get_template_priority("index.html"), 0);
1730        assert_eq!(tera.get_template_priority("themes/child/base.html"), 1);
1731        assert_eq!(tera.get_template_priority("themes/parent/base.html"), 2);
1732    }
1733
1734    #[test]
1735    fn test_component_duplicate_error_same_priority() {
1736        let mut tera = Tera::default();
1737        tera.set_fallback_prefixes(vec!["themes/".to_string()])
1738            .unwrap();
1739
1740        tera.add_raw_template("a.html", "{% component Foo() %}A{% endcomponent Foo %}")
1741            .unwrap();
1742
1743        let result =
1744            tera.add_raw_template("b.html", "{% component Foo() %}B{% endcomponent Foo %}");
1745        assert!(result.is_err());
1746        assert!(
1747            result
1748                .unwrap_err()
1749                .to_string()
1750                .contains("Component `Foo` is defined in both")
1751        );
1752    }
1753
1754    #[test]
1755    fn test_component_override_chain() {
1756        let mut tera = Tera::default();
1757        tera.set_fallback_prefixes(vec!["child/".to_string(), "parent/".to_string()])
1758            .unwrap();
1759
1760        tera.add_raw_template(
1761            "parent/c.html",
1762            "{% component X() %}parent{% endcomponent X %}",
1763        )
1764        .unwrap();
1765        tera.add_raw_template(
1766            "child/c.html",
1767            "{% component X() %}child{% endcomponent X %}",
1768        )
1769        .unwrap();
1770        tera.add_raw_template("user.html", "{% component X() %}user{% endcomponent X %}")
1771            .unwrap();
1772        tera.add_raw_template("test.html", "{{<X/>}}").unwrap();
1773
1774        let output = tera.render("test.html", &Context::new()).unwrap();
1775        assert_eq!(output.trim(), "user");
1776    }
1777
1778    #[test]
1779    fn test_fallback_template_resolution() {
1780        let mut tera = Tera::default();
1781        tera.set_fallback_prefixes(vec!["themes/cool/".to_string()])
1782            .unwrap();
1783
1784        tera.add_raw_template("themes/cool/base.html", "theme base")
1785            .unwrap();
1786        tera.add_raw_template("index.html", r#"{% extends "base.html" %}"#)
1787            .unwrap();
1788
1789        let output = tera.render("base.html", &Context::new()).unwrap();
1790        assert_eq!(output.trim(), "theme base");
1791    }
1792
1793    #[test]
1794    fn render_str() {
1795        let mut tera = Tera::new();
1796        tera.add_raw_template("partial.html", "I am partial")
1797            .unwrap();
1798        tera.add_raw_template(
1799            "components.html",
1800            r#"{% component Greet(name) %}Hello {{ name }}!{% endcomponent Greet %}"#,
1801        )
1802        .unwrap();
1803
1804        tera.register_filter("shout", |s: &str, _: Kwargs, _: &State| {
1805            s.to_ascii_uppercase().to_string()
1806        });
1807        let result = tera
1808            .render_str(
1809                r#"Hello {{ name }}!. {% include "partial.html" %} - {{<Greet name="World"/>}}"#,
1810                &context! { name => "world" },
1811                false,
1812            )
1813            .unwrap();
1814
1815        insta::assert_snapshot!(result, @"Hello world!. I am partial - Hello World!");
1816    }
1817
1818    #[test]
1819    fn render_str_errors_on_extends() {
1820        let tera = Tera::new();
1821        let result = tera.render_str(r#"{% extends "base.html" %}hi"#, &Context::new(), false);
1822        assert!(result.is_err());
1823    }
1824
1825    #[test]
1826    fn render_str_errors_on_blocks() {
1827        let tera = Tera::new();
1828        let result = tera.render_str(
1829            "Before {% block content %}default{% endblock content %} After",
1830            &Context::new(),
1831            false,
1832        );
1833        assert!(result.is_err());
1834    }
1835
1836    #[test]
1837    fn render_str_autoescape() {
1838        let tera = Tera::new();
1839        let result = tera
1840            .render_str("{{ html }}", &context! { html => "<script>" }, true)
1841            .unwrap();
1842        insta::assert_snapshot!(result, @"&lt;script&gt;");
1843        let result = tera
1844            .render_str("{{ html }}", &context! { html => "<script>" }, false)
1845            .unwrap();
1846        insta::assert_snapshot!(result, @"<script>");
1847    }
1848
1849    #[test]
1850    fn render_block_works() {
1851        let mut tera = Tera::default();
1852        tera.add_raw_templates(vec![
1853            (
1854                "base.html",
1855                "{% block nav %}nav{% endblock %}{% block content %}default{% endblock %}",
1856            ),
1857            (
1858                "child.html",
1859                "{% extends \"base.html\" %}{% block content %}child-{{super()}}{% endblock %}",
1860            ),
1861            (
1862                "nested.html",
1863                "{% block outer %}<o>{% block inner %}inner{% endblock %}</o>{% endblock %}",
1864            ),
1865        ])
1866        .unwrap();
1867
1868        // unknown blocks error
1869        assert!(
1870            tera.render_block("child.html", "unknown", &Context::new())
1871                .is_err()
1872        );
1873        let result = tera
1874            .render_block("child.html", "content", &Context::new())
1875            .unwrap();
1876        assert_eq!(result, "child-default");
1877
1878        let inner = tera
1879            .render_block("nested.html", "inner", &Context::new())
1880            .unwrap();
1881        assert_eq!(inner, "inner");
1882        let outer = tera
1883            .render_block("nested.html", "outer", &Context::new())
1884            .unwrap();
1885        assert_eq!(outer, "<o>inner</o>");
1886    }
1887}