deltastruct_proc 0.1.3

The proc-macro backend for the crate Deltastruct.
Documentation
extern crate proc_macro;

use proc_macro::{TokenStream};
use quote::{format_ident, quote};
use syn::{
  parse_macro_input, Ident, Type,
  punctuated::{Punctuated},
  token::{Comma}
};

use convert_case::{Case, Casing};


#[proc_macro_attribute]
pub fn deltastruct(_attrs : TokenStream, input : TokenStream) -> TokenStream {
  let input = parse_macro_input!(input as syn::ItemMod);
  parse_deltamod(input)
}

fn parse_deltamod(modblock : syn::ItemMod) -> TokenStream {
  let content = modblock.content.expect("Deltastruct requires content defined in a module definition").1;

  // Generate names for new definitions
  let ident_struct = modblock.ident.clone();
  let ident_delta = format_ident!("{}Delta", modblock.ident);
  
  // Vec of fields contained in the defined struct
  let mut fields : Option<Punctuated<syn::Field, _>> = None;

  // Vec of methods that should be included as part of the delta
  let mut methods : Option<Vec<syn::ImplItemMethod>> = None;

  // Keep track of items to re-inject later
  let mut user_defined_items = Vec::<syn::Item>::new();

  // Pull out the relevant data
  for item in content {
    match item {
      syn::Item::Struct(s) => {
        // Make sure we hit the right type
        if s.ident != modblock.ident {
          continue;
        }

        user_defined_items.push(syn::Item::Struct(s.clone()));
        fields = match s.fields {
            syn::Fields::Named(nf) => Some(nf.named),
          _ => panic!("Expected named fields as part of defined struct")
        }
      },
      syn::Item::Impl(i) => {
        // TODO: Check if the impl is for the correct struct. Currently
        // it's rather difficult because Item::Impls only have a type
        // field, not an identifier

        user_defined_items.push(syn::Item::Impl(i.clone()));
        methods = Some(i.items.iter().filter_map(|x| match x {
          syn::ImplItem::Method(m) => Some((*m).clone()),
          _ => None
        }).collect());
      },
      _ => continue
    }
  }

  // Pre-Validatation
  let fields = fields.expect("No data field definition found.");
  let methods = methods.expect("No impl definition found.");

  // Process data into need identifiers and whatnot

  // extract field names
  let field_names = fields.iter().enumerate().map(|(i, field)| {
    field.ident.clone().unwrap_or(
      format_ident!("f{}", i).clone()
    )
  }).collect::<Vec<_>>();

  // convert field names to camel case
  let field_names_camel = field_names.iter().map(|n| {
    format_ident!("{}", n.to_string().from_case(Case::Snake).to_case(Case::UpperCamel))
  }).collect::<Vec<_>>();

  // extract field types
  let field_types = fields.iter().map(|f| f.ty.clone()).collect::<Vec<_>>();

  // build identifier for each fn type
  let field_types_fn : Vec<_> = field_types.iter().map(|ty| {
    match ty {
      syn::Type::Path(p) => {
        let mut segs = p.path.segments.clone();
        let seg = segs.pop().expect("Empty type path on field.");
        let seg = seg.value();
        let nseg = syn::PathSegment{
          ident: format_ident!("{}Delta", seg.ident),
          ..seg.clone()
        };
        
        segs.push(nseg);
        segs
      },
      _ => panic!("Expected type path for field.")
    }
  }).collect();


  // extract method names
  let fns_names : Vec<Ident> = methods.iter().map(|x| 
    x.sig.ident.clone()
  ).collect();

  // convert method names to camel case
  let fns_names_camel : Vec<Ident> = methods.iter().map(|x| 
    format_ident!("{}", x.sig.ident.to_string().from_case(Case::Snake).to_case(Case::UpperCamel))
  ).collect();

  // extract method args
  let fns_args_types : Vec<Punctuated<Box<Type>, Comma>> = methods.iter().map(|m|
    m.sig.inputs.clone().into_iter().skip(1)
      .filter_map(|arg| match arg {
        syn::FnArg::Typed(targ) => Some(targ.ty),
        _ => None
      }).collect()
  ).collect();

  // generate dummy arg names to match against for each method
  let fns_args_names : Vec<Punctuated<syn::Ident, Comma>> = fns_args_types.iter().map(|ma| {
    (0..ma.len()).map(|i| format_ident!("f{}", i)).collect()
  }).collect::<Vec<_>>();

  // build fn_enum::variant pairs for matching
  let fns_self_match_names = fns_names_camel.iter().map(|x| {
    let mut res = Punctuated::<syn::PathSegment, syn::token::Colon2>::new();
    
    res.push(syn::PathSegment{
      ident: ident_delta.clone(),
      arguments: syn::PathArguments::None
    });
    res.push(syn::PathSegment{
      ident: x.clone(),
      arguments: syn::PathArguments::None
    });
    res
  });
  let fns_field_match_names = field_names_camel.iter().map(|x| {
    let mut res = Punctuated::<syn::PathSegment, syn::token::Colon2>::new();
    
    res.push(syn::PathSegment{
      ident: ident_delta.clone(),
      arguments: syn::PathArguments::None
    });
    res.push(syn::PathSegment{
      ident: x.clone(),
      arguments: syn::PathArguments::None
    });
    res
  });
  

  // Build the delta struct
  let token_delta_struct = quote!(
    #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
    pub enum #ident_delta {
      #(#fns_names_camel(#fns_args_types)),*,
      #(#field_names_camel(#field_types_fn)),*
    }
  );

  // Build impl
  let token_impl_match = quote!(
    #(#fns_self_match_names(#fns_args_names) => self.#fns_names(#fns_args_names)),*,
    #(#fns_field_match_names(ref x) => self.#field_names.apply(x)),*
  );
  let token_impl = quote!(
    impl DeltaStruct<#ident_delta> for #ident_struct {
      fn apply(&mut self, delta : &#ident_delta) {
        match *delta {
          #token_impl_match
        };
      }
    }
  );

  // Build the final set of definitions
  quote!(
    #(#user_defined_items)*
    #token_delta_struct
    #token_impl
  ).into()
}