use std::{
collections::BTreeMap,
path::{Path, PathBuf},
};
use femstache_eval::{Builtin, TemplateEnv, TemplateInputs, Value};
use femstache_loader::{TemplateLoader, TemplateRef, TemplateSource};
use femstache_parser::Template;
use femstache_types::TyContext;
use crate::{
FemstacheError, FemstacheResult, builtins,
output::{EscapeMode, Output},
renderer::TemplateRenderer,
};
pub struct CompilationContext {
pub ctx: TyContext,
pub loader: TemplateLoader,
pub escape: EscapeMode,
}
impl CompilationContext {
pub fn new(loader: TemplateLoader, escape: EscapeMode) -> Self {
let ctx = TyContext::new(loader.clone());
Self {
ctx,
loader,
escape,
}
}
}
pub struct CompiledTemplate<'ctx> {
ctx: &'ctx mut CompilationContext,
path: PathBuf,
template: TemplateRef,
builtins: Vec<(String, Builtin)>,
}
impl<'ctx> CompiledTemplate<'ctx> {
fn new(ctx: &'ctx mut CompilationContext, template: TemplateRef) -> FemstacheResult<Self> {
let builtins = builtins::add_builtins(ctx.ctx.store_mut());
for (name, builtin) in &builtins {
let fun_ty_id = builtin.instantiate(ctx.ctx.store_mut());
ctx.ctx.add_builtin(name, fun_ty_id);
}
let path = &template.meta().path;
ctx.ctx.infer_document(path, template.get().document())?;
Ok(Self {
ctx,
path: path.to_owned(),
template,
builtins,
})
}
pub fn render(&self, inputs: impl TemplateInputs + 'static) -> FemstacheResult<String> {
for (name, ty_id) in self.ctx.ctx.inputs() {
let Some(value) = inputs.get(name) else {
return Err(FemstacheError::VarNotFound(name.to_owned()));
};
if !value.conforms_to(self.ctx.ctx.store(), *ty_id) {
return Err(FemstacheError::VarInvalidType(name.to_owned()));
}
}
let mut env = TemplateEnv::new(inputs, self.path.clone());
for (name, builtin) in &self.builtins {
env.set(name, Value::Builtin(builtin.clone()));
}
let mut renderer = TemplateRenderer::new(env, BTreeMap::new(), self.ctx.loader.clone());
let mut output = Output::string(self.ctx.escape);
renderer.render_document(self.template.get().document(), &mut output)?;
Ok(output.into_inner())
}
}
#[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 new_ctx(&self) -> CompilationContext {
CompilationContext::new(self.loader.clone(), self.escape)
}
pub fn render(
&self,
path: impl AsRef<Path>,
inputs: impl TemplateInputs + 'static,
) -> FemstacheResult<String> {
let mut ctx = self.new_ctx();
let compiled = self.compile(&mut ctx, path)?;
compiled.render(inputs)
}
pub fn render_direct(
&self,
template: &Template,
inputs: impl TemplateInputs + 'static,
) -> FemstacheResult<String> {
let mut ctx = self.new_ctx();
let compiled = self.compile_direct(&mut ctx, template)?;
compiled.render(inputs)
}
pub fn compile<'ctx>(
&self,
ctx: &'ctx mut CompilationContext,
path: impl AsRef<Path>,
) -> FemstacheResult<CompiledTemplate<'ctx>> {
let path = path.as_ref();
let template_ref = self.loader.load(path)?;
CompiledTemplate::new(ctx, template_ref)
}
pub fn compile_direct<'ctx>(
&self,
ctx: &'ctx mut CompilationContext,
template: &Template,
) -> FemstacheResult<CompiledTemplate<'ctx>> {
let template_ref = self.loader.intern(template.clone())?;
CompiledTemplate::new(ctx, template_ref)
}
}
pub fn render(
template: &Template,
inputs: impl TemplateInputs + 'static,
) -> FemstacheResult<String> {
let femstache = Femstache::new(TemplateSource::none(), EscapeMode::None);
femstache.render_direct(template, inputs)
}