feat: add man subcommand to generate man pages with clap_mangen - #41
Conversation
This adds the `weaveffi man --out <dir>` subcommand to automatically generate man pages for the CLI using `clap_mangen`. It generates the top-level `weaveffi.1` man page along with a man page for every subcommand, respects the global `--quiet` flag, and includes an end-to-end integration test.
ced0a1e to
d350e70
Compare
owenthcarey
left a comment
There was a problem hiding this comment.
Thanks for the contribution, this is a really thorough first PR! The tests, docs, and --quiet handling are all exactly what the issue asked for, and CI is green across the board.
I'm requesting two small changes before merging, both inline below with suggestions you can apply directly. Once those are in, I'll squash merge. Thanks again!
| fn cmd_man(out: &str, quiet: bool) -> Result<()> { | ||
| let out_dir = std::path::PathBuf::from(out); | ||
| std::fs::create_dir_all(&out_dir) | ||
| .into_diagnostic() | ||
| .wrap_err_with(|| format!("failed to create directory: {out}"))?; | ||
|
|
||
| let mut cmd = Cli::command(); | ||
| cmd.build(); | ||
|
|
||
| let mut buffer = Vec::new(); | ||
| clap_mangen::Man::new(cmd.clone()) | ||
| .render(&mut buffer) | ||
| .into_diagnostic() | ||
| .wrap_err("failed to render man page for weaveffi")?; | ||
|
|
||
| std::fs::write(out_dir.join("weaveffi.1"), buffer) | ||
| .into_diagnostic() | ||
| .wrap_err("failed to write weaveffi.1")?; | ||
|
|
||
| for sub in cmd.get_subcommands() { | ||
| let sub_name = sub.get_name(); | ||
| if sub_name == "help" { | ||
| continue; | ||
| } | ||
|
|
||
| let sub_cmd = sub.clone().name(&*format!("weaveffi-{sub_name}").leak()); | ||
| let mut sub_buffer = Vec::new(); | ||
| clap_mangen::Man::new(sub_cmd) | ||
| .render(&mut sub_buffer) | ||
| .into_diagnostic() | ||
| .wrap_err_with(|| format!("failed to render man page for weaveffi-{sub_name}"))?; | ||
|
|
||
| std::fs::write(out_dir.join(format!("weaveffi-{sub_name}.1")), sub_buffer) | ||
| .into_diagnostic() | ||
| .wrap_err_with(|| format!("failed to write weaveffi-{sub_name}.1"))?; | ||
| } | ||
|
|
||
| if !quiet { | ||
| println!("Man pages written to {}", out_dir.display()); | ||
| } | ||
|
|
||
| Ok(()) | ||
| } |
There was a problem hiding this comment.
clap_mangen ships a helper that does all of this for you: clap_mangen::generate_to renders the top-level page plus one page per subcommand, names them weaveffi-<sub>.1 via clap's display names, skips the help subcommand, and recurses into nested subcommands if we ever add any.
That also gets rid of the .leak() workaround, which permanently leaks each formatted name (clap's Command::name needs a 'static string when the string feature is off, which is why you needed it, but we'd rather not have that pattern in the codebase).
Your existing man_generation test passes unchanged with this version:
| fn cmd_man(out: &str, quiet: bool) -> Result<()> { | |
| let out_dir = std::path::PathBuf::from(out); | |
| std::fs::create_dir_all(&out_dir) | |
| .into_diagnostic() | |
| .wrap_err_with(|| format!("failed to create directory: {out}"))?; | |
| let mut cmd = Cli::command(); | |
| cmd.build(); | |
| let mut buffer = Vec::new(); | |
| clap_mangen::Man::new(cmd.clone()) | |
| .render(&mut buffer) | |
| .into_diagnostic() | |
| .wrap_err("failed to render man page for weaveffi")?; | |
| std::fs::write(out_dir.join("weaveffi.1"), buffer) | |
| .into_diagnostic() | |
| .wrap_err("failed to write weaveffi.1")?; | |
| for sub in cmd.get_subcommands() { | |
| let sub_name = sub.get_name(); | |
| if sub_name == "help" { | |
| continue; | |
| } | |
| let sub_cmd = sub.clone().name(&*format!("weaveffi-{sub_name}").leak()); | |
| let mut sub_buffer = Vec::new(); | |
| clap_mangen::Man::new(sub_cmd) | |
| .render(&mut sub_buffer) | |
| .into_diagnostic() | |
| .wrap_err_with(|| format!("failed to render man page for weaveffi-{sub_name}"))?; | |
| std::fs::write(out_dir.join(format!("weaveffi-{sub_name}.1")), sub_buffer) | |
| .into_diagnostic() | |
| .wrap_err_with(|| format!("failed to write weaveffi-{sub_name}.1"))?; | |
| } | |
| if !quiet { | |
| println!("Man pages written to {}", out_dir.display()); | |
| } | |
| Ok(()) | |
| } | |
| fn cmd_man(out: &str, quiet: bool) -> Result<()> { | |
| let out_dir = std::path::PathBuf::from(out); | |
| std::fs::create_dir_all(&out_dir) | |
| .into_diagnostic() | |
| .wrap_err_with(|| format!("failed to create directory: {out}"))?; | |
| clap_mangen::generate_to(Cli::command(), &out_dir) | |
| .into_diagnostic() | |
| .wrap_err("failed to generate man pages")?; | |
| if !quiet { | |
| println!("Man pages written to {}", out_dir.display()); | |
| } | |
| Ok(()) | |
| } |
| tempfile = { workspace = true } | ||
| notify = { workspace = true } | ||
| schemars = { workspace = true } | ||
| clap_mangen = "0.2" |
There was a problem hiding this comment.
All dependencies in this workspace are pinned in [workspace.dependencies] in the root Cargo.toml (its siblings clap and clap_complete are there already). Could you add clap_mangen = "0.2" there and reference it here with:
| clap_mangen = "0.2" | |
| clap_mangen = { workspace = true } |
This addresses maintainer feedback by replacing the manual roff generation loop with the official `clap_mangen::generate_to` helper. It also migrates the `clap_mangen` dependency definition to the root workspace Cargo.toml to align with the rest of the project's dependency management strategy.
Summary
This PR resolves the issue by adding a plainly documented
weaveffi man --out <dir>subcommand to generate roff man pages usingclap_mangen.Implementation Details
weaveffi.1man page and one man page for every existing subcommand (e.g.,weaveffi-generate.1,weaveffi-doctor.1).clap::Commandsubcommands using.get_subcommands()and forces proper name formatting in the generated roff file.--quietflag (informational logging is suppressed when passed).clap_mangen = "0.2"tocrates/weaveffi-cli/Cargo.toml.man_generationmatching the style of the existingcompletions_bashtest to assert the command executes properly and generates the correct files.README.mdand added aGenerating Man Pagessection todocs/src/getting-started.md.Testing
cargo test -p weaveffi-cli.man ./man/weaveffi.1successfully renders without any roff parser errors.cargo fmt --checkandcargo clippy -D warningspass.Closes: #40