From 4fe133d9ce00008623247f2dc0537769eda2ea2e Mon Sep 17 00:00:00 2001 From: Kevin K Date: Mon, 2 May 2016 12:48:10 -0400 Subject: [PATCH 1/8] chore: removes unused imports --- src/app/help.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/app/help.rs b/src/app/help.rs index bce8ab3f5cb3..2a1d1a718d7e 100644 --- a/src/app/help.rs +++ b/src/app/help.rs @@ -1,9 +1,7 @@ - use std::io::{self, Cursor, Read, Write}; use std::collections::BTreeMap; use std::fmt::Display; use std::cmp; -use std::str; use vec_map::VecMap; From cb708093a7cd057f08c98b7bd1ed54c2db86ae7e Mon Sep 17 00:00:00 2001 From: Kevin K Date: Mon, 2 May 2016 12:48:47 -0400 Subject: [PATCH 2/8] docs: hides formatting from docs --- src/fmt.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/fmt.rs b/src/fmt.rs index 3b1ede7eb450..93349a05248b 100644 --- a/src/fmt.rs +++ b/src/fmt.rs @@ -9,6 +9,7 @@ use ansi_term::ANSIString; /// Defines styles for different types of error messages. Defaults to Error=Red, Warning=Yellow, /// and Good=Green #[derive(Debug)] +#[doc(hidden)] pub enum Format { /// Defines the style used for errors, defaults to Red Error(T), From 6987f37e71513a8eff31707dd43d191d9d8e3ac3 Mon Sep 17 00:00:00 2001 From: Kevin K Date: Mon, 2 May 2016 13:32:36 -0400 Subject: [PATCH 3/8] feat(Required): adds allowing args that are required unless certain args are present Adds three new methods of `Arg` which allow for specifying three new types of rules. * `Arg::required_unless` Allows saying a particular arg is only required if a specific other arg *isn't* present. * `Arg::required_unless_all` Allows saying a particular arg is only required so long as *all* the following args aren't present * `Arg::required_unless_one` Allows saying a particular arg is required unless at least one of the following args is present. --- src/app/mod.rs | 1 + src/app/parser.rs | 22 +++++- src/args/any_arg.rs | 1 + src/args/arg.rs | 118 +++++++++++++++++++++++++++++ src/args/arg_builder/flag.rs | 1 + src/args/arg_builder/option.rs | 10 +++ src/args/arg_builder/positional.rs | 5 ++ src/args/settings.rs | 25 +++--- 8 files changed, 169 insertions(+), 14 deletions(-) diff --git a/src/app/mod.rs b/src/app/mod.rs index 1f80b5c9a5e4..0cf44fcab479 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -936,6 +936,7 @@ impl<'n, 'e> AnyArg<'n, 'e> for App<'n, 'e> { fn overrides(&self) -> Option<&[&'e str]> { None } fn requires(&self) -> Option<&[&'e str]> { None } fn blacklist(&self) -> Option<&[&'e str]> { None } + fn required_unless(&self) -> Option<&[&'e str]> { None } fn val_names(&self) -> Option<&VecMap<&'e str>> { None } fn is_set(&self, _: ArgSettings) -> bool { false } fn set(&mut self, _: ArgSettings) { diff --git a/src/app/parser.rs b/src/app/parser.rs index 86e866cab0a8..53bbee15cef4 100644 --- a/src/app/parser.rs +++ b/src/app/parser.rs @@ -1245,11 +1245,11 @@ impl<'a, 'b> Parser<'a, 'b> where 'a: 'b { continue 'outer; } if let Some(a) = self.flags.iter().filter(|f| &f.name == name).next() { - if self._validate_blacklist_required(a, matcher) { continue 'outer; } + if self.is_missing_required_ok(a, matcher) { continue 'outer; } } else if let Some(a) = self.opts.iter().filter(|o| &o.name == name).next() { - if self._validate_blacklist_required(a, matcher) { continue 'outer; } + if self.is_missing_required_ok(a, matcher) { continue 'outer; } } else if let Some(a) = self.positionals.values().filter(|p| &p.name == name).next() { - if self._validate_blacklist_required(a, matcher) { continue 'outer; } + if self.is_missing_required_ok(a, matcher) { continue 'outer; } } let err = if self.settings.is_set(AppSettings::ArgRequiredElseHelp) && matcher.is_empty() { self._help().unwrap_err() @@ -1266,7 +1266,7 @@ impl<'a, 'b> Parser<'a, 'b> where 'a: 'b { Ok(()) } - fn _validate_blacklist_required(&self, a: &A, matcher: &ArgMatcher) -> bool where A: AnyArg<'a, 'b> { + fn is_missing_required_ok(&self, a: &A, matcher: &ArgMatcher) -> bool where A: AnyArg<'a, 'b> { if let Some(bl) = a.blacklist() { for n in bl.iter() { if matcher.contains(n) @@ -1276,6 +1276,20 @@ impl<'a, 'b> Parser<'a, 'b> where 'a: 'b { return true; } } + } else if let Some(ru) = a.required_unless() { + for n in ru.iter() { + if matcher.contains(n) + || self.groups + .get(n) + .map_or(false, |g| g.args.iter().any(|an| matcher.contains(an))) { + if !a.is_set(ArgSettings::RequiredUnlessAll) { + return true; + } + } else { + return false; + } + } + return true; } false } diff --git a/src/args/any_arg.rs b/src/args/any_arg.rs index 16f3d001be7a..88aee17859b7 100644 --- a/src/args/any_arg.rs +++ b/src/args/any_arg.rs @@ -10,6 +10,7 @@ pub trait AnyArg<'n, 'e> { fn overrides(&self) -> Option<&[&'e str]>; fn requires(&self) -> Option<&[&'e str]>; fn blacklist(&self) -> Option<&[&'e str]>; + fn required_unless(&self) -> Option<&[&'e str]>; fn is_set(&self, ArgSettings) -> bool; fn set(&mut self, ArgSettings); fn has_switch(&self) -> bool; diff --git a/src/args/arg.rs b/src/args/arg.rs index caed4db9ef2b..481d150d45fb 100644 --- a/src/args/arg.rs +++ b/src/args/arg.rs @@ -70,6 +70,8 @@ pub struct Arg<'a, 'b> where 'a: 'b { pub default_val: Option<&'a str>, #[doc(hidden)] pub disp_ord: usize, + #[doc(hidden)] + pub r_unless: Option>, } impl<'a, 'b> Default for Arg<'a, 'b> { @@ -94,6 +96,7 @@ impl<'a, 'b> Default for Arg<'a, 'b> { val_delim: Some(','), default_val: None, disp_ord: 999, + r_unless: None, } } } @@ -157,6 +160,7 @@ impl<'a, 'b> Arg<'a, 'b> { "value_name" => a.value_name(v.as_str().unwrap()), "use_delimiter" => a.use_delimiter(v.as_bool().unwrap()), "value_delimiter" => a.value_delimiter(v.as_str().unwrap()), + "required_unless" => a.required_unless(v.as_str().unwrap()), "display_order" => a.display_order(v.as_i64().unwrap() as usize), "value_names" => { for ys in v.as_vec().unwrap() { @@ -198,6 +202,23 @@ impl<'a, 'b> Arg<'a, 'b> { } a } + "required_unless_one" => { + for ys in v.as_vec().unwrap() { + if let Some(s) = ys.as_str() { + a = a.required_unless(s); + } + } + a + } + "required_unless_all" => { + for ys in v.as_vec().unwrap() { + if let Some(s) = ys.as_str() { + a = a.required_unless(s); + } + } + a.setb(ArgSettings::RequiredUnlessAll); + a + } s => panic!("Unknown Arg setting '{}' in YAML file for arg '{}'", s, name_str), @@ -520,6 +541,101 @@ impl<'a, 'b> Arg<'a, 'b> { if r { self.set(ArgSettings::Required) } else { self.unset(ArgSettings::Required) } } + pub fn required_unless(mut self, name: &'a str) -> Self { + if let Some(ref mut vec) = self.r_unless { + vec.push(name); + } else { + self.r_unless = Some(vec![name]); + } + self.required(true) + } + +<<<<<<< HEAD +======= + /// Sets args that override this arg's required setting. (i.e. this arg will be required unless + /// all these other argument are present). + /// + /// **NOTE:** If you wish for the this argument to only be required if *one of* these args are + /// present see `Arg::required_unless_one` + /// + /// # Examples + /// + /// ```rust + /// # use clap::Arg; + /// Arg::with_name("config") + /// .required_unless_all(&["cfg", "dbg"]) + /// # ; + /// ``` + /// + /// Setting `required_unless_all(names)` requires that the argument be used at runtime *unless* + /// *all* the args in `names` are present. In the following example, the required argument is + /// *not* provided, but it's not an error because all the `unless` args have been supplied. + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let res = App::new("unlessall") + /// .arg(Arg::with_name("cfg") + /// .required_unless_all(&["dbg", "infile"]) + /// .takes_value(true) + /// .long("config")) + /// .arg(Arg::with_name("dbg") + /// .long("debug")) + /// .arg(Arg::with_name("infile") + /// .short("i") + /// .takes_value(true)) + /// .get_matches_from_safe(vec![ + /// "unlessall", "--debug", "-i", "file" + /// ]); + /// + /// assert!(res.is_ok()); + /// ``` + /// + /// Setting `required_unless_all(names)` and *not* supplying *all* of `names` or this arg is an + /// error. + /// + /// ```rust + /// # use clap::{App, Arg, ErrorKind}; + /// let res = App::new("unlessall") + /// .arg(Arg::with_name("cfg") + /// .required_unless_all(&["dbg", "infile"]) + /// .takes_value(true) + /// .long("config")) + /// .arg(Arg::with_name("dbg") + /// .long("debug")) + /// .arg(Arg::with_name("infile") + /// .short("i") + /// .takes_value(true)) + /// .get_matches_from_safe(vec![ + /// "unlessall", "--debug" + /// ]); + /// + /// assert!(res.is_err()); + /// assert_eq!(res.unwrap_err().kind, ErrorKind::MissingRequiredArgument); + /// ``` +>>>>>>> 27cf173... fixup! wip: adding required_unless + pub fn required_unless_all(mut self, names: &[&'a str]) -> Self { + if let Some(ref mut vec) = self.r_unless { + for s in names { + vec.push(s); + } + } else { + self.r_unless = Some(names.iter().map(|s| *s).collect::>()); + } + self.setb(ArgSettings::RequiredUnlessAll); + self.required(true) + } + + pub fn required_unless_one(mut self, names: &[&'a str]) -> Self { + if let Some(ref mut vec) = self.r_unless { + for s in names { + vec.push(s); + } + } else { + self.r_unless = Some(names.iter().map(|s| *s).collect::>()); + } + self.required(true) + } + /// Sets a conflicting argument by name. I.e. when using this argument, /// the following argument can't be present and vice versa. /// @@ -1908,6 +2024,7 @@ impl<'a, 'b, 'z> From<&'z Arg<'a, 'b>> val_delim: a.val_delim, default_val: a.default_val, disp_ord: a.disp_ord, + r_unless: a.r_unless.clone(), } } } @@ -1934,6 +2051,7 @@ impl<'a, 'b> Clone for Arg<'a, 'b> { val_delim: self.val_delim, default_val: self.default_val, disp_ord: self.disp_ord, + r_unless: self.r_unless.clone(), } } } diff --git a/src/args/arg_builder/flag.rs b/src/args/arg_builder/flag.rs index c8d0d9bb7a23..48a32b005fce 100644 --- a/src/args/arg_builder/flag.rs +++ b/src/args/arg_builder/flag.rs @@ -108,6 +108,7 @@ impl<'n, 'e> AnyArg<'n, 'e> for FlagBuilder<'n, 'e> { fn overrides(&self) -> Option<&[&'e str]> { self.overrides.as_ref().map(|o| &o[..]) } fn requires(&self) -> Option<&[&'e str]> { self.requires.as_ref().map(|o| &o[..]) } fn blacklist(&self) -> Option<&[&'e str]> { self.blacklist.as_ref().map(|o| &o[..]) } + fn required_unless(&self) -> Option<&[&'e str]> { None } fn is_set(&self, s: ArgSettings) -> bool { self.settings.is_set(s) } fn has_switch(&self) -> bool { true } fn takes_value(&self) -> bool { false } diff --git a/src/args/arg_builder/option.rs b/src/args/arg_builder/option.rs index d64441e9a9d7..dbdf51bd4ca2 100644 --- a/src/args/arg_builder/option.rs +++ b/src/args/arg_builder/option.rs @@ -27,6 +27,7 @@ pub struct OptBuilder<'n, 'e> { pub val_delim: Option, pub default_val: Option<&'n str>, pub disp_ord: usize, + pub r_unless: Option>, } impl<'n, 'e> Default for OptBuilder<'n, 'e> { @@ -49,6 +50,7 @@ impl<'n, 'e> Default for OptBuilder<'n, 'e> { val_delim: Some(','), default_val: None, disp_ord: 999, + r_unless: None, } } } @@ -84,6 +86,7 @@ impl<'n, 'e> OptBuilder<'n, 'e> { settings: a.settings, default_val: a.default_val, disp_ord: a.disp_ord, + r_unless: a.r_unless.clone(), ..Default::default() }; if let Some(ref vec) = ob.val_names { @@ -91,6 +94,11 @@ impl<'n, 'e> OptBuilder<'n, 'e> { ob.num_vals = Some(vec.len() as u64); } } + if let Some(ref vec) = ob.val_names { + if vec.len() > 1 { + ob.num_vals = Some(vec.len() as u64); + } + } if let Some(ref p) = a.validator { ob.validator = Some(p.clone()); } @@ -160,6 +168,7 @@ impl<'n, 'e> Clone for OptBuilder<'n, 'e> { possible_vals: self.possible_vals.clone(), default_val: self.default_val, validator: self.validator.clone(), + r_unless: self.r_unless.clone(), } } } @@ -169,6 +178,7 @@ impl<'n, 'e> AnyArg<'n, 'e> for OptBuilder<'n, 'e> { fn overrides(&self) -> Option<&[&'e str]> { self.overrides.as_ref().map(|o| &o[..]) } fn requires(&self) -> Option<&[&'e str]> { self.requires.as_ref().map(|o| &o[..]) } fn blacklist(&self) -> Option<&[&'e str]> { self.blacklist.as_ref().map(|o| &o[..]) } + fn required_unless(&self) -> Option<&[&'e str]> { self.r_unless.as_ref().map(|o| &o[..]) } #[cfg_attr(feature = "lints", allow(map_clone))] fn val_names(&self) -> Option<&VecMap<&'e str>> { self.val_names.as_ref().map(|o| o) } fn is_set(&self, s: ArgSettings) -> bool { self.settings.is_set(s) } diff --git a/src/args/arg_builder/positional.rs b/src/args/arg_builder/positional.rs index ffa039daf5f2..40d2a6ba3cc5 100644 --- a/src/args/arg_builder/positional.rs +++ b/src/args/arg_builder/positional.rs @@ -27,6 +27,7 @@ pub struct PosBuilder<'n, 'e> { pub val_delim: Option, pub default_val: Option<&'n str>, pub disp_ord: usize, + pub r_unless: Option>, } impl<'n, 'e> Default for PosBuilder<'n, 'e> { @@ -48,6 +49,7 @@ impl<'n, 'e> Default for PosBuilder<'n, 'e> { val_delim: Some(','), default_val: None, disp_ord: 999, + r_unless: None, } } } @@ -85,6 +87,7 @@ impl<'n, 'e> PosBuilder<'n, 'e> { settings: a.settings, default_val: a.default_val, disp_ord: a.disp_ord, + r_unless: a.r_unless.clone(), ..Default::default() }; if a.max_vals.is_some() @@ -147,6 +150,7 @@ impl<'n, 'e> Clone for PosBuilder<'n, 'e> { possible_vals: self.possible_vals.clone(), default_val: self.default_val, validator: self.validator.clone(), + r_unless: self.r_unless.clone(), index: self.index, } } @@ -157,6 +161,7 @@ impl<'n, 'e> AnyArg<'n, 'e> for PosBuilder<'n, 'e> { fn overrides(&self) -> Option<&[&'e str]> { self.overrides.as_ref().map(|o| &o[..]) } fn requires(&self) -> Option<&[&'e str]> { self.requires.as_ref().map(|o| &o[..]) } fn blacklist(&self) -> Option<&[&'e str]> { self.blacklist.as_ref().map(|o| &o[..]) } + fn required_unless(&self) -> Option<&[&'e str]> { self.r_unless.as_ref().map(|o| &o[..]) } fn val_names(&self) -> Option<&VecMap<&'e str>> { self.val_names.as_ref() } fn is_set(&self, s: ArgSettings) -> bool { self.settings.is_set(s) } fn set(&mut self, s: ArgSettings) { self.settings.set(s) } diff --git a/src/args/settings.rs b/src/args/settings.rs index a7aa5dee82b6..5715f5831453 100644 --- a/src/args/settings.rs +++ b/src/args/settings.rs @@ -2,15 +2,16 @@ use std::str::FromStr; use std::ascii::AsciiExt; bitflags! { - flags Flags: u8 { - const REQUIRED = 0b00000001, - const MULTIPLE = 0b00000010, - const EMPTY_VALS = 0b00000100, - const GLOBAL = 0b00001000, - const HIDDEN = 0b00010000, - const TAKES_VAL = 0b00100000, - const USE_DELIM = 0b01000000, - const NEXT_LINE_HELP = 0b10000000, + flags Flags: u16 { + const REQUIRED = 0b000000001, + const MULTIPLE = 0b000000010, + const EMPTY_VALS = 0b000000100, + const GLOBAL = 0b000001000, + const HIDDEN = 0b000010000, + const TAKES_VAL = 0b000100000, + const USE_DELIM = 0b001000000, + const NEXT_LINE_HELP = 0b010000000, + const R_UNLESS_ALL = 0b100000000, } } @@ -31,7 +32,8 @@ impl ArgFlags { Hidden => HIDDEN, TakesValue => TAKES_VAL, UseValueDelimiter => USE_DELIM, - NextLineHelp => NEXT_LINE_HELP + NextLineHelp => NEXT_LINE_HELP, + RequiredUnlessAll => R_UNLESS_ALL } } @@ -62,6 +64,8 @@ pub enum ArgSettings { UseValueDelimiter, /// Prints the help text on the line after the argument NextLineHelp, + #[doc(hidden)] + RequiredUnlessAll, } impl FromStr for ArgSettings { @@ -76,6 +80,7 @@ impl FromStr for ArgSettings { "takesvalue" => Ok(ArgSettings::TakesValue), "usevaluedelimiter" => Ok(ArgSettings::UseValueDelimiter), "nextlinehelp" => Ok(ArgSettings::NextLineHelp), + "requiredunlessall" => Ok(ArgSettings::RequiredUnlessAll), _ => Err("unknown ArgSetting, cannot convert from str".to_owned()), } } From 7c89fc55ff5eb99e7913f930a7bb59d4e3dd54ea Mon Sep 17 00:00:00 2001 From: Kevin K Date: Mon, 2 May 2016 13:32:59 -0400 Subject: [PATCH 4/8] docs(required_unless): adds docs and examples for required_unless --- src/args/arg.rs | 119 ++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 115 insertions(+), 4 deletions(-) diff --git a/src/args/arg.rs b/src/args/arg.rs index 481d150d45fb..55aea50761ff 100644 --- a/src/args/arg.rs +++ b/src/args/arg.rs @@ -541,6 +541,59 @@ impl<'a, 'b> Arg<'a, 'b> { if r { self.set(ArgSettings::Required) } else { self.unset(ArgSettings::Required) } } + /// Sets an arg that override this arg's required setting. (i.e. this arg will be required + /// unless this other argument is present). + /// + /// **Pro Tip:** Using `Arg::required_unless` implies `Arg::required` and is therefore not + /// mandatory to also set. + /// + /// # Examples + /// + /// ```rust + /// # use clap::Arg; + /// Arg::with_name("config") + /// .required_unless("debug") + /// # ; + /// ``` + /// + /// Setting `required_unless(name)` requires that the argument be used at runtime *unless* + /// `name` is present. In the following example, the required argument is *not* provided, but + /// it's not an error because the `unless` arg has been supplied. + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let res = App::new("unlesstest") + /// .arg(Arg::with_name("cfg") + /// .required_unless("dbg") + /// .takes_value(true) + /// .long("config")) + /// .arg(Arg::with_name("dbg") + /// .long("debug")) + /// .get_matches_from_safe(vec![ + /// "unlesstest", "--debug" + /// ]); + /// + /// assert!(res.is_ok()); + /// ``` + /// + /// Setting `required_unless(name)` and *not* supplying `name` or this arg is an error. + /// + /// ```rust + /// # use clap::{App, Arg, ErrorKind}; + /// let res = App::new("unlesstest") + /// .arg(Arg::with_name("cfg") + /// .required_unless("dbg") + /// .takes_value(true) + /// .long("config")) + /// .arg(Arg::with_name("dbg") + /// .long("debug")) + /// .get_matches_from_safe(vec![ + /// "unlesstest" + /// ]); + /// + /// assert!(res.is_err()); + /// assert_eq!(res.unwrap_err().kind, ErrorKind::MissingRequiredArgument); + /// ``` pub fn required_unless(mut self, name: &'a str) -> Self { if let Some(ref mut vec) = self.r_unless { vec.push(name); @@ -550,8 +603,6 @@ impl<'a, 'b> Arg<'a, 'b> { self.required(true) } -<<<<<<< HEAD -======= /// Sets args that override this arg's required setting. (i.e. this arg will be required unless /// all these other argument are present). /// @@ -606,13 +657,12 @@ impl<'a, 'b> Arg<'a, 'b> { /// .short("i") /// .takes_value(true)) /// .get_matches_from_safe(vec![ - /// "unlessall", "--debug" + /// "unlessall" /// ]); /// /// assert!(res.is_err()); /// assert_eq!(res.unwrap_err().kind, ErrorKind::MissingRequiredArgument); /// ``` ->>>>>>> 27cf173... fixup! wip: adding required_unless pub fn required_unless_all(mut self, names: &[&'a str]) -> Self { if let Some(ref mut vec) = self.r_unless { for s in names { @@ -625,6 +675,67 @@ impl<'a, 'b> Arg<'a, 'b> { self.required(true) } + /// Sets args that override this arg's required setting. (i.e. this arg will be required unless + /// *at least one of* these other argument are present). + /// + /// **NOTE:** If you wish for the this argument to only be required if *all of* these args are + /// present see `Arg::required_unless_all` + /// + /// # Examples + /// + /// ```rust + /// # use clap::Arg; + /// Arg::with_name("config") + /// .required_unless_all(&["cfg", "dbg"]) + /// # ; + /// ``` + /// + /// Setting `required_unless_one(names)` requires that the argument be used at runtime *unless* + /// *at least one of* the args in `names` are present. In the following example, the required + /// argument is *not* provided, but it's not an error because one the `unless` args have been + /// supplied. + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let res = App::new("unlessone") + /// .arg(Arg::with_name("cfg") + /// .required_unless_one(&["dbg", "infile"]) + /// .takes_value(true) + /// .long("config")) + /// .arg(Arg::with_name("dbg") + /// .long("debug")) + /// .arg(Arg::with_name("infile") + /// .short("i") + /// .takes_value(true)) + /// .get_matches_from_safe(vec![ + /// "unlessone", "--debug" + /// ]); + /// + /// assert!(res.is_ok()); + /// ``` + /// + /// Setting `required_unless_one(names)` and *not* supplying *at least one of* `names` or this + /// arg is an error. + /// + /// ```rust + /// # use clap::{App, Arg, ErrorKind}; + /// let res = App::new("unlessone") + /// .arg(Arg::with_name("cfg") + /// .required_unless_one(&["dbg", "infile"]) + /// .takes_value(true) + /// .long("config")) + /// .arg(Arg::with_name("dbg") + /// .long("debug")) + /// .arg(Arg::with_name("infile") + /// .short("i") + /// .takes_value(true)) + /// .get_matches_from_safe(vec![ + /// "unlessone" + /// ]); + /// + /// assert!(res.is_err()); + /// assert_eq!(res.unwrap_err().kind, ErrorKind::MissingRequiredArgument); + /// ``` pub fn required_unless_one(mut self, names: &[&'a str]) -> Self { if let Some(ref mut vec) = self.r_unless { for s in names { From 9fdad2e970b84fb25db0cade6a3a42df2e87572e Mon Sep 17 00:00:00 2001 From: Kevin K Date: Mon, 2 May 2016 14:12:57 -0400 Subject: [PATCH 5/8] tests: adds tests for required_unless settings --- src/args/arg.rs | 24 ++++----- tests/require.rs | 127 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 139 insertions(+), 12 deletions(-) diff --git a/src/args/arg.rs b/src/args/arg.rs index 55aea50761ff..8b2d94fe4810 100644 --- a/src/args/arg.rs +++ b/src/args/arg.rs @@ -541,7 +541,7 @@ impl<'a, 'b> Arg<'a, 'b> { if r { self.set(ArgSettings::Required) } else { self.unset(ArgSettings::Required) } } - /// Sets an arg that override this arg's required setting. (i.e. this arg will be required + /// Sets an arg that override this arg's required setting. (i.e. this arg will be required /// unless this other argument is present). /// /// **Pro Tip:** Using `Arg::required_unless` implies `Arg::required` and is therefore not @@ -556,7 +556,7 @@ impl<'a, 'b> Arg<'a, 'b> { /// # ; /// ``` /// - /// Setting `required_unless(name)` requires that the argument be used at runtime *unless* + /// Setting `required_unless(name)` requires that the argument be used at runtime *unless* /// `name` is present. In the following example, the required argument is *not* provided, but /// it's not an error because the `unless` arg has been supplied. /// @@ -588,7 +588,7 @@ impl<'a, 'b> Arg<'a, 'b> { /// .arg(Arg::with_name("dbg") /// .long("debug")) /// .get_matches_from_safe(vec![ - /// "unlesstest" + /// "unlesstest" /// ]); /// /// assert!(res.is_err()); @@ -618,8 +618,8 @@ impl<'a, 'b> Arg<'a, 'b> { /// # ; /// ``` /// - /// Setting `required_unless_all(names)` requires that the argument be used at runtime *unless* - /// *all* the args in `names` are present. In the following example, the required argument is + /// Setting `required_unless_all(names)` requires that the argument be used at runtime *unless* + /// *all* the args in `names` are present. In the following example, the required argument is /// *not* provided, but it's not an error because all the `unless` args have been supplied. /// /// ```rust @@ -643,7 +643,7 @@ impl<'a, 'b> Arg<'a, 'b> { /// /// Setting `required_unless_all(names)` and *not* supplying *all* of `names` or this arg is an /// error. - /// + /// /// ```rust /// # use clap::{App, Arg, ErrorKind}; /// let res = App::new("unlessall") @@ -678,7 +678,7 @@ impl<'a, 'b> Arg<'a, 'b> { /// Sets args that override this arg's required setting. (i.e. this arg will be required unless /// *at least one of* these other argument are present). /// - /// **NOTE:** If you wish for the this argument to only be required if *all of* these args are + /// **NOTE:** If you wish for the this argument to only be required if *all of* these args are /// present see `Arg::required_unless_all` /// /// # Examples @@ -690,9 +690,9 @@ impl<'a, 'b> Arg<'a, 'b> { /// # ; /// ``` /// - /// Setting `required_unless_one(names)` requires that the argument be used at runtime *unless* - /// *at least one of* the args in `names` are present. In the following example, the required - /// argument is *not* provided, but it's not an error because one the `unless` args have been + /// Setting `required_unless_one(names)` requires that the argument be used at runtime *unless* + /// *at least one of* the args in `names` are present. In the following example, the required + /// argument is *not* provided, but it's not an error because one the `unless` args have been /// supplied. /// /// ```rust @@ -714,9 +714,9 @@ impl<'a, 'b> Arg<'a, 'b> { /// assert!(res.is_ok()); /// ``` /// - /// Setting `required_unless_one(names)` and *not* supplying *at least one of* `names` or this + /// Setting `required_unless_one(names)` and *not* supplying *at least one of* `names` or this /// arg is an error. - /// + /// /// ```rust /// # use clap::{App, Arg, ErrorKind}; /// let res = App::new("unlessone") diff --git a/tests/require.rs b/tests/require.rs index 7bf5a9f49d0a..b01d64e029ba 100644 --- a/tests/require.rs +++ b/tests/require.rs @@ -168,3 +168,130 @@ fn arg_require_group_3() { assert!(m.is_present("other")); assert!(m.is_present("flag")); } + +// REQUIRED_UNLESS + +#[test] +fn required_unless() { + let res = App::new("unlesstest") + .arg(Arg::with_name("cfg") + .required_unless("dbg") + .takes_value(true) + .long("config")) + .arg(Arg::with_name("dbg") + .long("debug")) + .get_matches_from_safe(vec![ + "unlesstest", "--debug" + ]); + + assert!(res.is_ok()); + let m = res.unwrap(); + assert!(m.is_present("dbg")); + assert!(!m.is_present("cfg")); +} + +#[test] +fn required_unless_err() { + let res = App::new("unlesstest") + .arg(Arg::with_name("cfg") + .required_unless("dbg") + .takes_value(true) + .long("config")) + .arg(Arg::with_name("dbg") + .long("debug")) + .get_matches_from_safe(vec![ + "unlesstest" + ]); + + assert!(res.is_err()); + assert_eq!(res.unwrap_err().kind, ErrorKind::MissingRequiredArgument); +} + +// REQUIRED_UNLESS_ALL + +#[test] +fn required_unless_all() { + let res = App::new("unlessall") + .arg(Arg::with_name("cfg") + .required_unless_all(&["dbg", "infile"]) + .takes_value(true) + .long("config")) + .arg(Arg::with_name("dbg") + .long("debug")) + .arg(Arg::with_name("infile") + .short("i") + .takes_value(true)) + .get_matches_from_safe(vec![ + "unlessall", "--debug", "-i", "file" + ]); + + assert!(res.is_ok()); + let m = res.unwrap(); + assert!(m.is_present("dbg")); + assert!(m.is_present("infile")); + assert!(!m.is_present("cfg")); +} + +#[test] +fn required_unless_all_err() { + let res = App::new("unlessall") + .arg(Arg::with_name("cfg") + .required_unless_all(&["dbg", "infile"]) + .takes_value(true) + .long("config")) + .arg(Arg::with_name("dbg") + .long("debug")) + .arg(Arg::with_name("infile") + .short("i") + .takes_value(true)) + .get_matches_from_safe(vec![ + "unlessall", "--debug" + ]); + + assert!(res.is_err()); + assert_eq!(res.unwrap_err().kind, ErrorKind::MissingRequiredArgument); +} + +// REQUIRED_UNLESS_ONE + +#[test] +fn required_unless_one() { + let res = App::new("unlessone") + .arg(Arg::with_name("cfg") + .required_unless_one(&["dbg", "infile"]) + .takes_value(true) + .long("config")) + .arg(Arg::with_name("dbg") + .long("debug")) + .arg(Arg::with_name("infile") + .short("i") + .takes_value(true)) + .get_matches_from_safe(vec![ + "unlessone", "--debug" + ]); + + assert!(res.is_ok()); + let m = res.unwrap(); + assert!(m.is_present("dbg")); + assert!(!m.is_present("cfg")); +} + +#[test] +fn required_unless_one_err() { + let res = App::new("unlessone") + .arg(Arg::with_name("cfg") + .required_unless_one(&["dbg", "infile"]) + .takes_value(true) + .long("config")) + .arg(Arg::with_name("dbg") + .long("debug")) + .arg(Arg::with_name("infile") + .short("i") + .takes_value(true)) + .get_matches_from_safe(vec![ + "unlessone" + ]); + + assert!(res.is_err()); + assert_eq!(res.unwrap_err().kind, ErrorKind::MissingRequiredArgument); +} From d8e4dbc9616a1af5da1865c534e4f73e75620389 Mon Sep 17 00:00:00 2001 From: Kevin K Date: Mon, 2 May 2016 14:25:05 -0400 Subject: [PATCH 6/8] feat(Help): adds support for displaying info before help message Can now use the `App::before_help` method to add additional information that will be displayed prior to the help message. Common uses are copyright, or license information. --- src/app/help.rs | 8 +++++++- src/app/meta.rs | 3 +++ src/app/mod.rs | 17 +++++++++++++++++ 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/app/help.rs b/src/app/help.rs index 2a1d1a718d7e..7a871379ebf4 100644 --- a/src/app/help.rs +++ b/src/app/help.rs @@ -692,7 +692,8 @@ impl<'a> Help<'a> { /// * `{options}` - Help for options. /// * `{positionals}` - Help for positionals arguments. /// * `{subcommands}` - Help for subcommands. - /// * `{after-help}` - Help for flags. + /// * `{after-help}` - Info to be displayed after the help message. + /// * `{before-help}` - Info to be displayed before the help message. /// /// The template system is, on purpose, very simple. Therefore the tags have to writen /// in the lowercase and without spacing. @@ -768,6 +769,11 @@ impl<'a> Help<'a> { "{}", parser.meta.more_help.unwrap_or("unknown after-help"))); } + b"before-help" => { + try!(write!(self.writer, + "{}", + parser.meta.pre_help.unwrap_or("unknown before-help"))); + } // Unknown tag, write it back. ref r => { try!(self.writer.write(b"{")); diff --git a/src/app/meta.rs b/src/app/meta.rs index c49f46379bf1..f4eb8e1a6434 100644 --- a/src/app/meta.rs +++ b/src/app/meta.rs @@ -7,6 +7,7 @@ pub struct AppMeta<'b> { pub version: Option<&'b str>, pub about: Option<&'b str>, pub more_help: Option<&'b str>, + pub pre_help: Option<&'b str>, pub usage_str: Option<&'b str>, pub usage: Option, pub help_str: Option<&'b str>, @@ -21,6 +22,7 @@ impl<'b> Default for AppMeta<'b> { author: None, about: None, more_help: None, + pre_help: None, version: None, usage_str: None, usage: None, @@ -49,6 +51,7 @@ impl<'b> Clone for AppMeta<'b> { author: self.author, about: self.about, more_help: self.more_help, + pre_help: self.pre_help, version: self.version, usage_str: self.usage_str, usage: self.usage.clone(), diff --git a/src/app/mod.rs b/src/app/mod.rs index 0cf44fcab479..11ffc55efccb 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -189,6 +189,23 @@ impl<'a, 'b> App<'a, 'b> { self } + /// Adds additional help information to be displayed in addition to auto-generated help. This + /// information is displayed **before** the auto-generated help information. This is often used + /// for header information. + /// + /// # Examples + /// + /// ```no_run + /// # use clap::App; + /// App::new("myprog") + /// .before_help("Some info I'd like to appear before the help info") + /// # ; + /// ``` + pub fn before_help>(mut self, help: S) -> Self { + self.p.meta.pre_help = Some(help.into()); + self + } + /// Sets a string of the version number to be displayed when displaying version or help /// information. /// From 89db52a965eef901231b60655a6f838d3c530d23 Mon Sep 17 00:00:00 2001 From: Kevin K Date: Mon, 2 May 2016 14:42:39 -0400 Subject: [PATCH 7/8] chore: increase version --- CHANGELOG.md | 19 +++++++++++++++++++ Cargo.toml | 2 +- README.md | 6 ++++++ 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ee1bdcf89a4..08c665563e2e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,22 @@ + +## v2.4.0 (2016-05-02) + + +#### Features + +* **Help:** adds support for displaying info before help message ([29fbfa3b](https://github.com/kbknapp/clap-rs/commit/29fbfa3b963f2f3ca7704bf5d3e1201531baa373)) +* **Required:** adds allowing args that are required unless certain args are present ([af1f7916](https://github.com/kbknapp/clap-rs/commit/af1f79168390ea7da4074d0d9777de458ea64971)) + +#### Documentation + +* hides formatting from docs ([cb708093](https://github.com/kbknapp/clap-rs/commit/cb708093a7cd057f08c98b7bd1ed54c2db86ae7e)) +* **required_unless:** adds docs and examples for required_unless ([ca727b52](https://github.com/kbknapp/clap-rs/commit/ca727b52423b9883acd88b2f227b2711bc144573)) + +#### Bug Fixes + +* **Required Args:** fixes issue where missing required args are sometimes duplicatd in error messages ([3beebd81](https://github.com/kbknapp/clap-rs/commit/3beebd81e7bc2faa4115ac109cf570e512c5477f), closes [#492](https://github.com/kbknapp/clap-rs/issues/492)) + + ## v2.3.0 (2016-04-18) diff --git a/Cargo.toml b/Cargo.toml index db05d9b1ec71..20033b5b536e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "clap" -version = "2.3.0" +version = "2.4.0" authors = ["Kevin K. "] exclude = ["examples/*", "clap-tests/*", "tests/*", "benches/*", "*.png", "clap-perf/*"] description = "A simple to use, efficient, and full featured Command Line Argument Parser" diff --git a/README.md b/README.md index 8c5803ce2e9c..39b6abed637a 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,12 @@ Created by [gh-md-toc](https://github.com/ekalinin/github-markdown-toc) ## What's New +Here's the highlights from v2.4.0 + +* **Before Help:** adds support for displaying info before help message +* **Required Unless:** adds support for allowing args that are required unless certain other args are present +* Bug fixes + Here's the highlights from v2.3.0 * **New Help Template Engine!**: Now you have full control over the layout of your help message. Major thanks to @hgrecco From 5b0120088a2d49f2ead1dffdef6a2d0e0894d67e Mon Sep 17 00:00:00 2001 From: Kevin K Date: Mon, 2 May 2016 19:02:24 -0400 Subject: [PATCH 8/8] fix(Required Args): fixes issue where missing required args are sometimes duplicatd in error messages Closes #492 --- src/app/parser.rs | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/app/parser.rs b/src/app/parser.rs index 53bbee15cef4..9fe171952a71 100644 --- a/src/app/parser.rs +++ b/src/app/parser.rs @@ -850,9 +850,11 @@ impl<'a, 'b> Parser<'a, 'b> where 'a: 'b { fn check_for_help_and_version_str(&self, arg: &OsStr) -> ClapResult<()> { debug!("Checking if --{} is help or version...", arg.to_str().unwrap()); if arg == "help" && self.settings.is_set(AppSettings::NeedsLongHelp) { + sdebugln!("Help"); try!(self._help()); } if arg == "version" && self.settings.is_set(AppSettings::NeedsLongVersion) { + sdebugln!("Version"); try!(self._version()); } sdebugln!("Neither"); @@ -862,8 +864,14 @@ impl<'a, 'b> Parser<'a, 'b> where 'a: 'b { fn check_for_help_and_version_char(&self, arg: char) -> ClapResult<()> { debug!("Checking if -{} is help or version...", arg); - if let Some(h) = self.help_short { if arg == h { try!(self._help()); } } - if let Some(v) = self.version_short { if arg == v { try!(self._version()); } } + if let Some(h) = self.help_short { + sdebugln!("Help"); + if arg == h { try!(self._help()); } + } + if let Some(v) = self.version_short { + sdebugln!("Help"); + if arg == v { try!(self._version()); } + } sdebugln!("Neither"); Ok(()) } @@ -1254,8 +1262,10 @@ impl<'a, 'b> Parser<'a, 'b> where 'a: 'b { let err = if self.settings.is_set(AppSettings::ArgRequiredElseHelp) && matcher.is_empty() { self._help().unwrap_err() } else { + let mut reqs = self.required.iter().map(|&r| &*r).collect::>(); + reqs.dedup(); Error::missing_required_argument( - &*self.get_required_from(&*self.required.iter().map(|&r| &*r).collect::>(), Some(matcher)) + &*self.get_required_from(&*reqs, Some(matcher)) .iter() .fold(String::new(), |acc, s| acc + &format!("\n {}", Format::Error(s))[..]), @@ -1337,6 +1347,7 @@ impl<'a, 'b> Parser<'a, 'b> where 'a: 'b { // after all arguments were parsed, but before any subcommands have been parsed // (so as to give subcommands their own usage recursively) pub fn create_usage_no_title(&self, used: &[&str]) -> String { + debugln!("fn=create_usage_no_title;"); let mut usage = String::with_capacity(75); if let Some(u) = self.meta.usage_str { usage.push_str(&*u); @@ -1346,7 +1357,8 @@ impl<'a, 'b> Parser<'a, 'b> where 'a: 'b { .unwrap_or(self.meta.bin_name .as_ref() .unwrap_or(&self.meta.name))); - let reqs: Vec<&str> = self.required().map(|r| &**r).collect(); + let mut reqs: Vec<&str> = self.required().map(|r| &**r).collect(); + reqs.dedup(); let req_string = self.get_required_from(&reqs, None) .iter() .fold(String::new(), |a, s| a + &format!(" {}", s)[..]); @@ -1395,8 +1407,10 @@ impl<'a, 'b> Parser<'a, 'b> where 'a: 'b { // Creates a context aware usage string, or "smart usage" from currently used // args, and requirements fn smart_usage(&self, usage: &mut String, used: &[&str]) { + debugln!("fn=smart_usage;"); let mut hs: Vec<&str> = self.required().map(|s| &**s).collect(); hs.extend_from_slice(used); + let r_string = self.get_required_from(&hs, None) .iter() .fold(String::new(), |acc, s| acc + &format!(" {}", s)[..]);