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        // 支持jsonparse语法:{% for item in items jsonparse %}   ... {% endfor %}
64        let for_pattern = format!(
65            "(?s){}\\s*for\\s+(\\w+)\\s+in\\s+(\\w+)(?:\\s+(split|jsonparse)(?:\\s+\"([^\"]+)\")?)?\\s*{}(.*?){}\\s*endfor\\s*{}",
66            for_left_escaped, for_right_escaped, for_left_escaped, for_right_escaped
67        );
68        let for_regex = Regex::new(&for_pattern).unwrap();
69
70        // include匹配正则:{% include "template.html" %}
71        let include_pattern = format!(
72            "{}\\s*include\\s+\"([^\"]+)\"\\s*{}",
73            for_left_escaped, for_right_escaped
74        );
75        let include_regex = Regex::new(&include_pattern).unwrap();
76
77        Self {
78            variables: HashMap::new(),
79            template_dir: None,
80            left_delimiter: var_left.to_string(),
81            right_delimiter: var_right.to_string(),
82            for_left_delimiter: for_left.to_string(),
83            for_right_delimiter: for_right.to_string(),
84            preserve_loop_newlines: true, // 默认保留换行符,保持向后兼容
85            var_regex,
86            for_regex,
87            include_regex,
88        }
89    }
90
91    /// 设置模板目录
92    pub fn set_template_dir<P: AsRef<Path>>(&mut self, path: P) -> &mut Self {
93        self.template_dir = Some(path.as_ref().to_string_lossy().to_string());
94        self
95    }
96
97    /// 设置变量
98    pub fn set_variable<K: Into<String>, V: Into<serde_json::Value>>(
99        &mut self,
100        key: K,
101        value: V,
102    ) -> &mut Self {
103        self.variables.insert(key.into(), value.into());
104        self
105    }
106
107    /// 批量设置变量
108    pub fn set_variables(&mut self, vars: HashMap<String, serde_json::Value>) -> &mut Self {
109        for (key, value) in vars {
110            self.variables.insert(key, value);
111        }
112        self
113    }
114
115    /// 设置是否保留循环中的换行符
116    pub fn set_preserve_loop_newlines(&mut self, preserve: bool) -> &mut Self {
117        self.preserve_loop_newlines = preserve;
118        self
119    }
120
121    /// 渲染模板字符串
122    pub fn render_string(&self, template: &str) -> Result<String> {
123        let mut result = template.to_string();
124
125        // 1. 处理include指令
126        result = self.process_includes(&result)?;
127
128        // 2. 处理for循环
129        result = self.process_for_loops(&result)?;
130
131        // 3. 处理变量替换
132        result = self.process_variables(&result)?;
133
134        Ok(result)
135    }
136
137    /// 渲染模板文件
138    pub fn render_file<P: AsRef<Path>>(&self, template_path: P) -> Result<String> {
139        let template_content = fs::read_to_string(template_path)?;
140        self.render_string(&template_content)
141    }
142
143    /// 处理include指令
144    fn process_includes(&self, template: &str) -> Result<String> {
145        let mut result = template.to_string();
146
147        while let Some(captures) = self.include_regex.captures(&result) {
148            let full_match = captures.get(0).unwrap().as_str();
149            let template_name = captures.get(1).unwrap().as_str();
150
151            let included_content = if let Some(ref dir) = self.template_dir {
152                let full_path = Path::new(dir).join(template_name);
153                fs::read_to_string(full_path)
154                    .map_err(|e| anyhow!("Failed to include template '{}': {}", template_name, e))?
155            } else {
156                return Err(anyhow!(
157                    "Template directory not set for include: {}",
158                    template_name
159                ));
160            };
161
162            result = result.replace(full_match, &included_content);
163        }
164
165        Ok(result)
166    }
167
168    /// 处理for循环
169    fn process_for_loops(&self, template: &str) -> Result<String> {
170        let mut result = template.to_string();
171
172        while let Some(captures) = self.for_regex.captures(&result) {
173            let full_match = captures.get(0).unwrap().as_str();
174            let item_name = captures.get(1).unwrap().as_str();
175            let array_name = captures.get(2).unwrap().as_str();
176            let operation = captures.get(3).map(|m| m.as_str());
177            let operation_param = captures.get(4).map(|m| m.as_str());
178            let loop_content = captures.get(5).unwrap().as_str();
179
180            let array_value = self
181                .variables
182                .get(array_name)
183                .ok_or_else(|| anyhow!("Array '{}' not found in variables", array_name))?;
184
185            // 根据操作类型处理不同的数据类型
186            let items: Vec<serde_json::Value> = match operation {
187                Some("split") => {
188                    // 处理split操作
189                    let delimiter = operation_param.ok_or_else(|| anyhow!("Split operation requires a delimiter"))?;
190                    match array_value {
191                        serde_json::Value::String(s) => {
192                            s.split(delimiter)
193                                .map(|part| serde_json::Value::String(part.to_string()))
194                                .collect()
195                        }
196                        _ => {
197                            return Err(anyhow!(
198                                "Cannot split non-string variable '{}'",
199                                array_name
200                            ))
201                        }
202                    }
203                }
204                Some("jsonparse") => {
205                    // 处理jsonparse操作
206                    match array_value {
207                        serde_json::Value::String(s) => {
208                            let parsed: serde_json::Value = serde_json::from_str(s)
209                                .map_err(|e| anyhow!("Failed to parse JSON from variable '{}': {}", array_name, e))?;
210                            
211                            match parsed {
212                                serde_json::Value::Array(arr) => arr,
213                                serde_json::Value::Object(obj) => {
214                                    // 如果是对象,转换为键值对数组
215                                    obj.into_iter()
216                                        .map(|(k, v)| {
217                                            serde_json::json!({
218                                                "key": k,
219                                                "value": v
220                                            })
221                                        })
222                                        .collect()
223                                }
224                                _ => {
225                                    return Err(anyhow!(
226                                        "JSON must be an array or object for iteration, got: {}",
227                                        parsed
228                                    ))
229                                }
230                            }
231                        }
232                        _ => {
233                            return Err(anyhow!(
234                                "Cannot jsonparse non-string variable '{}'",
235                                array_name
236                            ))
237                        }
238                    }
239                }
240                None => {
241                    // 处理普通数组
242                    if let serde_json::Value::Array(items) = array_value {
243                        items.clone()
244                    } else {
245                        return Err(anyhow!("'{}' is not an array", array_name));
246                    }
247                }
248                _ => {
249                    return Err(anyhow!("Unknown operation: {}", operation.unwrap()));
250                }
251            };
252
253            let mut loop_result = String::new();
254
255            for item in items {
256                let mut temp_vars = self.variables.clone();
257                temp_vars.insert(item_name.to_string(), item.clone());
258
259                let temp_engine = Self {
260                    variables: temp_vars,
261                    template_dir: self.template_dir.clone(),
262                    left_delimiter: self.left_delimiter.clone(),
263                    right_delimiter: self.right_delimiter.clone(),
264                    for_left_delimiter: self.for_left_delimiter.clone(),
265                    for_right_delimiter: self.for_right_delimiter.clone(),
266                    preserve_loop_newlines: self.preserve_loop_newlines,
267                    var_regex: self.var_regex.clone(),
268                    for_regex: self.for_regex.clone(),
269                    include_regex: self.include_regex.clone(),
270                };
271
272                let mut rendered = temp_engine.process_variables(loop_content)?;
273
274                // 如果不保留换行符,则去除循环产生的空行,但保留内容内的换行符和缩进
275                if !self.preserve_loop_newlines {
276                    // 按行分割,过滤掉只包含空白字符的行
277                    let lines: Vec<&str> = rendered
278                        .lines()
279                        .filter(|line| !line.trim().is_empty())
280                        .collect();
281
282                    // 重新组合,保留原有的缩进和格式
283                    if !lines.is_empty() {
284                        rendered = lines.join("\n");
285
286                        // 如果不是第一个循环项,在前面添加换行符
287                        if !loop_result.is_empty() {
288                            loop_result.push_str("\n");
289                        }
290                    } else {
291                        rendered = String::new();
292                    }
293                }
294
295                loop_result.push_str(&rendered);
296            }
297
298            result = result.replace(full_match, &loop_result);
299        }
300
301        Ok(result)
302    }
303
304    /// 处理变量替换
305    fn process_variables(&self, template: &str) -> Result<String> {
306        let mut result = template.to_string();
307
308        while let Some(captures) = self.var_regex.captures(&result) {
309            let full_match = captures.get(0).unwrap().as_str();
310            let variable_path = captures.get(1).unwrap().as_str();
311
312            let value = self.get_variable_value(variable_path)?;
313            let value_str = match value {
314                serde_json::Value::String(s) => s.clone(),
315                v => v.to_string(),
316            };
317
318            result = result.replace(full_match, &value_str);
319        }
320
321        Ok(result)
322    }
323
324    /// 获取变量值,支持点号路径访问嵌套对象
325    fn get_variable_value(&self, path: &str) -> Result<serde_json::Value> {
326        let parts: Vec<&str> = path.split('.').collect();
327
328        if parts.is_empty() {
329            return Err(anyhow!("Empty variable path"));
330        }
331
332        let current = self
333            .variables
334            .get(parts[0])
335            .ok_or_else(|| anyhow!("Variable '{}' not found", parts[0]))?;
336
337        if parts.len() == 1 {
338            return Ok(current.clone());
339        }
340
341        let mut result = current;
342        for part in &parts[1..] {
343            match result {
344                serde_json::Value::Object(map) => {
345                    result = map
346                        .get(*part)
347                        .ok_or_else(|| anyhow!("Property '{}' not found in variable", part))?;
348                }
349                _ => {
350                    return Err(anyhow!(
351                        "Cannot access property '{}' on non-object value",
352                        part
353                    ))
354                }
355            }
356        }
357
358        Ok(result.clone())
359    }
360}
361
362impl Default for TemplateEngine {
363    fn default() -> Self {
364        Self::new()
365    }
366}
367
368#[cfg(test)]
369mod tests {
370    use super::*;
371    use serde_json::json;
372    use tracing::instrument::WithSubscriber;
373
374    #[test]
375    fn test_variable_replacement() {
376        let mut engine = TemplateEngine::new();
377        engine.set_variable("name", "World");
378        engine.set_variable("age", 25);
379
380        let result = engine
381            .render_string("Hello, {{ name }}! You are {{ age }} years old.")
382            .unwrap();
383        assert_eq!(result, "Hello, World! You are 25 years old.");
384    }
385
386    #[test]
387    fn test_nested_variable_access() {
388        let mut engine = TemplateEngine::new();
389        engine.set_variable(
390            "user",
391            json!({
392                "name": "Alice",
393                "profile": {
394                    "age": 30,
395                    "city": "Beijing"
396                }
397            }),
398        );
399
400        let result = engine
401            .render_string("Name: {{ user.name }}, City: {{ user.profile.city }}")
402            .unwrap();
403        assert_eq!(result, "Name: Alice, City: Beijing");
404    }
405
406    #[test]
407    fn test_for_loop() {
408        let mut engine = TemplateEngine::with_all_delimiters("{{", "}}", "#{%", "%}");
409        engine.set_variable("items", json!(["apple", "banana", "cherry"]));
410
411        let template = r#"
412#{% for item in items %}
413        - {{ item }}
414#{% endfor %}"#;
415
416        let result = engine
417            .set_preserve_loop_newlines(false)
418            .render_string(template)
419            .unwrap();
420        let expected = r#"
421        - apple
422        - banana
423        - cherry"#;
424        assert_eq!(result, expected);
425    }
426
427    #[test]
428    fn test_custom_delimiters() {
429        let mut engine = TemplateEngine::with_delimiters("${", "}");
430        engine.set_variable("name", "Custom");
431
432        let result = engine.render_string("Hello, ${ name }!").unwrap();
433        assert_eq!(result, "Hello, Custom!");
434    }
435
436    #[test]
437    fn test_complex_template() {
438        let mut engine = TemplateEngine::new();
439        engine.set_variable("title", "User List");
440        engine.set_variable(
441            "users",
442            json!( [
443            {"name": "Alice", "age": 25},
444            {"name": "Bob", "age": 30},
445            {"name": "Charlie", "age": 35}
446        ] ),
447        );
448
449        let template = r#"
450        <h1>{{ title }}</h1>
451        <ul>
452        {% for user in users %}
453            <li>{{ user.name }} ({{ user.age }} years old)</li>
454        {% endfor %}
455        </ul>"#;
456
457        let result = engine.render_string(template).unwrap();
458
459        assert!(result.contains("<h1>User List</h1>"));
460        assert!(result.contains("<li>Alice (25 years old)</li>"));
461        assert!(result.contains("<li>Bob (30 years old)</li>"));
462        assert!(result.contains("<li>Charlie (35 years old)</li>"));
463    }
464
465    #[test]
466    fn test_custom_for_tags() {
467        let mut engine = TemplateEngine::with_all_delimiters("{{", "}}", "<%", "%>");
468        engine.set_variable("items", json!(["red", "green", "blue"]));
469
470        let template = r#"
471<% for color in items %>
472* {{ color }}
473<% endfor %>"#;
474
475        let result = engine.render_string(template).unwrap();
476
477        assert!(result.contains("* red"));
478        assert!(result.contains("* green"));
479        assert!(result.contains("* blue"));
480    }
481
482    #[test]
483    fn test_preserve_loop_newlines() {
484        // 测试默认保留换行符
485        let mut engine = TemplateEngine::new();
486        engine.set_variable("items", json!(["a", "b", "c"]));
487
488        let template = r#"
489{% for item in items %}
490- {{ item }}
491{% endfor %}"#;
492
493        let result = engine.render_string(template).unwrap();
494
495        // 默认应该保留换行符
496        assert!(result.contains("\n- a\n"));
497        assert!(result.contains("\n- b\n"));
498        assert!(result.contains("\n- c\n"));
499
500        // 测试不保留换行符
501        engine.set_preserve_loop_newlines(false);
502        let result = engine.render_string(template).unwrap();
503
504        // 不应该有多余的空行
505        assert!(result.contains("- a\n- b\n- c"));
506    }
507
508    #[test]
509    fn test_split_functionality() {
510        let mut engine = TemplateEngine::new();
511        engine.set_variable("csv_string", "apple,banana,cherry");
512
513        let template = r#"
514{% for fruit in csv_string split "," %}
515- {{ fruit }}
516{% endfor %}"#;
517
518        let result = engine
519            .set_preserve_loop_newlines(false)
520            .render_string(template)
521            .unwrap();
522        
523        let expected = r#"
524- apple
525- banana
526- cherry"#;
527        assert_eq!(result, expected);
528    }
529
530    #[test]
531    fn test_split_with_space_delimiter() {
532        let mut engine = TemplateEngine::new();
533        engine.set_variable("space_separated", "red green blue");
534
535        let template = r#"
536{% for color in space_separated split " " %}
537* {{ color }}
538{% endfor %}"#;
539
540        let result = engine
541            .set_preserve_loop_newlines(false)
542            .render_string(template)
543            .unwrap();
544        
545        assert!(result.contains("* red"));
546        assert!(result.contains("* green"));
547        assert!(result.contains("* blue"));
548    }
549
550    #[test]
551    fn test_split_with_complex_delimiter() {
552        let mut engine = TemplateEngine::new();
553        engine.set_variable("complex_string", "item1||item2||item3");
554
555        let template = r#"
556{% for item in complex_string split "||" %}
557{{ item }}
558{% endfor %}"#;
559
560        let result = engine
561            .set_preserve_loop_newlines(false)
562            .render_string(template)
563            .unwrap();
564        
565        assert!(result.contains("item1"));
566        assert!(result.contains("item2"));
567        assert!(result.contains("item3"));
568    }
569
570    #[test]
571    fn test_jsonparse_with_array() {
572        let mut engine = TemplateEngine::new();
573        engine.set_variable("json_string", r#"["apple", "banana", "cherry"]"#);
574
575        let template = r#"
576{% for fruit in json_string jsonparse %}
577- {{ fruit }}
578{% endfor %}"#;
579
580        let result = engine
581            .set_preserve_loop_newlines(false)
582            .render_string(template)
583            .unwrap();
584        
585        let expected = r#"
586- apple
587- banana
588- cherry"#;
589        assert_eq!(result, expected);
590    }
591
592    #[test]
593    fn test_jsonparse_with_object() {
594        let mut engine = TemplateEngine::new();
595        engine.set_variable("json_object", r#"{"name": "Alice", "age": 30, "city": "Beijing"}"#);
596
597        let template = r#"
598{% for item in json_object jsonparse %}
599Key: {{ item.key }}, Value: {{ item.value }}
600{% endfor %}"#;
601
602        let result = engine
603            .set_preserve_loop_newlines(false)
604            .render_string(template)
605            .unwrap();
606        
607        assert!(result.contains("Key: name, Value: Alice"));
608        assert!(result.contains("Key: age, Value: 30"));
609        assert!(result.contains("Key: city, Value: Beijing"));
610    }
611
612    #[test]
613    fn test_jsonparse_with_complex_array() {
614        let mut engine = TemplateEngine::new();
615        engine.set_variable("users_json", r#"[
616            {"name": "Alice", "age": 25},
617            {"name": "Bob", "age": 30},
618            {"name": "Charlie", "age": 35}
619        ]"#);
620
621        let template = r#"
622{% for user in users_json jsonparse %}
623- {{ user.name }} ({{ user.age }} years old)
624{% endfor %}"#;
625
626        let result = engine
627            .set_preserve_loop_newlines(false)
628            .render_string(template)
629            .unwrap();
630        
631        assert!(result.contains("- Alice (25 years old)"));
632        assert!(result.contains("- Bob (30 years old)"));
633        assert!(result.contains("- Charlie (35 years old)"));
634    }
635
636    #[test]
637    fn test_jsonparse_invalid_json() {
638        let mut engine = TemplateEngine::new();
639        engine.set_variable("invalid_json", r#"["apple", "banana", "cherry""#);
640
641        let template = r#"
642{% for fruit in invalid_json jsonparse %}
643- {{ fruit }}
644{% endfor %}"#;
645
646        let result = engine.render_string(template);
647        assert!(result.is_err());
648        assert!(result.unwrap_err().to_string().contains("Failed to parse JSON"));
649    }
650
651    #[test]
652    fn test_jsonparse_non_string_variable() {
653        let mut engine = TemplateEngine::new();
654        engine.set_variable("already_array", json!(["apple", "banana", "cherry"]));
655
656        let template = r#"
657{% for fruit in already_array jsonparse %}
658- {{ fruit }}
659{% endfor %}"#;
660
661        let result = engine.render_string(template);
662        assert!(result.is_err());
663        assert!(result.unwrap_err().to_string().contains("Cannot jsonparse non-string variable"));
664    }
665}