use crate::derives::attrs::{Attrs, Kind, Name, DEFAULT_CASING, DEFAULT_ENV_CASING}; use crate::derives::{from_argmatches, into_app, spanned::Sp}; use proc_macro2::{Ident, Span, TokenStream}; use proc_macro_error::{abort, abort_call_site}; use quote::{quote, quote_spanned}; use syn::{ punctuated::Punctuated, spanned::Spanned, Attribute, DataEnum, FieldsUnnamed, Token, Variant, }; pub fn gen_for_enum(name: &Ident, attrs: &[Attribute], e: &DataEnum) -> TokenStream { let attrs = Attrs::from_struct( Span::call_site(), attrs, Name::Derived(name.clone()), Sp::call_site(DEFAULT_CASING), Sp::call_site(DEFAULT_ENV_CASING), ); let from_subcommand = gen_from_subcommand(name, &e.variants, &attrs); let augment_subcommands = gen_augment_subcommands(&e.variants, &attrs); quote! { #[allow(dead_code, unreachable_code, unused_variables)] #[allow( clippy::style, clippy::complexity, clippy::pedantic, clippy::restriction, clippy::perf, clippy::deprecated, clippy::nursery, clippy::cargo )] #[deny(clippy::correctness)] impl ::clap::Subcommand for #name { #augment_subcommands #from_subcommand } } } fn gen_augment_subcommands( variants: &Punctuated, parent_attribute: &Attrs, ) -> proc_macro2::TokenStream { use syn::Fields::*; let subcommands = variants.iter().map(|variant| { let attrs = Attrs::from_struct( variant.span(), &variant.attrs, Name::Derived(variant.ident.clone()), parent_attribute.casing(), parent_attribute.env_casing(), ); let kind = attrs.kind(); match &*kind { Kind::Flatten => match variant.fields { Unnamed(FieldsUnnamed { ref unnamed, .. }) if unnamed.len() == 1 => { let ty = &unnamed[0]; quote! { let app = <#ty as ::clap::Subcommand>::augment_subcommands(app); } } _ => abort!( variant, "`flatten` is usable only with single-typed tuple variants" ), }, _ => { 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) } Unit => quote!( #app_var ), Unnamed(FieldsUnnamed { ref unnamed, .. }) if unnamed.len() == 1 => { let ty = &unnamed[0]; quote_spanned! { ty.span()=> { <#ty as ::clap::IntoApp>::augment_clap(#app_var) } } } Unnamed(..) => { abort!(variant, "non single-typed tuple enums are not supported") } }; let name = attrs.cased_name(); let from_attrs = attrs.top_level_methods(); let version = attrs.version(); quote! { let app = app.subcommand({ let #app_var = ::clap::App::new(#name); let #app_var = #arg_block; #app_var#from_attrs#version }); } } } }); let app_methods = parent_attribute.top_level_methods(); let version = parent_attribute.version(); quote! { fn augment_subcommands<'b>(app: ::clap::App<'b>) -> ::clap::App<'b> { let app = app #app_methods; #( #subcommands )*; app #version } } } fn gen_from_subcommand( name: &syn::Ident, variants: &Punctuated, parent_attribute: &Attrs, ) -> proc_macro2::TokenStream { use syn::Fields::*; let (flatten_variants, variants): (Vec<_>, Vec<_>) = variants .iter() .map(|variant| { let attrs = Attrs::from_struct( variant.span(), &variant.attrs, Name::Derived(variant.ident.clone()), parent_attribute.casing(), parent_attribute.env_casing(), ); (variant, attrs) }) .partition(|(_, attrs)| { let kind = attrs.kind(); match &*kind { Kind::Flatten => true, _ => false, } }); let match_arms = variants.iter().map(|(variant, attrs)| { let sub_name = attrs.cased_name(); let variant_name = &variant.ident; let constructor_block = match variant.fields { Named(ref fields) => from_argmatches::gen_constructor(&fields.named, &attrs), Unit => quote!(), Unnamed(ref fields) if fields.unnamed.len() == 1 => { let ty = &fields.unnamed[0]; quote!( ( <#ty as ::clap::FromArgMatches>::from_arg_matches(matches) ) ) } Unnamed(..) => abort_call_site!("{}: tuple enums are not supported", variant.ident), }; quote! { (#sub_name, Some(matches)) => { Some(#name :: #variant_name #constructor_block) } } }); let child_subcommands = flatten_variants.iter().map(|(variant, _attrs)| { let variant_name = &variant.ident; match variant.fields { Unnamed(ref fields) if fields.unnamed.len() == 1 => { let ty = &fields.unnamed[0]; quote! { if let Some(res) = <#ty as ::clap::Subcommand>::from_subcommand(other.0, other.1) { return Some(#name :: #variant_name (res)); } } } _ => abort!( variant, "`flatten` is usable only with single-typed tuple variants" ), } }); quote! { fn from_subcommand<'b>( name: &'b str, sub: Option<&'b ::clap::ArgMatches>) -> Option { match (name, sub) { #( #match_arms ),*, other => { #( #child_subcommands )*; None } } } } }