struct_field 0.1.0

Derive macros for generating each field in a struct
Documentation
use proc_macro2::{Ident, Span};
use quote::quote;
use syn::{Attribute, DeriveInput, Fields, Meta, NestedMeta, Type, Visibility};

#[proc_macro_derive(StructField, attributes(struct_field))]
pub fn derive_field(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
    let ast: DeriveInput = syn::parse(input).unwrap();
    let (vis, ty, generics) = (&ast.vis, &ast.ident, &ast.generics);
    let _field_enum_ident = Ident::new(&(ty.to_string() + "Field"), Span::call_site());

    let fields = filter_fields(match ast.data {
        syn::Data::Struct(ref s) => &s.fields,
        _ => panic!("Field can only be derived for structs"),
    });

    let _field_enum_var = fields.iter().map(|(_vis, ident, ty)| {
        quote! {
            #ident(#ty)
        }
    });

    let _update_branch = fields.iter().map(|(_vis, ident, _ty)| {
        quote! {
            #_field_enum_ident::#ident(#ident) => self.#ident = #ident
        }
    });

    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();

    let tokens = quote! {
        # [allow(non_camel_case_types)]
        #vis enum #_field_enum_ident #ty_generics
            #where_clause
        {
            #(#_field_enum_var),*
        }

        impl #impl_generics #ty #ty_generics
            #where_clause
        {
            pub fn update_field(&mut self, field: #_field_enum_ident #ty_generics){
                match field {
                    #(#_update_branch),*
                }
            }
        }
    };
    tokens.into()
}

fn filter_fields(fields: &Fields) -> Vec<(Visibility, Ident, Type)> {
    fields
        .iter()
        .filter_map(|field| {
            if field
                .attrs
                .iter()
                .find(|attr| has_skip_attr(attr, "struct_field"))
                .is_none()
                && field.ident.is_some()
            {
                let field_vis = field.vis.clone();
                let field_ident = field.ident.as_ref().unwrap().clone();
                let field_ty = field.ty.clone();
                Some((field_vis, field_ident, field_ty))
            } else {
                None
            }
        })
        .collect::<Vec<_>>()
}

const ATTR_META_SKIP: &'static str = "skip";

fn has_skip_attr(attr: &Attribute, path: &'static str) -> bool {
    if let Ok(Meta::List(meta_list)) = attr.parse_meta() {
        if meta_list.path.is_ident(path) {
            for nested_item in meta_list.nested.iter() {
                if let NestedMeta::Meta(Meta::Path(path)) = nested_item {
                    if path.is_ident(ATTR_META_SKIP) {
                        return true;
                    }
                }
            }
        }
    }
    false
}