fix: named_args/unnamed_args/args_without_attrs conflict (#9)

* fixed

* updated examples/advanced_struct

* refactored InteractiveClapAttrsCliField::new()

* Update interactive-clap-derive/src/derives/to_cli_args/methods/interactive_clap_attrs_cli_field.rs

Co-authored-by: Vlad Frolov <frolvlad@gmail.com>

* refactored InteractiveClapAttrsCliField

---------

Co-authored-by: Vlad Frolov <frolvlad@gmail.com>
This commit is contained in:
FroVolod
2023-10-05 10:46:46 +03:00
committed by GitHub
parent a627fc0c9f
commit 77cb26c33f
3 changed files with 135 additions and 45 deletions
+108 -2
View File
@@ -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<Option<ColorPalette>> {
let variants = ColorPaletteDiscriminants::iter().collect::<Vec<_>>();
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<Self, Self::Err> {
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 = <Args as interactive_clap::FromCli>::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())
@@ -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<proc_macro2::TokenStream>,
pub named_args: Option<proc_macro2::TokenStream>,
pub unnamed_args: Option<proc_macro2::TokenStream>,
pub subcommand_args: Option<proc_macro2::TokenStream>,
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)
}
}
@@ -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<proc_macro2::TokenStream> = 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();