1901: implement Arg::short_alias and Arg::short_aliases r=pksunkara a=connorskees



Co-authored-by: ConnorSkees <39542938+ConnorSkees@users.noreply.github.com>
This commit is contained in:
bors[bot] 2020-05-11 16:03:51 +00:00 committed by GitHub
commit 33c63c6e29
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 431 additions and 7 deletions

View file

@ -67,6 +67,7 @@ pub struct Arg<'help> {
pub(crate) short: Option<char>, pub(crate) short: Option<char>,
pub(crate) long: Option<&'help str>, pub(crate) long: Option<&'help str>,
pub(crate) aliases: Option<Vec<(&'help str, bool)>>, // (name, visible) pub(crate) aliases: Option<Vec<(&'help str, bool)>>, // (name, visible)
pub(crate) short_aliases: Vec<(char, bool)>, // (name, visible)
pub(crate) disp_ord: usize, pub(crate) disp_ord: usize,
pub(crate) unified_ord: usize, pub(crate) unified_ord: usize,
pub(crate) possible_vals: Option<Vec<&'help str>>, pub(crate) possible_vals: Option<Vec<&'help str>>,
@ -195,6 +196,7 @@ impl<'help> Arg<'help> {
"short" => yaml_to_char!(a, v, short), "short" => yaml_to_char!(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), "aliases" => yaml_vec_or_str!(v, a, alias),
"short_aliases" => yaml_to_chars!(a, v, short_aliases),
"about" => yaml_to_str!(a, v, about), "about" => yaml_to_str!(a, v, about),
"long_about" => yaml_to_str!(a, v, long_about), "long_about" => yaml_to_str!(a, v, long_about),
"help" => yaml_to_str!(a, v, about), "help" => yaml_to_str!(a, v, about),
@ -364,6 +366,36 @@ impl<'help> Arg<'help> {
self 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("prog")
/// .arg(Arg::with_name("test")
/// .short('t')
/// .short_alias('e')
/// .takes_value(true))
/// .get_matches_from(vec![
/// "prog", "-e", "cool"
/// ]);
/// assert!(m.is_present("test"));
/// assert_eq!(m.value_of("test"), Some("cool"));
/// ```
/// [`Arg`]: ./struct.Arg.html
pub fn short_alias(mut self, name: char) -> Self {
if name == '-' {
panic!("short alias name cannot be `-`");
}
self.short_aliases.push((name, false));
self
}
/// Allows adding [`Arg`] aliases, which function as "hidden" arguments that /// Allows adding [`Arg`] aliases, which function as "hidden" arguments that
/// automatically dispatch as if this argument was used. This is more efficient, and easier /// 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 /// than creating multiple hidden subcommands as one only needs to check for the existence of
@ -396,6 +428,37 @@ impl<'help> Arg<'help> {
self 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("prog")
/// .arg(Arg::with_name("test")
/// .short('t')
/// .short_aliases(&['e', 's'])
/// .help("the file to add")
/// .required(false))
/// .get_matches_from(vec![
/// "prog", "-s"
/// ]);
/// assert!(m.is_present("test"));
/// ```
/// [`Arg`]: ./struct.Arg.html
pub fn short_aliases(mut self, names: &[char]) -> Self {
for s in names {
if s == &'-' {
panic!("short alias name cannot be `-`");
}
self.short_aliases.push((*s, false));
}
self
}
/// Allows adding a [`Arg`] alias that functions exactly like those defined with /// Allows adding a [`Arg`] alias that functions exactly like those defined with
/// [`Arg::alias`], except that they are visible inside the help message. /// [`Arg::alias`], except that they are visible inside the help message.
/// ///
@ -425,6 +488,35 @@ impl<'help> Arg<'help> {
self 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("prog")
/// .arg(Arg::with_name("test")
/// .long("test")
/// .visible_short_alias('t')
/// .takes_value(true))
/// .get_matches_from(vec![
/// "prog", "-t", "coffee"
/// ]);
/// assert!(m.is_present("test"));
/// assert_eq!(m.value_of("test"), Some("coffee"));
/// ```
/// [`Arg`]: ./struct.Arg.html
/// [`App::alias`]: ./struct.Arg.html#method.short_alias
pub fn visible_short_alias(mut self, name: char) -> Self {
if name == '-' {
panic!("short alias name cannot be `-`");
}
self.short_aliases.push((name, true));
self
}
/// Allows adding multiple [`Arg`] aliases that functions exactly like those defined /// Allows adding multiple [`Arg`] aliases that functions exactly like those defined
/// with [`Arg::aliases`], except that they are visible inside the help message. /// with [`Arg::aliases`], except that they are visible inside the help message.
/// ///
@ -454,6 +546,34 @@ impl<'help> Arg<'help> {
self 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("prog")
/// .arg(Arg::with_name("test")
/// .long("test")
/// .visible_short_aliases(&['t', 'e']))
/// .get_matches_from(vec![
/// "prog", "-t"
/// ]);
/// assert!(m.is_present("test"));
/// ```
/// [`Arg`]: ./struct.Arg.html
/// [`App::aliases`]: ./struct.Arg.html#method.short_aliases
pub fn visible_short_aliases(mut self, names: &[char]) -> Self {
for n in names {
if n == &'-' {
panic!("short alias name cannot be `-`");
}
self.short_aliases.push((*n, true));
}
self
}
/// See [`Arg::about`](./struct.Arg.html#method.about) /// See [`Arg::about`](./struct.Arg.html#method.about)
#[deprecated(since = "3.0.0", note = "Please use `about` method instead")] #[deprecated(since = "3.0.0", note = "Please use `about` method instead")]
#[inline] #[inline]
@ -4363,10 +4483,10 @@ impl<'help> fmt::Debug for Arg<'help> {
"Arg {{ id: {:X?}, name: {:?}, help: {:?}, long_help: {:?}, conflicts_with: {:?}, \ "Arg {{ id: {:X?}, name: {:?}, help: {:?}, long_help: {:?}, conflicts_with: {:?}, \
settings: {:?}, required_unless: {:?}, overrides_with: {:?}, groups: {:?}, \ settings: {:?}, required_unless: {:?}, overrides_with: {:?}, groups: {:?}, \
requires: {:?}, requires_ifs: {:?}, short: {:?}, index: {:?}, long: {:?}, \ requires: {:?}, requires_ifs: {:?}, short: {:?}, index: {:?}, long: {:?}, \
aliases: {:?}, possible_values: {:?}, value_names: {:?}, number_of_values: {:?}, \ aliases: {:?}, short_aliases: {:?}, possible_values: {:?}, value_names: {:?}, \
max_values: {:?}, min_values: {:?}, value_delimiter: {:?}, default_value_ifs: {:?}, \ number_of_values: {:?}, max_values: {:?}, min_values: {:?}, value_delimiter: {:?}, \
value_terminator: {:?}, display_order: {:?}, env: {:?}, unified_ord: {:?}, \ default_value_ifs: {:?}, value_terminator: {:?}, display_order: {:?}, env: {:?}, \
default_value: {:?}, validator: {}, validator_os: {} \ unified_ord: {:?}, default_value: {:?}, validator: {}, validator_os: {} \
}}", }}",
self.id, self.id,
self.name, self.name,
@ -4383,6 +4503,7 @@ impl<'help> fmt::Debug for Arg<'help> {
self.index, self.index,
self.long, self.long,
self.aliases, self.aliases,
self.short_aliases,
self.possible_vals, self.possible_vals,
self.val_names, self.val_names,
self.num_vals, self.num_vals,
@ -4444,6 +4565,23 @@ mod test {
assert_eq!(&*format!("{}", f), "-f"); assert_eq!(&*format!("{}", f), "-f");
} }
#[test]
fn flag_display_single_short_alias() {
let mut f = Arg::with_name("flg");
f.short = Some('a');
f.short_aliases = vec![('b', true)];
assert_eq!(&*format!("{}", f), "-a")
}
#[test]
fn flag_display_multiple_short_aliases() {
let mut f = Arg::with_name("flg");
f.short = Some('a');
f.short_aliases = vec![('b', false), ('c', true), ('d', true), ('e', true)];
assert_eq!(&*format!("{}", f), "-a");
}
// Options // Options
#[test] #[test]
@ -4496,6 +4634,27 @@ mod test {
assert_eq!(&*format!("{}", o), "--option <opt>"); assert_eq!(&*format!("{}", o), "--option <opt>");
} }
#[test]
fn option_display_single_short_alias() {
let o = Arg::with_name("opt")
.takes_value(true)
.short('a')
.visible_short_alias('b');
assert_eq!(&*format!("{}", o), "-a <opt>");
}
#[test]
fn option_display_multiple_short_aliases() {
let o = Arg::with_name("opt")
.short('a')
.takes_value(true)
.visible_short_aliases(&['b', 'c', 'd'])
.short_alias('e');
assert_eq!(&*format!("{}", o), "-a <opt>");
}
// Positionals // Positionals
#[test] #[test]

View file

@ -87,6 +87,28 @@ macro_rules! yaml_char {
}}; }};
} }
#[cfg(feature = "yaml")]
macro_rules! yaml_chars {
($v:expr) => {{
&$v.as_vec()
.unwrap_or_else(|| panic!("failed to convert YAML {:?} value to a list", $v))
.into_iter()
.map(|s| {
s.as_str()
.unwrap_or_else(|| panic!("failed to convert YAML {:?} value to a string", s))
})
.map(|s| {
let mut chars = s.chars();
let c = chars.next().expect("short aliases must be a single char");
if chars.next().is_some() {
panic!("short aliases must be a single char");
}
c
})
.collect::<Vec<char>>()
}};
}
#[cfg(feature = "yaml")] #[cfg(feature = "yaml")]
macro_rules! yaml_str { macro_rules! yaml_str {
($v:expr) => {{ ($v:expr) => {{
@ -102,6 +124,13 @@ macro_rules! yaml_to_char {
}}; }};
} }
#[cfg(feature = "yaml")]
macro_rules! yaml_to_chars {
($a:ident, $v:ident, $c:ident) => {{
$a.$c(yaml_chars!($v))
}};
}
#[cfg(feature = "yaml")] #[cfg(feature = "yaml")]
macro_rules! yaml_to_str { macro_rules! yaml_to_str {
($a:ident, $v:ident, $c:ident) => {{ ($a:ident, $v:ident, $c:ident) => {{

View file

@ -140,6 +140,9 @@ fn _get_keys(arg: &Arg) -> Vec<KeyType> {
} }
let mut keys = vec![]; let mut keys = vec![];
for short in arg.short_aliases.iter().map(|(c, _)| KeyType::Short(*c)) {
keys.push(short);
}
if let Some(c) = arg.short { if let Some(c) = arg.short {
keys.push(KeyType::Short(c)); keys.push(KeyType::Short(c));
} }

View file

@ -500,6 +500,26 @@ impl<'b, 'c, 'd, 'w> Help<'b, 'c, 'd, 'w> {
spec_vals.push(format!(" [aliases: {}]", als)); spec_vals.push(format!(" [aliases: {}]", als));
} }
} }
if !a.short_aliases.is_empty() {
debug!(
"Help::spec_vals: Found short aliases...{:?}",
a.short_aliases
);
let als = a
.short_aliases
.iter()
.filter(|&als| als.1) // visible
.map(|&als| als.0.to_string()) // name
.collect::<Vec<_>>()
.join(", ");
if !als.is_empty() {
spec_vals.push(format!("[short aliases: {}]", als));
}
}
if !self.hide_pv && !a.is_set(ArgSettings::HidePossibleValues) { if !self.hide_pv && !a.is_set(ArgSettings::HidePossibleValues) {
if let Some(ref pv) = a.possible_vals { if let Some(ref pv) = a.possible_vals {
debug!("Help::spec_vals: Found possible vals...{:?}", pv); debug!("Help::spec_vals: Found possible vals...{:?}", pv);

204
tests/arg_aliases_short.rs Normal file
View file

@ -0,0 +1,204 @@
mod utils;
use clap::{App, Arg};
static SC_VISIBLE_ALIAS_HELP: &str = "ct-test 1.2
Some help
USAGE:
ct test [FLAGS] [OPTIONS]
FLAGS:
-f, --flag [aliases: flag1] [short aliases: a, b, 🦆]
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
-o, --opt <opt> [short aliases: v]";
static SC_INVISIBLE_ALIAS_HELP: &str = "ct-test 1.2
Some help
USAGE:
ct test [FLAGS] [OPTIONS]
FLAGS:
-f, --flag
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
-o, --opt <opt> ";
#[test]
fn single_short_alias_of_option() {
let a = App::new("single_alias")
.arg(
Arg::with_name("alias")
.long("alias")
.takes_value(true)
.about("single short alias")
.short_alias('a'),
)
.try_get_matches_from(vec!["", "-a", "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_short_aliases_of_option() {
let a = App::new("multiple_aliases").arg(
Arg::with_name("aliases")
.long("aliases")
.takes_value(true)
.about("multiple aliases")
.short_aliases(&['1', '2', '3']),
);
let long = a
.clone()
.try_get_matches_from(vec!["", "--aliases", "value"]);
assert!(long.is_ok());
let long = long.unwrap();
let als1 = a.clone().try_get_matches_from(vec!["", "-1", "value"]);
assert!(als1.is_ok());
let als1 = als1.unwrap();
let als2 = a.clone().try_get_matches_from(vec!["", "-2", "value"]);
assert!(als2.is_ok());
let als2 = als2.unwrap();
let als3 = a.clone().try_get_matches_from(vec!["", "-3", "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 single_short_alias_of_flag() {
let a = App::new("test")
.arg(Arg::with_name("flag").long("flag").short_alias('f'))
.try_get_matches_from(vec!["", "-f"]);
assert!(a.is_ok());
let a = a.unwrap();
assert!(a.is_present("flag"));
}
#[test]
fn multiple_short_aliases_of_flag() {
let a = App::new("test").arg(
Arg::with_name("flag")
.long("flag")
.short_aliases(&['a', 'b', 'c', 'd', 'e']),
);
let flag = a.clone().try_get_matches_from(vec!["", "--flag"]);
assert!(flag.is_ok());
let flag = flag.unwrap();
let als1 = a.clone().try_get_matches_from(vec!["", "-a"]);
assert!(als1.is_ok());
let als1 = als1.unwrap();
let als2 = a.clone().try_get_matches_from(vec!["", "-b"]);
assert!(als2.is_ok());
let als2 = als2.unwrap();
let als3 = a.clone().try_get_matches_from(vec!["", "-c"]);
assert!(als3.is_ok());
let als3 = als3.unwrap();
assert!(flag.is_present("flag"));
assert!(als1.is_present("flag"));
assert!(als2.is_present("flag"));
assert!(als3.is_present("flag"));
}
#[test]
fn short_alias_on_a_subcommand_option() {
let m = App::new("test")
.subcommand(
App::new("some").arg(
Arg::with_name("test")
.short('t')
.long("test")
.takes_value(true)
.short_alias('o')
.about("testing testing"),
),
)
.arg(
Arg::with_name("other")
.long("other")
.short_aliases(&['1', '2', '3']),
)
.get_matches_from(vec!["test", "some", "-o", "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_short_arg_aliases_help_output() {
let app = App::new("ct").author("Salim Afiune").subcommand(
App::new("test")
.about("Some help")
.version("1.2")
.arg(
Arg::with_name("opt")
.long("opt")
.short('o')
.takes_value(true)
.short_aliases(&['a', 'b', 'c']),
)
.arg(Arg::from("-f, --flag").short_aliases(&['a', 'b', 'c'])),
);
assert!(utils::compare_output(
app,
"ct test --help",
SC_INVISIBLE_ALIAS_HELP,
false
));
}
#[test]
fn visible_short_arg_aliases_help_output() {
let app = App::new("ct").author("Salim Afiune").subcommand(
App::new("test")
.about("Some help")
.version("1.2")
.arg(
Arg::with_name("opt")
.long("opt")
.short('o')
.takes_value(true)
.short_alias('i')
.visible_short_alias('v'),
)
.arg(
Arg::with_name("flg")
.long("flag")
.short('f')
.visible_alias("flag1")
.visible_short_aliases(&['a', 'b', '🦆']),
),
);
assert!(utils::compare_output(
app,
"ct test --help",
SC_VISIBLE_ALIAS_HELP,
false
));
}

View file

@ -83,6 +83,16 @@ args:
long: multaliases long: multaliases
about: Tests mutliple aliases about: Tests mutliple aliases
aliases: [als1, als2, als3] aliases: [als1, als2, als3]
- singleshortalias:
long: singleshortalias
about: Tests single short alias
short_aliases: [a]
required_if:
- [multvalsmo, two]
- multshortaliases:
long: multshortaliases
about: Tests mutliple short aliases
short_aliases: [a, b, c]
- minvals2: - minvals2:
long: minvals2 long: minvals2
multiple: true multiple: true

View file

@ -25,9 +25,8 @@ fn help_message() {
let mut help_buffer = Vec::new(); let mut help_buffer = Vec::new();
app.write_help(&mut help_buffer).unwrap(); app.write_help(&mut help_buffer).unwrap();
let help_string = String::from_utf8(help_buffer).unwrap(); let help_string = String::from_utf8(help_buffer).unwrap();
assert!( assert!(help_string
help_string.contains("-h, --help prints help with a nonstandard description\n") .contains("-h, --help prints help with a nonstandard description\n"));
);
} }
#[test] #[test]