From 62f08fd6ad0d5e11bd20eecb2b7f80a49b9df259 Mon Sep 17 00:00:00 2001 From: FroVolod Date: Sun, 21 Apr 2024 14:35:25 +0300 Subject: [PATCH] feat: Add support for "subargs" (#17) --- Cargo.toml | 3 +- examples/advanced_enum.rs | 6 +- examples/struct_with_flatten.rs | 284 ++++++++++++++++-- examples/struct_with_subargs.rs | 61 ++++ .../fields_with_skip_default_input_arg.rs | 1 + ...with_flatten.rs => fields_with_subargs.rs} | 4 +- .../methods/fields_with_subcommand.rs | 1 + .../methods/from_cli_for_struct.rs | 24 +- .../derives/interactive_clap/methods/mod.rs | 2 +- .../src/derives/interactive_clap/mod.rs | 21 +- .../interactive_clap_attrs_cli_field.rs | 4 +- 11 files changed, 360 insertions(+), 51 deletions(-) create mode 100644 examples/struct_with_subargs.rs rename interactive-clap-derive/src/derives/interactive_clap/methods/{fields_with_flatten.rs => fields_with_subargs.rs} (58%) diff --git a/Cargo.toml b/Cargo.toml index ad7b9c0..0752b4f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,5 +25,6 @@ shell-words = "1.0.0" clap = { version = "4.0.18", features = ["derive"] } inquire = "0.6" - +atty = "0.2.14" +colored = "2.0" color-eyre = "0.6" diff --git a/examples/advanced_enum.rs b/examples/advanced_enum.rs index 5ca608c..4046478 100644 --- a/examples/advanced_enum.rs +++ b/examples/advanced_enum.rs @@ -3,8 +3,8 @@ // 1) build an example: cargo build --example advanced_enum // 2) go to the `examples` folder: cd target/debug/examples // 3) run an example: ./advanced_enum (without parameters) => entered interactive mode -// ./advanced_enum network => mode: Ok(Network) -// ./advanced_enum offline => mode: Ok(Offline) +// ./advanced_enum network 23 QWE ASDFG => mode: Network(CliArgs { age: Some(23), first_name: Some("QWE"), second_name: Some("ASDFG") }) +// ./advanced_enum offline => mode: Offline // To learn more about the parameters, use "help" flag: ./advanced_enum --help use interactive_clap::{ResultFromCli, ToCliArgs}; @@ -62,7 +62,7 @@ fn main() -> color_eyre::Result<()> { } } }; - println!("cli_mode: {:?}", mode); + println!("mode: {:?}", mode); println!( "Your console command: {}", shell_words::join(&mode.to_cli_args()) diff --git a/examples/struct_with_flatten.rs b/examples/struct_with_flatten.rs index 65c4e3f..957c9e4 100644 --- a/examples/struct_with_flatten.rs +++ b/examples/struct_with_flatten.rs @@ -3,40 +3,278 @@ // 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) }) } +// ./struct_with_flatten --no-docker --no-abi --out-dir /Users/Documents/Rust --color never test.testnet offline => contract: CliContract { build_command_args: Some(CliBuildCommand { no_docker: true, no_release: false, no_abi: true, no_embed_abi: false, no_doc: false, out_dir: Some("/Users/Documents/Rust"), manifest_path: None, color: Some(Never) }), contract_account_id: Some("test.testnet"), mode: Some(Offline) } // To learn more about the parameters, use "help" flag: ./struct_with_flatten --help +// Note: currently there is no automatic generation of "interactive clap::From Cli" + 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(input_context = ())] +#[interactive_clap(output_context = ContractContext)] +#[interactive_clap(skip_default_from_cli)] +pub struct Contract { #[interactive_clap(flatten)] - account: Sender, + /// Specify a build command args: + build_command_args: BuildCommand, + /// What is the contract account ID? + contract_account_id: String, + #[interactive_clap(subcommand)] + pub mode: Mode, } -#[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, +#[derive(Debug, Clone)] +pub struct ContractContext; + +impl ContractContext { + pub fn from_previous_context( + previous_context: (), + scope: &::InteractiveClapContextScope, + ) -> color_eyre::eyre::Result { + // Your commands + Ok(Self) + } +} + +impl interactive_clap::FromCli for Contract { + type FromCliContext = (); + type FromCliError = color_eyre::eyre::Error; + fn from_cli( + 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 build_command_args = + if let Some(cli_build_command_args) = &clap_variant.build_command_args { + BuildCommand { + no_docker: cli_build_command_args.no_docker, + no_release: cli_build_command_args.no_release, + no_abi: cli_build_command_args.no_abi, + no_embed_abi: cli_build_command_args.no_embed_abi, + no_doc: cli_build_command_args.no_doc, + out_dir: cli_build_command_args.out_dir.clone(), + manifest_path: cli_build_command_args.manifest_path.clone(), + color: cli_build_command_args.color.clone(), + } + } else { + BuildCommand::default() + }; + + if clap_variant.contract_account_id.is_none() { + clap_variant.contract_account_id = match Self::input_contract_account_id(&context) { + Ok(Some(contract_account_id)) => Some(contract_account_id), + Ok(None) => return interactive_clap::ResultFromCli::Cancel(Some(clap_variant)), + Err(err) => return interactive_clap::ResultFromCli::Err(Some(clap_variant), err), + }; + } + let contract_account_id = clap_variant + .contract_account_id + .clone() + .expect("Unexpected error"); + + let new_context_scope = InteractiveClapContextScopeForContract { + build_command_args, + contract_account_id, + }; + + let output_context = + match ContractContext::from_previous_context(context, &new_context_scope) { + Ok(new_context) => new_context, + Err(err) => return interactive_clap::ResultFromCli::Err(Some(clap_variant), err), + }; + + match ::from_cli( + clap_variant.mode.take(), + context.into(), + ) { + interactive_clap::ResultFromCli::Ok(cli_field) => { + clap_variant.mode = Some(cli_field); + } + interactive_clap::ResultFromCli::Cancel(option_cli_field) => { + clap_variant.mode = option_cli_field; + return interactive_clap::ResultFromCli::Cancel(Some(clap_variant)); + } + interactive_clap::ResultFromCli::Cancel(option_cli_field) => { + clap_variant.mode = 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.mode = option_cli_field; + return interactive_clap::ResultFromCli::Err(Some(clap_variant), err); + } + }; + interactive_clap::ResultFromCli::Ok(clap_variant) + } +} + +#[derive(Debug, Default, Clone, interactive_clap::InteractiveClap)] +#[interactive_clap(input_context = ())] +#[interactive_clap(output_context = BuildCommandlContext)] +pub struct BuildCommand { + /// Build contract without SourceScan verification + #[interactive_clap(long)] + pub no_docker: bool, + /// Build contract in debug mode, without optimizations and bigger is size + #[interactive_clap(long)] + pub no_release: bool, + /// Do not generate ABI for the contract + #[interactive_clap(long)] + pub no_abi: bool, + /// Do not embed the ABI in the contract binary + #[interactive_clap(long)] + pub no_embed_abi: bool, + /// Do not include rustdocs in the embedded ABI + #[interactive_clap(long)] + pub no_doc: bool, + /// Copy final artifacts to this directory + #[interactive_clap(long)] + #[interactive_clap(skip_interactive_input)] + pub out_dir: Option, + /// Path to the `Cargo.toml` of the contract to build + #[interactive_clap(long)] + #[interactive_clap(skip_interactive_input)] + pub manifest_path: Option, + /// Coloring: auto, always, never + #[interactive_clap(long)] + #[interactive_clap(value_enum)] + #[interactive_clap(skip_interactive_input)] + pub color: Option, +} + +#[derive(Debug, Clone)] +pub struct BuildCommandlContext { + build_command_args: BuildCommand, +} + +impl BuildCommandlContext { + pub fn from_previous_context( + _previous_context: (), + scope: &::InteractiveClapContextScope, + ) -> color_eyre::eyre::Result { + let build_command_args = BuildCommand { + no_docker: scope.no_docker, + no_release: scope.no_release, + no_abi: scope.no_abi, + no_embed_abi: scope.no_embed_abi, + no_doc: scope.no_doc, + out_dir: scope.out_dir.clone(), + manifest_path: scope.manifest_path.clone(), + color: scope.color.clone(), + }; + Ok(Self { build_command_args }) + } +} + +use strum::{EnumDiscriminants, EnumIter, EnumMessage}; + +#[derive(Debug, EnumDiscriminants, Clone, interactive_clap::InteractiveClap)] +#[strum_discriminants(derive(EnumMessage, EnumIter))] +///To construct a transaction you will need to provide information about sender (signer) and receiver accounts, and actions that needs to be performed. +///Do you want to derive some information required for transaction construction automatically querying it online? +pub enum Mode { + /// Prepare and, optionally, submit a new transaction with online mode + #[strum_discriminants(strum(message = "Yes, I keep it simple"))] + Network, + /// Prepare and, optionally, submit a new transaction with offline mode + #[strum_discriminants(strum( + message = "No, I want to work in no-network (air-gapped) environment" + ))] + Offline, +} + +use std::{env, str::FromStr}; + +#[derive(Debug, EnumDiscriminants, Clone, clap::ValueEnum)] +#[strum_discriminants(derive(EnumMessage, EnumIter))] +pub enum ColorPreference { + Auto, + Always, + Never, +} + +impl interactive_clap::ToCli for ColorPreference { + type CliVariant = ColorPreference; +} + +impl std::fmt::Display for ColorPreference { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + Self::Auto => write!(f, "auto"), + Self::Always => write!(f, "always"), + Self::Never => write!(f, "never"), + } + } +} + +impl FromStr for ColorPreference { + type Err = String; + + fn from_str(s: &str) -> Result { + match s { + "auto" => Ok(default_mode()), + "always" => Ok(ColorPreference::Always), + "never" => Ok(ColorPreference::Never), + _ => Err(format!("invalid color preference: {}", s)), + } + } +} + +fn default_mode() -> ColorPreference { + match env::var("NO_COLOR") { + Ok(v) if v != "0" => ColorPreference::Never, + _ => { + if atty::is(atty::Stream::Stderr) { + ColorPreference::Always + } else { + ColorPreference::Never + } + } + } +} + +impl ColorPreference { + pub fn as_str(&self) -> &str { + match self { + ColorPreference::Auto => "auto", + ColorPreference::Always => "always", + ColorPreference::Never => "never", + } + } + + pub fn apply(&self) { + match self { + ColorPreference::Auto => { + default_mode().apply(); + } + ColorPreference::Always => colored::control::set_override(true), + ColorPreference::Never => colored::control::set_override(false), + } + } } fn main() -> color_eyre::Result<()> { - let mut cli_account = Account::parse(); + let mut cli_contract = Contract::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:?}"); + let contract = + ::from_cli(Some(cli_contract), context); + match contract { + ResultFromCli::Ok(cli_contract) | ResultFromCli::Cancel(Some(cli_contract)) => { + println!("contract: {cli_contract:?}"); println!( "Your console command: {}", - shell_words::join(&cli_account.to_cli_args()) + shell_words::join(&cli_contract.to_cli_args()) ); return Ok(()); } @@ -45,13 +283,13 @@ fn main() -> color_eyre::Result<()> { return Ok(()); } ResultFromCli::Back => { - cli_account = Default::default(); + cli_contract = Default::default(); } - ResultFromCli::Err(cli_account, err) => { - if let Some(cli_account) = cli_account { + ResultFromCli::Err(cli_contract, err) => { + if let Some(cli_contract) = cli_contract { println!( "Your console command: {}", - shell_words::join(&cli_account.to_cli_args()) + shell_words::join(&cli_contract.to_cli_args()) ); } return Err(err); diff --git a/examples/struct_with_subargs.rs b/examples/struct_with_subargs.rs new file mode 100644 index 0000000..31e22a9 --- /dev/null +++ b/examples/struct_with_subargs.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 subargs attributes. + +// 1) build an example: cargo build --example struct_with_subargs +// 2) go to the `examples` folder: cd target/debug/examples +// 3) run an example: ./struct_with_subargs (without parameters) => entered interactive mode +// ./struct_with_subargs 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_subargs --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(subargs)] + account: Sender, +} + +#[derive(Debug, Clone, interactive_clap::InteractiveClap)] +pub struct Sender { + /// What is the sender account ID? + sender_account_id: String, + /// How old is the sender? + 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_skip_default_input_arg.rs b/interactive-clap-derive/src/derives/interactive_clap/methods/fields_with_skip_default_input_arg.rs index fc71d85..db2c71b 100644 --- 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 @@ -14,5 +14,6 @@ pub fn is_field_with_skip_default_input_arg(field: &syn::Field) -> bool { .any(|attr_token| { attr_token.to_string().contains("skip_default_input_arg") || attr_token.to_string().contains("flatten") + || attr_token.to_string().contains("subargs") }) } 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_subargs.rs similarity index 58% rename from interactive-clap-derive/src/derives/interactive_clap/methods/fields_with_flatten.rs rename to interactive-clap-derive/src/derives/interactive_clap/methods/fields_with_subargs.rs index 26af0cb..4e52f60 100644 --- a/interactive-clap-derive/src/derives/interactive_clap/methods/fields_with_flatten.rs +++ b/interactive-clap-derive/src/derives/interactive_clap/methods/fields_with_subargs.rs @@ -2,7 +2,7 @@ extern crate proc_macro; use syn; -pub fn is_field_with_flatten(field: &syn::Field) -> bool { +pub fn is_field_with_subargs(field: &syn::Field) -> bool { if field.attrs.is_empty() { return false; } @@ -10,5 +10,5 @@ pub fn is_field_with_flatten(field: &syn::Field) -> bool { .attrs .iter() .flat_map(|attr| attr.tokens.clone()) - .any(|attr_token| attr_token.to_string().contains("flatten")) + .any(|attr_token| attr_token.to_string().contains("subargs")) } 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 index ef46121..b1acab7 100644 --- 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 @@ -2,6 +2,7 @@ extern crate proc_macro; use syn; +/// This function selects fields with: subcommand, named_arg pub fn is_field_with_subcommand(field: &syn::Field) -> bool { if field.attrs.is_empty() { return false; 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 8c07793..706be3c 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,11 +17,11 @@ pub fn from_cli_for_struct( return quote!(); }; - let fields_without_subcommand_and_flatten = fields + let fields_without_subcommand_and_subargs = fields .iter() .filter(|field| { !super::fields_with_subcommand::is_field_with_subcommand(field) - && !super::fields_with_flatten::is_field_with_flatten(field) + && !super::fields_with_subargs::is_field_with_subargs(field) }) .map(|field| { let ident_field = &field.clone().ident.expect("this field does not exist"); @@ -46,9 +46,9 @@ pub fn from_cli_for_struct( .find(|token_stream| !token_stream.is_empty()) .unwrap_or(quote!()); - let field_value_flatten = fields + let field_value_subargs = fields .iter() - .map(field_value_flatten) + .map(field_value_subargs) .find(|token_stream| !token_stream.is_empty()) .unwrap_or(quote!()); @@ -61,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_and_flatten,)* }; + let new_context_scope = #interactive_clap_context_scope_for_struct { #(#fields_without_subcommand_and_subargs,)* }; }; let output_context = match &interactive_clap_attrs_context.output_context_dir { @@ -89,7 +89,7 @@ pub fn from_cli_for_struct( #(#fields_value)* #new_context_scope #output_context - #field_value_flatten + #field_value_subargs #field_value_named_arg #field_value_subcommand; interactive_clap::ResultFromCli::Ok(clap_variant) @@ -107,8 +107,6 @@ 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() @@ -125,7 +123,9 @@ fn fields_value(field: &syn::Field) -> proc_macro2::TokenStream { }; let #ident_field = clap_variant.#ident_field.clone(); } - } else if !super::fields_with_subcommand::is_field_with_subcommand(field) { + } else if !super::fields_with_subcommand::is_field_with_subcommand(field) + && !super::fields_with_subargs::is_field_with_subargs(field) + { quote! { if clap_variant.#ident_field.is_none() { clap_variant @@ -153,7 +153,7 @@ fn field_value_named_arg(name: &syn::Ident, field: &syn::Field) -> proc_macro2:: .flat_map(|attr| attr.tokens.clone()) .filter(|attr_token| { match attr_token { - proc_macro2::TokenTree::Group(group) => group.stream().to_string().contains("named_arg"), + proc_macro2::TokenTree::Group(group) => group.stream().to_string() == *"named_arg", _ => abort_call_site!("Only option `TokenTree::Group` is needed") } }) @@ -241,7 +241,7 @@ fn field_value_subcommand(field: &syn::Field) -> proc_macro2::TokenStream { } } -fn field_value_flatten(field: &syn::Field) -> proc_macro2::TokenStream { +fn field_value_subargs(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() { @@ -252,7 +252,7 @@ fn field_value_flatten(field: &syn::Field) -> proc_macro2::TokenStream { .flat_map(|attr| attr.tokens.clone()) .filter(|attr_token| { match attr_token { - proc_macro2::TokenTree::Group(group) => group.stream().to_string().contains("flatten"), + proc_macro2::TokenTree::Group(group) => group.stream().to_string().contains("subargs"), _ => abort_call_site!("Only option `TokenTree::Group` is needed") } }) 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 e666a16..a1d5463 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,7 @@ pub mod choose_variant; pub mod cli_field_type; -pub mod fields_with_flatten; pub mod fields_with_skip_default_input_arg; +pub mod fields_with_subargs; pub mod fields_with_subcommand; pub mod from_cli_for_enum; pub mod from_cli_for_struct; diff --git a/interactive-clap-derive/src/derives/interactive_clap/mod.rs b/interactive-clap-derive/src/derives/interactive_clap/mod.rs index 306af10..271bc67 100644 --- a/interactive-clap-derive/src/derives/interactive_clap/mod.rs +++ b/interactive-clap-derive/src/derives/interactive_clap/mod.rs @@ -31,18 +31,18 @@ pub fn impl_interactive_clap(ast: &syn::DeriveInput) -> TokenStream { let mut clap_attr_vec: Vec = Vec::new(); let mut cfg_attr_vec: Vec = Vec::new(); for attr in &field.attrs { - if attr.path.is_ident("interactive_clap") | attr.path.is_ident("cfg") { + if attr.path.is_ident("interactive_clap") || attr.path.is_ident("cfg") { for attr_token in attr.tokens.clone() { match attr_token { proc_macro2::TokenTree::Group(group) => { if group.stream().to_string().contains("subcommand") - | group.stream().to_string().contains("value_enum") - | group.stream().to_string().contains("long") - | (group.stream().to_string() == *"skip") - | (group.stream().to_string() == *"flatten") + || group.stream().to_string().contains("value_enum") + || group.stream().to_string().contains("long") + || (group.stream().to_string() == *"skip") + || (group.stream().to_string() == *"flatten") { clap_attr_vec.push(group.stream()) - } else if group.stream().to_string().contains("named_arg") { + } else if group.stream().to_string() == *"named_arg" { let ident_subcommand = syn::Ident::new("subcommand", Span::call_site()); clap_attr_vec.push(quote! {#ident_subcommand}); @@ -71,6 +71,11 @@ pub fn impl_interactive_clap(ast: &syn::DeriveInput) -> TokenStream { if group.stream().to_string().contains("feature") { cfg_attr_vec.push(attr.into_token_stream()) }; + if group.stream().to_string().contains("subargs") { + let ident_subargs = + syn::Ident::new("flatten", Span::call_site()); + clap_attr_vec.push(quote! {#ident_subargs}); + }; if group.stream().to_string() == *"skip" { ident_skip_field_vec.push(ident_field.clone()); cli_field = quote!() @@ -134,7 +139,7 @@ pub fn impl_interactive_clap(ast: &syn::DeriveInput) -> TokenStream { .flat_map(|attr| attr.tokens.clone()) .filter(|attr_token| { match attr_token { - proc_macro2::TokenTree::Group(group) => group.stream().to_string().contains("named_arg"), + proc_macro2::TokenTree::Group(group) => group.stream().to_string() == *"named_arg", _ => abort_call_site!("Only option `TokenTree::Group` is needed") } }) @@ -364,7 +369,7 @@ fn context_scope_for_struct_field(field: &syn::Field) -> proc_macro2::TokenStrea let ident_field = &field.ident.clone().expect("this field does not exist"); let ty = &field.ty; if !self::methods::fields_with_subcommand::is_field_with_subcommand(field) - && !self::methods::fields_with_flatten::is_field_with_flatten(field) + && !self::methods::fields_with_subargs::is_field_with_subargs(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 index 973118b..8dc0fd8 100755 --- 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 @@ -45,7 +45,9 @@ impl InteractiveClapAttrsCliField { if ident == "flatten" { args_without_attrs = quote! { if let Some(arg) = &self.#ident_field { - args.append(&mut arg.to_cli_args()) + let mut to_cli_args = arg.to_cli_args(); + to_cli_args.append(&mut args); + std::mem::swap(&mut args, &mut to_cli_args); } }; }