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;
9use serde::Serialize;
10use serde_json::value::to_value;
11
12use crate::builtins::filters::{array, common, number, object, string, Filter};
13use crate::builtins::functions::{self, Function};
14use crate::builtins::testers::{self, Test};
15use crate::context::Context;
16use crate::errors::{Error, Result};
17use crate::renderer::Renderer;
18use crate::template::Template;
19use crate::utils::escape_html;
20
21pub type EscapeFn = fn(&str) -> String;
23
24pub struct Tera {
26 #[doc(hidden)]
28 glob: Option<String>,
29 #[doc(hidden)]
30 pub templates: HashMap<String, Template>,
31 #[doc(hidden)]
32 pub filters: HashMap<String, Arc<dyn Filter>>,
33 #[doc(hidden)]
34 pub testers: HashMap<String, Arc<dyn Test>>,
35 #[doc(hidden)]
36 pub functions: HashMap<String, Arc<dyn Function>>,
37 #[doc(hidden)]
40 pub autoescape_suffixes: Vec<&'static str>,
41 #[doc(hidden)]
42 escape_fn: EscapeFn,
43}
44
45impl Tera {
46 fn create(dir: &str, parse_only: bool) -> Result<Tera> {
47 if dir.find('*').is_none() {
48 return Err(Error::msg(format!(
49 "Tera expects a glob as input, no * were found in `{}`",
50 dir
51 )));
52 }
53
54 let mut tera = Tera {
55 glob: Some(dir.to_string()),
56 templates: HashMap::new(),
57 filters: HashMap::new(),
58 functions: HashMap::new(),
59 testers: HashMap::new(),
60 autoescape_suffixes: vec![".html", ".htm", ".xml"],
61 escape_fn: escape_html,
62 };
63
64 tera.load_from_glob()?;
65 if !parse_only {
66 tera.build_inheritance_chains()?;
67 tera.check_macro_files()?;
68 }
69 tera.register_tera_filters();
70 tera.register_tera_testers();
71 tera.register_tera_functions();
72 Ok(tera)
73 }
74
75 pub fn new(dir: &str) -> Result<Tera> {
89 Self::create(dir, false)
90 }
91
92 pub fn parse(dir: &str) -> Result<Tera> {
109 Self::create(dir, true)
110 }
111
112 fn load_from_glob(&mut self) -> Result<()> {
114 if self.glob.is_none() {
115 return Err(Error::msg("Tera can only load from glob if a glob is provided"));
116 }
117 self.templates = self
120 .templates
121 .iter()
122 .filter(|&(_, t)| t.from_extend)
123 .map(|(n, t)| (n.clone(), t.clone())) .collect();
125
126 let mut errors = String::new();
127
128 let dir = self.glob.clone().unwrap();
129 let mut parent_dir = dir.split_at(dir.find('*').unwrap()).0;
132 if parent_dir.starts_with("./") {
134 parent_dir = &parent_dir[2..];
135 }
136
137 for entry in glob(&dir).unwrap().filter_map(|e| e.ok()) {
139 let mut path = entry.into_path();
140 if path.is_file() {
142 if path.starts_with("./") {
143 path = path.strip_prefix("./").unwrap().to_path_buf();
144 }
145
146 let filepath = path
147 .strip_prefix(&parent_dir)
148 .unwrap()
149 .to_string_lossy()
150 .replace("\\", "/");
152
153 if let Err(e) = self.add_file(Some(&filepath), path) {
154 use std::error::Error;
155
156 errors += &format!("\n* {}", e);
157 let mut cause = e.source();
158 while let Some(e) = cause {
159 errors += &format!("\n{}", e);
160 cause = e.source();
161 }
162 }
163 }
164 }
165
166 if !errors.is_empty() {
167 return Err(Error::msg(errors));
168 }
169
170 Ok(())
171 }
172
173 fn add_file<P: AsRef<Path>>(&mut self, name: Option<&str>, path: P) -> Result<()> {
177 let path = path.as_ref();
178 let tpl_name = name.unwrap_or_else(|| path.to_str().unwrap());
179
180 let mut f = File::open(path)
181 .map_err(|e| Error::chain(format!("Couldn't open template '{:?}'", path), e))?;
182
183 let mut input = String::new();
184 f.read_to_string(&mut input)
185 .map_err(|e| Error::chain(format!("Failed to read template '{:?}'", path), e))?;
186
187 let tpl = Template::new(tpl_name, Some(path.to_str().unwrap().to_string()), &input)
188 .map_err(|e| Error::chain(format!("Failed to parse {:?}", path), e))?;
189
190 self.templates.insert(tpl_name.to_string(), tpl);
191 Ok(())
192 }
193
194 pub fn build_inheritance_chains(&mut self) -> Result<()> {
203 fn build_chain(
206 templates: &HashMap<String, Template>,
207 start: &Template,
208 template: &Template,
209 mut parents: Vec<String>,
210 ) -> Result<Vec<String>> {
211 if !parents.is_empty() && start.name == template.name {
212 return Err(Error::circular_extend(&start.name, parents));
213 }
214
215 match template.parent {
216 Some(ref p) => match templates.get(p) {
217 Some(parent) => {
218 parents.push(parent.name.clone());
219 build_chain(templates, start, parent, parents)
220 }
221 None => Err(Error::missing_parent(&template.name, &p)),
222 },
223 None => Ok(parents),
224 }
225 }
226
227 let mut tpl_parents = HashMap::new();
229 let mut tpl_block_definitions = HashMap::new();
230 for (name, template) in &self.templates {
231 if template.parent.is_none() && template.blocks.is_empty() {
232 continue;
233 }
234
235 let parents = build_chain(&self.templates, template, template, vec![])?;
236
237 let mut blocks_definitions = HashMap::new();
238 for (block_name, def) in &template.blocks {
239 let mut definitions = vec![(template.name.clone(), def.clone())];
241
242 for parent in &parents {
244 let t = self.get_template(parent)?;
245
246 if let Some(b) = t.blocks.get(block_name) {
247 definitions.push((t.name.clone(), b.clone()));
248 }
249 }
250 blocks_definitions.insert(block_name.clone(), definitions);
251 }
252 tpl_parents.insert(name.clone(), parents);
253 tpl_block_definitions.insert(name.clone(), blocks_definitions);
254 }
255
256 for template in self.templates.values_mut() {
257 if template.parent.is_none() && template.blocks.is_empty() {
259 continue;
260 }
261
262 template.parents = match tpl_parents.remove(&template.name) {
263 Some(parents) => parents,
264 None => vec![],
265 };
266 template.blocks_definitions = match tpl_block_definitions.remove(&template.name) {
267 Some(blocks) => blocks,
268 None => HashMap::new(),
269 };
270 }
271
272 Ok(())
273 }
274
275 pub fn check_macro_files(&self) -> Result<()> {
280 for template in self.templates.values() {
281 for &(ref tpl_name, _) in &template.imported_macro_files {
282 if !self.templates.contains_key(tpl_name) {
283 return Err(Error::msg(format!(
284 "Template `{}` loads macros from `{}` which isn't present in Tera",
285 template.name, tpl_name
286 )));
287 }
288 }
289 }
290
291 Ok(())
292 }
293
294 pub fn render(&self, template_name: &str, context: Context) -> Result<String> {
307 let template = self.get_template(template_name)?;
308 let renderer = Renderer::new(template, self, context.into_json());
309 renderer.render()
310 }
311
312 pub fn render_value<T: Serialize>(&self, template_name: &str, data: &T) -> Result<String> {
321 let value = to_value(data).map_err(Error::json)?;
322 if !value.is_object() {
323 return Err(Error::msg(format!(
324 "Failed to render '{}': context isn't a JSON object. \
325 The value passed needs to be a key-value object: context, struct, hashmap for example.",
326 template_name
327 )));
328 }
329
330 let template = self.get_template(template_name)?;
331 let renderer = Renderer::new(template, self, value);
332 renderer.render()
333 }
334
335 pub fn one_off(input: &str, context: Context, autoescape: bool) -> Result<String> {
348 let mut tera = Tera::default();
349 tera.add_raw_template("one_off", input)?;
350 if autoescape {
351 tera.autoescape_on(vec!["one_off"]);
352 }
353
354 tera.render("one_off", context)
355 }
356
357 pub fn one_off_value<T: Serialize>(input: &str, data: &T, autoescape: bool) -> Result<String> {
370 let mut tera = Tera::default();
371 tera.add_raw_template("one_off", input)?;
372 if autoescape {
373 tera.autoescape_on(vec!["one_off"]);
374 }
375
376 tera.render_value("one_off", data)
377 }
378 #[doc(hidden)]
379 #[inline]
380 pub fn get_template(&self, template_name: &str) -> Result<&Template> {
381 match self.templates.get(template_name) {
382 Some(tpl) => Ok(tpl),
383 None => Err(Error::template_not_found(template_name)),
384 }
385 }
386
387 pub fn add_raw_template(&mut self, name: &str, content: &str) -> Result<()> {
397 let tpl = Template::new(name, None, content)
398 .map_err(|e| Error::chain(format!("Failed to parse '{}'", name), e))?;
399 self.templates.insert(name.to_string(), tpl);
400 self.build_inheritance_chains()?;
401 self.check_macro_files()?;
402 Ok(())
403 }
404
405 pub fn add_raw_templates(&mut self, templates: Vec<(&str, &str)>) -> Result<()> {
417 for (name, content) in templates {
418 let tpl = Template::new(name, None, content)
419 .map_err(|e| Error::chain(format!("Failed to parse '{}'", name), e))?;
420 self.templates.insert(name.to_string(), tpl);
421 }
422 self.build_inheritance_chains()?;
423 self.check_macro_files()?;
424 Ok(())
425 }
426
427 pub fn add_template_file<P: AsRef<Path>>(&mut self, path: P, name: Option<&str>) -> Result<()> {
441 self.add_file(name, path)?;
442 self.build_inheritance_chains()?;
443 self.check_macro_files()?;
444 Ok(())
445 }
446
447 pub fn add_template_files<P: AsRef<Path>>(
460 &mut self,
461 files: Vec<(P, Option<&str>)>,
462 ) -> Result<()> {
463 for (path, name) in files {
464 self.add_file(name, path)?;
465 }
466 self.build_inheritance_chains()?;
467 self.check_macro_files()?;
468 Ok(())
469 }
470
471 #[doc(hidden)]
472 #[inline]
473 pub fn get_filter(&self, filter_name: &str) -> Result<&dyn Filter> {
474 match self.filters.get(filter_name) {
475 Some(fil) => Ok(&**fil),
476 None => Err(Error::filter_not_found(filter_name)),
477 }
478 }
479
480 pub fn register_filter<F: Filter + 'static>(&mut self, name: &str, filter: F) {
488 self.filters.insert(name.to_string(), Arc::new(filter));
489 }
490
491 #[doc(hidden)]
492 #[inline]
493 pub fn get_tester(&self, tester_name: &str) -> Result<&dyn Test> {
494 match self.testers.get(tester_name) {
495 Some(t) => Ok(&**t),
496 None => Err(Error::test_not_found(tester_name)),
497 }
498 }
499
500 pub fn register_tester<T: Test + 'static>(&mut self, name: &str, tester: T) {
508 self.testers.insert(name.to_string(), Arc::new(tester));
509 }
510
511 #[doc(hidden)]
512 #[inline]
513 pub fn get_function(&self, fn_name: &str) -> Result<&dyn Function> {
514 match self.functions.get(fn_name) {
515 Some(t) => Ok(&**t),
516 None => Err(Error::function_not_found(fn_name)),
517 }
518 }
519
520 pub fn register_function<F: Function + 'static>(&mut self, name: &str, function: F) {
528 self.functions.insert(name.to_string(), Arc::new(function));
529 }
530
531 fn register_tera_filters(&mut self) {
532 self.register_filter("upper", string::upper);
533 self.register_filter("lower", string::lower);
534 self.register_filter("trim", string::trim);
535 self.register_filter("truncate", string::truncate);
536 self.register_filter("wordcount", string::wordcount);
537 self.register_filter("replace", string::replace);
538 self.register_filter("capitalize", string::capitalize);
539 self.register_filter("title", string::title);
540 self.register_filter("striptags", string::striptags);
541 self.register_filter("urlencode", string::urlencode);
542 self.register_filter("escape", string::escape_html);
543 self.register_filter("slugify", string::slugify);
544 self.register_filter("addslashes", string::addslashes);
545 self.register_filter("split", string::split);
546
547 self.register_filter("first", array::first);
548 self.register_filter("last", array::last);
549 self.register_filter("nth", array::nth);
550 self.register_filter("join", array::join);
551 self.register_filter("sort", array::sort);
552 self.register_filter("slice", array::slice);
553 self.register_filter("group_by", array::group_by);
554 self.register_filter("filter", array::filter);
555 self.register_filter("concat", array::concat);
556
557 self.register_filter("pluralize", number::pluralize);
558 self.register_filter("round", number::round);
559 self.register_filter("filesizeformat", number::filesizeformat);
560
561 self.register_filter("length", common::length);
562 self.register_filter("reverse", common::reverse);
563 self.register_filter("date", common::date);
564 self.register_filter("json_encode", common::json_encode);
565 self.register_filter("as_str", common::as_str);
566
567 self.register_filter("get", object::get);
568 }
569
570 fn register_tera_testers(&mut self) {
571 self.register_tester("defined", testers::defined);
572 self.register_tester("undefined", testers::undefined);
573 self.register_tester("odd", testers::odd);
574 self.register_tester("even", testers::even);
575 self.register_tester("string", testers::string);
576 self.register_tester("number", testers::number);
577 self.register_tester("divisibleby", testers::divisible_by);
578 self.register_tester("iterable", testers::iterable);
579 self.register_tester("starting_with", testers::starting_with);
580 self.register_tester("ending_with", testers::ending_with);
581 self.register_tester("containing", testers::containing);
582 self.register_tester("matching", testers::matching);
583 }
584
585 fn register_tera_functions(&mut self) {
586 self.register_function("range", functions::range);
587 self.register_function("now", functions::now);
588 self.register_function("throw", functions::throw);
589 }
590
591 pub fn autoescape_on(&mut self, suffixes: Vec<&'static str>) {
604 self.autoescape_suffixes = suffixes;
605 }
606
607 #[doc(hidden)]
608 #[inline]
609 pub fn get_escape_fn(&self) -> &EscapeFn {
610 &self.escape_fn
611 }
612
613 pub fn set_escape_fn(&mut self, function: EscapeFn) {
628 self.escape_fn = function;
629 }
630
631 pub fn reset_escape_fn(&mut self) {
633 self.escape_fn = escape_html;
634 }
635
636 pub fn full_reload(&mut self) -> Result<()> {
643 if self.glob.is_some() {
644 self.load_from_glob()?;
645 } else {
646 return Err(Error::msg("Reloading is only available if you are using a glob"));
647 }
648
649 self.build_inheritance_chains()?;
650 self.check_macro_files()
651 }
652
653 pub fn extend(&mut self, other: &Tera) -> Result<()> {
663 for (name, template) in &other.templates {
664 if !self.templates.contains_key(name) {
665 let mut tpl = template.clone();
666 tpl.from_extend = true;
667 self.templates.insert(name.to_string(), tpl);
668 }
669 }
670
671 for (name, filter) in &other.filters {
672 if !self.filters.contains_key(name) {
673 self.filters.insert(name.to_string(), filter.clone());
674 }
675 }
676
677 for (name, tester) in &other.testers {
678 if !self.testers.contains_key(name) {
679 self.testers.insert(name.to_string(), tester.clone());
680 }
681 }
682
683 self.build_inheritance_chains()?;
684 self.check_macro_files()
685 }
686}
687
688impl Default for Tera {
689 fn default() -> Tera {
690 let mut tera = Tera {
691 glob: None,
692 templates: HashMap::new(),
693 filters: HashMap::new(),
694 testers: HashMap::new(),
695 functions: HashMap::new(),
696 autoescape_suffixes: vec![".html", ".htm", ".xml"],
697 escape_fn: escape_html,
698 };
699
700 tera.register_tera_filters();
701 tera.register_tera_testers();
702 tera.register_tera_functions();
703 tera
704 }
705}
706
707impl fmt::Debug for Tera {
709 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
710 write!(f, "Tera {{")?;
711 writeln!(f, "\n\ttemplates: [")?;
712
713 for template in self.templates.keys() {
714 writeln!(f, "\t\t{},", template)?;
715 }
716 write!(f, "\t]")?;
717 writeln!(f, "\n\tfilters: [")?;
718
719 for filter in self.filters.keys() {
720 writeln!(f, "\t\t{},", filter)?;
721 }
722 write!(f, "\t]")?;
723 writeln!(f, "\n\ttesters: [")?;
724
725 for tester in self.testers.keys() {
726 writeln!(f, "\t\t{},", tester)?;
727 }
728 writeln!(f, "\t]")?;
729
730 writeln!(f, "}}")
731 }
732}
733
734#[cfg(test)]
735mod tests {
736 use tempfile::tempdir;
737
738 use std::collections::HashMap;
739 use std::fs::File;
740
741 use super::Tera;
742 use crate::context::Context;
743 use serde_json::{Map as JsonObject, Value as JsonValue};
744
745 #[test]
746 fn test_get_inheritance_chain() {
747 let mut tera = Tera::default();
748 tera.add_raw_templates(vec![
749 ("a", "{% extends \"b\" %}"),
750 ("b", "{% extends \"c\" %}"),
751 ("c", "{% extends \"d\" %}"),
752 ("d", ""),
753 ])
754 .unwrap();
755
756 assert_eq!(
757 tera.get_template("a").unwrap().parents,
758 vec!["b".to_string(), "c".to_string(), "d".to_string()]
759 );
760
761 assert_eq!(tera.get_template("b").unwrap().parents, vec!["c".to_string(), "d".to_string()]);
762
763 assert_eq!(tera.get_template("c").unwrap().parents, vec!["d".to_string()]);
764
765 assert_eq!(tera.get_template("d").unwrap().parents.len(), 0);
766 }
767
768 #[test]
769 fn test_missing_parent_template() {
770 let mut tera = Tera::default();
771 assert_eq!(
772 tera.add_raw_template("a", "{% extends \"b\" %}").unwrap_err().to_string(),
773 "Template \'a\' is inheriting from \'b\', which doesn\'t exist or isn\'t loaded."
774 );
775 }
776
777 #[test]
778 fn test_circular_extends() {
779 let mut tera = Tera::default();
780 let err = tera
781 .add_raw_templates(vec![("a", "{% extends \"b\" %}"), ("b", "{% extends \"a\" %}")])
782 .unwrap_err();
783
784 assert!(err.to_string().contains("Circular extend detected for template"));
785 }
786
787 #[test]
788 fn test_get_parent_blocks_definition() {
789 let mut tera = Tera::default();
790 tera.add_raw_templates(vec![
791 (
792 "grandparent",
793 "{% block hey %}hello{% endblock hey %} {% block ending %}sincerely{% endblock ending %}",
794 ),
795 (
796 "parent",
797 "{% extends \"grandparent\" %}{% block hey %}hi and grandma says {{ super() }}{% endblock hey %}",
798 ),
799 (
800 "child",
801 "{% extends \"parent\" %}{% block hey %}dad says {{ super() }}{% endblock hey %}{% block ending %}{{ super() }} with love{% endblock ending %}",
802 ),
803 ]).unwrap();
804
805 let hey_definitions =
806 tera.get_template("child").unwrap().blocks_definitions.get("hey").unwrap();
807 assert_eq!(hey_definitions.len(), 3);
808
809 let ending_definitions =
810 tera.get_template("child").unwrap().blocks_definitions.get("ending").unwrap();
811 assert_eq!(ending_definitions.len(), 2);
812 }
813
814 #[test]
815 fn test_get_parent_blocks_definition_nested_block() {
816 let mut tera = Tera::default();
817 tera.add_raw_templates(vec![
818 ("grandparent", "{% block hey %}hello{% endblock hey %}"),
819 (
820 "parent",
821 "{% extends \"grandparent\" %}{% block hey %}hi and grandma says {{ super() }} {% block ending %}sincerely{% endblock ending %}{% endblock hey %}",
822 ),
823 (
824 "child",
825 "{% extends \"parent\" %}{% block hey %}dad says {{ super() }}{% endblock hey %}{% block ending %}{{ super() }} with love{% endblock ending %}",
826 ),
827 ]).unwrap();
828
829 let hey_definitions =
830 tera.get_template("child").unwrap().blocks_definitions.get("hey").unwrap();
831 assert_eq!(hey_definitions.len(), 3);
832
833 let ending_definitions =
834 tera.get_template("parent").unwrap().blocks_definitions.get("ending").unwrap();
835 assert_eq!(ending_definitions.len(), 1);
836 }
837
838 #[test]
839 fn test_can_autoescape_one_off_template() {
840 let mut context = Context::new();
841 context.insert("greeting", &"<p>");
842 let result = Tera::one_off("{{ greeting }} world", context, true).unwrap();
843
844 assert_eq!(result, "<p> world");
845 }
846
847 #[test]
848 fn test_can_disable_autoescape_one_off_template() {
849 let mut context = Context::new();
850 context.insert("greeting", &"<p>");
851 let result = Tera::one_off("{{ greeting }} world", context, false).unwrap();
852
853 assert_eq!(result, "<p> world");
854 }
855
856 #[test]
857 fn test_set_escape_function() {
858 let escape_c_string: super::EscapeFn = |input| {
859 let mut output = String::with_capacity(input.len() * 2);
860 for c in input.chars() {
861 match c {
862 '\'' => output.push_str("\\'"),
863 '\"' => output.push_str("\\\""),
864 '\\' => output.push_str("\\\\"),
865 '\n' => output.push_str("\\n"),
866 '\r' => output.push_str("\\r"),
867 '\t' => output.push_str("\\t"),
868 _ => output.push(c),
869 }
870 }
871 output
872 };
873 let mut tera = Tera::default();
874 tera.add_raw_template("foo", "\"{{ content }}\"").unwrap();
875 tera.autoescape_on(vec!["foo"]);
876 tera.set_escape_fn(escape_c_string);
877 let mut context = Context::new();
878 context.insert("content", &"Hello\n\'world\"!");
879 let result = tera.render("foo", context).unwrap();
880 assert_eq!(result, r#""Hello\n\'world\"!""#);
881 }
882
883 #[test]
884 fn test_reset_escape_function() {
885 let no_escape: super::EscapeFn = |input| input.to_string();
886 let mut tera = Tera::default();
887 tera.add_raw_template("foo", "{{ content }}").unwrap();
888 tera.autoescape_on(vec!["foo"]);
889 tera.set_escape_fn(no_escape);
890 tera.reset_escape_fn();
891 let mut context = Context::new();
892 context.insert("content", &"Hello\n\'world\"!");
893 let result = tera.render("foo", context).unwrap();
894 assert_eq!(result, "Hello\n'world"!");
895 }
896
897 #[test]
898 fn test_value_one_off_template() {
899 let mut context = JsonObject::new();
900 context.insert("greeting".to_string(), JsonValue::String("Good morning".to_string()));
901 let result = Tera::one_off_value("{{ greeting }} world", &context, true).unwrap();
902
903 assert_eq!(result, "Good morning world");
904 }
905
906 #[test]
907 fn test_extend_no_overlap() {
908 let mut my_tera = Tera::default();
909 my_tera
910 .add_raw_templates(vec![
911 ("one", "{% block hey %}1{% endblock hey %}"),
912 ("two", "{% block hey %}2{% endblock hey %}"),
913 ("three", "{% block hey %}3{% endblock hey %}"),
914 ])
915 .unwrap();
916
917 let mut framework_tera = Tera::default();
918 framework_tera.add_raw_templates(vec![("four", "Framework X")]).unwrap();
919
920 my_tera.extend(&framework_tera).unwrap();
921 assert_eq!(my_tera.templates.len(), 4);
922 let result = my_tera.render("four", Context::default()).unwrap();
923 assert_eq!(result, "Framework X");
924 }
925
926 #[test]
927 fn test_extend_with_overlap() {
928 let mut my_tera = Tera::default();
929 my_tera
930 .add_raw_templates(vec![
931 ("one", "MINE"),
932 ("two", "{% block hey %}2{% endblock hey %}"),
933 ("three", "{% block hey %}3{% endblock hey %}"),
934 ])
935 .unwrap();
936
937 let mut framework_tera = Tera::default();
938 framework_tera
939 .add_raw_templates(vec![("one", "FRAMEWORK"), ("four", "Framework X")])
940 .unwrap();
941
942 my_tera.extend(&framework_tera).unwrap();
943 assert_eq!(my_tera.templates.len(), 4);
944 let result = my_tera.render("one", Context::default()).unwrap();
945 assert_eq!(result, "MINE");
946 }
947
948 #[test]
949 fn test_extend_new_filter() {
950 let mut my_tera = Tera::default();
951 let mut framework_tera = Tera::default();
952 framework_tera.register_filter("hello", |_: &JsonValue, _: &HashMap<String, JsonValue>| {
953 Ok(JsonValue::Number(10.into()))
954 });
955 my_tera.extend(&framework_tera).unwrap();
956 assert!(my_tera.filters.contains_key("hello"));
957 }
958
959 #[test]
960 fn test_extend_new_tester() {
961 let mut my_tera = Tera::default();
962 let mut framework_tera = Tera::default();
963 framework_tera.register_tester("hello", |_: Option<&JsonValue>, _: &[JsonValue]| Ok(true));
964 my_tera.extend(&framework_tera).unwrap();
965 assert!(my_tera.testers.contains_key("hello"));
966 }
967
968 #[test]
969 fn can_load_from_glob() {
970 let tera = Tera::new("examples/basic/templates/**/*").unwrap();
971 assert!(tera.get_template("base.html").is_ok());
972 }
973
974 #[test]
975 fn can_load_from_glob_with_patterns() {
976 let tera = Tera::new("examples/basic/templates/**/*.{html, xml}").unwrap();
977 assert!(tera.get_template("base.html").is_ok());
978 }
979
980 #[test]
981 fn full_reload_with_glob() {
982 let mut tera = Tera::new("examples/basic/templates/**/*").unwrap();
983 tera.full_reload().unwrap();
984
985 assert!(tera.get_template("base.html").is_ok());
986 }
987
988 #[test]
989 fn full_reload_with_glob_after_extending() {
990 let mut tera = Tera::new("examples/basic/templates/**/*").unwrap();
991 let mut framework_tera = Tera::default();
992 framework_tera
993 .add_raw_templates(vec![("one", "FRAMEWORK"), ("four", "Framework X")])
994 .unwrap();
995 tera.extend(&framework_tera).unwrap();
996 tera.full_reload().unwrap();
997
998 assert!(tera.get_template("base.html").is_ok());
999 assert!(tera.get_template("one").is_ok());
1000 }
1001
1002 #[should_panic]
1003 #[test]
1004 fn test_can_only_parse_templates() {
1005 let mut tera = Tera::parse("examples/basic/templates/**/*").unwrap();
1006 for tpl in tera.templates.values_mut() {
1007 tpl.name = format!("a-theme/templates/{}", tpl.name);
1008 if let Some(ref parent) = tpl.parent.clone() {
1009 tpl.parent = Some(format!("a-theme/templates/{}", parent));
1010 }
1011 }
1012 tera.build_inheritance_chains().unwrap();
1015 }
1016
1017 #[test]
1019 fn glob_work_with_absolute_paths() {
1020 let tmp_dir = tempdir().expect("create temp dir");
1021 let cwd = tmp_dir.path().canonicalize().unwrap();
1022 File::create(cwd.join("hey.html")).expect("Failed to create a test file");
1023 File::create(cwd.join("ho.html")).expect("Failed to create a test file");
1024 let glob = cwd.join("*.html").into_os_string().into_string().unwrap();
1025 let tera = Tera::new(&glob).expect("Couldn't build Tera instance");
1026 assert_eq!(tera.templates.len(), 2);
1027 }
1028}