1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
use proc_macro::TokenStream;
use syn::{parse_macro_input, Data, DeriveInput, DataStruct, Fields, FieldsNamed,  Type};

#[macro_use]
extern crate quote;
extern crate proc_macro;

fn title_case(s: &str) -> String {
    let mut c = s.chars();
    match c.next() {
        None => String::new(),
        Some(f) => f.to_uppercase().chain(c).collect(),
    }
}

struct FieldMeta {
    t: Type,
    model_getter: String,
    struct_name: String,
}

#[proc_macro_derive(Model)]
pub fn derive_model(input: TokenStream) -> TokenStream {
    let ops = vec!["equals", "contains"];
    let input = parse_macro_input!(input as DeriveInput);
    let model_name = input.ident;

    let queries_struct_name = format_ident!("{}Queries", model_name);
    let operation_enum_name = format_ident!("{}Operation", model_name);
    let client_fn_name = format_ident!("{}", model_name.to_string().to_ascii_lowercase());
    let client_trait_name = format_ident!("{}Client", model_name);

    let fields = if let Data::Struct(DataStruct {
        fields: Fields::Named(FieldsNamed {
            ref named,
            ..
        }),
        ..
    }) = input.data {
        named
    } else {
        unimplemented!()
    };

    let field_metas = fields.iter().map(|f| {
        let field_name = f.ident.as_ref().unwrap();
        let t = f.ty.clone();

        FieldMeta {
            t,
            model_getter: field_name.to_string(),
            struct_name: format!("{}{}Field", model_name.to_string(), title_case(&field_name.to_string()))
        }
    }).collect::<Vec<FieldMeta>>();

    let field_struct_declarations = field_metas.iter().map(|meta| {
        let field_struct_name = format_ident!("{}", meta.struct_name);
        quote! {
            pub struct #field_struct_name {}
        }
    });

    let field_struct_getters = field_metas.iter().map(|meta| {
        let model_getter = format_ident!("{}", meta.model_getter);
        let struct_name = format_ident!("{}", meta.struct_name);

        quote! {
            pub fn #model_getter() -> #struct_name {
                #struct_name { }
            }
        }
    });

    let field_struct_impls = field_metas.iter().map(|meta| {
        let field_struct_name = format_ident!("{}", meta.struct_name);

        let field_ops = ops.iter().map(|&op| {
            let op_fn_name = format_ident!("{}", op);
            let op_enum_case = format_ident!("{}{}", title_case(&meta.model_getter), title_case(op));
            let field_type = &meta.t;

            quote! {
                pub fn #op_fn_name(&self, v: #field_type) -> #operation_enum_name {
                    #operation_enum_name::#op_enum_case(v)
                }
            }
        });

        quote! {
            impl #field_struct_name {
                #(#field_ops)*
            }
        }
    });

    let operation_enum_cases = field_metas.iter().map(|meta| {
        let t = &meta.t;
        let field_name = &title_case(&meta.model_getter);

        let field_cases = ops.iter().map(|&op| {
            let case_name = format_ident!("{}{}", field_name, title_case(op));

            quote! {
                #case_name(#t)
            }
        });

        quote! {
            #(#field_cases),*
        }
    });

    let m = quote! {
        #[derive(Debug)]
        pub enum #operation_enum_name {
            #(#operation_enum_cases),*
        }

        #(#field_struct_declarations)*

        #(#field_struct_impls)*

        impl Account {
            #(#field_struct_getters)*
        }

        pub struct #queries_struct_name { }

        impl #queries_struct_name {
            fn find_one(&self, operations: Vec<#operation_enum_name>) -> Result<#model_name, ()> {
                Err(())
            }
        }

        trait #client_trait_name {
            fn #client_fn_name(&self) -> #queries_struct_name;
        }

        impl #client_trait_name for Client {
            fn #client_fn_name(&self) -> #queries_struct_name {
                #queries_struct_name { }
            }
        }
    };

    TokenStream::from(m)
}