usage-cli 3.2.1

CLI for working with usage-based CLIs
Documentation
use std::path::PathBuf;

use super::parse_file_or_stdin;
use clap::Args;
use usage::docs::markdown::MarkdownRenderer;

/// Generate markdown documentation from usage specs
#[derive(Args)]
#[clap(visible_alias = "md")]
pub struct Markdown {
    /// A usage spec taken in as a file, use "-" to read from stdin
    #[clap(short, long)]
    file: PathBuf,
    // /// Pass a usage spec in an argument instead of a file
    // #[clap(short, long, required_unless_present = "file", overrides_with = "file")]
    // spec: Option<String>,
    /// Render each subcommand as a separate markdown file
    #[clap(short, long, requires = "out_dir", conflicts_with = "out_file")]
    multi: bool,

    /// Escape HTML in markdown
    #[clap(long)]
    html_encode: bool,

    /// Output markdown files to this directory (required when using --multi)
    #[clap(long, value_hint = clap::ValueHint::DirPath)]
    out_dir: Option<PathBuf>,

    /// Output file path for single-file markdown generation
    #[clap(long, value_hint = clap::ValueHint::FilePath, required_unless_present = "multi")]
    out_file: Option<PathBuf>,

    /// Replace `<pre>` tags with markdown code fences
    #[clap(long)]
    replace_pre_with_code_fences: bool,

    /// Prefix to add to all URLs
    #[clap(long)]
    url_prefix: Option<String>,
}

impl Markdown {
    pub fn run(&self) -> miette::Result<()> {
        let write = |path: &PathBuf, md: &str| -> miette::Result<()> {
            println!("writing to {}", path.display());
            xx::file::write(
                path,
                format!(
                    "<!-- @generated by usage-cli from usage spec -->\n{}\n",
                    md.trim()
                ),
            )?;
            Ok(())
        };
        let spec = parse_file_or_stdin(&self.file)?;
        let mut ctx = MarkdownRenderer::new(spec.clone())
            .with_html_encode(self.html_encode)
            .with_replace_pre_with_code_fences(self.replace_pre_with_code_fences);
        if let Some(url_prefix) = &self.url_prefix {
            ctx = ctx.with_url_prefix(url_prefix);
        }
        if self.multi {
            ctx = ctx.with_multi(true);
            let commands = spec.cmd.all_subcommands().into_iter().filter(|c| !c.hide);
            for cmd in commands {
                let md = ctx.render_cmd(cmd)?;
                let dir = cmd
                    .full_cmd
                    .iter()
                    .take(cmd.full_cmd.len() - 1)
                    .map(|c| c.to_string())
                    .collect::<Vec<_>>()
                    .join("/");
                let path = self
                    .out_dir
                    .as_ref()
                    .unwrap()
                    .join(dir)
                    .join(format!("{}.md", cmd.name));
                write(&path, &md)?;
            }
            let md_idx = ctx.render_index()?;
            let path_idx = self.out_dir.as_ref().unwrap().join("index.md");
            write(&path_idx, &md_idx)?;
        } else {
            let md = ctx.render_spec()?;
            let path = self.out_file.as_ref().unwrap();
            write(path, &md)?;
        }
        Ok(())
    }
}