use crate::core::backend::GeneratedFile;
use crate::core::config::Language;
use crate::core::extension::{Extension, ExtensionConfig};
use crate::core::ir::ApiSurface;
use crate::core::template_env::TemplateEnv;
use anyhow::{Context as _, Result};
use serde::Deserialize;
use std::path::{Path, PathBuf};
#[derive(Debug, Clone, Deserialize)]
pub struct TemplateBlock {
pub name: String,
pub language: Language,
pub template: PathBuf,
pub output: PathBuf,
}
#[derive(Debug, Clone, Default)]
pub struct TemplateExtensionConfig {
pub blocks: Vec<TemplateBlock>,
}
pub struct TemplateExtension;
impl Extension for TemplateExtension {
fn name(&self) -> &str {
"template"
}
fn parse_config(&self, raw: Option<&toml::Value>) -> Result<ExtensionConfig> {
let Some(raw) = raw else {
return Ok(ExtensionConfig::empty());
};
let blocks: Vec<TemplateBlock> = raw
.clone()
.try_into()
.context("failed to parse [[extensions.template]] blocks")?;
Ok(ExtensionConfig::with_typed(
TemplateExtensionConfig { blocks },
Some(raw.clone()),
))
}
fn emit_for_language(
&self,
api: &ApiSurface,
cfg: &ExtensionConfig,
language: Language,
_env: &TemplateEnv,
) -> Result<Vec<GeneratedFile>> {
let Some(typed) = cfg.downcast::<TemplateExtensionConfig>() else {
return Ok(vec![]);
};
let mut files = Vec::new();
for block in typed.blocks.iter().filter(|b| b.language == language) {
let template_src = read_template(&block.template)?;
let mut local_env = minijinja::Environment::new();
local_env.set_trim_blocks(true);
local_env.set_lstrip_blocks(true);
local_env.set_keep_trailing_newline(true);
local_env
.add_template_owned(block.name.clone(), template_src)
.with_context(|| format!("failed to load template `{}`", block.template.display()))?;
let ctx = minijinja::context! {
crate_name => &api.crate_name,
version => &api.version,
language => language.to_string(),
};
let content = local_env
.get_template(&block.name)
.unwrap()
.render(ctx)
.with_context(|| format!("failed to render template `{}`", block.template.display()))?;
files.push(GeneratedFile {
path: block.output.clone(),
content,
generated_header: true,
});
}
Ok(files)
}
}
fn read_template(path: &Path) -> Result<String> {
std::fs::read_to_string(path).with_context(|| format!("failed to read template file `{}`", path.display()))
}