enum-helper-derive 0.2.1

Procedural macro implementation for enum-helper
Documentation
use proc_macro2::TokenStream;
use quote::quote;

use crate::{
    enum_str::{Ir, error_msg::ErrorMsgVar},
    template::TemplateSegment,
};

pub fn generate(ir: Ir<'_>) -> TokenStream {
    let mut blocks = Vec::new();

    if ir.gen_rendering {
        blocks.push(gen_impl_enum_str(&ir));
        blocks.push(gen_impl_into_str(&ir));
        blocks.push(gen_impl_as_ref_str(&ir));
    }
    if ir.gen_error_struct {
        blocks.push(gen_error_struct(&ir));
        blocks.push(gen_error_impl_display(&ir));
        blocks.push(gen_error_impl_error(&ir));
    }
    if ir.gen_parsing {
        blocks.push(gen_impl_from_str(&ir));
        blocks.push(gen_impl_try_from_str(&ir));
    }

    quote! {
        #(#blocks)*
    }
}

fn gen_impl_enum_str(ir: &Ir<'_>) -> TokenStream {
    let ident = &ir.ident;
    let (impl_generics, ty_generics, where_clause) = &ir.generics.split_for_impl();

    let as_name_arms: Vec<_> = ir
        .variants
        .iter()
        .map(|v| {
            let v_ident = &v.ident;
            let name = &v.name;
            let pat = match v.fields {
                syn::Fields::Named(_) => quote! { Self::#v_ident {..} },
                syn::Fields::Unnamed(_) => quote! { Self::#v_ident(..) },
                syn::Fields::Unit => quote! { Self::#v_ident },
            };
            quote! { #pat => #name  }
        })
        .collect();

    let as_aliases_arms: Vec<_> = ir
        .variants
        .iter()
        .map(|v| {
            let v_ident = &v.ident;
            let alias = &v.aliases;
            let pat = match v.fields {
                syn::Fields::Named(_) => quote! { Self::#v_ident {..} },
                syn::Fields::Unnamed(_) => quote! { Self::#v_ident(..) },
                syn::Fields::Unit => quote! { Self::#v_ident },
            };
            quote! { #pat => &[#(#alias),*] }
        })
        .collect();

    quote! {
        #[automatically_derived]
        impl #impl_generics ::enum_helper::EnumStr for #ident #ty_generics #where_clause {
            fn as_name(&self) -> &'static str {
                match self {
                    #(#as_name_arms,)*
                }
            }

            fn as_aliases(&self) -> &'static [&'static str] {
                match self {
                    #(#as_aliases_arms,)*
                }
            }
        }
    }
}

fn gen_impl_into_str(ir: &Ir<'_>) -> TokenStream {
    let ident = &ir.ident;
    let (impl_generics, ty_generics, where_clause) = &ir.generics.split_for_impl();

    quote! {
        #[automatically_derived]
        impl #impl_generics ::core::convert::From<#ident #ty_generics> for &'static str #where_clause {
            fn from(value: #ident #ty_generics) -> Self {
                ::enum_helper::EnumStr::as_name(&value)
            }
        }
    }
}

fn gen_impl_as_ref_str(ir: &Ir<'_>) -> TokenStream {
    let ident = &ir.ident;
    let (impl_generics, ty_generics, where_clause) = &ir.generics.split_for_impl();

    quote! {
        #[automatically_derived]
        impl #impl_generics ::core::convert::AsRef<str> for #ident #ty_generics #where_clause {
            fn as_ref(&self) -> &str {
                ::enum_helper::EnumStr::as_name(self)
            }
        }
    }
}

fn gen_error_struct(ir: &Ir<'_>) -> TokenStream {
    let vis = &ir.vis;
    let error_ident = &ir.error.ident;

    if ir.error.should_store_input {
        quote! {
            #[automatically_derived]
            #[non_exhaustive]
            #[derive(Debug, Clone)]
            #vis struct #error_ident {
                pub input: ::std::string::String,
            }

            #[automatically_derived]
            impl #error_ident {
                pub fn new(input: &str) -> Self {
                    Self {
                        input: input.to_string(),
                    }
                }
            }
        }
    } else {
        quote! {
            #[automatically_derived]
            #[non_exhaustive]
            #[derive(Debug, Clone)]
            #vis struct #error_ident {}

            #[automatically_derived]
            impl #error_ident {
                pub fn new(_input: &str) -> Self {
                    Self {}
                }
            }
        }
    }
}

fn gen_error_impl_display(ir: &Ir<'_>) -> TokenStream {
    let error_ident = &ir.error.ident;

    fn seg_to_arg(var: &TemplateSegment<ErrorMsgVar>, ir: &Ir<'_>) -> TokenStream {
        match var {
            TemplateSegment::Lit(s) => quote! { #s },
            TemplateSegment::Var(var) => match var {
                ErrorMsgVar::Name => {
                    let s = ir.ident.to_string();
                    quote! { #s }
                }
                ErrorMsgVar::Names(list_mod) => {
                    let (sep, quote) = list_mod.get();
                    let names = ir
                        .variants
                        .iter()
                        .filter(|v| !v.skip)
                        .map(|v| format!("{}{}{}", quote, v.name, quote))
                        .collect::<Vec<_>>()
                        .join(sep);
                    quote! { #names }
                }
                ErrorMsgVar::Aliases(list_mod) => {
                    let (sep, quote) = list_mod.get();
                    let aliases = ir
                        .variants
                        .iter()
                        .filter(|v| !v.skip)
                        .flat_map(|v| v.aliases.iter().map(|a| format!("{}{}{}", quote, a, quote)))
                        .collect::<Vec<_>>()
                        .join(sep);
                    quote! { #aliases }
                }
                ErrorMsgVar::Input => {
                    quote! { &self.input }
                }
            },
        }
    }

    let format_str: String = std::iter::repeat_n("{}", ir.error.error_template.len()).collect();

    let args: Vec<_> = ir
        .error
        .error_template
        .iter()
        .map(|s| seg_to_arg(s, ir))
        .collect();

    quote! {
        #[automatically_derived]
        impl ::core::fmt::Display for #error_ident {
            fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
                ::core::write!(f, #format_str #(, #args)*)
            }
        }
    }
}

fn gen_error_impl_error(ir: &Ir<'_>) -> TokenStream {
    let error_ident = &ir.error.ident;

    quote! {
        #[automatically_derived]
        impl ::core::error::Error for #error_ident { }
    }
}

fn gen_impl_from_str(ir: &Ir<'_>) -> TokenStream {
    let ident = &ir.ident;
    let (impl_generics, ty_generics, where_clause) = &ir.generics.split_for_impl();
    let error_ident = &ir.error.ident;

    let default_val = quote! { ::core::default::Default::default() };

    let arms: Vec<_> = ir
        .variants
        .iter()
        .filter(|v| !v.skip)
        .map(|v| {
            let v_ident = &v.ident;
            let aliases = &v.aliases;
            let pat = quote! { #(#aliases)|* };
            let val = match v.fields {
                syn::Fields::Named(f) => {
                    let fields: Vec<_> = f
                        .named
                        .iter()
                        .map(|x| {
                            let i = x.ident.clone().expect("named field should have ident");
                            quote! { #i: #default_val }
                        })
                        .collect();
                    quote! { Self::#v_ident { #(#fields),* } }
                }
                syn::Fields::Unnamed(f) => {
                    let fields = std::iter::repeat_n(default_val.clone(), f.unnamed.len());
                    quote! { Self::#v_ident(#(#fields),*) }
                }
                syn::Fields::Unit => quote! { Self::#v_ident },
            };
            quote! { #pat => ::core::result::Result::Ok(#val) }
        })
        .collect();

    quote! {
        #[automatically_derived]
        impl #impl_generics ::core::str::FromStr for #ident #ty_generics #where_clause {
            type Err = #error_ident;

            fn from_str(s: &str) -> ::core::result::Result<Self, <Self as ::core::str::FromStr>::Err> {
                match s {
                    #(#arms,)*
                    _ => ::core::result::Result::Err(#error_ident::new(s)),
                }
            }
        }
    }
}

fn gen_impl_try_from_str(ir: &Ir<'_>) -> TokenStream {
    let ident = &ir.ident;
    let (impl_generics, ty_generics, where_clause) = &ir.generics.split_for_impl();
    let error_ident = &ir.error.ident;

    quote! {
        #[automatically_derived]
        impl #impl_generics ::core::convert::TryFrom<&str> for #ident #ty_generics #where_clause {
            type Error = #error_ident;

            fn try_from(value: &str) -> ::core::result::Result<Self, <Self as ::core::convert::TryFrom<&str>>::Error> {
                ::core::str::FromStr::from_str(value)
            }
        }
    }
}