1use serde_json::value::Value as Json;
2
3use super::block_util::create_block;
4use crate::block::{BlockContext, BlockParams};
5use crate::context::Context;
6use crate::error::RenderError;
7use crate::helpers::{HelperDef, HelperResult};
8use crate::json::value::to_json;
9use crate::output::Output;
10use crate::registry::Registry;
11use crate::render::{Helper, RenderContext, Renderable};
12use crate::util::copy_on_push_vec;
13use crate::RenderErrorReason;
14
15fn update_block_context(
16 block: &mut BlockContext<'_>,
17 base_path: Option<&Vec<String>>,
18 relative_path: String,
19 is_first: bool,
20 value: &Json,
21) {
22 if let Some(p) = base_path {
23 if is_first {
24 *block.base_path_mut() = copy_on_push_vec(p, relative_path);
25 } else if let Some(ptr) = block.base_path_mut().last_mut() {
26 *ptr = relative_path;
27 }
28 } else {
29 block.set_base_value(value.clone());
30 }
31}
32
33fn set_block_param<'rc>(
34 block: &mut BlockContext<'rc>,
35 h: &Helper<'rc>,
36 base_path: Option<&Vec<String>>,
37 k: &Json,
38 v: &Json,
39) -> Result<(), RenderError> {
40 if let Some(bp_val) = h.block_param() {
41 let mut params = BlockParams::new();
42 if base_path.is_some() {
43 params.add_path(bp_val, Vec::with_capacity(0))?;
44 } else {
45 params.add_value(bp_val, v.clone())?;
46 }
47
48 block.set_block_params(params);
49 } else if let Some((bp_val, bp_key)) = h.block_param_pair() {
50 let mut params = BlockParams::new();
51 if base_path.is_some() {
52 params.add_path(bp_val, Vec::with_capacity(0))?;
53 } else {
54 params.add_value(bp_val, v.clone())?;
55 }
56 params.add_value(bp_key, k.clone())?;
57
58 block.set_block_params(params);
59 }
60
61 Ok(())
62}
63
64#[derive(Clone, Copy)]
65pub struct EachHelper;
66
67impl HelperDef for EachHelper {
68 fn call<'reg: 'rc, 'rc>(
69 &self,
70 h: &Helper<'rc>,
71 r: &'reg Registry<'reg>,
72 ctx: &'rc Context,
73 rc: &mut RenderContext<'reg, 'rc>,
74 out: &mut dyn Output,
75 ) -> HelperResult {
76 let value = h
77 .param(0)
78 .ok_or(RenderErrorReason::ParamNotFoundForIndex("each", 0))?;
79
80 let template = h.template();
81
82 match template {
83 Some(t) => match *value.value() {
84 Json::Array(ref list)
85 if !list.is_empty() || (list.is_empty() && h.inverse().is_none()) =>
86 {
87 let block_context = create_block(value);
88 rc.push_block(block_context);
89
90 let len = list.len();
91
92 let array_path = value.context_path();
93
94 for (i, v) in list.iter().enumerate().take(len) {
95 if let Some(ref mut block) = rc.block_mut() {
96 let is_first = i == 0usize;
97 let is_last = i == len - 1;
98
99 let index = to_json(i);
100 block.set_local_var("first", to_json(is_first));
101 block.set_local_var("last", to_json(is_last));
102 block.set_local_var("index", index.clone());
103
104 update_block_context(block, array_path, i.to_string(), is_first, v);
105 set_block_param(block, h, array_path, &index, v)?;
106 }
107
108 t.render(r, ctx, rc, out)?;
109 }
110
111 rc.pop_block();
112 Ok(())
113 }
114 Json::Object(ref obj)
115 if !obj.is_empty() || (obj.is_empty() && h.inverse().is_none()) =>
116 {
117 let block_context = create_block(value);
118 rc.push_block(block_context);
119
120 let len = obj.len();
121
122 let obj_path = value.context_path();
123
124 for (i, (k, v)) in obj.iter().enumerate() {
125 if let Some(ref mut block) = rc.block_mut() {
126 let is_first = i == 0usize;
127 let is_last = i == len - 1;
128
129 let key = to_json(k);
130 block.set_local_var("first", to_json(is_first));
131 block.set_local_var("last", to_json(is_last));
132 block.set_local_var("key", key.clone());
133 block.set_local_var("index", to_json(i));
134
135 update_block_context(block, obj_path, k.to_string(), is_first, v);
136 set_block_param(block, h, obj_path, &key, v)?;
137 }
138
139 t.render(r, ctx, rc, out)?;
140 }
141
142 rc.pop_block();
143 Ok(())
144 }
145 _ => {
146 if let Some(else_template) = h.inverse() {
147 else_template.render(r, ctx, rc, out)
148 } else if r.strict_mode() {
149 Err(RenderError::strict_error(value.relative_path()))
150 } else {
151 Ok(())
152 }
153 }
154 },
155 None => Ok(()),
156 }
157 }
158}
159
160pub static EACH_HELPER: EachHelper = EachHelper;
161
162#[cfg(test)]
163mod test {
164 use crate::registry::Registry;
165 use serde_json::value::Value as Json;
166 use std::collections::BTreeMap;
167 use std::str::FromStr;
168
169 #[test]
170 fn test_empty_each() {
171 let mut hbs = Registry::new();
172 hbs.set_strict_mode(true);
173
174 let data = json!({
175 "a": [ ],
176 });
177
178 let template = "{{#each a}}each{{/each}}";
179 assert_eq!(hbs.render_template(template, &data).unwrap(), "");
180 }
181
182 #[test]
183 fn test_each() {
184 let mut handlebars = Registry::new();
185 assert!(handlebars
186 .register_template_string(
187 "t0",
188 "{{#each this}}{{@first}}|{{@last}}|{{@index}}:{{this}}|{{/each}}",
189 )
190 .is_ok());
191 assert!(handlebars
192 .register_template_string(
193 "t1",
194 "{{#each this}}{{@first}}|{{@last}}|{{@key}}:{{this}}|{{@index}}|{{/each}}",
195 )
196 .is_ok());
197
198 let r0 = handlebars.render("t0", &vec![1u16, 2u16, 3u16]);
199 assert_eq!(
200 r0.ok().unwrap(),
201 "true|false|0:1|false|false|1:2|false|true|2:3|".to_string()
202 );
203
204 let mut m: BTreeMap<String, u16> = BTreeMap::new();
205 m.insert("ftp".to_string(), 21);
206 m.insert("gopher".to_string(), 70);
207 m.insert("http".to_string(), 80);
208 let r1 = handlebars.render("t1", &m);
209 assert_eq!(
210 r1.ok().unwrap(),
211 "true|false|ftp:21|0|false|false|gopher:70|1|false|true|http:80|2|".to_string()
212 );
213 }
214
215 #[test]
216 fn test_each_with_parent() {
217 let json_str = r#"{"a":{"a":99,"c":[{"d":100},{"d":200}]}}"#;
218
219 let data = Json::from_str(json_str).unwrap();
220 let mut handlebars = Registry::new();
222
223 assert!(handlebars
227 .register_template_string("t0", "{{#each a.c}} d={{d}} b={{../a.a}} {{/each}}")
228 .is_ok());
229
230 let r1 = handlebars.render("t0", &data);
231 assert_eq!(r1.ok().unwrap(), " d=100 b=99 d=200 b=99 ".to_string());
232 }
233
234 #[test]
235 fn test_nested_each_with_parent() {
236 let json_str = r#"{"a": [{"b": [{"d": 100}], "c": 200}]}"#;
237
238 let data = Json::from_str(json_str).unwrap();
239 let mut handlebars = Registry::new();
240 assert!(handlebars
241 .register_template_string(
242 "t0",
243 "{{#each a}}{{#each b}}{{d}}:{{../c}}{{/each}}{{/each}}",
244 )
245 .is_ok());
246
247 let r1 = handlebars.render("t0", &data);
248 assert_eq!(r1.ok().unwrap(), "100:200".to_string());
249 }
250
251 #[test]
252 fn test_nested_each() {
253 let json_str = r#"{"a": [{"b": true}], "b": [[1, 2, 3],[4, 5]]}"#;
254
255 let data = Json::from_str(json_str).unwrap();
256
257 let mut handlebars = Registry::new();
258 assert!(handlebars
259 .register_template_string(
260 "t0",
261 "{{#each b}}{{#if ../a}}{{#each this}}{{this}}{{/each}}{{/if}}{{/each}}",
262 )
263 .is_ok());
264
265 let r1 = handlebars.render("t0", &data);
266 assert_eq!(r1.ok().unwrap(), "12345".to_string());
267 }
268
269 #[test]
270 fn test_nested_array() {
271 let mut handlebars = Registry::new();
272 assert!(handlebars
273 .register_template_string("t0", "{{#each this.[0]}}{{this}}{{/each}}")
274 .is_ok());
275
276 let r0 = handlebars.render("t0", &(vec![vec![1, 2, 3]]));
277
278 assert_eq!(r0.ok().unwrap(), "123".to_string());
279 }
280
281 #[test]
282 fn test_empty_key() {
283 let mut handlebars = Registry::new();
284 assert!(handlebars
285 .register_template_string("t0", "{{#each this}}{{@key}}-{{value}}\n{{/each}}")
286 .is_ok());
287
288 let r0 = handlebars
289 .render(
290 "t0",
291 &json!({
292 "foo": {
293 "value": "bar"
294 },
295 "": {
296 "value": "baz"
297 }
298 }),
299 )
300 .unwrap();
301
302 let mut r0_sp: Vec<_> = r0.split('\n').collect();
303 r0_sp.sort_unstable();
304
305 assert_eq!(r0_sp, vec!["", "-baz", "foo-bar"]);
306 }
307
308 #[test]
309 fn test_each_else() {
310 let mut handlebars = Registry::new();
311 assert!(handlebars
312 .register_template_string("t0", "{{#each a}}1{{else}}empty{{/each}}")
313 .is_ok());
314 let m1 = json!({
315 "a": []
316 });
317 let r0 = handlebars.render("t0", &m1).unwrap();
318 assert_eq!(r0, "empty");
319
320 let m2 = json!({
321 "b": []
322 });
323 let r1 = handlebars.render("t0", &m2).unwrap();
324 assert_eq!(r1, "empty");
325 }
326
327 #[test]
328 fn test_block_param() {
329 let mut handlebars = Registry::new();
330 assert!(handlebars
331 .register_template_string("t0", "{{#each a as |i|}}{{i}}{{/each}}")
332 .is_ok());
333 let m1 = json!({
334 "a": [1,2,3,4,5]
335 });
336 let r0 = handlebars.render("t0", &m1).unwrap();
337 assert_eq!(r0, "12345");
338 }
339
340 #[test]
341 fn test_each_object_block_param() {
342 let mut handlebars = Registry::new();
343 let template = "{{#each this as |v k|}}\
344 {{#with k as |inner_k|}}{{inner_k}}{{/with}}:{{v}}|\
345 {{/each}}";
346 assert!(handlebars.register_template_string("t0", template).is_ok());
347
348 let m = json!({
349 "ftp": 21,
350 "http": 80
351 });
352 let r0 = handlebars.render("t0", &m);
353 assert_eq!(r0.ok().unwrap(), "ftp:21|http:80|".to_string());
354 }
355
356 #[test]
357 fn test_each_object_block_param2() {
358 let mut handlebars = Registry::new();
359 let template = "{{#each this as |v k|}}\
360 {{#with v as |inner_v|}}{{k}}:{{inner_v}}{{/with}}|\
361 {{/each}}";
362
363 assert!(handlebars.register_template_string("t0", template).is_ok());
364
365 let m = json!({
366 "ftp": 21,
367 "http": 80
368 });
369 let r0 = handlebars.render("t0", &m);
370 assert_eq!(r0.ok().unwrap(), "ftp:21|http:80|".to_string());
371 }
372
373 fn test_nested_each_with_path_ups() {
374 let mut handlebars = Registry::new();
375 assert!(handlebars
376 .register_template_string(
377 "t0",
378 "{{#each a.b}}{{#each c}}{{../../d}}{{/each}}{{/each}}",
379 )
380 .is_ok());
381
382 let data = json!({
383 "a": {
384 "b": [{"c": [1]}]
385 },
386 "d": 1
387 });
388
389 let r0 = handlebars.render("t0", &data);
390 assert_eq!(r0.ok().unwrap(), "1".to_string());
391 }
392
393 #[test]
394 fn test_nested_each_with_path_up_this() {
395 let mut handlebars = Registry::new();
396 let template = "{{#each variant}}{{#each ../typearg}}\
397 {{#if @first}}template<{{/if}}{{this}}{{#if @last}}>{{else}},{{/if}}\
398 {{/each}}{{/each}}";
399 assert!(handlebars.register_template_string("t0", template).is_ok());
400 let data = json!({
401 "typearg": ["T"],
402 "variant": ["1", "2"]
403 });
404 let r0 = handlebars.render("t0", &data);
405 assert_eq!(r0.ok().unwrap(), "template<T>template<T>".to_string());
406 }
407
408 #[test]
409 fn test_key_iteration_with_unicode() {
410 let mut handlebars = Registry::new();
411 assert!(handlebars
412 .register_template_string("t0", "{{#each this}}{{@key}}: {{this}}\n{{/each}}")
413 .is_ok());
414 let data = json!({
415 "normal": 1,
416 "你好": 2,
417 "#special key": 3,
418 "😂": 4,
419 "me.dot.key": 5
420 });
421 let r0 = handlebars.render("t0", &data).ok().unwrap();
422 assert!(r0.contains("normal: 1"));
423 assert!(r0.contains("你好: 2"));
424 assert!(r0.contains("#special key: 3"));
425 assert!(r0.contains("😂: 4"));
426 assert!(r0.contains("me.dot.key: 5"));
427 }
428
429 #[test]
430 fn test_base_path_after_each() {
431 let mut handlebars = Registry::new();
432 assert!(handlebars
433 .register_template_string("t0", "{{#each a}}{{this}}{{/each}} {{b}}")
434 .is_ok());
435 let data = json!({
436 "a": [1, 2, 3, 4],
437 "b": "good",
438 });
439
440 let r0 = handlebars.render("t0", &data).ok().unwrap();
441
442 assert_eq!("1234 good", r0);
443 }
444
445 #[test]
446 fn test_else_context() {
447 let reg = Registry::new();
448 let template = "{{#each list}}A{{else}}{{foo}}{{/each}}";
449 let input = json!({"list": [], "foo": "bar"});
450 let rendered = reg.render_template(template, &input).unwrap();
451 assert_eq!("bar", rendered);
452 }
453
454 #[test]
455 fn test_block_context_leak() {
456 let reg = Registry::new();
457 let template = "{{#each list}}{{#each inner}}{{this}}{{/each}}{{foo}}{{/each}}";
458 let input = json!({"list": [{"inner": [], "foo": 1}, {"inner": [], "foo": 2}]});
459 let rendered = reg.render_template(template, &input).unwrap();
460 assert_eq!("12", rendered);
461 }
462
463 #[test]
464 fn test_derived_array_as_block_params() {
465 handlebars_helper!(range: |x: u64| (0..x).collect::<Vec<u64>>());
466 let mut reg = Registry::new();
467 reg.register_helper("range", Box::new(range));
468 let template = "{{#each (range 3) as |i|}}{{i}}{{/each}}";
469 let input = json!(0);
470 let rendered = reg.render_template(template, &input).unwrap();
471 assert_eq!("012", rendered);
472 }
473
474 #[test]
475 fn test_derived_object_as_block_params() {
476 handlebars_helper!(point: |x: u64, y: u64| json!({"x":x, "y":y}));
477 let mut reg = Registry::new();
478 reg.register_helper("point", Box::new(point));
479 let template = "{{#each (point 0 1) as |i|}}{{i}}{{/each}}";
480 let input = json!(0);
481 let rendered = reg.render_template(template, &input).unwrap();
482 assert_eq!("01", rendered);
483 }
484
485 #[test]
486 fn test_derived_array_without_block_param() {
487 handlebars_helper!(range: |x: u64| (0..x).collect::<Vec<u64>>());
488 let mut reg = Registry::new();
489 reg.register_helper("range", Box::new(range));
490 let template = "{{#each (range 3)}}{{this}}{{/each}}";
491 let input = json!(0);
492 let rendered = reg.render_template(template, &input).unwrap();
493 assert_eq!("012", rendered);
494 }
495
496 #[test]
497 fn test_derived_object_without_block_params() {
498 handlebars_helper!(point: |x: u64, y: u64| json!({"x":x, "y":y}));
499 let mut reg = Registry::new();
500 reg.register_helper("point", Box::new(point));
501 let template = "{{#each (point 0 1)}}{{this}}{{/each}}";
502 let input = json!(0);
503 let rendered = reg.render_template(template, &input).unwrap();
504 assert_eq!("01", rendered);
505 }
506
507 #[test]
508 fn test_non_iterable() {
509 let reg = Registry::new();
510 let template = "{{#each this}}each block{{else}}else block{{/each}}";
511 let input = json!("strings aren't iterable");
512 let rendered = reg.render_template(template, &input).unwrap();
513 assert_eq!("else block", rendered);
514 }
515
516 #[test]
517 fn test_render_array_without_trailig_commas() {
518 let reg = Registry::new();
519 let template = "Array: {{array}}";
520 let input = json!({"array": [1, 2, 3]});
521 let rendered = reg.render_template(template, &input);
522
523 assert_eq!("Array: [1, 2, 3]", rendered.unwrap());
524 }
525
526 #[test]
527 fn test_recursion() {
528 let mut reg = Registry::new();
529 assert!(reg
530 .register_template_string(
531 "walk",
532 "(\
533 {{#each this}}\
534 {{#if @key}}{{@key}}{{else}}{{@index}}{{/if}}: \
535 {{this}} \
536 {{> walk this}}, \
537 {{/each}}\
538 )",
539 )
540 .is_ok());
541
542 let input = json!({
543 "array": [42, {"wow": "cool"}, [[]]],
544 "object": { "a": { "b": "c", "d": ["e"] } },
545 "string": "hi"
546 });
547 let expected_output = "(\
548 array: [42, [object], [[]]] (\
549 0: 42 (), \
550 1: [object] (wow: cool (), ), \
551 2: [[]] (0: [] (), ), \
552 ), \
553 object: [object] (\
554 a: [object] (\
555 b: c (), \
556 d: [e] (0: e (), ), \
557 ), \
558 ), \
559 string: hi (), \
560 )";
561
562 let rendered = reg.render("walk", &input).unwrap();
563 assert_eq!(expected_output, rendered);
564 }
565
566 #[test]
567 fn test_strict_each() {
568 let mut reg = Registry::new();
569
570 assert!(reg
571 .render_template("{{#each data}}{{/each}}", &json!({}))
572 .is_ok());
573 assert!(reg
574 .render_template("{{#each data}}{{/each}}", &json!({"data": 24}))
575 .is_ok());
576
577 reg.set_strict_mode(true);
578
579 assert!(reg
580 .render_template("{{#each data}}{{/each}}", &json!({}))
581 .is_err());
582 assert!(reg
583 .render_template("{{#each data}}{{/each}}", &json!({"data": 24}))
584 .is_err());
585 assert!(reg
586 .render_template("{{#each data}}{{else}}food{{/each}}", &json!({}))
587 .is_ok());
588 assert!(reg
589 .render_template("{{#each data}}{{else}}food{{/each}}", &json!({"data": 24}))
590 .is_ok());
591 }
592
593 #[test]
594 fn newline_stripping_for_each() {
595 let reg = Registry::new();
596
597 let tpl = r"<ul>
598 {{#each a}}
599 {{!-- comment --}}
600 <li>{{this}}</li>
601 {{/each}}
602</ul>";
603 assert_eq!(
604 r"<ul>
605 <li>0</li>
606 <li>1</li>
607</ul>",
608 reg.render_template(tpl, &json!({"a": [0, 1]})).unwrap()
609 );
610 }
611}