Skip to content

Commit

Permalink
Auto merge of #501 - kbknapp:issue-469, r=kbknapp
Browse files Browse the repository at this point in the history
Issue 469
  • Loading branch information
homu committed May 10, 2016
2 parents b027c65 + fd8e211 commit 1de71c0
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 2 deletions.
3 changes: 3 additions & 0 deletions src/app/meta.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Vec<&'b str>>,
pub usage_str: Option<&'b str>,
pub usage: Option<String>,
pub help_str: Option<&'b str>,
Expand All @@ -30,6 +31,7 @@ impl<'b> Default for AppMeta<'b> {
help_str: None,
disp_ord: 999,
template: None,
aliases: None,
}
}
}
Expand Down Expand Up @@ -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(),
}
}
}
50 changes: 50 additions & 0 deletions src/app/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<S: Into<&'b str>>(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<S: AsRef<str> + '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)
Expand Down
32 changes: 30 additions & 2 deletions src/app/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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);
Expand Down
18 changes: 18 additions & 0 deletions tests/subcommands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down

0 comments on commit 1de71c0

Please sign in to comment.