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::support::str::newline_matcher;
18use crate::template::TemplateElement::{
19 DecoratorBlock, DecoratorExpression, Expression, HelperBlock, HtmlExpression, PartialBlock,
20 PartialExpression, RawString,
21};
22use crate::template::{
23 BlockParam, DecoratorTemplate, HelperTemplate, Parameter, Template, TemplateElement,
24 TemplateMapping,
25};
26use crate::{partial, RenderErrorReason};
27
28const HELPER_MISSING: &str = "helperMissing";
29const BLOCK_HELPER_MISSING: &str = "blockHelperMissing";
30
31#[derive(Clone)]
37pub struct RenderContext<'reg: 'rc, 'rc> {
38 dev_mode_templates: Option<&'rc BTreeMap<String, Cow<'rc, Template>>>,
39
40 blocks: VecDeque<BlockContext<'rc>>,
41
42 modified_context: Option<Rc<Context>>,
44
45 partials: BTreeMap<String, &'rc Template>,
46 partial_block_stack: VecDeque<&'rc Template>,
47 partial_block_depth: isize,
48 local_helpers: BTreeMap<String, Rc<dyn HelperDef + Send + Sync + 'rc>>,
49 current_template: Option<&'rc String>,
51 root_template: Option<&'reg String>,
53 disable_escape: bool,
54
55 trailing_newline: bool,
58
59 content_produced: bool,
62
63 indent_before_write: bool,
65 indent_string: Option<Cow<'rc, str>>,
66}
67
68impl<'reg: 'rc, 'rc> RenderContext<'reg, 'rc> {
69 pub fn new(root_template: Option<&'reg String>) -> RenderContext<'reg, 'rc> {
71 let mut blocks = VecDeque::with_capacity(5);
72 blocks.push_front(BlockContext::new());
73
74 let modified_context = None;
75 RenderContext {
76 partials: BTreeMap::new(),
77 partial_block_stack: VecDeque::new(),
78 partial_block_depth: 0,
79 local_helpers: BTreeMap::new(),
80 current_template: None,
81 root_template,
82 disable_escape: false,
83 trailing_newline: false,
84 content_produced: false,
85 indent_before_write: false,
86 indent_string: None,
87 blocks,
88 modified_context,
89 dev_mode_templates: None,
90 }
91 }
92
93 pub fn push_block(&mut self, block: BlockContext<'rc>) {
96 self.blocks.push_front(block);
97 }
98
99 pub fn pop_block(&mut self) {
102 self.blocks.pop_front();
103 }
104
105 pub(crate) fn clear_blocks(&mut self) {
107 self.blocks.clear();
108 }
109
110 pub fn block(&self) -> Option<&BlockContext<'rc>> {
112 self.blocks.front()
113 }
114
115 pub fn block_mut(&mut self) -> Option<&mut BlockContext<'rc>> {
118 self.blocks.front_mut()
119 }
120
121 pub fn replace_blocks(
123 &mut self,
124 blocks: VecDeque<BlockContext<'rc>>,
125 ) -> VecDeque<BlockContext<'rc>> {
126 std::mem::replace(&mut self.blocks, blocks)
127 }
128
129 pub fn context(&self) -> Option<Rc<Context>> {
131 self.modified_context.clone()
132 }
133
134 pub fn set_context(&mut self, ctx: Context) {
138 self.modified_context = Some(Rc::new(ctx));
139 }
140
141 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_or_else(|| ScopedJson::Missing, |v| ScopedJson::Derived(v.clone()))),
164 Path::Relative((segs, _)) => context.navigate(segs, &self.blocks),
165 }
166 }
167
168 pub fn get_partial(&self, name: &str) -> Option<&'rc Template> {
170 if name == partial::PARTIAL_BLOCK {
171 return self
172 .partial_block_stack
173 .get(self.partial_block_depth as usize)
174 .copied();
175 }
176 self.partials.get(name).copied()
177 }
178
179 pub fn set_partial(&mut self, name: String, partial: &'rc Template) {
181 self.partials.insert(name, partial);
182 }
183
184 pub(crate) fn push_partial_block(&mut self, partial: &'rc Template) {
185 self.partial_block_stack.push_front(partial);
186 }
187
188 pub(crate) fn pop_partial_block(&mut self) {
189 self.partial_block_stack.pop_front();
190 }
191
192 pub(crate) fn inc_partial_block_depth(&mut self) {
193 self.partial_block_depth += 1;
194 }
195
196 pub(crate) fn dec_partial_block_depth(&mut self) {
197 let depth = &mut self.partial_block_depth;
198 if *depth > 0 {
199 *depth -= 1;
200 }
201 }
202
203 pub(crate) fn set_indent_string(&mut self, indent: Option<Cow<'rc, str>>) {
204 self.indent_string = indent;
205 }
206
207 #[inline]
208 pub(crate) fn get_indent_string(&self) -> Option<&Cow<'rc, str>> {
209 self.indent_string.as_ref()
210 }
211
212 pub(crate) fn get_dev_mode_template(&self, name: &str) -> Option<&'rc Template> {
213 self.dev_mode_templates
214 .and_then(|dmt| dmt.get(name).map(|t| &**t))
215 }
216
217 pub(crate) fn set_dev_mode_templates(
218 &mut self,
219 t: Option<&'rc BTreeMap<String, Cow<'rc, Template>>>,
220 ) {
221 self.dev_mode_templates = t;
222 }
223
224 pub fn remove_partial(&mut self, name: &str) {
226 self.partials.remove(name);
227 }
228
229 fn get_local_var(&self, level: usize, name: &str) -> Option<&Json> {
230 self.blocks
231 .get(level)
232 .and_then(|blk| blk.get_local_var(name))
233 }
234
235 pub fn is_current_template(&self, p: &str) -> bool {
237 self.current_template.is_some_and(|s| s == p)
238 }
239
240 pub fn register_local_helper(
244 &mut self,
245 name: &str,
246 def: Box<dyn HelperDef + Send + Sync + 'rc>,
247 ) {
248 self.local_helpers.insert(name.to_string(), def.into());
249 }
250
251 pub fn unregister_local_helper(&mut self, name: &str) {
253 self.local_helpers.remove(name);
254 }
255
256 pub fn get_local_helper(&self, name: &str) -> Option<Rc<dyn HelperDef + Send + Sync + 'rc>> {
258 self.local_helpers.get(name).cloned()
259 }
260
261 #[inline]
262 fn has_local_helper(&self, name: &str) -> bool {
263 self.local_helpers.contains_key(name)
264 }
265
266 pub fn get_current_template_name(&self) -> Option<&'rc String> {
270 self.current_template
271 }
272
273 pub fn set_current_template_name(&mut self, name: Option<&'rc String>) {
275 self.current_template = name;
276 }
277
278 pub fn get_root_template_name(&self) -> Option<&'reg String> {
281 self.root_template
282 }
283
284 pub fn is_disable_escape(&self) -> bool {
286 self.disable_escape
287 }
288
289 pub fn set_disable_escape(&mut self, disable: bool) {
292 self.disable_escape = disable;
293 }
294
295 #[inline]
296 pub fn set_trailing_newline(&mut self, trailing_newline: bool) {
297 self.trailing_newline = trailing_newline;
298 }
299
300 #[inline]
301 pub fn get_trailine_newline(&self) -> bool {
302 self.trailing_newline
303 }
304
305 #[inline]
306 pub fn set_content_produced(&mut self, content_produced: bool) {
307 self.content_produced = content_produced;
308 }
309
310 #[inline]
311 pub fn get_content_produced(&self) -> bool {
312 self.content_produced
313 }
314
315 #[inline]
316 pub fn set_indent_before_write(&mut self, indent_before_write: bool) {
317 self.indent_before_write = indent_before_write;
318 }
319
320 #[inline]
321 pub fn get_indent_before_write(&self) -> bool {
322 self.indent_before_write
323 }
324}
325
326impl fmt::Debug for RenderContext<'_, '_> {
327 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
328 f.debug_struct("RenderContextInner")
329 .field("dev_mode_templates", &self.dev_mode_templates)
330 .field("blocks", &self.blocks)
331 .field("modified_context", &self.modified_context)
332 .field("partials", &self.partials)
333 .field("partial_block_stack", &self.partial_block_stack)
334 .field("partial_block_depth", &self.partial_block_depth)
335 .field("root_template", &self.root_template)
336 .field("current_template", &self.current_template)
337 .field("disable_escape", &self.disable_escape)
338 .finish()
339 }
340}
341
342#[derive(Debug, Clone)]
344pub struct Helper<'rc> {
345 name: Cow<'rc, str>,
346 params: Vec<PathAndJson<'rc>>,
347 hash: BTreeMap<&'rc str, PathAndJson<'rc>>,
348 template: Option<&'rc Template>,
349 inverse: Option<&'rc Template>,
350 block_param: Option<&'rc BlockParam>,
351 block: bool,
352}
353
354impl<'reg: 'rc, 'rc> Helper<'rc> {
355 fn try_from_template(
356 ht: &'rc HelperTemplate,
357 registry: &'reg Registry<'reg>,
358 context: &'rc Context,
359 render_context: &mut RenderContext<'reg, 'rc>,
360 ) -> Result<Helper<'rc>, RenderError> {
361 let name = ht.name.expand_as_name(registry, context, render_context)?;
362 let mut pv = Vec::with_capacity(ht.params.len());
363 for p in &ht.params {
364 let r = p.expand(registry, context, render_context)?;
365 pv.push(r);
366 }
367
368 let mut hm = BTreeMap::new();
369 for (k, p) in &ht.hash {
370 let r = p.expand(registry, context, render_context)?;
371 hm.insert(k.as_ref(), r);
372 }
373
374 Ok(Helper {
375 name,
376 params: pv,
377 hash: hm,
378 template: ht.template.as_ref(),
379 inverse: ht.inverse.as_ref(),
380 block_param: ht.block_param.as_ref(),
381 block: ht.block,
382 })
383 }
384
385 pub fn name(&self) -> &str {
387 &self.name
388 }
389
390 pub fn params(&self) -> &Vec<PathAndJson<'rc>> {
392 &self.params
393 }
394
395 pub fn param(&self, idx: usize) -> Option<&PathAndJson<'rc>> {
414 self.params.get(idx)
415 }
416
417 pub fn hash(&self) -> &BTreeMap<&'rc str, PathAndJson<'rc>> {
419 &self.hash
420 }
421
422 pub fn hash_get(&self, key: &str) -> Option<&PathAndJson<'rc>> {
441 self.hash.get(key)
442 }
443
444 pub fn template(&self) -> Option<&'rc Template> {
449 self.template
450 }
451
452 pub fn inverse(&self) -> Option<&'rc Template> {
454 self.inverse
455 }
456
457 pub fn is_block(&self) -> bool {
459 self.block
460 }
461
462 pub fn has_block_param(&self) -> bool {
464 self.block_param.is_some()
465 }
466
467 pub fn block_param(&self) -> Option<&'rc str> {
469 if let Some(&BlockParam::Single(Parameter::Name(ref s))) = self.block_param {
470 Some(s)
471 } else {
472 None
473 }
474 }
475
476 pub fn block_param_pair(&self) -> Option<(&'rc str, &'rc str)> {
478 if let Some(&BlockParam::Pair((Parameter::Name(ref s1), Parameter::Name(ref s2)))) =
479 self.block_param
480 {
481 Some((s1, s2))
482 } else {
483 None
484 }
485 }
486}
487
488#[derive(Debug)]
490pub struct Decorator<'rc> {
491 name: Cow<'rc, str>,
492 params: Vec<PathAndJson<'rc>>,
493 hash: BTreeMap<&'rc str, PathAndJson<'rc>>,
494 template: Option<&'rc Template>,
495 indent: Option<Cow<'rc, str>>,
496}
497
498impl<'reg: 'rc, 'rc> Decorator<'rc> {
499 fn try_from_template(
500 dt: &'rc DecoratorTemplate,
501 registry: &'reg Registry<'reg>,
502 context: &'rc Context,
503 render_context: &mut RenderContext<'reg, 'rc>,
504 ) -> Result<Decorator<'rc>, RenderError> {
505 let name = dt.name.expand_as_name(registry, context, render_context)?;
506
507 let mut pv = Vec::with_capacity(dt.params.len());
508 for p in &dt.params {
509 let r = p.expand(registry, context, render_context)?;
510 pv.push(r);
511 }
512
513 let mut hm = BTreeMap::new();
514 for (k, p) in &dt.hash {
515 let r = p.expand(registry, context, render_context)?;
516 hm.insert(k.as_ref(), r);
517 }
518
519 let indent = match (render_context.get_indent_string(), dt.indent.as_ref()) {
520 (None, None) => None,
521 (Some(s), None) => Some(s.clone()),
522 (None, Some(s)) => Some(Cow::Borrowed(&**s)),
523 (Some(s1), Some(s2)) => {
524 let mut res = s1.to_string();
525 res.push_str(s2);
526 Some(Cow::from(res))
527 }
528 };
529
530 Ok(Decorator {
531 name,
532 params: pv,
533 hash: hm,
534 template: dt.template.as_ref(),
535 indent,
536 })
537 }
538
539 pub fn name(&self) -> &str {
541 self.name.as_ref()
542 }
543
544 pub fn params(&self) -> &Vec<PathAndJson<'rc>> {
546 &self.params
547 }
548
549 pub fn param(&self, idx: usize) -> Option<&PathAndJson<'rc>> {
551 self.params.get(idx)
552 }
553
554 pub fn hash(&self) -> &BTreeMap<&'rc str, PathAndJson<'rc>> {
556 &self.hash
557 }
558
559 pub fn hash_get(&self, key: &str) -> Option<&PathAndJson<'rc>> {
561 self.hash.get(key)
562 }
563
564 pub fn template(&self) -> Option<&'rc Template> {
566 self.template
567 }
568
569 pub fn indent(&self) -> Option<&Cow<'rc, str>> {
570 self.indent.as_ref()
571 }
572}
573
574pub trait Renderable {
576 fn render<'reg: 'rc, 'rc>(
578 &'rc self,
579 registry: &'reg Registry<'reg>,
580 context: &'rc Context,
581 rc: &mut RenderContext<'reg, 'rc>,
582 out: &mut dyn Output,
583 ) -> Result<(), RenderError>;
584
585 fn renders<'reg: 'rc, 'rc>(
587 &'rc self,
588 registry: &'reg Registry<'reg>,
589 ctx: &'rc Context,
590 rc: &mut RenderContext<'reg, 'rc>,
591 ) -> Result<String, RenderError> {
592 let mut so = StringOutput::new();
593 self.render(registry, ctx, rc, &mut so)?;
594 so.into_string()
595 .map_err(|e| RenderErrorReason::from(e).into())
596 }
597}
598
599pub trait Evaluable {
601 fn eval<'reg: 'rc, 'rc>(
602 &'rc self,
603 registry: &'reg Registry<'reg>,
604 context: &'rc Context,
605 rc: &mut RenderContext<'reg, 'rc>,
606 ) -> Result<(), RenderError>;
607}
608
609#[inline]
610fn call_helper_for_value<'reg: 'rc, 'rc>(
611 hd: &dyn HelperDef,
612 ht: &Helper<'rc>,
613 r: &'reg Registry<'reg>,
614 ctx: &'rc Context,
615 rc: &mut RenderContext<'reg, 'rc>,
616) -> Result<PathAndJson<'rc>, RenderError> {
617 match hd.call_inner(ht, r, ctx, rc) {
618 Ok(result) => Ok(PathAndJson::new(None, result)),
619 Err(e) => {
620 if e.is_unimplemented() {
621 let mut so = StringOutput::new();
623
624 let disable_escape = rc.is_disable_escape();
627 rc.set_disable_escape(true);
628
629 hd.call(ht, r, ctx, rc, &mut so)?;
630 rc.set_disable_escape(disable_escape);
631
632 let string = so.into_string().map_err(RenderError::from)?;
633 Ok(PathAndJson::new(
634 None,
635 ScopedJson::Derived(Json::String(string)),
636 ))
637 } else {
638 Err(e)
639 }
640 }
641 }
642}
643
644impl Parameter {
645 pub fn expand_as_name<'reg: 'rc, 'rc>(
646 &'rc self,
647 registry: &'reg Registry<'reg>,
648 ctx: &'rc Context,
649 rc: &mut RenderContext<'reg, 'rc>,
650 ) -> Result<Cow<'rc, str>, RenderError> {
651 match self {
652 Parameter::Name(ref name) => Ok(Cow::Borrowed(name)),
653 Parameter::Path(ref p) => Ok(Cow::Borrowed(p.raw())),
654 Parameter::Subexpression(_) => self
655 .expand(registry, ctx, rc)
656 .map(|v| v.value().render())
657 .map(Cow::Owned),
658 Parameter::Literal(ref j) => Ok(Cow::Owned(j.render())),
659 }
660 }
661
662 pub fn expand<'reg: 'rc, 'rc>(
663 &'rc self,
664 registry: &'reg Registry<'reg>,
665 ctx: &'rc Context,
666 rc: &mut RenderContext<'reg, 'rc>,
667 ) -> Result<PathAndJson<'rc>, RenderError> {
668 match self {
669 Parameter::Name(ref name) => {
670 Ok(PathAndJson::new(Some(name.to_owned()), ScopedJson::Missing))
672 }
673 Parameter::Path(ref path) => {
674 if let Some(rc_context) = rc.context() {
675 let result = rc.evaluate2(rc_context.borrow(), path)?;
676 Ok(PathAndJson::new(
677 Some(path.raw().to_owned()),
678 ScopedJson::Derived(result.as_json().clone()),
679 ))
680 } else {
681 let result = rc.evaluate2(ctx, path)?;
682 Ok(PathAndJson::new(Some(path.raw().to_owned()), result))
683 }
684 }
685 Parameter::Literal(ref j) => Ok(PathAndJson::new(None, ScopedJson::Constant(j))),
686 Parameter::Subexpression(ref t) => match *t.as_element() {
687 Expression(ref ht) => {
688 let name = ht.name.expand_as_name(registry, ctx, rc)?;
689
690 let h = Helper::try_from_template(ht, registry, ctx, rc)?;
691 if let Some(ref d) = rc.get_local_helper(&name) {
692 call_helper_for_value(d.as_ref(), &h, registry, ctx, rc)
693 } else {
694 let mut helper = registry.get_or_load_helper(&name)?;
695
696 if helper.is_none() {
697 helper = registry.get_or_load_helper(if ht.block {
698 BLOCK_HELPER_MISSING
699 } else {
700 HELPER_MISSING
701 })?;
702 }
703
704 helper
705 .ok_or_else(|| {
706 RenderErrorReason::HelperNotFound(name.to_string()).into()
707 })
708 .and_then(|d| call_helper_for_value(d.as_ref(), &h, registry, ctx, rc))
709 }
710 }
711 _ => unreachable!(),
712 },
713 }
714 }
715}
716
717impl Renderable for Template {
718 fn render<'reg: 'rc, 'rc>(
719 &'rc self,
720 registry: &'reg Registry<'reg>,
721 ctx: &'rc Context,
722 rc: &mut RenderContext<'reg, 'rc>,
723 out: &mut dyn Output,
724 ) -> Result<(), RenderError> {
725 rc.set_current_template_name(self.name.as_ref());
726 let iter = self.elements.iter();
727
728 for (idx, t) in iter.enumerate() {
729 t.render(registry, ctx, rc, out).map_err(|mut e| {
730 if e.line_no.is_none() {
732 if let Some(&TemplateMapping(line, col)) = self.mapping.get(idx) {
733 e.line_no = Some(line);
734 e.column_no = Some(col);
735 }
736 }
737
738 if e.template_name.is_none() {
739 e.template_name.clone_from(&self.name);
740 }
741
742 e
743 })?;
744 }
745
746 Ok(())
747 }
748}
749
750impl Evaluable for Template {
751 fn eval<'reg: 'rc, 'rc>(
752 &'rc self,
753 registry: &'reg Registry<'reg>,
754 ctx: &'rc Context,
755 rc: &mut RenderContext<'reg, 'rc>,
756 ) -> Result<(), RenderError> {
757 let iter = self.elements.iter();
758
759 for (idx, t) in iter.enumerate() {
760 t.eval(registry, ctx, rc).map_err(|mut e| {
761 if e.line_no.is_none() {
762 if let Some(&TemplateMapping(line, col)) = self.mapping.get(idx) {
763 e.line_no = Some(line);
764 e.column_no = Some(col);
765 }
766 }
767
768 e.template_name.clone_from(&self.name);
769 e
770 })?;
771 }
772 Ok(())
773 }
774}
775
776fn helper_exists<'reg: 'rc, 'rc>(
777 name: &str,
778 reg: &Registry<'reg>,
779 rc: &RenderContext<'reg, 'rc>,
780) -> bool {
781 rc.has_local_helper(name) || reg.has_helper(name)
782}
783
784#[inline]
785fn render_helper<'reg: 'rc, 'rc>(
786 ht: &'rc HelperTemplate,
787 registry: &'reg Registry<'reg>,
788 ctx: &'rc Context,
789 rc: &mut RenderContext<'reg, 'rc>,
790 out: &mut dyn Output,
791) -> Result<(), RenderError> {
792 let h = Helper::try_from_template(ht, registry, ctx, rc)?;
793 debug!(
794 "Rendering helper: {:?}, params: {:?}, hash: {:?}",
795 h.name(),
796 h.params(),
797 h.hash()
798 );
799 let mut call_indent_aware = |helper_def: &dyn HelperDef, rc: &mut RenderContext<'reg, 'rc>| {
800 let indent_directive_before = rc.get_indent_before_write();
801 let content_produced_before = rc.get_content_produced();
802 rc.set_content_produced(false);
803 rc.set_indent_before_write(
804 indent_directive_before || (ht.indent_before_write && rc.get_trailine_newline()),
805 );
806
807 helper_def.call(&h, registry, ctx, rc, out)?;
808
809 if rc.get_content_produced() {
810 rc.set_indent_before_write(rc.get_trailine_newline());
811 } else {
812 rc.set_content_produced(content_produced_before);
813 rc.set_indent_before_write(indent_directive_before);
814 }
815 Ok(())
816 };
817 if let Some(ref d) = rc.get_local_helper(h.name()) {
818 call_indent_aware(&**d, rc)
819 } else {
820 let mut helper = registry.get_or_load_helper(h.name())?;
821
822 if helper.is_none() {
823 helper = registry.get_or_load_helper(if ht.block {
824 BLOCK_HELPER_MISSING
825 } else {
826 HELPER_MISSING
827 })?;
828 }
829
830 helper
831 .ok_or_else(|| RenderErrorReason::HelperNotFound(h.name().to_owned()).into())
832 .and_then(|d| call_indent_aware(&*d, rc))
833 }
834}
835
836pub(crate) fn do_escape(r: &Registry<'_>, rc: &RenderContext<'_, '_>, content: String) -> String {
837 if !rc.is_disable_escape() {
838 r.get_escape_fn()(&content)
839 } else {
840 content
841 }
842}
843
844#[inline]
845pub fn indent_aware_write(
846 v: &str,
847 rc: &mut RenderContext<'_, '_>,
848 out: &mut dyn Output,
849) -> Result<(), RenderError> {
850 if v.is_empty() {
851 return Ok(());
852 }
853 rc.set_content_produced(true);
854
855 if !v.starts_with(newline_matcher) && rc.get_indent_before_write() {
856 if let Some(indent) = rc.get_indent_string() {
857 out.write(indent)?;
858 }
859 }
860
861 if let Some(indent) = rc.get_indent_string() {
862 support::str::write_indented(v, indent, out)?;
863 } else {
864 out.write(v.as_ref())?;
865 }
866
867 let trailing_newline = v.ends_with(newline_matcher);
868 rc.set_trailing_newline(trailing_newline);
869 rc.set_indent_before_write(trailing_newline);
870
871 Ok(())
872}
873
874impl Renderable for TemplateElement {
875 fn render<'reg: 'rc, 'rc>(
876 &'rc self,
877 registry: &'reg Registry<'reg>,
878 ctx: &'rc Context,
879 rc: &mut RenderContext<'reg, 'rc>,
880 out: &mut dyn Output,
881 ) -> Result<(), RenderError> {
882 match self {
883 RawString(ref v) => indent_aware_write(v.as_ref(), rc, out),
884 Expression(ref ht) | HtmlExpression(ref ht) => {
885 let is_html_expression = matches!(self, HtmlExpression(_));
886 if is_html_expression {
887 rc.set_disable_escape(true);
888 }
889
890 let result = if ht.is_name_only() {
892 let helper_name = ht.name.expand_as_name(registry, ctx, rc)?;
893 if helper_exists(&helper_name, registry, rc) {
894 render_helper(ht, registry, ctx, rc, out)
895 } else {
896 debug!("Rendering value: {:?}", ht.name);
897 let context_json = ht.name.expand(registry, ctx, rc)?;
898 if context_json.is_value_missing() {
899 if registry.strict_mode() {
900 Err(RenderError::strict_error(context_json.relative_path()))
901 } else {
902 if let Some(hook) = registry.get_or_load_helper(HELPER_MISSING)? {
904 let h = Helper::try_from_template(ht, registry, ctx, rc)?;
905 hook.call(&h, registry, ctx, rc, out)
906 } else {
907 Ok(())
908 }
909 }
910 } else {
911 let rendered = context_json.value().render();
912 let output = do_escape(registry, rc, rendered);
913 indent_aware_write(output.as_ref(), rc, out)
914 }
915 }
916 } else {
917 render_helper(ht, registry, ctx, rc, out)
919 };
920
921 if is_html_expression {
922 rc.set_disable_escape(false);
923 }
924
925 result
926 }
927 HelperBlock(ref ht) => render_helper(ht, registry, ctx, rc, out),
928 DecoratorExpression(_) | DecoratorBlock(_) => self.eval(registry, ctx, rc),
929 PartialExpression(ref dt) | PartialBlock(ref dt) => {
930 let di = Decorator::try_from_template(dt, registry, ctx, rc)?;
931
932 let indent_directive_before = rc.get_indent_before_write();
933 let content_produced_before = rc.get_content_produced();
934
935 rc.set_indent_before_write(
936 dt.indent_before_write && (rc.get_trailine_newline() || dt.indent.is_some()),
937 );
938 rc.set_content_produced(false);
939
940 partial::expand_partial(&di, registry, ctx, rc, out)?;
941
942 if rc.get_content_produced() {
943 rc.set_indent_before_write(rc.get_trailine_newline());
944 } else {
945 rc.set_content_produced(content_produced_before);
946 rc.set_indent_before_write(indent_directive_before);
947 }
948 Ok(())
949 }
950 _ => Ok(()),
951 }
952 }
953}
954
955impl Evaluable for TemplateElement {
956 fn eval<'reg: 'rc, 'rc>(
957 &'rc self,
958 registry: &'reg Registry<'reg>,
959 ctx: &'rc Context,
960 rc: &mut RenderContext<'reg, 'rc>,
961 ) -> Result<(), RenderError> {
962 match *self {
963 DecoratorExpression(ref dt) | DecoratorBlock(ref dt) => {
964 let di = Decorator::try_from_template(dt, registry, ctx, rc)?;
965 match registry.get_decorator(di.name()) {
966 Some(d) => d.call(&di, registry, ctx, rc),
967 None => Err(RenderErrorReason::DecoratorNotFound(di.name().to_owned()).into()),
968 }
969 }
970 _ => Ok(()),
971 }
972 }
973}
974
975#[cfg(test)]
976mod test {
977 use std::collections::BTreeMap;
978
979 use super::{Helper, RenderContext, Renderable};
980 use crate::block::BlockContext;
981 use crate::context::Context;
982 use crate::error::RenderError;
983 use crate::json::path::Path;
984 use crate::json::value::JsonRender;
985 use crate::output::{Output, StringOutput};
986 use crate::registry::Registry;
987 use crate::template::TemplateElement::*;
988 use crate::template::{HelperTemplate, Template, TemplateElement};
989
990 #[test]
991 fn test_raw_string() {
992 let r = Registry::new();
993 let raw_string = RawString("<h1>hello world</h1>".to_string());
994
995 let mut out = StringOutput::new();
996 let ctx = Context::null();
997 {
998 let mut rc = RenderContext::new(None);
999 raw_string.render(&r, &ctx, &mut rc, &mut out).ok().unwrap();
1000 }
1001 assert_eq!(
1002 out.into_string().unwrap(),
1003 "<h1>hello world</h1>".to_string()
1004 );
1005 }
1006
1007 #[test]
1008 fn test_expression() {
1009 let r = Registry::new();
1010 let element = Expression(Box::new(HelperTemplate::with_path(Path::with_named_paths(
1011 &["hello"],
1012 ))));
1013
1014 let mut out = StringOutput::new();
1015 let mut m: BTreeMap<String, String> = BTreeMap::new();
1016 let value = "<p></p>".to_string();
1017 m.insert("hello".to_string(), value);
1018 let ctx = Context::wraps(&m).unwrap();
1019 {
1020 let mut rc = RenderContext::new(None);
1021 element.render(&r, &ctx, &mut rc, &mut out).ok().unwrap();
1022 }
1023
1024 assert_eq!(
1025 out.into_string().unwrap(),
1026 "<p></p>".to_string()
1027 );
1028 }
1029
1030 #[test]
1031 fn test_html_expression() {
1032 let r = Registry::new();
1033 let element = HtmlExpression(Box::new(HelperTemplate::with_path(Path::with_named_paths(
1034 &["hello"],
1035 ))));
1036
1037 let mut out = StringOutput::new();
1038 let mut m: BTreeMap<String, String> = BTreeMap::new();
1039 let value = "world";
1040 m.insert("hello".to_string(), value.to_string());
1041 let ctx = Context::wraps(&m).unwrap();
1042 {
1043 let mut rc = RenderContext::new(None);
1044 element.render(&r, &ctx, &mut rc, &mut out).ok().unwrap();
1045 }
1046
1047 assert_eq!(out.into_string().unwrap(), value.to_string());
1048 }
1049
1050 #[test]
1051 fn test_template() {
1052 let r = Registry::new();
1053 let mut out = StringOutput::new();
1054 let mut m: BTreeMap<String, String> = BTreeMap::new();
1055 let value = "world".to_string();
1056 m.insert("hello".to_string(), value);
1057 let ctx = Context::wraps(&m).unwrap();
1058
1059 let elements: Vec<TemplateElement> = vec![
1060 RawString("<h1>".to_string()),
1061 Expression(Box::new(HelperTemplate::with_path(Path::with_named_paths(
1062 &["hello"],
1063 )))),
1064 RawString("</h1>".to_string()),
1065 Comment(String::new()),
1066 ];
1067
1068 let template = Template {
1069 elements,
1070 name: None,
1071 mapping: Vec::new(),
1072 };
1073
1074 {
1075 let mut rc = RenderContext::new(None);
1076 template.render(&r, &ctx, &mut rc, &mut out).ok().unwrap();
1077 }
1078
1079 assert_eq!(out.into_string().unwrap(), "<h1>world</h1>".to_string());
1080 }
1081
1082 #[test]
1083 fn test_render_context_promotion_and_demotion() {
1084 use crate::json::value::to_json;
1085 let mut render_context = RenderContext::new(None);
1086 let mut block = BlockContext::new();
1087
1088 block.set_local_var("index", to_json(0));
1089 render_context.push_block(block);
1090
1091 render_context.push_block(BlockContext::new());
1092 assert_eq!(
1093 render_context.get_local_var(1, "index").unwrap(),
1094 &to_json(0)
1095 );
1096
1097 render_context.pop_block();
1098
1099 assert_eq!(
1100 render_context.get_local_var(0, "index").unwrap(),
1101 &to_json(0)
1102 );
1103 }
1104
1105 #[test]
1106 fn test_render_subexpression_issue_115() {
1107 use crate::support::str::StringWriter;
1108
1109 let mut r = Registry::new();
1110 r.register_helper(
1111 "format",
1112 Box::new(
1113 |h: &Helper<'_>,
1114 _: &Registry<'_>,
1115 _: &Context,
1116 _: &mut RenderContext<'_, '_>,
1117 out: &mut dyn Output|
1118 -> Result<(), RenderError> {
1119 out.write(&h.param(0).unwrap().value().render())
1120 .map_err(RenderError::from)
1121 },
1122 ),
1123 );
1124
1125 let mut sw = StringWriter::new();
1126 let mut m: BTreeMap<String, String> = BTreeMap::new();
1127 m.insert("a".to_string(), "123".to_string());
1128
1129 {
1130 if let Err(e) = r.render_template_to_write("{{format (format a)}}", &m, &mut sw) {
1131 panic!("{}", e);
1132 }
1133 }
1134
1135 assert_eq!(sw.into_string(), "123".to_string());
1136 }
1137
1138 #[test]
1139 fn test_render_error_line_no() {
1140 let mut r = Registry::new();
1141 let m: BTreeMap<String, String> = BTreeMap::new();
1142
1143 let name = "invalid_template";
1144 assert!(r
1145 .register_template_string(name, "<h1>\n{{#if true}}\n {{#each}}{{/each}}\n{{/if}}")
1146 .is_ok());
1147
1148 if let Err(e) = r.render(name, &m) {
1149 assert_eq!(e.line_no.unwrap(), 3);
1150 assert_eq!(e.column_no.unwrap(), 3);
1151 assert_eq!(e.template_name, Some(name.to_owned()));
1152 } else {
1153 panic!("Error expected");
1154 }
1155 }
1156
1157 #[test]
1158 fn test_partial_failback_render() {
1159 let mut r = Registry::new();
1160
1161 assert!(r
1162 .register_template_string("parent", "<html>{{> layout}}</html>")
1163 .is_ok());
1164 assert!(r
1165 .register_template_string(
1166 "child",
1167 "{{#*inline \"layout\"}}content{{/inline}}{{#> parent}}{{> seg}}{{/parent}}",
1168 )
1169 .is_ok());
1170 assert!(r.register_template_string("seg", "1234").is_ok());
1171
1172 let r = r.render("child", &true).expect("should work");
1173 assert_eq!(r, "<html>content</html>");
1174 }
1175
1176 #[test]
1177 fn test_key_with_slash() {
1178 let mut r = Registry::new();
1179
1180 assert!(r
1181 .register_template_string("t", "{{#each this}}{{@key}}: {{this}}\n{{/each}}")
1182 .is_ok());
1183
1184 let r = r.render("t", &json!({"/foo": "bar"})).unwrap();
1185
1186 assert_eq!(r, "/foo: bar\n");
1187 }
1188
1189 #[test]
1190 fn test_comment() {
1191 let r = Registry::new();
1192
1193 assert_eq!(
1194 r.render_template("Hello {{this}} {{! test me }}", &0)
1195 .unwrap(),
1196 "Hello 0 "
1197 );
1198 }
1199
1200 #[test]
1201 fn test_zero_args_heler() {
1202 let mut r = Registry::new();
1203
1204 r.register_helper(
1205 "name",
1206 Box::new(
1207 |_: &Helper<'_>,
1208 _: &Registry<'_>,
1209 _: &Context,
1210 _: &mut RenderContext<'_, '_>,
1211 out: &mut dyn Output|
1212 -> Result<(), RenderError> {
1213 out.write("N/A").map_err(Into::into)
1214 },
1215 ),
1216 );
1217
1218 r.register_template_string("t0", "Output name: {{name}}")
1219 .unwrap();
1220 r.register_template_string("t1", "Output name: {{first_name}}")
1221 .unwrap();
1222 r.register_template_string("t2", "Output name: {{./name}}")
1223 .unwrap();
1224
1225 assert_eq!(
1227 r.render("t0", &json!({"name": "Alex"})).unwrap(),
1228 "Output name: N/A"
1229 );
1230
1231 assert_eq!(
1233 r.render("t2", &json!({"name": "Alex"})).unwrap(),
1234 "Output name: Alex"
1235 );
1236
1237 assert_eq!(
1239 r.render("t1", &json!({"name": "Alex"})).unwrap(),
1240 "Output name: "
1241 );
1242
1243 r.set_strict_mode(true);
1245 assert!(r.render("t1", &json!({"name": "Alex"})).is_err());
1246
1247 r.set_strict_mode(false);
1249 r.register_helper(
1250 "helperMissing",
1251 Box::new(
1252 |h: &Helper<'_>,
1253 _: &Registry<'_>,
1254 _: &Context,
1255 _: &mut RenderContext<'_, '_>,
1256 out: &mut dyn Output|
1257 -> Result<(), RenderError> {
1258 let name = h.name();
1259 write!(out, "{name} not resolved")?;
1260 Ok(())
1261 },
1262 ),
1263 );
1264 assert_eq!(
1265 r.render("t1", &json!({"name": "Alex"})).unwrap(),
1266 "Output name: first_name not resolved"
1267 );
1268 }
1269
1270 #[test]
1271 fn test_identifiers_starting_with_numbers() {
1272 let mut r = Registry::new();
1273
1274 assert!(r
1275 .register_template_string("r1", "{{#if 0a}}true{{/if}}")
1276 .is_ok());
1277 let r1 = r.render("r1", &json!({"0a": true})).unwrap();
1278 assert_eq!(r1, "true");
1279
1280 assert!(r.register_template_string("r2", "{{eq 1a 1}}").is_ok());
1281 let r2 = r.render("r2", &json!({"1a": 2, "a": 1})).unwrap();
1282 assert_eq!(r2, "false");
1283
1284 assert!(r
1285 .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}}") .is_ok());
1287 let r3 = r
1288 .render("r3", &json!({"0": true, "1a": true, "2_2": true}))
1289 .unwrap();
1290 assert_eq!(
1291 r3,
1292 "0: true \n1a: true resolved from context\n2_2: true resolved from context"
1293 );
1294
1295 assert!(r.register_template_string("r4", "{{eq 1}}").is_ok());
1297 assert!(r.register_template_string("r5", "{{eq a1}}").is_ok());
1298 assert!(r.register_template_string("r6", "{{eq 1a}}").is_ok());
1299 assert!(r.render("r4", &()).is_err());
1300 assert!(r.render("r5", &()).is_err());
1301 assert!(r.render("r6", &()).is_err());
1302 }
1303}