Skip to content

Commit eb4010e

Browse files
committed
feat(Conditional Default Values): adds new Arg::default_value_if[s] methods for conditional default values
One can now implement conditional default values. I.e. a default value that is only applied in certain conditions, such as another arg being present, or another arg being present *and* containing a specific value. Now it's possible to say, "Only apply this default value if arg X is present" or "Only apply this value if arg X is present, but also only if arg X's value is equal to Y" This new method is fully compatible with the current `Arg::default_value`, which gets set only if the arg wasn't used at runtime *and* none of the specified conditions were met. Releates to #764
1 parent b03eff6 commit eb4010e

File tree

8 files changed

+261
-13
lines changed

8 files changed

+261
-13
lines changed

src/app/mod.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use std::rc::Rc;
1717
use std::result::Result as StdResult;
1818

1919
// Third Party
20-
use vec_map::VecMap;
20+
use vec_map::{self, VecMap};
2121
#[cfg(feature = "yaml")]
2222
use yaml_rust::Yaml;
2323

@@ -1532,6 +1532,7 @@ impl<'n, 'e> AnyArg<'n, 'e> for App<'n, 'e> {
15321532
fn takes_value(&self) -> bool { true }
15331533
fn help(&self) -> Option<&'e str> { self.p.meta.about }
15341534
fn default_val(&self) -> Option<&'n str> { None }
1535+
fn default_vals_ifs(&self) -> Option<vec_map::Values<(&'n str, Option<&'e str>, &'e str)>> {None}
15351536
fn longest_filter(&self) -> bool { true }
15361537
fn aliases(&self) -> Option<Vec<&'e str>> {
15371538
if let Some(ref aliases) = self.p.meta.aliases {

src/app/parser.rs

+40-8
Original file line numberDiff line numberDiff line change
@@ -1949,19 +1949,51 @@ impl<'a, 'b> Parser<'a, 'b>
19491949
fn add_defaults(&mut self, matcher: &mut ArgMatcher<'a>) -> ClapResult<()> {
19501950
macro_rules! add_val {
19511951
($_self:ident, $a:ident, $m:ident) => {
1952-
if $m.get($a.b.name).is_none() {
1953-
try!($_self.add_val_to_arg($a, OsStr::new($a.v.default_val
1954-
.as_ref()
1955-
.unwrap()),
1956-
$m));
1957-
arg_post_processing!($_self, $a, $m);
1952+
if let Some(ref val) = $a.v.default_val {
1953+
if $m.get($a.b.name).is_none() {
1954+
try!($_self.add_val_to_arg($a, OsStr::new(val), $m));
1955+
arg_post_processing!($_self, $a, $m);
1956+
}
1957+
}
1958+
};
1959+
}
1960+
1961+
macro_rules! add_vals_ifs {
1962+
($_self:ident, $a:ident, $m:ident) => {
1963+
if let Some(ref vm) = $a.v.default_vals_ifs {
1964+
let mut done = false;
1965+
if $m.get($a.b.name).is_none() {
1966+
for &(arg, val, default) in vm.values() {
1967+
let add = if let Some(a) = $m.get(arg) {
1968+
if let Some(v) = val {
1969+
a.vals.values().any(|value| v == value)
1970+
} else {
1971+
true
1972+
}
1973+
} else {
1974+
false
1975+
};
1976+
if add {
1977+
try!($_self.add_val_to_arg($a, OsStr::new(default), $m));
1978+
arg_post_processing!($_self, $a, $m);
1979+
done = true;
1980+
break;
1981+
}
1982+
}
1983+
}
1984+
1985+
if done {
1986+
continue;
1987+
}
19581988
}
19591989
};
19601990
}
1961-
for o in self.opts.iter().filter(|o| o.v.default_val.is_some()) {
1991+
for o in &self.opts {
1992+
add_vals_ifs!(self, o, matcher);
19621993
add_val!(self, o, matcher);
19631994
}
1964-
for p in self.positionals.values().filter(|p| p.v.default_val.is_some()) {
1995+
for p in self.positionals.values() {
1996+
add_vals_ifs!(self, p, matcher);
19651997
add_val!(self, p, matcher);
19661998
}
19671999
Ok(())

src/args/any_arg.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use std::rc::Rc;
33
use std::fmt as std_fmt;
44

55
// Third Party
6-
use vec_map::VecMap;
6+
use vec_map::{self, VecMap};
77

88
// Internal
99
use args::settings::ArgSettings;
@@ -33,6 +33,7 @@ pub trait AnyArg<'n, 'e>: std_fmt::Display {
3333
fn val_names(&self) -> Option<&VecMap<&'e str>>;
3434
fn help(&self) -> Option<&'e str>;
3535
fn default_val(&self) -> Option<&'n str>;
36+
fn default_vals_ifs(&self) -> Option<vec_map::Values<(&'n str, Option<&'e str>, &'e str)>>;
3637
fn longest_filter(&self) -> bool;
3738
fn kind(&self) -> ArgKind;
3839
}

src/args/arg.rs

+208
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ pub struct Arg<'a, 'b>
7474
#[doc(hidden)]
7575
pub default_val: Option<&'a str>,
7676
#[doc(hidden)]
77+
pub default_vals_ifs: Option<VecMap<(&'a str, Option<&'b str>, &'b str)>>,
78+
#[doc(hidden)]
7779
pub disp_ord: usize,
7880
#[doc(hidden)]
7981
pub r_unless: Option<Vec<&'a str>>,
@@ -101,6 +103,7 @@ impl<'a, 'b> Default for Arg<'a, 'b> {
101103
settings: ArgFlags::new(),
102104
val_delim: None,
103105
default_val: None,
106+
default_vals_ifs: None,
104107
disp_ord: 999,
105108
r_unless: None,
106109
}
@@ -2359,6 +2362,14 @@ impl<'a, 'b> Arg<'a, 'b> {
23592362
/// not, consider [`ArgMatches::occurrences_of`] which will return `0` if the argument was *not*
23602363
/// used at runtmie.
23612364
///
2365+
/// **NOTE:** This setting is perfectly compatible with [`Arg::default_value_if`] but slightly
2366+
/// different. `Arg::default_value` *only* takes affect when the user has not provided this arg
2367+
/// at runtime. `Arg::default_value_if` however only takes affect when the user has not provided
2368+
/// a value at runtime **and** these other conditions are met as well. If you have set
2369+
/// `Arg::default_value` and `Arg::default_value_if`, and the user **did not** provide a this
2370+
/// arg at runtime, nor did were the conditions met for `Arg::default_value_if`, the
2371+
/// `Arg::default_value` will be applied.
2372+
///
23622373
/// **NOTE:** This implicitly sets [`Arg::takes_value(true)`].
23632374
///
23642375
/// # Examples
@@ -2400,12 +2411,207 @@ impl<'a, 'b> Arg<'a, 'b> {
24002411
/// [`ArgMatches::value_of`]: ./struct.ArgMatches.html#method.value_of
24012412
/// [`Arg::takes_value(true)`]: ./struct.Arg.html#method.takes_value
24022413
/// [`ArgMatches::is_present`]: ./struct.ArgMatches.html#method.is_present
2414+
/// [`Arg::default_value_if`]: ./struct.Arg.html#method.default_value_if
24032415
pub fn default_value(mut self, val: &'a str) -> Self {
24042416
self.setb(ArgSettings::TakesValue);
24052417
self.default_val = Some(val);
24062418
self
24072419
}
24082420

2421+
/// Specifies the value of the argument if `arg` has been used at runtime. If `val` is set to
2422+
/// `None`, `arg` only needs to be present. If `val` is set to `"some-val"` then `arg` must be
2423+
/// present at runtime **and** have the value `val`.
2424+
///
2425+
/// **NOTE:** This setting is perfectly compatible with [`Arg::default_value`] but slightly
2426+
/// different. `Arg::default_value` *only* takes affect when the user has not provided this arg
2427+
/// at runtime. This setting however only takes affect when the user has not provided a value at
2428+
/// runtime **and** these other conditions are met as well. If you have set `Arg::default_value`
2429+
/// and `Arg::default_value_if`, and the user **did not** provide a this arg at runtime, nor did
2430+
/// were the conditions met for `Arg::default_value_if`, the `Arg::default_value` will be
2431+
/// applied.
2432+
///
2433+
/// **NOTE:** This implicitly sets [`Arg::takes_value(true)`].
2434+
///
2435+
/// # Examples
2436+
///
2437+
/// First we use the default value only if another arg is present at runtime.
2438+
///
2439+
/// ```rust
2440+
/// # use clap::{App, Arg};
2441+
/// let m = App::new("dvif")
2442+
/// .arg(Arg::with_name("flag")
2443+
/// .long("flag"))
2444+
/// .arg(Arg::with_name("other")
2445+
/// .long("other")
2446+
/// .default_value_if("flag", None, "default"))
2447+
/// .get_matches_from(vec![
2448+
/// "dvif", "--flag"
2449+
/// ]);
2450+
///
2451+
/// assert_eq!(m.value_of("other"), Some("default"));
2452+
/// ```
2453+
///
2454+
/// Next we run the same test, but without providing `--flag`.
2455+
///
2456+
/// ```rust
2457+
/// # use clap::{App, Arg};
2458+
/// let m = App::new("dvif")
2459+
/// .arg(Arg::with_name("flag")
2460+
/// .long("flag"))
2461+
/// .arg(Arg::with_name("other")
2462+
/// .long("other")
2463+
/// .default_value_if("flag", None, "default"))
2464+
/// .get_matches_from(vec![
2465+
/// "dvif"
2466+
/// ]);
2467+
///
2468+
/// assert_eq!(m.value_of("other"), None);
2469+
/// ```
2470+
///
2471+
/// Now lets only use the default value if `--opt` contains the value `special`.
2472+
///
2473+
/// ```rust
2474+
/// # use clap::{App, Arg};
2475+
/// let m = App::new("dvif")
2476+
/// .arg(Arg::with_name("opt")
2477+
/// .takes_value(true)
2478+
/// .long("opt"))
2479+
/// .arg(Arg::with_name("other")
2480+
/// .long("other")
2481+
/// .default_value_if("opt", Some("special"), "default"))
2482+
/// .get_matches_from(vec![
2483+
/// "dvif", "--opt", "special"
2484+
/// ]);
2485+
///
2486+
/// assert_eq!(m.value_of("other"), Some("default"));
2487+
/// ```
2488+
///
2489+
/// We can run the same test and provide any value *other than* `special` and we won't get a
2490+
/// default value.
2491+
///
2492+
/// ```rust
2493+
/// # use clap::{App, Arg};
2494+
/// let m = App::new("dvif")
2495+
/// .arg(Arg::with_name("opt")
2496+
/// .takes_value(true)
2497+
/// .long("opt"))
2498+
/// .arg(Arg::with_name("other")
2499+
/// .long("other")
2500+
/// .default_value_if("opt", Some("special"), "default"))
2501+
/// .get_matches_from(vec![
2502+
/// "dvif", "--opt", "hahaha"
2503+
/// ]);
2504+
///
2505+
/// assert_eq!(m.value_of("other"), None);
2506+
/// ```
2507+
/// [`Arg::takes_value(true)`]: ./struct.Arg.html#method.takes_value
2508+
/// [`Arg::default_value`]: ./struct.Arg.html#method.default_value
2509+
pub fn default_value_if(mut self, arg: &'a str, val: Option<&'b str>, default: &'b str) -> Self {
2510+
self.setb(ArgSettings::TakesValue);
2511+
if let Some(ref mut vm) = self.default_vals_ifs {
2512+
let l = vm.len();
2513+
vm.insert(l, (arg, val, default));
2514+
} else {
2515+
let mut vm = VecMap::new();
2516+
vm.insert(0, (arg, val, default));
2517+
self.default_vals_ifs = Some(vm);
2518+
}
2519+
self
2520+
}
2521+
2522+
/// Specifies multiple values and conditions in the same manner as [`Arg::default_value_if`].
2523+
/// The method takes a slice of tuples in the `(arg, Option<val>, default)` format.
2524+
///
2525+
/// **NOTE**: The conditions are stored in order and evaluated in the same order. I.e. the first
2526+
/// if multiple conditions are true, the first one found will be applied and the ultimate value.
2527+
///
2528+
/// # Examples
2529+
///
2530+
/// First we use the default value only if another arg is present at runtime.
2531+
///
2532+
/// ```rust
2533+
/// # use clap::{App, Arg};
2534+
/// let m = App::new("dvif")
2535+
/// .arg(Arg::with_name("flag")
2536+
/// .long("flag"))
2537+
/// .arg(Arg::with_name("opt")
2538+
/// .long("opt")
2539+
/// .takes_value(true))
2540+
/// .arg(Arg::with_name("other")
2541+
/// .long("other")
2542+
/// .default_value_ifs(&[
2543+
/// ("flag", None, "default"),
2544+
/// ("opt", Some("channal"), "chan"),
2545+
/// ])
2546+
/// .get_matches_from(vec![
2547+
/// "dvif", "--opt", "channal"
2548+
/// ]);
2549+
///
2550+
/// assert_eq!(m.value_of("other"), Some("chan"));
2551+
/// ```
2552+
///
2553+
/// Next we run the same test, but without providing `--flag`.
2554+
///
2555+
/// ```rust
2556+
/// # use clap::{App, Arg};
2557+
/// let m = App::new("dvif")
2558+
/// .arg(Arg::with_name("flag")
2559+
/// .long("flag"))
2560+
/// .arg(Arg::with_name("other")
2561+
/// .long("other")
2562+
/// .default_value_ifs(&[
2563+
/// ("flag", None, "default"),
2564+
/// ("opt", Some("channal"), "chan"),
2565+
/// ])
2566+
/// .get_matches_from(vec![
2567+
/// "dvif"
2568+
/// ]);
2569+
///
2570+
/// assert_eq!(m.value_of("other"), None);
2571+
/// ```
2572+
///
2573+
/// We can also see that these values are applied in order, and if more than one condition is
2574+
/// true, only the first evaluatd "wins"
2575+
///
2576+
/// ```rust
2577+
/// # use clap::{App, Arg};
2578+
/// let m = App::new("dvif")
2579+
/// .arg(Arg::with_name("flag")
2580+
/// .long("flag"))
2581+
/// .arg(Arg::with_name("other")
2582+
/// .long("other")
2583+
/// .default_value_ifs(&[
2584+
/// ("flag", None, "default"),
2585+
/// ("opt", Some("channal"), "chan"),
2586+
/// ])
2587+
/// .get_matches_from(vec![
2588+
/// "dvif", "--opt", "channal", "--flag"
2589+
/// ]);
2590+
///
2591+
/// assert_eq!(m.value_of("other"), Some("default"));
2592+
/// ```
2593+
/// [`Arg::takes_value(true)`]: ./struct.Arg.html#method.takes_value
2594+
/// [`Arg::default_value`]: ./struct.Arg.html#method.default_value
2595+
pub fn default_value_ifs(mut self, ifs: &[(&'a str, Option<&'b str>, &'b str)]) -> Self {
2596+
self.setb(ArgSettings::TakesValue);
2597+
if let Some(ref mut vm) = self.default_vals_ifs {
2598+
let mut l = vm.len();
2599+
for &(arg, val, default) in ifs {
2600+
vm.insert(l, (arg, val, default));
2601+
l += 1;
2602+
}
2603+
} else {
2604+
let mut vm = VecMap::new();
2605+
let mut l = 0;
2606+
for &(arg, val, default) in ifs {
2607+
vm.insert(l, (arg, val, default));
2608+
l += 1;
2609+
}
2610+
self.default_vals_ifs = Some(vm);
2611+
}
2612+
self
2613+
}
2614+
24092615
/// When set to `true` the help string will be displayed on the line after the argument and
24102616
/// indented once. This can be helpful for arguments with very long or complex help messages.
24112617
/// This can also be helpful for arguments with very long flag names, or many/long value names.
@@ -2567,6 +2773,7 @@ impl<'a, 'b, 'z> From<&'z Arg<'a, 'b>> for Arg<'a, 'b> {
25672773
settings: a.settings,
25682774
val_delim: a.val_delim,
25692775
default_val: a.default_val,
2776+
default_vals_ifs: a.default_vals_ifs.clone(),
25702777
disp_ord: a.disp_ord,
25712778
r_unless: a.r_unless.clone(),
25722779
}
@@ -2595,6 +2802,7 @@ impl<'a, 'b> Clone for Arg<'a, 'b> {
25952802
settings: self.settings,
25962803
val_delim: self.val_delim,
25972804
default_val: self.default_val,
2805+
default_vals_ifs: self.default_vals_ifs.clone(),
25982806
disp_ord: self.disp_ord,
25992807
r_unless: self.r_unless.clone(),
26002808
}

src/args/arg_builder/flag.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use std::rc::Rc;
55
use std::result::Result as StdResult;
66

77
// Third Party
8-
use vec_map::VecMap;
8+
use vec_map::{self, VecMap};
99

1010
// Internal
1111
use Arg;
@@ -70,6 +70,7 @@ impl<'n, 'e> AnyArg<'n, 'e> for FlagBuilder<'n, 'e> {
7070
fn val_delim(&self) -> Option<char> { None }
7171
fn help(&self) -> Option<&'e str> { self.b.help }
7272
fn default_val(&self) -> Option<&'n str> { None }
73+
fn default_vals_ifs(&self) -> Option<vec_map::Values<(&'n str, Option<&'e str>, &'e str)>> {None}
7374
fn longest_filter(&self) -> bool { self.s.long.is_some() }
7475
fn aliases(&self) -> Option<Vec<&'e str>> {
7576
if let Some(ref aliases) = self.s.aliases {

src/args/arg_builder/option.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use std::rc::Rc;
44
use std::result::Result as StdResult;
55

66
// Third Party
7-
use vec_map::VecMap;
7+
use vec_map::{self, VecMap};
88

99
// Internal
1010
use args::{ArgSettings, ArgKind, AnyArg, Base, Switched, Valued, Arg, DispOrder};
@@ -114,6 +114,7 @@ impl<'n, 'e> AnyArg<'n, 'e> for OptBuilder<'n, 'e> {
114114
fn takes_value(&self) -> bool { true }
115115
fn help(&self) -> Option<&'e str> { self.b.help }
116116
fn default_val(&self) -> Option<&'n str> { self.v.default_val }
117+
fn default_vals_ifs(&self) -> Option<vec_map::Values<(&'n str, Option<&'e str>, &'e str)>> { self.v.default_vals_ifs.as_ref().map(|vm| vm.values()) }
117118
fn longest_filter(&self) -> bool { true }
118119
fn aliases(&self) -> Option<Vec<&'e str>> {
119120
if let Some(ref aliases) = self.s.aliases {

0 commit comments

Comments
 (0)