ricecoder_keybinds/
parser.rs1use crate::error::ParseError;
4use crate::models::Keybind;
5use std::collections::HashMap;
6use std::sync::Arc;
7
8pub trait KeybindParser: Send + Sync {
10 fn parse(&self, content: &str) -> Result<Vec<Keybind>, ParseError>;
12}
13
14pub struct ParserRegistry {
16 parsers: HashMap<String, Arc<dyn KeybindParser>>,
17}
18
19impl ParserRegistry {
20 pub fn new() -> Self {
22 let mut parsers = HashMap::new();
23 parsers.insert("json".to_string(), Arc::new(JsonKeybindParser) as Arc<dyn KeybindParser>);
24 parsers.insert("markdown".to_string(), Arc::new(MarkdownKeybindParser) as Arc<dyn KeybindParser>);
25 parsers.insert("md".to_string(), Arc::new(MarkdownKeybindParser) as Arc<dyn KeybindParser>);
26
27 ParserRegistry { parsers }
28 }
29
30 pub fn register(&mut self, format: impl Into<String>, parser: Arc<dyn KeybindParser>) {
32 self.parsers.insert(format.into(), parser);
33 }
34
35 pub fn get_parser(&self, format: &str) -> Option<Arc<dyn KeybindParser>> {
37 self.parsers.get(format).cloned()
38 }
39
40 pub fn parse_auto(&self, content: &str) -> Result<Vec<Keybind>, ParseError> {
42 if let Ok(keybinds) = self.get_parser("json")
44 .ok_or_else(|| ParseError::InvalidJson("No JSON parser available".to_string()))?
45 .parse(content) {
46 return Ok(keybinds);
47 }
48
49 self.get_parser("markdown")
51 .ok_or_else(|| ParseError::InvalidMarkdown("No Markdown parser available".to_string()))?
52 .parse(content)
53 }
54
55 pub fn parse(&self, content: &str, format: &str) -> Result<Vec<Keybind>, ParseError> {
57 let parser = self.get_parser(format)
58 .ok_or_else(|| ParseError::InvalidJson(format!("Unknown format: {}", format)))?;
59 parser.parse(content)
60 }
61}
62
63impl Default for ParserRegistry {
64 fn default() -> Self {
65 Self::new()
66 }
67}
68
69pub struct JsonKeybindParser;
71
72impl KeybindParser for JsonKeybindParser {
73 fn parse(&self, content: &str) -> Result<Vec<Keybind>, ParseError> {
74 let value: serde_json::Value = serde_json::from_str(content)
75 .map_err(|e| ParseError::InvalidJson(e.to_string()))?;
76
77 let keybinds_array = value
78 .get("keybinds")
79 .and_then(|v| v.as_array())
80 .ok_or_else(|| ParseError::MissingField("keybinds".to_string()))?;
81
82 let mut keybinds = Vec::new();
83 for (idx, item) in keybinds_array.iter().enumerate() {
84 let keybind: Keybind = serde_json::from_value(item.clone()).map_err(|e| {
85 ParseError::LineError {
86 line: idx + 1,
87 message: e.to_string(),
88 }
89 })?;
90
91 if keybind.action_id.is_empty() {
93 return Err(ParseError::LineError {
94 line: idx + 1,
95 message: "Missing action_id".to_string(),
96 });
97 }
98 if keybind.key.is_empty() {
99 return Err(ParseError::LineError {
100 line: idx + 1,
101 message: "Missing key".to_string(),
102 });
103 }
104
105 keybinds.push(keybind);
106 }
107
108 Ok(keybinds)
109 }
110}
111
112pub struct MarkdownKeybindParser;
114
115impl KeybindParser for MarkdownKeybindParser {
116 fn parse(&self, content: &str) -> Result<Vec<Keybind>, ParseError> {
117 let mut keybinds = Vec::new();
118 let mut current_category = String::new();
119
120 for (line_num, line) in content.lines().enumerate() {
121 let trimmed = line.trim();
122
123 if trimmed.is_empty() || trimmed.starts_with("```") {
125 continue;
126 }
127
128 if let Some(category) = trimmed.strip_prefix("## ") {
130 current_category = category.trim().to_string();
131 continue;
132 }
133
134 if trimmed.starts_with("- `") {
136 let keybind = parse_markdown_entry(trimmed, ¤t_category, line_num + 1)?;
137 keybinds.push(keybind);
138 }
139 }
140
141 Ok(keybinds)
142 }
143}
144
145fn parse_markdown_entry(
148 line: &str,
149 category: &str,
150 line_num: usize,
151) -> Result<Keybind, ParseError> {
152 let content = line.strip_prefix("- `").ok_or_else(|| ParseError::LineError {
154 line: line_num,
155 message: "Invalid markdown format".to_string(),
156 })?;
157
158 let backtick_pos = content.find('`').ok_or_else(|| ParseError::LineError {
160 line: line_num,
161 message: "Missing closing backtick".to_string(),
162 })?;
163
164 let action_id = content[..backtick_pos].to_string();
165
166 let rest = &content[backtick_pos + 1..];
168 let colon_pos = rest.find(':').ok_or_else(|| ParseError::LineError {
169 line: line_num,
170 message: "Missing colon after action_id".to_string(),
171 })?;
172
173 let key_part = &rest[colon_pos + 1..];
175 let dash_pos = key_part.find(" - ").ok_or_else(|| ParseError::LineError {
176 line: line_num,
177 message: "Missing ' - ' separator".to_string(),
178 })?;
179
180 let key = key_part[..dash_pos].trim().to_string();
181 let description = key_part[dash_pos + 3..].trim().to_string();
182
183 Ok(Keybind {
184 action_id,
185 key,
186 category: category.to_string(),
187 description,
188 is_default: false,
189 })
190}
191
192#[cfg(test)]
193mod tests {
194 use super::*;
195
196 #[test]
197 fn test_parser_registry_creation() {
198 let registry = ParserRegistry::new();
199 assert!(registry.get_parser("json").is_some());
200 assert!(registry.get_parser("markdown").is_some());
201 assert!(registry.get_parser("md").is_some());
202 }
203
204 #[test]
205 fn test_parser_registry_parse_json() {
206 let registry = ParserRegistry::new();
207 let json = r#"{
208 "version": "1.0",
209 "keybinds": [
210 {
211 "action_id": "editor.save",
212 "key": "Ctrl+S",
213 "category": "editing",
214 "description": "Save file",
215 "is_default": true
216 }
217 ]
218 }"#;
219
220 let keybinds = registry.parse(json, "json").unwrap();
221 assert_eq!(keybinds.len(), 1);
222 assert_eq!(keybinds[0].action_id, "editor.save");
223 }
224
225 #[test]
226 fn test_parser_registry_parse_markdown() {
227 let registry = ParserRegistry::new();
228 let markdown = r#"# Keybinds
229
230## Editing
231
232- `editor.save`: Ctrl+S - Save file
233"#;
234
235 let keybinds = registry.parse(markdown, "markdown").unwrap();
236 assert_eq!(keybinds.len(), 1);
237 assert_eq!(keybinds[0].action_id, "editor.save");
238 }
239
240 #[test]
241 fn test_parser_registry_auto_detect_json() {
242 let registry = ParserRegistry::new();
243 let json = r#"{
244 "version": "1.0",
245 "keybinds": [
246 {
247 "action_id": "editor.save",
248 "key": "Ctrl+S",
249 "category": "editing",
250 "description": "Save file",
251 "is_default": true
252 }
253 ]
254 }"#;
255
256 let keybinds = registry.parse_auto(json).unwrap();
257 assert_eq!(keybinds.len(), 1);
258 }
259
260 #[test]
261 fn test_parser_registry_auto_detect_markdown() {
262 let registry = ParserRegistry::new();
263 let markdown = r#"# Keybinds
264
265## Editing
266
267- `editor.save`: Ctrl+S - Save file
268"#;
269
270 let keybinds = registry.parse_auto(markdown).unwrap();
271 assert_eq!(keybinds.len(), 1);
272 }
273
274 #[test]
275 fn test_json_parser_valid() {
276 let json = r#"{
277 "version": "1.0",
278 "keybinds": [
279 {
280 "action_id": "editor.save",
281 "key": "Ctrl+S",
282 "category": "editing",
283 "description": "Save file",
284 "is_default": true
285 }
286 ]
287 }"#;
288
289 let parser = JsonKeybindParser;
290 let keybinds = parser.parse(json).unwrap();
291 assert_eq!(keybinds.len(), 1);
292 assert_eq!(keybinds[0].action_id, "editor.save");
293 assert_eq!(keybinds[0].key, "Ctrl+S");
294 assert!(keybinds[0].is_default);
295 }
296
297 #[test]
298 fn test_json_parser_invalid() {
299 let json = "invalid json";
300 let parser = JsonKeybindParser;
301 assert!(parser.parse(json).is_err());
302 }
303
304 #[test]
305 fn test_json_parser_missing_keybinds() {
306 let json = r#"{"version": "1.0"}"#;
307 let parser = JsonKeybindParser;
308 assert!(parser.parse(json).is_err());
309 }
310
311 #[test]
312 fn test_markdown_parser_valid() {
313 let markdown = r#"# Keybinds
314
315## Editing
316
317- `editor.save`: Ctrl+S - Save file
318- `editor.undo`: Ctrl+Z - Undo
319
320## Navigation
321
322- `nav.next`: Tab - Next item
323"#;
324
325 let parser = MarkdownKeybindParser;
326 let keybinds = parser.parse(markdown).unwrap();
327 assert_eq!(keybinds.len(), 3);
328 assert_eq!(keybinds[0].action_id, "editor.save");
329 assert_eq!(keybinds[0].category, "Editing");
330 assert_eq!(keybinds[2].category, "Navigation");
331 }
332
333 #[test]
334 fn test_markdown_parser_empty() {
335 let markdown = "# Keybinds\n\n## Editing\n";
336 let parser = MarkdownKeybindParser;
337 let keybinds = parser.parse(markdown).unwrap();
338 assert_eq!(keybinds.len(), 0);
339 }
340}