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>(
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 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 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 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 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 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 pub fn contains_component(&self, component_name: &str) -> bool {
449 self.get_component_definition(component_name).is_some()
450 }
451
452 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 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 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 let mut component_sources: HashMap<&str, (&str, usize)> = HashMap::new();
626
627 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 component_sources.insert(component_name, (&tpl.name, current_priority));
643 } else if current_priority > existing_priority {
644 } else {
646 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 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 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 let mut tpl_blocks: HashMap<String, HashMap<String, Vec<Chunk>>> =
684 HashMap::with_capacity(self.templates.len());
685 let mut errors: Vec<(&str, usize, String)> = Vec::new();
687
688 for (name, tpl) in &self.templates {
689 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 pub fn contains_template(&self, template_name: &str) -> bool {
1009 self.resolve_template_name(template_name).is_some()
1010 }
1011
1012 pub fn get_template_names(&self) -> impl Iterator<Item = &str> {
1030 self.templates.keys().map(|s| s.as_str())
1031 }
1032
1033 #[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 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 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 pub fn global_context(&mut self) -> &mut Context {
1141 &mut self.global_context
1142 }
1143
1144 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 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 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 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 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 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 let template = self
1314 .templates
1315 .get(&chunk.name)
1316 .expect("Component source template must exist");
1317
1318 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 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 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 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 insta::assert_snapshot!(
1576 tera.render_component("Button", &context! { label => "Click" }, None, true).unwrap(),
1577 @r#"<button class="primary">Click</button>"#
1578 );
1579 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 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 insta::assert_snapshot!(
1591 tera.render_component("Display", &context! { content => "<script>" }, None, true).unwrap(),
1592 @"<script>"
1593 );
1594 insta::assert_snapshot!(
1596 tera.render_component("Raw", &context! { content => "<script>" }, None, false).unwrap(),
1597 @"<script>"
1598 );
1599 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 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, @"<script>");
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 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}