blob: 48d7b15e232628f752f1d566a3ccafee7b3169e5 [file] [log] [blame]
use serde_json::value::Value as Json;
use super::block_util::create_block;
use crate::block::{BlockContext, BlockParams};
use crate::context::Context;
use crate::error::RenderError;
use crate::helpers::{HelperDef, HelperResult};
use crate::json::value::to_json;
use crate::output::Output;
use crate::registry::Registry;
use crate::render::{Helper, RenderContext, Renderable};
use crate::util::copy_on_push_vec;
fn update_block_context<'reg>(
block: &mut BlockContext<'reg>,
base_path: Option<&Vec<String>>,
relative_path: String,
is_first: bool,
value: &Json,
) {
if let Some(ref p) = base_path {
if is_first {
*block.base_path_mut() = copy_on_push_vec(p, relative_path);
} else if let Some(ptr) = block.base_path_mut().last_mut() {
*ptr = relative_path;
}
} else {
block.set_base_value(value.clone());
}
}
fn set_block_param<'reg: 'rc, 'rc>(
block: &mut BlockContext<'reg>,
h: &Helper<'reg, 'rc>,
base_path: Option<&Vec<String>>,
k: &Json,
v: &Json,
) -> Result<(), RenderError> {
if let Some(bp_val) = h.block_param() {
let mut params = BlockParams::new();
if base_path.is_some() {
params.add_path(bp_val, Vec::with_capacity(0))?;
} else {
params.add_value(bp_val, v.clone())?;
}
block.set_block_params(params);
} else if let Some((bp_val, bp_key)) = h.block_param_pair() {
let mut params = BlockParams::new();
if base_path.is_some() {
params.add_path(bp_val, Vec::with_capacity(0))?;
} else {
params.add_value(bp_val, v.clone())?;
}
params.add_value(bp_key, k.clone())?;
block.set_block_params(params);
}
Ok(())
}
#[derive(Clone, Copy)]
pub struct EachHelper;
impl HelperDef for EachHelper {
fn call<'reg: 'rc, 'rc>(
&self,
h: &Helper<'reg, 'rc>,
r: &'reg Registry<'reg>,
ctx: &'rc Context,
rc: &mut RenderContext<'reg, 'rc>,
out: &mut dyn Output,
) -> HelperResult {
let value = h
.param(0)
.ok_or_else(|| RenderError::new("Param not found for helper \"each\""))?;
let template = h.template();
match template {
Some(t) => match *value.value() {
Json::Array(ref list) if !list.is_empty() => {
let block_context = create_block(&value)?;
rc.push_block(block_context);
let len = list.len();
let array_path = value.context_path();
for (i, v) in list.iter().enumerate().take(len) {
if let Some(ref mut block) = rc.block_mut() {
let is_first = i == 0usize;
let is_last = i == len - 1;
let index = to_json(i);
block.set_local_var("@first".to_string(), to_json(is_first));
block.set_local_var("@last".to_string(), to_json(is_last));
block.set_local_var("@index".to_string(), index.clone());
update_block_context(block, array_path, i.to_string(), is_first, &v);
set_block_param(block, h, array_path, &index, &v)?;
}
t.render(r, ctx, rc, out)?;
}
rc.pop_block();
Ok(())
}
Json::Object(ref obj) if !obj.is_empty() => {
let block_context = create_block(&value)?;
rc.push_block(block_context);
let mut is_first = true;
let obj_path = value.context_path();
for (k, v) in obj.iter() {
if let Some(ref mut block) = rc.block_mut() {
let key = to_json(k);
block.set_local_var("@first".to_string(), to_json(is_first));
block.set_local_var("@key".to_string(), key.clone());
update_block_context(block, obj_path, k.to_string(), is_first, &v);
set_block_param(block, h, obj_path, &key, &v)?;
}
t.render(r, ctx, rc, out)?;
if is_first {
is_first = false;
}
}
rc.pop_block();
Ok(())
}
_ => {
if let Some(else_template) = h.inverse() {
else_template.render(r, ctx, rc, out)?;
}
Ok(())
}
},
None => Ok(()),
}
}
}
pub static EACH_HELPER: EachHelper = EachHelper;
#[cfg(test)]
mod test {
use crate::json::value::to_json;
use crate::registry::Registry;
use serde_json::value::Value as Json;
use std::collections::BTreeMap;
use std::str::FromStr;
#[test]
fn test_each() {
let mut handlebars = Registry::new();
assert!(handlebars
.register_template_string(
"t0",
"{{#each this}}{{@first}}|{{@last}}|{{@index}}:{{this}}|{{/each}}"
)
.is_ok());
assert!(handlebars
.register_template_string("t1", "{{#each this}}{{@first}}|{{@key}}:{{this}}|{{/each}}")
.is_ok());
let r0 = handlebars.render("t0", &vec![1u16, 2u16, 3u16]);
assert_eq!(
r0.ok().unwrap(),
"true|false|0:1|false|false|1:2|false|true|2:3|".to_string()
);
let mut m: BTreeMap<String, u16> = BTreeMap::new();
m.insert("ftp".to_string(), 21);
m.insert("http".to_string(), 80);
let r1 = handlebars.render("t1", &m);
assert_eq!(r1.ok().unwrap(), "true|ftp:21|false|http:80|".to_string());
}
#[test]
fn test_each_with_parent() {
let json_str = r#"{"a":{"a":99,"c":[{"d":100},{"d":200}]}}"#;
let data = Json::from_str(json_str).unwrap();
// println!("data: {}", data);
let mut handlebars = Registry::new();
// previously, to access the parent in an each block,
// a user would need to specify ../../b, as the path
// that is computed includes the array index: ./a.c.[0]
assert!(handlebars
.register_template_string("t0", "{{#each a.c}} d={{d}} b={{../a.a}} {{/each}}")
.is_ok());
let r1 = handlebars.render("t0", &data);
assert_eq!(r1.ok().unwrap(), " d=100 b=99 d=200 b=99 ".to_string());
}
#[test]
fn test_nested_each_with_parent() {
let json_str = r#"{"a": [{"b": [{"d": 100}], "c": 200}]}"#;
let data = Json::from_str(json_str).unwrap();
let mut handlebars = Registry::new();
assert!(handlebars
.register_template_string(
"t0",
"{{#each a}}{{#each b}}{{d}}:{{../c}}{{/each}}{{/each}}"
)
.is_ok());
let r1 = handlebars.render("t0", &data);
assert_eq!(r1.ok().unwrap(), "100:200".to_string());
}
#[test]
fn test_nested_each() {
let json_str = r#"{"a": [{"b": true}], "b": [[1, 2, 3],[4, 5]]}"#;
let data = Json::from_str(json_str).unwrap();
let mut handlebars = Registry::new();
assert!(handlebars
.register_template_string(
"t0",
"{{#each b}}{{#if ../a}}{{#each this}}{{this}}{{/each}}{{/if}}{{/each}}"
)
.is_ok());
let r1 = handlebars.render("t0", &data);
assert_eq!(r1.ok().unwrap(), "12345".to_string());
}
#[test]
fn test_nested_array() {
let mut handlebars = Registry::new();
assert!(handlebars
.register_template_string("t0", "{{#each this.[0]}}{{this}}{{/each}}")
.is_ok());
let r0 = handlebars.render("t0", &(vec![vec![1, 2, 3]]));
assert_eq!(r0.ok().unwrap(), "123".to_string());
}
#[test]
fn test_empty_key() {
let mut handlebars = Registry::new();
assert!(handlebars
.register_template_string("t0", "{{#each this}}{{@key}}-{{value}}\n{{/each}}")
.is_ok());
let r0 = handlebars
.render(
"t0",
&json!({
"foo": {
"value": "bar"
},
"": {
"value": "baz"
}
}),
)
.unwrap();
let mut r0_sp: Vec<_> = r0.split('\n').collect();
r0_sp.sort();
assert_eq!(r0_sp, vec!["", "-baz", "foo-bar"]);
}
#[test]
fn test_each_else() {
let mut handlebars = Registry::new();
assert!(handlebars
.register_template_string("t0", "{{#each a}}1{{else}}empty{{/each}}")
.is_ok());
let m1 = btreemap! {
"a".to_string() => Vec::<String>::new(),
};
let r0 = handlebars.render("t0", &m1).unwrap();
assert_eq!(r0, "empty");
let m2 = btreemap! {
"b".to_string() => Vec::<String>::new()
};
let r1 = handlebars.render("t0", &m2).unwrap();
assert_eq!(r1, "empty");
}
#[test]
fn test_block_param() {
let mut handlebars = Registry::new();
assert!(handlebars
.register_template_string("t0", "{{#each a as |i|}}{{i}}{{/each}}")
.is_ok());
let m1 = btreemap! {
"a".to_string() => vec![1,2,3,4,5]
};
let r0 = handlebars.render("t0", &m1).unwrap();
assert_eq!(r0, "12345");
}
#[test]
fn test_each_object_block_param() {
let mut handlebars = Registry::new();
let template = "{{#each this as |v k|}}\
{{#with k as |inner_k|}}{{inner_k}}{{/with}}:{{v}}|\
{{/each}}";
assert!(handlebars.register_template_string("t0", template).is_ok());
let m = btreemap! {
"ftp".to_string() => 21,
"http".to_string() => 80
};
let r0 = handlebars.render("t0", &m);
assert_eq!(r0.ok().unwrap(), "ftp:21|http:80|".to_string());
}
#[test]
fn test_each_object_block_param2() {
let mut handlebars = Registry::new();
let template = "{{#each this as |v k|}}\
{{#with v as |inner_v|}}{{k}}:{{inner_v}}{{/with}}|\
{{/each}}";
assert!(handlebars.register_template_string("t0", template).is_ok());
let m = btreemap! {
"ftp".to_string() => 21,
"http".to_string() => 80
};
let r0 = handlebars.render("t0", &m);
assert_eq!(r0.ok().unwrap(), "ftp:21|http:80|".to_string());
}
fn test_nested_each_with_path_ups() {
let mut handlebars = Registry::new();
assert!(handlebars
.register_template_string(
"t0",
"{{#each a.b}}{{#each c}}{{../../d}}{{/each}}{{/each}}"
)
.is_ok());
let data = btreemap! {
"a".to_string() => to_json(&btreemap! {
"b".to_string() => vec![btreemap!{"c".to_string() => vec![1]}]
}),
"d".to_string() => to_json(&1)
};
let r0 = handlebars.render("t0", &data);
assert_eq!(r0.ok().unwrap(), "1".to_string());
}
#[test]
fn test_nested_each_with_path_up_this() {
let mut handlebars = Registry::new();
let template = "{{#each variant}}{{#each ../typearg}}\
{{#if @first}}template<{{/if}}{{this}}{{#if @last}}>{{else}},{{/if}}\
{{/each}}{{/each}}";
assert!(handlebars.register_template_string("t0", template).is_ok());
let data = btreemap! {
"typearg".to_string() => vec!["T".to_string()],
"variant".to_string() => vec!["1".to_string(), "2".to_string()]
};
let r0 = handlebars.render("t0", &data);
assert_eq!(r0.ok().unwrap(), "template<T>template<T>".to_string());
}
#[test]
fn test_key_iteration_with_unicode() {
let mut handlebars = Registry::new();
assert!(handlebars
.register_template_string("t0", "{{#each this}}{{@key}}: {{this}}\n{{/each}}")
.is_ok());
let data = json!({
"normal": 1,
"你好": 2,
"#special key": 3,
"😂": 4,
"me.dot.key": 5
});
let r0 = handlebars.render("t0", &data).ok().unwrap();
assert!(r0.contains("normal: 1"));
assert!(r0.contains("你好: 2"));
assert!(r0.contains("#special key: 3"));
assert!(r0.contains("😂: 4"));
assert!(r0.contains("me.dot.key: 5"));
}
#[test]
fn test_base_path_after_each() {
let mut handlebars = Registry::new();
assert!(handlebars
.register_template_string("t0", "{{#each a}}{{this}}{{/each}} {{b}}")
.is_ok());
let data = json!({
"a": [1, 2, 3, 4],
"b": "good",
});
let r0 = handlebars.render("t0", &data).ok().unwrap();
assert_eq!("1234 good", r0);
}
#[test]
fn test_else_context() {
let reg = Registry::new();
let template = "{{#each list}}A{{else}}{{foo}}{{/each}}";
let input = json!({"list": [], "foo": "bar"});
let rendered = reg.render_template(template, &input).unwrap();
assert_eq!("bar", rendered);
}
#[test]
fn test_block_context_leak() {
let reg = Registry::new();
let template = "{{#each list}}{{#each inner}}{{this}}{{/each}}{{foo}}{{/each}}";
let input = json!({"list": [{"inner": [], "foo": 1}, {"inner": [], "foo": 2}]});
let rendered = reg.render_template(template, &input).unwrap();
assert_eq!("12", rendered);
}
#[test]
fn test_derived_array_as_block_params() {
handlebars_helper!(range: |x: u64| (0..x).collect::<Vec<u64>>());
let mut reg = Registry::new();
reg.register_helper("range", Box::new(range));
let template = "{{#each (range 3) as |i|}}{{i}}{{/each}}";
let input = json!(0);
let rendered = reg.render_template(template, &input).unwrap();
assert_eq!("012", rendered);
}
#[test]
fn test_derived_object_as_block_params() {
handlebars_helper!(point: |x: u64, y: u64| json!({"x":x, "y":y}));
let mut reg = Registry::new();
reg.register_helper("point", Box::new(point));
let template = "{{#each (point 0 1) as |i|}}{{i}}{{/each}}";
let input = json!(0);
let rendered = reg.render_template(template, &input).unwrap();
assert_eq!("01", rendered);
}
#[test]
fn test_derived_array_without_block_param() {
handlebars_helper!(range: |x: u64| (0..x).collect::<Vec<u64>>());
let mut reg = Registry::new();
reg.register_helper("range", Box::new(range));
let template = "{{#each (range 3)}}{{this}}{{/each}}";
let input = json!(0);
let rendered = reg.render_template(template, &input).unwrap();
assert_eq!("012", rendered);
}
#[test]
fn test_derived_object_without_block_params() {
handlebars_helper!(point: |x: u64, y: u64| json!({"x":x, "y":y}));
let mut reg = Registry::new();
reg.register_helper("point", Box::new(point));
let template = "{{#each (point 0 1)}}{{this}}{{/each}}";
let input = json!(0);
let rendered = reg.render_template(template, &input).unwrap();
assert_eq!("01", rendered);
}
#[test]
fn test_non_iterable() {
let reg = Registry::new();
let template = "{{#each this}}each block{{else}}else block{{/each}}";
let input = json!("strings aren't iterable");
let rendered = reg.render_template(template, &input).unwrap();
assert_eq!("else block", rendered);
}
#[test]
fn test_recursion() {
let mut reg = Registry::new();
assert!(reg
.register_template_string(
"walk",
"(\
{{#each this}}\
{{#if @key}}{{@key}}{{else}}{{@index}}{{/if}}: \
{{this}} \
{{> walk this}}, \
{{/each}}\
)",
)
.is_ok());
let input = json!({
"array": [42, {"wow": "cool"}, [[]]],
"object": { "a": { "b": "c", "d": ["e"] } },
"string": "hi"
});
let expected_output = "(\
array: [42, [object], [[], ], ] (\
0: 42 (), \
1: [object] (wow: cool (), ), \
2: [[], ] (0: [] (), ), \
), \
object: [object] (\
a: [object] (\
b: c (), \
d: [e, ] (0: e (), ), \
), \
), \
string: hi (), \
)";
let rendered = reg.render("walk", &input).unwrap();
assert_eq!(expected_output, rendered);
}
}