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