diff --git a/Cargo.toml b/Cargo.toml index 13926d94..112964d8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ unicode-width = "=0.1.4" unicode-segmentation = "1.0.1" strsim = { version = "=0.6.0", optional = true } ansi_term = { version = "=0.9.0", optional = true } -term_size = { version = "=0.2.0", optional = true } +term_size = { version = "=0.2.1", optional = true } libc = { version = "=0.2.18", optional = true } yaml-rust = { version = "=0.3.5", optional = true } clippy = { version = "~0.0.104", optional = true } diff --git a/benches/03_complex.rs b/benches/03_complex.rs index a803cfeb..4048af25 100644 --- a/benches/03_complex.rs +++ b/benches/03_complex.rs @@ -4,7 +4,7 @@ extern crate clap; extern crate test; -use clap::{App, Arg, SubCommand}; +use clap::{App, Arg, SubCommand, AppSettings}; use test::Bencher; @@ -219,6 +219,10 @@ fn parse_complex2(b: &mut Bencher) { b.iter(|| create_app!().get_matches_from(vec!["myprog", "arg1", "-f", "arg2", "--long-option-2", "some", "-O", "slow", "--multvalsmo", "one", "two", "--minvals2", "3", "2", "1"])); } +#[bench] +fn parse_complex2_with_args_negate_scs(b: &mut Bencher) { + b.iter(|| create_app!().setting(AppSettings::ArgsNegateSubcommands).get_matches_from(vec!["myprog", "arg1", "-f", "arg2", "--long-option-2", "some", "-O", "slow", "--multvalsmo", "one", "two", "--minvals2", "3", "2", "1"])); +} #[bench] fn parse_sc_complex(b: &mut Bencher) { diff --git a/src/app/mod.rs b/src/app/mod.rs index 22e23fd2..361e92a5 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -486,7 +486,7 @@ impl<'a, 'b> App<'a, 'b> { /// [`AppSettings`]: ./enum.AppSettings.html pub fn global_setting(mut self, setting: AppSettings) -> Self { self.p.set(setting); - self.p.g_settings.push(setting); + self.p.g_settings.set(setting); self } @@ -510,7 +510,7 @@ impl<'a, 'b> App<'a, 'b> { pub fn global_settings(mut self, settings: &[AppSettings]) -> Self { for s in settings { self.p.set(*s); - self.p.g_settings.push(*s) + self.p.g_settings.set(*s) } self } @@ -1517,6 +1517,7 @@ impl<'n, 'e> AnyArg<'n, 'e> for App<'n, 'e> { 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 val_terminator(&self) -> Option<&'e str> {None} fn set(&mut self, _: ArgSettings) { unreachable!("App struct does not support AnyArg::set, this is a bug!") } diff --git a/src/app/parser.rs b/src/app/parser.rs index 47e85564..c7de9346 100644 --- a/src/app/parser.rs +++ b/src/app/parser.rs @@ -57,7 +57,7 @@ pub struct Parser<'a, 'b> help_short: Option, version_short: Option, settings: AppFlags, - pub g_settings: Vec, + pub g_settings: AppFlags, pub meta: AppMeta<'b>, pub id: usize, trailing_vals: bool, @@ -83,7 +83,7 @@ impl<'a, 'b> Default for Parser<'a, 'b> { groups: HashMap::new(), global_args: vec![], overrides: vec![], - g_settings: vec![], + g_settings: AppFlags::new(), settings: AppFlags::new(), meta: AppMeta::new(), trailing_vals: false, @@ -272,10 +272,8 @@ impl<'a, 'b> Parser<'a, 'b> sc.p.set(AppSettings::GlobalVersion); sc.p.meta.version = Some(self.meta.version.unwrap()); } - for s in &self.g_settings { - sc.p.set(*s); - sc.p.g_settings.push(*s); - } + sc.p.settings = sc.p.settings | self.g_settings; + sc.p.g_settings = sc.p.settings | self.g_settings; } sc.p.propogate_settings(); } @@ -553,14 +551,17 @@ impl<'a, 'b> Parser<'a, 'b> && p.v.num_vals.is_none()).map(|_| 1).sum::() <= 1, "Only one positional argument with .multiple(true) set is allowed per command"); - debug_assert!(self.positionals.values() - .rev() - .next() - .unwrap() - .is_set(ArgSettings::Required), + debug_assert!({ + let mut it = self.positionals.values().rev(); + // Either the final positional is required + it.next().unwrap().is_set(ArgSettings::Required) + // Or the second to last has a terminator set + || it.next().unwrap().v.terminator.is_some() + }, "When using a positional argument with .multiple(true) that is *not the last* \ positional argument, the last positional argument (i.e the one with the highest \ - index) *must* have .required(true) set."); + index) *must* have .required(true) set." + ); debug_assert!({ let num = self.positionals.len() - 1; @@ -611,7 +612,7 @@ impl<'a, 'b> Parser<'a, 'b> // Checks if the arg matches a subcommand name, or any of it's aliases (if defined) #[inline] fn possible_subcommand(&self, arg_os: &OsStr) -> bool { - debugln!("Parser::possible_subcommand;"); + debugln!("Parser::possible_subcommand: arg={:?}", arg_os); if self.is_set(AppSettings::ArgsNegateSubcommands) && self.valid_arg { return false; } @@ -691,9 +692,7 @@ impl<'a, 'b> Parser<'a, 'b> pb.b.help = Some("The subcommand whose help message to display"); pb.set(ArgSettings::Multiple); sc.positionals.insert(1, pb); - for s in self.g_settings.clone() { - sc.set(s); - } + sc.settings = sc.settings | self.g_settings; } else { sc.create_help_and_version(); } @@ -704,8 +703,8 @@ impl<'a, 'b> Parser<'a, 'b> } #[inline] - fn check_is_new_arg(&mut self, arg_os: &OsStr, needs_val_of: Option<&'a str>) -> bool { - debugln!("Parser::check_is_new_arg: Needs Val of={:?}", needs_val_of); + fn is_new_arg(&mut self, arg_os: &OsStr, needs_val_of: Option<&'a str>) -> bool { + debugln!("Parser::is_new_arg: arg={:?}, Needs Val of={:?}", arg_os, needs_val_of); let app_wide_settings = if self.is_set(AppSettings::AllowLeadingHyphen) { true } else if self.is_set(AppSettings::AllowNegativeNumbers) { @@ -721,19 +720,19 @@ impl<'a, 'b> Parser<'a, 'b> }; let arg_allows_tac = if let Some(name) = needs_val_of { if let Some(o) = find_by_name!(self, &name, opts, iter) { - !(o.is_set(ArgSettings::AllowLeadingHyphen) || app_wide_settings) - } else if let Some(p) = find_by_name!(self, &name, opts, iter) { - !(p.is_set(ArgSettings::AllowLeadingHyphen) || app_wide_settings) + (o.is_set(ArgSettings::AllowLeadingHyphen) || app_wide_settings) + } else if let Some(p) = find_by_name!(self, &name, positionals, values) { + (p.is_set(ArgSettings::AllowLeadingHyphen) || app_wide_settings) } else { - true + false } } else { - true + false }; - debugln!("Parser::check_is_new_arg: Does arg allow leading hyphen...{:?}", !arg_allows_tac); + debugln!("Parser::is_new_arg: Does arg allow leading hyphen...{:?}", arg_allows_tac); // Is this a new argument, or values from a previous option? - debug!("Parser::check_is_new_arg: Starts new arg..."); + debug!("Parser::is_new_arg: Starts new arg..."); let mut ret = if arg_os.starts_with(b"--") { sdebugln!("--"); if arg_os.len_() == 2 { @@ -749,9 +748,9 @@ impl<'a, 'b> Parser<'a, 'b> false }; - ret = ret && arg_allows_tac; + ret = ret && !arg_allows_tac; - debugln!("Parser::check_is_new_arg: Starts new arg...{:?}", ret); + debugln!("Parser::is_new_arg: Starts new arg...{:?}", ret); ret } @@ -781,94 +780,102 @@ impl<'a, 'b> Parser<'a, 'b> self.valid_neg_num = false; // Is this a new argument, or values from a previous option? - let starts_new_arg = self.check_is_new_arg(&arg_os, needs_val_of); + let starts_new_arg = self.is_new_arg(&arg_os, needs_val_of); // Has the user already passed '--'? Meaning only positional args follow if !self.trailing_vals { // Does the arg match a subcommand name, or any of it's aliases (if defined) - let pos_sc = self.possible_subcommand(&arg_os); - - // If the arg doesn't start with a `-` (except numbers, or AllowLeadingHyphen) and - // isn't a subcommand - if !starts_new_arg && !pos_sc { - // Check to see if parsing a value from an option - if let Some(name) = needs_val_of { - // get the OptBuilder so we can check the settings - if let Some(arg) = find_by_name!(self, &name, opts, iter) { - needs_val_of = try!(self.add_val_to_arg(&*arg, &arg_os, matcher)); - // get the next value from the iterator - continue; - } - } - } - if arg_os.starts_with(b"--") { - if arg_os.len_() == 2 { - // The user has passed '--' which means only positional args follow no - // matter what they start with - self.trailing_vals = true; - continue; - } - - needs_val_of = try!(self.parse_long_arg(matcher, &arg_os)); - if !(needs_val_of.is_none() && self.is_set(AppSettings::AllowLeadingHyphen)) { - continue; - } - } else if arg_os.starts_with(b"-") && arg_os.len_() != 1 { - // Try to parse short args like normal, if AllowLeadingHyphen or - // AllowNegativeNumbers is set, parse_short_arg will *not* throw - // an error, and instead return Ok(None) - needs_val_of = try!(self.parse_short_arg(matcher, &arg_os)); - // If it's None, we then check if one of those two AppSettings was set - if needs_val_of.is_none() { - if self.is_set(AppSettings::AllowNegativeNumbers) { - if !(arg_os.to_string_lossy().parse::().is_ok() || - arg_os.to_string_lossy().parse::().is_ok()) { - return Err(Error::unknown_argument(&*arg_os.to_string_lossy(), - "", - &*self.create_current_usage(matcher), - self.color())); - } - } else if !self.is_set(AppSettings::AllowLeadingHyphen) { - continue; - } - } else { - continue; - } - } - - if pos_sc { + if self.possible_subcommand(&arg_os) { if &*arg_os == "help" && self.is_set(AppSettings::NeedsSubcommandHelp) { try!(self.parse_help_subcommand(it)); } subcmd_name = Some(arg_os.to_str().expect(INVALID_UTF8).to_owned()); break; - } else if self.is_set(AppSettings::ArgsNegateSubcommands) { - (); - } else if let Some(cdate) = + } + + if !starts_new_arg { + if let Some(name) = needs_val_of { + // Check to see if parsing a value from a previous arg + if let Some(arg) = find_by_name!(self, &name, opts, iter) { + // get the OptBuilder so we can check the settings + needs_val_of = try!(self.add_val_to_arg(&*arg, &arg_os, matcher)); + // get the next value from the iterator + continue; + } + } + } else { + if arg_os.starts_with(b"--") { + if arg_os.len_() == 2 { + // The user has passed '--' which means only positional args follow no + // matter what they start with + self.trailing_vals = true; + continue; + } + + needs_val_of = try!(self.parse_long_arg(matcher, &arg_os)); + if !(needs_val_of.is_none() && self.is_set(AppSettings::AllowLeadingHyphen)) { + continue; + } + } else if arg_os.starts_with(b"-") && arg_os.len_() != 1 { + // Try to parse short args like normal, if AllowLeadingHyphen or + // AllowNegativeNumbers is set, parse_short_arg will *not* throw + // an error, and instead return Ok(None) + needs_val_of = try!(self.parse_short_arg(matcher, &arg_os)); + // If it's None, we then check if one of those two AppSettings was set + if needs_val_of.is_none() { + if self.is_set(AppSettings::AllowNegativeNumbers) { + if !(arg_os.to_string_lossy().parse::().is_ok() || + arg_os.to_string_lossy().parse::().is_ok()) { + return Err(Error::unknown_argument(&*arg_os.to_string_lossy(), + "", + &*self.create_current_usage(matcher), + self.color())); + } + } else if !self.is_set(AppSettings::AllowLeadingHyphen) { + continue; + } + } else { + continue; + } + } + } + + if !self.is_set(AppSettings::ArgsNegateSubcommands) { + if let Some(cdate) = suggestions::did_you_mean(&*arg_os.to_string_lossy(), self.subcommands .iter() .map(|s| &s.p.meta.name)) { - return Err(Error::invalid_subcommand(arg_os.to_string_lossy().into_owned(), - cdate, - self.meta - .bin_name - .as_ref() - .unwrap_or(&self.meta.name), - &*self.create_current_usage(matcher), - self.color())); + return Err(Error::invalid_subcommand(arg_os.to_string_lossy().into_owned(), + cdate, + self.meta + .bin_name + .as_ref() + .unwrap_or(&self.meta.name), + &*self.create_current_usage(matcher), + self.color())); + } } } + let low_index_mults = self.is_set(AppSettings::LowIndexMultiplePositional) && + !self.positionals.is_empty() && + pos_counter == (self.positionals.len() - 1); + debugln!("Parser::get_matches_with: Low index multiples...{:?}", low_index_mults); debugln!("Parser::get_matches_with: Positional counter...{}", pos_counter); - debug!("Parser::get_matches_with: Checking for low index multiples..."); - if self.is_set(AppSettings::LowIndexMultiplePositional) && - !self.positionals.is_empty() && - pos_counter == (self.positionals.len() - 1) { - sdebugln!("Found"); + if low_index_mults { if let Some(na) = it.peek() { let n = (*na).clone().into(); - if self.check_is_new_arg(&n, needs_val_of) || self.possible_subcommand(&n) || + needs_val_of = if let None = needs_val_of { + if let Some(p) = self.positionals.get(pos_counter) { + Some(p.b.name) + } else { + None + } + } else { + None + }; + if self.is_new_arg(&n, needs_val_of) || self.possible_subcommand(&n) || suggestions::did_you_mean(&n.to_string_lossy(), self.subcommands .iter() @@ -881,8 +888,6 @@ impl<'a, 'b> Parser<'a, 'b> debugln!("Parser::get_matches_with: Bumping the positional counter..."); pos_counter += 1; } - } else { - sdebugln!("None"); } if let Some(p) = self.positionals.get(pos_counter) { parse_positional!(self, p, arg_os, pos_counter, matcher); @@ -932,14 +937,15 @@ impl<'a, 'b> Parser<'a, 'b> let sc_name = &*self.subcommands .iter() .filter(|sc| sc.p.meta.aliases.is_some()) - .filter(|sc| sc.p - .meta - .aliases - .as_ref() - .expect(INTERNAL_ERROR_MSG) - .iter() - .any(|&(a, _)| &a == &&*pos_sc_name) - ) + .filter(|sc| { + sc.p + .meta + .aliases + .as_ref() + .expect(INTERNAL_ERROR_MSG) + .iter() + .any(|&(a, _)| &a == &&*pos_sc_name) + }) .map(|sc| sc.p.meta.name.clone()) .next() .expect(INTERNAL_ERROR_MSG); @@ -972,18 +978,19 @@ impl<'a, 'b> Parser<'a, 'b> let mut reqs_validated = false; if let Some(a) = needs_val_of { debugln!("Parser::validate: needs_val_of={:?}", a); - let o = find_by_name!(self, &a, opts, iter).expect(INTERNAL_ERROR_MSG); - try!(self.validate_required(matcher)); - reqs_validated = true; - let should_err = if let Some(v) = matcher.0.args.get(&*o.b.name) { - v.vals.is_empty() && !(o.v.min_vals.is_some() && o.v.min_vals.unwrap() == 0) - } else { - true - }; - if should_err { - return Err(Error::empty_value(o, - &*self.create_current_usage(matcher), - self.color())); + if let Some(o) = find_by_name!(self, &a, opts, iter) { + try!(self.validate_required(matcher)); + reqs_validated = true; + let should_err = if let Some(v) = matcher.0.args.get(&*o.b.name) { + v.vals.is_empty() && !(o.v.min_vals.is_some() && o.v.min_vals.unwrap() == 0) + } else { + true + }; + if should_err { + return Err(Error::empty_value(o, + &*self.create_current_usage(matcher), + self.color())); + } } } @@ -1523,6 +1530,11 @@ impl<'a, 'b> Parser<'a, 'b> { debugln!("Parser::add_single_val_to_arg;"); debugln!("Parser::add_single_val_to_arg: adding val...{:?}", v); + if let Some(t) = arg.val_terminator() { + if t == v { + return Ok(None); + } + } matcher.add_val_to(arg.name(), v); // Increment or create the group "args" @@ -2169,8 +2181,8 @@ impl<'a, 'b> Clone for Parser<'a, 'b> overrides: self.overrides.clone(), help_short: self.help_short, version_short: self.version_short, - settings: self.settings.clone(), - g_settings: self.g_settings.clone(), + settings: self.settings, + g_settings: self.g_settings, meta: self.meta.clone(), trailing_vals: self.trailing_vals, id: self.id, diff --git a/src/app/settings.rs b/src/app/settings.rs index 756c759d..7da20105 100644 --- a/src/app/settings.rs +++ b/src/app/settings.rs @@ -1,6 +1,7 @@ // Std use std::ascii::AsciiExt; use std::str::FromStr; +use std::ops::BitOr; bitflags! { flags Flags: u32 { @@ -40,13 +41,20 @@ bitflags! { } #[doc(hidden)] -#[derive(Debug)] +#[derive(Debug, Copy)] pub struct AppFlags(Flags); impl Clone for AppFlags { fn clone(&self) -> Self { AppFlags(self.0) } } +impl BitOr for AppFlags { + type Output = Self; + fn bitor(self, rhs: Self) -> Self { + AppFlags(self.0 | rhs.0) + } +} + impl Default for AppFlags { fn default() -> Self { AppFlags(NEEDS_LONG_VERSION | NEEDS_LONG_HELP | NEEDS_SC_HELP | UTF8_NONE | COLOR_AUTO) diff --git a/src/args/any_arg.rs b/src/args/any_arg.rs index 461ae8d1..efa664da 100644 --- a/src/args/any_arg.rs +++ b/src/args/any_arg.rs @@ -38,6 +38,7 @@ pub trait AnyArg<'n, 'e>: std_fmt::Display { fn default_vals_ifs(&self) -> Option, &'e str)>>; fn longest_filter(&self) -> bool; fn kind(&self) -> ArgKind; + fn val_terminator(&self) -> Option<&'e str>; } pub trait DispOrder { diff --git a/src/args/arg.rs b/src/args/arg.rs index 66916cb9..97754b60 100644 --- a/src/args/arg.rs +++ b/src/args/arg.rs @@ -84,6 +84,8 @@ pub struct Arg<'a, 'b> pub r_unless: Option>, #[doc(hidden)] pub r_ifs: Option>, + #[doc(hidden)] + pub val_terminator: Option<&'b str>, } impl<'a, 'b> Default for Arg<'a, 'b> { @@ -113,6 +115,7 @@ impl<'a, 'b> Default for Arg<'a, 'b> { disp_ord: 999, r_unless: None, r_ifs: None, + val_terminator: None, } } } @@ -1819,6 +1822,55 @@ impl<'a, 'b> Arg<'a, 'b> { } } + /// Specifies a value that *stops* parsing multiple values of a give argument. By default when + /// one sets [`multiple(true)`] on an argument, clap will continue parsing values for that + /// argument until it reaches another valid argument, or one of the other more specific settings + /// for multiple values is used (such as [`min_values`], [`max_values`] or + /// [`number_of_values`]). + /// + /// **NOTE:** This setting only applies to [options] and [positional arguments] + /// + /// **NOTE:** When the terminator is passed in on the command line, it is **not** stored as one + /// of the vaues + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, Arg}; + /// Arg::with_name("vals") + /// .takes_value(true) + /// .multiple(true) + /// .value_terminator(";") + /// # ; + /// ``` + /// The following example uses two arguments, a sequence of commands, and the location in which + /// to perform them + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let m = App::new("do") + /// .arg(Arg::with_name("cmds") + /// .multiple(true) + /// .allow_hyphen_values(true) + /// .value_terminator(";")) + /// .arg(Arg::with_name("location")) + /// .get_matches_from(vec!["do", "find", "-type", "f", "-name", "special", ";", "/home/clap"]); + /// let cmds: Vec<_> = m.values_of("cmds").unwrap().collect(); + /// assert_eq!(&cmds, &["find", "-type", "f", "-name", "special"]); + /// assert_eq!(m.value_of("location"), Some("/home/clap")); + /// ``` + /// [options]: ./struct.Arg.html#method.takes_value + /// [positional arguments]: ./struct.Arg.html#method.index + /// [`multiple(true)`]: ./struct.Arg.html#method.multiple + /// [`min_values`]: ./struct.Arg.html#method.min_values + /// [`number_of_values`]: ./struct.Arg.html#method.number_of_values + /// [`max_values`]: ./struct.Arg.html#method.max_values + pub fn value_terminator(mut self, term: &'b str) -> Self { + self.setb(ArgSettings::TakesValue); + self.val_terminator = Some(term); + self + } + /// Specifies that an argument can be matched to all child [`SubCommand`]s. /// /// **NOTE:** Global arguments *only* propagate down, **not** up (to parent commands) @@ -3153,6 +3205,7 @@ impl<'a, 'b, 'z> From<&'z Arg<'a, 'b>> for Arg<'a, 'b> { disp_ord: a.disp_ord, r_unless: a.r_unless.clone(), r_ifs: a.r_ifs.clone(), + val_terminator: a.val_terminator.clone(), } } } @@ -3184,6 +3237,7 @@ impl<'a, 'b> Clone for Arg<'a, 'b> { disp_ord: self.disp_ord, r_unless: self.r_unless.clone(), r_ifs: self.r_ifs.clone(), + val_terminator: self.val_terminator.clone(), } } } diff --git a/src/args/arg_builder/flag.rs b/src/args/arg_builder/flag.rs index eb4e5c0d..6371848a 100644 --- a/src/args/arg_builder/flag.rs +++ b/src/args/arg_builder/flag.rs @@ -71,6 +71,7 @@ impl<'n, 'e> AnyArg<'n, 'e> for FlagBuilder<'n, 'e> { fn long(&self) -> Option<&'e str> { self.s.long } fn val_delim(&self) -> Option { None } fn help(&self) -> Option<&'e str> { self.b.help } + fn val_terminator(&self) -> Option<&'e str> {None} fn default_val(&self) -> Option<&'n str> { None } fn default_vals_ifs(&self) -> Option, &'e str)>> {None} fn longest_filter(&self) -> bool { self.s.long.is_some() } diff --git a/src/args/arg_builder/option.rs b/src/args/arg_builder/option.rs index 96e20cbd..f091f49d 100644 --- a/src/args/arg_builder/option.rs +++ b/src/args/arg_builder/option.rs @@ -105,6 +105,7 @@ impl<'n, 'e> AnyArg<'n, 'e> for OptBuilder<'n, 'e> { fn has_switch(&self) -> bool { true } fn set(&mut self, s: ArgSettings) { self.b.settings.set(s) } fn max_vals(&self) -> Option { self.v.max_vals } + fn val_terminator(&self) -> Option<&'e str> { self.v.terminator } fn num_vals(&self) -> Option { self.v.num_vals } fn possible_vals(&self) -> Option<&[&'e str]> { self.v.possible_vals.as_ref().map(|o| &o[..]) } fn validator(&self) -> Option<&Rc StdResult<(), String>>> { diff --git a/src/args/arg_builder/positional.rs b/src/args/arg_builder/positional.rs index ab76f642..132e1da4 100644 --- a/src/args/arg_builder/positional.rs +++ b/src/args/arg_builder/positional.rs @@ -109,6 +109,7 @@ impl<'n, 'e> AnyArg<'n, 'e> for PosBuilder<'n, 'e> { fn set(&mut self, s: ArgSettings) { self.b.settings.set(s) } fn has_switch(&self) -> bool { false } fn max_vals(&self) -> Option { self.v.max_vals } + fn val_terminator(&self) -> Option<&'e str> { self.v.terminator } fn num_vals(&self) -> Option { self.v.num_vals } fn possible_vals(&self) -> Option<&[&'e str]> { self.v.possible_vals.as_ref().map(|o| &o[..]) } fn validator(&self) -> Option<&Rc StdResult<(), String>>> { diff --git a/src/args/arg_builder/valued.rs b/src/args/arg_builder/valued.rs index e93eb8dd..7b69ea4d 100644 --- a/src/args/arg_builder/valued.rs +++ b/src/args/arg_builder/valued.rs @@ -20,6 +20,7 @@ pub struct Valued<'a, 'b> pub val_delim: Option, pub default_val: Option<&'a str>, pub default_vals_ifs: Option, &'b str)>>, + pub terminator: Option<&'b str>, } impl<'n, 'e> Default for Valued<'n, 'e> { @@ -35,6 +36,7 @@ impl<'n, 'e> Default for Valued<'n, 'e> { val_delim: Some(','), default_val: None, default_vals_ifs: None, + terminator: None, } } } @@ -52,6 +54,7 @@ impl<'n, 'e, 'z> From<&'z Arg<'n, 'e>> for Valued<'n, 'e> { val_delim: a.val_delim, default_val: a.default_val, default_vals_ifs: a.default_vals_ifs.clone(), + terminator: a.val_terminator.clone(), }; if let Some(ref vec) = a.val_names { if vec.len() > 1 { diff --git a/tests/multiple_values.rs b/tests/multiple_values.rs index 34802ecd..155cacf7 100644 --- a/tests/multiple_values.rs +++ b/tests/multiple_values.rs @@ -1105,3 +1105,68 @@ fn low_index_positional_with_flag() { assert_eq!(m.value_of("target").unwrap(), "target"); assert!(m.is_present("flg")); } + +#[test] +fn multiple_value_terminator_option() { + let m = App::new("lip") + .arg(Arg::with_name("files") + .short("f") + .value_terminator(";") + .multiple(true)) + .arg(Arg::with_name("other")) + .get_matches_from_safe(vec![ + "lip", + "-f", "val1", "val2", ";", + "otherval" + ]); + + assert!(m.is_ok(), "{:?}", m.unwrap_err().kind); + let m = m.unwrap(); + + assert!(m.is_present("other")); + assert_eq!(m.occurrences_of("other"), 1); + assert!(m.is_present("files")); + assert_eq!(m.values_of("files").unwrap().collect::>(), ["val1", "val2"]); + assert_eq!(m.value_of("other"), Some("otherval")); +} + +#[test] +fn multiple_value_terminator_option_other_arg() { + let m = App::new("lip") + .arg(Arg::with_name("files") + .short("f") + .value_terminator(";") + .multiple(true)) + .arg(Arg::with_name("other")) + .arg(Arg::with_name("flag") + .short("-F")) + .get_matches_from_safe(vec![ + "lip", + "-f", "val1", "val2", + "-F", + "otherval" + ]); + + assert!(m.is_ok(), "{:?}", m.unwrap_err().kind); + let m = m.unwrap(); + + assert!(m.is_present("other")); + assert!(m.is_present("files")); + assert_eq!(m.values_of("files").unwrap().collect::>(), ["val1", "val2"]); + assert_eq!(m.value_of("other"), Some("otherval")); + assert!(m.is_present("flag")); +} + +#[test] +fn multiple_vals_with_hyphen() { + let m = App::new("do") + .arg(Arg::with_name("cmds") + .multiple(true) + .allow_hyphen_values(true) + .value_terminator(";")) + .arg(Arg::with_name("location")) + .get_matches_from(vec!["do", "find", "-type", "f", "-name", "special", ";", "/home/clap"]); + let cmds: Vec<_> = m.values_of("cmds").unwrap().collect(); + assert_eq!(&cmds, &["find", "-type", "f", "-name", "special"]); + assert_eq!(m.value_of("location"), Some("/home/clap")); +} \ No newline at end of file