Skip to main content

zerolaunch_plugin_api/services/parameter/
template_parser.rs

1//! 模板解析器 - 解析模板字符串中的参数占位符
2
3use crate::services::parameter::types::SystemParameter;
4
5/// 模板中的占位符类型
6///
7/// 解析模板后产生的占位符序列,按出现顺序排列。
8/// 新设计使用枚举替代旧版的带位置信息的 struct,从左到右顺序构建结果字符串,
9/// 避免旧方案中从后往前替换时的位置偏移问题。
10#[derive(Debug, Clone, PartialEq, Eq)]
11pub enum Placeholder {
12    /// 纯文本片段
13    Text(String),
14    /// 用户提供的位置参数 {}
15    UserArg,
16    /// 系统提供的参数 {clip} {hwnd} {selection}
17    System(SystemParameter),
18}
19
20/// 模板解析器
21///
22/// 负责将模板字符串解析为占位符序列,供 ParameterResolver 使用。
23pub struct TemplateParser;
24
25impl TemplateParser {
26    /// 解析模板,返回占位符序列
27    ///
28    /// 支持的占位符格式:
29    /// - `{}` - 位置参数
30    /// - `{clip}` - 剪贴板内容
31    /// - `{hwnd}` - 窗口句柄
32    /// - `{selection}` - 选中文本
33    pub fn parse(template: &str) -> Vec<Placeholder> {
34        let mut placeholders = Vec::new();
35        let chars: Vec<char> = template.chars().collect();
36        let mut i = 0;
37        let mut text_start = 0;
38
39        while i < chars.len() {
40            if chars[i] == '{' {
41                // 找到左大括号,先保存之前的文本
42                if i > text_start {
43                    let text: String = chars[text_start..i].iter().collect();
44                    placeholders.push(Placeholder::Text(text));
45                }
46
47                // 查找右大括号
48                if let Some(end) = Self::find_closing_brace(&chars, i) {
49                    let content: String = chars[i + 1..end].iter().collect();
50
51                    let placeholder = if content.is_empty() {
52                        // {} - 位置参数
53                        Placeholder::UserArg
54                    } else if let Some(sys_param) = SystemParameter::from_name(&content) {
55                        // {clip} / {hwnd} / {selection} - 系统参数
56                        Placeholder::System(sys_param)
57                    } else {
58                        // 未知参数名,当作位置参数处理
59                        Placeholder::UserArg
60                    };
61
62                    placeholders.push(placeholder);
63                    i = end + 1;
64                    text_start = i;
65                    continue;
66                }
67            }
68            i += 1;
69        }
70
71        // 保存末尾的文本
72        if text_start < chars.len() {
73            let text: String = chars[text_start..].iter().collect();
74            placeholders.push(Placeholder::Text(text));
75        }
76
77        placeholders
78    }
79
80    /// 查找匹配的右大括号
81    fn find_closing_brace(chars: &[char], start: usize) -> Option<usize> {
82        ((start + 1)..chars.len()).find(|&i| chars[i] == '}')
83    }
84
85    /// 统计模板中需要用户输入的参数数量(只统计位置参数)
86    pub fn count_user_args(template: &str) -> usize {
87        Self::parse(template)
88            .iter()
89            .filter(|p| matches!(p, Placeholder::UserArg))
90            .count()
91    }
92
93    /// 检查模板是否包含系统参数
94    pub fn has_system_params(template: &str) -> bool {
95        Self::parse(template)
96            .iter()
97            .any(|p| matches!(p, Placeholder::System(_)))
98    }
99}
100
101#[cfg(test)]
102mod tests {
103    use super::*;
104
105    #[test]
106    fn test_parse_empty_template() {
107        let placeholders = TemplateParser::parse("");
108        assert_eq!(placeholders.len(), 0);
109    }
110
111    #[test]
112    fn test_parse_no_parameters() {
113        let placeholders = TemplateParser::parse("notepad.exe test.txt");
114        assert_eq!(placeholders.len(), 1);
115        assert_eq!(
116            placeholders[0],
117            Placeholder::Text("notepad.exe test.txt".to_string())
118        );
119    }
120
121    #[test]
122    fn test_parse_positional_parameter() {
123        let placeholders = TemplateParser::parse("cmd /c echo {}");
124        assert_eq!(placeholders.len(), 2);
125        assert_eq!(
126            placeholders[0],
127            Placeholder::Text("cmd /c echo ".to_string())
128        );
129        assert_eq!(placeholders[1], Placeholder::UserArg);
130    }
131
132    #[test]
133    fn test_parse_multiple_positional() {
134        let placeholders = TemplateParser::parse("program {} {} {}");
135        assert_eq!(placeholders.len(), 6);
136        let user_args: Vec<_> = placeholders
137            .iter()
138            .filter(|p| matches!(p, Placeholder::UserArg))
139            .collect();
140        assert_eq!(user_args.len(), 3);
141    }
142
143    #[test]
144    fn test_parse_clipboard_parameter() {
145        let placeholders = TemplateParser::parse("notepad {clip}");
146        assert_eq!(placeholders.len(), 2);
147        assert_eq!(placeholders[0], Placeholder::Text("notepad ".to_string()));
148        assert_eq!(
149            placeholders[1],
150            Placeholder::System(SystemParameter::Clipboard)
151        );
152    }
153
154    #[test]
155    fn test_parse_hwnd_parameter() {
156        let placeholders = TemplateParser::parse("tool --hwnd {hwnd}");
157        assert_eq!(placeholders.len(), 2);
158        assert_eq!(
159            placeholders[1],
160            Placeholder::System(SystemParameter::WindowHandle)
161        );
162    }
163
164    #[test]
165    fn test_parse_selection_parameter() {
166        let placeholders = TemplateParser::parse("translate {selection}");
167        assert_eq!(placeholders.len(), 2);
168        assert_eq!(
169            placeholders[1],
170            Placeholder::System(SystemParameter::Selection)
171        );
172    }
173
174    #[test]
175    fn test_parse_mixed_parameters() {
176        let placeholders = TemplateParser::parse("program {} {clip} {} {hwnd}");
177        assert_eq!(placeholders.len(), 8);
178    }
179
180    #[test]
181    fn test_count_user_args() {
182        assert_eq!(
183            TemplateParser::count_user_args("program {} {clip} {} {hwnd}"),
184            2
185        );
186        assert_eq!(TemplateParser::count_user_args("program {clip} {hwnd}"), 0);
187        assert_eq!(TemplateParser::count_user_args("program {} {}"), 2);
188        assert_eq!(TemplateParser::count_user_args("program {selection}"), 0);
189        assert_eq!(
190            TemplateParser::count_user_args("program {} {selection} {}"),
191            2
192        );
193    }
194
195    #[test]
196    fn test_has_system_params() {
197        assert!(TemplateParser::has_system_params("program {clip}"));
198        assert!(TemplateParser::has_system_params("program {hwnd}"));
199        assert!(TemplateParser::has_system_params("program {selection}"));
200        assert!(TemplateParser::has_system_params("program {} {clip}"));
201        assert!(!TemplateParser::has_system_params("program {}"));
202        assert!(!TemplateParser::has_system_params("program"));
203    }
204
205    #[test]
206    fn test_parse_trailing_text() {
207        let placeholders = TemplateParser::parse("cmd /c echo {} --flag");
208        assert_eq!(placeholders.len(), 3);
209        assert_eq!(placeholders[2], Placeholder::Text(" --flag".to_string()));
210    }
211}