From d07d192fc635af90c2a565cc93d57dcb867464ad Mon Sep 17 00:00:00 2001 From: FroVolod Date: Mon, 25 Mar 2024 13:10:47 +0200 Subject: [PATCH] feat: Added support for "#[interactive_clap(flatten)]" (#15) Co-authored-by: FroVolod --- examples/struct_with_flatten.rs | 61 +++++ .../methods/fields_with_flatten.rs | 14 ++ .../fields_with_skip_default_input_arg.rs | 18 ++ .../methods/fields_with_subcommand.rs | 17 ++ .../fields_without_skip_default_input_arg.rs | 24 -- .../methods/fields_without_subcommand.rs | 25 -- .../methods/from_cli_for_struct.rs | 233 ++++++++++-------- .../interactive_clap/methods/input_arg.rs | 4 +- .../derives/interactive_clap/methods/mod.rs | 5 +- .../src/derives/interactive_clap/mod.rs | 93 ++++--- .../interactive_clap_attrs_cli_field.rs | 7 + 11 files changed, 303 insertions(+), 198 deletions(-) create mode 100644 examples/struct_with_flatten.rs create mode 100644 interactive-clap-derive/src/derives/interactive_clap/methods/fields_with_flatten.rs create mode 100644 interactive-clap-derive/src/derives/interactive_clap/methods/fields_with_skip_default_input_arg.rs create mode 100644 interactive-clap-derive/src/derives/interactive_clap/methods/fields_with_subcommand.rs delete mode 100644 interactive-clap-derive/src/derives/interactive_clap/methods/fields_without_skip_default_input_arg.rs delete mode 100644 interactive-clap-derive/src/derives/interactive_clap/methods/fields_without_subcommand.rs mode change 100644 => 100755 interactive-clap-derive/src/derives/to_cli_args/methods/interactive_clap_attrs_cli_field.rs diff --git a/examples/struct_with_flatten.rs b/examples/struct_with_flatten.rs new file mode 100644 index 0000000..65c4e3f --- /dev/null +++ b/examples/struct_with_flatten.rs @@ -0,0 +1,61 @@ +// This example shows additional functionality of the "interactive-clap" macro for parsing command-line data into a structure using the macro's flatten attributes. + +// 1) build an example: cargo build --example struct_with_flatten +// 2) go to the `examples` folder: cd target/debug/examples +// 3) run an example: ./struct_with_flatten (without parameters) => entered interactive mode +// ./struct_with_flatten QWERTY 18 => account: CliAccount { social_db_folder: None, account: Some(CliSender { sender_account_id: Some("QWERTY"), age: Some(18) }) } +// To learn more about the parameters, use "help" flag: ./struct_with_flatten --help + +use interactive_clap::{ResultFromCli, ToCliArgs}; + +#[derive(Debug, Clone, interactive_clap::InteractiveClap)] +struct Account { + /// Change SocialDb prefix + #[interactive_clap(long)] + #[interactive_clap(skip_interactive_input)] + social_db_folder: Option, + #[interactive_clap(flatten)] + account: Sender, +} + +#[derive(Debug, Clone, interactive_clap::InteractiveClap)] +pub struct Sender { + /// What is the sender account ID? + pub sender_account_id: String, + /// How old is the sender? + pub age: u64, +} + +fn main() -> color_eyre::Result<()> { + let mut cli_account = Account::parse(); + let context = (); // default: input_context = () + loop { + let account = ::from_cli(Some(cli_account), context); + match account { + ResultFromCli::Ok(cli_account) | ResultFromCli::Cancel(Some(cli_account)) => { + println!("account: {cli_account:?}"); + println!( + "Your console command: {}", + shell_words::join(&cli_account.to_cli_args()) + ); + return Ok(()); + } + ResultFromCli::Cancel(None) => { + println!("Goodbye!"); + return Ok(()); + } + ResultFromCli::Back => { + cli_account = Default::default(); + } + ResultFromCli::Err(cli_account, err) => { + if let Some(cli_account) = cli_account { + println!( + "Your console command: {}", + shell_words::join(&cli_account.to_cli_args()) + ); + } + return Err(err); + } + } + } +} diff --git a/interactive-clap-derive/src/derives/interactive_clap/methods/fields_with_flatten.rs b/interactive-clap-derive/src/derives/interactive_clap/methods/fields_with_flatten.rs new file mode 100644 index 0000000..26af0cb --- /dev/null +++ b/interactive-clap-derive/src/derives/interactive_clap/methods/fields_with_flatten.rs @@ -0,0 +1,14 @@ +extern crate proc_macro; + +use syn; + +pub fn is_field_with_flatten(field: &syn::Field) -> bool { + if field.attrs.is_empty() { + return false; + } + field + .attrs + .iter() + .flat_map(|attr| attr.tokens.clone()) + .any(|attr_token| attr_token.to_string().contains("flatten")) +} diff --git a/interactive-clap-derive/src/derives/interactive_clap/methods/fields_with_skip_default_input_arg.rs b/interactive-clap-derive/src/derives/interactive_clap/methods/fields_with_skip_default_input_arg.rs new file mode 100644 index 0000000..fc71d85 --- /dev/null +++ b/interactive-clap-derive/src/derives/interactive_clap/methods/fields_with_skip_default_input_arg.rs @@ -0,0 +1,18 @@ +extern crate proc_macro; + +use syn; + +pub fn is_field_with_skip_default_input_arg(field: &syn::Field) -> bool { + if field.attrs.is_empty() { + return false; + } + field + .attrs + .iter() + .filter(|attr| attr.path.is_ident("interactive_clap")) + .flat_map(|attr| attr.tokens.clone()) + .any(|attr_token| { + attr_token.to_string().contains("skip_default_input_arg") + || attr_token.to_string().contains("flatten") + }) +} diff --git a/interactive-clap-derive/src/derives/interactive_clap/methods/fields_with_subcommand.rs b/interactive-clap-derive/src/derives/interactive_clap/methods/fields_with_subcommand.rs new file mode 100644 index 0000000..ef46121 --- /dev/null +++ b/interactive-clap-derive/src/derives/interactive_clap/methods/fields_with_subcommand.rs @@ -0,0 +1,17 @@ +extern crate proc_macro; + +use syn; + +pub fn is_field_with_subcommand(field: &syn::Field) -> bool { + if field.attrs.is_empty() { + return false; + } + field + .attrs + .iter() + .flat_map(|attr| attr.tokens.clone()) + .any(|attr_token| { + attr_token.to_string().contains("named_arg") + || attr_token.to_string().contains("subcommand") + }) +} diff --git a/interactive-clap-derive/src/derives/interactive_clap/methods/fields_without_skip_default_input_arg.rs b/interactive-clap-derive/src/derives/interactive_clap/methods/fields_without_skip_default_input_arg.rs deleted file mode 100644 index fa56303..0000000 --- a/interactive-clap-derive/src/derives/interactive_clap/methods/fields_without_skip_default_input_arg.rs +++ /dev/null @@ -1,24 +0,0 @@ -extern crate proc_macro; - -use syn; - -pub fn is_field_without_skip_default_input_arg(field: &syn::Field) -> bool { - if field.attrs.is_empty() { - return true; - } - match field - .attrs - .iter() - .filter(|attr| attr.path.is_ident("interactive_clap")) - .flat_map(|attr| attr.tokens.clone()) - .find(|attr_token| match attr_token { - proc_macro2::TokenTree::Group(group) => group - .stream() - .to_string() - .contains("skip_default_input_arg"), - _ => false, // abort_call_site!("Only option `TokenTree::Group` is needed") - }) { - Some(_token_stream) => false, - None => true, - } -} diff --git a/interactive-clap-derive/src/derives/interactive_clap/methods/fields_without_subcommand.rs b/interactive-clap-derive/src/derives/interactive_clap/methods/fields_without_subcommand.rs deleted file mode 100644 index b0c8481..0000000 --- a/interactive-clap-derive/src/derives/interactive_clap/methods/fields_without_subcommand.rs +++ /dev/null @@ -1,25 +0,0 @@ -extern crate proc_macro; - -use syn; - -pub fn is_field_without_subcommand(field: &syn::Field) -> bool { - if field.attrs.is_empty() { - return true; - } - match field - .attrs - .iter() - .flat_map(|attr| attr.tokens.clone()) - .find(|attr_token| { - match attr_token { - proc_macro2::TokenTree::Group(group) => { - group.stream().to_string().contains("named_arg") - || group.stream().to_string().contains("subcommand") - } - _ => false, // abort_call_site!("Only option `TokenTree::Group` is needed") - } - }) { - Some(_token_stream) => false, - None => true, - } -} diff --git a/interactive-clap-derive/src/derives/interactive_clap/methods/from_cli_for_struct.rs b/interactive-clap-derive/src/derives/interactive_clap/methods/from_cli_for_struct.rs index cc94d41..8c07793 100644 --- a/interactive-clap-derive/src/derives/interactive_clap/methods/from_cli_for_struct.rs +++ b/interactive-clap-derive/src/derives/interactive_clap/methods/from_cli_for_struct.rs @@ -17,9 +17,12 @@ pub fn from_cli_for_struct( return quote!(); }; - let fields_without_subcommand = fields + let fields_without_subcommand_and_flatten = fields .iter() - .filter(|field| super::fields_without_subcommand::is_field_without_subcommand(field)) + .filter(|field| { + !super::fields_with_subcommand::is_field_with_subcommand(field) + && !super::fields_with_flatten::is_field_with_flatten(field) + }) .map(|field| { let ident_field = &field.clone().ident.expect("this field does not exist"); quote! {#ident_field: #ident_field.into()} @@ -31,25 +34,23 @@ pub fn from_cli_for_struct( .map(fields_value) .filter(|token_stream| !token_stream.is_empty()); - let field_value_named_arg = if let Some(token_stream) = fields + let field_value_named_arg = fields .iter() .map(|field| field_value_named_arg(name, field)) .find(|token_stream| !token_stream.is_empty()) - { - token_stream - } else { - quote!() - }; + .unwrap_or(quote!()); - let field_value_subcommand = if let Some(token_stream) = fields + let field_value_subcommand = fields .iter() .map(field_value_subcommand) .find(|token_stream| !token_stream.is_empty()) - { - token_stream - } else { - quote!() - }; + .unwrap_or(quote!()); + + let field_value_flatten = fields + .iter() + .map(field_value_flatten) + .find(|token_stream| !token_stream.is_empty()) + .unwrap_or(quote!()); let input_context_dir = interactive_clap_attrs_context .clone() @@ -60,7 +61,7 @@ pub fn from_cli_for_struct( Span::call_site(), ); let new_context_scope = quote! { - let new_context_scope = #interactive_clap_context_scope_for_struct { #(#fields_without_subcommand,)* }; + let new_context_scope = #interactive_clap_context_scope_for_struct { #(#fields_without_subcommand_and_flatten,)* }; }; let output_context = match &interactive_clap_attrs_context.output_context_dir { @@ -84,10 +85,11 @@ pub fn from_cli_for_struct( optional_clap_variant: Option<::CliVariant>, context: Self::FromCliContext, ) -> interactive_clap::ResultFromCli<::CliVariant, Self::FromCliError> where Self: Sized + interactive_clap::ToCli { - let mut clap_variant = optional_clap_variant.unwrap_or_default(); + let mut clap_variant = optional_clap_variant.clone().unwrap_or_default(); #(#fields_value)* #new_context_scope #output_context + #field_value_flatten #field_value_named_arg #field_value_subcommand; interactive_clap::ResultFromCli::Ok(clap_variant) @@ -105,6 +107,8 @@ fn fields_value(field: &syn::Field) -> proc_macro2::TokenStream { quote! { let #ident_field = clap_variant.#ident_field.clone(); } + } else if super::fields_with_flatten::is_field_with_flatten(field) { + quote!() } else if field .ty .to_token_stream() @@ -121,7 +125,7 @@ fn fields_value(field: &syn::Field) -> proc_macro2::TokenStream { }; let #ident_field = clap_variant.#ident_field.clone(); } - } else if super::fields_without_subcommand::is_field_without_subcommand(field) { + } else if !super::fields_with_subcommand::is_field_with_subcommand(field) { quote! { if clap_variant.#ident_field.is_none() { clap_variant @@ -144,56 +148,54 @@ fn field_value_named_arg(name: &syn::Ident, field: &syn::Field) -> proc_macro2:: if field.attrs.is_empty() { quote!() } else { - match field.attrs.iter() - .filter(|attr| attr.path.is_ident("interactive_clap")) - .flat_map(|attr| attr.tokens.clone()) - .filter(|attr_token| { - match attr_token { - proc_macro2::TokenTree::Group(group) => group.stream().to_string().contains("named_arg"), - _ => abort_call_site!("Only option `TokenTree::Group` is needed") - } - }) - .map(|_| { - let type_string = match ty { - syn::Type::Path(type_path) => { - match type_path.path.segments.last() { - Some(path_segment) => path_segment.ident.to_string(), - _ => String::new() - } - }, - _ => String::new() - }; - let enum_for_clap_named_arg = syn::Ident::new(&format!("ClapNamedArg{}For{}", &type_string, &name), Span::call_site()); - let variant_name_string = crate::helpers::snake_case_to_camel_case::snake_case_to_camel_case(ident_field.to_string()); - let variant_name = &syn::Ident::new(&variant_name_string, Span::call_site()); - quote! { - let optional_field = match clap_variant.#ident_field.take() { - Some(#enum_for_clap_named_arg::#variant_name(cli_arg)) => Some(cli_arg), - None => None, + field.attrs.iter() + .filter(|attr| attr.path.is_ident("interactive_clap")) + .flat_map(|attr| attr.tokens.clone()) + .filter(|attr_token| { + match attr_token { + proc_macro2::TokenTree::Group(group) => group.stream().to_string().contains("named_arg"), + _ => abort_call_site!("Only option `TokenTree::Group` is needed") + } + }) + .map(|_| { + let type_string = match ty { + syn::Type::Path(type_path) => { + match type_path.path.segments.last() { + Some(path_segment) => path_segment.ident.to_string(), + _ => String::new() + } + }, + _ => String::new() }; - match <#ty as interactive_clap::FromCli>::from_cli( - optional_field, - context.into(), - ) { - interactive_clap::ResultFromCli::Ok(cli_field) => { - clap_variant.#ident_field = Some(#enum_for_clap_named_arg::#variant_name(cli_field)); - } - interactive_clap::ResultFromCli::Cancel(optional_cli_field) => { - clap_variant.#ident_field = optional_cli_field.map(#enum_for_clap_named_arg::#variant_name); - return interactive_clap::ResultFromCli::Cancel(Some(clap_variant)); - } - interactive_clap::ResultFromCli::Back => return interactive_clap::ResultFromCli::Back, - interactive_clap::ResultFromCli::Err(optional_cli_field, err) => { - clap_variant.#ident_field = optional_cli_field.map(#enum_for_clap_named_arg::#variant_name); - return interactive_clap::ResultFromCli::Err(Some(clap_variant), err); + let enum_for_clap_named_arg = syn::Ident::new(&format!("ClapNamedArg{}For{}", &type_string, &name), Span::call_site()); + let variant_name_string = crate::helpers::snake_case_to_camel_case::snake_case_to_camel_case(ident_field.to_string()); + let variant_name = &syn::Ident::new(&variant_name_string, Span::call_site()); + quote! { + let optional_field = match clap_variant.#ident_field.take() { + Some(#enum_for_clap_named_arg::#variant_name(cli_arg)) => Some(cli_arg), + None => None, + }; + match <#ty as interactive_clap::FromCli>::from_cli( + optional_field, + context.into(), + ) { + interactive_clap::ResultFromCli::Ok(cli_field) => { + clap_variant.#ident_field = Some(#enum_for_clap_named_arg::#variant_name(cli_field)); + } + interactive_clap::ResultFromCli::Cancel(optional_cli_field) => { + clap_variant.#ident_field = optional_cli_field.map(#enum_for_clap_named_arg::#variant_name); + return interactive_clap::ResultFromCli::Cancel(Some(clap_variant)); + } + interactive_clap::ResultFromCli::Back => return interactive_clap::ResultFromCli::Back, + interactive_clap::ResultFromCli::Err(optional_cli_field, err) => { + clap_variant.#ident_field = optional_cli_field.map(#enum_for_clap_named_arg::#variant_name); + return interactive_clap::ResultFromCli::Err(Some(clap_variant), err); + } } } - } - }) - .next() { - Some(token_stream) => token_stream, - None => quote! () - } + }) + .next() + .unwrap_or(quote!()) } } @@ -203,40 +205,77 @@ fn field_value_subcommand(field: &syn::Field) -> proc_macro2::TokenStream { if field.attrs.is_empty() { quote!() } else { - match field.attrs.iter() - .filter(|attr| attr.path.is_ident("interactive_clap")) - .flat_map(|attr| attr.tokens.clone()) - .filter(|attr_token| { - match attr_token { - proc_macro2::TokenTree::Group(group) => group.stream().to_string().contains("subcommand"), - _ => abort_call_site!("Only option `TokenTree::Group` is needed") - } - }) - .map(|_| { - quote! { - match <#ty as interactive_clap::FromCli>::from_cli(clap_variant.#ident_field.take(), context.into()) { - interactive_clap::ResultFromCli::Ok(cli_field) => { - clap_variant.#ident_field = Some(cli_field); - } - interactive_clap::ResultFromCli::Cancel(option_cli_field) => { - clap_variant.#ident_field = option_cli_field; - return interactive_clap::ResultFromCli::Cancel(Some(clap_variant)); - } - interactive_clap::ResultFromCli::Cancel(option_cli_field) => { - clap_variant.#ident_field = option_cli_field; - return interactive_clap::ResultFromCli::Cancel(Some(clap_variant)); - } - interactive_clap::ResultFromCli::Back => return interactive_clap::ResultFromCli::Back, - interactive_clap::ResultFromCli::Err(option_cli_field, err) => { - clap_variant.#ident_field = option_cli_field; - return interactive_clap::ResultFromCli::Err(Some(clap_variant), err); + field.attrs.iter() + .filter(|attr| attr.path.is_ident("interactive_clap")) + .flat_map(|attr| attr.tokens.clone()) + .filter(|attr_token| { + match attr_token { + proc_macro2::TokenTree::Group(group) => group.stream().to_string().contains("subcommand"), + _ => abort_call_site!("Only option `TokenTree::Group` is needed") + } + }) + .map(|_| { + quote! { + match <#ty as interactive_clap::FromCli>::from_cli(clap_variant.#ident_field.take(), context.into()) { + interactive_clap::ResultFromCli::Ok(cli_field) => { + clap_variant.#ident_field = Some(cli_field); + } + interactive_clap::ResultFromCli::Cancel(option_cli_field) => { + clap_variant.#ident_field = option_cli_field; + return interactive_clap::ResultFromCli::Cancel(Some(clap_variant)); + } + interactive_clap::ResultFromCli::Cancel(option_cli_field) => { + clap_variant.#ident_field = option_cli_field; + return interactive_clap::ResultFromCli::Cancel(Some(clap_variant)); + } + interactive_clap::ResultFromCli::Back => return interactive_clap::ResultFromCli::Back, + interactive_clap::ResultFromCli::Err(option_cli_field, err) => { + clap_variant.#ident_field = option_cli_field; + return interactive_clap::ResultFromCli::Err(Some(clap_variant), err); + } } } - } - }) - .next() { - Some(token_stream) => token_stream, - None => quote! () - } + }) + .next() + .unwrap_or(quote!()) + } +} + +fn field_value_flatten(field: &syn::Field) -> proc_macro2::TokenStream { + let ident_field = &field.clone().ident.expect("this field does not exist"); + let ty = &field.ty; + if field.attrs.is_empty() { + quote!() + } else { + field.attrs.iter() + .filter(|attr| attr.path.is_ident("interactive_clap")) + .flat_map(|attr| attr.tokens.clone()) + .filter(|attr_token| { + match attr_token { + proc_macro2::TokenTree::Group(group) => group.stream().to_string().contains("flatten"), + _ => abort_call_site!("Only option `TokenTree::Group` is needed") + } + }) + .map(|_| { + quote! { + match #ty::from_cli( + optional_clap_variant.unwrap_or_default().#ident_field, + context.into(), + ) { + interactive_clap::ResultFromCli::Ok(cli_field) => clap_variant.#ident_field = Some(cli_field), + interactive_clap::ResultFromCli::Cancel(optional_cli_field) => { + clap_variant.#ident_field = optional_cli_field; + return interactive_clap::ResultFromCli::Cancel(Some(clap_variant)); + } + interactive_clap::ResultFromCli::Back => return interactive_clap::ResultFromCli::Back, + interactive_clap::ResultFromCli::Err(optional_cli_field, err) => { + clap_variant.#ident_field = optional_cli_field; + return interactive_clap::ResultFromCli::Err(Some(clap_variant), err); + } + }; + } + }) + .next() + .unwrap_or(quote!()) } } diff --git a/interactive-clap-derive/src/derives/interactive_clap/methods/input_arg.rs b/interactive-clap-derive/src/derives/interactive_clap/methods/input_arg.rs index d111dd3..f887304 100644 --- a/interactive-clap-derive/src/derives/interactive_clap/methods/input_arg.rs +++ b/interactive-clap-derive/src/derives/interactive_clap/methods/input_arg.rs @@ -12,9 +12,9 @@ pub fn vec_fn_input_arg( super::interactive_clap_attrs_context::InteractiveClapAttrsContext::new(ast); let vec_fn_input_arg = fields .iter() - .filter(|field| super::fields_without_subcommand::is_field_without_subcommand(field)) + .filter(|field| !super::fields_with_subcommand::is_field_with_subcommand(field)) .filter(|field| { - super::fields_without_skip_default_input_arg::is_field_without_skip_default_input_arg( + !super::fields_with_skip_default_input_arg::is_field_with_skip_default_input_arg( field, ) }) diff --git a/interactive-clap-derive/src/derives/interactive_clap/methods/mod.rs b/interactive-clap-derive/src/derives/interactive_clap/methods/mod.rs index f2ef79b..e666a16 100644 --- a/interactive-clap-derive/src/derives/interactive_clap/methods/mod.rs +++ b/interactive-clap-derive/src/derives/interactive_clap/methods/mod.rs @@ -1,7 +1,8 @@ pub mod choose_variant; pub mod cli_field_type; -pub mod fields_without_skip_default_input_arg; -pub mod fields_without_subcommand; +pub mod fields_with_flatten; +pub mod fields_with_skip_default_input_arg; +pub mod fields_with_subcommand; pub mod from_cli_for_enum; pub mod from_cli_for_struct; pub mod input_arg; diff --git a/interactive-clap-derive/src/derives/interactive_clap/mod.rs b/interactive-clap-derive/src/derives/interactive_clap/mod.rs index 10a4666..306af10 100644 --- a/interactive-clap-derive/src/derives/interactive_clap/mod.rs +++ b/interactive-clap-derive/src/derives/interactive_clap/mod.rs @@ -121,56 +121,52 @@ pub fn impl_interactive_clap(ast: &syn::DeriveInput) -> TokenStream { .collect::>(); let context_scope_for_struct = context_scope_for_struct(name, context_scope_fields); - let clap_enum_for_named_arg = - if let Some(token_stream) = fields.iter().find_map(|field| { - let ident_field = &field.clone().ident.expect("this field does not exist"); - let variant_name_string = crate::helpers::snake_case_to_camel_case::snake_case_to_camel_case(ident_field.to_string()); - let variant_name = &syn::Ident::new(&variant_name_string, Span::call_site()); - let attr_doc_vec: Vec<_> = field.attrs.iter() - .filter(|attr| attr.path.is_ident("doc")) - .map(|attr| attr.into_token_stream()) - .collect(); - field.attrs.iter() - .filter(|attr| attr.path.is_ident("interactive_clap")) - .flat_map(|attr| attr.tokens.clone()) - .filter(|attr_token| { - match attr_token { - proc_macro2::TokenTree::Group(group) => group.stream().to_string().contains("named_arg"), - _ => abort_call_site!("Only option `TokenTree::Group` is needed") - } - }) - .map(|_| { - let ty = &field.ty; - let type_string = match ty { - syn::Type::Path(type_path) => { - match type_path.path.segments.last() { - Some(path_segment) => path_segment.ident.to_string(), - _ => String::new() - } - }, - _ => String::new() - }; - let enum_for_clap_named_arg = syn::Ident::new(&format!("ClapNamedArg{}For{}", &type_string, &name), Span::call_site()); - quote! { - #[derive(Debug, Clone, clap::Parser, interactive_clap_derive::ToCliArgs)] - pub enum #enum_for_clap_named_arg { - #(#attr_doc_vec)* - #variant_name(<#ty as interactive_clap::ToCli>::CliVariant) + let clap_enum_for_named_arg = fields.iter().find_map(|field| { + let ident_field = &field.clone().ident.expect("this field does not exist"); + let variant_name_string = crate::helpers::snake_case_to_camel_case::snake_case_to_camel_case(ident_field.to_string()); + let variant_name = &syn::Ident::new(&variant_name_string, Span::call_site()); + let attr_doc_vec: Vec<_> = field.attrs.iter() + .filter(|attr| attr.path.is_ident("doc")) + .map(|attr| attr.into_token_stream()) + .collect(); + field.attrs.iter() + .filter(|attr| attr.path.is_ident("interactive_clap")) + .flat_map(|attr| attr.tokens.clone()) + .filter(|attr_token| { + match attr_token { + proc_macro2::TokenTree::Group(group) => group.stream().to_string().contains("named_arg"), + _ => abort_call_site!("Only option `TokenTree::Group` is needed") + } + }) + .map(|_| { + let ty = &field.ty; + let type_string = match ty { + syn::Type::Path(type_path) => { + match type_path.path.segments.last() { + Some(path_segment) => path_segment.ident.to_string(), + _ => String::new() } + }, + _ => String::new() + }; + let enum_for_clap_named_arg = syn::Ident::new(&format!("ClapNamedArg{}For{}", &type_string, &name), Span::call_site()); + quote! { + #[derive(Debug, Clone, clap::Parser, interactive_clap_derive::ToCliArgs)] + pub enum #enum_for_clap_named_arg { + #(#attr_doc_vec)* + #variant_name(<#ty as interactive_clap::ToCli>::CliVariant) + } - impl From<#ty> for #enum_for_clap_named_arg { - fn from(item: #ty) -> Self { - Self::#variant_name(<#ty as interactive_clap::ToCli>::CliVariant::from(item)) - } + impl From<#ty> for #enum_for_clap_named_arg { + fn from(item: #ty) -> Self { + Self::#variant_name(<#ty as interactive_clap::ToCli>::CliVariant::from(item)) } } - }) - .next() - }) { - token_stream - } else { - quote! () - }; + } + }) + .next() + }) + .unwrap_or(quote!()); quote! { #[derive(Debug, Default, Clone, clap::Parser, interactive_clap::ToCliArgs)] @@ -222,7 +218,6 @@ pub fn impl_interactive_clap(ast: &syn::DeriveInput) -> TokenStream { for attr in &variant.attrs { if attr.path.is_ident("doc") { attrs.push(attr.into_token_stream()); - // break; }; if attr.path.is_ident("cfg") { for attr_token in attr.tokens.clone() { @@ -368,7 +363,9 @@ fn context_scope_for_struct( fn context_scope_for_struct_field(field: &syn::Field) -> proc_macro2::TokenStream { let ident_field = &field.ident.clone().expect("this field does not exist"); let ty = &field.ty; - if self::methods::fields_without_subcommand::is_field_without_subcommand(field) { + if !self::methods::fields_with_subcommand::is_field_with_subcommand(field) + && !self::methods::fields_with_flatten::is_field_with_flatten(field) + { quote! { pub #ident_field: #ty } 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 old mode 100644 new mode 100755 index 2bfab5f..973118b --- 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 @@ -42,6 +42,13 @@ impl InteractiveClapAttrsCliField { .unwrap_or_default(); }); } + if ident == "flatten" { + args_without_attrs = quote! { + if let Some(arg) = &self.#ident_field { + args.append(&mut arg.to_cli_args()) + } + }; + } if ident == "value_enum" { args_without_attrs = quote! { if let Some(arg) = &self.#ident_field {