diff --git a/examples/advanced_struct.rs b/examples/advanced_struct.rs index 1e021d4..856b587 100644 --- a/examples/advanced_struct.rs +++ b/examples/advanced_struct.rs @@ -3,11 +3,13 @@ // 1) build an example: cargo build --example advanced_struct // 2) go to the `examples` folder: cd target/debug/examples // 3) run an example: ./advanced_struct (without parameters) => entered interactive mode -// ./advanced_struct --age-full-years 30 --first-name QWE --second-name QWERTY => -// => args: Ok(Args { age: 30, first_name: "QWE", second_name: "QWERTY" }) +// ./advanced_struct --age-full-years 30 --first-name QWE --second-name QWERTY --favorite-color red => +// => cli_args: CliArgs { age: Some(30), first_name: Some("QWE"), second_name: Some("QWERTY"), favorite_color: Some(Red) } // To learn more about the parameters, use "help" flag: ./advanced_struct --help +use inquire::Select; use interactive_clap::{ResultFromCli, ToCliArgs}; +use strum::{EnumDiscriminants, EnumIter, EnumMessage, IntoEnumIterator}; #[derive(Debug, Clone, interactive_clap::InteractiveClap)] #[interactive_clap(skip_default_from_cli)] @@ -21,6 +23,10 @@ struct Args { #[interactive_clap(long)] #[interactive_clap(skip_default_input_arg)] second_name: String, + #[interactive_clap(long)] + #[interactive_clap(value_enum)] + #[interactive_clap(skip_default_input_arg)] + favorite_color: ColorPalette, } impl interactive_clap::FromCli for Args { @@ -57,6 +63,17 @@ impl interactive_clap::FromCli for Args { }; } let _second_name = clap_variant.second_name.clone().expect("Unexpected error"); + if clap_variant.favorite_color.is_none() { + clap_variant.favorite_color = match Self::input_favorite_color(&context) { + Ok(Some(favorite_color)) => Some(favorite_color), + Ok(None) => return ResultFromCli::Cancel(Some(clap_variant)), + Err(err) => return ResultFromCli::Err(Some(clap_variant), err), + }; + } + let _favorite_color = clap_variant + .favorite_color + .clone() + .expect("Unexpected error"); ResultFromCli::Ok(clap_variant) } } @@ -83,6 +100,94 @@ impl Args { Err(err) => Err(err.into()), } } + + fn input_favorite_color(_context: &()) -> color_eyre::eyre::Result> { + let variants = ColorPaletteDiscriminants::iter().collect::>(); + let selected = Select::new("What color is your favorite?", variants).prompt()?; + match selected { + ColorPaletteDiscriminants::Red => Ok(Some(ColorPalette::Red)), + ColorPaletteDiscriminants::Orange => Ok(Some(ColorPalette::Orange)), + ColorPaletteDiscriminants::Yellow => Ok(Some(ColorPalette::Yellow)), + ColorPaletteDiscriminants::Green => Ok(Some(ColorPalette::Green)), + ColorPaletteDiscriminants::Blue => Ok(Some(ColorPalette::Blue)), + ColorPaletteDiscriminants::Indigo => Ok(Some(ColorPalette::Indigo)), + ColorPaletteDiscriminants::Violet => Ok(Some(ColorPalette::Violet)), + } + } +} + +#[derive(Debug, EnumDiscriminants, Clone, clap::ValueEnum)] +#[strum_discriminants(derive(EnumMessage, EnumIter))] +pub enum ColorPalette { + #[strum_discriminants(strum(message = "red"))] + /// Red + Red, + #[strum_discriminants(strum(message = "orange"))] + /// Orange + Orange, + #[strum_discriminants(strum(message = "yellow"))] + /// Yellow + Yellow, + #[strum_discriminants(strum(message = "green"))] + /// Green + Green, + #[strum_discriminants(strum(message = "blue"))] + /// Blue + Blue, + #[strum_discriminants(strum(message = "indigo"))] + /// Indigo + Indigo, + #[strum_discriminants(strum(message = "violet"))] + /// Violet + Violet, +} + +impl interactive_clap::ToCli for ColorPalette { + type CliVariant = ColorPalette; +} + +impl std::str::FromStr for ColorPalette { + type Err = String; + fn from_str(s: &str) -> Result { + match s { + "red" => Ok(Self::Red), + "orange" => Ok(Self::Orange), + "yellow" => Ok(Self::Yellow), + "green" => Ok(Self::Green), + "blue" => Ok(Self::Blue), + "indigo" => Ok(Self::Indigo), + "violet" => Ok(Self::Violet), + _ => Err("ColorPalette: incorrect value entered".to_string()), + } + } +} + +impl std::fmt::Display for ColorPalette { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + Self::Red => write!(f, "red"), + Self::Orange => write!(f, "orange"), + Self::Yellow => write!(f, "yellow"), + Self::Green => write!(f, "green"), + Self::Blue => write!(f, "blue"), + Self::Indigo => write!(f, "indigo"), + Self::Violet => write!(f, "violet"), + } + } +} + +impl std::fmt::Display for ColorPaletteDiscriminants { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + Self::Red => write!(f, "red"), + Self::Orange => write!(f, "orange"), + Self::Yellow => write!(f, "yellow"), + Self::Green => write!(f, "green"), + Self::Blue => write!(f, "blue"), + Self::Indigo => write!(f, "indigo"), + Self::Violet => write!(f, "violet"), + } + } } fn main() -> color_eyre::Result<()> { @@ -92,6 +197,7 @@ fn main() -> color_eyre::Result<()> { let args = ::from_cli(Some(cli_args), context); match args { ResultFromCli::Ok(cli_args) | ResultFromCli::Cancel(Some(cli_args)) => { + println!("cli_args: {cli_args:?}"); println!( "Your console command: {}", shell_words::join(&cli_args.to_cli_args()) diff --git a/interactive-clap-derive/src/derives/to_cli_args/methods/interactive_clap_attrs_cli_field.rs b/interactive-clap-derive/src/derives/to_cli_args/methods/interactive_clap_attrs_cli_field.rs index 7530fdd..2bfab5f 100644 --- a/interactive-clap-derive/src/derives/to_cli_args/methods/interactive_clap_attrs_cli_field.rs +++ b/interactive-clap-derive/src/derives/to_cli_args/methods/interactive_clap_attrs_cli_field.rs @@ -6,20 +6,14 @@ use quote::{quote, ToTokens}; use syn; #[derive(Debug, Clone)] -pub struct InteractiveClapAttrsCliField { - pub ident_field: syn::Ident, - pub args_without_attrs: Option, - pub named_args: Option, - pub unnamed_args: Option, - pub subcommand_args: Option, +pub enum InteractiveClapAttrsCliField { + RegularField(proc_macro2::TokenStream), + SubcommandField(proc_macro2::TokenStream), } impl InteractiveClapAttrsCliField { pub fn new(field: syn::Field) -> Self { let ident_field = field.ident.clone().expect("this field does not exist"); - let mut subcommand_args = quote! { - let mut args = std::collections::VecDeque::new(); - }; let mut args_without_attrs = quote!(); let mut named_args = quote!(); let mut unnamed_args = quote!(); @@ -40,14 +34,15 @@ impl InteractiveClapAttrsCliField { match &item { proc_macro2::TokenTree::Ident(ident) => { if ident == "subcommand" { - subcommand_args = quote! { + return Self::SubcommandField(quote! { let mut args = self .#ident_field .as_ref() .map(|subcommand| subcommand.to_cli_args()) .unwrap_or_default(); - }; - } else if ident == "value_enum" { + }); + } + if ident == "value_enum" { args_without_attrs = quote! { if let Some(arg) = &self.#ident_field { args.push_front(arg.to_string()) @@ -97,18 +92,15 @@ impl InteractiveClapAttrsCliField { } } }; - Self { - ident_field, - args_without_attrs: Some(args_without_attrs), - named_args: Some(named_args.clone()), - unnamed_args: { - if !named_args.is_empty() { - None - } else { - Some(unnamed_args) - } - }, - subcommand_args: Some(subcommand_args), - } + let token_stream_args: proc_macro2::TokenStream = if !named_args.is_empty() { + named_args + } else if !unnamed_args.is_empty() { + unnamed_args + } else if !args_without_attrs.is_empty() { + args_without_attrs + } else { + quote!() + }; + Self::RegularField(token_stream_args) } } diff --git a/interactive-clap-derive/src/derives/to_cli_args/mod.rs b/interactive-clap-derive/src/derives/to_cli_args/mod.rs index 4767dca..f2ad1ab 100644 --- a/interactive-clap-derive/src/derives/to_cli_args/mod.rs +++ b/interactive-clap-derive/src/derives/to_cli_args/mod.rs @@ -5,6 +5,8 @@ use proc_macro_error::abort_call_site; use quote::quote; use syn; +use self::methods::interactive_clap_attrs_cli_field::InteractiveClapAttrsCliField; + mod methods; pub fn impl_to_cli_args(ast: &syn::DeriveInput) -> TokenStream { @@ -17,24 +19,14 @@ pub fn impl_to_cli_args(ast: &syn::DeriveInput) -> TokenStream { let mut args_push_front_vec: Vec = Vec::new(); for field in data_struct.clone().fields.iter() { - let interactive_clap_attrs_cli_field = self::methods::interactive_clap_attrs_cli_field::InteractiveClapAttrsCliField::new(field.clone()); - args_subcommand = if let Some(subcommand_args) = - interactive_clap_attrs_cli_field.subcommand_args - { - subcommand_args - } else { - args_subcommand - }; - if let Some(unnamed_args) = interactive_clap_attrs_cli_field.unnamed_args { - args_push_front_vec.push(unnamed_args) - } else if let Some(args_without_attrs) = - interactive_clap_attrs_cli_field.args_without_attrs - { - args_push_front_vec.push(args_without_attrs) - }; - if let Some(named_args) = interactive_clap_attrs_cli_field.named_args { - args_push_front_vec.push(named_args) - }; + match InteractiveClapAttrsCliField::new(field.clone()) { + InteractiveClapAttrsCliField::RegularField(regular_field_args) => { + args_push_front_vec.push(regular_field_args) + } + InteractiveClapAttrsCliField::SubcommandField(subcommand_args) => { + args_subcommand = subcommand_args + } + } } let args_push_front_vec = args_push_front_vec.into_iter().rev();