1
#![allow(clippy::default_trait_access)]
2

            
3
use darling::{ast, FromDeriveInput, FromField, FromVariant, ToTokens};
4
use proc_macro2::TokenStream;
5
use proc_macro_error::abort;
6
use quote::quote;
7

            
8
use crate::ActionableArgs;
9

            
10
12
#[derive(Debug, FromDeriveInput)]
11
#[darling(supports(enum_any))]
12
struct Action {
13
    ident: syn::Ident,
14
    generics: syn::Generics,
15
    data: ast::Data<Variant, ()>,
16

            
17
    /// Overrides the crate name for `actionable` references.
18
    #[darling(skip)]
19
    actionable: Option<ActionableArgs>,
20
}
21

            
22
18
#[derive(Debug, FromVariant)]
23
struct Variant {
24
    ident: syn::Ident,
25
    fields: ast::Fields<Field>,
26
}
27

            
28
2
#[derive(Debug, FromField)]
29
struct Field {}
30

            
31
impl ToTokens for Action {
32
4
    fn to_tokens(&self, tokens: &mut TokenStream) {
33
4
        let name = &self.ident;
34
4
        let enum_data = self
35
4
            .data
36
4
            .as_ref()
37
4
            .take_enum()
38
4
            .expect("Expected enum in data");
39
4

            
40
4
        let actionable = self
41
4
            .actionable
42
4
            .as_ref()
43
4
            .and_then(|args| args.actionable.clone())
44
4
            .unwrap_or_else(|| {
45
2
                let mut segments = syn::punctuated::Punctuated::new();
46
2
                segments.push_value(syn::PathSegment {
47
2
                    ident: syn::Ident::new("actionable", name.span()),
48
2
                    arguments: syn::PathArguments::None,
49
2
                });
50
2
                syn::Path {
51
2
                    leading_colon: None,
52
2
                    segments,
53
2
                }
54
4
            });
55
4
        let (impl_generics, type_generics, where_clause) = self.generics.split_for_impl();
56
4

            
57
9
        let variants = enum_data.into_iter().map(|variant| {
58
9
			let ident = variant.ident.clone();
59
9
			let ident_as_string = ident.to_string();
60
9
			match variant.fields.len() {
61
				0 => {
62
8
					quote! {
63
8
						Self::#ident => #actionable::ActionName(vec![::std::borrow::Cow::Borrowed(#ident_as_string)])
64
8
					}
65
				}
66
				1 => {
67
1
					quote! {
68
1
						Self::#ident(subaction) => {
69
1
							let mut name = Action::name(subaction);
70
1
							name.0.insert(0, ::std::borrow::Cow::Borrowed(#ident_as_string));
71
1
							name
72
1
						}
73
1
					}
74
				}
75
				_ => {
76
					abort!(
77
						variant.ident,
78
						"For derive(Action), all enum variants may have at most 1 field"
79
					)
80
				}
81
			}
82
9
		});
83
4

            
84
4
        tokens.extend(quote! {
85
4
            impl#impl_generics Action for #name#type_generics #where_clause {
86
4
                fn name(&self) -> #actionable::ActionName {
87
4
                    match self {
88
4
                        #(
89
4
                            #variants
90
4
                        ),*
91
4
                    }
92
4
                }
93
4
            }
94
4
        });
95
4
    }
96
}
97

            
98
4
pub fn derive(input: &syn::DeriveInput) -> Result<TokenStream, darling::Error> {
99
4
    let mut actionable = Action::from_derive_input(input)?;
100

            
101
4
    if let Some(attr) = input
102
4
        .attrs
103
4
        .iter()
104
4
        .find(|attr| attr.path.segments.first().unwrap().ident == "action")
105
2
    {
106
2
        let args: ActionableArgs = syn::parse2(attr.tokens.clone())?;
107
2
        actionable.actionable = Some(args);
108
2
    }
109

            
110
4
    Ok(actionable.into_token_stream())
111
4
}