From 251dfcebed001ef1b97220e83cb0cb0bb3ed8dec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dj8yf0=CE=BCl?= Date: Wed, 11 Dec 2024 17:23:07 +0200 Subject: [PATCH 01/57] commented: add propagate doc attributes for clap's derive of `help` and `long_help` Add this functionality in commented form --- examples/struct_with_subargs.rs | 4 ++++ interactive-clap-derive/src/derives/interactive_clap/mod.rs | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/examples/struct_with_subargs.rs b/examples/struct_with_subargs.rs index 31e22a9..87b60e6 100644 --- a/examples/struct_with_subargs.rs +++ b/examples/struct_with_subargs.rs @@ -11,9 +11,13 @@ use interactive_clap::{ResultFromCli, ToCliArgs}; #[derive(Debug, Clone, interactive_clap::InteractiveClap)] struct Account { /// Change SocialDb prefix + /// + /// It's a paraghraph, describing, this argument usage in more detail + /// than just the headline #[interactive_clap(long)] #[interactive_clap(skip_interactive_input)] social_db_folder: Option, + /// Sender account #[interactive_clap(subargs)] account: Sender, } diff --git a/interactive-clap-derive/src/derives/interactive_clap/mod.rs b/interactive-clap-derive/src/derives/interactive_clap/mod.rs index db05a76..bbabf0e 100644 --- a/interactive-clap-derive/src/derives/interactive_clap/mod.rs +++ b/interactive-clap-derive/src/derives/interactive_clap/mod.rs @@ -33,6 +33,7 @@ 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(); + // let mut doc_attr_vec: Vec = Vec::new(); for attr in &field.attrs { if attr.path.is_ident("interactive_clap") || attr.path.is_ident("cfg") { for attr_token in attr.tokens.clone() { @@ -105,6 +106,9 @@ pub fn impl_interactive_clap(ast: &syn::DeriveInput) -> TokenStream { } } } + // if attr.path.is_ident("doc") { + // doc_attr_vec.push(attr.into_token_stream()) + // } } if cli_field.is_empty() { return cli_field; @@ -114,12 +118,14 @@ pub fn impl_interactive_clap(ast: &syn::DeriveInput) -> TokenStream { let clap_attrs = clap_attr_vec.iter(); quote! { #(#cfg_attrs)* + // #(#doc_attr_vec)* #[clap(#(#clap_attrs, )*)] #cli_field } } else { quote! { #(#cfg_attrs)* + // #(#doc_attr_vec)* #cli_field } } From ae76568e62697367bc987d4e2caef415e6c9a146 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dj8yf0=CE=BCl?= Date: Thu, 12 Dec 2024 20:26:59 +0200 Subject: [PATCH 02/57] test: add test_doc_comments_propagate --- ...simple_struct__doc_comments_propagate.snap | 128 ++++++++++++++++++ .../src/tests/test_simple_struct.rs | 29 ++++ 2 files changed, 157 insertions(+) create mode 100644 interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__doc_comments_propagate.snap diff --git a/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__doc_comments_propagate.snap b/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__doc_comments_propagate.snap new file mode 100644 index 0000000..2417c54 --- /dev/null +++ b/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__doc_comments_propagate.snap @@ -0,0 +1,128 @@ +--- +source: interactive-clap-derive/src/tests/test_simple_struct.rs +expression: pretty_codegen(&interactive_clap_codegen) +--- +#[derive(Debug, Default, Clone, clap::Parser, interactive_clap::ToCliArgs)] +#[clap(author, version, about, long_about = None)] +pub struct CliArgs { + pub first_field: Option<::CliVariant>, + pub second_field: Option<::CliVariant>, +} +impl interactive_clap::ToCli for Args { + type CliVariant = CliArgs; +} +pub struct InteractiveClapContextScopeForArgs { + pub first_field: u64, + pub second_field: String, +} +impl interactive_clap::ToInteractiveClapContextScope for Args { + type InteractiveClapContextScope = InteractiveClapContextScopeForArgs; +} +impl interactive_clap::FromCli for Args { + 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.clone().unwrap_or_default(); + if clap_variant.first_field.is_none() { + clap_variant + .first_field = match Self::input_first_field(&context) { + Ok(Some(first_field)) => Some(first_field), + Ok(None) => { + return interactive_clap::ResultFromCli::Cancel(Some(clap_variant)); + } + Err(err) => { + return interactive_clap::ResultFromCli::Err(Some(clap_variant), err); + } + }; + } + let first_field = clap_variant.first_field.clone().expect("Unexpected error"); + if clap_variant.second_field.is_none() { + clap_variant + .second_field = match Self::input_second_field(&context) { + Ok(Some(second_field)) => Some(second_field), + Ok(None) => { + return interactive_clap::ResultFromCli::Cancel(Some(clap_variant)); + } + Err(err) => { + return interactive_clap::ResultFromCli::Err(Some(clap_variant), err); + } + }; + } + let second_field = clap_variant.second_field.clone().expect("Unexpected error"); + let new_context_scope = InteractiveClapContextScopeForArgs { + first_field: first_field.into(), + second_field: second_field.into(), + }; + interactive_clap::ResultFromCli::Ok(clap_variant) + } +} +impl Args { + fn input_first_field(_context: &()) -> color_eyre::eyre::Result> { + match inquire::CustomType::new( + concat!( + r" short first field description", r"", + r" a longer paragraph, describing the usage and stuff with first field's", + r" awarenes of its possible applications", + ) + .trim(), + ) + .prompt() + { + Ok(value) => Ok(Some(value)), + Err( + inquire::error::InquireError::OperationCanceled + | inquire::error::InquireError::OperationInterrupted, + ) => Ok(None), + Err(err) => Err(err.into()), + } + } + fn input_second_field(_context: &()) -> color_eyre::eyre::Result> { + match inquire::CustomType::new( + concat!( + r" short second field description", r"", + r" a longer paragraph, describing the usage and stuff with second field's", + r" awareness of its possible applications", + ) + .trim(), + ) + .prompt() + { + Ok(value) => Ok(Some(value)), + Err( + inquire::error::InquireError::OperationCanceled + | inquire::error::InquireError::OperationInterrupted, + ) => Ok(None), + Err(err) => Err(err.into()), + } + } + pub fn try_parse() -> Result { + ::try_parse() + } + pub fn parse() -> CliArgs { + ::parse() + } + pub fn try_parse_from(itr: I) -> Result + where + I: ::std::iter::IntoIterator, + T: ::std::convert::Into<::std::ffi::OsString> + ::std::clone::Clone, + { + ::try_parse_from(itr) + } +} +impl From for CliArgs { + fn from(args: Args) -> Self { + Self { + first_field: Some(args.first_field.into()), + second_field: Some(args.second_field.into()), + } + } +} diff --git a/interactive-clap-derive/src/tests/test_simple_struct.rs b/interactive-clap-derive/src/tests/test_simple_struct.rs index 5b26aad..a342ed4 100644 --- a/interactive-clap-derive/src/tests/test_simple_struct.rs +++ b/interactive-clap-derive/src/tests/test_simple_struct.rs @@ -88,3 +88,32 @@ fn test_vec_multiple_opt_err() { insta::assert_snapshot!(pretty_codegen(&interactive_clap_codegen)); } + +/// this test checks if doc comments are propagated up to `CliArgs` struct, +/// which has `clap::Parser` derive on it +/// +/// also it checks that `#[interactive_clap(verbatim_doc_comment)]` attribute +/// gets transferred to `#[clap(verbatim_doc_comment)]` on `second_field` of +/// the same `CliArgs` struct +#[test] +fn test_doc_comments_propagate() { + let input = syn::parse_quote! { + struct Args { + /// short first field description + /// + /// a longer paragraph, describing the usage and stuff with first field's + /// awarenes of its possible applications + first_field: u64, + /// short second field description + /// + /// a longer paragraph, describing the usage and stuff with second field's + /// awareness of its possible applications + #[interactive_clap(verbatim_doc_comment)] + second_field: String, + } + }; + + let interactive_clap_codegen = crate::derives::interactive_clap::impl_interactive_clap(&input); + insta::assert_snapshot!(pretty_codegen(&interactive_clap_codegen)); + +} From 0c3ce3af6fa98bf597f0ee7e9da3de0663a59fe3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dj8yf0=CE=BCl?= Date: Thu, 12 Dec 2024 22:11:42 +0200 Subject: [PATCH 03/57] uncomment: propagating doc comments attributes --- .../src/derives/interactive_clap/mod.rs | 12 ++++++------ ...__test_simple_struct__doc_comments_propagate.snap | 8 ++++++++ ...clap_derive__tests__test_simple_struct__flag.snap | 1 + 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/interactive-clap-derive/src/derives/interactive_clap/mod.rs b/interactive-clap-derive/src/derives/interactive_clap/mod.rs index bbabf0e..fcdfa3f 100644 --- a/interactive-clap-derive/src/derives/interactive_clap/mod.rs +++ b/interactive-clap-derive/src/derives/interactive_clap/mod.rs @@ -33,7 +33,7 @@ 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(); - // let mut doc_attr_vec: Vec = Vec::new(); + let mut doc_attr_vec: Vec = Vec::new(); for attr in &field.attrs { if attr.path.is_ident("interactive_clap") || attr.path.is_ident("cfg") { for attr_token in attr.tokens.clone() { @@ -106,9 +106,9 @@ pub fn impl_interactive_clap(ast: &syn::DeriveInput) -> TokenStream { } } } - // if attr.path.is_ident("doc") { - // doc_attr_vec.push(attr.into_token_stream()) - // } + if attr.path.is_ident("doc") { + doc_attr_vec.push(attr.into_token_stream()) + } } if cli_field.is_empty() { return cli_field; @@ -118,14 +118,14 @@ pub fn impl_interactive_clap(ast: &syn::DeriveInput) -> TokenStream { let clap_attrs = clap_attr_vec.iter(); quote! { #(#cfg_attrs)* - // #(#doc_attr_vec)* + #(#doc_attr_vec)* #[clap(#(#clap_attrs, )*)] #cli_field } } else { quote! { #(#cfg_attrs)* - // #(#doc_attr_vec)* + #(#doc_attr_vec)* #cli_field } } diff --git a/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__doc_comments_propagate.snap b/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__doc_comments_propagate.snap index 2417c54..388ce43 100644 --- a/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__doc_comments_propagate.snap +++ b/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__doc_comments_propagate.snap @@ -5,7 +5,15 @@ expression: pretty_codegen(&interactive_clap_codegen) #[derive(Debug, Default, Clone, clap::Parser, interactive_clap::ToCliArgs)] #[clap(author, version, about, long_about = None)] pub struct CliArgs { + /// short first field description + /// + /// a longer paragraph, describing the usage and stuff with first field's + /// awarenes of its possible applications pub first_field: Option<::CliVariant>, + /// short second field description + /// + /// a longer paragraph, describing the usage and stuff with second field's + /// awareness of its possible applications pub second_field: Option<::CliVariant>, } impl interactive_clap::ToCli for Args { diff --git a/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__flag.snap b/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__flag.snap index 5dcb81d..fae77ce 100644 --- a/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__flag.snap +++ b/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__flag.snap @@ -5,6 +5,7 @@ expression: pretty_codegen(&interactive_clap_codegen) #[derive(Debug, Default, Clone, clap::Parser, interactive_clap::ToCliArgs)] #[clap(author, version, about, long_about = None)] pub struct CliArgs { + /// Offline mode #[clap(long)] pub offline: bool, } From e02529dadcdc2d8f12e48e5ad147f026c24ba005 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dj8yf0=CE=BCl?= Date: Thu, 12 Dec 2024 23:41:50 +0200 Subject: [PATCH 04/57] chore: add dbg_cond! macro --- examples/struct_with_subargs.rs | 2 +- interactive-clap-derive/Cargo.toml | 4 ++++ interactive-clap-derive/src/debug.rs | 13 +++++++++++++ .../methods/skip_interactive_input.rs | 1 - .../src/derives/interactive_clap/mod.rs | 8 +++++--- interactive-clap-derive/src/lib.rs | 3 +++ .../src/tests/test_simple_struct.rs | 9 +++------ 7 files changed, 29 insertions(+), 11 deletions(-) create mode 100644 interactive-clap-derive/src/debug.rs diff --git a/examples/struct_with_subargs.rs b/examples/struct_with_subargs.rs index 87b60e6..3c895de 100644 --- a/examples/struct_with_subargs.rs +++ b/examples/struct_with_subargs.rs @@ -11,7 +11,7 @@ use interactive_clap::{ResultFromCli, ToCliArgs}; #[derive(Debug, Clone, interactive_clap::InteractiveClap)] struct Account { /// Change SocialDb prefix - /// + /// /// It's a paraghraph, describing, this argument usage in more detail /// than just the headline #[interactive_clap(long)] diff --git a/interactive-clap-derive/Cargo.toml b/interactive-clap-derive/Cargo.toml index 9ceb11d..d647ffc 100644 --- a/interactive-clap-derive/Cargo.toml +++ b/interactive-clap-derive/Cargo.toml @@ -22,3 +22,7 @@ syn = "1" prettyplease = "0.1" insta = "1" syn = { version = "1", features = ["full"] } + +[features] +default = [] +introspect = [] diff --git a/interactive-clap-derive/src/debug.rs b/interactive-clap-derive/src/debug.rs new file mode 100644 index 0000000..e89f1ea --- /dev/null +++ b/interactive-clap-derive/src/debug.rs @@ -0,0 +1,13 @@ +#[cfg(feature = "introspect")] +macro_rules! dbg_cond { + ($($val:expr),* ) => { + dbg!($($val),*) + }; +} + +#[cfg(not(feature = "introspect"))] +macro_rules! dbg_cond { + ($($val:expr),*) => { + // no-op + }; +} diff --git a/interactive-clap-derive/src/derives/interactive_clap/methods/skip_interactive_input.rs b/interactive-clap-derive/src/derives/interactive_clap/methods/skip_interactive_input.rs index a7cc6b1..dc91e49 100644 --- a/interactive-clap-derive/src/derives/interactive_clap/methods/skip_interactive_input.rs +++ b/interactive-clap-derive/src/derives/interactive_clap/methods/skip_interactive_input.rs @@ -4,7 +4,6 @@ use syn; use crate::LONG_VEC_MUTLIPLE_OPT; - pub fn is_skip_interactive_input(field: &syn::Field) -> bool { field .attrs diff --git a/interactive-clap-derive/src/derives/interactive_clap/mod.rs b/interactive-clap-derive/src/derives/interactive_clap/mod.rs index fcdfa3f..9877d5c 100644 --- a/interactive-clap-derive/src/derives/interactive_clap/mod.rs +++ b/interactive-clap-derive/src/derives/interactive_clap/mod.rs @@ -35,8 +35,10 @@ pub fn impl_interactive_clap(ast: &syn::DeriveInput) -> TokenStream { let mut cfg_attr_vec: Vec = Vec::new(); let mut doc_attr_vec: Vec = Vec::new(); for attr in &field.attrs { + dbg_cond!(attr.path.to_token_stream().into_iter().collect::>()); if attr.path.is_ident("interactive_clap") || attr.path.is_ident("cfg") { - for attr_token in attr.tokens.clone() { + for (_index, attr_token) in attr.tokens.clone().into_iter().enumerate() { + dbg_cond!(_index, &attr_token); match attr_token { proc_macro2::TokenTree::Group(group) => { let group_string = group.stream().to_string(); @@ -434,12 +436,12 @@ fn for_cli_field( quote!() } else { let ty = &field.ty; - if field.attrs.iter().any(|attr| + if field.attrs.iter().any(|attr| attr.path.is_ident("interactive_clap") && attr.tokens.clone().into_iter().any( |attr_token| matches!( - attr_token, + attr_token, proc_macro2::TokenTree::Group(group) if group.stream().to_string() == LONG_VEC_MUTLIPLE_OPT ) ) diff --git a/interactive-clap-derive/src/lib.rs b/interactive-clap-derive/src/lib.rs index daa4fcb..7a0149f 100644 --- a/interactive-clap-derive/src/lib.rs +++ b/interactive-clap-derive/src/lib.rs @@ -3,6 +3,9 @@ extern crate proc_macro; use proc_macro::TokenStream; use proc_macro_error::proc_macro_error; +#[macro_use] +mod debug; + mod derives; mod helpers; #[cfg(test)] diff --git a/interactive-clap-derive/src/tests/test_simple_struct.rs b/interactive-clap-derive/src/tests/test_simple_struct.rs index a342ed4..d9bdb0f 100644 --- a/interactive-clap-derive/src/tests/test_simple_struct.rs +++ b/interactive-clap-derive/src/tests/test_simple_struct.rs @@ -56,7 +56,6 @@ fn test_vec_multiple_opt() { let interactive_clap_codegen = crate::derives::interactive_clap::impl_interactive_clap(&input); insta::assert_snapshot!(pretty_codegen(&interactive_clap_codegen)); - } #[test] @@ -73,7 +72,7 @@ fn test_vec_multiple_opt_to_cli_args() { } #[test] -// testing correct panic msg isn't really very compatible with +// testing correct panic msg isn't really very compatible with // `proc-macro-error` crate #[should_panic] fn test_vec_multiple_opt_err() { @@ -86,14 +85,13 @@ fn test_vec_multiple_opt_err() { let interactive_clap_codegen = crate::derives::interactive_clap::impl_interactive_clap(&input); insta::assert_snapshot!(pretty_codegen(&interactive_clap_codegen)); - } /// this test checks if doc comments are propagated up to `CliArgs` struct, /// which has `clap::Parser` derive on it /// -/// also it checks that `#[interactive_clap(verbatim_doc_comment)]` attribute -/// gets transferred to `#[clap(verbatim_doc_comment)]` on `second_field` of +/// also it checks that `#[interactive_clap(verbatim_doc_comment)]` attribute +/// gets transferred to `#[clap(verbatim_doc_comment)]` on `second_field` of /// the same `CliArgs` struct #[test] fn test_doc_comments_propagate() { @@ -115,5 +113,4 @@ fn test_doc_comments_propagate() { let interactive_clap_codegen = crate::derives::interactive_clap::impl_interactive_clap(&input); insta::assert_snapshot!(pretty_codegen(&interactive_clap_codegen)); - } From 912744d4365a93590794d1a19abdd3f4755447fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dj8yf0=CE=BCl?= Date: Fri, 13 Dec 2024 00:35:52 +0200 Subject: [PATCH 05/57] chore: add passthrough of `verbatim_doc_comment` attribute to clap --- examples/struct_with_subargs.rs | 1 + interactive-clap-derive/src/derives/interactive_clap/mod.rs | 3 ++- interactive-clap-derive/src/lib.rs | 5 +++++ ...e__tests__test_simple_struct__doc_comments_propagate.snap | 1 + 4 files changed, 9 insertions(+), 1 deletion(-) diff --git a/examples/struct_with_subargs.rs b/examples/struct_with_subargs.rs index 3c895de..5e30781 100644 --- a/examples/struct_with_subargs.rs +++ b/examples/struct_with_subargs.rs @@ -16,6 +16,7 @@ struct Account { /// than just the headline #[interactive_clap(long)] #[interactive_clap(skip_interactive_input)] + #[interactive_clap(verbatim_doc_comment)] social_db_folder: Option, /// Sender account #[interactive_clap(subargs)] diff --git a/interactive-clap-derive/src/derives/interactive_clap/mod.rs b/interactive-clap-derive/src/derives/interactive_clap/mod.rs index 9877d5c..5bce8b5 100644 --- a/interactive-clap-derive/src/derives/interactive_clap/mod.rs +++ b/interactive-clap-derive/src/derives/interactive_clap/mod.rs @@ -6,7 +6,7 @@ use proc_macro_error::abort_call_site; use quote::{quote, ToTokens}; use syn; -use crate::LONG_VEC_MUTLIPLE_OPT; +use crate::{LONG_VEC_MUTLIPLE_OPT, VERBATIM_DOC_COMMENT}; pub(crate) mod methods; @@ -47,6 +47,7 @@ pub fn impl_interactive_clap(ast: &syn::DeriveInput) -> TokenStream { || group_string.contains("long") || (group_string == *"skip") || (group_string == *"flatten") + || (group_string == VERBATIM_DOC_COMMENT) { if group_string != LONG_VEC_MUTLIPLE_OPT { clap_attr_vec.push(group.stream()) diff --git a/interactive-clap-derive/src/lib.rs b/interactive-clap-derive/src/lib.rs index 7a0149f..fb6bf9d 100644 --- a/interactive-clap-derive/src/lib.rs +++ b/interactive-clap-derive/src/lib.rs @@ -19,6 +19,11 @@ mod tests; /// implies `#[interactive_clap(skip_interactive_input)]`, as it's not intended for interactive input pub(crate) const LONG_VEC_MUTLIPLE_OPT: &str = "long_vec_multiple_opt"; +/// `#[interactive_clap(...)]` attribute which translates 1-to-1 into +/// `#[clap(verbatim_doc_comment)]` +/// More info on https://docs.rs/clap/4.5.23/clap/_derive/index.html#command-attributes +pub(crate) const VERBATIM_DOC_COMMENT: &str = "verbatim_doc_comment"; + #[proc_macro_derive(InteractiveClap, attributes(interactive_clap))] #[proc_macro_error] pub fn interactive_clap(input: TokenStream) -> TokenStream { diff --git a/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__doc_comments_propagate.snap b/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__doc_comments_propagate.snap index 388ce43..784c525 100644 --- a/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__doc_comments_propagate.snap +++ b/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__doc_comments_propagate.snap @@ -14,6 +14,7 @@ pub struct CliArgs { /// /// a longer paragraph, describing the usage and stuff with second field's /// awareness of its possible applications + #[clap(verbatim_doc_comment)] pub second_field: Option<::CliVariant>, } impl interactive_clap::ToCli for Args { From 451e7e31eca8d3a5f6f08caafd4da2cf5a435b3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dj8yf0=CE=BCl?= Date: Mon, 6 Jan 2025 22:41:48 +0200 Subject: [PATCH 06/57] ci: fix clippy --- interactive-clap-derive/src/derives/interactive_clap/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/interactive-clap-derive/src/derives/interactive_clap/mod.rs b/interactive-clap-derive/src/derives/interactive_clap/mod.rs index 5bce8b5..a83bd81 100644 --- a/interactive-clap-derive/src/derives/interactive_clap/mod.rs +++ b/interactive-clap-derive/src/derives/interactive_clap/mod.rs @@ -34,6 +34,7 @@ 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(); let mut doc_attr_vec: Vec = Vec::new(); + #[allow(clippy::unused_enumerate_index)] for attr in &field.attrs { dbg_cond!(attr.path.to_token_stream().into_iter().collect::>()); if attr.path.is_ident("interactive_clap") || attr.path.is_ident("cfg") { From 19c9d778b4f7b807771698a045460d9c319f1877 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dj8yf0=CE=BCl?= Date: Tue, 7 Jan 2025 22:19:53 +0200 Subject: [PATCH 07/57] test: add 2 simple tests for enums --- interactive-clap-derive/src/tests/mod.rs | 6 ++ ...ests__test_simple_enum__simple_enum-2.snap | 20 +++++ ..._tests__test_simple_enum__simple_enum.snap | 81 +++++++++++++++++++ ...imple_enum_with_strum_discriminants-2.snap | 20 +++++ ..._simple_enum_with_strum_discriminants.snap | 81 +++++++++++++++++++ .../src/tests/test_simple_enum.rs | 41 ++++++++++ .../src/tests/test_simple_struct.rs | 5 +- 7 files changed, 250 insertions(+), 4 deletions(-) create mode 100644 interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_enum__simple_enum-2.snap create mode 100644 interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_enum__simple_enum.snap create mode 100644 interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_enum__simple_enum_with_strum_discriminants-2.snap create mode 100644 interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_enum__simple_enum_with_strum_discriminants.snap create mode 100644 interactive-clap-derive/src/tests/test_simple_enum.rs diff --git a/interactive-clap-derive/src/tests/mod.rs b/interactive-clap-derive/src/tests/mod.rs index d489ea3..6d3e6c2 100644 --- a/interactive-clap-derive/src/tests/mod.rs +++ b/interactive-clap-derive/src/tests/mod.rs @@ -1 +1,7 @@ +mod test_simple_enum; mod test_simple_struct; + +fn pretty_codegen(ts: &proc_macro2::TokenStream) -> String { + let file = syn::parse_file(&ts.to_string()).unwrap(); + prettyplease::unparse(&file) +} diff --git a/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_enum__simple_enum-2.snap b/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_enum__simple_enum-2.snap new file mode 100644 index 0000000..c8e1264 --- /dev/null +++ b/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_enum__simple_enum-2.snap @@ -0,0 +1,20 @@ +--- +source: interactive-clap-derive/src/tests/test_simple_enum.rs +expression: pretty_codegen(&to_cli_args_codegen) +--- +impl interactive_clap::ToCliArgs for Mode { + fn to_cli_args(&self) -> std::collections::VecDeque { + match self { + Self::Network => { + let mut args = std::collections::VecDeque::new(); + args.push_front("network".to_owned()); + args + } + Self::Offline => { + let mut args = std::collections::VecDeque::new(); + args.push_front("offline".to_owned()); + args + } + } + } +} diff --git a/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_enum__simple_enum.snap b/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_enum__simple_enum.snap new file mode 100644 index 0000000..c95244a --- /dev/null +++ b/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_enum__simple_enum.snap @@ -0,0 +1,81 @@ +--- +source: interactive-clap-derive/src/tests/test_simple_enum.rs +expression: pretty_codegen(&interactive_clap_codegen) +--- +#[derive(Debug, Clone, clap::Parser, interactive_clap::ToCliArgs)] +pub enum CliMode { + /// Prepare and, optionally, submit a new transaction with online mode + Network, + /// Prepare and, optionally, submit a new transaction with offline mode + Offline, +} +impl interactive_clap::ToCli for Mode { + type CliVariant = CliMode; +} +pub type InteractiveClapContextScopeForMode = ModeDiscriminants; +impl interactive_clap::ToInteractiveClapContextScope for Mode { + type InteractiveClapContextScope = InteractiveClapContextScopeForMode; +} +impl From for CliMode { + fn from(command: Mode) -> Self { + match command { + Mode::Network => Self::Network, + Mode::Offline => Self::Offline, + } + } +} +impl interactive_clap::FromCli for Mode { + type FromCliContext = (); + type FromCliError = color_eyre::eyre::Error; + fn from_cli( + mut optional_clap_variant: Option<::CliVariant>, + context: Self::FromCliContext, + ) -> interactive_clap::ResultFromCli< + ::CliVariant, + Self::FromCliError, + > + where + Self: Sized + interactive_clap::ToCli, + { + loop { + return match optional_clap_variant { + Some(CliMode::Network) => { + interactive_clap::ResultFromCli::Ok(CliMode::Network) + } + Some(CliMode::Offline) => { + interactive_clap::ResultFromCli::Ok(CliMode::Offline) + } + None => { + match Self::choose_variant(context.clone()) { + interactive_clap::ResultFromCli::Ok(cli_args) => { + optional_clap_variant = Some(cli_args); + continue; + } + result => return result, + } + } + }; + } + } +} +impl Mode { + pub fn choose_variant( + context: (), + ) -> interactive_clap::ResultFromCli< + ::CliVariant, + ::FromCliError, + > {} + pub fn try_parse() -> Result { + ::try_parse() + } + pub fn parse() -> CliMode { + ::parse() + } + pub fn try_parse_from(itr: I) -> Result + where + I: ::std::iter::IntoIterator, + T: ::std::convert::Into<::std::ffi::OsString> + ::std::clone::Clone, + { + ::try_parse_from(itr) + } +} diff --git a/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_enum__simple_enum_with_strum_discriminants-2.snap b/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_enum__simple_enum_with_strum_discriminants-2.snap new file mode 100644 index 0000000..c8e1264 --- /dev/null +++ b/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_enum__simple_enum_with_strum_discriminants-2.snap @@ -0,0 +1,20 @@ +--- +source: interactive-clap-derive/src/tests/test_simple_enum.rs +expression: pretty_codegen(&to_cli_args_codegen) +--- +impl interactive_clap::ToCliArgs for Mode { + fn to_cli_args(&self) -> std::collections::VecDeque { + match self { + Self::Network => { + let mut args = std::collections::VecDeque::new(); + args.push_front("network".to_owned()); + args + } + Self::Offline => { + let mut args = std::collections::VecDeque::new(); + args.push_front("offline".to_owned()); + args + } + } + } +} diff --git a/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_enum__simple_enum_with_strum_discriminants.snap b/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_enum__simple_enum_with_strum_discriminants.snap new file mode 100644 index 0000000..c95244a --- /dev/null +++ b/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_enum__simple_enum_with_strum_discriminants.snap @@ -0,0 +1,81 @@ +--- +source: interactive-clap-derive/src/tests/test_simple_enum.rs +expression: pretty_codegen(&interactive_clap_codegen) +--- +#[derive(Debug, Clone, clap::Parser, interactive_clap::ToCliArgs)] +pub enum CliMode { + /// Prepare and, optionally, submit a new transaction with online mode + Network, + /// Prepare and, optionally, submit a new transaction with offline mode + Offline, +} +impl interactive_clap::ToCli for Mode { + type CliVariant = CliMode; +} +pub type InteractiveClapContextScopeForMode = ModeDiscriminants; +impl interactive_clap::ToInteractiveClapContextScope for Mode { + type InteractiveClapContextScope = InteractiveClapContextScopeForMode; +} +impl From for CliMode { + fn from(command: Mode) -> Self { + match command { + Mode::Network => Self::Network, + Mode::Offline => Self::Offline, + } + } +} +impl interactive_clap::FromCli for Mode { + type FromCliContext = (); + type FromCliError = color_eyre::eyre::Error; + fn from_cli( + mut optional_clap_variant: Option<::CliVariant>, + context: Self::FromCliContext, + ) -> interactive_clap::ResultFromCli< + ::CliVariant, + Self::FromCliError, + > + where + Self: Sized + interactive_clap::ToCli, + { + loop { + return match optional_clap_variant { + Some(CliMode::Network) => { + interactive_clap::ResultFromCli::Ok(CliMode::Network) + } + Some(CliMode::Offline) => { + interactive_clap::ResultFromCli::Ok(CliMode::Offline) + } + None => { + match Self::choose_variant(context.clone()) { + interactive_clap::ResultFromCli::Ok(cli_args) => { + optional_clap_variant = Some(cli_args); + continue; + } + result => return result, + } + } + }; + } + } +} +impl Mode { + pub fn choose_variant( + context: (), + ) -> interactive_clap::ResultFromCli< + ::CliVariant, + ::FromCliError, + > {} + pub fn try_parse() -> Result { + ::try_parse() + } + pub fn parse() -> CliMode { + ::parse() + } + pub fn try_parse_from(itr: I) -> Result + where + I: ::std::iter::IntoIterator, + T: ::std::convert::Into<::std::ffi::OsString> + ::std::clone::Clone, + { + ::try_parse_from(itr) + } +} diff --git a/interactive-clap-derive/src/tests/test_simple_enum.rs b/interactive-clap-derive/src/tests/test_simple_enum.rs new file mode 100644 index 0000000..4db17f9 --- /dev/null +++ b/interactive-clap-derive/src/tests/test_simple_enum.rs @@ -0,0 +1,41 @@ +use super::pretty_codegen; + +#[test] +fn test_simple_enum() { + let input = syn::parse_quote! { + pub enum Mode { + /// Prepare and, optionally, submit a new transaction with online mode + Network, + /// Prepare and, optionally, submit a new transaction with offline mode + Offline, + } + }; + + let interactive_clap_codegen = crate::derives::interactive_clap::impl_interactive_clap(&input); + insta::assert_snapshot!(pretty_codegen(&interactive_clap_codegen)); + + let to_cli_args_codegen = crate::derives::to_cli_args::impl_to_cli_args(&input); + insta::assert_snapshot!(pretty_codegen(&to_cli_args_codegen)); +} + +#[test] +fn test_simple_enum_with_strum_discriminants() { + let input = syn::parse_quote! { + 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, + } + }; + + let interactive_clap_codegen = crate::derives::interactive_clap::impl_interactive_clap(&input); + insta::assert_snapshot!(pretty_codegen(&interactive_clap_codegen)); + + let to_cli_args_codegen = crate::derives::to_cli_args::impl_to_cli_args(&input); + insta::assert_snapshot!(pretty_codegen(&to_cli_args_codegen)); +} diff --git a/interactive-clap-derive/src/tests/test_simple_struct.rs b/interactive-clap-derive/src/tests/test_simple_struct.rs index d9bdb0f..16b3c66 100644 --- a/interactive-clap-derive/src/tests/test_simple_struct.rs +++ b/interactive-clap-derive/src/tests/test_simple_struct.rs @@ -1,7 +1,4 @@ -fn pretty_codegen(ts: &proc_macro2::TokenStream) -> String { - let file = syn::parse_file(&ts.to_string()).unwrap(); - prettyplease::unparse(&file) -} +use super::pretty_codegen; #[test] fn test_simple_struct() { From 15bc985cd4342d4a92e1ce1c5c1174093c8abade Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dj8yf0=CE=BCl?= Date: Tue, 7 Jan 2025 22:37:29 +0200 Subject: [PATCH 08/57] test: add `strum_discriminants(strum` attributes to enum test --- .../methods/choose_variant.rs | 12 ++++++-- ..._simple_enum_with_strum_discriminants.snap | 29 ++++++++++++++++++- .../src/tests/test_simple_enum.rs | 1 + 3 files changed, 39 insertions(+), 3 deletions(-) diff --git a/interactive-clap-derive/src/derives/interactive_clap/methods/choose_variant.rs b/interactive-clap-derive/src/derives/interactive_clap/methods/choose_variant.rs index 4c9635f..acd34c7 100644 --- a/interactive-clap-derive/src/derives/interactive_clap/methods/choose_variant.rs +++ b/interactive-clap-derive/src/derives/interactive_clap/methods/choose_variant.rs @@ -9,6 +9,7 @@ pub fn fn_choose_variant( ast: &syn::DeriveInput, variants: &syn::punctuated::Punctuated, ) -> proc_macro2::TokenStream { + dbg_cond!("entered `fn_choose_variant`"); let name = &ast.ident; let interactive_clap_attrs_context = super::interactive_clap_attrs_context::InteractiveClapAttrsContext::new(ast); @@ -19,7 +20,9 @@ pub fn fn_choose_variant( let mut ast_attrs: Vec<&str> = std::vec::Vec::new(); if !ast.attrs.is_empty() { - for attr in ast.attrs.clone() { + #[allow(clippy::unused_enumerate_index)] + for (_index, attr) in ast.attrs.clone().into_iter().enumerate() { + dbg_cond!(_index, &attr); if attr.path.is_ident("interactive_clap") { for attr_token in attr.tokens.clone() { if let proc_macro2::TokenTree::Group(group) = attr_token { @@ -29,16 +32,21 @@ pub fn fn_choose_variant( } } }; + dbg_cond!(attr.path.is_ident("strum_discriminants")); if attr.path.is_ident("strum_discriminants") { for attr_token in attr.tokens.clone() { if let proc_macro2::TokenTree::Group(group) = attr_token { - if &group.stream().to_string() == "derive(EnumMessage, EnumIter)" { + let group_stream_no_whitespace = + group.stream().to_string().replace(" ", ""); + dbg_cond!(&group_stream_no_whitespace); + if &group_stream_no_whitespace == "derive(EnumMessage,EnumIter)" { ast_attrs.push("strum_discriminants"); }; } } }; } + dbg_cond!(&ast_attrs); if ast_attrs.contains(&"strum_discriminants") { let doc_attrs = ast .attrs diff --git a/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_enum__simple_enum_with_strum_discriminants.snap b/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_enum__simple_enum_with_strum_discriminants.snap index c95244a..ca5e4b1 100644 --- a/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_enum__simple_enum_with_strum_discriminants.snap +++ b/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_enum__simple_enum_with_strum_discriminants.snap @@ -64,7 +64,34 @@ impl Mode { ) -> interactive_clap::ResultFromCli< ::CliVariant, ::FromCliError, - > {} + > { + use interactive_clap::SelectVariantOrBack; + use inquire::Select; + use strum::{EnumMessage, IntoEnumIterator}; + let selected_variant = Select::new( + concat!().trim(), + ModeDiscriminants::iter() + .map(SelectVariantOrBack::Variant) + .chain([SelectVariantOrBack::Back]) + .collect(), + ) + .prompt(); + match selected_variant { + Ok(SelectVariantOrBack::Variant(variant)) => { + let cli_args = match variant { + ModeDiscriminants::Network => CliMode::Network, + ModeDiscriminants::Offline => CliMode::Offline, + }; + return interactive_clap::ResultFromCli::Ok(cli_args); + } + Ok(SelectVariantOrBack::Back) => return interactive_clap::ResultFromCli::Back, + Err( + inquire::error::InquireError::OperationCanceled + | inquire::error::InquireError::OperationInterrupted, + ) => return interactive_clap::ResultFromCli::Cancel(None), + Err(err) => return interactive_clap::ResultFromCli::Err(None, err.into()), + } + } pub fn try_parse() -> Result { ::try_parse() } diff --git a/interactive-clap-derive/src/tests/test_simple_enum.rs b/interactive-clap-derive/src/tests/test_simple_enum.rs index 4db17f9..97f3f12 100644 --- a/interactive-clap-derive/src/tests/test_simple_enum.rs +++ b/interactive-clap-derive/src/tests/test_simple_enum.rs @@ -21,6 +21,7 @@ fn test_simple_enum() { #[test] fn test_simple_enum_with_strum_discriminants() { let input = syn::parse_quote! { + #[strum_discriminants(derive(EnumMessage, EnumIter))] pub enum Mode { /// Prepare and, optionally, submit a new transaction with online mode #[strum_discriminants(strum(message = "Yes, I keep it simple"))] From 9de408236ecc81be78ad1f52e42242bb4e4c0cf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dj8yf0=CE=BCl?= Date: Tue, 7 Jan 2025 22:42:15 +0200 Subject: [PATCH 09/57] test: add doc comment over enum for test with strum-messages --- ..._test_simple_enum__simple_enum_with_strum_discriminants.snap | 2 +- interactive-clap-derive/src/tests/test_simple_enum.rs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_enum__simple_enum_with_strum_discriminants.snap b/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_enum__simple_enum_with_strum_discriminants.snap index ca5e4b1..330fca2 100644 --- a/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_enum__simple_enum_with_strum_discriminants.snap +++ b/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_enum__simple_enum_with_strum_discriminants.snap @@ -69,7 +69,7 @@ impl Mode { use inquire::Select; use strum::{EnumMessage, IntoEnumIterator}; let selected_variant = Select::new( - concat!().trim(), + concat!(r" A little beautiful comment about our choice",).trim(), ModeDiscriminants::iter() .map(SelectVariantOrBack::Variant) .chain([SelectVariantOrBack::Back]) diff --git a/interactive-clap-derive/src/tests/test_simple_enum.rs b/interactive-clap-derive/src/tests/test_simple_enum.rs index 97f3f12..c2f5263 100644 --- a/interactive-clap-derive/src/tests/test_simple_enum.rs +++ b/interactive-clap-derive/src/tests/test_simple_enum.rs @@ -22,6 +22,7 @@ fn test_simple_enum() { fn test_simple_enum_with_strum_discriminants() { let input = syn::parse_quote! { #[strum_discriminants(derive(EnumMessage, EnumIter))] + /// A little beautiful comment about our choice pub enum Mode { /// Prepare and, optionally, submit a new transaction with online mode #[strum_discriminants(strum(message = "Yes, I keep it simple"))] From e28aee71f14047e9b156d329a5d3440ad3240fe4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dj8yf0=CE=BCl?= Date: Tue, 7 Jan 2025 23:13:37 +0200 Subject: [PATCH 10/57] doc: check nice publish with private items --- interactive-clap-derive/Cargo.toml | 4 ++++ interactive-clap-derive/src/lib.rs | 2 +- src/lib.rs | 7 ++++++- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/interactive-clap-derive/Cargo.toml b/interactive-clap-derive/Cargo.toml index d647ffc..73981ce 100644 --- a/interactive-clap-derive/Cargo.toml +++ b/interactive-clap-derive/Cargo.toml @@ -23,6 +23,10 @@ prettyplease = "0.1" insta = "1" syn = { version = "1", features = ["full"] } +[package.metadata.docs.rs] +# Additional `RUSTDOCFLAGS` to set (default: []) +rustdoc-args = ["--document-private-items"] + [features] default = [] introspect = [] diff --git a/interactive-clap-derive/src/lib.rs b/interactive-clap-derive/src/lib.rs index fb6bf9d..80936d1 100644 --- a/interactive-clap-derive/src/lib.rs +++ b/interactive-clap-derive/src/lib.rs @@ -21,7 +21,7 @@ pub(crate) const LONG_VEC_MUTLIPLE_OPT: &str = "long_vec_multiple_opt"; /// `#[interactive_clap(...)]` attribute which translates 1-to-1 into /// `#[clap(verbatim_doc_comment)]` -/// More info on https://docs.rs/clap/4.5.23/clap/_derive/index.html#command-attributes +/// More info on pub(crate) const VERBATIM_DOC_COMMENT: &str = "verbatim_doc_comment"; #[proc_macro_derive(InteractiveClap, attributes(interactive_clap))] diff --git a/src/lib.rs b/src/lib.rs index e1b1999..1cd0c62 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,11 +1,16 @@ //! The Interactive-clap library is an add-on for the Command Line Argument -//! Parser (https://crates.io/crates/clap). Interactive-clap allows you to parse +//! Parser . Interactive-clap allows you to parse //! command line options. The peculiarity of this macro is that in the absence //! of command line parameters, the interactive mode of entering these data by //! the user is activated. pub use interactive_clap_derive::{InteractiveClap, ToCliArgs}; +/// Associated type [`Self::CliVariant`] is defined during derive of +/// [`macro@crate::InteractiveClap`] +/// +/// This type has derive of [`clap::Parser`](https://docs.rs/clap/4.5.24/clap/trait.Parser.html), which allows to parse +/// initial input on cli, which may be incomplete pub trait ToCli { type CliVariant; } From b831cbca45cd9fd3342b87b948e188ba22d4a477 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dj8yf0=CE=BCl?= Date: Tue, 7 Jan 2025 23:53:36 +0200 Subject: [PATCH 11/57] doc: add doc-string about dbg_cond! macro --- interactive-clap-derive/src/debug.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/interactive-clap-derive/src/debug.rs b/interactive-clap-derive/src/debug.rs index e89f1ea..73cc539 100644 --- a/interactive-clap-derive/src/debug.rs +++ b/interactive-clap-derive/src/debug.rs @@ -5,6 +5,16 @@ macro_rules! dbg_cond { }; } +/// this macro under `introspect` feature can be used to debug how derive proc macros +/// ([`crate::InteractiveClap`], [`crate::ToCliArgs`]) +/// work similar to the following: +/// +/// ```bash +/// # interactive-clap-derive folder +/// cargo test test_doc_comments_propagate --features introspect -- --nocapture +/// # from repo root +/// cargo run --example struct_with_subargs --features interactive-clap-derive/introspect +/// ``` #[cfg(not(feature = "introspect"))] macro_rules! dbg_cond { ($($val:expr),*) => { From 1344f022e329208945a0fdacfdb0d8cefa1d9c6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dj8yf0=CE=BCl?= Date: Thu, 9 Jan 2025 13:27:39 +0200 Subject: [PATCH 12/57] ci: resolve #34 --- .github/workflows/tests.yml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index f60c048..e6b25f9 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -20,3 +20,23 @@ jobs: profile: minimal - name: Tests run: cargo test --workspace + # there're sometimes warnings, which signal, that the generated doc + # won't look as expected, when rendered, and sometimes errors, which will prevent doc from being + # generated at release time altogether. + cargo-doc: + runs-on: ubuntu-20.04 + + steps: + - uses: actions/checkout@v4 + - name: Install Toolchain + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + default: true + - name: run cargo doc + env: + RUSTDOCFLAGS: -D warnings + run: | + cargo doc -p interactive-clap + cargo doc -p interactive-clap-derive --document-private-items From 222ee83c19a20f60b3643804871601db696cef3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dj8yf0=CE=BCl?= Date: Thu, 9 Jan 2025 14:25:30 +0200 Subject: [PATCH 13/57] chore: add rust-toolchain.toml (autoinstalls rust-analyzer for any contributor) --- rust-toolchain.toml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 rust-toolchain.toml diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..e5c329d --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,6 @@ +[toolchain] +# This specifies the version of Rust we use to build. +# Individual crates in the workspace may support a lower version, as indicated by `rust-version` field in each crate's `Cargo.toml`. +# The version specified below, should be at least as high as the maximum `rust-version` within the workspace. +channel = "stable" +components = ["rustfmt", "clippy", "rust-analyzer"] From 709e4bc916fb88cc9983fa89e1b19107196f6ea1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dj8yf0=CE=BCl?= Date: Thu, 9 Jan 2025 15:55:10 +0200 Subject: [PATCH 14/57] chore: extract logic to derives::interactive_clap::structs::cli_variant_of_to_cli_trait module --- interactive-clap-derive/src/debug.rs | 3 +- .../src/derives/interactive_clap/mod.rs | 284 ++++++++++-------- interactive-clap-derive/src/derives/mod.rs | 1 + 3 files changed, 166 insertions(+), 122 deletions(-) diff --git a/interactive-clap-derive/src/debug.rs b/interactive-clap-derive/src/debug.rs index 73cc539..28bc61f 100644 --- a/interactive-clap-derive/src/debug.rs +++ b/interactive-clap-derive/src/debug.rs @@ -6,8 +6,7 @@ macro_rules! dbg_cond { } /// this macro under `introspect` feature can be used to debug how derive proc macros -/// ([`crate::InteractiveClap`], [`crate::ToCliArgs`]) -/// work similar to the following: +/// ([`crate::InteractiveClap`], [`crate::ToCliArgs`]) work /// /// ```bash /// # interactive-clap-derive folder diff --git a/interactive-clap-derive/src/derives/interactive_clap/mod.rs b/interactive-clap-derive/src/derives/interactive_clap/mod.rs index a83bd81..aa84f73 100644 --- a/interactive-clap-derive/src/derives/interactive_clap/mod.rs +++ b/interactive-clap-derive/src/derives/interactive_clap/mod.rs @@ -6,8 +6,6 @@ use proc_macro_error::abort_call_site; use quote::{quote, ToTokens}; use syn; -use crate::{LONG_VEC_MUTLIPLE_OPT, VERBATIM_DOC_COMMENT}; - pub(crate) mod methods; pub fn impl_interactive_clap(ast: &syn::DeriveInput) -> TokenStream { @@ -17,125 +15,9 @@ pub fn impl_interactive_clap(ast: &syn::DeriveInput) -> TokenStream { match &ast.data { syn::Data::Struct(data_struct) => { let fields = data_struct.fields.clone(); - let mut ident_skip_field_vec: Vec = Vec::new(); - let cli_fields = fields - .iter() - .map(|field| { - let ident_field = field.ident.clone().expect("this field does not exist"); - let ty = &field.ty; - let cli_ty = self::methods::cli_field_type::cli_field_type(ty); - let mut cli_field = quote! { - pub #ident_field: #cli_ty - }; - if field.attrs.is_empty() { - return cli_field; - }; - let mut clap_attr_vec: Vec = Vec::new(); - let mut cfg_attr_vec: Vec = Vec::new(); - let mut doc_attr_vec: Vec = Vec::new(); - #[allow(clippy::unused_enumerate_index)] - for attr in &field.attrs { - dbg_cond!(attr.path.to_token_stream().into_iter().collect::>()); - if attr.path.is_ident("interactive_clap") || attr.path.is_ident("cfg") { - for (_index, attr_token) in attr.tokens.clone().into_iter().enumerate() { - dbg_cond!(_index, &attr_token); - match attr_token { - proc_macro2::TokenTree::Group(group) => { - let group_string = group.stream().to_string(); - if group_string.contains("subcommand") - || group_string.contains("value_enum") - || group_string.contains("long") - || (group_string == *"skip") - || (group_string == *"flatten") - || (group_string == VERBATIM_DOC_COMMENT) - { - if group_string != LONG_VEC_MUTLIPLE_OPT { - clap_attr_vec.push(group.stream()) - } - } 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}); - 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(), - ); - cli_field = quote! { - pub #ident_field: Option<#enum_for_clap_named_arg> - } - }; - 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!() - }; - if group.stream().to_string() == LONG_VEC_MUTLIPLE_OPT { - if !cli_field_type::starts_with_vec(ty) { - abort_call_site!("`{}` attribute is only supposed to be used with `Vec` types", LONG_VEC_MUTLIPLE_OPT) - } - // implies `#[interactive_clap(long)]` - clap_attr_vec.push(quote! { long }); - // type goes into output unchanged, otherwise it - // prevents clap deriving correctly its `remove_many` thing - cli_field = quote! { - pub #ident_field: #ty - }; - } - } - _ => { - abort_call_site!("Only option `TokenTree::Group` is needed") - } - } - } - } - if attr.path.is_ident("doc") { - doc_attr_vec.push(attr.into_token_stream()) - } - } - if cli_field.is_empty() { - return cli_field; - }; - let cfg_attrs = cfg_attr_vec.iter(); - if !clap_attr_vec.is_empty() { - let clap_attrs = clap_attr_vec.iter(); - quote! { - #(#cfg_attrs)* - #(#doc_attr_vec)* - #[clap(#(#clap_attrs, )*)] - #cli_field - } - } else { - quote! { - #(#cfg_attrs)* - #(#doc_attr_vec)* - #cli_field - } - } - }) - .filter(|token_stream| !token_stream.is_empty()) - .collect::>(); + let (cli_fields, ident_skip_field_vec) = + structs::cli_variant_of_to_cli_trait::fields(&fields, name); let for_cli_fields = fields .iter() @@ -383,6 +265,166 @@ pub fn impl_interactive_clap(ast: &syn::DeriveInput) -> TokenStream { } } +/// This module describes [`crate::InteractiveClap`] derive logic in case when [`syn::DeriveInput`] +/// is a struct +mod structs { + /// This module describes the derive logic of `#cli_name` struct used as `CliVariant` in + /// implementation of `interactive_clap::ToCli`, which happens during derive of [`crate::InteractiveClap`] for `#name` struct. + /// + /// ```ignore + /// #[derive(Debug, Default, Clone, clap::Parser, interactive_clap::ToCliArgs)] + /// #[clap(author, version, about, long_about = None)] + /// pub struct #cli_name { + /// #( #cli_fields, )* + /// } + /// + /// impl interactive_clap::ToCli for #name { + /// type CliVariant = #cli_name; + /// } + /// ``` + /// + /// Where `interactive_clap::ToCli` is: + /// + /// ```ignore + /// pub trait ToCli { + /// type CliVariant; + /// } + /// ``` + pub mod cli_variant_of_to_cli_trait { + use crate::LONG_VEC_MUTLIPLE_OPT; + use crate::VERBATIM_DOC_COMMENT; + use proc_macro2::{Span, TokenStream}; + use proc_macro_error::abort_call_site; + use quote::{quote, ToTokens}; + + pub fn fields( + fields: &syn::Fields, + name: &syn::Ident, + ) -> (Vec, Vec) { + let mut ident_skip_field_vec: Vec = Vec::new(); + + let fields = fields + .iter() + .map(|field| { + let ident_field = field.ident.clone().expect("this field does not exist"); + let ty = &field.ty; + let cli_ty = super::super::methods::cli_field_type::cli_field_type(ty); + let mut cli_field = quote! { + pub #ident_field: #cli_ty + }; + if field.attrs.is_empty() { + return cli_field; + }; + let mut clap_attr_vec: Vec = Vec::new(); + let mut cfg_attr_vec: Vec = Vec::new(); + let mut doc_attr_vec: Vec = Vec::new(); + #[allow(clippy::unused_enumerate_index)] + for attr in &field.attrs { + dbg_cond!(attr.path.to_token_stream().into_iter().collect::>()); + if attr.path.is_ident("interactive_clap") || attr.path.is_ident("cfg") { + for (_index, attr_token) in attr.tokens.clone().into_iter().enumerate() { + dbg_cond!(_index, &attr_token); + match attr_token { + proc_macro2::TokenTree::Group(group) => { + let group_string = group.stream().to_string(); + if group_string.contains("subcommand") + || group_string.contains("value_enum") + || group_string.contains("long") + || (group_string == *"skip") + || (group_string == *"flatten") + || (group_string == VERBATIM_DOC_COMMENT) + { + if group_string != LONG_VEC_MUTLIPLE_OPT { + clap_attr_vec.push(group.stream()) + } + } 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}); + 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(), + ); + cli_field = quote! { + pub #ident_field: Option<#enum_for_clap_named_arg> + } + }; + 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!() + }; + if group.stream().to_string() == LONG_VEC_MUTLIPLE_OPT { + if !super::super::cli_field_type::starts_with_vec(ty) { + abort_call_site!("`{}` attribute is only supposed to be used with `Vec` types", LONG_VEC_MUTLIPLE_OPT) + } + // implies `#[interactive_clap(long)]` + clap_attr_vec.push(quote! { long }); + // type goes into output unchanged, otherwise it + // prevents clap deriving correctly its `remove_many` thing + cli_field = quote! { + pub #ident_field: #ty + }; + } + } + _ => { + abort_call_site!("Only option `TokenTree::Group` is needed") + } + } + } + } + if attr.path.is_ident("doc") { + doc_attr_vec.push(attr.into_token_stream()) + } + } + if cli_field.is_empty() { + return cli_field; + }; + let cfg_attrs = cfg_attr_vec.iter(); + if !clap_attr_vec.is_empty() { + let clap_attrs = clap_attr_vec.iter(); + quote! { + #(#cfg_attrs)* + #(#doc_attr_vec)* + #[clap(#(#clap_attrs, )*)] + #cli_field + } + } else { + quote! { + #(#cfg_attrs)* + #(#doc_attr_vec)* + #cli_field + } + } + }) + .filter(|token_stream| !token_stream.is_empty()) + .collect::>(); + (fields, ident_skip_field_vec) + } + } +} + fn context_scope_for_struct( name: &syn::Ident, context_scope_fields: Vec, @@ -429,6 +471,8 @@ fn context_scope_for_enum(name: &syn::Ident) -> proc_macro2::TokenStream { } } +use crate::LONG_VEC_MUTLIPLE_OPT; + fn for_cli_field( field: &syn::Field, ident_skip_field_vec: &[syn::Ident], diff --git a/interactive-clap-derive/src/derives/mod.rs b/interactive-clap-derive/src/derives/mod.rs index ad346a0..f427573 100644 --- a/interactive-clap-derive/src/derives/mod.rs +++ b/interactive-clap-derive/src/derives/mod.rs @@ -1,2 +1,3 @@ +/// This module describes [`crate::InteractiveClap`] derive logic pub mod interactive_clap; pub mod to_cli_args; From 13e6f2a4dd8655214b73b8e9e7e8abd425ebcca9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dj8yf0=CE=BCl?= Date: Thu, 9 Jan 2025 16:35:43 +0200 Subject: [PATCH 15/57] chore: move derives::interactive_clap::methods::cli_field_type module to derives::interactive_clap::structs::cli_variant_of_to_cli_trait::field --- .../src/derives/interactive_clap/methods/mod.rs | 1 - .../src/derives/interactive_clap/mod.rs | 9 ++++++--- .../cli_variant_of_to_cli_trait/field/mod.rs} | 13 +------------ .../methods/interactive_clap_attrs_cli_field.rs | 6 +++--- interactive-clap-derive/src/helpers/mod.rs | 11 +++++++++++ 5 files changed, 21 insertions(+), 19 deletions(-) rename interactive-clap-derive/src/derives/interactive_clap/{methods/cli_field_type.rs => structs/cli_variant_of_to_cli_trait/field/mod.rs} (79%) 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 a1d5463..5c936e4 100644 --- a/interactive-clap-derive/src/derives/interactive_clap/methods/mod.rs +++ b/interactive-clap-derive/src/derives/interactive_clap/methods/mod.rs @@ -1,5 +1,4 @@ pub mod choose_variant; -pub mod cli_field_type; pub mod fields_with_skip_default_input_arg; pub mod fields_with_subargs; pub mod fields_with_subcommand; diff --git a/interactive-clap-derive/src/derives/interactive_clap/mod.rs b/interactive-clap-derive/src/derives/interactive_clap/mod.rs index aa84f73..8e05e8b 100644 --- a/interactive-clap-derive/src/derives/interactive_clap/mod.rs +++ b/interactive-clap-derive/src/derives/interactive_clap/mod.rs @@ -1,6 +1,5 @@ extern crate proc_macro; -use methods::cli_field_type; use proc_macro2::{Span, TokenStream}; use proc_macro_error::abort_call_site; use quote::{quote, ToTokens}; @@ -297,6 +296,10 @@ mod structs { use proc_macro_error::abort_call_site; use quote::{quote, ToTokens}; + /// this module describes derive of individual field of `#cli_name` struct + /// based on transformation of input field from `#name` struct + mod field; + pub fn fields( fields: &syn::Fields, name: &syn::Ident, @@ -308,7 +311,7 @@ mod structs { .map(|field| { let ident_field = field.ident.clone().expect("this field does not exist"); let ty = &field.ty; - let cli_ty = super::super::methods::cli_field_type::cli_field_type(ty); + let cli_ty = self::field::field_type(ty); let mut cli_field = quote! { pub #ident_field: #cli_ty }; @@ -376,7 +379,7 @@ mod structs { cli_field = quote!() }; if group.stream().to_string() == LONG_VEC_MUTLIPLE_OPT { - if !super::super::cli_field_type::starts_with_vec(ty) { + if !crate::helpers::type_starts_with_vec(ty) { abort_call_site!("`{}` attribute is only supposed to be used with `Vec` types", LONG_VEC_MUTLIPLE_OPT) } // implies `#[interactive_clap(long)]` diff --git a/interactive-clap-derive/src/derives/interactive_clap/methods/cli_field_type.rs b/interactive-clap-derive/src/derives/interactive_clap/structs/cli_variant_of_to_cli_trait/field/mod.rs similarity index 79% rename from interactive-clap-derive/src/derives/interactive_clap/methods/cli_field_type.rs rename to interactive-clap-derive/src/derives/interactive_clap/structs/cli_variant_of_to_cli_trait/field/mod.rs index debe791..ad94f9d 100644 --- a/interactive-clap-derive/src/derives/interactive_clap/methods/cli_field_type.rs +++ b/interactive-clap-derive/src/derives/interactive_clap/structs/cli_variant_of_to_cli_trait/field/mod.rs @@ -4,7 +4,7 @@ use proc_macro_error::abort_call_site; use quote::quote; use syn; -pub fn cli_field_type(ty: &syn::Type) -> proc_macro2::TokenStream { +pub fn field_type(ty: &syn::Type) -> proc_macro2::TokenStream { match &ty { syn::Type::Path(type_path) => match type_path.path.segments.first() { Some(path_segment) => { @@ -37,14 +37,3 @@ pub fn cli_field_type(ty: &syn::Type) -> proc_macro2::TokenStream { _ => abort_call_site!("Only option `Type::Path` is needed"), } } - -pub fn starts_with_vec(ty: &syn::Type) -> bool { - if let syn::Type::Path(type_path) = ty { - if let Some(path_segment) = type_path.path.segments.first() { - if path_segment.ident == "Vec" { - return true; - } - } - } - false -} 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 4a00cbb..6f93dcc 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 @@ -5,8 +5,6 @@ use proc_macro_error::abort_call_site; use quote::{quote, ToTokens}; use syn; -use crate::derives::interactive_clap::methods::cli_field_type; - #[derive(Debug, Clone)] pub enum InteractiveClapAttrsCliField { RegularField(proc_macro2::TokenStream), @@ -83,7 +81,9 @@ impl InteractiveClapAttrsCliField { args.push_front(std::concat!("--", #ident_field_to_kebab_case).to_string()); } }; - if cli_field_type::starts_with_vec(&field.ty) { + if crate::helpers::type_starts_with_vec( + &field.ty, + ) { unnamed_args = quote! { for arg in self.#ident_field.iter().rev() { args.push_front(arg.to_string()); diff --git a/interactive-clap-derive/src/helpers/mod.rs b/interactive-clap-derive/src/helpers/mod.rs index 970a12f..201cdcb 100644 --- a/interactive-clap-derive/src/helpers/mod.rs +++ b/interactive-clap-derive/src/helpers/mod.rs @@ -1,2 +1,13 @@ pub mod snake_case_to_camel_case; pub mod to_kebab_case; + +pub fn type_starts_with_vec(ty: &syn::Type) -> bool { + if let syn::Type::Path(type_path) = ty { + if let Some(path_segment) = type_path.path.segments.first() { + if path_segment.ident == "Vec" { + return true; + } + } + } + false +} From 63dea02844a7f8b2c998f0778307b11bdba07459 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dj8yf0=CE=BCl?= Date: Thu, 9 Jan 2025 17:43:08 +0200 Subject: [PATCH 16/57] chore: change `field_type` return type from TokenStream -> syn::Type --- .../structs/cli_variant_of_to_cli_trait/field/mod.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/interactive-clap-derive/src/derives/interactive_clap/structs/cli_variant_of_to_cli_trait/field/mod.rs b/interactive-clap-derive/src/derives/interactive_clap/structs/cli_variant_of_to_cli_trait/field/mod.rs index ad94f9d..27cf470 100644 --- a/interactive-clap-derive/src/derives/interactive_clap/structs/cli_variant_of_to_cli_trait/field/mod.rs +++ b/interactive-clap-derive/src/derives/interactive_clap/structs/cli_variant_of_to_cli_trait/field/mod.rs @@ -4,8 +4,8 @@ use proc_macro_error::abort_call_site; use quote::quote; use syn; -pub fn field_type(ty: &syn::Type) -> proc_macro2::TokenStream { - match &ty { +pub fn field_type(ty: &syn::Type) -> syn::Type { + let token_stream = match &ty { syn::Type::Path(type_path) => match type_path.path.segments.first() { Some(path_segment) => { if path_segment.ident == "Option" { @@ -35,5 +35,6 @@ pub fn field_type(ty: &syn::Type) -> proc_macro2::TokenStream { _ => abort_call_site!("Only option `PathSegment` is needed"), }, _ => abort_call_site!("Only option `Type::Path` is needed"), - } + }; + syn::parse2(token_stream).unwrap() } From 18ae13e545fb4fa55b9d96e4d0df1d7e4e4febeb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dj8yf0=CE=BCl?= Date: Thu, 9 Jan 2025 20:58:02 +0200 Subject: [PATCH 17/57] chore: group together derive items, that belong to `structs::cli_variant_of_to_cli_trait` --- .../src/derives/interactive_clap/mod.rs | 44 ++++++- ...simple_struct__doc_comments_propagate.snap | 124 +++++++++--------- ...rive__tests__test_simple_struct__flag.snap | 66 +++++----- ...ts__test_simple_struct__simple_struct.snap | 110 ++++++++-------- ..._test_simple_struct__vec_multiple_opt.snap | 41 +++--- 5 files changed, 212 insertions(+), 173 deletions(-) diff --git a/interactive-clap-derive/src/derives/interactive_clap/mod.rs b/interactive-clap-derive/src/derives/interactive_clap/mod.rs index 8e05e8b..14111c4 100644 --- a/interactive-clap-derive/src/derives/interactive_clap/mod.rs +++ b/interactive-clap-derive/src/derives/interactive_clap/mod.rs @@ -93,13 +93,7 @@ pub fn impl_interactive_clap(ast: &syn::DeriveInput) -> TokenStream { type CliVariant = #cli_name; } - #context_scope_for_struct - - #fn_from_cli_for_struct - impl #name { - #(#vec_fn_input_arg)* - pub fn try_parse() -> Result<#cli_name, clap::Error> { <#cli_name as clap::Parser>::try_parse() } @@ -125,6 +119,14 @@ pub fn impl_interactive_clap(ast: &syn::DeriveInput) -> TokenStream { } } + impl #name { + #(#vec_fn_input_arg)* + } + + #context_scope_for_struct + + #fn_from_cli_for_struct + #clap_enum_for_named_arg } } @@ -289,6 +291,36 @@ mod structs { /// type CliVariant; /// } /// ``` + /// Additionally a [`clap::Parser`](https://docs.rs/clap/4.5.24/clap/trait.Parser.html) adapter + /// for `#name` and `From<#name> for #cli_name` conversion are defined: + /// + /// ```ignore + /// impl #name { + /// pub fn try_parse() -> Result<#cli_name, clap::Error> { + /// <#cli_name as clap::Parser>::try_parse() + /// } + /// + /// pub fn parse() -> #cli_name { + /// <#cli_name as clap::Parser>::parse() + /// } + /// + /// pub fn try_parse_from(itr: I) -> Result<#cli_name, clap::Error> + /// where + /// I: ::std::iter::IntoIterator, + /// T: ::std::convert::Into<::std::ffi::OsString> + ::std::clone::Clone, + /// { + /// <#cli_name as clap::Parser>::try_parse_from(itr) + /// } + /// } + /// + /// impl From<#name> for #cli_name { + /// fn from(args: #name) -> Self { + /// Self { + /// #( #for_cli_fields, )* + /// } + /// } + /// } + /// ``` pub mod cli_variant_of_to_cli_trait { use crate::LONG_VEC_MUTLIPLE_OPT; use crate::VERBATIM_DOC_COMMENT; diff --git a/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__doc_comments_propagate.snap b/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__doc_comments_propagate.snap index 784c525..d46ec0a 100644 --- a/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__doc_comments_propagate.snap +++ b/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__doc_comments_propagate.snap @@ -20,6 +20,69 @@ pub struct CliArgs { impl interactive_clap::ToCli for Args { type CliVariant = CliArgs; } +impl Args { + pub fn try_parse() -> Result { + ::try_parse() + } + pub fn parse() -> CliArgs { + ::parse() + } + pub fn try_parse_from(itr: I) -> Result + where + I: ::std::iter::IntoIterator, + T: ::std::convert::Into<::std::ffi::OsString> + ::std::clone::Clone, + { + ::try_parse_from(itr) + } +} +impl From for CliArgs { + fn from(args: Args) -> Self { + Self { + first_field: Some(args.first_field.into()), + second_field: Some(args.second_field.into()), + } + } +} +impl Args { + fn input_first_field(_context: &()) -> color_eyre::eyre::Result> { + match inquire::CustomType::new( + concat!( + r" short first field description", r"", + r" a longer paragraph, describing the usage and stuff with first field's", + r" awarenes of its possible applications", + ) + .trim(), + ) + .prompt() + { + Ok(value) => Ok(Some(value)), + Err( + inquire::error::InquireError::OperationCanceled + | inquire::error::InquireError::OperationInterrupted, + ) => Ok(None), + Err(err) => Err(err.into()), + } + } + fn input_second_field(_context: &()) -> color_eyre::eyre::Result> { + match inquire::CustomType::new( + concat!( + r" short second field description", r"", + r" a longer paragraph, describing the usage and stuff with second field's", + r" awareness of its possible applications", + ) + .trim(), + ) + .prompt() + { + Ok(value) => Ok(Some(value)), + Err( + inquire::error::InquireError::OperationCanceled + | inquire::error::InquireError::OperationInterrupted, + ) => Ok(None), + Err(err) => Err(err.into()), + } + } +} pub struct InteractiveClapContextScopeForArgs { pub first_field: u64, pub second_field: String, @@ -74,64 +137,3 @@ impl interactive_clap::FromCli for Args { interactive_clap::ResultFromCli::Ok(clap_variant) } } -impl Args { - fn input_first_field(_context: &()) -> color_eyre::eyre::Result> { - match inquire::CustomType::new( - concat!( - r" short first field description", r"", - r" a longer paragraph, describing the usage and stuff with first field's", - r" awarenes of its possible applications", - ) - .trim(), - ) - .prompt() - { - Ok(value) => Ok(Some(value)), - Err( - inquire::error::InquireError::OperationCanceled - | inquire::error::InquireError::OperationInterrupted, - ) => Ok(None), - Err(err) => Err(err.into()), - } - } - fn input_second_field(_context: &()) -> color_eyre::eyre::Result> { - match inquire::CustomType::new( - concat!( - r" short second field description", r"", - r" a longer paragraph, describing the usage and stuff with second field's", - r" awareness of its possible applications", - ) - .trim(), - ) - .prompt() - { - Ok(value) => Ok(Some(value)), - Err( - inquire::error::InquireError::OperationCanceled - | inquire::error::InquireError::OperationInterrupted, - ) => Ok(None), - Err(err) => Err(err.into()), - } - } - pub fn try_parse() -> Result { - ::try_parse() - } - pub fn parse() -> CliArgs { - ::parse() - } - pub fn try_parse_from(itr: I) -> Result - where - I: ::std::iter::IntoIterator, - T: ::std::convert::Into<::std::ffi::OsString> + ::std::clone::Clone, - { - ::try_parse_from(itr) - } -} -impl From for CliArgs { - fn from(args: Args) -> Self { - Self { - first_field: Some(args.first_field.into()), - second_field: Some(args.second_field.into()), - } - } -} diff --git a/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__flag.snap b/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__flag.snap index fae77ce..67cdbaa 100644 --- a/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__flag.snap +++ b/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__flag.snap @@ -12,6 +12,40 @@ pub struct CliArgs { impl interactive_clap::ToCli for Args { type CliVariant = CliArgs; } +impl Args { + pub fn try_parse() -> Result { + ::try_parse() + } + pub fn parse() -> CliArgs { + ::parse() + } + pub fn try_parse_from(itr: I) -> Result + where + I: ::std::iter::IntoIterator, + T: ::std::convert::Into<::std::ffi::OsString> + ::std::clone::Clone, + { + ::try_parse_from(itr) + } +} +impl From for CliArgs { + fn from(args: Args) -> Self { + Self { + offline: args.offline.into(), + } + } +} +impl Args { + fn input_offline(_context: &()) -> color_eyre::eyre::Result> { + match inquire::CustomType::new(concat!(r" Offline mode",).trim()).prompt() { + Ok(value) => Ok(Some(value)), + Err( + inquire::error::InquireError::OperationCanceled + | inquire::error::InquireError::OperationInterrupted, + ) => Ok(None), + Err(err) => Err(err.into()), + } + } +} pub struct InteractiveClapContextScopeForArgs { pub offline: bool, } @@ -39,35 +73,3 @@ impl interactive_clap::FromCli for Args { interactive_clap::ResultFromCli::Ok(clap_variant) } } -impl Args { - fn input_offline(_context: &()) -> color_eyre::eyre::Result> { - match inquire::CustomType::new(concat!(r" Offline mode",).trim()).prompt() { - Ok(value) => Ok(Some(value)), - Err( - inquire::error::InquireError::OperationCanceled - | inquire::error::InquireError::OperationInterrupted, - ) => Ok(None), - Err(err) => Err(err.into()), - } - } - pub fn try_parse() -> Result { - ::try_parse() - } - pub fn parse() -> CliArgs { - ::parse() - } - pub fn try_parse_from(itr: I) -> Result - where - I: ::std::iter::IntoIterator, - T: ::std::convert::Into<::std::ffi::OsString> + ::std::clone::Clone, - { - ::try_parse_from(itr) - } -} -impl From for CliArgs { - fn from(args: Args) -> Self { - Self { - offline: args.offline.into(), - } - } -} diff --git a/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__simple_struct.snap b/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__simple_struct.snap index 8b00ce9..4d83e4e 100644 --- a/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__simple_struct.snap +++ b/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__simple_struct.snap @@ -12,6 +12,62 @@ pub struct CliArgs { impl interactive_clap::ToCli for Args { type CliVariant = CliArgs; } +impl Args { + pub fn try_parse() -> Result { + ::try_parse() + } + pub fn parse() -> CliArgs { + ::parse() + } + pub fn try_parse_from(itr: I) -> Result + where + I: ::std::iter::IntoIterator, + T: ::std::convert::Into<::std::ffi::OsString> + ::std::clone::Clone, + { + ::try_parse_from(itr) + } +} +impl From for CliArgs { + fn from(args: Args) -> Self { + Self { + age: Some(args.age.into()), + first_name: Some(args.first_name.into()), + second_name: Some(args.second_name.into()), + } + } +} +impl Args { + fn input_age(_context: &()) -> color_eyre::eyre::Result> { + match inquire::CustomType::new("age").prompt() { + Ok(value) => Ok(Some(value)), + Err( + inquire::error::InquireError::OperationCanceled + | inquire::error::InquireError::OperationInterrupted, + ) => Ok(None), + Err(err) => Err(err.into()), + } + } + fn input_first_name(_context: &()) -> color_eyre::eyre::Result> { + match inquire::CustomType::new("first_name").prompt() { + Ok(value) => Ok(Some(value)), + Err( + inquire::error::InquireError::OperationCanceled + | inquire::error::InquireError::OperationInterrupted, + ) => Ok(None), + Err(err) => Err(err.into()), + } + } + fn input_second_name(_context: &()) -> color_eyre::eyre::Result> { + match inquire::CustomType::new("second_name").prompt() { + Ok(value) => Ok(Some(value)), + Err( + inquire::error::InquireError::OperationCanceled + | inquire::error::InquireError::OperationInterrupted, + ) => Ok(None), + Err(err) => Err(err.into()), + } + } +} pub struct InteractiveClapContextScopeForArgs { pub age: u64, pub first_name: String, @@ -81,57 +137,3 @@ impl interactive_clap::FromCli for Args { interactive_clap::ResultFromCli::Ok(clap_variant) } } -impl Args { - fn input_age(_context: &()) -> color_eyre::eyre::Result> { - match inquire::CustomType::new("age").prompt() { - Ok(value) => Ok(Some(value)), - Err( - inquire::error::InquireError::OperationCanceled - | inquire::error::InquireError::OperationInterrupted, - ) => Ok(None), - Err(err) => Err(err.into()), - } - } - fn input_first_name(_context: &()) -> color_eyre::eyre::Result> { - match inquire::CustomType::new("first_name").prompt() { - Ok(value) => Ok(Some(value)), - Err( - inquire::error::InquireError::OperationCanceled - | inquire::error::InquireError::OperationInterrupted, - ) => Ok(None), - Err(err) => Err(err.into()), - } - } - fn input_second_name(_context: &()) -> color_eyre::eyre::Result> { - match inquire::CustomType::new("second_name").prompt() { - Ok(value) => Ok(Some(value)), - Err( - inquire::error::InquireError::OperationCanceled - | inquire::error::InquireError::OperationInterrupted, - ) => Ok(None), - Err(err) => Err(err.into()), - } - } - pub fn try_parse() -> Result { - ::try_parse() - } - pub fn parse() -> CliArgs { - ::parse() - } - pub fn try_parse_from(itr: I) -> Result - where - I: ::std::iter::IntoIterator, - T: ::std::convert::Into<::std::ffi::OsString> + ::std::clone::Clone, - { - ::try_parse_from(itr) - } -} -impl From for CliArgs { - fn from(args: Args) -> Self { - Self { - age: Some(args.age.into()), - first_name: Some(args.first_name.into()), - second_name: Some(args.second_name.into()), - } - } -} diff --git a/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__vec_multiple_opt.snap b/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__vec_multiple_opt.snap index 16a176f..fbbda1b 100644 --- a/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__vec_multiple_opt.snap +++ b/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__vec_multiple_opt.snap @@ -11,6 +11,27 @@ pub struct CliArgs { impl interactive_clap::ToCli for Args { type CliVariant = CliArgs; } +impl Args { + pub fn try_parse() -> Result { + ::try_parse() + } + pub fn parse() -> CliArgs { + ::parse() + } + pub fn try_parse_from(itr: I) -> Result + where + I: ::std::iter::IntoIterator, + T: ::std::convert::Into<::std::ffi::OsString> + ::std::clone::Clone, + { + ::try_parse_from(itr) + } +} +impl From for CliArgs { + fn from(args: Args) -> Self { + Self { env: args.env.into() } + } +} +impl Args {} pub struct InteractiveClapContextScopeForArgs { pub env: Vec, } @@ -38,23 +59,3 @@ impl interactive_clap::FromCli for Args { interactive_clap::ResultFromCli::Ok(clap_variant) } } -impl Args { - pub fn try_parse() -> Result { - ::try_parse() - } - pub fn parse() -> CliArgs { - ::parse() - } - pub fn try_parse_from(itr: I) -> Result - where - I: ::std::iter::IntoIterator, - T: ::std::convert::Into<::std::ffi::OsString> + ::std::clone::Clone, - { - ::try_parse_from(itr) - } -} -impl From for CliArgs { - fn from(args: Args) -> Self { - Self { env: args.env.into() } - } -} From 99d1c651401963ec598c7b690661533cf15d8766 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dj8yf0=CE=BCl?= Date: Thu, 9 Jan 2025 22:09:59 +0200 Subject: [PATCH 18/57] chore: move `impl From<#name> for #cli_name` derive into submodule --- .../src/derives/interactive_clap/mod.rs | 156 +++++++----------- .../from_conversion/mod.rs | 69 ++++++++ 2 files changed, 128 insertions(+), 97 deletions(-) create mode 100644 interactive-clap-derive/src/derives/interactive_clap/structs/cli_variant_of_to_cli_trait/from_conversion/mod.rs diff --git a/interactive-clap-derive/src/derives/interactive_clap/mod.rs b/interactive-clap-derive/src/derives/interactive_clap/mod.rs index 14111c4..84f2404 100644 --- a/interactive-clap-derive/src/derives/interactive_clap/mod.rs +++ b/interactive-clap-derive/src/derives/interactive_clap/mod.rs @@ -9,19 +9,16 @@ pub(crate) mod methods; pub fn impl_interactive_clap(ast: &syn::DeriveInput) -> TokenStream { let name = &ast.ident; - let cli_name_string = format!("Cli{}", &ast.ident); - let cli_name = &syn::Ident::new(&cli_name_string, Span::call_site()); + let cli_name = { + let cli_name_string = format!("Cli{}", name); + &syn::Ident::new(&cli_name_string, Span::call_site()) + }; match &ast.data { syn::Data::Struct(data_struct) => { let fields = data_struct.fields.clone(); - let (cli_fields, ident_skip_field_vec) = - structs::cli_variant_of_to_cli_trait::fields(&fields, name); - - let for_cli_fields = fields - .iter() - .map(|field| for_cli_field(field, &ident_skip_field_vec)) - .filter(|token_stream| !token_stream.is_empty()); + let cli_variant_of_to_cli_trait_block = + structs::cli_variant_of_to_cli_trait::token_stream(name, cli_name, &fields); let fn_from_cli_for_struct = self::methods::from_cli_for_struct::from_cli_for_struct(ast, &fields); @@ -83,41 +80,7 @@ pub fn impl_interactive_clap(ast: &syn::DeriveInput) -> TokenStream { .unwrap_or(quote!()); quote! { - #[derive(Debug, Default, Clone, clap::Parser, interactive_clap::ToCliArgs)] - #[clap(author, version, about, long_about = None)] - pub struct #cli_name { - #( #cli_fields, )* - } - - impl interactive_clap::ToCli for #name { - type CliVariant = #cli_name; - } - - impl #name { - pub fn try_parse() -> Result<#cli_name, clap::Error> { - <#cli_name as clap::Parser>::try_parse() - } - - pub fn parse() -> #cli_name { - <#cli_name as clap::Parser>::parse() - } - - pub fn try_parse_from(itr: I) -> Result<#cli_name, clap::Error> - where - I: ::std::iter::IntoIterator, - T: ::std::convert::Into<::std::ffi::OsString> + ::std::clone::Clone, - { - <#cli_name as clap::Parser>::try_parse_from(itr) - } - } - - impl From<#name> for #cli_name { - fn from(args: #name) -> Self { - Self { - #( #for_cli_fields, )* - } - } - } + #cli_variant_of_to_cli_trait_block impl #name { #(#vec_fn_input_arg)* @@ -328,14 +291,52 @@ mod structs { use proc_macro_error::abort_call_site; use quote::{quote, ToTokens}; - /// this module describes derive of individual field of `#cli_name` struct - /// based on transformation of input field from `#name` struct - mod field; - - pub fn fields( - fields: &syn::Fields, + /// returns the whole result `TokenStream` of derive logic of containing module + pub fn token_stream( name: &syn::Ident, - ) -> (Vec, Vec) { + cli_name: &syn::Ident, + input_fields: &syn::Fields, + ) -> TokenStream { + let (cli_fields, ident_skip_field_vec) = fields(input_fields, name); + + let from_trait_impl = + from_conversion::token_stream(name, cli_name, input_fields, &ident_skip_field_vec); + quote! { + #[derive(Debug, Default, Clone, clap::Parser, interactive_clap::ToCliArgs)] + #[clap(author, version, about, long_about = None)] + pub struct #cli_name { + #( #cli_fields, )* + } + + impl interactive_clap::ToCli for #name { + type CliVariant = #cli_name; + } + + impl #name { + pub fn try_parse() -> Result<#cli_name, clap::Error> { + <#cli_name as clap::Parser>::try_parse() + } + + pub fn parse() -> #cli_name { + <#cli_name as clap::Parser>::parse() + } + + pub fn try_parse_from(itr: I) -> Result<#cli_name, clap::Error> + where + I: ::std::iter::IntoIterator, + T: ::std::convert::Into<::std::ffi::OsString> + ::std::clone::Clone, + { + <#cli_name as clap::Parser>::try_parse_from(itr) + } + } + + #from_trait_impl + } + } + + /// this module describes derive of all fields of `#cli_name` struct + /// based on transformation of input fields from `#name` struct + fn fields(fields: &syn::Fields, name: &syn::Ident) -> (Vec, Vec) { let mut ident_skip_field_vec: Vec = Vec::new(); let fields = fields @@ -457,6 +458,13 @@ mod structs { .collect::>(); (fields, ident_skip_field_vec) } + + /// this module describes the derive of `impl From<#name> for #cli_name` + mod from_conversion; + + /// this module describes derive of individual field of `#cli_name` struct + /// based on transformation of input field from `#name` struct + mod field; } } @@ -505,49 +513,3 @@ fn context_scope_for_enum(name: &syn::Ident) -> proc_macro2::TokenStream { } } } - -use crate::LONG_VEC_MUTLIPLE_OPT; - -fn for_cli_field( - field: &syn::Field, - ident_skip_field_vec: &[syn::Ident], -) -> proc_macro2::TokenStream { - let ident_field = &field.clone().ident.expect("this field does not exist"); - if ident_skip_field_vec.contains(ident_field) { - quote!() - } else { - let ty = &field.ty; - if field.attrs.iter().any(|attr| - attr.path.is_ident("interactive_clap") && - attr.tokens.clone().into_iter().any( - |attr_token| - matches!( - attr_token, - proc_macro2::TokenTree::Group(group) if group.stream().to_string() == LONG_VEC_MUTLIPLE_OPT - ) - ) - ) { - return quote! { - #ident_field: args.#ident_field.into() - }; - } - - match &ty { - syn::Type::Path(type_path) => match type_path.path.segments.first() { - Some(path_segment) => { - if path_segment.ident == "Option" || path_segment.ident == "bool" { - quote! { - #ident_field: args.#ident_field.into() - } - } else { - quote! { - #ident_field: Some(args.#ident_field.into()) - } - } - } - _ => abort_call_site!("Only option `PathSegment` is needed"), - }, - _ => abort_call_site!("Only option `Type::Path` is needed"), - } - } -} diff --git a/interactive-clap-derive/src/derives/interactive_clap/structs/cli_variant_of_to_cli_trait/from_conversion/mod.rs b/interactive-clap-derive/src/derives/interactive_clap/structs/cli_variant_of_to_cli_trait/from_conversion/mod.rs new file mode 100644 index 0000000..18babcb --- /dev/null +++ b/interactive-clap-derive/src/derives/interactive_clap/structs/cli_variant_of_to_cli_trait/from_conversion/mod.rs @@ -0,0 +1,69 @@ +use crate::LONG_VEC_MUTLIPLE_OPT; +use proc_macro2::TokenStream; +use proc_macro_error::abort_call_site; +use quote::quote; + +/// returns the whole result `TokenStream` of derive logic of containing module +pub fn token_stream( + name: &syn::Ident, + cli_name: &syn::Ident, + input_fields: &syn::Fields, + ident_skip_field_vec: &[syn::Ident], +) -> TokenStream { + let fields_conversion = input_fields + .iter() + .map(|field| field_conversion(field, ident_skip_field_vec)) + .filter(|token_stream| !token_stream.is_empty()); + + quote! { + + impl From<#name> for #cli_name { + fn from(args: #name) -> Self { + Self { + #( #fields_conversion, )* + } + } + } + } +} + +fn field_conversion(field: &syn::Field, ident_skip_field_vec: &[syn::Ident]) -> TokenStream { + let ident_field = &field.clone().ident.expect("this field does not exist"); + if ident_skip_field_vec.contains(ident_field) { + quote!() + } else { + let ty = &field.ty; + if field.attrs.iter().any(|attr| + attr.path.is_ident("interactive_clap") && + attr.tokens.clone().into_iter().any( + |attr_token| + matches!( + attr_token, + proc_macro2::TokenTree::Group(group) if group.stream().to_string() == LONG_VEC_MUTLIPLE_OPT + ) + ) + ) { + return quote! { + #ident_field: args.#ident_field.into() + }; + } + + match &ty { + syn::Type::Path(type_path) => match type_path.path.segments.first() { + Some(path_segment) => { + if path_segment.ident == "Option" || path_segment.ident == "bool" { + quote! { + #ident_field: args.#ident_field.into() + } + } else { + quote! { + #ident_field: Some(args.#ident_field.into()) + } + } + } + _ => abort_call_site!("Only option `PathSegment` is needed"), + }, + _ => abort_call_site!("Only option `Type::Path` is needed"), + } + } +} From 4d84f7cc1dd0cf09d14ea5059ee1dd6dff9784ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dj8yf0=CE=BCl?= Date: Thu, 9 Jan 2025 23:46:42 +0200 Subject: [PATCH 19/57] chore: move impl #name (clap::Parser adapter block) into submodule --- .../src/derives/interactive_clap/mod.rs | 39 +++++++------------ .../clap_parser_trait_adapter/mod.rs | 26 +++++++++++++ .../{from_conversion => from_trait}/mod.rs | 0 3 files changed, 40 insertions(+), 25 deletions(-) create mode 100644 interactive-clap-derive/src/derives/interactive_clap/structs/cli_variant_of_to_cli_trait/clap_parser_trait_adapter/mod.rs rename interactive-clap-derive/src/derives/interactive_clap/structs/cli_variant_of_to_cli_trait/{from_conversion => from_trait}/mod.rs (100%) diff --git a/interactive-clap-derive/src/derives/interactive_clap/mod.rs b/interactive-clap-derive/src/derives/interactive_clap/mod.rs index 84f2404..41bacb4 100644 --- a/interactive-clap-derive/src/derives/interactive_clap/mod.rs +++ b/interactive-clap-derive/src/derives/interactive_clap/mod.rs @@ -254,7 +254,7 @@ mod structs { /// type CliVariant; /// } /// ``` - /// Additionally a [`clap::Parser`](https://docs.rs/clap/4.5.24/clap/trait.Parser.html) adapter + /// Additionally a [`clap::Parser`](https://docs.rs/clap/4.5.24/clap/trait.Parser.html) adapter /// for `#name` and `From<#name> for #cli_name` conversion are defined: /// /// ```ignore @@ -279,7 +279,7 @@ mod structs { /// impl From<#name> for #cli_name { /// fn from(args: #name) -> Self { /// Self { - /// #( #for_cli_fields, )* + /// #( #fields_conversion, )* /// } /// } /// } @@ -299,8 +299,9 @@ mod structs { ) -> TokenStream { let (cli_fields, ident_skip_field_vec) = fields(input_fields, name); + let clap_parser_adapter = clap_parser_trait_adapter::token_stream(name, cli_name); let from_trait_impl = - from_conversion::token_stream(name, cli_name, input_fields, &ident_skip_field_vec); + from_trait::token_stream(name, cli_name, input_fields, &ident_skip_field_vec); quote! { #[derive(Debug, Default, Clone, clap::Parser, interactive_clap::ToCliArgs)] #[clap(author, version, about, long_about = None)] @@ -312,29 +313,13 @@ mod structs { type CliVariant = #cli_name; } - impl #name { - pub fn try_parse() -> Result<#cli_name, clap::Error> { - <#cli_name as clap::Parser>::try_parse() - } - - pub fn parse() -> #cli_name { - <#cli_name as clap::Parser>::parse() - } - - pub fn try_parse_from(itr: I) -> Result<#cli_name, clap::Error> - where - I: ::std::iter::IntoIterator, - T: ::std::convert::Into<::std::ffi::OsString> + ::std::clone::Clone, - { - <#cli_name as clap::Parser>::try_parse_from(itr) - } - } + #clap_parser_adapter #from_trait_impl } } - /// this module describes derive of all fields of `#cli_name` struct + /// describes derive of all fields of `#cli_name` struct /// based on transformation of input fields from `#name` struct fn fields(fields: &syn::Fields, name: &syn::Ident) -> (Vec, Vec) { let mut ident_skip_field_vec: Vec = Vec::new(); @@ -459,12 +444,16 @@ mod structs { (fields, ident_skip_field_vec) } - /// this module describes the derive of `impl From<#name> for #cli_name` - mod from_conversion; - - /// this module describes derive of individual field of `#cli_name` struct + /// describes derive of individual field of `#cli_name` struct /// based on transformation of input field from `#name` struct mod field; + + /// describes logic of derive of [`clap::Parser`](https://docs.rs/clap/4.5.24/clap/trait.Parser.html) adapter + /// for `#name` struct, which returns instances of `#cli_name` struct + mod clap_parser_trait_adapter; + + /// describes the derive of `impl From<#name> for #cli_name` + mod from_trait; } } diff --git a/interactive-clap-derive/src/derives/interactive_clap/structs/cli_variant_of_to_cli_trait/clap_parser_trait_adapter/mod.rs b/interactive-clap-derive/src/derives/interactive_clap/structs/cli_variant_of_to_cli_trait/clap_parser_trait_adapter/mod.rs new file mode 100644 index 0000000..53a6b46 --- /dev/null +++ b/interactive-clap-derive/src/derives/interactive_clap/structs/cli_variant_of_to_cli_trait/clap_parser_trait_adapter/mod.rs @@ -0,0 +1,26 @@ +use proc_macro2::TokenStream; +use quote::quote; + +/// returns the whole result `TokenStream` of derive logic of containing module +pub fn token_stream(name: &syn::Ident, cli_name: &syn::Ident) -> TokenStream { + quote! { + + impl #name { + pub fn try_parse() -> Result<#cli_name, clap::Error> { + <#cli_name as clap::Parser>::try_parse() + } + + pub fn parse() -> #cli_name { + <#cli_name as clap::Parser>::parse() + } + + pub fn try_parse_from(itr: I) -> Result<#cli_name, clap::Error> + where + I: ::std::iter::IntoIterator, + T: ::std::convert::Into<::std::ffi::OsString> + ::std::clone::Clone, + { + <#cli_name as clap::Parser>::try_parse_from(itr) + } + } + } +} diff --git a/interactive-clap-derive/src/derives/interactive_clap/structs/cli_variant_of_to_cli_trait/from_conversion/mod.rs b/interactive-clap-derive/src/derives/interactive_clap/structs/cli_variant_of_to_cli_trait/from_trait/mod.rs similarity index 100% rename from interactive-clap-derive/src/derives/interactive_clap/structs/cli_variant_of_to_cli_trait/from_conversion/mod.rs rename to interactive-clap-derive/src/derives/interactive_clap/structs/cli_variant_of_to_cli_trait/from_trait/mod.rs From 96072dcb03e934fb03f2e94fae2ff517467acd6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dj8yf0=CE=BCl?= Date: Fri, 24 Jan 2025 19:26:03 +0200 Subject: [PATCH 20/57] chore: extract from_cli_for_struct module --- .../docs/structs_from_cli_trait_docstring.md | 65 +++++ .../docs/structs_to_cli_trait_docstring.md | 52 ++++ .../choose_variant.rs | 0 .../fields_with_skip_default_input_arg.rs | 0 .../fields_with_subargs.rs | 0 .../fields_with_subcommand.rs | 0 .../from_cli_for_enum.rs | 0 .../{methods => common_methods}/input_arg.rs | 0 .../interactive_clap_attrs_context.rs | 0 .../{methods => common_methods}/mod.rs | 1 - .../skip_interactive_input.rs | 0 .../src/derives/interactive_clap/mod.rs | 253 ++---------------- .../from_cli_trait.rs} | 20 +- .../clap_parser_trait_adapter/mod.rs | 0 .../field/mod.rs | 0 .../from_trait/mod.rs | 0 .../structs/to_cli_trait/mod.rs | 169 ++++++++++++ 17 files changed, 314 insertions(+), 246 deletions(-) create mode 100644 interactive-clap-derive/docs/structs_from_cli_trait_docstring.md create mode 100644 interactive-clap-derive/docs/structs_to_cli_trait_docstring.md rename interactive-clap-derive/src/derives/interactive_clap/{methods => common_methods}/choose_variant.rs (100%) rename interactive-clap-derive/src/derives/interactive_clap/{methods => common_methods}/fields_with_skip_default_input_arg.rs (100%) rename interactive-clap-derive/src/derives/interactive_clap/{methods => common_methods}/fields_with_subargs.rs (100%) rename interactive-clap-derive/src/derives/interactive_clap/{methods => common_methods}/fields_with_subcommand.rs (100%) rename interactive-clap-derive/src/derives/interactive_clap/{methods => common_methods}/from_cli_for_enum.rs (100%) rename interactive-clap-derive/src/derives/interactive_clap/{methods => common_methods}/input_arg.rs (100%) rename interactive-clap-derive/src/derives/interactive_clap/{methods => common_methods}/interactive_clap_attrs_context.rs (100%) rename interactive-clap-derive/src/derives/interactive_clap/{methods => common_methods}/mod.rs (89%) rename interactive-clap-derive/src/derives/interactive_clap/{methods => common_methods}/skip_interactive_input.rs (100%) rename interactive-clap-derive/src/derives/interactive_clap/{methods/from_cli_for_struct.rs => structs/from_cli_trait.rs} (94%) rename interactive-clap-derive/src/derives/interactive_clap/structs/{cli_variant_of_to_cli_trait => to_cli_trait}/clap_parser_trait_adapter/mod.rs (100%) rename interactive-clap-derive/src/derives/interactive_clap/structs/{cli_variant_of_to_cli_trait => to_cli_trait}/field/mod.rs (100%) rename interactive-clap-derive/src/derives/interactive_clap/structs/{cli_variant_of_to_cli_trait => to_cli_trait}/from_trait/mod.rs (100%) create mode 100644 interactive-clap-derive/src/derives/interactive_clap/structs/to_cli_trait/mod.rs diff --git a/interactive-clap-derive/docs/structs_from_cli_trait_docstring.md b/interactive-clap-derive/docs/structs_from_cli_trait_docstring.md new file mode 100644 index 0000000..be35021 --- /dev/null +++ b/interactive-clap-derive/docs/structs_from_cli_trait_docstring.md @@ -0,0 +1,65 @@ +This modules describes of `interactive_clap::FromCli` trait for `#name` struct, +which happens during derive of [`crate::InteractiveClap`] for `#name` struct: + +derive input `#name` + +```rust,ignore +struct #name { + age: u64, + first_name: String, + second_name: String, +} +``` + +gets transformed +=> + +```rust,ignore +impl interactive_clap::FromCli for #name { + 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.clone().unwrap_or_default(); + if clap_variant.age.is_none() { + clap_variant + .age = match Self::input_age(&context) { + Ok(Some(age)) => Some(age), + Ok(None) => { + return interactive_clap::ResultFromCli::Cancel(Some(clap_variant)); + } + Err(err) => { + return interactive_clap::ResultFromCli::Err(Some(clap_variant), err); + } + }; + } + let age = clap_variant.age.clone().expect("Unexpected error"); + if clap_variant.first_name.is_none() { + clap_variant + .first_name = match Self::input_first_name(&context) { + Ok(Some(first_name)) => Some(first_name), + Ok(None) => { + return interactive_clap::ResultFromCli::Cancel(Some(clap_variant)); + } + Err(err) => { + return interactive_clap::ResultFromCli::Err(Some(clap_variant), err); + } + }; + } + let first_name = clap_variant.first_name.clone().expect("Unexpected error"); + let new_context_scope = InteractiveClapContextScopeForArgs { + age: age.into(), + first_name: first_name.into(), + }; + interactive_clap::ResultFromCli::Ok(clap_variant) + } +} +``` diff --git a/interactive-clap-derive/docs/structs_to_cli_trait_docstring.md b/interactive-clap-derive/docs/structs_to_cli_trait_docstring.md new file mode 100644 index 0000000..a26fe44 --- /dev/null +++ b/interactive-clap-derive/docs/structs_to_cli_trait_docstring.md @@ -0,0 +1,52 @@ +This module describes the derive logic of `#cli_name` struct used as `CliVariant` in +implementation of `interactive_clap::ToCli`, which happens during derive of [`crate::InteractiveClap`] for `#name` struct. + +```rust,ignore +#[derive(Debug, Default, Clone, clap::Parser, interactive_clap::ToCliArgs)] +#[clap(author, version, about, long_about = None)] +pub struct #cli_name { + #( #cli_fields, )* +} + +impl interactive_clap::ToCli for #name { + type CliVariant = #cli_name; +} +``` + +Where `interactive_clap::ToCli` is: + +```rust,ignore +pub trait ToCli { + type CliVariant; +} +``` +Additionally a [`clap::Parser`](https://docs.rs/clap/4.5.24/clap/trait.Parser.html) adapter +for `#name` and `From<#name> for #cli_name` conversion are defined: + +```rust,ignore +impl #name { + pub fn try_parse() -> Result<#cli_name, clap::Error> { + <#cli_name as clap::Parser>::try_parse() + } + + pub fn parse() -> #cli_name { + <#cli_name as clap::Parser>::parse() + } + + pub fn try_parse_from(itr: I) -> Result<#cli_name, clap::Error> + where + I: ::std::iter::IntoIterator, + T: ::std::convert::Into<::std::ffi::OsString> + ::std::clone::Clone, + { + <#cli_name as clap::Parser>::try_parse_from(itr) + } +} + +impl From<#name> for #cli_name { + fn from(args: #name) -> Self { + Self { + #( #fields_conversion, )* + } + } +} +``` diff --git a/interactive-clap-derive/src/derives/interactive_clap/methods/choose_variant.rs b/interactive-clap-derive/src/derives/interactive_clap/common_methods/choose_variant.rs similarity index 100% rename from interactive-clap-derive/src/derives/interactive_clap/methods/choose_variant.rs rename to interactive-clap-derive/src/derives/interactive_clap/common_methods/choose_variant.rs 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/common_methods/fields_with_skip_default_input_arg.rs similarity index 100% rename from interactive-clap-derive/src/derives/interactive_clap/methods/fields_with_skip_default_input_arg.rs rename to interactive-clap-derive/src/derives/interactive_clap/common_methods/fields_with_skip_default_input_arg.rs diff --git a/interactive-clap-derive/src/derives/interactive_clap/methods/fields_with_subargs.rs b/interactive-clap-derive/src/derives/interactive_clap/common_methods/fields_with_subargs.rs similarity index 100% rename from interactive-clap-derive/src/derives/interactive_clap/methods/fields_with_subargs.rs rename to interactive-clap-derive/src/derives/interactive_clap/common_methods/fields_with_subargs.rs diff --git a/interactive-clap-derive/src/derives/interactive_clap/methods/fields_with_subcommand.rs b/interactive-clap-derive/src/derives/interactive_clap/common_methods/fields_with_subcommand.rs similarity index 100% rename from interactive-clap-derive/src/derives/interactive_clap/methods/fields_with_subcommand.rs rename to interactive-clap-derive/src/derives/interactive_clap/common_methods/fields_with_subcommand.rs diff --git a/interactive-clap-derive/src/derives/interactive_clap/methods/from_cli_for_enum.rs b/interactive-clap-derive/src/derives/interactive_clap/common_methods/from_cli_for_enum.rs similarity index 100% rename from interactive-clap-derive/src/derives/interactive_clap/methods/from_cli_for_enum.rs rename to interactive-clap-derive/src/derives/interactive_clap/common_methods/from_cli_for_enum.rs diff --git a/interactive-clap-derive/src/derives/interactive_clap/methods/input_arg.rs b/interactive-clap-derive/src/derives/interactive_clap/common_methods/input_arg.rs similarity index 100% rename from interactive-clap-derive/src/derives/interactive_clap/methods/input_arg.rs rename to interactive-clap-derive/src/derives/interactive_clap/common_methods/input_arg.rs diff --git a/interactive-clap-derive/src/derives/interactive_clap/methods/interactive_clap_attrs_context.rs b/interactive-clap-derive/src/derives/interactive_clap/common_methods/interactive_clap_attrs_context.rs similarity index 100% rename from interactive-clap-derive/src/derives/interactive_clap/methods/interactive_clap_attrs_context.rs rename to interactive-clap-derive/src/derives/interactive_clap/common_methods/interactive_clap_attrs_context.rs diff --git a/interactive-clap-derive/src/derives/interactive_clap/methods/mod.rs b/interactive-clap-derive/src/derives/interactive_clap/common_methods/mod.rs similarity index 89% rename from interactive-clap-derive/src/derives/interactive_clap/methods/mod.rs rename to interactive-clap-derive/src/derives/interactive_clap/common_methods/mod.rs index 5c936e4..05f2b37 100644 --- a/interactive-clap-derive/src/derives/interactive_clap/methods/mod.rs +++ b/interactive-clap-derive/src/derives/interactive_clap/common_methods/mod.rs @@ -3,7 +3,6 @@ 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; pub mod input_arg; pub mod interactive_clap_attrs_context; pub mod skip_interactive_input; diff --git a/interactive-clap-derive/src/derives/interactive_clap/methods/skip_interactive_input.rs b/interactive-clap-derive/src/derives/interactive_clap/common_methods/skip_interactive_input.rs similarity index 100% rename from interactive-clap-derive/src/derives/interactive_clap/methods/skip_interactive_input.rs rename to interactive-clap-derive/src/derives/interactive_clap/common_methods/skip_interactive_input.rs diff --git a/interactive-clap-derive/src/derives/interactive_clap/mod.rs b/interactive-clap-derive/src/derives/interactive_clap/mod.rs index 41bacb4..3962134 100644 --- a/interactive-clap-derive/src/derives/interactive_clap/mod.rs +++ b/interactive-clap-derive/src/derives/interactive_clap/mod.rs @@ -5,8 +5,6 @@ use proc_macro_error::abort_call_site; use quote::{quote, ToTokens}; use syn; -pub(crate) mod methods; - pub fn impl_interactive_clap(ast: &syn::DeriveInput) -> TokenStream { let name = &ast.ident; let cli_name = { @@ -17,13 +15,12 @@ pub fn impl_interactive_clap(ast: &syn::DeriveInput) -> TokenStream { syn::Data::Struct(data_struct) => { let fields = data_struct.fields.clone(); - let cli_variant_of_to_cli_trait_block = - structs::cli_variant_of_to_cli_trait::token_stream(name, cli_name, &fields); + let to_cli_trait_block = + self::structs::to_cli_trait::token_stream(name, cli_name, &fields); - let fn_from_cli_for_struct = - self::methods::from_cli_for_struct::from_cli_for_struct(ast, &fields); + let from_cli_trait_block = self::structs::from_cli_trait::token_stream(ast, &fields); - let vec_fn_input_arg = self::methods::input_arg::vec_fn_input_arg(ast, &fields); + let vec_fn_input_arg = self::common_methods::input_arg::vec_fn_input_arg(ast, &fields); let context_scope_fields = fields .iter() @@ -80,7 +77,7 @@ pub fn impl_interactive_clap(ast: &syn::DeriveInput) -> TokenStream { .unwrap_or(quote!()); quote! { - #cli_variant_of_to_cli_trait_block + #to_cli_trait_block impl #name { #(#vec_fn_input_arg)* @@ -88,7 +85,7 @@ pub fn impl_interactive_clap(ast: &syn::DeriveInput) -> TokenStream { #context_scope_for_struct - #fn_from_cli_for_struct + #from_cli_trait_block #clap_enum_for_named_arg } @@ -177,10 +174,11 @@ pub fn impl_interactive_clap(ast: &syn::DeriveInput) -> TokenStream { let scope_for_enum = context_scope_for_enum(name); - let fn_choose_variant = self::methods::choose_variant::fn_choose_variant(ast, variants); + let fn_choose_variant = + self::common_methods::choose_variant::fn_choose_variant(ast, variants); let fn_from_cli_for_enum = - self::methods::from_cli_for_enum::from_cli_for_enum(ast, variants); + self::common_methods::from_cli_for_enum::from_cli_for_enum(ast, variants); quote! { #[derive(Debug, Clone, clap::Parser, interactive_clap::ToCliArgs)] @@ -229,232 +227,17 @@ pub fn impl_interactive_clap(ast: &syn::DeriveInput) -> TokenStream { } } +/// these are common methods, reused for both the [structs] and `enums` derives +pub(crate) mod common_methods; + /// This module describes [`crate::InteractiveClap`] derive logic in case when [`syn::DeriveInput`] /// is a struct mod structs { - /// This module describes the derive logic of `#cli_name` struct used as `CliVariant` in - /// implementation of `interactive_clap::ToCli`, which happens during derive of [`crate::InteractiveClap`] for `#name` struct. - /// - /// ```ignore - /// #[derive(Debug, Default, Clone, clap::Parser, interactive_clap::ToCliArgs)] - /// #[clap(author, version, about, long_about = None)] - /// pub struct #cli_name { - /// #( #cli_fields, )* - /// } - /// - /// impl interactive_clap::ToCli for #name { - /// type CliVariant = #cli_name; - /// } - /// ``` - /// - /// Where `interactive_clap::ToCli` is: - /// - /// ```ignore - /// pub trait ToCli { - /// type CliVariant; - /// } - /// ``` - /// Additionally a [`clap::Parser`](https://docs.rs/clap/4.5.24/clap/trait.Parser.html) adapter - /// for `#name` and `From<#name> for #cli_name` conversion are defined: - /// - /// ```ignore - /// impl #name { - /// pub fn try_parse() -> Result<#cli_name, clap::Error> { - /// <#cli_name as clap::Parser>::try_parse() - /// } - /// - /// pub fn parse() -> #cli_name { - /// <#cli_name as clap::Parser>::parse() - /// } - /// - /// pub fn try_parse_from(itr: I) -> Result<#cli_name, clap::Error> - /// where - /// I: ::std::iter::IntoIterator, - /// T: ::std::convert::Into<::std::ffi::OsString> + ::std::clone::Clone, - /// { - /// <#cli_name as clap::Parser>::try_parse_from(itr) - /// } - /// } - /// - /// impl From<#name> for #cli_name { - /// fn from(args: #name) -> Self { - /// Self { - /// #( #fields_conversion, )* - /// } - /// } - /// } - /// ``` - pub mod cli_variant_of_to_cli_trait { - use crate::LONG_VEC_MUTLIPLE_OPT; - use crate::VERBATIM_DOC_COMMENT; - use proc_macro2::{Span, TokenStream}; - use proc_macro_error::abort_call_site; - use quote::{quote, ToTokens}; - - /// returns the whole result `TokenStream` of derive logic of containing module - pub fn token_stream( - name: &syn::Ident, - cli_name: &syn::Ident, - input_fields: &syn::Fields, - ) -> TokenStream { - let (cli_fields, ident_skip_field_vec) = fields(input_fields, name); - - let clap_parser_adapter = clap_parser_trait_adapter::token_stream(name, cli_name); - let from_trait_impl = - from_trait::token_stream(name, cli_name, input_fields, &ident_skip_field_vec); - quote! { - #[derive(Debug, Default, Clone, clap::Parser, interactive_clap::ToCliArgs)] - #[clap(author, version, about, long_about = None)] - pub struct #cli_name { - #( #cli_fields, )* - } - - impl interactive_clap::ToCli for #name { - type CliVariant = #cli_name; - } - - #clap_parser_adapter - - #from_trait_impl - } - } - - /// describes derive of all fields of `#cli_name` struct - /// based on transformation of input fields from `#name` struct - fn fields(fields: &syn::Fields, name: &syn::Ident) -> (Vec, Vec) { - let mut ident_skip_field_vec: Vec = Vec::new(); - - let fields = fields - .iter() - .map(|field| { - let ident_field = field.ident.clone().expect("this field does not exist"); - let ty = &field.ty; - let cli_ty = self::field::field_type(ty); - let mut cli_field = quote! { - pub #ident_field: #cli_ty - }; - if field.attrs.is_empty() { - return cli_field; - }; - let mut clap_attr_vec: Vec = Vec::new(); - let mut cfg_attr_vec: Vec = Vec::new(); - let mut doc_attr_vec: Vec = Vec::new(); - #[allow(clippy::unused_enumerate_index)] - for attr in &field.attrs { - dbg_cond!(attr.path.to_token_stream().into_iter().collect::>()); - if attr.path.is_ident("interactive_clap") || attr.path.is_ident("cfg") { - for (_index, attr_token) in attr.tokens.clone().into_iter().enumerate() { - dbg_cond!(_index, &attr_token); - match attr_token { - proc_macro2::TokenTree::Group(group) => { - let group_string = group.stream().to_string(); - if group_string.contains("subcommand") - || group_string.contains("value_enum") - || group_string.contains("long") - || (group_string == *"skip") - || (group_string == *"flatten") - || (group_string == VERBATIM_DOC_COMMENT) - { - if group_string != LONG_VEC_MUTLIPLE_OPT { - clap_attr_vec.push(group.stream()) - } - } 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}); - 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(), - ); - cli_field = quote! { - pub #ident_field: Option<#enum_for_clap_named_arg> - } - }; - 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!() - }; - if group.stream().to_string() == LONG_VEC_MUTLIPLE_OPT { - if !crate::helpers::type_starts_with_vec(ty) { - abort_call_site!("`{}` attribute is only supposed to be used with `Vec` types", LONG_VEC_MUTLIPLE_OPT) - } - // implies `#[interactive_clap(long)]` - clap_attr_vec.push(quote! { long }); - // type goes into output unchanged, otherwise it - // prevents clap deriving correctly its `remove_many` thing - cli_field = quote! { - pub #ident_field: #ty - }; - } - } - _ => { - abort_call_site!("Only option `TokenTree::Group` is needed") - } - } - } - } - if attr.path.is_ident("doc") { - doc_attr_vec.push(attr.into_token_stream()) - } - } - if cli_field.is_empty() { - return cli_field; - }; - let cfg_attrs = cfg_attr_vec.iter(); - if !clap_attr_vec.is_empty() { - let clap_attrs = clap_attr_vec.iter(); - quote! { - #(#cfg_attrs)* - #(#doc_attr_vec)* - #[clap(#(#clap_attrs, )*)] - #cli_field - } - } else { - quote! { - #(#cfg_attrs)* - #(#doc_attr_vec)* - #cli_field - } - } - }) - .filter(|token_stream| !token_stream.is_empty()) - .collect::>(); - (fields, ident_skip_field_vec) - } - - /// describes derive of individual field of `#cli_name` struct - /// based on transformation of input field from `#name` struct - mod field; + #[doc = include_str!("../../../docs/structs_to_cli_trait_docstring.md")] + pub mod to_cli_trait; - /// describes logic of derive of [`clap::Parser`](https://docs.rs/clap/4.5.24/clap/trait.Parser.html) adapter - /// for `#name` struct, which returns instances of `#cli_name` struct - mod clap_parser_trait_adapter; - - /// describes the derive of `impl From<#name> for #cli_name` - mod from_trait; - } + #[doc = include_str!("../../../docs/structs_from_cli_trait_docstring.md")] + pub mod from_cli_trait; } fn context_scope_for_struct( @@ -478,8 +261,8 @@ 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_with_subcommand::is_field_with_subcommand(field) - && !self::methods::fields_with_subargs::is_field_with_subargs(field) + if !self::common_methods::fields_with_subcommand::is_field_with_subcommand(field) + && !self::common_methods::fields_with_subargs::is_field_with_subargs(field) { quote! { pub #ident_field: #ty diff --git a/interactive-clap-derive/src/derives/interactive_clap/methods/from_cli_for_struct.rs b/interactive-clap-derive/src/derives/interactive_clap/structs/from_cli_trait.rs similarity index 94% rename from interactive-clap-derive/src/derives/interactive_clap/methods/from_cli_for_struct.rs rename to interactive-clap-derive/src/derives/interactive_clap/structs/from_cli_trait.rs index 706be3c..5b605d2 100644 --- a/interactive-clap-derive/src/derives/interactive_clap/methods/from_cli_for_struct.rs +++ b/interactive-clap-derive/src/derives/interactive_clap/structs/from_cli_trait.rs @@ -5,14 +5,14 @@ use proc_macro_error::abort_call_site; use quote::{quote, ToTokens}; use syn; -pub fn from_cli_for_struct( - ast: &syn::DeriveInput, - fields: &syn::Fields, -) -> proc_macro2::TokenStream { +use crate::derives::interactive_clap::common_methods; + +/// returns the whole result `TokenStream` of derive logic of containing module +pub fn token_stream(ast: &syn::DeriveInput, fields: &syn::Fields) -> proc_macro2::TokenStream { let name = &ast.ident; let interactive_clap_attrs_context = - super::interactive_clap_attrs_context::InteractiveClapAttrsContext::new(ast); + common_methods::interactive_clap_attrs_context::InteractiveClapAttrsContext::new(ast); if interactive_clap_attrs_context.is_skip_default_from_cli { return quote!(); }; @@ -20,8 +20,8 @@ pub fn from_cli_for_struct( let fields_without_subcommand_and_subargs = fields .iter() .filter(|field| { - !super::fields_with_subcommand::is_field_with_subcommand(field) - && !super::fields_with_subargs::is_field_with_subargs(field) + !common_methods::fields_with_subcommand::is_field_with_subcommand(field) + && !common_methods::fields_with_subargs::is_field_with_subargs(field) }) .map(|field| { let ident_field = &field.clone().ident.expect("this field does not exist"); @@ -102,7 +102,7 @@ fn fields_value(field: &syn::Field) -> proc_macro2::TokenStream { let ident_field = &field.clone().ident.expect("this field does not exist"); let fn_input_arg = syn::Ident::new(&format!("input_{}", &ident_field), Span::call_site()); if field.ty.to_token_stream().to_string() == "bool" - || super::skip_interactive_input::is_skip_interactive_input(field) + || common_methods::skip_interactive_input::is_skip_interactive_input(field) { quote! { let #ident_field = clap_variant.#ident_field.clone(); @@ -123,8 +123,8 @@ 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) - && !super::fields_with_subargs::is_field_with_subargs(field) + } else if !common_methods::fields_with_subcommand::is_field_with_subcommand(field) + && !common_methods::fields_with_subargs::is_field_with_subargs(field) { quote! { if clap_variant.#ident_field.is_none() { diff --git a/interactive-clap-derive/src/derives/interactive_clap/structs/cli_variant_of_to_cli_trait/clap_parser_trait_adapter/mod.rs b/interactive-clap-derive/src/derives/interactive_clap/structs/to_cli_trait/clap_parser_trait_adapter/mod.rs similarity index 100% rename from interactive-clap-derive/src/derives/interactive_clap/structs/cli_variant_of_to_cli_trait/clap_parser_trait_adapter/mod.rs rename to interactive-clap-derive/src/derives/interactive_clap/structs/to_cli_trait/clap_parser_trait_adapter/mod.rs diff --git a/interactive-clap-derive/src/derives/interactive_clap/structs/cli_variant_of_to_cli_trait/field/mod.rs b/interactive-clap-derive/src/derives/interactive_clap/structs/to_cli_trait/field/mod.rs similarity index 100% rename from interactive-clap-derive/src/derives/interactive_clap/structs/cli_variant_of_to_cli_trait/field/mod.rs rename to interactive-clap-derive/src/derives/interactive_clap/structs/to_cli_trait/field/mod.rs diff --git a/interactive-clap-derive/src/derives/interactive_clap/structs/cli_variant_of_to_cli_trait/from_trait/mod.rs b/interactive-clap-derive/src/derives/interactive_clap/structs/to_cli_trait/from_trait/mod.rs similarity index 100% rename from interactive-clap-derive/src/derives/interactive_clap/structs/cli_variant_of_to_cli_trait/from_trait/mod.rs rename to interactive-clap-derive/src/derives/interactive_clap/structs/to_cli_trait/from_trait/mod.rs diff --git a/interactive-clap-derive/src/derives/interactive_clap/structs/to_cli_trait/mod.rs b/interactive-clap-derive/src/derives/interactive_clap/structs/to_cli_trait/mod.rs new file mode 100644 index 0000000..5c859c4 --- /dev/null +++ b/interactive-clap-derive/src/derives/interactive_clap/structs/to_cli_trait/mod.rs @@ -0,0 +1,169 @@ +use crate::LONG_VEC_MUTLIPLE_OPT; +use crate::VERBATIM_DOC_COMMENT; +use proc_macro2::{Span, TokenStream}; +use proc_macro_error::abort_call_site; +use quote::{quote, ToTokens}; + +/// returns the whole result `TokenStream` of derive logic of containing module +pub fn token_stream( + name: &syn::Ident, + cli_name: &syn::Ident, + input_fields: &syn::Fields, +) -> TokenStream { + let (cli_fields, ident_skip_field_vec) = fields(input_fields, name); + + let clap_parser_adapter = clap_parser_trait_adapter::token_stream(name, cli_name); + let from_trait_impl = + from_trait::token_stream(name, cli_name, input_fields, &ident_skip_field_vec); + quote! { + #[derive(Debug, Default, Clone, clap::Parser, interactive_clap::ToCliArgs)] + #[clap(author, version, about, long_about = None)] + pub struct #cli_name { + #( #cli_fields, )* + } + + impl interactive_clap::ToCli for #name { + type CliVariant = #cli_name; + } + + #clap_parser_adapter + + #from_trait_impl + } +} + +/// describes derive of all fields of `#cli_name` struct +/// based on transformation of input fields from `#name` struct +fn fields(fields: &syn::Fields, name: &syn::Ident) -> (Vec, Vec) { + let mut ident_skip_field_vec: Vec = Vec::new(); + + let fields = fields + .iter() + .map(|field| { + let ident_field = field.ident.clone().expect("this field does not exist"); + let ty = &field.ty; + let cli_ty = self::field::field_type(ty); + let mut cli_field = quote! { + pub #ident_field: #cli_ty + }; + if field.attrs.is_empty() { + return cli_field; + }; + let mut clap_attr_vec: Vec = Vec::new(); + let mut cfg_attr_vec: Vec = Vec::new(); + let mut doc_attr_vec: Vec = Vec::new(); + #[allow(clippy::unused_enumerate_index)] + for attr in &field.attrs { + dbg_cond!(attr.path.to_token_stream().into_iter().collect::>()); + if attr.path.is_ident("interactive_clap") || attr.path.is_ident("cfg") { + for (_index, attr_token) in attr.tokens.clone().into_iter().enumerate() { + dbg_cond!(_index, &attr_token); + match attr_token { + proc_macro2::TokenTree::Group(group) => { + let group_string = group.stream().to_string(); + if group_string.contains("subcommand") + || group_string.contains("value_enum") + || group_string.contains("long") + || (group_string == *"skip") + || (group_string == *"flatten") + || (group_string == VERBATIM_DOC_COMMENT) + { + if group_string != LONG_VEC_MUTLIPLE_OPT { + clap_attr_vec.push(group.stream()) + } + } 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}); + 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(), + ); + cli_field = quote! { + pub #ident_field: Option<#enum_for_clap_named_arg> + } + }; + 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!() + }; + if group.stream().to_string() == LONG_VEC_MUTLIPLE_OPT { + if !crate::helpers::type_starts_with_vec(ty) { + abort_call_site!("`{}` attribute is only supposed to be used with `Vec` types", LONG_VEC_MUTLIPLE_OPT) + } + // implies `#[interactive_clap(long)]` + clap_attr_vec.push(quote! { long }); + // type goes into output unchanged, otherwise it + // prevents clap deriving correctly its `remove_many` thing + cli_field = quote! { + pub #ident_field: #ty + }; + } + } + _ => { + abort_call_site!("Only option `TokenTree::Group` is needed") + } + } + } + } + if attr.path.is_ident("doc") { + doc_attr_vec.push(attr.into_token_stream()) + } + } + if cli_field.is_empty() { + return cli_field; + }; + let cfg_attrs = cfg_attr_vec.iter(); + if !clap_attr_vec.is_empty() { + let clap_attrs = clap_attr_vec.iter(); + quote! { + #(#cfg_attrs)* + #(#doc_attr_vec)* + #[clap(#(#clap_attrs, )*)] + #cli_field + } + } else { + quote! { + #(#cfg_attrs)* + #(#doc_attr_vec)* + #cli_field + } + } + }) + .filter(|token_stream| !token_stream.is_empty()) + .collect::>(); + (fields, ident_skip_field_vec) +} + +/// describes derive of individual field of `#cli_name` struct +/// based on transformation of input field from `#name` struct +mod field; + +/// describes logic of derive of [`clap::Parser`](https://docs.rs/clap/4.5.24/clap/trait.Parser.html) adapter +/// for `#name` struct, which returns instances of `#cli_name` struct +mod clap_parser_trait_adapter; + +/// describes the derive of `impl From<#name> for #cli_name` +mod from_trait; From 0e9dc32e792af4e79e661f4850f4fb17d276f13a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dj8yf0=CE=BCl?= Date: Fri, 24 Jan 2025 22:02:38 +0200 Subject: [PATCH 21/57] chore: extract stucts::input_args_impl module --- .../docs/structs_from_cli_trait_docstring.md | 5 ++- .../docs/structs_input_args_impl_docstring.md | 44 +++++++++++++++++++ .../docs/structs_to_cli_trait_docstring.md | 2 + .../interactive_clap/common_methods/mod.rs | 1 - .../src/derives/interactive_clap/mod.rs | 9 ++-- .../input_args_impl.rs} | 26 +++++++---- src/lib.rs | 4 ++ 7 files changed, 76 insertions(+), 15 deletions(-) create mode 100644 interactive-clap-derive/docs/structs_input_args_impl_docstring.md rename interactive-clap-derive/src/derives/interactive_clap/{common_methods/input_arg.rs => structs/input_args_impl.rs} (75%) diff --git a/interactive-clap-derive/docs/structs_from_cli_trait_docstring.md b/interactive-clap-derive/docs/structs_from_cli_trait_docstring.md index be35021..68e4bcd 100644 --- a/interactive-clap-derive/docs/structs_from_cli_trait_docstring.md +++ b/interactive-clap-derive/docs/structs_from_cli_trait_docstring.md @@ -1,4 +1,6 @@ -This modules describes of `interactive_clap::FromCli` trait for `#name` struct, +`interactive_clap::FromCli` derive + +This modules describes derive of `interactive_clap::FromCli` trait for `#name` struct, which happens during derive of [`crate::InteractiveClap`] for `#name` struct: derive input `#name` @@ -7,7 +9,6 @@ derive input `#name` struct #name { age: u64, first_name: String, - second_name: String, } ``` diff --git a/interactive-clap-derive/docs/structs_input_args_impl_docstring.md b/interactive-clap-derive/docs/structs_input_args_impl_docstring.md new file mode 100644 index 0000000..1bd4785 --- /dev/null +++ b/interactive-clap-derive/docs/structs_input_args_impl_docstring.md @@ -0,0 +1,44 @@ +per-field input with [inquire::CustomType](https://docs.rs/inquire/0.6.2/inquire/struct.CustomType.html) impl block + +This modules describes derive of input args implementation block for `#name` struct, +which contains functions `input_#field_ident` per each field, +which prompt for value of each field via [inquire::CustomType](https://docs.rs/inquire/0.6.2/inquire/struct.CustomType.html) +, which happens during derive of [`crate::InteractiveClap`] for `#name` struct: + +derive input `#name` + +```rust,ignore +struct #name { + age: u64, + first_name: String, +} +``` + + +gets transformed +=> + +```rust,ignore +impl #name { + fn input_age(_context: &()) -> color_eyre::eyre::Result> { + match inquire::CustomType::new("age").prompt() { + Ok(value) => Ok(Some(value)), + Err( + inquire::error::InquireError::OperationCanceled + | inquire::error::InquireError::OperationInterrupted, + ) => Ok(None), + Err(err) => Err(err.into()), + } + } + fn input_first_name(_context: &()) -> color_eyre::eyre::Result> { + match inquire::CustomType::new("first_name").prompt() { + Ok(value) => Ok(Some(value)), + Err( + inquire::error::InquireError::OperationCanceled + | inquire::error::InquireError::OperationInterrupted, + ) => Ok(None), + Err(err) => Err(err.into()), + } + } +} +``` diff --git a/interactive-clap-derive/docs/structs_to_cli_trait_docstring.md b/interactive-clap-derive/docs/structs_to_cli_trait_docstring.md index a26fe44..3f95e0e 100644 --- a/interactive-clap-derive/docs/structs_to_cli_trait_docstring.md +++ b/interactive-clap-derive/docs/structs_to_cli_trait_docstring.md @@ -1,3 +1,5 @@ +`interactive_clap::ToCli` derive + This module describes the derive logic of `#cli_name` struct used as `CliVariant` in implementation of `interactive_clap::ToCli`, which happens during derive of [`crate::InteractiveClap`] for `#name` struct. diff --git a/interactive-clap-derive/src/derives/interactive_clap/common_methods/mod.rs b/interactive-clap-derive/src/derives/interactive_clap/common_methods/mod.rs index 05f2b37..352d83d 100644 --- a/interactive-clap-derive/src/derives/interactive_clap/common_methods/mod.rs +++ b/interactive-clap-derive/src/derives/interactive_clap/common_methods/mod.rs @@ -3,6 +3,5 @@ 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 input_arg; pub mod interactive_clap_attrs_context; pub mod skip_interactive_input; diff --git a/interactive-clap-derive/src/derives/interactive_clap/mod.rs b/interactive-clap-derive/src/derives/interactive_clap/mod.rs index 3962134..06414a3 100644 --- a/interactive-clap-derive/src/derives/interactive_clap/mod.rs +++ b/interactive-clap-derive/src/derives/interactive_clap/mod.rs @@ -20,7 +20,7 @@ pub fn impl_interactive_clap(ast: &syn::DeriveInput) -> TokenStream { let from_cli_trait_block = self::structs::from_cli_trait::token_stream(ast, &fields); - let vec_fn_input_arg = self::common_methods::input_arg::vec_fn_input_arg(ast, &fields); + let input_args_impl_block = self::structs::input_args_impl::token_stream(ast, &fields); let context_scope_fields = fields .iter() @@ -79,9 +79,7 @@ pub fn impl_interactive_clap(ast: &syn::DeriveInput) -> TokenStream { quote! { #to_cli_trait_block - impl #name { - #(#vec_fn_input_arg)* - } + #input_args_impl_block #context_scope_for_struct @@ -236,6 +234,9 @@ mod structs { #[doc = include_str!("../../../docs/structs_to_cli_trait_docstring.md")] pub mod to_cli_trait; + #[doc = include_str!("../../../docs/structs_input_args_impl_docstring.md")] + pub mod input_args_impl; + #[doc = include_str!("../../../docs/structs_from_cli_trait_docstring.md")] pub mod from_cli_trait; } diff --git a/interactive-clap-derive/src/derives/interactive_clap/common_methods/input_arg.rs b/interactive-clap-derive/src/derives/interactive_clap/structs/input_args_impl.rs similarity index 75% rename from interactive-clap-derive/src/derives/interactive_clap/common_methods/input_arg.rs rename to interactive-clap-derive/src/derives/interactive_clap/structs/input_args_impl.rs index f887304..c7cbdad 100644 --- a/interactive-clap-derive/src/derives/interactive_clap/common_methods/input_arg.rs +++ b/interactive-clap-derive/src/derives/interactive_clap/structs/input_args_impl.rs @@ -4,17 +4,27 @@ use proc_macro2::Span; use quote::quote; use syn; -pub fn vec_fn_input_arg( - ast: &syn::DeriveInput, - fields: &syn::Fields, -) -> Vec { +use crate::derives::interactive_clap::common_methods; + +/// returns the whole result `TokenStream` of derive logic of containing module +pub fn token_stream(ast: &syn::DeriveInput, fields: &syn::Fields) -> proc_macro2::TokenStream { + let name = &ast.ident; + let vec_fn_input_arg = vec_fn_input_arg(ast, &fields); + quote! { + impl #name { + #(#vec_fn_input_arg)* + } + } +} + +fn vec_fn_input_arg(ast: &syn::DeriveInput, fields: &syn::Fields) -> Vec { let interactive_clap_attrs_context = - super::interactive_clap_attrs_context::InteractiveClapAttrsContext::new(ast); + common_methods::interactive_clap_attrs_context::InteractiveClapAttrsContext::new(ast); let vec_fn_input_arg = fields .iter() - .filter(|field| !super::fields_with_subcommand::is_field_with_subcommand(field)) + .filter(|field| !common_methods::fields_with_subcommand::is_field_with_subcommand(field)) .filter(|field| { - !super::fields_with_skip_default_input_arg::is_field_with_skip_default_input_arg( + !common_methods::fields_with_skip_default_input_arg::is_field_with_skip_default_input_arg( field, ) }) @@ -44,7 +54,7 @@ pub fn vec_fn_input_arg( }; } - if super::skip_interactive_input::is_skip_interactive_input(field) { + if common_methods::skip_interactive_input::is_skip_interactive_input(field) { return quote! {}; } diff --git a/src/lib.rs b/src/lib.rs index 1cd0c62..2a2c3c3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -46,6 +46,10 @@ pub enum ResultFromCli { Err(Option, E), } +/// This trait drives the state machine of `interactive_clap` +/// +/// It selects next command variants with [inquire::Select](https://docs.rs/inquire/0.6.2/inquire/struct.Select.html) +/// and prompts for non-optional arguments with [inquire::CustomType](https://docs.rs/inquire/0.6.2/inquire/struct.CustomType.html) pub trait FromCli { type FromCliContext; type FromCliError; From f922cda17006803f027a5ee1e4446310168dfac1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dj8yf0=CE=BCl?= Date: Fri, 24 Jan 2025 22:38:37 +0200 Subject: [PATCH 22/57] chore: move fields_with_subcommand to structs::common_methods --- .../src/derives/interactive_clap/common_methods/mod.rs | 1 - interactive-clap-derive/src/derives/interactive_clap/mod.rs | 6 ++++-- .../common_methods/is_field_with_subcommand.rs} | 2 +- .../derives/interactive_clap/structs/common_methods/mod.rs | 1 + .../src/derives/interactive_clap/structs/from_cli_trait.rs | 5 +++-- .../src/derives/interactive_clap/structs/input_args_impl.rs | 3 ++- 6 files changed, 11 insertions(+), 7 deletions(-) rename interactive-clap-derive/src/derives/interactive_clap/{common_methods/fields_with_subcommand.rs => structs/common_methods/is_field_with_subcommand.rs} (86%) create mode 100644 interactive-clap-derive/src/derives/interactive_clap/structs/common_methods/mod.rs diff --git a/interactive-clap-derive/src/derives/interactive_clap/common_methods/mod.rs b/interactive-clap-derive/src/derives/interactive_clap/common_methods/mod.rs index 352d83d..d0a9980 100644 --- a/interactive-clap-derive/src/derives/interactive_clap/common_methods/mod.rs +++ b/interactive-clap-derive/src/derives/interactive_clap/common_methods/mod.rs @@ -1,7 +1,6 @@ pub mod choose_variant; 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 interactive_clap_attrs_context; pub mod skip_interactive_input; diff --git a/interactive-clap-derive/src/derives/interactive_clap/mod.rs b/interactive-clap-derive/src/derives/interactive_clap/mod.rs index 06414a3..76e0b46 100644 --- a/interactive-clap-derive/src/derives/interactive_clap/mod.rs +++ b/interactive-clap-derive/src/derives/interactive_clap/mod.rs @@ -226,7 +226,7 @@ pub fn impl_interactive_clap(ast: &syn::DeriveInput) -> TokenStream { } /// these are common methods, reused for both the [structs] and `enums` derives -pub(crate) mod common_methods; +pub(super) mod common_methods; /// This module describes [`crate::InteractiveClap`] derive logic in case when [`syn::DeriveInput`] /// is a struct @@ -239,6 +239,8 @@ mod structs { #[doc = include_str!("../../../docs/structs_from_cli_trait_docstring.md")] pub mod from_cli_trait; + + pub(super) mod common_methods; } fn context_scope_for_struct( @@ -262,7 +264,7 @@ 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::common_methods::fields_with_subcommand::is_field_with_subcommand(field) + if !self::structs::common_methods::is_field_with_subcommand::predicate(field) && !self::common_methods::fields_with_subargs::is_field_with_subargs(field) { quote! { diff --git a/interactive-clap-derive/src/derives/interactive_clap/common_methods/fields_with_subcommand.rs b/interactive-clap-derive/src/derives/interactive_clap/structs/common_methods/is_field_with_subcommand.rs similarity index 86% rename from interactive-clap-derive/src/derives/interactive_clap/common_methods/fields_with_subcommand.rs rename to interactive-clap-derive/src/derives/interactive_clap/structs/common_methods/is_field_with_subcommand.rs index b1acab7..6b44b71 100644 --- a/interactive-clap-derive/src/derives/interactive_clap/common_methods/fields_with_subcommand.rs +++ b/interactive-clap-derive/src/derives/interactive_clap/structs/common_methods/is_field_with_subcommand.rs @@ -3,7 +3,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 { +pub fn predicate(field: &syn::Field) -> bool { if field.attrs.is_empty() { return false; } diff --git a/interactive-clap-derive/src/derives/interactive_clap/structs/common_methods/mod.rs b/interactive-clap-derive/src/derives/interactive_clap/structs/common_methods/mod.rs new file mode 100644 index 0000000..3d593a7 --- /dev/null +++ b/interactive-clap-derive/src/derives/interactive_clap/structs/common_methods/mod.rs @@ -0,0 +1 @@ +pub mod is_field_with_subcommand; diff --git a/interactive-clap-derive/src/derives/interactive_clap/structs/from_cli_trait.rs b/interactive-clap-derive/src/derives/interactive_clap/structs/from_cli_trait.rs index 5b605d2..0fffe23 100644 --- a/interactive-clap-derive/src/derives/interactive_clap/structs/from_cli_trait.rs +++ b/interactive-clap-derive/src/derives/interactive_clap/structs/from_cli_trait.rs @@ -5,6 +5,7 @@ use proc_macro_error::abort_call_site; use quote::{quote, ToTokens}; use syn; +use super::common_methods as structs_methods; use crate::derives::interactive_clap::common_methods; /// returns the whole result `TokenStream` of derive logic of containing module @@ -20,7 +21,7 @@ pub fn token_stream(ast: &syn::DeriveInput, fields: &syn::Fields) -> proc_macro2 let fields_without_subcommand_and_subargs = fields .iter() .filter(|field| { - !common_methods::fields_with_subcommand::is_field_with_subcommand(field) + !structs_methods::is_field_with_subcommand::predicate(field) && !common_methods::fields_with_subargs::is_field_with_subargs(field) }) .map(|field| { @@ -123,7 +124,7 @@ fn fields_value(field: &syn::Field) -> proc_macro2::TokenStream { }; let #ident_field = clap_variant.#ident_field.clone(); } - } else if !common_methods::fields_with_subcommand::is_field_with_subcommand(field) + } else if !structs_methods::is_field_with_subcommand::predicate(field) && !common_methods::fields_with_subargs::is_field_with_subargs(field) { quote! { diff --git a/interactive-clap-derive/src/derives/interactive_clap/structs/input_args_impl.rs b/interactive-clap-derive/src/derives/interactive_clap/structs/input_args_impl.rs index c7cbdad..ac75c1e 100644 --- a/interactive-clap-derive/src/derives/interactive_clap/structs/input_args_impl.rs +++ b/interactive-clap-derive/src/derives/interactive_clap/structs/input_args_impl.rs @@ -4,6 +4,7 @@ use proc_macro2::Span; use quote::quote; use syn; +use super::common_methods as structs_methods; use crate::derives::interactive_clap::common_methods; /// returns the whole result `TokenStream` of derive logic of containing module @@ -22,7 +23,7 @@ fn vec_fn_input_arg(ast: &syn::DeriveInput, fields: &syn::Fields) -> Vec Date: Fri, 24 Jan 2025 23:02:39 +0200 Subject: [PATCH 23/57] chore: move fields_with_subargs to structs::common_methods --- .../src/derives/interactive_clap/common_methods/mod.rs | 1 - interactive-clap-derive/src/derives/interactive_clap/mod.rs | 2 +- .../common_methods/is_field_with_subargs.rs} | 2 +- .../derives/interactive_clap/structs/common_methods/mod.rs | 1 + .../src/derives/interactive_clap/structs/from_cli_trait.rs | 4 ++-- 5 files changed, 5 insertions(+), 5 deletions(-) rename interactive-clap-derive/src/derives/interactive_clap/{common_methods/fields_with_subargs.rs => structs/common_methods/is_field_with_subargs.rs} (81%) diff --git a/interactive-clap-derive/src/derives/interactive_clap/common_methods/mod.rs b/interactive-clap-derive/src/derives/interactive_clap/common_methods/mod.rs index d0a9980..fcb28a9 100644 --- a/interactive-clap-derive/src/derives/interactive_clap/common_methods/mod.rs +++ b/interactive-clap-derive/src/derives/interactive_clap/common_methods/mod.rs @@ -1,6 +1,5 @@ pub mod choose_variant; pub mod fields_with_skip_default_input_arg; -pub mod fields_with_subargs; pub mod from_cli_for_enum; pub mod interactive_clap_attrs_context; pub mod skip_interactive_input; diff --git a/interactive-clap-derive/src/derives/interactive_clap/mod.rs b/interactive-clap-derive/src/derives/interactive_clap/mod.rs index 76e0b46..b1e5e11 100644 --- a/interactive-clap-derive/src/derives/interactive_clap/mod.rs +++ b/interactive-clap-derive/src/derives/interactive_clap/mod.rs @@ -265,7 +265,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::structs::common_methods::is_field_with_subcommand::predicate(field) - && !self::common_methods::fields_with_subargs::is_field_with_subargs(field) + && !self::structs::common_methods::is_field_with_subargs::predicate(field) { quote! { pub #ident_field: #ty diff --git a/interactive-clap-derive/src/derives/interactive_clap/common_methods/fields_with_subargs.rs b/interactive-clap-derive/src/derives/interactive_clap/structs/common_methods/is_field_with_subargs.rs similarity index 81% rename from interactive-clap-derive/src/derives/interactive_clap/common_methods/fields_with_subargs.rs rename to interactive-clap-derive/src/derives/interactive_clap/structs/common_methods/is_field_with_subargs.rs index 4e52f60..21eed65 100644 --- a/interactive-clap-derive/src/derives/interactive_clap/common_methods/fields_with_subargs.rs +++ b/interactive-clap-derive/src/derives/interactive_clap/structs/common_methods/is_field_with_subargs.rs @@ -2,7 +2,7 @@ extern crate proc_macro; use syn; -pub fn is_field_with_subargs(field: &syn::Field) -> bool { +pub fn predicate(field: &syn::Field) -> bool { if field.attrs.is_empty() { return false; } diff --git a/interactive-clap-derive/src/derives/interactive_clap/structs/common_methods/mod.rs b/interactive-clap-derive/src/derives/interactive_clap/structs/common_methods/mod.rs index 3d593a7..9bfc857 100644 --- a/interactive-clap-derive/src/derives/interactive_clap/structs/common_methods/mod.rs +++ b/interactive-clap-derive/src/derives/interactive_clap/structs/common_methods/mod.rs @@ -1 +1,2 @@ +pub mod is_field_with_subargs; pub mod is_field_with_subcommand; diff --git a/interactive-clap-derive/src/derives/interactive_clap/structs/from_cli_trait.rs b/interactive-clap-derive/src/derives/interactive_clap/structs/from_cli_trait.rs index 0fffe23..276d176 100644 --- a/interactive-clap-derive/src/derives/interactive_clap/structs/from_cli_trait.rs +++ b/interactive-clap-derive/src/derives/interactive_clap/structs/from_cli_trait.rs @@ -22,7 +22,7 @@ pub fn token_stream(ast: &syn::DeriveInput, fields: &syn::Fields) -> proc_macro2 .iter() .filter(|field| { !structs_methods::is_field_with_subcommand::predicate(field) - && !common_methods::fields_with_subargs::is_field_with_subargs(field) + && !structs_methods::is_field_with_subargs::predicate(field) }) .map(|field| { let ident_field = &field.clone().ident.expect("this field does not exist"); @@ -125,7 +125,7 @@ fn fields_value(field: &syn::Field) -> proc_macro2::TokenStream { let #ident_field = clap_variant.#ident_field.clone(); } } else if !structs_methods::is_field_with_subcommand::predicate(field) - && !common_methods::fields_with_subargs::is_field_with_subargs(field) + && !structs_methods::is_field_with_subargs::predicate(field) { quote! { if clap_variant.#ident_field.is_none() { From 8d6750dda6546e16f3c8926b9b969faf13d08372 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dj8yf0=CE=BCl?= Date: Fri, 24 Jan 2025 23:20:41 +0200 Subject: [PATCH 24/57] chore: move skip_interactive_input to structs::common_methods --- .../derives/interactive_clap/common_methods/mod.rs | 1 - .../src/derives/interactive_clap/mod.rs | 6 +++--- .../structs/common_field_methods/mod.rs | 3 +++ .../with_skip_interactive_input.rs} | 2 +- .../with_subargs.rs} | 0 .../with_subcommand.rs} | 0 .../interactive_clap/structs/common_methods/mod.rs | 2 -- .../interactive_clap/structs/from_cli_trait.rs | 12 ++++++------ .../interactive_clap/structs/input_args_impl.rs | 6 +++--- 9 files changed, 16 insertions(+), 16 deletions(-) create mode 100644 interactive-clap-derive/src/derives/interactive_clap/structs/common_field_methods/mod.rs rename interactive-clap-derive/src/derives/interactive_clap/{common_methods/skip_interactive_input.rs => structs/common_field_methods/with_skip_interactive_input.rs} (89%) rename interactive-clap-derive/src/derives/interactive_clap/structs/{common_methods/is_field_with_subargs.rs => common_field_methods/with_subargs.rs} (100%) rename interactive-clap-derive/src/derives/interactive_clap/structs/{common_methods/is_field_with_subcommand.rs => common_field_methods/with_subcommand.rs} (100%) delete mode 100644 interactive-clap-derive/src/derives/interactive_clap/structs/common_methods/mod.rs diff --git a/interactive-clap-derive/src/derives/interactive_clap/common_methods/mod.rs b/interactive-clap-derive/src/derives/interactive_clap/common_methods/mod.rs index fcb28a9..f25832e 100644 --- a/interactive-clap-derive/src/derives/interactive_clap/common_methods/mod.rs +++ b/interactive-clap-derive/src/derives/interactive_clap/common_methods/mod.rs @@ -2,4 +2,3 @@ pub mod choose_variant; pub mod fields_with_skip_default_input_arg; pub mod from_cli_for_enum; pub mod interactive_clap_attrs_context; -pub mod skip_interactive_input; diff --git a/interactive-clap-derive/src/derives/interactive_clap/mod.rs b/interactive-clap-derive/src/derives/interactive_clap/mod.rs index b1e5e11..5724813 100644 --- a/interactive-clap-derive/src/derives/interactive_clap/mod.rs +++ b/interactive-clap-derive/src/derives/interactive_clap/mod.rs @@ -240,7 +240,7 @@ mod structs { #[doc = include_str!("../../../docs/structs_from_cli_trait_docstring.md")] pub mod from_cli_trait; - pub(super) mod common_methods; + pub(super) mod common_field_methods; } fn context_scope_for_struct( @@ -264,8 +264,8 @@ 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::structs::common_methods::is_field_with_subcommand::predicate(field) - && !self::structs::common_methods::is_field_with_subargs::predicate(field) + if !self::structs::common_field_methods::with_subcommand::predicate(field) + && !self::structs::common_field_methods::with_subargs::predicate(field) { quote! { pub #ident_field: #ty diff --git a/interactive-clap-derive/src/derives/interactive_clap/structs/common_field_methods/mod.rs b/interactive-clap-derive/src/derives/interactive_clap/structs/common_field_methods/mod.rs new file mode 100644 index 0000000..7fad14e --- /dev/null +++ b/interactive-clap-derive/src/derives/interactive_clap/structs/common_field_methods/mod.rs @@ -0,0 +1,3 @@ +pub mod with_skip_interactive_input; +pub mod with_subargs; +pub mod with_subcommand; diff --git a/interactive-clap-derive/src/derives/interactive_clap/common_methods/skip_interactive_input.rs b/interactive-clap-derive/src/derives/interactive_clap/structs/common_field_methods/with_skip_interactive_input.rs similarity index 89% rename from interactive-clap-derive/src/derives/interactive_clap/common_methods/skip_interactive_input.rs rename to interactive-clap-derive/src/derives/interactive_clap/structs/common_field_methods/with_skip_interactive_input.rs index dc91e49..e68c5ae 100644 --- a/interactive-clap-derive/src/derives/interactive_clap/common_methods/skip_interactive_input.rs +++ b/interactive-clap-derive/src/derives/interactive_clap/structs/common_field_methods/with_skip_interactive_input.rs @@ -4,7 +4,7 @@ use syn; use crate::LONG_VEC_MUTLIPLE_OPT; -pub fn is_skip_interactive_input(field: &syn::Field) -> bool { +pub fn predicate(field: &syn::Field) -> bool { field .attrs .iter() diff --git a/interactive-clap-derive/src/derives/interactive_clap/structs/common_methods/is_field_with_subargs.rs b/interactive-clap-derive/src/derives/interactive_clap/structs/common_field_methods/with_subargs.rs similarity index 100% rename from interactive-clap-derive/src/derives/interactive_clap/structs/common_methods/is_field_with_subargs.rs rename to interactive-clap-derive/src/derives/interactive_clap/structs/common_field_methods/with_subargs.rs diff --git a/interactive-clap-derive/src/derives/interactive_clap/structs/common_methods/is_field_with_subcommand.rs b/interactive-clap-derive/src/derives/interactive_clap/structs/common_field_methods/with_subcommand.rs similarity index 100% rename from interactive-clap-derive/src/derives/interactive_clap/structs/common_methods/is_field_with_subcommand.rs rename to interactive-clap-derive/src/derives/interactive_clap/structs/common_field_methods/with_subcommand.rs diff --git a/interactive-clap-derive/src/derives/interactive_clap/structs/common_methods/mod.rs b/interactive-clap-derive/src/derives/interactive_clap/structs/common_methods/mod.rs deleted file mode 100644 index 9bfc857..0000000 --- a/interactive-clap-derive/src/derives/interactive_clap/structs/common_methods/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod is_field_with_subargs; -pub mod is_field_with_subcommand; diff --git a/interactive-clap-derive/src/derives/interactive_clap/structs/from_cli_trait.rs b/interactive-clap-derive/src/derives/interactive_clap/structs/from_cli_trait.rs index 276d176..d9c2703 100644 --- a/interactive-clap-derive/src/derives/interactive_clap/structs/from_cli_trait.rs +++ b/interactive-clap-derive/src/derives/interactive_clap/structs/from_cli_trait.rs @@ -5,7 +5,7 @@ use proc_macro_error::abort_call_site; use quote::{quote, ToTokens}; use syn; -use super::common_methods as structs_methods; +use super::common_field_methods as field_methods; use crate::derives::interactive_clap::common_methods; /// returns the whole result `TokenStream` of derive logic of containing module @@ -21,8 +21,8 @@ pub fn token_stream(ast: &syn::DeriveInput, fields: &syn::Fields) -> proc_macro2 let fields_without_subcommand_and_subargs = fields .iter() .filter(|field| { - !structs_methods::is_field_with_subcommand::predicate(field) - && !structs_methods::is_field_with_subargs::predicate(field) + !field_methods::with_subcommand::predicate(field) + && !field_methods::with_subargs::predicate(field) }) .map(|field| { let ident_field = &field.clone().ident.expect("this field does not exist"); @@ -103,7 +103,7 @@ fn fields_value(field: &syn::Field) -> proc_macro2::TokenStream { let ident_field = &field.clone().ident.expect("this field does not exist"); let fn_input_arg = syn::Ident::new(&format!("input_{}", &ident_field), Span::call_site()); if field.ty.to_token_stream().to_string() == "bool" - || common_methods::skip_interactive_input::is_skip_interactive_input(field) + || field_methods::with_skip_interactive_input::predicate(field) { quote! { let #ident_field = clap_variant.#ident_field.clone(); @@ -124,8 +124,8 @@ fn fields_value(field: &syn::Field) -> proc_macro2::TokenStream { }; let #ident_field = clap_variant.#ident_field.clone(); } - } else if !structs_methods::is_field_with_subcommand::predicate(field) - && !structs_methods::is_field_with_subargs::predicate(field) + } else if !field_methods::with_subcommand::predicate(field) + && !field_methods::with_subargs::predicate(field) { quote! { if clap_variant.#ident_field.is_none() { diff --git a/interactive-clap-derive/src/derives/interactive_clap/structs/input_args_impl.rs b/interactive-clap-derive/src/derives/interactive_clap/structs/input_args_impl.rs index ac75c1e..a69e7b3 100644 --- a/interactive-clap-derive/src/derives/interactive_clap/structs/input_args_impl.rs +++ b/interactive-clap-derive/src/derives/interactive_clap/structs/input_args_impl.rs @@ -4,7 +4,7 @@ use proc_macro2::Span; use quote::quote; use syn; -use super::common_methods as structs_methods; +use super::common_field_methods as field_methods; use crate::derives::interactive_clap::common_methods; /// returns the whole result `TokenStream` of derive logic of containing module @@ -23,7 +23,7 @@ fn vec_fn_input_arg(ast: &syn::DeriveInput, fields: &syn::Fields) -> Vec Vec Date: Sat, 25 Jan 2025 00:00:33 +0200 Subject: [PATCH 25/57] chore: extract to_interactive_clap_context_scope_trait module --- .../docs/structs_from_cli_trait_docstring.md | 6 ++- ...tive_clap_context_scope_trait_docstring.md | 26 +++++++++++ .../src/derives/interactive_clap/mod.rs | 46 +++---------------- ...to_interactive_clap_context_scope_trait.rs | 40 ++++++++++++++++ src/lib.rs | 1 + 5 files changed, 79 insertions(+), 40 deletions(-) create mode 100644 interactive-clap-derive/docs/structs_to_interactive_clap_context_scope_trait_docstring.md create mode 100644 interactive-clap-derive/src/derives/interactive_clap/structs/to_interactive_clap_context_scope_trait.rs diff --git a/interactive-clap-derive/docs/structs_from_cli_trait_docstring.md b/interactive-clap-derive/docs/structs_from_cli_trait_docstring.md index 68e4bcd..10e0398 100644 --- a/interactive-clap-derive/docs/structs_from_cli_trait_docstring.md +++ b/interactive-clap-derive/docs/structs_from_cli_trait_docstring.md @@ -3,6 +3,10 @@ This modules describes derive of `interactive_clap::FromCli` trait for `#name` struct, which happens during derive of [`crate::InteractiveClap`] for `#name` struct: +The implementation combines usages of all of [super::structs::to_cli_trait], [super::structs::input_args_impl], +[super::structs::to_interactive_clap_context_scope_trait] + + derive input `#name` ```rust,ignore @@ -56,7 +60,7 @@ impl interactive_clap::FromCli for #name { }; } let first_name = clap_variant.first_name.clone().expect("Unexpected error"); - let new_context_scope = InteractiveClapContextScopeForArgs { + let new_context_scope = InteractiveClapContextScopeFor#name { age: age.into(), first_name: first_name.into(), }; diff --git a/interactive-clap-derive/docs/structs_to_interactive_clap_context_scope_trait_docstring.md b/interactive-clap-derive/docs/structs_to_interactive_clap_context_scope_trait_docstring.md new file mode 100644 index 0000000..c4a8438 --- /dev/null +++ b/interactive-clap-derive/docs/structs_to_interactive_clap_context_scope_trait_docstring.md @@ -0,0 +1,26 @@ +`interactive_clap::ToInteractiveClapContextScope` derive + +This modules describes derive of `interactive_clap::ToInteractiveClapContextScope` trait for `#name` struct, +which happens during derive of [`crate::InteractiveClap`] for `#name` struct: + +derive input `#name` + +```rust,ignore +struct #name { + age: u64, + first_name: String, +} +``` + +gets transformed +=> + +```rust,ignore +impl #name pub struct InteractiveClapContextScopeFor#name { + pub age: u64, + pub first_name: String, +} +impl interactive_clap::ToInteractiveClapContextScope for #name { + type InteractiveClapContextScope = InteractiveClapContextScopeFor#name; +} +``` diff --git a/interactive-clap-derive/src/derives/interactive_clap/mod.rs b/interactive-clap-derive/src/derives/interactive_clap/mod.rs index 5724813..34b63a4 100644 --- a/interactive-clap-derive/src/derives/interactive_clap/mod.rs +++ b/interactive-clap-derive/src/derives/interactive_clap/mod.rs @@ -22,12 +22,8 @@ pub fn impl_interactive_clap(ast: &syn::DeriveInput) -> TokenStream { let input_args_impl_block = self::structs::input_args_impl::token_stream(ast, &fields); - let context_scope_fields = fields - .iter() - .map(context_scope_for_struct_field) - .filter(|token_stream| !token_stream.is_empty()) - .collect::>(); - let context_scope_for_struct = context_scope_for_struct(name, context_scope_fields); + let to_interactive_clap_context_scope_trait_block = + self::structs::to_interactive_clap_context_scope_trait::token_stream(ast, &fields); let clap_enum_for_named_arg = fields.iter().find_map(|field| { let ident_field = &field.clone().ident.expect("this field does not exist"); @@ -81,7 +77,7 @@ pub fn impl_interactive_clap(ast: &syn::DeriveInput) -> TokenStream { #input_args_impl_block - #context_scope_for_struct + #to_interactive_clap_context_scope_trait_block #from_cli_trait_block @@ -237,44 +233,16 @@ mod structs { #[doc = include_str!("../../../docs/structs_input_args_impl_docstring.md")] pub mod input_args_impl; + #[doc = include_str!("../../../docs/structs_to_interactive_clap_context_scope_trait_docstring.md")] + pub mod to_interactive_clap_context_scope_trait; + #[doc = include_str!("../../../docs/structs_from_cli_trait_docstring.md")] pub mod from_cli_trait; + /// these are common field methods, reused by other [structs](super::structs) submodules pub(super) mod common_field_methods; } -fn context_scope_for_struct( - name: &syn::Ident, - context_scope_fields: Vec, -) -> proc_macro2::TokenStream { - let interactive_clap_context_scope_for_struct = syn::Ident::new( - &format!("InteractiveClapContextScopeFor{}", &name), - Span::call_site(), - ); - quote! { - pub struct #interactive_clap_context_scope_for_struct { - #(#context_scope_fields,)* - } - impl interactive_clap::ToInteractiveClapContextScope for #name { - type InteractiveClapContextScope = #interactive_clap_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::structs::common_field_methods::with_subcommand::predicate(field) - && !self::structs::common_field_methods::with_subargs::predicate(field) - { - quote! { - pub #ident_field: #ty - } - } else { - quote!() - } -} - fn context_scope_for_enum(name: &syn::Ident) -> proc_macro2::TokenStream { let interactive_clap_context_scope_for_enum = syn::Ident::new( &format!("InteractiveClapContextScopeFor{}", &name), diff --git a/interactive-clap-derive/src/derives/interactive_clap/structs/to_interactive_clap_context_scope_trait.rs b/interactive-clap-derive/src/derives/interactive_clap/structs/to_interactive_clap_context_scope_trait.rs new file mode 100644 index 0000000..9da2d3a --- /dev/null +++ b/interactive-clap-derive/src/derives/interactive_clap/structs/to_interactive_clap_context_scope_trait.rs @@ -0,0 +1,40 @@ +use proc_macro2::Span; +use quote::quote; + +use super::common_field_methods as field_methods; + +/// returns the whole result `TokenStream` of derive logic of containing module +pub fn token_stream(ast: &syn::DeriveInput, fields: &syn::Fields) -> proc_macro2::TokenStream { + let name = &ast.ident; + let context_scope_fields = fields + .iter() + .map(field_transform) + .filter(|token_stream| !token_stream.is_empty()) + .collect::>(); + let interactive_clap_context_scope_for_struct = syn::Ident::new( + &format!("InteractiveClapContextScopeFor{}", &name), + Span::call_site(), + ); + quote! { + pub struct #interactive_clap_context_scope_for_struct { + #(#context_scope_fields,)* + } + impl interactive_clap::ToInteractiveClapContextScope for #name { + type InteractiveClapContextScope = #interactive_clap_context_scope_for_struct; + } + } +} + +fn field_transform(field: &syn::Field) -> proc_macro2::TokenStream { + let ident_field = &field.ident.clone().expect("this field does not exist"); + let ty = &field.ty; + if !field_methods::with_subcommand::predicate(field) + && !field_methods::with_subargs::predicate(field) + { + quote! { + pub #ident_field: #ty + } + } else { + quote!() + } +} diff --git a/src/lib.rs b/src/lib.rs index 2a2c3c3..657efa2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,6 +31,7 @@ impl ToCli for bool { type CliVariant = bool; } +// TODO: the trait can clearly be shortened/renamed to `ContextScope` pub trait ToInteractiveClapContextScope { type InteractiveClapContextScope; } From 77604e2d54cf49b277d7c69314da2143841692b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dj8yf0=CE=BCl?= Date: Sat, 25 Jan 2025 00:08:01 +0200 Subject: [PATCH 26/57] test: add test for named_arg attribute --- ..._struct__simple_struct_with_named_arg.snap | 98 +++++++++++++++++++ .../src/tests/test_simple_struct.rs | 14 +++ 2 files changed, 112 insertions(+) create mode 100644 interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__simple_struct_with_named_arg.snap diff --git a/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__simple_struct_with_named_arg.snap b/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__simple_struct_with_named_arg.snap new file mode 100644 index 0000000..0444a59 --- /dev/null +++ b/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__simple_struct_with_named_arg.snap @@ -0,0 +1,98 @@ +--- +source: interactive-clap-derive/src/tests/test_simple_struct.rs +expression: pretty_codegen(&interactive_clap_codegen) +--- +#[derive(Debug, Default, Clone, clap::Parser, interactive_clap::ToCliArgs)] +#[clap(author, version, about, long_about = None)] +pub struct CliAccount { + ///Specify a sender + #[clap(subcommand)] + pub account: Option, +} +impl interactive_clap::ToCli for Account { + type CliVariant = CliAccount; +} +impl Account { + pub fn try_parse() -> Result { + ::try_parse() + } + pub fn parse() -> CliAccount { + ::parse() + } + pub fn try_parse_from(itr: I) -> Result + where + I: ::std::iter::IntoIterator, + T: ::std::convert::Into<::std::ffi::OsString> + ::std::clone::Clone, + { + ::try_parse_from(itr) + } +} +impl From for CliAccount { + fn from(args: Account) -> Self { + Self { + account: Some(args.account.into()), + } + } +} +impl Account {} +pub struct InteractiveClapContextScopeForAccount {} +impl interactive_clap::ToInteractiveClapContextScope for Account { + type InteractiveClapContextScope = InteractiveClapContextScopeForAccount; +} +impl interactive_clap::FromCli for Account { + 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.clone().unwrap_or_default(); + let new_context_scope = InteractiveClapContextScopeForAccount { + }; + let optional_field = match clap_variant.account.take() { + Some(ClapNamedArgSenderForAccount::Account(cli_arg)) => Some(cli_arg), + None => None, + }; + match ::from_cli( + optional_field, + context.into(), + ) { + interactive_clap::ResultFromCli::Ok(cli_field) => { + clap_variant + .account = Some(ClapNamedArgSenderForAccount::Account(cli_field)); + } + interactive_clap::ResultFromCli::Cancel(optional_cli_field) => { + clap_variant + .account = optional_cli_field + .map(ClapNamedArgSenderForAccount::Account); + 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 + .account = optional_cli_field + .map(ClapNamedArgSenderForAccount::Account); + return interactive_clap::ResultFromCli::Err(Some(clap_variant), err); + } + }; + interactive_clap::ResultFromCli::Ok(clap_variant) + } +} +#[derive(Debug, Clone, clap::Parser, interactive_clap_derive::ToCliArgs)] +pub enum ClapNamedArgSenderForAccount { + ///Specify a sender + Account(::CliVariant), +} +impl From for ClapNamedArgSenderForAccount { + fn from(item: Sender) -> Self { + Self::Account(::CliVariant::from(item)) + } +} diff --git a/interactive-clap-derive/src/tests/test_simple_struct.rs b/interactive-clap-derive/src/tests/test_simple_struct.rs index 16b3c66..b542d62 100644 --- a/interactive-clap-derive/src/tests/test_simple_struct.rs +++ b/interactive-clap-derive/src/tests/test_simple_struct.rs @@ -17,6 +17,20 @@ fn test_simple_struct() { insta::assert_snapshot!(pretty_codegen(&to_cli_args_codegen)); } +#[test] +fn test_simple_struct_with_named_arg() { + let input = syn::parse_quote! { + struct Account { + #[interactive_clap(named_arg)] + ///Specify a sender + account: Sender, + } + }; + + let interactive_clap_codegen = crate::derives::interactive_clap::impl_interactive_clap(&input); + insta::assert_snapshot!(pretty_codegen(&interactive_clap_codegen)); +} + #[test] fn test_flag() { let input = syn::parse_quote! { From 060f38ce3d601a92b44416648a68cb966b4c2da4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dj8yf0=CE=BCl?= Date: Sat, 25 Jan 2025 00:33:55 +0200 Subject: [PATCH 27/57] chore: extract clap_enum_for_named_arg module --- .../docs/clap_enum_for_named_arg_docstring.md | 27 ++++++++ .../src/derives/interactive_clap/mod.rs | 53 ++-------------- .../structs/clap_enum_for_named_arg.rs | 62 +++++++++++++++++++ .../structs/from_cli_trait.rs | 2 - ..._struct__simple_struct_with_named_arg.snap | 24 +++---- .../src/tests/test_simple_struct.rs | 2 +- 6 files changed, 109 insertions(+), 61 deletions(-) create mode 100644 interactive-clap-derive/docs/clap_enum_for_named_arg_docstring.md create mode 100644 interactive-clap-derive/src/derives/interactive_clap/structs/clap_enum_for_named_arg.rs diff --git a/interactive-clap-derive/docs/clap_enum_for_named_arg_docstring.md b/interactive-clap-derive/docs/clap_enum_for_named_arg_docstring.md new file mode 100644 index 0000000..ea527f8 --- /dev/null +++ b/interactive-clap-derive/docs/clap_enum_for_named_arg_docstring.md @@ -0,0 +1,27 @@ +derive of helper enum for structs with `#[interactive_clap(named_arg)]` on fields + + +```rust,ignore +struct #name { + #[interactive_clap(named_arg)] + ///Specify a sender + field_name: Sender, +} +``` + +gets transformed +=> + +```rust,ignore +#[derive(Debug, Clone, clap::Parser, interactive_clap_derive::ToCliArgs)] +pub enum ClapNamedArgSenderFor#name { + ///Specify a sender + FieldName(::CliVariant), +} +impl From for ClapNamedArgSenderFor#name { + fn from(item: Sender) -> Self { + Self::FieldName(::CliVariant::from(item)) + } +} +``` + diff --git a/interactive-clap-derive/src/derives/interactive_clap/mod.rs b/interactive-clap-derive/src/derives/interactive_clap/mod.rs index 34b63a4..e3fde95 100644 --- a/interactive-clap-derive/src/derives/interactive_clap/mod.rs +++ b/interactive-clap-derive/src/derives/interactive_clap/mod.rs @@ -25,52 +25,8 @@ pub fn impl_interactive_clap(ast: &syn::DeriveInput) -> TokenStream { let to_interactive_clap_context_scope_trait_block = self::structs::to_interactive_clap_context_scope_trait::token_stream(ast, &fields); - 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() == *"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)) - } - } - } - }) - .next() - }) - .unwrap_or(quote!()); + let clap_enum_for_named_arg_block = + self::structs::clap_enum_for_named_arg::token_stream(ast, &fields); quote! { #to_cli_trait_block @@ -81,7 +37,7 @@ pub fn impl_interactive_clap(ast: &syn::DeriveInput) -> TokenStream { #from_cli_trait_block - #clap_enum_for_named_arg + #clap_enum_for_named_arg_block } } syn::Data::Enum(syn::DataEnum { variants, .. }) => { @@ -239,6 +195,9 @@ mod structs { #[doc = include_str!("../../../docs/structs_from_cli_trait_docstring.md")] pub mod from_cli_trait; + #[doc = include_str!("../../../docs/clap_enum_for_named_arg_docstring.md")] + pub mod clap_enum_for_named_arg; + /// these are common field methods, reused by other [structs](super::structs) submodules pub(super) mod common_field_methods; } diff --git a/interactive-clap-derive/src/derives/interactive_clap/structs/clap_enum_for_named_arg.rs b/interactive-clap-derive/src/derives/interactive_clap/structs/clap_enum_for_named_arg.rs new file mode 100644 index 0000000..0de5650 --- /dev/null +++ b/interactive-clap-derive/src/derives/interactive_clap/structs/clap_enum_for_named_arg.rs @@ -0,0 +1,62 @@ +use proc_macro2::Span; +use proc_macro_error::abort_call_site; +use quote::{quote, ToTokens}; +use syn; + +/// returns the whole result `TokenStream` of derive logic of containing module +pub fn token_stream(ast: &syn::DeriveInput, fields: &syn::Fields) -> proc_macro2::TokenStream { + let name = &ast.ident; + fields + .iter() + .find_map(|field| field_transform(name, field)) + .unwrap_or(quote!()) +} + +fn field_transform(name: &syn::Ident, field: &syn::Field) -> Option { + 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() == *"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)) + } + } + } + }) + .next() +} diff --git a/interactive-clap-derive/src/derives/interactive_clap/structs/from_cli_trait.rs b/interactive-clap-derive/src/derives/interactive_clap/structs/from_cli_trait.rs index d9c2703..afcf528 100644 --- a/interactive-clap-derive/src/derives/interactive_clap/structs/from_cli_trait.rs +++ b/interactive-clap-derive/src/derives/interactive_clap/structs/from_cli_trait.rs @@ -1,5 +1,3 @@ -extern crate proc_macro; - use proc_macro2::Span; use proc_macro_error::abort_call_site; use quote::{quote, ToTokens}; diff --git a/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__simple_struct_with_named_arg.snap b/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__simple_struct_with_named_arg.snap index 0444a59..8f07140 100644 --- a/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__simple_struct_with_named_arg.snap +++ b/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__simple_struct_with_named_arg.snap @@ -7,7 +7,7 @@ expression: pretty_codegen(&interactive_clap_codegen) pub struct CliAccount { ///Specify a sender #[clap(subcommand)] - pub account: Option, + pub field_name: Option, } impl interactive_clap::ToCli for Account { type CliVariant = CliAccount; @@ -30,7 +30,7 @@ impl Account { impl From for CliAccount { fn from(args: Account) -> Self { Self { - account: Some(args.account.into()), + field_name: Some(args.field_name.into()), } } } @@ -55,8 +55,8 @@ impl interactive_clap::FromCli for Account { let mut clap_variant = optional_clap_variant.clone().unwrap_or_default(); let new_context_scope = InteractiveClapContextScopeForAccount { }; - let optional_field = match clap_variant.account.take() { - Some(ClapNamedArgSenderForAccount::Account(cli_arg)) => Some(cli_arg), + let optional_field = match clap_variant.field_name.take() { + Some(ClapNamedArgSenderForAccount::FieldName(cli_arg)) => Some(cli_arg), None => None, }; match ::from_cli( @@ -65,12 +65,14 @@ impl interactive_clap::FromCli for Account { ) { interactive_clap::ResultFromCli::Ok(cli_field) => { clap_variant - .account = Some(ClapNamedArgSenderForAccount::Account(cli_field)); + .field_name = Some( + ClapNamedArgSenderForAccount::FieldName(cli_field), + ); } interactive_clap::ResultFromCli::Cancel(optional_cli_field) => { clap_variant - .account = optional_cli_field - .map(ClapNamedArgSenderForAccount::Account); + .field_name = optional_cli_field + .map(ClapNamedArgSenderForAccount::FieldName); return interactive_clap::ResultFromCli::Cancel(Some(clap_variant)); } interactive_clap::ResultFromCli::Back => { @@ -78,8 +80,8 @@ impl interactive_clap::FromCli for Account { } interactive_clap::ResultFromCli::Err(optional_cli_field, err) => { clap_variant - .account = optional_cli_field - .map(ClapNamedArgSenderForAccount::Account); + .field_name = optional_cli_field + .map(ClapNamedArgSenderForAccount::FieldName); return interactive_clap::ResultFromCli::Err(Some(clap_variant), err); } }; @@ -89,10 +91,10 @@ impl interactive_clap::FromCli for Account { #[derive(Debug, Clone, clap::Parser, interactive_clap_derive::ToCliArgs)] pub enum ClapNamedArgSenderForAccount { ///Specify a sender - Account(::CliVariant), + FieldName(::CliVariant), } impl From for ClapNamedArgSenderForAccount { fn from(item: Sender) -> Self { - Self::Account(::CliVariant::from(item)) + Self::FieldName(::CliVariant::from(item)) } } diff --git a/interactive-clap-derive/src/tests/test_simple_struct.rs b/interactive-clap-derive/src/tests/test_simple_struct.rs index b542d62..d228425 100644 --- a/interactive-clap-derive/src/tests/test_simple_struct.rs +++ b/interactive-clap-derive/src/tests/test_simple_struct.rs @@ -23,7 +23,7 @@ fn test_simple_struct_with_named_arg() { struct Account { #[interactive_clap(named_arg)] ///Specify a sender - account: Sender, + field_name: Sender, } }; From 1db0c1203fdbd980a5a635ec40286c6d6e81ef13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dj8yf0=CE=BCl?= Date: Sat, 25 Jan 2025 00:48:51 +0200 Subject: [PATCH 28/57] chore: overall structs derive layout established --- .../src/derives/interactive_clap/mod.rs | 76 +++++++++++-------- ...amed_arg.rs => clap_for_named_arg_enum.rs} | 0 2 files changed, 45 insertions(+), 31 deletions(-) rename interactive-clap-derive/src/derives/interactive_clap/structs/{clap_enum_for_named_arg.rs => clap_for_named_arg_enum.rs} (100%) diff --git a/interactive-clap-derive/src/derives/interactive_clap/mod.rs b/interactive-clap-derive/src/derives/interactive_clap/mod.rs index e3fde95..489731f 100644 --- a/interactive-clap-derive/src/derives/interactive_clap/mod.rs +++ b/interactive-clap-derive/src/derives/interactive_clap/mod.rs @@ -15,30 +15,7 @@ pub fn impl_interactive_clap(ast: &syn::DeriveInput) -> TokenStream { syn::Data::Struct(data_struct) => { let fields = data_struct.fields.clone(); - let to_cli_trait_block = - self::structs::to_cli_trait::token_stream(name, cli_name, &fields); - - let from_cli_trait_block = self::structs::from_cli_trait::token_stream(ast, &fields); - - let input_args_impl_block = self::structs::input_args_impl::token_stream(ast, &fields); - - let to_interactive_clap_context_scope_trait_block = - self::structs::to_interactive_clap_context_scope_trait::token_stream(ast, &fields); - - let clap_enum_for_named_arg_block = - self::structs::clap_enum_for_named_arg::token_stream(ast, &fields); - - quote! { - #to_cli_trait_block - - #input_args_impl_block - - #to_interactive_clap_context_scope_trait_block - - #from_cli_trait_block - - #clap_enum_for_named_arg_block - } + self::structs::token_stream(name, cli_name, ast, &fields) } syn::Data::Enum(syn::DataEnum { variants, .. }) => { let enum_variants = variants.iter().map(|variant| { @@ -180,23 +157,60 @@ pub fn impl_interactive_clap(ast: &syn::DeriveInput) -> TokenStream { /// these are common methods, reused for both the [structs] and `enums` derives pub(super) mod common_methods; -/// This module describes [`crate::InteractiveClap`] derive logic in case when [`syn::DeriveInput`] -/// is a struct +/** This module describes [`crate::InteractiveClap`] derive logic in case when [`syn::DeriveInput`] +is a struct + +The structure of produced derive output is as follows, where code blocks are generated by +submodules with corresponding names: + +```rust,ignore +quote::quote! { + #to_cli_trait_block + #input_args_impl_block + #to_interactive_clap_context_scope_trait_block + #from_cli_trait_block + #clap_for_named_arg_enum_block +} +``` +*/ mod structs { + /// returns the whole result `TokenStream` of derive logic of containing module + pub fn token_stream( + name: &syn::Ident, + cli_name: &syn::Ident, + ast: &syn::DeriveInput, + fields: &syn::Fields, + ) -> proc_macro2::TokenStream { + let to_cli_trait_block = to_cli_trait::token_stream(name, cli_name, &fields); + let from_cli_trait_block = from_cli_trait::token_stream(ast, &fields); + let input_args_impl_block = input_args_impl::token_stream(ast, &fields); + let to_interactive_clap_context_scope_trait_block = + to_interactive_clap_context_scope_trait::token_stream(ast, &fields); + let clap_for_named_arg_enum_block = clap_for_named_arg_enum::token_stream(ast, &fields); + + quote::quote! { + #to_cli_trait_block + #input_args_impl_block + #to_interactive_clap_context_scope_trait_block + #from_cli_trait_block + #clap_for_named_arg_enum_block + } + } + #[doc = include_str!("../../../docs/structs_to_cli_trait_docstring.md")] - pub mod to_cli_trait; + mod to_cli_trait; #[doc = include_str!("../../../docs/structs_input_args_impl_docstring.md")] - pub mod input_args_impl; + mod input_args_impl; #[doc = include_str!("../../../docs/structs_to_interactive_clap_context_scope_trait_docstring.md")] - pub mod to_interactive_clap_context_scope_trait; + mod to_interactive_clap_context_scope_trait; #[doc = include_str!("../../../docs/structs_from_cli_trait_docstring.md")] - pub mod from_cli_trait; + mod from_cli_trait; #[doc = include_str!("../../../docs/clap_enum_for_named_arg_docstring.md")] - pub mod clap_enum_for_named_arg; + mod clap_for_named_arg_enum; /// these are common field methods, reused by other [structs](super::structs) submodules pub(super) mod common_field_methods; diff --git a/interactive-clap-derive/src/derives/interactive_clap/structs/clap_enum_for_named_arg.rs b/interactive-clap-derive/src/derives/interactive_clap/structs/clap_for_named_arg_enum.rs similarity index 100% rename from interactive-clap-derive/src/derives/interactive_clap/structs/clap_enum_for_named_arg.rs rename to interactive-clap-derive/src/derives/interactive_clap/structs/clap_for_named_arg_enum.rs From 6bc76721eadcaf9a2582fa905a692efc6175b7a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dj8yf0=CE=BCl?= Date: Sat, 25 Jan 2025 00:59:59 +0200 Subject: [PATCH 29/57] chore: tidy + clippy --- .../src/derives/interactive_clap/mod.rs | 21 +++-- .../structs/input_args_impl.rs | 2 +- ...simple_struct__doc_comments_propagate.snap | 82 ++++--------------- .../src/tests/test_simple_struct.rs | 12 +++ 4 files changed, 38 insertions(+), 79 deletions(-) diff --git a/interactive-clap-derive/src/derives/interactive_clap/mod.rs b/interactive-clap-derive/src/derives/interactive_clap/mod.rs index 489731f..45d77f3 100644 --- a/interactive-clap-derive/src/derives/interactive_clap/mod.rs +++ b/interactive-clap-derive/src/derives/interactive_clap/mod.rs @@ -181,19 +181,18 @@ mod structs { ast: &syn::DeriveInput, fields: &syn::Fields, ) -> proc_macro2::TokenStream { - let to_cli_trait_block = to_cli_trait::token_stream(name, cli_name, &fields); - let from_cli_trait_block = from_cli_trait::token_stream(ast, &fields); - let input_args_impl_block = input_args_impl::token_stream(ast, &fields); - let to_interactive_clap_context_scope_trait_block = - to_interactive_clap_context_scope_trait::token_stream(ast, &fields); - let clap_for_named_arg_enum_block = clap_for_named_arg_enum::token_stream(ast, &fields); + let b1 = to_cli_trait::token_stream(name, cli_name, fields); + let b2 = input_args_impl::token_stream(ast, fields); + let b3 = to_interactive_clap_context_scope_trait::token_stream(ast, fields); + let b4 = from_cli_trait::token_stream(ast, fields); + let b5 = clap_for_named_arg_enum::token_stream(ast, fields); quote::quote! { - #to_cli_trait_block - #input_args_impl_block - #to_interactive_clap_context_scope_trait_block - #from_cli_trait_block - #clap_for_named_arg_enum_block + #b1 + #b2 + #b3 + #b4 + #b5 } } diff --git a/interactive-clap-derive/src/derives/interactive_clap/structs/input_args_impl.rs b/interactive-clap-derive/src/derives/interactive_clap/structs/input_args_impl.rs index a69e7b3..6adffc2 100644 --- a/interactive-clap-derive/src/derives/interactive_clap/structs/input_args_impl.rs +++ b/interactive-clap-derive/src/derives/interactive_clap/structs/input_args_impl.rs @@ -10,7 +10,7 @@ use crate::derives::interactive_clap::common_methods; /// returns the whole result `TokenStream` of derive logic of containing module pub fn token_stream(ast: &syn::DeriveInput, fields: &syn::Fields) -> proc_macro2::TokenStream { let name = &ast.ident; - let vec_fn_input_arg = vec_fn_input_arg(ast, &fields); + let vec_fn_input_arg = vec_fn_input_arg(ast, fields); quote! { impl #name { #(#vec_fn_input_arg)* diff --git a/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__doc_comments_propagate.snap b/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__doc_comments_propagate.snap index d46ec0a..2a6c7a6 100644 --- a/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__doc_comments_propagate.snap +++ b/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__doc_comments_propagate.snap @@ -9,13 +9,20 @@ pub struct CliArgs { /// /// a longer paragraph, describing the usage and stuff with first field's /// awarenes of its possible applications + #[clap(long)] pub first_field: Option<::CliVariant>, /// short second field description /// /// a longer paragraph, describing the usage and stuff with second field's /// awareness of its possible applications - #[clap(verbatim_doc_comment)] + #[clap(long, verbatim_doc_comment)] pub second_field: Option<::CliVariant>, + /// short third field description + /// + /// a longer paragraph, describing the usage and stuff with third field's + /// awareness of its possible applications + #[clap(long, verbatim_doc_comment)] + pub third_field: bool, } impl interactive_clap::ToCli for Args { type CliVariant = CliArgs; @@ -40,52 +47,15 @@ impl From for CliArgs { Self { first_field: Some(args.first_field.into()), second_field: Some(args.second_field.into()), + third_field: args.third_field.into(), } } } -impl Args { - fn input_first_field(_context: &()) -> color_eyre::eyre::Result> { - match inquire::CustomType::new( - concat!( - r" short first field description", r"", - r" a longer paragraph, describing the usage and stuff with first field's", - r" awarenes of its possible applications", - ) - .trim(), - ) - .prompt() - { - Ok(value) => Ok(Some(value)), - Err( - inquire::error::InquireError::OperationCanceled - | inquire::error::InquireError::OperationInterrupted, - ) => Ok(None), - Err(err) => Err(err.into()), - } - } - fn input_second_field(_context: &()) -> color_eyre::eyre::Result> { - match inquire::CustomType::new( - concat!( - r" short second field description", r"", - r" a longer paragraph, describing the usage and stuff with second field's", - r" awareness of its possible applications", - ) - .trim(), - ) - .prompt() - { - Ok(value) => Ok(Some(value)), - Err( - inquire::error::InquireError::OperationCanceled - | inquire::error::InquireError::OperationInterrupted, - ) => Ok(None), - Err(err) => Err(err.into()), - } - } -} +impl Args {} pub struct InteractiveClapContextScopeForArgs { pub first_field: u64, pub second_field: String, + pub third_field: bool, } impl interactive_clap::ToInteractiveClapContextScope for Args { type InteractiveClapContextScope = InteractiveClapContextScopeForArgs; @@ -104,35 +74,13 @@ impl interactive_clap::FromCli for Args { Self: Sized + interactive_clap::ToCli, { let mut clap_variant = optional_clap_variant.clone().unwrap_or_default(); - if clap_variant.first_field.is_none() { - clap_variant - .first_field = match Self::input_first_field(&context) { - Ok(Some(first_field)) => Some(first_field), - Ok(None) => { - return interactive_clap::ResultFromCli::Cancel(Some(clap_variant)); - } - Err(err) => { - return interactive_clap::ResultFromCli::Err(Some(clap_variant), err); - } - }; - } - let first_field = clap_variant.first_field.clone().expect("Unexpected error"); - if clap_variant.second_field.is_none() { - clap_variant - .second_field = match Self::input_second_field(&context) { - Ok(Some(second_field)) => Some(second_field), - Ok(None) => { - return interactive_clap::ResultFromCli::Cancel(Some(clap_variant)); - } - Err(err) => { - return interactive_clap::ResultFromCli::Err(Some(clap_variant), err); - } - }; - } - let second_field = clap_variant.second_field.clone().expect("Unexpected error"); + let first_field = clap_variant.first_field.clone(); + let second_field = clap_variant.second_field.clone(); + let third_field = clap_variant.third_field.clone(); let new_context_scope = InteractiveClapContextScopeForArgs { first_field: first_field.into(), second_field: second_field.into(), + third_field: third_field.into(), }; interactive_clap::ResultFromCli::Ok(clap_variant) } diff --git a/interactive-clap-derive/src/tests/test_simple_struct.rs b/interactive-clap-derive/src/tests/test_simple_struct.rs index d228425..dd162c1 100644 --- a/interactive-clap-derive/src/tests/test_simple_struct.rs +++ b/interactive-clap-derive/src/tests/test_simple_struct.rs @@ -112,13 +112,25 @@ fn test_doc_comments_propagate() { /// /// a longer paragraph, describing the usage and stuff with first field's /// awarenes of its possible applications + #[interactive_clap(long)] + #[interactive_clap(skip_interactive_input)] first_field: u64, /// short second field description /// /// a longer paragraph, describing the usage and stuff with second field's /// awareness of its possible applications + #[interactive_clap(long)] + #[interactive_clap(skip_interactive_input)] #[interactive_clap(verbatim_doc_comment)] second_field: String, + /// short third field description + /// + /// a longer paragraph, describing the usage and stuff with third field's + /// awareness of its possible applications + #[interactive_clap(long)] + #[interactive_clap(skip_interactive_input)] + #[interactive_clap(verbatim_doc_comment)] + third_field: bool, } }; From 48f96f63795731156b0cef792f69cfb35784e1d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dj8yf0=CE=BCl?= Date: Mon, 3 Feb 2025 20:37:31 +0200 Subject: [PATCH 30/57] test: add bug_fix_to_cli_args test --- ...ruct__bug_fix_of_to_cli_args_derive-2.snap | 13 +++ ...struct__bug_fix_of_to_cli_args_derive.snap | 91 +++++++++++++++++++ .../src/tests/test_simple_struct.rs | 24 +++++ 3 files changed, 128 insertions(+) create mode 100644 interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__bug_fix_of_to_cli_args_derive-2.snap create mode 100644 interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__bug_fix_of_to_cli_args_derive.snap diff --git a/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__bug_fix_of_to_cli_args_derive-2.snap b/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__bug_fix_of_to_cli_args_derive-2.snap new file mode 100644 index 0000000..5bc3547 --- /dev/null +++ b/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__bug_fix_of_to_cli_args_derive-2.snap @@ -0,0 +1,13 @@ +--- +source: interactive-clap-derive/src/tests/test_simple_struct.rs +expression: pretty_codegen(&to_cli_args_codegen) +--- +impl interactive_clap::ToCliArgs for CliViewAccountSummary { + fn to_cli_args(&self) -> std::collections::VecDeque { + let mut args = std::collections::VecDeque::new(); + if let Some(arg) = &self.account_id { + args.push_front(arg.to_string()) + } + args + } +} diff --git a/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__bug_fix_of_to_cli_args_derive.snap b/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__bug_fix_of_to_cli_args_derive.snap new file mode 100644 index 0000000..0dbe546 --- /dev/null +++ b/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__bug_fix_of_to_cli_args_derive.snap @@ -0,0 +1,91 @@ +--- +source: interactive-clap-derive/src/tests/test_simple_struct.rs +expression: pretty_codegen(&interactive_clap_codegen) +--- +#[derive(Debug, Default, Clone, clap::Parser, interactive_clap::ToCliArgs)] +#[clap(author, version, about, long_about = None)] +pub struct CliViewAccountSummary { + pub account_id: Option< + ::CliVariant, + >, +} +impl interactive_clap::ToCli for ViewAccountSummary { + type CliVariant = CliViewAccountSummary; +} +pub struct InteractiveClapContextScopeForViewAccountSummary { + pub account_id: crate::types::account_id::AccountId, +} +impl interactive_clap::ToInteractiveClapContextScope for ViewAccountSummary { + type InteractiveClapContextScope = InteractiveClapContextScopeForViewAccountSummary; +} +impl interactive_clap::FromCli for ViewAccountSummary { + 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.clone().unwrap_or_default(); + if clap_variant.account_id.is_none() { + clap_variant + .account_id = match Self::input_account_id(&context) { + Ok(Some(account_id)) => Some(account_id), + Ok(None) => { + return interactive_clap::ResultFromCli::Cancel(Some(clap_variant)); + } + Err(err) => { + return interactive_clap::ResultFromCli::Err(Some(clap_variant), err); + } + }; + } + let account_id = clap_variant.account_id.clone().expect("Unexpected error"); + let new_context_scope = InteractiveClapContextScopeForViewAccountSummary { + account_id: account_id.into(), + }; + interactive_clap::ResultFromCli::Ok(clap_variant) + } +} +impl ViewAccountSummary { + fn input_account_id( + _context: &(), + ) -> color_eyre::eyre::Result> { + match inquire::CustomType::new( + concat!(r" What Account ID do you need to view?",).trim(), + ) + .prompt() + { + Ok(value) => Ok(Some(value)), + Err( + inquire::error::InquireError::OperationCanceled + | inquire::error::InquireError::OperationInterrupted, + ) => Ok(None), + Err(err) => Err(err.into()), + } + } + pub fn try_parse() -> Result { + ::try_parse() + } + pub fn parse() -> CliViewAccountSummary { + ::parse() + } + pub fn try_parse_from(itr: I) -> Result + where + I: ::std::iter::IntoIterator, + T: ::std::convert::Into<::std::ffi::OsString> + ::std::clone::Clone, + { + ::try_parse_from(itr) + } +} +impl From for CliViewAccountSummary { + fn from(args: ViewAccountSummary) -> Self { + Self { + account_id: Some(args.account_id.into()), + } + } +} diff --git a/interactive-clap-derive/src/tests/test_simple_struct.rs b/interactive-clap-derive/src/tests/test_simple_struct.rs index dd162c1..5858a2c 100644 --- a/interactive-clap-derive/src/tests/test_simple_struct.rs +++ b/interactive-clap-derive/src/tests/test_simple_struct.rs @@ -31,6 +31,30 @@ fn test_simple_struct_with_named_arg() { insta::assert_snapshot!(pretty_codegen(&interactive_clap_codegen)); } +#[test] +fn test_bug_fix_of_to_cli_args_derive() { + let input = syn::parse_quote! { + pub struct ViewAccountSummary { + /// What Account ID do you need to view? + account_id: crate::types::account_id::AccountId, + } + }; + + let interactive_clap_codegen = crate::derives::interactive_clap::impl_interactive_clap(&input); + insta::assert_snapshot!(pretty_codegen(&interactive_clap_codegen)); + + let input = syn::parse_quote! { + pub struct CliViewAccountSummary { + pub account_id: Option< + ::CliVariant, + >, + } + }; + + let to_cli_args_codegen = crate::derives::to_cli_args::impl_to_cli_args(&input); + insta::assert_snapshot!(pretty_codegen(&to_cli_args_codegen)); +} + #[test] fn test_flag() { let input = syn::parse_quote! { From 19b20993c19a13d79eab7696d1be6f0f7805fe02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dj8yf0=CE=BCl?= Date: Mon, 3 Feb 2025 20:51:36 +0200 Subject: [PATCH 31/57] test: change order of fragments in test_bug_fix_of_to_cli_args_derive snapshot, generated on master --- ...struct__bug_fix_of_to_cli_args_derive.snap | 79 ++++++++++--------- 1 file changed, 41 insertions(+), 38 deletions(-) diff --git a/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__bug_fix_of_to_cli_args_derive.snap b/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__bug_fix_of_to_cli_args_derive.snap index 0dbe546..fe5efde 100644 --- a/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__bug_fix_of_to_cli_args_derive.snap +++ b/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__bug_fix_of_to_cli_args_derive.snap @@ -5,6 +5,7 @@ expression: pretty_codegen(&interactive_clap_codegen) #[derive(Debug, Default, Clone, clap::Parser, interactive_clap::ToCliArgs)] #[clap(author, version, about, long_about = None)] pub struct CliViewAccountSummary { + /// What Account ID do you need to view? pub account_id: Option< ::CliVariant, >, @@ -12,6 +13,46 @@ pub struct CliViewAccountSummary { impl interactive_clap::ToCli for ViewAccountSummary { type CliVariant = CliViewAccountSummary; } +impl ViewAccountSummary { + pub fn try_parse() -> Result { + ::try_parse() + } + pub fn parse() -> CliViewAccountSummary { + ::parse() + } + pub fn try_parse_from(itr: I) -> Result + where + I: ::std::iter::IntoIterator, + T: ::std::convert::Into<::std::ffi::OsString> + ::std::clone::Clone, + { + ::try_parse_from(itr) + } +} +impl From for CliViewAccountSummary { + fn from(args: ViewAccountSummary) -> Self { + Self { + account_id: Some(args.account_id.into()), + } + } +} +impl ViewAccountSummary { + fn input_account_id( + _context: &(), + ) -> color_eyre::eyre::Result> { + match inquire::CustomType::new( + concat!(r" What Account ID do you need to view?",).trim(), + ) + .prompt() + { + Ok(value) => Ok(Some(value)), + Err( + inquire::error::InquireError::OperationCanceled + | inquire::error::InquireError::OperationInterrupted, + ) => Ok(None), + Err(err) => Err(err.into()), + } + } +} pub struct InteractiveClapContextScopeForViewAccountSummary { pub account_id: crate::types::account_id::AccountId, } @@ -51,41 +92,3 @@ impl interactive_clap::FromCli for ViewAccountSummary { interactive_clap::ResultFromCli::Ok(clap_variant) } } -impl ViewAccountSummary { - fn input_account_id( - _context: &(), - ) -> color_eyre::eyre::Result> { - match inquire::CustomType::new( - concat!(r" What Account ID do you need to view?",).trim(), - ) - .prompt() - { - Ok(value) => Ok(Some(value)), - Err( - inquire::error::InquireError::OperationCanceled - | inquire::error::InquireError::OperationInterrupted, - ) => Ok(None), - Err(err) => Err(err.into()), - } - } - pub fn try_parse() -> Result { - ::try_parse() - } - pub fn parse() -> CliViewAccountSummary { - ::parse() - } - pub fn try_parse_from(itr: I) -> Result - where - I: ::std::iter::IntoIterator, - T: ::std::convert::Into<::std::ffi::OsString> + ::std::clone::Clone, - { - ::try_parse_from(itr) - } -} -impl From for CliViewAccountSummary { - fn from(args: ViewAccountSummary) -> Self { - Self { - account_id: Some(args.account_id.into()), - } - } -} From 29c9aeaf7f219a7b7cc0fbaa8430d4586190d84d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dj8yf0=CE=BCl?= Date: Mon, 3 Feb 2025 20:54:15 +0200 Subject: [PATCH 32/57] test: add doc comment to intermediate struct field, which causes ToCliArgs derive bug --- interactive-clap-derive/src/tests/test_simple_struct.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/interactive-clap-derive/src/tests/test_simple_struct.rs b/interactive-clap-derive/src/tests/test_simple_struct.rs index 5858a2c..037576f 100644 --- a/interactive-clap-derive/src/tests/test_simple_struct.rs +++ b/interactive-clap-derive/src/tests/test_simple_struct.rs @@ -45,6 +45,7 @@ fn test_bug_fix_of_to_cli_args_derive() { let input = syn::parse_quote! { pub struct CliViewAccountSummary { + /// What Account ID do you need to view? pub account_id: Option< ::CliVariant, >, From c66953e9838f4bf5bcc689a0d90e3ea66a1da39e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dj8yf0=CE=BCl?= Date: Mon, 3 Feb 2025 21:46:54 +0200 Subject: [PATCH 33/57] fix: bug with to_cli_args command printing --- .../methods/interactive_clap_attrs_cli_field.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) 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 6f93dcc..053ae77 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 @@ -18,7 +18,12 @@ impl InteractiveClapAttrsCliField { let mut named_args = quote!(); let mut unnamed_args = quote!(); - if field.attrs.is_empty() { + if !field.attrs.iter().any(|attr| attr.path.is_ident("clap")) { + // BUGFIX: changed when this branch is being taken + // from: field attributes are empty + // to: there're no field attributes with `clap` identificator + // + // in order to allow `doc` attributes args_without_attrs = quote! { if let Some(arg) = &self.#ident_field { args.push_front(arg.to_string()) From 424cef0576878aa002329bfa9b90d4267a1e5123 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dj8yf0=CE=BCl?= Date: Tue, 4 Feb 2025 22:09:01 +0200 Subject: [PATCH 34/57] test: ignore snapshots for test_simple_struct --- ...__test_simple_struct__simple_struct-2.snap | 20 --- ...ts__test_simple_struct__simple_struct.snap | 139 ------------------ .../src/tests/test_simple_struct.rs | 1 + 3 files changed, 1 insertion(+), 159 deletions(-) delete mode 100644 interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__simple_struct-2.snap delete mode 100644 interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__simple_struct.snap diff --git a/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__simple_struct-2.snap b/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__simple_struct-2.snap deleted file mode 100644 index 70c84fd..0000000 --- a/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__simple_struct-2.snap +++ /dev/null @@ -1,20 +0,0 @@ ---- -source: interactive-clap-derive/src/tests/test_simple_struct.rs -expression: pretty_codegen(&to_cli_args_codegen) ---- -impl interactive_clap::ToCliArgs for Args { - fn to_cli_args(&self) -> std::collections::VecDeque { - let mut args = std::collections::VecDeque::new(); - if let Some(arg) = &self.second_name { - args.push_front(arg.to_string()) - } - if let Some(arg) = &self.first_name { - args.push_front(arg.to_string()) - } - if let Some(arg) = &self.age { - args.push_front(arg.to_string()) - } - args - } -} - diff --git a/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__simple_struct.snap b/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__simple_struct.snap deleted file mode 100644 index 4d83e4e..0000000 --- a/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__simple_struct.snap +++ /dev/null @@ -1,139 +0,0 @@ ---- -source: interactive-clap-derive/src/tests/test_simple_struct.rs -expression: pretty_codegen(&interactive_clap_codegen) ---- -#[derive(Debug, Default, Clone, clap::Parser, interactive_clap::ToCliArgs)] -#[clap(author, version, about, long_about = None)] -pub struct CliArgs { - pub age: Option<::CliVariant>, - pub first_name: Option<::CliVariant>, - pub second_name: Option<::CliVariant>, -} -impl interactive_clap::ToCli for Args { - type CliVariant = CliArgs; -} -impl Args { - pub fn try_parse() -> Result { - ::try_parse() - } - pub fn parse() -> CliArgs { - ::parse() - } - pub fn try_parse_from(itr: I) -> Result - where - I: ::std::iter::IntoIterator, - T: ::std::convert::Into<::std::ffi::OsString> + ::std::clone::Clone, - { - ::try_parse_from(itr) - } -} -impl From for CliArgs { - fn from(args: Args) -> Self { - Self { - age: Some(args.age.into()), - first_name: Some(args.first_name.into()), - second_name: Some(args.second_name.into()), - } - } -} -impl Args { - fn input_age(_context: &()) -> color_eyre::eyre::Result> { - match inquire::CustomType::new("age").prompt() { - Ok(value) => Ok(Some(value)), - Err( - inquire::error::InquireError::OperationCanceled - | inquire::error::InquireError::OperationInterrupted, - ) => Ok(None), - Err(err) => Err(err.into()), - } - } - fn input_first_name(_context: &()) -> color_eyre::eyre::Result> { - match inquire::CustomType::new("first_name").prompt() { - Ok(value) => Ok(Some(value)), - Err( - inquire::error::InquireError::OperationCanceled - | inquire::error::InquireError::OperationInterrupted, - ) => Ok(None), - Err(err) => Err(err.into()), - } - } - fn input_second_name(_context: &()) -> color_eyre::eyre::Result> { - match inquire::CustomType::new("second_name").prompt() { - Ok(value) => Ok(Some(value)), - Err( - inquire::error::InquireError::OperationCanceled - | inquire::error::InquireError::OperationInterrupted, - ) => Ok(None), - Err(err) => Err(err.into()), - } - } -} -pub struct InteractiveClapContextScopeForArgs { - pub age: u64, - pub first_name: String, - pub second_name: String, -} -impl interactive_clap::ToInteractiveClapContextScope for Args { - type InteractiveClapContextScope = InteractiveClapContextScopeForArgs; -} -impl interactive_clap::FromCli for Args { - 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.clone().unwrap_or_default(); - if clap_variant.age.is_none() { - clap_variant - .age = match Self::input_age(&context) { - Ok(Some(age)) => Some(age), - Ok(None) => { - return interactive_clap::ResultFromCli::Cancel(Some(clap_variant)); - } - Err(err) => { - return interactive_clap::ResultFromCli::Err(Some(clap_variant), err); - } - }; - } - let age = clap_variant.age.clone().expect("Unexpected error"); - if clap_variant.first_name.is_none() { - clap_variant - .first_name = match Self::input_first_name(&context) { - Ok(Some(first_name)) => Some(first_name), - Ok(None) => { - return interactive_clap::ResultFromCli::Cancel(Some(clap_variant)); - } - Err(err) => { - return interactive_clap::ResultFromCli::Err(Some(clap_variant), err); - } - }; - } - let first_name = clap_variant.first_name.clone().expect("Unexpected error"); - if clap_variant.second_name.is_none() { - clap_variant - .second_name = match Self::input_second_name(&context) { - Ok(Some(second_name)) => Some(second_name), - Ok(None) => { - return interactive_clap::ResultFromCli::Cancel(Some(clap_variant)); - } - Err(err) => { - return interactive_clap::ResultFromCli::Err(Some(clap_variant), err); - } - }; - } - let second_name = clap_variant.second_name.clone().expect("Unexpected error"); - let new_context_scope = InteractiveClapContextScopeForArgs { - age: age.into(), - first_name: first_name.into(), - second_name: second_name.into(), - }; - interactive_clap::ResultFromCli::Ok(clap_variant) - } -} diff --git a/interactive-clap-derive/src/tests/test_simple_struct.rs b/interactive-clap-derive/src/tests/test_simple_struct.rs index 037576f..063ed92 100644 --- a/interactive-clap-derive/src/tests/test_simple_struct.rs +++ b/interactive-clap-derive/src/tests/test_simple_struct.rs @@ -1,6 +1,7 @@ use super::pretty_codegen; #[test] +#[ignore] fn test_simple_struct() { let input = syn::parse_quote! { struct Args { From e9e96abf1d1238c16b2a660a3c6d173d93eefd9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dj8yf0=CE=BCl?= Date: Tue, 4 Feb 2025 22:23:13 +0200 Subject: [PATCH 35/57] test: forwardport test+snapshots for test_simple_struct from `master` --- ...__test_simple_struct__simple_struct-2.snap | 19 +++ ...ts__test_simple_struct__simple_struct.snap | 137 ++++++++++++++++++ .../src/tests/test_simple_struct.rs | 11 +- 3 files changed, 165 insertions(+), 2 deletions(-) create mode 100644 interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__simple_struct-2.snap create mode 100644 interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__simple_struct.snap diff --git a/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__simple_struct-2.snap b/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__simple_struct-2.snap new file mode 100644 index 0000000..fbaae2f --- /dev/null +++ b/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__simple_struct-2.snap @@ -0,0 +1,19 @@ +--- +source: interactive-clap-derive/src/tests/test_simple_struct.rs +expression: pretty_codegen(&to_cli_args_codegen) +--- +impl interactive_clap::ToCliArgs for CliArgs { + fn to_cli_args(&self) -> std::collections::VecDeque { + let mut args = std::collections::VecDeque::new(); + if let Some(arg) = &self.second_name { + args.push_front(arg.to_string()) + } + if let Some(arg) = &self.first_name { + args.push_front(arg.to_string()) + } + if let Some(arg) = &self.age { + args.push_front(arg.to_string()) + } + args + } +} diff --git a/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__simple_struct.snap b/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__simple_struct.snap new file mode 100644 index 0000000..8b00ce9 --- /dev/null +++ b/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__simple_struct.snap @@ -0,0 +1,137 @@ +--- +source: interactive-clap-derive/src/tests/test_simple_struct.rs +expression: pretty_codegen(&interactive_clap_codegen) +--- +#[derive(Debug, Default, Clone, clap::Parser, interactive_clap::ToCliArgs)] +#[clap(author, version, about, long_about = None)] +pub struct CliArgs { + pub age: Option<::CliVariant>, + pub first_name: Option<::CliVariant>, + pub second_name: Option<::CliVariant>, +} +impl interactive_clap::ToCli for Args { + type CliVariant = CliArgs; +} +pub struct InteractiveClapContextScopeForArgs { + pub age: u64, + pub first_name: String, + pub second_name: String, +} +impl interactive_clap::ToInteractiveClapContextScope for Args { + type InteractiveClapContextScope = InteractiveClapContextScopeForArgs; +} +impl interactive_clap::FromCli for Args { + 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.clone().unwrap_or_default(); + if clap_variant.age.is_none() { + clap_variant + .age = match Self::input_age(&context) { + Ok(Some(age)) => Some(age), + Ok(None) => { + return interactive_clap::ResultFromCli::Cancel(Some(clap_variant)); + } + Err(err) => { + return interactive_clap::ResultFromCli::Err(Some(clap_variant), err); + } + }; + } + let age = clap_variant.age.clone().expect("Unexpected error"); + if clap_variant.first_name.is_none() { + clap_variant + .first_name = match Self::input_first_name(&context) { + Ok(Some(first_name)) => Some(first_name), + Ok(None) => { + return interactive_clap::ResultFromCli::Cancel(Some(clap_variant)); + } + Err(err) => { + return interactive_clap::ResultFromCli::Err(Some(clap_variant), err); + } + }; + } + let first_name = clap_variant.first_name.clone().expect("Unexpected error"); + if clap_variant.second_name.is_none() { + clap_variant + .second_name = match Self::input_second_name(&context) { + Ok(Some(second_name)) => Some(second_name), + Ok(None) => { + return interactive_clap::ResultFromCli::Cancel(Some(clap_variant)); + } + Err(err) => { + return interactive_clap::ResultFromCli::Err(Some(clap_variant), err); + } + }; + } + let second_name = clap_variant.second_name.clone().expect("Unexpected error"); + let new_context_scope = InteractiveClapContextScopeForArgs { + age: age.into(), + first_name: first_name.into(), + second_name: second_name.into(), + }; + interactive_clap::ResultFromCli::Ok(clap_variant) + } +} +impl Args { + fn input_age(_context: &()) -> color_eyre::eyre::Result> { + match inquire::CustomType::new("age").prompt() { + Ok(value) => Ok(Some(value)), + Err( + inquire::error::InquireError::OperationCanceled + | inquire::error::InquireError::OperationInterrupted, + ) => Ok(None), + Err(err) => Err(err.into()), + } + } + fn input_first_name(_context: &()) -> color_eyre::eyre::Result> { + match inquire::CustomType::new("first_name").prompt() { + Ok(value) => Ok(Some(value)), + Err( + inquire::error::InquireError::OperationCanceled + | inquire::error::InquireError::OperationInterrupted, + ) => Ok(None), + Err(err) => Err(err.into()), + } + } + fn input_second_name(_context: &()) -> color_eyre::eyre::Result> { + match inquire::CustomType::new("second_name").prompt() { + Ok(value) => Ok(Some(value)), + Err( + inquire::error::InquireError::OperationCanceled + | inquire::error::InquireError::OperationInterrupted, + ) => Ok(None), + Err(err) => Err(err.into()), + } + } + pub fn try_parse() -> Result { + ::try_parse() + } + pub fn parse() -> CliArgs { + ::parse() + } + pub fn try_parse_from(itr: I) -> Result + where + I: ::std::iter::IntoIterator, + T: ::std::convert::Into<::std::ffi::OsString> + ::std::clone::Clone, + { + ::try_parse_from(itr) + } +} +impl From for CliArgs { + fn from(args: Args) -> Self { + Self { + age: Some(args.age.into()), + first_name: Some(args.first_name.into()), + second_name: Some(args.second_name.into()), + } + } +} diff --git a/interactive-clap-derive/src/tests/test_simple_struct.rs b/interactive-clap-derive/src/tests/test_simple_struct.rs index 063ed92..657a4e3 100644 --- a/interactive-clap-derive/src/tests/test_simple_struct.rs +++ b/interactive-clap-derive/src/tests/test_simple_struct.rs @@ -1,7 +1,6 @@ use super::pretty_codegen; #[test] -#[ignore] fn test_simple_struct() { let input = syn::parse_quote! { struct Args { @@ -14,7 +13,15 @@ fn test_simple_struct() { let interactive_clap_codegen = crate::derives::interactive_clap::impl_interactive_clap(&input); insta::assert_snapshot!(pretty_codegen(&interactive_clap_codegen)); - let to_cli_args_codegen = crate::derives::to_cli_args::impl_to_cli_args(&input); + let step_one_output = syn::parse_quote! { + pub struct CliArgs { + pub age: Option<::CliVariant>, + pub first_name: Option<::CliVariant>, + pub second_name: Option<::CliVariant>, + } + }; + + let to_cli_args_codegen = crate::derives::to_cli_args::impl_to_cli_args(&step_one_output); insta::assert_snapshot!(pretty_codegen(&to_cli_args_codegen)); } From eb4b1243f1b6d74a0afd113173b4952d86f19e8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dj8yf0=CE=BCl?= Date: Tue, 4 Feb 2025 22:35:06 +0200 Subject: [PATCH 36/57] testcode + snapshot change of master -> (pr) for `test_simple_struct` --- ...ts__test_simple_struct__simple_struct.snap | 110 +++++++++--------- 1 file changed, 56 insertions(+), 54 deletions(-) diff --git a/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__simple_struct.snap b/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__simple_struct.snap index 8b00ce9..4d83e4e 100644 --- a/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__simple_struct.snap +++ b/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__simple_struct.snap @@ -12,6 +12,62 @@ pub struct CliArgs { impl interactive_clap::ToCli for Args { type CliVariant = CliArgs; } +impl Args { + pub fn try_parse() -> Result { + ::try_parse() + } + pub fn parse() -> CliArgs { + ::parse() + } + pub fn try_parse_from(itr: I) -> Result + where + I: ::std::iter::IntoIterator, + T: ::std::convert::Into<::std::ffi::OsString> + ::std::clone::Clone, + { + ::try_parse_from(itr) + } +} +impl From for CliArgs { + fn from(args: Args) -> Self { + Self { + age: Some(args.age.into()), + first_name: Some(args.first_name.into()), + second_name: Some(args.second_name.into()), + } + } +} +impl Args { + fn input_age(_context: &()) -> color_eyre::eyre::Result> { + match inquire::CustomType::new("age").prompt() { + Ok(value) => Ok(Some(value)), + Err( + inquire::error::InquireError::OperationCanceled + | inquire::error::InquireError::OperationInterrupted, + ) => Ok(None), + Err(err) => Err(err.into()), + } + } + fn input_first_name(_context: &()) -> color_eyre::eyre::Result> { + match inquire::CustomType::new("first_name").prompt() { + Ok(value) => Ok(Some(value)), + Err( + inquire::error::InquireError::OperationCanceled + | inquire::error::InquireError::OperationInterrupted, + ) => Ok(None), + Err(err) => Err(err.into()), + } + } + fn input_second_name(_context: &()) -> color_eyre::eyre::Result> { + match inquire::CustomType::new("second_name").prompt() { + Ok(value) => Ok(Some(value)), + Err( + inquire::error::InquireError::OperationCanceled + | inquire::error::InquireError::OperationInterrupted, + ) => Ok(None), + Err(err) => Err(err.into()), + } + } +} pub struct InteractiveClapContextScopeForArgs { pub age: u64, pub first_name: String, @@ -81,57 +137,3 @@ impl interactive_clap::FromCli for Args { interactive_clap::ResultFromCli::Ok(clap_variant) } } -impl Args { - fn input_age(_context: &()) -> color_eyre::eyre::Result> { - match inquire::CustomType::new("age").prompt() { - Ok(value) => Ok(Some(value)), - Err( - inquire::error::InquireError::OperationCanceled - | inquire::error::InquireError::OperationInterrupted, - ) => Ok(None), - Err(err) => Err(err.into()), - } - } - fn input_first_name(_context: &()) -> color_eyre::eyre::Result> { - match inquire::CustomType::new("first_name").prompt() { - Ok(value) => Ok(Some(value)), - Err( - inquire::error::InquireError::OperationCanceled - | inquire::error::InquireError::OperationInterrupted, - ) => Ok(None), - Err(err) => Err(err.into()), - } - } - fn input_second_name(_context: &()) -> color_eyre::eyre::Result> { - match inquire::CustomType::new("second_name").prompt() { - Ok(value) => Ok(Some(value)), - Err( - inquire::error::InquireError::OperationCanceled - | inquire::error::InquireError::OperationInterrupted, - ) => Ok(None), - Err(err) => Err(err.into()), - } - } - pub fn try_parse() -> Result { - ::try_parse() - } - pub fn parse() -> CliArgs { - ::parse() - } - pub fn try_parse_from(itr: I) -> Result - where - I: ::std::iter::IntoIterator, - T: ::std::convert::Into<::std::ffi::OsString> + ::std::clone::Clone, - { - ::try_parse_from(itr) - } -} -impl From for CliArgs { - fn from(args: Args) -> Self { - Self { - age: Some(args.age.into()), - first_name: Some(args.first_name.into()), - second_name: Some(args.second_name.into()), - } - } -} From 7c296a4dbc2cca4caf79d478d037656b22797991 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dj8yf0=CE=BCl?= Date: Tue, 4 Feb 2025 22:39:01 +0200 Subject: [PATCH 37/57] test: add context for why test_bug_fix_of_to_cli_args_derive was added --- interactive-clap-derive/src/tests/test_simple_struct.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/interactive-clap-derive/src/tests/test_simple_struct.rs b/interactive-clap-derive/src/tests/test_simple_struct.rs index 657a4e3..fcfc3a6 100644 --- a/interactive-clap-derive/src/tests/test_simple_struct.rs +++ b/interactive-clap-derive/src/tests/test_simple_struct.rs @@ -39,6 +39,7 @@ fn test_simple_struct_with_named_arg() { insta::assert_snapshot!(pretty_codegen(&interactive_clap_codegen)); } +/// this tested this problem https://github.com/near/near-cli-rs/pull/444#issuecomment-2631866217 #[test] fn test_bug_fix_of_to_cli_args_derive() { let input = syn::parse_quote! { From 65f6fb76d0649ffe4f7fcebf64ddd15c4c18be20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dj8yf0=CE=BCl?= Date: Tue, 4 Feb 2025 22:47:47 +0200 Subject: [PATCH 38/57] test: ignore snapshots for test_simple_struct_with_named_arg --- ..._struct__simple_struct_with_named_arg.snap | 100 ------------------ .../src/tests/test_simple_struct.rs | 1 + 2 files changed, 1 insertion(+), 100 deletions(-) delete mode 100644 interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__simple_struct_with_named_arg.snap diff --git a/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__simple_struct_with_named_arg.snap b/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__simple_struct_with_named_arg.snap deleted file mode 100644 index 8f07140..0000000 --- a/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__simple_struct_with_named_arg.snap +++ /dev/null @@ -1,100 +0,0 @@ ---- -source: interactive-clap-derive/src/tests/test_simple_struct.rs -expression: pretty_codegen(&interactive_clap_codegen) ---- -#[derive(Debug, Default, Clone, clap::Parser, interactive_clap::ToCliArgs)] -#[clap(author, version, about, long_about = None)] -pub struct CliAccount { - ///Specify a sender - #[clap(subcommand)] - pub field_name: Option, -} -impl interactive_clap::ToCli for Account { - type CliVariant = CliAccount; -} -impl Account { - pub fn try_parse() -> Result { - ::try_parse() - } - pub fn parse() -> CliAccount { - ::parse() - } - pub fn try_parse_from(itr: I) -> Result - where - I: ::std::iter::IntoIterator, - T: ::std::convert::Into<::std::ffi::OsString> + ::std::clone::Clone, - { - ::try_parse_from(itr) - } -} -impl From for CliAccount { - fn from(args: Account) -> Self { - Self { - field_name: Some(args.field_name.into()), - } - } -} -impl Account {} -pub struct InteractiveClapContextScopeForAccount {} -impl interactive_clap::ToInteractiveClapContextScope for Account { - type InteractiveClapContextScope = InteractiveClapContextScopeForAccount; -} -impl interactive_clap::FromCli for Account { - 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.clone().unwrap_or_default(); - let new_context_scope = InteractiveClapContextScopeForAccount { - }; - let optional_field = match clap_variant.field_name.take() { - Some(ClapNamedArgSenderForAccount::FieldName(cli_arg)) => Some(cli_arg), - None => None, - }; - match ::from_cli( - optional_field, - context.into(), - ) { - interactive_clap::ResultFromCli::Ok(cli_field) => { - clap_variant - .field_name = Some( - ClapNamedArgSenderForAccount::FieldName(cli_field), - ); - } - interactive_clap::ResultFromCli::Cancel(optional_cli_field) => { - clap_variant - .field_name = optional_cli_field - .map(ClapNamedArgSenderForAccount::FieldName); - 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 - .field_name = optional_cli_field - .map(ClapNamedArgSenderForAccount::FieldName); - return interactive_clap::ResultFromCli::Err(Some(clap_variant), err); - } - }; - interactive_clap::ResultFromCli::Ok(clap_variant) - } -} -#[derive(Debug, Clone, clap::Parser, interactive_clap_derive::ToCliArgs)] -pub enum ClapNamedArgSenderForAccount { - ///Specify a sender - FieldName(::CliVariant), -} -impl From for ClapNamedArgSenderForAccount { - fn from(item: Sender) -> Self { - Self::FieldName(::CliVariant::from(item)) - } -} diff --git a/interactive-clap-derive/src/tests/test_simple_struct.rs b/interactive-clap-derive/src/tests/test_simple_struct.rs index fcfc3a6..b58ccb7 100644 --- a/interactive-clap-derive/src/tests/test_simple_struct.rs +++ b/interactive-clap-derive/src/tests/test_simple_struct.rs @@ -26,6 +26,7 @@ fn test_simple_struct() { } #[test] +#[ignore] fn test_simple_struct_with_named_arg() { let input = syn::parse_quote! { struct Account { From ccfbb2c92ab3fa9da1c9c8aec9c2c596c663680a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dj8yf0=CE=BCl?= Date: Tue, 4 Feb 2025 22:51:24 +0200 Subject: [PATCH 39/57] test: forwardport test+snapshots for test_simple_struct_with_named_arg from `master` --- ...truct__simple_struct_with_named_arg-2.snap | 14 +++ ..._struct__simple_struct_with_named_arg.snap | 97 +++++++++++++++++++ .../src/tests/test_simple_struct.rs | 12 ++- 3 files changed, 121 insertions(+), 2 deletions(-) create mode 100644 interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__simple_struct_with_named_arg-2.snap create mode 100644 interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__simple_struct_with_named_arg.snap diff --git a/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__simple_struct_with_named_arg-2.snap b/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__simple_struct_with_named_arg-2.snap new file mode 100644 index 0000000..18034f4 --- /dev/null +++ b/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__simple_struct_with_named_arg-2.snap @@ -0,0 +1,14 @@ +--- +source: interactive-clap-derive/src/tests/test_simple_struct.rs +expression: pretty_codegen(&to_cli_args_codegen) +--- +impl interactive_clap::ToCliArgs for CliAccount { + fn to_cli_args(&self) -> std::collections::VecDeque { + let mut args = self + .field_name + .as_ref() + .map(|subcommand| subcommand.to_cli_args()) + .unwrap_or_default(); + args + } +} diff --git a/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__simple_struct_with_named_arg.snap b/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__simple_struct_with_named_arg.snap new file mode 100644 index 0000000..4e3cbaa --- /dev/null +++ b/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__simple_struct_with_named_arg.snap @@ -0,0 +1,97 @@ +--- +source: interactive-clap-derive/src/tests/test_simple_struct.rs +expression: pretty_codegen(&interactive_clap_codegen) +--- +#[derive(Debug, Default, Clone, clap::Parser, interactive_clap::ToCliArgs)] +#[clap(author, version, about, long_about = None)] +pub struct CliAccount { + #[clap(subcommand)] + pub field_name: Option, +} +impl interactive_clap::ToCli for Account { + type CliVariant = CliAccount; +} +pub struct InteractiveClapContextScopeForAccount {} +impl interactive_clap::ToInteractiveClapContextScope for Account { + type InteractiveClapContextScope = InteractiveClapContextScopeForAccount; +} +impl interactive_clap::FromCli for Account { + 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.clone().unwrap_or_default(); + let new_context_scope = InteractiveClapContextScopeForAccount { + }; + let optional_field = match clap_variant.field_name.take() { + Some(ClapNamedArgSenderForAccount::FieldName(cli_arg)) => Some(cli_arg), + None => None, + }; + match ::from_cli( + optional_field, + context.into(), + ) { + interactive_clap::ResultFromCli::Ok(cli_field) => { + clap_variant + .field_name = Some( + ClapNamedArgSenderForAccount::FieldName(cli_field), + ); + } + interactive_clap::ResultFromCli::Cancel(optional_cli_field) => { + clap_variant + .field_name = optional_cli_field + .map(ClapNamedArgSenderForAccount::FieldName); + 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 + .field_name = optional_cli_field + .map(ClapNamedArgSenderForAccount::FieldName); + return interactive_clap::ResultFromCli::Err(Some(clap_variant), err); + } + }; + interactive_clap::ResultFromCli::Ok(clap_variant) + } +} +impl Account { + pub fn try_parse() -> Result { + ::try_parse() + } + pub fn parse() -> CliAccount { + ::parse() + } + pub fn try_parse_from(itr: I) -> Result + where + I: ::std::iter::IntoIterator, + T: ::std::convert::Into<::std::ffi::OsString> + ::std::clone::Clone, + { + ::try_parse_from(itr) + } +} +impl From for CliAccount { + fn from(args: Account) -> Self { + Self { + field_name: Some(args.field_name.into()), + } + } +} +#[derive(Debug, Clone, clap::Parser, interactive_clap_derive::ToCliArgs)] +pub enum ClapNamedArgSenderForAccount { + FieldName(::CliVariant), +} +impl From for ClapNamedArgSenderForAccount { + fn from(item: Sender) -> Self { + Self::FieldName(::CliVariant::from(item)) + } +} diff --git a/interactive-clap-derive/src/tests/test_simple_struct.rs b/interactive-clap-derive/src/tests/test_simple_struct.rs index b58ccb7..4e921e5 100644 --- a/interactive-clap-derive/src/tests/test_simple_struct.rs +++ b/interactive-clap-derive/src/tests/test_simple_struct.rs @@ -26,18 +26,26 @@ fn test_simple_struct() { } #[test] -#[ignore] fn test_simple_struct_with_named_arg() { let input = syn::parse_quote! { struct Account { #[interactive_clap(named_arg)] - ///Specify a sender field_name: Sender, } }; let interactive_clap_codegen = crate::derives::interactive_clap::impl_interactive_clap(&input); insta::assert_snapshot!(pretty_codegen(&interactive_clap_codegen)); + + let step_one_output = syn::parse_quote! { + pub struct CliAccount { + #[clap(subcommand)] + pub field_name: Option, + } + }; + + let to_cli_args_codegen = crate::derives::to_cli_args::impl_to_cli_args(&step_one_output); + insta::assert_snapshot!(pretty_codegen(&to_cli_args_codegen)); } /// this tested this problem https://github.com/near/near-cli-rs/pull/444#issuecomment-2631866217 From ddb38910a99251f9f115a41f328dfaec80ecb146 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dj8yf0=CE=BCl?= Date: Tue, 4 Feb 2025 22:54:43 +0200 Subject: [PATCH 40/57] testcode + snapshot change of master -> (pr) for `test_simple_struct_with_named_arg` --- ..._struct__simple_struct_with_named_arg.snap | 45 ++++++++++--------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__simple_struct_with_named_arg.snap b/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__simple_struct_with_named_arg.snap index 4e3cbaa..247315c 100644 --- a/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__simple_struct_with_named_arg.snap +++ b/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__simple_struct_with_named_arg.snap @@ -11,6 +11,29 @@ pub struct CliAccount { impl interactive_clap::ToCli for Account { type CliVariant = CliAccount; } +impl Account { + pub fn try_parse() -> Result { + ::try_parse() + } + pub fn parse() -> CliAccount { + ::parse() + } + pub fn try_parse_from(itr: I) -> Result + where + I: ::std::iter::IntoIterator, + T: ::std::convert::Into<::std::ffi::OsString> + ::std::clone::Clone, + { + ::try_parse_from(itr) + } +} +impl From for CliAccount { + fn from(args: Account) -> Self { + Self { + field_name: Some(args.field_name.into()), + } + } +} +impl Account {} pub struct InteractiveClapContextScopeForAccount {} impl interactive_clap::ToInteractiveClapContextScope for Account { type InteractiveClapContextScope = InteractiveClapContextScopeForAccount; @@ -64,28 +87,6 @@ impl interactive_clap::FromCli for Account { interactive_clap::ResultFromCli::Ok(clap_variant) } } -impl Account { - pub fn try_parse() -> Result { - ::try_parse() - } - pub fn parse() -> CliAccount { - ::parse() - } - pub fn try_parse_from(itr: I) -> Result - where - I: ::std::iter::IntoIterator, - T: ::std::convert::Into<::std::ffi::OsString> + ::std::clone::Clone, - { - ::try_parse_from(itr) - } -} -impl From for CliAccount { - fn from(args: Account) -> Self { - Self { - field_name: Some(args.field_name.into()), - } - } -} #[derive(Debug, Clone, clap::Parser, interactive_clap_derive::ToCliArgs)] pub enum ClapNamedArgSenderForAccount { FieldName(::CliVariant), From 632ac61e671f004f8a68e057720e1e8df3b847e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dj8yf0=CE=BCl?= Date: Tue, 4 Feb 2025 23:14:19 +0200 Subject: [PATCH 41/57] test: ignore test+snapshots for test_doc_comments_propagate --- ...simple_struct__doc_comments_propagate.snap | 87 ------------------- .../src/tests/test_simple_struct.rs | 1 + 2 files changed, 1 insertion(+), 87 deletions(-) delete mode 100644 interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__doc_comments_propagate.snap diff --git a/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__doc_comments_propagate.snap b/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__doc_comments_propagate.snap deleted file mode 100644 index 2a6c7a6..0000000 --- a/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__doc_comments_propagate.snap +++ /dev/null @@ -1,87 +0,0 @@ ---- -source: interactive-clap-derive/src/tests/test_simple_struct.rs -expression: pretty_codegen(&interactive_clap_codegen) ---- -#[derive(Debug, Default, Clone, clap::Parser, interactive_clap::ToCliArgs)] -#[clap(author, version, about, long_about = None)] -pub struct CliArgs { - /// short first field description - /// - /// a longer paragraph, describing the usage and stuff with first field's - /// awarenes of its possible applications - #[clap(long)] - pub first_field: Option<::CliVariant>, - /// short second field description - /// - /// a longer paragraph, describing the usage and stuff with second field's - /// awareness of its possible applications - #[clap(long, verbatim_doc_comment)] - pub second_field: Option<::CliVariant>, - /// short third field description - /// - /// a longer paragraph, describing the usage and stuff with third field's - /// awareness of its possible applications - #[clap(long, verbatim_doc_comment)] - pub third_field: bool, -} -impl interactive_clap::ToCli for Args { - type CliVariant = CliArgs; -} -impl Args { - pub fn try_parse() -> Result { - ::try_parse() - } - pub fn parse() -> CliArgs { - ::parse() - } - pub fn try_parse_from(itr: I) -> Result - where - I: ::std::iter::IntoIterator, - T: ::std::convert::Into<::std::ffi::OsString> + ::std::clone::Clone, - { - ::try_parse_from(itr) - } -} -impl From for CliArgs { - fn from(args: Args) -> Self { - Self { - first_field: Some(args.first_field.into()), - second_field: Some(args.second_field.into()), - third_field: args.third_field.into(), - } - } -} -impl Args {} -pub struct InteractiveClapContextScopeForArgs { - pub first_field: u64, - pub second_field: String, - pub third_field: bool, -} -impl interactive_clap::ToInteractiveClapContextScope for Args { - type InteractiveClapContextScope = InteractiveClapContextScopeForArgs; -} -impl interactive_clap::FromCli for Args { - 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.clone().unwrap_or_default(); - let first_field = clap_variant.first_field.clone(); - let second_field = clap_variant.second_field.clone(); - let third_field = clap_variant.third_field.clone(); - let new_context_scope = InteractiveClapContextScopeForArgs { - first_field: first_field.into(), - second_field: second_field.into(), - third_field: third_field.into(), - }; - interactive_clap::ResultFromCli::Ok(clap_variant) - } -} diff --git a/interactive-clap-derive/src/tests/test_simple_struct.rs b/interactive-clap-derive/src/tests/test_simple_struct.rs index 4e921e5..6a0618a 100644 --- a/interactive-clap-derive/src/tests/test_simple_struct.rs +++ b/interactive-clap-derive/src/tests/test_simple_struct.rs @@ -148,6 +148,7 @@ fn test_vec_multiple_opt_err() { /// gets transferred to `#[clap(verbatim_doc_comment)]` on `second_field` of /// the same `CliArgs` struct #[test] +#[ignore] fn test_doc_comments_propagate() { let input = syn::parse_quote! { struct Args { From 26ee82dd25f01026efb36d28b92be70b2bf84d49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dj8yf0=CE=BCl?= Date: Tue, 4 Feb 2025 23:22:25 +0200 Subject: [PATCH 42/57] test: forwardport test+snapshots for test_doc_comments_propagate from `master` --- ...mple_struct__doc_comments_propagate-2.snap | 21 ++++++ ...simple_struct__doc_comments_propagate.snap | 74 +++++++++++++++++++ .../src/tests/test_simple_struct.rs | 15 +++- 3 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__doc_comments_propagate-2.snap create mode 100644 interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__doc_comments_propagate.snap diff --git a/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__doc_comments_propagate-2.snap b/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__doc_comments_propagate-2.snap new file mode 100644 index 0000000..d4dc0ba --- /dev/null +++ b/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__doc_comments_propagate-2.snap @@ -0,0 +1,21 @@ +--- +source: interactive-clap-derive/src/tests/test_simple_struct.rs +expression: pretty_codegen(&to_cli_args_codegen) +--- +impl interactive_clap::ToCliArgs for CliArgs { + fn to_cli_args(&self) -> std::collections::VecDeque { + let mut args = std::collections::VecDeque::new(); + if self.third_field { + args.push_front(std::concat!("--", "third-field").to_string()); + } + if let Some(arg) = &self.second_field { + args.push_front(arg.to_string()); + args.push_front(std::concat!("--", "second-field").to_string()); + } + if let Some(arg) = &self.first_field { + args.push_front(arg.to_string()); + args.push_front(std::concat!("--", "first-field").to_string()); + } + args + } +} diff --git a/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__doc_comments_propagate.snap b/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__doc_comments_propagate.snap new file mode 100644 index 0000000..acb50ca --- /dev/null +++ b/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__doc_comments_propagate.snap @@ -0,0 +1,74 @@ +--- +source: interactive-clap-derive/src/tests/test_simple_struct.rs +expression: pretty_codegen(&interactive_clap_codegen) +--- +#[derive(Debug, Default, Clone, clap::Parser, interactive_clap::ToCliArgs)] +#[clap(author, version, about, long_about = None)] +pub struct CliArgs { + #[clap(long)] + pub first_field: Option<::CliVariant>, + #[clap(long)] + pub second_field: Option<::CliVariant>, + #[clap(long)] + pub third_field: bool, +} +impl interactive_clap::ToCli for Args { + type CliVariant = CliArgs; +} +pub struct InteractiveClapContextScopeForArgs { + pub first_field: u64, + pub second_field: String, + pub third_field: bool, +} +impl interactive_clap::ToInteractiveClapContextScope for Args { + type InteractiveClapContextScope = InteractiveClapContextScopeForArgs; +} +impl interactive_clap::FromCli for Args { + 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.clone().unwrap_or_default(); + let first_field = clap_variant.first_field.clone(); + let second_field = clap_variant.second_field.clone(); + let third_field = clap_variant.third_field.clone(); + let new_context_scope = InteractiveClapContextScopeForArgs { + first_field: first_field.into(), + second_field: second_field.into(), + third_field: third_field.into(), + }; + interactive_clap::ResultFromCli::Ok(clap_variant) + } +} +impl Args { + pub fn try_parse() -> Result { + ::try_parse() + } + pub fn parse() -> CliArgs { + ::parse() + } + pub fn try_parse_from(itr: I) -> Result + where + I: ::std::iter::IntoIterator, + T: ::std::convert::Into<::std::ffi::OsString> + ::std::clone::Clone, + { + ::try_parse_from(itr) + } +} +impl From for CliArgs { + fn from(args: Args) -> Self { + Self { + first_field: Some(args.first_field.into()), + second_field: Some(args.second_field.into()), + third_field: args.third_field.into(), + } + } +} diff --git a/interactive-clap-derive/src/tests/test_simple_struct.rs b/interactive-clap-derive/src/tests/test_simple_struct.rs index 6a0618a..2c58f04 100644 --- a/interactive-clap-derive/src/tests/test_simple_struct.rs +++ b/interactive-clap-derive/src/tests/test_simple_struct.rs @@ -148,7 +148,6 @@ fn test_vec_multiple_opt_err() { /// gets transferred to `#[clap(verbatim_doc_comment)]` on `second_field` of /// the same `CliArgs` struct #[test] -#[ignore] fn test_doc_comments_propagate() { let input = syn::parse_quote! { struct Args { @@ -180,4 +179,18 @@ fn test_doc_comments_propagate() { let interactive_clap_codegen = crate::derives::interactive_clap::impl_interactive_clap(&input); insta::assert_snapshot!(pretty_codegen(&interactive_clap_codegen)); + + let step_one_output = syn::parse_quote! { + pub struct CliArgs { + #[clap(long)] + pub first_field: Option<::CliVariant>, + #[clap(long)] + pub second_field: Option<::CliVariant>, + #[clap(long)] + pub third_field: bool, + } + }; + + let to_cli_args_codegen = crate::derives::to_cli_args::impl_to_cli_args(&step_one_output); + insta::assert_snapshot!(pretty_codegen(&to_cli_args_codegen)); } From 6461299b8ee2926c634ba4d9f69e30e372c0c32f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dj8yf0=CE=BCl?= Date: Tue, 4 Feb 2025 23:30:03 +0200 Subject: [PATCH 43/57] testcode + snapshot change of master -> (pr) for `test_doc_comments_propagate` --- ...simple_struct__doc_comments_propagate.snap | 65 +++++++++++-------- .../src/tests/test_simple_struct.rs | 16 ++++- 2 files changed, 53 insertions(+), 28 deletions(-) diff --git a/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__doc_comments_propagate.snap b/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__doc_comments_propagate.snap index acb50ca..2a6c7a6 100644 --- a/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__doc_comments_propagate.snap +++ b/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__doc_comments_propagate.snap @@ -5,16 +5,53 @@ expression: pretty_codegen(&interactive_clap_codegen) #[derive(Debug, Default, Clone, clap::Parser, interactive_clap::ToCliArgs)] #[clap(author, version, about, long_about = None)] pub struct CliArgs { + /// short first field description + /// + /// a longer paragraph, describing the usage and stuff with first field's + /// awarenes of its possible applications #[clap(long)] pub first_field: Option<::CliVariant>, - #[clap(long)] + /// short second field description + /// + /// a longer paragraph, describing the usage and stuff with second field's + /// awareness of its possible applications + #[clap(long, verbatim_doc_comment)] pub second_field: Option<::CliVariant>, - #[clap(long)] + /// short third field description + /// + /// a longer paragraph, describing the usage and stuff with third field's + /// awareness of its possible applications + #[clap(long, verbatim_doc_comment)] pub third_field: bool, } impl interactive_clap::ToCli for Args { type CliVariant = CliArgs; } +impl Args { + pub fn try_parse() -> Result { + ::try_parse() + } + pub fn parse() -> CliArgs { + ::parse() + } + pub fn try_parse_from(itr: I) -> Result + where + I: ::std::iter::IntoIterator, + T: ::std::convert::Into<::std::ffi::OsString> + ::std::clone::Clone, + { + ::try_parse_from(itr) + } +} +impl From for CliArgs { + fn from(args: Args) -> Self { + Self { + first_field: Some(args.first_field.into()), + second_field: Some(args.second_field.into()), + third_field: args.third_field.into(), + } + } +} +impl Args {} pub struct InteractiveClapContextScopeForArgs { pub first_field: u64, pub second_field: String, @@ -48,27 +85,3 @@ impl interactive_clap::FromCli for Args { interactive_clap::ResultFromCli::Ok(clap_variant) } } -impl Args { - pub fn try_parse() -> Result { - ::try_parse() - } - pub fn parse() -> CliArgs { - ::parse() - } - pub fn try_parse_from(itr: I) -> Result - where - I: ::std::iter::IntoIterator, - T: ::std::convert::Into<::std::ffi::OsString> + ::std::clone::Clone, - { - ::try_parse_from(itr) - } -} -impl From for CliArgs { - fn from(args: Args) -> Self { - Self { - first_field: Some(args.first_field.into()), - second_field: Some(args.second_field.into()), - third_field: args.third_field.into(), - } - } -} diff --git a/interactive-clap-derive/src/tests/test_simple_struct.rs b/interactive-clap-derive/src/tests/test_simple_struct.rs index 2c58f04..a291032 100644 --- a/interactive-clap-derive/src/tests/test_simple_struct.rs +++ b/interactive-clap-derive/src/tests/test_simple_struct.rs @@ -182,11 +182,23 @@ fn test_doc_comments_propagate() { let step_one_output = syn::parse_quote! { pub struct CliArgs { + /// short first field description + /// + /// a longer paragraph, describing the usage and stuff with first field's + /// awarenes of its possible applications #[clap(long)] pub first_field: Option<::CliVariant>, - #[clap(long)] + /// short second field description + /// + /// a longer paragraph, describing the usage and stuff with second field's + /// awareness of its possible applications + #[clap(long, verbatim_doc_comment)] pub second_field: Option<::CliVariant>, - #[clap(long)] + /// short third field description + /// + /// a longer paragraph, describing the usage and stuff with third field's + /// awareness of its possible applications + #[clap(long, verbatim_doc_comment)] pub third_field: bool, } }; From 840636abf5d2418191c24e8c867a36a7ab42f786 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dj8yf0=CE=BCl?= Date: Wed, 5 Feb 2025 00:18:45 +0200 Subject: [PATCH 44/57] test: ignore test+snapshots for test_simple_enum --- ...ests__test_simple_enum__simple_enum-2.snap | 20 ----- ..._tests__test_simple_enum__simple_enum.snap | 81 ------------------- .../src/tests/test_simple_enum.rs | 1 + 3 files changed, 1 insertion(+), 101 deletions(-) delete mode 100644 interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_enum__simple_enum-2.snap delete mode 100644 interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_enum__simple_enum.snap diff --git a/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_enum__simple_enum-2.snap b/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_enum__simple_enum-2.snap deleted file mode 100644 index c8e1264..0000000 --- a/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_enum__simple_enum-2.snap +++ /dev/null @@ -1,20 +0,0 @@ ---- -source: interactive-clap-derive/src/tests/test_simple_enum.rs -expression: pretty_codegen(&to_cli_args_codegen) ---- -impl interactive_clap::ToCliArgs for Mode { - fn to_cli_args(&self) -> std::collections::VecDeque { - match self { - Self::Network => { - let mut args = std::collections::VecDeque::new(); - args.push_front("network".to_owned()); - args - } - Self::Offline => { - let mut args = std::collections::VecDeque::new(); - args.push_front("offline".to_owned()); - args - } - } - } -} diff --git a/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_enum__simple_enum.snap b/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_enum__simple_enum.snap deleted file mode 100644 index c95244a..0000000 --- a/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_enum__simple_enum.snap +++ /dev/null @@ -1,81 +0,0 @@ ---- -source: interactive-clap-derive/src/tests/test_simple_enum.rs -expression: pretty_codegen(&interactive_clap_codegen) ---- -#[derive(Debug, Clone, clap::Parser, interactive_clap::ToCliArgs)] -pub enum CliMode { - /// Prepare and, optionally, submit a new transaction with online mode - Network, - /// Prepare and, optionally, submit a new transaction with offline mode - Offline, -} -impl interactive_clap::ToCli for Mode { - type CliVariant = CliMode; -} -pub type InteractiveClapContextScopeForMode = ModeDiscriminants; -impl interactive_clap::ToInteractiveClapContextScope for Mode { - type InteractiveClapContextScope = InteractiveClapContextScopeForMode; -} -impl From for CliMode { - fn from(command: Mode) -> Self { - match command { - Mode::Network => Self::Network, - Mode::Offline => Self::Offline, - } - } -} -impl interactive_clap::FromCli for Mode { - type FromCliContext = (); - type FromCliError = color_eyre::eyre::Error; - fn from_cli( - mut optional_clap_variant: Option<::CliVariant>, - context: Self::FromCliContext, - ) -> interactive_clap::ResultFromCli< - ::CliVariant, - Self::FromCliError, - > - where - Self: Sized + interactive_clap::ToCli, - { - loop { - return match optional_clap_variant { - Some(CliMode::Network) => { - interactive_clap::ResultFromCli::Ok(CliMode::Network) - } - Some(CliMode::Offline) => { - interactive_clap::ResultFromCli::Ok(CliMode::Offline) - } - None => { - match Self::choose_variant(context.clone()) { - interactive_clap::ResultFromCli::Ok(cli_args) => { - optional_clap_variant = Some(cli_args); - continue; - } - result => return result, - } - } - }; - } - } -} -impl Mode { - pub fn choose_variant( - context: (), - ) -> interactive_clap::ResultFromCli< - ::CliVariant, - ::FromCliError, - > {} - pub fn try_parse() -> Result { - ::try_parse() - } - pub fn parse() -> CliMode { - ::parse() - } - pub fn try_parse_from(itr: I) -> Result - where - I: ::std::iter::IntoIterator, - T: ::std::convert::Into<::std::ffi::OsString> + ::std::clone::Clone, - { - ::try_parse_from(itr) - } -} diff --git a/interactive-clap-derive/src/tests/test_simple_enum.rs b/interactive-clap-derive/src/tests/test_simple_enum.rs index c2f5263..cc9e853 100644 --- a/interactive-clap-derive/src/tests/test_simple_enum.rs +++ b/interactive-clap-derive/src/tests/test_simple_enum.rs @@ -1,6 +1,7 @@ use super::pretty_codegen; #[test] +#[ignore] fn test_simple_enum() { let input = syn::parse_quote! { pub enum Mode { From 5dc36c10a40da2c2787529b0b3a850182ca22980 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dj8yf0=CE=BCl?= Date: Wed, 5 Feb 2025 00:26:57 +0200 Subject: [PATCH 45/57] test: forwardport test+snapshots for test_simple_enum from `master` --- ...ests__test_simple_enum__simple_enum-2.snap | 20 +++++ ..._tests__test_simple_enum__simple_enum.snap | 81 +++++++++++++++++++ .../src/tests/test_simple_enum.rs | 12 ++- 3 files changed, 111 insertions(+), 2 deletions(-) create mode 100644 interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_enum__simple_enum-2.snap create mode 100644 interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_enum__simple_enum.snap diff --git a/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_enum__simple_enum-2.snap b/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_enum__simple_enum-2.snap new file mode 100644 index 0000000..eb9f69c --- /dev/null +++ b/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_enum__simple_enum-2.snap @@ -0,0 +1,20 @@ +--- +source: interactive-clap-derive/src/tests/test_simple_enum.rs +expression: pretty_codegen(&to_cli_args_codegen) +--- +impl interactive_clap::ToCliArgs for CliMode { + fn to_cli_args(&self) -> std::collections::VecDeque { + match self { + Self::Network => { + let mut args = std::collections::VecDeque::new(); + args.push_front("network".to_owned()); + args + } + Self::Offline => { + let mut args = std::collections::VecDeque::new(); + args.push_front("offline".to_owned()); + args + } + } + } +} diff --git a/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_enum__simple_enum.snap b/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_enum__simple_enum.snap new file mode 100644 index 0000000..c95244a --- /dev/null +++ b/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_enum__simple_enum.snap @@ -0,0 +1,81 @@ +--- +source: interactive-clap-derive/src/tests/test_simple_enum.rs +expression: pretty_codegen(&interactive_clap_codegen) +--- +#[derive(Debug, Clone, clap::Parser, interactive_clap::ToCliArgs)] +pub enum CliMode { + /// Prepare and, optionally, submit a new transaction with online mode + Network, + /// Prepare and, optionally, submit a new transaction with offline mode + Offline, +} +impl interactive_clap::ToCli for Mode { + type CliVariant = CliMode; +} +pub type InteractiveClapContextScopeForMode = ModeDiscriminants; +impl interactive_clap::ToInteractiveClapContextScope for Mode { + type InteractiveClapContextScope = InteractiveClapContextScopeForMode; +} +impl From for CliMode { + fn from(command: Mode) -> Self { + match command { + Mode::Network => Self::Network, + Mode::Offline => Self::Offline, + } + } +} +impl interactive_clap::FromCli for Mode { + type FromCliContext = (); + type FromCliError = color_eyre::eyre::Error; + fn from_cli( + mut optional_clap_variant: Option<::CliVariant>, + context: Self::FromCliContext, + ) -> interactive_clap::ResultFromCli< + ::CliVariant, + Self::FromCliError, + > + where + Self: Sized + interactive_clap::ToCli, + { + loop { + return match optional_clap_variant { + Some(CliMode::Network) => { + interactive_clap::ResultFromCli::Ok(CliMode::Network) + } + Some(CliMode::Offline) => { + interactive_clap::ResultFromCli::Ok(CliMode::Offline) + } + None => { + match Self::choose_variant(context.clone()) { + interactive_clap::ResultFromCli::Ok(cli_args) => { + optional_clap_variant = Some(cli_args); + continue; + } + result => return result, + } + } + }; + } + } +} +impl Mode { + pub fn choose_variant( + context: (), + ) -> interactive_clap::ResultFromCli< + ::CliVariant, + ::FromCliError, + > {} + pub fn try_parse() -> Result { + ::try_parse() + } + pub fn parse() -> CliMode { + ::parse() + } + pub fn try_parse_from(itr: I) -> Result + where + I: ::std::iter::IntoIterator, + T: ::std::convert::Into<::std::ffi::OsString> + ::std::clone::Clone, + { + ::try_parse_from(itr) + } +} diff --git a/interactive-clap-derive/src/tests/test_simple_enum.rs b/interactive-clap-derive/src/tests/test_simple_enum.rs index cc9e853..14cc734 100644 --- a/interactive-clap-derive/src/tests/test_simple_enum.rs +++ b/interactive-clap-derive/src/tests/test_simple_enum.rs @@ -1,7 +1,6 @@ use super::pretty_codegen; #[test] -#[ignore] fn test_simple_enum() { let input = syn::parse_quote! { pub enum Mode { @@ -15,7 +14,16 @@ fn test_simple_enum() { let interactive_clap_codegen = crate::derives::interactive_clap::impl_interactive_clap(&input); insta::assert_snapshot!(pretty_codegen(&interactive_clap_codegen)); - let to_cli_args_codegen = crate::derives::to_cli_args::impl_to_cli_args(&input); + let step_one_output = syn::parse_quote! { + pub enum CliMode { + /// Prepare and, optionally, submit a new transaction with online mode + Network, + /// Prepare and, optionally, submit a new transaction with offline mode + Offline, + } + }; + + let to_cli_args_codegen = crate::derives::to_cli_args::impl_to_cli_args(&step_one_output); insta::assert_snapshot!(pretty_codegen(&to_cli_args_codegen)); } From 7e1ec802edd0f759a0d8e1ae71ed5f19aab3d73b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dj8yf0=CE=BCl?= Date: Wed, 5 Feb 2025 01:12:21 +0200 Subject: [PATCH 46/57] test: ignore test+snapshots for test_simple_enum_with_strum_discriminants --- ...imple_enum_with_strum_discriminants-2.snap | 20 ---- ..._simple_enum_with_strum_discriminants.snap | 108 ------------------ .../src/tests/test_simple_enum.rs | 1 + 3 files changed, 1 insertion(+), 128 deletions(-) delete mode 100644 interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_enum__simple_enum_with_strum_discriminants-2.snap delete mode 100644 interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_enum__simple_enum_with_strum_discriminants.snap diff --git a/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_enum__simple_enum_with_strum_discriminants-2.snap b/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_enum__simple_enum_with_strum_discriminants-2.snap deleted file mode 100644 index c8e1264..0000000 --- a/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_enum__simple_enum_with_strum_discriminants-2.snap +++ /dev/null @@ -1,20 +0,0 @@ ---- -source: interactive-clap-derive/src/tests/test_simple_enum.rs -expression: pretty_codegen(&to_cli_args_codegen) ---- -impl interactive_clap::ToCliArgs for Mode { - fn to_cli_args(&self) -> std::collections::VecDeque { - match self { - Self::Network => { - let mut args = std::collections::VecDeque::new(); - args.push_front("network".to_owned()); - args - } - Self::Offline => { - let mut args = std::collections::VecDeque::new(); - args.push_front("offline".to_owned()); - args - } - } - } -} diff --git a/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_enum__simple_enum_with_strum_discriminants.snap b/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_enum__simple_enum_with_strum_discriminants.snap deleted file mode 100644 index 330fca2..0000000 --- a/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_enum__simple_enum_with_strum_discriminants.snap +++ /dev/null @@ -1,108 +0,0 @@ ---- -source: interactive-clap-derive/src/tests/test_simple_enum.rs -expression: pretty_codegen(&interactive_clap_codegen) ---- -#[derive(Debug, Clone, clap::Parser, interactive_clap::ToCliArgs)] -pub enum CliMode { - /// Prepare and, optionally, submit a new transaction with online mode - Network, - /// Prepare and, optionally, submit a new transaction with offline mode - Offline, -} -impl interactive_clap::ToCli for Mode { - type CliVariant = CliMode; -} -pub type InteractiveClapContextScopeForMode = ModeDiscriminants; -impl interactive_clap::ToInteractiveClapContextScope for Mode { - type InteractiveClapContextScope = InteractiveClapContextScopeForMode; -} -impl From for CliMode { - fn from(command: Mode) -> Self { - match command { - Mode::Network => Self::Network, - Mode::Offline => Self::Offline, - } - } -} -impl interactive_clap::FromCli for Mode { - type FromCliContext = (); - type FromCliError = color_eyre::eyre::Error; - fn from_cli( - mut optional_clap_variant: Option<::CliVariant>, - context: Self::FromCliContext, - ) -> interactive_clap::ResultFromCli< - ::CliVariant, - Self::FromCliError, - > - where - Self: Sized + interactive_clap::ToCli, - { - loop { - return match optional_clap_variant { - Some(CliMode::Network) => { - interactive_clap::ResultFromCli::Ok(CliMode::Network) - } - Some(CliMode::Offline) => { - interactive_clap::ResultFromCli::Ok(CliMode::Offline) - } - None => { - match Self::choose_variant(context.clone()) { - interactive_clap::ResultFromCli::Ok(cli_args) => { - optional_clap_variant = Some(cli_args); - continue; - } - result => return result, - } - } - }; - } - } -} -impl Mode { - pub fn choose_variant( - context: (), - ) -> interactive_clap::ResultFromCli< - ::CliVariant, - ::FromCliError, - > { - use interactive_clap::SelectVariantOrBack; - use inquire::Select; - use strum::{EnumMessage, IntoEnumIterator}; - let selected_variant = Select::new( - concat!(r" A little beautiful comment about our choice",).trim(), - ModeDiscriminants::iter() - .map(SelectVariantOrBack::Variant) - .chain([SelectVariantOrBack::Back]) - .collect(), - ) - .prompt(); - match selected_variant { - Ok(SelectVariantOrBack::Variant(variant)) => { - let cli_args = match variant { - ModeDiscriminants::Network => CliMode::Network, - ModeDiscriminants::Offline => CliMode::Offline, - }; - return interactive_clap::ResultFromCli::Ok(cli_args); - } - Ok(SelectVariantOrBack::Back) => return interactive_clap::ResultFromCli::Back, - Err( - inquire::error::InquireError::OperationCanceled - | inquire::error::InquireError::OperationInterrupted, - ) => return interactive_clap::ResultFromCli::Cancel(None), - Err(err) => return interactive_clap::ResultFromCli::Err(None, err.into()), - } - } - pub fn try_parse() -> Result { - ::try_parse() - } - pub fn parse() -> CliMode { - ::parse() - } - pub fn try_parse_from(itr: I) -> Result - where - I: ::std::iter::IntoIterator, - T: ::std::convert::Into<::std::ffi::OsString> + ::std::clone::Clone, - { - ::try_parse_from(itr) - } -} diff --git a/interactive-clap-derive/src/tests/test_simple_enum.rs b/interactive-clap-derive/src/tests/test_simple_enum.rs index 14cc734..6b3d89b 100644 --- a/interactive-clap-derive/src/tests/test_simple_enum.rs +++ b/interactive-clap-derive/src/tests/test_simple_enum.rs @@ -28,6 +28,7 @@ fn test_simple_enum() { } #[test] +#[ignore] fn test_simple_enum_with_strum_discriminants() { let input = syn::parse_quote! { #[strum_discriminants(derive(EnumMessage, EnumIter))] From d87ee59f5a87363d2c14ceb36756aa5b81f9b3cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dj8yf0=CE=BCl?= Date: Wed, 5 Feb 2025 01:16:46 +0200 Subject: [PATCH 47/57] test: forwardport test+snapshots for test_simple_enum_with_strum_discriminants from `master` --- ...imple_enum_with_strum_discriminants-2.snap | 20 ++++ ..._simple_enum_with_strum_discriminants.snap | 108 ++++++++++++++++++ .../src/tests/test_simple_enum.rs | 12 +- 3 files changed, 138 insertions(+), 2 deletions(-) create mode 100644 interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_enum__simple_enum_with_strum_discriminants-2.snap create mode 100644 interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_enum__simple_enum_with_strum_discriminants.snap diff --git a/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_enum__simple_enum_with_strum_discriminants-2.snap b/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_enum__simple_enum_with_strum_discriminants-2.snap new file mode 100644 index 0000000..eb9f69c --- /dev/null +++ b/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_enum__simple_enum_with_strum_discriminants-2.snap @@ -0,0 +1,20 @@ +--- +source: interactive-clap-derive/src/tests/test_simple_enum.rs +expression: pretty_codegen(&to_cli_args_codegen) +--- +impl interactive_clap::ToCliArgs for CliMode { + fn to_cli_args(&self) -> std::collections::VecDeque { + match self { + Self::Network => { + let mut args = std::collections::VecDeque::new(); + args.push_front("network".to_owned()); + args + } + Self::Offline => { + let mut args = std::collections::VecDeque::new(); + args.push_front("offline".to_owned()); + args + } + } + } +} diff --git a/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_enum__simple_enum_with_strum_discriminants.snap b/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_enum__simple_enum_with_strum_discriminants.snap new file mode 100644 index 0000000..330fca2 --- /dev/null +++ b/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_enum__simple_enum_with_strum_discriminants.snap @@ -0,0 +1,108 @@ +--- +source: interactive-clap-derive/src/tests/test_simple_enum.rs +expression: pretty_codegen(&interactive_clap_codegen) +--- +#[derive(Debug, Clone, clap::Parser, interactive_clap::ToCliArgs)] +pub enum CliMode { + /// Prepare and, optionally, submit a new transaction with online mode + Network, + /// Prepare and, optionally, submit a new transaction with offline mode + Offline, +} +impl interactive_clap::ToCli for Mode { + type CliVariant = CliMode; +} +pub type InteractiveClapContextScopeForMode = ModeDiscriminants; +impl interactive_clap::ToInteractiveClapContextScope for Mode { + type InteractiveClapContextScope = InteractiveClapContextScopeForMode; +} +impl From for CliMode { + fn from(command: Mode) -> Self { + match command { + Mode::Network => Self::Network, + Mode::Offline => Self::Offline, + } + } +} +impl interactive_clap::FromCli for Mode { + type FromCliContext = (); + type FromCliError = color_eyre::eyre::Error; + fn from_cli( + mut optional_clap_variant: Option<::CliVariant>, + context: Self::FromCliContext, + ) -> interactive_clap::ResultFromCli< + ::CliVariant, + Self::FromCliError, + > + where + Self: Sized + interactive_clap::ToCli, + { + loop { + return match optional_clap_variant { + Some(CliMode::Network) => { + interactive_clap::ResultFromCli::Ok(CliMode::Network) + } + Some(CliMode::Offline) => { + interactive_clap::ResultFromCli::Ok(CliMode::Offline) + } + None => { + match Self::choose_variant(context.clone()) { + interactive_clap::ResultFromCli::Ok(cli_args) => { + optional_clap_variant = Some(cli_args); + continue; + } + result => return result, + } + } + }; + } + } +} +impl Mode { + pub fn choose_variant( + context: (), + ) -> interactive_clap::ResultFromCli< + ::CliVariant, + ::FromCliError, + > { + use interactive_clap::SelectVariantOrBack; + use inquire::Select; + use strum::{EnumMessage, IntoEnumIterator}; + let selected_variant = Select::new( + concat!(r" A little beautiful comment about our choice",).trim(), + ModeDiscriminants::iter() + .map(SelectVariantOrBack::Variant) + .chain([SelectVariantOrBack::Back]) + .collect(), + ) + .prompt(); + match selected_variant { + Ok(SelectVariantOrBack::Variant(variant)) => { + let cli_args = match variant { + ModeDiscriminants::Network => CliMode::Network, + ModeDiscriminants::Offline => CliMode::Offline, + }; + return interactive_clap::ResultFromCli::Ok(cli_args); + } + Ok(SelectVariantOrBack::Back) => return interactive_clap::ResultFromCli::Back, + Err( + inquire::error::InquireError::OperationCanceled + | inquire::error::InquireError::OperationInterrupted, + ) => return interactive_clap::ResultFromCli::Cancel(None), + Err(err) => return interactive_clap::ResultFromCli::Err(None, err.into()), + } + } + pub fn try_parse() -> Result { + ::try_parse() + } + pub fn parse() -> CliMode { + ::parse() + } + pub fn try_parse_from(itr: I) -> Result + where + I: ::std::iter::IntoIterator, + T: ::std::convert::Into<::std::ffi::OsString> + ::std::clone::Clone, + { + ::try_parse_from(itr) + } +} diff --git a/interactive-clap-derive/src/tests/test_simple_enum.rs b/interactive-clap-derive/src/tests/test_simple_enum.rs index 6b3d89b..e9c5173 100644 --- a/interactive-clap-derive/src/tests/test_simple_enum.rs +++ b/interactive-clap-derive/src/tests/test_simple_enum.rs @@ -28,7 +28,6 @@ fn test_simple_enum() { } #[test] -#[ignore] fn test_simple_enum_with_strum_discriminants() { let input = syn::parse_quote! { #[strum_discriminants(derive(EnumMessage, EnumIter))] @@ -48,6 +47,15 @@ fn test_simple_enum_with_strum_discriminants() { let interactive_clap_codegen = crate::derives::interactive_clap::impl_interactive_clap(&input); insta::assert_snapshot!(pretty_codegen(&interactive_clap_codegen)); - let to_cli_args_codegen = crate::derives::to_cli_args::impl_to_cli_args(&input); + let step_one_output = syn::parse_quote! { + pub enum CliMode { + /// Prepare and, optionally, submit a new transaction with online mode + Network, + /// Prepare and, optionally, submit a new transaction with offline mode + Offline, + } + }; + + let to_cli_args_codegen = crate::derives::to_cli_args::impl_to_cli_args(&step_one_output); insta::assert_snapshot!(pretty_codegen(&to_cli_args_codegen)); } From 985a46571b95d8ca2f289426dd5f47e93715ce9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dj8yf0=CE=BCl?= Date: Wed, 5 Feb 2025 15:42:30 +0200 Subject: [PATCH 48/57] test: unify 2-step variables names in derive tests --- .../src/tests/test_simple_struct.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/interactive-clap-derive/src/tests/test_simple_struct.rs b/interactive-clap-derive/src/tests/test_simple_struct.rs index a291032..1be8fdd 100644 --- a/interactive-clap-derive/src/tests/test_simple_struct.rs +++ b/interactive-clap-derive/src/tests/test_simple_struct.rs @@ -61,7 +61,7 @@ fn test_bug_fix_of_to_cli_args_derive() { let interactive_clap_codegen = crate::derives::interactive_clap::impl_interactive_clap(&input); insta::assert_snapshot!(pretty_codegen(&interactive_clap_codegen)); - let input = syn::parse_quote! { + let step_one_output = syn::parse_quote! { pub struct CliViewAccountSummary { /// What Account ID do you need to view? pub account_id: Option< @@ -70,7 +70,7 @@ fn test_bug_fix_of_to_cli_args_derive() { } }; - let to_cli_args_codegen = crate::derives::to_cli_args::impl_to_cli_args(&input); + let to_cli_args_codegen = crate::derives::to_cli_args::impl_to_cli_args(&step_one_output); insta::assert_snapshot!(pretty_codegen(&to_cli_args_codegen)); } @@ -87,15 +87,15 @@ fn test_flag() { let interactive_clap_codegen = crate::derives::interactive_clap::impl_interactive_clap(&input); insta::assert_snapshot!(pretty_codegen(&interactive_clap_codegen)); - let input = syn::parse_quote! { - struct CliArgs { + let step_one_output = syn::parse_quote! { + pub struct CliArgs { /// Offline mode #[clap(long)] - offline: bool + pub offline: bool, } }; - let to_cli_args_codegen = crate::derives::to_cli_args::impl_to_cli_args(&input); + let to_cli_args_codegen = crate::derives::to_cli_args::impl_to_cli_args(&step_one_output); insta::assert_snapshot!(pretty_codegen(&to_cli_args_codegen)); } @@ -114,14 +114,14 @@ fn test_vec_multiple_opt() { #[test] fn test_vec_multiple_opt_to_cli_args() { - let input = syn::parse_quote! { + let step_one_output = syn::parse_quote! { pub struct CliArgs { #[clap(long)] pub env: Vec, } }; - let to_cli_args_codegen = crate::derives::to_cli_args::impl_to_cli_args(&input); + let to_cli_args_codegen = crate::derives::to_cli_args::impl_to_cli_args(&step_one_output); insta::assert_snapshot!(pretty_codegen(&to_cli_args_codegen)); } From 382cc33d615128b520d19a2421d63081bfd4a2e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dj8yf0=CE=BCl?= Date: Wed, 5 Feb 2025 16:48:23 +0200 Subject: [PATCH 49/57] chore: extract cli_variant_struct derive module for reusability in tests --- .../{ => cli_variant_struct}/field/mod.rs | 0 .../to_cli_trait/cli_variant_struct/mod.rs | 154 ++++++++++++++++++ .../structs/to_cli_trait/mod.rs | 146 +---------------- 3 files changed, 161 insertions(+), 139 deletions(-) rename interactive-clap-derive/src/derives/interactive_clap/structs/to_cli_trait/{ => cli_variant_struct}/field/mod.rs (100%) create mode 100644 interactive-clap-derive/src/derives/interactive_clap/structs/to_cli_trait/cli_variant_struct/mod.rs diff --git a/interactive-clap-derive/src/derives/interactive_clap/structs/to_cli_trait/field/mod.rs b/interactive-clap-derive/src/derives/interactive_clap/structs/to_cli_trait/cli_variant_struct/field/mod.rs similarity index 100% rename from interactive-clap-derive/src/derives/interactive_clap/structs/to_cli_trait/field/mod.rs rename to interactive-clap-derive/src/derives/interactive_clap/structs/to_cli_trait/cli_variant_struct/field/mod.rs diff --git a/interactive-clap-derive/src/derives/interactive_clap/structs/to_cli_trait/cli_variant_struct/mod.rs b/interactive-clap-derive/src/derives/interactive_clap/structs/to_cli_trait/cli_variant_struct/mod.rs new file mode 100644 index 0000000..ae3a85d --- /dev/null +++ b/interactive-clap-derive/src/derives/interactive_clap/structs/to_cli_trait/cli_variant_struct/mod.rs @@ -0,0 +1,154 @@ +use proc_macro2::{Span, TokenStream}; +use proc_macro_error::abort_call_site; +use quote::{quote, ToTokens}; + +use crate::{LONG_VEC_MUTLIPLE_OPT, VERBATIM_DOC_COMMENT}; + +/// describes derive of individual field of `#cli_name` struct +/// based on transformation of input field from `#name` struct +mod field; + +/// returns the whole result `TokenStream` of derive logic of containing module +/// and additional info as second returned tuple's element, needed for another derive +pub fn token_stream( + name: &syn::Ident, + cli_name: &syn::Ident, + input_fields: &syn::Fields, +) -> (TokenStream, Vec) { + let (cli_fields, ident_skip_field_vec) = fields(input_fields, name); + + let token_stream = quote! { + #[derive(Debug, Default, Clone, clap::Parser, interactive_clap::ToCliArgs)] + #[clap(author, version, about, long_about = None)] + pub struct #cli_name { + #( #cli_fields, )* + } + + }; + (token_stream, ident_skip_field_vec) +} + +/// describes derive of all fields of `#cli_name` struct +/// based on transformation of input fields from `#name` struct +fn fields(fields: &syn::Fields, name: &syn::Ident) -> (Vec, Vec) { + let mut ident_skip_field_vec: Vec = Vec::new(); + + let fields = fields + .iter() + .map(|field| { + let ident_field = field.ident.clone().expect("this field does not exist"); + let ty = &field.ty; + let cli_ty = self::field::field_type(ty); + let mut cli_field = quote! { + pub #ident_field: #cli_ty + }; + if field.attrs.is_empty() { + return cli_field; + }; + let mut clap_attr_vec: Vec = Vec::new(); + let mut cfg_attr_vec: Vec = Vec::new(); + let mut doc_attr_vec: Vec = Vec::new(); + #[allow(clippy::unused_enumerate_index)] + for attr in &field.attrs { + dbg_cond!(attr.path.to_token_stream().into_iter().collect::>()); + if attr.path.is_ident("interactive_clap") || attr.path.is_ident("cfg") { + for (_index, attr_token) in attr.tokens.clone().into_iter().enumerate() { + dbg_cond!(_index, &attr_token); + match attr_token { + proc_macro2::TokenTree::Group(group) => { + let group_string = group.stream().to_string(); + if group_string.contains("subcommand") + || group_string.contains("value_enum") + || group_string.contains("long") + || (group_string == *"skip") + || (group_string == *"flatten") + || (group_string == VERBATIM_DOC_COMMENT) + { + if group_string != LONG_VEC_MUTLIPLE_OPT { + clap_attr_vec.push(group.stream()) + } + } 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}); + 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(), + ); + cli_field = quote! { + pub #ident_field: Option<#enum_for_clap_named_arg> + } + }; + 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!() + }; + if group.stream().to_string() == LONG_VEC_MUTLIPLE_OPT { + if !crate::helpers::type_starts_with_vec(ty) { + abort_call_site!("`{}` attribute is only supposed to be used with `Vec` types", LONG_VEC_MUTLIPLE_OPT) + } + // implies `#[interactive_clap(long)]` + clap_attr_vec.push(quote! { long }); + // type goes into output unchanged, otherwise it + // prevents clap deriving correctly its `remove_many` thing + cli_field = quote! { + pub #ident_field: #ty + }; + } + } + _ => { + abort_call_site!("Only option `TokenTree::Group` is needed") + } + } + } + } + if attr.path.is_ident("doc") { + doc_attr_vec.push(attr.into_token_stream()) + } + } + if cli_field.is_empty() { + return cli_field; + }; + let cfg_attrs = cfg_attr_vec.iter(); + if !clap_attr_vec.is_empty() { + let clap_attrs = clap_attr_vec.iter(); + quote! { + #(#cfg_attrs)* + #(#doc_attr_vec)* + #[clap(#(#clap_attrs, )*)] + #cli_field + } + } else { + quote! { + #(#cfg_attrs)* + #(#doc_attr_vec)* + #cli_field + } + } + }) + .filter(|token_stream| !token_stream.is_empty()) + .collect::>(); + (fields, ident_skip_field_vec) +} diff --git a/interactive-clap-derive/src/derives/interactive_clap/structs/to_cli_trait/mod.rs b/interactive-clap-derive/src/derives/interactive_clap/structs/to_cli_trait/mod.rs index 5c859c4..c5b3abe 100644 --- a/interactive-clap-derive/src/derives/interactive_clap/structs/to_cli_trait/mod.rs +++ b/interactive-clap-derive/src/derives/interactive_clap/structs/to_cli_trait/mod.rs @@ -1,8 +1,5 @@ -use crate::LONG_VEC_MUTLIPLE_OPT; -use crate::VERBATIM_DOC_COMMENT; -use proc_macro2::{Span, TokenStream}; -use proc_macro_error::abort_call_site; -use quote::{quote, ToTokens}; +use proc_macro2::TokenStream; +use quote::quote; /// returns the whole result `TokenStream` of derive logic of containing module pub fn token_stream( @@ -10,17 +7,14 @@ pub fn token_stream( cli_name: &syn::Ident, input_fields: &syn::Fields, ) -> TokenStream { - let (cli_fields, ident_skip_field_vec) = fields(input_fields, name); + let (cli_variant_struct, ident_skip_field_vec) = + cli_variant_struct::token_stream(name, cli_name, input_fields); let clap_parser_adapter = clap_parser_trait_adapter::token_stream(name, cli_name); let from_trait_impl = from_trait::token_stream(name, cli_name, input_fields, &ident_skip_field_vec); quote! { - #[derive(Debug, Default, Clone, clap::Parser, interactive_clap::ToCliArgs)] - #[clap(author, version, about, long_about = None)] - pub struct #cli_name { - #( #cli_fields, )* - } + #cli_variant_struct impl interactive_clap::ToCli for #name { type CliVariant = #cli_name; @@ -32,134 +26,8 @@ pub fn token_stream( } } -/// describes derive of all fields of `#cli_name` struct -/// based on transformation of input fields from `#name` struct -fn fields(fields: &syn::Fields, name: &syn::Ident) -> (Vec, Vec) { - let mut ident_skip_field_vec: Vec = Vec::new(); - - let fields = fields - .iter() - .map(|field| { - let ident_field = field.ident.clone().expect("this field does not exist"); - let ty = &field.ty; - let cli_ty = self::field::field_type(ty); - let mut cli_field = quote! { - pub #ident_field: #cli_ty - }; - if field.attrs.is_empty() { - return cli_field; - }; - let mut clap_attr_vec: Vec = Vec::new(); - let mut cfg_attr_vec: Vec = Vec::new(); - let mut doc_attr_vec: Vec = Vec::new(); - #[allow(clippy::unused_enumerate_index)] - for attr in &field.attrs { - dbg_cond!(attr.path.to_token_stream().into_iter().collect::>()); - if attr.path.is_ident("interactive_clap") || attr.path.is_ident("cfg") { - for (_index, attr_token) in attr.tokens.clone().into_iter().enumerate() { - dbg_cond!(_index, &attr_token); - match attr_token { - proc_macro2::TokenTree::Group(group) => { - let group_string = group.stream().to_string(); - if group_string.contains("subcommand") - || group_string.contains("value_enum") - || group_string.contains("long") - || (group_string == *"skip") - || (group_string == *"flatten") - || (group_string == VERBATIM_DOC_COMMENT) - { - if group_string != LONG_VEC_MUTLIPLE_OPT { - clap_attr_vec.push(group.stream()) - } - } 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}); - 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(), - ); - cli_field = quote! { - pub #ident_field: Option<#enum_for_clap_named_arg> - } - }; - 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!() - }; - if group.stream().to_string() == LONG_VEC_MUTLIPLE_OPT { - if !crate::helpers::type_starts_with_vec(ty) { - abort_call_site!("`{}` attribute is only supposed to be used with `Vec` types", LONG_VEC_MUTLIPLE_OPT) - } - // implies `#[interactive_clap(long)]` - clap_attr_vec.push(quote! { long }); - // type goes into output unchanged, otherwise it - // prevents clap deriving correctly its `remove_many` thing - cli_field = quote! { - pub #ident_field: #ty - }; - } - } - _ => { - abort_call_site!("Only option `TokenTree::Group` is needed") - } - } - } - } - if attr.path.is_ident("doc") { - doc_attr_vec.push(attr.into_token_stream()) - } - } - if cli_field.is_empty() { - return cli_field; - }; - let cfg_attrs = cfg_attr_vec.iter(); - if !clap_attr_vec.is_empty() { - let clap_attrs = clap_attr_vec.iter(); - quote! { - #(#cfg_attrs)* - #(#doc_attr_vec)* - #[clap(#(#clap_attrs, )*)] - #cli_field - } - } else { - quote! { - #(#cfg_attrs)* - #(#doc_attr_vec)* - #cli_field - } - } - }) - .filter(|token_stream| !token_stream.is_empty()) - .collect::>(); - (fields, ident_skip_field_vec) -} - -/// describes derive of individual field of `#cli_name` struct -/// based on transformation of input field from `#name` struct -mod field; +/// describes derive of `#cli_name` struct based on input `#name` struct +mod cli_variant_struct; /// describes logic of derive of [`clap::Parser`](https://docs.rs/clap/4.5.24/clap/trait.Parser.html) adapter /// for `#name` struct, which returns instances of `#cli_name` struct From 3db17e1e90c56ab7707c875d6d527ff0387c08e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dj8yf0=CE=BCl?= Date: Wed, 5 Feb 2025 18:11:48 +0200 Subject: [PATCH 50/57] test: add to_cli_args_structs_test_bridge module for automatic step in tests --- .../src/derives/interactive_clap/mod.rs | 52 ++++++++++-- .../structs/to_cli_trait/mod.rs | 2 +- ...st_simple_struct__vec_multiple_opt-2.snap} | 0 .../src/tests/test_simple_struct.rs | 84 +++++-------------- 4 files changed, 65 insertions(+), 73 deletions(-) rename interactive-clap-derive/src/tests/snapshots/{interactive_clap_derive__tests__test_simple_struct__vec_multiple_opt_to_cli_args.snap => interactive_clap_derive__tests__test_simple_struct__vec_multiple_opt-2.snap} (100%) diff --git a/interactive-clap-derive/src/derives/interactive_clap/mod.rs b/interactive-clap-derive/src/derives/interactive_clap/mod.rs index 45d77f3..22b5818 100644 --- a/interactive-clap-derive/src/derives/interactive_clap/mod.rs +++ b/interactive-clap-derive/src/derives/interactive_clap/mod.rs @@ -5,17 +5,55 @@ use proc_macro_error::abort_call_site; use quote::{quote, ToTokens}; use syn; -pub fn impl_interactive_clap(ast: &syn::DeriveInput) -> TokenStream { +#[cfg(test)] +pub(crate) mod to_cli_args_structs_test_bridge { + struct Opts { + name: syn::Ident, + cli_name: syn::Ident, + input_fields: syn::Fields, + } + fn prepare(ast: &syn::DeriveInput) -> Opts { + let (name, cli_name) = super::get_names(ast); + let input_fields = match &ast.data { + syn::Data::Struct(data_struct) => data_struct.fields.clone(), + syn::Data::Enum(..) | syn::Data::Union(..) => { + unreachable!("stuct DeriveInput expected"); + } + }; + Opts { + name: name.clone(), + cli_name, + input_fields, + } + } + + pub fn partial_output(ast: &syn::DeriveInput) -> syn::Result { + let opts = prepare(ast); + + let (token_stream, _unused_byproduct) = + super::structs::to_cli_trait::cli_variant_struct::token_stream( + &opts.name, + &opts.cli_name, + &opts.input_fields, + ); + syn::parse2(token_stream) + } +} + +fn get_names(ast: &syn::DeriveInput) -> (&syn::Ident, syn::Ident) { let name = &ast.ident; let cli_name = { let cli_name_string = format!("Cli{}", name); - &syn::Ident::new(&cli_name_string, Span::call_site()) + syn::Ident::new(&cli_name_string, Span::call_site()) }; + (name, cli_name) +} + +pub fn impl_interactive_clap(ast: &syn::DeriveInput) -> TokenStream { + let (name, cli_name) = get_names(ast); match &ast.data { syn::Data::Struct(data_struct) => { - let fields = data_struct.fields.clone(); - - self::structs::token_stream(name, cli_name, ast, &fields) + self::structs::token_stream(name, &cli_name, ast, &data_struct.fields) } syn::Data::Enum(syn::DataEnum { variants, .. }) => { let enum_variants = variants.iter().map(|variant| { @@ -173,7 +211,7 @@ quote::quote! { } ``` */ -mod structs { +pub(crate) mod structs { /// returns the whole result `TokenStream` of derive logic of containing module pub fn token_stream( name: &syn::Ident, @@ -197,7 +235,7 @@ mod structs { } #[doc = include_str!("../../../docs/structs_to_cli_trait_docstring.md")] - mod to_cli_trait; + pub(crate) mod to_cli_trait; #[doc = include_str!("../../../docs/structs_input_args_impl_docstring.md")] mod input_args_impl; diff --git a/interactive-clap-derive/src/derives/interactive_clap/structs/to_cli_trait/mod.rs b/interactive-clap-derive/src/derives/interactive_clap/structs/to_cli_trait/mod.rs index c5b3abe..f84a933 100644 --- a/interactive-clap-derive/src/derives/interactive_clap/structs/to_cli_trait/mod.rs +++ b/interactive-clap-derive/src/derives/interactive_clap/structs/to_cli_trait/mod.rs @@ -27,7 +27,7 @@ pub fn token_stream( } /// describes derive of `#cli_name` struct based on input `#name` struct -mod cli_variant_struct; +pub(crate) mod cli_variant_struct; /// describes logic of derive of [`clap::Parser`](https://docs.rs/clap/4.5.24/clap/trait.Parser.html) adapter /// for `#name` struct, which returns instances of `#cli_name` struct diff --git a/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__vec_multiple_opt_to_cli_args.snap b/interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__vec_multiple_opt-2.snap similarity index 100% rename from interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__vec_multiple_opt_to_cli_args.snap rename to interactive-clap-derive/src/tests/snapshots/interactive_clap_derive__tests__test_simple_struct__vec_multiple_opt-2.snap diff --git a/interactive-clap-derive/src/tests/test_simple_struct.rs b/interactive-clap-derive/src/tests/test_simple_struct.rs index 1be8fdd..2c9fd86 100644 --- a/interactive-clap-derive/src/tests/test_simple_struct.rs +++ b/interactive-clap-derive/src/tests/test_simple_struct.rs @@ -1,4 +1,5 @@ use super::pretty_codegen; +use crate::derives::interactive_clap::to_cli_args_structs_test_bridge; #[test] fn test_simple_struct() { @@ -13,15 +14,10 @@ fn test_simple_struct() { let interactive_clap_codegen = crate::derives::interactive_clap::impl_interactive_clap(&input); insta::assert_snapshot!(pretty_codegen(&interactive_clap_codegen)); - let step_one_output = syn::parse_quote! { - pub struct CliArgs { - pub age: Option<::CliVariant>, - pub first_name: Option<::CliVariant>, - pub second_name: Option<::CliVariant>, - } - }; + let step_two_input = to_cli_args_structs_test_bridge::partial_output(&input) + .unwrap_or_else(|err| panic!("couldn't parse syn::DeriveInput: {:?}", err)); - let to_cli_args_codegen = crate::derives::to_cli_args::impl_to_cli_args(&step_one_output); + let to_cli_args_codegen = crate::derives::to_cli_args::impl_to_cli_args(&step_two_input); insta::assert_snapshot!(pretty_codegen(&to_cli_args_codegen)); } @@ -37,14 +33,10 @@ fn test_simple_struct_with_named_arg() { let interactive_clap_codegen = crate::derives::interactive_clap::impl_interactive_clap(&input); insta::assert_snapshot!(pretty_codegen(&interactive_clap_codegen)); - let step_one_output = syn::parse_quote! { - pub struct CliAccount { - #[clap(subcommand)] - pub field_name: Option, - } - }; + let step_two_input = to_cli_args_structs_test_bridge::partial_output(&input) + .unwrap_or_else(|err| panic!("couldn't parse syn::DeriveInput: {:?}", err)); - let to_cli_args_codegen = crate::derives::to_cli_args::impl_to_cli_args(&step_one_output); + let to_cli_args_codegen = crate::derives::to_cli_args::impl_to_cli_args(&step_two_input); insta::assert_snapshot!(pretty_codegen(&to_cli_args_codegen)); } @@ -61,16 +53,10 @@ fn test_bug_fix_of_to_cli_args_derive() { let interactive_clap_codegen = crate::derives::interactive_clap::impl_interactive_clap(&input); insta::assert_snapshot!(pretty_codegen(&interactive_clap_codegen)); - let step_one_output = syn::parse_quote! { - pub struct CliViewAccountSummary { - /// What Account ID do you need to view? - pub account_id: Option< - ::CliVariant, - >, - } - }; + let step_two_input = to_cli_args_structs_test_bridge::partial_output(&input) + .unwrap_or_else(|err| panic!("couldn't parse syn::DeriveInput: {:?}", err)); - let to_cli_args_codegen = crate::derives::to_cli_args::impl_to_cli_args(&step_one_output); + let to_cli_args_codegen = crate::derives::to_cli_args::impl_to_cli_args(&step_two_input); insta::assert_snapshot!(pretty_codegen(&to_cli_args_codegen)); } @@ -87,15 +73,10 @@ fn test_flag() { let interactive_clap_codegen = crate::derives::interactive_clap::impl_interactive_clap(&input); insta::assert_snapshot!(pretty_codegen(&interactive_clap_codegen)); - let step_one_output = syn::parse_quote! { - pub struct CliArgs { - /// Offline mode - #[clap(long)] - pub offline: bool, - } - }; + let step_two_input = to_cli_args_structs_test_bridge::partial_output(&input) + .unwrap_or_else(|err| panic!("couldn't parse syn::DeriveInput: {:?}", err)); - let to_cli_args_codegen = crate::derives::to_cli_args::impl_to_cli_args(&step_one_output); + let to_cli_args_codegen = crate::derives::to_cli_args::impl_to_cli_args(&step_two_input); insta::assert_snapshot!(pretty_codegen(&to_cli_args_codegen)); } @@ -110,18 +91,11 @@ fn test_vec_multiple_opt() { let interactive_clap_codegen = crate::derives::interactive_clap::impl_interactive_clap(&input); insta::assert_snapshot!(pretty_codegen(&interactive_clap_codegen)); -} -#[test] -fn test_vec_multiple_opt_to_cli_args() { - let step_one_output = syn::parse_quote! { - pub struct CliArgs { - #[clap(long)] - pub env: Vec, - } - }; + let step_two_input = to_cli_args_structs_test_bridge::partial_output(&input) + .unwrap_or_else(|err| panic!("couldn't parse syn::DeriveInput: {:?}", err)); - let to_cli_args_codegen = crate::derives::to_cli_args::impl_to_cli_args(&step_one_output); + let to_cli_args_codegen = crate::derives::to_cli_args::impl_to_cli_args(&step_two_input); insta::assert_snapshot!(pretty_codegen(&to_cli_args_codegen)); } @@ -180,29 +154,9 @@ fn test_doc_comments_propagate() { let interactive_clap_codegen = crate::derives::interactive_clap::impl_interactive_clap(&input); insta::assert_snapshot!(pretty_codegen(&interactive_clap_codegen)); - let step_one_output = syn::parse_quote! { - pub struct CliArgs { - /// short first field description - /// - /// a longer paragraph, describing the usage and stuff with first field's - /// awarenes of its possible applications - #[clap(long)] - pub first_field: Option<::CliVariant>, - /// short second field description - /// - /// a longer paragraph, describing the usage and stuff with second field's - /// awareness of its possible applications - #[clap(long, verbatim_doc_comment)] - pub second_field: Option<::CliVariant>, - /// short third field description - /// - /// a longer paragraph, describing the usage and stuff with third field's - /// awareness of its possible applications - #[clap(long, verbatim_doc_comment)] - pub third_field: bool, - } - }; + let step_two_input = to_cli_args_structs_test_bridge::partial_output(&input) + .unwrap_or_else(|err| panic!("couldn't parse syn::DeriveInput: {:?}", err)); - let to_cli_args_codegen = crate::derives::to_cli_args::impl_to_cli_args(&step_one_output); + let to_cli_args_codegen = crate::derives::to_cli_args::impl_to_cli_args(&step_two_input); insta::assert_snapshot!(pretty_codegen(&to_cli_args_codegen)); } From f85e43799e5fb20019161d24266eae2c72cd824a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dj8yf0=CE=BCl?= <26653921+dj8yfo@users.noreply.github.com> Date: Mon, 10 Feb 2025 14:03:35 +0200 Subject: [PATCH 51/57] suggestion ui: Update src/lib.rs Co-authored-by: Artur Yurii Korchynskyi <42449190+akorchyn@users.noreply.github.com> --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 657efa2..da88e2d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,5 @@ //! The Interactive-clap library is an add-on for the Command Line Argument -//! Parser . Interactive-clap allows you to parse +//! Parser ([`CLAP`](https://crates.io/crates/clap>)). Interactive-clap allows you to parse //! command line options. The peculiarity of this macro is that in the absence //! of command line parameters, the interactive mode of entering these data by //! the user is activated. From 87848e439f6f54b62a1a042fe19a498d47744e35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dj8yf0=CE=BCl?= Date: Mon, 10 Feb 2025 19:18:22 +0200 Subject: [PATCH 52/57] review: addresses https://github.com/near-cli-rs/interactive-clap/pull/26#discussion_r1946509328 --- interactive-clap-derive/Cargo.toml | 2 +- interactive-clap-derive/src/debug.rs | 9 +++++---- .../interactive_clap/common_methods/choose_variant.rs | 3 +-- .../structs/to_cli_trait/cli_variant_struct/mod.rs | 3 +-- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/interactive-clap-derive/Cargo.toml b/interactive-clap-derive/Cargo.toml index 73981ce..4e66d81 100644 --- a/interactive-clap-derive/Cargo.toml +++ b/interactive-clap-derive/Cargo.toml @@ -21,7 +21,7 @@ syn = "1" [dev-dependencies] prettyplease = "0.1" insta = "1" -syn = { version = "1", features = ["full"] } +syn = { version = "1", features = ["full", "extra-traits"] } [package.metadata.docs.rs] # Additional `RUSTDOCFLAGS` to set (default: []) diff --git a/interactive-clap-derive/src/debug.rs b/interactive-clap-derive/src/debug.rs index 28bc61f..989cb55 100644 --- a/interactive-clap-derive/src/debug.rs +++ b/interactive-clap-derive/src/debug.rs @@ -1,7 +1,7 @@ #[cfg(feature = "introspect")] macro_rules! dbg_cond { - ($($val:expr),* ) => { - dbg!($($val),*) + ($val:expr) => { + dbg!($val) }; } @@ -16,7 +16,8 @@ macro_rules! dbg_cond { /// ``` #[cfg(not(feature = "introspect"))] macro_rules! dbg_cond { - ($($val:expr),*) => { - // no-op + ($val:expr) => { + #[allow(unused)] + $val }; } diff --git a/interactive-clap-derive/src/derives/interactive_clap/common_methods/choose_variant.rs b/interactive-clap-derive/src/derives/interactive_clap/common_methods/choose_variant.rs index acd34c7..974557f 100644 --- a/interactive-clap-derive/src/derives/interactive_clap/common_methods/choose_variant.rs +++ b/interactive-clap-derive/src/derives/interactive_clap/common_methods/choose_variant.rs @@ -20,9 +20,8 @@ pub fn fn_choose_variant( let mut ast_attrs: Vec<&str> = std::vec::Vec::new(); if !ast.attrs.is_empty() { - #[allow(clippy::unused_enumerate_index)] for (_index, attr) in ast.attrs.clone().into_iter().enumerate() { - dbg_cond!(_index, &attr); + dbg_cond!((_index, &attr)); if attr.path.is_ident("interactive_clap") { for attr_token in attr.tokens.clone() { if let proc_macro2::TokenTree::Group(group) = attr_token { diff --git a/interactive-clap-derive/src/derives/interactive_clap/structs/to_cli_trait/cli_variant_struct/mod.rs b/interactive-clap-derive/src/derives/interactive_clap/structs/to_cli_trait/cli_variant_struct/mod.rs index ae3a85d..db12858 100644 --- a/interactive-clap-derive/src/derives/interactive_clap/structs/to_cli_trait/cli_variant_struct/mod.rs +++ b/interactive-clap-derive/src/derives/interactive_clap/structs/to_cli_trait/cli_variant_struct/mod.rs @@ -48,12 +48,11 @@ fn fields(fields: &syn::Fields, name: &syn::Ident) -> (Vec, Vec = Vec::new(); let mut cfg_attr_vec: Vec = Vec::new(); let mut doc_attr_vec: Vec = Vec::new(); - #[allow(clippy::unused_enumerate_index)] for attr in &field.attrs { dbg_cond!(attr.path.to_token_stream().into_iter().collect::>()); if attr.path.is_ident("interactive_clap") || attr.path.is_ident("cfg") { for (_index, attr_token) in attr.tokens.clone().into_iter().enumerate() { - dbg_cond!(_index, &attr_token); + dbg_cond!((_index, &attr_token)); match attr_token { proc_macro2::TokenTree::Group(group) => { let group_string = group.stream().to_string(); From bc0a3639499dc353cfa6c6a9416a628b8c890a74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dj8yf0=CE=BCl?= Date: Mon, 10 Feb 2025 19:30:07 +0200 Subject: [PATCH 53/57] review: silence a clippy lint too https://github.com/near-cli-rs/interactive-clap/pull/26#discussion_r1946509328 --- interactive-clap-derive/src/debug.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/interactive-clap-derive/src/debug.rs b/interactive-clap-derive/src/debug.rs index 989cb55..2d1777d 100644 --- a/interactive-clap-derive/src/debug.rs +++ b/interactive-clap-derive/src/debug.rs @@ -18,6 +18,7 @@ macro_rules! dbg_cond { macro_rules! dbg_cond { ($val:expr) => { #[allow(unused)] + #[allow(clippy::no_effect)] $val }; } From aad7bda7a14aeae49fe5a0407e879f8e08e0719b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dj8yf0=CE=BCl?= Date: Mon, 10 Feb 2025 22:31:54 +0200 Subject: [PATCH 54/57] review: resolves (1) https://github.com/near-cli-rs/interactive-clap/pull/26#discussion_r1946522575 (2) https://github.com/near-cli-rs/interactive-clap/pull/26#discussion_r1946527788 (3) https://github.com/near-cli-rs/interactive-clap/pull/26#discussion_r1946516669 --- .../src/derives/interactive_clap/mod.rs | 110 +++++++++--------- 1 file changed, 55 insertions(+), 55 deletions(-) diff --git a/interactive-clap-derive/src/derives/interactive_clap/mod.rs b/interactive-clap-derive/src/derives/interactive_clap/mod.rs index 22b5818..f0ab5c6 100644 --- a/interactive-clap-derive/src/derives/interactive_clap/mod.rs +++ b/interactive-clap-derive/src/derives/interactive_clap/mod.rs @@ -5,40 +5,8 @@ use proc_macro_error::abort_call_site; use quote::{quote, ToTokens}; use syn; -#[cfg(test)] -pub(crate) mod to_cli_args_structs_test_bridge { - struct Opts { - name: syn::Ident, - cli_name: syn::Ident, - input_fields: syn::Fields, - } - fn prepare(ast: &syn::DeriveInput) -> Opts { - let (name, cli_name) = super::get_names(ast); - let input_fields = match &ast.data { - syn::Data::Struct(data_struct) => data_struct.fields.clone(), - syn::Data::Enum(..) | syn::Data::Union(..) => { - unreachable!("stuct DeriveInput expected"); - } - }; - Opts { - name: name.clone(), - cli_name, - input_fields, - } - } - - pub fn partial_output(ast: &syn::DeriveInput) -> syn::Result { - let opts = prepare(ast); - - let (token_stream, _unused_byproduct) = - super::structs::to_cli_trait::cli_variant_struct::token_stream( - &opts.name, - &opts.cli_name, - &opts.input_fields, - ); - syn::parse2(token_stream) - } -} +/// these are common methods, reused for both the [structs] and `enums` derives +pub(super) mod common_methods; fn get_names(ast: &syn::DeriveInput) -> (&syn::Ident, syn::Ident) { let name = &ast.ident; @@ -192,9 +160,6 @@ pub fn impl_interactive_clap(ast: &syn::DeriveInput) -> TokenStream { } } -/// these are common methods, reused for both the [structs] and `enums` derives -pub(super) mod common_methods; - /** This module describes [`crate::InteractiveClap`] derive logic in case when [`syn::DeriveInput`] is a struct @@ -212,6 +177,24 @@ quote::quote! { ``` */ pub(crate) mod structs { + #[doc = include_str!("../../../docs/structs_to_cli_trait_docstring.md")] + pub(crate) mod to_cli_trait; + + #[doc = include_str!("../../../docs/structs_input_args_impl_docstring.md")] + mod input_args_impl; + + #[doc = include_str!("../../../docs/structs_to_interactive_clap_context_scope_trait_docstring.md")] + mod to_interactive_clap_context_scope_trait; + + #[doc = include_str!("../../../docs/structs_from_cli_trait_docstring.md")] + mod from_cli_trait; + + #[doc = include_str!("../../../docs/clap_enum_for_named_arg_docstring.md")] + mod clap_for_named_arg_enum; + + /// these are common field methods, reused by other [structs](super::structs) submodules + pub(super) mod common_field_methods; + /// returns the whole result `TokenStream` of derive logic of containing module pub fn token_stream( name: &syn::Ident, @@ -233,24 +216,6 @@ pub(crate) mod structs { #b5 } } - - #[doc = include_str!("../../../docs/structs_to_cli_trait_docstring.md")] - pub(crate) mod to_cli_trait; - - #[doc = include_str!("../../../docs/structs_input_args_impl_docstring.md")] - mod input_args_impl; - - #[doc = include_str!("../../../docs/structs_to_interactive_clap_context_scope_trait_docstring.md")] - mod to_interactive_clap_context_scope_trait; - - #[doc = include_str!("../../../docs/structs_from_cli_trait_docstring.md")] - mod from_cli_trait; - - #[doc = include_str!("../../../docs/clap_enum_for_named_arg_docstring.md")] - mod clap_for_named_arg_enum; - - /// these are common field methods, reused by other [structs](super::structs) submodules - pub(super) mod common_field_methods; } fn context_scope_for_enum(name: &syn::Ident) -> proc_macro2::TokenStream { @@ -266,3 +231,38 @@ fn context_scope_for_enum(name: &syn::Ident) -> proc_macro2::TokenStream { } } } + +#[cfg(test)] +pub(crate) mod to_cli_args_structs_test_bridge { + struct Opts { + name: syn::Ident, + cli_name: syn::Ident, + input_fields: syn::Fields, + } + fn prepare(ast: &syn::DeriveInput) -> Opts { + let (name, cli_name) = super::get_names(ast); + let input_fields = match &ast.data { + syn::Data::Struct(data_struct) => data_struct.fields.clone(), + syn::Data::Enum(..) | syn::Data::Union(..) => { + unreachable!("stuct DeriveInput expected"); + } + }; + Opts { + name: name.clone(), + cli_name, + input_fields, + } + } + + pub fn partial_output(ast: &syn::DeriveInput) -> syn::Result { + let opts = prepare(ast); + + let (token_stream, _unused_byproduct) = + super::structs::to_cli_trait::cli_variant_struct::token_stream( + &opts.name, + &opts.cli_name, + &opts.input_fields, + ); + syn::parse2(token_stream) + } +} From 9bedd978f3bc377ae518335723ae4e8f74e0c978 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dj8yf0=CE=BCl?= Date: Mon, 10 Feb 2025 22:54:30 +0200 Subject: [PATCH 55/57] review: addresses https://github.com/near-cli-rs/interactive-clap/pull/26#discussion_r1946513458 --- .../interactive_clap/common_methods/choose_variant.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/interactive-clap-derive/src/derives/interactive_clap/common_methods/choose_variant.rs b/interactive-clap-derive/src/derives/interactive_clap/common_methods/choose_variant.rs index 974557f..d508ed3 100644 --- a/interactive-clap-derive/src/derives/interactive_clap/common_methods/choose_variant.rs +++ b/interactive-clap-derive/src/derives/interactive_clap/common_methods/choose_variant.rs @@ -35,8 +35,12 @@ pub fn fn_choose_variant( if attr.path.is_ident("strum_discriminants") { for attr_token in attr.tokens.clone() { if let proc_macro2::TokenTree::Group(group) = attr_token { - let group_stream_no_whitespace = - group.stream().to_string().replace(" ", ""); + let group_stream_no_whitespace = group + .stream() + .to_string() + .split_whitespace() + .collect::>() + .join(""); dbg_cond!(&group_stream_no_whitespace); if &group_stream_no_whitespace == "derive(EnumMessage,EnumIter)" { ast_attrs.push("strum_discriminants"); From 804e12028894841a232687d37b00358b3df0de79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dj8yf0=CE=BCl?= Date: Mon, 10 Feb 2025 23:08:51 +0200 Subject: [PATCH 56/57] review: addresses https://github.com/near-cli-rs/interactive-clap/pull/26#discussion_r1946548619 --- .../mod.rs => clap_parser_trait_adapter.rs} | 0 .../to_cli_trait/cli_variant_struct/{field/mod.rs => field.rs} | 0 .../structs/to_cli_trait/{from_trait/mod.rs => from_trait.rs} | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename interactive-clap-derive/src/derives/interactive_clap/structs/to_cli_trait/{clap_parser_trait_adapter/mod.rs => clap_parser_trait_adapter.rs} (100%) rename interactive-clap-derive/src/derives/interactive_clap/structs/to_cli_trait/cli_variant_struct/{field/mod.rs => field.rs} (100%) rename interactive-clap-derive/src/derives/interactive_clap/structs/to_cli_trait/{from_trait/mod.rs => from_trait.rs} (100%) diff --git a/interactive-clap-derive/src/derives/interactive_clap/structs/to_cli_trait/clap_parser_trait_adapter/mod.rs b/interactive-clap-derive/src/derives/interactive_clap/structs/to_cli_trait/clap_parser_trait_adapter.rs similarity index 100% rename from interactive-clap-derive/src/derives/interactive_clap/structs/to_cli_trait/clap_parser_trait_adapter/mod.rs rename to interactive-clap-derive/src/derives/interactive_clap/structs/to_cli_trait/clap_parser_trait_adapter.rs diff --git a/interactive-clap-derive/src/derives/interactive_clap/structs/to_cli_trait/cli_variant_struct/field/mod.rs b/interactive-clap-derive/src/derives/interactive_clap/structs/to_cli_trait/cli_variant_struct/field.rs similarity index 100% rename from interactive-clap-derive/src/derives/interactive_clap/structs/to_cli_trait/cli_variant_struct/field/mod.rs rename to interactive-clap-derive/src/derives/interactive_clap/structs/to_cli_trait/cli_variant_struct/field.rs diff --git a/interactive-clap-derive/src/derives/interactive_clap/structs/to_cli_trait/from_trait/mod.rs b/interactive-clap-derive/src/derives/interactive_clap/structs/to_cli_trait/from_trait.rs similarity index 100% rename from interactive-clap-derive/src/derives/interactive_clap/structs/to_cli_trait/from_trait/mod.rs rename to interactive-clap-derive/src/derives/interactive_clap/structs/to_cli_trait/from_trait.rs From c9cce0657048596087c4c86b4f6441167903c478 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dj8yf0=CE=BCl?= Date: Mon, 10 Feb 2025 23:14:21 +0200 Subject: [PATCH 57/57] review: addresses https://github.com/near-cli-rs/interactive-clap/pull/26#discussion_r1946533737 --- .../docs/clap_enum_for_named_arg_docstring.md | 27 ------- .../docs/structs_from_cli_trait_docstring.md | 70 ------------------ .../docs/structs_input_args_impl_docstring.md | 44 ------------ .../docs/structs_to_cli_trait_docstring.md | 54 -------------- ...tive_clap_context_scope_trait_docstring.md | 26 ------- .../src/derives/interactive_clap/mod.rs | 5 -- .../structs/clap_for_named_arg_enum.rs | 28 ++++++++ .../structs/from_cli_trait.rs | 72 +++++++++++++++++++ .../structs/input_args_impl.rs | 46 ++++++++++++ .../structs/to_cli_trait/mod.rs | 56 +++++++++++++++ ...to_interactive_clap_context_scope_trait.rs | 28 ++++++++ 11 files changed, 230 insertions(+), 226 deletions(-) delete mode 100644 interactive-clap-derive/docs/clap_enum_for_named_arg_docstring.md delete mode 100644 interactive-clap-derive/docs/structs_from_cli_trait_docstring.md delete mode 100644 interactive-clap-derive/docs/structs_input_args_impl_docstring.md delete mode 100644 interactive-clap-derive/docs/structs_to_cli_trait_docstring.md delete mode 100644 interactive-clap-derive/docs/structs_to_interactive_clap_context_scope_trait_docstring.md diff --git a/interactive-clap-derive/docs/clap_enum_for_named_arg_docstring.md b/interactive-clap-derive/docs/clap_enum_for_named_arg_docstring.md deleted file mode 100644 index ea527f8..0000000 --- a/interactive-clap-derive/docs/clap_enum_for_named_arg_docstring.md +++ /dev/null @@ -1,27 +0,0 @@ -derive of helper enum for structs with `#[interactive_clap(named_arg)]` on fields - - -```rust,ignore -struct #name { - #[interactive_clap(named_arg)] - ///Specify a sender - field_name: Sender, -} -``` - -gets transformed -=> - -```rust,ignore -#[derive(Debug, Clone, clap::Parser, interactive_clap_derive::ToCliArgs)] -pub enum ClapNamedArgSenderFor#name { - ///Specify a sender - FieldName(::CliVariant), -} -impl From for ClapNamedArgSenderFor#name { - fn from(item: Sender) -> Self { - Self::FieldName(::CliVariant::from(item)) - } -} -``` - diff --git a/interactive-clap-derive/docs/structs_from_cli_trait_docstring.md b/interactive-clap-derive/docs/structs_from_cli_trait_docstring.md deleted file mode 100644 index 10e0398..0000000 --- a/interactive-clap-derive/docs/structs_from_cli_trait_docstring.md +++ /dev/null @@ -1,70 +0,0 @@ -`interactive_clap::FromCli` derive - -This modules describes derive of `interactive_clap::FromCli` trait for `#name` struct, -which happens during derive of [`crate::InteractiveClap`] for `#name` struct: - -The implementation combines usages of all of [super::structs::to_cli_trait], [super::structs::input_args_impl], -[super::structs::to_interactive_clap_context_scope_trait] - - -derive input `#name` - -```rust,ignore -struct #name { - age: u64, - first_name: String, -} -``` - -gets transformed -=> - -```rust,ignore -impl interactive_clap::FromCli for #name { - 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.clone().unwrap_or_default(); - if clap_variant.age.is_none() { - clap_variant - .age = match Self::input_age(&context) { - Ok(Some(age)) => Some(age), - Ok(None) => { - return interactive_clap::ResultFromCli::Cancel(Some(clap_variant)); - } - Err(err) => { - return interactive_clap::ResultFromCli::Err(Some(clap_variant), err); - } - }; - } - let age = clap_variant.age.clone().expect("Unexpected error"); - if clap_variant.first_name.is_none() { - clap_variant - .first_name = match Self::input_first_name(&context) { - Ok(Some(first_name)) => Some(first_name), - Ok(None) => { - return interactive_clap::ResultFromCli::Cancel(Some(clap_variant)); - } - Err(err) => { - return interactive_clap::ResultFromCli::Err(Some(clap_variant), err); - } - }; - } - let first_name = clap_variant.first_name.clone().expect("Unexpected error"); - let new_context_scope = InteractiveClapContextScopeFor#name { - age: age.into(), - first_name: first_name.into(), - }; - interactive_clap::ResultFromCli::Ok(clap_variant) - } -} -``` diff --git a/interactive-clap-derive/docs/structs_input_args_impl_docstring.md b/interactive-clap-derive/docs/structs_input_args_impl_docstring.md deleted file mode 100644 index 1bd4785..0000000 --- a/interactive-clap-derive/docs/structs_input_args_impl_docstring.md +++ /dev/null @@ -1,44 +0,0 @@ -per-field input with [inquire::CustomType](https://docs.rs/inquire/0.6.2/inquire/struct.CustomType.html) impl block - -This modules describes derive of input args implementation block for `#name` struct, -which contains functions `input_#field_ident` per each field, -which prompt for value of each field via [inquire::CustomType](https://docs.rs/inquire/0.6.2/inquire/struct.CustomType.html) -, which happens during derive of [`crate::InteractiveClap`] for `#name` struct: - -derive input `#name` - -```rust,ignore -struct #name { - age: u64, - first_name: String, -} -``` - - -gets transformed -=> - -```rust,ignore -impl #name { - fn input_age(_context: &()) -> color_eyre::eyre::Result> { - match inquire::CustomType::new("age").prompt() { - Ok(value) => Ok(Some(value)), - Err( - inquire::error::InquireError::OperationCanceled - | inquire::error::InquireError::OperationInterrupted, - ) => Ok(None), - Err(err) => Err(err.into()), - } - } - fn input_first_name(_context: &()) -> color_eyre::eyre::Result> { - match inquire::CustomType::new("first_name").prompt() { - Ok(value) => Ok(Some(value)), - Err( - inquire::error::InquireError::OperationCanceled - | inquire::error::InquireError::OperationInterrupted, - ) => Ok(None), - Err(err) => Err(err.into()), - } - } -} -``` diff --git a/interactive-clap-derive/docs/structs_to_cli_trait_docstring.md b/interactive-clap-derive/docs/structs_to_cli_trait_docstring.md deleted file mode 100644 index 3f95e0e..0000000 --- a/interactive-clap-derive/docs/structs_to_cli_trait_docstring.md +++ /dev/null @@ -1,54 +0,0 @@ -`interactive_clap::ToCli` derive - -This module describes the derive logic of `#cli_name` struct used as `CliVariant` in -implementation of `interactive_clap::ToCli`, which happens during derive of [`crate::InteractiveClap`] for `#name` struct. - -```rust,ignore -#[derive(Debug, Default, Clone, clap::Parser, interactive_clap::ToCliArgs)] -#[clap(author, version, about, long_about = None)] -pub struct #cli_name { - #( #cli_fields, )* -} - -impl interactive_clap::ToCli for #name { - type CliVariant = #cli_name; -} -``` - -Where `interactive_clap::ToCli` is: - -```rust,ignore -pub trait ToCli { - type CliVariant; -} -``` -Additionally a [`clap::Parser`](https://docs.rs/clap/4.5.24/clap/trait.Parser.html) adapter -for `#name` and `From<#name> for #cli_name` conversion are defined: - -```rust,ignore -impl #name { - pub fn try_parse() -> Result<#cli_name, clap::Error> { - <#cli_name as clap::Parser>::try_parse() - } - - pub fn parse() -> #cli_name { - <#cli_name as clap::Parser>::parse() - } - - pub fn try_parse_from(itr: I) -> Result<#cli_name, clap::Error> - where - I: ::std::iter::IntoIterator, - T: ::std::convert::Into<::std::ffi::OsString> + ::std::clone::Clone, - { - <#cli_name as clap::Parser>::try_parse_from(itr) - } -} - -impl From<#name> for #cli_name { - fn from(args: #name) -> Self { - Self { - #( #fields_conversion, )* - } - } -} -``` diff --git a/interactive-clap-derive/docs/structs_to_interactive_clap_context_scope_trait_docstring.md b/interactive-clap-derive/docs/structs_to_interactive_clap_context_scope_trait_docstring.md deleted file mode 100644 index c4a8438..0000000 --- a/interactive-clap-derive/docs/structs_to_interactive_clap_context_scope_trait_docstring.md +++ /dev/null @@ -1,26 +0,0 @@ -`interactive_clap::ToInteractiveClapContextScope` derive - -This modules describes derive of `interactive_clap::ToInteractiveClapContextScope` trait for `#name` struct, -which happens during derive of [`crate::InteractiveClap`] for `#name` struct: - -derive input `#name` - -```rust,ignore -struct #name { - age: u64, - first_name: String, -} -``` - -gets transformed -=> - -```rust,ignore -impl #name pub struct InteractiveClapContextScopeFor#name { - pub age: u64, - pub first_name: String, -} -impl interactive_clap::ToInteractiveClapContextScope for #name { - type InteractiveClapContextScope = InteractiveClapContextScopeFor#name; -} -``` diff --git a/interactive-clap-derive/src/derives/interactive_clap/mod.rs b/interactive-clap-derive/src/derives/interactive_clap/mod.rs index f0ab5c6..95d2b4a 100644 --- a/interactive-clap-derive/src/derives/interactive_clap/mod.rs +++ b/interactive-clap-derive/src/derives/interactive_clap/mod.rs @@ -177,19 +177,14 @@ quote::quote! { ``` */ pub(crate) mod structs { - #[doc = include_str!("../../../docs/structs_to_cli_trait_docstring.md")] pub(crate) mod to_cli_trait; - #[doc = include_str!("../../../docs/structs_input_args_impl_docstring.md")] mod input_args_impl; - #[doc = include_str!("../../../docs/structs_to_interactive_clap_context_scope_trait_docstring.md")] mod to_interactive_clap_context_scope_trait; - #[doc = include_str!("../../../docs/structs_from_cli_trait_docstring.md")] mod from_cli_trait; - #[doc = include_str!("../../../docs/clap_enum_for_named_arg_docstring.md")] mod clap_for_named_arg_enum; /// these are common field methods, reused by other [structs](super::structs) submodules diff --git a/interactive-clap-derive/src/derives/interactive_clap/structs/clap_for_named_arg_enum.rs b/interactive-clap-derive/src/derives/interactive_clap/structs/clap_for_named_arg_enum.rs index 0de5650..7dfc68d 100644 --- a/interactive-clap-derive/src/derives/interactive_clap/structs/clap_for_named_arg_enum.rs +++ b/interactive-clap-derive/src/derives/interactive_clap/structs/clap_for_named_arg_enum.rs @@ -1,3 +1,31 @@ +/*! +derive of helper enum for structs with `#[interactive_clap(named_arg)]` on fields + + +```rust,ignore +struct #name { + #[interactive_clap(named_arg)] + ///Specify a sender + field_name: Sender, +} +``` + +gets transformed +=> + +```rust,ignore +#[derive(Debug, Clone, clap::Parser, interactive_clap_derive::ToCliArgs)] +pub enum ClapNamedArgSenderFor#name { + ///Specify a sender + FieldName(::CliVariant), +} +impl From for ClapNamedArgSenderFor#name { + fn from(item: Sender) -> Self { + Self::FieldName(::CliVariant::from(item)) + } +} +``` +*/ use proc_macro2::Span; use proc_macro_error::abort_call_site; use quote::{quote, ToTokens}; diff --git a/interactive-clap-derive/src/derives/interactive_clap/structs/from_cli_trait.rs b/interactive-clap-derive/src/derives/interactive_clap/structs/from_cli_trait.rs index afcf528..17d1c35 100644 --- a/interactive-clap-derive/src/derives/interactive_clap/structs/from_cli_trait.rs +++ b/interactive-clap-derive/src/derives/interactive_clap/structs/from_cli_trait.rs @@ -1,3 +1,75 @@ +/*! +`interactive_clap::FromCli` derive + +This modules describes derive of `interactive_clap::FromCli` trait for `#name` struct, +which happens during derive of [`crate::InteractiveClap`] for `#name` struct: + +The implementation combines usages of all of [super::to_cli_trait], [super::input_args_impl], +[super::to_interactive_clap_context_scope_trait] + + +derive input `#name` + +```rust,ignore +struct #name { + age: u64, + first_name: String, +} +``` + +gets transformed +=> + +```rust,ignore +impl interactive_clap::FromCli for #name { + 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.clone().unwrap_or_default(); + if clap_variant.age.is_none() { + clap_variant + .age = match Self::input_age(&context) { + Ok(Some(age)) => Some(age), + Ok(None) => { + return interactive_clap::ResultFromCli::Cancel(Some(clap_variant)); + } + Err(err) => { + return interactive_clap::ResultFromCli::Err(Some(clap_variant), err); + } + }; + } + let age = clap_variant.age.clone().expect("Unexpected error"); + if clap_variant.first_name.is_none() { + clap_variant + .first_name = match Self::input_first_name(&context) { + Ok(Some(first_name)) => Some(first_name), + Ok(None) => { + return interactive_clap::ResultFromCli::Cancel(Some(clap_variant)); + } + Err(err) => { + return interactive_clap::ResultFromCli::Err(Some(clap_variant), err); + } + }; + } + let first_name = clap_variant.first_name.clone().expect("Unexpected error"); + let new_context_scope = InteractiveClapContextScopeFor#name { + age: age.into(), + first_name: first_name.into(), + }; + interactive_clap::ResultFromCli::Ok(clap_variant) + } +} +``` +*/ use proc_macro2::Span; use proc_macro_error::abort_call_site; use quote::{quote, ToTokens}; diff --git a/interactive-clap-derive/src/derives/interactive_clap/structs/input_args_impl.rs b/interactive-clap-derive/src/derives/interactive_clap/structs/input_args_impl.rs index 6adffc2..b1bc233 100644 --- a/interactive-clap-derive/src/derives/interactive_clap/structs/input_args_impl.rs +++ b/interactive-clap-derive/src/derives/interactive_clap/structs/input_args_impl.rs @@ -1,3 +1,49 @@ +/*! +per-field input with [inquire::CustomType](https://docs.rs/inquire/0.6.2/inquire/struct.CustomType.html) impl block + +This modules describes derive of input args implementation block for `#name` struct, +which contains functions `input_#field_ident` per each field, +which prompt for value of each field via [inquire::CustomType](https://docs.rs/inquire/0.6.2/inquire/struct.CustomType.html) +, which happens during derive of [`crate::InteractiveClap`] for `#name` struct: + +derive input `#name` + +```rust,ignore +struct #name { + age: u64, + first_name: String, +} +``` + + +gets transformed +=> + +```rust,ignore +impl #name { + fn input_age(_context: &()) -> color_eyre::eyre::Result> { + match inquire::CustomType::new("age").prompt() { + Ok(value) => Ok(Some(value)), + Err( + inquire::error::InquireError::OperationCanceled + | inquire::error::InquireError::OperationInterrupted, + ) => Ok(None), + Err(err) => Err(err.into()), + } + } + fn input_first_name(_context: &()) -> color_eyre::eyre::Result> { + match inquire::CustomType::new("first_name").prompt() { + Ok(value) => Ok(Some(value)), + Err( + inquire::error::InquireError::OperationCanceled + | inquire::error::InquireError::OperationInterrupted, + ) => Ok(None), + Err(err) => Err(err.into()), + } + } +} +``` +*/ extern crate proc_macro; use proc_macro2::Span; diff --git a/interactive-clap-derive/src/derives/interactive_clap/structs/to_cli_trait/mod.rs b/interactive-clap-derive/src/derives/interactive_clap/structs/to_cli_trait/mod.rs index f84a933..0731b8e 100644 --- a/interactive-clap-derive/src/derives/interactive_clap/structs/to_cli_trait/mod.rs +++ b/interactive-clap-derive/src/derives/interactive_clap/structs/to_cli_trait/mod.rs @@ -1,3 +1,59 @@ +/*! +`interactive_clap::ToCli` derive + +This module describes the derive logic of `#cli_name` struct used as `CliVariant` in +implementation of `interactive_clap::ToCli`, which happens during derive of [`crate::InteractiveClap`] for `#name` struct. + +```rust,ignore +#[derive(Debug, Default, Clone, clap::Parser, interactive_clap::ToCliArgs)] +#[clap(author, version, about, long_about = None)] +pub struct #cli_name { + #( #cli_fields, )* +} + +impl interactive_clap::ToCli for #name { + type CliVariant = #cli_name; +} +``` + +Where `interactive_clap::ToCli` is: + +```rust,ignore +pub trait ToCli { + type CliVariant; +} +``` +Additionally a [`clap::Parser`](https://docs.rs/clap/4.5.24/clap/trait.Parser.html) adapter +for `#name` and `From<#name> for #cli_name` conversion are defined: + +```rust,ignore +impl #name { + pub fn try_parse() -> Result<#cli_name, clap::Error> { + <#cli_name as clap::Parser>::try_parse() + } + + pub fn parse() -> #cli_name { + <#cli_name as clap::Parser>::parse() + } + + pub fn try_parse_from(itr: I) -> Result<#cli_name, clap::Error> + where + I: ::std::iter::IntoIterator, + T: ::std::convert::Into<::std::ffi::OsString> + ::std::clone::Clone, + { + <#cli_name as clap::Parser>::try_parse_from(itr) + } +} + +impl From<#name> for #cli_name { + fn from(args: #name) -> Self { + Self { + #( #fields_conversion, )* + } + } +} +``` +*/ use proc_macro2::TokenStream; use quote::quote; diff --git a/interactive-clap-derive/src/derives/interactive_clap/structs/to_interactive_clap_context_scope_trait.rs b/interactive-clap-derive/src/derives/interactive_clap/structs/to_interactive_clap_context_scope_trait.rs index 9da2d3a..93f980a 100644 --- a/interactive-clap-derive/src/derives/interactive_clap/structs/to_interactive_clap_context_scope_trait.rs +++ b/interactive-clap-derive/src/derives/interactive_clap/structs/to_interactive_clap_context_scope_trait.rs @@ -1,3 +1,31 @@ +/*! +`interactive_clap::ToInteractiveClapContextScope` derive + +This modules describes derive of `interactive_clap::ToInteractiveClapContextScope` trait for `#name` struct, +which happens during derive of [`crate::InteractiveClap`] for `#name` struct: + +derive input `#name` + +```rust,ignore +struct #name { + age: u64, + first_name: String, +} +``` + +gets transformed +=> + +```rust,ignore +impl #name pub struct InteractiveClapContextScopeFor#name { + pub age: u64, + pub first_name: String, +} +impl interactive_clap::ToInteractiveClapContextScope for #name { + type InteractiveClapContextScope = InteractiveClapContextScopeFor#name; +} +``` +*/ use proc_macro2::Span; use quote::quote;