handlebars/
registry.rs

1use std::borrow::Cow;
2use std::collections::{BTreeMap, HashMap};
3use std::convert::AsRef;
4use std::fmt::{self, Debug, Formatter};
5use std::io::{Error as IoError, Write};
6use std::path::Path;
7use std::sync::Arc;
8
9use serde::Serialize;
10
11use crate::context::Context;
12use crate::decorators::{self, DecoratorDef};
13#[cfg(feature = "script_helper")]
14use crate::error::ScriptError;
15use crate::error::{RenderError, RenderErrorReason, TemplateError};
16use crate::helpers::{self, HelperDef};
17use crate::output::{Output, StringOutput, WriteOutput};
18use crate::render::{RenderContext, Renderable};
19use crate::sources::{FileSource, Source};
20use crate::support::str::{self, StringWriter};
21use crate::template::{Template, TemplateOptions};
22
23#[cfg(feature = "dir_source")]
24use walkdir::WalkDir;
25
26#[cfg(feature = "dir_source")]
27use derive_builder::Builder;
28
29#[cfg(feature = "script_helper")]
30use rhai::Engine;
31
32#[cfg(feature = "script_helper")]
33use crate::helpers::scripting::ScriptHelper;
34
35#[cfg(feature = "rust-embed")]
36use crate::sources::LazySource;
37#[cfg(feature = "rust-embed")]
38use rust_embed::RustEmbed;
39
40/// This type represents an *escape fn*, that is a function whose purpose it is
41/// to escape potentially problematic characters in a string.
42///
43/// An *escape fn* is represented as a `Box` to avoid unnecessary type
44/// parameters (and because traits cannot be aliased using `type`).
45pub type EscapeFn = Arc<dyn Fn(&str) -> String + Send + Sync>;
46
47/// The default *escape fn* replaces the characters `&"<>`
48/// with the equivalent html / xml entities.
49pub fn html_escape(data: &str) -> String {
50    str::escape_html(data)
51}
52
53/// `EscapeFn` that does not change anything. Useful when using in a non-html
54/// environment.
55pub fn no_escape(data: &str) -> String {
56    data.to_owned()
57}
58
59/// The single entry point of your Handlebars templates
60///
61/// It maintains compiled templates and registered helpers.
62#[derive(Clone)]
63pub struct Registry<'reg> {
64    templates: HashMap<String, Template>,
65
66    helpers: HashMap<String, Arc<dyn HelperDef + Send + Sync + 'reg>>,
67    decorators: HashMap<String, Arc<dyn DecoratorDef + Send + Sync + 'reg>>,
68
69    escape_fn: EscapeFn,
70    strict_mode: bool,
71    dev_mode: bool,
72    prevent_indent: bool,
73    #[cfg(feature = "script_helper")]
74    pub(crate) engine: Arc<Engine>,
75
76    template_sources:
77        HashMap<String, Arc<dyn Source<Item = String, Error = IoError> + Send + Sync + 'reg>>,
78    #[cfg(feature = "script_helper")]
79    script_sources:
80        HashMap<String, Arc<dyn Source<Item = String, Error = IoError> + Send + Sync + 'reg>>,
81}
82
83impl Debug for Registry<'_> {
84    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
85        f.debug_struct("Handlebars")
86            .field("templates", &self.templates)
87            .field("helpers", &self.helpers.keys())
88            .field("decorators", &self.decorators.keys())
89            .field("strict_mode", &self.strict_mode)
90            .field("dev_mode", &self.dev_mode)
91            .finish()
92    }
93}
94
95impl Default for Registry<'_> {
96    fn default() -> Self {
97        Self::new()
98    }
99}
100
101#[cfg(feature = "script_helper")]
102fn rhai_engine() -> Engine {
103    Engine::new()
104}
105
106/// Options for importing template files from a directory.
107#[non_exhaustive]
108#[derive(Builder)]
109#[cfg(feature = "dir_source")]
110pub struct DirectorySourceOptions {
111    /// The name extension for template files
112    #[builder(setter(into))]
113    pub tpl_extension: String,
114    /// Whether to include hidden files (file name that starts with `.`)
115    pub hidden: bool,
116    /// Whether to include temporary files (file name that starts with `#`)
117    pub temporary: bool,
118}
119
120#[cfg(feature = "dir_source")]
121impl DirectorySourceOptions {
122    fn ignore_file(&self, name: &str) -> bool {
123        self.ignored_as_hidden_file(name) || self.ignored_as_temporary_file(name)
124    }
125
126    #[inline]
127    fn ignored_as_hidden_file(&self, name: &str) -> bool {
128        !self.hidden && name.starts_with('.')
129    }
130
131    #[inline]
132    fn ignored_as_temporary_file(&self, name: &str) -> bool {
133        !self.temporary && name.starts_with('#')
134    }
135}
136
137#[cfg(feature = "dir_source")]
138impl Default for DirectorySourceOptions {
139    fn default() -> Self {
140        DirectorySourceOptions {
141            tpl_extension: ".hbs".to_owned(),
142            hidden: false,
143            temporary: false,
144        }
145    }
146}
147
148impl<'reg> Registry<'reg> {
149    pub fn new() -> Registry<'reg> {
150        let r = Registry {
151            templates: HashMap::new(),
152            template_sources: HashMap::new(),
153            helpers: HashMap::new(),
154            decorators: HashMap::new(),
155            escape_fn: Arc::new(html_escape),
156            strict_mode: false,
157            dev_mode: false,
158            prevent_indent: false,
159            #[cfg(feature = "script_helper")]
160            engine: Arc::new(rhai_engine()),
161            #[cfg(feature = "script_helper")]
162            script_sources: HashMap::new(),
163        };
164
165        r.setup_builtins()
166    }
167
168    fn setup_builtins(mut self) -> Registry<'reg> {
169        self.register_helper("if", Box::new(helpers::IF_HELPER));
170        self.register_helper("unless", Box::new(helpers::UNLESS_HELPER));
171        self.register_helper("each", Box::new(helpers::EACH_HELPER));
172        self.register_helper("with", Box::new(helpers::WITH_HELPER));
173        self.register_helper("lookup", Box::new(helpers::LOOKUP_HELPER));
174        self.register_helper("raw", Box::new(helpers::RAW_HELPER));
175        self.register_helper("log", Box::new(helpers::LOG_HELPER));
176
177        self.register_helper("eq", Box::new(helpers::helper_extras::eq));
178        self.register_helper("ne", Box::new(helpers::helper_extras::ne));
179        self.register_helper("gt", Box::new(helpers::helper_extras::gt));
180        self.register_helper("gte", Box::new(helpers::helper_extras::gte));
181        self.register_helper("lt", Box::new(helpers::helper_extras::lt));
182        self.register_helper("lte", Box::new(helpers::helper_extras::lte));
183        self.register_helper("and", Box::new(helpers::helper_extras::and));
184        self.register_helper("or", Box::new(helpers::helper_extras::or));
185        self.register_helper("not", Box::new(helpers::helper_extras::not));
186        self.register_helper("len", Box::new(helpers::helper_extras::len));
187
188        #[cfg(feature = "string_helpers")]
189        self.register_string_helpers();
190
191        self.register_decorator("inline", Box::new(decorators::INLINE_DECORATOR));
192        self
193    }
194
195    /// Enable or disable handlebars strict mode
196    ///
197    /// By default, handlebars renders empty string for value that
198    /// undefined or never exists. Since rust is a static type
199    /// language, we offer strict mode in handlebars-rust.  In strict
200    /// mode, if you were to render a value that doesn't exist, a
201    /// `RenderError` will be raised.
202    pub fn set_strict_mode(&mut self, enabled: bool) {
203        self.strict_mode = enabled;
204    }
205
206    /// Return strict mode state, default is false.
207    ///
208    /// By default, handlebars renders empty string for value that
209    /// undefined or never exists. Since rust is a static type
210    /// language, we offer strict mode in handlebars-rust.  In strict
211    /// mode, if you were access a value that doesn't exist, a
212    /// `RenderError` will be raised.
213    pub fn strict_mode(&self) -> bool {
214        self.strict_mode
215    }
216
217    /// Return dev mode state, default is false
218    ///
219    /// With dev mode turned on, handlebars enables a set of development
220    /// friendly features, that may affect its performance.
221    pub fn dev_mode(&self) -> bool {
222        self.dev_mode
223    }
224
225    /// Enable or disable dev mode
226    ///
227    /// With dev mode turned on, handlebars enables a set of development
228    /// friendly features, that may affect its performance.
229    ///
230    /// **Note that you have to enable dev mode before adding templates to
231    /// the registry**. Otherwise it won't take effect at all.
232    pub fn set_dev_mode(&mut self, enabled: bool) {
233        self.dev_mode = enabled;
234
235        // clear template source when disabling dev mode
236        if !enabled {
237            self.template_sources.clear();
238        }
239    }
240
241    /// Enable or disable indent for partial include tag `{{>}}`
242    ///
243    /// By default handlebars keeps indent whitespaces for partial
244    /// include tag, to change this behaviour, set this toggle to `true`.
245    pub fn set_prevent_indent(&mut self, enable: bool) {
246        self.prevent_indent = enable;
247    }
248
249    /// Return state for `prevent_indent` option, default to `false`.
250    pub fn prevent_indent(&self) -> bool {
251        self.prevent_indent
252    }
253
254    /// Register a `Template`
255    ///
256    /// This is infallible since the template has already been parsed and
257    /// insert cannot fail. If there is an existing template with this name it
258    /// will be overwritten.
259    ///
260    /// Dev mode doesn't apply for pre-compiled template because it's lifecycle
261    /// is not managed by the registry.
262    pub fn register_template(&mut self, name: &str, tpl: Template) {
263        self.templates.insert(name.to_string(), tpl);
264    }
265
266    /// Register a template string
267    ///
268    /// Returns `TemplateError` if there is syntax error on parsing the template.
269    pub fn register_template_string<S>(
270        &mut self,
271        name: &str,
272        tpl_str: S,
273    ) -> Result<(), TemplateError>
274    where
275        S: AsRef<str>,
276    {
277        let template = Template::compile2(
278            tpl_str.as_ref(),
279            TemplateOptions {
280                name: Some(name.to_owned()),
281                is_partial: false,
282                prevent_indent: self.prevent_indent,
283            },
284        )?;
285        self.register_template(name, template);
286        Ok(())
287    }
288
289    /// Register a partial string
290    ///
291    /// A named partial will be added to the registry. It will overwrite template with
292    /// same name. Currently a registered partial is just identical to a template.
293    pub fn register_partial<S>(&mut self, name: &str, partial_str: S) -> Result<(), TemplateError>
294    where
295        S: AsRef<str>,
296    {
297        self.register_template_string(name, partial_str)
298    }
299
300    /// Register a template from a path on file system
301    ///
302    /// If dev mode is enabled, the registry will keep reading the template file
303    /// from file system everytime it's visited.
304    pub fn register_template_file<P>(
305        &mut self,
306        name: &str,
307        tpl_path: P,
308    ) -> Result<(), TemplateError>
309    where
310        P: AsRef<Path>,
311    {
312        let source = FileSource::new(tpl_path.as_ref().into());
313        let template_string = source
314            .load()
315            .map_err(|err| TemplateError::from((err, name.to_owned())))?;
316
317        self.register_template_string(name, template_string)?;
318        if self.dev_mode {
319            self.template_sources
320                .insert(name.to_owned(), Arc::new(source));
321        }
322
323        Ok(())
324    }
325
326    /// Register templates from a directory
327    ///
328    /// * `tpl_extension`: the template file extension
329    /// * `dir_path`: the path of directory
330    ///
331    /// Hidden files and tempfile (starts with `#`) will be ignored by default.
332    /// Set `DirectorySourceOptions` to something other than `DirectorySourceOptions::default()` to adjust this.
333    /// All registered templates will use their relative path to determine their template name.
334    /// For example, when `dir_path` is `templates/` and `DirectorySourceOptions.tpl_extension` is `.hbs`, the file
335    /// `templates/some/path/file.hbs` will be registered as `some/path/file`.
336    ///
337    /// This method is not available by default.
338    /// You will need to enable the `dir_source` feature to use it.
339    ///
340    /// When dev_mode is enabled, like with `register_template_file`, templates are reloaded
341    /// from the file system every time they're visited.
342    #[cfg(feature = "dir_source")]
343    #[cfg_attr(docsrs, doc(cfg(feature = "dir_source")))]
344    pub fn register_templates_directory<P>(
345        &mut self,
346        dir_path: P,
347        options: DirectorySourceOptions,
348    ) -> Result<(), TemplateError>
349    where
350        P: AsRef<Path>,
351    {
352        let dir_path = dir_path.as_ref();
353
354        let walker = WalkDir::new(dir_path);
355        let dir_iter = walker
356            .min_depth(1)
357            .into_iter()
358            .filter_map(|e| e.ok().map(|e| e.into_path()))
359            // Checks if extension matches
360            .filter(|tpl_path| {
361                tpl_path
362                    .to_string_lossy()
363                    .ends_with(options.tpl_extension.as_str())
364            })
365            // Rejects any hidden or temporary files.
366            .filter(|tpl_path| {
367                tpl_path
368                    .file_stem()
369                    .map(|stem| !options.ignore_file(&stem.to_string_lossy()))
370                    .unwrap_or(false)
371            })
372            .filter_map(|tpl_path| {
373                tpl_path
374                    .strip_prefix(dir_path)
375                    .ok()
376                    .map(|tpl_canonical_name| {
377                        let tpl_name = tpl_canonical_name
378                            .components()
379                            .map(|component| component.as_os_str().to_string_lossy())
380                            .collect::<Vec<_>>()
381                            .join("/");
382
383                        tpl_name
384                            .strip_suffix(options.tpl_extension.as_str())
385                            .map(|s| s.to_owned())
386                            .unwrap_or(tpl_name)
387                    })
388                    .map(|tpl_canonical_name| (tpl_canonical_name, tpl_path))
389            });
390
391        for (tpl_canonical_name, tpl_path) in dir_iter {
392            self.register_template_file(&tpl_canonical_name, &tpl_path)?;
393        }
394
395        Ok(())
396    }
397
398    /// Register templates using a
399    /// [RustEmbed](https://github.com/pyros2097/rust-embed) type
400    /// Calls register_embed_templates_with_extension with empty extension.
401    ///
402    /// File names from embed struct are used as template name.
403    ///
404    /// ```skip
405    /// #[derive(RustEmbed)]
406    /// #[folder = "templates"]
407    /// #[include = "*.hbs"]
408    /// struct Assets;
409    ///
410    /// let mut hbs = Handlebars::new();
411    /// hbs.register_embed_templates::<Assets>();
412    /// ```
413    ///
414    #[cfg(feature = "rust-embed")]
415    #[cfg_attr(docsrs, doc(cfg(feature = "rust-embed")))]
416    pub fn register_embed_templates<E>(&mut self) -> Result<(), TemplateError>
417    where
418        E: RustEmbed,
419    {
420        self.register_embed_templates_with_extension::<E>("")
421    }
422
423    /// Register templates using a
424    /// [RustEmbed](https://github.com/pyros2097/rust-embed) type
425    /// * `tpl_extension`: the template file extension
426    ///
427    /// File names from embed struct are used as template name, but extension is stripped.
428    ///
429    /// When dev_mode enabled templates is reloaded
430    /// from embed struct everytime it's visied.
431    ///
432    /// ```skip
433    /// #[derive(RustEmbed)]
434    /// #[folder = "templates"]
435    /// struct Assets;
436    ///
437    /// let mut hbs = Handlebars::new();
438    /// hbs.register_embed_templates_with_extension::<Assets>(".hbs");
439    /// ```
440    ///
441    #[cfg(feature = "rust-embed")]
442    #[cfg_attr(docsrs, doc(cfg(feature = "rust-embed")))]
443    pub fn register_embed_templates_with_extension<E>(
444        &mut self,
445        tpl_extension: &str,
446    ) -> Result<(), TemplateError>
447    where
448        E: RustEmbed,
449    {
450        for file_name in E::iter().filter(|x| x.ends_with(tpl_extension)) {
451            let tpl_name = file_name
452                .strip_suffix(tpl_extension)
453                .unwrap_or(&file_name)
454                .to_owned();
455            let source = LazySource::new(move || {
456                E::get(&file_name)
457                    .map(|file| file.data.to_vec())
458                    .and_then(|data| String::from_utf8(data).ok())
459            });
460            let tpl_content = source
461                .load()
462                .map_err(|e| (e, "Template load error".to_owned()))?;
463            self.register_template_string(&tpl_name, &tpl_content)?;
464
465            if self.dev_mode {
466                self.template_sources.insert(tpl_name, Arc::new(source));
467            }
468        }
469        Ok(())
470    }
471
472    /// Remove a template from the registry
473    pub fn unregister_template(&mut self, name: &str) {
474        self.templates.remove(name);
475        self.template_sources.remove(name);
476    }
477
478    /// Register a helper
479    pub fn register_helper(&mut self, name: &str, def: Box<dyn HelperDef + Send + Sync + 'reg>) {
480        self.helpers.insert(name.to_string(), def.into());
481    }
482
483    /// Register a [rhai](https://docs.rs/rhai/) script as handlebars helper
484    ///
485    /// Currently only simple helpers are supported. You can do computation or
486    /// string formatting with rhai script.
487    ///
488    /// Helper parameters and hash are available in rhai script as array `params`
489    /// and map `hash`. Example script:
490    ///
491    /// ```handlebars
492    /// {{percent 0.34 label="%"}}
493    /// ```
494    ///
495    /// ```rhai
496    /// // percent.rhai
497    /// let value = params[0];
498    /// let label = hash["label"];
499    ///
500    /// (value * 100).to_string() + label
501    /// ```
502    ///
503    #[cfg(feature = "script_helper")]
504    #[cfg_attr(docsrs, doc(cfg(feature = "script_helper")))]
505    pub fn register_script_helper(&mut self, name: &str, script: &str) -> Result<(), ScriptError> {
506        let compiled = self.engine.compile(script)?;
507        let script_helper = ScriptHelper { script: compiled };
508        self.helpers
509            .insert(name.to_string(), Arc::new(script_helper));
510        Ok(())
511    }
512
513    /// Register a [rhai](https://docs.rs/rhai/) script from file
514    ///
515    /// When dev mode is enable, script file is reloaded from original file
516    /// everytime it is called.
517    #[cfg(feature = "script_helper")]
518    #[cfg_attr(docsrs, doc(cfg(feature = "script_helper")))]
519    pub fn register_script_helper_file<P>(
520        &mut self,
521        name: &str,
522        script_path: P,
523    ) -> Result<(), ScriptError>
524    where
525        P: AsRef<Path>,
526    {
527        let source = FileSource::new(script_path.as_ref().into());
528        let script = source.load()?;
529
530        self.script_sources
531            .insert(name.to_owned(), Arc::new(source));
532        self.register_script_helper(name, &script)
533    }
534
535    /// Borrow a read-only reference to current rhai engine
536    #[cfg(feature = "script_helper")]
537    #[cfg_attr(docsrs, doc(cfg(feature = "script_helper")))]
538    pub fn engine(&self) -> &Engine {
539        self.engine.as_ref()
540    }
541
542    /// Set a custom rhai engine for the registry.
543    ///
544    /// *Note that* you need to set custom engine before adding scripts.
545    #[cfg(feature = "script_helper")]
546    #[cfg_attr(docsrs, doc(cfg(feature = "script_helper")))]
547    pub fn set_engine(&mut self, engine: Engine) {
548        self.engine = Arc::new(engine);
549    }
550
551    /// Register a decorator
552    pub fn register_decorator(
553        &mut self,
554        name: &str,
555        def: Box<dyn DecoratorDef + Send + Sync + 'reg>,
556    ) {
557        self.decorators.insert(name.to_string(), def.into());
558    }
559
560    /// Register a new *escape fn* to be used from now on by this registry.
561    pub fn register_escape_fn<F: 'static + Fn(&str) -> String + Send + Sync>(
562        &mut self,
563        escape_fn: F,
564    ) {
565        self.escape_fn = Arc::new(escape_fn);
566    }
567
568    /// Restore the default *escape fn*.
569    pub fn unregister_escape_fn(&mut self) {
570        self.escape_fn = Arc::new(html_escape);
571    }
572
573    /// Get a reference to the current *escape fn*.
574    pub fn get_escape_fn(&self) -> &dyn Fn(&str) -> String {
575        self.escape_fn.as_ref()
576    }
577
578    /// Return `true` if a template is registered for the given name
579    pub fn has_template(&self, name: &str) -> bool {
580        self.get_template(name).is_some()
581    }
582
583    /// Return a registered template,
584    pub fn get_template(&self, name: &str) -> Option<&Template> {
585        self.templates.get(name)
586    }
587
588    #[inline]
589    pub(crate) fn get_or_load_template_optional(
590        &'reg self,
591        name: &str,
592    ) -> Option<Result<Cow<'reg, Template>, RenderError>> {
593        if let (true, Some(source)) = (self.dev_mode, self.template_sources.get(name)) {
594            let r = source
595                .load()
596                .map_err(|e| TemplateError::from((e, name.to_owned())))
597                .and_then(|tpl_str| {
598                    Template::compile2(
599                        tpl_str.as_ref(),
600                        TemplateOptions {
601                            name: Some(name.to_owned()),
602                            prevent_indent: self.prevent_indent,
603                            is_partial: false,
604                        },
605                    )
606                })
607                .map(Cow::Owned)
608                .map_err(RenderError::from);
609            Some(r)
610        } else {
611            self.templates.get(name).map(|t| Ok(Cow::Borrowed(t)))
612        }
613    }
614
615    #[inline]
616    pub(crate) fn get_or_load_template(
617        &'reg self,
618        name: &str,
619    ) -> Result<Cow<'reg, Template>, RenderError> {
620        if let Some(result) = self.get_or_load_template_optional(name) {
621            result
622        } else {
623            Err(RenderErrorReason::TemplateNotFound(name.to_owned()).into())
624        }
625    }
626
627    /// Return a registered helper
628    #[inline]
629    pub(crate) fn get_or_load_helper(
630        &'reg self,
631        name: &str,
632    ) -> Result<Option<Arc<dyn HelperDef + Send + Sync + 'reg>>, RenderError> {
633        #[cfg(feature = "script_helper")]
634        if let (true, Some(source)) = (self.dev_mode, self.script_sources.get(name)) {
635            return source
636                .load()
637                .map_err(ScriptError::from)
638                .and_then(|s| {
639                    let helper = Box::new(ScriptHelper {
640                        script: self.engine.compile(s)?,
641                    }) as Box<dyn HelperDef + Send + Sync>;
642                    Ok(Some(helper.into()))
643                })
644                .map_err(|e| RenderError::from(RenderErrorReason::from(e)));
645        }
646
647        Ok(self.helpers.get(name).cloned())
648    }
649
650    #[inline]
651    pub(crate) fn has_helper(&self, name: &str) -> bool {
652        self.helpers.contains_key(name)
653    }
654
655    /// Return a registered decorator
656    #[inline]
657    pub(crate) fn get_decorator(
658        &self,
659        name: &str,
660    ) -> Option<&(dyn DecoratorDef + Send + Sync + 'reg)> {
661        self.decorators.get(name).map(AsRef::as_ref)
662    }
663
664    /// Return all templates registered
665    ///
666    /// **Note that** in dev mode, the template returned from this method may
667    /// not reflect its latest state. This method doesn't try to reload templates
668    /// from its source.
669    pub fn get_templates(&self) -> &HashMap<String, Template> {
670        &self.templates
671    }
672
673    /// Unregister all templates
674    pub fn clear_templates(&mut self) {
675        self.templates.clear();
676        self.template_sources.clear();
677    }
678
679    fn gather_dev_mode_templates(
680        &'reg self,
681        prebound: Option<(&str, Cow<'reg, Template>)>,
682    ) -> Result<BTreeMap<String, Cow<'reg, Template>>, RenderError> {
683        let prebound_name = prebound.as_ref().map(|(name, _)| *name);
684        let mut res = BTreeMap::new();
685        for name in self.template_sources.keys() {
686            if Some(&**name) == prebound_name {
687                continue;
688            }
689            res.insert(name.clone(), self.get_or_load_template(name)?);
690        }
691        if let Some((name, prebound)) = prebound {
692            res.insert(name.to_owned(), prebound);
693        }
694        Ok(res)
695    }
696
697    fn render_resolved_template_to_output(
698        &self,
699        name: Option<&str>,
700        template: Cow<'_, Template>,
701        ctx: &Context,
702        output: &mut impl Output,
703    ) -> Result<(), RenderError> {
704        if !self.dev_mode {
705            let mut render_context = RenderContext::new(template.name.as_ref());
706            return template.render(self, ctx, &mut render_context, output);
707        }
708
709        let dev_mode_templates;
710        let template = if let Some(name) = name {
711            dev_mode_templates = self.gather_dev_mode_templates(Some((name, template)))?;
712            &dev_mode_templates[name]
713        } else {
714            dev_mode_templates = self.gather_dev_mode_templates(None)?;
715            &template
716        };
717
718        let mut render_context = RenderContext::new(template.name.as_ref());
719
720        render_context.set_dev_mode_templates(Some(&dev_mode_templates));
721
722        template.render(self, ctx, &mut render_context, output)
723    }
724
725    #[inline]
726    fn render_to_output<O>(
727        &self,
728        name: &str,
729        ctx: &Context,
730        output: &mut O,
731    ) -> Result<(), RenderError>
732    where
733        O: Output,
734    {
735        self.render_resolved_template_to_output(
736            Some(name),
737            self.get_or_load_template(name)?,
738            ctx,
739            output,
740        )
741    }
742
743    /// Render a registered template with some data into a string
744    ///
745    /// * `name` is the template name you registered previously
746    /// * `data` is the data that implements `serde::Serialize`
747    ///
748    /// Returns rendered string or a struct with error information
749    pub fn render<T>(&self, name: &str, data: &T) -> Result<String, RenderError>
750    where
751        T: Serialize,
752    {
753        let mut output = StringOutput::new();
754        let ctx = Context::wraps(data)?;
755        self.render_to_output(name, &ctx, &mut output)?;
756        output.into_string().map_err(RenderError::from)
757    }
758
759    /// Render a registered template with reused context
760    pub fn render_with_context(&self, name: &str, ctx: &Context) -> Result<String, RenderError> {
761        let mut output = StringOutput::new();
762        self.render_to_output(name, ctx, &mut output)?;
763        output.into_string().map_err(RenderError::from)
764    }
765
766    /// Render a registered template and write data to the `std::io::Write`
767    pub fn render_to_write<T, W>(&self, name: &str, data: &T, writer: W) -> Result<(), RenderError>
768    where
769        T: Serialize,
770        W: Write,
771    {
772        let mut output = WriteOutput::new(writer);
773        let ctx = Context::wraps(data)?;
774        self.render_to_output(name, &ctx, &mut output)
775    }
776
777    /// Render a registered template using reusable `Context`, and write data to
778    /// the `std::io::Write`
779    pub fn render_with_context_to_write<W>(
780        &self,
781        name: &str,
782        ctx: &Context,
783        writer: W,
784    ) -> Result<(), RenderError>
785    where
786        W: Write,
787    {
788        let mut output = WriteOutput::new(writer);
789        self.render_to_output(name, ctx, &mut output)
790    }
791
792    /// Render a template string using current registry without registering it
793    pub fn render_template<T>(&self, template_string: &str, data: &T) -> Result<String, RenderError>
794    where
795        T: Serialize,
796    {
797        let mut writer = StringWriter::new();
798        self.render_template_to_write(template_string, data, &mut writer)?;
799        Ok(writer.into_string())
800    }
801
802    /// Render a template string using reusable context data
803    pub fn render_template_with_context(
804        &self,
805        template_string: &str,
806        ctx: &Context,
807    ) -> Result<String, RenderError> {
808        let tpl = Template::compile2(
809            template_string,
810            TemplateOptions {
811                prevent_indent: self.prevent_indent,
812                ..Default::default()
813            },
814        )
815        .map_err(RenderError::from)?;
816
817        let mut out = StringOutput::new();
818        self.render_resolved_template_to_output(None, Cow::Owned(tpl), ctx, &mut out)?;
819
820        out.into_string().map_err(RenderError::from)
821    }
822
823    /// Render a template string using resuable context, and write data into
824    /// `std::io::Write`
825    pub fn render_template_with_context_to_write<W>(
826        &self,
827        template_string: &str,
828        ctx: &Context,
829        writer: W,
830    ) -> Result<(), RenderError>
831    where
832        W: Write,
833    {
834        let tpl = Template::compile2(
835            template_string,
836            TemplateOptions {
837                prevent_indent: self.prevent_indent,
838                ..Default::default()
839            },
840        )
841        .map_err(RenderError::from)?;
842        let mut out = WriteOutput::new(writer);
843
844        self.render_resolved_template_to_output(None, Cow::Owned(tpl), ctx, &mut out)
845    }
846
847    /// Render a template string using current registry without registering it
848    pub fn render_template_to_write<T, W>(
849        &self,
850        template_string: &str,
851        data: &T,
852        writer: W,
853    ) -> Result<(), RenderError>
854    where
855        T: Serialize,
856        W: Write,
857    {
858        let ctx = Context::wraps(data)?;
859        self.render_template_with_context_to_write(template_string, &ctx, writer)
860    }
861
862    #[cfg(feature = "string_helpers")]
863    #[inline]
864    fn register_string_helpers(&mut self) {
865        use helpers::string_helpers::{
866            kebab_case, lower_camel_case, shouty_kebab_case, shouty_snake_case, snake_case,
867            title_case, train_case, upper_camel_case,
868        };
869
870        self.register_helper("lowerCamelCase", Box::new(lower_camel_case));
871        self.register_helper("upperCamelCase", Box::new(upper_camel_case));
872        self.register_helper("snakeCase", Box::new(snake_case));
873        self.register_helper("kebabCase", Box::new(kebab_case));
874        self.register_helper("shoutySnakeCase", Box::new(shouty_snake_case));
875        self.register_helper("shoutyKebabCase", Box::new(shouty_kebab_case));
876        self.register_helper("titleCase", Box::new(title_case));
877        self.register_helper("trainCase", Box::new(train_case));
878    }
879}
880
881#[cfg(test)]
882mod test {
883    use crate::context::Context;
884    use crate::error::{RenderError, RenderErrorReason};
885    use crate::helpers::HelperDef;
886    use crate::output::Output;
887    use crate::registry::Registry;
888    use crate::render::{Helper, RenderContext, Renderable};
889    use crate::support::str::StringWriter;
890    use crate::template::Template;
891    use std::fs::File;
892    use std::io::Write;
893    use tempfile::tempdir;
894
895    #[derive(Clone, Copy)]
896    struct DummyHelper;
897
898    impl HelperDef for DummyHelper {
899        fn call<'reg: 'rc, 'rc>(
900            &self,
901            h: &Helper<'rc>,
902            r: &'reg Registry<'reg>,
903            ctx: &'rc Context,
904            rc: &mut RenderContext<'reg, 'rc>,
905            out: &mut dyn Output,
906        ) -> Result<(), RenderError> {
907            h.template().unwrap().render(r, ctx, rc, out)
908        }
909    }
910
911    static DUMMY_HELPER: DummyHelper = DummyHelper;
912
913    #[test]
914    fn test_registry_operations() {
915        let mut r = Registry::new();
916
917        assert!(r.register_template_string("index", "<h1></h1>").is_ok());
918
919        let tpl = Template::compile("<h2></h2>").unwrap();
920        r.register_template("index2", tpl);
921
922        assert_eq!(r.templates.len(), 2);
923
924        r.unregister_template("index");
925        assert_eq!(r.templates.len(), 1);
926
927        r.clear_templates();
928        assert_eq!(r.templates.len(), 0);
929
930        r.register_helper("dummy", Box::new(DUMMY_HELPER));
931
932        // built-in helpers plus 1
933        let num_helpers = 7;
934        let num_boolean_helpers = 10; // stuff like gt and lte
935        let num_custom_helpers = 1; // dummy from above
936        #[cfg(feature = "string_helpers")]
937        let string_helpers = 8;
938        #[cfg(not(feature = "string_helpers"))]
939        let string_helpers = 0;
940        assert_eq!(
941            r.helpers.len(),
942            num_helpers + num_boolean_helpers + num_custom_helpers + string_helpers
943        );
944    }
945
946    #[test]
947    #[cfg(feature = "dir_source")]
948    fn test_register_templates_directory() {
949        use std::fs::DirBuilder;
950
951        use crate::registry::DirectorySourceOptions;
952
953        let mut r = Registry::new();
954        {
955            let dir = tempdir().unwrap();
956
957            assert_eq!(r.templates.len(), 0);
958
959            let file1_path = dir.path().join("t1.hbs");
960            let mut file1: File = File::create(file1_path).unwrap();
961            writeln!(file1, "<h1>Hello {{world}}!</h1>").unwrap();
962
963            let file2_path = dir.path().join("t2.hbs");
964            let mut file2: File = File::create(file2_path).unwrap();
965            writeln!(file2, "<h1>Hola {{world}}!</h1>").unwrap();
966
967            let file3_path = dir.path().join("t3.hbs");
968            let mut file3: File = File::create(file3_path).unwrap();
969            writeln!(file3, "<h1>Hallo {{world}}!</h1>").unwrap();
970
971            let file4_path = dir.path().join(".t4.hbs");
972            let mut file4: File = File::create(file4_path).unwrap();
973            writeln!(file4, "<h1>Hallo {{world}}!</h1>").unwrap();
974
975            r.register_templates_directory(dir.path(), DirectorySourceOptions::default())
976                .unwrap();
977
978            assert_eq!(r.templates.len(), 3);
979            assert!(r.templates.contains_key("t1"));
980            assert!(r.templates.contains_key("t2"));
981            assert!(r.templates.contains_key("t3"));
982            assert!(!r.templates.contains_key("t4"));
983
984            drop(file1);
985            drop(file2);
986            drop(file3);
987
988            dir.close().unwrap();
989        }
990
991        {
992            let dir = tempdir().unwrap();
993
994            let file1_path = dir.path().join("t4.hbs");
995            let mut file1: File = File::create(file1_path).unwrap();
996            writeln!(file1, "<h1>Hello {{world}}!</h1>").unwrap();
997
998            let file2_path = dir.path().join("t5.erb");
999            let mut file2: File = File::create(file2_path).unwrap();
1000            writeln!(file2, "<h1>Hello {{% world %}}!</h1>").unwrap();
1001
1002            let file3_path = dir.path().join("t6.html");
1003            let mut file3: File = File::create(file3_path).unwrap();
1004            writeln!(file3, "<h1>Hello world!</h1>").unwrap();
1005
1006            r.register_templates_directory(dir.path(), DirectorySourceOptions::default())
1007                .unwrap();
1008
1009            assert_eq!(r.templates.len(), 4);
1010            assert!(r.templates.contains_key("t4"));
1011
1012            drop(file1);
1013            drop(file2);
1014            drop(file3);
1015
1016            dir.close().unwrap();
1017        }
1018
1019        {
1020            let dir = tempdir().unwrap();
1021
1022            DirBuilder::new().create(dir.path().join("french")).unwrap();
1023            DirBuilder::new()
1024                .create(dir.path().join("portugese"))
1025                .unwrap();
1026            DirBuilder::new()
1027                .create(dir.path().join("italian"))
1028                .unwrap();
1029
1030            let file1_path = dir.path().join("french/t7.hbs");
1031            let mut file1: File = File::create(file1_path).unwrap();
1032            writeln!(file1, "<h1>Bonjour {{world}}!</h1>").unwrap();
1033
1034            let file2_path = dir.path().join("portugese/t8.hbs");
1035            let mut file2: File = File::create(file2_path).unwrap();
1036            writeln!(file2, "<h1>Ola {{world}}!</h1>").unwrap();
1037
1038            let file3_path = dir.path().join("italian/t9.hbs");
1039            let mut file3: File = File::create(file3_path).unwrap();
1040            writeln!(file3, "<h1>Ciao {{world}}!</h1>").unwrap();
1041
1042            r.register_templates_directory(dir.path(), DirectorySourceOptions::default())
1043                .unwrap();
1044
1045            assert_eq!(r.templates.len(), 7);
1046            assert!(r.templates.contains_key("french/t7"));
1047            assert!(r.templates.contains_key("portugese/t8"));
1048            assert!(r.templates.contains_key("italian/t9"));
1049
1050            drop(file1);
1051            drop(file2);
1052            drop(file3);
1053
1054            dir.close().unwrap();
1055        }
1056
1057        {
1058            let dir = tempdir().unwrap();
1059
1060            let file1_path = dir.path().join("t10.hbs");
1061            let mut file1: File = File::create(file1_path).unwrap();
1062            writeln!(file1, "<h1>Bonjour {{world}}!</h1>").unwrap();
1063
1064            let mut dir_path = dir
1065                .path()
1066                .to_string_lossy()
1067                .replace(std::path::MAIN_SEPARATOR, "/");
1068            if !dir_path.ends_with('/') {
1069                dir_path.push('/');
1070            }
1071            r.register_templates_directory(dir_path, DirectorySourceOptions::default())
1072                .unwrap();
1073
1074            assert_eq!(r.templates.len(), 8);
1075            assert!(r.templates.contains_key("t10"));
1076
1077            drop(file1);
1078            dir.close().unwrap();
1079        }
1080
1081        {
1082            let dir = tempdir().unwrap();
1083            let mut r = Registry::new();
1084
1085            let file1_path = dir.path().join("t11.hbs.html");
1086            let mut file1: File = File::create(file1_path).unwrap();
1087            writeln!(file1, "<h1>Bonjour {{world}}!</h1>").unwrap();
1088
1089            let mut dir_path = dir
1090                .path()
1091                .to_string_lossy()
1092                .replace(std::path::MAIN_SEPARATOR, "/");
1093            if !dir_path.ends_with('/') {
1094                dir_path.push('/');
1095            }
1096            r.register_templates_directory(
1097                dir_path,
1098                DirectorySourceOptions {
1099                    tpl_extension: ".hbs.html".to_owned(),
1100                    ..Default::default()
1101                },
1102            )
1103            .unwrap();
1104
1105            assert_eq!(r.templates.len(), 1);
1106            assert!(r.templates.contains_key("t11"));
1107
1108            drop(file1);
1109            dir.close().unwrap();
1110        }
1111
1112        {
1113            let dir = tempdir().unwrap();
1114            let mut r = Registry::new();
1115
1116            assert_eq!(r.templates.len(), 0);
1117
1118            let file1_path = dir.path().join(".t12.hbs");
1119            let mut file1: File = File::create(file1_path).unwrap();
1120            writeln!(file1, "<h1>Hello {{world}}!</h1>").unwrap();
1121
1122            r.register_templates_directory(
1123                dir.path(),
1124                DirectorySourceOptions {
1125                    hidden: true,
1126                    ..Default::default()
1127                },
1128            )
1129            .unwrap();
1130
1131            assert_eq!(r.templates.len(), 1);
1132            assert!(r.templates.contains_key(".t12"));
1133
1134            drop(file1);
1135
1136            dir.close().unwrap();
1137        }
1138    }
1139
1140    #[test]
1141    fn test_render_to_write() {
1142        let mut r = Registry::new();
1143
1144        assert!(r.register_template_string("index", "<h1></h1>").is_ok());
1145
1146        let mut sw = StringWriter::new();
1147        {
1148            r.render_to_write("index", &(), &mut sw).ok().unwrap();
1149        }
1150
1151        assert_eq!("<h1></h1>".to_string(), sw.into_string());
1152    }
1153
1154    #[test]
1155    fn test_escape_fn() {
1156        let mut r = Registry::new();
1157
1158        let input = String::from("\"<>&");
1159
1160        r.register_template_string("test", String::from("{{this}}"))
1161            .unwrap();
1162
1163        assert_eq!("&quot;&lt;&gt;&amp;", r.render("test", &input).unwrap());
1164
1165        r.register_escape_fn(|s| s.into());
1166
1167        assert_eq!("\"<>&", r.render("test", &input).unwrap());
1168
1169        r.unregister_escape_fn();
1170
1171        assert_eq!("&quot;&lt;&gt;&amp;", r.render("test", &input).unwrap());
1172    }
1173
1174    #[test]
1175    fn test_escape() {
1176        let r = Registry::new();
1177        let data = json!({"hello": "world"});
1178
1179        assert_eq!(
1180            "{{hello}}",
1181            r.render_template(r"\{{hello}}", &data).unwrap()
1182        );
1183
1184        assert_eq!(
1185            " {{hello}}",
1186            r.render_template(r" \{{hello}}", &data).unwrap()
1187        );
1188
1189        assert_eq!(r"\world", r.render_template(r"\\{{hello}}", &data).unwrap());
1190    }
1191
1192    #[test]
1193    fn test_strict_mode() {
1194        let mut r = Registry::new();
1195        assert!(!r.strict_mode());
1196
1197        r.set_strict_mode(true);
1198        assert!(r.strict_mode());
1199
1200        let data = json!({
1201            "the_only_key": "the_only_value"
1202        });
1203
1204        assert!(r
1205            .render_template("accessing the_only_key {{the_only_key}}", &data)
1206            .is_ok());
1207        assert!(r
1208            .render_template("accessing non-exists key {{the_key_never_exists}}", &data)
1209            .is_err());
1210
1211        let render_error = r
1212            .render_template("accessing non-exists key {{the_key_never_exists}}", &data)
1213            .unwrap_err();
1214        assert_eq!(render_error.column_no.unwrap(), 26);
1215        assert_eq!(
1216            match render_error.reason() {
1217                RenderErrorReason::MissingVariable(path) => path.as_ref().unwrap(),
1218                _ => unreachable!(),
1219            },
1220            "the_key_never_exists"
1221        );
1222
1223        let data2 = json!([1, 2, 3]);
1224        assert!(r
1225            .render_template("accessing valid array index {{this.[2]}}", &data2)
1226            .is_ok());
1227        assert!(r
1228            .render_template("accessing invalid array index {{this.[3]}}", &data2)
1229            .is_err());
1230        let render_error2 = r
1231            .render_template("accessing invalid array index {{this.[3]}}", &data2)
1232            .unwrap_err();
1233        assert_eq!(render_error2.column_no.unwrap(), 31);
1234        assert_eq!(
1235            match render_error2.reason() {
1236                RenderErrorReason::MissingVariable(path) => path.as_ref().unwrap(),
1237                _ => unreachable!(),
1238            },
1239            "this.[3]"
1240        );
1241    }
1242
1243    use crate::json::value::ScopedJson;
1244    struct GenMissingHelper;
1245    impl HelperDef for GenMissingHelper {
1246        fn call_inner<'reg: 'rc, 'rc>(
1247            &self,
1248            _: &Helper<'rc>,
1249            _: &'reg Registry<'reg>,
1250            _: &'rc Context,
1251            _: &mut RenderContext<'reg, 'rc>,
1252        ) -> Result<ScopedJson<'rc>, RenderError> {
1253            Ok(ScopedJson::Missing)
1254        }
1255    }
1256
1257    #[test]
1258    fn test_strict_mode_in_helper() {
1259        let mut r = Registry::new();
1260        r.set_strict_mode(true);
1261
1262        r.register_helper(
1263            "check_missing",
1264            Box::new(
1265                |h: &Helper<'_>,
1266                 _: &Registry<'_>,
1267                 _: &Context,
1268                 _: &mut RenderContext<'_, '_>,
1269                 _: &mut dyn Output|
1270                 -> Result<(), RenderError> {
1271                    let value = h.param(0).unwrap();
1272                    assert!(value.is_value_missing());
1273                    Ok(())
1274                },
1275            ),
1276        );
1277
1278        r.register_helper("generate_missing_value", Box::new(GenMissingHelper));
1279
1280        let data = json!({
1281            "the_key_we_have": "the_value_we_have"
1282        });
1283        assert!(r
1284            .render_template("accessing non-exists key {{the_key_we_dont_have}}", &data)
1285            .is_err());
1286        assert!(r
1287            .render_template(
1288                "accessing non-exists key from helper {{check_missing the_key_we_dont_have}}",
1289                &data
1290            )
1291            .is_ok());
1292        assert!(r
1293            .render_template(
1294                "accessing helper that generates missing value {{generate_missing_value}}",
1295                &data
1296            )
1297            .is_err());
1298    }
1299
1300    #[test]
1301    fn test_html_expression() {
1302        let reg = Registry::new();
1303        assert_eq!(
1304            reg.render_template("{{{ a }}}", &json!({"a": "<b>bold</b>"}))
1305                .unwrap(),
1306            "<b>bold</b>"
1307        );
1308        assert_eq!(
1309            reg.render_template("{{ &a }}", &json!({"a": "<b>bold</b>"}))
1310                .unwrap(),
1311            "<b>bold</b>"
1312        );
1313    }
1314
1315    #[test]
1316    fn test_render_context() {
1317        let mut reg = Registry::new();
1318
1319        let data = json!([0, 1, 2, 3]);
1320
1321        assert_eq!(
1322            "0123",
1323            reg.render_template_with_context(
1324                "{{#each this}}{{this}}{{/each}}",
1325                &Context::wraps(&data).unwrap()
1326            )
1327            .unwrap()
1328        );
1329
1330        reg.register_template_string("t0", "{{#each this}}{{this}}{{/each}}")
1331            .unwrap();
1332        assert_eq!(
1333            "0123",
1334            reg.render_with_context("t0", &Context::from(data)).unwrap()
1335        );
1336    }
1337
1338    #[test]
1339    fn test_keys_starts_with_null() {
1340        env_logger::init();
1341        let reg = Registry::new();
1342        let data = json!({
1343            "optional": true,
1344            "is_null": true,
1345            "nullable": true,
1346            "null": true,
1347            "falsevalue": true,
1348        });
1349        assert_eq!(
1350            "optional: true --> true",
1351            reg.render_template(
1352                "optional: {{optional}} --> {{#if optional }}true{{else}}false{{/if}}",
1353                &data
1354            )
1355            .unwrap()
1356        );
1357        assert_eq!(
1358            "is_null: true --> true",
1359            reg.render_template(
1360                "is_null: {{is_null}} --> {{#if is_null }}true{{else}}false{{/if}}",
1361                &data
1362            )
1363            .unwrap()
1364        );
1365        assert_eq!(
1366            "nullable: true --> true",
1367            reg.render_template(
1368                "nullable: {{nullable}} --> {{#if nullable }}true{{else}}false{{/if}}",
1369                &data
1370            )
1371            .unwrap()
1372        );
1373        assert_eq!(
1374            "falsevalue: true --> true",
1375            reg.render_template(
1376                "falsevalue: {{falsevalue}} --> {{#if falsevalue }}true{{else}}false{{/if}}",
1377                &data
1378            )
1379            .unwrap()
1380        );
1381        assert_eq!(
1382            "null: true --> false",
1383            reg.render_template(
1384                "null: {{null}} --> {{#if null }}true{{else}}false{{/if}}",
1385                &data
1386            )
1387            .unwrap()
1388        );
1389        assert_eq!(
1390            "null: true --> true",
1391            reg.render_template(
1392                "null: {{null}} --> {{#if this.[null]}}true{{else}}false{{/if}}",
1393                &data
1394            )
1395            .unwrap()
1396        );
1397    }
1398
1399    #[test]
1400    fn test_dev_mode_template_reload() {
1401        let mut reg = Registry::new();
1402        reg.set_dev_mode(true);
1403        assert!(reg.dev_mode());
1404
1405        let dir = tempdir().unwrap();
1406        let file1_path = dir.path().join("t1.hbs");
1407        {
1408            let mut file1: File = File::create(&file1_path).unwrap();
1409            write!(file1, "<h1>Hello {{{{name}}}}!</h1>").unwrap();
1410        }
1411
1412        reg.register_template_file("t1", &file1_path).unwrap();
1413
1414        assert_eq!(
1415            reg.render("t1", &json!({"name": "Alex"})).unwrap(),
1416            "<h1>Hello Alex!</h1>"
1417        );
1418
1419        {
1420            let mut file1: File = File::create(&file1_path).unwrap();
1421            write!(file1, "<h1>Privet {{{{name}}}}!</h1>").unwrap();
1422        }
1423
1424        assert_eq!(
1425            reg.render("t1", &json!({"name": "Alex"})).unwrap(),
1426            "<h1>Privet Alex!</h1>"
1427        );
1428
1429        dir.close().unwrap();
1430    }
1431
1432    #[test]
1433    #[cfg(feature = "script_helper")]
1434    fn test_script_helper() {
1435        let mut reg = Registry::new();
1436
1437        reg.register_script_helper("acc", "params.reduce(|sum, x| x + sum, 0)")
1438            .unwrap();
1439
1440        assert_eq!(
1441            reg.render_template("{{acc 1 2 3 4}}", &json!({})).unwrap(),
1442            "10"
1443        );
1444    }
1445
1446    #[test]
1447    #[cfg(feature = "script_helper")]
1448    fn test_script_helper_dev_mode() {
1449        let mut reg = Registry::new();
1450        reg.set_dev_mode(true);
1451
1452        let dir = tempdir().unwrap();
1453        let file1_path = dir.path().join("acc.rhai");
1454        {
1455            let mut file1: File = File::create(&file1_path).unwrap();
1456            write!(file1, "params.reduce(|sum, x| x + sum, 0)").unwrap();
1457        }
1458
1459        reg.register_script_helper_file("acc", &file1_path).unwrap();
1460
1461        assert_eq!(
1462            reg.render_template("{{acc 1 2 3 4}}", &json!({})).unwrap(),
1463            "10"
1464        );
1465
1466        {
1467            let mut file1: File = File::create(&file1_path).unwrap();
1468            write!(file1, "params.reduce(|sum, x| x * sum, 1)").unwrap();
1469        }
1470
1471        assert_eq!(
1472            reg.render_template("{{acc 1 2 3 4}}", &json!({})).unwrap(),
1473            "24"
1474        );
1475
1476        dir.close().unwrap();
1477    }
1478
1479    #[test]
1480    #[cfg(feature = "script_helper")]
1481    fn test_engine_access() {
1482        use rhai::Engine;
1483
1484        let mut registry = Registry::new();
1485        let mut eng = Engine::new();
1486        eng.set_max_string_size(1000);
1487        registry.set_engine(eng);
1488
1489        assert_eq!(1000, registry.engine().max_string_size());
1490    }
1491}