Skip to content

Commit

Permalink
Auto merge of #674 - afiune:afiune/arg-aliases, r=kbknapp
Browse files Browse the repository at this point in the history
feat(arg_aliases): Ability to alias arguments

There are some cases where you need to have an argument to have an
alias, an example could be when you deprecate one option in favor of
another one.

Now you are going to be able to alias arguments as follows:
```rust
Arg::with_name("opt")
    .long("opt")
    .short("o")
    .takes_value(true)
    .alias("invisible")
    .visible_alias("visible")
```

Closes #669

Also you can alias flags as follow:
```rust
Arg::with_name("flg")
    .long("flag")
    .short("f")
    .alias("not_visible_flag")
    .visible_alias("awesome_v_flag")
```
  • Loading branch information
homu committed Oct 4, 2016
2 parents 4c0ed77 + 905d3b9 commit 4e7466c
Show file tree
Hide file tree
Showing 11 changed files with 505 additions and 33 deletions.
11 changes: 11 additions & 0 deletions clap-test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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![""]);
Expand Down
22 changes: 11 additions & 11 deletions src/app/help.rs
Original file line number Diff line number Diff line change
Expand Up @@ -212,8 +212,8 @@ impl<'a> Help<'a> {
}
}
let mut first = true;
for (_, btm) in ord_m.into_iter() {
for (_, arg) in btm.into_iter() {
for (_, btm) in ord_m {
for arg in btm.values() {
if !first {
try!(self.writer.write(b"\n"));
} else {
Expand Down Expand Up @@ -380,11 +380,11 @@ impl<'a> Help<'a> {
help.push_str(h);
&*help
};
if help.contains("\n") {
if let Some(part) = help.split("\n").next() {
if help.contains('\n') {
if let Some(part) = help.split('\n').next() {
try!(write!(self.writer, "{}", part));
}
for part in help.split("\n").skip(1) {
for part in help.split('\n').skip(1) {
try!(write!(self.writer, "\n{}", part));
}
} else {
Expand Down Expand Up @@ -464,11 +464,11 @@ impl<'a> Help<'a> {
help.push_str(&*spec_vals);
&*help
};
if help.contains("\n") {
if let Some(part) = help.split("\n").next() {
if help.contains('\n') {
if let Some(part) = help.split('\n').next() {
try!(write!(self.writer, "{}", part));
}
for part in help.split("\n").skip(1) {
for part in help.split('\n').skip(1) {
try!(write!(self.writer, "\n"));
if nlh || force_next_line {
try!(write!(self.writer, "{}{}{}", TAB, TAB, TAB));
Expand Down Expand Up @@ -616,14 +616,14 @@ impl<'a> Help<'a> {
}

let mut first = true;
for (_, btm) in ord_m.into_iter() {
for (_, sc) in btm.into_iter() {
for (_, btm) in ord_m {
for sc in btm.values() {
if !first {
try!(self.writer.write(b"\n"));
} else {
first = false;
}
try!(self.write_arg(&sc, longest));
try!(self.write_arg(sc, longest));
}
}
Ok(())
Expand Down
2 changes: 1 addition & 1 deletion src/app/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -928,7 +928,7 @@ impl<'a, 'b> App<'a, 'b> {
pub fn subcommands<I>(mut self, subcmds: I) -> Self
where I: IntoIterator<Item = App<'a, 'b>>
{
for subcmd in subcmds.into_iter() {
for subcmd in subcmds {
self.p.add_subcommand(subcmd);
}
self
Expand Down
56 changes: 47 additions & 9 deletions src/app/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -355,13 +355,13 @@ impl<'a, 'b> Parser<'a, 'b>
grps.dedup();
let mut args_in_groups = vec![];
for g in &grps {
for a in self.arg_names_in_group(g).into_iter() {
for a in self.arg_names_in_group(g) {
args_in_groups.push(a);
}
}

let mut pmap = BTreeMap::new();
for p in c_pos.into_iter() {
for p in c_pos {
if matcher.is_some() && matcher.as_ref().unwrap().contains(p) {
continue;
}
Expand All @@ -382,7 +382,7 @@ impl<'a, 'b> Parser<'a, 'b>
}
macro_rules! write_arg {
($i:expr, $m:ident, $v:ident, $r:ident, $aig:ident) => {
for f in $v.into_iter() {
for f in $v {
if $m.is_some() && $m.as_ref().unwrap().contains(f) || $aig.contains(&f) {
continue;
}
Expand All @@ -393,13 +393,13 @@ impl<'a, 'b> Parser<'a, 'b>
write_arg!(self.flags.iter(), matcher, c_flags, ret_val, args_in_groups);
write_arg!(self.opts.iter(), matcher, c_opt, ret_val, args_in_groups);
let mut g_vec = vec![];
for g in grps.into_iter() {
for g in grps {
let g_string = self.args_in_group(g)
.join("|");
g_vec.push(format!("<{}>", &g_string[..g_string.len()]));
}
g_vec.dedup();
for g in g_vec.into_iter() {
for g in g_vec {
ret_val.push_back(g);
}

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -1213,15 +1233,33 @@ 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);

return Ok(ret);
} else if let Some(flag) = self.flags
.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 flag '{}'", flag.to_string());
// Only flags could be help or version, and we need to check the raw long
// so this is the first point to check
Expand Down
118 changes: 118 additions & 0 deletions src/args/arg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ pub struct Arg<'a, 'b>
#[doc(hidden)]
pub long: Option<&'b str>,
#[doc(hidden)]
pub aliases: Option<Vec<(&'b str, bool)>>, // (name, visible)
#[doc(hidden)]
pub help: Option<&'b str>,
#[doc(hidden)]
pub index: Option<u64>,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -409,6 +413,118 @@ impl<'a, 'b> Arg<'a, 'b> {
self
}

/// Allows adding a [`Arg`] alias, which function as "hidden" arguments that
/// automatically dispatch as if this argument was used. This is more efficient, and easier
/// than creating multiple hidden arguments as one only needs to check for the existence of
/// this command, and not all variants.
///
/// # Examples
///
/// ```rust
/// # use clap::{App, Arg};
/// let m = App::new("myprog")
/// .arg(Arg::with_name("test")
/// .long("test")
/// .alias("alias")
/// .takes_value(true))
/// .get_matches_from(vec!["myprog", "--alias", "cool"]);
/// assert!(m.is_present("test"));
/// assert_eq!(m.value_of("test"), Some("cool"));
/// ```
/// [`Arg`]: ./struct.Arg.html
pub fn alias<S: Into<&'b str>>(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
}

/// Allows adding [`Arg`] aliases, which function as "hidden" arguments that
/// automatically dispatch as if this argument was used. This is more efficient, and easier
/// than creating multiple hidden subcommands as one only needs to check for the existence of
/// this command, and not all variants.
///
/// # Examples
///
/// ```rust
/// # use clap::{App, Arg};
/// let m = App::new("myprog")
/// .arg(Arg::with_name("test")
/// .long("test")
/// .aliases(&["do-stuff", "do-tests", "tests"])
/// .help("the file to add")
/// .required(false))
/// .get_matches_from(vec!["myprog", "--do-tests"]);
/// assert!(m.is_present("test"));
/// ```
/// [`Arg`]: ./struct.Arg.html
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::<Vec<_>>());
}
self
}

/// Allows adding a [`Arg`] alias that functions exactly like those defined with
/// [`Arg::alias`], except that they are visible inside the help message.
///
/// # Examples
///
/// ```rust
/// # use clap::{App, Arg};
/// let m = App::new("myprog")
/// .arg(Arg::with_name("test")
/// .visible_alias("something-awesome")
/// .long("test")
/// .takes_value(true))
/// .get_matches_from(vec!["myprog", "--something-awesome", "coffee"]);
/// assert!(m.is_present("test"));
/// assert_eq!(m.value_of("test"), Some("coffee"));
/// ```
/// [`Arg`]: ./struct.Arg.html
/// [`App::alias`]: ./struct.Arg.html#method.alias
pub fn visible_alias<S: Into<&'b str>>(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
}

/// Allows adding multiple [`Arg`] aliases that functions exactly like those defined
/// with [`Arg::aliases`], except that they are visible inside the help message.
///
/// # Examples
///
/// ```rust
/// # use clap::{App, Arg};
/// let m = App::new("myprog")
/// .arg(Arg::with_name("test")
/// .long("test")
/// .visible_aliases(&["something", "awesome", "cool"]))
/// .get_matches_from(vec!["myprog", "--awesome"]);
/// assert!(m.is_present("test"));
/// ```
/// [`Arg`]: ./struct.Arg.html
/// [`App::aliases`]: ./struct.Arg.html#method.aliases
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::<Vec<_>>());
}
self
}

/// Sets the help text of the argument that will be displayed to the user when they print the
/// usage/help information.
///
Expand Down Expand Up @@ -2380,6 +2496,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(),
Expand Down Expand Up @@ -2407,6 +2524,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(),
Expand Down
Loading

0 comments on commit 4e7466c

Please sign in to comment.