nargo-template 0.0.1

Nargo template engine management
Documentation
#![warn(missing_docs)]

use std::{collections::HashMap, path::Path};

use async_trait::async_trait;
use oak_core::Parser;
use oak_jinja::{JinjaLanguage, JinjaLexer, JinjaParser};

use super::TemplateRenderer;

/// Jinja2 模板渲染器
pub struct Jinja2Renderer {
    templates: HashMap<String, String>,
    language: JinjaLanguage,
}

impl Jinja2Renderer {
    /// 创建新的 Jinja2 渲染器
    pub fn new() -> Self {
        Self { templates: HashMap::new(), language: JinjaLanguage::default() }
    }

    /// 渲染模板内容
    fn render_content(&self, content: &str, context: &serde_json::Value) -> crate::TemplateResult<String> {
        // 这里使用 oak-jinja 解析模板,然后手动实现渲染逻辑
        // 由于 oak-jinja 只是一个解析器,没有内置渲染功能
        let _lexer = JinjaLexer::new(&self.language);
        let parser = JinjaParser::new(&self.language);

        // 创建解析会话
        let mut session = oak_core::parser::ParseSession::<JinjaLanguage>::new(16);

        // 解析模板
        let _output = parser.parse(content, &[], &mut session);

        // 简单实现:直接返回模板内容,替换变量
        // 注意:这只是一个临时实现,需要根据 oak-jinja 的 AST 结构实现完整的渲染逻辑
        let mut result = content.to_string();

        // 简单的变量替换
        if let serde_json::Value::Object(map) = context {
            for (key, value) in map {
                let placeholder = format!("{{{{ {} }}}}", key);
                let value_str = match value {
                    serde_json::Value::String(s) => s.clone(),
                    serde_json::Value::Number(n) => n.to_string(),
                    serde_json::Value::Bool(b) => b.to_string(),
                    _ => value.to_string(),
                };
                result = result.replace(&placeholder, &value_str);
            }
        }

        Ok(result)
    }
}

impl Default for Jinja2Renderer {
    fn default() -> Self {
        Self::new()
    }
}

#[async_trait]
impl TemplateRenderer for Jinja2Renderer {
    fn render(&self, template_name: &str, context: &serde_json::Value) -> crate::TemplateResult<String> {
        let template_content = self.templates.get(template_name).ok_or_else(|| std::io::Error::new(std::io::ErrorKind::NotFound, format!("Template '{}' not found", template_name)))?;

        self.render_content(template_content, context)
    }

    fn register_template(&mut self, name: &str, content: &str) -> crate::TemplateResult<()> {
        self.templates.insert(name.to_string(), content.to_string());
        Ok(())
    }

    fn register_template_file(&mut self, name: &str, path: &Path) -> crate::TemplateResult<()> {
        let content = std::fs::read_to_string(path).map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, format!("Failed to read template file '{}': {}", path.display(), e)))?;
        self.register_template(name, &content)
    }

    fn register_templates_from_dir(&mut self, dir: &Path, extension: Option<&str>) -> crate::TemplateResult<()> {
        let dir_path = dir;
        let ext = extension.unwrap_or("jinja2");

        for entry in walkdir::WalkDir::new(dir_path) {
            let entry = entry?;
            let path = entry.path();

            if path.is_file() {
                if let Some(file_ext) = path.extension() {
                    if file_ext == ext {
                        let relative_path = path.strip_prefix(dir_path).map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?;
                        let template_name = relative_path.with_extension("").to_string_lossy().replace(std::path::MAIN_SEPARATOR, "/");

                        self.register_template_file(&template_name, path)?;
                    }
                }
            }
        }

        Ok(())
    }
}