From 92919f5f67f8e6163d5f349fcbc93ebda1f77d58 Mon Sep 17 00:00:00 2001 From: Kevin K Date: Fri, 3 Feb 2017 17:43:49 -0500 Subject: [PATCH] Call this PR "Raid" cause it's squashin bugs! (#843) * tests: adds tests for default values triggering conditional requirements * fix: fixes a bug where default values should have triggered a conditional requirement but didnt Closes #831 * tests: adds tests for missing conditional requirements in usage string of errors * fix: fixes a bug where conditionally required args werent appearing in errors * tests: adds tests for completion generators * tests: adds tests for completions with binaries names that have underscores * fix: fixes a bug where ZSH completions would panic if the binary name had an underscore in it Closes #581 * fix: fixes bash completions for commands that have an underscore in the name Closes #581 * chore: fix the category for crates.io * docs(Macros): adds a warning about changing values in Cargo.toml not triggering a rebuild automatically Closes #838 * fix(Completions): fixes a bug where global args weren't included in the generated completion scripts Closes #841 * fix: fixes a println->debugln typo * chore: increase version --- CHANGELOG.md | 18 + Cargo.toml | 4 +- README.md | 13 +- src/app/macros.rs | 2 +- src/app/parser.rs | 113 +++--- src/completions/bash.rs | 12 +- src/completions/mod.rs | 4 +- src/completions/zsh.rs | 12 +- src/macros.rs | 7 + tests/completions.rs | 744 ++++++++++++++++++++++++++++++++++++++-- tests/default_vals.rs | 54 ++- tests/require.rs | 205 +++++------ 12 files changed, 971 insertions(+), 217 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e727bee9c8c..c2fe69bf269 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,21 @@ + +### v2.20.3 (2017-02-03) + + +#### Documentation + +* **Macros:** adds a warning about changing values in Cargo.toml not triggering a rebuild automatically ([112aea3e](https://github.com/kbknapp/clap-rs/commit/112aea3e42ae9e0c0a2d33ebad89496dbdd95e5d), closes [#838](https://github.com/kbknapp/clap-rs/issues/838)) + +#### Bug Fixes + +* fixes a println->debugln typo ([279aa62e](https://github.com/kbknapp/clap-rs/commit/279aa62eaf08f56ce090ba16b937bc763cbb45be)) +* fixes bash completions for commands that have an underscore in the name ([7f5cfa72](https://github.com/kbknapp/clap-rs/commit/7f5cfa724f0ac4e098f5fe466c903febddb2d994), closes [#581](https://github.com/kbknapp/clap-rs/issues/581)) +* fixes a bug where ZSH completions would panic if the binary name had an underscore in it ([891a2a00](https://github.com/kbknapp/clap-rs/commit/891a2a006f775e92c556dda48bb32fac9807c4fb), closes [#581](https://github.com/kbknapp/clap-rs/issues/581)) +* allow final word to be wrapped in wrap_help ([564c5f0f](https://github.com/kbknapp/clap-rs/commit/564c5f0f1730f4a2c1cdd128664f1a981c31dcd4), closes [#828](https://github.com/kbknapp/clap-rs/issues/828)) +* fixes a bug where global args weren't included in the generated completion scripts ([9a1e006e](https://github.com/kbknapp/clap-rs/commit/9a1e006eb75ad5a6057ebd119aa90f7e06c0ace8), closes [#841](https://github.com/kbknapp/clap-rs/issues/841)) + + + ### v2.20.2 (2017-02-03) diff --git a/Cargo.toml b/Cargo.toml index be77e3450dc..2137eeb75ac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "clap" -version = "2.20.2" +version = "2.20.3" authors = ["Kevin K. "] exclude = ["examples/*", "clap-test/*", "tests/*", "benches/*", "*.png", "clap-perf/*", "*.dot"] repository = "https://github.com/kbknapp/clap-rs.git" @@ -10,7 +10,7 @@ homepage = "https://clap.rs/" readme = "README.md" license = "MIT" keywords = ["argument", "command", "arg", "parser", "parse"] -categories = ["command-line-interface"] +categories = ["command-line interface"] description = """ A simple to use, efficient, and full featured Command Line Argument Parser """ diff --git a/README.md b/README.md index 0d309daf9fe..531056bc4aa 100644 --- a/README.md +++ b/README.md @@ -47,12 +47,17 @@ Created by [gh-md-toc](https://github.com/ekalinin/github-markdown-toc) Here's the highlights for v2.20.2 -* fixes a critical bug where subcommand settings were being propogated too far -* adds ArgGroup::multiple to the supported YAML fields for building ArgGroups from YAML +* fixes a println->debugln typo +* **Completions**: fixes bash completions for commands that have an underscore in the name +* **Completions**: fixes a bug where ZSH completions would panic if the binary name had an underscore in it +* allow final word to be wrapped in wrap_help +* **Completions**: fixes a bug where global args weren't included in the generated completion scripts +* **Macros Documentation:** adds a warning about changing values in Cargo.toml not triggering a rebuild automatically +Here's the highlights from v2.0.0 to v2.20.2 -Here's the highlights from v2.0.0 to v2.20.1 - +* Fixes a critical bug where subcommand settings were being propogated too far +* Adds ArgGroup::multiple to the supported YAML fields for building ArgGroups from YAML * Fixes a bug where the final word wasn't wrapped in help messages * Fixes finding required arguments in group arguments * **ArgsNegateSubcommands:** disables args being allowed between subcommands diff --git a/src/app/macros.rs b/src/app/macros.rs index 61d861f3c59..f589216f396 100644 --- a/src/app/macros.rs +++ b/src/app/macros.rs @@ -132,7 +132,7 @@ macro_rules! validate_multiples { if $m.contains(&$a.b.name) && !$a.b.settings.is_set(ArgSettings::Multiple) { // Not the first time, and we don't allow multiples return Err(Error::unexpected_multiple_usage($a, - &*$_self.create_current_usage($m), + &*$_self.create_current_usage($m, None), $_self.color())) } }; diff --git a/src/app/parser.rs b/src/app/parser.rs index 1304b19b149..f2fbbe49cb8 100644 --- a/src/app/parser.rs +++ b/src/app/parser.rs @@ -117,6 +117,8 @@ impl<'a, 'b> Parser<'a, 'b> self.propogate_help_version(); self.build_bin_names(); + self.propogate_globals(); + self.propogate_settings(); ComplGen::new(self).generate(for_shell, buf) } @@ -304,23 +306,32 @@ impl<'a, 'b> Parser<'a, 'b> pub fn get_required_from(&self, reqs: &[&'a str], - matcher: Option<&ArgMatcher<'a>>) + matcher: Option<&ArgMatcher<'a>>, + extra: Option<&str>) -> VecDeque { - debugln!("Parser::get_required_from; reqs={:?}", reqs); + debugln!("Parser::get_required_from: reqs={:?}, extra={:?}", reqs, extra); let mut c_flags: Vec<&str> = vec![]; let mut c_pos: Vec<&str> = vec![]; let mut c_opt: Vec<&str> = vec![]; let mut grps: Vec<&str> = vec![]; - for name in reqs { - if self.flags.iter().any(|f| &f.b.name == name) { - c_flags.push(name); - } else if self.opts.iter().any(|o| &o.b.name == name) { - c_opt.push(name); - } else if self.groups.contains_key(name) { - grps.push(name); - } else { - c_pos.push(name); + macro_rules! categorize { + ($_self:ident, $name:ident, $c_flags:ident, $c_pos:ident, $c_opt:ident, $grps:ident) => { + if $_self.flags.iter().any(|f| &f.b.name == $name) { + $c_flags.push($name); + } else if self.opts.iter().any(|o| &o.b.name == $name) { + $c_opt.push($name); + } else if self.groups.contains_key($name) { + $grps.push($name); + } else { + $c_pos.push($name); + } } + }; + for name in reqs { + categorize!(self, name, c_flags, c_pos, c_opt, grps); + } + if let Some(ref name) = extra { + categorize!(self, name, c_flags, c_pos, c_opt, grps); } macro_rules! fill_vecs { ($_self:ident { @@ -843,7 +854,7 @@ impl<'a, 'b> Parser<'a, 'b> arg_os.to_string_lossy().parse::().is_ok()) { return Err(Error::unknown_argument(&*arg_os.to_string_lossy(), "", - &*self.create_current_usage(matcher), + &*self.create_current_usage(matcher, None), self.color())); } } else if !self.is_set(AppSettings::AllowLeadingHyphen) { @@ -867,7 +878,7 @@ impl<'a, 'b> Parser<'a, 'b> .bin_name .as_ref() .unwrap_or(&self.meta.name), - &*self.create_current_usage(matcher), + &*self.create_current_usage(matcher, None), self.color())); } } @@ -916,7 +927,7 @@ impl<'a, 'b> Parser<'a, 'b> Some(s) => s.to_string(), None => { if !self.settings.is_set(AppSettings::StrictUtf8) { - return Err(Error::invalid_utf8(&*self.create_current_usage(matcher), + return Err(Error::invalid_utf8(&*self.create_current_usage(matcher, None), self.color())); } arg_os.to_string_lossy().into_owned() @@ -928,7 +939,7 @@ impl<'a, 'b> Parser<'a, 'b> while let Some(v) = it.next() { let a = v.into(); if a.to_str().is_none() && !self.settings.is_set(AppSettings::StrictUtf8) { - return Err(Error::invalid_utf8(&*self.create_current_usage(matcher), + return Err(Error::invalid_utf8(&*self.create_current_usage(matcher, None), self.color())); } sc_m.add_val_to("", &a); @@ -942,7 +953,7 @@ impl<'a, 'b> Parser<'a, 'b> self.is_set(AppSettings::AllowNegativeNumbers)) { return Err(Error::unknown_argument(&*arg_os.to_string_lossy(), "", - &*self.create_current_usage(matcher), + &*self.create_current_usage(matcher, None), self.color())); } } @@ -972,7 +983,7 @@ impl<'a, 'b> Parser<'a, 'b> } else if self.is_set(AppSettings::SubcommandRequired) { let bn = self.meta.bin_name.as_ref().unwrap_or(&self.meta.name); return Err(Error::missing_subcommand(bn, - &self.create_current_usage(matcher), + &self.create_current_usage(matcher, None), self.color())); } else if self.is_set(AppSettings::SubcommandRequiredElseHelp) { debugln!("parser::get_matches_with: SubcommandRequiredElseHelp=true"); @@ -995,6 +1006,7 @@ impl<'a, 'b> Parser<'a, 'b> -> ClapResult<()> { debugln!("Parser::validate;"); let mut reqs_validated = false; + try!(self.add_defaults(matcher)); if let Some(a) = needs_val_of { debugln!("Parser::validate: needs_val_of={:?}", a); if let Some(o) = find_by_name!(self, &a, opts, iter) { @@ -1007,7 +1019,7 @@ impl<'a, 'b> Parser<'a, 'b> }; if should_err { return Err(Error::empty_value(o, - &*self.create_current_usage(matcher), + &*self.create_current_usage(matcher, None), self.color())); } } @@ -1018,7 +1030,6 @@ impl<'a, 'b> Parser<'a, 'b> !reqs_validated { try!(self.validate_required(matcher)); } - try!(self.add_defaults(matcher)); try!(self.validate_matched_args(matcher)); matcher.usage(self.create_usage(&[])); @@ -1086,7 +1097,7 @@ impl<'a, 'b> Parser<'a, 'b> for k in matcher.arg_names() { hs.push(k); } - let reqs = self.get_required_from(&hs, Some(matcher)); + let reqs = self.get_required_from(&hs, Some(matcher), None); for s in &reqs { write!(&mut mid_string, " {}", s).expect(INTERNAL_ERROR_MSG); @@ -1118,8 +1129,8 @@ impl<'a, 'b> Parser<'a, 'b> "" }, &*sc.p.meta.name)); - println!("Parser::parse_subcommand: About to parse sc={}", sc.p.meta.name); - println!("Parser::parse_subcommand: sc settings={:#?}", sc.p.settings); + debugln!("Parser::parse_subcommand: About to parse sc={}", sc.p.meta.name); + debugln!("Parser::parse_subcommand: sc settings={:#?}", sc.p.settings); try!(sc.p.get_matches_with(&mut sc_matcher, it)); matcher.subcommand(SubCommand { name: sc.p.meta.name.clone(), @@ -1264,8 +1275,8 @@ impl<'a, 'b> Parser<'a, 'b> // Retrieves the names of all args the user has supplied thus far, except required ones // because those will be listed in self.required - pub fn create_current_usage(&self, matcher: &'b ArgMatcher<'a>) -> String { - self.create_usage(&*matcher.arg_names() + pub fn create_current_usage(&self, matcher: &'b ArgMatcher<'a>, extra: Option<&str>) -> String { + let mut args: Vec<_> = matcher.arg_names() .iter() .filter(|n| { if let Some(o) = find_by_name!(self, *n, opts, iter) { @@ -1277,7 +1288,11 @@ impl<'a, 'b> Parser<'a, 'b> } }) .map(|&n| n) - .collect::>()) + .collect(); + if let Some(r) = extra { + args.push(r); + } + self.create_usage(&*args) } fn check_for_help_and_version_str(&self, arg: &OsStr) -> ClapResult<()> { @@ -1462,7 +1477,7 @@ impl<'a, 'b> Parser<'a, 'b> arg.push(c); return Err(Error::unknown_argument(&*arg, "", - &*self.create_current_usage(matcher), + &*self.create_current_usage(matcher, None), self.color())); } } @@ -1485,7 +1500,7 @@ impl<'a, 'b> Parser<'a, 'b> if !opt.is_set(ArgSettings::EmptyValues) && v.len_() == 0 { sdebugln!("Found Empty - Error"); return Err(Error::empty_value(opt, - &*self.create_current_usage(matcher), + &*self.create_current_usage(matcher, None), self.color())); } sdebugln!("Found - {:?}, len: {}", v, v.len_()); @@ -1579,7 +1594,7 @@ impl<'a, 'b> Parser<'a, 'b> { debugln!("Parser::validate_value: val={:?}", val); if self.is_set(AppSettings::StrictUtf8) && val.to_str().is_none() { - return Err(Error::invalid_utf8(&*self.create_current_usage(matcher), self.color())); + return Err(Error::invalid_utf8(&*self.create_current_usage(matcher, None), self.color())); } if let Some(p_vals) = arg.possible_vals() { let val_str = val.to_string_lossy(); @@ -1587,13 +1602,13 @@ impl<'a, 'b> Parser<'a, 'b> return Err(Error::invalid_value(val_str, p_vals, arg, - &*self.create_current_usage(matcher), + &*self.create_current_usage(matcher, None), self.color())); } } if !arg.is_set(ArgSettings::EmptyValues) && val.is_empty_() && matcher.contains(&*arg.name()) { - return Err(Error::empty_value(arg, &*self.create_current_usage(matcher), self.color())); + return Err(Error::empty_value(arg, &*self.create_current_usage(matcher, None), self.color())); } if let Some(vtor) = arg.validator() { if let Err(e) = vtor(val.to_string_lossy().into_owned()) { @@ -1641,7 +1656,7 @@ impl<'a, 'b> Parser<'a, 'b> ); debugln!("build_err!: '{:?}' conflicts with '{}'", c_with, $name); $matcher.remove($name); - let usg = $me.create_current_usage($matcher); + let usg = $me.create_current_usage($matcher, None); if let Some(f) = find_by_name!($me, $name, flags, iter) { debugln!("build_err!: It was a flag..."); Error::argument_conflict(f, c_with, &*usg, self.color()) @@ -1695,7 +1710,7 @@ impl<'a, 'b> Parser<'a, 'b> } else if let Some(grp) = self.groups.get(name) { if let Some(ref g_reqs) = grp.requires { if g_reqs.iter().any(|&n| !matcher.contains(n)) { - return self.missing_required_error(matcher); + return self.missing_required_error(matcher, None); } } } @@ -1735,7 +1750,7 @@ impl<'a, 'b> Parser<'a, 'b> } else { "ere" }, - &*self.create_current_usage(matcher), + &*self.create_current_usage(matcher, None), self.color())); } } @@ -1752,7 +1767,7 @@ impl<'a, 'b> Parser<'a, 'b> .to_str() .expect(INVALID_UTF8), a, - &*self.create_current_usage(matcher), + &*self.create_current_usage(matcher, None), self.color())); } } @@ -1763,13 +1778,13 @@ impl<'a, 'b> Parser<'a, 'b> return Err(Error::too_few_values(a, num, ma.vals.len(), - &*self.create_current_usage(matcher), + &*self.create_current_usage(matcher, None), self.color())); } } // Issue 665 (https://github.com/kbknapp/clap-rs/issues/665) if a.takes_value() && !a.is_set(ArgSettings::EmptyValues) && ma.vals.is_empty() { - return Err(Error::empty_value(a, &*self.create_current_usage(matcher), self.color())); + return Err(Error::empty_value(a, &*self.create_current_usage(matcher, None), self.color())); } Ok(()) } @@ -1787,7 +1802,7 @@ impl<'a, 'b> Parser<'a, 'b> if ma.vals .values() .any(|v| v == val.expect(INTERNAL_ERROR_MSG) && !matcher.contains(name)) { - return self.missing_required_error(matcher); + return self.missing_required_error(matcher, None); } } } @@ -1795,21 +1810,27 @@ impl<'a, 'b> Parser<'a, 'b> } #[inline] - fn missing_required_error(&self, matcher: &ArgMatcher) -> ClapResult<()> { + fn missing_required_error(&self, matcher: &ArgMatcher, extra: Option<&str>) -> ClapResult<()> { + debugln!("Parser::missing_required_error: extra={:?}", extra); let c = Colorizer { use_stderr: true, when: self.color(), }; let mut reqs = self.required.iter().map(|&r| &*r).collect::>(); + if let Some(r) = extra { + reqs.push(r); + } reqs.retain(|n| !matcher.contains(n)); reqs.dedup(); - Err(Error::missing_required_argument(&*self.get_required_from(&self.required[..], - Some(matcher)) + debugln!("Parser::missing_required_error: reqs={:#?}", reqs); + Err(Error::missing_required_argument(&*self.get_required_from(&reqs[..], + Some(matcher), + extra) .iter() .fold(String::new(), |acc, s| { acc + &format!("\n {}", c.error(s))[..] }), - &*self.create_current_usage(matcher), + &*self.create_current_usage(matcher, extra), self.color())) } @@ -1843,7 +1864,7 @@ impl<'a, 'b> Parser<'a, 'b> continue 'outer; } } - return self.missing_required_error(matcher); + return self.missing_required_error(matcher, None); } // Validate the conditionally required args @@ -1851,7 +1872,7 @@ impl<'a, 'b> Parser<'a, 'b> if let Some(ma) = matcher.get(a) { for val in ma.vals.values() { if v == val && matcher.get(r).is_none() { - return self.missing_required_error(matcher); + return self.missing_required_error(matcher, Some(r)); } } } @@ -1923,7 +1944,7 @@ impl<'a, 'b> Parser<'a, 'b> let used_arg = format!("--{}", arg); Err(Error::unknown_argument(&*used_arg, &*suffix.0, - &*self.create_current_usage(matcher), + &*self.create_current_usage(matcher, None), self.color())) } @@ -1957,7 +1978,7 @@ impl<'a, 'b> Parser<'a, 'b> .unwrap_or(&self.meta.name))); let mut reqs: Vec<&str> = self.required().map(|r| &**r).collect(); reqs.dedup(); - let req_string = self.get_required_from(&reqs, None) + let req_string = self.get_required_from(&reqs, None, None) .iter() .fold(String::new(), |a, s| a + &format!(" {}", s)[..]); @@ -2012,7 +2033,7 @@ impl<'a, 'b> Parser<'a, 'b> 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) + let r_string = self.get_required_from(&hs, None, None) .iter() .fold(String::new(), |acc, s| acc + &format!(" {}", s)[..]); diff --git a/src/completions/bash.rs b/src/completions/bash.rs index 47a69e14b6d..ab94865b8e5 100644 --- a/src/completions/bash.rs +++ b/src/completions/bash.rs @@ -77,10 +77,10 @@ complete -F _{name} -o bashdefault -o default {name} for sc in &scs { subcmds = format!("{} {name}) - cmd+=\"_{name}\" + cmd+=\"__{name}\" ;;", subcmds, - name = sc.replace("-", "_")); + name = sc.replace("-", "__")); } subcmds @@ -111,9 +111,9 @@ complete -F _{name} -o bashdefault -o default {name} return 0 ;;", subcmd_dets, - subcmd = sc.replace("-", "_"), + subcmd = sc.replace("-", "__"), sc_opts = self.all_options_for_path(&*sc), - level = sc.split("_").map(|_| 1).fold(0, |acc, n| acc + n), + level = sc.split("__").map(|_| 1).fold(0, |acc, n| acc + n), opts_details = self.option_details_for_path(&*sc)); } @@ -123,7 +123,7 @@ complete -F _{name} -o bashdefault -o default {name} fn option_details_for_path(&self, path: &str) -> String { debugln!("BashGen::option_details_for_path: path={}", path); let mut p = self.p; - for sc in path.split('_').skip(1) { + for sc in path.split("__").skip(1) { debugln!("BashGen::option_details_for_path:iter: sc={}", sc); p = &p.subcommands .iter() @@ -212,7 +212,7 @@ complete -F _{name} -o bashdefault -o default {name} fn all_options_for_path(&self, path: &str) -> String { debugln!("BashGen::all_options_for_path: path={}", path); let mut p = self.p; - for sc in path.split('_').skip(1) { + for sc in path.split("__").skip(1) { debugln!("BashGen::all_options_for_path:iter: sc={}", sc); p = &p.subcommands .iter() diff --git a/src/completions/mod.rs b/src/completions/mod.rs index 8a7c21f1b2f..6d067882bf0 100644 --- a/src/completions/mod.rs +++ b/src/completions/mod.rs @@ -125,7 +125,7 @@ pub fn get_all_subcommand_paths(p: &Parser, first: bool) -> Vec { if !p.has_subcommands() { if !first { let name = &*p.meta.name; - let path = p.meta.bin_name.as_ref().unwrap().clone().replace(" ", "_"); + let path = p.meta.bin_name.as_ref().unwrap().clone().replace(" ", "__"); let mut ret = vec![path.clone()]; if let Some(ref aliases) = p.meta.aliases { for &(n, _) in aliases { @@ -138,7 +138,7 @@ pub fn get_all_subcommand_paths(p: &Parser, first: bool) -> Vec { } for sc in &p.subcommands { let name = &*sc.p.meta.name; - let path = sc.p.meta.bin_name.as_ref().unwrap().clone().replace(" ", "_"); + let path = sc.p.meta.bin_name.as_ref().unwrap().clone().replace(" ", "__"); subcmds.push(path.clone()); if let Some(ref aliases) = sc.p.meta.aliases { for &(n, _) in aliases { diff --git a/src/completions/zsh.rs b/src/completions/zsh.rs index a3bb2fb3bd5..fa84342f8c2 100644 --- a/src/completions/zsh.rs +++ b/src/completions/zsh.rs @@ -29,10 +29,10 @@ impl<'a, 'b> ZshGen<'a, 'b> { #compdef {name} _{name}() {{ - typeset -A opt_args - local ret=1 + typeset -A opt_args + local ret=1 - local context curcontext=\"$curcontext\" state line + local context curcontext=\"$curcontext\" state line {initial_args} {subcommands} }} @@ -86,7 +86,7 @@ _{bin_name_underscore}_commands() {{ ) _describe -t commands '{bin_name} commands' commands \"$@\" }}", - bin_name_underscore = p.meta.bin_name.as_ref().unwrap().replace(" ", "_"), + bin_name_underscore = p.meta.bin_name.as_ref().unwrap().replace(" ", "__"), bin_name = p.meta.bin_name.as_ref().unwrap(), subcommands_and_args = subcommands_and_args_of(p))]; @@ -104,7 +104,7 @@ _{bin_name_underscore}_commands() {{ ) _describe -t commands '{bin_name} commands' commands \"$@\" }}", - bin_name_underscore = bin_name.replace(" ", "_"), + bin_name_underscore = bin_name.replace(" ", "__"), bin_name = bin_name, subcommands_and_args = subcommands_and_args_of(parser_of(p, bin_name)))); } @@ -265,7 +265,7 @@ fn get_args_of(p: &Parser) -> String { let flags = write_flags_of(p); let sc_or_a = if p.has_subcommands() || p.has_positionals() { format!("\"1:: :_{name}_commands\" \\", - name = p.meta.bin_name.as_ref().unwrap().replace(" ", "_")) + name = p.meta.bin_name.as_ref().unwrap().replace(" ", "__")) } else { String::new() }; diff --git a/src/macros.rs b/src/macros.rs index 86a2c8eb063..ee43113f737 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -498,6 +498,13 @@ macro_rules! crate_name { /// Provided separator is for the [`crate_authors!`](macro.crate_authors.html) macro, /// refer to the documentation therefor. /// +/// **NOTE:** Changing the values in your `Cargo.toml` does not trigger a re-build automatically, +/// and therefore won't change the generated output until you recompile. +/// +/// **Pro Tip:** In some cases you can "trick" the compiler into triggering a rebuild when your +/// `Cargo.toml` is changed by including this in your `src/main.rs` file +/// `include_str!("../Cargo.toml");` +/// /// # Examples /// /// ```no_run diff --git a/tests/completions.rs b/tests/completions.rs index 6aa99f5d7ab..8b4f843ffd0 100644 --- a/tests/completions.rs +++ b/tests/completions.rs @@ -1,25 +1,719 @@ -extern crate clap; - -use clap::{App, Arg, SubCommand, Shell}; - -#[test] -fn test_generation() { - let mut app = App::new("myapp") - .about("Tests completions") - .arg(Arg::with_name("file") - .help("some input file")) - .subcommand(SubCommand::with_name("test") - .about("tests things") - .arg(Arg::with_name("case") - .long("case") - .takes_value(true) - .help("the case to test"))); - let mut buf = vec![]; - app.gen_completions_to("myapp", Shell::Bash, &mut buf); - let string = String::from_utf8(buf).unwrap(); - let first_line = string.lines().nth(0).unwrap(); - let last_line = string.lines().rev().nth(0).unwrap(); - - assert_eq!(first_line, "_myapp() {"); - assert_eq!(last_line, "complete -F _myapp -o bashdefault -o default myapp"); -} +extern crate regex; +extern crate clap; + +use clap::{App, Arg, SubCommand, Shell}; +use regex::Regex; + +static BASH: &'static str = r#"_myapp() { + local i cur prev opts cmds + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + cmd="" + opts="" + + for i in ${COMP_WORDS[@]} + do + case "${i}" in + myapp) + cmd="myapp" + ;; + + help) + cmd+="__help" + ;; + test) + cmd+="__test" + ;; + *) + ;; + esac + done + + case "${cmd}" in + myapp) + opts=" -h -V --help --version test help" + if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then + COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) + return 0 + fi + case "${prev}" in + + *) + COMPREPLY=() + ;; + esac + COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) + return 0 + ;; + + myapp__help) + opts=" -h -V --help --version " + if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then + COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) + return 0 + fi + case "${prev}" in + + *) + COMPREPLY=() + ;; + esac + COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) + return 0 + ;; + myapp__test) + opts=" -h -V --case --help --version " + if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then + COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) + return 0 + fi + case "${prev}" in + + --case) + COMPREPLY=("") + return 0 + ;; + *) + COMPREPLY=() + ;; + esac + COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) + return 0 + ;; + esac +} + +complete -F _myapp -o bashdefault -o default myapp +"#; + +static ZSH: &'static str = r#"#compdef myapp + +_myapp() { + typeset -A opt_args + local ret=1 + + local context curcontext="$curcontext" state line + _arguments -s -S -C \ +"-h[Prints help information]" \ +"--help[Prints help information]" \ +"-V[Prints version information]" \ +"--version[Prints version information]" \ +"1:: :_myapp_commands" \ +"*:: :->myapp" \ +&& ret=0 + case $state in + (myapp) + curcontext="${curcontext%:*:*}:myapp-command-$words[1]:" + case $line[1] in + (test) +_arguments -s -S -C \ +"--case+[the case to test]" \ +"-h[Prints help information]" \ +"--help[Prints help information]" \ +"-V[Prints version information]" \ +"--version[Prints version information]" \ +&& ret=0 +;; +(help) +_arguments -s -S -C \ +"-h[Prints help information]" \ +"--help[Prints help information]" \ +"-V[Prints version information]" \ +"--version[Prints version information]" \ +&& ret=0 +;; + esac + ;; +esac +} + +(( $+functions[_myapp_commands] )) || +_myapp_commands() { + local commands; commands=( + "test:tests things" \ +"help:Prints this message or the help of the given subcommand(s)" \ +"FILE:some input file" \ + ) + _describe -t commands 'myapp commands' commands "$@" +} +(( $+functions[_myapp__help_commands] )) || +_myapp__help_commands() { + local commands; commands=( + + ) + _describe -t commands 'myapp help commands' commands "$@" +} +(( $+functions[_myapp__test_commands] )) || +_myapp__test_commands() { + local commands; commands=( + + ) + _describe -t commands 'myapp test commands' commands "$@" +} + +_myapp "$@""#; + +static FISH: &'static str = r#"function __fish_using_command + set cmd (commandline -opc) + if [ (count $cmd) -eq (count $argv) ] + for i in (seq (count $argv)) + if [ $cmd[$i] != $argv[$i] ] + return 1 + end + end + return 0 + end + return 1 +end + +complete -c myapp -n "__fish_using_command myapp" -s h -l help -d "Prints help information" +complete -c myapp -n "__fish_using_command myapp" -s V -l version -d "Prints version information" +complete -c myapp -n "__fish_using_command myapp" -f -a "test" +complete -c myapp -n "__fish_using_command myapp" -f -a "help" +complete -c myapp -n "__fish_using_command myapp test" -l case -d "the case to test" +complete -c myapp -n "__fish_using_command myapp test" -s h -l help -d "Prints help information" +complete -c myapp -n "__fish_using_command myapp test" -s V -l version -d "Prints version information" +complete -c myapp -n "__fish_using_command myapp help" -s h -l help -d "Prints help information" +complete -c myapp -n "__fish_using_command myapp help" -s V -l version -d "Prints version information" +"#; + +#[cfg(not(target_os="windows"))] +static POWERSHELL: &'static str = r#" +@('myapp', './myapp') | %{ + Register-ArgumentCompleter -Native -CommandName $_ -ScriptBlock { + param($wordToComplete, $commandAst, $cursorPosition) + + $command = '_myapp' + $commandAst.CommandElements | + Select-Object -Skip 1 | + %{ + switch ($_.ToString()) { + + 'test' { + $command += '_test' + break + } + + 'help' { + $command += '_help' + break + } + + } + } + + $completions = @() + + switch ($command) { + + '_myapp' { + $completions = @('test', 'help', '-h', '-V', '--help', '--version') + } + + '_myapp_test' { + $completions = @('-h', '-V', '--case', '--help', '--version') + } + + '_myapp_help' { + $completions = @('-h', '-V', '--help', '--version') + } + + } + + $completions | + ?{ $_ -like "$wordToComplete*" } | + Sort-Object | + %{ New-Object System.Management.Automation.CompletionResult $_, $_, 'ParameterValue', $_ } + } +} +"#; + +#[cfg(target_os="windows")] +static POWERSHELL: &'static str = r#" +@('myapp', './myapp', 'myapp.exe', '.\myapp', '.\myapp.exe', './myapp.exe') | %{ + Register-ArgumentCompleter -Native -CommandName $_ -ScriptBlock { + param($wordToComplete, $commandAst, $cursorPosition) + $command = '_myapp' + $commandAst.CommandElements | + Select-Object -Skip 1 | + %{ + switch ($_.ToString()) { + 'test' { + $command += '_test' + break + } + 'help' { + $command += '_help' + break + } + } + } + $completions = @() + switch ($command) { + '_myapp' { + $completions = @('test', 'help', '-h', '-V', '--help', '--version') + } + '_myapp_test' { + $completions = @('-h', '-V', '--case', '--help', '--version') + } + '_myapp_help' { + $completions = @('-h', '-V', '--help', '--version') + } + } + $completions | + ?{ $_ -like "$wordToComplete*" } | + Sort-Object | + %{ New-Object System.Management.Automation.CompletionResult $_, $_, 'ParameterValue', $_ } + } +} +"#; + +#[cfg(not(target_os="windows"))] +static POWERSHELL_WUS: &'static str = r#" +@('my_app', './my_app') | %{ + Register-ArgumentCompleter -Native -CommandName $_ -ScriptBlock { + param($wordToComplete, $commandAst, $cursorPosition) + + $command = '_my_app' + $commandAst.CommandElements | + Select-Object -Skip 1 | + %{ + switch ($_.ToString()) { + + 'test' { + $command += '_test' + break + } + + 'some_cmd' { + $command += '_some_cmd' + break + } + + 'help' { + $command += '_help' + break + } + + } + } + + $completions = @() + + switch ($command) { + + '_my_app' { + $completions = @('test', 'some_cmd', 'help', '-h', '-V', '--help', '--version') + } + + '_my_app_test' { + $completions = @('-h', '-V', '--case', '--help', '--version') + } + + '_my_app_some_cmd' { + $completions = @('-h', '-V', '--config', '--help', '--version') + } + + '_my_app_help' { + $completions = @('-h', '-V', '--help', '--version') + } + + } + + $completions | + ?{ $_ -like "$wordToComplete*" } | + Sort-Object | + %{ New-Object System.Management.Automation.CompletionResult $_, $_, 'ParameterValue', $_ } + } +} +"#; + +#[cfg(target_os="windows")] +static POWERSHELL_WUS: &'static str = r#" +@('my_app', './my_app', 'my_app.exe', '.\my_app', '.\my_app.exe', './my_app.exe') | %{ + Register-ArgumentCompleter -Native -CommandName $_ -ScriptBlock { + param($wordToComplete, $commandAst, $cursorPosition) + $command = '_my_app' + $commandAst.CommandElements | + Select-Object -Skip 1 | + %{ + switch ($_.ToString()) { + 'test' { + $command += '_test' + break + } + 'some_cmd' { + $command += '_some_cmd' + break + } + 'help' { + $command += '_help' + break + } + } + } + $completions = @() + switch ($command) { + '_my_app' { + $completions = @('test', 'some_cmd', 'help', '-h', '-V', '--help', '--version') + } + '_my_app_test' { + $completions = @('-h', '-V', '--case', '--help', '--version') + } + '_my_app_some_cmd' { + $completions = @('-h', '-V', '--config', '--help', '--version') + } + '_my_app_help' { + $completions = @('-h', '-V', '--help', '--version') + } + } + $completions | + ?{ $_ -like "$wordToComplete*" } | + Sort-Object | + %{ New-Object System.Management.Automation.CompletionResult $_, $_, 'ParameterValue', $_ } + } +} +"#; + +static ZSH_WUS: &'static str = r#"#compdef my_app + +_my_app() { + typeset -A opt_args + local ret=1 + + local context curcontext="$curcontext" state line + _arguments -s -S -C \ +"-h[Prints help information]" \ +"--help[Prints help information]" \ +"-V[Prints version information]" \ +"--version[Prints version information]" \ +"1:: :_my_app_commands" \ +"*:: :->my_app" \ +&& ret=0 + case $state in + (my_app) + curcontext="${curcontext%:*:*}:my_app-command-$words[1]:" + case $line[1] in + (test) +_arguments -s -S -C \ +"--case+[the case to test]" \ +"-h[Prints help information]" \ +"--help[Prints help information]" \ +"-V[Prints version information]" \ +"--version[Prints version information]" \ +&& ret=0 +;; +(some_cmd) +_arguments -s -S -C \ +"--config+[the other case to test]" \ +"-h[Prints help information]" \ +"--help[Prints help information]" \ +"-V[Prints version information]" \ +"--version[Prints version information]" \ +&& ret=0 +;; +(help) +_arguments -s -S -C \ +"-h[Prints help information]" \ +"--help[Prints help information]" \ +"-V[Prints version information]" \ +"--version[Prints version information]" \ +&& ret=0 +;; + esac + ;; +esac +} + +(( $+functions[_my_app_commands] )) || +_my_app_commands() { + local commands; commands=( + "test:tests things" \ +"some_cmd:tests other things" \ +"help:Prints this message or the help of the given subcommand(s)" \ +"FILE:some input file" \ + ) + _describe -t commands 'my_app commands' commands "$@" +} +(( $+functions[_my_app__help_commands] )) || +_my_app__help_commands() { + local commands; commands=( + + ) + _describe -t commands 'my_app help commands' commands "$@" +} +(( $+functions[_my_app__some_cmd_commands] )) || +_my_app__some_cmd_commands() { + local commands; commands=( + + ) + _describe -t commands 'my_app some_cmd commands' commands "$@" +} +(( $+functions[_my_app__test_commands] )) || +_my_app__test_commands() { + local commands; commands=( + + ) + _describe -t commands 'my_app test commands' commands "$@" +} + +_my_app "$@""#; + +static FISH_WUS: &'static str = r#"function __fish_using_command + set cmd (commandline -opc) + if [ (count $cmd) -eq (count $argv) ] + for i in (seq (count $argv)) + if [ $cmd[$i] != $argv[$i] ] + return 1 + end + end + return 0 + end + return 1 +end + +complete -c my_app -n "__fish_using_command my_app" -s h -l help -d "Prints help information" +complete -c my_app -n "__fish_using_command my_app" -s V -l version -d "Prints version information" +complete -c my_app -n "__fish_using_command my_app" -f -a "test" +complete -c my_app -n "__fish_using_command my_app" -f -a "some_cmd" +complete -c my_app -n "__fish_using_command my_app" -f -a "help" +complete -c my_app -n "__fish_using_command my_app test" -l case -d "the case to test" +complete -c my_app -n "__fish_using_command my_app test" -s h -l help -d "Prints help information" +complete -c my_app -n "__fish_using_command my_app test" -s V -l version -d "Prints version information" +complete -c my_app -n "__fish_using_command my_app some_cmd" -l config -d "the other case to test" +complete -c my_app -n "__fish_using_command my_app some_cmd" -s h -l help -d "Prints help information" +complete -c my_app -n "__fish_using_command my_app some_cmd" -s V -l version -d "Prints version information" +complete -c my_app -n "__fish_using_command my_app help" -s h -l help -d "Prints help information" +complete -c my_app -n "__fish_using_command my_app help" -s V -l version -d "Prints version information" +"#; + +static BASH_WUS: &'static str = r#"_my_app() { + local i cur prev opts cmds + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + cmd="" + opts="" + + for i in ${COMP_WORDS[@]} + do + case "${i}" in + my_app) + cmd="my_app" + ;; + + help) + cmd+="__help" + ;; + some_cmd) + cmd+="__some_cmd" + ;; + test) + cmd+="__test" + ;; + *) + ;; + esac + done + + case "${cmd}" in + my_app) + opts=" -h -V --help --version test some_cmd help" + if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then + COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) + return 0 + fi + case "${prev}" in + + *) + COMPREPLY=() + ;; + esac + COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) + return 0 + ;; + + my_app__help) + opts=" -h -V --help --version " + if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then + COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) + return 0 + fi + case "${prev}" in + + *) + COMPREPLY=() + ;; + esac + COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) + return 0 + ;; + my_app__some_cmd) + opts=" -h -V --config --help --version " + if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then + COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) + return 0 + fi + case "${prev}" in + + --config) + COMPREPLY=("") + return 0 + ;; + *) + COMPREPLY=() + ;; + esac + COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) + return 0 + ;; + my_app__test) + opts=" -h -V --case --help --version " + if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then + COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) + return 0 + fi + case "${prev}" in + + --case) + COMPREPLY=("") + return 0 + ;; + *) + COMPREPLY=() + ;; + esac + COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) + return 0 + ;; + esac +} + +complete -F _my_app -o bashdefault -o default my_app +"#; + +fn compare(left: &str, right: &str) -> bool { + let b = left == right; + if !b { + let re = Regex::new(" ").unwrap(); + println!(""); + println!("--> left"); + // println!("{}", left); + println!("{}", re.replace_all(left, "\u{2022}")); + println!("--> right"); + println!("{}", re.replace_all(right, "\u{2022}")); + // println!("{}", right); + println!("--") + } + b +} + +fn build_app() -> App<'static, 'static> { + build_app_with_name("myapp") +} + +fn build_app_with_name(s: &'static str) -> App<'static, 'static> { + App::new(s) + .about("Tests completions") + .arg(Arg::with_name("file") + .help("some input file")) + .subcommand(SubCommand::with_name("test") + .about("tests things") + .arg(Arg::with_name("case") + .long("case") + .takes_value(true) + .help("the case to test"))) +} + +fn build_app_with_underscore() -> App<'static, 'static> { + build_app_with_name("my_app") + .subcommand(SubCommand::with_name("some_cmd") + .about("tests other things") + .arg(Arg::with_name("config") + .long("--config") + .takes_value(true) + .help("the other case to test"))) +} + +#[test] +fn bash() { + let mut app = build_app(); + let mut buf = vec![]; + app.gen_completions_to("myapp", Shell::Bash, &mut buf); + let string = String::from_utf8(buf).unwrap(); + + assert!(compare(&*string, BASH)); +} + +#[test] +fn zsh() { + let mut app = build_app(); + let mut buf = vec![]; + app.gen_completions_to("myapp", Shell::Zsh, &mut buf); + let string = String::from_utf8(buf).unwrap(); + + assert!(compare(&*string, ZSH)); +} + +#[test] +fn fish() { + let mut app = build_app(); + let mut buf = vec![]; + app.gen_completions_to("myapp", Shell::Fish, &mut buf); + let string = String::from_utf8(buf).unwrap(); + + assert!(compare(&*string, FISH)); +} + +// Disabled until I figure out this windows line ending and AppVeyor issues +//#[test] +fn powershell() { + let mut app = build_app(); + let mut buf = vec![]; + app.gen_completions_to("myapp", Shell::PowerShell, &mut buf); + let string = String::from_utf8(buf).unwrap(); + + assert!(compare(&*string, POWERSHELL)); +} + +// Disabled until I figure out this windows line ending and AppVeyor issues +//#[test] +fn powershell_with_underscore() { + let mut app = build_app_with_underscore(); + let mut buf = vec![]; + app.gen_completions_to("my_app", Shell::PowerShell, &mut buf); + let string = String::from_utf8(buf).unwrap(); + + assert!(compare(&*string, POWERSHELL_WUS)); +} + +#[test] +fn bash_with_underscore() { + let mut app = build_app_with_underscore(); + let mut buf = vec![]; + app.gen_completions_to("my_app", Shell::Bash, &mut buf); + let string = String::from_utf8(buf).unwrap(); + + assert!(compare(&*string, BASH_WUS)); +} + +#[test] +fn fish_with_underscore() { + let mut app = build_app_with_underscore(); + let mut buf = vec![]; + app.gen_completions_to("my_app", Shell::Fish, &mut buf); + let string = String::from_utf8(buf).unwrap(); + + assert!(compare(&*string, FISH_WUS)); +} + +#[test] +fn zsh_with_underscore() { + let mut app = build_app_with_underscore(); + let mut buf = vec![]; + app.gen_completions_to("my_app", Shell::Zsh, &mut buf); + let string = String::from_utf8(buf).unwrap(); + + assert!(compare(&*string, ZSH_WUS)); +} \ No newline at end of file diff --git a/tests/default_vals.rs b/tests/default_vals.rs index ce6769ffbd6..17cbf443d5d 100644 --- a/tests/default_vals.rs +++ b/tests/default_vals.rs @@ -3,7 +3,7 @@ extern crate regex; include!("../clap-test.rs"); -use clap::{App, Arg}; +use clap::{App, Arg, ErrorKind}; #[test] fn opts() { @@ -316,4 +316,56 @@ fn default_ifs_arg_present_order() { let m = r.unwrap(); assert!(m.is_present("arg")); assert_eq!(m.value_of("arg").unwrap(), "default"); +} + +#[test] +fn conditional_reqs_fail() { + let m = App::new("Test app") + .version("1.0") + .author("F0x06") + .about("Arg test") + .arg(Arg::with_name("target") + .takes_value(true) + .default_value("file") + .possible_values(&["file", "stdout"]) + .long("target")) + .arg(Arg::with_name("input") + .takes_value(true) + .required(true) + .long("input")) + .arg(Arg::with_name("output") + .takes_value(true) + .required_if("target", "file") + .long("output")) + .get_matches_from_safe(vec!["test", "--input", "some"]); + + assert!(m.is_err()); + assert_eq!(m.unwrap_err().kind, ErrorKind::MissingRequiredArgument); +} + +#[test] +fn conditional_reqs_pass() { + let m = App::new("Test app") + .version("1.0") + .author("F0x06") + .about("Arg test") + .arg(Arg::with_name("target") + .takes_value(true) + .default_value("file") + .possible_values(&["file", "stdout"]) + .long("target")) + .arg(Arg::with_name("input") + .takes_value(true) + .required(true) + .long("input")) + .arg(Arg::with_name("output") + .takes_value(true) + .required_if("target", "file") + .long("output")) + .get_matches_from_safe(vec!["test", "--input", "some", "--output", "other"]); + + assert!(m.is_ok()); + let m = m.unwrap(); + assert_eq!(m.value_of("output"), Some("other")); + assert_eq!(m.value_of("input"), Some("some")); } \ No newline at end of file diff --git a/tests/require.rs b/tests/require.rs index 987c45a032a..f8eb4105823 100644 --- a/tests/require.rs +++ b/tests/require.rs @@ -14,11 +14,18 @@ USAGE: For more information try --help"; +static COND_REQ_IN_USAGE: &'static str = "error: The following required arguments were not provided: + --output + +USAGE: + test --target --input --output + +For more information try --help"; + #[test] fn flag_required() { let result = App::new("flag_required") - .arg(Arg::from_usage("-f, --flag 'some flag'") - .requires("color")) + .arg(Arg::from_usage("-f, --flag 'some flag'").requires("color")) .arg(Arg::from_usage("-c, --color 'third flag'")) .get_matches_from_safe(vec!["", "-f"]); assert!(result.is_err()); @@ -29,8 +36,7 @@ fn flag_required() { #[test] fn flag_required_2() { let m = App::new("flag_required") - .arg(Arg::from_usage("-f, --flag 'some flag'") - .requires("color")) + .arg(Arg::from_usage("-f, --flag 'some flag'").requires("color")) .arg(Arg::from_usage("-c, --color 'third flag'")) .get_matches_from(vec!["", "-f", "-c"]); assert!(m.is_present("color")); @@ -40,8 +46,7 @@ fn flag_required_2() { #[test] fn option_required() { let result = App::new("option_required") - .arg(Arg::from_usage("-f [flag] 'some flag'") - .requires("color")) + .arg(Arg::from_usage("-f [flag] 'some flag'").requires("color")) .arg(Arg::from_usage("-c [color] 'third flag'")) .get_matches_from_safe(vec!["", "-f", "val"]); assert!(result.is_err()); @@ -52,8 +57,7 @@ fn option_required() { #[test] fn option_required_2() { let m = App::new("option_required") - .arg(Arg::from_usage("-f [flag] 'some flag'") - .requires("c")) + .arg(Arg::from_usage("-f [flag] 'some flag'").requires("c")) .arg(Arg::from_usage("-c [color] 'third flag'")) .get_matches_from(vec!["", "-f", "val", "-c", "other_val"]); assert!(m.is_present("c")); @@ -136,8 +140,7 @@ fn group_required_3() { #[test] fn arg_require_group() { let result = App::new("arg_require_group") - .arg(Arg::from_usage("-f, --flag 'some flag'") - .requires("gr")) + .arg(Arg::from_usage("-f, --flag 'some flag'").requires("gr")) .group(ArgGroup::with_name("gr") .arg("some") .arg("other")) @@ -152,8 +155,7 @@ fn arg_require_group() { #[test] fn arg_require_group_2() { let m = App::new("arg_require_group") - .arg(Arg::from_usage("-f, --flag 'some flag'") - .requires("gr")) + .arg(Arg::from_usage("-f, --flag 'some flag'").requires("gr")) .group(ArgGroup::with_name("gr") .arg("some") .arg("other")) @@ -168,8 +170,7 @@ fn arg_require_group_2() { #[test] fn arg_require_group_3() { let m = App::new("arg_require_group") - .arg(Arg::from_usage("-f, --flag 'some flag'") - .requires("gr")) + .arg(Arg::from_usage("-f, --flag 'some flag'").requires("gr")) .group(ArgGroup::with_name("gr") .arg("some") .arg("other")) @@ -197,7 +198,7 @@ fn issue_753() { .arg(Arg::from_usage("-p, --port=[SERVER_PORT] 'NTP server port'") .default_value("123")) .get_matches_from_safe(vec!["test", "--list"]); - assert!(m.is_ok()); + assert!(m.is_ok()); } #[test] @@ -207,11 +208,8 @@ fn required_unless() { .required_unless("dbg") .takes_value(true) .long("config")) - .arg(Arg::with_name("dbg") - .long("debug")) - .get_matches_from_safe(vec![ - "unlesstest", "--debug" - ]); + .arg(Arg::with_name("dbg").long("debug")) + .get_matches_from_safe(vec!["unlesstest", "--debug"]); assert!(res.is_ok()); let m = res.unwrap(); @@ -226,11 +224,8 @@ fn required_unless_err() { .required_unless("dbg") .takes_value(true) .long("config")) - .arg(Arg::with_name("dbg") - .long("debug")) - .get_matches_from_safe(vec![ - "unlesstest" - ]); + .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); @@ -245,14 +240,11 @@ fn required_unless_all() { .required_unless_all(&["dbg", "infile"]) .takes_value(true) .long("config")) - .arg(Arg::with_name("dbg") - .long("debug")) + .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" - ]); + .get_matches_from_safe(vec!["unlessall", "--debug", "-i", "file"]); assert!(res.is_ok()); let m = res.unwrap(); @@ -268,14 +260,11 @@ fn required_unless_all_err() { .required_unless_all(&["dbg", "infile"]) .takes_value(true) .long("config")) - .arg(Arg::with_name("dbg") - .long("debug")) + .arg(Arg::with_name("dbg").long("debug")) .arg(Arg::with_name("infile") .short("i") .takes_value(true)) - .get_matches_from_safe(vec![ - "unlessall", "--debug" - ]); + .get_matches_from_safe(vec!["unlessall", "--debug"]); assert!(res.is_err()); assert_eq!(res.unwrap_err().kind, ErrorKind::MissingRequiredArgument); @@ -290,14 +279,11 @@ fn required_unless_one() { .required_unless_one(&["dbg", "infile"]) .takes_value(true) .long("config")) - .arg(Arg::with_name("dbg") - .long("debug")) + .arg(Arg::with_name("dbg").long("debug")) .arg(Arg::with_name("infile") .short("i") .takes_value(true)) - .get_matches_from_safe(vec![ - "unlessone", "--debug" - ]); + .get_matches_from_safe(vec!["unlessone", "--debug"]); assert!(res.is_ok()); let m = res.unwrap(); @@ -314,14 +300,11 @@ fn required_unless_one_2() { .required_unless_one(&["dbg", "infile"]) .takes_value(true) .long("config")) - .arg(Arg::with_name("dbg") - .long("debug")) + .arg(Arg::with_name("dbg").long("debug")) .arg(Arg::with_name("infile") .short("i") .takes_value(true)) - .get_matches_from_safe(vec![ - "unlessone", "-i", "file" - ]); + .get_matches_from_safe(vec!["unlessone", "-i", "file"]); assert!(res.is_ok()); let m = res.unwrap(); @@ -336,14 +319,11 @@ fn required_unless_one_1() { .required_unless_one(&["dbg", "infile"]) .takes_value(true) .long("config")) - .arg(Arg::with_name("dbg") - .long("debug")) + .arg(Arg::with_name("dbg").long("debug")) .arg(Arg::with_name("infile") .short("i") .takes_value(true)) - .get_matches_from_safe(vec![ - "unlessone", "--debug" - ]); + .get_matches_from_safe(vec!["unlessone", "--debug"]); assert!(res.is_ok()); let m = res.unwrap(); @@ -359,14 +339,11 @@ fn required_unless_one_err() { .required_unless_one(&["dbg", "infile"]) .takes_value(true) .long("config")) - .arg(Arg::with_name("dbg") - .long("debug")) + .arg(Arg::with_name("dbg").long("debug")) .arg(Arg::with_name("infile") .short("i") .takes_value(true)) - .get_matches_from_safe(vec![ - "unlessone" - ]); + .get_matches_from_safe(vec!["unlessone"]); assert!(res.is_err()); assert_eq!(res.unwrap_err().kind, ErrorKind::MissingRequiredArgument); @@ -386,11 +363,8 @@ fn requires_if_present_val() { .requires_if("my.cfg", "extra") .takes_value(true) .long("config")) - .arg(Arg::with_name("extra") - .long("extra")) - .get_matches_from_safe(vec![ - "unlessone", "--config=my.cfg" - ]); + .arg(Arg::with_name("extra").long("extra")) + .get_matches_from_safe(vec!["unlessone", "--config=my.cfg"]); assert!(res.is_err()); assert_eq!(res.unwrap_err().kind, ErrorKind::MissingRequiredArgument); @@ -400,19 +374,12 @@ fn requires_if_present_val() { fn requires_if_present_mult() { let res = App::new("unlessone") .arg(Arg::with_name("cfg") - .requires_ifs(&[ - ("my.cfg", "extra"), - ("other.cfg", "other"), - ]) + .requires_ifs(&[("my.cfg", "extra"), ("other.cfg", "other")]) .takes_value(true) .long("config")) - .arg(Arg::with_name("extra") - .long("extra")) - .arg(Arg::with_name("other") - .long("other")) - .get_matches_from_safe(vec![ - "unlessone", "--config=other.cfg" - ]); + .arg(Arg::with_name("extra").long("extra")) + .arg(Arg::with_name("other").long("other")) + .get_matches_from_safe(vec!["unlessone", "--config=other.cfg"]); assert!(res.is_err()); assert_eq!(res.unwrap_err().kind, ErrorKind::MissingRequiredArgument); @@ -422,19 +389,12 @@ fn requires_if_present_mult() { fn requires_if_present_mult_pass() { let res = App::new("unlessone") .arg(Arg::with_name("cfg") - .requires_ifs(&[ - ("my.cfg", "extra"), - ("other.cfg", "other"), - ]) + .requires_ifs(&[("my.cfg", "extra"), ("other.cfg", "other")]) .takes_value(true) .long("config")) - .arg(Arg::with_name("extra") - .long("extra")) - .arg(Arg::with_name("other") - .long("other")) - .get_matches_from_safe(vec![ - "unlessone", "--config=some.cfg" - ]); + .arg(Arg::with_name("extra").long("extra")) + .arg(Arg::with_name("other").long("other")) + .get_matches_from_safe(vec!["unlessone", "--config=some.cfg"]); assert!(res.is_ok()); // assert_eq!(res.unwrap_err().kind, ErrorKind::MissingRequiredArgument); @@ -447,11 +407,8 @@ fn requires_if_present_val_no_present_pass() { .requires_if("my.cfg", "extra") .takes_value(true) .long("config")) - .arg(Arg::with_name("extra") - .long("extra")) - .get_matches_from_safe(vec![ - "unlessone", - ]); + .arg(Arg::with_name("extra").long("extra")) + .get_matches_from_safe(vec!["unlessone"]); assert!(res.is_ok()); } @@ -468,9 +425,7 @@ fn required_if_val_present_pass() { .arg(Arg::with_name("extra") .takes_value(true) .long("extra")) - .get_matches_from_safe(vec![ - "ri", "--extra", "val", "--config", "my.cfg" - ]); + .get_matches_from_safe(vec!["ri", "--extra", "val", "--config", "my.cfg"]); assert!(res.is_ok()); } @@ -485,14 +440,38 @@ fn required_if_val_present_fail() { .arg(Arg::with_name("extra") .takes_value(true) .long("extra")) - .get_matches_from_safe(vec![ - "ri", "--extra", "val" - ]); + .get_matches_from_safe(vec!["ri", "--extra", "val"]); assert!(res.is_err()); assert_eq!(res.unwrap_err().kind, ErrorKind::MissingRequiredArgument); } +#[test] +fn required_if_val_present_fail_error_output() { + let app = App::new("Test app") + .version("1.0") + .author("F0x06") + .about("Arg test") + .arg(Arg::with_name("target") + .takes_value(true) + .required(true) + .possible_values(&["file", "stdout"]) + .long("target")) + .arg(Arg::with_name("input") + .takes_value(true) + .required(true) + .long("input")) + .arg(Arg::with_name("output") + .takes_value(true) + .required_if("target", "file") + .long("output")); + + assert!(test::compare_output(app, + "test --input somepath --target file", + COND_REQ_IN_USAGE, + true)); +} + #[test] fn required_if_wrong_val() { let res = App::new("ri") @@ -503,9 +482,7 @@ fn required_if_wrong_val() { .arg(Arg::with_name("extra") .takes_value(true) .long("extra")) - .get_matches_from_safe(vec![ - "ri", "--extra", "other" - ]); + .get_matches_from_safe(vec!["ri", "--extra", "other"]); assert!(res.is_ok()); } @@ -514,10 +491,7 @@ fn required_if_wrong_val() { fn required_ifs_val_present_pass() { let res = App::new("ri") .arg(Arg::with_name("cfg") - .required_ifs(&[ - ("extra", "val"), - ("option", "spec") - ]) + .required_ifs(&[("extra", "val"), ("option", "spec")]) .takes_value(true) .long("config")) .arg(Arg::with_name("option") @@ -526,9 +500,7 @@ fn required_ifs_val_present_pass() { .arg(Arg::with_name("extra") .takes_value(true) .long("extra")) - .get_matches_from_safe(vec![ - "ri", "--option", "spec", "--config", "my.cfg" - ]); + .get_matches_from_safe(vec!["ri", "--option", "spec", "--config", "my.cfg"]); assert!(res.is_ok()); // assert_eq!(res.unwrap_err().kind, ErrorKind::MissingRequiredArgument); @@ -538,10 +510,7 @@ fn required_ifs_val_present_pass() { fn required_ifs_val_present_fail() { let res = App::new("ri") .arg(Arg::with_name("cfg") - .required_ifs(&[ - ("extra", "val"), - ("option", "spec") - ]) + .required_ifs(&[("extra", "val"), ("option", "spec")]) .takes_value(true) .long("config")) .arg(Arg::with_name("extra") @@ -550,9 +519,7 @@ fn required_ifs_val_present_fail() { .arg(Arg::with_name("option") .takes_value(true) .long("option")) - .get_matches_from_safe(vec![ - "ri", "--option", "spec" - ]); + .get_matches_from_safe(vec!["ri", "--option", "spec"]); assert!(res.is_err()); assert_eq!(res.unwrap_err().kind, ErrorKind::MissingRequiredArgument); @@ -562,10 +529,7 @@ fn required_ifs_val_present_fail() { fn required_ifs_wrong_val() { let res = App::new("ri") .arg(Arg::with_name("cfg") - .required_ifs(&[ - ("extra", "val"), - ("option", "spec") - ]) + .required_ifs(&[("extra", "val"), ("option", "spec")]) .takes_value(true) .long("config")) .arg(Arg::with_name("extra") @@ -574,9 +538,7 @@ fn required_ifs_wrong_val() { .arg(Arg::with_name("option") .takes_value(true) .long("option")) - .get_matches_from_safe(vec![ - "ri", "--option", "other" - ]); + .get_matches_from_safe(vec!["ri", "--option", "other"]); assert!(res.is_ok()); } @@ -585,10 +547,7 @@ fn required_ifs_wrong_val() { fn required_ifs_wrong_val_mult_fail() { let res = App::new("ri") .arg(Arg::with_name("cfg") - .required_ifs(&[ - ("extra", "val"), - ("option", "spec") - ]) + .required_ifs(&[("extra", "val"), ("option", "spec")]) .takes_value(true) .long("config")) .arg(Arg::with_name("extra") @@ -597,9 +556,7 @@ fn required_ifs_wrong_val_mult_fail() { .arg(Arg::with_name("option") .takes_value(true) .long("option")) - .get_matches_from_safe(vec![ - "ri", "--extra", "other", "--option", "spec" - ]); + .get_matches_from_safe(vec!["ri", "--extra", "other", "--option", "spec"]); assert!(res.is_err()); assert_eq!(res.unwrap_err().kind, ErrorKind::MissingRequiredArgument);