1use std::collections::{HashMap, VecDeque};
2
3use serde_json::Value as Json;
4
5use crate::block::BlockContext;
6use crate::context::{merge_json, Context};
7use crate::error::RenderError;
8use crate::output::Output;
9use crate::registry::Registry;
10use crate::render::{Decorator, Evaluable, RenderContext, Renderable};
11use crate::template::Template;
12use crate::{Path, RenderErrorReason};
13
14pub(crate) const PARTIAL_BLOCK: &str = "@partial-block";
15
16fn find_partial<'reg: 'rc, 'rc>(
17 rc: &RenderContext<'reg, 'rc>,
18 r: &'reg Registry<'reg>,
19 d: &Decorator<'rc>,
20 name: &str,
21) -> Result<Option<&'rc Template>, RenderError> {
22 if let Some(partial) = rc.get_partial(name) {
23 return Ok(Some(partial));
24 }
25
26 if let Some(t) = rc.get_dev_mode_template(name) {
27 return Ok(Some(t));
28 }
29
30 if let Some(t) = r.get_template(name) {
31 return Ok(Some(t));
32 }
33
34 if let Some(tpl) = d.template() {
35 return Ok(Some(tpl));
36 }
37
38 Ok(None)
39}
40
41pub fn expand_partial<'reg: 'rc, 'rc>(
42 d: &Decorator<'rc>,
43 r: &'reg Registry<'reg>,
44 ctx: &'rc Context,
45 rc: &mut RenderContext<'reg, 'rc>,
46 out: &mut dyn Output,
47) -> Result<(), RenderError> {
48 if let Some(t) = d.template() {
50 t.eval(r, ctx, rc)?;
51 }
52
53 let tname = d.name();
54
55 let current_template_before = rc.get_current_template_name();
56 let indent_before = rc.get_indent_string().cloned();
57
58 if rc.is_current_template(tname) {
59 return Err(RenderErrorReason::CannotIncludeSelf.into());
60 }
61
62 let partial = find_partial(rc, r, d, tname)?;
63
64 let Some(partial) = partial else {
65 return Err(RenderErrorReason::PartialNotFound(tname.to_owned()).into());
66 };
67
68 let is_partial_block = tname == PARTIAL_BLOCK;
69
70 if is_partial_block {
73 rc.inc_partial_block_depth();
74 } else {
75 rc.dec_partial_block_depth();
78 }
79
80 let hash_ctx = d
82 .hash()
83 .iter()
84 .map(|(k, v)| (*k, v.value()))
85 .collect::<HashMap<&str, &Json>>();
86
87 let mut partial_include_block = BlockContext::new();
88 let merged_context = if let Some(p) = d.param(0) {
90 if let Some(relative_path) = p.relative_path() {
91 merge_json(rc.evaluate(ctx, relative_path)?.as_json(), &hash_ctx)
93 } else {
94 merge_json(p.value(), &hash_ctx)
96 }
97 } else {
98 merge_json(rc.evaluate2(ctx, &Path::current())?.as_json(), &hash_ctx)
100 };
101 partial_include_block.set_base_value(merged_context);
102
103 let current_blocks = rc.replace_blocks(VecDeque::with_capacity(1));
105 rc.push_block(partial_include_block);
106
107 if let Some(pb) = d.template() {
109 rc.push_partial_block(pb);
110 }
111
112 rc.set_indent_string(d.indent().cloned());
114
115 let result = partial.render(r, ctx, rc, out);
116
117 let trailing_newline = rc.get_trailine_newline();
119
120 if d.template().is_some() {
121 rc.pop_partial_block();
122 }
123
124 let _ = rc.replace_blocks(current_blocks);
125 rc.set_trailing_newline(trailing_newline);
126 rc.set_current_template_name(current_template_before);
127 rc.set_indent_string(indent_before);
128
129 result
130}
131
132#[cfg(test)]
133mod test {
134 use crate::context::Context;
135 use crate::error::RenderError;
136 use crate::output::Output;
137 use crate::registry::Registry;
138 use crate::render::{Helper, RenderContext};
139
140 #[test]
141 fn test() {
142 let mut handlebars = Registry::new();
143 assert!(handlebars
144 .register_template_string("t0", "{{> t1}}")
145 .is_ok());
146 assert!(handlebars
147 .register_template_string("t1", "{{this}}")
148 .is_ok());
149 assert!(handlebars
150 .register_template_string("t2", "{{#> t99}}not there{{/t99}}")
151 .is_ok());
152 assert!(handlebars
153 .register_template_string("t3", "{{#*inline \"t31\"}}{{this}}{{/inline}}{{> t31}}")
154 .is_ok());
155 assert!(handlebars
156 .register_template_string(
157 "t4",
158 "{{#> t5}}{{#*inline \"nav\"}}navbar{{/inline}}{{/t5}}"
159 )
160 .is_ok());
161 assert!(handlebars
162 .register_template_string("t5", "include {{> nav}}")
163 .is_ok());
164 assert!(handlebars
165 .register_template_string("t6", "{{> t1 a}}")
166 .is_ok());
167 assert!(handlebars
168 .register_template_string(
169 "t7",
170 "{{#*inline \"t71\"}}{{a}}{{/inline}}{{> t71 a=\"world\"}}"
171 )
172 .is_ok());
173 assert!(handlebars.register_template_string("t8", "{{a}}").is_ok());
174 assert!(handlebars
175 .register_template_string("t9", "{{> t8 a=2}}")
176 .is_ok());
177
178 assert_eq!(handlebars.render("t0", &1).ok().unwrap(), "1".to_string());
179 assert_eq!(
180 handlebars.render("t2", &1).ok().unwrap(),
181 "not there".to_string()
182 );
183 assert_eq!(handlebars.render("t3", &1).ok().unwrap(), "1".to_string());
184 assert_eq!(
185 handlebars.render("t4", &1).ok().unwrap(),
186 "include navbar".to_string()
187 );
188 assert_eq!(
189 handlebars.render("t6", &json!({"a": "2"})).ok().unwrap(),
190 "2".to_string()
191 );
192 assert_eq!(
193 handlebars.render("t7", &1).ok().unwrap(),
194 "world".to_string()
195 );
196 assert_eq!(handlebars.render("t9", &1).ok().unwrap(), "2".to_string());
197 }
198
199 #[test]
200 fn test_include_partial_block() {
201 let t0 = "hello {{> @partial-block}}";
202 let t1 = "{{#> t0}}inner {{this}}{{/t0}}";
203
204 let mut handlebars = Registry::new();
205 assert!(handlebars.register_template_string("t0", t0).is_ok());
206 assert!(handlebars.register_template_string("t1", t1).is_ok());
207
208 let r0 = handlebars.render("t1", &true);
209 assert_eq!(r0.ok().unwrap(), "hello inner true".to_string());
210 }
211
212 #[test]
213 fn test_self_inclusion() {
214 let t0 = "hello {{> t1}} {{> t0}}";
215 let t1 = "some template";
216 let mut handlebars = Registry::new();
217 assert!(handlebars.register_template_string("t0", t0).is_ok());
218 assert!(handlebars.register_template_string("t1", t1).is_ok());
219
220 let r0 = handlebars.render("t0", &true);
221 assert!(r0.is_err());
222 }
223
224 #[test]
225 fn test_issue_143() {
226 let main_template = "one{{> two }}three{{> two }}";
227 let two_partial = "--- two ---";
228
229 let mut handlebars = Registry::new();
230 assert!(handlebars
231 .register_template_string("template", main_template)
232 .is_ok());
233 assert!(handlebars
234 .register_template_string("two", two_partial)
235 .is_ok());
236
237 let r0 = handlebars.render("template", &true);
238 assert_eq!(r0.ok().unwrap(), "one--- two ---three--- two ---");
239 }
240
241 #[test]
242 fn test_hash_context_outscope() {
243 let main_template = "In: {{> p a=2}} Out: {{a}}";
244 let p_partial = "{{a}}";
245
246 let mut handlebars = Registry::new();
247 assert!(handlebars
248 .register_template_string("template", main_template)
249 .is_ok());
250 assert!(handlebars.register_template_string("p", p_partial).is_ok());
251
252 let r0 = handlebars.render("template", &true);
253 assert_eq!(r0.ok().unwrap(), "In: 2 Out: ");
254 }
255
256 #[test]
257 fn test_partial_context_hash() {
258 let mut hbs = Registry::new();
259 hbs.register_template_string("one", "This is a test. {{> two name=\"fred\" }}")
260 .unwrap();
261 hbs.register_template_string("two", "Lets test {{name}}")
262 .unwrap();
263 assert_eq!(
264 "This is a test. Lets test fred",
265 hbs.render("one", &0).unwrap()
266 );
267 }
268
269 #[test]
270 fn teset_partial_context_with_both_hash_and_param() {
271 let mut hbs = Registry::new();
272 hbs.register_template_string("one", "This is a test. {{> two this name=\"fred\" }}")
273 .unwrap();
274 hbs.register_template_string("two", "Lets test {{name}} and {{root_name}}")
275 .unwrap();
276 assert_eq!(
277 "This is a test. Lets test fred and tom",
278 hbs.render("one", &json!({"root_name": "tom"})).unwrap()
279 );
280 }
281
282 #[test]
283 fn test_partial_subexpression_context_hash() {
284 let mut hbs = Registry::new();
285 hbs.register_template_string("one", "This is a test. {{> (x @root) name=\"fred\" }}")
286 .unwrap();
287 hbs.register_template_string("two", "Lets test {{name}}")
288 .unwrap();
289
290 hbs.register_helper(
291 "x",
292 Box::new(
293 |_: &Helper<'_>,
294 _: &Registry<'_>,
295 _: &Context,
296 _: &mut RenderContext<'_, '_>,
297 out: &mut dyn Output|
298 -> Result<(), RenderError> {
299 out.write("two")?;
300 Ok(())
301 },
302 ),
303 );
304 assert_eq!(
305 "This is a test. Lets test fred",
306 hbs.render("one", &0).unwrap()
307 );
308 }
309
310 #[test]
311 fn test_nested_partial_scope() {
312 let t = "{{#*inline \"pp\"}}{{a}} {{b}}{{/inline}}{{#each c}}{{> pp a=2}}{{/each}}";
313 let data = json!({"c": [{"b": true}, {"b": false}]});
314
315 let mut handlebars = Registry::new();
316 assert!(handlebars.register_template_string("t", t).is_ok());
317 let r0 = handlebars.render("t", &data);
318 assert_eq!(r0.ok().unwrap(), "2 true2 false");
319 }
320
321 #[test]
322 fn test_nested_partial_block() {
323 let mut handlebars = Registry::new();
324 let template1 = "<outer>{{> @partial-block }}</outer>";
325 let template2 = "{{#> t1 }}<inner>{{> @partial-block }}</inner>{{/ t1 }}";
326 let template3 = "{{#> t2 }}Hello{{/ t2 }}";
327
328 handlebars
329 .register_template_string("t1", template1)
330 .unwrap();
331 handlebars
332 .register_template_string("t2", template2)
333 .unwrap();
334
335 let page = handlebars.render_template(template3, &json!({})).unwrap();
336 assert_eq!("<outer><inner>Hello</inner></outer>", page);
337 }
338
339 #[test]
340 fn test_up_to_partial_level() {
341 let outer = r#"{{>inner name="fruit:" vegetables=fruits}}"#;
342 let inner = "{{#each vegetables}}{{../name}} {{this}},{{/each}}";
343
344 let data = json!({ "fruits": ["carrot", "tomato"] });
345
346 let mut handlebars = Registry::new();
347 handlebars.register_template_string("outer", outer).unwrap();
348 handlebars.register_template_string("inner", inner).unwrap();
349
350 assert_eq!(
351 handlebars.render("outer", &data).unwrap(),
352 "fruit: carrot,fruit: tomato,"
353 );
354 }
355
356 #[test]
357 fn line_stripping_with_inline_and_partial() {
358 let tpl0 = r#"{{#*inline "foo"}}foo
359{{/inline}}
360{{> foo}}
361{{> foo}}
362{{> foo}}"#;
363 let tpl1 = r#"{{#*inline "foo"}}foo{{/inline}}
364{{> foo}}
365{{> foo}}
366{{> foo}}"#;
367
368 let hbs = Registry::new();
369 assert_eq!(
370 r"foo
371foo
372foo
373",
374 hbs.render_template(tpl0, &json!({})).unwrap()
375 );
376 assert_eq!(
377 r"
378foofoofoo",
379 hbs.render_template(tpl1, &json!({})).unwrap()
380 );
381 }
382
383 #[test]
384 fn test_partial_indent() {
385 let outer = r" {{> inner inner_solo}}
386
387{{#each inners}}
388 {{> inner}}
389{{/each}}
390
391 {{#each inners}}
392 {{> inner}}
393 {{/each}}
394";
395 let inner = r"name: {{name}}
396";
397
398 let mut hbs = Registry::new();
399
400 hbs.register_template_string("inner", inner).unwrap();
401 hbs.register_template_string("outer", outer).unwrap();
402
403 let result = hbs
404 .render(
405 "outer",
406 &json!({
407 "inner_solo": {"name": "inner_solo"},
408 "inners": [
409 {"name": "hello"},
410 {"name": "there"}
411 ]
412 }),
413 )
414 .unwrap();
415
416 assert_eq!(
417 result,
418 r" name: inner_solo
419
420 name: hello
421 name: there
422
423 name: hello
424 name: there
425"
426 );
427 }
428 #[test]
431 fn test_partial_prevent_indent() {
432 let outer = r" {{> inner inner_solo}}
433
434{{#each inners}}
435 {{> inner}}
436{{/each}}
437
438 {{#each inners}}
439 {{> inner}}
440 {{/each}}
441";
442 let inner = r"name: {{name}}
443";
444
445 let mut hbs = Registry::new();
446 hbs.set_prevent_indent(true);
447
448 hbs.register_template_string("inner", inner).unwrap();
449 hbs.register_template_string("outer", outer).unwrap();
450
451 let result = hbs
452 .render(
453 "outer",
454 &json!({
455 "inner_solo": {"name": "inner_solo"},
456 "inners": [
457 {"name": "hello"},
458 {"name": "there"}
459 ]
460 }),
461 )
462 .unwrap();
463
464 assert_eq!(
465 result,
466 r" name: inner_solo
467
468 name: hello
469 name: there
470
471 name: hello
472 name: there
473"
474 );
475 }
476
477 #[test]
478 fn test_nested_partials() {
479 let mut hb = Registry::new();
480 hb.register_template_string("partial", "{{> @partial-block}}")
481 .unwrap();
482 hb.register_template_string(
483 "index",
484 r"{{#>partial}}
485 Yo
486 {{#>partial}}
487 Yo 2
488 {{/partial}}
489{{/partial}}",
490 )
491 .unwrap();
492 assert_eq!(
493 r" Yo
494 Yo 2
495",
496 hb.render("index", &()).unwrap()
497 );
498
499 hb.register_template_string("partial2", "{{> @partial-block}}")
500 .unwrap();
501 let r2 = hb
502 .render_template(
503 r"{{#> partial}}
504{{#> partial2}}
505:(
506{{/partial2}}
507{{/partial}}",
508 &(),
509 )
510 .unwrap();
511 assert_eq!(":(\n", r2);
512 }
513
514 #[test]
515 fn test_partial_context_issue_495() {
516 let mut hb = Registry::new();
517 hb.register_template_string(
518 "t1",
519 r#"{{~#*inline "displayName"~}}
520Template:{{name}}
521{{/inline}}
522{{#each data as |name|}}
523Name:{{name}}
524{{>displayName name="aaaa"}}
525{{/each}}"#,
526 )
527 .unwrap();
528
529 hb.register_template_string(
530 "t2",
531 r#"{{~#*inline "displayName"~}}
532Template:{{this}}
533{{/inline}}
534{{#each data as |name|}}
535Name:{{name}}
536{{>displayName}}
537{{/each}}"#,
538 )
539 .unwrap();
540
541 let data = json!({
542 "data": ["hudel", "test"]
543 });
544
545 assert_eq!(
546 r"Name:hudel
547Template:aaaa
548Name:test
549Template:aaaa
550",
551 hb.render("t1", &data).unwrap()
552 );
553 assert_eq!(
554 r"Name:hudel
555Template:hudel
556Name:test
557Template:test
558",
559 hb.render("t2", &data).unwrap()
560 );
561 }
562
563 #[test]
564 fn test_multiline_partial_indent() {
565 let mut hb = Registry::new();
566
567 hb.register_template_string(
568 "t1",
569 r#"{{#*inline "thepartial"}}
570 inner first line
571 inner second line
572{{/inline}}
573 {{> thepartial}}
574outer third line"#,
575 )
576 .unwrap();
577 assert_eq!(
578 r" inner first line
579 inner second line
580outer third line",
581 hb.render("t1", &()).unwrap()
582 );
583
584 hb.register_template_string(
585 "t2",
586 r#"{{#*inline "thepartial"}}inner first line
587inner second line
588{{/inline}}
589 {{> thepartial}}
590outer third line"#,
591 )
592 .unwrap();
593 assert_eq!(
594 r" inner first line
595 inner second line
596outer third line",
597 hb.render("t2", &()).unwrap()
598 );
599
600 hb.register_template_string(
601 "t3",
602 r#"{{#*inline "thepartial"}}{{a}}{{/inline}}
603 {{> thepartial}}
604outer third line"#,
605 )
606 .unwrap();
607 assert_eq!(
608 r"
609 inner first line
610 inner second lineouter third line",
611 hb.render("t3", &json!({"a": "inner first line\ninner second line"}))
612 .unwrap()
613 );
614
615 hb.register_template_string(
616 "t4",
617 r#"{{#*inline "thepartial"}}
618 inner first line
619 inner second line
620{{/inline}}
621 {{~> thepartial}}
622outer third line"#,
623 )
624 .unwrap();
625 assert_eq!(
626 r" inner first line
627 inner second line
628outer third line",
629 hb.render("t4", &()).unwrap()
630 );
631
632 let mut hb2 = Registry::new();
633 hb2.set_prevent_indent(true);
634
635 hb2.register_template_string(
636 "t1",
637 r#"{{#*inline "thepartial"}}
638 inner first line
639 inner second line
640{{/inline}}
641 {{> thepartial}}
642outer third line"#,
643 )
644 .unwrap();
645 assert_eq!(
646 r" inner first line
647 inner second line
648outer third line",
649 hb2.render("t1", &()).unwrap()
650 );
651 }
652
653 #[test]
654 fn test_indent_level_on_nested_partials() {
655 let nested_partial = "
656<div>
657 content
658</div>
659";
660 let partial = "
661<div>
662 {{>nested_partial}}
663</div>
664";
665
666 let partial_indented = "
667<div>
668 {{>partial}}
669</div>
670";
671
672 let result = "
673<div>
674 <div>
675 <div>
676 content
677 </div>
678 </div>
679</div>
680";
681
682 let mut hb = Registry::new();
683 hb.register_template_string("nested_partial", nested_partial.trim_start())
684 .unwrap();
685 hb.register_template_string("partial", partial.trim_start())
686 .unwrap();
687 hb.register_template_string("partial_indented", partial_indented.trim_start())
688 .unwrap();
689
690 let s = hb.render("partial_indented", &()).unwrap();
691
692 assert_eq!(&s, result.trim_start());
693 }
694
695 #[test]
696 fn test_issue_534() {
697 let t1 = "{{title}}";
698 let t2 = "{{#each modules}}{{> (lookup this \"module\") content name=0}}{{/each}}";
699
700 let data = json!({
701 "modules": [
702 {"module": "t1", "content": {"title": "foo"}},
703 {"module": "t1", "content": {"title": "bar"}},
704 ]
705 });
706
707 let mut hbs = Registry::new();
708 hbs.register_template_string("t1", t1).unwrap();
709 hbs.register_template_string("t2", t2).unwrap();
710
711 assert_eq!("foobar", hbs.render("t2", &data).unwrap());
712 }
713
714 #[test]
715 fn test_partial_not_found() {
716 let t1 = "{{> bar}}";
717 let hbs = Registry::new();
718 assert!(hbs.render_template(t1, &()).is_err());
719 }
720
721 #[test]
722 fn test_issue_643_this_context() {
723 let t1 = "{{this}}";
724 let t2 = "{{> t1 \"hello world\"}}";
725
726 let mut hbs = Registry::new();
727 hbs.register_template_string("t1", t1).unwrap();
728 hbs.register_template_string("t2", t2).unwrap();
729
730 assert_eq!("hello world", hbs.render("t2", &()).unwrap());
731
732 let t1 = "{{a}} {{[0]}} {{[1]}}";
733 let t2 = "{{> t1 \"hello world\" a=1}}";
734
735 let mut hbs = Registry::new();
736 hbs.register_template_string("t1", t1).unwrap();
737 hbs.register_template_string("t2", t2).unwrap();
738
739 assert_eq!("1 h e", hbs.render("t2", &()).unwrap());
740
741 let t1 = "{{#each this}}{{@key}}:{{this}},{{/each}}";
742 let t2 = "{{> t1 a=1}}";
743
744 let mut hbs = Registry::new();
745 hbs.register_template_string("t1", t1).unwrap();
746 hbs.register_template_string("t2", t2).unwrap();
747
748 assert_eq!("a:1,", hbs.render("t2", &()).unwrap());
749
750 let t1 = "{{#each this}}{{@key}}:{{this}},{{/each}}";
751 let t2 = "{{> t1 a=1}}";
752
753 let mut hbs = Registry::new();
754 hbs.register_template_string("t1", t1).unwrap();
755 hbs.register_template_string("t2", t2).unwrap();
756
757 assert_eq!("a:1,b:2,", hbs.render("t2", &json!({"b": 2})).unwrap());
758
759 let t1 = "{{#each this}}{{@key}}:{{this}},{{/each}}";
760 let t2 = "{{> t1 b a=1}}";
761
762 let mut hbs = Registry::new();
763 hbs.register_template_string("t1", t1).unwrap();
764 hbs.register_template_string("t2", t2).unwrap();
765
766 assert_eq!("a:1,", hbs.render("t2", &json!({"b": 2})).unwrap());
767 }
768}