colonbuilder 0.1.6

A rust builder offers from_str method to build struct from colon separate string.
Documentation
use proc_macro2::{Ident, TokenStream};
use quote::{format_ident, quote};
use syn::{parse_macro_input, Field, Fields, Lit, Meta, Type};

static CB_PATH: &str = "cb";
static CB_ABBR: &str = "abbr";

#[proc_macro_derive(ColonBuilder, attributes(cb))]
pub fn derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
    let struct_def = parse_macro_input!(input);
    impl_derive(&struct_def).into()
}

fn impl_derive(struct_def: &syn::DeriveInput) -> TokenStream {
    let t = match struct_def.data {
        syn::Data::Struct(ref s) => handle_data_struct(s, struct_def),
        _ => panic!("#[derive(PrintFields)] is only defined for structs"),
    };
    t.into()
}

fn handle_data_struct(s: &syn::DataStruct, struct_def: &syn::DeriveInput) -> TokenStream {
    let (fields, methods, method_names) = collect_fields_methods(&s.fields);
    let name = &struct_def.ident;
    let builder_ident = format_ident!("{}Builder", name);

    quote! {
        pub struct #builder_ident;
        impl #builder_ident {
            #(#methods)*
            fn build(s: &str) -> #name {
                #name {
                    #(
                        #fields: #builder_ident::#method_names(s),
                    )*
                }
            }
        }
        impl #name {
            pub fn from_str(s: &str) -> #name {
                #builder_ident::build(s)
            }
        }
    }
}

fn collect_fields_methods(
    fields: &Fields,
) -> (Vec<TokenStream>, Vec<TokenStream>, Vec<TokenStream>) {
    let mut gen_fields = Vec::<TokenStream>::new();
    let mut methods = Vec::<TokenStream>::new();
    let mut method_names = Vec::<TokenStream>::new();

    match fields {
        Fields::Named(fs) => {
            handle_named_field(fs, &mut gen_fields, &mut methods, &mut method_names);
        }
        _ => panic!("Builder pattern only supports structs with named fields."),
    }
    (gen_fields, methods, method_names)
}

fn handle_named_field(fs: &syn::FieldsNamed, gen_fields: &mut Vec<TokenStream>, methods: &mut Vec<TokenStream>, method_names: &mut Vec<TokenStream>) {
    fs.named.iter().for_each(|f| {
        let ident = f.ident.as_ref().unwrap();
        let abbr = get_abbr(f);
        let match_prefix = abbr.unwrap_or(format!("{}", ident));
        let regexp = get_regexp(match_prefix);
        let method_name = format_ident!("set_{}", ident);

        let method = generate_set_method(f, regexp, &method_name);
        gen_fields.push(quote!(#ident));
        methods.push(method);
        method_names.push(quote!(#method_name));
    })
}
fn generate_set_method(f: &Field, regexp: String, method_name: &Ident) -> TokenStream {
    let vec_string_type = syn::parse_quote!(Vec<String>);
    let string_type = syn::parse_quote!(String);
    let option_vec_string_type: Type = syn::parse_quote!(Option<Vec<String>>);
    let ty = &f.ty;
    let get_match = quote!{
        {
            let pat = reg!(#regexp);
            pat.captures(s).and_then(|m| m.name("v").map(|s| s.as_str().trim().to_owned()))
        }
    };
    if ty == &vec_string_type {
        quote! {
            #[allow(non_snake_case)]
            pub fn #method_name(s: &str) -> Vec<String> {
                let m = #get_match;
                m.map(|s| {
                    s.split(',')
                    .into_iter()
                    .map(|s| s.trim().to_owned())
                    .collect::<Vec<String>>()
                }).unwrap_or(vec![])
            }
        }
    } else if ty == &string_type {
        quote! {
            #[allow(non_snake_case)]
            pub fn #method_name(s: &str) -> String {
                let m = #get_match;
                m.unwrap_or("".to_string())      
            }
        }
    } else if ty == &option_vec_string_type {
        quote! {
            #[allow(non_snake_case)]
            pub fn #method_name(s: &str) -> Option<Vec<String>> {
                let m = #get_match;
                m.and_then(|m| {
                    let tc: Vec<String> = m
                    .split(',')
                    .into_iter()
                    .map(|s| s.trim().to_owned())
                    .collect();
                    Some(tc)
                })
            }
        }
    } else {
        quote! {
            #[allow(non_snake_case)]
            pub fn #method_name(s: &str) -> Option<String> {
                #get_match
            }
        }
    }
}

fn get_abbr(f: &syn::Field) -> Option<String> {
    f.attrs
        .iter()
        .find(|attr| attr.path.is_ident(CB_PATH))
        .and_then(|attr| match attr.parse_meta() {
            Ok(ref data) => process_meta(data),
            Err(_) => None,
        })
}

fn process_meta(data: &Meta) -> Option<String> {
    match data {
        Meta::NameValue(name_value) => {
            if name_value.path.is_ident(CB_ABBR) {
                if let Lit::Str(lit_str) = &name_value.lit {
                    Some(lit_str.value())
                } else {
                    None
                }
            } else {
                None
            }
        }
        syn::Meta::Path(_) => None,
        syn::Meta::List(p) => process_meta_list(p),
    }
}

fn process_meta_list(p: &syn::MetaList) -> Option<String> {
    p.nested.iter().find_map(|att| match att {
        syn::NestedMeta::Meta(meta) => process_meta(meta),
        _ => todo!(),
    })
}

fn get_regexp(field: String) -> String {
    format!(r"{}:\s*(?P<v>.+?)((\r?\n)|$)", field)
}