1use std::collections::HashMap;
2use std::fmt;
3use std::fs::File;
4use std::io::prelude::*;
5use std::path::Path;
6use std::sync::Arc;
7
8use globwalk::glob_builder;
9
10use crate::builtins::filters::{array, common, number, object, string, Filter};
11use crate::builtins::functions::{self, Function};
12use crate::builtins::testers::{self, Test};
13use crate::context::Context;
14use crate::errors::{Error, Result};
15use crate::renderer::Renderer;
16use crate::template::Template;
17use crate::utils::escape_html;
18
19const ONE_OFF_TEMPLATE_NAME: &str = "__tera_one_off";
21
22pub type EscapeFn = fn(&str) -> String;
24
25#[derive(Clone)]
61pub struct Tera {
62 #[doc(hidden)]
64 glob: Option<String>,
65 #[doc(hidden)]
66 pub templates: HashMap<String, Template>,
67 #[doc(hidden)]
68 pub filters: HashMap<String, Arc<dyn Filter>>,
69 #[doc(hidden)]
70 pub testers: HashMap<String, Arc<dyn Test>>,
71 #[doc(hidden)]
72 pub functions: HashMap<String, Arc<dyn Function>>,
73 #[doc(hidden)]
76 pub autoescape_suffixes: Vec<&'static str>,
77 #[doc(hidden)]
78 escape_fn: EscapeFn,
79}
80
81impl Tera {
82 fn create(dir: &str, parse_only: bool) -> Result<Tera> {
83 if dir.find('*').is_none() {
84 return Err(Error::msg(format!(
85 "Tera expects a glob as input, no * were found in `{}`",
86 dir
87 )));
88 }
89
90 let mut tera = Tera {
91 glob: Some(dir.to_string()),
92 templates: HashMap::new(),
93 filters: HashMap::new(),
94 functions: HashMap::new(),
95 testers: HashMap::new(),
96 autoescape_suffixes: vec![".html", ".htm", ".xml"],
97 escape_fn: escape_html,
98 };
99
100 tera.load_from_glob()?;
101 if !parse_only {
102 tera.build_inheritance_chains()?;
103 tera.check_macro_files()?;
104 }
105 tera.register_tera_filters();
106 tera.register_tera_testers();
107 tera.register_tera_functions();
108 Ok(tera)
109 }
110
111 pub fn new(dir: &str) -> Result<Tera> {
133 Self::create(dir, false)
134 }
135
136 pub fn parse(dir: &str) -> Result<Tera> {
158 Self::create(dir, true)
159 }
160
161 fn load_from_glob(&mut self) -> Result<()> {
163 let glob = match &self.glob {
164 Some(g) => g,
165 None => return Err(Error::msg("Tera can only load from glob if a glob is provided")),
166 };
167
168 self.templates = self
171 .templates
172 .iter()
173 .filter(|&(_, t)| t.from_extend)
174 .map(|(n, t)| (n.clone(), t.clone())) .collect();
176
177 let mut errors = String::new();
178
179 let (parent_dir, glob_end) = glob.split_at(glob.find('*').unwrap());
184 let parent_dir = match std::fs::canonicalize(parent_dir) {
185 Ok(d) => d,
186 Err(_) => std::path::PathBuf::from(parent_dir),
190 };
191 let dir = parent_dir.join(glob_end).into_os_string().into_string().unwrap();
192
193 for entry in glob_builder(&dir)
195 .follow_links(true)
196 .build()
197 .unwrap()
198 .filter_map(std::result::Result::ok)
199 {
200 let mut path = entry.into_path();
201 if path.is_file() {
203 if path.starts_with("./") {
204 path = path.strip_prefix("./").unwrap().to_path_buf();
205 }
206
207 let filepath = path
208 .strip_prefix(&parent_dir)
209 .unwrap()
210 .to_string_lossy()
211 .replace('\\', "/");
213
214 if let Err(e) = self.add_file(Some(&filepath), path) {
215 use std::error::Error;
216
217 errors += &format!("\n* {}", e);
218 let mut cause = e.source();
219 while let Some(e) = cause {
220 errors += &format!("\n{}", e);
221 cause = e.source();
222 }
223 }
224 }
225 }
226
227 if !errors.is_empty() {
228 return Err(Error::msg(errors));
229 }
230
231 Ok(())
232 }
233
234 fn add_file<P: AsRef<Path>>(&mut self, name: Option<&str>, path: P) -> Result<()> {
238 let path = path.as_ref();
239 let tpl_name = name.unwrap_or_else(|| path.to_str().unwrap());
240
241 let mut f = File::open(path)
242 .map_err(|e| Error::chain(format!("Couldn't open template '{:?}'", path), e))?;
243
244 let mut input = String::new();
245 f.read_to_string(&mut input)
246 .map_err(|e| Error::chain(format!("Failed to read template '{:?}'", path), e))?;
247
248 let tpl = Template::new(tpl_name, Some(path.to_str().unwrap().to_string()), &input)
249 .map_err(|e| Error::chain(format!("Failed to parse {:?}", path), e))?;
250
251 self.templates.insert(tpl_name.to_string(), tpl);
252 Ok(())
253 }
254
255 pub fn build_inheritance_chains(&mut self) -> Result<()> {
265 fn build_chain(
268 templates: &HashMap<String, Template>,
269 start: &Template,
270 template: &Template,
271 mut parents: Vec<String>,
272 ) -> Result<Vec<String>> {
273 if !parents.is_empty() && start.name == template.name {
274 return Err(Error::circular_extend(&start.name, parents));
275 }
276
277 match template.parent {
278 Some(ref p) => match templates.get(p) {
279 Some(parent) => {
280 parents.push(parent.name.clone());
281 build_chain(templates, start, parent, parents)
282 }
283 None => Err(Error::missing_parent(&template.name, p)),
284 },
285 None => Ok(parents),
286 }
287 }
288
289 let mut tpl_parents = HashMap::new();
291 let mut tpl_block_definitions = HashMap::new();
292 for (name, template) in &self.templates {
293 if template.parent.is_none() && template.blocks.is_empty() {
294 continue;
295 }
296
297 let parents = build_chain(&self.templates, template, template, vec![])?;
298
299 let mut blocks_definitions = HashMap::new();
300 for (block_name, def) in &template.blocks {
301 let mut definitions = vec![(template.name.clone(), def.clone())];
303
304 for parent in &parents {
306 let t = self.get_template(parent)?;
307
308 if let Some(b) = t.blocks.get(block_name) {
309 definitions.push((t.name.clone(), b.clone()));
310 }
311 }
312 blocks_definitions.insert(block_name.clone(), definitions);
313 }
314 tpl_parents.insert(name.clone(), parents);
315 tpl_block_definitions.insert(name.clone(), blocks_definitions);
316 }
317
318 for template in self.templates.values_mut() {
319 if template.parent.is_none() && template.blocks.is_empty() {
321 continue;
322 }
323
324 template.parents = match tpl_parents.remove(&template.name) {
325 Some(parents) => parents,
326 None => vec![],
327 };
328 template.blocks_definitions = match tpl_block_definitions.remove(&template.name) {
329 Some(blocks) => blocks,
330 None => HashMap::new(),
331 };
332 }
333
334 Ok(())
335 }
336
337 pub fn check_macro_files(&self) -> Result<()> {
342 for template in self.templates.values() {
343 for (tpl_name, _) in &template.imported_macro_files {
344 if !self.templates.contains_key(tpl_name) {
345 return Err(Error::msg(format!(
346 "Template `{}` loads macros from `{}` which isn't present in Tera",
347 template.name, tpl_name
348 )));
349 }
350 }
351 }
352
353 Ok(())
354 }
355
356 pub fn render(&self, template_name: &str, context: &Context) -> Result<String> {
390 let template = self.get_template(template_name)?;
391 let renderer = Renderer::new(template, self, context);
392 renderer.render()
393 }
394
395 pub fn render_to(
420 &self,
421 template_name: &str,
422 context: &Context,
423 write: impl Write,
424 ) -> Result<()> {
425 let template = self.get_template(template_name)?;
426 let renderer = Renderer::new(template, self, context);
427 renderer.render_to(write)
428 }
429
430 pub fn render_str(&mut self, input: &str, context: &Context) -> Result<String> {
444 self.add_raw_template(ONE_OFF_TEMPLATE_NAME, input)?;
445 let result = self.render(ONE_OFF_TEMPLATE_NAME, context);
446 self.templates.remove(ONE_OFF_TEMPLATE_NAME);
447 result
448 }
449
450 pub fn one_off(input: &str, context: &Context, autoescape: bool) -> Result<String> {
463 let mut tera = Tera::default();
464
465 if autoescape {
466 tera.autoescape_on(vec![ONE_OFF_TEMPLATE_NAME]);
467 }
468
469 tera.render_str(input, context)
470 }
471
472 #[doc(hidden)]
473 #[inline]
474 pub fn get_template(&self, template_name: &str) -> Result<&Template> {
475 match self.templates.get(template_name) {
476 Some(tpl) => Ok(tpl),
477 None => Err(Error::template_not_found(template_name)),
478 }
479 }
480
481 #[inline]
499 pub fn get_template_names(&self) -> impl Iterator<Item = &str> {
500 self.templates.keys().map(|s| s.as_str())
501 }
502
503 pub fn add_raw_template(&mut self, name: &str, content: &str) -> Result<()> {
523 let tpl = Template::new(name, None, content)
524 .map_err(|e| Error::chain(format!("Failed to parse '{}'", name), e))?;
525 self.templates.insert(name.to_string(), tpl);
526 self.build_inheritance_chains()?;
527 self.check_macro_files()?;
528 Ok(())
529 }
530
531 pub fn add_raw_templates<I, N, C>(&mut self, templates: I) -> Result<()>
543 where
544 I: IntoIterator<Item = (N, C)>,
545 N: AsRef<str>,
546 C: AsRef<str>,
547 {
548 for (name, content) in templates {
549 let name = name.as_ref();
550 let tpl = Template::new(name, None, content.as_ref())
551 .map_err(|e| Error::chain(format!("Failed to parse '{}'", name), e))?;
552 self.templates.insert(name.to_string(), tpl);
553 }
554 self.build_inheritance_chains()?;
555 self.check_macro_files()?;
556 Ok(())
557 }
558
559 pub fn add_template_file<P: AsRef<Path>>(&mut self, path: P, name: Option<&str>) -> Result<()> {
575 self.add_file(name, path)?;
576 self.build_inheritance_chains()?;
577 self.check_macro_files()?;
578 Ok(())
579 }
580
581 pub fn add_template_files<I, P, N>(&mut self, files: I) -> Result<()>
598 where
599 I: IntoIterator<Item = (P, Option<N>)>,
600 P: AsRef<Path>,
601 N: AsRef<str>,
602 {
603 for (path, name) in files {
604 self.add_file(name.as_ref().map(AsRef::as_ref), path)?;
605 }
606 self.build_inheritance_chains()?;
607 self.check_macro_files()?;
608 Ok(())
609 }
610
611 #[doc(hidden)]
612 #[inline]
613 pub fn get_filter(&self, filter_name: &str) -> Result<&dyn Filter> {
614 match self.filters.get(filter_name) {
615 Some(fil) => Ok(&**fil),
616 None => Err(Error::filter_not_found(filter_name)),
617 }
618 }
619
620 pub fn register_filter<F: Filter + 'static>(&mut self, name: &str, filter: F) {
628 self.filters.insert(name.to_string(), Arc::new(filter));
629 }
630
631 #[doc(hidden)]
632 #[inline]
633 pub fn get_tester(&self, tester_name: &str) -> Result<&dyn Test> {
634 match self.testers.get(tester_name) {
635 Some(t) => Ok(&**t),
636 None => Err(Error::test_not_found(tester_name)),
637 }
638 }
639
640 pub fn register_tester<T: Test + 'static>(&mut self, name: &str, tester: T) {
648 self.testers.insert(name.to_string(), Arc::new(tester));
649 }
650
651 #[doc(hidden)]
652 #[inline]
653 pub fn get_function(&self, fn_name: &str) -> Result<&dyn Function> {
654 match self.functions.get(fn_name) {
655 Some(t) => Ok(&**t),
656 None => Err(Error::function_not_found(fn_name)),
657 }
658 }
659
660 pub fn register_function<F: Function + 'static>(&mut self, name: &str, function: F) {
669 self.functions.insert(name.to_string(), Arc::new(function));
670 }
671
672 fn register_tera_filters(&mut self) {
673 self.register_filter("upper", string::upper);
674 self.register_filter("lower", string::lower);
675 self.register_filter("trim", string::trim);
676 self.register_filter("trim_start", string::trim_start);
677 self.register_filter("trim_end", string::trim_end);
678 self.register_filter("trim_start_matches", string::trim_start_matches);
679 self.register_filter("trim_end_matches", string::trim_end_matches);
680 self.register_filter("truncate", string::truncate);
681 self.register_filter("wordcount", string::wordcount);
682 self.register_filter("replace", string::replace);
683 self.register_filter("capitalize", string::capitalize);
684 self.register_filter("title", string::title);
685 self.register_filter("linebreaksbr", string::linebreaksbr);
686 self.register_filter("indent", string::indent);
687 self.register_filter("striptags", string::striptags);
688 self.register_filter("spaceless", string::spaceless);
689 #[cfg(feature = "urlencode")]
690 self.register_filter("urlencode", string::urlencode);
691 #[cfg(feature = "urlencode")]
692 self.register_filter("urlencode_strict", string::urlencode_strict);
693 self.register_filter("escape", string::escape_html);
694 self.register_filter("escape_xml", string::escape_xml);
695 #[cfg(feature = "builtins")]
696 self.register_filter("slugify", string::slugify);
697 self.register_filter("addslashes", string::addslashes);
698 self.register_filter("split", string::split);
699 self.register_filter("int", string::int);
700 self.register_filter("float", string::float);
701
702 self.register_filter("first", array::first);
703 self.register_filter("last", array::last);
704 self.register_filter("nth", array::nth);
705 self.register_filter("join", array::join);
706 self.register_filter("sort", array::sort);
707 self.register_filter("unique", array::unique);
708 self.register_filter("slice", array::slice);
709 self.register_filter("group_by", array::group_by);
710 self.register_filter("filter", array::filter);
711 self.register_filter("map", array::map);
712 self.register_filter("concat", array::concat);
713
714 self.register_filter("abs", number::abs);
715 self.register_filter("pluralize", number::pluralize);
716 self.register_filter("round", number::round);
717
718 #[cfg(feature = "builtins")]
719 self.register_filter("filesizeformat", number::filesizeformat);
720
721 self.register_filter("length", common::length);
722 self.register_filter("reverse", common::reverse);
723 #[cfg(feature = "builtins")]
724 self.register_filter("date", common::date);
725 self.register_filter("json_encode", common::json_encode);
726 self.register_filter("as_str", common::as_str);
727
728 self.register_filter("get", object::get);
729 }
730
731 fn register_tera_testers(&mut self) {
732 self.register_tester("defined", testers::defined);
733 self.register_tester("undefined", testers::undefined);
734 self.register_tester("odd", testers::odd);
735 self.register_tester("even", testers::even);
736 self.register_tester("string", testers::string);
737 self.register_tester("number", testers::number);
738 self.register_tester("divisibleby", testers::divisible_by);
739 self.register_tester("iterable", testers::iterable);
740 self.register_tester("object", testers::object);
741 self.register_tester("starting_with", testers::starting_with);
742 self.register_tester("ending_with", testers::ending_with);
743 self.register_tester("containing", testers::containing);
744 self.register_tester("matching", testers::matching);
745 }
746
747 fn register_tera_functions(&mut self) {
748 self.register_function("range", functions::range);
749 #[cfg(feature = "builtins")]
750 self.register_function("now", functions::now);
751 self.register_function("throw", functions::throw);
752 #[cfg(feature = "builtins")]
753 self.register_function("get_random", functions::get_random);
754 self.register_function("get_env", functions::get_env);
755 }
756
757 pub fn autoescape_on(&mut self, suffixes: Vec<&'static str>) {
775 self.autoescape_suffixes = suffixes;
776 }
777
778 #[doc(hidden)]
779 #[inline]
780 pub fn get_escape_fn(&self) -> &EscapeFn {
781 &self.escape_fn
782 }
783
784 pub fn set_escape_fn(&mut self, function: EscapeFn) {
822 self.escape_fn = function;
823 }
824
825 pub fn reset_escape_fn(&mut self) {
827 self.escape_fn = escape_html;
828 }
829
830 pub fn full_reload(&mut self) -> Result<()> {
838 if self.glob.is_some() {
839 self.load_from_glob()?;
840 } else {
841 return Err(Error::msg("Reloading is only available if you are using a glob"));
842 }
843
844 self.build_inheritance_chains()?;
845 self.check_macro_files()
846 }
847
848 pub fn extend(&mut self, other: &Tera) -> Result<()> {
861 for (name, template) in &other.templates {
862 if !self.templates.contains_key(name) {
863 let mut tpl = template.clone();
864 tpl.from_extend = true;
865 self.templates.insert(name.to_string(), tpl);
866 }
867 }
868
869 for (name, filter) in &other.filters {
870 if !self.filters.contains_key(name) {
871 self.filters.insert(name.to_string(), filter.clone());
872 }
873 }
874
875 for (name, tester) in &other.testers {
876 if !self.testers.contains_key(name) {
877 self.testers.insert(name.to_string(), tester.clone());
878 }
879 }
880
881 for (name, function) in &other.functions {
882 if !self.functions.contains_key(name) {
883 self.functions.insert(name.to_string(), function.clone());
884 }
885 }
886
887 self.build_inheritance_chains()?;
888 self.check_macro_files()
889 }
890}
891
892impl Default for Tera {
893 fn default() -> Tera {
894 let mut tera = Tera {
895 glob: None,
896 templates: HashMap::new(),
897 filters: HashMap::new(),
898 testers: HashMap::new(),
899 functions: HashMap::new(),
900 autoescape_suffixes: vec![".html", ".htm", ".xml"],
901 escape_fn: escape_html,
902 };
903
904 tera.register_tera_filters();
905 tera.register_tera_testers();
906 tera.register_tera_functions();
907 tera
908 }
909}
910
911impl fmt::Debug for Tera {
913 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
914 write!(f, "Tera {{")?;
915 writeln!(f, "\n\ttemplates: [")?;
916
917 for template in self.templates.keys() {
918 writeln!(f, "\t\t{},", template)?;
919 }
920 write!(f, "\t]")?;
921 writeln!(f, "\n\tfilters: [")?;
922
923 for filter in self.filters.keys() {
924 writeln!(f, "\t\t{},", filter)?;
925 }
926 write!(f, "\t]")?;
927 writeln!(f, "\n\ttesters: [")?;
928
929 for tester in self.testers.keys() {
930 writeln!(f, "\t\t{},", tester)?;
931 }
932 writeln!(f, "\t]")?;
933
934 writeln!(f, "}}")
935 }
936}
937
938#[cfg(test)]
939mod tests {
940 use tempfile::tempdir;
941
942 use std::collections::HashMap;
943 use std::fs::File;
944
945 use super::Tera;
946 use crate::context::Context;
947 use serde_json::{json, Value as JsonValue};
948
949 #[test]
950 fn test_get_inheritance_chain() {
951 let mut tera = Tera::default();
952 tera.add_raw_templates(vec![
953 ("a", "{% extends \"b\" %}"),
954 ("b", "{% extends \"c\" %}"),
955 ("c", "{% extends \"d\" %}"),
956 ("d", ""),
957 ])
958 .unwrap();
959
960 assert_eq!(
961 tera.get_template("a").unwrap().parents,
962 vec!["b".to_string(), "c".to_string(), "d".to_string()]
963 );
964 assert_eq!(tera.get_template("b").unwrap().parents, vec!["c".to_string(), "d".to_string()]);
965 assert_eq!(tera.get_template("c").unwrap().parents, vec!["d".to_string()]);
966 assert_eq!(tera.get_template("d").unwrap().parents.len(), 0);
967 }
968
969 #[test]
970 fn test_missing_parent_template() {
971 let mut tera = Tera::default();
972 assert_eq!(
973 tera.add_raw_template("a", "{% extends \"b\" %}").unwrap_err().to_string(),
974 "Template \'a\' is inheriting from \'b\', which doesn\'t exist or isn\'t loaded."
975 );
976 }
977
978 #[test]
979 fn test_circular_extends() {
980 let mut tera = Tera::default();
981 let err = tera
982 .add_raw_templates(vec![("a", "{% extends \"b\" %}"), ("b", "{% extends \"a\" %}")])
983 .unwrap_err();
984
985 assert!(err.to_string().contains("Circular extend detected for template"));
986 }
987
988 #[test]
989 fn test_get_parent_blocks_definition() {
990 let mut tera = Tera::default();
991 tera.add_raw_templates(vec![
992 (
993 "grandparent",
994 "{% block hey %}hello{% endblock hey %} {% block ending %}sincerely{% endblock ending %}",
995 ),
996 (
997 "parent",
998 "{% extends \"grandparent\" %}{% block hey %}hi and grandma says {{ super() }}{% endblock hey %}",
999 ),
1000 (
1001 "child",
1002 "{% extends \"parent\" %}{% block hey %}dad says {{ super() }}{% endblock hey %}{% block ending %}{{ super() }} with love{% endblock ending %}",
1003 ),
1004 ]).unwrap();
1005
1006 let hey_definitions =
1007 tera.get_template("child").unwrap().blocks_definitions.get("hey").unwrap();
1008 assert_eq!(hey_definitions.len(), 3);
1009
1010 let ending_definitions =
1011 tera.get_template("child").unwrap().blocks_definitions.get("ending").unwrap();
1012 assert_eq!(ending_definitions.len(), 2);
1013 }
1014
1015 #[test]
1016 fn test_get_parent_blocks_definition_nested_block() {
1017 let mut tera = Tera::default();
1018 tera.add_raw_templates(vec![
1019 ("grandparent", "{% block hey %}hello{% endblock hey %}"),
1020 (
1021 "parent",
1022 "{% extends \"grandparent\" %}{% block hey %}hi and grandma says {{ super() }} {% block ending %}sincerely{% endblock ending %}{% endblock hey %}",
1023 ),
1024 (
1025 "child",
1026 "{% extends \"parent\" %}{% block hey %}dad says {{ super() }}{% endblock hey %}{% block ending %}{{ super() }} with love{% endblock ending %}",
1027 ),
1028 ]).unwrap();
1029
1030 let hey_definitions =
1031 tera.get_template("child").unwrap().blocks_definitions.get("hey").unwrap();
1032 assert_eq!(hey_definitions.len(), 3);
1033
1034 let ending_definitions =
1035 tera.get_template("parent").unwrap().blocks_definitions.get("ending").unwrap();
1036 assert_eq!(ending_definitions.len(), 1);
1037 }
1038
1039 #[test]
1040 fn test_can_autoescape_one_off_template() {
1041 let mut context = Context::new();
1042 context.insert("greeting", &"<p>");
1043 let result = Tera::one_off("{{ greeting }} world", &context, true).unwrap();
1044
1045 assert_eq!(result, "<p> world");
1046 }
1047
1048 #[test]
1049 fn test_can_disable_autoescape_one_off_template() {
1050 let mut context = Context::new();
1051 context.insert("greeting", &"<p>");
1052 let result = Tera::one_off("{{ greeting }} world", &context, false).unwrap();
1053
1054 assert_eq!(result, "<p> world");
1055 }
1056
1057 #[test]
1058 fn test_set_escape_function() {
1059 let escape_c_string: super::EscapeFn = |input| {
1060 let mut output = String::with_capacity(input.len() * 2);
1061 for c in input.chars() {
1062 match c {
1063 '\'' => output.push_str("\\'"),
1064 '\"' => output.push_str("\\\""),
1065 '\\' => output.push_str("\\\\"),
1066 '\n' => output.push_str("\\n"),
1067 '\r' => output.push_str("\\r"),
1068 '\t' => output.push_str("\\t"),
1069 _ => output.push(c),
1070 }
1071 }
1072 output
1073 };
1074 let mut tera = Tera::default();
1075 tera.add_raw_template("foo", "\"{{ content }}\"").unwrap();
1076 tera.autoescape_on(vec!["foo"]);
1077 tera.set_escape_fn(escape_c_string);
1078 let mut context = Context::new();
1079 context.insert("content", &"Hello\n\'world\"!");
1080 let result = tera.render("foo", &context).unwrap();
1081 assert_eq!(result, r#""Hello\n\'world\"!""#);
1082 }
1083
1084 #[test]
1085 fn test_reset_escape_function() {
1086 let no_escape: super::EscapeFn = |input| input.to_string();
1087 let mut tera = Tera::default();
1088 tera.add_raw_template("foo", "{{ content }}").unwrap();
1089 tera.autoescape_on(vec!["foo"]);
1090 tera.set_escape_fn(no_escape);
1091 tera.reset_escape_fn();
1092 let mut context = Context::new();
1093 context.insert("content", &"Hello\n\'world\"!");
1094 let result = tera.render("foo", &context).unwrap();
1095 assert_eq!(result, "Hello\n'world"!");
1096 }
1097
1098 #[test]
1099 fn test_value_one_off_template() {
1100 let m = json!({
1101 "greeting": "Good morning"
1102 });
1103 let result =
1104 Tera::one_off("{{ greeting }} world", &Context::from_value(m).unwrap(), true).unwrap();
1105
1106 assert_eq!(result, "Good morning world");
1107 }
1108
1109 #[test]
1110 fn test_render_str_with_custom_function() {
1111 let mut tera = Tera::default();
1112 tera.register_function("echo", |args: &HashMap<_, JsonValue>| {
1113 Ok(args.get("greeting").map(JsonValue::to_owned).unwrap())
1114 });
1115
1116 let result =
1117 tera.render_str("{{ echo(greeting='Hello') }} world", &Context::default()).unwrap();
1118
1119 assert_eq!(result, "Hello world");
1120 }
1121
1122 #[test]
1123 fn test_render_map_with_dotted_keys() {
1124 let mut my_tera = Tera::default();
1125 my_tera
1126 .add_raw_templates(vec![
1127 ("dots", r#"{{ map["a.b.c"] }}"#),
1128 ("urls", r#"{{ map["https://example.com"] }}"#),
1129 ])
1130 .unwrap();
1131
1132 let mut map = HashMap::new();
1133 map.insert("a.b.c", "success");
1134 map.insert("https://example.com", "success");
1135
1136 let mut tera_context = Context::new();
1137 tera_context.insert("map", &map);
1138
1139 my_tera.render("dots", &tera_context).unwrap();
1140 my_tera.render("urls", &tera_context).unwrap();
1141 }
1142
1143 #[test]
1144 fn test_extend_no_overlap() {
1145 let mut my_tera = Tera::default();
1146 my_tera
1147 .add_raw_templates(vec![
1148 ("one", "{% block hey %}1{% endblock hey %}"),
1149 ("two", "{% block hey %}2{% endblock hey %}"),
1150 ("three", "{% block hey %}3{% endblock hey %}"),
1151 ])
1152 .unwrap();
1153
1154 let mut framework_tera = Tera::default();
1155 framework_tera.add_raw_templates(vec![("four", "Framework X")]).unwrap();
1156
1157 my_tera.extend(&framework_tera).unwrap();
1158 assert_eq!(my_tera.templates.len(), 4);
1159 let result = my_tera.render("four", &Context::default()).unwrap();
1160 assert_eq!(result, "Framework X");
1161 }
1162
1163 #[test]
1164 fn test_extend_with_overlap() {
1165 let mut my_tera = Tera::default();
1166 my_tera
1167 .add_raw_templates(vec![
1168 ("one", "MINE"),
1169 ("two", "{% block hey %}2{% endblock hey %}"),
1170 ("three", "{% block hey %}3{% endblock hey %}"),
1171 ])
1172 .unwrap();
1173
1174 let mut framework_tera = Tera::default();
1175 framework_tera
1176 .add_raw_templates(vec![("one", "FRAMEWORK"), ("four", "Framework X")])
1177 .unwrap();
1178
1179 my_tera.extend(&framework_tera).unwrap();
1180 assert_eq!(my_tera.templates.len(), 4);
1181 let result = my_tera.render("one", &Context::default()).unwrap();
1182 assert_eq!(result, "MINE");
1183 }
1184
1185 #[test]
1186 fn test_extend_new_filter() {
1187 let mut my_tera = Tera::default();
1188 let mut framework_tera = Tera::default();
1189 framework_tera.register_filter("hello", |_: &JsonValue, _: &HashMap<String, JsonValue>| {
1190 Ok(JsonValue::Number(10.into()))
1191 });
1192 my_tera.extend(&framework_tera).unwrap();
1193 assert!(my_tera.filters.contains_key("hello"));
1194 }
1195
1196 #[test]
1197 fn test_extend_new_tester() {
1198 let mut my_tera = Tera::default();
1199 let mut framework_tera = Tera::default();
1200 framework_tera.register_tester("hello", |_: Option<&JsonValue>, _: &[JsonValue]| Ok(true));
1201 my_tera.extend(&framework_tera).unwrap();
1202 assert!(my_tera.testers.contains_key("hello"));
1203 }
1204
1205 #[test]
1206 fn can_load_from_glob() {
1207 let tera = Tera::new("examples/basic/templates/**/*").unwrap();
1208 assert!(tera.get_template("base.html").is_ok());
1209 }
1210
1211 #[test]
1212 fn can_load_from_glob_with_patterns() {
1213 let tera = Tera::new("examples/basic/templates/**/*.{html, xml}").unwrap();
1214 assert!(tera.get_template("base.html").is_ok());
1215 }
1216
1217 #[test]
1218 fn full_reload_with_glob() {
1219 let mut tera = Tera::new("examples/basic/templates/**/*").unwrap();
1220 tera.full_reload().unwrap();
1221
1222 assert!(tera.get_template("base.html").is_ok());
1223 }
1224
1225 #[test]
1226 fn full_reload_with_glob_after_extending() {
1227 let mut tera = Tera::new("examples/basic/templates/**/*").unwrap();
1228 let mut framework_tera = Tera::default();
1229 framework_tera
1230 .add_raw_templates(vec![("one", "FRAMEWORK"), ("four", "Framework X")])
1231 .unwrap();
1232 tera.extend(&framework_tera).unwrap();
1233 tera.full_reload().unwrap();
1234
1235 assert!(tera.get_template("base.html").is_ok());
1236 assert!(tera.get_template("one").is_ok());
1237 }
1238
1239 #[should_panic]
1240 #[test]
1241 fn test_can_only_parse_templates() {
1242 let mut tera = Tera::parse("examples/basic/templates/**/*").unwrap();
1243 for tpl in tera.templates.values_mut() {
1244 tpl.name = format!("a-theme/templates/{}", tpl.name);
1245 if let Some(ref parent) = tpl.parent.clone() {
1246 tpl.parent = Some(format!("a-theme/templates/{}", parent));
1247 }
1248 }
1249 tera.build_inheritance_chains().unwrap();
1252 }
1253
1254 #[test]
1256 fn glob_work_with_absolute_paths() {
1257 let tmp_dir = tempdir().expect("create temp dir");
1258 let cwd = tmp_dir.path().canonicalize().unwrap();
1259 File::create(cwd.join("hey.html")).expect("Failed to create a test file");
1260 File::create(cwd.join("ho.html")).expect("Failed to create a test file");
1261 let glob = cwd.join("*.html").into_os_string().into_string().unwrap();
1262 let tera = Tera::new(&glob).expect("Couldn't build Tera instance");
1263 assert_eq!(tera.templates.len(), 2);
1264 }
1265
1266 #[test]
1267 fn glob_work_with_absolute_paths_and_double_star() {
1268 let tmp_dir = tempdir().expect("create temp dir");
1269 let cwd = tmp_dir.path().canonicalize().unwrap();
1270 File::create(cwd.join("hey.html")).expect("Failed to create a test file");
1271 File::create(cwd.join("ho.html")).expect("Failed to create a test file");
1272 let glob = cwd.join("**").join("*.html").into_os_string().into_string().unwrap();
1273 let tera = Tera::new(&glob).expect("Couldn't build Tera instance");
1274 assert_eq!(tera.templates.len(), 2);
1275 }
1276
1277 #[test]
1279 fn glob_work_with_paths_starting_with_dots() {
1280 use std::path::PathBuf;
1281
1282 let this_dir = std::env::current_dir()
1283 .expect("Could not retrieve the executable's current directory.");
1284
1285 let scratch_dir = tempfile::Builder::new()
1286 .prefix("tera_test_scratchspace")
1287 .tempdir_in(&this_dir)
1288 .expect(&format!(
1289 "Could not create temporary directory for test in current directory ({}).",
1290 this_dir.display()
1291 ));
1292 dbg!(&scratch_dir.path().display());
1293
1294 File::create(scratch_dir.path().join("hey.html")).expect("Failed to create a test file");
1295 File::create(scratch_dir.path().join("ho.html")).expect("Failed to create a test file");
1296 let glob = PathBuf::from("./")
1297 .join(scratch_dir.path().file_name().unwrap())
1298 .join("**")
1299 .join("*.html")
1300 .into_os_string()
1301 .into_string()
1302 .unwrap();
1303 let tera = Tera::new(&glob).expect("Couldn't build Tera instance.");
1304 assert_eq!(tera.templates.len(), 2);
1305 }
1306
1307 #[test]
1309 fn issues_found_fuzzing_expressions_are_fixed() {
1310 let samples: Vec<(&str, Option<&str>)> = vec![
1311 ("{{0%0}}", None),
1313 ("{{W>W>vv}}{", None),
1314 ("{{22220222222022222220}}", None),
1315 ("{_{{W~1+11}}k{", None),
1316 ("{{n~n<n.11}}}", None),
1317 ("{{266314325266577770*4167}}7}}7", None),
1318 ("{{0~1~``~0~0~177777777777777777777~``~0~0~h}}", None),
1319 ];
1320
1321 for (sample, expected_output) in samples {
1322 let res = Tera::one_off(sample, &Context::new(), true);
1323 if let Some(output) = expected_output {
1324 assert!(res.is_ok());
1325 assert_eq!(res.unwrap(), output);
1326 } else {
1327 assert!(res.is_err());
1328 }
1329 }
1330 }
1331
1332 #[test]
1333 fn issues_found_fuzzing_conditions_are_fixed() {
1334 let samples: Vec<(&str, Option<&str>)> = vec![
1335 ("C~Q", None),
1337 ("s is V*0", None),
1338 ("x0x::N()", None),
1339 ];
1342
1343 for (sample, expected_output) in samples {
1344 println!("{}, {:?}", sample, expected_output);
1345 let res = Tera::one_off(
1346 &format!("{{% if {} %}}true{{% endif %}}", sample),
1347 &Context::new(),
1348 true,
1349 );
1350 if let Some(output) = expected_output {
1351 assert!(res.is_ok());
1352 assert_eq!(res.unwrap(), output);
1353 } else {
1354 assert!(res.is_err());
1355 }
1356 }
1357 }
1358
1359 #[test]
1361 fn empty_list_on_invalid_glob() {
1362 let tera = Tera::new("\\dev/null/*");
1363 println!("{:?}", tera);
1364 assert!(tera.is_ok());
1365 assert!(tera.unwrap().templates.is_empty());
1366 }
1367}