1#![recursion_limit = "128"]
2
3extern crate proc_macro;
4#[macro_use]
5extern crate syn;
6#[macro_use]
7extern crate quote;
8extern crate conform;
9
10use proc_macro::TokenStream;
11use quote::ToTokens;
12
13#[proc_macro_derive(Conform, attributes(conform))]
14pub fn derive_conform(input: TokenStream) -> TokenStream {
15 let ast = syn::parse(input).unwrap();
16 impl_conform(&ast).into()
17}
18
19fn impl_conform(ast: &syn::DeriveInput) -> quote::Tokens {
20 let fields: Vec<syn::Field> = match ast.data {
21 syn::Data::Struct(syn::DataStruct { ref fields, .. }) => {
22 if fields.iter().any(|field| field.ident.is_none()) {
23 panic!("struct has unnamed fields");
24 }
25 fields.iter().cloned().collect()
26 }
27 _ => panic!("#[derive(Conform)] is only defined for structs"),
28 };
29
30 let mut tokens = vec![];
31
32 for field in &fields {
33 for attr in &field.attrs {
34 if attr.path != parse_quote!(conform) {
35 continue;
36 }
37
38 let field_ident = field.ident.clone().unwrap();
39
40 match attr.interpret_meta() {
41 Some(syn::Meta::List(syn::MetaList { ref nested, .. })) => {
42 let meta_items: Vec<&syn::NestedMeta> = nested.iter().collect();
43 for meta_item in meta_items {
44 match *meta_item {
45 syn::NestedMeta::Meta(ref item) => match *item {
46 syn::Meta::Word(ref name) => match name.to_string().as_ref() {
47 "trim" => tokens.push(field_tokens(
48 field,
49 field_ident,
50 quote!(conform::make::trim),
51 )),
52 "trim_left" => tokens.push(field_tokens(
53 field,
54 field_ident,
55 quote!(conform::make::trim_left),
56 )),
57 "trim_right" => tokens.push(field_tokens(
58 field,
59 field_ident,
60 quote!(conform::make::trim_right),
61 )),
62 "lower" => tokens.push(field_tokens(
63 field,
64 field_ident,
65 quote!(conform::make::lower),
66 )),
67 "upper" => tokens.push(field_tokens(
68 field,
69 field_ident,
70 quote!(conform::make::upper),
71 )),
72 "sentence" => tokens.push(field_tokens(
73 field,
74 field_ident,
75 quote!(conform::make::sentence),
76 )),
77 "title" => tokens.push(field_tokens(
78 field,
79 field_ident,
80 quote!(conform::make::title),
81 )),
82 "camel" => tokens.push(field_tokens(
83 field,
84 field_ident,
85 quote!(conform::make::camel),
86 )),
87 "pascal" => tokens.push(field_tokens(
88 field,
89 field_ident,
90 quote!(conform::make::pascal),
91 )),
92 "kebab" => tokens.push(field_tokens(
93 field,
94 field_ident,
95 quote!(conform::make::kebab),
96 )),
97 "train" => tokens.push(field_tokens(
98 field,
99 field_ident,
100 quote!(conform::make::train),
101 )),
102 "snake" => tokens.push(field_tokens(
103 field,
104 field_ident,
105 quote!(conform::make::snake),
106 )),
107 "constant" => tokens.push(field_tokens(
108 field,
109 field_ident,
110 quote!(conform::make::constant),
111 )),
112 _ => panic!("Unexpected conform argument: {}", name),
113 },
114 _ => unreachable!("Found a non Word while looking for conform arguments"),
115 },
116 _ => unreachable!("Found a non Meta while looking for conform arguments"),
117 }
118 }
119 }
120 _ => unreachable!(
121 "Got something other than a list of attributes while checking field `{}`",
122 field_ident
123 ),
124 }
125 }
126 }
127
128 let ident = &ast.ident;
129 let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
130
131 quote! {
132 impl #impl_generics Conform for #ident #ty_generics #where_clause {
133 fn conform(&mut self) -> &mut Self {
134 use conform;
135 #(#tokens)*
136 self
137 }
138 }
139 }
140}
141
142fn field_tokens(
143 field: &syn::Field,
144 field_ident: syn::Ident,
145 transform: quote::Tokens,
146) -> quote::Tokens {
147 match field.ty {
148 syn::Type::Path(syn::TypePath { ref path, .. }) => {
149 let mut tokens = quote::Tokens::new();
150 path.to_tokens(&mut tokens);
151 match tokens.to_string().replace(' ', "").as_ref() {
152 "String" => {
153 quote! {
154 self.#field_ident = #transform(&self.#field_ident);
155 }
156 }
157 "Option<String>" => {
158 quote! {
159 self.#field_ident = match self.#field_ident {
160 Some(ref value) => Some(#transform(value)),
161 None => None
162 };
163 }
164 }
165 _ => panic!(
166 "Field `{}`: only `String` & `Option<String>` types are supported",
167 field_ident
168 ),
169 }
170 }
171 _ => panic!(
172 "Field `{}`: only `String` & `Option<String>` types are supported",
173 field_ident
174 ),
175 }
176}