From 3e99887ef8e98fc826a001b67cc62adc799cd0d7 Mon Sep 17 00:00:00 2001 From: Kevin K Date: Mon, 30 Jan 2017 21:04:47 -0500 Subject: [PATCH 01/13] tests: adds tests for default values triggering conditional requirements --- tests/default_vals.rs | 54 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) 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 From a7a44babbddd21fecda2db0bc77869dff7280a5c Mon Sep 17 00:00:00 2001 From: Kevin K Date: Mon, 30 Jan 2017 21:08:39 -0500 Subject: [PATCH 02/13] fix: fixes a bug where default values should have triggered a conditional requirement but didnt Closes #831 --- src/app/parser.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/parser.rs b/src/app/parser.rs index 1304b19b149..01415b758ac 100644 --- a/src/app/parser.rs +++ b/src/app/parser.rs @@ -995,6 +995,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) { @@ -1018,7 +1019,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(&[])); From 196f666cd83d8f9ada8bf54390765e3c2803c951 Mon Sep 17 00:00:00 2001 From: Kevin K Date: Mon, 30 Jan 2017 21:17:30 -0500 Subject: [PATCH 03/13] tests: adds tests for missing conditional requirements in usage string of errors --- tests/require.rs | 205 +++++++++++++++++++---------------------------- 1 file changed, 81 insertions(+), 124 deletions(-) diff --git a/tests/require.rs b/tests/require.rs index 987c45a032a..3d75e6224a3 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: + clap_test [OPTIONS] --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); From 94c6d31b08f5efa358728b673ee19f4623fbf744 Mon Sep 17 00:00:00 2001 From: Kevin K Date: Mon, 30 Jan 2017 22:00:25 -0500 Subject: [PATCH 04/13] fix: fixes a bug where conditionally required args werent appearing in errors --- src/app/macros.rs | 2 +- src/app/parser.rs | 105 +++++++++++++++++++++++++++------------------- tests/require.rs | 4 +- 3 files changed, 65 insertions(+), 46 deletions(-) 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 01415b758ac..2f52f8dbd7d 100644 --- a/src/app/parser.rs +++ b/src/app/parser.rs @@ -304,23 +304,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 +852,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 +876,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 +925,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 +937,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 +951,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 +981,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"); @@ -1008,7 +1017,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())); } } @@ -1086,7 +1095,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); @@ -1264,8 +1273,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 +1286,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 +1475,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 +1498,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 +1592,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 +1600,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 +1654,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 +1708,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 +1748,7 @@ impl<'a, 'b> Parser<'a, 'b> } else { "ere" }, - &*self.create_current_usage(matcher), + &*self.create_current_usage(matcher, None), self.color())); } } @@ -1752,7 +1765,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 +1776,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 +1800,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 +1808,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 +1862,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 +1870,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 +1942,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 +1976,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 +2031,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/tests/require.rs b/tests/require.rs index 3d75e6224a3..f8eb4105823 100644 --- a/tests/require.rs +++ b/tests/require.rs @@ -15,10 +15,10 @@ USAGE: For more information try --help"; static COND_REQ_IN_USAGE: &'static str = "error: The following required arguments were not provided: - --output + --output USAGE: - clap_test [OPTIONS] --target --input --output + test --target --input --output For more information try --help"; From 0a77bb9663fc9b9ab2e8f2020ee6bf09fa0f29c6 Mon Sep 17 00:00:00 2001 From: Kevin K Date: Mon, 30 Jan 2017 22:36:25 -0500 Subject: [PATCH 05/13] tests: adds tests for completion generators --- src/completions/zsh.rs | 6 +- tests/completions.rs | 289 +++++++++++++++++++++++++++++++++++++++-- 2 files changed, 284 insertions(+), 11 deletions(-) diff --git a/src/completions/zsh.rs b/src/completions/zsh.rs index a3bb2fb3bd5..3b8d8972f21 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} }} diff --git a/tests/completions.rs b/tests/completions.rs index 6aa99f5d7ab..d06d36bb5c5 100644 --- a/tests/completions.rs +++ b/tests/completions.rs @@ -1,10 +1,251 @@ +extern crate regex; extern crate clap; use clap::{App, Arg, SubCommand, Shell}; +use regex::Regex; -#[test] -fn test_generation() { - let mut app = App::new("myapp") +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" +"#; + +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', $_ } + } +} +"#; + +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> { + App::new("myapp") .about("Tests completions") .arg(Arg::with_name("file") .help("some input file")) @@ -13,13 +254,45 @@ fn test_generation() { .arg(Arg::with_name("case") .long("case") .takes_value(true) - .help("the case to test"))); + .help("the 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(); - 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"); + 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)); +} + +#[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)); } From 09a6c0aab72dd1950950bfe43248acc988b98f59 Mon Sep 17 00:00:00 2001 From: Kevin K Date: Mon, 30 Jan 2017 23:45:25 -0500 Subject: [PATCH 06/13] tests: adds tests for completions with binaries names that have underscores --- tests/completions.rs | 1017 +++++++++++++++++++++++++++++------------- 1 file changed, 719 insertions(+), 298 deletions(-) diff --git a/tests/completions.rs b/tests/completions.rs index d06d36bb5c5..8b4f843ffd0 100644 --- a/tests/completions.rs +++ b/tests/completions.rs @@ -1,298 +1,719 @@ -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" -"#; - -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', $_ } - } -} -"#; - -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> { - 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"))) -} - -#[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)); -} - -#[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)); -} +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 From 891a2a006f775e92c556dda48bb32fac9807c4fb Mon Sep 17 00:00:00 2001 From: Kevin K Date: Mon, 30 Jan 2017 23:56:18 -0500 Subject: [PATCH 07/13] fix: fixes a bug where ZSH completions would panic if the binary name had an underscore in it Closes #581 --- src/completions/zsh.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/completions/zsh.rs b/src/completions/zsh.rs index 3b8d8972f21..fa84342f8c2 100644 --- a/src/completions/zsh.rs +++ b/src/completions/zsh.rs @@ -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() }; From 7f5cfa724f0ac4e098f5fe466c903febddb2d994 Mon Sep 17 00:00:00 2001 From: Kevin K Date: Mon, 30 Jan 2017 23:56:36 -0500 Subject: [PATCH 08/13] fix: fixes bash completions for commands that have an underscore in the name Closes #581 --- src/completions/bash.rs | 12 ++++++------ src/completions/mod.rs | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) 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 { From a5e4a9854cfb010e4bd33c4e8c46e91cb41322d8 Mon Sep 17 00:00:00 2001 From: Kevin K Date: Fri, 3 Feb 2017 12:15:32 -0500 Subject: [PATCH 09/13] chore: fix the category for crates.io --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index be77e3450dc..fd0fb8ccdea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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 """ From 112aea3e42ae9e0c0a2d33ebad89496dbdd95e5d Mon Sep 17 00:00:00 2001 From: Kevin K Date: Fri, 3 Feb 2017 12:29:45 -0500 Subject: [PATCH 10/13] docs(Macros): adds a warning about changing values in Cargo.toml not triggering a rebuild automatically Closes #838 --- src/macros.rs | 7 +++++++ 1 file changed, 7 insertions(+) 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 From 9a1e006eb75ad5a6057ebd119aa90f7e06c0ace8 Mon Sep 17 00:00:00 2001 From: Kevin K Date: Fri, 3 Feb 2017 12:50:59 -0500 Subject: [PATCH 11/13] fix(Completions): fixes a bug where global args weren't included in the generated completion scripts Closes #841 --- src/app/parser.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/app/parser.rs b/src/app/parser.rs index 2f52f8dbd7d..6810a7a1fbe 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) } From 279aa62eaf08f56ce090ba16b937bc763cbb45be Mon Sep 17 00:00:00 2001 From: Kevin K Date: Fri, 3 Feb 2017 17:38:27 -0500 Subject: [PATCH 12/13] fix: fixes a println->debugln typo --- src/app/parser.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/parser.rs b/src/app/parser.rs index 6810a7a1fbe..f2fbbe49cb8 100644 --- a/src/app/parser.rs +++ b/src/app/parser.rs @@ -1129,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(), From 6d63cf761552cc49a5694b836bc87f343fabb411 Mon Sep 17 00:00:00 2001 From: Kevin K Date: Fri, 3 Feb 2017 17:41:58 -0500 Subject: [PATCH 13/13] chore: increase version --- CHANGELOG.md | 18 ++++++++++++++++++ Cargo.toml | 2 +- README.md | 13 +++++++++---- 3 files changed, 28 insertions(+), 5 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 fd0fb8ccdea..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" 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