From 698f8c2f01ea549d77d7dc3338a12e04c11057b9 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 17 Apr 2024 14:02:58 +0200 Subject: Adding upstream version 1.64.0+dfsg1. Signed-off-by: Daniel Baumann --- vendor/handlebars/src/helpers/block_util.rs | 17 + vendor/handlebars/src/helpers/helper_each.rs | 593 +++++++++++++++++++++++++ vendor/handlebars/src/helpers/helper_extras.rs | 112 +++++ vendor/handlebars/src/helpers/helper_if.rs | 151 +++++++ vendor/handlebars/src/helpers/helper_log.rs | 72 +++ vendor/handlebars/src/helpers/helper_lookup.rs | 104 +++++ vendor/handlebars/src/helpers/helper_raw.rs | 44 ++ vendor/handlebars/src/helpers/helper_with.rs | 276 ++++++++++++ vendor/handlebars/src/helpers/mod.rs | 291 ++++++++++++ vendor/handlebars/src/helpers/scripting.rs | 111 +++++ 10 files changed, 1771 insertions(+) create mode 100644 vendor/handlebars/src/helpers/block_util.rs create mode 100644 vendor/handlebars/src/helpers/helper_each.rs create mode 100644 vendor/handlebars/src/helpers/helper_extras.rs create mode 100644 vendor/handlebars/src/helpers/helper_if.rs create mode 100644 vendor/handlebars/src/helpers/helper_log.rs create mode 100644 vendor/handlebars/src/helpers/helper_lookup.rs create mode 100644 vendor/handlebars/src/helpers/helper_raw.rs create mode 100644 vendor/handlebars/src/helpers/helper_with.rs create mode 100644 vendor/handlebars/src/helpers/mod.rs create mode 100644 vendor/handlebars/src/helpers/scripting.rs (limited to 'vendor/handlebars/src/helpers') diff --git a/vendor/handlebars/src/helpers/block_util.rs b/vendor/handlebars/src/helpers/block_util.rs new file mode 100644 index 000000000..6971fdd8a --- /dev/null +++ b/vendor/handlebars/src/helpers/block_util.rs @@ -0,0 +1,17 @@ +use crate::block::BlockContext; +use crate::json::value::PathAndJson; + +pub(crate) fn create_block<'reg: 'rc, 'rc>( + param: &'rc PathAndJson<'reg, 'rc>, +) -> BlockContext<'reg> { + let mut block = BlockContext::new(); + + if let Some(new_path) = param.context_path() { + *block.base_path_mut() = new_path.clone(); + } else { + // use clone for now + block.set_base_value(param.value().clone()); + } + + block +} diff --git a/vendor/handlebars/src/helpers/helper_each.rs b/vendor/handlebars/src/helpers/helper_each.rs new file mode 100644 index 000000000..4b76e7ce7 --- /dev/null +++ b/vendor/handlebars/src/helpers/helper_each.rs @@ -0,0 +1,593 @@ +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>, + 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>, + 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() || (list.is_empty() && h.inverse().is_none()) => + { + 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_json(is_first)); + block.set_local_var("last", to_json(is_last)); + block.set_local_var("index", 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() || (obj.is_empty() && h.inverse().is_none()) => + { + 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_json(is_first)); + block.set_local_var("key", 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) + } else if r.strict_mode() { + Err(RenderError::strict_error(value.relative_path())) + } else { + 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_empty_each() { + let mut hbs = Registry::new(); + hbs.set_strict_mode(true); + + let data = json!({ + "a": [ ], + }); + + let template = "{{#each a}}each{{/each}}"; + assert_eq!(hbs.render_template(template, &data).unwrap(), ""); + } + + #[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 = 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::::new(), + }; + let r0 = handlebars.render("t0", &m1).unwrap(); + assert_eq!(r0, "empty"); + + let m2 = btreemap! { + "b".to_string() => Vec::::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(), "templatetemplate".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::>()); + 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::>()); + 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); + } + + #[test] + fn test_strict_each() { + let mut reg = Registry::new(); + + assert!(reg + .render_template("{{#each data}}{{/each}}", &json!({})) + .is_ok()); + assert!(reg + .render_template("{{#each data}}{{/each}}", &json!({"data": 24})) + .is_ok()); + + reg.set_strict_mode(true); + + assert!(reg + .render_template("{{#each data}}{{/each}}", &json!({})) + .is_err()); + assert!(reg + .render_template("{{#each data}}{{/each}}", &json!({"data": 24})) + .is_err()); + assert!(reg + .render_template("{{#each data}}{{else}}food{{/each}}", &json!({})) + .is_ok()); + assert!(reg + .render_template("{{#each data}}{{else}}food{{/each}}", &json!({"data": 24})) + .is_ok()); + } + + #[test] + fn newline_stripping_for_each() { + let reg = Registry::new(); + + let tpl = r#"
    + {{#each a}} + {{!-- comment --}} +
  • {{this}}
  • + {{/each}} +
"#; + assert_eq!( + r#"
    +
  • 0
  • +
  • 1
  • +
"#, + reg.render_template(tpl, &json!({"a": [0, 1]})).unwrap() + ); + } +} diff --git a/vendor/handlebars/src/helpers/helper_extras.rs b/vendor/handlebars/src/helpers/helper_extras.rs new file mode 100644 index 000000000..fe8f7a7d3 --- /dev/null +++ b/vendor/handlebars/src/helpers/helper_extras.rs @@ -0,0 +1,112 @@ +//! Helpers for boolean operations +use serde_json::Value as Json; + +use crate::json::value::JsonTruthy; + +handlebars_helper!(eq: |x: Json, y: Json| x == y); +handlebars_helper!(ne: |x: Json, y: Json| x != y); +handlebars_helper!(gt: |x: i64, y: i64| x > y); +handlebars_helper!(gte: |x: i64, y: i64| x >= y); +handlebars_helper!(lt: |x: i64, y: i64| x < y); +handlebars_helper!(lte: |x: i64, y: i64| x <= y); +handlebars_helper!(and: |x: Json, y: Json| x.is_truthy(false) && y.is_truthy(false)); +handlebars_helper!(or: |x: Json, y: Json| x.is_truthy(false) || y.is_truthy(false)); +handlebars_helper!(not: |x: Json| !x.is_truthy(false)); +handlebars_helper!(len: |x: Json| { + match x { + Json::Array(a) => a.len(), + Json::Object(m) => m.len(), + Json::String(s) => s.len(), + _ => 0 + } +}); + +#[cfg(test)] +mod test_conditions { + fn test_condition(condition: &str, expected: bool) { + let handlebars = crate::Handlebars::new(); + + let result = handlebars + .render_template( + &format!( + "{{{{#if {condition}}}}}lorem{{{{else}}}}ipsum{{{{/if}}}}", + condition = condition + ), + &json!({}), + ) + .unwrap(); + assert_eq!(&result, if expected { "lorem" } else { "ipsum" }); + } + + #[test] + fn foo() { + test_condition("(gt 5 3)", true); + test_condition("(gt 3 5)", false); + test_condition("(or (gt 3 5) (gt 5 3))", true); + test_condition("(not [])", true); + test_condition("(and null 4)", false); + } + + #[test] + fn test_eq() { + test_condition("(eq 5 5)", true); + test_condition("(eq 5 6)", false); + test_condition(r#"(eq "foo" "foo")"#, true); + test_condition(r#"(eq "foo" "Foo")"#, false); + test_condition(r#"(eq [5] [5])"#, true); + test_condition(r#"(eq [5] [4])"#, false); + test_condition(r#"(eq 5 "5")"#, false); + test_condition(r#"(eq 5 [5])"#, false); + } + + #[test] + fn test_ne() { + test_condition("(ne 5 6)", true); + test_condition("(ne 5 5)", false); + test_condition(r#"(ne "foo" "foo")"#, false); + test_condition(r#"(ne "foo" "Foo")"#, true); + } + + #[test] + fn nested_conditions() { + let handlebars = crate::Handlebars::new(); + + let result = handlebars + .render_template("{{#if (gt 5 3)}}lorem{{else}}ipsum{{/if}}", &json!({})) + .unwrap(); + assert_eq!(&result, "lorem"); + + let result = handlebars + .render_template( + "{{#if (not (gt 5 3))}}lorem{{else}}ipsum{{/if}}", + &json!({}), + ) + .unwrap(); + assert_eq!(&result, "ipsum"); + } + + #[test] + fn test_len() { + let handlebars = crate::Handlebars::new(); + + let result = handlebars + .render_template("{{len value}}", &json!({"value": [1,2,3]})) + .unwrap(); + assert_eq!(&result, "3"); + + let result = handlebars + .render_template("{{len value}}", &json!({"value": {"a" :1, "b": 2}})) + .unwrap(); + assert_eq!(&result, "2"); + + let result = handlebars + .render_template("{{len value}}", &json!({"value": "tomcat"})) + .unwrap(); + assert_eq!(&result, "6"); + + let result = handlebars + .render_template("{{len value}}", &json!({"value": 3})) + .unwrap(); + assert_eq!(&result, "0"); + } +} diff --git a/vendor/handlebars/src/helpers/helper_if.rs b/vendor/handlebars/src/helpers/helper_if.rs new file mode 100644 index 000000000..5a6e42fc0 --- /dev/null +++ b/vendor/handlebars/src/helpers/helper_if.rs @@ -0,0 +1,151 @@ +use crate::context::Context; +use crate::error::RenderError; +use crate::helpers::{HelperDef, HelperResult}; +use crate::json::value::JsonTruthy; +use crate::output::Output; +use crate::registry::Registry; +use crate::render::{Helper, RenderContext, Renderable}; + +#[derive(Clone, Copy)] +pub struct IfHelper { + positive: bool, +} + +impl HelperDef for IfHelper { + 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 param = h + .param(0) + .ok_or_else(|| RenderError::new("Param not found for helper \"if\""))?; + let include_zero = h + .hash_get("includeZero") + .and_then(|v| v.value().as_bool()) + .unwrap_or(false); + + let mut value = param.value().is_truthy(include_zero); + + if !self.positive { + value = !value; + } + + let tmpl = if value { h.template() } else { h.inverse() }; + match tmpl { + Some(ref t) => t.render(r, ctx, rc, out), + None => Ok(()), + } + } +} + +pub static IF_HELPER: IfHelper = IfHelper { positive: true }; +pub static UNLESS_HELPER: IfHelper = IfHelper { positive: false }; + +#[cfg(test)] +mod test { + use crate::helpers::WITH_HELPER; + use crate::registry::Registry; + use serde_json::value::Value as Json; + use std::str::FromStr; + + #[test] + fn test_if() { + let mut handlebars = Registry::new(); + assert!(handlebars + .register_template_string("t0", "{{#if this}}hello{{/if}}") + .is_ok()); + assert!(handlebars + .register_template_string("t1", "{{#unless this}}hello{{else}}world{{/unless}}") + .is_ok()); + + let r0 = handlebars.render("t0", &true); + assert_eq!(r0.ok().unwrap(), "hello".to_string()); + + let r1 = handlebars.render("t1", &true); + assert_eq!(r1.ok().unwrap(), "world".to_string()); + + let r2 = handlebars.render("t0", &false); + assert_eq!(r2.ok().unwrap(), "".to_string()); + } + + #[test] + fn test_if_context() { + let json_str = r#"{"a":{"b":99,"c":{"d": true}}}"#; + let data = Json::from_str(json_str).unwrap(); + + let mut handlebars = Registry::new(); + handlebars.register_helper("with", Box::new(WITH_HELPER)); + assert!(handlebars + .register_template_string("t0", "{{#if a.c.d}}hello {{a.b}}{{/if}}") + .is_ok()); + assert!(handlebars + .register_template_string( + "t1", + "{{#with a}}{{#if c.d}}hello {{../a.b}}{{/if}}{{/with}}" + ) + .is_ok()); + + let r0 = handlebars.render("t0", &data); + assert_eq!(r0.unwrap(), "hello 99".to_string()); + + let r1 = handlebars.render("t1", &data); + assert_eq!(r1.unwrap(), "hello 99".to_string()); + } + + #[test] + fn test_if_include_zero() { + use std::f64; + let handlebars = Registry::new(); + + assert_eq!( + "0".to_owned(), + handlebars + .render_template("{{#if a}}1{{else}}0{{/if}}", &json!({"a": 0})) + .unwrap() + ); + assert_eq!( + "1".to_owned(), + handlebars + .render_template( + "{{#if a includeZero=true}}1{{else}}0{{/if}}", + &json!({"a": 0}) + ) + .unwrap() + ); + assert_eq!( + "0".to_owned(), + handlebars + .render_template( + "{{#if a includeZero=true}}1{{else}}0{{/if}}", + &json!({ "a": f64::NAN }) + ) + .unwrap() + ); + } + + #[test] + fn test_invisible_line_stripping() { + let hbs = Registry::new(); + assert_eq!( + "yes\n", + hbs.render_template("{{#if a}}\nyes\n{{/if}}\n", &json!({"a": true})) + .unwrap() + ); + + assert_eq!( + "x\ny", + hbs.render_template("{{#if a}}x{{/if}}\ny", &json!({"a": true})) + .unwrap() + ); + + assert_eq!( + "y\nz", + hbs.render_template("{{#if a}}\nx\n{{^}}\ny\n{{/if}}\nz", &json!({"a": false})) + .unwrap() + ); + } +} diff --git a/vendor/handlebars/src/helpers/helper_log.rs b/vendor/handlebars/src/helpers/helper_log.rs new file mode 100644 index 000000000..5821efa35 --- /dev/null +++ b/vendor/handlebars/src/helpers/helper_log.rs @@ -0,0 +1,72 @@ +use crate::context::Context; +#[cfg(not(feature = "no_logging"))] +use crate::error::RenderError; +use crate::helpers::{HelperDef, HelperResult}; +#[cfg(not(feature = "no_logging"))] +use crate::json::value::JsonRender; +use crate::output::Output; +use crate::registry::Registry; +use crate::render::{Helper, RenderContext}; +#[cfg(not(feature = "no_logging"))] +use log::Level; +#[cfg(not(feature = "no_logging"))] +use std::str::FromStr; + +#[derive(Clone, Copy)] +pub struct LogHelper; + +#[cfg(not(feature = "no_logging"))] +impl HelperDef for LogHelper { + fn call<'reg: 'rc, 'rc>( + &self, + h: &Helper<'reg, 'rc>, + _: &'reg Registry<'reg>, + _: &'rc Context, + _: &mut RenderContext<'reg, 'rc>, + _: &mut dyn Output, + ) -> HelperResult { + let param_to_log = h + .params() + .iter() + .map(|p| { + if let Some(ref relative_path) = p.relative_path() { + format!("{}: {}", relative_path, p.value().render()) + } else { + p.value().render() + } + }) + .collect::>() + .join(", "); + + let level = h + .hash_get("level") + .and_then(|v| v.value().as_str()) + .unwrap_or("info"); + + if let Ok(log_level) = Level::from_str(level) { + log!(log_level, "{}", param_to_log) + } else { + return Err(RenderError::new(&format!( + "Unsupported logging level {}", + level + ))); + } + Ok(()) + } +} + +#[cfg(feature = "no_logging")] +impl HelperDef for LogHelper { + fn call<'reg: 'rc, 'rc>( + &self, + _: &Helper<'reg, 'rc>, + _: &Registry<'reg>, + _: &Context, + _: &mut RenderContext<'reg, 'rc>, + _: &mut dyn Output, + ) -> HelperResult { + Ok(()) + } +} + +pub static LOG_HELPER: LogHelper = LogHelper; diff --git a/vendor/handlebars/src/helpers/helper_lookup.rs b/vendor/handlebars/src/helpers/helper_lookup.rs new file mode 100644 index 000000000..bf887debe --- /dev/null +++ b/vendor/handlebars/src/helpers/helper_lookup.rs @@ -0,0 +1,104 @@ +use serde_json::value::Value as Json; + +use crate::context::Context; +use crate::error::RenderError; +use crate::helpers::HelperDef; +use crate::json::value::ScopedJson; +use crate::registry::Registry; +use crate::render::{Helper, RenderContext}; + +#[derive(Clone, Copy)] +pub struct LookupHelper; + +impl HelperDef for LookupHelper { + fn call_inner<'reg: 'rc, 'rc>( + &self, + h: &Helper<'reg, 'rc>, + r: &'reg Registry<'reg>, + _: &'rc Context, + _: &mut RenderContext<'reg, 'rc>, + ) -> Result, RenderError> { + let collection_value = h + .param(0) + .ok_or_else(|| RenderError::new("Param not found for helper \"lookup\""))?; + let index = h + .param(1) + .ok_or_else(|| RenderError::new("Insufficient params for helper \"lookup\""))?; + + let value = match *collection_value.value() { + Json::Array(ref v) => index + .value() + .as_u64() + .and_then(|u| v.get(u as usize)) + .unwrap_or(&Json::Null), + Json::Object(ref m) => index + .value() + .as_str() + .and_then(|k| m.get(k)) + .unwrap_or(&Json::Null), + _ => &Json::Null, + }; + if r.strict_mode() && value.is_null() { + Err(RenderError::strict_error(None)) + } else { + Ok(value.clone().into()) + } + } +} + +pub static LOOKUP_HELPER: LookupHelper = LookupHelper; + +#[cfg(test)] +mod test { + use crate::registry::Registry; + + use std::collections::BTreeMap; + + #[test] + fn test_lookup() { + let mut handlebars = Registry::new(); + assert!(handlebars + .register_template_string("t0", "{{#each v1}}{{lookup ../v2 @index}}{{/each}}") + .is_ok()); + assert!(handlebars + .register_template_string("t1", "{{#each v1}}{{lookup ../v2 1}}{{/each}}") + .is_ok()); + assert!(handlebars + .register_template_string("t2", "{{lookup kk \"a\"}}") + .is_ok()); + + let mut m: BTreeMap> = BTreeMap::new(); + m.insert("v1".to_string(), vec![1u16, 2u16, 3u16]); + m.insert("v2".to_string(), vec![9u16, 8u16, 7u16]); + + let m2 = btreemap! { + "kk".to_string() => btreemap!{"a".to_string() => "world".to_string()} + }; + + let r0 = handlebars.render("t0", &m); + assert_eq!(r0.ok().unwrap(), "987".to_string()); + + let r1 = handlebars.render("t1", &m); + assert_eq!(r1.ok().unwrap(), "888".to_string()); + + let r2 = handlebars.render("t2", &m2); + assert_eq!(r2.ok().unwrap(), "world".to_string()); + } + + #[test] + fn test_strict_lookup() { + let mut hbs = Registry::new(); + + assert_eq!( + hbs.render_template("{{lookup kk 1}}", &json!({"kk": []})) + .unwrap(), + "" + ); + + hbs.set_strict_mode(true); + + assert!(hbs + .render_template("{{lookup kk 1}}", &json!({"kk": []})) + .is_err()); + } +} diff --git a/vendor/handlebars/src/helpers/helper_raw.rs b/vendor/handlebars/src/helpers/helper_raw.rs new file mode 100644 index 000000000..55aef6bfc --- /dev/null +++ b/vendor/handlebars/src/helpers/helper_raw.rs @@ -0,0 +1,44 @@ +use crate::context::Context; +use crate::helpers::{HelperDef, HelperResult}; +use crate::output::Output; +use crate::registry::Registry; +use crate::render::{Helper, RenderContext, Renderable}; + +#[derive(Clone, Copy)] +pub struct RawHelper; + +impl HelperDef for RawHelper { + 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 tpl = h.template(); + if let Some(t) = tpl { + t.render(r, ctx, rc, out) + } else { + Ok(()) + } + } +} + +pub static RAW_HELPER: RawHelper = RawHelper; + +#[cfg(test)] +mod test { + use crate::registry::Registry; + + #[test] + fn test_raw_helper() { + let mut handlebars = Registry::new(); + assert!(handlebars + .register_template_string("t0", "a{{{{raw}}}}{{content}}{{else}}hello{{{{/raw}}}}") + .is_ok()); + + let r = handlebars.render("t0", &()); + assert_eq!(r.ok().unwrap(), "a{{content}}{{else}}hello"); + } +} diff --git a/vendor/handlebars/src/helpers/helper_with.rs b/vendor/handlebars/src/helpers/helper_with.rs new file mode 100644 index 000000000..c4d31cd0e --- /dev/null +++ b/vendor/handlebars/src/helpers/helper_with.rs @@ -0,0 +1,276 @@ +use super::block_util::create_block; +use crate::block::BlockParams; +use crate::context::Context; +use crate::error::RenderError; +use crate::helpers::{HelperDef, HelperResult}; +use crate::json::value::JsonTruthy; +use crate::output::Output; +use crate::registry::Registry; +use crate::render::{Helper, RenderContext, Renderable}; + +#[derive(Clone, Copy)] +pub struct WithHelper; + +impl HelperDef for WithHelper { + 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 param = h + .param(0) + .ok_or_else(|| RenderError::new("Param not found for helper \"with\""))?; + + if param.value().is_truthy(false) { + let mut block = create_block(¶m); + + if let Some(block_param) = h.block_param() { + let mut params = BlockParams::new(); + if param.context_path().is_some() { + params.add_path(block_param, Vec::with_capacity(0))?; + } else { + params.add_value(block_param, param.value().clone())?; + } + + block.set_block_params(params); + } + + rc.push_block(block); + + if let Some(t) = h.template() { + t.render(r, ctx, rc, out)?; + }; + + rc.pop_block(); + Ok(()) + } else if let Some(t) = h.inverse() { + t.render(r, ctx, rc, out) + } else if r.strict_mode() { + Err(RenderError::strict_error(param.relative_path())) + } else { + Ok(()) + } + } +} + +pub static WITH_HELPER: WithHelper = WithHelper; + +#[cfg(test)] +mod test { + use crate::json::value::to_json; + use crate::registry::Registry; + + #[derive(Serialize)] + struct Address { + city: String, + country: String, + } + + #[derive(Serialize)] + struct Person { + name: String, + age: i16, + addr: Address, + titles: Vec, + } + + #[test] + fn test_with() { + let addr = Address { + city: "Beijing".to_string(), + country: "China".to_string(), + }; + + let person = Person { + name: "Ning Sun".to_string(), + age: 27, + addr, + titles: vec!["programmer".to_string(), "cartographier".to_string()], + }; + + let mut handlebars = Registry::new(); + assert!(handlebars + .register_template_string("t0", "{{#with addr}}{{city}}{{/with}}") + .is_ok()); + assert!(handlebars + .register_template_string("t1", "{{#with notfound}}hello{{else}}world{{/with}}") + .is_ok()); + assert!(handlebars + .register_template_string("t2", "{{#with addr/country}}{{this}}{{/with}}") + .is_ok()); + + let r0 = handlebars.render("t0", &person); + assert_eq!(r0.ok().unwrap(), "Beijing".to_string()); + + let r1 = handlebars.render("t1", &person); + assert_eq!(r1.ok().unwrap(), "world".to_string()); + + let r2 = handlebars.render("t2", &person); + assert_eq!(r2.ok().unwrap(), "China".to_string()); + } + + #[test] + fn test_with_block_param() { + let addr = Address { + city: "Beijing".to_string(), + country: "China".to_string(), + }; + + let person = Person { + name: "Ning Sun".to_string(), + age: 27, + addr, + titles: vec!["programmer".to_string(), "cartographier".to_string()], + }; + + let mut handlebars = Registry::new(); + assert!(handlebars + .register_template_string("t0", "{{#with addr as |a|}}{{a.city}}{{/with}}") + .is_ok()); + assert!(handlebars + .register_template_string("t1", "{{#with notfound as |c|}}hello{{else}}world{{/with}}") + .is_ok()); + assert!(handlebars + .register_template_string("t2", "{{#with addr/country as |t|}}{{t}}{{/with}}") + .is_ok()); + + let r0 = handlebars.render("t0", &person); + assert_eq!(r0.ok().unwrap(), "Beijing".to_string()); + + let r1 = handlebars.render("t1", &person); + assert_eq!(r1.ok().unwrap(), "world".to_string()); + + let r2 = handlebars.render("t2", &person); + assert_eq!(r2.ok().unwrap(), "China".to_string()); + } + + #[test] + fn test_with_in_each() { + let addr = Address { + city: "Beijing".to_string(), + country: "China".to_string(), + }; + + let person = Person { + name: "Ning Sun".to_string(), + age: 27, + addr, + titles: vec!["programmer".to_string(), "cartographier".to_string()], + }; + + let addr2 = Address { + city: "Beijing".to_string(), + country: "China".to_string(), + }; + + let person2 = Person { + name: "Ning Sun".to_string(), + age: 27, + addr: addr2, + titles: vec!["programmer".to_string(), "cartographier".to_string()], + }; + + let people = vec![person, person2]; + + let mut handlebars = Registry::new(); + assert!(handlebars + .register_template_string( + "t0", + "{{#each this}}{{#with addr}}{{city}}{{/with}}{{/each}}" + ) + .is_ok()); + assert!(handlebars + .register_template_string( + "t1", + "{{#each this}}{{#with addr}}{{../age}}{{/with}}{{/each}}" + ) + .is_ok()); + assert!(handlebars + .register_template_string( + "t2", + "{{#each this}}{{#with addr}}{{@../index}}{{/with}}{{/each}}" + ) + .is_ok()); + + let r0 = handlebars.render("t0", &people); + assert_eq!(r0.ok().unwrap(), "BeijingBeijing".to_string()); + + let r1 = handlebars.render("t1", &people); + assert_eq!(r1.ok().unwrap(), "2727".to_string()); + + let r2 = handlebars.render("t2", &people); + assert_eq!(r2.ok().unwrap(), "01".to_string()); + } + + #[test] + fn test_path_up() { + let mut handlebars = Registry::new(); + assert!(handlebars + .register_template_string("t0", "{{#with a}}{{#with b}}{{../../d}}{{/with}}{{/with}}") + .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_else_context() { + let reg = Registry::new(); + let template = "{{#with list}}A{{else}}{{foo}}{{/with}}"; + let input = json!({"list": [], "foo": "bar"}); + let rendered = reg.render_template(template, &input).unwrap(); + assert_eq!("bar", rendered); + } + + #[test] + fn test_derived_value() { + let hb = Registry::new(); + let data = json!({"a": {"b": {"c": "d"}}}); + let template = "{{#with (lookup a.b \"c\")}}{{this}}{{/with}}"; + assert_eq!("d", hb.render_template(template, &data).unwrap()); + } + + #[test] + fn test_nested_derived_value() { + let hb = Registry::new(); + let data = json!({"a": {"b": {"c": "d"}}}); + let template = "{{#with (lookup a \"b\")}}{{#with this}}{{c}}{{/with}}{{/with}}"; + assert_eq!("d", hb.render_template(template, &data).unwrap()); + } + + #[test] + fn test_strict_with() { + let mut hb = Registry::new(); + + assert_eq!( + hb.render_template("{{#with name}}yes{{/with}}", &json!({})) + .unwrap(), + "" + ); + assert_eq!( + hb.render_template("{{#with name}}yes{{else}}no{{/with}}", &json!({})) + .unwrap(), + "no" + ); + + hb.set_strict_mode(true); + + assert!(hb + .render_template("{{#with name}}yes{{/with}}", &json!({})) + .is_err()); + assert_eq!( + hb.render_template("{{#with name}}yes{{else}}no{{/with}}", &json!({})) + .unwrap(), + "no" + ); + } +} diff --git a/vendor/handlebars/src/helpers/mod.rs b/vendor/handlebars/src/helpers/mod.rs new file mode 100644 index 000000000..bfd50c1f4 --- /dev/null +++ b/vendor/handlebars/src/helpers/mod.rs @@ -0,0 +1,291 @@ +use crate::context::Context; +use crate::error::RenderError; +use crate::json::value::ScopedJson; +use crate::output::Output; +use crate::registry::Registry; +use crate::render::{do_escape, Helper, RenderContext}; + +pub use self::helper_each::EACH_HELPER; +pub use self::helper_if::{IF_HELPER, UNLESS_HELPER}; +pub use self::helper_log::LOG_HELPER; +pub use self::helper_lookup::LOOKUP_HELPER; +pub use self::helper_raw::RAW_HELPER; +pub use self::helper_with::WITH_HELPER; + +/// A type alias for `Result<(), RenderError>` +pub type HelperResult = Result<(), RenderError>; + +/// Helper Definition +/// +/// Implement `HelperDef` to create custom helpers. You can retrieve useful information from these arguments. +/// +/// * `&Helper`: current helper template information, contains name, params, hashes and nested template +/// * `&Registry`: the global registry, you can find templates by name from registry +/// * `&Context`: the whole data to render, in most case you can use data from `Helper` +/// * `&mut RenderContext`: you can access data or modify variables (starts with @)/partials in render context, for example, @index of #each. See its document for detail. +/// * `&mut dyn Output`: where you write output to +/// +/// By default, you can use a bare function as a helper definition because we have supported unboxed_closure. If you have stateful or configurable helper, you can create a struct to implement `HelperDef`. +/// +/// ## Define an inline helper +/// +/// ``` +/// use handlebars::*; +/// +/// fn upper(h: &Helper<'_, '_>, _: &Handlebars<'_>, _: &Context, rc: &mut RenderContext<'_, '_>, out: &mut Output) +/// -> HelperResult { +/// // get parameter from helper or throw an error +/// let param = h.param(0).and_then(|v| v.value().as_str()).unwrap_or(""); +/// out.write(param.to_uppercase().as_ref())?; +/// Ok(()) +/// } +/// ``` +/// +/// ## Define block helper +/// +/// Block helper is like `#if` or `#each` which has a inner template and an optional *inverse* template (the template in else branch). You can access the inner template by `helper.template()` and `helper.inverse()`. In most cases you will just call `render` on it. +/// +/// ``` +/// use handlebars::*; +/// +/// fn dummy_block<'reg, 'rc>( +/// h: &Helper<'reg, 'rc>, +/// r: &'reg Handlebars<'reg>, +/// ctx: &'rc Context, +/// rc: &mut RenderContext<'reg, 'rc>, +/// out: &mut dyn Output, +/// ) -> HelperResult { +/// h.template() +/// .map(|t| t.render(r, ctx, rc, out)) +/// .unwrap_or(Ok(())) +/// } +/// ``` +/// +/// ## Define helper function using macro +/// +/// In most cases you just need some simple function to call from templates. We have a `handlebars_helper!` macro to simplify the job. +/// +/// ``` +/// use handlebars::*; +/// +/// handlebars_helper!(plus: |x: i64, y: i64| x + y); +/// +/// let mut hbs = Handlebars::new(); +/// hbs.register_helper("plus", Box::new(plus)); +/// ``` +/// +pub trait HelperDef { + /// A simplified api to define helper + /// + /// To implement your own `call_inner`, you will return a new `ScopedJson` + /// which has a JSON value computed from current context. + /// + /// ### Calling from subexpression + /// + /// When calling the helper as a subexpression, the value and its type can + /// be received by upper level helpers. + /// + /// Note that the value can be `json!(null)` which is treated as `false` in + /// helpers like `if` and rendered as empty string. + fn call_inner<'reg: 'rc, 'rc>( + &self, + _: &Helper<'reg, 'rc>, + _: &'reg Registry<'reg>, + _: &'rc Context, + _: &mut RenderContext<'reg, 'rc>, + ) -> Result, RenderError> { + Err(RenderError::unimplemented()) + } + + /// A complex version of helper interface. + /// + /// This function offers `Output`, which you can write custom string into + /// and render child template. Helpers like `if` and `each` are implemented + /// with this. Because the data written into `Output` are typically without + /// type information. So helpers defined by this function are not composable. + /// + /// ### Calling from subexpression + /// + /// Although helpers defined by this are not composable, when called from + /// subexpression, handlebars tries to parse the string output as JSON to + /// re-build its type. This can be buggy with numrical and other literal values. + /// So it is not recommended to use these helpers in subexpression. + 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 { + match self.call_inner(h, r, ctx, rc) { + Ok(result) => { + if r.strict_mode() && result.is_missing() { + Err(RenderError::strict_error(None)) + } else { + // auto escape according to settings + let output = do_escape(r, rc, result.render()); + out.write(output.as_ref())?; + Ok(()) + } + } + Err(e) => { + if e.is_unimplemented() { + // default implementation, do nothing + Ok(()) + } else { + Err(e) + } + } + } + } +} + +/// implement HelperDef for bare function so we can use function as helper +impl< + F: for<'reg, 'rc> Fn( + &Helper<'reg, 'rc>, + &'reg Registry<'reg>, + &'rc Context, + &mut RenderContext<'reg, 'rc>, + &mut dyn Output, + ) -> HelperResult, + > HelperDef for F +{ + 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 { + (*self)(h, r, ctx, rc, out) + } +} + +mod block_util; +mod helper_each; +pub(crate) mod helper_extras; +mod helper_if; +mod helper_log; +mod helper_lookup; +mod helper_raw; +mod helper_with; +#[cfg(feature = "script_helper")] +pub(crate) mod scripting; + +// pub type HelperDef = for <'a, 'b, 'c> Fn<(&'a Context, &'b Helper, &'b Registry, &'c mut RenderContext), Result>; +// +// pub fn helper_dummy (ctx: &Context, h: &Helper, r: &Registry, rc: &mut RenderContext) -> Result { +// h.template().unwrap().render(ctx, r, rc).unwrap() +// } +// +#[cfg(test)] +mod test { + use std::collections::BTreeMap; + + use crate::context::Context; + use crate::error::RenderError; + use crate::helpers::HelperDef; + use crate::json::value::JsonRender; + use crate::output::Output; + use crate::registry::Registry; + use crate::render::{Helper, RenderContext, Renderable}; + + #[derive(Clone, Copy)] + struct MetaHelper; + + impl HelperDef for MetaHelper { + 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, + ) -> Result<(), RenderError> { + let v = h.param(0).unwrap(); + + if !h.is_block() { + let output = format!("{}:{}", h.name(), v.value().render()); + out.write(output.as_ref())?; + } else { + let output = format!("{}:{}", h.name(), v.value().render()); + out.write(output.as_ref())?; + out.write("->")?; + h.template().unwrap().render(r, ctx, rc, out)?; + }; + Ok(()) + } + } + + #[test] + fn test_meta_helper() { + let mut handlebars = Registry::new(); + assert!(handlebars + .register_template_string("t0", "{{foo this}}") + .is_ok()); + assert!(handlebars + .register_template_string("t1", "{{#bar this}}nice{{/bar}}") + .is_ok()); + + let meta_helper = MetaHelper; + handlebars.register_helper("helperMissing", Box::new(meta_helper)); + handlebars.register_helper("blockHelperMissing", Box::new(meta_helper)); + + let r0 = handlebars.render("t0", &true); + assert_eq!(r0.ok().unwrap(), "foo:true".to_string()); + + let r1 = handlebars.render("t1", &true); + assert_eq!(r1.ok().unwrap(), "bar:true->nice".to_string()); + } + + #[test] + fn test_helper_for_subexpression() { + let mut handlebars = Registry::new(); + assert!(handlebars + .register_template_string("t2", "{{foo value=(bar 0)}}") + .is_ok()); + + handlebars.register_helper( + "helperMissing", + Box::new( + |h: &Helper<'_, '_>, + _: &Registry<'_>, + _: &Context, + _: &mut RenderContext<'_, '_>, + out: &mut dyn Output| + -> Result<(), RenderError> { + let output = format!("{}{}", h.name(), h.param(0).unwrap().value()); + out.write(output.as_ref())?; + Ok(()) + }, + ), + ); + handlebars.register_helper( + "foo", + Box::new( + |h: &Helper<'_, '_>, + _: &Registry<'_>, + _: &Context, + _: &mut RenderContext<'_, '_>, + out: &mut dyn Output| + -> Result<(), RenderError> { + let output = format!("{}", h.hash_get("value").unwrap().value().render()); + out.write(output.as_ref())?; + Ok(()) + }, + ), + ); + + let mut data = BTreeMap::new(); + // handlebars should never try to lookup this value because + // subexpressions are now resolved as string literal + data.insert("bar0".to_string(), true); + + let r2 = handlebars.render("t2", &data); + + assert_eq!(r2.ok().unwrap(), "bar0".to_string()); + } +} diff --git a/vendor/handlebars/src/helpers/scripting.rs b/vendor/handlebars/src/helpers/scripting.rs new file mode 100644 index 000000000..cec3b9763 --- /dev/null +++ b/vendor/handlebars/src/helpers/scripting.rs @@ -0,0 +1,111 @@ +use std::collections::{BTreeMap, HashMap}; + +use crate::context::Context; +use crate::error::RenderError; +use crate::helpers::HelperDef; +use crate::json::value::{PathAndJson, ScopedJson}; +use crate::registry::Registry; +use crate::render::{Helper, RenderContext}; + +use rhai::serde::{from_dynamic, to_dynamic}; +use rhai::{Dynamic, Engine, Scope, AST}; + +use serde_json::value::Value as Json; + +pub(crate) struct ScriptHelper { + pub(crate) script: AST, +} + +#[inline] +fn call_script_helper<'reg: 'rc, 'rc>( + params: &[PathAndJson<'reg, 'rc>], + hash: &BTreeMap<&'reg str, PathAndJson<'reg, 'rc>>, + engine: &Engine, + script: &AST, +) -> Result, RenderError> { + let params: Dynamic = to_dynamic(params.iter().map(|p| p.value()).collect::>())?; + + let hash: Dynamic = to_dynamic( + hash.iter() + .map(|(k, v)| ((*k).to_owned(), v.value())) + .collect::>(), + )?; + + let mut scope = Scope::new(); + scope.push_dynamic("params", params); + scope.push_dynamic("hash", hash); + + let result = engine + .eval_ast_with_scope::(&mut scope, script) + .map_err(RenderError::from)?; + + let result_json: Json = from_dynamic(&result)?; + + Ok(ScopedJson::Derived(result_json)) +} + +impl HelperDef for ScriptHelper { + fn call_inner<'reg: 'rc, 'rc>( + &self, + h: &Helper<'reg, 'rc>, + reg: &'reg Registry<'reg>, + _ctx: &'rc Context, + _rc: &mut RenderContext<'reg, 'rc>, + ) -> Result, RenderError> { + call_script_helper(h.params(), h.hash(), ®.engine, &self.script) + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::json::value::{PathAndJson, ScopedJson}; + use rhai::Engine; + + #[test] + fn test_dynamic_convert() { + let j0 = json! { + [{"name": "tomcat"}, {"name": "jetty"}] + }; + + let d0 = to_dynamic(&j0).unwrap(); + assert_eq!("array", d0.type_name()); + + let j1 = json!({ + "name": "tomcat", + "value": 4000, + }); + + let d1 = to_dynamic(&j1).unwrap(); + assert_eq!("map", d1.type_name()); + } + + #[test] + fn test_to_json() { + let d0 = Dynamic::from("tomcat".to_owned()); + + assert_eq!( + Json::String("tomcat".to_owned()), + from_dynamic::(&d0).unwrap() + ); + } + + #[test] + fn test_script_helper_value_access() { + let engine = Engine::new(); + + let script = "let plen = len(params); let p0 = params[0]; let hlen = len(hash); let hme = hash[\"me\"]; plen + \",\" + p0 + \",\" + hlen + \",\" + hme"; + let ast = engine.compile(&script).unwrap(); + + let params = vec![PathAndJson::new(None, ScopedJson::Derived(json!(true)))]; + let hash = btreemap! { + "me" => PathAndJson::new(None, ScopedJson::Derived(json!("no"))), + "you" => PathAndJson::new(None, ScopedJson::Derived(json!("yes"))), + }; + + let result = call_script_helper(¶ms, &hash, &engine, &ast) + .unwrap() + .render(); + assert_eq!("1,true,2,no", &result); + } +} -- cgit v1.2.3