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
diff --git a/src/app/help.rs b/src/app/help.rs
index bce8ab3f5cb3..7a871379ebf4 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;
@@ -694,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.
@@ -770,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 1f80b5c9a5e4..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.
///
@@ -936,6 +953,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..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(())
}
@@ -1245,17 +1253,19 @@ 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()
} 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))[..]),
@@ -1266,7 +1276,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 +1286,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
}
@@ -1323,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);
@@ -1332,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)[..]);
@@ -1381,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)[..]);
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..8b2d94fe4810 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,212 @@ 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);
+ } else {
+ self.r_unless = Some(vec![name]);
+ }
+ self.required(true)
+ }
+
+ /// 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"
+ /// ]);
+ ///
+ /// assert!(res.is_err());
+ /// assert_eq!(res.unwrap_err().kind, ErrorKind::MissingRequiredArgument);
+ /// ```
+ 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)
+ }
+
+ /// 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 {
+ 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 +2135,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 +2162,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()),
}
}
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),
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);
+}