dx-driven 0.1.0

Professional AI-assisted development orchestrator with binary-first architecture
Documentation
//! Generic markdown emitter

use super::{RuleEmitter, ensure_parent_dir, format_bullet_list, format_heading};
use crate::{Editor, Result, parser::UnifiedRule};
use std::path::Path;

/// Generic markdown emitter for unsupported editors
#[derive(Debug, Default)]
pub struct GenericEmitter;

impl GenericEmitter {
    /// Create a new generic emitter
    pub fn new() -> Self {
        Self
    }
}

impl RuleEmitter for GenericEmitter {
    fn emit_file(&self, rules: &[UnifiedRule], path: &Path) -> Result<()> {
        ensure_parent_dir(path)?;
        let content = self.emit_string(rules)?;
        std::fs::write(path, content)?;
        Ok(())
    }

    fn emit_string(&self, rules: &[UnifiedRule]) -> Result<String> {
        let mut output = String::new();

        output.push_str(&format_heading(1, "AI Coding Instructions"));
        output.push_str("Generated by Driven - Universal AI Rule Format\n\n");

        for rule in rules {
            match rule {
                UnifiedRule::Persona {
                    name,
                    role,
                    identity,
                    style,
                    traits,
                    principles,
                } => {
                    output.push_str(&format_heading(2, &format!("Persona: {}", name)));
                    output.push_str(&format!("**Role:** {}\n\n", role));

                    if let Some(id) = identity {
                        output.push_str(&format!("**Identity:** {}\n\n", id));
                    }

                    if let Some(s) = style {
                        output.push_str(&format!("**Style:** {}\n\n", s));
                    }

                    if !traits.is_empty() {
                        output.push_str("**Traits:**\n");
                        output.push_str(&format_bullet_list(traits));
                        output.push('\n');
                    }

                    if !principles.is_empty() {
                        output.push_str("**Principles:**\n");
                        output.push_str(&format_bullet_list(principles));
                        output.push('\n');
                    }
                }
                UnifiedRule::Standard {
                    category,
                    priority,
                    description,
                    pattern,
                } => {
                    output.push_str(&format!(
                        "- **[{:?}:{}]** {}\n",
                        category, priority, description
                    ));

                    if let Some(p) = pattern {
                        output.push_str(&format!("  - Pattern: `{}`\n", p));
                    }
                }
                UnifiedRule::Context {
                    includes,
                    excludes,
                    focus,
                } => {
                    output.push_str(&format_heading(2, "Context"));

                    if !includes.is_empty() {
                        output.push_str("**Include:**\n");
                        output.push_str(&format_bullet_list(includes));
                    }

                    if !excludes.is_empty() {
                        output.push_str("**Exclude:**\n");
                        output.push_str(&format_bullet_list(excludes));
                    }

                    if !focus.is_empty() {
                        output.push_str("**Focus:**\n");
                        output.push_str(&format_bullet_list(focus));
                    }
                    output.push('\n');
                }
                UnifiedRule::Workflow { name, steps } => {
                    output.push_str(&format_heading(2, &format!("Workflow: {}", name)));

                    for (i, step) in steps.iter().enumerate() {
                        output.push_str(&format!("### Step {}: {}\n\n", i + 1, step.name));
                        output.push_str(&step.description);
                        output.push_str("\n\n");

                        if let Some(condition) = &step.condition {
                            output.push_str(&format!("*Condition: {}*\n\n", condition));
                        }

                        if !step.actions.is_empty() {
                            output.push_str("**Actions:**\n");
                            output.push_str(&format_bullet_list(&step.actions));
                            output.push('\n');
                        }
                    }
                }
                UnifiedRule::Raw { content } => {
                    output.push_str(content);
                    output.push_str("\n\n");
                }
            }
        }

        Ok(output.trim().to_string())
    }

    fn editor(&self) -> Editor {
        Editor::Aider // Default fallback
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_emit_generic() {
        let rules = vec![
            UnifiedRule::persona("Test Agent", "Testing role"),
            UnifiedRule::standard(crate::format::RuleCategory::Style, 0, "Test standard"),
        ];

        let emitter = GenericEmitter::new();
        let output = emitter.emit_string(&rules).unwrap();

        assert!(output.contains("AI Coding Instructions"));
        assert!(output.contains("Persona: Test Agent"));
        assert!(output.contains("Test standard"));
    }
}