Skip to content

Commit

Permalink
feat: adds support for comma separated values
Browse files Browse the repository at this point in the history
This commit adds support for values separated by commas such as
--option=val1,val2,val3. It also includes support for uses
without the equals and shorts (both with and without)

--option=val1,val2
--option val1,val2
-oval1,val2
-o=val1,val2
-o val1,val2

Closes #348
  • Loading branch information
kbknapp committed Jan 28, 2016
1 parent 4b8b5b6 commit e69da6a
Show file tree
Hide file tree
Showing 3 changed files with 155 additions and 94 deletions.
172 changes: 79 additions & 93 deletions src/app/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ use SubCommand;
use fmt::Format;
use osstringext::OsStrExt2;
use app::meta::AppMeta;
use args::MatchedArg;

pub struct Parser<'a, 'b> where 'a: 'b {
required: Vec<&'b str>,
Expand Down Expand Up @@ -1043,8 +1044,7 @@ impl<'a, 'b> Parser<'a, 'b> where 'a: 'b {
// Increment or create the group "args"
self.groups_for_arg(opt.name).and_then(|vec| Some(matcher.inc_occurrences_of(&*vec)));

if (opt.is_set(ArgSettings::Multiple) || opt.num_vals().is_some())
|| val.is_none() {
if val.is_none() || opt.is_set(ArgSettings::Multiple) {
return Ok(Some(opt.name));
}
Ok(None)
Expand All @@ -1056,18 +1056,24 @@ impl<'a, 'b> Parser<'a, 'b> where 'a: 'b {
matcher: &mut ArgMatcher<'a>)
-> ClapResult<Option<&'a str>>
where A: AnyArg<'a, 'b> + Display {
matcher.add_val_to(&*arg.name(), val);

// Increment or create the group "args"
if let Some(grps) = self.groups_for_arg(&*arg.name()) {
for grp in grps {
matcher.add_val_to(&*grp, val);
debugln!("fn=add_val_to_arg;");
let mut ret = None;
for v in val.split(b',') {
debugln!("adding val: {:?}", v);
matcher.add_val_to(&*arg.name(), v);

// Increment or create the group "args"
if let Some(grps) = self.groups_for_arg(&*arg.name()) {
for grp in grps {
matcher.add_val_to(&*grp, v);
}
}
}

// The validation must come AFTER inserting into 'matcher' or the usage string
// can't be built
self.validate_value(arg, val, matcher)
// The validation must come AFTER inserting into 'matcher' or the usage string
// can't be built
ret = try!(self.validate_value(arg, v, matcher));
}
Ok(ret)
}

fn validate_value<A>(&self, arg: &A, val: &OsStr, matcher: &ArgMatcher<'a>) -> ClapResult<Option<&'a str>>
Expand Down Expand Up @@ -1186,6 +1192,7 @@ impl<'a, 'b> Parser<'a, 'b> where 'a: 'b {
}

fn validate_num_args(&self, matcher: &mut ArgMatcher) -> ClapResult<()> {
debugln!("fn=validate_num_args;");
for (name, ma) in matcher.iter() {
if self.groups.contains_key(&**name) {
continue;
Expand All @@ -1194,97 +1201,76 @@ impl<'a, 'b> Parser<'a, 'b> where 'a: 'b {
.iter()
.filter(|o| &o.name == name)
.next() {
if let Some(num) = opt.num_vals {
let should_err = if opt.settings.is_set(ArgSettings::Multiple) {
((ma.vals.len() as u8) % num) != 0
} else {
num != (ma.vals.len() as u8)
};
if should_err {
return Err(Error::wrong_number_of_values(
opt,
num,
if opt.settings.is_set(ArgSettings::Multiple) {
(ma.vals.len() % num as usize)
} else {
ma.vals.len()
},
if ma.vals.len() == 1 ||
(opt.settings.is_set(ArgSettings::Multiple) &&
(ma.vals.len() % num as usize) == 1) {
"as"
} else {
"ere"
},
&*self.create_current_usage(matcher)));
}
}
if let Some(num) = opt.max_vals {
if (ma.vals.len() as u8) > num {
return Err(Error::too_many_values(
ma.vals.get(&ma.vals.keys()
.last()
.expect(INTERNAL_ERROR_MSG))
.expect(INTERNAL_ERROR_MSG).to_str().expect(INVALID_UTF8),
opt,
&*self.create_current_usage(matcher)));
}
}
if let Some(num) = opt.min_vals {
if (ma.vals.len() as u8) < num {
return Err(Error::too_few_values(
opt,
num,
ma.vals.len(),
&*self.create_current_usage(matcher)));
}
}
try!(self._validate_num_vals(opt, ma, matcher));
} else if let Some(pos) = self.positionals
.values()
.filter(|p| &p.name == name)
.next() {
if let Some(num) = pos.num_vals {
if num != ma.vals.len() as u8 {
return Err(Error::wrong_number_of_values(
pos,
num,
ma.vals.len(),
if ma.vals.len() == 1 {
"as"
} else {
"ere"
},
&*self.create_current_usage(matcher)));
}
} else if let Some(max) = pos.max_vals {
if (ma.vals.len() as u8) > max {
return Err(
Error::too_many_values(
ma.vals.get(&ma.vals.keys()
.last()
.expect(INTERNAL_ERROR_MSG))
.expect(INTERNAL_ERROR_MSG)
.to_string_lossy()
.into_owned(),
pos,
&*self.create_current_usage(matcher)));
}
}
if let Some(min) = pos.min_vals {
if (ma.vals.len() as u8) < min {
return Err(Error::too_few_values(
pos,
min,
ma.vals.len(),
&*self.create_current_usage(matcher)));
}
}
try!(self._validate_num_vals(pos, ma, matcher));
}
}
}
Ok(())
}

fn _validate_num_vals<A>(&self, a: &A, ma: &MatchedArg, matcher: &ArgMatcher) -> ClapResult<()>
where A: AnyArg<'a, 'b>
{
debugln!("fn=_validate_num_vals;");
if let Some(num) = a.num_vals() {
debugln!("num_vals set: {}", num);
let should_err = if a.is_set(ArgSettings::Multiple) {
((ma.vals.len() as u8) % num) != 0
} else {
num != (ma.vals.len() as u8)
};
if should_err {
debugln!("Sending error WrongNumberOfValues");
return Err(Error::wrong_number_of_values(
a,
num,
if a.is_set(ArgSettings::Multiple) {
(ma.vals.len() % num as usize)
} else {
ma.vals.len()
},
if ma.vals.len() == 1 ||
(a.is_set(ArgSettings::Multiple) &&
(ma.vals.len() % num as usize) == 1) {
"as"
} else {
"ere"
},
&*self.create_current_usage(matcher)));
}
}
if let Some(num) = a.max_vals() {
debugln!("max_vals set: {}", num);
if (ma.vals.len() as u8) > num {
debugln!("Sending error TooManyValues");
return Err(Error::too_many_values(
ma.vals.get(&ma.vals.keys()
.last()
.expect(INTERNAL_ERROR_MSG))
.expect(INTERNAL_ERROR_MSG).to_str().expect(INVALID_UTF8),
a,
&*self.create_current_usage(matcher)));
}
}
if let Some(num) = a.min_vals() {
debugln!("min_vals set: {}", num);
if (ma.vals.len() as u8) < num {
debugln!("Sending error TooFewValues");
return Err(Error::too_few_values(
a,
num,
ma.vals.len(),
&*self.create_current_usage(matcher)));
}
}
Ok(())
}

fn validate_required(&self, matcher: &ArgMatcher) -> ClapResult<()> {
'outer: for name in &self.required {
if matcher.contains(name) {
Expand Down
4 changes: 3 additions & 1 deletion src/args/arg_builder/option.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,9 @@ impl<'n, 'e> OptBuilder<'n, 'e> {
ob.settings.set(ArgSettings::Hidden);
}
if let Some(ref vec) = ob.val_names {
ob.num_vals = Some(vec.len() as u8);
if vec.len() > 1 {
ob.num_vals = Some(vec.len() as u8);
}
}
// Check if there is anything in the blacklist (mutually excludes list) and add
// any
Expand Down
73 changes: 73 additions & 0 deletions src/osstringext.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
use std::ffi::OsStr;
#[cfg(not(target_os = "windows"))]
use std::os::unix::ffi::OsStrExt;

#[cfg(target_os = "windows")]
trait OsStrExt3 {
fn from_bytes(b: &[u8]) -> Self;
fn as_bytes(&self) -> &[u8];
}

#[doc(hidden)]
pub trait OsStrExt2 {
fn starts_with(&self, s: &[u8]) -> bool;
fn split_at_byte(&self, b: u8) -> (&OsStr, &OsStr);
Expand All @@ -9,6 +17,18 @@ pub trait OsStrExt2 {
fn len(&self) -> usize;
fn contains_byte(&self, b: u8) -> bool;
fn is_empty(&self) -> bool;
fn split(&self, b: u8) -> OsSplit;
}

#[cfg(target_os = "windows")]
impl OsStrExt3 for OsStr {
fn from_bytes(b: &[u8]) -> Self {
use ::std::mem;
unsafe { mem::transmute(b) }
}
fn as_bytes(&self) -> &[u8] {
self.as_inner().inner
}
}

impl OsStrExt2 for OsStr {
Expand Down Expand Up @@ -52,4 +72,57 @@ impl OsStrExt2 for OsStr {
fn len(&self) -> usize {
self.as_bytes().len()
}

fn split(&self, b: u8) -> OsSplit {
OsSplit { sep: b, val: self.as_bytes(), pos: 0 }
}
}

#[derive(Clone, Debug)]
pub struct OsSplit<'a> {
sep: u8,
val: &'a [u8],
pos: usize,
}

impl<'a> Iterator for OsSplit<'a> {
type Item = &'a OsStr;

fn next(&mut self) -> Option<&'a OsStr> {
debugln!("fn=OsSplit::next;");
debugln!("OsSplit: {:?}", self);
if self.pos == self.val.len() { return None; }
let start = self.pos;
for b in &self.val[start..] {
self.pos += 1;
if *b == self.sep {
return Some(&OsStr::from_bytes(&self.val[start..self.pos - 1]));
}
}
Some(&OsStr::from_bytes(&self.val[start..]))
}
fn size_hint(&self) -> (usize, Option<usize>) {
let mut count = 0;
for b in &self.val[self.pos..] {
if *b == self.sep { count += 1; }
}
if count > 0 {
return (count, Some(count));
}
(0, None)
}
}

impl<'a> DoubleEndedIterator for OsSplit<'a> {
fn next_back(&mut self) -> Option<&'a OsStr> {
if self.pos == 0 { return None; }
let start = self.pos;
for b in self.val[..self.pos].iter().rev() {
self.pos -= 1;
if *b == self.sep {
return Some(&OsStr::from_bytes(&self.val[self.pos + 1..start]));
}
}
Some(&OsStr::from_bytes(&self.val[..start]))
}
}

0 comments on commit e69da6a

Please sign in to comment.