Skip to main content

zoi_cli/cmd/
gen_man.rs

1//! Implementation of the `zoi gen-man` command.
2//!
3//! This command generates man pages for the Zoi CLI and all its subcommands.
4
5use std::path::{Path, PathBuf};
6use std::{env, fs, io};
7
8use clap::{Command, CommandFactory};
9use clap_mangen::Man;
10
11use crate::cli::Cli;
12
13/// Runs the 'gen-man' command.
14///
15/// Generates man pages for the Zoi CLI and all its subcommands in the 'manuals'
16/// directory.
17///
18/// # Errors
19///
20/// Returns an error if the 'manuals' directory cannot be created or if there is
21/// an issue generating the man pages.
22pub fn run() -> io::Result<()> {
23    let out_dir = env::var("OUT_DIR").unwrap_or_else(|_| "manuals".to_string());
24    let out_path = PathBuf::from(out_dir);
25    fs::create_dir_all(&out_path)?;
26
27    let app = Cli::command();
28    println!("Generating man pages in {}...", out_path.display());
29
30    generate_man_page(&app, &out_path)?;
31
32    for sub_command in app.get_subcommands() {
33        generate_man_pages_recursive(sub_command, &out_path, app.get_name())?;
34    }
35
36    println!(
37        "\nSuccessfully generated man pages in '{}'.",
38        out_path.display()
39    );
40    Ok(())
41}
42
43/// Recursively generates man pages for a command and all its subcommands.
44fn generate_man_pages_recursive(
45    cmd: &Command,
46    out_path: &Path,
47    parent_name: &str
48) -> io::Result<()> {
49    if cmd.is_hide_set() {
50        return Ok(());
51    }
52
53    let full_name = format!("{}-{}", parent_name, cmd.get_name());
54    let leaked_name: &'static str = Box::leak(full_name.into_boxed_str());
55    let new_cmd = cmd.clone().name(leaked_name);
56    generate_man_page(&new_cmd, out_path)?;
57
58    for sub_cmd in new_cmd.get_subcommands() {
59        generate_man_pages_recursive(sub_cmd, out_path, leaked_name)?;
60    }
61
62    Ok(())
63}
64
65/// Generates a single man page for a command.
66fn generate_man_page(app: &Command, out_path: &Path) -> io::Result<()> {
67    let name = app.get_name();
68    let out_file = out_path.join(format!("{name}.1"));
69
70    let man = Man::new(app.clone());
71    let mut buffer = Vec::<u8>::new();
72    man.render(&mut buffer)?;
73
74    fs::write(&out_file, &buffer)?;
75    println!("- {}", out_file.display());
76
77    Ok(())
78}