Skip to main content

nargo_template/renderers/
liquid.rs

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