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
extern crate proc_macro;

use proc_macro::TokenStream;
use proc_macro2::{Ident, Span};
use proc_macro_error::abort;
use quote::quote;

#[proc_macro_derive(Patch, attributes(patch_derive))]
pub fn derive_patch(item: TokenStream) -> TokenStream {
    let input = syn::parse_macro_input!(item as syn::DeriveInput);
    let struct_name = &input.ident;
    let patch_struct_name = Ident::new(&format!("{}Patch", struct_name), Span::call_site());
    let mut patch_derive = None;
    let attrs = &input.attrs;
    for syn::Attribute { path, tokens, .. } in attrs.iter() {
        if Some("patch_derive".into()) == path.segments.first().map(|s| s.ident.to_string()) {
            patch_derive = Some(tokens);
        }
    }

    let fields_with_type = match &input.data {
        syn::Data::Struct(syn::DataStruct {
            fields: syn::Fields::Named(f),
            ..
        }) => f
            .clone()
            .named
            .into_pairs()
            .map(|p| p.into_value())
            .map(|f| (f.ident.unwrap(), f.ty))
            .collect::<Vec<_>>(),
        _ => abort!(&input.ident, "Patch derive only use for struct"),
    };

    let wrapped_fields = &mut fields_with_type
        .iter()
        .map(|(f, t)| {
            (
                f.clone(),
                syn::parse::<syn::Path>(quote!(Option<#t>).to_string().parse().unwrap()).unwrap(),
            )
        })
        .collect::<Vec<_>>();

    let field_names = wrapped_fields.iter().map(|(f, _)| f);
    let field_names_clone = field_names.clone();
    let wrapped_types = wrapped_fields.iter().map(|(_, t)| t);

    let mut output = if let Some(patch_derive) = patch_derive {
        quote!(
            #[derive #patch_derive]
            pub struct #patch_struct_name {
                #(pub #field_names: #wrapped_types,)*
            }
        )
    } else {
        quote::quote!(
            #[derive(Default)]
            pub struct #patch_struct_name {
                #(pub #field_names: #wrapped_types,)*
            }
        )
    }
    .to_string();

    output += &quote!(
        impl struct_patch_trait::traits::Patch< #patch_struct_name > for #struct_name {
            fn apply(&mut self, patch: #patch_struct_name) {
                #(
                    if let Some(v) = patch.#field_names_clone {
                        self.#field_names_clone = v;
                    }
                )*
            }
        }
    )
    .to_string();

    output.parse().unwrap()
}