diff --git a/src/app/macros.rs b/src/app/macros.rs index 1a1049d9f57..433bedd32b1 100644 --- a/src/app/macros.rs +++ b/src/app/macros.rs @@ -100,6 +100,7 @@ macro_rules! _handle_group_reqs{ let found = if grp.args.contains(&$arg.name()) { vec_remove!($me.required, &$arg.name()); if let Some(ref reqs) = grp.requires { + debugln!("Adding {:?} to the required list", reqs); $me.required.extend(reqs); } if let Some(ref bl) = grp.conflicts { diff --git a/src/app/parser.rs b/src/app/parser.rs index bd494806081..6926be5764d 100644 --- a/src/app/parser.rs +++ b/src/app/parser.rs @@ -1624,6 +1624,12 @@ impl<'a, 'b> Parser<'a, 'b> } else if let Some(pos) = find_by_name!(self, name, positionals, values) { try!(self.validate_arg_num_vals(pos, ma, matcher)); try!(self.validate_arg_requires(pos, ma, matcher)); + } else if let Some(grp) = self.groups.get(name) { + if let Some(ref g_reqs) = grp.requires { + if g_reqs.iter().any(|&n| !matcher.contains(n)) { + return self.missing_required_error(matcher); + } + } } } Ok(()) @@ -1706,34 +1712,37 @@ impl<'a, 'b> Parser<'a, 'b> debugln!("fn=validate_arg_requires;"); if let Some(a_reqs) = a.requires() { for &(val, name) in a_reqs.iter().filter(|&&(val, _)| val.is_some()) { - if ma.vals.values().any(|v| v == val.expect(INTERNAL_ERROR_MSG)) { - if matcher.contains(name) { - continue; - } - - let mut reqs = self.required.iter().map(|&r| &*r).collect::>(); - reqs.retain(|n| !matcher.contains(n)); - reqs.dedup(); - return Err(Error::missing_required_argument( - &*self.get_required_from(&self.required[..], Some(matcher)) - .iter() - .fold(String::new(), - |acc, s| acc + &format!("\n {}", Format::Error(s))[..]), - &*self.create_current_usage(matcher), - self.color()) - ); + if ma.vals + .values() + .any(|v| v == val.expect(INTERNAL_ERROR_MSG) && !matcher.contains(name)) { + return self.missing_required_error(matcher); } } } Ok(()) } - fn validate_required(&self, matcher: &ArgMatcher) -> ClapResult<()> { - debugln!("fn=validate_required;required={:?};", self.required); + fn missing_required_error(&self, matcher: &ArgMatcher) -> ClapResult<()> { let c = Colorizer { use_stderr: true, when: self.color(), - }; + }; + let mut reqs = self.required.iter().map(|&r| &*r).collect::>(); + reqs.retain(|n| !matcher.contains(n)); + reqs.dedup(); + Err(Error::missing_required_argument(&*self.get_required_from(&self.required[..], + Some(matcher)) + .iter() + .fold(String::new(), |acc, s| { + acc + + &format!("\n {}", c.errors(s))[..] + }), + &*self.create_current_usage(matcher), + self.color())) + } + + fn validate_required(&self, matcher: &ArgMatcher) -> ClapResult<()> { + debugln!("fn=validate_required;required={:?};", self.required); 'outer: for name in &self.required { debugln!("iter;name={}", name); if matcher.contains(name) { @@ -1762,7 +1771,7 @@ impl<'a, 'b> Parser<'a, 'b> continue 'outer; } } - return Err(err()); + return self.missing_required_error(matcher); } // Validate the conditionally required args @@ -1771,7 +1780,7 @@ impl<'a, 'b> Parser<'a, 'b> for val in ma.vals.values() { if v == val { if matcher.get(r).is_none() { - return Err(err()); + return self.missing_required_error(matcher); } } } diff --git a/src/args/arg.rs b/src/args/arg.rs index 4d91798170b..b5721b057ad 100644 --- a/src/args/arg.rs +++ b/src/args/arg.rs @@ -163,6 +163,8 @@ impl<'a, 'b> Arg<'a, 'b> { "aliases" => yaml_vec_or_str!(v, a, alias), "help" => yaml_to_str!(a, v, help), "required" => yaml_to_bool!(a, v, required), + "required_if" => yaml_tuple2!(a, v, required_if), + "required_ifs" => yaml_tuple2!(a, v, required_if), "takes_value" => yaml_to_bool!(a, v, takes_value), "index" => yaml_to_u64!(a, v, index), "global" => yaml_to_bool!(a, v, global), @@ -182,9 +184,13 @@ impl<'a, 'b> Arg<'a, 'b> { "required_unless" => yaml_to_str!(a, v, required_unless), "display_order" => yaml_to_usize!(a, v, display_order), "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), "value_names" => yaml_vec_or_str!(v, a, value_name), "groups" => yaml_vec_or_str!(v, a, group), "requires" => yaml_vec_or_str!(v, a, requires), + "requires_if" => yaml_tuple2!(a, v, requires_if), + "requires_ifs" => yaml_tuple2!(a, v, requires_if), "conflicts_with" => yaml_vec_or_str!(v, a, conflicts_with), "overrides_with" => yaml_vec_or_str!(v, a, overrides_with), "possible_values" => yaml_vec_or_str!(v, a, possible_value), @@ -1150,6 +1156,13 @@ impl<'a, 'b> Arg<'a, 'b> { /// Allows a conditional requirement. The requirement will only become valid if this arg's value /// equals `val`. /// + /// **NOTE:** If using YAML the values should be laid out as follows + /// + /// ```yaml + /// requires_if: + /// - [val, arg] + /// ``` + /// /// # Examples /// /// ```rust @@ -1211,6 +1224,14 @@ impl<'a, 'b> Arg<'a, 'b> { /// Allows multiple conditional requirements. The requirement will only become valid if this arg's value /// equals `val`. /// + /// **NOTE:** If using YAML the values should be laid out as follows + /// + /// ```yaml + /// requires_if: + /// - [val, arg] + /// - [val2, arg2] + /// ``` + /// /// # Examples /// /// ```rust @@ -1269,6 +1290,13 @@ impl<'a, 'b> Arg<'a, 'b> { /// Allows specifying that an argument is [required] conditionally. The requirement will only /// become valid if the specified `arg`'s value equals `val`. /// + /// **NOTE:** If using YAML the values should be laid out as follows + /// + /// ```yaml + /// required_if: + /// - [arg, val] + /// ``` + /// /// # Examples /// /// ```rust @@ -1335,6 +1363,14 @@ impl<'a, 'b> Arg<'a, 'b> { /// conditions are set up in a `(arg, val)` style tuple. The requirement will only become valid /// if one of the specified `arg`'s value equals it's corresponding `val`. /// + /// **NOTE:** If using YAML the values should be laid out as follows + /// + /// ```yaml + /// required_if: + /// - [arg, val] + /// - [arg2, val2] + /// ``` + /// /// # Examples /// /// ```rust @@ -2712,6 +2748,14 @@ impl<'a, 'b> Arg<'a, 'b> { /// /// **NOTE:** This implicitly sets [`Arg::takes_value(true)`]. /// + /// **NOTE:** If using YAML the values should be laid out as follows (`None` can be represented + /// as `null` in YAML) + /// + /// ```yaml + /// default_value_if: + /// - [arg, val, default] + /// ``` + /// /// # Examples /// /// First we use the default value only if another arg is present at runtime. @@ -2809,6 +2853,14 @@ impl<'a, 'b> Arg<'a, 'b> { /// **NOTE**: The conditions are stored in order and evaluated in the same order. I.e. the first /// if multiple conditions are true, the first one found will be applied and the ultimate value. /// + /// **NOTE:** If using YAML the values should be laid out as follows + /// + /// ```yaml + /// default_value_if: + /// - [arg, val, default] + /// - [arg2, null, default2] + /// ``` + /// /// # Examples /// /// First we use the default value only if another arg is present at runtime. diff --git a/src/args/macros.rs b/src/args/macros.rs index 1a0ad6bbee7..dc38506e640 100644 --- a/src/args/macros.rs +++ b/src/args/macros.rs @@ -1,4 +1,42 @@ +macro_rules! yaml_tuple2 { + ($a:ident, $v:ident, $c:ident) => {{ + if let Some(vec) = $v.as_vec() { + for ys in vec { + if let Some(tup) = ys.as_vec() { + debug_assert_eq!(2, tup.len()); + $a = $a.$c(yaml_str!(tup[0]), yaml_str!(tup[1])); + } else { + panic!("Failed to convert YAML value to vec"); + } + } + } else { + panic!("Failed to convert YAML value to vec"); + } + $a + } + }; +} + +macro_rules! yaml_tuple3 { + ($a:ident, $v:ident, $c:ident) => {{ + if let Some(vec) = $v.as_vec() { + for ys in vec { + if let Some(tup) = ys.as_vec() { + debug_assert_eq!(3, tup.len()); + $a = $a.$c(yaml_str!(tup[0]), yaml_opt_str!(tup[1]), yaml_str!(tup[2])); + } else { + panic!("Failed to convert YAML value to vec"); + } + } + } else { + panic!("Failed to convert YAML value to vec"); + } + $a + } + }; +} + macro_rules! yaml_vec_or_str { ($v:ident, $a:ident, $c:ident) => {{ let maybe_vec = $v.as_vec(); @@ -22,9 +60,26 @@ macro_rules! yaml_vec_or_str { }; } +macro_rules! yaml_opt_str { + ($v:expr) => {{ + if $v.is_null() { + Some($v.as_str().unwrap_or_else(|| panic!("failed to convert YAML {:?} value to a string", $v))) + } else { + None + } + }}; +} + +macro_rules! yaml_str { + ($v:expr) => {{ + $v.as_str().unwrap_or_else(|| panic!("failed to convert YAML {:?} value to a string", $v)) + }}; +} + + macro_rules! yaml_to_str { ($a:ident, $v:ident, $c:ident) => {{ - $a.$c($v.as_str().unwrap_or_else(|| panic!("failed to convert YAML {:?} value to a string", $v))) + $a.$c(yaml_str!($v)) }}; }