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