Skip to content

Commit 870ff30

Browse files
committed
test: Add test for flatten with mixed headings
- add test case to verify that mixed headings are flattened correctly - update usage and help templates to display in custom headings order
1 parent 87740bd commit 870ff30

File tree

3 files changed

+202
-5
lines changed

3 files changed

+202
-5
lines changed

clap_builder/src/output/help_template.rs

Lines changed: 81 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -892,7 +892,18 @@ impl HelpTemplate<'_, '_> {
892892
subcommand,
893893
);
894894
}
895-
for (_, subcommand) in ord_v {
895+
896+
let custom_headings = self
897+
.cmd
898+
.get_subcommands()
899+
.filter_map(|cmd| cmd.get_help_heading())
900+
.collect::<FlatSet<_>>();
901+
902+
// Write commands that have no custom heading
903+
for (_, subcommand) in ord_v
904+
.iter()
905+
.filter(|item| item.1.get_help_heading().is_none())
906+
{
896907
if !*first {
897908
self.writer.push_str("\n\n");
898909
}
@@ -931,6 +942,71 @@ impl HelpTemplate<'_, '_> {
931942
sub_help.write_flat_subcommands(subcommand, first);
932943
}
933944
}
945+
946+
// Commands under custom headings
947+
if !custom_headings.is_empty() {
948+
for heading in custom_headings {
949+
let cmds = self
950+
.cmd
951+
.get_subcommands()
952+
.filter(|sc| {
953+
if let Some(help_heading) = sc.get_help_heading() {
954+
return help_heading == heading;
955+
}
956+
false
957+
})
958+
.filter(|sc| should_show_subcommand(sc))
959+
.collect::<Vec<_>>();
960+
961+
if !cmds.is_empty() {
962+
for (_, subcommand) in ord_v
963+
.clone()
964+
.into_iter()
965+
.filter(|item| item.1.get_help_heading() == Some(heading))
966+
{
967+
if !*first {
968+
self.writer.push_str("\n\n");
969+
}
970+
*first = false;
971+
972+
let heading = subcommand.get_usage_name_fallback();
973+
let about = subcommand
974+
.get_about()
975+
.or_else(|| subcommand.get_long_about())
976+
.unwrap_or_default();
977+
978+
let _ = write!(self.writer, "{header}{heading}:{header:#}",);
979+
if !about.is_empty() {
980+
let _ = write!(self.writer, "\n{about}",);
981+
}
982+
983+
let args = subcommand
984+
.get_arguments()
985+
.filter(|arg| {
986+
should_show_arg(self.use_long, arg) && !arg.is_global_set()
987+
})
988+
.collect::<Vec<_>>();
989+
if !args.is_empty() {
990+
self.writer.push_str("\n");
991+
}
992+
993+
let mut sub_help = HelpTemplate {
994+
writer: self.writer,
995+
cmd: subcommand,
996+
styles: self.styles,
997+
usage: self.usage,
998+
next_line_help: self.next_line_help,
999+
term_w: self.term_w,
1000+
use_long: self.use_long,
1001+
};
1002+
sub_help.write_args(&args, heading, option_sort_key);
1003+
if subcommand.is_flatten_help_set() {
1004+
sub_help.write_flat_subcommands(subcommand, first);
1005+
}
1006+
}
1007+
}
1008+
}
1009+
}
9341010
}
9351011

9361012
/// Writes help for subcommands of a Parser Object to the wrapped stream.
@@ -968,7 +1044,7 @@ impl HelpTemplate<'_, '_> {
9681044

9691045
let next_line_help = self.will_subcommands_wrap(cmd.get_subcommands(), longest);
9701046

971-
let command_custom_headings = self
1047+
let custom_headings = self
9721048
.cmd
9731049
.get_subcommands()
9741050
.filter_map(|cmd| cmd.get_help_heading())
@@ -988,8 +1064,9 @@ impl HelpTemplate<'_, '_> {
9881064
self.write_subcommand(sc_str.1, sc, next_line_help, longest);
9891065
}
9901066

991-
if !command_custom_headings.is_empty() {
992-
for heading in command_custom_headings {
1067+
// Commands under custom headings
1068+
if !custom_headings.is_empty() {
1069+
for heading in custom_headings {
9931070
let cmds = self
9941071
.cmd
9951072
.get_subcommands()

clap_builder/src/output/usage.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,17 +113,53 @@ impl Usage<'_> {
113113
}
114114
let mut cmd = self.cmd.clone();
115115
cmd.build();
116+
117+
// Get the custom headings
118+
let custom_headings = cmd
119+
.get_subcommands()
120+
.filter_map(|sc| sc.get_help_heading())
121+
.collect::<FlatSet<_>>();
122+
123+
// Write commands without headings
116124
for (i, sub) in cmd
117125
.get_subcommands()
118126
.filter(|c| !c.is_hide_set())
127+
.filter(|c| c.get_help_heading().is_none())
119128
.enumerate()
120129
{
130+
if sub.get_name() == "help" {
131+
continue;
132+
}
121133
if i != 0 {
122134
styled.trim_end();
123135
let _ = write!(styled, "{USAGE_SEP}");
124136
}
125137
Usage::new(sub).write_usage_no_title(styled, &[]);
126138
}
139+
140+
// Write commands with headings
141+
for heading in custom_headings {
142+
for sub in cmd
143+
.get_subcommands()
144+
.filter(|c| !c.is_hide_set())
145+
.filter(|c| c.get_help_heading() == Some(heading))
146+
{
147+
if sub.get_name() == "help" {
148+
continue;
149+
}
150+
styled.trim_end();
151+
let _ = write!(styled, "{USAGE_SEP}");
152+
Usage::new(sub).write_usage_no_title(styled, &[]);
153+
}
154+
}
155+
156+
// Write help command last (regardless of custom headings)
157+
if let Some(sub) = cmd.find_subcommand("help") {
158+
159+
styled.trim_end();
160+
let _ = write!(styled, "{USAGE_SEP}");
161+
Usage::new(sub).write_usage_no_title(styled, &[]);
162+
}
127163
} else {
128164
self.write_arg_usage(styled, &[], true);
129165
self.write_subcommand_usage(styled);

tests/builder/help.rs

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4756,7 +4756,7 @@ Options:
47564756
}
47574757

47584758
#[test]
4759-
fn test_multiple_commands_mixed_standard_and_custom_headers() {
4759+
fn test_multiple_commands_mixed_standard_and_custom_headers() {
47604760
static VISIBLE_ALIAS_HELP: &str = "\
47614761
Usage: clap-test [COMMAND]
47624762
@@ -4853,6 +4853,90 @@ Options:
48534853
utils::assert_output(cmd, "clap-test --help", VISIBLE_ALIAS_HELP, false);
48544854
}
48554855

4856+
#[test]
4857+
fn test_multiple_commands_mixed_headings_flatten() {
4858+
static VISIBLE_ALIAS_HELP: &str = "\
4859+
Usage: clap-test
4860+
clap-test def_cmd1
4861+
clap-test def_cmd2
4862+
clap-test cust_cmd1
4863+
clap-test cust_cmd2 --child <child>
4864+
clap-test other_cmd1
4865+
clap-test other_cmd2 --child <child>
4866+
clap-test help [COMMAND]...
4867+
4868+
Options:
4869+
-h, --help Print help
4870+
-V, --version Print version
4871+
4872+
clap-test def_cmd1:
4873+
Def_cmd1 under default command heading
4874+
-h, --help Print help
4875+
4876+
clap-test def_cmd2:
4877+
Def_cmd2 under default command heading
4878+
-h, --help Print help
4879+
4880+
clap-test help:
4881+
Print this message or the help of the given subcommand(s)
4882+
[COMMAND]... Print help for the subcommand(s)
4883+
4884+
clap-test cust_cmd1:
4885+
Cust_cmd1 under custom command heading
4886+
-h, --help Print help
4887+
4888+
clap-test cust_cmd2:
4889+
Cust_cmd2 under custom command heading
4890+
--child <child> child help
4891+
-h, --help Print help
4892+
4893+
clap-test other_cmd1:
4894+
Other_cmd1 under other command heading
4895+
-h, --help Print help
4896+
4897+
clap-test other_cmd2:
4898+
Other_cmd2 under other command heading
4899+
--child <child>
4900+
-h, --help Print help
4901+
";
4902+
4903+
let cmd = Command::new("clap-test")
4904+
.version("2.6")
4905+
.flatten_help(true)
4906+
.subcommand(
4907+
Command::new("def_cmd1")
4908+
.about("Def_cmd1 under default command heading")
4909+
)
4910+
.subcommand(
4911+
Command::new("cust_cmd1")
4912+
.about("Cust_cmd1 under custom command heading")
4913+
.help_heading("Custom Heading"),
4914+
)
4915+
.subcommand(
4916+
Command::new("other_cmd1")
4917+
.about("Other_cmd1 under other command heading")
4918+
.help_heading("Other Heading"),
4919+
)
4920+
.subcommand(
4921+
Command::new("other_cmd2")
4922+
.about("Other_cmd2 under other command heading")
4923+
.help_heading("Other Heading")
4924+
.arg(Arg::new("child").long("child").required(true)),
4925+
)
4926+
.subcommand(
4927+
Command::new("def_cmd2")
4928+
.about("Def_cmd2 under default command heading")
4929+
)
4930+
.subcommand(
4931+
Command::new("cust_cmd2")
4932+
.about("Cust_cmd2 under custom command heading")
4933+
.help_heading("Custom Heading")
4934+
.arg(Arg::new("child").long("child").required(true).help("child help")),
4935+
);
4936+
4937+
utils::assert_output(cmd, "clap-test --help", VISIBLE_ALIAS_HELP, false);
4938+
}
4939+
48564940

48574941
#[test]
48584942
fn test_help_header_hide_commands_header() {

0 commit comments

Comments
 (0)