net_shell/
template.rs

1use anyhow::{anyhow, Result};
2use regex::Regex;
3use std::collections::HashMap;
4use std::fs;
5use std::path::Path;
6
7/// 模板引擎结构体
8pub struct TemplateEngine {
9    /// 变量映射
10    variables: HashMap<String, serde_json::Value>,
11    /// 模板目录路径
12    template_dir: Option<String>,
13    /// 左定界符
14    left_delimiter: String,
15    /// 右定界符
16    right_delimiter: String,
17    /// for循环左定界符
18    for_left_delimiter: String,
19    /// for循环右定界符
20    for_right_delimiter: String,
21    /// 是否保留循环中的换行符
22    preserve_loop_newlines: bool,
23    /// 变量正则表达式
24    var_regex: Regex,
25    /// for循环正则表达式
26    for_regex: Regex,
27    /// include正则表达式
28    include_regex: Regex,
29}
30
31impl TemplateEngine {
32    /// 创建新的模板引擎实例
33    pub fn new() -> Self {
34        Self::with_delimiters("{{", "}}")
35    }
36
37    /// 使用自定义定界符创建模板引擎实例
38    pub fn with_delimiters(left: &str, right: &str) -> Self {
39        Self::with_all_delimiters(left, right, "{%", "%}")
40    }
41
42    /// 使用自定义定界符创建模板引擎实例,包括for循环定界符
43    pub fn with_all_delimiters(
44        var_left: &str,
45        var_right: &str,
46        for_left: &str,
47        for_right: &str,
48    ) -> Self {
49        let var_left_escaped = regex::escape(var_left);
50        let var_right_escaped = regex::escape(var_right);
51        let for_left_escaped = regex::escape(for_left);
52        let for_right_escaped = regex::escape(for_right);
53
54        // 变量匹配正则:{{ variable }}
55        let var_pattern = format!(
56            r"{}\s*([a-zA-Z_][a-zA-Z0-9_]*(?:\.[a-zA-Z_][a-zA-Z0-9_]*)*)\s*{}",
57            var_left_escaped, var_right_escaped
58        );
59        let var_regex = Regex::new(&var_pattern).unwrap();
60
61        // for循环匹配正则:{% for item in items %}   ... {% endfor %}
62        // 支持split语法:{% for item in items split "," %}   ... {% endfor %}
63        let for_pattern = format!(
64            "(?s){}\\s*for\\s+(\\w+)\\s+in\\s+(\\w+)(?:\\s+split\\s+\"([^\"]+)\")?\\s*{}(.*?){}\\s*endfor\\s*{}",
65            for_left_escaped, for_right_escaped, for_left_escaped, for_right_escaped
66        );
67        let for_regex = Regex::new(&for_pattern).unwrap();
68
69        // include匹配正则:{% include "template.html" %}
70        let include_pattern = format!(
71            "{}\\s*include\\s+\"([^\"]+)\"\\s*{}",
72            for_left_escaped, for_right_escaped
73        );
74        let include_regex = Regex::new(&include_pattern).unwrap();
75
76        Self {
77            variables: HashMap::new(),
78            template_dir: None,
79            left_delimiter: var_left.to_string(),
80            right_delimiter: var_right.to_string(),
81            for_left_delimiter: for_left.to_string(),
82            for_right_delimiter: for_right.to_string(),
83            preserve_loop_newlines: true, // 默认保留换行符,保持向后兼容
84            var_regex,
85            for_regex,
86            include_regex,
87        }
88    }
89
90    /// 设置模板目录
91    pub fn set_template_dir<P: AsRef<Path>>(&mut self, path: P) -> &mut Self {
92        self.template_dir = Some(path.as_ref().to_string_lossy().to_string());
93        self
94    }
95
96    /// 设置变量
97    pub fn set_variable<K: Into<String>, V: Into<serde_json::Value>>(
98        &mut self,
99        key: K,
100        value: V,
101    ) -> &mut Self {
102        self.variables.insert(key.into(), value.into());
103        self
104    }
105
106    /// 批量设置变量
107    pub fn set_variables(&mut self, vars: HashMap<String, serde_json::Value>) -> &mut Self {
108        for (key, value) in vars {
109            self.variables.insert(key, value);
110        }
111        self
112    }
113
114    /// 设置是否保留循环中的换行符
115    pub fn set_preserve_loop_newlines(&mut self, preserve: bool) -> &mut Self {
116        self.preserve_loop_newlines = preserve;
117        self
118    }
119
120    /// 渲染模板字符串
121    pub fn render_string(&self, template: &str) -> Result<String> {
122        let mut result = template.to_string();
123
124        // 1. 处理include指令
125        result = self.process_includes(&result)?;
126
127        // 2. 处理for循环
128        result = self.process_for_loops(&result)?;
129
130        // 3. 处理变量替换
131        result = self.process_variables(&result)?;
132
133        Ok(result)
134    }
135
136    /// 渲染模板文件
137    pub fn render_file<P: AsRef<Path>>(&self, template_path: P) -> Result<String> {
138        let template_content = fs::read_to_string(template_path)?;
139        self.render_string(&template_content)
140    }
141
142    /// 处理include指令
143    fn process_includes(&self, template: &str) -> Result<String> {
144        let mut result = template.to_string();
145
146        while let Some(captures) = self.include_regex.captures(&result) {
147            let full_match = captures.get(0).unwrap().as_str();
148            let template_name = captures.get(1).unwrap().as_str();
149
150            let included_content = if let Some(ref dir) = self.template_dir {
151                let full_path = Path::new(dir).join(template_name);
152                fs::read_to_string(full_path)
153                    .map_err(|e| anyhow!("Failed to include template '{}': {}", template_name, e))?
154            } else {
155                return Err(anyhow!(
156                    "Template directory not set for include: {}",
157                    template_name
158                ));
159            };
160
161            result = result.replace(full_match, &included_content);
162        }
163
164        Ok(result)
165    }
166
167    /// 处理for循环
168    fn process_for_loops(&self, template: &str) -> Result<String> {
169        let mut result = template.to_string();
170
171        while let Some(captures) = self.for_regex.captures(&result) {
172            let full_match = captures.get(0).unwrap().as_str();
173            let item_name = captures.get(1).unwrap().as_str();
174            let array_name = captures.get(2).unwrap().as_str();
175            let split_delimiter = captures.get(3).map(|m| m.as_str());
176            let loop_content = captures.get(4).unwrap().as_str();
177
178            let array_value = self
179                .variables
180                .get(array_name)
181                .ok_or_else(|| anyhow!("Array '{}' not found in variables", array_name))?;
182
183            // 根据是否有split参数处理不同的数据类型
184            let items: Vec<serde_json::Value> = if let Some(delimiter) = split_delimiter {
185                // 处理split操作
186                match array_value {
187                    serde_json::Value::String(s) => {
188                        s.split(delimiter)
189                            .map(|part| serde_json::Value::String(part.to_string()))
190                            .collect()
191                    }
192                    _ => {
193                        return Err(anyhow!(
194                            "Cannot split non-string variable '{}'",
195                            array_name
196                        ))
197                    }
198                }
199            } else {
200                // 处理数组
201                if let serde_json::Value::Array(items) = array_value {
202                    items.clone()
203                } else {
204                    return Err(anyhow!("'{}' is not an array", array_name));
205                }
206            };
207
208            let mut loop_result = String::new();
209
210            for item in items {
211                let mut temp_vars = self.variables.clone();
212                temp_vars.insert(item_name.to_string(), item.clone());
213
214                let temp_engine = Self {
215                    variables: temp_vars,
216                    template_dir: self.template_dir.clone(),
217                    left_delimiter: self.left_delimiter.clone(),
218                    right_delimiter: self.right_delimiter.clone(),
219                    for_left_delimiter: self.for_left_delimiter.clone(),
220                    for_right_delimiter: self.for_right_delimiter.clone(),
221                    preserve_loop_newlines: self.preserve_loop_newlines,
222                    var_regex: self.var_regex.clone(),
223                    for_regex: self.for_regex.clone(),
224                    include_regex: self.include_regex.clone(),
225                };
226
227                let mut rendered = temp_engine.process_variables(loop_content)?;
228
229                // 如果不保留换行符,则去除循环产生的空行,但保留内容内的换行符和缩进
230                if !self.preserve_loop_newlines {
231                    // 按行分割,过滤掉只包含空白字符的行
232                    let lines: Vec<&str> = rendered
233                        .lines()
234                        .filter(|line| !line.trim().is_empty())
235                        .collect();
236
237                    // 重新组合,保留原有的缩进和格式
238                    if !lines.is_empty() {
239                        rendered = lines.join("\n");
240
241                        // 如果不是第一个循环项,在前面添加换行符
242                        if !loop_result.is_empty() {
243                            loop_result.push_str("\n");
244                        }
245                    } else {
246                        rendered = String::new();
247                    }
248                }
249
250                loop_result.push_str(&rendered);
251            }
252
253            result = result.replace(full_match, &loop_result);
254        }
255
256        Ok(result)
257    }
258
259    /// 处理变量替换
260    fn process_variables(&self, template: &str) -> Result<String> {
261        let mut result = template.to_string();
262
263        while let Some(captures) = self.var_regex.captures(&result) {
264            let full_match = captures.get(0).unwrap().as_str();
265            let variable_path = captures.get(1).unwrap().as_str();
266
267            let value = self.get_variable_value(variable_path)?;
268            let value_str = match value {
269                serde_json::Value::String(s) => s.clone(),
270                v => v.to_string(),
271            };
272
273            result = result.replace(full_match, &value_str);
274        }
275
276        Ok(result)
277    }
278
279    /// 获取变量值,支持点号路径访问嵌套对象
280    fn get_variable_value(&self, path: &str) -> Result<serde_json::Value> {
281        let parts: Vec<&str> = path.split('.').collect();
282
283        if parts.is_empty() {
284            return Err(anyhow!("Empty variable path"));
285        }
286
287        let current = self
288            .variables
289            .get(parts[0])
290            .ok_or_else(|| anyhow!("Variable '{}' not found", parts[0]))?;
291
292        if parts.len() == 1 {
293            return Ok(current.clone());
294        }
295
296        let mut result = current;
297        for part in &parts[1..] {
298            match result {
299                serde_json::Value::Object(map) => {
300                    result = map
301                        .get(*part)
302                        .ok_or_else(|| anyhow!("Property '{}' not found in variable", part))?;
303                }
304                _ => {
305                    return Err(anyhow!(
306                        "Cannot access property '{}' on non-object value",
307                        part
308                    ))
309                }
310            }
311        }
312
313        Ok(result.clone())
314    }
315}
316
317impl Default for TemplateEngine {
318    fn default() -> Self {
319        Self::new()
320    }
321}
322
323#[cfg(test)]
324mod tests {
325    use super::*;
326    use serde_json::json;
327    use tracing::instrument::WithSubscriber;
328
329    #[test]
330    fn test_variable_replacement() {
331        let mut engine = TemplateEngine::new();
332        engine.set_variable("name", "World");
333        engine.set_variable("age", 25);
334
335        let result = engine
336            .render_string("Hello, {{ name }}! You are {{ age }} years old.")
337            .unwrap();
338        assert_eq!(result, "Hello, World! You are 25 years old.");
339    }
340
341    #[test]
342    fn test_nested_variable_access() {
343        let mut engine = TemplateEngine::new();
344        engine.set_variable(
345            "user",
346            json!({
347                "name": "Alice",
348                "profile": {
349                    "age": 30,
350                    "city": "Beijing"
351                }
352            }),
353        );
354
355        let result = engine
356            .render_string("Name: {{ user.name }}, City: {{ user.profile.city }}")
357            .unwrap();
358        assert_eq!(result, "Name: Alice, City: Beijing");
359    }
360
361    #[test]
362    fn test_for_loop() {
363        let mut engine = TemplateEngine::with_all_delimiters("{{", "}}", "#{%", "%}");
364        engine.set_variable("items", json!(["apple", "banana", "cherry"]));
365
366        let template = r#"
367#{% for item in items %}
368        - {{ item }}
369#{% endfor %}"#;
370
371        let result = engine
372            .set_preserve_loop_newlines(false)
373            .render_string(template)
374            .unwrap();
375        let expected = r#"
376        - apple
377        - banana
378        - cherry"#;
379        assert_eq!(result, expected);
380    }
381
382    #[test]
383    fn test_custom_delimiters() {
384        let mut engine = TemplateEngine::with_delimiters("${", "}");
385        engine.set_variable("name", "Custom");
386
387        let result = engine.render_string("Hello, ${ name }!").unwrap();
388        assert_eq!(result, "Hello, Custom!");
389    }
390
391    #[test]
392    fn test_complex_template() {
393        let mut engine = TemplateEngine::new();
394        engine.set_variable("title", "User List");
395        engine.set_variable(
396            "users",
397            json!( [
398            {"name": "Alice", "age": 25},
399            {"name": "Bob", "age": 30},
400            {"name": "Charlie", "age": 35}
401        ] ),
402        );
403
404        let template = r#"
405        <h1>{{ title }}</h1>
406        <ul>
407        {% for user in users %}
408            <li>{{ user.name }} ({{ user.age }} years old)</li>
409        {% endfor %}
410        </ul>"#;
411
412        let result = engine.render_string(template).unwrap();
413
414        assert!(result.contains("<h1>User List</h1>"));
415        assert!(result.contains("<li>Alice (25 years old)</li>"));
416        assert!(result.contains("<li>Bob (30 years old)</li>"));
417        assert!(result.contains("<li>Charlie (35 years old)</li>"));
418    }
419
420    #[test]
421    fn test_custom_for_tags() {
422        let mut engine = TemplateEngine::with_all_delimiters("{{", "}}", "<%", "%>");
423        engine.set_variable("items", json!(["red", "green", "blue"]));
424
425        let template = r#"
426<% for color in items %>
427* {{ color }}
428<% endfor %>"#;
429
430        let result = engine.render_string(template).unwrap();
431
432        assert!(result.contains("* red"));
433        assert!(result.contains("* green"));
434        assert!(result.contains("* blue"));
435    }
436
437    #[test]
438    fn test_preserve_loop_newlines() {
439        // 测试默认保留换行符
440        let mut engine = TemplateEngine::new();
441        engine.set_variable("items", json!(["a", "b", "c"]));
442
443        let template = r#"
444{% for item in items %}
445- {{ item }}
446{% endfor %}"#;
447
448        let result = engine.render_string(template).unwrap();
449
450        // 默认应该保留换行符
451        assert!(result.contains("\n- a\n"));
452        assert!(result.contains("\n- b\n"));
453        assert!(result.contains("\n- c\n"));
454
455        // 测试不保留换行符
456        engine.set_preserve_loop_newlines(false);
457        let result = engine.render_string(template).unwrap();
458
459        // 不应该有多余的空行
460        assert!(result.contains("- a\n- b\n- c"));
461    }
462
463    #[test]
464    fn test_split_functionality() {
465        let mut engine = TemplateEngine::new();
466        engine.set_variable("csv_string", "apple,banana,cherry");
467
468        let template = r#"
469{% for fruit in csv_string split "," %}
470- {{ fruit }}
471{% endfor %}"#;
472
473        let result = engine
474            .set_preserve_loop_newlines(false)
475            .render_string(template)
476            .unwrap();
477        
478        let expected = r#"
479- apple
480- banana
481- cherry"#;
482        assert_eq!(result, expected);
483    }
484
485    #[test]
486    fn test_split_with_space_delimiter() {
487        let mut engine = TemplateEngine::new();
488        engine.set_variable("space_separated", "red green blue");
489
490        let template = r#"
491{% for color in space_separated split " " %}
492* {{ color }}
493{% endfor %}"#;
494
495        let result = engine
496            .set_preserve_loop_newlines(false)
497            .render_string(template)
498            .unwrap();
499        
500        assert!(result.contains("* red"));
501        assert!(result.contains("* green"));
502        assert!(result.contains("* blue"));
503    }
504
505    #[test]
506    fn test_split_with_complex_delimiter() {
507        let mut engine = TemplateEngine::new();
508        engine.set_variable("complex_string", "item1||item2||item3");
509
510        let template = r#"
511{% for item in complex_string split "||" %}
512{{ item }}
513{% endfor %}"#;
514
515        let result = engine
516            .set_preserve_loop_newlines(false)
517            .render_string(template)
518            .unwrap();
519        
520        assert!(result.contains("item1"));
521        assert!(result.contains("item2"));
522        assert!(result.contains("item3"));
523    }
524}