use std::collections::BTreeMap;
use femstache_parser::nodes::{
Control, Document, DocumentNode, DocumentNodes, Expr, Literal, Stache,
};
use crate::{
env::TemplateEnv,
error::{EvalError, EvalResult},
input::TemplateInput,
loader::{LoaderError, TemplateLoader},
output::Output,
value::Value,
};
pub struct TemplateRenderer<'i, I: TemplateInput> {
env: TemplateEnv<'i, I>,
blocks: BTreeMap<String, String>,
loader: Option<TemplateLoader>,
}
impl<'i, I: TemplateInput> TemplateRenderer<'i, I> {
pub fn new(
env: TemplateEnv<'i, I>,
blocks: BTreeMap<String, String>,
loader: Option<TemplateLoader>,
) -> Self {
Self {
env,
blocks,
loader,
}
}
pub fn render_document(&mut self, document: &Document, output: &mut Output) -> EvalResult<()> {
if let Some(meta) = &document.meta {
if let Some(inherit) = &meta.inherit {
if let Some(loader) = self.loader.as_mut() {
let template = loader.load(&inherit.path.as_str())?;
self.env.push();
self.render_document_nodes(&document.nodes, &mut Output::Null)?;
self.env.pop();
self.env.push();
self.render_document(&template.doc, output)?;
self.env.pop();
} else {
return Err(EvalError::Loader(LoaderError::NoLoader));
}
}
}
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 = self.eval_expr(&stache.expr)?;
let s = val.as_str().ok_or_else(|| EvalError::InvalidType)?;
output.push(s);
Ok(())
}
pub fn render_control(&mut self, control: &Control, output: &mut Output) -> EvalResult<()> {
match control {
Control::IfControl(if_control) => {
let cond = self.eval_expr(&if_control.open.cond)?;
if cond.is_truthy() {
self.env.push();
self.render_document_nodes(&if_control.inner, output)?;
self.env.pop();
}
}
Control::ForControl(for_control) => {
let value = self.eval_expr(&for_control.open.expr)?;
let list = value.into_list().ok_or_else(|| EvalError::InvalidType)?;
for item in list {
self.env.push();
self.env.set(&for_control.open.var.data, item);
self.render_document_nodes(&for_control.inner, output)?;
self.env.pop();
}
}
Control::BlockControl(block_control) => {
self.env.push();
if let Some(block) = self.blocks.get(&block_control.open.ident.data) {
output.push(&block);
} else {
let key = block_control.open.ident.data.clone();
let mut block = Output::String(String::new());
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::IncludeControl(include_control) => {
if let Some(loader) = self.loader.as_mut() {
let template = loader.load(&include_control.path.as_str())?;
self.env.push();
self.render_document(&template.doc, output)?;
self.env.pop();
} else {
return Err(EvalError::Loader(LoaderError::NoLoader));
}
}
Control::LetControl(let_control) => {
let value = self.eval_expr(&let_control.expr)?;
self.env.set(&let_control.var.data, value);
}
}
Ok(())
}
pub fn eval_expr(&mut self, expr: &Expr) -> EvalResult<Value> {
match expr {
Expr::Ident(ident) => self
.env
.get(&ident.data)
.ok_or_else(|| EvalError::VariableNotFound(ident.clone())),
Expr::Literal(literal) => Ok(match literal {
Literal::Null(_) => Value::Null,
Literal::True(_) => Value::Bool(true),
Literal::False(_) => Value::Bool(false),
Literal::String(s) => Value::String(s.as_str().to_owned()),
Literal::List(l) => {
let mut items = Vec::new();
for item in &l.items {
let value = self.eval_expr(&item)?;
items.push(value);
}
Value::List(items)
}
}),
}
}
}