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