driven/emitter/
generic.rs1use super::{RuleEmitter, ensure_parent_dir, format_bullet_list, format_heading};
4use crate::{Editor, Result, parser::UnifiedRule};
5use std::path::Path;
6
7#[derive(Debug, Default)]
9pub struct GenericEmitter;
10
11impl GenericEmitter {
12 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 }
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}