From 66b4dea65c44d8f77ff522238a9237aed1bcab6d Mon Sep 17 00:00:00 2001 From: Kevin K Date: Tue, 10 May 2016 15:21:19 -0400 Subject: [PATCH] feat(SubCommands): adds support for subcommand aliases 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. Example: ``` 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")); ``` Example using multiple aliases: ``` 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")); ``` Closes #469 --- src/app/meta.rs | 3 +++ src/app/mod.rs | 50 +++++++++++++++++++++++++++++++++++++++++++++++ src/app/parser.rs | 32 ++++++++++++++++++++++++++++-- 3 files changed, 83 insertions(+), 2 deletions(-) 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);