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
26const ONE_OFF_TEMPLATE_NAME: &str = "__tera_one_off";
28
29pub type EscapeFn = fn(&str, &mut dyn Write) -> std::io::Result<()>;
31
32#[derive(Clone)]
67pub struct Tera {
68 #[allow(dead_code)]
71 pub(crate) glob: Option<String>,
72 pub(crate) templates: HashMap<String, Template>,
73 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 delimiters: Delimiters,
85 fallback_prefixes: Vec<Cow<'static, str>>,
87}
88
89impl Tera {
90 pub fn new() -> Self {
92 Self::default()
93 }
94
95 #[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 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 if result.is_err() {
151 self.templates = prev_templates;
152 self.glob = prev_glob;
153 }
154 result
155 }
156
157 #[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 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 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 pub fn set_escape_fn(&mut self, function: EscapeFn) {
287 self.escape_fn = function;
288 }
289
290 pub fn reset_escape_fn(&mut self) {
292 self.escape_fn = escape_html;
293 }
294
295 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 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 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 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 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 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 pub fn contains_component(&self, component_name: &str) -> bool {
451 self.get_component_definition(component_name).is_some()
452 }
453
454 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 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 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 let mut component_sources: HashMap<&str, (&str, usize)> = HashMap::new();
629
630 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 component_sources.insert(component_name, (&tpl.name, current_priority));
646 } else if current_priority > existing_priority {
647 } else {
649 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 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 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 let mut tpl_blocks: HashMap<String, HashMap<String, Vec<Chunk>>> =
687 HashMap::with_capacity(self.templates.len());
688 let mut errors: Vec<(&str, usize, String)> = Vec::new();
690
691 for (name, tpl) in &self.templates {
692 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 pub fn contains_template(&self, template_name: &str) -> bool {
1012 self.resolve_template_name(template_name).is_some()
1013 }
1014
1015 pub fn get_template_names(&self) -> impl Iterator<Item = &str> {
1033 self.templates.keys().map(|s| s.as_str())
1034 }
1035
1036 #[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 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 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 pub fn global_context(&mut self) -> &mut Context {
1144 &mut self.global_context
1145 }
1146
1147 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 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 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 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 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 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 let template = self
1317 .templates
1318 .get(&chunk.name)
1319 .expect("Component source template must exist");
1320
1321 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 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 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 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 insta::assert_snapshot!(
1578 tera.render_component("Button", &context! { label => "Click" }, None, true).unwrap(),
1579 @r#"<button class="primary">Click</button>"#
1580 );
1581 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 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 insta::assert_snapshot!(
1593 tera.render_component("Display", &context! { content => "<script>" }, None, true).unwrap(),
1594 @"<script>"
1595 );
1596 insta::assert_snapshot!(
1598 tera.render_component("Raw", &context! { content => "<script>" }, None, false).unwrap(),
1599 @"<script>"
1600 );
1601 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 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, @"<script>");
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 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}