use anyhow::{Context, Result};
use std::collections::HashMap;
use std::path::{Path, PathBuf};
pub struct TemplateEngine {
template_dir: PathBuf,
}
impl TemplateEngine {
pub fn new(template_dir: impl Into<PathBuf>) -> Self {
Self {
template_dir: template_dir.into(),
}
}
pub fn default() -> Self {
let template_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("templates");
Self::new(template_dir)
}
pub fn render(&self, template_path: &str, variables: &HashMap<&str, &str>) -> Result<String> {
let full_path = self.template_dir.join(template_path);
let template_content =
std::fs::read_to_string(&full_path).context(format!("Failed to read template: {}", full_path.display()))?;
let mut result = template_content;
for (key, value) in variables {
let placeholder = format!("{{{{{}}}}}", key);
result = result.replace(&placeholder, value);
}
Ok(result)
}
pub async fn render_to_file(
&self,
template_path: &str,
output_path: impl AsRef<Path>,
variables: &HashMap<&str, &str>,
) -> Result<()> {
let rendered = self.render(template_path, variables)?;
tokio::fs::write(output_path.as_ref(), rendered)
.await
.context(format!("Failed to write file: {}", output_path.as_ref().display()))?;
Ok(())
}
pub fn list_templates(&self, category: &str) -> Result<Vec<String>> {
let category_path = self.template_dir.join(category);
if !category_path.exists() {
return Ok(Vec::new());
}
let mut templates = Vec::new();
let entries = std::fs::read_dir(&category_path).context(format!(
"Failed to read template directory: {}",
category_path.display()
))?;
for entry in entries {
let entry = entry?;
let path = entry.path();
if path.is_file() {
if let Some(file_name) = path.file_name().and_then(|n| n.to_str()) {
if file_name.ends_with(".template") {
let name = file_name.trim_end_matches(".template").to_string();
templates.push(name);
}
}
}
}
templates.sort();
Ok(templates)
}
pub fn template_exists(&self, template_path: &str) -> bool {
self.template_dir.join(template_path).exists()
}
}
pub struct TemplateVars {
vars: HashMap<String, String>,
}
impl TemplateVars {
pub fn new() -> Self {
Self { vars: HashMap::new() }
}
#[allow(dead_code)]
pub fn add(&mut self, key: impl Into<String>, value: impl Into<String>) -> &mut Self {
self.vars.insert(key.into(), value.into());
self
}
pub fn add_name(&mut self, name: &str) -> &mut Self {
use crate::utils::{to_pascal_case, to_snake_case};
self.vars.insert("name".to_string(), name.to_string());
self.vars.insert("PascalName".to_string(), to_pascal_case(name));
self.vars.insert("snake_name".to_string(), to_snake_case(name));
self.vars
.insert("UPPER_NAME".to_string(), to_snake_case(name).to_uppercase());
self
}
pub fn to_hashmap(&self) -> HashMap<&str, &str> {
self.vars.iter().map(|(k, v)| (k.as_str(), v.as_str())).collect()
}
}
impl Default for TemplateVars {
fn default() -> Self {
Self::new()
}
}