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
extern crate proc_macro;
extern crate proc_macro2;
#[macro_use]
extern crate quote;
#[macro_use]
extern crate syn;

use proc_macro::TokenStream;
use proc_macro2::{Ident, Span};
use prune::Operator;
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 {
    // Ensure the macro is on a struct with named fields
    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 operators = vec![];

    for field in &fields {
        let field_operations = find_field_operations(field);
        for operator in field_operations {
            quote_field_operators(&field, operator, &mut operators);
        }
    }

    let ident = &ast.ident;

    let option_ident = Ident::new(&format!("PruneOption{}", ident), Span::call_site());

    let (impl_generics, type_generics, where_clause) = ast.generics.split_for_impl();
    let expanded = quote! {
        impl #impl_generics ::prune::Prune for #ident #type_generics #where_clause {
            fn prune(mut self) -> Self {
                #(#operators)*

                self
            }
        }

        pub trait #option_ident {
            fn prune(self) -> Self;
        }

        impl #impl_generics #option_ident for Option<#ident #type_generics> #where_clause {
            fn prune(mut self) -> Self {
                use prune::Prune;
                self.map(|s|s.prune())
            }
        }
    };

    // println!("{}", expanded.to_string());

    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"),
                    }
                }
            }
            Some(syn::Meta::Word(_)) => operators.push(Operator::Nested),
            _ => 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 ident = &field.ident;

    operations.push(match operator {
        Operator::Trim => {
            quote! {{
                use ::prune::trim::PruneTrim;
                self.#ident = self.#ident.prune_trim();
            }}
        }
        Operator::TrimStart => {
            quote! {{
                use ::prune::trim::PruneTrimStart;
                self.#ident = self.#ident.prune_trim_start();
            }}
        }
        Operator::TrimEnd => {
            quote! {{
                use ::prune::trim::PruneTrimEnd;
                self.#ident = self.#ident.prune_trim_end();
            }}
        }
        Operator::NoWhitespace => {
            quote! {{
                use ::prune::trim::PruneNoWhitespace;
                self.#ident = self.#ident.prune_no_whitespace();
            }}
        }
        Operator::Nested => {
            quote! {{
                use ::prune::Prune;
                self.#ident = self.#ident.prune();
            }}
        }
    });
}