mirror of
https://github.com/Drop-OSS/interactive-clap.git
synced 2026-01-30 20:55:25 +01:00
feat: add long_vec_multiple_opt attribute (#22)
screenshot from `cargo-near` with
[patched](06fd2569ec)
`interactive-clap` version from pr's fork:

---------
Co-authored-by: dj8yf0μl <noreply@nowhere.org>
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,3 +1,4 @@
|
||||
/target
|
||||
.DS_Store
|
||||
Cargo.lock
|
||||
*.snap.new
|
||||
|
||||
@@ -63,6 +63,7 @@ impl interactive_clap::FromCli for Contract {
|
||||
out_dir: cli_build_command_args.out_dir.clone(),
|
||||
manifest_path: cli_build_command_args.manifest_path.clone(),
|
||||
color: cli_build_command_args.color.clone(),
|
||||
env: cli_build_command_args.env.clone(),
|
||||
}
|
||||
} else {
|
||||
BuildCommand::default()
|
||||
@@ -150,6 +151,11 @@ pub struct BuildCommand {
|
||||
#[interactive_clap(value_enum)]
|
||||
#[interactive_clap(skip_interactive_input)]
|
||||
pub color: Option<ColorPreference>,
|
||||
|
||||
// `long_vec_multiple_opt` implies `skip_interactive_input`
|
||||
// `long_vec_multiple_opt` implies `long`
|
||||
#[interactive_clap(long_vec_multiple_opt)]
|
||||
pub env: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -171,6 +177,7 @@ impl BuildCommandlContext {
|
||||
out_dir: scope.out_dir.clone(),
|
||||
manifest_path: scope.manifest_path.clone(),
|
||||
color: scope.color.clone(),
|
||||
env: scope.env.clone(),
|
||||
};
|
||||
Ok(Self { build_command_args })
|
||||
}
|
||||
@@ -193,7 +200,7 @@ pub enum Mode {
|
||||
Offline,
|
||||
}
|
||||
|
||||
use std::{env, str::FromStr};
|
||||
use std::str::FromStr;
|
||||
|
||||
#[derive(Debug, EnumDiscriminants, Clone, clap::ValueEnum)]
|
||||
#[strum_discriminants(derive(EnumMessage, EnumIter))]
|
||||
@@ -252,7 +259,7 @@ fn main() -> color_eyre::Result<()> {
|
||||
<Contract as interactive_clap::FromCli>::from_cli(Some(cli_contract), context);
|
||||
match contract {
|
||||
ResultFromCli::Ok(cli_contract) | ResultFromCli::Cancel(Some(cli_contract)) => {
|
||||
println!("contract: {cli_contract:?}");
|
||||
println!("contract: {cli_contract:#?}");
|
||||
println!(
|
||||
"Your console command: {}",
|
||||
shell_words::join(&cli_contract.to_cli_args())
|
||||
|
||||
@@ -37,3 +37,14 @@ 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
|
||||
}
|
||||
|
||||
@@ -2,6 +2,9 @@ extern crate proc_macro;
|
||||
|
||||
use syn;
|
||||
|
||||
use crate::LONG_VEC_MUTLIPLE_OPT;
|
||||
|
||||
|
||||
pub fn is_skip_interactive_input(field: &syn::Field) -> bool {
|
||||
field
|
||||
.attrs
|
||||
@@ -9,10 +12,11 @@ pub fn is_skip_interactive_input(field: &syn::Field) -> bool {
|
||||
.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"),
|
||||
proc_macro2::TokenTree::Group(group) => {
|
||||
let group_string = group.stream().to_string();
|
||||
group_string.contains("skip_interactive_input")
|
||||
|| group_string.contains(LONG_VEC_MUTLIPLE_OPT)
|
||||
}
|
||||
_ => false,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
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};
|
||||
use syn;
|
||||
|
||||
mod methods;
|
||||
use crate::LONG_VEC_MUTLIPLE_OPT;
|
||||
|
||||
pub(crate) mod methods;
|
||||
|
||||
pub fn impl_interactive_clap(ast: &syn::DeriveInput) -> TokenStream {
|
||||
let name = &ast.ident;
|
||||
@@ -35,13 +38,16 @@ pub fn impl_interactive_clap(ast: &syn::DeriveInput) -> TokenStream {
|
||||
for attr_token in attr.tokens.clone() {
|
||||
match attr_token {
|
||||
proc_macro2::TokenTree::Group(group) => {
|
||||
if group.stream().to_string().contains("subcommand")
|
||||
|| group.stream().to_string().contains("value_enum")
|
||||
|| group.stream().to_string().contains("long")
|
||||
|| (group.stream().to_string() == *"skip")
|
||||
|| (group.stream().to_string() == *"flatten")
|
||||
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")
|
||||
{
|
||||
clap_attr_vec.push(group.stream())
|
||||
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());
|
||||
@@ -80,6 +86,18 @@ pub fn impl_interactive_clap(ast: &syn::DeriveInput) -> TokenStream {
|
||||
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")
|
||||
@@ -410,6 +428,21 @@ fn for_cli_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) => {
|
||||
|
||||
@@ -5,6 +5,8 @@ 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),
|
||||
@@ -66,6 +68,7 @@ impl InteractiveClapAttrsCliField {
|
||||
&ident_field_to_kebab_case_string,
|
||||
Span::call_site(),
|
||||
);
|
||||
|
||||
if field.ty.to_token_stream().to_string() == "bool"
|
||||
{
|
||||
unnamed_args = quote! {
|
||||
@@ -80,6 +83,14 @@ impl InteractiveClapAttrsCliField {
|
||||
args.push_front(std::concat!("--", #ident_field_to_kebab_case).to_string());
|
||||
}
|
||||
};
|
||||
if cli_field_type::starts_with_vec(&field.ty) {
|
||||
unnamed_args = quote! {
|
||||
for arg in self.#ident_field.iter().rev() {
|
||||
args.push_front(arg.to_string());
|
||||
args.push_front(std::concat!("--", #ident_field_to_kebab_case).to_string());
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,14 @@ mod helpers;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
/// `#[interactive_clap(...)]` attribute used for specifying multiple values with `Vec<..>` type,
|
||||
/// by repeating corresponding flag `--field-name` (kebab case) for each value
|
||||
///
|
||||
/// implies `#[interactive_clap(long)]`
|
||||
///
|
||||
/// 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";
|
||||
|
||||
#[proc_macro_derive(InteractiveClap, attributes(interactive_clap))]
|
||||
#[proc_macro_error]
|
||||
pub fn interactive_clap(input: TokenStream) -> TokenStream {
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
---
|
||||
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 env: Vec<String>,
|
||||
}
|
||||
impl interactive_clap::ToCli for Args {
|
||||
type CliVariant = CliArgs;
|
||||
}
|
||||
pub struct InteractiveClapContextScopeForArgs {
|
||||
pub env: Vec<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<<Self as interactive_clap::ToCli>::CliVariant>,
|
||||
context: Self::FromCliContext,
|
||||
) -> interactive_clap::ResultFromCli<
|
||||
<Self as interactive_clap::ToCli>::CliVariant,
|
||||
Self::FromCliError,
|
||||
>
|
||||
where
|
||||
Self: Sized + interactive_clap::ToCli,
|
||||
{
|
||||
let mut clap_variant = optional_clap_variant.clone().unwrap_or_default();
|
||||
let env = clap_variant.env.clone();
|
||||
let new_context_scope = InteractiveClapContextScopeForArgs {
|
||||
env: env.into(),
|
||||
};
|
||||
interactive_clap::ResultFromCli::Ok(clap_variant)
|
||||
}
|
||||
}
|
||||
impl Args {
|
||||
pub fn try_parse() -> Result<CliArgs, clap::Error> {
|
||||
<CliArgs as clap::Parser>::try_parse()
|
||||
}
|
||||
pub fn parse() -> CliArgs {
|
||||
<CliArgs as clap::Parser>::parse()
|
||||
}
|
||||
pub fn try_parse_from<I, T>(itr: I) -> Result<CliArgs, clap::Error>
|
||||
where
|
||||
I: ::std::iter::IntoIterator<Item = T>,
|
||||
T: ::std::convert::Into<::std::ffi::OsString> + ::std::clone::Clone,
|
||||
{
|
||||
<CliArgs as clap::Parser>::try_parse_from(itr)
|
||||
}
|
||||
}
|
||||
impl From<Args> for CliArgs {
|
||||
fn from(args: Args) -> Self {
|
||||
Self { env: args.env.into() }
|
||||
}
|
||||
}
|
||||
@@ -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 CliArgs {
|
||||
fn to_cli_args(&self) -> std::collections::VecDeque<String> {
|
||||
let mut args = std::collections::VecDeque::new();
|
||||
for arg in self.env.iter().rev() {
|
||||
args.push_front(arg.to_string());
|
||||
args.push_front(std::concat!("--", "env").to_string());
|
||||
}
|
||||
args
|
||||
}
|
||||
}
|
||||
@@ -44,3 +44,47 @@ fn test_flag() {
|
||||
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_vec_multiple_opt() {
|
||||
let input = syn::parse_quote! {
|
||||
struct Args {
|
||||
#[interactive_clap(long_vec_multiple_opt)]
|
||||
pub env: Vec<String>,
|
||||
}
|
||||
};
|
||||
|
||||
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 input = syn::parse_quote! {
|
||||
pub struct CliArgs {
|
||||
#[clap(long)]
|
||||
pub env: Vec<String>,
|
||||
}
|
||||
};
|
||||
|
||||
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]
|
||||
// testing correct panic msg isn't really very compatible with
|
||||
// `proc-macro-error` crate
|
||||
#[should_panic]
|
||||
fn test_vec_multiple_opt_err() {
|
||||
let input = syn::parse_quote! {
|
||||
struct Args {
|
||||
#[interactive_clap(long_vec_multiple_opt)]
|
||||
pub env: String,
|
||||
}
|
||||
};
|
||||
|
||||
let interactive_clap_codegen = crate::derives::interactive_clap::impl_interactive_clap(&input);
|
||||
insta::assert_snapshot!(pretty_codegen(&interactive_clap_codegen));
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user