Skip to main content

driven/emitter/
generic.rs

1//! Generic markdown emitter
2
3use super::{RuleEmitter, ensure_parent_dir, format_bullet_list, format_heading};
4use crate::{Editor, Result, parser::UnifiedRule};
5use std::path::Path;
6
7/// Generic markdown emitter for unsupported editors
8#[derive(Debug, Default)]
9pub struct GenericEmitter;
10
11impl GenericEmitter {
12    /// Create a new generic emitter
13    pub fn new() -> Self {
14        Self
15    }
16}
17
18impl RuleEmitter for GenericEmitter {
19    fn emit_file(&self, rules: &[UnifiedRule], path: &Path) -> Result<()> {
20        ensure_parent_dir(path)?;
21        let content = self.emit_string(rules)?;
22        std::fs::write(path, content)?;
23        Ok(())
24    }
25
26    fn emit_string(&self, rules: &[UnifiedRule]) -> Result<String> {
27        let mut output = String::new();
28
29        output.push_str(&format_heading(1, "AI Coding Instructions"));
30        output.push_str("Generated by Driven - Universal AI Rule Format\n\n");
31
32        for rule in rules {
33            match rule {
34                UnifiedRule::Persona {
35                    name,
36                    role,
37                    identity,
38                    style,
39                    traits,
40                    principles,
41                } => {
42                    output.push_str(&format_heading(2, &format!("Persona: {}", name)));
43                    output.push_str(&format!("**Role:** {}\n\n", role));
44
45                    if let Some(id) = identity {
46                        output.push_str(&format!("**Identity:** {}\n\n", id));
47                    }
48
49                    if let Some(s) = style {
50                        output.push_str(&format!("**Style:** {}\n\n", s));
51                    }
52
53                    if !traits.is_empty() {
54                        output.push_str("**Traits:**\n");
55                        output.push_str(&format_bullet_list(traits));
56                        output.push('\n');
57                    }
58
59                    if !principles.is_empty() {
60                        output.push_str("**Principles:**\n");
61                        output.push_str(&format_bullet_list(principles));
62                        output.push('\n');
63                    }
64                }
65                UnifiedRule::Standard {
66                    category,
67                    priority,
68                    description,
69                    pattern,
70                } => {
71                    output.push_str(&format!(
72                        "- **[{:?}:{}]** {}\n",
73                        category, priority, description
74                    ));
75
76                    if let Some(p) = pattern {
77                        output.push_str(&format!("  - Pattern: `{}`\n", p));
78                    }
79                }
80                UnifiedRule::Context {
81                    includes,
82                    excludes,
83                    focus,
84                } => {
85                    output.push_str(&format_heading(2, "Context"));
86
87                    if !includes.is_empty() {
88                        output.push_str("**Include:**\n");
89                        output.push_str(&format_bullet_list(includes));
90                    }
91
92                    if !excludes.is_empty() {
93                        output.push_str("**Exclude:**\n");
94                        output.push_str(&format_bullet_list(excludes));
95                    }
96
97                    if !focus.is_empty() {
98                        output.push_str("**Focus:**\n");
99                        output.push_str(&format_bullet_list(focus));
100                    }
101                    output.push('\n');
102                }
103                UnifiedRule::Workflow { name, steps } => {
104                    output.push_str(&format_heading(2, &format!("Workflow: {}", name)));
105
106                    for (i, step) in steps.iter().enumerate() {
107                        output.push_str(&format!("### Step {}: {}\n\n", i + 1, step.name));
108                        output.push_str(&step.description);
109                        output.push_str("\n\n");
110
111                        if let Some(condition) = &step.condition {
112                            output.push_str(&format!("*Condition: {}*\n\n", condition));
113                        }
114
115                        if !step.actions.is_empty() {
116                            output.push_str("**Actions:**\n");
117                            output.push_str(&format_bullet_list(&step.actions));
118                            output.push('\n');
119                        }
120                    }
121                }
122                UnifiedRule::Raw { content } => {
123                    output.push_str(content);
124                    output.push_str("\n\n");
125                }
126            }
127        }
128
129        Ok(output.trim().to_string())
130    }
131
132    fn editor(&self) -> Editor {
133        Editor::Aider // Default fallback
134    }
135}
136
137#[cfg(test)]
138mod tests {
139    use super::*;
140
141    #[test]
142    fn test_emit_generic() {
143        let rules = vec![
144            UnifiedRule::persona("Test Agent", "Testing role"),
145            UnifiedRule::standard(crate::format::RuleCategory::Style, 0, "Test standard"),
146        ];
147
148        let emitter = GenericEmitter::new();
149        let output = emitter.emit_string(&rules).unwrap();
150
151        assert!(output.contains("AI Coding Instructions"));
152        assert!(output.contains("Persona: Test Agent"));
153        assert!(output.contains("Test standard"));
154    }
155}