repopilot 0.16.0

Local-first CLI for reviewing Git changes, security boundaries, and blast radius before merge.
Documentation
use super::model::KnowledgeCatalogReport;
use crate::output::OutputFormat;

pub fn render_knowledge_catalog_report(
    report: &KnowledgeCatalogReport,
    format: OutputFormat,
) -> Result<String, serde_json::Error> {
    match format {
        OutputFormat::Console => Ok(render_console(report)),
        OutputFormat::Markdown => Ok(render_markdown(report)),
        OutputFormat::Json | OutputFormat::Html | OutputFormat::Sarif => {
            serde_json::to_string_pretty(report)
        }
    }
}

fn render_console(report: &KnowledgeCatalogReport) -> String {
    let mut output = String::new();

    output.push_str("RepoPilot Knowledge Catalog\n\n");
    output.push_str("Summary:\n");
    output.push_str(&format!(" Languages: {}\n", report.summary.languages));
    output.push_str(&format!(" Frameworks: {}\n", report.summary.frameworks));
    output.push_str(&format!(" Runtimes: {}\n", report.summary.runtimes));
    output.push_str(&format!(" Paradigms: {}\n", report.summary.paradigms));
    output.push_str(&format!(" Rules: {}\n", report.summary.rules));

    if !report.languages.is_empty() {
        output.push_str("\nLanguages:\n");
        for language in &report.languages {
            output.push_str(&format!(
                " - {} ({}) [{}]\n",
                language.name, language.id, language.support
            ));
            if !language.extensions.is_empty() {
                output.push_str(&format!(
                    "   extensions: {}\n",
                    language.extensions.join(", ")
                ));
            }
            if !language.filenames.is_empty() {
                output.push_str(&format!(
                    "   filenames: {}\n",
                    language.filenames.join(", ")
                ));
            }
        }
    }

    if !report.frameworks.is_empty() {
        output.push_str("\nFrameworks:\n");
        for framework in &report.frameworks {
            output.push_str(&format!(" - {} ({})\n", framework.name, framework.id));
        }
    }

    if !report.runtimes.is_empty() {
        output.push_str("\nRuntimes:\n");
        for runtime in &report.runtimes {
            output.push_str(&format!(" - {} ({})\n", runtime.name, runtime.id));
        }
    }

    if !report.paradigms.is_empty() {
        output.push_str("\nParadigms:\n");
        for paradigm in &report.paradigms {
            output.push_str(&format!(" - {} ({})\n", paradigm.name, paradigm.id));
        }
    }

    if !report.rules.is_empty() {
        output.push_str("\nRules:\n");
        for rule in &report.rules {
            output.push_str(&format!(" - {}\n", rule.rule_id));
            if let Some(minimum_support) = &rule.minimum_support {
                output.push_str(&format!("   minimum support: {minimum_support}\n"));
            }
            output.push_str(&format!(
                "   languages: {}\n",
                comma_or_all(&rule.languages)
            ));
            output.push_str(&format!(
                "   frameworks: {}\n",
                comma_or_all(&rule.frameworks)
            ));
            output.push_str(&format!("   runtimes: {}\n", comma_or_all(&rule.runtimes)));
            output.push_str(&format!(
                "   paradigms: {}\n",
                comma_or_all(&rule.paradigms)
            ));
            output.push_str(&format!("   overrides: {}\n", rule.overrides));
        }
    }

    output
}

fn render_markdown(report: &KnowledgeCatalogReport) -> String {
    let mut output = String::new();

    output.push_str("# RepoPilot Knowledge Catalog\n\n");
    output.push_str("## Summary\n\n");
    output.push_str(&format!("- **Languages:** {}\n", report.summary.languages));
    output.push_str(&format!(
        "- **Frameworks:** {}\n",
        report.summary.frameworks
    ));
    output.push_str(&format!("- **Runtimes:** {}\n", report.summary.runtimes));
    output.push_str(&format!("- **Paradigms:** {}\n", report.summary.paradigms));
    output.push_str(&format!("- **Rules:** {}\n", report.summary.rules));

    if !report.languages.is_empty() {
        output.push_str("\n## Languages\n\n");
        for language in &report.languages {
            output.push_str(&format!(
                "- `{}` — {} — support: `{}`\n",
                language.id, language.name, language.support
            ));
            if !language.extensions.is_empty() {
                output.push_str(&format!(
                    "  - extensions: {}\n",
                    markdown_code_list(&language.extensions)
                ));
            }
            if !language.filenames.is_empty() {
                output.push_str(&format!(
                    "  - filenames: {}\n",
                    markdown_code_list(&language.filenames)
                ));
            }
        }
    }

    if !report.frameworks.is_empty() {
        output.push_str("\n## Frameworks\n\n");
        for framework in &report.frameworks {
            output.push_str(&format!("- `{}` — {}\n", framework.id, framework.name));
        }
    }

    if !report.runtimes.is_empty() {
        output.push_str("\n## Runtimes\n\n");
        for runtime in &report.runtimes {
            output.push_str(&format!("- `{}` — {}\n", runtime.id, runtime.name));
        }
    }

    if !report.paradigms.is_empty() {
        output.push_str("\n## Paradigms\n\n");
        for paradigm in &report.paradigms {
            output.push_str(&format!("- `{}` — {}\n", paradigm.id, paradigm.name));
        }
    }

    if !report.rules.is_empty() {
        output.push_str("\n## Rules\n\n");
        for rule in &report.rules {
            output.push_str(&format!("- `{}`\n", rule.rule_id));
            if let Some(minimum_support) = &rule.minimum_support {
                output.push_str(&format!("  - minimum support: `{minimum_support}`\n"));
            }
            output.push_str(&format!(
                "  - languages: {}\n",
                markdown_code_list_or_all(&rule.languages)
            ));
            output.push_str(&format!(
                "  - frameworks: {}\n",
                markdown_code_list_or_all(&rule.frameworks)
            ));
            output.push_str(&format!(
                "  - runtimes: {}\n",
                markdown_code_list_or_all(&rule.runtimes)
            ));
            output.push_str(&format!(
                "  - paradigms: {}\n",
                markdown_code_list_or_all(&rule.paradigms)
            ));
            output.push_str(&format!("  - overrides: `{}`\n", rule.overrides));
        }
    }

    output
}

fn comma_or_all(values: &[String]) -> String {
    if values.is_empty() {
        "all".to_string()
    } else {
        values.join(", ")
    }
}

fn markdown_code_list(values: &[String]) -> String {
    values
        .iter()
        .map(|value| format!("`{value}`"))
        .collect::<Vec<_>>()
        .join(", ")
}

fn markdown_code_list_or_all(values: &[String]) -> String {
    if values.is_empty() {
        "`all`".to_string()
    } else {
        markdown_code_list(values)
    }
}