handbar 0.0.5

Fork of Handlebars.
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::output::Output;
use crate::registry::Registry;
use crate::render::{Helper, RenderContext, Renderable};

#[derive(Clone, Copy)]
pub struct CtxHelper;

impl HelperDef for CtxHelper {
    fn call<'reg: 'rc, 'rc>(
        &self,
        h: &Helper<'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 \"ctx\""))?;

        let mut block = create_block(param);

        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(())
    }
}

pub static CTX_HELPER: CtxHelper = CtxHelper;

#[cfg(test)]
mod test {
    use crate::registry::Registry;

    #[derive(Serialize, Clone)]
    struct Address {
        city: String,
        country: String,
    }

    #[derive(Serialize)]
    struct Person {
        name: String,
        age: i16,
        addr: Address,
        titles: Vec<String>,
    }

    #[test]
    fn test_ctx() {
        let addr = Address {
            city: "Beijing".to_string(),
            country: "China".to_string(),
        };

        assert_renders![
            (r##"{{#ctx this}}{{city}}{{/ctx}}"##, r##"Beijing"##),
            (r##"{{#ctx notfound}}hello{{else}}world{{/ctx}}"##, r##"hello"##),
            (r##"{{#ctx this/country}}{{this}}{{/ctx}}"##, r##"China"##),
            @&addr,
        ];
    }

    #[test]
    fn test_ctx_block_param() {
        let addr = Address {
            city: "Beijing".to_string(),
            country: "China".to_string(),
        };

        assert_renders![
            (r##"{{#ctx this as |a|}}{{a.city}}{{/ctx}}"##, r##"Beijing"##),
            (r##"{{#ctx notfound as |a|}}hello{{else}}world{{/ctx}}"##, r##"hello"##),
            (r##"{{#ctx this/country as |a|}}{{a}}{{/ctx}}"##, r##"China"##),
            (r##"{{#ctx this/country as |a|}}{{this}}{{/ctx}}"##, r##"China"##),
            @&addr,
        ];
    }

    #[test]
    fn test_ctx_in_each() {
        let addr = Address {
            city: "Beijing".to_string(),
            country: "China".to_string(),
        };

        let person = Person {
            name: "Ning Sun".to_string(),
            age: 27,
            addr: addr.clone(),
            titles: vec!["programmer".to_string(), "cartographier".to_string()],
        };

        let person2 = Person {
            name: "Ning Sun".to_string(),
            age: 27,
            addr,
            titles: vec!["programmer".to_string(), "cartographier".to_string()],
        };

        assert_renders![
            (r##"{{#each this}}{{#ctx addr}}{{city}}{{/ctx}}{{/each}}"##, r##"BeijingBeijing"##),
            (r##"{{#each this}}{{#ctx addr}}{{../age}}{{/ctx}}{{/each}}"##, r##"2727"##),
            (r##"{{#each this}}{{#ctx addr}}{{@../index}}{{/ctx}}{{/each}}"##, r##"01"##),
            @&vec![person, person2],
        ];
    }

    #[test]
    fn test_ctx_up() {
        assert_renders![
            (r##"{{#ctx a}}{{#ctx b}}{{../../d}}{{/ctx}}{{/ctx}}"##, r##"1"##),
            @&json!({
                "a": {
                    "b": [{"c": [1]}]
                },
                "d": 1
            }),
        ];
    }

    #[test]
    fn test_else_context() {
        let reg = Registry::new();
        let template = "{{#ctx list}}A{{else}}{{foo}}{{/ctx}}";
        let input = json!({"list": [], "foo": "bar"});
        let rendered = reg.render_template(template, &input).unwrap();
        assert_eq!("A", rendered);
    }

    #[test]
    fn test_derived_value() {
        let hb = Registry::new();
        let data = json!({"a": {"b": {"c": "d"}}});
        let template = "{{#ctx (lookup a.b \"c\")}}{{this}}{{/ctx}}";
        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 = "{{#ctx (lookup a \"b\")}}{{#ctx this}}{{c}}{{/ctx}}{{/ctx}}";
        assert_eq!("d", hb.render_template(template, &data).unwrap());
    }

    #[test]
    fn test_strict_ctx() {
        let mut hb = Registry::new();

        assert_eq!(
            hb.render_template("{{#ctx name}}yes{{/ctx}}", &json!({}))
                .unwrap(),
            "yes"
        );
        assert_eq!(
            hb.render_template("{{#ctx name}}yes{{else}}no{{/ctx}}", &json!({}))
                .unwrap(),
            "yes"
        );

        hb.set_strict_mode(true);

        assert!(hb
            .render_template("{{#ctx name}}yes{{/ctx}}", &json!({}))
            .is_ok());
        assert_eq!(
            hb.render_template("{{#ctx name}}yes{{else}}no{{/ctx}}", &json!({}))
                .unwrap(),
            "yes"
        );
    }
}