handlebars/
render.rs

1use std::borrow::{Borrow, Cow};
2use std::collections::{BTreeMap, VecDeque};
3use std::fmt;
4use std::rc::Rc;
5
6use serde_json::value::Value as Json;
7
8use crate::block::BlockContext;
9use crate::context::Context;
10use crate::error::RenderError;
11use crate::helpers::HelperDef;
12use crate::json::path::Path;
13use crate::json::value::{JsonRender, PathAndJson, ScopedJson};
14use crate::output::{Output, StringOutput};
15use crate::registry::Registry;
16use crate::support;
17use crate::template::TemplateElement::*;
18use crate::template::{
19    BlockParam, DecoratorTemplate, HelperTemplate, Parameter, Template, TemplateElement,
20    TemplateMapping,
21};
22use crate::{partial, RenderErrorReason};
23
24const HELPER_MISSING: &str = "helperMissing";
25const BLOCK_HELPER_MISSING: &str = "blockHelperMissing";
26
27/// The context of a render call
28///
29/// This context stores information of a render and a writer where generated
30/// content is written to.
31///
32#[derive(Clone, Debug)]
33pub struct RenderContext<'reg, 'rc> {
34    inner: Rc<RenderContextInner<'reg, 'rc>>,
35    blocks: VecDeque<BlockContext<'rc>>,
36    // copy-on-write context
37    modified_context: Option<Rc<Context>>,
38}
39
40#[derive(Clone)]
41pub struct RenderContextInner<'reg: 'rc, 'rc> {
42    partials: BTreeMap<String, &'rc Template>,
43    partial_block_stack: VecDeque<&'reg Template>,
44    partial_block_depth: isize,
45    local_helpers: BTreeMap<String, Rc<dyn HelperDef + Send + Sync + 'rc>>,
46    /// current template name
47    current_template: Option<&'rc String>,
48    /// root template name
49    root_template: Option<&'reg String>,
50    disable_escape: bool,
51    indent_string: Option<&'reg String>,
52}
53
54impl<'reg: 'rc, 'rc> RenderContext<'reg, 'rc> {
55    /// Create a render context
56    pub fn new(root_template: Option<&'reg String>) -> RenderContext<'reg, 'rc> {
57        let inner = Rc::new(RenderContextInner {
58            partials: BTreeMap::new(),
59            partial_block_stack: VecDeque::new(),
60            partial_block_depth: 0,
61            local_helpers: BTreeMap::new(),
62            current_template: None,
63            root_template,
64            disable_escape: false,
65            indent_string: None,
66        });
67
68        let mut blocks = VecDeque::with_capacity(5);
69        blocks.push_front(BlockContext::new());
70
71        let modified_context = None;
72        RenderContext {
73            inner,
74            blocks,
75            modified_context,
76        }
77    }
78
79    pub(crate) fn new_for_block(&self) -> RenderContext<'reg, 'rc> {
80        let inner = self.inner.clone();
81
82        let mut blocks = VecDeque::with_capacity(2);
83        blocks.push_front(BlockContext::new());
84
85        let modified_context = self.modified_context.clone();
86
87        RenderContext {
88            inner,
89            blocks,
90            modified_context,
91        }
92    }
93
94    /// Push a block context into render context stack. This is typically
95    /// called when you entering a block scope.
96    pub fn push_block(&mut self, block: BlockContext<'rc>) {
97        self.blocks.push_front(block);
98    }
99
100    /// Pop and drop current block context.
101    /// This is typically called when leaving a block scope.
102    pub fn pop_block(&mut self) {
103        self.blocks.pop_front();
104    }
105
106    pub(crate) fn clear_blocks(&mut self) {
107        self.blocks.clear();
108    }
109
110    /// Borrow a reference to current block context
111    pub fn block(&self) -> Option<&BlockContext<'rc>> {
112        self.blocks.front()
113    }
114
115    /// Borrow a mutable reference to current block context in order to
116    /// modify some data.
117    pub fn block_mut(&mut self) -> Option<&mut BlockContext<'rc>> {
118        self.blocks.front_mut()
119    }
120
121    fn inner(&self) -> &RenderContextInner<'reg, 'rc> {
122        self.inner.borrow()
123    }
124
125    fn inner_mut(&mut self) -> &mut RenderContextInner<'reg, 'rc> {
126        Rc::make_mut(&mut self.inner)
127    }
128
129    /// Get the modified context data if any
130    pub fn context(&self) -> Option<Rc<Context>> {
131        self.modified_context.clone()
132    }
133
134    /// Set new context data into the render process.
135    /// This is typically called in decorators where user can modify
136    /// the data they were rendering.
137    pub fn set_context(&mut self, ctx: Context) {
138        self.modified_context = Some(Rc::new(ctx))
139    }
140
141    /// Evaluate a Json path in current scope.
142    ///
143    /// Typically you don't need to evaluate it by yourself.
144    /// The Helper and Decorator API will provide your evaluated value of
145    /// their parameters and hash data.
146    pub fn evaluate(
147        &self,
148        context: &'rc Context,
149        relative_path: &str,
150    ) -> Result<ScopedJson<'rc>, RenderError> {
151        let path = Path::parse(relative_path)?;
152        self.evaluate2(context, &path)
153    }
154
155    pub(crate) fn evaluate2(
156        &self,
157        context: &'rc Context,
158        path: &Path,
159    ) -> Result<ScopedJson<'rc>, RenderError> {
160        match path {
161            Path::Local((level, name, _)) => Ok(self
162                .get_local_var(*level, name)
163                .map(|v| ScopedJson::Derived(v.clone()))
164                .unwrap_or_else(|| ScopedJson::Missing)),
165            Path::Relative((segs, _)) => context.navigate(segs, &self.blocks),
166        }
167    }
168
169    /// Get registered partial in this render context
170    pub fn get_partial(&self, name: &str) -> Option<&Template> {
171        if name == partial::PARTIAL_BLOCK {
172            return self
173                .inner()
174                .partial_block_stack
175                .get(self.inner().partial_block_depth as usize)
176                .copied();
177        }
178        self.inner().partials.get(name).copied()
179    }
180
181    /// Register a partial for this context
182    pub fn set_partial(&mut self, name: String, partial: &'rc Template) {
183        self.inner_mut().partials.insert(name, partial);
184    }
185
186    pub(crate) fn push_partial_block(&mut self, partial: &'reg Template) {
187        self.inner_mut().partial_block_stack.push_front(partial);
188    }
189
190    pub(crate) fn pop_partial_block(&mut self) {
191        self.inner_mut().partial_block_stack.pop_front();
192    }
193
194    pub(crate) fn inc_partial_block_depth(&mut self) {
195        self.inner_mut().partial_block_depth += 1;
196    }
197
198    pub(crate) fn dec_partial_block_depth(&mut self) {
199        let depth = &mut self.inner_mut().partial_block_depth;
200        if *depth > 0 {
201            *depth -= 1;
202        }
203    }
204
205    pub(crate) fn set_indent_string(&mut self, indent: Option<&'reg String>) {
206        self.inner_mut().indent_string = indent;
207    }
208
209    #[inline]
210    pub(crate) fn get_indent_string(&self) -> Option<&'reg String> {
211        self.inner.indent_string
212    }
213
214    /// Remove a registered partial
215    pub fn remove_partial(&mut self, name: &str) {
216        self.inner_mut().partials.remove(name);
217    }
218
219    fn get_local_var(&self, level: usize, name: &str) -> Option<&Json> {
220        self.blocks
221            .get(level)
222            .and_then(|blk| blk.get_local_var(name))
223    }
224
225    /// Test if given template name is current template.
226    pub fn is_current_template(&self, p: &str) -> bool {
227        self.inner()
228            .current_template
229            .map(|s| s == p)
230            .unwrap_or(false)
231    }
232
233    /// Register a helper in this render context.
234    /// This is a feature provided by Decorator where you can create
235    /// temporary helpers.
236    pub fn register_local_helper(
237        &mut self,
238        name: &str,
239        def: Box<dyn HelperDef + Send + Sync + 'rc>,
240    ) {
241        self.inner_mut()
242            .local_helpers
243            .insert(name.to_string(), def.into());
244    }
245
246    /// Remove a helper from render context
247    pub fn unregister_local_helper(&mut self, name: &str) {
248        self.inner_mut().local_helpers.remove(name);
249    }
250
251    /// Attempt to get a helper from current render context.
252    pub fn get_local_helper(&self, name: &str) -> Option<Rc<dyn HelperDef + Send + Sync + 'rc>> {
253        self.inner().local_helpers.get(name).cloned()
254    }
255
256    #[inline]
257    fn has_local_helper(&self, name: &str) -> bool {
258        self.inner.local_helpers.contains_key(name)
259    }
260
261    /// Returns the current template name.
262    /// Note that the name can be vary from root template when you are rendering
263    /// from partials.
264    pub fn get_current_template_name(&self) -> Option<&'rc String> {
265        self.inner().current_template
266    }
267
268    /// Set the current template name.
269    pub fn set_current_template_name(&mut self, name: Option<&'rc String>) {
270        self.inner_mut().current_template = name;
271    }
272
273    /// Get root template name if any.
274    /// This is the template name that you call `render` from `Handlebars`.
275    pub fn get_root_template_name(&self) -> Option<&'reg String> {
276        self.inner().root_template
277    }
278
279    /// Get the escape toggle
280    pub fn is_disable_escape(&self) -> bool {
281        self.inner().disable_escape
282    }
283
284    /// Set the escape toggle.
285    /// When toggle is on, escape_fn will be called when rendering.
286    pub fn set_disable_escape(&mut self, disable: bool) {
287        self.inner_mut().disable_escape = disable
288    }
289}
290
291impl<'reg, 'rc> fmt::Debug for RenderContextInner<'reg, 'rc> {
292    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
293        f.debug_struct("RenderContextInner")
294            .field("partials", &self.partials)
295            .field("partial_block_stack", &self.partial_block_stack)
296            .field("partial_block_depth", &self.partial_block_depth)
297            .field("root_template", &self.root_template)
298            .field("current_template", &self.current_template)
299            .field("disable_escape", &self.disable_escape)
300            .finish()
301    }
302}
303
304/// Render-time Helper data when using in a helper definition
305#[derive(Debug, Clone)]
306pub struct Helper<'rc> {
307    name: Cow<'rc, str>,
308    params: Vec<PathAndJson<'rc>>,
309    hash: BTreeMap<&'rc str, PathAndJson<'rc>>,
310    template: Option<&'rc Template>,
311    inverse: Option<&'rc Template>,
312    block_param: Option<&'rc BlockParam>,
313    block: bool,
314}
315
316impl<'reg: 'rc, 'rc> Helper<'rc> {
317    fn try_from_template(
318        ht: &'rc HelperTemplate,
319        registry: &'reg Registry<'reg>,
320        context: &'rc Context,
321        render_context: &mut RenderContext<'reg, 'rc>,
322    ) -> Result<Helper<'rc>, RenderError> {
323        let name = ht.name.expand_as_name(registry, context, render_context)?;
324        let mut pv = Vec::with_capacity(ht.params.len());
325        for p in &ht.params {
326            let r = p.expand(registry, context, render_context)?;
327            pv.push(r);
328        }
329
330        let mut hm = BTreeMap::new();
331        for (k, p) in &ht.hash {
332            let r = p.expand(registry, context, render_context)?;
333            hm.insert(k.as_ref(), r);
334        }
335
336        Ok(Helper {
337            name,
338            params: pv,
339            hash: hm,
340            template: ht.template.as_ref(),
341            inverse: ht.inverse.as_ref(),
342            block_param: ht.block_param.as_ref(),
343            block: ht.block,
344        })
345    }
346
347    /// Returns helper name
348    pub fn name(&self) -> &str {
349        &self.name
350    }
351
352    /// Returns all helper params, resolved within the context
353    pub fn params(&self) -> &Vec<PathAndJson<'rc>> {
354        &self.params
355    }
356
357    /// Returns nth helper param, resolved within the context.
358    ///
359    /// ## Example
360    ///
361    /// To get the first param in `{{my_helper abc}}` or `{{my_helper 2}}`,
362    /// use `h.param(0)` in helper definition.
363    /// Variable `abc` is auto resolved in current context.
364    ///
365    /// ```
366    /// use handlebars::*;
367    ///
368    /// fn my_helper(h: &Helper, rc: &mut RenderContext) -> Result<(), RenderError> {
369    ///     let v = h.param(0).map(|v| v.value())
370    ///         .ok_or(RenderErrorReason::ParamNotFoundForIndex("myhelper", 0));
371    ///     // ..
372    ///     Ok(())
373    /// }
374    /// ```
375    pub fn param(&self, idx: usize) -> Option<&PathAndJson<'rc>> {
376        self.params.get(idx)
377    }
378
379    /// Returns hash, resolved within the context
380    pub fn hash(&self) -> &BTreeMap<&'rc str, PathAndJson<'rc>> {
381        &self.hash
382    }
383
384    /// Return hash value of a given key, resolved within the context
385    ///
386    /// ## Example
387    ///
388    /// To get the first param in `{{my_helper v=abc}}` or `{{my_helper v=2}}`,
389    /// use `h.hash_get("v")` in helper definition.
390    /// Variable `abc` is auto resolved in current context.
391    ///
392    /// ```
393    /// use handlebars::*;
394    ///
395    /// fn my_helper(h: &Helper, rc: &mut RenderContext) -> Result<(), RenderError> {
396    ///     let v = h.hash_get("v").map(|v| v.value())
397    ///         .ok_or(RenderErrorReason::ParamNotFoundForIndex("my_helper", 0));
398    ///     // ..
399    ///     Ok(())
400    /// }
401    /// ```
402    pub fn hash_get(&self, key: &str) -> Option<&PathAndJson<'rc>> {
403        self.hash.get(key)
404    }
405
406    /// Returns the default inner template if the helper is a block helper.
407    ///
408    /// Typically you will render the template via: `template.render(registry, render_context)`
409    ///
410    pub fn template(&self) -> Option<&'rc Template> {
411        self.template
412    }
413
414    /// Returns the template of `else` branch if any
415    pub fn inverse(&self) -> Option<&'rc Template> {
416        self.inverse
417    }
418
419    /// Returns if the helper is a block one `{{#helper}}{{/helper}}` or not `{{helper 123}}`
420    pub fn is_block(&self) -> bool {
421        self.block
422    }
423
424    /// Returns if the helper has either a block param or block param pair
425    pub fn has_block_param(&self) -> bool {
426        self.block_param.is_some()
427    }
428
429    /// Returns block param if any
430    pub fn block_param(&self) -> Option<&'rc str> {
431        if let Some(&BlockParam::Single(Parameter::Name(ref s))) = self.block_param {
432            Some(s)
433        } else {
434            None
435        }
436    }
437
438    /// Return block param pair (for example |key, val|) if any
439    pub fn block_param_pair(&self) -> Option<(&'rc str, &'rc str)> {
440        if let Some(&BlockParam::Pair((Parameter::Name(ref s1), Parameter::Name(ref s2)))) =
441            self.block_param
442        {
443            Some((s1, s2))
444        } else {
445            None
446        }
447    }
448}
449
450/// Render-time Decorator data when using in a decorator definition
451#[derive(Debug)]
452pub struct Decorator<'rc> {
453    name: Cow<'rc, str>,
454    params: Vec<PathAndJson<'rc>>,
455    hash: BTreeMap<&'rc str, PathAndJson<'rc>>,
456    template: Option<&'rc Template>,
457    indent: Option<&'rc String>,
458}
459
460impl<'reg: 'rc, 'rc> Decorator<'rc> {
461    fn try_from_template(
462        dt: &'rc DecoratorTemplate,
463        registry: &'reg Registry<'reg>,
464        context: &'rc Context,
465        render_context: &mut RenderContext<'reg, 'rc>,
466    ) -> Result<Decorator<'rc>, RenderError> {
467        let name = dt.name.expand_as_name(registry, context, render_context)?;
468
469        let mut pv = Vec::with_capacity(dt.params.len());
470        for p in &dt.params {
471            let r = p.expand(registry, context, render_context)?;
472            pv.push(r);
473        }
474
475        let mut hm = BTreeMap::new();
476        for (k, p) in &dt.hash {
477            let r = p.expand(registry, context, render_context)?;
478            hm.insert(k.as_ref(), r);
479        }
480
481        Ok(Decorator {
482            name,
483            params: pv,
484            hash: hm,
485            template: dt.template.as_ref(),
486            indent: dt.indent.as_ref(),
487        })
488    }
489
490    /// Returns helper name
491    pub fn name(&self) -> &str {
492        self.name.as_ref()
493    }
494
495    /// Returns all helper params, resolved within the context
496    pub fn params(&self) -> &Vec<PathAndJson<'rc>> {
497        &self.params
498    }
499
500    /// Returns nth helper param, resolved within the context
501    pub fn param(&self, idx: usize) -> Option<&PathAndJson<'rc>> {
502        self.params.get(idx)
503    }
504
505    /// Returns hash, resolved within the context
506    pub fn hash(&self) -> &BTreeMap<&'rc str, PathAndJson<'rc>> {
507        &self.hash
508    }
509
510    /// Return hash value of a given key, resolved within the context
511    pub fn hash_get(&self, key: &str) -> Option<&PathAndJson<'rc>> {
512        self.hash.get(key)
513    }
514
515    /// Returns the default inner template if any
516    pub fn template(&self) -> Option<&'rc Template> {
517        self.template
518    }
519
520    pub fn indent(&self) -> Option<&'rc String> {
521        self.indent
522    }
523}
524
525/// Render trait
526pub trait Renderable {
527    /// render into RenderContext's `writer`
528    fn render<'reg: 'rc, 'rc>(
529        &'rc self,
530        registry: &'reg Registry<'reg>,
531        context: &'rc Context,
532        rc: &mut RenderContext<'reg, 'rc>,
533        out: &mut dyn Output,
534    ) -> Result<(), RenderError>;
535
536    /// render into string
537    fn renders<'reg: 'rc, 'rc>(
538        &'rc self,
539        registry: &'reg Registry<'reg>,
540        ctx: &'rc Context,
541        rc: &mut RenderContext<'reg, 'rc>,
542    ) -> Result<String, RenderError> {
543        let mut so = StringOutput::new();
544        self.render(registry, ctx, rc, &mut so)?;
545        so.into_string()
546            .map_err(|e| RenderErrorReason::from(e).into())
547    }
548}
549
550/// Evaluate decorator
551pub trait Evaluable {
552    fn eval<'reg: 'rc, 'rc>(
553        &'rc self,
554        registry: &'reg Registry<'reg>,
555        context: &'rc Context,
556        rc: &mut RenderContext<'reg, 'rc>,
557    ) -> Result<(), RenderError>;
558}
559
560#[inline]
561fn call_helper_for_value<'reg: 'rc, 'rc>(
562    hd: &dyn HelperDef,
563    ht: &Helper<'rc>,
564    r: &'reg Registry<'reg>,
565    ctx: &'rc Context,
566    rc: &mut RenderContext<'reg, 'rc>,
567) -> Result<PathAndJson<'rc>, RenderError> {
568    match hd.call_inner(ht, r, ctx, rc) {
569        Ok(result) => Ok(PathAndJson::new(None, result)),
570        Err(e) => {
571            if e.is_unimplemented() {
572                // parse value from output
573                let mut so = StringOutput::new();
574
575                // here we don't want subexpression result escaped,
576                // so we temporarily disable it
577                let disable_escape = rc.is_disable_escape();
578                rc.set_disable_escape(true);
579
580                hd.call(ht, r, ctx, rc, &mut so)?;
581                rc.set_disable_escape(disable_escape);
582
583                let string = so.into_string().map_err(RenderError::from)?;
584                Ok(PathAndJson::new(
585                    None,
586                    ScopedJson::Derived(Json::String(string)),
587                ))
588            } else {
589                Err(e)
590            }
591        }
592    }
593}
594
595impl Parameter {
596    pub fn expand_as_name<'reg: 'rc, 'rc>(
597        &'rc self,
598        registry: &'reg Registry<'reg>,
599        ctx: &'rc Context,
600        rc: &mut RenderContext<'reg, 'rc>,
601    ) -> Result<Cow<'rc, str>, RenderError> {
602        match self {
603            Parameter::Name(ref name) => Ok(Cow::Borrowed(name)),
604            Parameter::Path(ref p) => Ok(Cow::Borrowed(p.raw())),
605            Parameter::Subexpression(_) => self
606                .expand(registry, ctx, rc)
607                .map(|v| v.value().render())
608                .map(Cow::Owned),
609            Parameter::Literal(ref j) => Ok(Cow::Owned(j.render())),
610        }
611    }
612
613    pub fn expand<'reg: 'rc, 'rc>(
614        &'rc self,
615        registry: &'reg Registry<'reg>,
616        ctx: &'rc Context,
617        rc: &mut RenderContext<'reg, 'rc>,
618    ) -> Result<PathAndJson<'rc>, RenderError> {
619        match self {
620            Parameter::Name(ref name) => {
621                // FIXME: raise error when expanding with name?
622                Ok(PathAndJson::new(Some(name.to_owned()), ScopedJson::Missing))
623            }
624            Parameter::Path(ref path) => {
625                if let Some(rc_context) = rc.context() {
626                    let result = rc.evaluate2(rc_context.borrow(), path)?;
627                    Ok(PathAndJson::new(
628                        Some(path.raw().to_owned()),
629                        ScopedJson::Derived(result.as_json().clone()),
630                    ))
631                } else {
632                    let result = rc.evaluate2(ctx, path)?;
633                    Ok(PathAndJson::new(Some(path.raw().to_owned()), result))
634                }
635            }
636            Parameter::Literal(ref j) => Ok(PathAndJson::new(None, ScopedJson::Constant(j))),
637            Parameter::Subexpression(ref t) => match *t.as_element() {
638                Expression(ref ht) => {
639                    let name = ht.name.expand_as_name(registry, ctx, rc)?;
640
641                    let h = Helper::try_from_template(ht, registry, ctx, rc)?;
642                    if let Some(ref d) = rc.get_local_helper(&name) {
643                        call_helper_for_value(d.as_ref(), &h, registry, ctx, rc)
644                    } else {
645                        let mut helper = registry.get_or_load_helper(&name)?;
646
647                        if helper.is_none() {
648                            helper = registry.get_or_load_helper(if ht.block {
649                                BLOCK_HELPER_MISSING
650                            } else {
651                                HELPER_MISSING
652                            })?;
653                        }
654
655                        helper
656                            .ok_or_else(|| {
657                                RenderErrorReason::HelperNotFound(name.to_string()).into()
658                            })
659                            .and_then(|d| call_helper_for_value(d.as_ref(), &h, registry, ctx, rc))
660                    }
661                }
662                _ => unreachable!(),
663            },
664        }
665    }
666}
667
668impl Renderable for Template {
669    fn render<'reg: 'rc, 'rc>(
670        &'rc self,
671        registry: &'reg Registry<'reg>,
672        ctx: &'rc Context,
673        rc: &mut RenderContext<'reg, 'rc>,
674        out: &mut dyn Output,
675    ) -> Result<(), RenderError> {
676        rc.set_current_template_name(self.name.as_ref());
677        let iter = self.elements.iter();
678
679        for (idx, t) in iter.enumerate() {
680            t.render(registry, ctx, rc, out).map_err(|mut e| {
681                // add line/col number if the template has mapping data
682                if e.line_no.is_none() {
683                    if let Some(&TemplateMapping(line, col)) = self.mapping.get(idx) {
684                        e.line_no = Some(line);
685                        e.column_no = Some(col);
686                    }
687                }
688
689                if e.template_name.is_none() {
690                    e.template_name = self.name.clone();
691                }
692
693                e
694            })?;
695        }
696        Ok(())
697    }
698}
699
700impl Evaluable for Template {
701    fn eval<'reg: 'rc, 'rc>(
702        &'rc self,
703        registry: &'reg Registry<'reg>,
704        ctx: &'rc Context,
705        rc: &mut RenderContext<'reg, 'rc>,
706    ) -> Result<(), RenderError> {
707        let iter = self.elements.iter();
708
709        for (idx, t) in iter.enumerate() {
710            t.eval(registry, ctx, rc).map_err(|mut e| {
711                if e.line_no.is_none() {
712                    if let Some(&TemplateMapping(line, col)) = self.mapping.get(idx) {
713                        e.line_no = Some(line);
714                        e.column_no = Some(col);
715                    }
716                }
717
718                e.template_name = self.name.clone();
719                e
720            })?;
721        }
722        Ok(())
723    }
724}
725
726fn helper_exists<'reg: 'rc, 'rc>(
727    name: &str,
728    reg: &Registry<'reg>,
729    rc: &RenderContext<'reg, 'rc>,
730) -> bool {
731    rc.has_local_helper(name) || reg.has_helper(name)
732}
733
734#[inline]
735fn render_helper<'reg: 'rc, 'rc>(
736    ht: &'rc HelperTemplate,
737    registry: &'reg Registry<'reg>,
738    ctx: &'rc Context,
739    rc: &mut RenderContext<'reg, 'rc>,
740    out: &mut dyn Output,
741) -> Result<(), RenderError> {
742    let h = Helper::try_from_template(ht, registry, ctx, rc)?;
743    debug!(
744        "Rendering helper: {:?}, params: {:?}, hash: {:?}",
745        h.name(),
746        h.params(),
747        h.hash()
748    );
749    if let Some(ref d) = rc.get_local_helper(h.name()) {
750        d.call(&h, registry, ctx, rc, out)
751    } else {
752        let mut helper = registry.get_or_load_helper(h.name())?;
753
754        if helper.is_none() {
755            helper = registry.get_or_load_helper(if ht.block {
756                BLOCK_HELPER_MISSING
757            } else {
758                HELPER_MISSING
759            })?;
760        }
761
762        helper
763            .ok_or_else(|| RenderErrorReason::HelperNotFound(h.name().to_owned()).into())
764            .and_then(|d| d.call(&h, registry, ctx, rc, out))
765    }
766}
767
768pub(crate) fn do_escape(r: &Registry<'_>, rc: &RenderContext<'_, '_>, content: String) -> String {
769    if !rc.is_disable_escape() {
770        r.get_escape_fn()(&content)
771    } else {
772        content
773    }
774}
775
776#[inline]
777fn indent_aware_write(
778    v: &str,
779    rc: &RenderContext<'_, '_>,
780    out: &mut dyn Output,
781) -> Result<(), RenderError> {
782    if let Some(indent) = rc.get_indent_string() {
783        out.write(support::str::with_indent(v, indent).as_ref())?;
784    } else {
785        out.write(v.as_ref())?;
786    }
787    Ok(())
788}
789
790impl Renderable for TemplateElement {
791    fn render<'reg: 'rc, 'rc>(
792        &'rc self,
793        registry: &'reg Registry<'reg>,
794        ctx: &'rc Context,
795        rc: &mut RenderContext<'reg, 'rc>,
796        out: &mut dyn Output,
797    ) -> Result<(), RenderError> {
798        match self {
799            RawString(ref v) => indent_aware_write(v.as_ref(), rc, out),
800            Expression(ref ht) | HtmlExpression(ref ht) => {
801                let is_html_expression = matches!(self, HtmlExpression(_));
802                if is_html_expression {
803                    rc.set_disable_escape(true);
804                }
805
806                // test if the expression is to render some value
807                let result = if ht.is_name_only() {
808                    let helper_name = ht.name.expand_as_name(registry, ctx, rc)?;
809                    if helper_exists(&helper_name, registry, rc) {
810                        render_helper(ht, registry, ctx, rc, out)
811                    } else {
812                        debug!("Rendering value: {:?}", ht.name);
813                        let context_json = ht.name.expand(registry, ctx, rc)?;
814                        if context_json.is_value_missing() {
815                            if registry.strict_mode() {
816                                Err(RenderError::strict_error(context_json.relative_path()))
817                            } else {
818                                // helper missing
819                                if let Some(hook) = registry.get_or_load_helper(HELPER_MISSING)? {
820                                    let h = Helper::try_from_template(ht, registry, ctx, rc)?;
821                                    hook.call(&h, registry, ctx, rc, out)
822                                } else {
823                                    Ok(())
824                                }
825                            }
826                        } else {
827                            let rendered = context_json.value().render();
828                            let output = do_escape(registry, rc, rendered);
829                            indent_aware_write(output.as_ref(), rc, out)
830                        }
831                    }
832                } else {
833                    // this is a helper expression
834                    render_helper(ht, registry, ctx, rc, out)
835                };
836
837                if is_html_expression {
838                    rc.set_disable_escape(false);
839                }
840
841                result
842            }
843            HelperBlock(ref ht) => render_helper(ht, registry, ctx, rc, out),
844            DecoratorExpression(_) | DecoratorBlock(_) => self.eval(registry, ctx, rc),
845            PartialExpression(ref dt) | PartialBlock(ref dt) => {
846                let di = Decorator::try_from_template(dt, registry, ctx, rc)?;
847
848                partial::expand_partial(&di, registry, ctx, rc, out)
849            }
850            _ => Ok(()),
851        }
852    }
853}
854
855impl Evaluable for TemplateElement {
856    fn eval<'reg: 'rc, 'rc>(
857        &'rc self,
858        registry: &'reg Registry<'reg>,
859        ctx: &'rc Context,
860        rc: &mut RenderContext<'reg, 'rc>,
861    ) -> Result<(), RenderError> {
862        match *self {
863            DecoratorExpression(ref dt) | DecoratorBlock(ref dt) => {
864                let di = Decorator::try_from_template(dt, registry, ctx, rc)?;
865                match registry.get_decorator(di.name()) {
866                    Some(d) => d.call(&di, registry, ctx, rc),
867                    None => Err(RenderErrorReason::DecoratorNotFound(di.name().to_owned()).into()),
868                }
869            }
870            _ => Ok(()),
871        }
872    }
873}
874
875#[cfg(test)]
876mod test {
877    use std::collections::BTreeMap;
878
879    use super::{Helper, RenderContext, Renderable};
880    use crate::block::BlockContext;
881    use crate::context::Context;
882    use crate::error::RenderError;
883    use crate::json::path::Path;
884    use crate::json::value::JsonRender;
885    use crate::output::{Output, StringOutput};
886    use crate::registry::Registry;
887    use crate::template::TemplateElement::*;
888    use crate::template::{HelperTemplate, Template, TemplateElement};
889
890    #[test]
891    fn test_raw_string() {
892        let r = Registry::new();
893        let raw_string = RawString("<h1>hello world</h1>".to_string());
894
895        let mut out = StringOutput::new();
896        let ctx = Context::null();
897        {
898            let mut rc = RenderContext::new(None);
899            raw_string.render(&r, &ctx, &mut rc, &mut out).ok().unwrap();
900        }
901        assert_eq!(
902            out.into_string().unwrap(),
903            "<h1>hello world</h1>".to_string()
904        );
905    }
906
907    #[test]
908    fn test_expression() {
909        let r = Registry::new();
910        let element = Expression(Box::new(HelperTemplate::with_path(Path::with_named_paths(
911            &["hello"],
912        ))));
913
914        let mut out = StringOutput::new();
915        let mut m: BTreeMap<String, String> = BTreeMap::new();
916        let value = "<p></p>".to_string();
917        m.insert("hello".to_string(), value);
918        let ctx = Context::wraps(&m).unwrap();
919        {
920            let mut rc = RenderContext::new(None);
921            element.render(&r, &ctx, &mut rc, &mut out).ok().unwrap();
922        }
923
924        assert_eq!(
925            out.into_string().unwrap(),
926            "&lt;p&gt;&lt;/p&gt;".to_string()
927        );
928    }
929
930    #[test]
931    fn test_html_expression() {
932        let r = Registry::new();
933        let element = HtmlExpression(Box::new(HelperTemplate::with_path(Path::with_named_paths(
934            &["hello"],
935        ))));
936
937        let mut out = StringOutput::new();
938        let mut m: BTreeMap<String, String> = BTreeMap::new();
939        let value = "world";
940        m.insert("hello".to_string(), value.to_string());
941        let ctx = Context::wraps(&m).unwrap();
942        {
943            let mut rc = RenderContext::new(None);
944            element.render(&r, &ctx, &mut rc, &mut out).ok().unwrap();
945        }
946
947        assert_eq!(out.into_string().unwrap(), value.to_string());
948    }
949
950    #[test]
951    fn test_template() {
952        let r = Registry::new();
953        let mut out = StringOutput::new();
954        let mut m: BTreeMap<String, String> = BTreeMap::new();
955        let value = "world".to_string();
956        m.insert("hello".to_string(), value);
957        let ctx = Context::wraps(&m).unwrap();
958
959        let elements: Vec<TemplateElement> = vec![
960            RawString("<h1>".to_string()),
961            Expression(Box::new(HelperTemplate::with_path(Path::with_named_paths(
962                &["hello"],
963            )))),
964            RawString("</h1>".to_string()),
965            Comment("".to_string()),
966        ];
967
968        let template = Template {
969            elements,
970            name: None,
971            mapping: Vec::new(),
972            span: (0,0)
973        };
974
975        {
976            let mut rc = RenderContext::new(None);
977            template.render(&r, &ctx, &mut rc, &mut out).ok().unwrap();
978        }
979
980        assert_eq!(out.into_string().unwrap(), "<h1>world</h1>".to_string());
981    }
982
983    #[test]
984    fn test_render_context_promotion_and_demotion() {
985        use crate::json::value::to_json;
986        let mut render_context = RenderContext::new(None);
987        let mut block = BlockContext::new();
988
989        block.set_local_var("index", to_json(0));
990        render_context.push_block(block);
991
992        render_context.push_block(BlockContext::new());
993        assert_eq!(
994            render_context.get_local_var(1, "index").unwrap(),
995            &to_json(0)
996        );
997
998        render_context.pop_block();
999
1000        assert_eq!(
1001            render_context.get_local_var(0, "index").unwrap(),
1002            &to_json(0)
1003        );
1004    }
1005
1006    #[test]
1007    fn test_render_subexpression_issue_115() {
1008        use crate::support::str::StringWriter;
1009
1010        let mut r = Registry::new();
1011        r.register_helper(
1012            "format",
1013            Box::new(
1014                |h: &Helper<'_>,
1015                 _: &Registry<'_>,
1016                 _: &Context,
1017                 _: &mut RenderContext<'_, '_>,
1018                 out: &mut dyn Output|
1019                 -> Result<(), RenderError> {
1020                    out.write(&h.param(0).unwrap().value().render())
1021                        .map(|_| ())
1022                        .map_err(RenderError::from)
1023                },
1024            ),
1025        );
1026
1027        let mut sw = StringWriter::new();
1028        let mut m: BTreeMap<String, String> = BTreeMap::new();
1029        m.insert("a".to_string(), "123".to_string());
1030
1031        {
1032            if let Err(e) = r.render_template_to_write("{{format (format a)}}", &m, &mut sw) {
1033                panic!("{}", e);
1034            }
1035        }
1036
1037        assert_eq!(sw.into_string(), "123".to_string());
1038    }
1039
1040    #[test]
1041    fn test_render_error_line_no() {
1042        let mut r = Registry::new();
1043        let m: BTreeMap<String, String> = BTreeMap::new();
1044
1045        let name = "invalid_template";
1046        assert!(r
1047            .register_template_string(name, "<h1>\n{{#if true}}\n  {{#each}}{{/each}}\n{{/if}}")
1048            .is_ok());
1049
1050        if let Err(e) = r.render(name, &m) {
1051            assert_eq!(e.line_no.unwrap(), 3);
1052            assert_eq!(e.column_no.unwrap(), 3);
1053            assert_eq!(e.template_name, Some(name.to_owned()));
1054        } else {
1055            panic!("Error expected");
1056        }
1057    }
1058
1059    #[test]
1060    fn test_partial_failback_render() {
1061        let mut r = Registry::new();
1062
1063        assert!(r
1064            .register_template_string("parent", "<html>{{> layout}}</html>")
1065            .is_ok());
1066        assert!(r
1067            .register_template_string(
1068                "child",
1069                "{{#*inline \"layout\"}}content{{/inline}}{{#> parent}}{{> seg}}{{/parent}}",
1070            )
1071            .is_ok());
1072        assert!(r.register_template_string("seg", "1234").is_ok());
1073
1074        let r = r.render("child", &true).expect("should work");
1075        assert_eq!(r, "<html>content</html>");
1076    }
1077
1078    #[test]
1079    fn test_key_with_slash() {
1080        let mut r = Registry::new();
1081
1082        assert!(r
1083            .register_template_string("t", "{{#each this}}{{@key}}: {{this}}\n{{/each}}")
1084            .is_ok());
1085
1086        let r = r.render("t", &json!({"/foo": "bar"})).unwrap();
1087
1088        assert_eq!(r, "/foo: bar\n");
1089    }
1090
1091    #[test]
1092    fn test_comment() {
1093        let r = Registry::new();
1094
1095        assert_eq!(
1096            r.render_template("Hello {{this}} {{! test me }}", &0)
1097                .unwrap(),
1098            "Hello 0 "
1099        );
1100    }
1101
1102    #[test]
1103    fn test_zero_args_heler() {
1104        let mut r = Registry::new();
1105
1106        r.register_helper(
1107            "name",
1108            Box::new(
1109                |_: &Helper<'_>,
1110                 _: &Registry<'_>,
1111                 _: &Context,
1112                 _: &mut RenderContext<'_, '_>,
1113                 out: &mut dyn Output|
1114                 -> Result<(), RenderError> {
1115                    out.write("N/A").map_err(Into::into)
1116                },
1117            ),
1118        );
1119
1120        r.register_template_string("t0", "Output name: {{name}}")
1121            .unwrap();
1122        r.register_template_string("t1", "Output name: {{first_name}}")
1123            .unwrap();
1124        r.register_template_string("t2", "Output name: {{./name}}")
1125            .unwrap();
1126
1127        // when "name" is available in context, use context first
1128        assert_eq!(
1129            r.render("t0", &json!({"name": "Alex"})).unwrap(),
1130            "Output name: N/A"
1131        );
1132
1133        // when "name" is unavailable, call helper with same name
1134        assert_eq!(
1135            r.render("t2", &json!({"name": "Alex"})).unwrap(),
1136            "Output name: Alex"
1137        );
1138
1139        // output nothing when neither context nor helper available
1140        assert_eq!(
1141            r.render("t1", &json!({"name": "Alex"})).unwrap(),
1142            "Output name: "
1143        );
1144
1145        // generate error in strict mode for above case
1146        r.set_strict_mode(true);
1147        assert!(r.render("t1", &json!({"name": "Alex"})).is_err());
1148
1149        // output nothing when helperMissing was defined
1150        r.set_strict_mode(false);
1151        r.register_helper(
1152            "helperMissing",
1153            Box::new(
1154                |h: &Helper<'_>,
1155                 _: &Registry<'_>,
1156                 _: &Context,
1157                 _: &mut RenderContext<'_, '_>,
1158                 out: &mut dyn Output|
1159                 -> Result<(), RenderError> {
1160                    let name = h.name();
1161                    write!(out, "{} not resolved", name)?;
1162                    Ok(())
1163                },
1164            ),
1165        );
1166        assert_eq!(
1167            r.render("t1", &json!({"name": "Alex"})).unwrap(),
1168            "Output name: first_name not resolved"
1169        );
1170    }
1171
1172    #[test]
1173    fn test_identifiers_starting_with_numbers() {
1174        let mut r = Registry::new();
1175
1176        assert!(r
1177            .register_template_string("r1", "{{#if 0a}}true{{/if}}")
1178            .is_ok());
1179        let r1 = r.render("r1", &json!({"0a": true})).unwrap();
1180        assert_eq!(r1, "true");
1181
1182        assert!(r.register_template_string("r2", "{{eq 1a 1}}").is_ok());
1183        let r2 = r.render("r2", &json!({"1a": 2, "a": 1})).unwrap();
1184        assert_eq!(r2, "false");
1185
1186        assert!(r
1187            .register_template_string("r3", "0: {{0}} {{#if (eq 0 true)}}resolved from context{{/if}}\n1a: {{1a}} {{#if (eq 1a true)}}resolved from context{{/if}}\n2_2: {{2_2}} {{#if (eq 2_2 true)}}resolved from context{{/if}}") // YUP it is just eq that barfs! is if handled specially? maybe this test should go nearer to specific helpers that fail?
1188            .is_ok());
1189        let r3 = r
1190            .render("r3", &json!({"0": true, "1a": true, "2_2": true}))
1191            .unwrap();
1192        assert_eq!(
1193            r3,
1194            "0: true \n1a: true resolved from context\n2_2: true resolved from context"
1195        );
1196
1197        // these should all be errors:
1198        assert!(r.register_template_string("r4", "{{eq 1}}").is_ok());
1199        assert!(r.register_template_string("r5", "{{eq a1}}").is_ok());
1200        assert!(r.register_template_string("r6", "{{eq 1a}}").is_ok());
1201        assert!(r.render("r4", &()).is_err());
1202        assert!(r.render("r5", &()).is_err());
1203        assert!(r.render("r6", &()).is_err());
1204    }
1205}