Skip to content

Commit

Permalink
imp(YAML): adds conditional requirements and conditional default valu…
Browse files Browse the repository at this point in the history
…es to YAML

All of the conditional default values and conditional requirements
(requried and requires) can now be used with YAML builders as well.

Closes #764
  • Loading branch information
kbknapp committed Dec 29, 2016
1 parent 60e1a3a commit 9a4df32
Show file tree
Hide file tree
Showing 4 changed files with 139 additions and 22 deletions.
1 change: 1 addition & 0 deletions src/app/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
51 changes: 30 additions & 21 deletions src/app/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(())
Expand Down Expand Up @@ -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::<Vec<_>>();
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::<Vec<_>>();
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) {
Expand Down Expand Up @@ -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
Expand All @@ -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);
}
}
}
Expand Down
52 changes: 52 additions & 0 deletions src/args/arg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand All @@ -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),
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down
57 changes: 56 additions & 1 deletion src/args/macros.rs
Original file line number Diff line number Diff line change
@@ -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();
Expand All @@ -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))
}};
}

Expand Down

0 comments on commit 9a4df32

Please sign in to comment.