Skip to content

Commit 2e8b10a

Browse files
Eric GumbaEric Gumba
authored andcommitted
responded to code review:
1. Reused sorting method in help_template.rs. 2. More thorough testing of configured ordering, instead of relying solely on snapbox. 3. Added sorting to subcommand section of clap_mangen
1 parent 521f01d commit 2e8b10a

File tree

5 files changed

+191
-6
lines changed

5 files changed

+191
-6
lines changed

clap_mangen/src/render.rs

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,26 @@
11
use clap::{Arg, ArgAction};
22
use roff::{bold, italic, roman, Inline, Roff};
33

4+
pub(crate) fn option_sort_key(arg: &Arg) -> (usize, String) {
5+
let key = if let Some(x) = arg.get_short() {
6+
let mut s = x.to_ascii_lowercase().to_string();
7+
s.push(if x.is_ascii_lowercase() { '0' } else { '1' });
8+
s
9+
} else if let Some(x) = arg.get_long() {
10+
x.to_string()
11+
} else {
12+
let mut s = '{'.to_string();
13+
s.push_str(arg.get_id().as_str());
14+
s
15+
};
16+
(arg.get_display_order(), key)
17+
}
18+
19+
/// Provides consistent sorting for subcommands
20+
pub(crate) fn subcmd_sort_key(subcmd: &clap::Command) -> (usize, &str) {
21+
(subcmd.get_display_order(), subcmd.get_name())
22+
}
23+
424
pub(crate) fn subcommand_heading(cmd: &clap::Command) -> &str {
525
match cmd.get_subcommand_help_heading() {
626
Some(title) => title,
@@ -35,7 +55,7 @@ pub(crate) fn synopsis(roff: &mut Roff, cmd: &clap::Command) {
3555

3656
let mut opts: Vec<_> = cmd.get_arguments().filter(|i| !i.is_hide_set()).collect();
3757

38-
opts.sort_by_key(|opt| opt.get_display_order());
58+
opts.sort_by_key(|opt| option_sort_key(opt));
3959

4060
for opt in opts {
4161
let (lhs, rhs) = option_markers(opt);
@@ -94,7 +114,7 @@ pub(crate) fn synopsis(roff: &mut Roff, cmd: &clap::Command) {
94114

95115
pub(crate) fn options(roff: &mut Roff, items: &[&Arg]) {
96116
let mut sorted_items = items.to_vec();
97-
sorted_items.sort_by_key(|opt| opt.get_display_order());
117+
sorted_items.sort_by_key(|opt| option_sort_key(opt));
98118

99119
for opt in sorted_items.iter().filter(|a| !a.is_positional()) {
100120
let mut header = match (opt.get_short(), opt.get_long()) {
@@ -138,7 +158,7 @@ pub(crate) fn options(roff: &mut Roff, items: &[&Arg]) {
138158
}
139159
}
140160

141-
for pos in sorted_items.iter().filter(|a| a.is_positional()) {
161+
for pos in items.iter().filter(|a| a.is_positional()) {
142162
let mut header = vec![];
143163
let (lhs, rhs) = option_markers(pos);
144164
header.push(roman(lhs));
@@ -206,7 +226,10 @@ fn possible_options(roff: &mut Roff, arg: &Arg, arg_help_written: bool) {
206226
}
207227

208228
pub(crate) fn subcommands(roff: &mut Roff, cmd: &clap::Command, section: &str) {
209-
for sub in cmd.get_subcommands().filter(|s| !s.is_hide_set()) {
229+
let mut sorted_subcommands: Vec<_> =
230+
cmd.get_subcommands().filter(|s| !s.is_hide_set()).collect();
231+
sorted_subcommands.sort_by_key(|sub| subcmd_sort_key(sub));
232+
for sub in sorted_subcommands {
210233
roff.control("TP", []);
211234

212235
let name = format!(
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
.ie \n(.g .ds Aq \(aq
2+
.el .ds Aq '
3+
.TH my-app 1 "my-app 1"
4+
.SH NAME
5+
my\-app
6+
.SH SYNOPSIS
7+
\fBmy\-app\fR [\fB\-h\fR|\fB\-\-help\fR] [\fB\-V\fR|\fB\-\-version\fR] [\fIsubcommands\fR]
8+
.SH DESCRIPTION
9+
.SH OPTIONS
10+
.TP
11+
\fB\-h\fR, \fB\-\-help\fR
12+
Print help
13+
.TP
14+
\fB\-V\fR, \fB\-\-version\fR
15+
Print version
16+
.SH SUBCOMMANDS
17+
.TP
18+
my\-app\-a1(1)
19+
blah a1
20+
.TP
21+
my\-app\-b1(1)
22+
blah b1
23+
.TP
24+
my\-app\-help(1)
25+
Print this message or the help of the given subcommand(s)
26+
.SH VERSION
27+
v1
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
.ie \n(.g .ds Aq \(aq
2+
.el .ds Aq '
3+
.TH my-app 1 "my-app 1"
4+
.SH NAME
5+
my\-app
6+
.SH SYNOPSIS
7+
\fBmy\-app\fR [\fB\-h\fR|\fB\-\-help\fR] [\fB\-V\fR|\fB\-\-version\fR] [\fIsubcommands\fR]
8+
.SH DESCRIPTION
9+
.SH OPTIONS
10+
.TP
11+
\fB\-h\fR, \fB\-\-help\fR
12+
Print help
13+
.TP
14+
\fB\-V\fR, \fB\-\-version\fR
15+
Print version
16+
.SH SUBCOMMANDS
17+
.TP
18+
my\-app\-b1(1)
19+
blah b1
20+
.TP
21+
my\-app\-a1(1)
22+
blah a1
23+
.TP
24+
my\-app\-help(1)
25+
Print this message or the help of the given subcommand(s)
26+
.SH VERSION
27+
v1

clap_mangen/tests/testsuite/common.rs

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,39 @@ pub(crate) fn env_value_command(name: &'static str) -> clap::Command {
273273
)
274274
}
275275

276+
pub(crate) fn mangen_output(cmd: &clap::Command) -> String {
277+
let mut buf = vec![];
278+
clap_mangen::Man::new(cmd.clone()).render(&mut buf).unwrap();
279+
280+
let s = match std::str::from_utf8(&buf) {
281+
Ok(s) => s,
282+
Err(e) => panic!("Failed to convert output to UTF-8: {e}"),
283+
};
284+
285+
s.to_string()
286+
}
287+
288+
// Go through the output and assert that the keywords
289+
// appear in the expected order
290+
pub(crate) fn is_correct_ordering(
291+
keywords: &[&'static str],
292+
text: &str,
293+
) -> bool {
294+
let mut s = text;
295+
for keyword in keywords {
296+
if !s.contains(keyword) {
297+
return false;
298+
}
299+
// everytime we find a match,
300+
// we only look at the rest of the string
301+
s = match s.split(keyword).last() {
302+
Some(rest) => rest,
303+
None => return false,
304+
};
305+
}
306+
true
307+
}
308+
276309
pub(crate) fn assert_matches(expected: impl IntoData, cmd: clap::Command) {
277310
let mut buf = vec![];
278311
clap_mangen::Man::new(cmd).render(&mut buf).unwrap();
@@ -324,7 +357,6 @@ pub(crate) fn value_name_without_arg(name: &'static str) -> clap::Command {
324357
)
325358
}
326359

327-
328360
pub(crate) fn configured_display_order_args(name: &'static str) -> clap::Command {
329361
clap::Command::new(name)
330362
.arg(clap::Arg::new("1st").help("1st"))
@@ -360,7 +392,6 @@ pub(crate) fn configured_display_order_args(name: &'static str) -> clap::Command
360392

361393
}
362394

363-
364395
pub(crate) fn help_headings(name: &'static str) -> clap::Command {
365396
clap::Command::new(name)
366397
.arg(
@@ -391,3 +422,30 @@ pub(crate) fn help_headings(name: &'static str) -> clap::Command {
391422
.value_parser(["always", "never", "auto"]),
392423
)
393424
}
425+
426+
pub(crate) fn configured_subcmd_order(name: &'static str) -> clap::Command {
427+
clap::Command::new(name)
428+
.version("1")
429+
.next_display_order(None)
430+
.subcommands(vec![
431+
clap::Command::new("b1")
432+
.about("blah b1")
433+
.arg(clap::Arg::new("test").short('t').action(clap::ArgAction::SetTrue)),
434+
clap::Command::new("a1")
435+
.about("blah a1")
436+
.arg(clap::Arg::new("roster").short('r').action(clap::ArgAction::SetTrue)),
437+
])
438+
}
439+
440+
pub(crate) fn default_subcmd_order(name: &'static str) -> clap::Command {
441+
clap::Command::new(name)
442+
.version("1")
443+
.subcommands(vec![
444+
clap::Command::new("b1")
445+
.about("blah b1")
446+
.arg(clap::Arg::new("test").short('t').action(clap::ArgAction::SetTrue)),
447+
clap::Command::new("a1")
448+
.about("blah a1")
449+
.arg(clap::Arg::new("roster").short('r').action(clap::ArgAction::SetTrue)),
450+
])
451+
}

clap_mangen/tests/testsuite/roff.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,8 +118,58 @@ fn configured_display_order_args() {
118118
let name = "my-app";
119119
let cmd = common::configured_display_order_args(name);
120120

121+
let s = common::mangen_output(&cmd);
122+
123+
let ordered_keywords = [
124+
"first", "second", "third", "fourth", "1st", "2nd", "3rd",
125+
];
126+
let default_ordered_keywords = [
127+
"third", "fourth", "first", "second", "1st", "2nd", "3rd",
128+
];
129+
130+
assert!(common::is_correct_ordering(&ordered_keywords, &s));
131+
assert!(!common::is_correct_ordering(&default_ordered_keywords, &s));
132+
121133
common::assert_matches(
122134
snapbox::file!["../snapshots/configured_display_order_args.roff"],
123135
cmd,
124136
);
125137
}
138+
139+
#[test]
140+
fn configured_subcmd_order() {
141+
let name = "my-app";
142+
let cmd = common::configured_subcmd_order(name);
143+
144+
let s = common::mangen_output(&cmd);
145+
146+
let ordered_keywords = ["a1", "b1"];
147+
let default_ordered_keywords = ["b1", "a1"];
148+
149+
assert!(common::is_correct_ordering(&ordered_keywords, &s));
150+
assert!(!common::is_correct_ordering(&default_ordered_keywords, &s));
151+
152+
common::assert_matches(
153+
snapbox::file!["../snapshots/configured_subcmd_order.roff"],
154+
cmd,
155+
);
156+
}
157+
158+
#[test]
159+
fn default_subcmd_order() {
160+
let name = "my-app";
161+
let cmd = common::default_subcmd_order(name);
162+
163+
let s = common::mangen_output(&cmd);
164+
165+
let ordered_keywords = ["a1", "b1"];
166+
let default_ordered_keywords = ["b1", "a1"];
167+
168+
assert!(!common::is_correct_ordering(&ordered_keywords, &s));
169+
assert!(common::is_correct_ordering(&default_ordered_keywords, &s));
170+
171+
common::assert_matches(
172+
snapbox::file!["../snapshots/default_subcmd_order.roff"],
173+
cmd,
174+
);
175+
}

0 commit comments

Comments
 (0)