blob: ca76d189baa12e9ccf352c6c78ce95df753f3f22 [file] [log] [blame]
//! Helpers for writing generators
use clap::{Arg, Command};
/// Gets all subcommands including child subcommands in the form of `("name", "bin_name")`.
///
/// Subcommand `rustup toolchain install` would be converted to
/// `("install", "rustup toolchain install")`.
pub fn all_subcommands(cmd: &Command) -> Vec<(String, String)> {
let mut subcmds: Vec<_> = subcommands(cmd);
for sc_v in cmd.get_subcommands().map(all_subcommands) {
subcmds.extend(sc_v);
}
subcmds
}
/// Finds the subcommand [`clap::Command`] from the given [`clap::Command`] with the given path.
///
/// **NOTE:** `path` should not contain the root `bin_name`.
pub fn find_subcommand_with_path<'cmd>(p: &'cmd Command, path: Vec<&str>) -> &'cmd Command {
let mut cmd = p;
for sc in path {
cmd = cmd.find_subcommand(sc).unwrap();
}
cmd
}
/// Gets subcommands of [`clap::Command`] in the form of `("name", "bin_name")`.
///
/// Subcommand `rustup toolchain install` would be converted to
/// `("install", "rustup toolchain install")`.
pub fn subcommands(p: &Command) -> Vec<(String, String)> {
debug!("subcommands: name={}", p.get_name());
debug!("subcommands: Has subcommands...{:?}", p.has_subcommands());
let mut subcmds = vec![];
for sc in p.get_subcommands() {
let sc_bin_name = sc.get_bin_name().unwrap();
debug!(
"subcommands:iter: name={}, bin_name={}",
sc.get_name(),
sc_bin_name
);
subcmds.push((sc.get_name().to_string(), sc_bin_name.to_string()));
}
subcmds
}
/// Gets all the short options, their visible aliases and flags of a [`clap::Command`].
/// Includes `h` and `V` depending on the [`clap::Command`] settings.
pub fn shorts_and_visible_aliases(p: &Command) -> Vec<char> {
debug!("shorts: name={}", p.get_name());
p.get_arguments()
.filter_map(|a| {
if !a.is_positional() {
if a.get_visible_short_aliases().is_some() && a.get_short().is_some() {
let mut shorts_and_visible_aliases = a.get_visible_short_aliases().unwrap();
shorts_and_visible_aliases.push(a.get_short().unwrap());
Some(shorts_and_visible_aliases)
} else if a.get_visible_short_aliases().is_none() && a.get_short().is_some() {
Some(vec![a.get_short().unwrap()])
} else {
None
}
} else {
None
}
})
.flatten()
.collect()
}
/// Gets all the long options, their visible aliases and flags of a [`clap::Command`].
/// Includes `help` and `version` depending on the [`clap::Command`] settings.
pub fn longs_and_visible_aliases(p: &Command) -> Vec<String> {
debug!("longs: name={}", p.get_name());
p.get_arguments()
.filter_map(|a| {
if !a.is_positional() {
if a.get_visible_aliases().is_some() && a.get_long().is_some() {
let mut visible_aliases: Vec<_> = a
.get_visible_aliases()
.unwrap()
.into_iter()
.map(|s| s.to_string())
.collect();
visible_aliases.push(a.get_long().unwrap().to_string());
Some(visible_aliases)
} else if a.get_visible_aliases().is_none() && a.get_long().is_some() {
Some(vec![a.get_long().unwrap().to_string()])
} else {
None
}
} else {
None
}
})
.flatten()
.collect()
}
/// Gets all the flags of a [`clap::Command`](Command).
/// Includes `help` and `version` depending on the [`clap::Command`] settings.
pub fn flags(p: &Command) -> Vec<Arg> {
debug!("flags: name={}", p.get_name());
p.get_arguments()
.filter(|a| !a.get_num_args().expect("built").takes_values() && !a.is_positional())
.cloned()
.collect()
}
/// Get the possible values for completion
pub fn possible_values(a: &Arg) -> Option<Vec<clap::builder::PossibleValue>> {
if !a.get_num_args().expect("built").takes_values() {
None
} else {
a.get_value_parser()
.possible_values()
.map(|pvs| pvs.collect())
}
}
#[cfg(test)]
mod tests {
use super::*;
use clap::Arg;
use clap::ArgAction;
fn common_app() -> Command {
Command::new("myapp")
.subcommand(
Command::new("test").subcommand(Command::new("config")).arg(
Arg::new("file")
.short('f')
.short_alias('c')
.visible_short_alias('p')
.long("file")
.action(ArgAction::SetTrue)
.visible_alias("path"),
),
)
.subcommand(Command::new("hello"))
.bin_name("my-cmd")
}
fn built() -> Command {
let mut cmd = common_app();
cmd.build();
cmd
}
fn built_with_version() -> Command {
let mut cmd = common_app().version("3.0");
cmd.build();
cmd
}
#[test]
fn test_subcommands() {
let cmd = built_with_version();
assert_eq!(
subcommands(&cmd),
vec![
("test".to_string(), "my-cmd test".to_string()),
("hello".to_string(), "my-cmd hello".to_string()),
("help".to_string(), "my-cmd help".to_string()),
]
);
}
#[test]
fn test_all_subcommands() {
let cmd = built_with_version();
assert_eq!(
all_subcommands(&cmd),
vec![
("test".to_string(), "my-cmd test".to_string()),
("hello".to_string(), "my-cmd hello".to_string()),
("help".to_string(), "my-cmd help".to_string()),
("config".to_string(), "my-cmd test config".to_string()),
("help".to_string(), "my-cmd test help".to_string()),
("config".to_string(), "my-cmd test help config".to_string()),
("help".to_string(), "my-cmd test help help".to_string()),
("test".to_string(), "my-cmd help test".to_string()),
("hello".to_string(), "my-cmd help hello".to_string()),
("help".to_string(), "my-cmd help help".to_string()),
("config".to_string(), "my-cmd help test config".to_string()),
]
);
}
#[test]
fn test_find_subcommand_with_path() {
let cmd = built_with_version();
let sc_app = find_subcommand_with_path(&cmd, "test config".split(' ').collect());
assert_eq!(sc_app.get_name(), "config");
}
#[test]
fn test_flags() {
let cmd = built_with_version();
let actual_flags = flags(&cmd);
assert_eq!(actual_flags.len(), 2);
assert_eq!(actual_flags[0].get_long(), Some("help"));
assert_eq!(actual_flags[1].get_long(), Some("version"));
let sc_flags = flags(find_subcommand_with_path(&cmd, vec!["test"]));
assert_eq!(sc_flags.len(), 2);
assert_eq!(sc_flags[0].get_long(), Some("file"));
assert_eq!(sc_flags[1].get_long(), Some("help"));
}
#[test]
fn test_flag_subcommand() {
let cmd = built();
let actual_flags = flags(&cmd);
assert_eq!(actual_flags.len(), 1);
assert_eq!(actual_flags[0].get_long(), Some("help"));
let sc_flags = flags(find_subcommand_with_path(&cmd, vec!["test"]));
assert_eq!(sc_flags.len(), 2);
assert_eq!(sc_flags[0].get_long(), Some("file"));
assert_eq!(sc_flags[1].get_long(), Some("help"));
}
#[test]
fn test_shorts() {
let cmd = built_with_version();
let shorts = shorts_and_visible_aliases(&cmd);
assert_eq!(shorts.len(), 2);
assert_eq!(shorts[0], 'h');
assert_eq!(shorts[1], 'V');
let sc_shorts = shorts_and_visible_aliases(find_subcommand_with_path(&cmd, vec!["test"]));
assert_eq!(sc_shorts.len(), 3);
assert_eq!(sc_shorts[0], 'p');
assert_eq!(sc_shorts[1], 'f');
assert_eq!(sc_shorts[2], 'h');
}
#[test]
fn test_longs() {
let cmd = built_with_version();
let longs = longs_and_visible_aliases(&cmd);
assert_eq!(longs.len(), 2);
assert_eq!(longs[0], "help");
assert_eq!(longs[1], "version");
let sc_longs = longs_and_visible_aliases(find_subcommand_with_path(&cmd, vec!["test"]));
assert_eq!(sc_longs.len(), 3);
assert_eq!(sc_longs[0], "path");
assert_eq!(sc_longs[1], "file");
assert_eq!(sc_longs[2], "help");
}
}