Skip to main content

nargo_template/renderers/
jinja.rs

1#![warn(missing_docs)]
2
3use std::{collections::HashMap, path::Path};
4
5use async_trait::async_trait;
6use oak_core::Parser;
7use oak_jinja::{JinjaLanguage, JinjaLexer, JinjaParser};
8
9use super::TemplateRenderer;
10
11/// Jinja2 模板渲染器
12pub struct Jinja2Renderer {
13    templates: HashMap<String, String>,
14    language: JinjaLanguage,
15}
16
17impl Jinja2Renderer {
18    /// 创建新的 Jinja2 渲染器
19    pub fn new() -> Self {
20        Self { templates: HashMap::new(), language: JinjaLanguage::default() }
21    }
22
23    /// 渲染模板内容
24    fn render_content(&self, content: &str, context: &serde_json::Value) -> crate::TemplateResult<String> {
25        // 这里使用 oak-jinja 解析模板,然后手动实现渲染逻辑
26        // 由于 oak-jinja 只是一个解析器,没有内置渲染功能
27        let _lexer = JinjaLexer::new(&self.language);
28        let parser = JinjaParser::new(&self.language);
29
30        // 创建解析会话
31        let mut session = oak_core::parser::ParseSession::<JinjaLanguage>::new(16);
32
33        // 解析模板
34        let _output = parser.parse(content, &[], &mut session);
35
36        // 简单实现:直接返回模板内容,替换变量
37        // 注意:这只是一个临时实现,需要根据 oak-jinja 的 AST 结构实现完整的渲染逻辑
38        let mut result = content.to_string();
39
40        // 简单的变量替换
41        if let serde_json::Value::Object(map) = context {
42            for (key, value) in map {
43                let placeholder = format!("{{{{ {} }}}}", key);
44                let value_str = match value {
45                    serde_json::Value::String(s) => s.clone(),
46                    serde_json::Value::Number(n) => n.to_string(),
47                    serde_json::Value::Bool(b) => b.to_string(),
48                    _ => value.to_string(),
49                };
50                result = result.replace(&placeholder, &value_str);
51            }
52        }
53
54        Ok(result)
55    }
56}
57
58impl Default for Jinja2Renderer {
59    fn default() -> Self {
60        Self::new()
61    }
62}
63
64#[async_trait]
65impl TemplateRenderer for Jinja2Renderer {
66    fn render(&self, template_name: &str, context: &serde_json::Value) -> crate::TemplateResult<String> {
67        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)))?;
68
69        self.render_content(template_content, context)
70    }
71
72    fn register_template(&mut self, name: &str, content: &str) -> crate::TemplateResult<()> {
73        self.templates.insert(name.to_string(), content.to_string());
74        Ok(())
75    }
76
77    fn register_template_file(&mut self, name: &str, path: &Path) -> crate::TemplateResult<()> {
78        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)))?;
79        self.register_template(name, &content)
80    }
81
82    fn register_templates_from_dir(&mut self, dir: &Path, extension: Option<&str>) -> crate::TemplateResult<()> {
83        let dir_path = dir;
84        let ext = extension.unwrap_or("jinja2");
85
86        for entry in walkdir::WalkDir::new(dir_path) {
87            let entry = entry?;
88            let path = entry.path();
89
90            if path.is_file() {
91                if let Some(file_ext) = path.extension() {
92                    if file_ext == ext {
93                        let relative_path = path.strip_prefix(dir_path).map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?;
94                        let template_name = relative_path.with_extension("").to_string_lossy().replace(std::path::MAIN_SEPARATOR, "/");
95
96                        self.register_template_file(&template_name, path)?;
97                    }
98                }
99            }
100        }
101
102        Ok(())
103    }
104}