1 use std
::collections
::HashMap
;
4 use std
::io
::prelude
::*;
8 use globwalk
::glob_builder
;
10 use crate::builtins
::filters
::{array, common, number, object, string, Filter}
;
11 use crate::builtins
::functions
::{self, Function}
;
12 use crate::builtins
::testers
::{self, Test}
;
13 use crate::context
::Context
;
14 use crate::errors
::{Error, Result}
;
15 use crate::renderer
::Renderer
;
16 use crate::template
::Template
;
17 use crate::utils
::escape_html
;
19 /// The of the the template used for `Tera::render_str` and `Tera::one_off`.
20 const ONE_OFF_TEMPLATE_NAME
: &str = "__tera_one_off";
22 /// The escape function type definition
23 pub type EscapeFn
= fn(&str) -> String
;
25 /// The main point of interaction in this library.
28 // The glob used in `Tera::new`, None if Tera was instantiated differently
32 pub templates
: HashMap
<String
, Template
>,
34 pub filters
: HashMap
<String
, Arc
<dyn Filter
>>,
36 pub testers
: HashMap
<String
, Arc
<dyn Test
>>,
38 pub functions
: HashMap
<String
, Arc
<dyn Function
>>,
39 // Which extensions does Tera automatically autoescape on.
40 // Defaults to [".html", ".htm", ".xml"]
42 pub autoescape_suffixes
: Vec
<&'
static str>,
48 fn create(dir
: &str, parse_only
: bool
) -> Result
<Tera
> {
49 if dir
.find('
*'
).is_none() {
50 return Err(Error
::msg(format
!(
51 "Tera expects a glob as input, no * were found in `{}`",
57 glob
: Some(dir
.to_string()),
58 templates
: HashMap
::new(),
59 filters
: HashMap
::new(),
60 functions
: HashMap
::new(),
61 testers
: HashMap
::new(),
62 autoescape_suffixes
: vec
![".html", ".htm", ".xml"],
63 escape_fn
: escape_html
,
66 tera
.load_from_glob()?
;
68 tera
.build_inheritance_chains()?
;
69 tera
.check_macro_files()?
;
71 tera
.register_tera_filters();
72 tera
.register_tera_testers();
73 tera
.register_tera_functions();
77 /// Create a new instance of Tera, containing all the parsed templates found in the `dir` glob
80 ///match Tera::new("templates/**/*") {
83 /// println!("Parsing error(s): {}", e);
84 /// ::std::process::exit(1);
88 pub fn new(dir
: &str) -> Result
<Tera
> {
89 Self::create(dir
, false)
92 /// Create a new instance of Tera, containing all the parsed templates found in the `dir` glob
93 /// The difference with `Tera::new` is that it won't build the inheritance chains automatically
94 /// so you are free to modify the templates if you need to.
95 /// You will NOT get a working Tera instance using `Tera::parse`, you will need to call
96 /// `tera.build_inheritance_chains()` to make it usable
99 ///let mut tera = match Tera::parse("templates/**/*") {
102 /// println!("Parsing error(s): {}", e);
103 /// ::std::process::exit(1);
106 ///tera.build_inheritance_chains()?;
108 pub fn parse(dir
: &str) -> Result
<Tera
> {
109 Self::create(dir
, true)
112 /// Loads all the templates found in the glob that was given to Tera::new
113 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"));
117 // We want to preserve templates that have been added through
118 // Tera::extend so we only keep those
119 self.templates
= self
122 .filter(|&(_
, t
)| t
.from_extend
)
123 .map(|(n
, t
)| (n
.clone(), t
.clone())) // TODO: avoid that clone
126 let mut errors
= String
::new();
128 let dir
= self.glob
.clone().unwrap();
129 // We clean the filename by removing the dir given
130 // to Tera so users don't have to prefix everytime
131 let mut parent_dir
= dir
.split_at(dir
.find('
*'
).unwrap()).0;
132 // Remove `./` from the glob if used as it would cause an error in strip_prefix
133 if parent_dir
.starts_with("./") {
134 parent_dir
= &parent_dir
[2..];
137 // We are parsing all the templates on instantiation
138 for entry
in glob_builder(&dir
)
142 .filter_map(std
::result
::Result
::ok
)
144 let mut path
= entry
.into_path();
145 // We only care about actual files
147 if path
.starts_with("./") {
148 path
= path
.strip_prefix("./").unwrap().to_path_buf();
152 .strip_prefix(&parent_dir
)
155 // unify on forward slash
158 if let Err(e
) = self.add_file(Some(&filepath
), path
) {
159 use std
::error
::Error
;
161 errors
+= &format
!("\n* {}", e
);
162 let mut cause
= e
.source();
163 while let Some(e
) = cause
{
164 errors
+= &format
!("\n{}", e
);
171 if !errors
.is_empty() {
172 return Err(Error
::msg(errors
));
178 // Add a template from a path: reads the file and parses it.
179 // This will return an error if the template is invalid and doesn't check the validity of
180 // inheritance chains.
181 fn add_file
<P
: AsRef
<Path
>>(&mut self, name
: Option
<&str>, path
: P
) -> Result
<()> {
182 let path
= path
.as_ref();
183 let tpl_name
= name
.unwrap_or_else(|| path
.to_str().unwrap());
185 let mut f
= File
::open(path
)
186 .map_err(|e
| Error
::chain(format
!("Couldn't open template '{:?}'", path
), e
))?
;
188 let mut input
= String
::new();
189 f
.read_to_string(&mut input
)
190 .map_err(|e
| Error
::chain(format
!("Failed to read template '{:?}'", path
), e
))?
;
192 let tpl
= Template
::new(tpl_name
, Some(path
.to_str().unwrap().to_string()), &input
)
193 .map_err(|e
| Error
::chain(format
!("Failed to parse {:?}", path
), e
))?
;
195 self.templates
.insert(tpl_name
.to_string(), tpl
);
199 /// We need to know the hierarchy of templates to be able to render multiple extends level
200 /// This happens at compile-time to avoid checking it every time we want to render a template
201 /// This also checks for soundness issues in the inheritance chains, such as missing template or
202 /// circular extends.
203 /// It also builds the block inheritance chain and detects when super() is called in a place
204 /// where it can't possibly work
206 /// You generally don't need to call that yourself, unless you used `Tera::parse`
207 pub fn build_inheritance_chains(&mut self) -> Result
<()> {
208 // Recursive fn that finds all the parents and put them in an ordered Vec from closest to first parent
211 templates
: &HashMap
<String
, Template
>,
214 mut parents
: Vec
<String
>,
215 ) -> Result
<Vec
<String
>> {
216 if !parents
.is_empty() && start
.name
== template
.name
{
217 return Err(Error
::circular_extend(&start
.name
, parents
));
220 match template
.parent
{
221 Some(ref p
) => match templates
.get(p
) {
223 parents
.push(parent
.name
.clone());
224 build_chain(templates
, start
, parent
, parents
)
226 None
=> Err(Error
::missing_parent(&template
.name
, &p
)),
232 // TODO: if we can rewrite the 2 loops below to be only one loop, that'd be great
233 let mut tpl_parents
= HashMap
::new();
234 let mut tpl_block_definitions
= HashMap
::new();
235 for (name
, template
) in &self.templates
{
236 if template
.parent
.is_none() && template
.blocks
.is_empty() {
240 let parents
= build_chain(&self.templates
, template
, template
, vec
![])?
;
242 let mut blocks_definitions
= HashMap
::new();
243 for (block_name
, def
) in &template
.blocks
{
244 // push our own block first
245 let mut definitions
= vec
![(template
.name
.clone(), def
.clone())];
247 // and then see if our parents have it
248 for parent
in &parents
{
249 let t
= self.get_template(parent
)?
;
251 if let Some(b
) = t
.blocks
.get(block_name
) {
252 definitions
.push((t
.name
.clone(), b
.clone()));
255 blocks_definitions
.insert(block_name
.clone(), definitions
);
257 tpl_parents
.insert(name
.clone(), parents
);
258 tpl_block_definitions
.insert(name
.clone(), blocks_definitions
);
261 for template
in self.templates
.values_mut() {
262 // Simple template: no inheritance or blocks -> nothing to do
263 if template
.parent
.is_none() && template
.blocks
.is_empty() {
267 template
.parents
= match tpl_parents
.remove(&template
.name
) {
268 Some(parents
) => parents
,
271 template
.blocks_definitions
= match tpl_block_definitions
.remove(&template
.name
) {
272 Some(blocks
) => blocks
,
273 None
=> HashMap
::new(),
280 /// We keep track of macro files loaded in each Template so we can know whether one or them
281 /// is missing and error accordingly before the user tries to render a template.
283 /// As with `self::build_inheritance_chains`, you don't usually need to call that yourself.
284 pub fn check_macro_files(&self) -> Result
<()> {
285 for template
in self.templates
.values() {
286 for &(ref tpl_name
, _
) in &template
.imported_macro_files
{
287 if !self.templates
.contains_key(tpl_name
) {
288 return Err(Error
::msg(format
!(
289 "Template `{}` loads macros from `{}` which isn't present in Tera",
290 template
.name
, tpl_name
299 /// Renders a Tera template given a `tera::Context`,
301 /// To render a template with an empty context, simply pass a new `tera::Context` object
304 /// // Rendering a template with a normal context
305 /// let mut context = Context::new();
306 /// context.insert("age", 18);
307 /// tera.render("hello.html", context);
308 /// // Rendering a template with an empty context
309 /// tera.render("hello.html", Context::new());
311 pub fn render(&self, template_name
: &str, context
: &Context
) -> Result
<String
> {
312 let template
= self.get_template(template_name
)?
;
313 let renderer
= Renderer
::new(template
, self, context
);
317 /// Renders a Tera template given a `tera::Context` to a [std::io::Write],
319 /// The only difference from [Self::render] is that this version doesn't convert buffer to a String,
320 /// allowing to render directly to anything that implements [std::io::Write].
322 /// Any i/o error will be reported in the result.
325 /// // Rendering a template to an internal buffer
326 /// let mut buffer = Vec::new();
327 /// let mut context = Context::new();
328 /// context.insert("age", 18);
329 /// tera.render_to("hello.html", context, &mut buffer);
337 let template
= self.get_template(template_name
)?
;
338 let renderer
= Renderer
::new(template
, self, context
);
339 renderer
.render_to(write
)
342 /// Renders a one off template (for example a template coming from a user
343 /// input) given a `Context` and an instance of Tera. This allows you to
344 /// render templates using custom filters or functions.
346 /// Any errors will mention the `__tera_one_off` template: this is the name
347 /// given to the template by Tera.
350 /// let mut context = Context::new();
351 /// context.insert("greeting", &"Hello");
352 /// let string = tera.render_str("{{ greeting }} World!", &context)?;
353 /// assert_eq!(string, "Hello World!");
355 pub fn render_str(&mut self, input
: &str, context
: &Context
) -> Result
<String
> {
356 self.add_raw_template(ONE_OFF_TEMPLATE_NAME
, input
)?
;
357 let result
= self.render(ONE_OFF_TEMPLATE_NAME
, &context
);
358 self.templates
.remove(ONE_OFF_TEMPLATE_NAME
);
362 /// Renders a one off template (for example a template coming from a user input) given a `Context`
364 /// This creates a separate instance of Tera with no possibilities of adding custom filters
365 /// or testers, parses the template and render it immediately.
366 /// Any errors will mention the `__tera_one_off` template: this is the name given to the template by
370 /// let mut context = Context::new();
371 /// context.insert("greeting", &"hello");
372 /// Tera::one_off("{{ greeting }} world", &context, true);
374 pub fn one_off(input
: &str, context
: &Context
, autoescape
: bool
) -> Result
<String
> {
375 let mut tera
= Tera
::default();
378 tera
.autoescape_on(vec
![ONE_OFF_TEMPLATE_NAME
]);
381 tera
.render_str(input
, context
)
386 pub fn get_template(&self, template_name
: &str) -> Result
<&Template
> {
387 match self.templates
.get(template_name
) {
388 Some(tpl
) => Ok(tpl
),
389 None
=> Err(Error
::template_not_found(template_name
)),
393 /// Returns an iterator over the names of all registered templates in an
394 /// unspecified order.
401 /// let mut tera = Tera::default();
402 /// tera.add_raw_template("foo", "{{ hello }}");
403 /// tera.add_raw_template("another-one.html", "contents go here");
405 /// let names: Vec<_> = tera.get_template_names().collect();
406 /// assert_eq!(names.len(), 2);
407 /// assert!(names.contains(&"foo"));
408 /// assert!(names.contains(&"another-one.html"));
411 pub fn get_template_names(&self) -> impl Iterator
<Item
= &str> {
412 self.templates
.keys().map(|s
| s
.as_str())
415 /// Add a single template to the Tera instance
417 /// This will error if the inheritance chain can't be built, such as adding a child
418 /// template without the parent one.
419 /// If you want to add several templates, use [Tera::add_templates](struct.Tera.html#method.add_raw_templates)
422 /// tera.add_raw_template("new.html", "Blabla");
424 pub fn add_raw_template(&mut self, name
: &str, content
: &str) -> Result
<()> {
425 let tpl
= Template
::new(name
, None
, content
)
426 .map_err(|e
| Error
::chain(format
!("Failed to parse '{}'", name
), e
))?
;
427 self.templates
.insert(name
.to_string(), tpl
);
428 self.build_inheritance_chains()?
;
429 self.check_macro_files()?
;
433 /// Add all the templates given to the Tera instance
435 /// This will error if the inheritance chain can't be built, such as adding a child
436 /// template without the parent one.
439 /// tera.add_raw_templates(vec![
440 /// ("new.html", "blabla"),
441 /// ("new2.html", "hello"),
444 pub fn add_raw_templates
<I
, N
, C
>(&mut self, templates
: I
) -> Result
<()>
446 I
: IntoIterator
<Item
= (N
, C
)>,
450 for (name
, content
) in templates
{
451 let name
= name
.as_ref();
452 let tpl
= Template
::new(name
, None
, content
.as_ref())
453 .map_err(|e
| Error
::chain(format
!("Failed to parse '{}'", name
), e
))?
;
454 self.templates
.insert(name
.to_string(), tpl
);
456 self.build_inheritance_chains()?
;
457 self.check_macro_files()?
;
461 /// Add a single template from a path to the Tera instance. The default name for the template is
462 /// the path given, but this can be renamed with the `name` parameter
464 /// This will error if the inheritance chain can't be built, such as adding a child
465 /// template without the parent one.
466 /// If you want to add several file, use [Tera::add_template_files](struct.Tera.html#method.add_template_files)
469 /// // Use path as name
470 /// tera.add_template_file(path, None);
472 /// tera.add_template_file(path, Some("index"));
474 pub fn add_template_file
<P
: AsRef
<Path
>>(&mut self, path
: P
, name
: Option
<&str>) -> Result
<()> {
475 self.add_file(name
, path
)?
;
476 self.build_inheritance_chains()?
;
477 self.check_macro_files()?
;
481 /// Add several templates from paths to the Tera instance. The default name for the template is
482 /// the path given, but this can be renamed with the second parameter of the tuple
484 /// This will error if the inheritance chain can't be built, such as adding a child
485 /// template without the parent one.
488 /// tera.add_template_files(vec![
489 /// (path1, None), // this template will have the value of path1 as name
490 /// (path2, Some("hey")), // this template will have `hey` as name
493 pub fn add_template_files
<I
, P
, N
>(&mut self, files
: I
) -> Result
<()>
495 I
: IntoIterator
<Item
= (P
, Option
<N
>)>,
499 for (path
, name
) in files
{
500 self.add_file(name
.as_ref().map(AsRef
::as_ref
), path
)?
;
502 self.build_inheritance_chains()?
;
503 self.check_macro_files()?
;
509 pub fn get_filter(&self, filter_name
: &str) -> Result
<&dyn Filter
> {
510 match self.filters
.get(filter_name
) {
511 Some(fil
) => Ok(&**fil
),
512 None
=> Err(Error
::filter_not_found(filter_name
)),
516 /// Register a filter with Tera.
518 /// If a filter with that name already exists, it will be overwritten
521 /// tera.register_filter("upper", string::upper);
523 pub fn register_filter
<F
: Filter
+ '
static>(&mut self, name
: &str, filter
: F
) {
524 self.filters
.insert(name
.to_string(), Arc
::new(filter
));
529 pub fn get_tester(&self, tester_name
: &str) -> Result
<&dyn Test
> {
530 match self.testers
.get(tester_name
) {
532 None
=> Err(Error
::test_not_found(tester_name
)),
536 /// Register a tester with Tera.
538 /// If a tester with that name already exists, it will be overwritten
541 /// tera.register_tester("odd", testers::odd);
543 pub fn register_tester
<T
: Test
+ '
static>(&mut self, name
: &str, tester
: T
) {
544 self.testers
.insert(name
.to_string(), Arc
::new(tester
));
549 pub fn get_function(&self, fn_name
: &str) -> Result
<&dyn Function
> {
550 match self.functions
.get(fn_name
) {
552 None
=> Err(Error
::function_not_found(fn_name
)),
556 /// Register a function with Tera.
558 /// If a function with that name already exists, it will be overwritten
561 /// tera.register_function("range", range);
563 pub fn register_function
<F
: Function
+ '
static>(&mut self, name
: &str, function
: F
) {
564 self.functions
.insert(name
.to_string(), Arc
::new(function
));
567 fn register_tera_filters(&mut self) {
568 self.register_filter("upper", string
::upper
);
569 self.register_filter("lower", string
::lower
);
570 self.register_filter("trim", string
::trim
);
571 self.register_filter("trim_start", string
::trim_start
);
572 self.register_filter("trim_end", string
::trim_end
);
573 self.register_filter("trim_start_matches", string
::trim_start_matches
);
574 self.register_filter("trim_end_matches", string
::trim_end_matches
);
575 #[cfg(feature = "builtins")]
576 self.register_filter("truncate", string
::truncate
);
577 self.register_filter("wordcount", string
::wordcount
);
578 self.register_filter("replace", string
::replace
);
579 self.register_filter("capitalize", string
::capitalize
);
580 self.register_filter("title", string
::title
);
581 self.register_filter("linebreaksbr", string
::linebreaksbr
);
582 self.register_filter("striptags", string
::striptags
);
583 #[cfg(feature = "builtins")]
584 self.register_filter("urlencode", string
::urlencode
);
585 #[cfg(feature = "builtins")]
586 self.register_filter("urlencode_strict", string
::urlencode_strict
);
587 self.register_filter("escape", string
::escape_html
);
588 self.register_filter("escape_xml", string
::escape_xml
);
589 #[cfg(feature = "builtins")]
590 self.register_filter("slugify", string
::slugify
);
591 self.register_filter("addslashes", string
::addslashes
);
592 self.register_filter("split", string
::split
);
593 self.register_filter("int", string
::int
);
594 self.register_filter("float", string
::float
);
596 self.register_filter("first", array
::first
);
597 self.register_filter("last", array
::last
);
598 self.register_filter("nth", array
::nth
);
599 self.register_filter("join", array
::join
);
600 self.register_filter("sort", array
::sort
);
601 self.register_filter("unique", array
::unique
);
602 self.register_filter("slice", array
::slice
);
603 self.register_filter("group_by", array
::group_by
);
604 self.register_filter("filter", array
::filter
);
605 self.register_filter("map", array
::map
);
606 self.register_filter("concat", array
::concat
);
608 self.register_filter("pluralize", number
::pluralize
);
609 self.register_filter("round", number
::round
);
611 #[cfg(feature = "builtins")]
612 self.register_filter("filesizeformat", number
::filesizeformat
);
614 self.register_filter("length", common
::length
);
615 self.register_filter("reverse", common
::reverse
);
616 #[cfg(feature = "builtins")]
617 self.register_filter("date", common
::date
);
618 self.register_filter("json_encode", common
::json_encode
);
619 self.register_filter("as_str", common
::as_str
);
621 self.register_filter("get", object
::get
);
624 fn register_tera_testers(&mut self) {
625 self.register_tester("defined", testers
::defined
);
626 self.register_tester("undefined", testers
::undefined
);
627 self.register_tester("odd", testers
::odd
);
628 self.register_tester("even", testers
::even
);
629 self.register_tester("string", testers
::string
);
630 self.register_tester("number", testers
::number
);
631 self.register_tester("divisibleby", testers
::divisible_by
);
632 self.register_tester("iterable", testers
::iterable
);
633 self.register_tester("object", testers
::object
);
634 self.register_tester("starting_with", testers
::starting_with
);
635 self.register_tester("ending_with", testers
::ending_with
);
636 self.register_tester("containing", testers
::containing
);
637 self.register_tester("matching", testers
::matching
);
640 fn register_tera_functions(&mut self) {
641 self.register_function("range", functions
::range
);
642 #[cfg(feature = "builtins")]
643 self.register_function("now", functions
::now
);
644 self.register_function("throw", functions
::throw
);
645 #[cfg(feature = "builtins")]
646 self.register_function("get_random", functions
::get_random
);
647 self.register_function("get_env", functions
::get_env
);
650 /// Select which suffix(es) to automatically do HTML escaping on,
651 ///`[".html", ".htm", ".xml"]` by default.
653 /// Only call this function if you wish to change the defaults.
657 /// // escape only files ending with `.php.html`
658 /// tera.autoescape_on(vec![".php.html"]);
659 /// // disable autoescaping completely
660 /// tera.autoescape_on(vec![]);
662 pub fn autoescape_on(&mut self, suffixes
: Vec
<&'
static str>) {
663 self.autoescape_suffixes
= suffixes
;
668 pub fn get_escape_fn(&self) -> &EscapeFn
{
672 /// Set user-defined function which applied to a rendered content.
675 /// fn escape_c_string(input: &str) -> String { ... }
677 /// // make valid C string literal
678 /// tera.set_escape_fn(escape_c_string);
679 /// tera.add_raw_template("foo", "\"{{ content }}\"").unwrap();
680 /// tera.autoescape_on(vec!["foo"]);
681 /// let mut context = Context::new();
682 /// context.insert("content", &"Hello\n\'world\"!");
683 /// let result = tera.render("foo", &context).unwrap();
684 /// assert_eq!(result, r#""Hello\n\'world\"!""#);
686 pub fn set_escape_fn(&mut self, function
: EscapeFn
) {
687 self.escape_fn
= function
;
690 /// Reset escape function to default `tera::escape_html`.
691 pub fn reset_escape_fn(&mut self) {
692 self.escape_fn
= escape_html
;
695 /// Re-parse all templates found in the glob given to Tera
696 /// Use this when you are watching a directory and want to reload everything,
697 /// for example when a file is added.
699 /// If you are adding templates without using a glob, we can't know when a template
700 /// is deleted, which would result in an error if we are trying to reload that file
701 pub fn full_reload(&mut self) -> Result
<()> {
702 if self.glob
.is_some() {
703 self.load_from_glob()?
;
705 return Err(Error
::msg("Reloading is only available if you are using a glob"));
708 self.build_inheritance_chains()?
;
709 self.check_macro_files()
712 /// Use that method when you want to add a given Tera instance templates/filters/testers
713 /// to your own. If a template/filter/tester with the same name already exists in your instance,
714 /// it will not be overwritten.
717 /// // add all the templates from FRAMEWORK_TERA
718 /// // except the ones that have an identical name to the ones in `my_tera`
719 /// my_tera.extend(&FRAMEWORK_TERA);
721 pub fn extend(&mut self, other
: &Tera
) -> Result
<()> {
722 for (name
, template
) in &other
.templates
{
723 if !self.templates
.contains_key(name
) {
724 let mut tpl
= template
.clone();
725 tpl
.from_extend
= true;
726 self.templates
.insert(name
.to_string(), tpl
);
730 for (name
, filter
) in &other
.filters
{
731 if !self.filters
.contains_key(name
) {
732 self.filters
.insert(name
.to_string(), filter
.clone());
736 for (name
, tester
) in &other
.testers
{
737 if !self.testers
.contains_key(name
) {
738 self.testers
.insert(name
.to_string(), tester
.clone());
742 self.build_inheritance_chains()?
;
743 self.check_macro_files()
747 impl Default
for Tera
{
748 fn default() -> Tera
{
749 let mut tera
= Tera
{
751 templates
: HashMap
::new(),
752 filters
: HashMap
::new(),
753 testers
: HashMap
::new(),
754 functions
: HashMap
::new(),
755 autoescape_suffixes
: vec
![".html", ".htm", ".xml"],
756 escape_fn
: escape_html
,
759 tera
.register_tera_filters();
760 tera
.register_tera_testers();
761 tera
.register_tera_functions();
766 // Needs a manual implementation since borrows in Fn's don't implement Debug.
767 impl fmt
::Debug
for Tera
{
768 fn fmt(&self, f
: &mut fmt
::Formatter
) -> fmt
::Result
{
769 write
!(f
, "Tera {{")?
;
770 writeln
!(f
, "\n\ttemplates: [")?
;
772 for template
in self.templates
.keys() {
773 writeln
!(f
, "\t\t{},", template
)?
;
776 writeln
!(f
, "\n\tfilters: [")?
;
778 for filter
in self.filters
.keys() {
779 writeln
!(f
, "\t\t{},", filter
)?
;
782 writeln
!(f
, "\n\ttesters: [")?
;
784 for tester
in self.testers
.keys() {
785 writeln
!(f
, "\t\t{},", tester
)?
;
795 use tempfile
::tempdir
;
797 use std
::collections
::HashMap
;
801 use crate::context
::Context
;
802 use serde_json
::{json, Value as JsonValue}
;
805 fn test_get_inheritance_chain() {
806 let mut tera
= Tera
::default();
807 tera
.add_raw_templates(vec
![
808 ("a", "{% extends \"b\" %}"),
809 ("b", "{% extends \"c\" %}"),
810 ("c", "{% extends \"d\" %}"),
816 tera
.get_template("a").unwrap().parents
,
817 vec
!["b".to_string(), "c".to_string(), "d".to_string()]
819 assert_eq
!(tera
.get_template("b").unwrap().parents
, vec
!["c".to_string(), "d".to_string()]);
820 assert_eq
!(tera
.get_template("c").unwrap().parents
, vec
!["d".to_string()]);
821 assert_eq
!(tera
.get_template("d").unwrap().parents
.len(), 0);
825 fn test_missing_parent_template() {
826 let mut tera
= Tera
::default();
828 tera
.add_raw_template("a", "{% extends \"b\" %}").unwrap_err().to_string(),
829 "Template \'a\' is inheriting from \'b\', which doesn\'t exist or isn\'t loaded."
834 fn test_circular_extends() {
835 let mut tera
= Tera
::default();
837 .add_raw_templates(vec
![("a", "{% extends \"b\" %}"), ("b", "{% extends \"a\" %}")])
840 assert
!(err
.to_string().contains("Circular extend detected for template"));
844 fn test_get_parent_blocks_definition() {
845 let mut tera
= Tera
::default();
846 tera
.add_raw_templates(vec
![
849 "{% block hey %}hello{% endblock hey %} {% block ending %}sincerely{% endblock ending %}",
853 "{% extends \"grandparent\" %}{% block hey %}hi and grandma says {{ super() }}{% endblock hey %}",
857 "{% extends \"parent\" %}{% block hey %}dad says {{ super() }}{% endblock hey %}{% block ending %}{{ super() }} with love{% endblock ending %}",
861 let hey_definitions
=
862 tera
.get_template("child").unwrap().blocks_definitions
.get("hey").unwrap();
863 assert_eq
!(hey_definitions
.len(), 3);
865 let ending_definitions
=
866 tera
.get_template("child").unwrap().blocks_definitions
.get("ending").unwrap();
867 assert_eq
!(ending_definitions
.len(), 2);
871 fn test_get_parent_blocks_definition_nested_block() {
872 let mut tera
= Tera
::default();
873 tera
.add_raw_templates(vec
![
874 ("grandparent", "{% block hey %}hello{% endblock hey %}"),
877 "{% extends \"grandparent\" %}{% block hey %}hi and grandma says {{ super() }} {% block ending %}sincerely{% endblock ending %}{% endblock hey %}",
881 "{% extends \"parent\" %}{% block hey %}dad says {{ super() }}{% endblock hey %}{% block ending %}{{ super() }} with love{% endblock ending %}",
885 let hey_definitions
=
886 tera
.get_template("child").unwrap().blocks_definitions
.get("hey").unwrap();
887 assert_eq
!(hey_definitions
.len(), 3);
889 let ending_definitions
=
890 tera
.get_template("parent").unwrap().blocks_definitions
.get("ending").unwrap();
891 assert_eq
!(ending_definitions
.len(), 1);
895 fn test_can_autoescape_one_off_template() {
896 let mut context
= Context
::new();
897 context
.insert("greeting", &"<p>");
898 let result
= Tera
::one_off("{{ greeting }} world", &context
, true).unwrap();
900 assert_eq
!(result
, "<p> world");
904 fn test_can_disable_autoescape_one_off_template() {
905 let mut context
= Context
::new();
906 context
.insert("greeting", &"<p>");
907 let result
= Tera
::one_off("{{ greeting }} world", &context
, false).unwrap();
909 assert_eq
!(result
, "<p> world");
913 fn test_set_escape_function() {
914 let escape_c_string
: super::EscapeFn
= |input
| {
915 let mut output
= String
::with_capacity(input
.len() * 2);
916 for c
in input
.chars() {
918 '
\''
=> output
.push_str("\\'"),
919 '
\"'
=> output
.push_str("\\\""),
920 '
\\'
=> output
.push_str("\\\\"),
921 '
\n'
=> output
.push_str("\\n"),
922 '
\r'
=> output
.push_str("\\r"),
923 '
\t'
=> output
.push_str("\\t"),
929 let mut tera
= Tera
::default();
930 tera
.add_raw_template("foo", "\"{{ content }}\"").unwrap();
931 tera
.autoescape_on(vec
!["foo"]);
932 tera
.set_escape_fn(escape_c_string
);
933 let mut context
= Context
::new();
934 context
.insert("content", &"Hello\n\'world\"!");
935 let result
= tera
.render("foo", &context
).unwrap();
936 assert_eq
!(result
, r
#""Hello\n\'world\"!""#);
940 fn test_reset_escape_function() {
941 let no_escape
: super::EscapeFn
= |input
| input
.to_string();
942 let mut tera
= Tera
::default();
943 tera
.add_raw_template("foo", "{{ content }}").unwrap();
944 tera
.autoescape_on(vec
!["foo"]);
945 tera
.set_escape_fn(no_escape
);
946 tera
.reset_escape_fn();
947 let mut context
= Context
::new();
948 context
.insert("content", &"Hello\n\'world\"!");
949 let result
= tera
.render("foo", &context
).unwrap();
950 assert_eq
!(result
, "Hello\n'world"!");
954 fn test_value_one_off_template() {
956 "greeting": "Good morning"
959 Tera
::one_off("{{ greeting }} world", &Context
::from_value(m
).unwrap(), true).unwrap();
961 assert_eq
!(result
, "Good morning world");
965 fn test_render_str_with_custom_function() {
966 let mut tera
= Tera
::default();
967 tera
.register_function("echo", |args
: &HashMap
<_
, JsonValue
>| {
968 Ok(args
.get("greeting").map(JsonValue
::to_owned
).unwrap())
972 tera
.render_str("{{ echo(greeting='Hello') }} world", &Context
::default()).unwrap();
974 assert_eq
!(result
, "Hello world");
978 fn test_render_map_with_dotted_keys() {
979 let mut my_tera
= Tera
::default();
981 .add_raw_templates(vec
![
982 ("dots", r
#"{{ map["a.b.c"] }}"#),
983 ("urls", r
#"{{ map["https://example.com"] }}"#),
987 let mut map
= HashMap
::new();
988 map
.insert("a.b.c", "success");
989 map
.insert("https://example.com", "success");
991 let mut tera_context
= Context
::new();
992 tera_context
.insert("map", &map
);
994 my_tera
.render("dots", &tera_context
).unwrap();
995 my_tera
.render("urls", &tera_context
).unwrap();
999 fn test_extend_no_overlap() {
1000 let mut my_tera
= Tera
::default();
1002 .add_raw_templates(vec
![
1003 ("one", "{% block hey %}1{% endblock hey %}"),
1004 ("two", "{% block hey %}2{% endblock hey %}"),
1005 ("three", "{% block hey %}3{% endblock hey %}"),
1009 let mut framework_tera
= Tera
::default();
1010 framework_tera
.add_raw_templates(vec
![("four", "Framework X")]).unwrap();
1012 my_tera
.extend(&framework_tera
).unwrap();
1013 assert_eq
!(my_tera
.templates
.len(), 4);
1014 let result
= my_tera
.render("four", &Context
::default()).unwrap();
1015 assert_eq
!(result
, "Framework X");
1019 fn test_extend_with_overlap() {
1020 let mut my_tera
= Tera
::default();
1022 .add_raw_templates(vec
![
1024 ("two", "{% block hey %}2{% endblock hey %}"),
1025 ("three", "{% block hey %}3{% endblock hey %}"),
1029 let mut framework_tera
= Tera
::default();
1031 .add_raw_templates(vec
![("one", "FRAMEWORK"), ("four", "Framework X")])
1034 my_tera
.extend(&framework_tera
).unwrap();
1035 assert_eq
!(my_tera
.templates
.len(), 4);
1036 let result
= my_tera
.render("one", &Context
::default()).unwrap();
1037 assert_eq
!(result
, "MINE");
1041 fn test_extend_new_filter() {
1042 let mut my_tera
= Tera
::default();
1043 let mut framework_tera
= Tera
::default();
1044 framework_tera
.register_filter("hello", |_
: &JsonValue
, _
: &HashMap
<String
, JsonValue
>| {
1045 Ok(JsonValue
::Number(10.into
()))
1047 my_tera
.extend(&framework_tera
).unwrap();
1048 assert
!(my_tera
.filters
.contains_key("hello"));
1052 fn test_extend_new_tester() {
1053 let mut my_tera
= Tera
::default();
1054 let mut framework_tera
= Tera
::default();
1055 framework_tera
.register_tester("hello", |_
: Option
<&JsonValue
>, _
: &[JsonValue
]| Ok(true));
1056 my_tera
.extend(&framework_tera
).unwrap();
1057 assert
!(my_tera
.testers
.contains_key("hello"));
1061 fn can_load_from_glob() {
1062 let tera
= Tera
::new("examples/basic/templates/**/*").unwrap();
1063 assert
!(tera
.get_template("base.html").is_ok());
1067 fn can_load_from_glob_with_patterns() {
1068 let tera
= Tera
::new("examples/basic/templates/**/*.{html, xml}").unwrap();
1069 assert
!(tera
.get_template("base.html").is_ok());
1073 fn full_reload_with_glob() {
1074 let mut tera
= Tera
::new("examples/basic/templates/**/*").unwrap();
1075 tera
.full_reload().unwrap();
1077 assert
!(tera
.get_template("base.html").is_ok());
1081 fn full_reload_with_glob_after_extending() {
1082 let mut tera
= Tera
::new("examples/basic/templates/**/*").unwrap();
1083 let mut framework_tera
= Tera
::default();
1085 .add_raw_templates(vec
![("one", "FRAMEWORK"), ("four", "Framework X")])
1087 tera
.extend(&framework_tera
).unwrap();
1088 tera
.full_reload().unwrap();
1090 assert
!(tera
.get_template("base.html").is_ok());
1091 assert
!(tera
.get_template("one").is_ok());
1096 fn test_can_only_parse_templates() {
1097 let mut tera
= Tera
::parse("examples/basic/templates/**/*").unwrap();
1098 for tpl
in tera
.templates
.values_mut() {
1099 tpl
.name
= format
!("a-theme/templates/{}", tpl
.name
);
1100 if let Some(ref parent
) = tpl
.parent
.clone() {
1101 tpl
.parent
= Some(format
!("a-theme/templates/{}", parent
));
1104 // Will panic here as we changed the parent and it won't be able
1105 // to build the inheritance chain in this case
1106 tera
.build_inheritance_chains().unwrap();
1109 // https://github.com/Keats/tera/issues/380
1111 fn glob_work_with_absolute_paths() {
1112 let tmp_dir
= tempdir().expect("create temp dir");
1113 let cwd
= tmp_dir
.path().canonicalize().unwrap();
1114 File
::create(cwd
.join("hey.html")).expect("Failed to create a test file");
1115 File
::create(cwd
.join("ho.html")).expect("Failed to create a test file");
1116 let glob
= cwd
.join("*.html").into_os_string().into_string().unwrap();
1117 let tera
= Tera
::new(&glob
).expect("Couldn't build Tera instance");
1118 assert_eq
!(tera
.templates
.len(), 2);
1122 fn glob_work_with_absolute_paths_and_double_star() {
1123 let tmp_dir
= tempdir().expect("create temp dir");
1124 let cwd
= tmp_dir
.path().canonicalize().unwrap();
1125 File
::create(cwd
.join("hey.html")).expect("Failed to create a test file");
1126 File
::create(cwd
.join("ho.html")).expect("Failed to create a test file");
1127 let glob
= cwd
.join("**").join("*.html").into_os_string().into_string().unwrap();
1128 let tera
= Tera
::new(&glob
).expect("Couldn't build Tera instance");
1129 assert_eq
!(tera
.templates
.len(), 2);
1132 // https://github.com/Keats/tera/issues/396
1134 fn issues_found_fuzzing_expressions_are_fixed() {
1135 let samples
: Vec
<(&str, Option
<&str>)> = vec
![
1136 // sample, expected result if it isn't an error
1138 ("{{W>W>vv}}{", None
),
1139 ("{{22220222222022222220}}", None
),
1140 ("{_{{W~1+11}}k{", None
),
1141 ("{{n~n<n.11}}}", None
),
1142 ("{{266314325266577770*4167}}7}}7", None
),
1143 ("{{0~1~``~0~0~177777777777777777777~``~0~0~h}}", None
),
1146 for (sample
, expected_output
) in samples
{
1147 let res
= Tera
::one_off(sample
, &Context
::new(), true);
1148 if let Some(output
) = expected_output
{
1149 assert
!(res
.is_ok());
1150 assert_eq
!(res
.unwrap(), output
);
1152 assert
!(res
.is_err());
1158 fn issues_found_fuzzing_conditions_are_fixed() {
1159 let samples
: Vec
<(&str, Option
<&str>)> = vec
![
1160 // (sample, expected result if it isn't an error)
1164 // this is an issue in pest itself: https://github.com/pest-parser/pest/issues/402
1165 // ("_(p=__(p=[_(p=__(p=[_(p=[_(p=[_1", None),
1168 for (sample
, expected_output
) in samples
{
1169 println
!("{}, {:?}", sample
, expected_output
);
1170 let res
= Tera
::one_off(
1171 &format
!("{{% if {} %}}true{{% endif %}}", sample
),
1175 if let Some(output
) = expected_output
{
1176 assert
!(res
.is_ok());
1177 assert_eq
!(res
.unwrap(), output
);
1179 assert
!(res
.is_err());