diff --git a/clap_mangen/src/lib.rs b/clap_mangen/src/lib.rs index 7b8d8af7900..50b7dbac6d1 100644 --- a/clap_mangen/src/lib.rs +++ b/clap_mangen/src/lib.rs @@ -244,8 +244,39 @@ impl Man { } fn _render_options_section(&self, roff: &mut Roff) { - roff.control("SH", ["OPTIONS"]); - render::options(roff, &self.cmd); + let help_headings = self + .cmd + .get_arguments() + .filter(|a| !a.is_hide_set()) + .filter_map(|arg| arg.get_help_heading()) + .fold(vec![], |mut acc, header| { + if !acc.contains(&header) { + acc.push(header); + } + + acc + }); + + let (args, mut args_with_heading) = + self.cmd + .get_arguments() + .filter(|a| !a.is_hide_set()) + .partition::, _>(|a| a.get_help_heading().is_none()); + + if !args.is_empty() { + roff.control("SH", ["OPTIONS"]); + render::options(roff, &args); + } + + for heading in help_headings { + let args; + (args, args_with_heading) = args_with_heading + .into_iter() + .partition(|&a| a.get_help_heading() == Some(heading)); + + roff.control("SH", [heading.to_uppercase().as_str()]); + render::options(roff, &args); + } } /// Render the SUBCOMMANDS section into the writer. diff --git a/clap_mangen/src/render.rs b/clap_mangen/src/render.rs index 81d4e8c6b1c..c48d13a4812 100644 --- a/clap_mangen/src/render.rs +++ b/clap_mangen/src/render.rs @@ -88,9 +88,7 @@ pub(crate) fn synopsis(roff: &mut Roff, cmd: &clap::Command) { roff.text(line); } -pub(crate) fn options(roff: &mut Roff, cmd: &clap::Command) { - let items: Vec<_> = cmd.get_arguments().filter(|i| !i.is_hide_set()).collect(); - +pub(crate) fn options(roff: &mut Roff, items: &[&Arg]) { for opt in items.iter().filter(|a| !a.is_positional()) { let mut header = match (opt.get_short(), opt.get_long()) { (Some(short), Some(long)) => { diff --git a/clap_mangen/tests/snapshots/help_headings.bash.roff b/clap_mangen/tests/snapshots/help_headings.bash.roff new file mode 100644 index 00000000000..e3400e2501b --- /dev/null +++ b/clap_mangen/tests/snapshots/help_headings.bash.roff @@ -0,0 +1,25 @@ +.ie \n(.g .ds Aq \(aq +.el .ds Aq ' +.TH my-app 1 "my-app " +.SH NAME +my\-app +.SH SYNOPSIS +\fBmy\-app\fR [\fB\-r\fR|\fB\-\-recursive\fR] [\fB\-f\fR|\fB\-\-force\fR] [\fB\-h\fR|\fB\-\-help\fR] [\fIcolor\fR] +.SH DESCRIPTION +.SH OPTIONS +.TP +\fB\-r\fR, \fB\-\-recursive\fR + +.TP +\fB\-h\fR, \fB\-\-help\fR +Print help +.SH "CONFLICT OPTIONS" +.TP +\fB\-f\fR, \fB\-\-force\fR + +.SH "GLOBAL OPTIONS" +.TP +[\fIcolor\fR] + +.br +[\fIpossible values: \fRalways, never, auto] diff --git a/clap_mangen/tests/testsuite/common.rs b/clap_mangen/tests/testsuite/common.rs index fcb20cd8f33..e4ed80c535b 100644 --- a/clap_mangen/tests/testsuite/common.rs +++ b/clap_mangen/tests/testsuite/common.rs @@ -323,3 +323,34 @@ pub(crate) fn value_name_without_arg(name: &'static str) -> clap::Command { .action(clap::ArgAction::SetTrue), ) } + +pub(crate) fn help_headings(name: &'static str) -> clap::Command { + clap::Command::new(name) + .arg( + clap::Arg::new("recursive") + .long("recursive") + .short('r') + .action(clap::ArgAction::SetTrue), + ) + .next_help_heading("Conflict Options") + .arg( + clap::Arg::new("force") + .long("force") + .short('f') + .action(clap::ArgAction::SetTrue), + ) + .next_help_heading("Hidden Options") + .arg( + clap::Arg::new("debug") + .long("debug") + .short('d') + .hide(true) + .action(clap::ArgAction::SetTrue), + ) + .next_help_heading("Global Options") + .arg( + clap::Arg::new("color") + .global(true) + .value_parser(["always", "never", "auto"]), + ) +} diff --git a/clap_mangen/tests/testsuite/roff.rs b/clap_mangen/tests/testsuite/roff.rs index 9f3f4ee660e..6adfcd238fc 100644 --- a/clap_mangen/tests/testsuite/roff.rs +++ b/clap_mangen/tests/testsuite/roff.rs @@ -96,6 +96,13 @@ fn sub_subcommands_help() { } } +#[test] +fn help_headings() { + let name = "my-app"; + let cmd = common::help_headings(name); + common::assert_matches(snapbox::file!["../snapshots/help_headings.bash.roff"], cmd); +} + #[test] fn value_name_without_arg() { let name = "my-app";