fancy-default-derive 0.1.0

A better `derive(Default)` implementation.
Documentation
use case::CaseExt;
use proc_macro2::{Ident, TokenStream};
use quote::{format_ident, quote};
use syn::{
    parse2, spanned::Spanned, Attribute, DataEnum, DeriveInput, Expr, ExprLit, Generics, Lit,
    LitBool, Meta, Token,
};

use crate::default::enum_impl;

pub(crate) fn process_variant_default(input: TokenStream) -> syn::Result<TokenStream> {
    let input: DeriveInput = parse2(input)?;
    match &input.data {
        syn::Data::Enum(enum_data) => {
            impl_enum_variant(&input.attrs, enum_data, &input.ident, &input.generics)
        }
        _ => unimplemented!(),
    }
}

#[derive(Clone, Copy)]
pub struct VariantConfig {
    constant: bool,
    function: bool,
}

impl VariantConfig {
    pub(crate) fn parse_attr(
        default: VariantConfig,
        attrs: &[Attribute],
    ) -> syn::Result<VariantConfig> {
        let mut var_cfg = default;

        for attr in attrs.iter().filter(|a| a.path().is_ident("variant")) {
            var_cfg.function = true;
            
            match &attr.meta {
                Meta::List(ml) => ml.parse_nested_meta(|meta| {
                    if meta.path.is_ident("const") || meta.path.is_ident("constant") {
                        if meta.input.peek(Token![=]) {
                            let val: LitBool = meta.value()?.parse()?;
                            var_cfg.constant = val.value();
                        } else {
                            var_cfg.constant = true;
                        }
                        return Ok(());
                    }

                    if meta.path.get_ident().is_some_and(|s| {
                        matches!(s.to_string().as_str(), "fn" | "func" | "function")
                    }) {
                        if meta.input.peek(Token![=]) {
                            let val: LitBool = meta.value()?.parse()?;
                            var_cfg.function = val.value();
                        } else {
                            var_cfg.function = true;
                        }
                        return Ok(());
                    }

                    Err(meta.error("unrecognized variant-default parameter"))
                })?,
                Meta::NameValue(nv) => {
                    if let Expr::Lit(ExprLit {
                        lit: Lit::Bool(b), ..
                    }) = &nv.value
                    {
                        if b.value() {
                            var_cfg = default;
                        } else {
                            var_cfg = VariantConfig {
                                function: false,
                                constant: false,
                            };
                        }
                    } else {
                        Err(syn::Error::new(
                            nv.span(),
                            "unknown variant-default parameter",
                        ))?
                    }
                }
                _ => {},
            }
        }

        Ok(var_cfg)
    }

    pub(crate) fn should_generate(&self) -> bool {
        self.constant || self.function
    }
}

pub(crate) fn impl_enum_variant(
    meta_attr: &[Attribute],
    data: &DataEnum,
    name: &Ident,
    generics: &Generics,
) -> syn::Result<TokenStream> {
    let meta_config = VariantConfig::parse_attr(
        if meta_attr.iter().any(|a| a.meta.path().is_ident("variant")) {
            VariantConfig {
                function: true,
                constant: false,
            }
        } else {
            VariantConfig {
                function: false,
                constant: false,
            }
        },
        meta_attr,
    )?;

    let (impl_g, type_g, where_clause) = generics.split_for_impl();

    let mut expanded = TokenStream::new();

    for variant in &data.variants {
        let cfg = VariantConfig::parse_attr(meta_config, &variant.attrs)?;
        if !cfg.should_generate() {
            continue;
        }

        let default_block = enum_impl::impl_enum_variant(variant, name)?;
        let variant_name = variant.ident.to_string();

        if cfg.function {
            let fn_name = format_ident!("default_{}", variant_name.to_snake());
            expanded.extend(quote! {
                fn #fn_name() -> Self {
                    #default_block
                }
            })
        }

        if cfg.constant {
            let const_name = format_ident!("{}", variant_name.to_snake().to_ascii_uppercase());
            expanded.extend(quote! {
                const #const_name: Self = #default_block;
            })
        }
    }

    Ok(quote! {
        impl #impl_g #name #type_g #where_clause {
            #expanded
        }
    })
}