Skip to content

feat: add man subcommand to generate man pages with clap_mangen - #41

Merged
owenthcarey merged 2 commits into
weavefoundry:mainfrom
Rahul-pamula:feat/cli-man-pages
Sep 2, 2026
Merged

owenthcarey merged 2 commits into
weavefoundry:mainfrom
Rahul-pamula:feat/cli-man-pages

Conversation

@Rahul-pamula

@Rahul-pamula Rahul-pamula commented Aug 29, 2026 •

Copy link
Copy Markdown
Contributor

Summary
This PR resolves the issue by adding a plainly documented weaveffi man --out <dir> subcommand to generate roff man pages using clap_mangen.

Implementation Details

  • Automatically generates the top-level weaveffi.1 man page and one man page for every existing subcommand (e.g., weaveffi-generate.1, weaveffi-doctor.1).
  • Dynamically iterates over the clap::Command subcommands using .get_subcommands() and forces proper name formatting in the generated roff file.
  • Respects the global --quiet flag (informational logging is suppressed when passed).
  • Added clap_mangen = "0.2" to crates/weaveffi-cli/Cargo.toml.
  • Added an integration test man_generation matching the style of the existing completions_bash test to assert the command executes properly and generates the correct files.
  • Mentioned the new command in the CLI reference table of README.md and added a Generating Man Pages section to docs/src/getting-started.md.

Testing

  • All tests pass locally via cargo test -p weaveffi-cli.
  • Verified man ./man/weaveffi.1 successfully renders without any roff parser errors.
  • Verified cargo fmt --check and cargo clippy -D warnings pass.

Closes: #40

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.

@owenthcarey owenthcarey left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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!

Comment on lines +295 to +337
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(())
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

Suggested change
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(())
}

Comment thread crates/weaveffi-cli/Cargo.toml Outdated
tempfile = { workspace = true }
notify = { workspace = true }
schemars = { workspace = true }
clap_mangen = "0.2"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

Suggested change
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.
@owenthcarey owenthcarey changed the title feat(cli): add man subcommand to generate roff man pages feat: add man subcommand to generate man pages with clap_mangen Sep 2, 2026
@owenthcarey
owenthcarey merged commit 5fa5c76 into weavefoundry:main Sep 2, 2026
20 of 22 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Generate man pages with clap_mangen

2 participants