Skip to content

Commit

Permalink
fix(help): Optional arg values in brackets
Browse files Browse the repository at this point in the history
When an Arg uses .min_values(0), that arg's value(s) are effectively
optional. This is conventionaly denoted in help messages by wrapping the
arg's values in square brackets. For example:

    --foo[=value]
    --bar [value]

This kind of argument can be seen in the wild in many git commands; e.g.
git-status(1).

Signed-off-by: Peter Grayson <pete@jpgrayson.net>
  • Loading branch information
jpgrayson committed Jan 26, 2022
1 parent 50b3d29 commit 86474a2
Show file tree
Hide file tree
Showing 5 changed files with 54 additions and 19 deletions.
12 changes: 11 additions & 1 deletion src/build/arg/mod.rs
Expand Up @@ -5013,9 +5013,16 @@ impl<'help> Display for Arg<'help> {
} else if let Some(s) = self.short {
write!(f, "-{}", s)?;
}
let is_optional_val = self.min_vals == Some(0);
if !self.is_positional() && self.is_set(ArgSettings::TakesValue) {
let sep = if self.is_set(ArgSettings::RequireEquals) {
"="
if is_optional_val {
"[="
} else {
"="
}
} else if is_optional_val {
" ["
} else {
" "
};
Expand All @@ -5024,6 +5031,9 @@ impl<'help> Display for Arg<'help> {
if self.is_set(ArgSettings::TakesValue) || self.is_positional() {
display_arg_val(self, |s, _| write!(f, "{}", s))?;
}
if !self.is_positional() && self.is_set(ArgSettings::TakesValue) && is_optional_val {
write!(f, "]")?;
}

Ok(())
}
Expand Down
46 changes: 30 additions & 16 deletions src/output/help.rs
Expand Up @@ -269,13 +269,11 @@ impl<'help, 'app, 'parser, 'writer> Help<'help, 'app, 'parser, 'writer> {
}
}

/// Writes argument's long command to the wrapped stream.
/// Writes argument's long command and possible values to the wrapped stream.
fn long(&mut self, arg: &Arg<'help>) -> io::Result<()> {
debug!("Help::long");
if arg.is_positional() {
return Ok(());
}
if arg.is_set(ArgSettings::TakesValue) {
let is_optional_val = arg.min_vals == Some(0);
if !arg.is_positional() && arg.is_set(ArgSettings::TakesValue) {
if let Some(l) = arg.long {
if arg.short.is_some() {
self.none(", ")?;
Expand All @@ -284,7 +282,13 @@ impl<'help, 'app, 'parser, 'writer> Help<'help, 'app, 'parser, 'writer> {
}

let sep = if arg.is_set(ArgSettings::RequireEquals) {
"="
if is_optional_val {
"[="
} else {
"="
}
} else if is_optional_val {
" ["
} else {
" "
};
Expand All @@ -295,26 +299,36 @@ impl<'help, 'app, 'parser, 'writer> Help<'help, 'app, 'parser, 'writer> {
}
self.good(&format!("--{}", l))?;
}
Ok(())
}

/// Writes argument's possible values to the wrapped stream.
fn val(&mut self, arg: &Arg<'help>, next_line_help: bool, longest: usize) -> io::Result<()> {
debug!("Help::val: arg={}", arg.name);
if arg.is_set(ArgSettings::TakesValue) || arg.is_positional() {
display_arg_val(
arg,
|s, good| if good { self.good(s) } else { self.none(s) },
)?;
}

debug!("Help::val: Has switch...");
if !arg.is_positional() && arg.is_set(ArgSettings::TakesValue) && is_optional_val {
self.none("]")?;
}

Ok(())
}

/// Write alignment padding between arg's switches/values and its about message.
fn align_to_about(
&mut self,
arg: &Arg<'help>,
next_line_help: bool,
longest: usize,
) -> io::Result<()> {
debug!("Help::align_to_about: arg={}", arg.name);
debug!("Help::align_to_about: Has switch...");
if self.use_long {
// long help prints messages on the next line so it don't need to align text
debug!("Help::val: printing long help so skip alignment");
// long help prints messages on the next line so it doesn't need to align text
debug!("Help::align_to_about: printing long help so skip alignment");
} else if !arg.is_positional() {
debug!("Yes");
debug!("Help::val: nlh...{:?}", next_line_help);
debug!("Help::align_to_about: nlh...{:?}", next_line_help);
if !next_line_help {
let self_len = display_width(arg.to_string().as_str());
// subtract ourself
Expand Down Expand Up @@ -439,7 +453,7 @@ impl<'help, 'app, 'parser, 'writer> Help<'help, 'app, 'parser, 'writer> {
) -> io::Result<()> {
self.short(arg)?;
self.long(arg)?;
self.val(arg, next_line_help, longest)?;
self.align_to_about(arg, next_line_help, longest)?;

let about = if self.use_long {
arg.long_help.unwrap_or_else(|| arg.help.unwrap_or(""))
Expand Down
2 changes: 2 additions & 0 deletions tests/builder/help.rs
Expand Up @@ -38,6 +38,8 @@ OPTIONS:
--multvalsmo <one> <two> Tests multiple values, and mult occs
-o, --option <opt>... tests options
-O, --option3 <option3> specific vals [possible values: fast, slow]
--optvaleq[=<optval>] Tests optional value, require = sign
--optvalnoeq [<optval>] Tests optional value
-V, --version Print version information
SUBCOMMANDS:
Expand Down
9 changes: 9 additions & 0 deletions tests/builder/utils.rs
Expand Up @@ -94,6 +94,15 @@ pub fn complex_app() -> App<'static> {
arg!(--maxvals3 <maxvals> "Tests 3 max vals")
.required(false)
.max_values(3),
arg!(--optvaleq <optval> "Tests optional value, require = sign")
.required(false)
.min_values(0)
.number_of_values(1)
.require_equals(true),
arg!(--optvalnoeq <optval> "Tests optional value")
.required(false)
.min_values(0)
.number_of_values(1),
])
.subcommand(
App::new("subcmd")
Expand Down
4 changes: 2 additions & 2 deletions tests/derive/options.rs
Expand Up @@ -199,8 +199,8 @@ fn option_option_type_help() {
arg: Option<Option<i32>>,
}
let help = utils::get_help::<Opt>();
assert!(help.contains("--arg <val>"));
assert!(!help.contains("--arg <val>..."));
assert!(help.contains("--arg [<val>]"));
assert!(!help.contains("--arg [<val>]..."));
}

#[test]
Expand Down

0 comments on commit 86474a2

Please sign in to comment.