diff --git a/clap-test.rs b/clap-test.rs index e3425d60..0de3790e 100644 --- a/clap-test.rs +++ b/clap-test.rs @@ -50,6 +50,17 @@ mod test { assert_eq!(use_stderr, err.use_stderr()); } + pub fn check_subcommand_help(mut a: App, cmd: &str, out: &str) { + // We call a get_matches method to cause --help and --version to be built + let _ = a.get_matches_from_safe_borrow(vec![""]); + let sc = a.p.subcommands.iter().filter(|s| s.p.meta.name == cmd).next().unwrap(); + + // Now we check the output of print_help() + let mut help = vec![]; + sc.write_help(&mut help).ok().expect("failed to print help"); + assert_eq!(str::from_utf8(&help).unwrap(), out); + } + pub fn check_help(mut a: App, out: &str) { // We call a get_matches method to cause --help and --version to be built let _ = a.get_matches_from_safe_borrow(vec![""]); diff --git a/src/app/parser.rs b/src/app/parser.rs index 7c896203..d0635a36 100644 --- a/src/app/parser.rs +++ b/src/app/parser.rs @@ -604,7 +604,17 @@ impl<'a, 'b> Parser<'a, 'b> // Check to see if parsing a value from an option if let Some(nvo) = needs_val_of { // get the OptBuilder so we can check the settings - if let Some(opt) = self.opts.iter().find(|o| &o.name == &nvo) { + if let Some(opt) = self.opts + .iter() + .find(|o| { + &o.name == &nvo || + (o.aliases.is_some() && + o.aliases + .as_ref() + .unwrap() + .iter() + .any(|&(a, _)| a == &*nvo)) + }) { needs_val_of = try!(self.add_val_to_arg(opt, &arg_os, matcher)); // get the next value from the iterator continue; @@ -765,7 +775,17 @@ impl<'a, 'b> Parser<'a, 'b> if let Some(a) = needs_val_of { debugln!("needs_val_of={}", a); debug!("Is this an opt..."); - if let Some(o) = self.opts.iter().find(|o| &o.name == &a) { + if let Some(o) = self.opts + .iter() + .find(|o| { + &o.name == &a || + (o.aliases.is_some() && + o.aliases + .as_ref() + .unwrap() + .iter() + .any(|&(n, _)| n == &*a)) + }) { sdebugln!("Yes"); try!(self.validate_required(matcher)); reqs_validated = true; @@ -1213,7 +1233,16 @@ impl<'a, 'b> Parser<'a, 'b> if let Some(opt) = self.opts .iter() - .find(|v| v.long.is_some() && &*v.long.unwrap() == arg) { + .find(|v| + (v.long.is_some() && + &*v.long.unwrap() == arg) || + (v.aliases.is_some() && + v.aliases + .as_ref() + .unwrap() + .iter() + .any(|&(n, _)| n == &*arg)) + ) { debugln!("Found valid opt '{}'", opt.to_string()); let ret = try!(self.parse_opt(val, opt, matcher)); arg_post_processing!(self, opt, matcher); diff --git a/src/args/arg.rs b/src/args/arg.rs index f8a24610..3ba090f1 100644 --- a/src/args/arg.rs +++ b/src/args/arg.rs @@ -42,6 +42,8 @@ pub struct Arg<'a, 'b> #[doc(hidden)] pub long: Option<&'b str>, #[doc(hidden)] + pub aliases: Option>, // (name, visible) + #[doc(hidden)] pub help: Option<&'b str>, #[doc(hidden)] pub index: Option, @@ -83,6 +85,7 @@ impl<'a, 'b> Default for Arg<'a, 'b> { name: "".as_ref(), short: None, long: None, + aliases: None, help: None, index: None, blacklist: None, @@ -149,6 +152,7 @@ impl<'a, 'b> Arg<'a, 'b> { a = match k.as_str().unwrap() { "short" => yaml_to_str!(a, v, short), "long" => yaml_to_str!(a, v, long), + "aliases" => yaml_vec_or_str!(v, a, alias), "help" => yaml_to_str!(a, v, help), "required" => yaml_to_bool!(a, v, required), "takes_value" => yaml_to_bool!(a, v, takes_value), @@ -409,6 +413,50 @@ impl<'a, 'b> Arg<'a, 'b> { self } + /// Add docs + pub fn alias>(mut self, name: S) -> Self { + if let Some(ref mut als) = self.aliases { + als.push((name.into(), false)); + } else { + self.aliases = Some(vec![(name.into(), false)]); + } + self + } + + /// Add docs + pub fn aliases(mut self, names: &[&'b str]) -> Self { + if let Some(ref mut als) = self.aliases { + for n in names { + als.push((n, false)); + } + } else { + self.aliases = Some(names.iter().map(|n| (*n, false)).collect::>()); + } + self + } + + /// Add docs + pub fn visible_alias>(mut self, name: S) -> Self { + if let Some(ref mut als) = self.aliases { + als.push((name.into(), true)); + } else { + self.aliases = Some(vec![(name.into(), true)]); + } + self + } + + /// Add docs + pub fn visible_aliases(mut self, names: &[&'b str]) -> Self { + if let Some(ref mut als) = self.aliases { + for n in names { + als.push((n, true)); + } + } else { + self.aliases = Some(names.iter().map(|n| (*n, true)).collect::>()); + } + self + } + /// Sets the help text of the argument that will be displayed to the user when they print the /// usage/help information. /// @@ -2380,6 +2428,7 @@ impl<'a, 'b, 'z> From<&'z Arg<'a, 'b>> for Arg<'a, 'b> { name: a.name, short: a.short, long: a.long, + aliases: a.aliases.clone(), help: a.help, index: a.index, possible_vals: a.possible_vals.clone(), @@ -2407,6 +2456,7 @@ impl<'a, 'b> Clone for Arg<'a, 'b> { name: self.name, short: self.short, long: self.long, + aliases: self.aliases.clone(), help: self.help, index: self.index, possible_vals: self.possible_vals.clone(), diff --git a/src/args/arg_builder/option.rs b/src/args/arg_builder/option.rs index 646aa1ce..438cc820 100644 --- a/src/args/arg_builder/option.rs +++ b/src/args/arg_builder/option.rs @@ -16,6 +16,7 @@ pub struct OptBuilder<'n, 'e> { pub name: &'n str, pub short: Option, pub long: Option<&'e str>, + pub aliases: Option>, pub help: Option<&'e str>, pub blacklist: Option>, pub possible_vals: Option>, @@ -39,6 +40,7 @@ impl<'n, 'e> Default for OptBuilder<'n, 'e> { name: "", short: None, long: None, + aliases: None, help: None, blacklist: None, possible_vals: None, @@ -74,6 +76,7 @@ impl<'n, 'e> OptBuilder<'n, 'e> { name: a.name, short: a.short, long: a.long, + aliases: a.aliases.clone(), help: a.help, num_vals: a.num_vals, min_vals: a.min_vals, @@ -157,6 +160,21 @@ impl<'n, 'e> Display for OptBuilder<'n, 'e> { })); } + // Write aliases such as [aliases: alias, new-alias] + if let Some(ref vec) = self.aliases { + try!(write!(f, " [aliases: ")); + let mut it = vec.iter().peekable(); + while let Some(&(val, b)) = it.next() { + if b { + try!(write!(f, "{}", val)); + if it.peek().is_some() { + try!(write!(f, ", ")); + } + } + } + try!(write!(f, "]")); + } + Ok(()) } } @@ -167,6 +185,7 @@ impl<'n, 'e> Clone for OptBuilder<'n, 'e> { name: self.name, short: self.short, long: self.long, + aliases: self.aliases.clone(), help: self.help, blacklist: self.blacklist.clone(), overrides: self.overrides.clone(), @@ -252,7 +271,19 @@ impl<'n, 'e> AnyArg<'n, 'e> for OptBuilder<'n, 'e> { true } fn aliases(&self) -> Option> { - None + if let Some(ref aliases) = self.aliases { + let vis_aliases: Vec<_> = + aliases.iter() + .filter_map(|&(n, v)| if v { Some(n) } else { None }) + .collect(); + if vis_aliases.is_empty() { + None + } else { + Some(vis_aliases) + } + } else { + None + } } } @@ -303,4 +334,25 @@ mod test { assert_eq!(&*format!("{}", o2), "-o "); } + + #[test] + fn optbuilder_display_single_alias() { + let mut o = OptBuilder::new("opt"); + o.long = Some("option"); + o.aliases = Some(vec![("als", true)]); + + assert_eq!(&*format!("{}", o), "--option [aliases: als]"); + } + + fn optbuilder_display_multiple_aliases() { + let mut o = OptBuilder::new("opt"); + o.long = Some("option"); + o.aliases = Some(vec![ + ("als_not_visible", false), + ("als2", true), + ("als3", true), + ("als4", true) + ]); + assert_eq!(&*format!("{}", o), "--option [aliases: als2, als3, als4]"); + } } diff --git a/tests/app.yml b/tests/app.yml index 0e78f5bb..1588a00c 100644 --- a/tests/app.yml +++ b/tests/app.yml @@ -64,6 +64,14 @@ args: help: Tests mutliple values with required delimiter multiple: true require_delimiter: true + - singlealias: + long: singlealias + help: Tests single alias + aliases: [alias] + - multaliases: + long: multaliases + help: Tests mutliple aliases + aliases: [als1, als2, als3] - minvals2: long: minvals2 multiple: true diff --git a/tests/arg_aliases.rs b/tests/arg_aliases.rs new file mode 100644 index 00000000..d794f9de --- /dev/null +++ b/tests/arg_aliases.rs @@ -0,0 +1,135 @@ +extern crate clap; +extern crate regex; + +include!("../clap-test.rs"); + +use clap::{App, Arg, SubCommand}; + +static SC_VISIBLE_ALIAS_HELP: &'static str = "test +Some help + +USAGE: + test [OPTIONS] + +OPTIONS: + --opt [aliases: visible]"; + +static SC_INVISIBLE_ALIAS_HELP: &'static str = "test +Some help + +USAGE: + test [OPTIONS] + +OPTIONS: + --opt "; + +#[test] +fn single_alias_of_option_long() { + let a = App::new("single_alias") + .arg(Arg::with_name("alias") + .long("alias") + .takes_value(true) + .help("single alias") + .alias("new-opt")) + .get_matches_from_safe(vec![ + "", "--new-opt", "cool" + ]); + assert!(a.is_ok()); + let a = a.unwrap(); + assert!(a.is_present("alias")); + assert_eq!(a.value_of("alias").unwrap(), "cool"); +} + +#[test] +fn multiple_aliases_of_option_long() { + let a = App::new("multiple_aliases") + .arg(Arg::with_name("aliases") + .long("aliases") + .takes_value(true) + .help("multiple aliases") + .aliases(&vec![ + "alias1", + "alias2", + "alias3" + ])); + let long = a.clone().get_matches_from_safe(vec![ + "", "--aliases", "value" + ]); + assert!(long.is_ok()); + let long = long.unwrap(); + + let als1 = a.clone().get_matches_from_safe(vec![ + "", "--alias1", "value" + ]); + assert!(als1.is_ok()); + let als1 = als1.unwrap(); + + let als2 = a.clone().get_matches_from_safe(vec![ + "", "--alias2", "value" + ]); + assert!(als2.is_ok()); + let als2 = als2.unwrap(); + + let als3 = a.clone().get_matches_from_safe(vec![ + "", "--alias3", "value" + ]); + assert!(als3.is_ok()); + let als3 = als3.unwrap(); + + assert!(long.is_present("aliases")); + assert!(als1.is_present("aliases")); + assert!(als2.is_present("aliases")); + assert!(als3.is_present("aliases")); + assert_eq!(long.value_of("aliases").unwrap(), "value"); + assert_eq!(als1.value_of("aliases").unwrap(), "value"); + assert_eq!(als2.value_of("aliases").unwrap(), "value"); + assert_eq!(als3.value_of("aliases").unwrap(), "value"); +} + +#[test] +fn alias_on_a_subcommand_option() { + let m = App::new("test") + .subcommand(SubCommand::with_name("some") + .arg(Arg::with_name("test") + .short("t") + .long("test") + .takes_value(true) + .alias("opt") + .help("testing testing"))) + .arg(Arg::with_name("other") + .long("other") + .aliases(&vec!["o1", "o2", "o3"])) + .get_matches_from(vec![ + "test", "some", "--opt", "awesome" + ]); + + assert!(m.subcommand_matches("some").is_some()); + let sub_m = m.subcommand_matches("some").unwrap(); + assert!(sub_m.is_present("test")); + assert_eq!(sub_m.value_of("test").unwrap(), "awesome"); +} + +#[test] +fn invisible_arg_aliases_help_output() { + let app = App::new("clap-test") + .subcommand(SubCommand::with_name("test") + .about("Some help") + .arg(Arg::with_name("opt") + .long("opt") + .takes_value(true) + .aliases(&["invisible", "als1", "more"]))); + test::check_subcommand_help(app, "test", SC_INVISIBLE_ALIAS_HELP); +} + +#[test] +fn visible_arg_aliases_help_output() { + let app = App::new("clap-test") + .subcommand(SubCommand::with_name("test") + .about("Some help") + .arg(Arg::with_name("opt") + .long("opt") + .takes_value(true) + .alias("invisible") + .visible_alias("visible"))); + test::check_subcommand_help(app, "test", SC_VISIBLE_ALIAS_HELP); +} diff --git a/tests/help.rs b/tests/help.rs index 5cc72fbb..5d8fa633 100644 --- a/tests/help.rs +++ b/tests/help.rs @@ -262,13 +262,8 @@ fn no_wrap_help() { #[test] fn complex_subcommand_help_output() { - let mut a = test::complex_app(); - let _ = a.get_matches_from_safe_borrow(vec![""]); - let sc = a.p.subcommands.iter().filter(|s| s.p.meta.name == "subcmd").next().unwrap(); - // Now we check the output of print_help() - let mut help = vec![]; - sc.write_help(&mut help).ok().expect("failed to print help"); - assert_eq!(&*String::from_utf8(help).unwrap(), SC_HELP); + let a = test::complex_app(); + test::check_subcommand_help(a, "subcmd", SC_HELP); }