From ec88899e54e960f1cbdff0f43754450f0f8d6809 Mon Sep 17 00:00:00 2001 From: FroVolod <36816899+FroVolod@users.noreply.github.com> Date: Sun, 2 Apr 2023 22:24:06 +0300 Subject: [PATCH] refactor: Use a custom ResultFromCli to enable Cancel and Back variants and lift CLI args on Ok/Err (#4) --- .DS_Store | Bin 6148 -> 0 bytes .gitignore | 1 + Cargo.lock | 744 ------------------ Cargo.toml | 4 +- examples/advanced_enum.rs | 71 ++ examples/advanced_struct.rs | 146 ++-- examples/simple_enum.rs | 33 +- examples/simple_struct.rs | 40 +- examples/struct_with_context.rs | 82 +- examples/struct_with_named_arg.rs | 42 +- examples/struct_with_subcommand.rs | 43 +- examples/to_cli_args.rs | 167 +++- interactive-clap-derive/Cargo.toml | 2 +- .../methods/choose_variant.rs | 188 ++--- ...ields_without_skip_default_from_cli_arg.rs | 21 - .../methods/from_cli_for_enum.rs | 104 ++- .../methods/from_cli_for_struct.rs | 183 ++--- .../methods/get_arg_from_cli_for_struct.rs | 104 --- .../interactive_clap/methods/input_arg.rs | 16 +- .../derives/interactive_clap/methods/mod.rs | 2 - .../src/derives/interactive_clap/mod.rs | 7 +- src/lib.rs | 9 +- 22 files changed, 748 insertions(+), 1261 deletions(-) delete mode 100644 .DS_Store delete mode 100644 Cargo.lock create mode 100644 examples/advanced_enum.rs delete mode 100644 interactive-clap-derive/src/derives/interactive_clap/methods/fields_without_skip_default_from_cli_arg.rs delete mode 100644 interactive-clap-derive/src/derives/interactive_clap/methods/get_arg_from_cli_for_struct.rs diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 5008ddfcf53c02e82d7eee2e57c38e5672ef89f6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0"] edition = "2018" license = "MIT OR Apache-2.0" @@ -15,7 +15,7 @@ description = "Interactive mode extension crate to Command Line Arguments Parser # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -interactive-clap-derive = { path = "interactive-clap-derive", version = "0.1.0" } +interactive-clap-derive = { path = "interactive-clap-derive", version = "0.2.0" } strum = { version = "0.24", features = ["derive"] } strum_macros = "0.24" diff --git a/examples/advanced_enum.rs b/examples/advanced_enum.rs new file mode 100644 index 0000000..5ca608c --- /dev/null +++ b/examples/advanced_enum.rs @@ -0,0 +1,71 @@ +//This example shows how to parse data from the command line to an enum using the "interactive-clap" macro. + +// 1) build an example: cargo build --example advanced_enum +// 2) go to the `examples` folder: cd target/debug/examples +// 3) run an example: ./advanced_enum (without parameters) => entered interactive mode +// ./advanced_enum network => mode: Ok(Network) +// ./advanced_enum offline => mode: Ok(Offline) +// To learn more about the parameters, use "help" flag: ./advanced_enum --help + +use interactive_clap::{ResultFromCli, ToCliArgs}; +use strum::{EnumDiscriminants, EnumIter, EnumMessage}; + +#[derive(Debug, EnumDiscriminants, Clone, interactive_clap::InteractiveClap)] +#[strum_discriminants(derive(EnumMessage, EnumIter))] +///To construct a transaction you will need to provide information about sender (signer) and receiver accounts, and actions that needs to be performed. +///Do you want to derive some information required for transaction construction automatically querying it online? +pub enum Mode { + /// Prepare and, optionally, submit a new transaction with online mode + #[strum_discriminants(strum(message = "Yes, I keep it simple"))] + Network(Args), + /// 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, +} + +#[derive(Debug, Clone, interactive_clap::InteractiveClap)] +pub struct Args { + age: u64, + first_name: String, + second_name: String, +} + +fn main() -> color_eyre::Result<()> { + let cli_mode = Mode::try_parse().ok(); + let context = (); // default: input_context = () + let mode = loop { + let mode = ::from_cli(cli_mode.clone(), context); + match mode { + ResultFromCli::Ok(cli_mode) => break cli_mode, + ResultFromCli::Cancel(Some(cli_mode)) => { + println!( + "Your console command: {}", + shell_words::join(&cli_mode.to_cli_args()) + ); + return Ok(()); + } + ResultFromCli::Cancel(None) => { + println!("Goodbye!"); + return Ok(()); + } + ResultFromCli::Back => {} + ResultFromCli::Err(optional_cli_mode, err) => { + if let Some(cli_mode) = optional_cli_mode { + println!( + "Your console command: {}", + shell_words::join(&cli_mode.to_cli_args()) + ); + } + return Err(err); + } + } + }; + println!("cli_mode: {:?}", mode); + println!( + "Your console command: {}", + shell_words::join(&mode.to_cli_args()) + ); + Ok(()) +} diff --git a/examples/advanced_struct.rs b/examples/advanced_struct.rs index 01e6574..d7c427d 100644 --- a/examples/advanced_struct.rs +++ b/examples/advanced_struct.rs @@ -7,15 +7,14 @@ // => args: Ok(Args { age: 30, first_name: "QWE", second_name: "QWERTY" }) // To learn more about the parameters, use "help" flag: ./advanced_struct --help -use inquire::{CustomType, Text}; +use interactive_clap::{ResultFromCli, ToCliArgs}; -#[derive(Debug, interactive_clap_derive::InteractiveClap)] +#[derive(Debug, Clone, interactive_clap::InteractiveClap)] #[interactive_clap(skip_default_from_cli)] struct Args { - #[interactive_clap(long = "age-full-years")] // hgfashdgfajdfsadajsdfh - #[interactive_clap(skip_default_from_cli_arg)] // указать для чего этот атрибут нужен + #[interactive_clap(long = "age-full-years")] #[interactive_clap(skip_default_input_arg)] - age: u64, + age: Option, #[interactive_clap(long)] ///What is your first name? first_name: String, @@ -24,62 +23,97 @@ struct Args { second_name: String, } +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, + ) -> ResultFromCli<::CliVariant, Self::FromCliError> + where + Self: Sized + interactive_clap::ToCli, + { + let mut clap_variant = optional_clap_variant.unwrap_or_default(); + if clap_variant.age.is_none() { + clap_variant.age = match Self::input_age(&context) { + Ok(optional_age) => optional_age, + Err(err) => return ResultFromCli::Err(Some(clap_variant), err), + }; + } + let age = clap_variant.age; + 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 ResultFromCli::Cancel(Some(clap_variant)), + Err(err) => return 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 ResultFromCli::Cancel(Some(clap_variant)), + Err(err) => return ResultFromCli::Err(Some(clap_variant), err), + }; + } + let second_name = clap_variant.second_name.clone().expect("Unexpected error"); + ResultFromCli::Ok(clap_variant) + } +} + impl Args { - pub fn from_cli( - optional_clap_variant: Option, - context: (), - ) -> color_eyre::eyre::Result> { - let age = Self::from_cli_age( - optional_clap_variant - .clone() - .and_then(|clap_variant| clap_variant.age), - &context, - )?; - let first_name = Self::from_cli_first_name( - optional_clap_variant - .clone() - .and_then(|clap_variant| clap_variant.first_name), - &context, - )?; - let second_name = Self::from_cli_second_name( - optional_clap_variant - .clone() - .and_then(|clap_variant| clap_variant.second_name), - &context, - )?; - let new_context_scope = InteractiveClapContextScopeForArgs { - age, - first_name, - second_name, - }; - Ok(Some(Self { - age: new_context_scope.age, - first_name: new_context_scope.first_name, - second_name: new_context_scope.second_name, - })) + fn input_age(_context: &()) -> color_eyre::eyre::Result> { + match inquire::CustomType::new("Input age full years".to_string().as_str()).prompt() { + Ok(value) => Ok(Some(value)), + Err( + inquire::error::InquireError::OperationCanceled + | inquire::error::InquireError::OperationInterrupted, + ) => Ok(None), + Err(err) => Err(err.into()), + } } - fn input_age(_context: &()) -> color_eyre::eyre::Result { - Ok(CustomType::new("How old are you?").prompt()?) - } - - fn input_second_name(_context: &()) -> color_eyre::eyre::Result { - Ok(Text::new("What is your last name?").prompt()?) - } - - fn from_cli_age( - optional_cli_age: Option, - context: &(), // default: input_context = () - ) -> color_eyre::eyre::Result { - match optional_cli_age { - Some(age) => Ok(age), - None => Self::input_age(&context), + fn input_second_name(_context: &()) -> color_eyre::eyre::Result> { + match inquire::Text::new("Input second name".to_string().as_str()).prompt() { + Ok(value) => Ok(Some(value)), + Err( + inquire::error::InquireError::OperationCanceled + | inquire::error::InquireError::OperationInterrupted, + ) => Ok(None), + Err(err) => Err(err.into()), } } } -fn main() { - let cli_args = Args::parse(); - let args = Args::from_cli(Some(cli_args), ()); - println!("args: {:?}", args) +fn main() -> color_eyre::Result<()> { + let mut cli_args = Args::parse(); + let context = (); // default: input_context = () + loop { + let args = ::from_cli(Some(cli_args), context); + match args { + ResultFromCli::Ok(cli_args) | ResultFromCli::Cancel(Some(cli_args)) => { + println!( + "Your console command: {}", + shell_words::join(&cli_args.to_cli_args()) + ); + return Ok(()); + } + ResultFromCli::Cancel(None) => { + println!("Goodbye!"); + return Ok(()); + } + ResultFromCli::Back => { + cli_args = Default::default(); + } + ResultFromCli::Err(cli_args, err) => { + if let Some(cli_args) = cli_args { + println!( + "Your console command: {}", + shell_words::join(&cli_args.to_cli_args()) + ); + } + return Err(err); + } + } + } } diff --git a/examples/simple_enum.rs b/examples/simple_enum.rs index f3adccf..f9b6c9c 100644 --- a/examples/simple_enum.rs +++ b/examples/simple_enum.rs @@ -7,9 +7,10 @@ // ./simple_enum offline => mode: Ok(Offline) // To learn more about the parameters, use "help" flag: ./simple_enum --help +use interactive_clap::{ResultFromCli, ToCliArgs}; use strum::{EnumDiscriminants, EnumIter, EnumMessage}; -#[derive(Debug, Clone, EnumDiscriminants, interactive_clap_derive::InteractiveClap)] +#[derive(Debug, EnumDiscriminants, Clone, interactive_clap::InteractiveClap)] #[strum_discriminants(derive(EnumMessage, EnumIter))] ///To construct a transaction you will need to provide information about sender (signer) and receiver accounts, and actions that needs to be performed. ///Do you want to derive some information required for transaction construction automatically querying it online? @@ -24,9 +25,33 @@ pub enum Mode { Offline, } -fn main() { +fn main() -> color_eyre::Result<()> { let cli_mode = Mode::try_parse().ok(); let context = (); // default: input_context = () - let mode = ::from_cli(cli_mode, context); - println!("mode: {:?}", mode) + loop { + let mode = ::from_cli(cli_mode.clone(), context); + match mode { + ResultFromCli::Ok(cli_mode) | ResultFromCli::Cancel(Some(cli_mode)) => { + println!( + "Your console command: {}", + shell_words::join(&cli_mode.to_cli_args()) + ); + return Ok(()); + } + ResultFromCli::Cancel(None) => { + println!("Goodbye!"); + return Ok(()); + } + ResultFromCli::Back => {} + ResultFromCli::Err(optional_cli_mode, err) => { + if let Some(cli_mode) = optional_cli_mode { + println!( + "Your console command: {}", + shell_words::join(&cli_mode.to_cli_args()) + ); + } + return Err(err); + } + } + } } diff --git a/examples/simple_struct.rs b/examples/simple_struct.rs index 0d01f46..56ae9b2 100644 --- a/examples/simple_struct.rs +++ b/examples/simple_struct.rs @@ -6,16 +6,44 @@ // ./simple_struct 30 QWE QWERTY => args: Ok(Args { age: 30, first_name: "QWE", second_name: "QWERTY" }) // To learn more about the parameters, use "help" flag: ./simple_struct --help -#[derive(Debug, interactive_clap::InteractiveClap)] -struct Args { +use interactive_clap::{ResultFromCli, ToCliArgs}; + +#[derive(Debug, Clone, interactive_clap::InteractiveClap)] +pub struct Args { age: u64, first_name: String, second_name: String, } -fn main() { - let cli_args = Args::parse(); +fn main() -> color_eyre::Result<()> { + let mut cli_args = Args::parse(); let context = (); // default: input_context = () - let args = ::from_cli(Some(cli_args), context); - println!("args: {:?}", args) + loop { + let args = ::from_cli(Some(cli_args), context); + match args { + ResultFromCli::Ok(cli_args) | ResultFromCli::Cancel(Some(cli_args)) => { + println!( + "Your console command: {}", + shell_words::join(&cli_args.to_cli_args()) + ); + return Ok(()); + } + ResultFromCli::Cancel(None) => { + println!("Goodbye!"); + return Ok(()); + } + ResultFromCli::Back => { + cli_args = Default::default(); + } + ResultFromCli::Err(cli_args, err) => { + if let Some(cli_args) = cli_args { + println!( + "Your console command: {}", + shell_words::join(&cli_args.to_cli_args()) + ); + } + return Err(err); + } + } + } } diff --git a/examples/struct_with_context.rs b/examples/struct_with_context.rs index 0bbd9c5..3ffd053 100644 --- a/examples/struct_with_context.rs +++ b/examples/struct_with_context.rs @@ -6,17 +6,18 @@ // ./struct_with_context account QWERTY => offline_args: Ok(OfflineArgs { account: Sender { sender_account_id: "QWERTY" } }) // To learn more about the parameters, use "help" flag: ./struct_with_context --help -use inquire::Text; +use interactive_clap::{ResultFromCli, ToCliArgs}; mod common; +mod simple_enum; -#[derive(Debug, Clone, interactive_clap_derive::InteractiveClap)] +#[derive(Debug, Clone, interactive_clap::InteractiveClap)] #[interactive_clap(input_context = ())] -#[interactive_clap(output_context = NetworkContext)] +#[interactive_clap(output_context = OfflineArgsContext)] pub struct OfflineArgs { #[interactive_clap(named_arg)] ///Specify a sender - account: Sender, + sender: Sender, } #[derive(Debug)] @@ -27,7 +28,7 @@ pub struct OfflineArgsContext { impl OfflineArgsContext { fn from_previous_context( _previous_context: (), - scope: &::InteractiveClapContextScope, + _scope: &::InteractiveClapContextScope, ) -> color_eyre::eyre::Result { Ok(Self { some_context_field: 42, @@ -43,29 +44,82 @@ impl From for NetworkContext { } } +impl From<()> for NetworkContext { + fn from(_: ()) -> Self { + Self { + connection_config: None, + } + } +} + +impl From for () { + fn from(_: NetworkContext) -> Self { + () + } +} + #[derive(Debug)] pub struct NetworkContext { pub connection_config: Option, } -#[derive(Debug, Clone, interactive_clap_derive::InteractiveClap)] +#[derive(Debug, Clone, interactive_clap::InteractiveClap)] #[interactive_clap(context = NetworkContext)] pub struct Sender { #[interactive_clap(skip_default_input_arg)] - pub sender_account_id: String, + sender_account_id: String, + #[interactive_clap(subcommand)] + network: simple_enum::Mode, } impl Sender { - fn input_sender_account_id(context: &NetworkContext) -> color_eyre::eyre::Result { + fn input_sender_account_id( + context: &NetworkContext, + ) -> color_eyre::eyre::Result> { println!("Let's use context: {:?}", context); - Ok(Text::new("What is the account ID?").prompt()?) + match inquire::CustomType::new("What is the account ID?").prompt() { + Ok(value) => Ok(Some(value)), + Err( + inquire::error::InquireError::OperationCanceled + | inquire::error::InquireError::OperationInterrupted, + ) => Ok(None), + Err(err) => Err(err.into()), + } } } -fn main() { - let cli_offline_args = OfflineArgs::parse(); +fn main() -> color_eyre::Result<()> { + let mut cli_offline_args = OfflineArgs::parse(); let context = (); // #[interactive_clap(input_context = ())] - let offline_args = - ::from_cli(Some(cli_offline_args), context); - println!("offline_args: {:?}", offline_args) + loop { + let offline_args = ::from_cli( + Some(cli_offline_args.clone()), + context, + ); + match offline_args { + ResultFromCli::Ok(cli_offline_args) | ResultFromCli::Cancel(Some(cli_offline_args)) => { + println!( + "Your console command: {}", + shell_words::join(&cli_offline_args.to_cli_args()) + ); + return Ok(()); + } + ResultFromCli::Cancel(None) => { + println!("Goodbye!"); + return Ok(()); + } + ResultFromCli::Back => { + cli_offline_args = Default::default(); + } + ResultFromCli::Err(cli_offline_args, err) => { + if let Some(cli_offline_args) = cli_offline_args { + println!( + "Your console command: {}", + shell_words::join(&cli_offline_args.to_cli_args()) + ); + } + return Err(err); + } + } + } } diff --git a/examples/struct_with_named_arg.rs b/examples/struct_with_named_arg.rs index c387d5b..c2e62b9 100644 --- a/examples/struct_with_named_arg.rs +++ b/examples/struct_with_named_arg.rs @@ -7,22 +7,50 @@ // ./struct_with_named_arg account QWERTY => account: Ok(Account { account: Sender { sender_account_id: "QWERTY" } }) // To learn more about the parameters, use "help" flag: ./struct_with_named_arg --help -#[derive(Debug, Clone, interactive_clap_derive::InteractiveClap)] +use interactive_clap::{ResultFromCli, ToCliArgs}; + +#[derive(Debug, Clone, interactive_clap::InteractiveClap)] struct Account { #[interactive_clap(named_arg)] ///Specify a sender account: Sender, } -#[derive(Debug, Clone, interactive_clap_derive::InteractiveClap)] +#[derive(Debug, Clone, interactive_clap::InteractiveClap)] pub struct Sender { - ///What is the account ID? + ///What is the sender account ID? pub sender_account_id: String, } -fn main() { - let cli_account = Account::parse(); +fn main() -> color_eyre::Result<()> { + let mut cli_account = Account::parse(); let context = (); // default: input_context = () - let account = ::from_cli(Some(cli_account), context); - println!("account: {:?}", account) + loop { + let account = ::from_cli(Some(cli_account), context); + match account { + ResultFromCli::Ok(cli_account) | ResultFromCli::Cancel(Some(cli_account)) => { + println!( + "Your console command: {}", + shell_words::join(&cli_account.to_cli_args()) + ); + return Ok(()); + } + ResultFromCli::Cancel(None) => { + println!("Goodbye!"); + return Ok(()); + } + ResultFromCli::Back => { + cli_account = Default::default(); + } + ResultFromCli::Err(cli_account, err) => { + if let Some(cli_account) = cli_account { + println!( + "Your console command: {}", + shell_words::join(&cli_account.to_cli_args()) + ); + } + return Err(err); + } + } + } } diff --git a/examples/struct_with_subcommand.rs b/examples/struct_with_subcommand.rs index 9b4c6b1..86b96cd 100644 --- a/examples/struct_with_subcommand.rs +++ b/examples/struct_with_subcommand.rs @@ -7,18 +7,49 @@ // ./struct_with_subcommand offline => operation_mode: Ok(OperationMode { mode: Offline }) // To learn more about the parameters, use "help" flag: ./struct_with_subcommand --help +use interactive_clap::{ResultFromCli, ToCliArgs}; + mod simple_enum; -#[derive(Debug, Clone, interactive_clap_derive::InteractiveClap)] +#[derive(Debug, Clone, interactive_clap::InteractiveClap)] pub struct OperationMode { #[interactive_clap(subcommand)] pub mode: simple_enum::Mode, } -fn main() { - let cli_operation_mode = OperationMode::parse(); +fn main() -> color_eyre::Result<()> { + let mut cli_operation_mode = OperationMode::parse(); let context = (); // default: input_context = () - let operation_mode = - ::from_cli(Some(cli_operation_mode), context); - println!("operation_mode: {:?}", &operation_mode); + loop { + let operation_mode = ::from_cli( + Some(cli_operation_mode), + context, + ); + match operation_mode { + ResultFromCli::Ok(cli_operation_mode) + | ResultFromCli::Cancel(Some(cli_operation_mode)) => { + println!( + "Your console command: {}", + shell_words::join(&cli_operation_mode.to_cli_args()) + ); + return Ok(()); + } + ResultFromCli::Cancel(None) => { + println!("Goodbye!"); + return Ok(()); + } + ResultFromCli::Back => { + cli_operation_mode = Default::default(); + } + ResultFromCli::Err(cli_operation_mode, err) => { + if let Some(cli_operation_mode) = cli_operation_mode { + println!( + "Your console command: {}", + shell_words::join(&cli_operation_mode.to_cli_args()) + ); + } + return Err(err); + } + } + } } diff --git a/examples/to_cli_args.rs b/examples/to_cli_args.rs index c1e06b3..4387756 100644 --- a/examples/to_cli_args.rs +++ b/examples/to_cli_args.rs @@ -8,54 +8,105 @@ // To learn more about the parameters, use "help" flag: ./to_cli_args --help use inquire::Select; +use interactive_clap::{ResultFromCli, SelectVariantOrBack, ToCliArgs}; use strum::{EnumDiscriminants, EnumIter, EnumMessage, IntoEnumIterator}; -use interactive_clap::{SelectVariantOrBack, ToCliArgs}; - mod common; -#[derive(Debug, Clone, interactive_clap_derive::InteractiveClap)] +#[derive(Debug, Clone, interactive_clap::InteractiveClap)] #[interactive_clap(context = common::ConnectionConfig)] struct OnlineArgs { + /// What is the name of the network + #[interactive_clap(skip_default_input_arg)] + network_name: String, #[interactive_clap(subcommand)] submit: Submit, } -#[derive(Debug, EnumDiscriminants, Clone, clap::Parser, interactive_clap_derive::ToCliArgs)] +impl OnlineArgs { + fn input_network_name( + _context: &common::ConnectionConfig, + ) -> color_eyre::eyre::Result> { + match inquire::Text::new("Input network name").prompt() { + Ok(value) => Ok(Some(value)), + Err( + inquire::error::InquireError::OperationCanceled + | inquire::error::InquireError::OperationInterrupted, + ) => Ok(None), + Err(err) => Err(err.into()), + } + } +} + +#[derive(Debug, EnumDiscriminants, Clone, clap::Parser)] #[strum_discriminants(derive(EnumMessage, EnumIter))] pub enum Submit { #[strum_discriminants(strum(message = "I want to send the transaction to the network"))] - Send, + Send(Args), #[strum_discriminants(strum( message = "I only want to print base64-encoded transaction for JSON RPC input and exit" ))] Display, } +#[derive(Debug, EnumDiscriminants, Clone, clap::Parser)] +pub enum CliSubmit { + Send(CliArgs), + Display, +} + +impl From for CliSubmit { + fn from(command: Submit) -> Self { + match command { + Submit::Send(args) => Self::Send(args.into()), + Submit::Display => Self::Display, + } + } +} + impl interactive_clap::FromCli for Submit { type FromCliContext = common::ConnectionConfig; type FromCliError = color_eyre::eyre::Error; fn from_cli( optional_clap_variant: Option<::CliVariant>, - _context: Self::FromCliContext, - ) -> Result, Self::FromCliError> + context: Self::FromCliContext, + ) -> ResultFromCli<::CliVariant, Self::FromCliError> where Self: Sized + interactive_clap::ToCli, { - let submit: Option = optional_clap_variant.clone(); - match submit { - Some(submit) => Ok(Some(submit)), - None => Ok(Some(Submit::Display)), + match optional_clap_variant { + Some(submit) => ResultFromCli::Ok(submit), + None => Self::choose_variant(context), + } + } +} + +impl interactive_clap::ToCliArgs for CliSubmit { + fn to_cli_args(&self) -> std::collections::VecDeque { + match self { + Self::Send(cli_args) => { + let mut args = cli_args.to_cli_args(); + args.push_front("send".to_owned()); + args + } + Self::Display => { + let mut args = std::collections::VecDeque::new(); + args.push_front("display".to_owned()); + args + } } } } impl Submit { fn choose_variant( - _context: common::ConnectionConfig, - ) -> color_eyre::eyre::Result> { - let selected_variant = Select::new( + context: common::ConnectionConfig, + ) -> ResultFromCli< + ::CliVariant, + ::FromCliError, + > { + match Select::new( "How would you like to proceed", SubmitDiscriminants::iter() .map(SelectVariantOrBack::Variant) @@ -63,17 +114,40 @@ impl Submit { .collect(), ) .prompt() - .unwrap(); - match selected_variant { - SelectVariantOrBack::Variant(SubmitDiscriminants::Send) => Ok(Some(Submit::Send)), - SelectVariantOrBack::Variant(SubmitDiscriminants::Display) => Ok(Some(Submit::Display)), - SelectVariantOrBack::Back => Ok(None), + { + Ok(SelectVariantOrBack::Variant(variant)) => ResultFromCli::Ok(match variant { + SubmitDiscriminants::Send => { + let cli_args = + match ::from_cli(None, context) { + ResultFromCli::Ok(cli_args) => cli_args, + ResultFromCli::Cancel(optional_cli_args) => { + return ResultFromCli::Cancel(Some(CliSubmit::Send( + optional_cli_args.unwrap_or_default(), + ))); + } + ResultFromCli::Back => return ResultFromCli::Back, + ResultFromCli::Err(optional_cli_args, err) => { + return ResultFromCli::Err( + Some(CliSubmit::Send(optional_cli_args.unwrap_or_default())), + err, + ); + } + }; + CliSubmit::Send(cli_args) + } + SubmitDiscriminants::Display => CliSubmit::Display, + }), + Ok(SelectVariantOrBack::Back) => ResultFromCli::Back, + Err( + inquire::error::InquireError::OperationCanceled + | inquire::error::InquireError::OperationInterrupted, + ) => ResultFromCli::Cancel(None), + Err(err) => ResultFromCli::Err(None, err.into()), } } } - impl interactive_clap::ToCli for Submit { - type CliVariant = Submit; + type CliVariant = CliSubmit; } impl std::fmt::Display for SubmitDiscriminants { @@ -85,23 +159,52 @@ impl std::fmt::Display for SubmitDiscriminants { } } -fn main() { +#[derive(Debug, Clone, interactive_clap::InteractiveClap, clap::Args)] +#[interactive_clap(context = common::ConnectionConfig)] +pub struct Args { + age: u64, + first_name: String, + second_name: String, +} + +fn main() -> color_eyre::Result<()> { let mut cli_online_args = OnlineArgs::parse(); let context = common::ConnectionConfig::Testnet; //#[interactive_clap(context = common::ConnectionConfig)] - let online_args = loop { - if let Some(args) = ::from_cli( - Some(cli_online_args.clone()), + let cli_args = loop { + match ::from_cli( + Some(cli_online_args), context.clone(), - ) - .unwrap() - { - break args; + ) { + ResultFromCli::Ok(cli_args) => break cli_args, + ResultFromCli::Cancel(Some(cli_args)) => { + println!( + "Your console command: {}", + shell_words::join(&cli_args.to_cli_args()) + ); + return Ok(()); + } + ResultFromCli::Cancel(None) => { + println!("Goodbye!"); + return Ok(()); + } + ResultFromCli::Back => { + cli_online_args = Default::default(); + } + ResultFromCli::Err(cli_args, err) => { + if let Some(cli_args) = cli_args { + println!( + "Your console command: {}", + shell_words::join(&cli_args.to_cli_args()) + ); + } + return Err(err); + } } }; - cli_online_args = online_args.into(); - let completed_cli = cli_online_args.to_cli_args(); + println!("cli_args: {:?}", cli_args); println!( "Your console command: {}", - shell_words::join(&completed_cli) + shell_words::join(&cli_args.to_cli_args()) ); + Ok(()) } diff --git a/interactive-clap-derive/Cargo.toml b/interactive-clap-derive/Cargo.toml index bd093e6..1d7c190 100644 --- a/interactive-clap-derive/Cargo.toml +++ b/interactive-clap-derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "interactive-clap-derive" -version = "0.1.0" +version = "0.2.0" authors = ["FroVolod "] edition = "2018" license = "MIT OR Apache-2.0" 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 09cec3d..c628911 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 @@ -12,13 +12,10 @@ pub fn fn_choose_variant( let name = &ast.ident; let interactive_clap_attrs_context = super::interactive_clap_attrs_context::InteractiveClapAttrsContext::new(ast); - let command_discriminants = - syn::Ident::new(&format!("{}Discriminants", name), Span::call_site()); - let cli_command = syn::Ident::new(&format!("Cli{}", name), Span::call_site()); + let command_discriminants = syn::Ident::new(&format!("{name}Discriminants"), Span::call_site()); + let cli_command = syn::Ident::new(&format!("Cli{name}"), Span::call_site()); - let variant_ident = &variants[0].ident; let mut cli_variant = quote!(); - let mut actions_push_back = quote! {.chain([SelectVariantOrBack::Back])}; let mut ast_attrs: Vec<&str> = std::vec::Vec::new(); if !ast.attrs.is_empty() { @@ -26,13 +23,7 @@ pub fn fn_choose_variant( if attr.path.is_ident("interactive_clap") { for attr_token in attr.tokens.clone() { if let proc_macro2::TokenTree::Group(group) = attr_token { - if group - .stream() - .to_string() - .contains("disable_strum_discriminants") - { - ast_attrs.push("disable_strum_discriminants"); - } else if group.stream().to_string().contains("disable_back") { + if group.stream().to_string().contains("disable_back") { ast_attrs.push("disable_back"); }; } @@ -48,106 +39,91 @@ pub fn fn_choose_variant( } }; } - if ast_attrs.contains(&"disable_strum_discriminants") { - match &variants[0].fields { - syn::Fields::Unnamed(_) => { - cli_variant = quote! { - let cli_variant = #cli_command::#variant_ident(Default::default()); - }; - } - syn::Fields::Unit => { - cli_variant = quote! { - let cli_variant = #cli_command::#variant_ident; - }; - } - _ => abort_call_site!("Only option `Fields::Unnamed` or `Fields::Unit` is needed"), - } - } else { - if ast_attrs.contains(&"disable_back") { - actions_push_back = quote!(); - } - if ast_attrs.contains(&"strum_discriminants") { - let doc_attrs = ast - .attrs - .iter() - .filter(|attr| attr.path.is_ident("doc")) - .map(|attr| { - let mut literal_string = String::new(); - for attr_token in attr.tokens.clone() { - if let proc_macro2::TokenTree::Literal(literal) = attr_token { - literal_string = literal.to_string(); - } + if ast_attrs.contains(&"strum_discriminants") { + let doc_attrs = ast + .attrs + .iter() + .filter(|attr| attr.path.is_ident("doc")) + .map(|attr| { + let mut literal_string = String::new(); + for attr_token in attr.tokens.clone() { + if let proc_macro2::TokenTree::Literal(literal) = attr_token { + literal_string = literal.to_string(); } - literal_string - }) - .collect::>(); - let literal_vec = doc_attrs - .iter() - .map(|s| s.replace('\"', "")) - .collect::>(); - let literal = proc_macro2::Literal::string(literal_vec.join("\n ").as_str()); - - let enum_variants = variants.iter().map(|variant| { - let variant_ident = &variant.ident; - - match &variant.fields { - syn::Fields::Unnamed(_) => { - quote! { - #command_discriminants::#variant_ident => #cli_command::#variant_ident(Default::default()) - } - }, - syn::Fields::Unit => { - quote! { - #command_discriminants::#variant_ident => #cli_command::#variant_ident - } - }, - _ => abort_call_site!("Only option `Fields::Unnamed` or `Fields::Unit` is needed") } - }); - cli_variant = quote! { - use interactive_clap::SelectVariantOrBack; - use inquire::Select; - use strum::{EnumMessage, IntoEnumIterator}; - fn prompt_variant(prompt: &str) -> color_eyre::eyre::Result> - where - T: IntoEnumIterator + EnumMessage, - T: Copy + Clone, - { - let selected_variant = Select::new( - prompt, - T::iter() - .map(SelectVariantOrBack::Variant) - #actions_push_back - .collect(), - ) - .prompt()?; - match selected_variant { - SelectVariantOrBack::Variant(variant) => Ok(Some(variant)), - SelectVariantOrBack::Back => Ok(None), + literal_string + }) + .collect::>(); + let literal_vec = doc_attrs + .iter() + .map(|s| s.replace('\"', "")) + .collect::>(); + let literal = proc_macro2::Literal::string(literal_vec.join("\n ").as_str()); + + let enum_variants = variants.iter().map(|variant| { + let variant_ident = &variant.ident; + match &variant.fields { + syn::Fields::Unnamed(_) => { + quote! { + #command_discriminants::#variant_ident => { + #cli_command::#variant_ident(Default::default()) + } } - }; - let variant = if let Some(variant) = prompt_variant(#literal.to_string().as_str())? { - variant - } else { - return Ok(None); - }; - let cli_variant = match variant { - #( #enum_variants, )* - }; - }; - } + } + syn::Fields::Unit => { + quote! { + #command_discriminants::#variant_ident => #cli_command::#variant_ident + } + } + _ => abort_call_site!( + "Only option `Fields::Unnamed` or `Fields::Unit` is needed" + ), + } + }); + let actions_push_back = if ast_attrs.contains(&"disable_back") { + quote!() + } else { + quote! {.chain([SelectVariantOrBack::Back])} + }; + + cli_variant = quote! { + use interactive_clap::SelectVariantOrBack; + use inquire::Select; + use strum::{EnumMessage, IntoEnumIterator}; + + let selected_variant = Select::new( + #literal, + #command_discriminants::iter() + .map(SelectVariantOrBack::Variant) + #actions_push_back + .collect(), + ) + .prompt(); + match selected_variant { + Ok(SelectVariantOrBack::Variant(variant)) => { + let cli_args = match variant { + #( #enum_variants, )* + }; + 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()), + } + }; } }; - let input_context = interactive_clap_attrs_context.get_input_context_dir(); + let context = interactive_clap_attrs_context.get_input_context_dir(); quote! { - pub fn choose_variant(context: #input_context) -> color_eyre::eyre::Result> { - loop { - #cli_variant - if let Some(variant) = ::from_cli(Some(cli_variant), context.clone())? { - return Ok(Some(variant)); - } - } - } + pub fn choose_variant(context: #context) -> interactive_clap::ResultFromCli< + ::CliVariant, + ::FromCliError, + > { + #cli_variant + } } } diff --git a/interactive-clap-derive/src/derives/interactive_clap/methods/fields_without_skip_default_from_cli_arg.rs b/interactive-clap-derive/src/derives/interactive_clap/methods/fields_without_skip_default_from_cli_arg.rs deleted file mode 100644 index 0292b7d..0000000 --- a/interactive-clap-derive/src/derives/interactive_clap/methods/fields_without_skip_default_from_cli_arg.rs +++ /dev/null @@ -1,21 +0,0 @@ -extern crate proc_macro; - -use syn; - -pub fn is_field_without_skip_default_from_cli_arg(field: &syn::Field) -> bool { - if field.attrs.is_empty() { - return true; - } - !field - .attrs - .iter() - .filter(|attr| attr.path.is_ident("interactive_clap")) - .flat_map(|attr| attr.tokens.clone()) - .any(|attr_token| match attr_token { - proc_macro2::TokenTree::Group(group) => group - .stream() - .to_string() - .contains("skip_default_from_cli_arg"), - _ => false, // abort_call_site!("Only option `TokenTree::Group` is needed") - }) -} diff --git a/interactive-clap-derive/src/derives/interactive_clap/methods/from_cli_for_enum.rs b/interactive-clap-derive/src/derives/interactive_clap/methods/from_cli_for_enum.rs index eb22e0f..5f1db73 100644 --- a/interactive-clap-derive/src/derives/interactive_clap/methods/from_cli_for_enum.rs +++ b/interactive-clap-derive/src/derives/interactive_clap/methods/from_cli_for_enum.rs @@ -10,7 +10,7 @@ pub fn from_cli_for_enum( variants: &syn::punctuated::Punctuated, ) -> proc_macro2::TokenStream { let name = &ast.ident; - let cli_name = syn::Ident::new(&format!("Cli{}", name), Span::call_site()); + let cli_name = syn::Ident::new(&format!("Cli{name}"), Span::call_site()); let interactive_clap_attrs_context = super::interactive_clap_attrs_context::InteractiveClapAttrsContext::new(ast); @@ -20,42 +20,74 @@ pub fn from_cli_for_enum( let from_cli_variants = variants.iter().map(|variant| { let variant_ident = &variant.ident; + + let output_context = match &interactive_clap_attrs_context.output_context_dir { + Some(output_context_dir) => { + quote! { + type Alias = <#name as interactive_clap::ToInteractiveClapContextScope>::InteractiveClapContextScope; + let new_context_scope = Alias::#variant_ident; + let output_context = match #output_context_dir::from_previous_context(context.clone(), &new_context_scope) { + Ok(new_context) => new_context, + Err(err) => return interactive_clap::ResultFromCli::Err(Some(#cli_name::#variant_ident(inner_cli_args)), err), + }; + } + } + None => { + quote! { + let output_context = context.clone(); + } + } + }; + match &variant.fields { syn::Fields::Unnamed(fields) => { let ty = &fields.unnamed[0].ty; - let context_name = syn::Ident::new(&format!("{}Context", &name), Span::call_site()); - - - match &interactive_clap_attrs_context.output_context_dir { - Some(output_context_dir) => quote! { - Some(#cli_name::#variant_ident(inner_cli_args)) => { - type Alias = <#name as interactive_clap::ToInteractiveClapContextScope>::InteractiveClapContextScope; - let new_context_scope = Alias::#variant_ident; - let new_context = #context_name::from_previous_context(context.clone(), &new_context_scope)?; - let output_context = #output_context_dir::from(new_context); - let optional_inner_args = <#ty as interactive_clap::FromCli>::from_cli(Some(inner_cli_args), output_context)?; - if let Some(inner_args) = optional_inner_args { - Ok(Some(Self::#variant_ident(inner_args,))) - } else { - Self::choose_variant(context.clone()) + quote! { + Some(#cli_name::#variant_ident(inner_cli_args)) => { + #output_context + let cli_inner_args = <#ty as interactive_clap::FromCli>::from_cli(Some(inner_cli_args), output_context.into()); + match cli_inner_args { + interactive_clap::ResultFromCli::Ok(cli_args) => { + interactive_clap::ResultFromCli::Ok(#cli_name::#variant_ident(cli_args)) } - } - }, - None => quote! { - Some(#cli_name::#variant_ident(inner_cli_args)) => { - let optional_inner_args = <#ty as interactive_clap::FromCli>::from_cli(Some(inner_cli_args), context.clone().into())?; - if let Some(inner_args) = optional_inner_args { - Ok(Some(Self::#variant_ident(inner_args,))) - } else { - Self::choose_variant(context.clone()) + interactive_clap::ResultFromCli::Back => { + optional_clap_variant = None; + continue; + }, + interactive_clap::ResultFromCli::Cancel(Some(cli_args)) => { + interactive_clap::ResultFromCli::Cancel(Some(#cli_name::#variant_ident(cli_args))) + } + interactive_clap::ResultFromCli::Cancel(None) => { + interactive_clap::ResultFromCli::Cancel(None) + } + interactive_clap::ResultFromCli::Err(Some(cli_args), err) => { + interactive_clap::ResultFromCli::Err(Some(#cli_name::#variant_ident(cli_args)), err) + } + interactive_clap::ResultFromCli::Err(None, err) => { + interactive_clap::ResultFromCli::Err(None, err) } } } } }, syn::Fields::Unit => { - quote! { - Some(#cli_name::#variant_ident) => Ok(Some(Self::#variant_ident)), + match &interactive_clap_attrs_context.output_context_dir { + Some(output_context_dir) => quote! { + Some(#cli_name::#variant_ident) => { + type Alias = <#name as interactive_clap::ToInteractiveClapContextScope>::InteractiveClapContextScope; + let new_context_scope = Alias::#variant_ident; + let output_context = match #output_context_dir::from_previous_context(context.clone(), &new_context_scope) { + Ok(new_context) => new_context, + Err(err) => return interactive_clap::ResultFromCli::Err(Some(#cli_name::#variant_ident), err), + }; + interactive_clap::ResultFromCli::Ok(#cli_name::#variant_ident) + } + }, + None => quote! { + Some(#cli_name::#variant_ident) => { + interactive_clap::ResultFromCli::Ok(#cli_name::#variant_ident) + }, + } } }, _ => abort_call_site!("Only option `Fields::Unnamed` or `Fields::Unit` is needed") @@ -71,12 +103,20 @@ pub fn from_cli_for_enum( type FromCliContext = #input_context_dir; type FromCliError = color_eyre::eyre::Error; fn from_cli( - optional_clap_variant: Option<::CliVariant>, + mut optional_clap_variant: Option<::CliVariant>, context: Self::FromCliContext, - ) -> Result, Self::FromCliError> where Self: Sized + interactive_clap::ToCli { - match optional_clap_variant { - #(#from_cli_variants)* - None => Self::choose_variant(context.clone()), + ) -> interactive_clap::ResultFromCli<::CliVariant, Self::FromCliError> where Self: Sized + interactive_clap::ToCli { + loop { + return match optional_clap_variant { + #(#from_cli_variants)* + None => match Self::choose_variant(context.clone()) { + interactive_clap::ResultFromCli::Ok(cli_args) => { + optional_clap_variant = Some(cli_args); + continue; + }, + result => return result, + }, + } } } } diff --git a/interactive-clap-derive/src/derives/interactive_clap/methods/from_cli_for_struct.rs b/interactive-clap-derive/src/derives/interactive_clap/methods/from_cli_for_struct.rs index e532bca..1d75873 100644 --- a/interactive-clap-derive/src/derives/interactive_clap/methods/from_cli_for_struct.rs +++ b/interactive-clap-derive/src/derives/interactive_clap/methods/from_cli_for_struct.rs @@ -33,13 +33,7 @@ pub fn from_cli_for_struct( let field_value_named_arg = if let Some(token_stream) = fields .iter() - .map(|field| { - field_value_named_arg( - name, - field, - &interactive_clap_attrs_context.output_context_dir, - ) - }) + .map(|field| field_value_named_arg(name, field)) .find(|token_stream| !token_stream.is_empty()) { token_stream @@ -49,13 +43,7 @@ pub fn from_cli_for_struct( let field_value_subcommand = if let Some(token_stream) = fields .iter() - .map(|field| { - field_value_subcommand( - name, - field, - &interactive_clap_attrs_context.output_context_dir, - ) - }) + .map(field_value_subcommand) .find(|token_stream| !token_stream.is_empty()) { token_stream @@ -63,11 +51,9 @@ pub fn from_cli_for_struct( quote!() }; - let struct_fields = fields - .iter() - .map(|field| struct_field(field, &fields_without_subcommand)); - - let input_context_dir = interactive_clap_attrs_context.get_input_context_dir(); + let input_context_dir = interactive_clap_attrs_context + .clone() + .get_input_context_dir(); let interactive_clap_context_scope_for_struct = syn::Ident::new( &format!("InteractiveClapContextScopeFor{}", &name), @@ -77,6 +63,19 @@ pub fn from_cli_for_struct( let new_context_scope = #interactive_clap_context_scope_for_struct { #(#fields_without_subcommand,)* }; }; + let output_context = match &interactive_clap_attrs_context.output_context_dir { + Some(output_context_dir) => { + quote! { + let output_context = match #output_context_dir::from_previous_context(context.clone(), &new_context_scope) { + Ok(new_context) => new_context, + Err(err) => return interactive_clap::ResultFromCli::Err(Some(clap_variant), err), + }; + let context = output_context; + } + } + None => quote!(), + }; + quote! { impl interactive_clap::FromCli for #name { type FromCliContext = #input_context_dir; @@ -84,12 +83,14 @@ pub fn from_cli_for_struct( fn from_cli( optional_clap_variant: Option<::CliVariant>, context: Self::FromCliContext, - ) -> Result, Self::FromCliError> where Self: Sized + interactive_clap::ToCli { + ) -> interactive_clap::ResultFromCli<::CliVariant, Self::FromCliError> where Self: Sized + interactive_clap::ToCli { + let mut clap_variant = optional_clap_variant.unwrap_or_default(); #(#fields_value)* #new_context_scope + #output_context #field_value_named_arg #field_value_subcommand; - Ok(Some(Self{ #(#struct_fields,)* })) + interactive_clap::ResultFromCli::Ok(clap_variant) } } } @@ -97,26 +98,25 @@ pub fn from_cli_for_struct( fn fields_value(field: &syn::Field) -> proc_macro2::TokenStream { let ident_field = &field.clone().ident.expect("this field does not exist"); - let fn_from_cli_arg = syn::Ident::new(&format!("from_cli_{}", &ident_field), Span::call_site()); + let fn_input_arg = syn::Ident::new(&format!("input_{}", &ident_field), Span::call_site()); if super::fields_without_subcommand::is_field_without_subcommand(field) { quote! { - let #ident_field = Self::#fn_from_cli_arg( - optional_clap_variant - .clone() - .and_then(|clap_variant| clap_variant.#ident_field), - &context, - )?; + if clap_variant.#ident_field.is_none() { + clap_variant + .#ident_field = match Self::#fn_input_arg(&context) { + Ok(Some(#ident_field)) => Some(#ident_field), + Ok(None) => return interactive_clap::ResultFromCli::Cancel(Some(clap_variant)), + Err(err) => return interactive_clap::ResultFromCli::Err(Some(clap_variant), err), + }; + }; + let #ident_field = clap_variant.#ident_field.clone().expect("Unexpected error"); } } else { quote!() } } -fn field_value_named_arg( - name: &syn::Ident, - field: &syn::Field, - output_context_dir: &Option, -) -> proc_macro2::TokenStream { +fn field_value_named_arg(name: &syn::Ident, field: &syn::Field) -> proc_macro2::TokenStream { let ident_field = &field.clone().ident.expect("this field does not exist"); let ty = &field.ty; if field.attrs.is_empty() { @@ -144,38 +144,26 @@ fn field_value_named_arg( let enum_for_clap_named_arg = syn::Ident::new(&format!("ClapNamedArg{}For{}", &type_string, &name), Span::call_site()); let variant_name_string = crate::helpers::snake_case_to_camel_case::snake_case_to_camel_case(ident_field.to_string()); let variant_name = &syn::Ident::new(&variant_name_string, Span::call_site()); - match output_context_dir { - Some(output_context_dir) => { - let context_for_struct = syn::Ident::new(&format!("{}Context", &name), Span::call_site()); - quote! { - let new_context = #context_for_struct::from_previous_context(context, &new_context_scope)?; - let output_context = #output_context_dir::from(new_context); - let #ident_field = <#ty as interactive_clap::FromCli>::from_cli( - optional_clap_variant.and_then(|clap_variant| match clap_variant.#ident_field { - Some(#enum_for_clap_named_arg::#variant_name(cli_arg)) => Some(cli_arg), - None => None, - }), - output_context, - )?; - let #ident_field = if let Some(value) = #ident_field { - value - } else { - return Ok(None); - } + quote! { + let optional_field = match clap_variant.#ident_field.take() { + Some(#enum_for_clap_named_arg::#variant_name(cli_arg)) => Some(cli_arg), + None => None, + }; + match <#ty as interactive_clap::FromCli>::from_cli( + optional_field, + context.into(), + ) { + interactive_clap::ResultFromCli::Ok(cli_field) => { + clap_variant.#ident_field = Some(#enum_for_clap_named_arg::#variant_name(cli_field)); } - }, - None => quote! { - let #ident_field = <#ty as interactive_clap::FromCli>::from_cli( - optional_clap_variant.and_then(|clap_variant| match clap_variant.#ident_field { - Some(#enum_for_clap_named_arg::#variant_name(cli_sender)) => Some(cli_sender), - None => None, - }), - context.into(), - )?; - let #ident_field = if let Some(value) = #ident_field { - value - } else { - return Ok(None); + interactive_clap::ResultFromCli::Cancel(optional_cli_field) => { + clap_variant.#ident_field = optional_cli_field.map(#enum_for_clap_named_arg::#variant_name); + return interactive_clap::ResultFromCli::Cancel(Some(clap_variant)); + } + interactive_clap::ResultFromCli::Back => return interactive_clap::ResultFromCli::Back, + interactive_clap::ResultFromCli::Err(optional_cli_field, err) => { + clap_variant.#ident_field = optional_cli_field.map(#enum_for_clap_named_arg::#variant_name); + return interactive_clap::ResultFromCli::Err(Some(clap_variant), err); } } } @@ -187,11 +175,7 @@ fn field_value_named_arg( } } -fn field_value_subcommand( - name: &syn::Ident, - field: &syn::Field, - output_context_dir: &Option, -) -> proc_macro2::TokenStream { +fn field_value_subcommand(field: &syn::Field) -> proc_macro2::TokenStream { let ident_field = &field.clone().ident.expect("this field does not exist"); let ty = &field.ty; if field.attrs.is_empty() { @@ -207,32 +191,23 @@ fn field_value_subcommand( } }) .map(|_| { - match output_context_dir { - Some(output_context_dir) => { - let context_for_struct = syn::Ident::new(&format!("{}Context", &name), Span::call_site()); - quote! { - let new_context = #context_for_struct::from_previous_context(context, &new_context_scope)?; - let output_context = #output_context_dir::from(new_context); - let #ident_field = match optional_clap_variant.and_then(|clap_variant| clap_variant.#ident_field) { - Some(cli_arg) => <#ty as interactive_clap::FromCli>::from_cli(Some(cli_arg), output_context)?, - None => #ty::choose_variant(output_context)?, - }; - let #ident_field = if let Some(value) = #ident_field { - value - } else { - return Ok(None); - } + quote! { + match <#ty as interactive_clap::FromCli>::from_cli(clap_variant.#ident_field.take(), context.into()) { + interactive_clap::ResultFromCli::Ok(cli_field) => { + clap_variant.#ident_field = Some(cli_field); } - }, - None => quote! { - let #ident_field = match optional_clap_variant.and_then(|clap_variant| clap_variant.#ident_field) { - Some(cli_arg) => <#ty as interactive_clap::FromCli>::from_cli(Some(cli_arg), context)?, - None => #ty::choose_variant(context.into())?, - }; - let #ident_field = if let Some(value) = #ident_field { - value - } else { - return Ok(None); + interactive_clap::ResultFromCli::Cancel(option_cli_field) => { + clap_variant.#ident_field = option_cli_field; + return interactive_clap::ResultFromCli::Cancel(Some(clap_variant)); + } + interactive_clap::ResultFromCli::Cancel(option_cli_field) => { + clap_variant.#ident_field = option_cli_field; + return interactive_clap::ResultFromCli::Cancel(Some(clap_variant)); + } + interactive_clap::ResultFromCli::Back => return interactive_clap::ResultFromCli::Back, + interactive_clap::ResultFromCli::Err(option_cli_field, err) => { + clap_variant.#ident_field = option_cli_field; + return interactive_clap::ResultFromCli::Err(Some(clap_variant), err); } } } @@ -243,23 +218,3 @@ fn field_value_subcommand( } } } - -fn struct_field( - field: &syn::Field, - fields_without_subcommand: &[proc_macro2::TokenStream], -) -> proc_macro2::TokenStream { - let ident_field = &field.clone().ident.expect("this field does not exist"); - if fields_without_subcommand - .iter() - .map(|token_stream| token_stream.to_string()) - .any(|x| *ident_field == x) - { - quote! { - #ident_field: new_context_scope.#ident_field - } - } else { - quote! { - #ident_field - } - } -} diff --git a/interactive-clap-derive/src/derives/interactive_clap/methods/get_arg_from_cli_for_struct.rs b/interactive-clap-derive/src/derives/interactive_clap/methods/get_arg_from_cli_for_struct.rs deleted file mode 100644 index 830399b..0000000 --- a/interactive-clap-derive/src/derives/interactive_clap/methods/get_arg_from_cli_for_struct.rs +++ /dev/null @@ -1,104 +0,0 @@ -extern crate proc_macro; - -use proc_macro2::Span; -use quote::quote; -use syn; - -pub fn from_cli_arg(ast: &syn::DeriveInput, fields: &syn::Fields) -> Vec { - let interactive_clap_attrs_context = - super::interactive_clap_attrs_context::InteractiveClapAttrsContext::new(ast); - - let fields_without_subcommand = fields - .iter() - .filter(|field| super::fields_without_subcommand::is_field_without_subcommand(field)) - .map(|field| { - let ident_field = &field.clone().ident.expect("this field does not exist"); - quote! {#ident_field} - }) - .collect::>(); - - let fields_without_skip_default_from_cli_arg = fields - .iter() - .filter(|field| { - super::fields_without_skip_default_from_cli_arg::is_field_without_skip_default_from_cli_arg( - field, - ) - }) - .map(|field| { - let ident_field = &field.clone().ident.expect("this field does not exist"); - quote! {#ident_field} - }) - .collect::>(); - - let get_arg_for_fields = fields - .iter() - .map(|field| { - let ident_field = &field.clone().ident.expect("this field does not exist"); - let ty = &field.ty; - let fields_without_subcommand_to_string = fields_without_subcommand - .iter() - .map(|token_stream| token_stream.to_string()) - .collect::>(); - let fields_without_skip_default_from_cli_arg_to_string = - fields_without_skip_default_from_cli_arg - .iter() - .map(|token_stream| token_stream.to_string()) - .collect::>(); - if fields_without_subcommand_to_string.contains(&ident_field.to_string()) - & fields_without_skip_default_from_cli_arg_to_string - .iter() - .map(|token_stream| token_stream.to_string()) - .any(|x| *ident_field == x) - { - let fn_from_cli_arg = - syn::Ident::new(&format!("from_cli_{}", &ident_field), Span::call_site()); - let optional_cli_field_name = - syn::Ident::new(&format!("optional_cli_{}", ident_field), Span::call_site()); - let input_context_dir = interactive_clap_attrs_context - .clone() - .get_input_context_dir(); - let cli_field_type = super::cli_field_type::cli_field_type(ty); - let fn_input_arg = - syn::Ident::new(&format!("input_{}", &ident_field), Span::call_site()); - - 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(), - }; - if let "Option" = type_string.as_str() { - quote! { - fn #fn_from_cli_arg( - #optional_cli_field_name: #cli_field_type, - context: &#input_context_dir, - ) -> color_eyre::eyre::Result<#ty> { - match #optional_cli_field_name { - Some(#ident_field) => Ok(Some(#ident_field)), - None => Self::#fn_input_arg(&context), - } - } - } - } else { - quote! { - fn #fn_from_cli_arg( - #optional_cli_field_name: #cli_field_type, - context: &#input_context_dir, - ) -> color_eyre::eyre::Result<#ty> { - match #optional_cli_field_name { - Some(#ident_field) => Ok(#ident_field), - None => Self::#fn_input_arg(&context), - } - } - } - } - } else { - quote!() - } - }) - .filter(|token_stream| !token_stream.is_empty()) - .collect::>(); - - get_arg_for_fields -} diff --git a/interactive-clap-derive/src/derives/interactive_clap/methods/input_arg.rs b/interactive-clap-derive/src/derives/interactive_clap/methods/input_arg.rs index 5800b76..48391ce 100644 --- a/interactive-clap-derive/src/derives/interactive_clap/methods/input_arg.rs +++ b/interactive-clap-derive/src/derives/interactive_clap/methods/input_arg.rs @@ -34,8 +34,12 @@ pub fn vec_fn_input_arg( return quote! { fn #fn_input_arg( _context: &#input_context_dir, - ) -> color_eyre::eyre::Result<#ty> { - Ok(inquire::CustomType::new(#promt).prompt()?) + ) -> color_eyre::eyre::Result> { + match inquire::CustomType::new(#promt).prompt() { + Ok(value) => Ok(Some(value)), + Err(inquire::error::InquireError::OperationCanceled | inquire::error::InquireError::OperationInterrupted) => Ok(None), + Err(err) => Err(err.into()), + } } }; } @@ -63,8 +67,12 @@ pub fn vec_fn_input_arg( quote! { fn #fn_input_arg( _context: &#input_context_dir, - ) -> color_eyre::eyre::Result<#ty> { - Ok(inquire::CustomType::new(#literal.to_string().as_str()).prompt()?) + ) -> color_eyre::eyre::Result> { + match inquire::CustomType::new(#literal).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/src/derives/interactive_clap/methods/mod.rs b/interactive-clap-derive/src/derives/interactive_clap/methods/mod.rs index 429190f..17f11d9 100644 --- a/interactive-clap-derive/src/derives/interactive_clap/methods/mod.rs +++ b/interactive-clap-derive/src/derives/interactive_clap/methods/mod.rs @@ -1,10 +1,8 @@ pub mod choose_variant; pub mod cli_field_type; -pub mod fields_without_skip_default_from_cli_arg; pub mod fields_without_skip_default_input_arg; pub mod fields_without_subcommand; pub mod from_cli_for_enum; pub mod from_cli_for_struct; -pub mod get_arg_from_cli_for_struct; pub mod input_arg; pub mod interactive_clap_attrs_context; diff --git a/interactive-clap-derive/src/derives/interactive_clap/mod.rs b/interactive-clap-derive/src/derives/interactive_clap/mod.rs index 1f2cf6e..ce80728 100644 --- a/interactive-clap-derive/src/derives/interactive_clap/mod.rs +++ b/interactive-clap-derive/src/derives/interactive_clap/mod.rs @@ -112,8 +112,6 @@ pub fn impl_interactive_clap(ast: &syn::DeriveInput) -> TokenStream { let fn_from_cli_for_struct = self::methods::from_cli_for_struct::from_cli_for_struct(ast, &fields); - let fn_get_arg = self::methods::get_arg_from_cli_for_struct::from_cli_arg(ast, &fields); - let vec_fn_input_arg = self::methods::input_arg::vec_fn_input_arg(ast, &fields); let context_scope_fields = fields @@ -175,7 +173,7 @@ pub fn impl_interactive_clap(ast: &syn::DeriveInput) -> TokenStream { }; let gen = quote! { - #[derive(Debug, Default, Clone, clap::Parser, interactive_clap_derive::ToCliArgs)] + #[derive(Debug, Default, Clone, clap::Parser, interactive_clap::ToCliArgs)] #[clap(author, version, about, long_about = None)] pub struct #cli_name { #( #cli_fields, )* @@ -190,7 +188,6 @@ pub fn impl_interactive_clap(ast: &syn::DeriveInput) -> TokenStream { #fn_from_cli_for_struct impl #name { - #(#fn_get_arg)* #(#vec_fn_input_arg)* fn try_parse() -> Result<#cli_name, clap::Error> { @@ -305,7 +302,7 @@ pub fn impl_interactive_clap(ast: &syn::DeriveInput) -> TokenStream { self::methods::from_cli_for_enum::from_cli_for_enum(ast, variants); let gen = quote! { - #[derive(Debug, Clone, clap::Parser, interactive_clap_derive::ToCliArgs)] + #[derive(Debug, Clone, clap::Parser, interactive_clap::ToCliArgs)] pub enum #cli_name { #( #enum_variants, )* } diff --git a/src/lib.rs b/src/lib.rs index 1e65808..42b2d3a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -30,13 +30,20 @@ pub trait ToCliArgs { fn to_cli_args(&self) -> std::collections::VecDeque; } +pub enum ResultFromCli { + Ok(T), + Cancel(Option), + Back, + Err(Option, E), +} + pub trait FromCli { type FromCliContext; type FromCliError; fn from_cli( optional_clap_variant: Option<::CliVariant>, context: Self::FromCliContext, - ) -> Result, Self::FromCliError> + ) -> ResultFromCli<::CliVariant, Self::FromCliError> where Self: Sized + ToCli; }