mirror of
https://github.com/Drop-OSS/interactive-clap.git
synced 2026-01-30 20:55:25 +01:00
feat: Added possibility to process optional fields (#13)
Automatic generation of "interactive_clap::FromCli" for structures with optional fields --------- Co-authored-by: Vlad Frolov <frolvlad@gmail.com>
This commit is contained in:
@@ -5,6 +5,8 @@
|
||||
// 3) run an example: ./advanced_struct (without parameters) => entered interactive mode
|
||||
// ./advanced_struct --age-full-years 30 --first-name QWE --second-name QWERTY --favorite-color red =>
|
||||
// => cli_args: CliArgs { age: Some(30), first_name: Some("QWE"), second_name: Some("QWERTY"), favorite_color: Some(Red) }
|
||||
// ./advanced_struct --first-name QWE --second-name QWERTY --favorite-color red =>
|
||||
// => cli_args: CliArgs { age: None, first_name: Some("QWE"), second_name: Some("QWERTY"), favorite_color: Some(Red) }
|
||||
// To learn more about the parameters, use "help" flag: ./advanced_struct --help
|
||||
|
||||
use inquire::Select;
|
||||
@@ -12,13 +14,13 @@ use interactive_clap::{ResultFromCli, ToCliArgs};
|
||||
use strum::{EnumDiscriminants, EnumIter, EnumMessage, IntoEnumIterator};
|
||||
|
||||
#[derive(Debug, Clone, interactive_clap::InteractiveClap)]
|
||||
#[interactive_clap(skip_default_from_cli)]
|
||||
struct Args {
|
||||
#[interactive_clap(long = "age-full-years")]
|
||||
#[interactive_clap(skip_default_input_arg)]
|
||||
#[interactive_clap(skip_interactive_input)]
|
||||
/// If you want, enter the full age on the command line
|
||||
age: Option<u64>,
|
||||
#[interactive_clap(long)]
|
||||
///What is your first name?
|
||||
/// What is your first name?
|
||||
first_name: String,
|
||||
#[interactive_clap(long)]
|
||||
#[interactive_clap(skip_default_input_arg)]
|
||||
@@ -29,67 +31,7 @@ struct Args {
|
||||
favorite_color: ColorPalette,
|
||||
}
|
||||
|
||||
impl interactive_clap::FromCli for Args {
|
||||
type FromCliContext = ();
|
||||
type FromCliError = color_eyre::eyre::Error;
|
||||
fn from_cli(
|
||||
optional_clap_variant: Option<<Self as interactive_clap::ToCli>::CliVariant>,
|
||||
context: Self::FromCliContext,
|
||||
) -> ResultFromCli<<Self as interactive_clap::ToCli>::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");
|
||||
if clap_variant.favorite_color.is_none() {
|
||||
clap_variant.favorite_color = match Self::input_favorite_color(&context) {
|
||||
Ok(Some(favorite_color)) => Some(favorite_color),
|
||||
Ok(None) => return ResultFromCli::Cancel(Some(clap_variant)),
|
||||
Err(err) => return ResultFromCli::Err(Some(clap_variant), err),
|
||||
};
|
||||
}
|
||||
let _favorite_color = clap_variant
|
||||
.favorite_color
|
||||
.clone()
|
||||
.expect("Unexpected error");
|
||||
ResultFromCli::Ok(clap_variant)
|
||||
}
|
||||
}
|
||||
|
||||
impl Args {
|
||||
fn input_age(_context: &()) -> color_eyre::eyre::Result<Option<u64>> {
|
||||
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_second_name(_context: &()) -> color_eyre::eyre::Result<Option<String>> {
|
||||
match inquire::Text::new("Input second name".to_string().as_str()).prompt() {
|
||||
Ok(value) => Ok(Some(value)),
|
||||
|
||||
@@ -99,10 +99,28 @@ 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_input_arg = syn::Ident::new(&format!("input_{}", &ident_field), Span::call_site());
|
||||
if field.ty.to_token_stream().to_string() == "bool" {
|
||||
if field.ty.to_token_stream().to_string() == "bool"
|
||||
|| super::skip_interactive_input::is_skip_interactive_input(field)
|
||||
{
|
||||
quote! {
|
||||
let #ident_field = clap_variant.#ident_field.clone();
|
||||
}
|
||||
} else if field
|
||||
.ty
|
||||
.to_token_stream()
|
||||
.to_string()
|
||||
.starts_with("Option <")
|
||||
{
|
||||
quote! {
|
||||
if clap_variant.#ident_field.is_none() {
|
||||
clap_variant
|
||||
.#ident_field = match Self::#fn_input_arg(&context) {
|
||||
Ok(optional_field) => optional_field,
|
||||
Err(err) => return interactive_clap::ResultFromCli::Err(Some(clap_variant), err),
|
||||
};
|
||||
};
|
||||
let #ident_field = clap_variant.#ident_field.clone();
|
||||
}
|
||||
} else if super::fields_without_subcommand::is_field_without_subcommand(field) {
|
||||
quote! {
|
||||
if clap_variant.#ident_field.is_none() {
|
||||
|
||||
@@ -44,6 +44,10 @@ pub fn vec_fn_input_arg(
|
||||
};
|
||||
}
|
||||
|
||||
if super::skip_interactive_input::is_skip_interactive_input(field) {
|
||||
return quote! {};
|
||||
}
|
||||
|
||||
let doc_attrs = field
|
||||
.attrs
|
||||
.iter()
|
||||
|
||||
@@ -6,3 +6,4 @@ 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;
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
extern crate proc_macro;
|
||||
|
||||
use syn;
|
||||
|
||||
pub fn is_skip_interactive_input(field: &syn::Field) -> bool {
|
||||
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_interactive_input"),
|
||||
_ => false,
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user