From 536471cb7d63ef41af13335a3941b3d23c26dc93 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Tue, 12 Oct 2021 08:27:24 -0500 Subject: [PATCH] fix(gen)!: Simplify `Generator` trait This is a followup to #2721 where we decided to remove all of the helpers from the trait. They are now in a `utils` mod. A part of me wants this module to be private but it'd be a lot to duplicate for the split out modules. Note: this doesn't update fig. Fig was never added to the workspace and is already broken (and not just from #2721). My plan is to get this merged and then fix Fig, rather than fix it in multiple passes. --- clap_generate/src/generators/mod.rs | 253 +----------------- clap_generate/src/generators/shells/bash.rs | 19 +- clap_generate/src/generators/shells/elvish.rs | 3 +- clap_generate/src/generators/shells/fish.rs | 3 +- .../src/generators/shells/powershell.rs | 3 +- clap_generate/src/generators/shells/zsh.rs | 7 +- clap_generate/src/lib.rs | 2 + clap_generate/src/utils.rs | 240 +++++++++++++++++ 8 files changed, 261 insertions(+), 269 deletions(-) create mode 100644 clap_generate/src/utils.rs diff --git a/clap_generate/src/generators/mod.rs b/clap_generate/src/generators/mod.rs index 43a79904..73d58434 100644 --- a/clap_generate/src/generators/mod.rs +++ b/clap_generate/src/generators/mod.rs @@ -4,7 +4,7 @@ mod shells; use std::io::Write; // Internal -use clap::{App, Arg}; +use clap::App; pub use shells::*; /// Generator trait which can be used to write generators @@ -53,255 +53,4 @@ pub trait Generator { /// } /// ``` fn generate(&self, app: &App, buf: &mut dyn Write); - - /// 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")`. - fn all_subcommands(&self, app: &App) -> Vec<(String, String)> { - let mut subcmds: Vec<_> = self.subcommands(app); - - for sc_v in app.get_subcommands().map(|s| self.all_subcommands(s)) { - subcmds.extend(sc_v); - } - - subcmds - } - - /// Finds the subcommand [`clap::App`] from the given [`clap::App`] with the given path. - /// - /// **NOTE:** `path` should not contain the root `bin_name`. - fn find_subcommand_with_path<'help, 'app>( - &self, - p: &'app App<'help>, - path: Vec<&str>, - ) -> &'app App<'help> { - let mut app = p; - - for sc in path { - app = app.find_subcommand(sc).unwrap(); - } - - app - } - - /// Gets subcommands of [`clap::App`] in the form of `("name", "bin_name")`. - /// - /// Subcommand `rustup toolchain install` would be converted to - /// `("install", "rustup toolchain install")`. - fn subcommands(&self, p: &App) -> Vec<(String, String)> { - debug!("subcommands: name={}", p.get_name()); - debug!("subcommands: Has subcommands...{:?}", p.has_subcommands()); - - let mut subcmds = vec![]; - - if !p.has_subcommands() { - return subcmds; - } - - 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::App`]. - /// Includes `h` and `V` depending on the [`clap::AppSettings`]. - fn shorts_and_visible_aliases(&self, p: &App) -> Vec { - debug!("shorts: name={}", p.get_name()); - - p.get_arguments() - .filter_map(|a| { - if a.get_index().is_none() { - 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::App`]. - /// Includes `help` and `version` depending on the [`clap::AppSettings`]. - fn longs_and_visible_aliases(&self, p: &App) -> Vec { - debug!("longs: name={}", p.get_name()); - - p.get_arguments() - .filter_map(|a| { - if a.get_index().is_none() { - 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::App`](App). - /// Includes `help` and `version` depending on the [`clap::AppSettings`]. - fn flags<'help>(&self, p: &App<'help>) -> Vec> { - debug!("flags: name={}", p.get_name()); - p.get_flags().cloned().collect() - } -} - -#[cfg(test)] -mod tests { - use super::*; - use pretty_assertions::assert_eq; - - struct Foo; - - impl Generator for Foo { - fn generate(&self, _: &App, _: &mut dyn Write) {} - - fn file_name(&self, name: &str) -> String { - name.to_string() - } - } - - fn common() -> App<'static> { - let mut app = App::new("myapp") - .version("3.0") - .subcommand( - App::new("test").subcommand(App::new("config")).arg( - Arg::new("file") - .short('f') - .short_alias('c') - .visible_short_alias('p') - .long("file") - .visible_alias("path"), - ), - ) - .subcommand(App::new("hello")) - .bin_name("my-app"); - - app._build(); - app._build_bin_names(); - app - } - - #[test] - fn test_subcommands() { - let app = common(); - - assert_eq!( - Foo.subcommands(&app), - vec![ - ("test".to_string(), "my-app test".to_string()), - ("hello".to_string(), "my-app hello".to_string()), - ("help".to_string(), "my-app help".to_string()), - ] - ); - } - - #[test] - fn test_all_subcommands() { - let app = common(); - - assert_eq!( - Foo.all_subcommands(&app), - vec![ - ("test".to_string(), "my-app test".to_string()), - ("hello".to_string(), "my-app hello".to_string()), - ("help".to_string(), "my-app help".to_string()), - ("config".to_string(), "my-app test config".to_string()), - ] - ); - } - - #[test] - fn test_find_subcommand_with_path() { - let app = common(); - let sc_app = Foo.find_subcommand_with_path(&app, "test config".split(' ').collect()); - - assert_eq!(sc_app.get_name(), "config"); - } - - #[test] - fn test_flags() { - let app = common(); - let flags = Foo.flags(&app); - - assert_eq!(flags.len(), 2); - assert_eq!(flags[0].get_long(), Some("help")); - assert_eq!(flags[1].get_long(), Some("version")); - - let sc_flags = Foo.flags(Foo.find_subcommand_with_path(&app, vec!["test"])); - - assert_eq!(sc_flags.len(), 3); - assert_eq!(sc_flags[0].get_long(), Some("file")); - assert_eq!(sc_flags[1].get_long(), Some("help")); - assert_eq!(sc_flags[2].get_long(), Some("version")); - } - - #[test] - fn test_shorts() { - let app = common(); - let shorts = Foo.shorts_and_visible_aliases(&app); - - assert_eq!(shorts.len(), 2); - assert_eq!(shorts[0], 'h'); - assert_eq!(shorts[1], 'V'); - - let sc_shorts = - Foo.shorts_and_visible_aliases(Foo.find_subcommand_with_path(&app, vec!["test"])); - - assert_eq!(sc_shorts.len(), 4); - assert_eq!(sc_shorts[0], 'p'); - assert_eq!(sc_shorts[1], 'f'); - assert_eq!(sc_shorts[2], 'h'); - assert_eq!(sc_shorts[3], 'V'); - } - - #[test] - fn test_longs() { - let app = common(); - let longs = Foo.longs_and_visible_aliases(&app); - - assert_eq!(longs.len(), 2); - assert_eq!(longs[0], "help"); - assert_eq!(longs[1], "version"); - - let sc_longs = - Foo.longs_and_visible_aliases(Foo.find_subcommand_with_path(&app, vec!["test"])); - - assert_eq!(sc_longs.len(), 4); - assert_eq!(sc_longs[0], "path"); - assert_eq!(sc_longs[1], "file"); - assert_eq!(sc_longs[2], "help"); - assert_eq!(sc_longs[3], "version"); - } } diff --git a/clap_generate/src/generators/shells/bash.rs b/clap_generate/src/generators/shells/bash.rs index bb2689e7..11075726 100644 --- a/clap_generate/src/generators/shells/bash.rs +++ b/clap_generate/src/generators/shells/bash.rs @@ -2,6 +2,7 @@ use std::io::Write; // Internal +use crate::utils; use crate::Generator; use clap::*; @@ -75,8 +76,7 @@ fn all_subcommands(app: &App) -> String { debug!("all_subcommands"); let mut subcmds = vec![String::new()]; - let mut scs = Bash - .all_subcommands(app) + let mut scs = utils::all_subcommands(app) .iter() .map(|x| x.0.clone()) .collect::>(); @@ -101,8 +101,7 @@ fn subcommand_details(app: &App) -> String { debug!("subcommand_details"); let mut subcmd_dets = vec![String::new()]; - let mut scs = Bash - .all_subcommands(app) + let mut scs = utils::all_subcommands(app) .iter() .map(|x| x.1.replace(" ", "__")) .collect::>(); @@ -138,7 +137,7 @@ fn subcommand_details(app: &App) -> String { fn option_details_for_path(app: &App, path: &str) -> String { debug!("option_details_for_path: path={}", path); - let p = Bash.find_subcommand_with_path(app, path.split("__").skip(1).collect()); + let p = utils::find_subcommand_with_path(app, path.split("__").skip(1).collect()); let mut opts = vec![String::new()]; for o in p.get_opts() { @@ -191,17 +190,15 @@ fn vals_for(o: &Arg) -> String { fn all_options_for_path(app: &App, path: &str) -> String { debug!("all_options_for_path: path={}", path); - let p = Bash.find_subcommand_with_path(app, path.split("__").skip(1).collect()); - let scs: Vec<_> = Bash.subcommands(p).iter().map(|x| x.0.clone()).collect(); + let p = utils::find_subcommand_with_path(app, path.split("__").skip(1).collect()); + let scs: Vec<_> = utils::subcommands(p).iter().map(|x| x.0.clone()).collect(); let opts = format!( "{shorts} {longs} {pos} {subcmds}", - shorts = Bash - .shorts_and_visible_aliases(p) + shorts = utils::shorts_and_visible_aliases(p) .iter() .fold(String::new(), |acc, s| format!("{} -{}", acc, s)), - longs = Bash - .longs_and_visible_aliases(p) + longs = utils::longs_and_visible_aliases(p) .iter() .fold(String::new(), |acc, l| format!("{} --{}", acc, l)), pos = p diff --git a/clap_generate/src/generators/shells/elvish.rs b/clap_generate/src/generators/shells/elvish.rs index c377727e..e5acceab 100644 --- a/clap_generate/src/generators/shells/elvish.rs +++ b/clap_generate/src/generators/shells/elvish.rs @@ -2,6 +2,7 @@ use std::io::Write; // Internal +use crate::utils; use crate::Generator; use crate::INTERNAL_ERROR_MSG; use clap::*; @@ -99,7 +100,7 @@ fn generate_inner<'help>( } } - for flag in Elvish.flags(p) { + for flag in utils::flags(p) { if let Some(shorts) = flag.get_short_and_visible_aliases() { let tooltip = get_tooltip(flag.get_about(), shorts[0]); for short in shorts { diff --git a/clap_generate/src/generators/shells/fish.rs b/clap_generate/src/generators/shells/fish.rs index c2b81197..fcc5dc6d 100644 --- a/clap_generate/src/generators/shells/fish.rs +++ b/clap_generate/src/generators/shells/fish.rs @@ -2,6 +2,7 @@ use std::io::Write; // Internal +use crate::utils; use crate::Generator; use clap::*; @@ -96,7 +97,7 @@ fn gen_fish_inner(root_command: &str, parent_commands: &[&str], app: &App, buffe buffer.push('\n'); } - for flag in Fish.flags(app) { + for flag in utils::flags(app) { let mut template = basic_template.clone(); if let Some(shorts) = flag.get_short_and_visible_aliases() { diff --git a/clap_generate/src/generators/shells/powershell.rs b/clap_generate/src/generators/shells/powershell.rs index 575efc99..2e6c36cb 100644 --- a/clap_generate/src/generators/shells/powershell.rs +++ b/clap_generate/src/generators/shells/powershell.rs @@ -2,6 +2,7 @@ use std::io::Write; // Internal +use crate::utils; use crate::Generator; use crate::INTERNAL_ERROR_MSG; use clap::*; @@ -115,7 +116,7 @@ fn generate_inner<'help>( } } - for flag in PowerShell.flags(p) { + for flag in utils::flags(p) { if let Some(shorts) = flag.get_short_and_visible_aliases() { let tooltip = get_tooltip(flag.get_about(), shorts[0]); for short in shorts { diff --git a/clap_generate/src/generators/shells/zsh.rs b/clap_generate/src/generators/shells/zsh.rs index 79424277..3e23fa97 100644 --- a/clap_generate/src/generators/shells/zsh.rs +++ b/clap_generate/src/generators/shells/zsh.rs @@ -2,6 +2,7 @@ use std::io::Write; // Internal +use crate::utils; use crate::Generator; use crate::INTERNAL_ERROR_MSG; use clap::*; @@ -100,7 +101,7 @@ _{bin_name_underscore}_commands() {{ ret.push(parent_text); // Next we start looping through all the children, grandchildren, etc. - let mut all_subcommands = Zsh.all_subcommands(p); + let mut all_subcommands = utils::all_subcommands(p); all_subcommands.sort(); all_subcommands.dedup(); @@ -216,7 +217,7 @@ fn get_subcommands_of(parent: &App) -> String { return String::new(); } - let subcommand_names = Zsh.subcommands(parent); + let subcommand_names = utils::subcommands(parent); let mut all_subcommands = vec![]; for &(ref name, ref bin_name) in &subcommand_names { @@ -528,7 +529,7 @@ fn write_flags_of(p: &App, p_global: Option<&App>) -> String { let mut ret = vec![]; - for f in Zsh.flags(p) { + for f in utils::flags(p) { debug!("write_flags_of:iter: f={}", f.get_name()); let help = f.get_about().map_or(String::new(), escape_help); diff --git a/clap_generate/src/lib.rs b/clap_generate/src/lib.rs index d94ff960..ba234dd1 100644 --- a/clap_generate/src/lib.rs +++ b/clap_generate/src/lib.rs @@ -66,6 +66,8 @@ mod macros; pub mod generators; /// Contains supported shells for auto-completion scripts mod shell; +/// Helpers for writing generators +pub mod utils; use std::ffi::OsString; use std::fs::File; diff --git a/clap_generate/src/utils.rs b/clap_generate/src/utils.rs new file mode 100644 index 00000000..c877e609 --- /dev/null +++ b/clap_generate/src/utils.rs @@ -0,0 +1,240 @@ +use clap::{App, Arg}; + +/// 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(app: &App) -> Vec<(String, String)> { + let mut subcmds: Vec<_> = subcommands(app); + + for sc_v in app.get_subcommands().map(|s| all_subcommands(s)) { + subcmds.extend(sc_v); + } + + subcmds +} + +/// Finds the subcommand [`clap::App`] from the given [`clap::App`] with the given path. +/// +/// **NOTE:** `path` should not contain the root `bin_name`. +pub fn find_subcommand_with_path<'help, 'app>( + p: &'app App<'help>, + path: Vec<&str>, +) -> &'app App<'help> { + let mut app = p; + + for sc in path { + app = app.find_subcommand(sc).unwrap(); + } + + app +} + +/// Gets subcommands of [`clap::App`] in the form of `("name", "bin_name")`. +/// +/// Subcommand `rustup toolchain install` would be converted to +/// `("install", "rustup toolchain install")`. +pub fn subcommands(p: &App) -> Vec<(String, String)> { + debug!("subcommands: name={}", p.get_name()); + debug!("subcommands: Has subcommands...{:?}", p.has_subcommands()); + + let mut subcmds = vec![]; + + if !p.has_subcommands() { + return subcmds; + } + + 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::App`]. +/// Includes `h` and `V` depending on the [`clap::AppSettings`]. +pub fn shorts_and_visible_aliases(p: &App) -> Vec { + debug!("shorts: name={}", p.get_name()); + + p.get_arguments() + .filter_map(|a| { + if a.get_index().is_none() { + 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::App`]. +/// Includes `help` and `version` depending on the [`clap::AppSettings`]. +pub fn longs_and_visible_aliases(p: &App) -> Vec { + debug!("longs: name={}", p.get_name()); + + p.get_arguments() + .filter_map(|a| { + if a.get_index().is_none() { + 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::App`](App). +/// Includes `help` and `version` depending on the [`clap::AppSettings`]. +pub fn flags<'help>(p: &App<'help>) -> Vec> { + debug!("flags: name={}", p.get_name()); + p.get_flags().cloned().collect() +} + +#[cfg(test)] +mod tests { + use super::*; + use clap::Arg; + use pretty_assertions::assert_eq; + + fn common() -> App<'static> { + let mut app = App::new("myapp") + .version("3.0") + .subcommand( + App::new("test").subcommand(App::new("config")).arg( + Arg::new("file") + .short('f') + .short_alias('c') + .visible_short_alias('p') + .long("file") + .visible_alias("path"), + ), + ) + .subcommand(App::new("hello")) + .bin_name("my-app"); + + app._build(); + app._build_bin_names(); + app + } + + #[test] + fn test_subcommands() { + let app = common(); + + assert_eq!( + subcommands(&app), + vec![ + ("test".to_string(), "my-app test".to_string()), + ("hello".to_string(), "my-app hello".to_string()), + ("help".to_string(), "my-app help".to_string()), + ] + ); + } + + #[test] + fn test_all_subcommands() { + let app = common(); + + assert_eq!( + all_subcommands(&app), + vec![ + ("test".to_string(), "my-app test".to_string()), + ("hello".to_string(), "my-app hello".to_string()), + ("help".to_string(), "my-app help".to_string()), + ("config".to_string(), "my-app test config".to_string()), + ] + ); + } + + #[test] + fn test_find_subcommand_with_path() { + let app = common(); + let sc_app = find_subcommand_with_path(&app, "test config".split(' ').collect()); + + assert_eq!(sc_app.get_name(), "config"); + } + + #[test] + fn test_flags() { + let app = common(); + let actual_flags = flags(&app); + + 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(&app, vec!["test"])); + + assert_eq!(sc_flags.len(), 3); + assert_eq!(sc_flags[0].get_long(), Some("file")); + assert_eq!(sc_flags[1].get_long(), Some("help")); + assert_eq!(sc_flags[2].get_long(), Some("version")); + } + + #[test] + fn test_shorts() { + let app = common(); + let shorts = shorts_and_visible_aliases(&app); + + 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(&app, vec!["test"])); + + assert_eq!(sc_shorts.len(), 4); + assert_eq!(sc_shorts[0], 'p'); + assert_eq!(sc_shorts[1], 'f'); + assert_eq!(sc_shorts[2], 'h'); + assert_eq!(sc_shorts[3], 'V'); + } + + #[test] + fn test_longs() { + let app = common(); + let longs = longs_and_visible_aliases(&app); + + 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(&app, vec!["test"])); + + assert_eq!(sc_longs.len(), 4); + assert_eq!(sc_longs[0], "path"); + assert_eq!(sc_longs[1], "file"); + assert_eq!(sc_longs[2], "help"); + assert_eq!(sc_longs[3], "version"); + } +}