This commit is contained in:
FroVolod
2022-02-17 14:09:45 +02:00
parent 940a49c0cf
commit ab874b06be
15 changed files with 470 additions and 358 deletions

View File

@@ -2,18 +2,22 @@ extern crate proc_macro;
use proc_macro2::Span;
use proc_macro_error::abort_call_site;
use syn;
use quote::quote;
use syn;
pub fn fn_choose_variant(ast: &syn::DeriveInput, variants: &syn::punctuated::Punctuated<syn::Variant, syn::token::Comma>) -> proc_macro2::TokenStream {
pub fn fn_choose_variant(
ast: &syn::DeriveInput,
variants: &syn::punctuated::Punctuated<syn::Variant, syn::token::Comma>,
) -> proc_macro2::TokenStream {
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 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 variant_ident = &variants[0].ident;
let mut cli_variant = quote! ();
let mut cli_variant = quote!();
if !ast.attrs.is_empty() {
for attr in ast.attrs.clone() {
@@ -21,23 +25,30 @@ pub fn fn_choose_variant(ast: &syn::DeriveInput, variants: &syn::punctuated::Pun
for attr_token in attr.tokens.clone() {
match attr_token {
proc_macro2::TokenTree::Group(group) => {
if group.stream().to_string().contains("disable_strum_discriminants").clone() {
if group
.stream()
.to_string()
.contains("disable_strum_discriminants")
.clone()
{
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")
}
_ => abort_call_site!(
"Only option `Fields::Unnamed` or `Fields::Unit` is needed"
),
}
};
}
_ => () //abort_call_site!("Only option `TokenTree::Group` is needed")
_ => (), //abort_call_site!("Only option `TokenTree::Group` is needed")
}
}
};
@@ -46,23 +57,29 @@ pub fn fn_choose_variant(ast: &syn::DeriveInput, variants: &syn::punctuated::Pun
match attr_token {
proc_macro2::TokenTree::Group(group) => {
if &group.stream().to_string() == "derive(EnumMessage, EnumIter)" {
let doc_attrs = ast.attrs.iter()
let doc_attrs = ast
.attrs
.iter()
.filter(|attr| attr.path.is_ident("doc".into()))
.map(|attr| {
let mut literal_string = String::new();
for attr_token in attr.tokens.clone() {
match attr_token {
proc_macro2::TokenTree::Literal(literal) => {
literal_string = literal.to_string();
}
_ => () //abort_call_site!("Only option `TokenTree::Literal` is needed")
for attr_token in attr.tokens.clone() {
match attr_token {
proc_macro2::TokenTree::Literal(literal) => {
literal_string = literal.to_string();
}
};
_ => (), //abort_call_site!("Only option `TokenTree::Literal` is needed")
}
}
literal_string
})
.collect::<Vec<_>>();
let literal_vec = doc_attrs.iter().map(|s| s.replace("\"", "")).collect::<Vec<_>>();
let literal = proc_macro2::Literal::string(literal_vec.join("\n ").as_str());
let literal_vec = doc_attrs
.iter()
.map(|s| s.replace("\"", ""))
.collect::<Vec<_>>();
let literal =
proc_macro2::Literal::string(literal_vec.join("\n ").as_str());
let enum_variants = variants.iter().map(|variant| {
let variant_ident = &variant.ident;
@@ -80,8 +97,6 @@ pub fn fn_choose_variant(ast: &syn::DeriveInput, variants: &syn::punctuated::Pun
},
_ => abort_call_site!("Only option `Fields::Unnamed` or `Fields::Unit` is needed")
}
});
cli_variant = quote! {
@@ -101,27 +116,27 @@ pub fn fn_choose_variant(ast: &syn::DeriveInput, variants: &syn::punctuated::Pun
.to_owned()
})
.collect::<Vec<_>>();
let selected = Select::with_theme(&ColorfulTheme::default())
.with_prompt(prompt)
.items(&actions)
.default(0)
.interact()
.unwrap();
variants[selected]
}
let cli_variant = match prompt_variant(#literal.to_string().as_str()) {
#( #enum_variants, )*
};
};
};
};
}
_ => () //abort_call_site!("Only option `TokenTree::Group` is needed")
_ => (), //abort_call_site!("Only option `TokenTree::Group` is needed")
}
}
};
};
}
};
let input_context = interactive_clap_attrs_context.get_input_context_dir();

View File

@@ -1,38 +1,35 @@
extern crate proc_macro;
use proc_macro_error::abort_call_site;
use syn;
use quote::quote;
use syn;
pub fn cli_field_type(ty: &syn::Type) -> proc_macro2::TokenStream {
match &ty {
syn::Type::Path(type_path) => {
match type_path.path.segments.first() {
Some(path_segment) => {
if path_segment.ident.eq("Option".into()) {
match &path_segment.arguments {
syn::PathArguments::AngleBracketed(gen_args) => {
let ty_option = &gen_args.args;
quote! {
Option<<#ty_option as interactive_clap::ToCli>::CliVariant>
}
},
_ => {
quote! {
Option<<#ty as interactive_clap::ToCli>::CliVariant>
}
},
syn::Type::Path(type_path) => match type_path.path.segments.first() {
Some(path_segment) => {
if path_segment.ident.eq("Option".into()) {
match &path_segment.arguments {
syn::PathArguments::AngleBracketed(gen_args) => {
let ty_option = &gen_args.args;
quote! {
Option<<#ty_option as interactive_clap::ToCli>::CliVariant>
}
}
} else {
quote! {
Option<<#ty as interactive_clap::ToCli>::CliVariant>
_ => {
quote! {
Option<<#ty as interactive_clap::ToCli>::CliVariant>
}
}
}
},
_ => abort_call_site!("Only option `PathSegment` is needed")
} else {
quote! {
Option<<#ty as interactive_clap::ToCli>::CliVariant>
}
}
}
_ => abort_call_site!("Only option `PathSegment` is needed"),
},
_ => abort_call_site!("Only option `Type::Path` is needed")
_ => abort_call_site!("Only option `Type::Path` is needed"),
}
}

View File

@@ -21,9 +21,9 @@ pub fn is_field_without_skip_default_from_cli(field: &syn::Field) -> bool {
} else {
false
}
},
_ => false // abort_call_site!("Only option `TokenTree::Group` is needed")
})
}
_ => false, // abort_call_site!("Only option `TokenTree::Group` is needed")
})
.next()
{
Some(token_stream) => false,

View File

@@ -1,32 +1,35 @@
extern crate proc_macro;
use proc_macro_error::abort_call_site;
use syn;
use quote::quote;
use syn;
pub fn is_field_without_subcommand(field: &syn::Field) -> bool {
if field.attrs.is_empty() {
return true
return true;
}
match field.attrs.iter()
.map(|attr| {
attr.tokens.clone()
})
match field
.attrs
.iter()
.map(|attr| attr.tokens.clone())
.flatten()
.filter(|attr_token| {
match attr_token {
proc_macro2::TokenTree::Group(group) => {
if group.stream().to_string().contains("named_arg") || group.stream().to_string().contains("subcommand") {
if group.stream().to_string().contains("named_arg")
|| group.stream().to_string().contains("subcommand")
{
true
} else {
false
}
},
_ => false // abort_call_site!("Only option `TokenTree::Group` is needed")
}
_ => false, // abort_call_site!("Only option `TokenTree::Group` is needed")
}
})
.next() {
Some(token_stream) => false,
None => true
}
.next()
{
Some(token_stream) => false,
None => true,
}
}

View File

@@ -2,17 +2,20 @@ extern crate proc_macro;
use proc_macro2::Span;
use proc_macro_error::abort_call_site;
use syn;
use quote::quote;
use syn;
pub fn from_cli_for_enum(ast: &syn::DeriveInput, variants: &syn::punctuated::Punctuated<syn::Variant, syn::token::Comma>) -> proc_macro2::TokenStream {
pub fn from_cli_for_enum(
ast: &syn::DeriveInput,
variants: &syn::punctuated::Punctuated<syn::Variant, syn::token::Comma>,
) -> proc_macro2::TokenStream {
let name = &ast.ident;
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);
let interactive_clap_attrs_context =
super::interactive_clap_attrs_context::InteractiveClapAttrsContext::new(&ast);
if interactive_clap_attrs_context.is_skip_default_from_cli {
return quote! ();
return quote!();
};
let from_cli_variants = variants.iter().map(|variant| {
@@ -44,11 +47,12 @@ pub fn from_cli_for_enum(ast: &syn::DeriveInput, variants: &syn::punctuated::Pun
},
_ => abort_call_site!("Only option `Fields::Unnamed` or `Fields::Unit` is needed")
}
});
let input_context_dir = interactive_clap_attrs_context.clone().get_input_context_dir();
let input_context_dir = interactive_clap_attrs_context
.clone()
.get_input_context_dir();
quote! {
pub fn from_cli(
optional_clap_variant: Option<#cli_name>,
@@ -56,7 +60,7 @@ pub fn from_cli_for_enum(ast: &syn::DeriveInput, variants: &syn::punctuated::Pun
) -> color_eyre::eyre::Result<Self> {
match optional_clap_variant {
#(#from_cli_variants)*
None => Self::choose_variant(context.clone()),
None => Self::choose_variant(context.clone()),
}
}
}

View File

@@ -2,69 +2,84 @@ extern crate proc_macro;
use proc_macro2::Span;
use proc_macro_error::abort_call_site;
use syn;
use quote::quote;
use syn;
pub fn from_cli_for_struct(ast: &syn::DeriveInput, fields: &syn::Fields) -> proc_macro2::TokenStream {
pub fn from_cli_for_struct(
ast: &syn::DeriveInput,
fields: &syn::Fields,
) -> proc_macro2::TokenStream {
let name = &ast.ident;
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);
let interactive_clap_attrs_context =
super::interactive_clap_attrs_context::InteractiveClapAttrsContext::new(&ast);
if interactive_clap_attrs_context.is_skip_default_from_cli {
return quote! ();
return quote!();
};
let fields_without_subcommand = fields.iter()
.filter(|field| {
super::fields_without_subcommand::is_field_without_subcommand(field)
})
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::<Vec<_>>();
let fields_value = fields.iter().map(|field| {
fields_value(field)
})
.filter(|token_stream| !token_stream.is_empty());
let fields_value = fields
.iter()
.map(|field| fields_value(field))
.filter(|token_stream| !token_stream.is_empty());
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)
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,
)
})
.filter(|token_stream| !token_stream.is_empty())
.next()
{
token_stream
} else {
quote! ()
};
{
token_stream
} else {
quote!()
};
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)
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,
)
})
.filter(|token_stream| !token_stream.is_empty())
.next()
{
token_stream
} else {
quote! ()
};
{
token_stream
} else {
quote!()
};
let struct_fields = fields.iter().map(|field| {
struct_field(field, &fields_without_subcommand)
});
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 interactive_clap_context_scope_for_struct = syn::Ident::new(&format!("InteractiveClapContextScopeFor{}", &name), Span::call_site());
let interactive_clap_context_scope_for_struct = syn::Ident::new(
&format!("InteractiveClapContextScopeFor{}", &name),
Span::call_site(),
);
let new_context_scope = quote! {
let new_context_scope = #interactive_clap_context_scope_for_struct { #(#fields_without_subcommand,)* };
};
quote! {
pub fn from_cli(
optional_clap_variant: Option<#cli_name>,
@@ -92,15 +107,19 @@ fn fields_value(field: &syn::Field) -> proc_macro2::TokenStream {
)?;
}
} else {
quote! ()
quote!()
}
}
fn field_value_named_arg(name: &syn::Ident, field: &syn::Field, output_context_dir: &Option<proc_macro2::TokenStream>) -> proc_macro2::TokenStream {
fn field_value_named_arg(
name: &syn::Ident,
field: &syn::Field,
output_context_dir: &Option<proc_macro2::TokenStream>,
) -> proc_macro2::TokenStream {
let ident_field = &field.clone().ident.expect("this field does not exist");
let ty = &field.ty;
if field.attrs.is_empty() {
quote! ()
quote!()
} else {
match field.attrs.iter()
.filter(|attr| attr.path.is_ident("interactive_clap".into()))
@@ -163,11 +182,15 @@ fn field_value_named_arg(name: &syn::Ident, field: &syn::Field, output_context_d
}
}
fn field_value_subcommand(name: &syn::Ident, field: &syn::Field, output_context_dir: &Option<proc_macro2::TokenStream>) -> proc_macro2::TokenStream {
fn field_value_subcommand(
name: &syn::Ident,
field: &syn::Field,
output_context_dir: &Option<proc_macro2::TokenStream>,
) -> proc_macro2::TokenStream {
let ident_field = &field.clone().ident.expect("this field does not exist");
let ty = &field.ty;
if field.attrs.is_empty() {
quote! ()
quote!()
} else {
match field.attrs.iter()
.filter(|attr| attr.path.is_ident("interactive_clap".into()))
@@ -212,9 +235,15 @@ fn field_value_subcommand(name: &syn::Ident, field: &syn::Field, output_context_
}
}
fn struct_field(field: &syn::Field, fields_without_subcommand: &Vec<proc_macro2::TokenStream>) -> proc_macro2::TokenStream {
fn struct_field(
field: &syn::Field,
fields_without_subcommand: &Vec<proc_macro2::TokenStream>,
) -> proc_macro2::TokenStream {
let ident_field = &field.clone().ident.expect("this field does not exist");
let fields_without_subcommand_to_string = fields_without_subcommand.iter().map(|token_stream| token_stream.to_string()).collect::<Vec<_>>();
let fields_without_subcommand_to_string = fields_without_subcommand
.iter()
.map(|token_stream| token_stream.to_string())
.collect::<Vec<_>>();
if fields_without_subcommand_to_string.contains(&ident_field.to_string()) {
quote! {
#ident_field: new_context_scope.#ident_field

View File

@@ -1,19 +1,31 @@
extern crate proc_macro;
use proc_macro2::Span;
use syn;
use quote::quote;
use syn;
pub fn from_cli_arg(ast: &syn::DeriveInput, fields: &syn::Fields) -> Vec<proc_macro2::TokenStream> {
let interactive_clap_attrs_context = super::interactive_clap_attrs_context::InteractiveClapAttrsContext::new(&ast);
let interactive_clap_attrs_context =
super::interactive_clap_attrs_context::InteractiveClapAttrsContext::new(&ast);
if interactive_clap_attrs_context.is_skip_default_from_cli {
return vec![quote! ()];
return vec![quote!()];
};
let fields_without_subcommand = fields.iter()
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::<Vec<_>>();
let fields_without_skip_default_from_cli = fields
.iter()
.filter(|field| {
super::fields_without_subcommand::is_field_without_subcommand(field)
super::fields_without_skip_default_from_cli::is_field_without_skip_default_from_cli(
field,
)
})
.map(|field| {
let ident_field = &field.clone().ident.expect("this field does not exist");
@@ -21,44 +33,50 @@ pub fn from_cli_arg(ast: &syn::DeriveInput, fields: &syn::Fields) -> Vec<proc_ma
})
.collect::<Vec<_>>();
let fields_without_skip_default_from_cli = fields.iter()
.filter(|field| {
super::fields_without_skip_default_from_cli::is_field_without_skip_default_from_cli(field)
})
let get_arg_for_fields = fields
.iter()
.map(|field| {
let ident_field = &field.clone().ident.expect("this field does not exist");
quote! {#ident_field}
})
.collect::<Vec<_>>();
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::<Vec<_>>();
let fields_without_skip_default_from_cli_to_string = fields_without_skip_default_from_cli.iter().map(|token_stream| token_stream.to_string()).collect::<Vec<_>>();
if fields_without_subcommand_to_string.contains(&ident_field.to_string()) & fields_without_skip_default_from_cli_to_string.contains(&ident_field.to_string()) {
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());
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),
let ty = &field.ty;
let fields_without_subcommand_to_string = fields_without_subcommand
.iter()
.map(|token_stream| token_stream.to_string())
.collect::<Vec<_>>();
let fields_without_skip_default_from_cli_to_string =
fields_without_skip_default_from_cli
.iter()
.map(|token_stream| token_stream.to_string())
.collect::<Vec<_>>();
if fields_without_subcommand_to_string.contains(&ident_field.to_string())
& fields_without_skip_default_from_cli_to_string.contains(&ident_field.to_string())
{
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());
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!()
}
} else {
quote! ()
}
})
.filter(|token_stream| !token_stream.is_empty())
.collect::<Vec<proc_macro2::TokenStream>>();
})
.filter(|token_stream| !token_stream.is_empty())
.collect::<Vec<proc_macro2::TokenStream>>();
get_arg_for_fields
}

View File

@@ -1,7 +1,7 @@
extern crate proc_macro;
use syn;
use quote::quote;
use syn;
#[derive(Debug, Clone)]
pub struct InteractiveClapAttrsContext {
@@ -12,12 +12,10 @@ pub struct InteractiveClapAttrsContext {
}
impl InteractiveClapAttrsContext {
pub fn new(
ast: &syn::DeriveInput,
) -> Self {
let mut context_dir = quote! ();
let mut input_context_dir = quote! ();
let mut output_context_dir = quote! ();
pub fn new(ast: &syn::DeriveInput) -> Self {
let mut context_dir = quote!();
let mut input_context_dir = quote!();
let mut output_context_dir = quote!();
let mut is_skip_default_from_cli = false;
if !ast.attrs.is_empty() {
for attr in ast.attrs.clone() {
@@ -26,46 +24,45 @@ impl InteractiveClapAttrsContext {
match attr_token {
proc_macro2::TokenTree::Group(group) => {
if group.stream().to_string().contains("output_context") {
let group_stream = &group.stream()
.into_iter()
.collect::<Vec<_>>()[2..];
let group_stream =
&group.stream().into_iter().collect::<Vec<_>>()[2..];
output_context_dir = quote! {#(#group_stream)*};
} else if group.stream().to_string().contains("input_context") {
let group_stream = &group.stream()
.into_iter()
.collect::<Vec<_>>()[2..];
let group_stream =
&group.stream().into_iter().collect::<Vec<_>>()[2..];
input_context_dir = quote! {#(#group_stream)*};
} else if group.stream().to_string().contains("context") {
let group_stream = &group.stream()
.into_iter()
.collect::<Vec<_>>()[2..];
let group_stream =
&group.stream().into_iter().collect::<Vec<_>>()[2..];
context_dir = quote! {#(#group_stream)*};
};
if group.stream().to_string().contains("skip_default_from_cli") {
is_skip_default_from_cli = true;
};
}
_ => () //abort_call_site!("Only option `TokenTree::Group` is needed")
_ => (), //abort_call_site!("Only option `TokenTree::Group` is needed")
}
}
};
};
}
};
let context_dir: Option<proc_macro2::TokenStream> = if let true = !context_dir.is_empty() {
Some(context_dir)
} else {
None
};
let input_context_dir: Option<proc_macro2::TokenStream> = if let true = !input_context_dir.is_empty() {
Some(input_context_dir)
} else {
None
};
let output_context_dir: Option<proc_macro2::TokenStream> = if let true = !output_context_dir.is_empty() {
Some(output_context_dir)
} else {
None
};
let input_context_dir: Option<proc_macro2::TokenStream> =
if let true = !input_context_dir.is_empty() {
Some(input_context_dir)
} else {
None
};
let output_context_dir: Option<proc_macro2::TokenStream> =
if let true = !output_context_dir.is_empty() {
Some(output_context_dir)
} else {
None
};
Self {
context_dir,
input_context_dir,
@@ -77,12 +74,14 @@ impl InteractiveClapAttrsContext {
pub fn get_input_context_dir(self) -> proc_macro2::TokenStream {
let context_dir = match self.context_dir {
Some(context_dir) => context_dir,
None => quote! ()
None => quote!(),
};
if !context_dir.is_empty() {
return context_dir;
};
if !context_dir.is_empty() { return context_dir };
let input_context_dir = match self.input_context_dir {
Some(input_context_dir) => input_context_dir,
None => quote! {()}
None => quote! {()},
};
input_context_dir
}

View File

@@ -3,12 +3,11 @@ extern crate proc_macro;
use proc_macro::TokenStream;
use proc_macro2::Span;
use proc_macro_error::abort_call_site;
use quote::{quote, ToTokens};
use syn;
use quote::{ToTokens, quote};
mod methods;
pub fn impl_interactive_clap(ast: &syn::DeriveInput) -> TokenStream {
let name = &ast.ident;
let cli_name_string = format!("Cli{}", &ast.ident);
@@ -18,92 +17,112 @@ pub fn impl_interactive_clap(ast: &syn::DeriveInput) -> TokenStream {
let fields = data_struct.fields.clone();
let mut ident_skip_field_vec: Vec<syn::Ident> = 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<proc_macro2::TokenStream> = Vec::new();
let mut cfg_attr_vec: Vec<proc_macro2::TokenStream> = Vec::new();
for attr in &field.attrs {
if attr.path.is_ident("interactive_clap".into()) | attr.path.is_ident("cfg".into()) {
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("long") | (group.stream().to_string() == "skip".to_string()) {
clap_attr_vec.push(group.stream())
} else if group.stream().to_string().contains("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()
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<proc_macro2::TokenStream> = Vec::new();
let mut cfg_attr_vec: Vec<proc_macro2::TokenStream> = Vec::new();
for attr in &field.attrs {
if attr.path.is_ident("interactive_clap".into())
| attr.path.is_ident("cfg".into())
{
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("long")
| (group.stream().to_string() == "skip".to_string())
{
clap_attr_vec.push(group.stream())
} else if group.stream().to_string().contains("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()
_ => 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>
}
};
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() == "skip".to_string() {
ident_skip_field_vec.push(ident_field.clone());
cli_field = quote! ()
};
},
_ => abort_call_site!("Only option `TokenTree::Group` is needed")
if group.stream().to_string().contains("feature") {
cfg_attr_vec.push(attr.into_token_stream())
};
if group.stream().to_string() == "skip".to_string() {
ident_skip_field_vec.push(ident_field.clone());
cli_field = quote!()
};
}
_ => {
abort_call_site!("Only option `TokenTree::Group` is needed")
}
}
}
};
}
}
};
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)*
#[clap(#(#clap_attrs, )*)]
#cli_field
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)*
#[clap(#(#clap_attrs, )*)]
#cli_field
}
} else {
quote! {
#(#cfg_attrs)*
#cli_field
}
}
} else {
quote! {
#(#cfg_attrs)*
#cli_field
}
}
})
.filter(|token_stream| !token_stream.is_empty())
.collect::<Vec<_>>();
let for_cli_fields = fields.iter().map(|field| {
for_cli_field(field, &ident_skip_field_vec)
})
.filter(|token_stream| !token_stream.is_empty());
})
.filter(|token_stream| !token_stream.is_empty())
.collect::<Vec<_>>();
let fn_from_cli_for_struct = self::methods::from_cli_for_struct::from_cli_for_struct(&ast, &fields);
let for_cli_fields = fields
.iter()
.map(|field| for_cli_field(field, &ident_skip_field_vec))
.filter(|token_stream| !token_stream.is_empty());
let fn_get_arg = self::methods::get_arg_from_cli_for_struct::from_cli_arg(&ast, &fields);
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_input_arg(&ast, &fields);
let context_scope_fields = fields.iter().map(|field| {
context_scope_for_struct_field(field)
})
.filter(|token_stream| !token_stream.is_empty())
.collect::<Vec<_>>();
let context_scope_fields = fields
.iter()
.map(|field| context_scope_for_struct_field(field))
.filter(|token_stream| !token_stream.is_empty())
.collect::<Vec<_>>();
let context_scope_for_struct = context_scope_for_struct(&name, context_scope_fields);
let clap_enum_for_named_arg =
@@ -115,7 +134,6 @@ pub fn impl_interactive_clap(ast: &syn::DeriveInput) -> TokenStream {
.filter(|attr| attr.path.is_ident("doc".into()))
.map(|attr| attr.into_token_stream())
.collect();
field.attrs.iter()
.filter(|attr| attr.path.is_ident("interactive_clap".into()))
.map(|attr| attr.tokens.clone())
@@ -224,16 +242,17 @@ pub fn impl_interactive_clap(ast: &syn::DeriveInput) -> TokenStream {
proc_macro2::TokenTree::Group(group) => {
if group.stream().to_string().contains("feature") {
attrs.push(attr.into_token_stream());
} else {
continue;
};
},
_ => abort_call_site!("Only option `TokenTree::Group` is needed")
}
_ => {
abort_call_site!("Only option `TokenTree::Group` is needed")
}
}
};
}
};
};
}
match &variant.fields {
syn::Fields::Unnamed(fields) => {
let ty = &fields.unnamed[0].ty;
@@ -245,7 +264,7 @@ pub fn impl_interactive_clap(ast: &syn::DeriveInput) -> TokenStream {
#ident(<#ty as interactive_clap::ToCli>::CliVariant)
}
}
},
}
syn::Fields::Unit => {
if attrs.is_empty() {
quote! {#ident}
@@ -255,20 +274,23 @@ pub fn impl_interactive_clap(ast: &syn::DeriveInput) -> TokenStream {
#ident
}
}
},
_ => abort_call_site!("Only option `Fields::Unnamed` or `Fields::Unit` is needed")
}
_ => abort_call_site!(
"Only option `Fields::Unnamed` or `Fields::Unit` is needed"
),
}
} else {
match &variant.fields {
syn::Fields::Unnamed(fields) => {
let ty = &fields.unnamed[0].ty;
quote! { #ident(<#ty as interactive_clap::ToCli>::CliVariant) }
},
}
syn::Fields::Unit => {
quote! { #ident }
},
_ => abort_call_site!("Only option `Fields::Unnamed` or `Fields::Unit` is needed")
}
_ => abort_call_site!(
"Only option `Fields::Unnamed` or `Fields::Unit` is needed"
),
}
}
});
@@ -277,11 +299,13 @@ pub fn impl_interactive_clap(ast: &syn::DeriveInput) -> TokenStream {
match &variant.fields {
syn::Fields::Unnamed(_) => {
quote! { #name::#ident(arg) => Self::#ident(arg.into()) }
},
}
syn::Fields::Unit => {
quote! { #name::#ident => Self::#ident }
},
_ => abort_call_site!("Only option `Fields::Unnamed` or `Fields::Unit` is needed")
}
_ => abort_call_site!(
"Only option `Fields::Unnamed` or `Fields::Unit` is needed"
),
}
});
@@ -289,7 +313,8 @@ pub fn impl_interactive_clap(ast: &syn::DeriveInput) -> TokenStream {
let fn_choose_variant = self::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);
let fn_from_cli_for_enum =
self::methods::from_cli_for_enum::from_cli_for_enum(ast, variants);
let gen = quote! {
#[derive(Debug, Clone, clap::Clap, interactive_clap_derive::ToCliArgs)]
@@ -302,7 +327,7 @@ pub fn impl_interactive_clap(ast: &syn::DeriveInput) -> TokenStream {
}
#scope_for_enum
impl From<#name> for #cli_name {
fn from(command: #name) -> Self {
match command {
@@ -310,7 +335,7 @@ pub fn impl_interactive_clap(ast: &syn::DeriveInput) -> TokenStream {
}
}
}
impl #name {
#fn_choose_variant
#fn_from_cli_for_enum
@@ -318,12 +343,18 @@ pub fn impl_interactive_clap(ast: &syn::DeriveInput) -> TokenStream {
};
gen.into()
}
_ => abort_call_site!("`#[derive(InteractiveClap)]` only supports structs and enums")
_ => abort_call_site!("`#[derive(InteractiveClap)]` only supports structs and enums"),
}
}
fn context_scope_for_struct(name: &syn::Ident, context_scope_fields: Vec<proc_macro2::TokenStream>) -> proc_macro2::TokenStream {
let interactive_clap_context_scope_for_struct = syn::Ident::new(&format!("InteractiveClapContextScopeFor{}", &name), Span::call_site());
fn context_scope_for_struct(
name: &syn::Ident,
context_scope_fields: Vec<proc_macro2::TokenStream>,
) -> 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,)*
@@ -342,13 +373,15 @@ fn context_scope_for_struct_field(field: &syn::Field) -> proc_macro2::TokenStrea
pub #ident_field: #ty
}
} else {
quote! ()
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), Span::call_site());
let interactive_clap_context_scope_for_enum = syn::Ident::new(
&format!("InteractiveClapContextScopeFor{}", &name),
Span::call_site(),
);
let enum_discriminants = syn::Ident::new(&format!("{}Discriminants", &name), Span::call_site());
quote! {
pub type #interactive_clap_context_scope_for_enum = #enum_discriminants;
@@ -358,29 +391,31 @@ fn context_scope_for_enum(name: &syn::Ident) -> proc_macro2::TokenStream {
}
}
fn for_cli_field(field: &syn::Field, ident_skip_field_vec: &Vec<syn::Ident>) -> proc_macro2::TokenStream {
fn for_cli_field(
field: &syn::Field,
ident_skip_field_vec: &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! ()
quote!()
} else {
let ty = &field.ty;
match &ty {
syn::Type::Path(type_path) => {
match type_path.path.segments.first() {
Some(path_segment) => {
if path_segment.ident.eq("Option".into()) {
quote! {
#ident_field: args.#ident_field.into()
}
} else {
quote! {
#ident_field: Some(args.#ident_field.into())
}
syn::Type::Path(type_path) => match type_path.path.segments.first() {
Some(path_segment) => {
if path_segment.ident.eq("Option".into()) {
quote! {
#ident_field: args.#ident_field.into()
}
},
_ => abort_call_site!("Only option `PathSegment` is needed")
}},
_ => abort_call_site!("Only option `Type::Path` is needed")
} 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"),
}
}
}

View File

@@ -2,9 +2,8 @@ extern crate proc_macro;
use proc_macro2::Span;
use proc_macro_error::abort_call_site;
use syn;
use quote::quote;
use syn;
#[derive(Debug, Clone)]
pub struct InteractiveClapAttrsCliField {
@@ -21,12 +20,12 @@ impl InteractiveClapAttrsCliField {
let mut subcommand_args = quote! {
let mut args = std::collections::VecDeque::new();
};
let mut args_without_attrs = quote! ();
let mut named_args = quote! ();
let mut unnamed_args = quote! ();
let mut args_without_attrs = quote!();
let mut named_args = quote!();
let mut unnamed_args = quote!();
if field.attrs.is_empty() {
args_without_attrs = quote!{
args_without_attrs = quote! {
if let Some(arg) = &self.#ident_field {
args.push_front(arg.to_string())
}
@@ -49,46 +48,52 @@ impl InteractiveClapAttrsCliField {
.unwrap_or_default();
};
} else if "long".to_string() == ident.to_string() {
let ident_field_to_kebab_case_string = crate::helpers::to_kebab_case::to_kebab_case(ident_field.to_string());
let ident_field_to_kebab_case = &syn::LitStr::new(&ident_field_to_kebab_case_string, Span::call_site());
unnamed_args = quote!{
let ident_field_to_kebab_case_string =
crate::helpers::to_kebab_case::to_kebab_case(
ident_field.to_string(),
);
let ident_field_to_kebab_case = &syn::LitStr::new(
&ident_field_to_kebab_case_string,
Span::call_site(),
);
unnamed_args = quote! {
if let Some(arg) = &self.#ident_field {
args.push_front(arg.to_string());
args.push_front(std::concat!("--", #ident_field_to_kebab_case).to_string());
}
};
}
},
proc_macro2::TokenTree::Literal(literal) =>{
named_args = quote!{
if let Some(arg) = &self.#ident_field {
args.push_front(arg.to_string());
args.push_front(std::concat!("--", #literal).to_string());
}
};
},
_ => () //abort_call_site!("Only option `TokenTree::Ident` is needed")
}
proc_macro2::TokenTree::Literal(literal) => {
named_args = quote! {
if let Some(arg) = &self.#ident_field {
args.push_front(arg.to_string());
args.push_front(std::concat!("--", #literal).to_string());
}
};
}
_ => (), //abort_call_site!("Only option `TokenTree::Ident` is needed")
};
};
},
_ => abort_call_site!("Only option `TokenTree::Group` is needed")
}
}
_ => abort_call_site!("Only option `TokenTree::Group` is needed"),
}
};
}
}
};
}
};
Self{
Self {
ident_field,
args_without_attrs: Some(args_without_attrs),
named_args: Some(named_args.clone()),
unnamed_args: {
if !named_args.is_empty() {
None
} else {
Some(unnamed_args)
}
},
subcommand_args: Some(subcommand_args)
if !named_args.is_empty() {
None
} else {
Some(unnamed_args)
}
},
subcommand_args: Some(subcommand_args),
}
}
}

View File

@@ -3,12 +3,11 @@ extern crate proc_macro;
use proc_macro::TokenStream;
use proc_macro2::Span;
use proc_macro_error::abort_call_site;
use syn;
use quote::quote;
use syn;
mod methods;
pub fn impl_to_cli_args(ast: &syn::DeriveInput) -> TokenStream {
let cli_name = &ast.ident;
match &ast.data {
@@ -17,15 +16,19 @@ pub fn impl_to_cli_args(ast: &syn::DeriveInput) -> TokenStream {
let mut args = std::collections::VecDeque::new();
};
let mut args_push_front_vec: Vec<proc_macro2::TokenStream> = Vec::new();
for field in data_struct.clone().fields.iter() {
let interactive_clap_attrs_cli_field = self::methods::interactive_clap_attrs_cli_field::InteractiveClapAttrsCliField::new(field.clone());
args_subcommand = if let Some(subcommand_args) = interactive_clap_attrs_cli_field.subcommand_args {
args_subcommand = if let Some(subcommand_args) =
interactive_clap_attrs_cli_field.subcommand_args
{
subcommand_args
} else {
args_subcommand
};
if let Some(args_without_attrs) = interactive_clap_attrs_cli_field.args_without_attrs {
if let Some(args_without_attrs) =
interactive_clap_attrs_cli_field.args_without_attrs
{
args_push_front_vec.push(args_without_attrs)
};
if let Some(named_args) = interactive_clap_attrs_cli_field.named_args {
@@ -34,7 +37,7 @@ pub fn impl_to_cli_args(ast: &syn::DeriveInput) -> TokenStream {
if let Some(unnamed_args) = interactive_clap_attrs_cli_field.unnamed_args {
args_push_front_vec.push(unnamed_args)
};
};
}
let args_push_front_vec = args_push_front_vec.into_iter().rev();
let gen = quote! {
@@ -47,11 +50,12 @@ pub fn impl_to_cli_args(ast: &syn::DeriveInput) -> TokenStream {
}
};
gen.into()
},
}
syn::Data::Enum(syn::DataEnum { variants, .. }) => {
let enum_variants = variants.iter().map(|variant| {
let ident = &variant.ident;
let variant_name_string = crate::helpers::to_kebab_case::to_kebab_case(ident.to_string());
let variant_name_string =
crate::helpers::to_kebab_case::to_kebab_case(ident.to_string());
let variant_name = &syn::LitStr::new(&variant_name_string, Span::call_site());
match &variant.fields {
@@ -63,7 +67,7 @@ pub fn impl_to_cli_args(ast: &syn::DeriveInput) -> TokenStream {
args
}
}
},
}
syn::Fields::Unit => {
quote! {
Self::#ident => {
@@ -72,8 +76,10 @@ pub fn impl_to_cli_args(ast: &syn::DeriveInput) -> TokenStream {
args
}
}
},
_ => abort_call_site!("Only options `Fields::Unnamed` or `Fields::Unit` are needed")
}
_ => abort_call_site!(
"Only options `Fields::Unnamed` or `Fields::Unit` are needed"
),
}
});
let gen = quote! {
@@ -86,7 +92,7 @@ pub fn impl_to_cli_args(ast: &syn::DeriveInput) -> TokenStream {
}
};
gen.into()
},
_ => abort_call_site!("`#[derive(InteractiveClap)]` only supports structs and enums")
}
_ => abort_call_site!("`#[derive(InteractiveClap)]` only supports structs and enums"),
}
}

View File

@@ -1,2 +1,2 @@
pub mod to_kebab_case;
pub mod snake_case_to_camel_case;
pub mod to_kebab_case;

View File

@@ -1,5 +1,7 @@
pub fn snake_case_to_camel_case(s: String) -> String {
let s_vec: Vec<String> = s.to_lowercase().split("_")
let s_vec: Vec<String> = s
.to_lowercase()
.split("_")
.map(|s| s.replacen(&s[..1], &s[..1].to_ascii_uppercase(), 1))
.collect();
s_vec.join("")

View File

@@ -5,6 +5,6 @@ pub fn to_kebab_case(s: String) -> String {
snake.push('-');
}
snake.push(ch.to_ascii_lowercase());
};
}
snake.as_str().replace("_", "-")
}

View File

@@ -7,7 +7,6 @@ use syn;
mod derives;
mod helpers;
#[proc_macro_derive(InteractiveClap, attributes(interactive_clap))]
#[proc_macro_error]
pub fn interactive_clap(input: TokenStream) -> TokenStream {