moxy-derive 0.0.4

derive macros for moxy crate
Documentation
use proc_macro2::TokenStream;
use quote::quote;

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

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

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

impl Render for StructSyntax {
    type Args = params::StructParams;

    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 attributes = Attrs::parse(&args.input.attrs)?;

        let fields: Vec<_> = args
            .data
            .fields
            .iter()
            .enumerate()
            .map(|(i, field)| Field::parse(i, field))
            .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!(args.data.fields, syn::Fields::Named(_));
        let is_unit = matches!(args.data.fields, syn::Fields::Unit);

        let alias = attributes
            .get("display")?
            .iter()
            .find_map(|a| a.as_attr())
            .and_then(|attr| attr.get_string("alias"));
        let name = alias.unwrap_or_else(|| ident.to_string());

        if is_unit || visible_fields.is_empty() {
            let body = quote! { ::std::write!(f, #name) };
            return 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 {
                        #body
                    }
                }
            });
        }

        if let Some(custom) = CustomFmtSyntax::parse(&attributes)? {
            let opts = DisplayOptions {
                name,
                fields: &visible_fields,
                is_named,
                use_self: true,
            };
            let body = custom.render(&opts);
            return 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 {
                        #body
                    }
                }
            });
        }

        let opts = DisplayOptions {
            name,
            fields: &visible_fields,
            is_named,
            use_self: true,
        };

        let style = StyleSyntax::parse(&attributes)?;
        #[allow(unused_mut)]
        let mut output = style.render(&opts)?;
        #[allow(unused_mut)]
        let mut color_import = quote!();

        #[cfg(feature = "color")]
        {
            let color = ColorSyntax::parse(&attributes)?;
            color_import = color.import_tokens();
            output = color.render(output);
        }

        let body = quote! { #output };

        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
                    #body
                }
            }
        })
    }
}