femstache 0.1.13

Embrace the feminine mustache to unlock your templating dreams! Or something, I don't know. :3
Documentation
use std::{
    collections::{BTreeMap, BTreeSet},
    path::PathBuf,
};

use femstache_eval::{
    EvalError, EvalErrorKind, EvalResult, Fragment, FunClosure, FunRet, FunValue, StringValue,
    TemplateEnv, Value, check_pat, eval_expr,
};
use femstache_loader::TemplateLoader;
use femstache_parser::nodes::{
    Control, Document, DocumentNode, DocumentNodes, ForControlExpr, IfControlCond, IfControlEnd,
    Stache,
};

use crate::output::Output;

pub struct TemplateRenderer {
    env: TemplateEnv,
    blocks: BTreeMap<String, String>,
    loader: TemplateLoader,
}

impl TemplateRenderer {
    pub fn new(env: TemplateEnv, blocks: BTreeMap<String, String>, loader: TemplateLoader) -> Self {
        Self {
            env,
            blocks,
            loader,
        }
    }

    pub fn render_document(&mut self, document: &Document, output: &mut Output) -> EvalResult<()> {
        if let Some(meta) = &document.meta
            && let Some(inherit) = &meta.inherit
        {
            let path = PathBuf::from(inherit.path.as_str());
            let template = self.loader.load(&path).map_err(|err| {
                EvalError::new(
                    EvalErrorKind::Loader(err),
                    self.env.path().to_owned(),
                    inherit.path.span(),
                )
            })?;
            self.env.push();
            self.render_document_nodes(&document.nodes, &mut Output::null(output.escape()))?;
            self.env.pop();
            self.env.push();
            self.env.set_path(&path);
            self.render_document(template.get().document(), output)?;
            self.env.pop();
        } else {
            self.render_document_nodes(&document.nodes, output)?;
        }
        Ok(())
    }

    pub fn render_document_nodes(
        &mut self,
        nodes: &DocumentNodes,
        output: &mut Output,
    ) -> EvalResult<()> {
        for node in &nodes.nodes {
            self.render_document_node(node, output)?;
        }
        Ok(())
    }

    pub fn render_document_node(
        &mut self,
        node: &DocumentNode,
        output: &mut Output,
    ) -> EvalResult<()> {
        match node {
            DocumentNode::Output(o) => {
                output.push(&o.data);
                Ok(())
            }
            DocumentNode::Stache(s) => self.render_stache(s, output),
            DocumentNode::Control(c) => self.render_control(c, output),
        }
    }

    pub fn render_stache(&mut self, stache: &Stache, output: &mut Output) -> EvalResult<()> {
        let val = eval_expr(&mut self.env, &stache.expr)?;
        let s = val.as_string().ok_or_else(|| {
            EvalError::new(
                EvalErrorKind::InvalidType,
                self.env.path().to_owned(),
                stache.expr.span(),
            )
        })?;
        match s {
            StringValue::Regular(s) => output.push_escaped(s),
            StringValue::Raw(s) => output.push(s),
        }
        Ok(())
    }

    pub fn render_if_control(
        &mut self,
        cond: &IfControlCond,
        inner: &DocumentNodes,
        close: &IfControlEnd,
        output: &mut Output,
    ) -> EvalResult<()> {
        match cond {
            IfControlCond::Bool(cond) => {
                let cond_val = eval_expr(&mut self.env, &cond.expr)?;
                if cond_val.as_bool().ok_or_else(|| {
                    EvalError::new(
                        EvalErrorKind::InvalidType,
                        self.env.path().to_owned(),
                        cond.expr.span(),
                    )
                })? {
                    self.env.push();
                    self.render_document_nodes(inner, output)?;
                    self.env.pop();
                } else {
                    self.render_if_control_end(close, output)?;
                }
            }
            IfControlCond::Let(cond) => {
                let val = eval_expr(&mut self.env, &cond.expr)?;
                self.env.push();
                if check_pat(&mut self.env, &cond.pat, val, |env, var, val| {
                    env.set(var, val.clone())
                })? {
                    self.render_document_nodes(inner, output)?;
                } else {
                    self.env.pop();
                    self.env.push();
                    self.render_if_control_end(close, output)?;
                }
                self.env.pop();
            }
        }
        Ok(())
    }

    pub fn render_if_control_end(
        &mut self,
        if_end: &IfControlEnd,
        output: &mut Output,
    ) -> EvalResult<()> {
        match if_end {
            IfControlEnd::End(_) => {}
            IfControlEnd::Else(control) => {
                self.render_document_nodes(&control.inner, output)?;
            }
            IfControlEnd::ElseIf(control) => {
                self.render_if_control(&control.open.cond, &control.inner, &control.close, output)?;
            }
        }
        Ok(())
    }

    pub fn render_control(&mut self, control: &Control, output: &mut Output) -> EvalResult<()> {
        match control {
            Control::If(if_control) => {
                self.render_if_control(
                    &if_control.open.cond,
                    &if_control.inner,
                    &if_control.close,
                    output,
                )?;
            }
            Control::For(for_control) => match &for_control.open.expr {
                ForControlExpr::List(l) => {
                    let value = eval_expr(&mut self.env, &l.expr)?;
                    let list = value.into_list().ok_or_else(|| {
                        EvalError::new(
                            EvalErrorKind::InvalidType,
                            self.env.path().to_owned(),
                            l.expr.span(),
                        )
                    })?;
                    for item in list {
                        self.env.push();
                        let success = check_pat(
                            &mut self.env,
                            &for_control.open.pat,
                            item,
                            |env, var, val| env.set(var, val.clone()),
                        )?;
                        if !success {
                            unreachable!("pattern in a for loop must be infallible");
                        }
                        self.render_document_nodes(&for_control.inner, output)?;
                        self.env.pop();
                    }
                }
                ForControlExpr::Dict(d) => {
                    let value = eval_expr(&mut self.env, &d.expr)?;
                    let dict = value.into_dict().ok_or_else(|| {
                        EvalError::new(
                            EvalErrorKind::InvalidType,
                            self.env.path().to_owned(),
                            d.expr.span(),
                        )
                    })?;
                    for (key, val) in dict {
                        self.env.push();
                        let mut map = BTreeMap::new();
                        map.insert("key".to_owned(), Value::string(key));
                        map.insert("val".to_owned(), val);
                        let success = check_pat(
                            &mut self.env,
                            &for_control.open.pat,
                            Value::Struct(map),
                            |env, var, val| env.set(var, val.clone()),
                        )?;
                        if !success {
                            unreachable!("pattern in a for loop must be infallible");
                        }
                        self.render_document_nodes(&for_control.inner, output)?;
                        self.env.pop();
                    }
                }
                ForControlExpr::Range(r) => {
                    let start = eval_expr(&mut self.env, &r.start)?;
                    let end = eval_expr(&mut self.env, &r.end)?;
                    let start_num = start.as_int().ok_or(EvalError::new(
                        EvalErrorKind::InvalidType,
                        self.env.path().to_owned(),
                        r.start.span(),
                    ))?;
                    let end_num = end.as_int().ok_or(EvalError::new(
                        EvalErrorKind::InvalidType,
                        self.env.path().to_owned(),
                        r.end.span(),
                    ))?;
                    for i in start_num..end_num {
                        self.env.push();
                        let success = check_pat(
                            &mut self.env,
                            &for_control.open.pat,
                            Value::Int(i),
                            |env, var, val| env.set(var, val.clone()),
                        )?;
                        if !success {
                            unreachable!("pattern in a for loop must be infallible");
                        }
                        self.render_document_nodes(&for_control.inner, output)?;
                        self.env.pop();
                    }
                }
            },
            Control::Block(block_control) => {
                self.env.push();
                if let Some(block) = self.blocks.get(block_control.open.name.as_str()) {
                    output.push(block);
                } else {
                    let key = block_control.open.name.as_str().to_owned();
                    let mut block = Output::string(output.escape());
                    self.env.push();
                    self.render_document_nodes(&block_control.inner, &mut block)?;
                    let block = block.into_inner();
                    output.push(&block);
                    self.blocks.insert(key, block);
                    self.env.pop();
                }
                self.env.pop();
            }
            Control::Macro(macro_control) => {
                let mut vars = BTreeSet::new();
                macro_control.inner.free_vars(&mut vars);
                for pat in &macro_control.open.args.args.items {
                    pat.remove_free_vars(&mut vars);
                }
                let closure = FunClosure::gather(&self.env, vars)?;
                let args = macro_control.open.args.args.items.clone();
                let fragment = Fragment::new(macro_control.inner.clone());
                let fun = Value::Fun(FunValue::new(closure, args, FunRet::Fragment(fragment)));
                self.env.set(&macro_control.open.ident.data, fun);
            }
            Control::Render(render_control) => {
                let fragment_value = eval_expr(&mut self.env, &render_control.expr)?;
                let fragment = fragment_value.into_fragment().ok_or_else(|| {
                    EvalError::new(
                        EvalErrorKind::InvalidType,
                        self.env.path().to_owned(),
                        render_control.expr.span(),
                    )
                })?;
                self.env.push();
                // TODO: closure
                for (var, val) in fragment.vars {
                    self.env.set(var, val);
                }
                self.render_document_nodes(&fragment.nodes, output)?;
                self.env.pop();
            }
            Control::Include(include_control) => {
                let path = PathBuf::from(include_control.path.as_str());
                let template = self.loader.load(&path).map_err(|err| {
                    EvalError::new(
                        EvalErrorKind::Loader(err),
                        self.env.path().to_owned(),
                        include_control.path.span(),
                    )
                })?;
                self.env.push();
                self.env.set_path(&path);
                self.render_document(template.get().document(), output)?;
                self.env.pop();
            }
            Control::Let(let_control) => {
                let value = eval_expr(&mut self.env, &let_control.expr)?;
                let success =
                    check_pat(&mut self.env, &let_control.pat, value, |env, var, val| {
                        env.set(var, val.clone())
                    })?;
                if !success {
                    unreachable!("pattern matches on let should be infallible");
                }
            }
        }
        Ok(())
    }
}