From 35d53d9dcfcdd2629d61a6cb133d9528c1bebbf0 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 18 Aug 2021 13:00:56 -0500 Subject: [PATCH] feat(generate): 'impl ArgEnum for Shell' These keeps `FromStr` for ease of use with `value_of_t`. This includes adding test to make sure everything works as expected. --- clap_derive/examples/value_hints_derive.rs | 27 ++++------ clap_generate/src/shell.rs | 58 +++++++++++++--------- src/derive.rs | 18 ++++--- src/lib.rs | 2 - 4 files changed, 56 insertions(+), 49 deletions(-) diff --git a/clap_derive/examples/value_hints_derive.rs b/clap_derive/examples/value_hints_derive.rs index 804e6fa1..0966d13a 100644 --- a/clap_derive/examples/value_hints_derive.rs +++ b/clap_derive/examples/value_hints_derive.rs @@ -12,23 +12,13 @@ //! . ./value_hints_derive.fish //! ./target/debug/examples/value_hints_derive -- //! ``` -use clap::{App, AppSettings, ArgEnum, IntoApp, Parser, ValueHint}; +use clap::{App, AppSettings, IntoApp, Parser, ValueHint}; use clap_generate::generators::{Bash, Elvish, Fish, PowerShell, Zsh}; -use clap_generate::{generate, Generator}; +use clap_generate::{generate, Generator, Shell}; use std::ffi::OsString; use std::io; use std::path::PathBuf; -#[derive(ArgEnum, Debug, PartialEq, Clone)] -enum GeneratorChoice { - Bash, - Elvish, - Fish, - #[clap(name = "powershell")] - PowerShell, - Zsh, -} - #[derive(Parser, Debug, PartialEq)] #[clap( name = "value_hints_derive", @@ -38,7 +28,7 @@ enum GeneratorChoice { struct Opt { /// If provided, outputs the completion file for given shell #[clap(long = "generate", arg_enum)] - generator: Option, + generator: Option, // Showcasing all possible ValueHints: #[clap(long, value_hint = ValueHint::Unknown)] unknown: Option, @@ -79,11 +69,12 @@ fn main() { let mut app = Opt::into_app(); eprintln!("Generating completion file for {:?}...", generator); match generator { - GeneratorChoice::Bash => print_completions::(&mut app), - GeneratorChoice::Elvish => print_completions::(&mut app), - GeneratorChoice::Fish => print_completions::(&mut app), - GeneratorChoice::PowerShell => print_completions::(&mut app), - GeneratorChoice::Zsh => print_completions::(&mut app), + Shell::Bash => print_completions::(&mut app), + Shell::Elvish => print_completions::(&mut app), + Shell::Fish => print_completions::(&mut app), + Shell::PowerShell => print_completions::(&mut app), + Shell::Zsh => print_completions::(&mut app), + _ => unimplemented!("New shell type"), } } else { println!("{:#?}", opt); diff --git a/clap_generate/src/shell.rs b/clap_generate/src/shell.rs index 229c45ef..551acc9a 100644 --- a/clap_generate/src/shell.rs +++ b/clap_generate/src/shell.rs @@ -1,6 +1,8 @@ use std::fmt::Display; use std::str::FromStr; +use clap::{ArgEnum, ArgValue}; + /// Shell with auto-generated completion script available. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] #[non_exhaustive] @@ -17,22 +19,12 @@ pub enum Shell { Zsh, } -impl Shell { - /// A list of supported shells in `[&'static str]` form. - pub fn variants() -> [&'static str; 5] { - ["bash", "elvish", "fish", "powershell", "zsh"] - } -} - impl Display for Shell { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match *self { - Shell::Bash => write!(f, "bash"), - Shell::Elvish => write!(f, "elvish"), - Shell::Fish => write!(f, "fish"), - Shell::PowerShell => write!(f, "powershell"), - Shell::Zsh => write!(f, "zsh"), - } + self.to_arg_value() + .expect("no values are skipped") + .get_name() + .fmt(f) } } @@ -40,15 +32,35 @@ impl FromStr for Shell { type Err = String; fn from_str(s: &str) -> Result { - match s.to_ascii_lowercase().as_str() { - "bash" => Ok(Shell::Bash), - "elvish" => Ok(Shell::Elvish), - "fish" => Ok(Shell::Fish), - "powershell" => Ok(Shell::PowerShell), - "zsh" => Ok(Shell::Zsh), - _ => Err(String::from( - "[valid values: bash, elvish, fish, powershell, zsh]", - )), + for variant in Self::value_variants() { + if variant.to_arg_value().unwrap().matches(s, false) { + return Ok(*variant); + } } + Err(format!("Invalid variant: {}", s)) + } +} + +// Hand-rolled so it can work even when `derive` feature is disabled +impl ArgEnum for Shell { + fn value_variants<'a>() -> &'a [Self] { + &[ + Shell::Bash, + Shell::Elvish, + Shell::Fish, + Shell::PowerShell, + Shell::Zsh, + ] + } + + fn to_arg_value<'a>(&self) -> Option> { + let value = match self { + Shell::Bash => ArgValue::new("bash"), + Shell::Elvish => ArgValue::new("elvish"), + Shell::Fish => ArgValue::new("fish"), + Shell::PowerShell => ArgValue::new("powershell"), + Shell::Zsh => ArgValue::new("zsh"), + }; + Some(value) } } diff --git a/src/derive.rs b/src/derive.rs index 18ad6456..d51c00c6 100644 --- a/src/derive.rs +++ b/src/derive.rs @@ -25,7 +25,8 @@ use std::ffi::OsString; /// throughout the application representing the normalized values coming from /// the CLI. /// -/// ```rust +#[cfg_attr(not(feature = "derive"), doc = " ```ignore")] +#[cfg_attr(feature = "derive", doc = " ```")] /// /// My super CLI /// #[derive(clap::Parser)] /// #[clap(name = "demo")] @@ -150,7 +151,8 @@ pub trait FromArgMatches: Sized { /// Motivation: If our application had two CLI options, `--name /// ` and the flag `--debug`, we may create a struct as follows: /// - /// ```no_run + #[cfg_attr(not(feature = "derive"), doc = " ```ignore")] + #[cfg_attr(feature = "derive", doc = " ```no_run")] /// struct Context { /// name: String, /// debug: bool @@ -160,7 +162,8 @@ pub trait FromArgMatches: Sized { /// We then need to convert the `ArgMatches` that `clap` generated into our struct. /// `from_arg_matches` serves as the equivalent of: /// - /// ```no_run + #[cfg_attr(not(feature = "derive"), doc = " ```ignore")] + #[cfg_attr(feature = "derive", doc = " ```no_run")] /// # use clap::ArgMatches; /// # struct Context { /// # name: String, @@ -192,7 +195,8 @@ pub trait FromArgMatches: Sized { /// /// # Example /// -/// ```rust +#[cfg_attr(not(feature = "derive"), doc = " ```ignore")] +#[cfg_attr(feature = "derive", doc = " ```")] /// #[derive(clap::Parser)] /// struct Args { /// #[clap(flatten)] @@ -229,7 +233,8 @@ pub trait Args: FromArgMatches + Sized { /// /// # Example /// -/// ```rust +#[cfg_attr(not(feature = "derive"), doc = " ```ignore")] +#[cfg_attr(feature = "derive", doc = " ```")] /// #[derive(clap::Parser)] /// struct Args { /// #[clap(subcommand)] @@ -265,7 +270,8 @@ pub trait Subcommand: FromArgMatches + Sized { /// /// # Example /// -/// ```rust +#[cfg_attr(not(feature = "derive"), doc = " ```ignore")] +#[cfg_attr(feature = "derive", doc = " ```")] /// #[derive(clap::Parser)] /// struct Args { /// #[clap(arg_enum)] diff --git a/src/lib.rs b/src/lib.rs index fc84e360..0c44c350 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,7 +31,6 @@ pub use crate::{ parse::{ArgMatches, Indices, OsValues, Values}, }; -#[cfg(feature = "derive")] pub use crate::derive::{ArgEnum, Args, FromArgMatches, IntoApp, Parser, Subcommand}; #[cfg(feature = "yaml")] @@ -50,7 +49,6 @@ pub use lazy_static; #[allow(missing_docs)] mod macros; -#[cfg(feature = "derive")] mod derive; #[cfg(feature = "regex")]