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 depricate one option in favor of
another one.

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

Closes #669
This commit is contained in:
Salim Afiune 2016-09-30 17:54:14 -04:00
parent 4c0ed77601
commit 33b5f6ef2c
7 changed files with 291 additions and 11 deletions

View file

@ -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![""]);

View file

@ -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;
@ -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;
@ -1213,7 +1233,16 @@ 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);

View file

@ -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>,
@ -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,
@ -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),
@ -409,6 +413,50 @@ impl<'a, 'b> Arg<'a, 'b> {
self
}
/// Add docs
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
}
/// Add docs
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
}
/// Add docs
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
}
/// Add docs
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.
///
@ -2380,6 +2428,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(),
@ -2407,6 +2456,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(),

View file

@ -16,6 +16,7 @@ pub struct OptBuilder<'n, 'e> {
pub name: &'n str,
pub short: Option<char>,
pub long: Option<&'e str>,
pub aliases: Option<Vec<(&'e str, bool)>>,
pub help: Option<&'e str>,
pub blacklist: Option<Vec<&'e str>>,
pub possible_vals: Option<Vec<&'e str>>,
@ -39,6 +40,7 @@ impl<'n, 'e> Default for OptBuilder<'n, 'e> {
name: "",
short: None,
long: None,
aliases: None,
help: None,
blacklist: None,
possible_vals: None,
@ -74,6 +76,7 @@ impl<'n, 'e> OptBuilder<'n, 'e> {
name: a.name,
short: a.short,
long: a.long,
aliases: a.aliases.clone(),
help: a.help,
num_vals: a.num_vals,
min_vals: a.min_vals,
@ -157,6 +160,21 @@ impl<'n, 'e> Display for OptBuilder<'n, 'e> {
}));
}
// Write aliases such as [aliases: alias, new-alias]
if let Some(ref vec) = self.aliases {
try!(write!(f, " [aliases: "));
let mut it = vec.iter().peekable();
while let Some(&(val, b)) = it.next() {
if b {
try!(write!(f, "{}", val));
if it.peek().is_some() {
try!(write!(f, ", "));
}
}
}
try!(write!(f, "]"));
}
Ok(())
}
}
@ -167,6 +185,7 @@ impl<'n, 'e> Clone for OptBuilder<'n, 'e> {
name: self.name,
short: self.short,
long: self.long,
aliases: self.aliases.clone(),
help: self.help,
blacklist: self.blacklist.clone(),
overrides: self.overrides.clone(),
@ -252,7 +271,19 @@ impl<'n, 'e> AnyArg<'n, 'e> for OptBuilder<'n, 'e> {
true
}
fn aliases(&self) -> Option<Vec<&'e str>> {
None
if let Some(ref aliases) = self.aliases {
let vis_aliases: Vec<_> =
aliases.iter()
.filter_map(|&(n, v)| if v { Some(n) } else { None })
.collect();
if vis_aliases.is_empty() {
None
} else {
Some(vis_aliases)
}
} else {
None
}
}
}
@ -303,4 +334,25 @@ mod test {
assert_eq!(&*format!("{}", o2), "-o <file> <name>");
}
#[test]
fn optbuilder_display_single_alias() {
let mut o = OptBuilder::new("opt");
o.long = Some("option");
o.aliases = Some(vec![("als", true)]);
assert_eq!(&*format!("{}", o), "--option <opt> [aliases: als]");
}
fn optbuilder_display_multiple_aliases() {
let mut o = OptBuilder::new("opt");
o.long = Some("option");
o.aliases = Some(vec![
("als_not_visible", false),
("als2", true),
("als3", true),
("als4", true)
]);
assert_eq!(&*format!("{}", o), "--option <opt> [aliases: als2, als3, als4]");
}
}

View file

@ -64,6 +64,14 @@ args:
help: Tests mutliple values with required delimiter
multiple: true
require_delimiter: true
- singlealias:
long: singlealias
help: Tests single alias
aliases: [alias]
- multaliases:
long: multaliases
help: Tests mutliple aliases
aliases: [als1, als2, als3]
- minvals2:
long: minvals2
multiple: true

135
tests/arg_aliases.rs Normal file
View file

@ -0,0 +1,135 @@
extern crate clap;
extern crate regex;
include!("../clap-test.rs");
use clap::{App, Arg, SubCommand};
static SC_VISIBLE_ALIAS_HELP: &'static str = "test
Some help
USAGE:
test [OPTIONS]
OPTIONS:
--opt <opt> [aliases: visible]";
static SC_INVISIBLE_ALIAS_HELP: &'static str = "test
Some help
USAGE:
test [OPTIONS]
OPTIONS:
--opt <opt> ";
#[test]
fn single_alias_of_option_long() {
let a = App::new("single_alias")
.arg(Arg::with_name("alias")
.long("alias")
.takes_value(true)
.help("single alias")
.alias("new-opt"))
.get_matches_from_safe(vec![
"", "--new-opt", "cool"
]);
assert!(a.is_ok());
let a = a.unwrap();
assert!(a.is_present("alias"));
assert_eq!(a.value_of("alias").unwrap(), "cool");
}
#[test]
fn multiple_aliases_of_option_long() {
let a = App::new("multiple_aliases")
.arg(Arg::with_name("aliases")
.long("aliases")
.takes_value(true)
.help("multiple aliases")
.aliases(&vec![
"alias1",
"alias2",
"alias3"
]));
let long = a.clone().get_matches_from_safe(vec![
"", "--aliases", "value"
]);
assert!(long.is_ok());
let long = long.unwrap();
let als1 = a.clone().get_matches_from_safe(vec![
"", "--alias1", "value"
]);
assert!(als1.is_ok());
let als1 = als1.unwrap();
let als2 = a.clone().get_matches_from_safe(vec![
"", "--alias2", "value"
]);
assert!(als2.is_ok());
let als2 = als2.unwrap();
let als3 = a.clone().get_matches_from_safe(vec![
"", "--alias3", "value"
]);
assert!(als3.is_ok());
let als3 = als3.unwrap();
assert!(long.is_present("aliases"));
assert!(als1.is_present("aliases"));
assert!(als2.is_present("aliases"));
assert!(als3.is_present("aliases"));
assert_eq!(long.value_of("aliases").unwrap(), "value");
assert_eq!(als1.value_of("aliases").unwrap(), "value");
assert_eq!(als2.value_of("aliases").unwrap(), "value");
assert_eq!(als3.value_of("aliases").unwrap(), "value");
}
#[test]
fn alias_on_a_subcommand_option() {
let m = App::new("test")
.subcommand(SubCommand::with_name("some")
.arg(Arg::with_name("test")
.short("t")
.long("test")
.takes_value(true)
.alias("opt")
.help("testing testing")))
.arg(Arg::with_name("other")
.long("other")
.aliases(&vec!["o1", "o2", "o3"]))
.get_matches_from(vec![
"test", "some", "--opt", "awesome"
]);
assert!(m.subcommand_matches("some").is_some());
let sub_m = m.subcommand_matches("some").unwrap();
assert!(sub_m.is_present("test"));
assert_eq!(sub_m.value_of("test").unwrap(), "awesome");
}
#[test]
fn invisible_arg_aliases_help_output() {
let app = App::new("clap-test")
.subcommand(SubCommand::with_name("test")
.about("Some help")
.arg(Arg::with_name("opt")
.long("opt")
.takes_value(true)
.aliases(&["invisible", "als1", "more"])));
test::check_subcommand_help(app, "test", SC_INVISIBLE_ALIAS_HELP);
}
#[test]
fn visible_arg_aliases_help_output() {
let app = App::new("clap-test")
.subcommand(SubCommand::with_name("test")
.about("Some help")
.arg(Arg::with_name("opt")
.long("opt")
.takes_value(true)
.alias("invisible")
.visible_alias("visible")));
test::check_subcommand_help(app, "test", SC_VISIBLE_ALIAS_HELP);
}

View file

@ -262,13 +262,8 @@ fn no_wrap_help() {
#[test]
fn complex_subcommand_help_output() {
let mut a = test::complex_app();
let _ = a.get_matches_from_safe_borrow(vec![""]);
let sc = a.p.subcommands.iter().filter(|s| s.p.meta.name == "subcmd").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!(&*String::from_utf8(help).unwrap(), SC_HELP);
let a = test::complex_app();
test::check_subcommand_help(a, "subcmd", SC_HELP);
}