use std::collections::BTreeMap;
use femstache_eval::{EvalError, EvalErrorKind, StringValue, TemplateEnv, TemplateInput, Value};
use femstache_loader::{TemplateLoader, TemplateSource};
use femstache_parser::{Span, Template};
use femstache_types::{Ty, TyContext, TyError, TyStore};
use crate::{
FemstacheError, FemstacheResult,
output::{EscapeMode, Output},
renderer::TemplateRenderer,
};
fn make_builtins(store: &mut TyStore) -> Vec<(String, Value)> {
let mut builtins = Vec::new();
let arg_ty_id = store.intern(Ty::String);
let ret_ty_id = store.intern(Ty::String);
builtins.push((
"raw".to_owned(),
Value::builtin(arg_ty_id, ret_ty_id, |v| {
let Some(s) = v.into_string() else {
return Err(EvalError::new(EvalErrorKind::InvalidType, Span::new(0, 0)));
};
Ok(Value::String(match s {
StringValue::Regular(s) => StringValue::Raw(s),
StringValue::Raw(s) => StringValue::Raw(s),
}))
}),
));
builtins
}
#[derive(Debug)]
pub struct Femstache {
loader: TemplateLoader,
escape: EscapeMode,
}
impl Femstache {
pub fn new(source: TemplateSource, escape: EscapeMode) -> Self {
let loader = TemplateLoader::new(source);
Self { loader, escape }
}
pub fn render(&self, path: &str, inputs: &impl TemplateInput) -> FemstacheResult<String> {
let template = self.loader.load(path)?;
self.render_direct(&template, inputs)
}
pub fn render_direct(
&self,
template: &Template,
inputs: &impl TemplateInput,
) -> FemstacheResult<String> {
let mut ctx = TyContext::new(self.loader.clone());
let builtins = make_builtins(ctx.store_mut());
for (name, value) in &builtins {
let Some(builtin) = value.as_builtin() else {
panic!("only builtins are supported as builtins");
};
let fun_ty_id = ctx.store_mut().intern(Ty::Fun(builtin.arg, builtin.ret));
ctx.add_builtin(name, fun_ty_id);
}
ctx.infer_document(template.document())?;
for (name, ty_id) in ctx.inputs() {
let Some(value) = inputs.get(name) else {
return Err(FemstacheError::TypeError(TyError::VarNotFound(
name.to_owned(),
)));
};
if !value.conforms_to(ctx.store(), *ty_id) {
return Err(FemstacheError::TypeError(TyError::VarInvalidType(
name.to_owned(),
)));
}
}
let mut env = TemplateEnv::new(inputs);
for (name, value) in &builtins {
env.set(name, value.clone());
}
let mut renderer = TemplateRenderer::new(env, BTreeMap::new(), Some(self.loader.clone()));
let mut output = Output::string(self.escape);
renderer.render_document(template.document(), &mut output)?;
Ok(output.into_inner())
}
}
pub fn render(template: &Template, inputs: &impl TemplateInput) -> FemstacheResult<String> {
let femstache = Femstache::new(TemplateSource::none(), EscapeMode::None);
femstache.render_direct(template, inputs)
}