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:
bors[bot] 2020-07-18 14:06:50 +00:00 committed by GitHub
commit ff6beebd6e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 1286 additions and 30 deletions

View 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
}
}

View file

@ -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> {

View file

@ -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(())

View file

@ -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
View 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
));
}