1 | use std::borrow::Cow; |
2 | use std::collections::HashMap; |
3 | |
4 | use serde_json::value::Value as Json; |
5 | |
6 | use crate::block::BlockContext; |
7 | use crate::context::{merge_json, Context}; |
8 | use crate::error::RenderError; |
9 | use crate::json::path::Path; |
10 | use crate::output::Output; |
11 | use crate::registry::Registry; |
12 | use crate::render::{Decorator, Evaluable, RenderContext, Renderable}; |
13 | use crate::template::Template; |
14 | |
15 | pub(crate) const PARTIAL_BLOCK: &str = "@partial-block" ; |
16 | |
17 | fn 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 | |
38 | pub 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)] |
155 | mod 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 |
393 | foo |
394 | foo |
395 | "# , |
396 | hbs.render_template(tpl0, &json!({})).unwrap() |
397 | ); |
398 | assert_eq!( |
399 | r#" |
400 | foofoofoo"# , |
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"~}} |
542 | Template:{{name}} |
543 | {{/inline}} |
544 | {{#each data as |name|}} |
545 | Name:{{name}} |
546 | {{>displayName name="aaaa"}} |
547 | {{/each}}"# , |
548 | ) |
549 | .unwrap(); |
550 | |
551 | hb.register_template_string( |
552 | "t1" , |
553 | r#"{{~#*inline "displayName"~}} |
554 | Template:{{this}} |
555 | {{/inline}} |
556 | {{#each data as |name|}} |
557 | Name:{{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 |
569 | Template:hudel |
570 | Name:test |
571 | Template: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}} |
588 | outer third line"# , |
589 | ) |
590 | .unwrap(); |
591 | assert_eq!( |
592 | r#" inner first line |
593 | inner second line |
594 | outer third line"# , |
595 | hb.render("t1" , &()).unwrap() |
596 | ); |
597 | |
598 | hb.register_template_string( |
599 | "t2" , |
600 | r#"{{#*inline "thepartial"}}inner first line |
601 | inner second line |
602 | {{/inline}} |
603 | {{> thepartial}} |
604 | outer third line"# , |
605 | ) |
606 | .unwrap(); |
607 | assert_eq!( |
608 | r#" inner first line |
609 | inner second line |
610 | outer third line"# , |
611 | hb.render("t2" , &()).unwrap() |
612 | ); |
613 | |
614 | hb.register_template_string( |
615 | "t3" , |
616 | r#"{{#*inline "thepartial"}}{{a}}{{/inline}} |
617 | {{> thepartial}} |
618 | outer 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}} |
636 | outer third line"# , |
637 | ) |
638 | .unwrap(); |
639 | assert_eq!( |
640 | r#" inner first line |
641 | inner second line |
642 | outer 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}} |
656 | outer third line"# , |
657 | ) |
658 | .unwrap(); |
659 | assert_eq!( |
660 | r#" inner first line |
661 | inner second line |
662 | outer third line"# , |
663 | hb2.render("t1" , &()).unwrap() |
664 | ) |
665 | } |
666 | } |
667 | |
668 | #[test ] |
669 | fn 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 | |