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
extern crate proc_macro;
extern crate proc_macro2;
#[macro_use]
extern crate quote;
#[macro_use]
extern crate syn;
mod operator;
use crate::operator::Operator;
use proc_macro::TokenStream;
use syn::{parse_macro_input, DeriveInput};
#[proc_macro_derive(Prune, attributes(prune))]
pub fn prune(input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput);
impl_prune(&ast).into()
}
fn impl_prune(ast: &syn::DeriveInput) -> proc_macro2::TokenStream {
let fields = match ast.data {
syn::Data::Struct(syn::DataStruct { ref fields, .. }) => {
if fields.iter().any(|field| field.ident.is_none()) {
panic!("struct has unnamed fields");
}
fields.iter().cloned().collect::<Vec<syn::Field>>()
}
_ => panic!("#[derive(Prune)] can only be used with structs"),
};
let mut validations = vec![];
for field in &fields {
let field_operations = find_field_operations(field);
for operator in field_operations {
quote_field_operators(&field, operator, &mut validations);
}
}
let ident = &ast.ident;
let (impl_generics, type_generics, where_clause) = ast.generics.split_for_impl();
let expanded = quote! {
impl #impl_generics Prune for #ident #type_generics #where_clause {
fn prune(mut self) -> Self {
#(#validations)*
self
}
}
};
expanded
}
fn find_field_operations(field: &syn::Field) -> Vec<Operator> {
let field_ident = field.ident.clone().unwrap().to_string();
let mut operators = vec![];
for attr in &field.attrs {
if attr.path != parse_quote!(prune) {
continue;
}
match attr.interpret_meta() {
Some(syn::Meta::List(syn::MetaList { ref nested, .. })) => {
for meta in nested.iter() {
match *meta {
syn::NestedMeta::Meta(ref item) => match *item {
syn::Meta::Word(ref name) => match name.to_string().as_ref() {
"trim" => operators.push(Operator::Trim),
"trim_start" => operators.push(Operator::TrimStart),
"trim_end" => operators.push(Operator::TrimEnd),
"no_whitespace" => operators.push(Operator::NoWhitespace),
_ => panic!("Unexpected operation: {}", name),
},
_ => unreachable!("Found a non Meta while looking for operations"),
},
_ => unreachable!("Found a non Meta while looking for operations"),
}
}
}
_ => unreachable!(
"Got something other than a list of attributes while checking field `{}`",
field_ident
),
}
}
operators
}
fn quote_field_operators(
field: &syn::Field,
operator: Operator,
operations: &mut Vec<proc_macro2::TokenStream>,
) {
let name = &field.ident;
match operator {
Operator::Trim => operations.push(quote! {
self.#name = self.#name.trim().into();
}),
Operator::TrimStart => operations.push(quote! {
self.#name = self.#name.trim_start().into();
}),
Operator::TrimEnd => operations.push(quote! {
self.#name = self.#name.trim_end().into();
}),
Operator::NoWhitespace => operations.push(quote! {
self.#name = self.#name.split_whitespace().collect::<Vec<&str>>().join("").into();
}),
}
}