feature-manifest 0.7.1

Document, validate, and render Cargo feature metadata.
Documentation
use anyhow::Context as _;
use clap::Command;

use crate::lint_docs;

use super::command_definition;

pub fn render_cli_markdown() -> String {
    let root = command_definition();
    let mut output = String::new();
    output.push_str("# CLI Reference\n\n");
    output.push_str("Generated from the Clap command definitions. Update this file with `cargo fm help-markdown > docs/cli.md`.\n\n");

    push_command_section(
        &mut output,
        "cargo fm",
        root.clone().bin_name("cargo fm"),
        2,
    );

    for subcommand in root
        .get_subcommands()
        .filter(|command| !command.is_hide_set())
    {
        let mut command = subcommand.clone();
        let name = command.get_name().to_owned();
        command = command.bin_name(format!("cargo fm {name}"));
        push_command_section(&mut output, &format!("cargo fm {name}"), command, 2);
    }

    output
}

pub fn render_lint_markdown() -> String {
    let mut output = String::new();
    output.push_str("# Lint Reference\n\n");
    output.push_str("Generated from the feature-manifest lint registry. Update this file with `cargo fm lints --markdown > docs/lints.md`.\n\n");
    output.push_str("| Code | Default | Meaning | Fix |\n");
    output.push_str("| --- | --- | --- | --- |\n");

    for lint in lint_docs() {
        output.push_str(&format!(
            "| `{}` | `{}` | {} | {} |\n",
            lint.code,
            lint.default_severity,
            escape_markdown_table_cell(lint.summary),
            escape_markdown_table_cell(lint.guidance)
        ));
    }

    output
}

fn push_command_section(output: &mut String, title: &str, mut command: Command, level: usize) {
    output.push_str(&format!("{} `{title}`\n\n", "#".repeat(level)));
    output.push_str("```text\n");

    let mut help = Vec::new();
    command
        .write_long_help(&mut help)
        .context("failed to render CLI help")
        .expect("writing CLI help to a buffer should not fail");
    let help = String::from_utf8(help).expect("Clap help should be valid UTF-8");
    output.push_str(help.trim_end());

    output.push_str("\n```\n\n");
}

fn escape_markdown_table_cell(value: &str) -> String {
    value.replace('|', "\\|").replace('\n', "<br>")
}