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::partial;
16use crate::registry::Registry;
17use crate::support;
18use crate::template::TemplateElement::*;
19use crate::template::{
20 BlockParam, DecoratorTemplate, HelperTemplate, Parameter, Template, TemplateElement,
21 TemplateMapping,
22};
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<'reg>>,
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, &'reg 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<&'reg 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<'reg>) {
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<'reg>> {
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<'reg>> {
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<'reg, '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<'reg, '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: &'reg 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<&'reg 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<&'reg 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&mut DebugStruct<'_, '_>.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(name:"disable_escape", &self.disable_escape)
300 .finish()
301 }
302}
303
304/// Render-time Helper data when using in a helper definition
305#[derive(Debug)]
306pub struct Helper<'reg, 'rc> {
307 name: Cow<'reg, str>,
308 params: Vec<PathAndJson<'reg, 'rc>>,
309 hash: BTreeMap<&'reg str, PathAndJson<'reg, 'rc>>,
310 template: Option<&'reg Template>,
311 inverse: Option<&'reg Template>,
312 block_param: Option<&'reg BlockParam>,
313 block: bool,
314}
315
316impl<'reg: 'rc, 'rc> Helper<'reg, 'rc> {
317 fn try_from_template(
318 ht: &'reg HelperTemplate,
319 registry: &'reg Registry<'reg>,
320 context: &'rc Context,
321 render_context: &mut RenderContext<'reg, 'rc>,
322 ) -> Result<Helper<'reg, '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<'reg, '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(RenderError::new("param not found"));
371 /// // ..
372 /// Ok(())
373 /// }
374 /// ```
375 pub fn param(&self, idx: usize) -> Option<&PathAndJson<'reg, 'rc>> {
376 self.params.get(idx)
377 }
378
379 /// Returns hash, resolved within the context
380 pub fn hash(&self) -> &BTreeMap<&'reg str, PathAndJson<'reg, '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(RenderError::new("param not found"));
398 /// // ..
399 /// Ok(())
400 /// }
401 /// ```
402 pub fn hash_get(&self, key: &str) -> Option<&PathAndJson<'reg, '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<&'reg Template> {
411 self.template
412 }
413
414 /// Returns the template of `else` branch if any
415 pub fn inverse(&self) -> Option<&'reg 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<&'reg 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<(&'reg str, &'reg 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<'reg, 'rc> {
453 name: Cow<'reg, str>,
454 params: Vec<PathAndJson<'reg, 'rc>>,
455 hash: BTreeMap<&'reg str, PathAndJson<'reg, 'rc>>,
456 template: Option<&'reg Template>,
457 indent: Option<&'reg String>,
458}
459
460impl<'reg: 'rc, 'rc> Decorator<'reg, 'rc> {
461 fn try_from_template(
462 dt: &'reg DecoratorTemplate,
463 registry: &'reg Registry<'reg>,
464 context: &'rc Context,
465 render_context: &mut RenderContext<'reg, 'rc>,
466 ) -> Result<Decorator<'reg, '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<'reg, 'rc>> {
497 &self.params
498 }
499
500 /// Returns nth helper param, resolved within the context
501 pub fn param(&self, idx: usize) -> Option<&PathAndJson<'reg, 'rc>> {
502 self.params.get(idx)
503 }
504
505 /// Returns hash, resolved within the context
506 pub fn hash(&self) -> &BTreeMap<&'reg str, PathAndJson<'reg, '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<'reg, 'rc>> {
512 self.hash.get(key)
513 }
514
515 /// Returns the default inner template if any
516 pub fn template(&self) -> Option<&'reg Template> {
517 self.template
518 }
519
520 pub fn indent(&self) -> Option<&'reg 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 &'reg 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 &'reg 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 = StringOutput::new();
544 self.render(registry, context:ctx, rc, &mut so)?;
545 so.into_string().map_err(op:RenderError::from)
546 }
547}
548
549/// Evaluate decorator
550pub trait Evaluable {
551 fn eval<'reg: 'rc, 'rc>(
552 &'reg self,
553 registry: &'reg Registry<'reg>,
554 context: &'rc Context,
555 rc: &mut RenderContext<'reg, 'rc>,
556 ) -> Result<(), RenderError>;
557}
558
559#[inline]
560fn call_helper_for_value<'reg: 'rc, 'rc>(
561 hd: &dyn HelperDef,
562 ht: &Helper<'reg, 'rc>,
563 r: &'reg Registry<'reg>,
564 ctx: &'rc Context,
565 rc: &mut RenderContext<'reg, 'rc>,
566) -> Result<PathAndJson<'reg, 'rc>, RenderError> {
567 match hd.call_inner(ht, r, ctx, rc) {
568 Ok(result) => Ok(PathAndJson::new(None, result)),
569 Err(e) => {
570 if e.is_unimplemented() {
571 // parse value from output
572 let mut so = StringOutput::new();
573
574 // here we don't want subexpression result escaped,
575 // so we temporarily disable it
576 let disable_escape = rc.is_disable_escape();
577 rc.set_disable_escape(true);
578
579 hd.call(ht, r, ctx, rc, &mut so)?;
580 rc.set_disable_escape(disable_escape);
581
582 let string = so.into_string().map_err(RenderError::from)?;
583 Ok(PathAndJson::new(
584 None,
585 ScopedJson::Derived(Json::String(string)),
586 ))
587 } else {
588 Err(e)
589 }
590 }
591 }
592}
593
594impl Parameter {
595 pub fn expand_as_name<'reg: 'rc, 'rc>(
596 &'reg self,
597 registry: &'reg Registry<'reg>,
598 ctx: &'rc Context,
599 rc: &mut RenderContext<'reg, 'rc>,
600 ) -> Result<Cow<'reg, str>, RenderError> {
601 match self {
602 Parameter::Name(ref name) => Ok(Cow::Borrowed(name)),
603 Parameter::Path(ref p) => Ok(Cow::Borrowed(p.raw())),
604 Parameter::Subexpression(_) => self
605 .expand(registry, ctx, rc)
606 .map(|v| v.value().render())
607 .map(Cow::Owned),
608 Parameter::Literal(ref j) => Ok(Cow::Owned(j.render())),
609 }
610 }
611
612 pub fn expand<'reg: 'rc, 'rc>(
613 &'reg self,
614 registry: &'reg Registry<'reg>,
615 ctx: &'rc Context,
616 rc: &mut RenderContext<'reg, 'rc>,
617 ) -> Result<PathAndJson<'reg, 'rc>, RenderError> {
618 match self {
619 Parameter::Name(ref name) => {
620 // FIXME: raise error when expanding with name?
621 Ok(PathAndJson::new(Some(name.to_owned()), ScopedJson::Missing))
622 }
623 Parameter::Path(ref path) => {
624 if let Some(rc_context) = rc.context() {
625 let result = rc.evaluate2(rc_context.borrow(), path)?;
626 Ok(PathAndJson::new(
627 Some(path.raw().to_owned()),
628 ScopedJson::Derived(result.as_json().clone()),
629 ))
630 } else {
631 let result = rc.evaluate2(ctx, path)?;
632 Ok(PathAndJson::new(Some(path.raw().to_owned()), result))
633 }
634 }
635 Parameter::Literal(ref j) => Ok(PathAndJson::new(None, ScopedJson::Constant(j))),
636 Parameter::Subexpression(ref t) => match *t.as_element() {
637 Expression(ref ht) => {
638 let name = ht.name.expand_as_name(registry, ctx, rc)?;
639
640 let h = Helper::try_from_template(ht, registry, ctx, rc)?;
641 if let Some(ref d) = rc.get_local_helper(&name) {
642 call_helper_for_value(d.as_ref(), &h, registry, ctx, rc)
643 } else {
644 let mut helper = registry.get_or_load_helper(&name)?;
645
646 if helper.is_none() {
647 helper = registry.get_or_load_helper(if ht.block {
648 BLOCK_HELPER_MISSING
649 } else {
650 HELPER_MISSING
651 })?;
652 }
653
654 helper
655 .ok_or_else(|| {
656 RenderError::new(format!("Helper not defined: {:?}", ht.name))
657 })
658 .and_then(|d| call_helper_for_value(d.as_ref(), &h, registry, ctx, rc))
659 }
660 }
661 _ => unreachable!(),
662 },
663 }
664 }
665}
666
667impl Renderable for Template {
668 fn render<'reg: 'rc, 'rc>(
669 &'reg self,
670 registry: &'reg Registry<'reg>,
671 ctx: &'rc Context,
672 rc: &mut RenderContext<'reg, 'rc>,
673 out: &mut dyn Output,
674 ) -> Result<(), RenderError> {
675 rc.set_current_template_name(self.name.as_ref());
676 let iter = self.elements.iter();
677
678 for (idx, t) in iter.enumerate() {
679 t.render(registry, ctx, rc, out).map_err(|mut e| {
680 // add line/col number if the template has mapping data
681 if e.line_no.is_none() {
682 if let Some(&TemplateMapping(line, col)) = self.mapping.get(idx) {
683 e.line_no = Some(line);
684 e.column_no = Some(col);
685 }
686 }
687
688 if e.template_name.is_none() {
689 e.template_name = self.name.clone();
690 }
691
692 e
693 })?;
694 }
695 Ok(())
696 }
697}
698
699impl Evaluable for Template {
700 fn eval<'reg: 'rc, 'rc>(
701 &'reg self,
702 registry: &'reg Registry<'reg>,
703 ctx: &'rc Context,
704 rc: &mut RenderContext<'reg, 'rc>,
705 ) -> Result<(), RenderError> {
706 let iter = self.elements.iter();
707
708 for (idx, t) in iter.enumerate() {
709 t.eval(registry, ctx, rc).map_err(|mut e| {
710 if e.line_no.is_none() {
711 if let Some(&TemplateMapping(line, col)) = self.mapping.get(idx) {
712 e.line_no = Some(line);
713 e.column_no = Some(col);
714 }
715 }
716
717 e.template_name = self.name.clone();
718 e
719 })?;
720 }
721 Ok(())
722 }
723}
724
725fn helper_exists<'reg: 'rc, 'rc>(
726 name: &str,
727 reg: &Registry<'reg>,
728 rc: &RenderContext<'reg, 'rc>,
729) -> bool {
730 rc.has_local_helper(name) || reg.has_helper(name)
731}
732
733#[inline]
734fn render_helper<'reg: 'rc, 'rc>(
735 ht: &'reg HelperTemplate,
736 registry: &'reg Registry<'reg>,
737 ctx: &'rc Context,
738 rc: &mut RenderContext<'reg, 'rc>,
739 out: &mut dyn Output,
740) -> Result<(), RenderError> {
741 let h = Helper::try_from_template(ht, registry, ctx, rc)?;
742 debug!(
743 "Rendering helper: {:?}, params: {:?}, hash: {:?}",
744 h.name(),
745 h.params(),
746 h.hash()
747 );
748 if let Some(ref d) = rc.get_local_helper(h.name()) {
749 d.call(&h, registry, ctx, rc, out)
750 } else {
751 let mut helper = registry.get_or_load_helper(h.name())?;
752
753 if helper.is_none() {
754 helper = registry.get_or_load_helper(if ht.block {
755 BLOCK_HELPER_MISSING
756 } else {
757 HELPER_MISSING
758 })?;
759 }
760
761 helper
762 .ok_or_else(|| RenderError::new(format!("Helper not defined: {:?}", h.name())))
763 .and_then(|d| d.call(&h, registry, ctx, rc, out))
764 }
765}
766
767pub(crate) fn do_escape(r: &Registry<'_>, rc: &RenderContext<'_, '_>, content: String) -> String {
768 if !rc.is_disable_escape() {
769 r.get_escape_fn()(&content)
770 } else {
771 content
772 }
773}
774
775#[inline]
776fn indent_aware_write(
777 v: &str,
778 rc: &mut RenderContext<'_, '_>,
779 out: &mut dyn Output,
780) -> Result<(), RenderError> {
781 if let Some(indent: &String) = rc.get_indent_string() {
782 out.write(seg:support::str::with_indent(s:v, indent).as_ref())?;
783 } else {
784 out.write(seg:v.as_ref())?;
785 }
786 Ok(())
787}
788
789impl Renderable for TemplateElement {
790 fn render<'reg: 'rc, 'rc>(
791 &'reg self,
792 registry: &'reg Registry<'reg>,
793 ctx: &'rc Context,
794 rc: &mut RenderContext<'reg, 'rc>,
795 out: &mut dyn Output,
796 ) -> Result<(), RenderError> {
797 match self {
798 RawString(ref v) => indent_aware_write(v.as_ref(), rc, out),
799 Expression(ref ht) | HtmlExpression(ref ht) => {
800 let is_html_expression = matches!(self, HtmlExpression(_));
801 if is_html_expression {
802 rc.set_disable_escape(true);
803 }
804
805 // test if the expression is to render some value
806 let result = if ht.is_name_only() {
807 let helper_name = ht.name.expand_as_name(registry, ctx, rc)?;
808 if helper_exists(&helper_name, registry, rc) {
809 render_helper(ht, registry, ctx, rc, out)
810 } else {
811 debug!("Rendering value: {:?}", ht.name);
812 let context_json = ht.name.expand(registry, ctx, rc)?;
813 if context_json.is_value_missing() {
814 if registry.strict_mode() {
815 Err(RenderError::strict_error(context_json.relative_path()))
816 } else {
817 // helper missing
818 if let Some(hook) = registry.get_or_load_helper(HELPER_MISSING)? {
819 let h = Helper::try_from_template(ht, registry, ctx, rc)?;
820 hook.call(&h, registry, ctx, rc, out)
821 } else {
822 Ok(())
823 }
824 }
825 } else {
826 let rendered = context_json.value().render();
827 let output = do_escape(registry, rc, rendered);
828 indent_aware_write(output.as_ref(), rc, out)
829 }
830 }
831 } else {
832 // this is a helper expression
833 render_helper(ht, registry, ctx, rc, out)
834 };
835
836 if is_html_expression {
837 rc.set_disable_escape(false);
838 }
839
840 result
841 }
842 HelperBlock(ref ht) => render_helper(ht, registry, ctx, rc, out),
843 DecoratorExpression(_) | DecoratorBlock(_) => self.eval(registry, ctx, rc),
844 PartialExpression(ref dt) | PartialBlock(ref dt) => {
845 let di = Decorator::try_from_template(dt, registry, ctx, rc)?;
846
847 partial::expand_partial(&di, registry, ctx, rc, out)
848 }
849 _ => Ok(()),
850 }
851 }
852}
853
854impl Evaluable for TemplateElement {
855 fn eval<'reg: 'rc, 'rc>(
856 &'reg self,
857 registry: &'reg Registry<'reg>,
858 ctx: &'rc Context,
859 rc: &mut RenderContext<'reg, 'rc>,
860 ) -> Result<(), RenderError> {
861 match *self {
862 DecoratorExpression(ref dt: &Box) | DecoratorBlock(ref dt: &Box) => {
863 let di: Decorator<'_, '_> = Decorator::try_from_template(dt, registry, context:ctx, render_context:rc)?;
864 match registry.get_decorator(di.name()) {
865 Some(d: &(dyn DecoratorDef + Sync + Send)) => d.call(&di, r:registry, ctx, rc),
866 None => Err(RenderError::new(desc:format!(
867 "Decorator not defined: {:?}",
868 dt.name
869 ))),
870 }
871 }
872 _ => Ok(()),
873 }
874 }
875}
876
877#[cfg(test)]
878mod test {
879 use std::collections::BTreeMap;
880
881 use super::{Helper, RenderContext, Renderable};
882 use crate::block::BlockContext;
883 use crate::context::Context;
884 use crate::error::RenderError;
885 use crate::json::path::Path;
886 use crate::json::value::JsonRender;
887 use crate::output::{Output, StringOutput};
888 use crate::registry::Registry;
889 use crate::template::TemplateElement::*;
890 use crate::template::{HelperTemplate, Template, TemplateElement};
891
892 #[test]
893 fn test_raw_string() {
894 let r = Registry::new();
895 let raw_string = RawString("<h1>hello world</h1>".to_string());
896
897 let mut out = StringOutput::new();
898 let ctx = Context::null();
899 {
900 let mut rc = RenderContext::new(None);
901 raw_string.render(&r, &ctx, &mut rc, &mut out).ok().unwrap();
902 }
903 assert_eq!(
904 out.into_string().unwrap(),
905 "<h1>hello world</h1>".to_string()
906 );
907 }
908
909 #[test]
910 fn test_expression() {
911 let r = Registry::new();
912 let element = Expression(Box::new(HelperTemplate::with_path(Path::with_named_paths(
913 &["hello"],
914 ))));
915
916 let mut out = StringOutput::new();
917 let mut m: BTreeMap<String, String> = BTreeMap::new();
918 let value = "<p></p>".to_string();
919 m.insert("hello".to_string(), value);
920 let ctx = Context::wraps(&m).unwrap();
921 {
922 let mut rc = RenderContext::new(None);
923 element.render(&r, &ctx, &mut rc, &mut out).ok().unwrap();
924 }
925
926 assert_eq!(
927 out.into_string().unwrap(),
928 "&lt;p&gt;&lt;/p&gt;".to_string()
929 );
930 }
931
932 #[test]
933 fn test_html_expression() {
934 let r = Registry::new();
935 let element = HtmlExpression(Box::new(HelperTemplate::with_path(Path::with_named_paths(
936 &["hello"],
937 ))));
938
939 let mut out = StringOutput::new();
940 let mut m: BTreeMap<String, String> = BTreeMap::new();
941 let value = "world";
942 m.insert("hello".to_string(), value.to_string());
943 let ctx = Context::wraps(&m).unwrap();
944 {
945 let mut rc = RenderContext::new(None);
946 element.render(&r, &ctx, &mut rc, &mut out).ok().unwrap();
947 }
948
949 assert_eq!(out.into_string().unwrap(), value.to_string());
950 }
951
952 #[test]
953 fn test_template() {
954 let r = Registry::new();
955 let mut out = StringOutput::new();
956 let mut m: BTreeMap<String, String> = BTreeMap::new();
957 let value = "world".to_string();
958 m.insert("hello".to_string(), value);
959 let ctx = Context::wraps(&m).unwrap();
960
961 let elements: Vec<TemplateElement> = vec![
962 RawString("<h1>".to_string()),
963 Expression(Box::new(HelperTemplate::with_path(Path::with_named_paths(
964 &["hello"],
965 )))),
966 RawString("</h1>".to_string()),
967 Comment("".to_string()),
968 ];
969
970 let template = Template {
971 elements,
972 name: None,
973 mapping: Vec::new(),
974 };
975
976 {
977 let mut rc = RenderContext::new(None);
978 template.render(&r, &ctx, &mut rc, &mut out).ok().unwrap();
979 }
980
981 assert_eq!(out.into_string().unwrap(), "<h1>world</h1>".to_string());
982 }
983
984 #[test]
985 fn test_render_context_promotion_and_demotion() {
986 use crate::json::value::to_json;
987 let mut render_context = RenderContext::new(None);
988 let mut block = BlockContext::new();
989
990 block.set_local_var("index", to_json(0));
991 render_context.push_block(block);
992
993 render_context.push_block(BlockContext::new());
994 assert_eq!(
995 render_context.get_local_var(1, "index").unwrap(),
996 &to_json(0)
997 );
998
999 render_context.pop_block();
1000
1001 assert_eq!(
1002 render_context.get_local_var(0, "index").unwrap(),
1003 &to_json(0)
1004 );
1005 }
1006
1007 #[test]
1008 fn test_render_subexpression_issue_115() {
1009 use crate::support::str::StringWriter;
1010
1011 let mut r = Registry::new();
1012 r.register_helper(
1013 "format",
1014 Box::new(
1015 |h: &Helper<'_, '_>,
1016 _: &Registry<'_>,
1017 _: &Context,
1018 _: &mut RenderContext<'_, '_>,
1019 out: &mut dyn Output|
1020 -> Result<(), RenderError> {
1021 out.write(&h.param(0).unwrap().value().render())
1022 .map(|_| ())
1023 .map_err(RenderError::from)
1024 },
1025 ),
1026 );
1027
1028 let mut sw = StringWriter::new();
1029 let mut m: BTreeMap<String, String> = BTreeMap::new();
1030 m.insert("a".to_string(), "123".to_string());
1031
1032 {
1033 if let Err(e) = r.render_template_to_write("{{format (format a)}}", &m, &mut sw) {
1034 panic!("{}", e);
1035 }
1036 }
1037
1038 assert_eq!(sw.into_string(), "123".to_string());
1039 }
1040
1041 #[test]
1042 fn test_render_error_line_no() {
1043 let mut r = Registry::new();
1044 let m: BTreeMap<String, String> = BTreeMap::new();
1045
1046 let name = "invalid_template";
1047 assert!(r
1048 .register_template_string(name, "<h1>\n{{#if true}}\n {{#each}}{{/each}}\n{{/if}}")
1049 .is_ok());
1050
1051 if let Err(e) = r.render(name, &m) {
1052 assert_eq!(e.line_no.unwrap(), 3);
1053 assert_eq!(e.column_no.unwrap(), 3);
1054 assert_eq!(e.template_name, Some(name.to_owned()));
1055 } else {
1056 panic!("Error expected");
1057 }
1058 }
1059
1060 #[test]
1061 fn test_partial_failback_render() {
1062 let mut r = Registry::new();
1063
1064 assert!(r
1065 .register_template_string("parent", "<html>{{> layout}}</html>")
1066 .is_ok());
1067 assert!(r
1068 .register_template_string(
1069 "child",
1070 "{{#*inline \"layout\"}}content{{/inline}}{{#> parent}}{{> seg}}{{/parent}}"
1071 )
1072 .is_ok());
1073 assert!(r.register_template_string("seg", "1234").is_ok());
1074
1075 let r = r.render("child", &true).expect("should work");
1076 assert_eq!(r, "<html>content</html>");
1077 }
1078
1079 #[test]
1080 fn test_key_with_slash() {
1081 let mut r = Registry::new();
1082
1083 assert!(r
1084 .register_template_string("t", "{{#each this}}{{@key}}: {{this}}\n{{/each}}")
1085 .is_ok());
1086
1087 let r = r.render("t", &json!({"/foo": "bar"})).unwrap();
1088
1089 assert_eq!(r, "/foo: bar\n");
1090 }
1091
1092 #[test]
1093 fn test_comment() {
1094 let r = Registry::new();
1095
1096 assert_eq!(
1097 r.render_template("Hello {{this}} {{! test me }}", &0)
1098 .unwrap(),
1099 "Hello 0 "
1100 );
1101 }
1102
1103 #[test]
1104 fn test_zero_args_heler() {
1105 let mut r = Registry::new();
1106
1107 r.register_helper(
1108 "name",
1109 Box::new(
1110 |_: &Helper<'_, '_>,
1111 _: &Registry<'_>,
1112 _: &Context,
1113 _: &mut RenderContext<'_, '_>,
1114 out: &mut dyn Output|
1115 -> Result<(), RenderError> {
1116 out.write("N/A").map_err(Into::into)
1117 },
1118 ),
1119 );
1120
1121 r.register_template_string("t0", "Output name: {{name}}")
1122 .unwrap();
1123 r.register_template_string("t1", "Output name: {{first_name}}")
1124 .unwrap();
1125 r.register_template_string("t2", "Output name: {{./name}}")
1126 .unwrap();
1127
1128 // when "name" is available in context, use context first
1129 assert_eq!(
1130 r.render("t0", &json!({"name": "Alex"})).unwrap(),
1131 "Output name: N/A"
1132 );
1133
1134 // when "name" is unavailable, call helper with same name
1135 assert_eq!(
1136 r.render("t2", &json!({"name": "Alex"})).unwrap(),
1137 "Output name: Alex"
1138 );
1139
1140 // output nothing when neither context nor helper available
1141 assert_eq!(
1142 r.render("t1", &json!({"name": "Alex"})).unwrap(),
1143 "Output name: "
1144 );
1145
1146 // generate error in strict mode for above case
1147 r.set_strict_mode(true);
1148 assert!(r.render("t1", &json!({"name": "Alex"})).is_err());
1149
1150 // output nothing when helperMissing was defined
1151 r.set_strict_mode(false);
1152 r.register_helper(
1153 "helperMissing",
1154 Box::new(
1155 |h: &Helper<'_, '_>,
1156 _: &Registry<'_>,
1157 _: &Context,
1158 _: &mut RenderContext<'_, '_>,
1159 out: &mut dyn Output|
1160 -> Result<(), RenderError> {
1161 let name = h.name();
1162 write!(out, "{} not resolved", name)?;
1163 Ok(())
1164 },
1165 ),
1166 );
1167 assert_eq!(
1168 r.render("t1", &json!({"name": "Alex"})).unwrap(),
1169 "Output name: first_name not resolved"
1170 );
1171 }
1172
1173 #[test]
1174 fn test_identifiers_starting_with_numbers() {
1175 let mut r = Registry::new();
1176
1177 assert!(r
1178 .register_template_string("r1", "{{#if 0a}}true{{/if}}")
1179 .is_ok());
1180 let r1 = r.render("r1", &json!({"0a": true})).unwrap();
1181 assert_eq!(r1, "true");
1182
1183 assert!(r.register_template_string("r2", "{{eq 1a 1}}").is_ok());
1184 let r2 = r.render("r2", &json!({"1a": 2, "a": 1})).unwrap();
1185 assert_eq!(r2, "false");
1186
1187 assert!(r
1188 .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?
1189 .is_ok());
1190 let r3 = r
1191 .render("r3", &json!({"0": true, "1a": true, "2_2": true}))
1192 .unwrap();
1193 assert_eq!(
1194 r3,
1195 "0: true \n1a: true resolved from context\n2_2: true resolved from context"
1196 );
1197
1198 // these should all be errors:
1199 assert!(r.register_template_string("r4", "{{eq 1}}").is_ok());
1200 assert!(r.register_template_string("r5", "{{eq a1}}").is_ok());
1201 assert!(r.register_template_string("r6", "{{eq 1a}}").is_ok());
1202 assert!(r.render("r4", &()).is_err());
1203 assert!(r.render("r5", &()).is_err());
1204 assert!(r.render("r6", &()).is_err());
1205 }
1206}
1207