1 | use serde_json::value::Value as Json; |
2 | |
3 | use super::block_util::create_block; |
4 | use crate::block::{BlockContext, BlockParams}; |
5 | use crate::context::Context; |
6 | use crate::error::RenderError; |
7 | use crate::helpers::{HelperDef, HelperResult}; |
8 | use crate::json::value::to_json; |
9 | use crate::output::Output; |
10 | use crate::registry::Registry; |
11 | use crate::render::{Helper, RenderContext, Renderable}; |
12 | use crate::util::copy_on_push_vec; |
13 | |
14 | fn 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 | |
32 | fn 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)] |
64 | pub struct EachHelper; |
65 | |
66 | impl 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 | |
158 | pub static EACH_HELPER: EachHelper = EachHelper; |
159 | |
160 | #[cfg (test)] |
161 | mod 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 | |