2855: fix(gen)!: Simplify `Generator` trait r=pksunkara a=epage



Co-authored-by: Ed Page <eopage@gmail.com>
This commit is contained in:
bors[bot] 2021-10-12 14:50:55 +00:00 committed by GitHub
commit 36e172672c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 261 additions and 269 deletions

View file

@ -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<char> {
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<String> {
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<Arg<'help>> {
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");
}
}

View file

@ -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::<Vec<_>>();
@ -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::<Vec<_>>();
@ -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

View file

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

View file

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

View file

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

View file

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

View file

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

240
clap_generate/src/utils.rs Normal file
View file

@ -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<char> {
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<String> {
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<Arg<'help>> {
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");
}
}