mirror of
https://github.com/clap-rs/clap
synced 2024-11-10 14:54:15 +00:00
Merge #1974
1974: Flag subcommands r=CreepySkeleton a=NickHackman Co-authored-by: NickHackman <snickhackman@gmail.com> Co-authored-by: Nick Hackman <31719071+NickHackman@users.noreply.github.com>
This commit is contained in:
commit
ff6beebd6e
5 changed files with 1286 additions and 30 deletions
119
examples/23_flag_subcommands_pacman.rs
Normal file
119
examples/23_flag_subcommands_pacman.rs
Normal file
|
@ -0,0 +1,119 @@
|
|||
// This feature allows users of the app to pass subcommands in the fashion of short or long flags.
|
||||
// You may be familiar with it if you ever used [`pacman`](https://wiki.archlinux.org/index.php/pacman).
|
||||
// Some made up examples of what flag subcommands are:
|
||||
//
|
||||
// ```shell
|
||||
// $ pacman -S
|
||||
// ^--- short flag subcommand.
|
||||
// $ pacman --sync
|
||||
// ^--- long flag subcommand.
|
||||
// $ pacman -Ss
|
||||
// ^--- short flag subcommand followed by a short flag
|
||||
// (users can "stack" short subcommands with short flags or with other short flag subcommands)
|
||||
// $ pacman -S -s
|
||||
// ^--- same as above
|
||||
// $ pacman -S --sync
|
||||
// ^--- short flag subcommand followed by a long flag
|
||||
// ```
|
||||
// NOTE: Keep in mind that subcommands, flags, and long flags are *case sensitive*: `-Q` and `-q` are different flags/subcommands. For example, you can have both `-Q` subcommand and `-q` flag, and they will be properly disambiguated.
|
||||
// Let's make a quick program to illustrate.
|
||||
|
||||
use clap::{App, AppSettings, Arg};
|
||||
|
||||
fn main() {
|
||||
let matches = App::new("pacman")
|
||||
.about("package manager utility")
|
||||
.version("5.2.1")
|
||||
.setting(AppSettings::SubcommandRequiredElseHelp)
|
||||
.author("Pacman Development Team")
|
||||
// Query subcommand
|
||||
//
|
||||
// Only a few of its arguments are implemented below.
|
||||
.subcommand(
|
||||
App::new("query")
|
||||
.short_flag('Q')
|
||||
.long_flag("query")
|
||||
.about("Query the package database.")
|
||||
.arg(
|
||||
Arg::new("search")
|
||||
.short('s')
|
||||
.long("search")
|
||||
.about("search locally-installed packages for matching strings")
|
||||
.conflicts_with("info")
|
||||
.multiple_values(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("info")
|
||||
.long("info")
|
||||
.short('i')
|
||||
.conflicts_with("search")
|
||||
.about("view package information")
|
||||
.multiple_values(true),
|
||||
),
|
||||
)
|
||||
// Sync subcommand
|
||||
//
|
||||
// Only a few of its arguments are implemented below.
|
||||
.subcommand(
|
||||
App::new("sync")
|
||||
.short_flag('S')
|
||||
.long_flag("sync")
|
||||
.about("Synchronize packages.")
|
||||
.arg(
|
||||
Arg::new("search")
|
||||
.short('s')
|
||||
.long("search")
|
||||
.conflicts_with("info")
|
||||
.takes_value(true)
|
||||
.multiple_values(true)
|
||||
.about("search remote repositories for matching strings"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("info")
|
||||
.long("info")
|
||||
.conflicts_with("search")
|
||||
.short('i')
|
||||
.about("view package information"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("package")
|
||||
.about("packages")
|
||||
.multiple(true)
|
||||
.required_unless_one(&["search"])
|
||||
.takes_value(true),
|
||||
),
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
match matches.subcommand() {
|
||||
("sync", Some(sync_matches)) => {
|
||||
if sync_matches.is_present("search") {
|
||||
let packages: Vec<_> = sync_matches.values_of("search").unwrap().collect();
|
||||
let values = packages.join(", ");
|
||||
println!("Searching for {}...", values);
|
||||
return;
|
||||
}
|
||||
|
||||
let packages: Vec<_> = sync_matches.values_of("package").unwrap().collect();
|
||||
let values = packages.join(", ");
|
||||
|
||||
if sync_matches.is_present("info") {
|
||||
println!("Retrieving info for {}...", values);
|
||||
} else {
|
||||
println!("Installing {}...", values);
|
||||
}
|
||||
}
|
||||
("query", Some(query_matches)) => {
|
||||
if let Some(packages) = query_matches.values_of("info") {
|
||||
let comma_sep = packages.collect::<Vec<_>>().join(", ");
|
||||
println!("Retrieving info for {}...", comma_sep);
|
||||
} else if let Some(queries) = query_matches.values_of("search") {
|
||||
let comma_sep = queries.collect::<Vec<_>>().join(", ");
|
||||
println!("Searching Locally for {}...", comma_sep);
|
||||
} else {
|
||||
println!("Displaying all locally installed packages...");
|
||||
}
|
||||
}
|
||||
_ => unreachable!(), // If all subcommands are defined above, anything else is unreachable
|
||||
}
|
||||
}
|
|
@ -25,7 +25,7 @@ use crate::{
|
|||
mkeymap::MKeyMap,
|
||||
output::{fmt::Colorizer, Help, HelpWriter, Usage},
|
||||
parse::{ArgMatcher, ArgMatches, Input, Parser},
|
||||
util::{safe_exit, termcolor::ColorChoice, Id, Key},
|
||||
util::{safe_exit, termcolor::ColorChoice, ArgStr, Id, Key},
|
||||
Result as ClapResult, INTERNAL_ERROR_MSG,
|
||||
};
|
||||
|
||||
|
@ -72,6 +72,8 @@ pub(crate) enum Propagation {
|
|||
pub struct App<'b> {
|
||||
pub(crate) id: Id,
|
||||
pub(crate) name: String,
|
||||
pub(crate) long_flag: Option<&'b str>,
|
||||
pub(crate) short_flag: Option<char>,
|
||||
pub(crate) bin_name: Option<String>,
|
||||
pub(crate) author: Option<&'b str>,
|
||||
pub(crate) version: Option<&'b str>,
|
||||
|
@ -81,6 +83,8 @@ pub struct App<'b> {
|
|||
pub(crate) before_help: Option<&'b str>,
|
||||
pub(crate) after_help: Option<&'b str>,
|
||||
pub(crate) aliases: Vec<(&'b str, bool)>, // (name, visible)
|
||||
pub(crate) short_flag_aliases: Vec<(char, bool)>, // (name, visible)
|
||||
pub(crate) long_flag_aliases: Vec<(&'b str, bool)>, // (name, visible)
|
||||
pub(crate) usage_str: Option<&'b str>,
|
||||
pub(crate) usage: Option<String>,
|
||||
pub(crate) help_str: Option<&'b str>,
|
||||
|
@ -106,6 +110,18 @@ impl<'b> App<'b> {
|
|||
&self.name
|
||||
}
|
||||
|
||||
/// Get the short flag of the subcommand
|
||||
#[inline]
|
||||
pub fn get_short_flag(&self) -> Option<char> {
|
||||
self.short_flag
|
||||
}
|
||||
|
||||
/// Get the long flag of the subcommand
|
||||
#[inline]
|
||||
pub fn get_long_flag(&self) -> Option<&str> {
|
||||
self.long_flag
|
||||
}
|
||||
|
||||
/// Get the name of the binary
|
||||
#[inline]
|
||||
pub fn get_bin_name(&self) -> Option<&str> {
|
||||
|
@ -129,13 +145,43 @@ impl<'b> App<'b> {
|
|||
self.aliases.iter().filter(|(_, vis)| *vis).map(|a| a.0)
|
||||
}
|
||||
|
||||
/// Iterate through the *visible* short aliases for this subcommand.
|
||||
#[inline]
|
||||
pub fn get_visible_short_flag_aliases(&self) -> impl Iterator<Item = char> + '_ {
|
||||
self.short_flag_aliases
|
||||
.iter()
|
||||
.filter(|(_, vis)| *vis)
|
||||
.map(|a| a.0)
|
||||
}
|
||||
|
||||
/// Iterate through the *visible* short aliases for this subcommand.
|
||||
#[inline]
|
||||
pub fn get_visible_long_flag_aliases(&self) -> impl Iterator<Item = &'b str> + '_ {
|
||||
self.long_flag_aliases
|
||||
.iter()
|
||||
.filter(|(_, vis)| *vis)
|
||||
.map(|a| a.0)
|
||||
}
|
||||
|
||||
/// Iterate through the set of *all* the aliases for this subcommand, both visible and hidden.
|
||||
#[inline]
|
||||
pub fn get_all_aliases(&self) -> impl Iterator<Item = &str> {
|
||||
self.aliases.iter().map(|a| a.0)
|
||||
}
|
||||
|
||||
/// Iterate through the set of subcommands.
|
||||
/// Iterate through the set of *all* the short aliases for this subcommand, both visible and hidden.
|
||||
#[inline]
|
||||
pub fn get_all_short_flag_aliases(&self) -> impl Iterator<Item = char> + '_ {
|
||||
self.short_flag_aliases.iter().map(|a| a.0)
|
||||
}
|
||||
|
||||
/// Iterate through the set of *all* the long aliases for this subcommand, both visible and hidden.
|
||||
#[inline]
|
||||
pub fn get_all_long_flag_aliases(&self) -> impl Iterator<Item = &'b str> + '_ {
|
||||
self.long_flag_aliases.iter().map(|a| a.0)
|
||||
}
|
||||
|
||||
/// Get the list of subcommands
|
||||
#[inline]
|
||||
pub fn get_subcommands(&self) -> impl Iterator<Item = &App<'b>> {
|
||||
self.subcommands.iter()
|
||||
|
@ -388,6 +434,68 @@ impl<'b> App<'b> {
|
|||
self
|
||||
}
|
||||
|
||||
/// Allows the subcommand to be used as if it were an [`Arg::short`]
|
||||
///
|
||||
/// Sets the short version of the subcommand flag without the preceeding `-`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use clap::{App, Arg};
|
||||
/// let matches = App::new("pacman")
|
||||
/// .subcommand(
|
||||
/// App::new("sync").short_flag('S').arg(
|
||||
/// Arg::new("search")
|
||||
/// .short('s')
|
||||
/// .long("search")
|
||||
/// .about("search remote repositories for matching strings"),
|
||||
/// ),
|
||||
/// )
|
||||
/// .get_matches_from(vec!["pacman", "-Ss"]);
|
||||
///
|
||||
/// assert_eq!(matches.subcommand_name().unwrap(), "sync");
|
||||
/// let sync_matches = matches.subcommand_matches("sync").unwrap();
|
||||
/// assert!(sync_matches.is_present("search"));
|
||||
/// ```
|
||||
pub fn short_flag(mut self, short: char) -> Self {
|
||||
self.short_flag = Some(short);
|
||||
self
|
||||
}
|
||||
|
||||
/// Allows the subcommand to be used as if it were an [`Arg::long`]
|
||||
///
|
||||
/// Sets the long version of the subcommand flag without the preceeding `--`.
|
||||
///
|
||||
/// **NOTE:** Any leading `-` characters will be stripped
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// To set `long_flag` use a word containing valid UTF-8 codepoints. If you supply a double leading
|
||||
/// `--` such as `--sync` they will be stripped. Hyphens in the middle of the word; however,
|
||||
/// will *not* be stripped (i.e. `sync-file` is allowed)
|
||||
///
|
||||
/// ```
|
||||
/// # use clap::{App, Arg};
|
||||
/// let matches = App::new("pacman")
|
||||
/// .subcommand(
|
||||
/// App::new("sync").long_flag("sync").arg(
|
||||
/// Arg::new("search")
|
||||
/// .short('s')
|
||||
/// .long("search")
|
||||
/// .about("search remote repositories for matching strings"),
|
||||
/// ),
|
||||
/// )
|
||||
/// .get_matches_from(vec!["pacman", "--sync", "--search"]);
|
||||
///
|
||||
/// assert_eq!(matches.subcommand_name().unwrap(), "sync");
|
||||
/// let sync_matches = matches.subcommand_matches("sync").unwrap();
|
||||
/// assert!(sync_matches.is_present("search"));
|
||||
/// ```
|
||||
pub fn long_flag(mut self, long: &'b str) -> Self {
|
||||
self.long_flag = Some(long.trim_start_matches(|c| c == '-'));
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets a string of the version number to be displayed when displaying version or help
|
||||
/// information with `-V`.
|
||||
///
|
||||
|
@ -793,6 +901,49 @@ impl<'b> App<'b> {
|
|||
self
|
||||
}
|
||||
|
||||
/// Allows adding an alias, which function as "hidden" short flag 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 existence of
|
||||
/// this command, and not all variants.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # use clap::{App, Arg, };
|
||||
/// let m = App::new("myprog")
|
||||
/// .subcommand(App::new("test").short_flag('t')
|
||||
/// .short_flag_alias('d'))
|
||||
/// .get_matches_from(vec!["myprog", "-d"]);
|
||||
/// assert_eq!(m.subcommand_name(), Some("test"));
|
||||
/// ```
|
||||
pub fn short_flag_alias(mut self, name: char) -> Self {
|
||||
if name == '-' {
|
||||
panic!("short alias name cannot be `-`");
|
||||
}
|
||||
self.short_flag_aliases.push((name, false));
|
||||
self
|
||||
}
|
||||
|
||||
/// Allows adding an alias, which function as "hidden" long flag 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 existence of
|
||||
/// this command, and not all variants.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # use clap::{App, Arg, };
|
||||
/// let m = App::new("myprog")
|
||||
/// .subcommand(App::new("test").long_flag("test")
|
||||
/// .long_flag_alias("testing"))
|
||||
/// .get_matches_from(vec!["myprog", "--testing"]);
|
||||
/// assert_eq!(m.subcommand_name(), Some("test"));
|
||||
/// ```
|
||||
pub fn long_flag_alias(mut self, name: &'b str) -> Self {
|
||||
self.long_flag_aliases.push((name, false));
|
||||
self
|
||||
}
|
||||
|
||||
/// Allows adding [``] 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 existence of
|
||||
|
@ -818,6 +969,61 @@ impl<'b> App<'b> {
|
|||
self
|
||||
}
|
||||
|
||||
/// Allows adding aliases, which function as "hidden" short flag 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 existence of
|
||||
/// this command, and not all variants.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use clap::{App, Arg, };
|
||||
/// let m = App::new("myprog")
|
||||
/// .subcommand(App::new("test").short_flag('t')
|
||||
/// .short_flag_aliases(&['a', 'b', 'c']))
|
||||
/// .arg(Arg::new("input")
|
||||
/// .about("the file to add")
|
||||
/// .index(1)
|
||||
/// .required(false))
|
||||
/// .get_matches_from(vec!["myprog", "-a"]);
|
||||
/// assert_eq!(m.subcommand_name(), Some("test"));
|
||||
/// ```
|
||||
pub fn short_flag_aliases(mut self, names: &[char]) -> Self {
|
||||
for s in names {
|
||||
if s == &'-' {
|
||||
panic!("short alias name cannot be `-`");
|
||||
}
|
||||
self.short_flag_aliases.push((*s, false));
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Allows adding aliases, which function as "hidden" long flag 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 existence of
|
||||
/// this command, and not all variants.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use clap::{App, Arg, };
|
||||
/// let m = App::new("myprog")
|
||||
/// .subcommand(App::new("test").long_flag("test")
|
||||
/// .long_flag_aliases(&["testing", "testall", "test_all"]))
|
||||
/// .arg(Arg::new("input")
|
||||
/// .about("the file to add")
|
||||
/// .index(1)
|
||||
/// .required(false))
|
||||
/// .get_matches_from(vec!["myprog", "--testing"]);
|
||||
/// assert_eq!(m.subcommand_name(), Some("test"));
|
||||
/// ```
|
||||
pub fn long_flag_aliases(mut self, names: &[&'b str]) -> Self {
|
||||
for s in names {
|
||||
self.long_flag_aliases.push((s, false));
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Allows adding a [``] alias that functions exactly like those defined with
|
||||
/// [`App::alias`], except that they are visible inside the help message.
|
||||
///
|
||||
|
@ -838,6 +1044,47 @@ impl<'b> App<'b> {
|
|||
self
|
||||
}
|
||||
|
||||
/// Allows adding an alias that functions exactly like those defined with
|
||||
/// [`App::short_flag_alias`], except that they are visible inside the help message.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # use clap::{App, Arg, };
|
||||
/// let m = App::new("myprog")
|
||||
/// .subcommand(App::new("test").short_flag('t')
|
||||
/// .visible_short_flag_alias('d'))
|
||||
/// .get_matches_from(vec!["myprog", "-d"]);
|
||||
/// assert_eq!(m.subcommand_name(), Some("test"));
|
||||
/// ```
|
||||
/// [`App::short_flag_alias`]: ./struct.App.html#method.short_flag_alias
|
||||
pub fn visible_short_flag_alias(mut self, name: char) -> Self {
|
||||
if name == '-' {
|
||||
panic!("short alias name cannot be `-`");
|
||||
}
|
||||
self.short_flag_aliases.push((name, true));
|
||||
self
|
||||
}
|
||||
|
||||
/// Allows adding an alias that functions exactly like those defined with
|
||||
/// [`App::long_flag_alias`], except that they are visible inside the help message.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # use clap::{App, Arg, };
|
||||
/// let m = App::new("myprog")
|
||||
/// .subcommand(App::new("test").long_flag("test")
|
||||
/// .visible_long_flag_alias("testing"))
|
||||
/// .get_matches_from(vec!["myprog", "--testing"]);
|
||||
/// assert_eq!(m.subcommand_name(), Some("test"));
|
||||
/// ```
|
||||
/// [`App::long_flag_alias`]: ./struct.App.html#method.long_flag_alias
|
||||
pub fn visible_long_flag_alias(mut self, name: &'b str) -> Self {
|
||||
self.long_flag_aliases.push((name, true));
|
||||
self
|
||||
}
|
||||
|
||||
/// Allows adding multiple [``] aliases that functions exactly like those defined
|
||||
/// with [`App::aliases`], except that they are visible inside the help message.
|
||||
///
|
||||
|
@ -858,6 +1105,51 @@ impl<'b> App<'b> {
|
|||
self
|
||||
}
|
||||
|
||||
/// Allows adding multiple short flag aliases that functions exactly like those defined
|
||||
/// with [`App::short_flag_aliases`], except that they are visible inside the help message.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # use clap::{App, Arg, };
|
||||
/// let m = App::new("myprog")
|
||||
/// .subcommand(App::new("test").short_flag('b')
|
||||
/// .visible_short_flag_aliases(&['t']))
|
||||
/// .get_matches_from(vec!["myprog", "-t"]);
|
||||
/// assert_eq!(m.subcommand_name(), Some("test"));
|
||||
/// ```
|
||||
/// [`App::short_flag_aliases`]: ./struct.App.html#method.short_flag_aliases
|
||||
pub fn visible_short_flag_aliases(mut self, names: &[char]) -> Self {
|
||||
for s in names {
|
||||
if s == &'-' {
|
||||
panic!("short alias name cannot be `-`");
|
||||
}
|
||||
self.short_flag_aliases.push((*s, true));
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Allows adding multiple long flag aliases that functions exactly like those defined
|
||||
/// with [`App::long_flag_aliases`], except that they are visible inside the help message.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # use clap::{App, Arg, };
|
||||
/// let m = App::new("myprog")
|
||||
/// .subcommand(App::new("test").long_flag("test")
|
||||
/// .visible_long_flag_aliases(&["testing", "testall", "test_all"]))
|
||||
/// .get_matches_from(vec!["myprog", "--testing"]);
|
||||
/// assert_eq!(m.subcommand_name(), Some("test"));
|
||||
/// ```
|
||||
/// [`App::short_flag_aliases`]: ./struct.App.html#method.short_flag_aliases
|
||||
pub fn visible_long_flag_aliases(mut self, names: &[&'b str]) -> Self {
|
||||
for s in names {
|
||||
self.long_flag_aliases.push((s, true));
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Replaces an argument to this application with other arguments.
|
||||
///
|
||||
/// Below, when the given args are `app install`, they will be changed to `app module install`.
|
||||
|
@ -1546,6 +1838,41 @@ impl<'b> App<'b> {
|
|||
}
|
||||
}
|
||||
|
||||
// Allows checking for conflicts between `Args` and `Apps` with subcommand flags
|
||||
#[cfg(debug_assertions)]
|
||||
#[derive(Debug)]
|
||||
enum Flag<'a> {
|
||||
App(&'a App<'a>, String),
|
||||
Arg(&'a Arg<'a>, String),
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
impl<'a> Flag<'_> {
|
||||
pub fn value(&self) -> &str {
|
||||
match self {
|
||||
Self::App(_, value) => value,
|
||||
Self::Arg(_, value) => value,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn id(&self) -> &Id {
|
||||
match self {
|
||||
Self::App(app, _) => &app.id,
|
||||
Self::Arg(arg, _) => &arg.id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
impl<'a> fmt::Display for Flag<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Self::App(app, _) => write!(f, "App named '{}'", app.name),
|
||||
Self::Arg(arg, _) => write!(f, "Arg named '{}'", arg.name),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Internally used only
|
||||
impl<'b> App<'b> {
|
||||
fn _do_parse(&mut self, it: &mut Input) -> ClapResult<ArgMatches> {
|
||||
|
@ -1647,6 +1974,68 @@ impl<'b> App<'b> {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
fn two_long_flags_of<F>(&self, condition: F) -> Option<(Flag, Flag)>
|
||||
where
|
||||
F: Fn(&Flag<'_>) -> bool,
|
||||
{
|
||||
let mut flags: Vec<Flag> = Vec::new();
|
||||
for sc in &self.subcommands {
|
||||
if let Some(long) = sc.long_flag {
|
||||
flags.push(Flag::App(&sc, long.to_string()));
|
||||
}
|
||||
flags.extend(
|
||||
sc.get_all_long_flag_aliases()
|
||||
.map(|alias| Flag::App(&sc, alias.to_string())),
|
||||
);
|
||||
self.args.args.iter().for_each(|arg| {
|
||||
flags.extend(
|
||||
arg.aliases
|
||||
.iter()
|
||||
.map(|(alias, _)| Flag::Arg(arg, alias.to_string())),
|
||||
)
|
||||
});
|
||||
flags.extend(
|
||||
self.args
|
||||
.args
|
||||
.iter()
|
||||
.filter_map(|arg| arg.long.map(|l| Flag::Arg(arg, l.to_string()))),
|
||||
);
|
||||
}
|
||||
two_elements_of(flags.into_iter().filter(|f| condition(f)))
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
fn two_short_flags_of<F>(&self, condition: F) -> Option<(Flag, Flag)>
|
||||
where
|
||||
F: Fn(&Flag<'_>) -> bool,
|
||||
{
|
||||
let mut flags: Vec<Flag> = Vec::new();
|
||||
for sc in &self.subcommands {
|
||||
if let Some(short) = sc.short_flag {
|
||||
flags.push(Flag::App(&sc, short.to_string()));
|
||||
}
|
||||
flags.extend(
|
||||
sc.get_all_short_flag_aliases()
|
||||
.map(|alias| Flag::App(&sc, alias.to_string())),
|
||||
);
|
||||
self.args.args.iter().for_each(|arg| {
|
||||
flags.extend(
|
||||
arg.short_aliases
|
||||
.iter()
|
||||
.map(|(alias, _)| Flag::Arg(arg, alias.to_string())),
|
||||
)
|
||||
});
|
||||
flags.extend(
|
||||
self.args
|
||||
.args
|
||||
.iter()
|
||||
.filter_map(|arg| arg.short.map(|l| Flag::Arg(arg, l.to_string()))),
|
||||
);
|
||||
}
|
||||
two_elements_of(flags.into_iter().filter(|f| condition(f)))
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
fn two_args_of<F>(&self, condition: F) -> Option<(&Arg, &Arg)>
|
||||
where
|
||||
|
@ -1670,6 +2059,38 @@ impl<'b> App<'b> {
|
|||
fn _debug_asserts(&self) {
|
||||
debug!("App::_debug_asserts");
|
||||
|
||||
for sc in &self.subcommands {
|
||||
// Conflicts between flag subcommands and long args
|
||||
if let Some(l) = sc.long_flag {
|
||||
if let Some((first, second)) = self.two_long_flags_of(|f| f.value() == l) {
|
||||
// Prevent conflicts with itself
|
||||
if first.id() != second.id() {
|
||||
panic!(
|
||||
"Long option names must be unique for each argument, \
|
||||
but '--{}' is used by both an {} and an {}",
|
||||
l, first, second
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Conflicts between flag subcommands and long args
|
||||
if let Some(s) = sc.short_flag {
|
||||
if let Some((first, second)) =
|
||||
self.two_short_flags_of(|f| f.value() == s.to_string())
|
||||
{
|
||||
// Prevent conflicts with itself
|
||||
if first.id() != second.id() {
|
||||
panic!(
|
||||
"Short option names must be unique for each argument, \
|
||||
but '-{}' is used by both an {} and an {}",
|
||||
s, first, second
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for arg in &self.args.args {
|
||||
arg._debug_asserts();
|
||||
|
||||
|
@ -1869,7 +2290,11 @@ impl<'b> App<'b> {
|
|||
.args
|
||||
.iter()
|
||||
.any(|x| x.long == Some("help") || x.id == Id::help_hash())
|
||||
|| self.is_set(AppSettings::DisableHelpFlags))
|
||||
|| self.is_set(AppSettings::DisableHelpFlags)
|
||||
|| self
|
||||
.subcommands
|
||||
.iter()
|
||||
.any(|sc| sc.short_flag == Some('h') || sc.long_flag == Some("help")))
|
||||
{
|
||||
debug!("App::_create_help_and_version: Building --help");
|
||||
let mut help = Arg::new("help")
|
||||
|
@ -1886,7 +2311,11 @@ impl<'b> App<'b> {
|
|||
.args
|
||||
.iter()
|
||||
.any(|x| x.long == Some("version") || x.id == Id::version_hash())
|
||||
|| self.is_set(AppSettings::DisableVersion))
|
||||
|| self.is_set(AppSettings::DisableVersion)
|
||||
|| self
|
||||
.subcommands
|
||||
.iter()
|
||||
.any(|sc| sc.short_flag == Some('V') || sc.long_flag == Some("version")))
|
||||
{
|
||||
debug!("App::_create_help_and_version: Building --version");
|
||||
let mut version = Arg::new("version")
|
||||
|
@ -2084,6 +2513,29 @@ impl<'b> App<'b> {
|
|||
*name == *self.get_name() || self.get_all_aliases().any(|alias| *name == *alias)
|
||||
}
|
||||
|
||||
/// Check if this subcommand can be referred to as `name`. In other words,
|
||||
/// check if `name` is the name of this short flag subcommand or is one of its short flag aliases.
|
||||
#[inline]
|
||||
pub(crate) fn short_flag_aliases_to(&self, flag: char) -> bool {
|
||||
Some(flag) == self.short_flag
|
||||
|| self.get_all_short_flag_aliases().any(|alias| flag == alias)
|
||||
}
|
||||
|
||||
/// Check if this subcommand can be referred to as `name`. In other words,
|
||||
/// check if `name` is the name of this long flag subcommand or is one of its long flag aliases.
|
||||
#[inline]
|
||||
pub(crate) fn long_flag_aliases_to<T>(&self, flag: &T) -> bool
|
||||
where
|
||||
T: PartialEq<str> + ?Sized,
|
||||
{
|
||||
match self.long_flag {
|
||||
Some(long_flag) => {
|
||||
flag == long_flag || self.get_all_long_flag_aliases().any(|alias| flag == alias)
|
||||
}
|
||||
None => self.get_all_long_flag_aliases().any(|alias| flag == alias),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
pub(crate) fn id_exists(&self, id: &Id) -> bool {
|
||||
self.args.args.iter().any(|x| x.id == *id) || self.groups.iter().any(|x| x.id == *id)
|
||||
|
@ -2180,6 +2632,20 @@ impl<'b> App<'b> {
|
|||
|
||||
args
|
||||
}
|
||||
|
||||
/// Find a flag subcommand name by short flag or an alias
|
||||
pub(crate) fn find_short_subcmd(&self, c: char) -> Option<&str> {
|
||||
self.get_subcommands()
|
||||
.find(|sc| sc.short_flag_aliases_to(c))
|
||||
.map(|sc| sc.get_name())
|
||||
}
|
||||
|
||||
/// Find a flag subcommand name by long flag or an alias
|
||||
pub(crate) fn find_long_subcmd(&self, long: &ArgStr<'_>) -> Option<&str> {
|
||||
self.get_subcommands()
|
||||
.find(|sc| sc.long_flag_aliases_to(long))
|
||||
.map(|sc| sc.get_name())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'b> Index<&'_ Id> for App<'b> {
|
||||
|
|
|
@ -547,16 +547,16 @@ impl<'b, 'c, 'd, 'w> Help<'b, 'c, 'd, 'w> {
|
|||
|
||||
/// Methods to write a single subcommand
|
||||
impl<'b, 'c, 'd, 'w> Help<'b, 'c, 'd, 'w> {
|
||||
fn write_subcommand(&mut self, app: &App<'b>) -> io::Result<()> {
|
||||
fn write_subcommand(&mut self, sc_str: &str, app: &App<'b>) -> io::Result<()> {
|
||||
debug!("Help::write_subcommand");
|
||||
self.none(TAB)?;
|
||||
self.good(&app.name)?;
|
||||
let spec_vals = self.sc_val(app)?;
|
||||
self.good(sc_str)?;
|
||||
let spec_vals = self.sc_val(sc_str, app)?;
|
||||
self.sc_help(app, &*spec_vals)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn sc_val(&mut self, app: &App<'b>) -> Result<String, io::Error> {
|
||||
fn sc_val(&mut self, sc_str: &str, app: &App<'b>) -> Result<String, io::Error> {
|
||||
debug!("Help::sc_val: app={}", app.name);
|
||||
let spec_vals = self.sc_spec_vals(app);
|
||||
let h = app.about.unwrap_or("");
|
||||
|
@ -569,7 +569,7 @@ impl<'b, 'c, 'd, 'w> Help<'b, 'c, 'd, 'w> {
|
|||
&& h_w > (self.term_w - taken);
|
||||
|
||||
if !(nlh || self.force_next_line) {
|
||||
write_nspaces!(self, self.longest + 4 - (str_width(&app.name)));
|
||||
write_nspaces!(self, self.longest + 4 - (str_width(sc_str)));
|
||||
}
|
||||
Ok(spec_vals)
|
||||
}
|
||||
|
@ -577,19 +577,26 @@ impl<'b, 'c, 'd, 'w> Help<'b, 'c, 'd, 'w> {
|
|||
fn sc_spec_vals(&self, a: &App) -> String {
|
||||
debug!("Help::sc_spec_vals: a={}", a.name);
|
||||
let mut spec_vals = vec![];
|
||||
if !a.aliases.is_empty() {
|
||||
if !a.aliases.is_empty() || !a.short_flag_aliases.is_empty() {
|
||||
debug!("Help::spec_vals: Found aliases...{:?}", a.aliases);
|
||||
debug!(
|
||||
"Help::spec_vals: Found short flag aliases...{:?}",
|
||||
a.short_flag_aliases
|
||||
);
|
||||
|
||||
let als = a
|
||||
.aliases
|
||||
.iter()
|
||||
.filter(|&als| als.1) // visible
|
||||
.map(|&als| als.0) // name
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
let mut short_als = a
|
||||
.get_visible_short_flag_aliases()
|
||||
.map(|a| format!("-{}", a))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if !als.is_empty() {
|
||||
spec_vals.push(format!(" [aliases: {}]", als));
|
||||
let als = a.get_visible_aliases().map(|s| s.to_string());
|
||||
|
||||
short_als.extend(als);
|
||||
|
||||
let all_als = short_als.join(", ");
|
||||
|
||||
if !all_als.is_empty() {
|
||||
spec_vals.push(format!(" [aliases: {}]", all_als));
|
||||
}
|
||||
}
|
||||
spec_vals.join(" ")
|
||||
|
@ -776,19 +783,25 @@ impl<'b, 'c, 'd, 'w> Help<'b, 'c, 'd, 'w> {
|
|||
.filter(|s| !s.is_set(AppSettings::Hidden))
|
||||
{
|
||||
let btm = ord_m.entry(sc.disp_ord).or_insert(BTreeMap::new());
|
||||
self.longest = cmp::max(self.longest, str_width(sc.name.as_str()));
|
||||
btm.insert(sc.name.clone(), sc.clone());
|
||||
let mut sc_str = String::new();
|
||||
sc_str.push_str(&sc.short_flag.map_or(String::new(), |c| format!("-{}, ", c)));
|
||||
sc_str.push_str(&sc.long_flag.map_or(String::new(), |c| format!("--{}, ", c)));
|
||||
sc_str.push_str(&sc.name);
|
||||
self.longest = cmp::max(self.longest, str_width(&sc_str));
|
||||
btm.insert(sc_str, sc.clone());
|
||||
}
|
||||
|
||||
debug!("Help::write_subcommands longest = {}", self.longest);
|
||||
|
||||
let mut first = true;
|
||||
for btm in ord_m.values() {
|
||||
for sc in btm.values() {
|
||||
for (sc_str, sc) in btm {
|
||||
if first {
|
||||
first = false;
|
||||
} else {
|
||||
self.none("\n")?;
|
||||
}
|
||||
self.write_subcommand(sc)?;
|
||||
self.write_subcommand(sc_str, sc)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
|
|
@ -25,6 +25,9 @@ use crate::{
|
|||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub(crate) enum ParseResult {
|
||||
Flag(Id),
|
||||
FlagSubCommand(String),
|
||||
// subcommand name, whether there are more shorts args remaining
|
||||
FlagSubCommandShort(String, bool),
|
||||
Opt(Id),
|
||||
Pos(Id),
|
||||
MaybeHyphenValue,
|
||||
|
@ -92,6 +95,7 @@ where
|
|||
pub(crate) overriden: Vec<Id>,
|
||||
pub(crate) seen: Vec<Id>,
|
||||
pub(crate) cur_idx: Cell<usize>,
|
||||
pub(crate) skip_idxs: usize,
|
||||
}
|
||||
|
||||
// Initializing Methods
|
||||
|
@ -116,6 +120,7 @@ where
|
|||
overriden: Vec::new(),
|
||||
seen: Vec::new(),
|
||||
cur_idx: Cell::new(0),
|
||||
skip_idxs: 0,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -303,7 +308,7 @@ where
|
|||
true
|
||||
}
|
||||
|
||||
#[allow(clippy::block_in_if_condition_stmt)]
|
||||
#[allow(clippy::blocks_in_if_conditions)]
|
||||
// Does all the initializing and prepares the parser
|
||||
pub(crate) fn _build(&mut self) {
|
||||
debug!("Parser::_build");
|
||||
|
@ -382,6 +387,7 @@ where
|
|||
let has_args = self.has_args();
|
||||
|
||||
let mut subcmd_name: Option<String> = None;
|
||||
let mut keep_state = false;
|
||||
let mut external_subcommand = false;
|
||||
let mut needs_val_of: ParseResult = ParseResult::NotFound;
|
||||
let mut pos_counter = 1;
|
||||
|
@ -461,7 +467,12 @@ where
|
|||
self.maybe_inc_pos_counter(&mut pos_counter, id);
|
||||
continue;
|
||||
}
|
||||
|
||||
ParseResult::FlagSubCommand(ref name) => {
|
||||
debug!("Parser::get_matches_with: FlagSubCommand found in long arg {:?}", name);
|
||||
subcmd_name = Some(name.to_owned());
|
||||
break;
|
||||
}
|
||||
ParseResult::FlagSubCommandShort(_, _) => unreachable!(),
|
||||
_ => (),
|
||||
}
|
||||
} else if arg_os.starts_with("-") && arg_os.len() != 1 {
|
||||
|
@ -494,6 +505,18 @@ where
|
|||
self.maybe_inc_pos_counter(&mut pos_counter, id);
|
||||
continue;
|
||||
}
|
||||
ParseResult::FlagSubCommandShort(ref name, done) => {
|
||||
// There are more short args, revist the current short args skipping the subcommand
|
||||
keep_state = !done;
|
||||
if keep_state {
|
||||
it.cursor -= 1;
|
||||
self.skip_idxs = self.cur_idx.get();
|
||||
}
|
||||
|
||||
subcmd_name = Some(name.to_owned());
|
||||
break;
|
||||
}
|
||||
ParseResult::FlagSubCommand(_) => unreachable!(),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
@ -739,7 +762,7 @@ where
|
|||
.expect(INTERNAL_ERROR_MSG)
|
||||
.name
|
||||
.clone();
|
||||
self.parse_subcommand(&sc_name, matcher, it)?;
|
||||
self.parse_subcommand(&sc_name, matcher, it, keep_state)?;
|
||||
} else if self.is_set(AS::SubcommandRequired) {
|
||||
let bn = self.app.bin_name.as_ref().unwrap_or(&self.app.name);
|
||||
return Err(ClapError::missing_subcommand(
|
||||
|
@ -857,6 +880,40 @@ where
|
|||
None
|
||||
}
|
||||
|
||||
// Checks if the arg matches a long flag subcommand name, or any of it's aliases (if defined)
|
||||
fn possible_long_flag_subcommand(&self, arg_os: &ArgStr<'_>) -> Option<&str> {
|
||||
debug!("Parser::possible_long_flag_subcommand: arg={:?}", arg_os);
|
||||
if self.is_set(AS::InferSubcommands) {
|
||||
let options = self
|
||||
.app
|
||||
.get_subcommands()
|
||||
.fold(Vec::new(), |mut options, sc| {
|
||||
if let Some(long) = sc.long_flag {
|
||||
if arg_os.is_prefix_of(long) {
|
||||
options.push(long);
|
||||
}
|
||||
options.extend(
|
||||
sc.get_all_aliases()
|
||||
.filter(|alias| arg_os.is_prefix_of(alias)),
|
||||
)
|
||||
}
|
||||
options
|
||||
});
|
||||
if options.len() == 1 {
|
||||
return Some(options[0]);
|
||||
}
|
||||
|
||||
for sc in &options {
|
||||
if sc == arg_os {
|
||||
return Some(sc);
|
||||
}
|
||||
}
|
||||
} else if let Some(sc_name) = self.app.find_long_subcmd(arg_os) {
|
||||
return Some(sc_name);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn parse_help_subcommand(&self, cmds: &[OsString]) -> ClapResult<ParseResult> {
|
||||
debug!("Parser::parse_help_subcommand");
|
||||
|
||||
|
@ -987,6 +1044,7 @@ where
|
|||
sc_name: &str,
|
||||
matcher: &mut ArgMatcher,
|
||||
it: &mut Input,
|
||||
keep_state: bool,
|
||||
) -> ClapResult<()> {
|
||||
use std::fmt::Write;
|
||||
|
||||
|
@ -1011,8 +1069,22 @@ where
|
|||
|
||||
if let Some(sc) = self.app.subcommands.iter_mut().find(|s| s.name == sc_name) {
|
||||
let mut sc_matcher = ArgMatcher::default();
|
||||
// bin_name should be parent's bin_name + [<reqs>] + the sc's name separated by
|
||||
// a space
|
||||
// Display subcommand name, short and long in usage
|
||||
let mut sc_names = sc.name.clone();
|
||||
let mut flag_subcmd = false;
|
||||
if let Some(l) = sc.long_flag {
|
||||
sc_names.push_str(&format!(", --{}", l));
|
||||
flag_subcmd = true;
|
||||
}
|
||||
if let Some(s) = sc.short_flag {
|
||||
sc_names.push_str(&format!(", -{}", s));
|
||||
flag_subcmd = true;
|
||||
}
|
||||
|
||||
if flag_subcmd {
|
||||
sc_names = format!("{{{}}}", sc_names);
|
||||
}
|
||||
|
||||
sc.usage = Some(format!(
|
||||
"{}{}{}",
|
||||
self.app.bin_name.as_ref().unwrap_or(&String::new()),
|
||||
|
@ -1021,8 +1093,11 @@ where
|
|||
} else {
|
||||
""
|
||||
},
|
||||
&*sc.name
|
||||
sc_names
|
||||
));
|
||||
|
||||
// bin_name should be parent's bin_name + [<reqs>] + the sc's name separated by
|
||||
// a space
|
||||
sc.bin_name = Some(format!(
|
||||
"{}{}{}",
|
||||
self.app.bin_name.as_ref().unwrap_or(&String::new()),
|
||||
|
@ -1037,6 +1112,12 @@ where
|
|||
|
||||
{
|
||||
let mut p = Parser::new(sc);
|
||||
// HACK: maintain indexes between parsers
|
||||
// FlagSubCommand short arg needs to revist the current short args, but skip the subcommand itself
|
||||
if keep_state {
|
||||
p.cur_idx.set(self.cur_idx.get());
|
||||
p.skip_idxs = self.skip_idxs;
|
||||
}
|
||||
p.get_matches_with(&mut sc_matcher, it)?;
|
||||
}
|
||||
let name = &sc.name;
|
||||
|
@ -1158,6 +1239,8 @@ where
|
|||
self.parse_flag(opt, matcher)?;
|
||||
|
||||
return Ok(ParseResult::Flag(opt.id.clone()));
|
||||
} else if let Some(sc_name) = self.possible_long_flag_subcommand(&arg) {
|
||||
return Ok(ParseResult::FlagSubCommand(sc_name.to_string()));
|
||||
} else if self.is_set(AS::AllowLeadingHyphen) {
|
||||
return Ok(ParseResult::MaybeHyphenValue);
|
||||
} else if self.is_set(AS::ValidNegNumFound) {
|
||||
|
@ -1196,7 +1279,9 @@ where
|
|||
}
|
||||
|
||||
let mut ret = ParseResult::NotFound;
|
||||
for c in arg.chars() {
|
||||
let skip = self.skip_idxs;
|
||||
self.skip_idxs = 0;
|
||||
for c in arg.chars().skip(skip) {
|
||||
debug!("Parser::parse_short_arg:iter:{}", c);
|
||||
|
||||
// update each index because `-abcd` is four indices to clap
|
||||
|
@ -1241,6 +1326,11 @@ where
|
|||
|
||||
// Default to "we're expecting a value later"
|
||||
return self.parse_opt(&val, opt, false, matcher);
|
||||
} else if let Some(sc_name) = self.app.find_short_subcmd(c) {
|
||||
debug!("Parser::parse_short_arg:iter:{}: subcommand={}", c, sc_name);
|
||||
let name = sc_name.to_string();
|
||||
let done_short_args = self.cur_idx.get() == arg.len();
|
||||
return Ok(ParseResult::FlagSubCommandShort(name, done_short_args));
|
||||
} else {
|
||||
let arg = format!("-{}", c);
|
||||
|
||||
|
|
568
tests/flag_subcommands.rs
Normal file
568
tests/flag_subcommands.rs
Normal file
|
@ -0,0 +1,568 @@
|
|||
mod utils;
|
||||
|
||||
use clap::{App, AppSettings, Arg, ErrorKind};
|
||||
|
||||
#[test]
|
||||
fn flag_subcommand_normal() {
|
||||
let matches = App::new("test")
|
||||
.subcommand(
|
||||
App::new("some").short_flag('S').long_flag("some").arg(
|
||||
Arg::new("test")
|
||||
.short('t')
|
||||
.long("test")
|
||||
.about("testing testing"),
|
||||
),
|
||||
)
|
||||
.get_matches_from(vec!["myprog", "some", "--test"]);
|
||||
assert_eq!(matches.subcommand_name().unwrap(), "some");
|
||||
let sub_matches = matches.subcommand_matches("some").unwrap();
|
||||
assert!(sub_matches.is_present("test"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flag_subcommand_normal_with_alias() {
|
||||
let matches = App::new("test")
|
||||
.subcommand(
|
||||
App::new("some")
|
||||
.short_flag('S')
|
||||
.long_flag("S")
|
||||
.arg(
|
||||
Arg::new("test")
|
||||
.short('t')
|
||||
.long("test")
|
||||
.about("testing testing"),
|
||||
)
|
||||
.alias("result"),
|
||||
)
|
||||
.get_matches_from(vec!["myprog", "result", "--test"]);
|
||||
assert_eq!(matches.subcommand_name().unwrap(), "some");
|
||||
let sub_matches = matches.subcommand_matches("some").unwrap();
|
||||
assert!(sub_matches.is_present("test"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flag_subcommand_short() {
|
||||
let matches = App::new("test")
|
||||
.subcommand(
|
||||
App::new("some").short_flag('S').arg(
|
||||
Arg::new("test")
|
||||
.short('t')
|
||||
.long("test")
|
||||
.about("testing testing"),
|
||||
),
|
||||
)
|
||||
.get_matches_from(vec!["myprog", "-S", "--test"]);
|
||||
assert_eq!(matches.subcommand_name().unwrap(), "some");
|
||||
let sub_matches = matches.subcommand_matches("some").unwrap();
|
||||
assert!(sub_matches.is_present("test"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flag_subcommand_short_with_args() {
|
||||
let matches = App::new("test")
|
||||
.subcommand(
|
||||
App::new("some").short_flag('S').arg(
|
||||
Arg::new("test")
|
||||
.short('t')
|
||||
.long("test")
|
||||
.about("testing testing"),
|
||||
),
|
||||
)
|
||||
.get_matches_from(vec!["myprog", "-St"]);
|
||||
assert_eq!(matches.subcommand_name().unwrap(), "some");
|
||||
let sub_matches = matches.subcommand_matches("some").unwrap();
|
||||
assert!(sub_matches.is_present("test"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flag_subcommand_short_with_alias() {
|
||||
let matches = App::new("test")
|
||||
.subcommand(
|
||||
App::new("some")
|
||||
.short_flag('S')
|
||||
.arg(
|
||||
Arg::new("test")
|
||||
.short('t')
|
||||
.long("test")
|
||||
.about("testing testing"),
|
||||
)
|
||||
.short_flag_alias('M')
|
||||
.short_flag_alias('B'),
|
||||
)
|
||||
.get_matches_from(vec!["myprog", "-Bt"]);
|
||||
assert_eq!(matches.subcommand_name().unwrap(), "some");
|
||||
let sub_matches = matches.subcommand_matches("some").unwrap();
|
||||
assert!(sub_matches.is_present("test"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flag_subcommand_short_with_alias_same_as_short_flag() {
|
||||
let matches = App::new("test")
|
||||
.subcommand(App::new("some").short_flag('S').short_flag_alias('S'))
|
||||
.get_matches_from(vec!["myprog", "-S"]);
|
||||
assert_eq!(matches.subcommand_name().unwrap(), "some");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flag_subcommand_long_with_alias_same_as_long_flag() {
|
||||
let matches = App::new("test")
|
||||
.subcommand(App::new("some").long_flag("sync").long_flag_alias("sync"))
|
||||
.get_matches_from(vec!["myprog", "--sync"]);
|
||||
assert_eq!(matches.subcommand_name().unwrap(), "some");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flag_subcommand_short_with_aliases_vis_and_hidden() {
|
||||
let app = App::new("test").subcommand(
|
||||
App::new("some")
|
||||
.short_flag('S')
|
||||
.arg(
|
||||
Arg::new("test")
|
||||
.short('t')
|
||||
.long("test")
|
||||
.about("testing testing"),
|
||||
)
|
||||
.visible_short_flag_aliases(&['M', 'B'])
|
||||
.short_flag_alias('C'),
|
||||
);
|
||||
let app1 = app.clone();
|
||||
let matches1 = app1.get_matches_from(vec!["test", "-M"]);
|
||||
assert_eq!(matches1.subcommand_name().unwrap(), "some");
|
||||
|
||||
let app2 = app.clone();
|
||||
let matches2 = app2.get_matches_from(vec!["test", "-C"]);
|
||||
assert_eq!(matches2.subcommand_name().unwrap(), "some");
|
||||
|
||||
let app3 = app.clone();
|
||||
let matches3 = app3.get_matches_from(vec!["test", "-B"]);
|
||||
assert_eq!(matches3.subcommand_name().unwrap(), "some");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flag_subcommand_short_with_aliases() {
|
||||
let matches = App::new("test")
|
||||
.subcommand(
|
||||
App::new("some")
|
||||
.short_flag('S')
|
||||
.arg(
|
||||
Arg::new("test")
|
||||
.short('t')
|
||||
.long("test")
|
||||
.about("testing testing"),
|
||||
)
|
||||
.short_flag_aliases(&['M', 'B']),
|
||||
)
|
||||
.get_matches_from(vec!["myprog", "-Bt"]);
|
||||
assert_eq!(matches.subcommand_name().unwrap(), "some");
|
||||
let sub_matches = matches.subcommand_matches("some").unwrap();
|
||||
assert!(sub_matches.is_present("test"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn flag_subcommand_short_with_alias_hyphen() {
|
||||
let _ = App::new("test")
|
||||
.subcommand(
|
||||
App::new("some")
|
||||
.short_flag('S')
|
||||
.arg(
|
||||
Arg::new("test")
|
||||
.short('t')
|
||||
.long("test")
|
||||
.about("testing testing"),
|
||||
)
|
||||
.short_flag_alias('-'),
|
||||
)
|
||||
.get_matches_from(vec!["myprog", "-Bt"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn flag_subcommand_short_with_aliases_hyphen() {
|
||||
let _ = App::new("test")
|
||||
.subcommand(
|
||||
App::new("some")
|
||||
.short_flag('S')
|
||||
.arg(
|
||||
Arg::new("test")
|
||||
.short('t')
|
||||
.long("test")
|
||||
.about("testing testing"),
|
||||
)
|
||||
.short_flag_aliases(&['-', '-', '-']),
|
||||
)
|
||||
.get_matches_from(vec!["myprog", "-Bt"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flag_subcommand_long() {
|
||||
let matches = App::new("test")
|
||||
.subcommand(
|
||||
App::new("some").long_flag("some").arg(
|
||||
Arg::new("test")
|
||||
.short('t')
|
||||
.long("test")
|
||||
.about("testing testing"),
|
||||
),
|
||||
)
|
||||
.get_matches_from(vec!["myprog", "--some", "--test"]);
|
||||
assert_eq!(matches.subcommand_name().unwrap(), "some");
|
||||
let sub_matches = matches.subcommand_matches("some").unwrap();
|
||||
assert!(sub_matches.is_present("test"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flag_subcommand_long_with_alias() {
|
||||
let matches = App::new("test")
|
||||
.subcommand(
|
||||
App::new("some")
|
||||
.long_flag("some")
|
||||
.arg(
|
||||
Arg::new("test")
|
||||
.short('t')
|
||||
.long("test")
|
||||
.about("testing testing"),
|
||||
)
|
||||
.long_flag_alias("result"),
|
||||
)
|
||||
.get_matches_from(vec!["myprog", "--result", "--test"]);
|
||||
assert_eq!(matches.subcommand_name().unwrap(), "some");
|
||||
let sub_matches = matches.subcommand_matches("some").unwrap();
|
||||
assert!(sub_matches.is_present("test"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flag_subcommand_long_with_aliases() {
|
||||
let matches = App::new("test")
|
||||
.subcommand(
|
||||
App::new("some")
|
||||
.long_flag("some")
|
||||
.arg(
|
||||
Arg::new("test")
|
||||
.short('t')
|
||||
.long("test")
|
||||
.about("testing testing"),
|
||||
)
|
||||
.long_flag_aliases(&["result", "someall"]),
|
||||
)
|
||||
.get_matches_from(vec!["myprog", "--result", "--test"]);
|
||||
assert_eq!(matches.subcommand_name().unwrap(), "some");
|
||||
let sub_matches = matches.subcommand_matches("some").unwrap();
|
||||
assert!(sub_matches.is_present("test"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flag_subcommand_multiple() {
|
||||
let matches = App::new("test")
|
||||
.subcommand(
|
||||
App::new("some")
|
||||
.short_flag('S')
|
||||
.long_flag("some")
|
||||
.arg(Arg::from("-f, --flag 'some flag'"))
|
||||
.arg(Arg::from("-p, --print 'print something'"))
|
||||
.subcommand(
|
||||
App::new("result")
|
||||
.short_flag('R')
|
||||
.long_flag("result")
|
||||
.arg(Arg::from("-f, --flag 'some flag'"))
|
||||
.arg(Arg::from("-p, --print 'print something'")),
|
||||
),
|
||||
)
|
||||
.get_matches_from(vec!["myprog", "-SfpRfp"]);
|
||||
assert_eq!(matches.subcommand_name().unwrap(), "some");
|
||||
let sub_matches = matches.subcommand_matches("some").unwrap();
|
||||
assert!(sub_matches.is_present("flag"));
|
||||
assert!(sub_matches.is_present("print"));
|
||||
assert_eq!(sub_matches.subcommand_name().unwrap(), "result");
|
||||
let result_matches = sub_matches.subcommand_matches("result").unwrap();
|
||||
assert!(result_matches.is_present("flag"));
|
||||
assert!(result_matches.is_present("print"));
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
#[test]
|
||||
#[should_panic = "Short option names must be unique for each argument, but \'-f\' is used by both an App named \'some\' and an Arg named \'test\'"]
|
||||
fn flag_subcommand_short_conflict_with_arg() {
|
||||
let _ = App::new("test")
|
||||
.subcommand(App::new("some").short_flag('f').long_flag("some"))
|
||||
.arg(Arg::new("test").short('f'))
|
||||
.get_matches_from(vec!["myprog", "-f"]);
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
#[test]
|
||||
#[should_panic = "Short option names must be unique for each argument, but \'-f\' is used by both an App named \'some\' and an App named \'result\'"]
|
||||
fn flag_subcommand_short_conflict_with_alias() {
|
||||
let _ = App::new("test")
|
||||
.subcommand(App::new("some").short_flag('f').long_flag("some"))
|
||||
.subcommand(App::new("result").short_flag('t').short_flag_alias('f'))
|
||||
.get_matches_from(vec!["myprog", "-f"]);
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
#[test]
|
||||
#[should_panic = "Long option names must be unique for each argument, but \'--flag\' is used by both an App named \'some\' and an App named \'result\'"]
|
||||
fn flag_subcommand_long_conflict_with_alias() {
|
||||
let _ = App::new("test")
|
||||
.subcommand(App::new("some").long_flag("flag"))
|
||||
.subcommand(App::new("result").long_flag("test").long_flag_alias("flag"))
|
||||
.get_matches_from(vec!["myprog", "--flag"]);
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
#[test]
|
||||
#[should_panic = "Short option names must be unique for each argument, but \'-f\' is used by both an App named \'some\' and an Arg named \'test\'"]
|
||||
fn flag_subcommand_short_conflict_with_arg_alias() {
|
||||
let _ = App::new("test")
|
||||
.subcommand(App::new("some").short_flag('f').long_flag("some"))
|
||||
.arg(Arg::new("test").short('t').short_alias('f'))
|
||||
.get_matches_from(vec!["myprog", "-f"]);
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
#[test]
|
||||
#[should_panic = "Long option names must be unique for each argument, but \'--some\' is used by both an App named \'some\' and an Arg named \'test\'"]
|
||||
fn flag_subcommand_long_conflict_with_arg_alias() {
|
||||
let _ = App::new("test")
|
||||
.subcommand(App::new("some").short_flag('f').long_flag("some"))
|
||||
.arg(Arg::new("test").long("test").alias("some"))
|
||||
.get_matches_from(vec!["myprog", "--some"]);
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
#[test]
|
||||
#[should_panic = "Long option names must be unique for each argument, but \'--flag\' is used by both an App named \'some\' and an Arg named \'flag\'"]
|
||||
fn flag_subcommand_long_conflict_with_arg() {
|
||||
let _ = App::new("test")
|
||||
.subcommand(App::new("some").short_flag('a').long_flag("flag"))
|
||||
.arg(Arg::new("flag").long("flag"))
|
||||
.get_matches_from(vec!["myprog", "--flag"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flag_subcommand_conflict_with_help() {
|
||||
let _ = App::new("test")
|
||||
.subcommand(App::new("help").short_flag('h').long_flag("help"))
|
||||
.get_matches_from(vec!["myprog", "--help"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flag_subcommand_conflict_with_version() {
|
||||
let _ = App::new("test")
|
||||
.subcommand(App::new("ver").short_flag('V').long_flag("version"))
|
||||
.get_matches_from(vec!["myprog", "--version"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flag_subcommand_long_infer_pass() {
|
||||
let m = App::new("prog")
|
||||
.setting(AppSettings::InferSubcommands)
|
||||
.subcommand(App::new("test").long_flag("test"))
|
||||
.get_matches_from(vec!["prog", "--te"]);
|
||||
assert_eq!(m.subcommand_name(), Some("test"));
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "suggestions"))]
|
||||
#[test]
|
||||
fn flag_subcommand_long_infer_fail() {
|
||||
let m = App::new("prog")
|
||||
.setting(AppSettings::InferSubcommands)
|
||||
.subcommand(App::new("test").long_flag("test"))
|
||||
.subcommand(App::new("temp").long_flag("temp"))
|
||||
.try_get_matches_from(vec!["prog", "--te"]);
|
||||
assert!(m.is_err(), "{:#?}", m.unwrap());
|
||||
assert_eq!(m.unwrap_err().kind, ErrorKind::UnknownArgument);
|
||||
}
|
||||
|
||||
#[cfg(feature = "suggestions")]
|
||||
#[test]
|
||||
fn flag_subcommand_long_infer_fail() {
|
||||
let m = App::new("prog")
|
||||
.setting(AppSettings::InferSubcommands)
|
||||
.subcommand(App::new("test").long_flag("test"))
|
||||
.subcommand(App::new("temp").long_flag("temp"))
|
||||
.try_get_matches_from(vec!["prog", "--te"]);
|
||||
assert!(m.is_err(), "{:#?}", m.unwrap());
|
||||
assert_eq!(m.unwrap_err().kind, ErrorKind::UnknownArgument);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flag_subcommand_long_infer_pass_close() {
|
||||
let m = App::new("prog")
|
||||
.setting(AppSettings::InferSubcommands)
|
||||
.subcommand(App::new("test").long_flag("test"))
|
||||
.subcommand(App::new("temp").long_flag("temp"))
|
||||
.get_matches_from(vec!["prog", "--tes"]);
|
||||
assert_eq!(m.subcommand_name(), Some("test"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flag_subcommand_long_infer_exact_match() {
|
||||
let m = App::new("prog")
|
||||
.setting(AppSettings::InferSubcommands)
|
||||
.subcommand(App::new("test").long_flag("test"))
|
||||
.subcommand(App::new("testa").long_flag("testa"))
|
||||
.subcommand(App::new("testb").long_flag("testb"))
|
||||
.get_matches_from(vec!["prog", "--test"]);
|
||||
assert_eq!(m.subcommand_name(), Some("test"));
|
||||
}
|
||||
|
||||
static FLAG_SUBCOMMAND_HELP: &str = "pacman-query
|
||||
Query the package database.
|
||||
|
||||
USAGE:
|
||||
pacman {query, --query, -Q} [OPTIONS]
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
-V, --version Prints version information
|
||||
|
||||
OPTIONS:
|
||||
-i, --info <info>... view package information
|
||||
-s, --search <search>... search locally-installed packages for matching strings";
|
||||
|
||||
#[test]
|
||||
fn flag_subcommand_long_short_normal_usage_string() {
|
||||
let app = App::new("pacman")
|
||||
.about("package manager utility")
|
||||
.version("5.2.1")
|
||||
.setting(AppSettings::SubcommandRequiredElseHelp)
|
||||
.author("Pacman Development Team")
|
||||
// Query subcommand
|
||||
//
|
||||
// Only a few of its arguments are implemented below.
|
||||
.subcommand(
|
||||
App::new("query")
|
||||
.short_flag('Q')
|
||||
.long_flag("query")
|
||||
.about("Query the package database.")
|
||||
.arg(
|
||||
Arg::new("search")
|
||||
.short('s')
|
||||
.long("search")
|
||||
.about("search locally-installed packages for matching strings")
|
||||
.conflicts_with("info")
|
||||
.multiple_values(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("info")
|
||||
.long("info")
|
||||
.short('i')
|
||||
.conflicts_with("search")
|
||||
.about("view package information")
|
||||
.multiple_values(true),
|
||||
),
|
||||
);
|
||||
assert!(utils::compare_output(
|
||||
app,
|
||||
"pacman -Qh",
|
||||
FLAG_SUBCOMMAND_HELP,
|
||||
false
|
||||
));
|
||||
}
|
||||
|
||||
static FLAG_SUBCOMMAND_NO_SHORT_HELP: &str = "pacman-query
|
||||
Query the package database.
|
||||
|
||||
USAGE:
|
||||
pacman {query, --query} [OPTIONS]
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
-V, --version Prints version information
|
||||
|
||||
OPTIONS:
|
||||
-i, --info <info>... view package information
|
||||
-s, --search <search>... search locally-installed packages for matching strings";
|
||||
|
||||
#[test]
|
||||
fn flag_subcommand_long_normal_usage_string() {
|
||||
let app = App::new("pacman")
|
||||
.about("package manager utility")
|
||||
.version("5.2.1")
|
||||
.setting(AppSettings::SubcommandRequiredElseHelp)
|
||||
.author("Pacman Development Team")
|
||||
// Query subcommand
|
||||
//
|
||||
// Only a few of its arguments are implemented below.
|
||||
.subcommand(
|
||||
App::new("query")
|
||||
.long_flag("query")
|
||||
.about("Query the package database.")
|
||||
.arg(
|
||||
Arg::new("search")
|
||||
.short('s')
|
||||
.long("search")
|
||||
.about("search locally-installed packages for matching strings")
|
||||
.conflicts_with("info")
|
||||
.multiple_values(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("info")
|
||||
.long("info")
|
||||
.short('i')
|
||||
.conflicts_with("search")
|
||||
.about("view package information")
|
||||
.multiple_values(true),
|
||||
),
|
||||
);
|
||||
assert!(utils::compare_output(
|
||||
app,
|
||||
"pacman query --help",
|
||||
FLAG_SUBCOMMAND_NO_SHORT_HELP,
|
||||
false
|
||||
));
|
||||
}
|
||||
|
||||
static FLAG_SUBCOMMAND_NO_LONG_HELP: &str = "pacman-query
|
||||
Query the package database.
|
||||
|
||||
USAGE:
|
||||
pacman {query, -Q} [OPTIONS]
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
-V, --version Prints version information
|
||||
|
||||
OPTIONS:
|
||||
-i, --info <info>... view package information
|
||||
-s, --search <search>... search locally-installed packages for matching strings";
|
||||
|
||||
#[test]
|
||||
fn flag_subcommand_short_normal_usage_string() {
|
||||
let app = App::new("pacman")
|
||||
.about("package manager utility")
|
||||
.version("5.2.1")
|
||||
.setting(AppSettings::SubcommandRequiredElseHelp)
|
||||
.author("Pacman Development Team")
|
||||
// Query subcommand
|
||||
//
|
||||
// Only a few of its arguments are implemented below.
|
||||
.subcommand(
|
||||
App::new("query")
|
||||
.short_flag('Q')
|
||||
.about("Query the package database.")
|
||||
.arg(
|
||||
Arg::new("search")
|
||||
.short('s')
|
||||
.long("search")
|
||||
.about("search locally-installed packages for matching strings")
|
||||
.conflicts_with("info")
|
||||
.multiple_values(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("info")
|
||||
.long("info")
|
||||
.short('i')
|
||||
.conflicts_with("search")
|
||||
.about("view package information")
|
||||
.multiple_values(true),
|
||||
),
|
||||
);
|
||||
assert!(utils::compare_output(
|
||||
app,
|
||||
"pacman query --help",
|
||||
FLAG_SUBCOMMAND_NO_LONG_HELP,
|
||||
false
|
||||
));
|
||||
}
|
Loading…
Reference in a new issue