Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add 'default_missing_value' configuration option #1587

Merged
merged 3 commits into from
May 21, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 111 additions & 2 deletions src/build/arg/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ pub struct Arg<'help> {
pub(crate) val_delim: Option<char>,
pub(crate) default_vals: Vec<&'help OsStr>,
pub(crate) default_vals_ifs: VecMap<(Id, Option<&'help OsStr>, &'help OsStr)>,
pub(crate) default_missing_vals: Vec<&'help OsStr>,
pub(crate) env: Option<(&'help OsStr, Option<OsString>)>,
pub(crate) terminator: Option<&'help str>,
pub(crate) index: Option<u64>,
Expand Down Expand Up @@ -2299,6 +2300,111 @@ impl<'help> Arg<'help> {
self
}

/// Specifies a value for the argument when the argument is supplied and a value is required
/// but the value is *not* specified at runtime.
///
/// This configuration option is often used to give the user a shortcut and allow them to
/// efficiently specify an option argument without requiring an explicitly value. The `--color`
/// argument is a common example. By, supplying an default, such as `default_missing_value("always")`,
/// the user can quickly just add `--color` to the command line to produce the desired color output.
///
/// **NOTE:** using this configuration option requires the use of the `.min_values(0)` and the
/// `.require_equals(true)` configuration option. These are required in order to unambiguously
/// determine what, if any, value was supplied for the argument.
///
/// # Examples
///
/// Here is an implementation of the common POSIX style `--color` argument.
///
/// ```rust
/// # use clap::{App, Arg};
///
/// macro_rules! app {
/// () => {{
/// App::new("prog")
/// .arg(Arg::new("color").long("color")
/// .value_name("WHEN")
/// .possible_values(&["always", "auto", "never"])
/// .default_value("auto")
/// .overrides_with("color")
/// .min_values(0)
/// .require_equals(true)
/// .default_missing_value("always")
/// .about("Specify WHEN to colorize output.")
/// )
/// }};
/// }
///
/// let mut m;
///
/// // first, we'll provide no arguments
///
/// m = app!().get_matches_from(vec![
/// "prog"
/// ]);
///
/// assert_eq!(m.value_of("color"), Some("auto"));
/// assert!(m.is_present("color"));
/// assert_eq!(m.occurrences_of("color"), 0);
///
/// // next, we'll provide a runtime value to override the default (as usually done).
///
/// m = app!().get_matches_from(vec![
/// "prog", "--color=never"
/// ]);
///
/// assert_eq!(m.value_of("color"), Some("never"));
/// assert!(m.is_present("color"));
/// assert_eq!(m.occurrences_of("color"), 1);
///
/// // finally, we will use the shortcut and only provide the argument without a value.
///
/// m = app!().get_matches_from(vec![
/// "prog", "--color"
/// ]);
///
/// assert_eq!(m.value_of("color"), Some("always"));
/// assert!(m.is_present("color"));
/// assert_eq!(m.occurrences_of("color"), 1);
/// ```
/// [`ArgMatches::occurrences_of`]: ./struct.ArgMatches.html#method.occurrences_of
/// [`ArgMatches::value_of`]: ./struct.ArgMatches.html#method.value_of
/// [`Arg::takes_value(true)`]: ./struct.Arg.html#method.takes_value
/// [`ArgMatches::is_present`]: ./struct.ArgMatches.html#method.is_present
/// [`Arg::default_value`]: ./struct.Arg.html#method.default_value
#[inline]
pub fn default_missing_value(self, val: &'help str) -> Self {
self.default_missing_values_os(&[OsStr::new(val)])
}

/// Provides a default value in the exact same manner as [`Arg::default_missing_value`]
/// only using [`OsStr`]s instead.
/// [`Arg::default_value`]: ./struct.Arg.html#method.default_value
/// [`OsStr`]: https://doc.rust-lang.org/std/ffi/struct.OsStr.html
#[inline]
pub fn default_missing_value_os(self, val: &'help OsStr) -> Self {
self.default_missing_values_os(&[val])
}

/// Like [`Arg::default_missing_value'] but for args taking multiple values
/// [`Arg::default_value`]: ./struct.Arg.html#method.default_value
#[inline]
pub fn default_missing_values(self, vals: &[&'help str]) -> Self {
let vals_vec: Vec<_> = vals.iter().map(|val| OsStr::new(*val)).collect();
self.default_missing_values_os(&vals_vec[..])
}

/// Provides default values in the exact same manner as [`Arg::default_values`]
/// only using [`OsStr`]s instead.
/// [`Arg::default_values`]: ./struct.Arg.html#method.default_values
/// [`OsStr`]: https://doc.rust-lang.org/std/ffi/struct.OsStr.html
#[inline]
pub fn default_missing_values_os(mut self, vals: &[&'help OsStr]) -> Self {
self.set_mut(ArgSettings::TakesValue);
self.default_missing_vals = vals.to_vec();
self
}

/// Specifies the value of the argument if `arg` has been used at runtime. If `val` is set to
/// `None`, `arg` only needs to be present. If `val` is set to `"some-val"` then `arg` must be
/// present at runtime **and** have the value `val`.
Expand Down Expand Up @@ -4174,6 +4280,7 @@ impl<'a> From<&'a Yaml> for Arg<'a> {
"default_value" => yaml_to_str!(a, v, default_value),
"default_value_if" => yaml_tuple3!(a, v, default_value_if),
"default_value_ifs" => yaml_tuple3!(a, v, default_value_if),
"default_missing_value" => yaml_to_str!(a, v, default_missing_value),
pksunkara marked this conversation as resolved.
Show resolved Hide resolved
"env" => yaml_to_str!(a, v, env),
"value_names" => yaml_vec_or_str!(v, a, value_name),
"groups" => yaml_vec_or_str!(v, a, group),
Expand Down Expand Up @@ -4340,7 +4447,8 @@ impl<'help> fmt::Debug for Arg<'help> {
aliases: {:?}, short_aliases: {:?}, possible_values: {:?}, value_names: {:?}, \
number_of_values: {:?}, max_values: {:?}, min_values: {:?}, value_delimiter: {:?}, \
default_value_ifs: {:?}, value_terminator: {:?}, display_order: {:?}, env: {:?}, \
unified_ord: {:?}, default_value: {:?}, validator: {}, validator_os: {} \
unified_ord: {:?}, default_value: {:?}, validator: {}, validator_os: {}, \
CreepySkeleton marked this conversation as resolved.
Show resolved Hide resolved
default_missing_value: {:?}, \
}}",
self.id,
self.name,
Expand Down Expand Up @@ -4371,7 +4479,8 @@ impl<'help> fmt::Debug for Arg<'help> {
self.unified_ord,
self.default_vals,
self.validator.as_ref().map_or("None", |_| "Some(Fn)"),
self.validator_os.as_ref().map_or("None", |_| "Some(Fn)")
self.validator_os.as_ref().map_or("None", |_| "Some(Fn)"),
self.default_missing_vals
)
}
}
Expand Down
12 changes: 12 additions & 0 deletions src/parse/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1278,6 +1278,18 @@ where
&*Usage::new(self).create_usage_with_title(&[]),
self.app.color(),
)?);
} else if needs_eq && min_vals_zero {
debug!("None and requires equals, but min_vals == 0");
if !opt.default_missing_vals.is_empty() {
debug!("Parser::parse_opt: has default_missing_vals");
for val in &opt.default_missing_vals {
debug!(
"Parser::parse_opt: adding value from default_missing_values; val = {:?}",
val
);
self.add_val_to_arg(opt, &ArgStr::new(val), matcher, ValueType::CommandLine)?;
}
};
} else {
debug!("None");
}
Expand Down
110 changes: 110 additions & 0 deletions tests/default_missing_vals.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
use clap::{App, Arg};

#[test]
fn opt_missing() {
let r = App::new("df")
.arg(
Arg::new("color")
.long("color")
.default_value("auto")
.min_values(0)
.require_equals(true)
.default_missing_value("always"),
)
.try_get_matches_from(vec![""]);
assert!(r.is_ok());
let m = r.unwrap();
assert!(m.is_present("color"));
assert_eq!(m.value_of("color").unwrap(), "auto");
assert_eq!(m.occurrences_of("color"), 0);
}

#[test]
fn opt_present_with_missing_value() {
let r = App::new("df")
.arg(
Arg::new("color")
.long("color")
.default_value("auto")
.min_values(0)
.require_equals(true)
.default_missing_value("always"),
)
.try_get_matches_from(vec!["", "--color"]);
assert!(r.is_ok());
let m = r.unwrap();
assert!(m.is_present("color"));
assert_eq!(m.value_of("color").unwrap(), "always");
assert_eq!(m.occurrences_of("color"), 1);
}

#[test]
fn opt_present_with_value() {
let r = App::new("df")
.arg(
Arg::new("color")
.long("color")
.default_value("auto")
.min_values(0)
.require_equals(true)
.default_missing_value("always"),
)
.try_get_matches_from(vec!["", "--color=never"]);
assert!(r.is_ok());
let m = r.unwrap();
assert!(m.is_present("color"));
assert_eq!(m.value_of("color").unwrap(), "never");
assert_eq!(m.occurrences_of("color"), 1);
}

// ToDO: [2020-05-20; rivy] test currently fails as empty values are still a work-in-progress (see <https://github.com/clap-rs/clap/pull/1587#issuecomment-631648788>)
// #[test]
// fn opt_present_with_empty_value() {
// let r = App::new("df")
// .arg(Arg::new("color").long("color")
// .default_value("auto")
// .min_values(0)
// .require_equals(true)
// .default_missing_value("always")
// )
// .try_get_matches_from(vec!["", "--color="]);
// assert!(r.is_ok());
// let m = r.unwrap();
// assert!(m.is_present("color"));
// assert_eq!(m.value_of("color").unwrap(), "");
// assert_eq!(m.occurrences_of("color"), 1);
// }

//## `default_value`/`default_missing_value` non-interaction checks

#[test]
fn opt_default() {
// assert no change to usual argument handling when adding default_missing_value()
let r = App::new("app")
.arg(
Arg::from("-o [opt] 'some opt'")
.default_value("default")
.default_missing_value("default_missing"),
)
.try_get_matches_from(vec![""]);
assert!(r.is_ok());
let m = r.unwrap();
assert!(m.is_present("o"));
assert_eq!(m.value_of("o").unwrap(), "default");
}

#[test]
fn opt_default_user_override() {
// assert no change to usual argument handling when adding default_missing_value()
let r = App::new("app")
.arg(
Arg::from("-o [opt] 'some opt'")
.default_value("default")
.default_missing_value("default_missing"),
)
.try_get_matches_from(vec!["", "-o=value"]);
assert!(r.is_ok());
let m = r.unwrap();
assert!(m.is_present("o"));
assert_eq!(m.value_of("o").unwrap(), "value");
}
rivy marked this conversation as resolved.
Show resolved Hide resolved