summaryrefslogtreecommitdiff
path: root/src/generator/utils.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/generator/utils.rs')
-rw-r--r--src/generator/utils.rs278
1 files changed, 278 insertions, 0 deletions
diff --git a/src/generator/utils.rs b/src/generator/utils.rs
new file mode 100644
index 0000000..ca76d18
--- /dev/null
+++ b/src/generator/utils.rs
@@ -0,0 +1,278 @@
+//! 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");
+ }
+}