target_handler 0.1.1

A derive macro to create a handler for multiple enum variants
Documentation
use darling::FromDeriveInput;
use proc_macro::TokenStream;
use quote::quote;
use syn::{self, Ident, Data, Variant, Fields, FieldsNamed, DataEnum};

type TokenStream2 = proc_macro2::TokenStream;

#[derive(FromDeriveInput, Default)]
#[darling(attributes(handler))]
struct HandlerOpts {
    returns: Option<String>,
    trait_name: Option<String>,
    method: Option<String>
}

impl HandlerOpts {
    fn get_returns(&self) -> TokenStream2 {
        str_to_tok(&self.returns.as_ref().map_or("()", |s| { s.as_str() }))
    }

    fn get_trait_name(&self, ast: &syn::DeriveInput) -> TokenStream2 {
        if let Some(name) = &self.trait_name {
            return str_to_tok(name);
        }
        let name = &ast.ident;
        str_to_tok(&format!("{name}Handler"))
    }

    fn get_handler_method(&self, ast: &syn::DeriveInput) -> TokenStream2 {
        if let Some(method) = &self.method {
            return str_to_tok(method);
        }
        let name = lower_name(ast.ident.to_string());
        str_to_tok(&format!("handle_{name}"))
    }
}

fn lower_name(name: String) -> String {
    name.to_lowercase()
}

fn str_to_tok(arg: &str) -> TokenStream2 {
    arg.parse().unwrap()
}

fn enum_variant_to_handle_ident(var: &Variant) -> Ident {
    let ident = &var.ident;
    let name = lower_name(ident.to_string());
    Ident::new(&name, ident.span())
} 

fn enum_variant_to_handle_arguments(var: &Variant) -> TokenStream2 {
    if let Fields::Named(fields) = &var.fields {
        return arguments_from_named_fields(fields);
    }
    quote! { &self }
}

fn arguments_from_named_fields(fields: &FieldsNamed) -> TokenStream2 {
    let args = fields.named.iter().filter_map(|field| {
        let ident = field.ident.clone()?;
        let ty = &field.ty;
        Some(quote! {#ident: #ty})
    });
    quote! { &self, #(#args),* }
}

fn get_field_name_list(fields: &Fields) -> TokenStream2
{
    match &fields {
        Fields::Named(fields) => get_named_fields_name_list(fields),
        _                     => TokenStream2::new()
    }
}

fn get_named_fields_name_list(fields: &FieldsNamed) -> TokenStream2 {
    let names = get_idents_of_named_fields(fields);
    quote! { #(#names),* }
}

fn get_idents_of_named_fields<'a>(fields: &'a FieldsNamed) -> impl Iterator<Item=&Ident> + 'a {
    fields.named.iter().filter_map(|field| { field.ident.as_ref() })
}

struct TargetMacroGenerator {
    opts:      HandlerOpts,
    ast:       syn::DeriveInput,
}

impl<'a> TargetMacroGenerator {
    fn new(ast: syn::DeriveInput, opts: HandlerOpts) -> TargetMacroGenerator {
        TargetMacroGenerator { ast, opts }
    }

    fn get_data_enum(&self) -> &DataEnum {
        if let Data::Enum(data) = &self.ast.data {
            return data;
        }
        panic!("Target must be an enum.");
    }

    fn generate(&self) -> TokenStream {
        let trait_name = self.opts.get_trait_name(&self.ast);
        let handles = self.get_handles();
        let handler_function = self.get_handler_function();
        quote! {
            trait #trait_name {
                #(#handles)*

                #handler_function
            }
        }.into()
    }

    fn get_handles(&self) -> impl Iterator<Item=TokenStream2> + '_ {
        self.get_data_enum().variants
            .iter()
            .map(|var| { self.enum_variant_to_handle(var) })
    }

    fn enum_variant_to_handle(&self, var: &Variant) -> TokenStream2 {
        let ident = enum_variant_to_handle_ident(var);
        let arguments = enum_variant_to_handle_arguments(var);
        let returns = self.opts.get_returns();
        quote! { fn #ident(#arguments) -> #returns; }
    }

    fn get_handler_function(&self) -> TokenStream2 {
        let handler_method = self.opts.get_handler_method(&self.ast);
        let enum_name      = &self.ast.ident;
        let handler_arms   = self.get_handler_arms();
        let returns        = self.opts.get_returns();

        quote! {
            fn #handler_method(&self, handled_enum: #enum_name) -> #returns {
                match handled_enum {
                    #(#handler_arms)*
                }
            }
        }
    }

    fn get_handler_arms(&self) -> impl Iterator<Item=TokenStream2> + '_ {
        self.get_data_enum().variants
            .iter()
            .map(|var| { self.enum_variant_to_match_arm(var) })
    }

    fn enum_variant_to_match_arm(&self, variant: &Variant) -> TokenStream2 {
        let enum_name = &self.ast.ident;
        let variant_name = &variant.ident;
        let variant_handle_name = enum_variant_to_handle_ident(variant);
        let field_name_list = get_field_name_list(&variant.fields);

        quote! {
            #enum_name::#variant_name { #field_name_list } => {
                self.#variant_handle_name(#field_name_list)
            }
        }
    }
}

#[proc_macro_derive(Target, attributes(handler))]
pub fn targets_derive(input: TokenStream) -> TokenStream {
    let ast: syn::DeriveInput = syn::parse(input).unwrap();
    let opts = HandlerOpts::from_derive_input(&ast).expect("Wrong options for 'handler'.");
    TargetMacroGenerator::new(ast, opts).generate()
}