1use super::{RuleParser, UnifiedRule, extract_bullet_points};
4use crate::{DrivenError, Editor, Result};
5use std::path::Path;
6
7#[derive(Debug, Default)]
9pub struct AiderParser;
10
11impl AiderParser {
12 pub fn new() -> Self {
14 Self
15 }
16}
17
18impl RuleParser for AiderParser {
19 fn parse_file(&self, path: &Path) -> Result<Vec<UnifiedRule>> {
20 let content = std::fs::read_to_string(path)
21 .map_err(|e| DrivenError::Parse(format!("Failed to read {}: {}", path.display(), e)))?;
22
23 if path
25 .extension()
26 .is_some_and(|ext| ext == "yml" || ext == "yaml")
27 {
28 return self.parse_yaml(&content);
29 }
30
31 self.parse_content(&content)
32 }
33
34 fn parse_content(&self, content: &str) -> Result<Vec<UnifiedRule>> {
35 let mut rules = Vec::new();
36
37 let points = extract_bullet_points(content);
39 if !points.is_empty() {
40 for (i, point) in points.into_iter().enumerate() {
41 rules.push(UnifiedRule::Standard {
42 category: crate::format::RuleCategory::Style,
43 priority: i as u8,
44 description: point,
45 pattern: None,
46 });
47 }
48 } else if !content.trim().is_empty() {
49 rules.push(UnifiedRule::raw(content.trim()));
51 }
52
53 Ok(rules)
54 }
55
56 fn editor(&self) -> Editor {
57 Editor::Aider
58 }
59}
60
61impl AiderParser {
62 fn parse_yaml(&self, content: &str) -> Result<Vec<UnifiedRule>> {
63 let mut rules = Vec::new();
64
65 if let Ok(yaml) = serde_yaml::from_str::<serde_yaml::Value>(content) {
67 if let Some(mapping) = yaml.as_mapping() {
68 if let Some(conventions) = mapping.get("conventions") {
70 if let Some(seq) = conventions.as_sequence() {
71 for (i, item) in seq.iter().enumerate() {
72 if let Some(text) = item.as_str() {
73 rules.push(UnifiedRule::Standard {
74 category: crate::format::RuleCategory::Style,
75 priority: i as u8,
76 description: text.to_string(),
77 pattern: None,
78 });
79 }
80 }
81 }
82 }
83
84 if let Some(read_only) = mapping.get("read_only_files") {
85 if let Some(seq) = read_only.as_sequence() {
86 let patterns: Vec<String> = seq
87 .iter()
88 .filter_map(|v| v.as_str().map(String::from))
89 .collect();
90
91 if !patterns.is_empty() {
92 rules.push(UnifiedRule::Context {
93 includes: Vec::new(),
94 excludes: patterns,
95 focus: Vec::new(),
96 });
97 }
98 }
99 }
100 }
101 }
102
103 Ok(rules)
104 }
105}
106
107#[cfg(test)]
108mod tests {
109 use super::*;
110
111 #[test]
112 fn test_parse_aider_simple() {
113 let content = r#"
114- Use descriptive variable names
115- Write tests for all functions
116- Keep functions small and focused
117"#;
118
119 let parser = AiderParser::new();
120 let rules = parser.parse_content(content).unwrap();
121
122 assert_eq!(rules.len(), 3);
123 }
124
125 #[test]
126 fn test_parse_aider_yaml() {
127 let content = r#"
128conventions:
129 - Use snake_case for functions
130 - Use PascalCase for types
131read_only_files:
132 - "*.lock"
133 - "vendor/**"
134"#;
135
136 let parser = AiderParser::new();
137 let rules = parser.parse_yaml(content).unwrap();
138
139 assert!(!rules.is_empty());
140 }
141}