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 ¯o_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(¯o_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();
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(())
}
}