]> git.proxmox.com Git - rustc.git/blob - vendor/tera/src/tera.rs
New upstream version 1.55.0+dfsg1
[rustc.git] / vendor / tera / src / tera.rs
1 use std::collections::HashMap;
2 use std::fmt;
3 use std::fs::File;
4 use std::io::prelude::*;
5 use std::path::Path;
6 use std::sync::Arc;
7
8 use globwalk::glob_builder;
9
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;
18
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";
21
22 /// The escape function type definition
23 pub type EscapeFn = fn(&str) -> String;
24
25 /// The main point of interaction in this library.
26 #[derive(Clone)]
27 pub struct Tera {
28 // The glob used in `Tera::new`, None if Tera was instantiated differently
29 #[doc(hidden)]
30 glob: Option<String>,
31 #[doc(hidden)]
32 pub templates: HashMap<String, Template>,
33 #[doc(hidden)]
34 pub filters: HashMap<String, Arc<dyn Filter>>,
35 #[doc(hidden)]
36 pub testers: HashMap<String, Arc<dyn Test>>,
37 #[doc(hidden)]
38 pub functions: HashMap<String, Arc<dyn Function>>,
39 // Which extensions does Tera automatically autoescape on.
40 // Defaults to [".html", ".htm", ".xml"]
41 #[doc(hidden)]
42 pub autoescape_suffixes: Vec<&'static str>,
43 #[doc(hidden)]
44 escape_fn: EscapeFn,
45 }
46
47 impl Tera {
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 `{}`",
52 dir
53 )));
54 }
55
56 let mut tera = Tera {
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,
64 };
65
66 tera.load_from_glob()?;
67 if !parse_only {
68 tera.build_inheritance_chains()?;
69 tera.check_macro_files()?;
70 }
71 tera.register_tera_filters();
72 tera.register_tera_testers();
73 tera.register_tera_functions();
74 Ok(tera)
75 }
76
77 /// Create a new instance of Tera, containing all the parsed templates found in the `dir` glob
78 ///
79 ///```ignore
80 ///match Tera::new("templates/**/*") {
81 /// Ok(t) => t,
82 /// Err(e) => {
83 /// println!("Parsing error(s): {}", e);
84 /// ::std::process::exit(1);
85 /// }
86 ///}
87 ///```
88 pub fn new(dir: &str) -> Result<Tera> {
89 Self::create(dir, false)
90 }
91
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
97 ///
98 ///```ignore
99 ///let mut tera = match Tera::parse("templates/**/*") {
100 /// Ok(t) => t,
101 /// Err(e) => {
102 /// println!("Parsing error(s): {}", e);
103 /// ::std::process::exit(1);
104 /// }
105 ///};
106 ///tera.build_inheritance_chains()?;
107 ///```
108 pub fn parse(dir: &str) -> Result<Tera> {
109 Self::create(dir, true)
110 }
111
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"));
116 }
117 // We want to preserve templates that have been added through
118 // Tera::extend so we only keep those
119 self.templates = self
120 .templates
121 .iter()
122 .filter(|&(_, t)| t.from_extend)
123 .map(|(n, t)| (n.clone(), t.clone())) // TODO: avoid that clone
124 .collect();
125
126 let mut errors = String::new();
127
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..];
135 }
136
137 // We are parsing all the templates on instantiation
138 for entry in glob_builder(&dir)
139 .follow_links(true)
140 .build()
141 .unwrap()
142 .filter_map(std::result::Result::ok)
143 {
144 let mut path = entry.into_path();
145 // We only care about actual files
146 if path.is_file() {
147 if path.starts_with("./") {
148 path = path.strip_prefix("./").unwrap().to_path_buf();
149 }
150
151 let filepath = path
152 .strip_prefix(&parent_dir)
153 .unwrap()
154 .to_string_lossy()
155 // unify on forward slash
156 .replace("\\", "/");
157
158 if let Err(e) = self.add_file(Some(&filepath), path) {
159 use std::error::Error;
160
161 errors += &format!("\n* {}", e);
162 let mut cause = e.source();
163 while let Some(e) = cause {
164 errors += &format!("\n{}", e);
165 cause = e.source();
166 }
167 }
168 }
169 }
170
171 if !errors.is_empty() {
172 return Err(Error::msg(errors));
173 }
174
175 Ok(())
176 }
177
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());
184
185 let mut f = File::open(path)
186 .map_err(|e| Error::chain(format!("Couldn't open template '{:?}'", path), e))?;
187
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))?;
191
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))?;
194
195 self.templates.insert(tpl_name.to_string(), tpl);
196 Ok(())
197 }
198
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
205 ///
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
209 // parent template
210 fn build_chain(
211 templates: &HashMap<String, Template>,
212 start: &Template,
213 template: &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));
218 }
219
220 match template.parent {
221 Some(ref p) => match templates.get(p) {
222 Some(parent) => {
223 parents.push(parent.name.clone());
224 build_chain(templates, start, parent, parents)
225 }
226 None => Err(Error::missing_parent(&template.name, &p)),
227 },
228 None => Ok(parents),
229 }
230 }
231
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() {
237 continue;
238 }
239
240 let parents = build_chain(&self.templates, template, template, vec![])?;
241
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())];
246
247 // and then see if our parents have it
248 for parent in &parents {
249 let t = self.get_template(parent)?;
250
251 if let Some(b) = t.blocks.get(block_name) {
252 definitions.push((t.name.clone(), b.clone()));
253 }
254 }
255 blocks_definitions.insert(block_name.clone(), definitions);
256 }
257 tpl_parents.insert(name.clone(), parents);
258 tpl_block_definitions.insert(name.clone(), blocks_definitions);
259 }
260
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() {
264 continue;
265 }
266
267 template.parents = match tpl_parents.remove(&template.name) {
268 Some(parents) => parents,
269 None => vec![],
270 };
271 template.blocks_definitions = match tpl_block_definitions.remove(&template.name) {
272 Some(blocks) => blocks,
273 None => HashMap::new(),
274 };
275 }
276
277 Ok(())
278 }
279
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.
282 ///
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
291 )));
292 }
293 }
294 }
295
296 Ok(())
297 }
298
299 /// Renders a Tera template given a `tera::Context`,
300 ///
301 /// To render a template with an empty context, simply pass a new `tera::Context` object
302 ///
303 /// ```rust,ignore
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());
310 /// ```
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);
314 renderer.render()
315 }
316
317 /// Renders a Tera template given a `tera::Context` to a [std::io::Write],
318 ///
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].
321 ///
322 /// Any i/o error will be reported in the result.
323 ///
324 /// ```rust,ignore
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);
330 /// ```
331 pub fn render_to(
332 &self,
333 template_name: &str,
334 context: &Context,
335 write: impl Write,
336 ) -> Result<()> {
337 let template = self.get_template(template_name)?;
338 let renderer = Renderer::new(template, self, context);
339 renderer.render_to(write)
340 }
341
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.
345 ///
346 /// Any errors will mention the `__tera_one_off` template: this is the name
347 /// given to the template by Tera.
348 ///
349 /// ```rust,ignore
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!");
354 /// ```
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);
359 result
360 }
361
362 /// Renders a one off template (for example a template coming from a user input) given a `Context`
363 ///
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
367 /// Tera
368 ///
369 /// ```rust,ignore
370 /// let mut context = Context::new();
371 /// context.insert("greeting", &"hello");
372 /// Tera::one_off("{{ greeting }} world", &context, true);
373 /// ```
374 pub fn one_off(input: &str, context: &Context, autoescape: bool) -> Result<String> {
375 let mut tera = Tera::default();
376
377 if autoescape {
378 tera.autoescape_on(vec![ONE_OFF_TEMPLATE_NAME]);
379 }
380
381 tera.render_str(input, context)
382 }
383
384 #[doc(hidden)]
385 #[inline]
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)),
390 }
391 }
392
393 /// Returns an iterator over the names of all registered templates in an
394 /// unspecified order.
395 ///
396 /// # Example
397 ///
398 /// ```rust
399 /// use tera::Tera;
400 ///
401 /// let mut tera = Tera::default();
402 /// tera.add_raw_template("foo", "{{ hello }}");
403 /// tera.add_raw_template("another-one.html", "contents go here");
404 ///
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"));
409 /// ```
410 #[inline]
411 pub fn get_template_names(&self) -> impl Iterator<Item = &str> {
412 self.templates.keys().map(|s| s.as_str())
413 }
414
415 /// Add a single template to the Tera instance
416 ///
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)
420 ///
421 /// ```rust,ignore
422 /// tera.add_raw_template("new.html", "Blabla");
423 /// ```
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()?;
430 Ok(())
431 }
432
433 /// Add all the templates given to the Tera instance
434 ///
435 /// This will error if the inheritance chain can't be built, such as adding a child
436 /// template without the parent one.
437 ///
438 /// ```rust,ignore
439 /// tera.add_raw_templates(vec![
440 /// ("new.html", "blabla"),
441 /// ("new2.html", "hello"),
442 /// ]);
443 /// ```
444 pub fn add_raw_templates<I, N, C>(&mut self, templates: I) -> Result<()>
445 where
446 I: IntoIterator<Item = (N, C)>,
447 N: AsRef<str>,
448 C: AsRef<str>,
449 {
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);
455 }
456 self.build_inheritance_chains()?;
457 self.check_macro_files()?;
458 Ok(())
459 }
460
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
463 ///
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)
467 ///
468 /// ```rust,ignore
469 /// // Use path as name
470 /// tera.add_template_file(path, None);
471 /// // Rename
472 /// tera.add_template_file(path, Some("index"));
473 /// ```
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()?;
478 Ok(())
479 }
480
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
483 ///
484 /// This will error if the inheritance chain can't be built, such as adding a child
485 /// template without the parent one.
486 ///
487 /// ```rust,ignore
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
491 /// ]);
492 /// ```
493 pub fn add_template_files<I, P, N>(&mut self, files: I) -> Result<()>
494 where
495 I: IntoIterator<Item = (P, Option<N>)>,
496 P: AsRef<Path>,
497 N: AsRef<str>,
498 {
499 for (path, name) in files {
500 self.add_file(name.as_ref().map(AsRef::as_ref), path)?;
501 }
502 self.build_inheritance_chains()?;
503 self.check_macro_files()?;
504 Ok(())
505 }
506
507 #[doc(hidden)]
508 #[inline]
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)),
513 }
514 }
515
516 /// Register a filter with Tera.
517 ///
518 /// If a filter with that name already exists, it will be overwritten
519 ///
520 /// ```rust,ignore
521 /// tera.register_filter("upper", string::upper);
522 /// ```
523 pub fn register_filter<F: Filter + 'static>(&mut self, name: &str, filter: F) {
524 self.filters.insert(name.to_string(), Arc::new(filter));
525 }
526
527 #[doc(hidden)]
528 #[inline]
529 pub fn get_tester(&self, tester_name: &str) -> Result<&dyn Test> {
530 match self.testers.get(tester_name) {
531 Some(t) => Ok(&**t),
532 None => Err(Error::test_not_found(tester_name)),
533 }
534 }
535
536 /// Register a tester with Tera.
537 ///
538 /// If a tester with that name already exists, it will be overwritten
539 ///
540 /// ```rust,ignore
541 /// tera.register_tester("odd", testers::odd);
542 /// ```
543 pub fn register_tester<T: Test + 'static>(&mut self, name: &str, tester: T) {
544 self.testers.insert(name.to_string(), Arc::new(tester));
545 }
546
547 #[doc(hidden)]
548 #[inline]
549 pub fn get_function(&self, fn_name: &str) -> Result<&dyn Function> {
550 match self.functions.get(fn_name) {
551 Some(t) => Ok(&**t),
552 None => Err(Error::function_not_found(fn_name)),
553 }
554 }
555
556 /// Register a function with Tera.
557 ///
558 /// If a function with that name already exists, it will be overwritten
559 ///
560 /// ```rust,ignore
561 /// tera.register_function("range", range);
562 /// ```
563 pub fn register_function<F: Function + 'static>(&mut self, name: &str, function: F) {
564 self.functions.insert(name.to_string(), Arc::new(function));
565 }
566
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);
595
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);
607
608 self.register_filter("pluralize", number::pluralize);
609 self.register_filter("round", number::round);
610
611 #[cfg(feature = "builtins")]
612 self.register_filter("filesizeformat", number::filesizeformat);
613
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);
620
621 self.register_filter("get", object::get);
622 }
623
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);
638 }
639
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);
648 }
649
650 /// Select which suffix(es) to automatically do HTML escaping on,
651 ///`[".html", ".htm", ".xml"]` by default.
652 ///
653 /// Only call this function if you wish to change the defaults.
654 ///
655 ///
656 ///```ignore
657 /// // escape only files ending with `.php.html`
658 /// tera.autoescape_on(vec![".php.html"]);
659 /// // disable autoescaping completely
660 /// tera.autoescape_on(vec![]);
661 ///```
662 pub fn autoescape_on(&mut self, suffixes: Vec<&'static str>) {
663 self.autoescape_suffixes = suffixes;
664 }
665
666 #[doc(hidden)]
667 #[inline]
668 pub fn get_escape_fn(&self) -> &EscapeFn {
669 &self.escape_fn
670 }
671
672 /// Set user-defined function which applied to a rendered content.
673 ///
674 ///```rust,ignore
675 /// fn escape_c_string(input: &str) -> String { ... }
676 ///
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\"!""#);
685 ///```
686 pub fn set_escape_fn(&mut self, function: EscapeFn) {
687 self.escape_fn = function;
688 }
689
690 /// Reset escape function to default `tera::escape_html`.
691 pub fn reset_escape_fn(&mut self) {
692 self.escape_fn = escape_html;
693 }
694
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.
698 ///
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()?;
704 } else {
705 return Err(Error::msg("Reloading is only available if you are using a glob"));
706 }
707
708 self.build_inheritance_chains()?;
709 self.check_macro_files()
710 }
711
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.
715 ///
716 ///```rust,ignore
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);
720 ///```
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);
727 }
728 }
729
730 for (name, filter) in &other.filters {
731 if !self.filters.contains_key(name) {
732 self.filters.insert(name.to_string(), filter.clone());
733 }
734 }
735
736 for (name, tester) in &other.testers {
737 if !self.testers.contains_key(name) {
738 self.testers.insert(name.to_string(), tester.clone());
739 }
740 }
741
742 self.build_inheritance_chains()?;
743 self.check_macro_files()
744 }
745 }
746
747 impl Default for Tera {
748 fn default() -> Tera {
749 let mut tera = Tera {
750 glob: None,
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,
757 };
758
759 tera.register_tera_filters();
760 tera.register_tera_testers();
761 tera.register_tera_functions();
762 tera
763 }
764 }
765
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: [")?;
771
772 for template in self.templates.keys() {
773 writeln!(f, "\t\t{},", template)?;
774 }
775 write!(f, "\t]")?;
776 writeln!(f, "\n\tfilters: [")?;
777
778 for filter in self.filters.keys() {
779 writeln!(f, "\t\t{},", filter)?;
780 }
781 write!(f, "\t]")?;
782 writeln!(f, "\n\ttesters: [")?;
783
784 for tester in self.testers.keys() {
785 writeln!(f, "\t\t{},", tester)?;
786 }
787 writeln!(f, "\t]")?;
788
789 writeln!(f, "}}")
790 }
791 }
792
793 #[cfg(test)]
794 mod tests {
795 use tempfile::tempdir;
796
797 use std::collections::HashMap;
798 use std::fs::File;
799
800 use super::Tera;
801 use crate::context::Context;
802 use serde_json::{json, Value as JsonValue};
803
804 #[test]
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\" %}"),
811 ("d", ""),
812 ])
813 .unwrap();
814
815 assert_eq!(
816 tera.get_template("a").unwrap().parents,
817 vec!["b".to_string(), "c".to_string(), "d".to_string()]
818 );
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);
822 }
823
824 #[test]
825 fn test_missing_parent_template() {
826 let mut tera = Tera::default();
827 assert_eq!(
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."
830 );
831 }
832
833 #[test]
834 fn test_circular_extends() {
835 let mut tera = Tera::default();
836 let err = tera
837 .add_raw_templates(vec![("a", "{% extends \"b\" %}"), ("b", "{% extends \"a\" %}")])
838 .unwrap_err();
839
840 assert!(err.to_string().contains("Circular extend detected for template"));
841 }
842
843 #[test]
844 fn test_get_parent_blocks_definition() {
845 let mut tera = Tera::default();
846 tera.add_raw_templates(vec![
847 (
848 "grandparent",
849 "{% block hey %}hello{% endblock hey %} {% block ending %}sincerely{% endblock ending %}",
850 ),
851 (
852 "parent",
853 "{% extends \"grandparent\" %}{% block hey %}hi and grandma says {{ super() }}{% endblock hey %}",
854 ),
855 (
856 "child",
857 "{% extends \"parent\" %}{% block hey %}dad says {{ super() }}{% endblock hey %}{% block ending %}{{ super() }} with love{% endblock ending %}",
858 ),
859 ]).unwrap();
860
861 let hey_definitions =
862 tera.get_template("child").unwrap().blocks_definitions.get("hey").unwrap();
863 assert_eq!(hey_definitions.len(), 3);
864
865 let ending_definitions =
866 tera.get_template("child").unwrap().blocks_definitions.get("ending").unwrap();
867 assert_eq!(ending_definitions.len(), 2);
868 }
869
870 #[test]
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 %}"),
875 (
876 "parent",
877 "{% extends \"grandparent\" %}{% block hey %}hi and grandma says {{ super() }} {% block ending %}sincerely{% endblock ending %}{% endblock hey %}",
878 ),
879 (
880 "child",
881 "{% extends \"parent\" %}{% block hey %}dad says {{ super() }}{% endblock hey %}{% block ending %}{{ super() }} with love{% endblock ending %}",
882 ),
883 ]).unwrap();
884
885 let hey_definitions =
886 tera.get_template("child").unwrap().blocks_definitions.get("hey").unwrap();
887 assert_eq!(hey_definitions.len(), 3);
888
889 let ending_definitions =
890 tera.get_template("parent").unwrap().blocks_definitions.get("ending").unwrap();
891 assert_eq!(ending_definitions.len(), 1);
892 }
893
894 #[test]
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();
899
900 assert_eq!(result, "&lt;p&gt; world");
901 }
902
903 #[test]
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();
908
909 assert_eq!(result, "<p> world");
910 }
911
912 #[test]
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() {
917 match c {
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"),
924 _ => output.push(c),
925 }
926 }
927 output
928 };
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\"!""#);
937 }
938
939 #[test]
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&#x27;world&quot;!");
951 }
952
953 #[test]
954 fn test_value_one_off_template() {
955 let m = json!({
956 "greeting": "Good morning"
957 });
958 let result =
959 Tera::one_off("{{ greeting }} world", &Context::from_value(m).unwrap(), true).unwrap();
960
961 assert_eq!(result, "Good morning world");
962 }
963
964 #[test]
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())
969 });
970
971 let result =
972 tera.render_str("{{ echo(greeting='Hello') }} world", &Context::default()).unwrap();
973
974 assert_eq!(result, "Hello world");
975 }
976
977 #[test]
978 fn test_render_map_with_dotted_keys() {
979 let mut my_tera = Tera::default();
980 my_tera
981 .add_raw_templates(vec![
982 ("dots", r#"{{ map["a.b.c"] }}"#),
983 ("urls", r#"{{ map["https://example.com"] }}"#),
984 ])
985 .unwrap();
986
987 let mut map = HashMap::new();
988 map.insert("a.b.c", "success");
989 map.insert("https://example.com", "success");
990
991 let mut tera_context = Context::new();
992 tera_context.insert("map", &map);
993
994 my_tera.render("dots", &tera_context).unwrap();
995 my_tera.render("urls", &tera_context).unwrap();
996 }
997
998 #[test]
999 fn test_extend_no_overlap() {
1000 let mut my_tera = Tera::default();
1001 my_tera
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 %}"),
1006 ])
1007 .unwrap();
1008
1009 let mut framework_tera = Tera::default();
1010 framework_tera.add_raw_templates(vec![("four", "Framework X")]).unwrap();
1011
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");
1016 }
1017
1018 #[test]
1019 fn test_extend_with_overlap() {
1020 let mut my_tera = Tera::default();
1021 my_tera
1022 .add_raw_templates(vec![
1023 ("one", "MINE"),
1024 ("two", "{% block hey %}2{% endblock hey %}"),
1025 ("three", "{% block hey %}3{% endblock hey %}"),
1026 ])
1027 .unwrap();
1028
1029 let mut framework_tera = Tera::default();
1030 framework_tera
1031 .add_raw_templates(vec![("one", "FRAMEWORK"), ("four", "Framework X")])
1032 .unwrap();
1033
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");
1038 }
1039
1040 #[test]
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()))
1046 });
1047 my_tera.extend(&framework_tera).unwrap();
1048 assert!(my_tera.filters.contains_key("hello"));
1049 }
1050
1051 #[test]
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"));
1058 }
1059
1060 #[test]
1061 fn can_load_from_glob() {
1062 let tera = Tera::new("examples/basic/templates/**/*").unwrap();
1063 assert!(tera.get_template("base.html").is_ok());
1064 }
1065
1066 #[test]
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());
1070 }
1071
1072 #[test]
1073 fn full_reload_with_glob() {
1074 let mut tera = Tera::new("examples/basic/templates/**/*").unwrap();
1075 tera.full_reload().unwrap();
1076
1077 assert!(tera.get_template("base.html").is_ok());
1078 }
1079
1080 #[test]
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();
1084 framework_tera
1085 .add_raw_templates(vec![("one", "FRAMEWORK"), ("four", "Framework X")])
1086 .unwrap();
1087 tera.extend(&framework_tera).unwrap();
1088 tera.full_reload().unwrap();
1089
1090 assert!(tera.get_template("base.html").is_ok());
1091 assert!(tera.get_template("one").is_ok());
1092 }
1093
1094 #[should_panic]
1095 #[test]
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));
1102 }
1103 }
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();
1107 }
1108
1109 // https://github.com/Keats/tera/issues/380
1110 #[test]
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);
1119 }
1120
1121 #[test]
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);
1130 }
1131
1132 // https://github.com/Keats/tera/issues/396
1133 #[test]
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
1137 ("{{0%0}}", None),
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),
1144 ];
1145
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);
1151 } else {
1152 assert!(res.is_err());
1153 }
1154 }
1155 }
1156
1157 #[test]
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)
1161 ("C~Q", None),
1162 ("s is V*0", None),
1163 ("x0x::N()", None),
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),
1166 ];
1167
1168 for (sample, expected_output) in samples {
1169 println!("{}, {:?}", sample, expected_output);
1170 let res = Tera::one_off(
1171 &format!("{{% if {} %}}true{{% endif %}}", sample),
1172 &Context::new(),
1173 true,
1174 );
1175 if let Some(output) = expected_output {
1176 assert!(res.is_ok());
1177 assert_eq!(res.unwrap(), output);
1178 } else {
1179 assert!(res.is_err());
1180 }
1181 }
1182 }
1183 }