diff --git a/src/app/meta.rs b/src/app/meta.rs index edabfbba791..5b99d0c2403 100644 --- a/src/app/meta.rs +++ b/src/app/meta.rs @@ -8,6 +8,7 @@ pub struct AppMeta<'b> { pub about: Option<&'b str>, pub more_help: Option<&'b str>, pub pre_help: Option<&'b str>, + pub aliases: Option>, pub usage_str: Option<&'b str>, pub usage: Option, pub help_str: Option<&'b str>, @@ -30,6 +31,7 @@ impl<'b> Default for AppMeta<'b> { help_str: None, disp_ord: 999, template: None, + aliases: None, } } } @@ -58,6 +60,7 @@ impl<'b> Clone for AppMeta<'b> { help_str: self.help_str, disp_ord: self.disp_ord, template: self.template, + aliases: self.aliases.clone(), } } } diff --git a/src/app/mod.rs b/src/app/mod.rs index 53ef1ab7f9c..b3db33907c1 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -504,6 +504,56 @@ impl<'a, 'b> App<'a, 'b> { self } + /// Allows adding a subcommand alias, which function as "hidden" subcommands that automatically + /// dispatch as if this subcommand was used. This is more efficient, and easier than creating + /// multiple hidden subcommands as one only needs to check for the existing of this command, + /// and not all vairants. + /// + /// # Examples + /// + /// ```no_run + /// # use clap::{App, Arg, SubCommand}; + /// let m = App::new("myprog") + /// .subcommand(SubCommand::with_name("test") + /// .alias("do-stuff")) + /// .get_matches_from(vec!["myprog", "do-stuff"]); + /// assert_eq!(m.subcommand_name(), Some("test")); + /// ``` + pub fn alias>(mut self, name: S) -> Self { + if let Some(ref mut als) = self.p.meta.aliases { + als.push(name.into()); + } else { + self.p.meta.aliases = Some(vec![name.into()]); + } + self + } + + /// Allows adding subcommand aliases, which function as "hidden" subcommands that automatically + /// dispatch as if this subcommand was used. This is more efficient, and easier than creating + /// multiple hidden subcommands as one only needs to check for the existing of this command, + /// and not all vairants. + /// + /// # Examples + /// + /// ```no_run + /// # use clap::{App, Arg, SubCommand}; + /// let m = App::new("myprog") + /// .subcommand(SubCommand::with_name("test") + /// .aliases(&["do-stuff", "do-tests", "tests"])) + /// .get_matches_from(vec!["myprog", "do-tests"]); + /// assert_eq!(m.subcommand_name(), Some("test")); + /// ``` + pub fn aliases + 'b>(mut self, names: &'b [S]) -> Self { + if let Some(ref mut als) = self.p.meta.aliases { + for n in names { + als.push(n.as_ref()); + } + } else { + self.p.meta.aliases = Some(names.iter().map(|n| n.as_ref()).collect()); + } + self + } + /// Adds an `ArgGroup` to the application. `ArgGroup`s are a family of related arguments. By /// placing them in a logical group, you can build easier requirement and exclusion rules. For /// instance, you can make an entire `ArgGroup` required, meaning that one (and *only* one) diff --git a/src/app/parser.rs b/src/app/parser.rs index e369a7c32ae..ecdaaa855f8 100644 --- a/src/app/parser.rs +++ b/src/app/parser.rs @@ -466,7 +466,16 @@ impl<'a, 'b> Parser<'a, 'b> // Has the user already passed '--'? if !pos_only { - let pos_sc = self.subcommands.iter().any(|s| &s.p.meta.name[..] == &*arg_os); + // Does the arg match a subcommand name, or any of it's aliases (if defined) + let pos_sc = self.subcommands + .iter() + .any(|s| &s.p.meta.name[..] == &*arg_os || + (s.p.meta.aliases.is_some() && + s.p.meta.aliases + .as_ref() + .unwrap() + .iter() + .any(|&a| a == &*arg_os))); if (!starts_new_arg || self.is_set(AppSettings::AllowLeadingHyphen)) && !pos_sc { // Check to see if parsing a value from an option if let Some(nvo) = needs_val_of { @@ -603,7 +612,26 @@ impl<'a, 'b> Parser<'a, 'b> !reqs_validated { try!(self.validate_required(matcher)); } - if let Some(sc_name) = subcmd_name { + if let Some(pos_sc_name) = subcmd_name { + // is this is a real subcommand, or an alias + let sc_name = if self.subcommands.iter().any(|sc| sc.p.meta.name == pos_sc_name) { + pos_sc_name + } else { + self.subcommands + .iter() + .filter(|sc| sc.p.meta.aliases.is_some()) + .filter_map(|sc| if sc.p.meta.aliases + .as_ref() + .unwrap() + .iter() + .any(|&a| a == &*pos_sc_name) { + Some(sc.p.meta.name.clone()) + } else { + None + }) + .next() + .expect(INTERNAL_ERROR_MSG) + }; try!(self.parse_subcommand(sc_name, matcher, it)); } else if self.is_set(AppSettings::SubcommandRequired) { let bn = self.meta.bin_name.as_ref().unwrap_or(&self.meta.name); diff --git a/tests/subcommands.rs b/tests/subcommands.rs index a3b140f7f63..2d055461ef7 100644 --- a/tests/subcommands.rs +++ b/tests/subcommands.rs @@ -62,6 +62,24 @@ fn subcommand_multiple() { assert_eq!(sub_m.value_of("test").unwrap(), "testing"); } +#[test] +fn single_alias() { + let m = App::new("myprog") + .subcommand(SubCommand::with_name("test") + .alias("do-stuff")) + .get_matches_from(vec!["myprog", "do-stuff"]); + assert_eq!(m.subcommand_name(), Some("test")); +} + +#[test] +fn multiple_aliases() { + let m = App::new("myprog") + .subcommand(SubCommand::with_name("test") + .aliases(&["do-stuff", "test-stuff"])) + .get_matches_from(vec!["myprog", "test-stuff"]); + assert_eq!(m.subcommand_name(), Some("test")); +} + #[test] fn subcmd_did_you_mean_output() { test::check_err_output(test::complex_app(), "clap-test subcm",