clap (Rust)
clap_usage generates a usage spec from a CLI built with clap.
Installation
[dependencies]
clap_usage = "6"Quick Start
use clap::Command;
let mut cmd = Command::new("mycli")
.version("1.0")
.arg(clap::Arg::new("input"));
let mut buf = Vec::new();
clap_usage::generate(&mut cmd, "mycli", &mut buf);
println!("{}", String::from_utf8(buf).unwrap());For migrations, use spec_with_report (or generate_with_report) and require a clean report before trusting the generated spec:
let (spec, report) = clap_usage::spec_with_report(&mut cmd, "mycli");
for loss in report.losses() {
eprintln!("{}: {:?}", loss.command.join(" "), loss);
}
assert!(report.is_lossless());
println!("{spec}");The report includes the command path, clap argument ID, feature, and source detail for each detectable loss. is_lossless() therefore means lossless for behavior visible through clap's public getters, not for every setter clap exposes. Before treating the generated spec as fully compatible, audit the declaration against the compatibility matrix, especially its usage-only and lossy bridge rows.
Integration Pattern
A common approach is to add a hidden --usage-spec flag that outputs the spec:
use clap::{Arg, Command};
use std::io;
let mut cmd = Command::new("mycli")
.arg(Arg::new("usage-spec")
.long("usage-spec")
.hide(true)
.action(clap::ArgAction::SetTrue));
let matches = cmd.clone().get_matches();
if matches.get_flag("usage-spec") {
clap_usage::generate(&mut cmd, "mycli", &mut io::stdout());
return;
}Then pipe the output to usage:
mycli --usage-spec | usage generate completion bash
mycli --usage-spec | usage generate md --out-file docs.md
mycli --usage-spec | usage generate manpage --out-file mycli.1What a generated spec cannot carry
The spec is produced by reading a clap::Command back, so it can only carry what clap exposes a getter for. requires is the notable one it does not: Arg::requires, requires_if, requires_ifs and requires_all are setters with no reader, so a flag declared with them arrives in the spec with no requirement on it, and everything downstream — help, docs, completions — describes a CLI without that constraint. Arg::default_value_if is the same hole: a generated spec never carries default_if.
multicall is one clap does expose: Command::is_multicall_set reaches the spec as multicall #true.