Skip to content

Commit

Permalink
feat(SubCommands): adds support for subcommand aliases
Browse files Browse the repository at this point in the history
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
  • Loading branch information
kbknapp committed May 10, 2016
1 parent b027c65 commit 66b4dea
Show file tree
Hide file tree
Showing 3 changed files with 83 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

0 comments on commit 66b4dea

Please sign in to comment.