//! 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 { 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 { 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 { 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> { 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"); } }