use std::fs;
use std::path::Path;
use anyhow::{Context, Result};
use clap::{Args, CommandFactory};
use clap_mangen::Man;
use crate::cli::Cli;
#[derive(Debug, Args)]
pub(crate) struct MangenArgs {
#[arg(long, value_name = "PATH")]
output_dir: Option<std::path::PathBuf>,
}
pub(crate) fn run(args: &MangenArgs) -> Result<()> {
let cmd = Cli::command();
match args.output_dir.as_deref() {
None => write_root_to_stdout(&cmd)?,
Some(dir) => write_to_dir(&cmd, dir)?,
}
Ok(())
}
fn write_root_to_stdout(cmd: &clap::Command) -> Result<()> {
let stdout = std::io::stdout();
let mut handle = stdout.lock();
Man::new(cmd.clone())
.render(&mut handle)
.context("failed to render cabin(1) man page")?;
Ok(())
}
fn write_to_dir(cmd: &clap::Command, dir: &Path) -> Result<()> {
fs::create_dir_all(dir)
.with_context(|| format!("failed to create man-page output dir {}", dir.display()))?;
let root_path = dir.join(filename_for_root());
let mut file = fs::File::create(&root_path)
.with_context(|| format!("failed to create {}", root_path.display()))?;
Man::new(cmd.clone())
.render(&mut file)
.with_context(|| format!("failed to render {}", root_path.display()))?;
for sub in cmd.get_subcommands() {
if sub.get_name() == "help" {
continue;
}
let display_name: &'static str =
Box::leak(format!("cabin-{}", sub.get_name()).into_boxed_str());
let renamed = sub.clone().name(display_name).hide(false);
let path = dir.join(filename_for_subcommand(display_name));
let mut file = fs::File::create(&path)
.with_context(|| format!("failed to create {}", path.display()))?;
Man::new(renamed)
.render(&mut file)
.with_context(|| format!("failed to render {}", path.display()))?;
}
Ok(())
}
fn filename_for_root() -> String {
"cabin.1".to_owned()
}
fn filename_for_subcommand(display_name: &str) -> String {
format!("{display_name}.1")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn root_filename_is_cabin_dot_one() {
assert_eq!(filename_for_root(), "cabin.1");
}
#[test]
fn subcommand_filename_uses_dashed_form() {
assert_eq!(filename_for_subcommand("cabin-build"), "cabin-build.1");
}
#[test]
fn hidden_subcommands_are_known_and_curated() {
use std::collections::BTreeSet;
let cmd = Cli::command();
let hidden: BTreeSet<&str> = cmd
.get_subcommands()
.filter(|s| s.is_hide_set())
.map(clap::Command::get_name)
.collect();
let expected: BTreeSet<&str> = [
"compgen", "explain", "fetch", "mangen", "metadata", "package", "resolve", "tree",
"vendor",
]
.iter()
.copied()
.collect();
assert_eq!(
hidden, expected,
"hidden subcommand set drifted; update tests and review --help surface"
);
}
}