handbar 0.0.5

Fork of Handlebars.
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};
use crate::ScopedJson;

#[derive(Clone, Copy)]
pub struct IfHelper {
    name: crate::HelperName,
    positive: bool,
}

impl HelperDef for IfHelper {
    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 condition = h.param(0).ok_or_else(|| {
            RenderError::new(format!(
                "Param not found for helper \"{}\": condition",
                self.name
            ))
        })?;
        let include_zero = h
            .hash_get("include_zero")
            .and_then(|v| v.value().as_bool())
            .unwrap_or(false);

        let mut condition = condition.value().is_truthy(include_zero);

        if !self.positive {
            condition = !condition;
        }

        let tmpl = if condition { h.template() } else { h.inverse() };
        match tmpl {
            Some(t) => t.render(r, ctx, rc, out),
            None => Ok(()),
        }
    }
}

#[derive(Clone, Copy)]
pub struct IfTernaryHelper {
    name: crate::HelperName,
    positive: bool,
}

impl HelperDef for IfTernaryHelper {
    fn call_inner<'reg: 'rc, 'rc>(
        &self,
        h: &Helper<'rc>,
        _: &'reg Registry<'reg>,
        _: &'rc Context,
        _: &mut RenderContext<'reg, 'rc>,
    ) -> Result<ScopedJson<'rc>, RenderError> {
        let condition = h.param(0).ok_or_else(|| {
            RenderError::new(format!(
                "Param not found for helper \"{}\": condition",
                self.name
            ))
        })?;

        let true_value = h
            .param(1)
            .ok_or_else(|| {
                RenderError::new(format!(
                    "Param not found for helper \"{}\": true_value",
                    self.name
                ))
            })?
            .value()
            .clone();

        let false_value = h
            .param(2)
            .ok_or_else(|| {
                RenderError::new(format!(
                    "Param not found for helper \"{}\": false_value",
                    self.name
                ))
            })?
            .value()
            .clone();

        let include_zero = h
            .hash_get("include_zero")
            .and_then(|v| v.value().as_bool())
            .unwrap_or(false);

        let mut condition = condition.value().is_truthy(include_zero);

        if !self.positive {
            condition = !condition;
        }

        if condition {
            return Ok(true_value.into());
        }

        return Ok(false_value.into());
    }
}

pub static IF_HELPER: IfHelper = IfHelper {
    name: crate::HelperName::If,
    positive: true,
};

pub static IF_TERNARY_HELPER: IfTernaryHelper = IfTernaryHelper {
    name: crate::HelperName::IfTernary,
    positive: true,
};

pub static UNLESS_HELPER: IfHelper = IfHelper {
    name: crate::HelperName::Unless,
    positive: false,
};

pub static UNLESS_TERNARY_HELPER: IfTernaryHelper = IfTernaryHelper {
    name: crate::HelperName::UnlessTernary,
    positive: false,
};

#[cfg(test)]
mod test {
    #[test]
    fn test_if() {
        assert_renders![
            ("{{#if true}}hello{{/if}}", "hello"),
            ("{{#unless true}}hello{{else}}world{{/unless}}", "world"),
        ];
    }

    #[test]
    fn test_if_context() {
        assert_renders![
            ("{{#if a.c.d}}hello {{a.b}}{{/if}}", "hello 99"),
            ("{{#with a}}{{#if c.d}}hello {{../a.b}}{{/if}}{{/with}}", "hello 99"),
            @&json!({"a": {"b": 99, "c": {"d": true}}}),
        ];
    }

    #[test]
    fn test_if_include_zero() {
        assert_renders![
            ("{{#if 0}}1{{else}}0{{/if}}", "0"),
            ("{{#if 0 include_zero=true}}1{{else}}0{{/if}}", "1"),
            ("{{#if nan include_zero=true}}1{{else}}0{{/if}}", "0"),
            @&json!({"nan": std::f64::NAN}),
        ];
    }

    #[test]
    fn test_invisible_line_stripping() {
        assert_renders![
            ("{{#if true}}\nyes\n{{/if}}\n", "yes\n"),
            ("{{#if true}}\r\nyes\r\n{{/if}}\r\n", "yes\r\n"),
            ("{{#if true}}x{{/if}}\ny", "x\ny"),
            ("{{#if a}}\nx\n{{^}}\ny\n{{/if}}\nz", "y\nz"),
        ];
    }

    #[test]
    fn test_if_ternary() {
        assert_renders![
            (r#"{{if_ternary true "true" "false"}}"#, "true"),
            (r#"{{if_ternary false "true" "false"}}"#, "false"),
            (r#"{{if_ternary null "true" "false"}}"#, "false"),
            (r#"{{if_ternary 0 "true" "false"}}"#, "false"),
            (
                r#"{{if_ternary include_zero=true 0 "true" "false"}}"#,
                "true"
            ),
        ];
    }
}