moxy-derive 0.0.4

derive macros for moxy crate
Documentation
use proc_macro2::TokenStream;
use quote::{format_ident, quote};

use crate::{
    Render,
    core::{Attrs, Field, FieldName},
    params,
};

#[cfg(feature = "color")]
use super::rules::color::ColorSyntax;
use super::rules::{DisplayOptions, custom::CustomFmtSyntax, style::StyleSyntax};

#[derive(Clone, Default)]
pub struct EnumSyntax;

impl Render for EnumSyntax {
    type Args = params::EnumParams;

    fn render(&self, args: Self::Args) -> syn::Result<TokenStream> {
        let ident = &args.input.ident;
        let (impl_generics, type_generics, where_generics) = args.input.generics.split_for_impl();
        let enum_attrs = Attrs::parse(&args.input.attrs)?;
        let enum_style = StyleSyntax::parse(&enum_attrs)?;
        let mut arms = Vec::new();
        #[allow(unused_mut)]
        let mut color_import = quote!();

        #[cfg(feature = "color")]
        let enum_color = ColorSyntax::parse(&enum_attrs)?;

        for variant in &args.data.variants {
            let variant_ident = &variant.ident;
            let variant_attrs = Attrs::parse(&variant.attrs)?;
            let fields: Vec<_> = variant
                .fields
                .iter()
                .enumerate()
                .map(|(i, f)| Field::parse(i, f))
                .collect::<syn::Result<Vec<_>>>()?;
            let visible_fields = fields
                .iter()
                .map(|f| -> syn::Result<Option<&Field>> {
                    let field_display = f.attrs().get("display")?;
                    let field_attr = field_display.iter().find_map(|a| a.as_attr());
                    Ok(if field_attr.map(|a| a.exists("skip")).unwrap_or(false) {
                        None
                    } else {
                        Some(f)
                    })
                })
                .collect::<syn::Result<Vec<_>>>()?
                .into_iter()
                .flatten()
                .collect::<Vec<_>>();
            let is_named = matches!(variant.fields, syn::Fields::Named(_));
            let is_unit = matches!(variant.fields, syn::Fields::Unit);
            let v_style = StyleSyntax::parse(&variant_attrs)?;
            let mut style = if v_style.is_default() && !enum_style.is_default() {
                StyleSyntax::parse(&enum_attrs)?
            } else {
                v_style
            };

            if enum_style.is_pretty() {
                style = style.with_pretty(true);
            }

            let alias = variant_attrs
                .get("display")?
                .iter()
                .find_map(|a| a.as_attr())
                .and_then(|attr| attr.get_string("alias"));
            let name_str = alias.unwrap_or_else(|| variant_ident.to_string());

            let (pattern, body) = if is_unit {
                let pattern = quote! { Self::#variant_ident };
                let body = quote! { ::std::write!(f, #name_str) };
                (pattern, body)
            } else if is_named {
                let visible_idents: Vec<_> = visible_fields
                    .iter()
                    .filter_map(|f| match f.name() {
                        FieldName::Ident(id) => Some(id.clone()),
                        _ => None,
                    })
                    .collect();
                let pattern = quote! { Self::#variant_ident { #(#visible_idents,)* .. } };

                if visible_fields.is_empty() {
                    (pattern, quote! { ::std::write!(f, #name_str) })
                } else if let Some(custom) = CustomFmtSyntax::parse(&variant_attrs)? {
                    let opts = DisplayOptions {
                        name: name_str,
                        fields: &visible_fields,
                        is_named: true,
                        use_self: false,
                    };
                    (pattern, custom.render(&opts))
                } else {
                    let opts = DisplayOptions {
                        name: name_str,
                        fields: &visible_fields,
                        is_named: true,
                        use_self: false,
                    };
                    #[allow(unused_mut)]
                    let mut output = style.render(&opts)?;

                    #[cfg(feature = "color")]
                    {
                        let v_color = ColorSyntax::parse(&variant_attrs)?;
                        let color = if v_color.theme.is_some() {
                            &v_color
                        } else {
                            &enum_color
                        };
                        if color.needs_import() {
                            color_import = color.import_tokens();
                        }
                        output = color.render(output);
                    }

                    (pattern, quote! { #output })
                }
            } else {
                let bindings: Vec<_> = fields
                    .iter()
                    .enumerate()
                    .map(|(i, _)| format_ident!("__v{}", i))
                    .collect();
                let pattern = quote! { Self::#variant_ident(#(#bindings),*) };

                if visible_fields.is_empty() {
                    (pattern, quote! { ::std::write!(f, #name_str) })
                } else if let Some(custom) = CustomFmtSyntax::parse(&variant_attrs)? {
                    let opts = DisplayOptions {
                        name: name_str,
                        fields: &visible_fields,
                        is_named: false,
                        use_self: false,
                    };
                    (pattern, custom.render(&opts))
                } else {
                    let opts = DisplayOptions {
                        name: name_str,
                        fields: &visible_fields,
                        is_named: false,
                        use_self: false,
                    };
                    #[allow(unused_mut)]
                    let mut output = style.render(&opts)?;

                    #[cfg(feature = "color")]
                    {
                        let v_color = ColorSyntax::parse(&variant_attrs)?;
                        let color = if v_color.theme.is_some() {
                            &v_color
                        } else {
                            &enum_color
                        };
                        if color.needs_import() {
                            color_import = color.import_tokens();
                        }
                        output = color.render(output);
                    }

                    (pattern, quote! { #output })
                }
            };

            arms.push(quote! { #pattern => { #body } });
        }

        Ok(quote! {
            impl #impl_generics ::std::fmt::Display for #ident #type_generics #where_generics {
                fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
                    #color_import
                    match self {
                        #(#arms,)*
                    }
                }
            }
        })
    }
}