moxy-derive 0.0.4

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

use crate::core::Attrs;

#[derive(Clone)]
pub struct Field {
    raw_attrs: Vec<syn::Attribute>,
    attrs: Attrs,
    vis: syn::Visibility,
    name: FieldName,
    ty: syn::Type,
}

impl Field {
    pub fn parse(i: usize, field: &syn::Field) -> syn::Result<Self> {
        Ok(Self {
            raw_attrs: field.attrs.clone(),
            attrs: Attrs::parse(&field.attrs)?,
            vis: field.vis.clone(),
            name: match &field.ident {
                None => syn::Index::from(i).into(),
                Some(v) => v.clone().into(),
            },
            ty: field.ty.clone(),
        })
    }

    #[allow(unused)]
    pub fn raw_attrs(&self) -> &[syn::Attribute] {
        &self.raw_attrs
    }

    pub fn attrs(&self) -> &Attrs {
        &self.attrs
    }

    pub fn vis(&self) -> &syn::Visibility {
        &self.vis
    }

    pub fn name(&self) -> &FieldName {
        &self.name
    }

    pub fn ty(&self) -> &syn::Type {
        &self.ty
    }

    pub fn display_name(&self) -> syn::Result<String> {
        let display = self.attrs.get("display")?;
        Ok(display
            .iter()
            .find_map(|a| a.as_attr())
            .and_then(|attr| attr.get_string("alias"))
            .unwrap_or_else(|| self.name.to_string()))
    }

    pub fn is_option(&self) -> bool {
        self.as_inner().is_some()
    }

    pub fn as_inner(&self) -> Option<&syn::Type> {
        let syn::Type::Path(type_path) = &self.ty else {
            return None;
        };
        let segment = type_path.path.segments.last()?;
        if segment.ident != "Option" {
            return None;
        }
        let syn::PathArguments::AngleBracketed(args) = &segment.arguments else {
            return None;
        };
        let syn::GenericArgument::Type(inner) = args.args.first()? else {
            return None;
        };
        Some(inner)
    }

    pub fn is_bool(&self) -> bool {
        matches!(&self.ty, syn::Type::Path(p) if p.path.is_ident("bool"))
    }

    pub fn docs(&self) -> Vec<&syn::Attribute> {
        self.raw_attrs
            .iter()
            .filter(|a| a.path().is_ident("doc"))
            .collect()
    }

    pub fn default_value(&self) -> Option<proc_macro2::TokenStream> {
        self.attrs.iter().find_map(|attr| {
            attr.args()
                .iter()
                .find(|arg| arg.path().is_ident("default"))
                .and_then(|arg| arg.as_value_tokens())
        })
    }
}

impl quote::ToTokens for Field {
    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
        let attrs = self.attrs();
        let vis = self.vis();
        let name = self.name();
        let ty = self.ty();

        tokens.extend(quote! {
            #[#attrs] #vis #name : #ty,
        });
    }
}

#[derive(Clone, PartialEq, Eq)]
pub enum FieldName {
    Index(syn::Index),
    Ident(syn::Ident),
}

impl From<syn::Index> for FieldName {
    fn from(value: syn::Index) -> Self {
        Self::Index(value)
    }
}

impl From<syn::Ident> for FieldName {
    fn from(value: syn::Ident) -> Self {
        Self::Ident(value)
    }
}

impl quote::ToTokens for FieldName {
    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
        tokens.extend(match self {
            Self::Index(v) => quote!(#v),
            Self::Ident(v) => quote!(#v),
        });
    }
}

impl std::fmt::Display for FieldName {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Ident(v) => write!(f, "{}", v),
            Self::Index(v) => write!(f, "{}", v.index),
        }
    }
}