refactor(derive): Move into_app into coupled derives

This commit is contained in:
Ed Page 2021-07-14 16:01:47 -05:00
parent f6fa3771a6
commit 507f0bf1cc
3 changed files with 222 additions and 223 deletions

View file

@ -53,6 +53,215 @@ pub fn gen_from_arg_matches_for_struct(
}
}
}
/// Generate a block of code to add arguments/subcommands corresponding to
/// the `fields` to an app.
pub fn gen_augment(
fields: &Punctuated<Field, Comma>,
app_var: &Ident,
parent_attribute: &Attrs,
override_required: bool,
) -> TokenStream {
let mut subcmds = fields.iter().filter_map(|field| {
let attrs = Attrs::from_field(
field,
parent_attribute.casing(),
parent_attribute.env_casing(),
);
let kind = attrs.kind();
if let Kind::Subcommand(ty) = &*kind {
let subcmd_type = match (**ty, sub_type(&field.ty)) {
(Ty::Option, Some(sub_type)) => sub_type,
_ => &field.ty,
};
let required = if **ty == Ty::Option {
quote!()
} else {
quote_spanned! { kind.span()=>
let #app_var = #app_var.setting(
clap::AppSettings::SubcommandRequiredElseHelp
);
}
};
let span = field.span();
let ts = if override_required {
quote! {
let #app_var = <#subcmd_type as clap::Subcommand>::augment_subcommands_for_update( #app_var );
}
} else{
quote! {
let #app_var = <#subcmd_type as clap::Subcommand>::augment_subcommands( #app_var );
#required
}
};
Some((span, ts))
} else {
None
}
});
let subcmd = subcmds.next().map(|(_, ts)| ts);
if let Some((span, _)) = subcmds.next() {
abort!(
span,
"multiple subcommand sets are not allowed, that's the second"
);
}
let args = fields.iter().filter_map(|field| {
let attrs = Attrs::from_field(
field,
parent_attribute.casing(),
parent_attribute.env_casing(),
);
let kind = attrs.kind();
match &*kind {
Kind::Subcommand(_)
| Kind::Skip(_)
| Kind::FromGlobal(_)
| Kind::ExternalSubcommand => None,
Kind::Flatten => {
let ty = &field.ty;
Some(quote_spanned! { kind.span()=>
let #app_var = <#ty as clap::IntoApp>::augment_clap(#app_var);
})
}
Kind::Arg(ty) => {
let convert_type = match **ty {
Ty::Vec | Ty::Option => sub_type(&field.ty).unwrap_or(&field.ty),
Ty::OptionOption | Ty::OptionVec => {
sub_type(&field.ty).and_then(sub_type).unwrap_or(&field.ty)
}
_ => &field.ty,
};
let occurrences = *attrs.parser().kind == ParserKind::FromOccurrences;
let flag = *attrs.parser().kind == ParserKind::FromFlag;
let parser = attrs.parser();
let func = &parser.func;
let validator = match *parser.kind {
_ if attrs.is_enum() => quote!(),
ParserKind::TryFromStr => quote_spanned! { func.span()=>
.validator(|s| {
#func(s)
.map(|_: #convert_type| ())
})
},
ParserKind::TryFromOsStr => quote_spanned! { func.span()=>
.validator_os(|s| #func(s).map(|_: #convert_type| ()))
},
_ => quote!(),
};
let modifier = match **ty {
Ty::Bool => quote!(),
Ty::Option => {
let mut possible_values = quote!();
if attrs.is_enum() {
if let Some(subty) = subty_if_name(&field.ty, "Option") {
possible_values = gen_arg_enum_possible_values(subty);
}
};
quote_spanned! { ty.span()=>
.takes_value(true)
#possible_values
#validator
}
}
Ty::OptionOption => quote_spanned! { ty.span()=>
.takes_value(true)
.multiple_values(false)
.min_values(0)
.max_values(1)
#validator
},
Ty::OptionVec => quote_spanned! { ty.span()=>
.takes_value(true)
.multiple_values(true)
.min_values(0)
#validator
},
Ty::Vec => {
let mut possible_values = quote!();
if attrs.is_enum() {
if let Some(subty) = subty_if_name(&field.ty, "Vec") {
possible_values = gen_arg_enum_possible_values(subty);
}
};
quote_spanned! { ty.span()=>
.takes_value(true)
.multiple_values(true)
#possible_values
#validator
}
}
Ty::Other if occurrences => quote_spanned! { ty.span()=>
.multiple_occurrences(true)
},
Ty::Other if flag => quote_spanned! { ty.span()=>
.takes_value(false)
.multiple_values(false)
},
Ty::Other => {
let required = !attrs.has_method("default_value") && !override_required;
let mut possible_values = quote!();
if attrs.is_enum() {
possible_values = gen_arg_enum_possible_values(&field.ty);
};
quote_spanned! { ty.span()=>
.takes_value(true)
.required(#required)
#possible_values
#validator
}
}
};
let name = attrs.cased_name();
let methods = attrs.field_methods();
Some(quote_spanned! { field.span()=>
let #app_var = #app_var.arg(
clap::Arg::new(#name)
#modifier
#methods
);
})
}
}
});
let app_methods = parent_attribute.top_level_methods();
let version = parent_attribute.version();
quote! {{
let #app_var = #app_var#app_methods;
#( #args )*
#subcmd
#app_var#version
}}
}
fn gen_arg_enum_possible_values(ty: &Type) -> TokenStream {
quote_spanned! { ty.span()=>
.possible_values(&<#ty as clap::ArgEnum>::VARIANTS)
}
}
pub fn gen_constructor(fields: &Punctuated<Field, Comma>, parent_attribute: &Attrs) -> TokenStream {
let fields = fields.iter().map(|field| {
let attrs = Attrs::from_field(

View file

@ -15,17 +15,18 @@
use std::env;
use proc_macro2::{Span, TokenStream};
use proc_macro_error::{abort, abort_call_site};
use quote::{quote, quote_spanned};
use proc_macro_error::abort_call_site;
use quote::quote;
use syn::{
punctuated::Punctuated, spanned::Spanned, token::Comma, Attribute, Data, DataStruct,
DeriveInput, Field, Fields, Ident, Type,
punctuated::Punctuated, token::Comma, Attribute, Data, DataStruct, DeriveInput, Field, Fields,
Ident,
};
use crate::{
attrs::{Attrs, GenOutput, Kind, Name, ParserKind, DEFAULT_CASING, DEFAULT_ENV_CASING},
attrs::{Attrs, GenOutput, Name, DEFAULT_CASING, DEFAULT_ENV_CASING},
derives::args,
dummies,
utils::{sub_type, subty_if_name, Sp, Ty},
utils::Sp,
};
pub fn derive_into_app(input: &DeriveInput) -> TokenStream {
@ -151,8 +152,8 @@ fn gen_into_app_fn(attrs: &[Attribute]) -> GenOutput {
fn gen_augment_clap_fn(fields: &Punctuated<Field, Comma>, parent_attribute: &Attrs) -> TokenStream {
let app_var = Ident::new("app", Span::call_site());
let augmentation = gen_app_augmentation(fields, &app_var, parent_attribute, false);
let augmentation_update = gen_app_augmentation(fields, &app_var, parent_attribute, true);
let augmentation = args::gen_augment(fields, &app_var, parent_attribute, false);
let augmentation_update = args::gen_augment(fields, &app_var, parent_attribute, true);
quote! {
fn augment_clap<'b>(#app_var: clap::App<'b>) -> clap::App<'b> {
#augmentation
@ -162,211 +163,3 @@ fn gen_augment_clap_fn(fields: &Punctuated<Field, Comma>, parent_attribute: &Att
}
}
}
fn gen_arg_enum_possible_values(ty: &Type) -> TokenStream {
quote_spanned! { ty.span()=>
.possible_values(&<#ty as clap::ArgEnum>::VARIANTS)
}
}
/// Generate a block of code to add arguments/subcommands corresponding to
/// the `fields` to an app.
pub fn gen_app_augmentation(
fields: &Punctuated<Field, Comma>,
app_var: &Ident,
parent_attribute: &Attrs,
override_required: bool,
) -> TokenStream {
let mut subcmds = fields.iter().filter_map(|field| {
let attrs = Attrs::from_field(
field,
parent_attribute.casing(),
parent_attribute.env_casing(),
);
let kind = attrs.kind();
if let Kind::Subcommand(ty) = &*kind {
let subcmd_type = match (**ty, sub_type(&field.ty)) {
(Ty::Option, Some(sub_type)) => sub_type,
_ => &field.ty,
};
let required = if **ty == Ty::Option {
quote!()
} else {
quote_spanned! { kind.span()=>
let #app_var = #app_var.setting(
clap::AppSettings::SubcommandRequiredElseHelp
);
}
};
let span = field.span();
let ts = if override_required {
quote! {
let #app_var = <#subcmd_type as clap::Subcommand>::augment_subcommands_for_update( #app_var );
}
} else{
quote! {
let #app_var = <#subcmd_type as clap::Subcommand>::augment_subcommands( #app_var );
#required
}
};
Some((span, ts))
} else {
None
}
});
let subcmd = subcmds.next().map(|(_, ts)| ts);
if let Some((span, _)) = subcmds.next() {
abort!(
span,
"multiple subcommand sets are not allowed, that's the second"
);
}
let args = fields.iter().filter_map(|field| {
let attrs = Attrs::from_field(
field,
parent_attribute.casing(),
parent_attribute.env_casing(),
);
let kind = attrs.kind();
match &*kind {
Kind::Subcommand(_)
| Kind::Skip(_)
| Kind::FromGlobal(_)
| Kind::ExternalSubcommand => None,
Kind::Flatten => {
let ty = &field.ty;
Some(quote_spanned! { kind.span()=>
let #app_var = <#ty as clap::IntoApp>::augment_clap(#app_var);
})
}
Kind::Arg(ty) => {
let convert_type = match **ty {
Ty::Vec | Ty::Option => sub_type(&field.ty).unwrap_or(&field.ty),
Ty::OptionOption | Ty::OptionVec => {
sub_type(&field.ty).and_then(sub_type).unwrap_or(&field.ty)
}
_ => &field.ty,
};
let occurrences = *attrs.parser().kind == ParserKind::FromOccurrences;
let flag = *attrs.parser().kind == ParserKind::FromFlag;
let parser = attrs.parser();
let func = &parser.func;
let validator = match *parser.kind {
_ if attrs.is_enum() => quote!(),
ParserKind::TryFromStr => quote_spanned! { func.span()=>
.validator(|s| {
#func(s)
.map(|_: #convert_type| ())
})
},
ParserKind::TryFromOsStr => quote_spanned! { func.span()=>
.validator_os(|s| #func(s).map(|_: #convert_type| ()))
},
_ => quote!(),
};
let modifier = match **ty {
Ty::Bool => quote!(),
Ty::Option => {
let mut possible_values = quote!();
if attrs.is_enum() {
if let Some(subty) = subty_if_name(&field.ty, "Option") {
possible_values = gen_arg_enum_possible_values(subty);
}
};
quote_spanned! { ty.span()=>
.takes_value(true)
#possible_values
#validator
}
}
Ty::OptionOption => quote_spanned! { ty.span()=>
.takes_value(true)
.multiple_values(false)
.min_values(0)
.max_values(1)
#validator
},
Ty::OptionVec => quote_spanned! { ty.span()=>
.takes_value(true)
.multiple_values(true)
.min_values(0)
#validator
},
Ty::Vec => {
let mut possible_values = quote!();
if attrs.is_enum() {
if let Some(subty) = subty_if_name(&field.ty, "Vec") {
possible_values = gen_arg_enum_possible_values(subty);
}
};
quote_spanned! { ty.span()=>
.takes_value(true)
.multiple_values(true)
#possible_values
#validator
}
}
Ty::Other if occurrences => quote_spanned! { ty.span()=>
.multiple_occurrences(true)
},
Ty::Other if flag => quote_spanned! { ty.span()=>
.takes_value(false)
.multiple_values(false)
},
Ty::Other => {
let required = !attrs.has_method("default_value") && !override_required;
let mut possible_values = quote!();
if attrs.is_enum() {
possible_values = gen_arg_enum_possible_values(&field.ty);
};
quote_spanned! { ty.span()=>
.takes_value(true)
.required(#required)
#possible_values
#validator
}
}
};
let name = attrs.cased_name();
let methods = attrs.field_methods();
Some(quote_spanned! { field.span()=>
let #app_var = #app_var.arg(
clap::Arg::new(#name)
#modifier
#methods
);
})
}
}
});
let app_methods = parent_attribute.top_level_methods();
let version = parent_attribute.version();
quote! {{
let #app_var = #app_var#app_methods;
#( #args )*
#subcmd
#app_var#version
}}
}

View file

@ -1,6 +1,6 @@
use crate::{
attrs::{Attrs, Kind, Name, DEFAULT_CASING, DEFAULT_ENV_CASING},
derives::{args, into_app},
derives::args,
dummies,
utils::{is_simple_ty, subty_if_name, Sp},
};
@ -129,12 +129,9 @@ fn gen_augment(
_ => {
let app_var = Ident::new("subcommand", Span::call_site());
let arg_block = match variant.fields {
Named(ref fields) => into_app::gen_app_augmentation(
&fields.named,
&app_var,
&attrs,
override_required,
),
Named(ref fields) => {
args::gen_augment(&fields.named, &app_var, &attrs, override_required)
}
Unit => quote!( #app_var ),
Unnamed(FieldsUnnamed { ref unnamed, .. }) if unnamed.len() == 1 => {
let ty = &unnamed[0];