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()); 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) { pub fn check_help(mut a: App, out: &str) {
// We call a get_matches method to cause --help and --version to be built // We call a get_matches method to cause --help and --version to be built
let _ = a.get_matches_from_safe_borrow(vec![""]); 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 // Check to see if parsing a value from an option
if let Some(nvo) = needs_val_of { if let Some(nvo) = needs_val_of {
// get the OptBuilder so we can check the settings // 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)); needs_val_of = try!(self.add_val_to_arg(opt, &arg_os, matcher));
// get the next value from the iterator // get the next value from the iterator
continue; continue;
@ -765,7 +775,17 @@ impl<'a, 'b> Parser<'a, 'b>
if let Some(a) = needs_val_of { if let Some(a) = needs_val_of {
debugln!("needs_val_of={}", a); debugln!("needs_val_of={}", a);
debug!("Is this an opt..."); 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"); sdebugln!("Yes");
try!(self.validate_required(matcher)); try!(self.validate_required(matcher));
reqs_validated = true; reqs_validated = true;
@ -1213,7 +1233,16 @@ impl<'a, 'b> Parser<'a, 'b>
if let Some(opt) = self.opts if let Some(opt) = self.opts
.iter() .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()); debugln!("Found valid opt '{}'", opt.to_string());
let ret = try!(self.parse_opt(val, opt, matcher)); let ret = try!(self.parse_opt(val, opt, matcher));
arg_post_processing!(self, opt, matcher); arg_post_processing!(self, opt, matcher);

View file

@ -42,6 +42,8 @@ pub struct Arg<'a, 'b>
#[doc(hidden)] #[doc(hidden)]
pub long: Option<&'b str>, pub long: Option<&'b str>,
#[doc(hidden)] #[doc(hidden)]
pub aliases: Option<Vec<(&'b str, bool)>>, // (name, visible)
#[doc(hidden)]
pub help: Option<&'b str>, pub help: Option<&'b str>,
#[doc(hidden)] #[doc(hidden)]
pub index: Option<u64>, pub index: Option<u64>,
@ -83,6 +85,7 @@ impl<'a, 'b> Default for Arg<'a, 'b> {
name: "".as_ref(), name: "".as_ref(),
short: None, short: None,
long: None, long: None,
aliases: None,
help: None, help: None,
index: None, index: None,
blacklist: None, blacklist: None,
@ -149,6 +152,7 @@ impl<'a, 'b> Arg<'a, 'b> {
a = match k.as_str().unwrap() { a = match k.as_str().unwrap() {
"short" => yaml_to_str!(a, v, short), "short" => yaml_to_str!(a, v, short),
"long" => yaml_to_str!(a, v, long), "long" => yaml_to_str!(a, v, long),
"aliases" => yaml_vec_or_str!(v, a, alias),
"help" => yaml_to_str!(a, v, help), "help" => yaml_to_str!(a, v, help),
"required" => yaml_to_bool!(a, v, required), "required" => yaml_to_bool!(a, v, required),
"takes_value" => yaml_to_bool!(a, v, takes_value), "takes_value" => yaml_to_bool!(a, v, takes_value),
@ -409,6 +413,50 @@ impl<'a, 'b> Arg<'a, 'b> {
self 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 /// Sets the help text of the argument that will be displayed to the user when they print the
/// usage/help information. /// usage/help information.
/// ///
@ -2380,6 +2428,7 @@ impl<'a, 'b, 'z> From<&'z Arg<'a, 'b>> for Arg<'a, 'b> {
name: a.name, name: a.name,
short: a.short, short: a.short,
long: a.long, long: a.long,
aliases: a.aliases.clone(),
help: a.help, help: a.help,
index: a.index, index: a.index,
possible_vals: a.possible_vals.clone(), possible_vals: a.possible_vals.clone(),
@ -2407,6 +2456,7 @@ impl<'a, 'b> Clone for Arg<'a, 'b> {
name: self.name, name: self.name,
short: self.short, short: self.short,
long: self.long, long: self.long,
aliases: self.aliases.clone(),
help: self.help, help: self.help,
index: self.index, index: self.index,
possible_vals: self.possible_vals.clone(), possible_vals: self.possible_vals.clone(),

View file

@ -16,6 +16,7 @@ pub struct OptBuilder<'n, 'e> {
pub name: &'n str, pub name: &'n str,
pub short: Option<char>, pub short: Option<char>,
pub long: Option<&'e str>, pub long: Option<&'e str>,
pub aliases: Option<Vec<(&'e str, bool)>>,
pub help: Option<&'e str>, pub help: Option<&'e str>,
pub blacklist: Option<Vec<&'e str>>, pub blacklist: Option<Vec<&'e str>>,
pub possible_vals: Option<Vec<&'e str>>, pub possible_vals: Option<Vec<&'e str>>,
@ -39,6 +40,7 @@ impl<'n, 'e> Default for OptBuilder<'n, 'e> {
name: "", name: "",
short: None, short: None,
long: None, long: None,
aliases: None,
help: None, help: None,
blacklist: None, blacklist: None,
possible_vals: None, possible_vals: None,
@ -74,6 +76,7 @@ impl<'n, 'e> OptBuilder<'n, 'e> {
name: a.name, name: a.name,
short: a.short, short: a.short,
long: a.long, long: a.long,
aliases: a.aliases.clone(),
help: a.help, help: a.help,
num_vals: a.num_vals, num_vals: a.num_vals,
min_vals: a.min_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(()) Ok(())
} }
} }
@ -167,6 +185,7 @@ impl<'n, 'e> Clone for OptBuilder<'n, 'e> {
name: self.name, name: self.name,
short: self.short, short: self.short,
long: self.long, long: self.long,
aliases: self.aliases.clone(),
help: self.help, help: self.help,
blacklist: self.blacklist.clone(), blacklist: self.blacklist.clone(),
overrides: self.overrides.clone(), overrides: self.overrides.clone(),
@ -252,7 +271,19 @@ impl<'n, 'e> AnyArg<'n, 'e> for OptBuilder<'n, 'e> {
true true
} }
fn aliases(&self) -> Option<Vec<&'e str>> { 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>"); 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 help: Tests mutliple values with required delimiter
multiple: true multiple: true
require_delimiter: 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: - minvals2:
long: minvals2 long: minvals2
multiple: true 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] #[test]
fn complex_subcommand_help_output() { fn complex_subcommand_help_output() {
let mut a = test::complex_app(); let a = test::complex_app();
let _ = a.get_matches_from_safe_borrow(vec![""]); test::check_subcommand_help(a, "subcmd", SC_HELP);
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);
} }