1use super::{RuleParser, UnifiedRule, extract_bullet_points, parse_markdown_sections};
4use crate::{DrivenError, Editor, Result};
5use std::path::Path;
6
7#[derive(Debug, Default)]
9pub struct CopilotParser;
10
11impl CopilotParser {
12 pub fn new() -> Self {
14 Self
15 }
16}
17
18impl RuleParser for CopilotParser {
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 self.parse_content(&content)
23 }
24
25 fn parse_content(&self, content: &str) -> Result<Vec<UnifiedRule>> {
26 let mut rules = Vec::new();
27 let sections = parse_markdown_sections(content);
28
29 if sections.is_empty() {
30 if !content.trim().is_empty() {
32 rules.push(UnifiedRule::raw(content.trim()));
33 }
34 return Ok(rules);
35 }
36
37 for (heading, body) in sections {
38 let lower_heading = heading.to_lowercase();
39
40 if lower_heading.contains("project")
41 || lower_heading.contains("overview")
42 || lower_heading.contains("context")
43 {
44 let points = extract_bullet_points(&body);
46 if !points.is_empty() {
47 rules.push(UnifiedRule::Context {
48 includes: Vec::new(),
49 excludes: Vec::new(),
50 focus: points,
51 });
52 }
53 } else if lower_heading.contains("code")
54 || lower_heading.contains("style")
55 || lower_heading.contains("convention")
56 || lower_heading.contains("quality")
57 {
58 let points = extract_bullet_points(&body);
60 for (i, point) in points.into_iter().enumerate() {
61 rules.push(UnifiedRule::Standard {
62 category: crate::format::RuleCategory::Style,
63 priority: i as u8,
64 description: point,
65 pattern: None,
66 });
67 }
68 } else if lower_heading.contains("structure") || lower_heading.contains("architecture")
69 {
70 let points = extract_bullet_points(&body);
72 for (i, point) in points.into_iter().enumerate() {
73 rules.push(UnifiedRule::Standard {
74 category: crate::format::RuleCategory::Architecture,
75 priority: i as u8,
76 description: point,
77 pattern: None,
78 });
79 }
80 } else if lower_heading.contains("test") {
81 let points = extract_bullet_points(&body);
83 for (i, point) in points.into_iter().enumerate() {
84 rules.push(UnifiedRule::Standard {
85 category: crate::format::RuleCategory::Testing,
86 priority: i as u8,
87 description: point,
88 pattern: None,
89 });
90 }
91 } else if lower_heading.contains("doc") {
92 let points = extract_bullet_points(&body);
94 for (i, point) in points.into_iter().enumerate() {
95 rules.push(UnifiedRule::Standard {
96 category: crate::format::RuleCategory::Documentation,
97 priority: i as u8,
98 description: point,
99 pattern: None,
100 });
101 }
102 } else {
103 if !body.trim().is_empty() {
105 rules.push(UnifiedRule::raw(format!("# {}\n{}", heading, body)));
106 }
107 }
108 }
109
110 Ok(rules)
111 }
112
113 fn editor(&self) -> Editor {
114 Editor::Copilot
115 }
116}
117
118#[cfg(test)]
119mod tests {
120 use super::*;
121
122 #[test]
123 fn test_parse_copilot_instructions() {
124 let content = r#"# Project Overview
125
126This is a Rust project for binary-first web development.
127
128- Focus on performance
129- Zero-copy operations
130
131# Code Quality
132
133- Use `cargo fmt` before commits
134- Run `cargo clippy` and fix warnings
135- Write unit tests for all public functions
136
137# Testing Standards
138
139- Use property-based testing where applicable
140- Aim for 80% code coverage
141"#;
142
143 let parser = CopilotParser::new();
144 let rules = parser.parse_content(content).unwrap();
145
146 assert!(!rules.is_empty());
147
148 let has_context = rules
150 .iter()
151 .any(|r| matches!(r, UnifiedRule::Context { .. }));
152 let has_testing = rules.iter().any(|r| {
153 matches!(
154 r,
155 UnifiedRule::Standard {
156 category: crate::format::RuleCategory::Testing,
157 ..
158 }
159 )
160 });
161
162 assert!(has_context);
163 assert!(has_testing);
164 }
165
166 #[test]
167 fn test_parse_empty() {
168 let parser = CopilotParser::new();
169 let rules = parser.parse_content("").unwrap();
170 assert!(rules.is_empty());
171 }
172}