Skip to content

Commit c04bd0d

Browse files
authored
Merge pull request #6074 from ericgumba/issue_3358
fix: clap_man doesn't render optional values with [=<VALUE>]
2 parents 4c03930 + f4ba05b commit c04bd0d

File tree

8 files changed

+229
-4
lines changed

8 files changed

+229
-4
lines changed

clap_mangen/src/render.rs

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -99,10 +99,28 @@ pub(crate) fn options(roff: &mut Roff, items: &[&Arg]) {
9999
(None, None) => vec![],
100100
};
101101

102-
if opt.get_num_args().expect("built").takes_values() {
103-
if let Some(value) = &opt.get_value_names() {
104-
header.push(roman("="));
105-
header.push(italic(value.join(" ")));
102+
let arg_range = opt.get_num_args().expect("built");
103+
if arg_range.takes_values() {
104+
if let Some(value_names) = &opt.get_value_names() {
105+
let (lhs, rhs) = option_value_markers(opt);
106+
107+
header.push(roman(lhs));
108+
for (i, name) in value_names.iter().enumerate() {
109+
if i > 0 {
110+
header.push(italic(" "));
111+
}
112+
113+
let mut val = format!("<{name}>");
114+
115+
// If this is the last value and it's variadic, add "..."
116+
let is_last = i == value_names.len() - 1;
117+
118+
if is_last && arg_range.max_values() > value_names.len() {
119+
val.push_str("...");
120+
}
121+
header.push(italic(val));
122+
}
123+
header.push(roman(rhs));
106124
}
107125
}
108126

@@ -251,6 +269,31 @@ fn markers(required: bool) -> (&'static str, &'static str) {
251269
}
252270
}
253271

272+
fn option_value_markers(arg: &Arg) -> (&'static str, &'static str) {
273+
let range = arg.get_num_args().expect("built");
274+
275+
if !range.takes_values() {
276+
return ("", ""); // no value, so nothing to render
277+
}
278+
279+
let required = range.min_values() > 0;
280+
let require_equals = arg.is_require_equals_set();
281+
282+
match (required, require_equals) {
283+
// Required, no equals: <VALUE>
284+
(true, false) => (" ", ""),
285+
286+
// Optional, no equals: [<VALUE>]
287+
(false, false) => (" [", "]"),
288+
289+
// Optional, with equals: [=<VALUE>]
290+
(false, true) => ("[=", "]"),
291+
292+
// Required, with equals
293+
(true, true) => ("=", ""),
294+
}
295+
}
296+
254297
fn short_option(opt: char) -> Inline {
255298
bold(format!("-{opt}"))
256299
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
.ie \n(.g .ds Aq \(aq
2+
.el .ds Aq '
3+
.TH my-app 1 "my-app "
4+
.SH NAME
5+
my\-app
6+
.SH SYNOPSIS
7+
\fBmy\-app\fR [\fB\-\-config\fR] [\fB\-h\fR|\fB\-\-help\fR]
8+
.SH DESCRIPTION
9+
.SH OPTIONS
10+
.TP
11+
\fB\-\-config\fR [\fI<FILE1>\fR\fI \fR\fI<FILE2>\fR]
12+
Optional config file
13+
.TP
14+
\fB\-h\fR, \fB\-\-help\fR
15+
Print help
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
.ie \n(.g .ds Aq \(aq
2+
.el .ds Aq '
3+
.TH my-app 1 "my-app "
4+
.SH NAME
5+
my\-app
6+
.SH SYNOPSIS
7+
\fBmy\-app\fR [\fB\-\-config\fR] [\fB\-h\fR|\fB\-\-help\fR]
8+
.SH DESCRIPTION
9+
.SH OPTIONS
10+
.TP
11+
\fB\-\-config\fR [\fI<FILE>\fR]
12+
Optional config file
13+
.TP
14+
\fB\-h\fR, \fB\-\-help\fR
15+
Print help
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
.ie \n(.g .ds Aq \(aq
2+
.el .ds Aq '
3+
.TH my-app 1 "my-app "
4+
.SH NAME
5+
my\-app
6+
.SH SYNOPSIS
7+
\fBmy\-app\fR [\fB\-\-config\fR] [\fB\-h\fR|\fB\-\-help\fR]
8+
.SH DESCRIPTION
9+
.SH OPTIONS
10+
.TP
11+
\fB\-\-config\fR[=\fI<FILE>\fR]
12+
Optional config file
13+
.TP
14+
\fB\-h\fR, \fB\-\-help\fR
15+
Print help
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
.ie \n(.g .ds Aq \(aq
2+
.el .ds Aq '
3+
.TH my-app 1 "my-app "
4+
.SH NAME
5+
my\-app
6+
.SH SYNOPSIS
7+
\fBmy\-app\fR [\fB\-\-config\fR] [\fB\-h\fR|\fB\-\-help\fR]
8+
.SH DESCRIPTION
9+
.SH OPTIONS
10+
.TP
11+
\fB\-\-config\fR=\fI<FILE>\fR
12+
Optional config file
13+
.TP
14+
\fB\-h\fR, \fB\-\-help\fR
15+
Print help
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
.ie \n(.g .ds Aq \(aq
2+
.el .ds Aq '
3+
.TH my-app 1 "my-app "
4+
.SH NAME
5+
my\-app
6+
.SH SYNOPSIS
7+
\fBmy\-app\fR [\fB\-\-config\fR] [\fB\-h\fR|\fB\-\-help\fR]
8+
.SH DESCRIPTION
9+
.SH OPTIONS
10+
.TP
11+
\fB\-\-config\fR \fI<FILE1>\fR\fI \fR\fI<FILE2>...\fR
12+
Optional config file
13+
.TP
14+
\fB\-h\fR, \fB\-\-help\fR
15+
Print help

clap_mangen/tests/testsuite/common.rs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,3 +354,60 @@ pub(crate) fn help_headings(name: &'static str) -> clap::Command {
354354
.value_parser(["always", "never", "auto"]),
355355
)
356356
}
357+
358+
pub(crate) fn value_with_required_equals(name: &'static str) -> clap::Command {
359+
clap::Command::new(name)
360+
.arg(
361+
clap::Arg::new("config")
362+
.long("config")
363+
.value_name("FILE")
364+
.require_equals(true)
365+
.help("Optional config file"),
366+
)
367+
}
368+
369+
pub(crate) fn optional_value_with_required_equals(name: &'static str) -> clap::Command {
370+
clap::Command::new(name)
371+
.arg(
372+
clap::Arg::new("config")
373+
.long("config")
374+
.value_name("FILE")
375+
.require_equals(true)
376+
.num_args(0..=1)
377+
.help("Optional config file"),
378+
)
379+
}
380+
381+
pub(crate) fn optional_value(name: &'static str) -> clap::Command {
382+
clap::Command::new(name)
383+
.arg(
384+
clap::Arg::new("config")
385+
.long("config")
386+
.value_name("FILE")
387+
.num_args(0..=1)
388+
.help("Optional config file"),
389+
)
390+
}
391+
392+
pub(crate) fn multiple_optional_values(name: &'static str) -> clap::Command {
393+
clap::Command::new(name)
394+
.arg(
395+
clap::Arg::new("config")
396+
.long("config")
397+
.value_names(["FILE1", "FILE2"])
398+
.num_args(0..=2)
399+
.help("Optional config file"),
400+
)
401+
}
402+
403+
pub(crate) fn variadic_values(name: &'static str) -> clap::Command {
404+
clap::Command::new(name)
405+
.arg(
406+
clap::Arg::new("config")
407+
.long("config")
408+
.value_names(["FILE1", "FILE2"])
409+
.require_equals(false)
410+
.num_args(3)
411+
.help("Optional config file"),
412+
)
413+
}

clap_mangen/tests/testsuite/roff.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,3 +112,53 @@ fn value_name_without_arg() {
112112
cmd,
113113
);
114114
}
115+
116+
#[test]
117+
fn value_with_required_equals() {
118+
let name = "my-app";
119+
let cmd = common::value_with_required_equals(name);
120+
common::assert_matches(
121+
snapbox::file!["../snapshots/value_with_required_equals.bash.roff"],
122+
cmd,
123+
);
124+
}
125+
126+
#[test]
127+
fn optional_value_with_required_equals() {
128+
let name = "my-app";
129+
let cmd = common::optional_value_with_required_equals(name);
130+
common::assert_matches(
131+
snapbox::file!["../snapshots/optional_with_required_equals_value.bash.roff"],
132+
cmd,
133+
);
134+
}
135+
136+
#[test]
137+
fn optional_value() {
138+
let name = "my-app";
139+
let cmd = common::optional_value(name);
140+
common::assert_matches(
141+
snapbox::file!["../snapshots/optional_value.bash.roff"],
142+
cmd,
143+
);
144+
}
145+
146+
#[test]
147+
fn multiple_optional_values() {
148+
let name = "my-app";
149+
let cmd = common::multiple_optional_values(name);
150+
common::assert_matches(
151+
snapbox::file!["../snapshots/multiple_optional_values.bash.roff"],
152+
cmd,
153+
);
154+
}
155+
156+
#[test]
157+
fn variadic_values() {
158+
let name = "my-app";
159+
let cmd = common::variadic_values(name);
160+
common::assert_matches(
161+
snapbox::file!["../snapshots/variadic_values.bash.roff"],
162+
cmd,
163+
);
164+
}

0 commit comments

Comments
 (0)