Skip to main content

ethanol_derive/
lib.rs

1use proc_macro::TokenStream;
2use syn::{parse_macro_input, Data, DeriveInput, DataStruct, Fields, FieldsNamed,  Type};
3
4#[macro_use]
5extern crate quote;
6extern crate proc_macro;
7
8fn title_case(s: &str) -> String {
9    let mut c = s.chars();
10    match c.next() {
11        None => String::new(),
12        Some(f) => f.to_uppercase().chain(c).collect(),
13    }
14}
15
16struct FieldMeta {
17    t: Type,
18    model_getter: String,
19    struct_name: String,
20}
21
22#[proc_macro_derive(Model)]
23pub fn derive_model(input: TokenStream) -> TokenStream {
24    let ops = vec!["equals", "contains"];
25    let input = parse_macro_input!(input as DeriveInput);
26    let model_name = input.ident;
27
28    let queries_struct_name = format_ident!("{}Queries", model_name);
29    let operation_enum_name = format_ident!("{}Operation", model_name);
30    let client_fn_name = format_ident!("{}", model_name.to_string().to_ascii_lowercase());
31    let client_trait_name = format_ident!("{}Client", model_name);
32
33    let fields = if let Data::Struct(DataStruct {
34        fields: Fields::Named(FieldsNamed {
35            ref named,
36            ..
37        }),
38        ..
39    }) = input.data {
40        named
41    } else {
42        unimplemented!()
43    };
44
45    let field_metas = fields.iter().map(|f| {
46        let field_name = f.ident.as_ref().unwrap();
47        let t = f.ty.clone();
48
49        FieldMeta {
50            t,
51            model_getter: field_name.to_string(),
52            struct_name: format!("{}{}Field", model_name.to_string(), title_case(&field_name.to_string()))
53        }
54    }).collect::<Vec<FieldMeta>>();
55
56    let field_struct_declarations = field_metas.iter().map(|meta| {
57        let field_struct_name = format_ident!("{}", meta.struct_name);
58        quote! {
59            pub struct #field_struct_name {}
60        }
61    });
62
63    let field_struct_getters = field_metas.iter().map(|meta| {
64        let model_getter = format_ident!("{}", meta.model_getter);
65        let struct_name = format_ident!("{}", meta.struct_name);
66
67        quote! {
68            pub fn #model_getter() -> #struct_name {
69                #struct_name { }
70            }
71        }
72    });
73
74    let field_struct_impls = field_metas.iter().map(|meta| {
75        let field_struct_name = format_ident!("{}", meta.struct_name);
76
77        let field_ops = ops.iter().map(|&op| {
78            let op_fn_name = format_ident!("{}", op);
79            let op_enum_case = format_ident!("{}{}", title_case(&meta.model_getter), title_case(op));
80            let field_type = &meta.t;
81
82            quote! {
83                pub fn #op_fn_name(&self, v: #field_type) -> #operation_enum_name {
84                    #operation_enum_name::#op_enum_case(v)
85                }
86            }
87        });
88
89        quote! {
90            impl #field_struct_name {
91                #(#field_ops)*
92            }
93        }
94    });
95
96    let operation_enum_cases = field_metas.iter().map(|meta| {
97        let t = &meta.t;
98        let field_name = &title_case(&meta.model_getter);
99
100        let field_cases = ops.iter().map(|&op| {
101            let case_name = format_ident!("{}{}", field_name, title_case(op));
102
103            quote! {
104                #case_name(#t)
105            }
106        });
107
108        quote! {
109            #(#field_cases),*
110        }
111    });
112
113    let m = quote! {
114        #[derive(Debug)]
115        pub enum #operation_enum_name {
116            #(#operation_enum_cases),*
117        }
118
119        #(#field_struct_declarations)*
120
121        #(#field_struct_impls)*
122
123        impl Account {
124            #(#field_struct_getters)*
125        }
126
127        pub struct #queries_struct_name { }
128
129        impl #queries_struct_name {
130            fn find_one(&self, operations: Vec<#operation_enum_name>) -> Result<#model_name, ()> {
131                Err(())
132            }
133        }
134
135        trait #client_trait_name {
136            fn #client_fn_name(&self) -> #queries_struct_name;
137        }
138
139        impl #client_trait_name for Client {
140            fn #client_fn_name(&self) -> #queries_struct_name {
141                #queries_struct_name { }
142            }
143        }
144    };
145
146    TokenStream::from(m)
147}