diff --git a/CHANGELOG.md b/CHANGELOG.md
index bbafdde5cfcb..ad89c1128577 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,21 @@
+
+## v2.8.0 (2016-06-30)
+
+
+#### Features
+
+* **Arg:** adds new setting `Arg::require_delimiter` which requires val delimiter to parse multiple values ([920b5595](https://github.com/kbknapp/clap-rs/commit/920b5595ed72abfb501ce054ab536067d8df2a66))
+
+#### Bug Fixes
+
+* Declare term::Winsize as repr(C) ([5d663d90](https://github.com/kbknapp/clap-rs/commit/5d663d905c9829ce6e7a164f1f0896cdd70236dd))
+
+#### Documentation
+
+* **Arg:** adds docs for ([49af4e38](https://github.com/kbknapp/clap-rs/commit/49af4e38a5dae2ab0a7fc3b4147e2c053d532484))
+
+
+
### v2.7.1 (2016-06-29)
diff --git a/Cargo.toml b/Cargo.toml
index fe9a4c55c613..e1a0b980fa36 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,7 +1,7 @@
[package]
name = "clap"
-version = "2.7.1"
+version = "2.8.0"
authors = ["Kevin K. "]
exclude = ["examples/*", "clap-test/*", "tests/*", "benches/*", "*.png", "clap-perf/*", "*.dot"]
description = "A simple to use, efficient, and full featured Command Line Argument Parser"
@@ -20,6 +20,7 @@ strsim = { version = "~0.4.0", optional = true }
yaml-rust = { version = "~0.3.2", optional = true }
clippy = { version = "~0.0.74", optional = true }
unicode-width = { version = "~0.1.3", optional = true }
+term_size = { version = "~0.1.0", optional = true }
[dev-dependencies]
regex = "~0.1.69"
@@ -29,7 +30,7 @@ default = ["suggestions", "color", "wrap_help"]
suggestions = ["strsim"]
color = ["ansi_term", "libc"]
yaml = ["yaml-rust"]
-wrap_help = ["libc", "unicode-width"]
+wrap_help = ["libc", "unicode-width", "term_size"]
lints = ["clippy", "nightly"]
nightly = [] # for building with nightly and unstable features
unstable = [] # for building with unstable features on stable Rust
diff --git a/README.md b/README.md
index 1163997fa495..c923fe316c41 100644
--- a/README.md
+++ b/README.md
@@ -38,6 +38,13 @@ Created by [gh-md-toc](https://github.com/ekalinin/github-markdown-toc)
## What's New
+Here's the highlights for v2.8.0
+
+* **Arg:** adds new optional setting `Arg::require_delimiter` which requires val delimiter to parse multiple values
+* The terminal sizing portion has been factored out into a separate crate, [term_size](https://crates.io/crates/term_size)
+* Minor bug fixes
+
+
Here's the highlights for v2.7.1
* **Options:**
diff --git a/src/app/help.rs b/src/app/help.rs
index b77e47539d8a..2c60f20a5312 100644
--- a/src/app/help.rs
+++ b/src/app/help.rs
@@ -12,7 +12,14 @@ use app::{App, AppSettings};
use app::parser::Parser;
use fmt::{Format, Colorizer};
-use term;
+#[cfg(all(feature = "wrap_help", not(target_os = "windows")))]
+use term_size;
+#[cfg(any(not(feature = "wrap_help"), target_os = "windows"))]
+mod term_size {
+ pub fn dimensions() -> Option<(usize, usize)> {
+ None
+ }
+}
#[cfg(all(feature = "wrap_help", not(target_os = "windows")))]
use unicode_width::UnicodeWidthStr;
@@ -96,7 +103,7 @@ impl<'a> Help<'a> {
hide_pv: hide_pv,
term_w: match term_w {
Some(width) => width,
- None => term::dimensions().map(|(w, _)| w).unwrap_or(120),
+ None => term_size::dimensions().map(|(w, _)| w).unwrap_or(120),
},
color: color,
cizer: cizer,
diff --git a/src/app/parser.rs b/src/app/parser.rs
index cb5725be43e1..d51017fc0407 100644
--- a/src/app/parser.rs
+++ b/src/app/parser.rs
@@ -1256,9 +1256,9 @@ impl<'a, 'b> Parser<'a, 'b>
ret = try!(self.add_single_val_to_arg(arg, v, matcher));
}
// If there was a delimiter used, we're not looking for more values
- if val.contains_byte(delim as u32 as u8) {
+ if val.contains_byte(delim as u32 as u8) || arg.is_set(ArgSettings::RequireDelimiter) {
ret = None;
- }
+ }
}
} else {
ret = try!(self.add_single_val_to_arg(arg, val, matcher));
diff --git a/src/args/arg.rs b/src/args/arg.rs
index ac0c5dc8f5a5..aabe4f2f34b4 100644
--- a/src/args/arg.rs
+++ b/src/args/arg.rs
@@ -1919,6 +1919,88 @@ impl<'a, 'b> Arg<'a, 'b> {
}
}
+ /// Specifies that *multiple values* may only be set using the delimiter. This means if an
+ /// if an option is encountered, and no delimiter is found, it automatically assumed that no
+ /// additional values for that option follow. This is unlike the default, where it is generally
+ /// assumed that more values will follow regardless of whether or not a delimiter is used.
+ ///
+ /// **NOTE:** The default is `false`.
+ ///
+ /// **NOTE:** It's a good idea to inform the user that use of a delimiter is required, either
+ /// through help text or other means.
+ ///
+ /// # Examples
+ ///
+ /// These examples demonstrate what happens when `require_delimiter(true)` is used. Notice
+ /// everything works in this first example, as we use a delimiter, as expected.
+ ///
+ /// ```rust
+ /// # use clap::{App, Arg};
+ /// let delims = App::new("reqdelims")
+ /// .arg(Arg::with_name("opt")
+ /// .short("o")
+ /// .takes_value(true)
+ /// .multiple(true)
+ /// .require_delimiter(true))
+ /// // Simulate "$ reqdelims -o val1,val2,val3"
+ /// .get_matches_from(vec![
+ /// "reqdelims", "-o", "val1,val2,val3",
+ /// ]);
+ ///
+ /// assert!(delims.is_present("opt"));
+ /// assert_eq!(delims.values_of("opt").unwrap().collect::>(), ["val1", "val2", "val3"]);
+ /// ```
+ /// In this next example, we will *not* use a delimiter. Notice it's now an error.
+ ///
+ /// ```rust
+ /// # use clap::{App, Arg, ErrorKind};
+ /// let res = App::new("reqdelims")
+ /// .arg(Arg::with_name("opt")
+ /// .short("o")
+ /// .takes_value(true)
+ /// .multiple(true)
+ /// .require_delimiter(true))
+ /// // Simulate "$ reqdelims -o val1 val2 val3"
+ /// .get_matches_from_safe(vec![
+ /// "reqdelims", "-o", "val1", "val2", "val3",
+ /// ]);
+ ///
+ /// assert!(res.is_err());
+ /// let err = res.unwrap_err();
+ /// assert_eq!(err.kind, ErrorKind::UnknownArgument);
+ /// ```
+ /// What's happening is `-o` is getting `val1`, and because delimiters are required yet none
+ /// were present, it stops parsing `-o`. At this point it reaches `val2` and because no
+ /// positional arguments have been defined, it's an error of an unexpected argument.
+ ///
+ /// In this final example, we contrast the above with `clap`'s default behavior where the above
+ /// is *not* an error.
+ ///
+ /// ```rust
+ /// # use clap::{App, Arg};
+ /// let delims = App::new("reqdelims")
+ /// .arg(Arg::with_name("opt")
+ /// .short("o")
+ /// .takes_value(true)
+ /// .multiple(true))
+ /// // Simulate "$ reqdelims -o val1 val2 val3"
+ /// .get_matches_from(vec![
+ /// "reqdelims", "-o", "val1", "val2", "val3",
+ /// ]);
+ ///
+ /// assert!(delims.is_present("opt"));
+ /// assert_eq!(delims.values_of("opt").unwrap().collect::>(), ["val1", "val2", "val3"]);
+ /// ```
+ pub fn require_delimiter(mut self, d: bool) -> Self {
+ if d {
+ self.setb(ArgSettings::UseValueDelimiter);
+ self.set(ArgSettings::RequireDelimiter)
+ } else {
+ self.unsetb(ArgSettings::UseValueDelimiter);
+ self.unset(ArgSettings::RequireDelimiter)
+ }
+ }
+
/// Specifies the separator to use when values are clumped together, defaults to `,` (comma).
///
/// **NOTE:** implicitly sets [`Arg::use_delimiter(true)`]
diff --git a/src/args/settings.rs b/src/args/settings.rs
index c4573c07184a..4921fcd17d34 100644
--- a/src/args/settings.rs
+++ b/src/args/settings.rs
@@ -3,15 +3,16 @@ use std::ascii::AsciiExt;
bitflags! {
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,
+ const REQUIRED = 0b0000000001,
+ const MULTIPLE = 0b0000000010,
+ const EMPTY_VALS = 0b0000000100,
+ const GLOBAL = 0b0000001000,
+ const HIDDEN = 0b0000010000,
+ const TAKES_VAL = 0b0000100000,
+ const USE_DELIM = 0b0001000000,
+ const NEXT_LINE_HELP = 0b0010000000,
+ const R_UNLESS_ALL = 0b0100000000,
+ const REQ_DELIM = 0b1000000000,
}
}
@@ -33,7 +34,8 @@ impl ArgFlags {
TakesValue => TAKES_VAL,
UseValueDelimiter => USE_DELIM,
NextLineHelp => NEXT_LINE_HELP,
- RequiredUnlessAll => R_UNLESS_ALL
+ RequiredUnlessAll => R_UNLESS_ALL,
+ RequireDelimiter => REQ_DELIM
}
}
@@ -67,6 +69,8 @@ pub enum ArgSettings {
UseValueDelimiter,
/// Prints the help text on the line after the argument
NextLineHelp,
+ /// Requires the use of a value delimiter for all multiple values
+ RequireDelimiter,
#[doc(hidden)]
RequiredUnlessAll,
}
@@ -84,6 +88,7 @@ impl FromStr for ArgSettings {
"usevaluedelimiter" => Ok(ArgSettings::UseValueDelimiter),
"nextlinehelp" => Ok(ArgSettings::NextLineHelp),
"requiredunlessall" => Ok(ArgSettings::RequiredUnlessAll),
+ "requiredelimiter" => Ok(ArgSettings::RequireDelimiter),
_ => Err("unknown ArgSetting, cannot convert from str".to_owned()),
}
}
diff --git a/src/lib.rs b/src/lib.rs
index 4538b4af1774..22074ad94403 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -414,6 +414,8 @@ extern crate unicode_width;
#[macro_use]
extern crate bitflags;
extern crate vec_map;
+#[cfg(feature = "wrap_help")]
+extern crate term_size;
#[cfg(feature = "yaml")]
pub use yaml_rust::YamlLoader;
@@ -431,7 +433,6 @@ mod fmt;
mod suggestions;
mod errors;
mod osstringext;
-mod term;
mod strext;
const INTERNAL_ERROR_MSG: &'static str = "Fatal internal error. Please consider filing a bug \
diff --git a/src/term.rs b/src/term.rs
deleted file mode 100644
index f4ac97a57b7b..000000000000
--- a/src/term.rs
+++ /dev/null
@@ -1,81 +0,0 @@
-// The following was taken and adapated from exa source
-// repo: https://github.com/ogham/exa
-// commit: b9eb364823d0d4f9085eb220233c704a13d0f611
-// license: MIT - Copyright (c) 2014 Benjamin Sago
-
-//! System calls for getting the terminal size.
-//!
-//! Getting the terminal size is performed using an ioctl command that takes
-//! the file handle to the terminal -- which in this case, is stdout -- and
-//! populates a structure containing the values.
-//!
-//! The size is needed when the user wants the output formatted into columns:
-//! the default grid view, or the hybrid grid-details view.
-
-#[cfg(all(feature = "wrap_help", not(target_os = "windows")))]
-use std::mem::zeroed;
-#[cfg(all(feature = "wrap_help", not(target_os = "windows")))]
-use libc::{STDOUT_FILENO, c_int, c_ulong, c_ushort};
-
-
-/// The number of rows and columns of a terminal.
-#[cfg(all(feature = "wrap_help", not(target_os = "windows")))]
-#[repr(C)]
-struct Winsize {
- ws_row: c_ushort,
- ws_col: c_ushort,
-}
-
-// Unfortunately the actual command is not standardised...
-
-#[cfg(any(target_os = "linux", target_os = "android"))]
-#[cfg(feature = "wrap_help")]
-static TIOCGWINSZ: c_ulong = 0x5413;
-
-#[cfg(any(target_os = "macos",
- target_os = "ios",
- target_os = "bitrig",
- target_os = "dragonfly",
- target_os = "freebsd",
- target_os = "netbsd",
- target_os = "openbsd"))]
-#[cfg(feature = "wrap_help")]
-static TIOCGWINSZ: c_ulong = 0x40087468;
-
-extern "C" {
-#[cfg(all(feature = "wrap_help", not(target_os = "windows")))]
- pub fn ioctl(fd: c_int, request: c_ulong, ...) -> c_int;
-}
-
-/// Runs the ioctl command. Returns (0, 0) if output is not to a terminal, or
-/// there is an error. (0, 0) is an invalid size to have anyway, which is why
-/// it can be used as a nil value.
-#[cfg(all(feature = "wrap_help", not(target_os = "windows")))]
-unsafe fn get_dimensions() -> Winsize {
- let mut window: Winsize = zeroed();
- let result = ioctl(STDOUT_FILENO, TIOCGWINSZ, &mut window);
-
- if result == -1 {
- zeroed()
- } else {
- window
- }
-}
-
-/// Query the current processes's output, returning its width and height as a
-/// number of characters. Returns `None` if the output isn't to a terminal.
-#[cfg(all(feature = "wrap_help", not(target_os = "windows")))]
-pub fn dimensions() -> Option<(usize, usize)> {
- let w = unsafe { get_dimensions() };
-
- if w.ws_col == 0 || w.ws_row == 0 {
- None
- } else {
- Some((w.ws_col as usize, w.ws_row as usize))
- }
-}
-
-#[cfg(any(not(feature = "wrap_help"), target_os = "windows"))]
-pub fn dimensions() -> Option<(usize, usize)> {
- None
-}
diff --git a/tests/opts.rs b/tests/opts.rs
index 97ba46499c6b..a2c58dd4366e 100644
--- a/tests/opts.rs
+++ b/tests/opts.rs
@@ -3,7 +3,7 @@ extern crate regex;
include!("../clap-test.rs");
-use clap::{App, Arg};
+use clap::{App, Arg, ErrorKind};
#[test]
fn stdin_char() {
@@ -200,6 +200,31 @@ fn multiple_vals_pos_arg_delim() {
assert_eq!(m.value_of("file").unwrap(), "some");
}
+#[test]
+fn require_delims_no_delim() {
+ let r = App::new("mvae")
+ .arg( Arg::from_usage("-o [opt]... 'some opt'").require_delimiter(true) )
+ .arg( Arg::from_usage("[file] 'some file'") )
+ .get_matches_from_safe(vec!["mvae", "-o", "1", "2", "some"]);
+ assert!(r.is_err());
+ let err = r.unwrap_err();
+ assert_eq!(err.kind, ErrorKind::UnknownArgument);
+}
+
+#[test]
+fn require_delims() {
+ let r = App::new("mvae")
+ .arg( Arg::from_usage("-o [opt]... 'some opt'").require_delimiter(true) )
+ .arg( Arg::from_usage("[file] 'some file'") )
+ .get_matches_from_safe(vec!["", "-o", "1,2", "some"]);
+ assert!(r.is_ok());
+ let m = r.unwrap();
+ assert!(m.is_present("o"));
+ assert_eq!(m.values_of("o").unwrap().collect::>(), &["1", "2"]);
+ assert!(m.is_present("file"));
+ assert_eq!(m.value_of("file").unwrap(), "some");
+}
+
#[test]
fn did_you_mean() {
test::check_err_output(test::complex_app(), "clap-test --optio=foo",