sato 0.1.10

some sort of s-expr based html templating system
Documentation
use crate::renderer::{Attributes, Renderer, RenderValue, RenderError, basic_html_tag};
use crate::context::{ContextValue, RenderContext};
use crate::template::{TemplateExprNode, TemplateTag};


pub(crate) fn do_html(attrs: Attributes, expr: &[TemplateExprNode], renderer: &Renderer, context: &RenderContext) -> Result<RenderValue, RenderError> {
    let mut v: Vec<RenderValue> = vec!["<!doctype html5>".into()];
    v.push(basic_html_tag("html".into(), &attrs, &expr, renderer, context)?.into());
    Ok(v.into())
}

pub(crate) fn do_is_set(_: Attributes, expr: &[TemplateExprNode], _render: &Renderer, context: &RenderContext) -> Result<RenderValue, RenderError> {
    match expr.get(0) {
        Some(TemplateExprNode::Identifier(ident)) => {
            Ok(match context.get(ident) {
                Some(_) => RenderValue::Boolean(true),
                None => RenderValue::Boolean(false)
            })
        },
        _ => Err(RenderError::IsSet("expected identifier".into(), expr.to_vec()))
    }
}

pub(crate) fn do_cmp_op<F>(_: Attributes, expr: &[TemplateExprNode], renderer: &Renderer, context: &RenderContext, op: F) -> Result<RenderValue, RenderError>
where
    F: FnOnce(ContextValue, ContextValue) -> bool,
{
    let exp1 = expr.get(0)
        .and_then(|e| {
            match e {
                TemplateExprNode::Identifier(ident) => Some(context.get(ident).cloned().unwrap_or(ContextValue::String(ident.clone()))),
                TemplateExprNode::Integer(i) => Some(ContextValue::Integer(*i)),
                TemplateExprNode::Tag(_tag) => Some(renderer.evaluate(e, context).unwrap().into()),
            }
        })
        .ok_or_else(|| RenderError::Cmp("missing expr 1".into(), expr.to_vec()))?;
    let exp2 = expr.get(1)
        .and_then(|e| {
            match e {
                TemplateExprNode::Identifier(ident) => Some(context.get(ident).cloned().unwrap_or(ContextValue::String(ident.clone()))),
                TemplateExprNode::Integer(i) => Some(ContextValue::Integer(*i)),
                TemplateExprNode::Tag(_tag) => Some(renderer.evaluate(e, context).ok()?.into()),
            }
        })
        .ok_or_else(|| RenderError::Cmp("missing expr 2".into(), expr.to_vec()))?;

    Ok(op(exp1, exp2).into())
}


pub(crate) fn do_math_op<F>(_: Attributes, expr: &[TemplateExprNode], renderer: &Renderer, context: &RenderContext, op: F) -> Result<RenderValue, RenderError>
where
    F: FnOnce(i64, i64) -> i64
{
    let exp1 = expr.get(0)
        .and_then(|e| renderer.evaluate(e, context).ok())
        .and_then(|rv| rv.as_int())
        .ok_or_else(|| RenderError::Math("missing expr 1".into(), expr.to_vec()))?;

    let exp2 = expr.get(1)
        .and_then(|e| renderer.evaluate(e, context).ok())
        .and_then(|rv| rv.as_int())
        .ok_or_else(|| RenderError::Math("missing expr 2".into(), expr.to_vec()))?;

    Ok(op(exp1, exp2).into())
}


pub(crate) fn do_if(_: Attributes, expr: &[TemplateExprNode], renderer: &Renderer, context: &RenderContext) -> Result<RenderValue, RenderError> {
    let conditional = expr.get(0)
        .ok_or_else(|| RenderError::If("condition not found".into(), expr.to_vec()))?;

    let result = renderer.evaluate(conditional, context)?;

    let is_true = match result {
        RenderValue::Boolean(b) => b,
        RenderValue::String(s) if s != "" => true,
        RenderValue::Integer(i) if i != 0 => true,
        _ => false
    };

    Ok(
        if is_true {
            renderer.evaluate(expr
                              .get(1)
                              .ok_or_else(|| RenderError::If("code block not found".into(), expr.to_vec()))?,
                              context)?
        }
        else {
            match expr.get(2) {
                Some(e) => renderer.evaluate(e, context)?,
                None => RenderValue::Empty,
            }
        })
}

pub(crate) fn do_case(_: Attributes, expr: &[TemplateExprNode], renderer: &Renderer, context: &RenderContext) -> Result<RenderValue, RenderError> {
    let condition = expr.get(0)
        .ok_or_else(|| RenderError::Case("variant not found".into(), expr.to_vec()))?;

    let switch_value = context.get("__switch")
        .ok_or_else(|| RenderError::Case("builtin switch variable not found?".into(), expr.to_vec()))?;

    let body = expr.get(1..)
        .unwrap_or_default();

    match (condition, switch_value) {
        (TemplateExprNode::Identifier(condition_str), ContextValue::String(switch_str)) if condition_str == switch_str => {
            renderer.evaluate_multiple(body, context)
        },
        _ => return Ok(RenderValue::Empty)
    }
}

pub(crate) fn do_switch(_: Attributes, expr: &[TemplateExprNode], renderer: &Renderer, context: &RenderContext) -> Result<RenderValue, RenderError> {
    let variable = renderer.evaluate(expr.get(0)
                                     .ok_or_else(|| RenderError::Switch("variable not found".into(), expr.to_vec()))?,
                                     context)?;
    let cases = expr.get(1..);
    let mut context = context.clone();
    context.insert("__switch", &variable);
    Ok(cases.iter()
        .map(|case| {
            renderer.evaluate_multiple(*case, &context)
        })
        .collect::<Result<Vec<_>, _>>()?
        .into())
}


fn parse_range(tag: &TemplateTag, renderer: &Renderer, context: &RenderContext) -> Option<ContextValue> {
    let min = tag.children.get(0)
        .and_then(|e| renderer.evaluate(e, context).ok())
        .and_then(|e| e.as_int())?;
    let max = tag.children.get(1)
        .and_then(|e| renderer.evaluate(e, context).ok())
        .and_then(|e| e.as_int())?;
    let step = tag.children.get(2)
        .and_then(|e| renderer.evaluate(e, context).ok())
        .and_then(|e| e.as_int())
        .unwrap_or(1) as usize;

    let range = (min..max)
        .step_by(step)
        .map(Into::into)
        .collect();

    Some(ContextValue::Vec(range))
}

pub(crate) fn do_for(attrs: Attributes, expr: &[TemplateExprNode], renderer: &Renderer, context: &RenderContext) -> Result<RenderValue, RenderError> {
    let in_position = expr.iter()
        .position(|b| {
            match b {
                TemplateExprNode::Identifier(ident) if ident == "in" => true,
                _ => false,
            }
        });

    if let Some(in_position) = in_position {
        let iterable = expr.get(in_position+1)
            .map(|e| {
                match e {
                    TemplateExprNode::Identifier(ident) => {
                        crate::renderer::expand_variable(ident, renderer, context)
                            .map(|k| k.into())
                    },
                    TemplateExprNode::Tag(tag) if tag.tag == "range" => {
                        parse_range(tag, renderer, context)
                            .ok_or_else(|| RenderError::For("invalid range".into(), attrs.clone(), expr.to_vec()))
                    },
                    _ => Err(RenderError::For("iteration variable is not a valid type".into(), attrs.clone(), expr.to_vec()))
                }
            })
            .ok_or_else(|| RenderError::For("no iteration variable specified".into(), attrs.clone(), expr.to_vec()))??;
        let body = expr.get(in_position+2..)
            .unwrap_or_default();

        match iterable {
            ContextValue::Vec(v) => {
                enum IterType {
                    Normal(String),
                    Enum(String, String),
                }

                let val = expr.get(in_position-1)
                    .and_then(|e| {
                        match e {
                            TemplateExprNode::Identifier(_) => renderer.evaluate(e, context).map(|e| IterType::Normal(e.finalize())).ok(),
                            TemplateExprNode::Tag(tag) if tag.tag == "enumerate" => {
                                let index = tag.children.get(0)
                                    .and_then(TemplateExprNode::as_identifier)?;
                                let iter = tag.children.get(1)
                                    .and_then(TemplateExprNode::as_identifier)?;
                                Some(IterType::Enum(iter.clone(), index.clone()))
                            },
                            _ => None
                        }
                    })
                    .ok_or_else(|| RenderError::For("missing variable to iterate over".into(), attrs, expr.to_vec()))?;

                let mut second_context = context.clone();
                Ok(v.iter()
                   .enumerate()
                   .map(|(i, value)| {
                       match &val {
                           IterType::Normal(val) => {
                               second_context.insert(val.clone(), value.clone());
                           }
                           IterType::Enum(iter, index) => {
                               second_context.insert(iter.clone(), value.clone());
                               second_context.insert(index.clone(), i);
                           }
                       }
                       renderer.evaluate_multiple(body, &second_context)
                   })
                   .collect::<Result<Vec<_>, RenderError>>()?
                   .into())
            },
            ContextValue::Object(o) => {
                let key_var = expr.get(in_position-2)
                    .and_then(|a| renderer.evaluate(a, context).ok())
                    .map(|e| e.finalize())
                    .ok_or_else(|| RenderError::For("missing key variable to iterate over".into(), attrs.clone(), expr.to_vec()))?;
                let value_var = expr.get(in_position-1)
                    .and_then(|a| renderer.evaluate(a, context).ok())
                    .map(|e| e.finalize())
                    .ok_or_else(|| RenderError::For("missing value variable to iterate over".into(), attrs.clone(), expr.to_vec()))?;
                let mut second_context = context.clone();
                Ok(o.0.iter()
                   .map(|(key, value)| {
                       second_context.insert(key_var.clone(), ContextValue::String(key.clone()));
                       second_context.insert(value_var.clone(), value.clone());
                       renderer.evaluate_multiple(body, &second_context)
                   })
                   .collect::<Result<Vec<_>, RenderError>>()?
                   .into())
            },
            _ => Err(RenderError::For("element is not iterable".into(), attrs, expr.to_vec()))
        }
    }
    else {
        Err(RenderError::For("invalid syntax".into(), attrs, expr.to_vec()))
    }
}

pub(crate) fn do_get(_: Attributes, expr: &[TemplateExprNode], renderer: &Renderer, context: &RenderContext) -> Result<RenderValue, RenderError> {
    let indexable = expr.get(0)
        .and_then(|e| renderer.evaluate(e, context).ok())
        .unwrap();

    let index = expr.get(1)
        .and_then(|e| renderer.evaluate(e, context).ok())
        .unwrap();

    match (indexable, index){
        (RenderValue::Vec(v), RenderValue::Integer(i)) => {
            Ok(v.get(i as usize)
                .ok_or_else(|| RenderError::Get("array out of bounds".into(), expr.to_vec()))?
                .clone())

        },
        (RenderValue::Object(o), RenderValue::String(s)) => {
            Ok(o.get(&s).ok_or_else(|| RenderError::Get("array out of bounds".into(), expr.to_vec()))?.clone())
        },
        _ => Err(RenderError::Get("invalid index/indexable".into(), expr.to_vec()))
    }
}