1use proc_macro::TokenStream;
2use quote::quote;
3use syn::{parse_macro_input, Attribute, DeriveInput, Lit};
4
5#[proc_macro_derive(Plural, attributes(plural))]
7pub fn derive_plural(input: TokenStream) -> TokenStream {
8 let input = parse_macro_input!(input as DeriveInput);
10
11 let ident = input.ident.clone();
13 let data = if let syn::Data::Enum(data) = input.data {
14 data
15 } else {
16 return syn::Error::new_spanned(input, "Plural can only be derived for enums")
17 .to_compile_error()
18 .into();
19 };
20
21 if data.variants.is_empty() {
22 return quote! {
24 impl PluralDisplay for #ident {
25 fn plural(&self) -> &'static str {
26 unreachable!("Empty enums cannot have instances.");
27 }
28 }
29 }
30 .into();
31 }
32
33 let variants = data.variants.iter().map(|variant| {
35 let variant_ident = &variant.ident;
36
37 let custom_plural = find_custom_plural(&variant.attrs);
39
40 let plural_form = if let Some(custom) = custom_plural {
42 custom
43 } else {
44 let name = variant_ident.to_string();
46 let spaced = name
47 .chars()
48 .flat_map(|c| if c.is_uppercase() {
49 vec![' ', c.to_ascii_lowercase()]
50 } else {
51 vec![c]
52 })
53 .collect::<String>()
54 .trim()
55 .to_owned();
56 format!("{}s", spaced)
57 };
58
59 quote! {
61 Self::#variant_ident => #plural_form,
62 }
63 });
64
65 let expanded = quote! {
67 impl PluralDisplay for #ident {
68 fn plural(&self) -> &'static str {
69 match self {
70 #(#variants)*
71 }
72 }
73 }
74 };
75
76 expanded.into()
77}
78
79fn find_custom_plural(attrs: &[Attribute]) -> Option<String> {
81 for attr in attrs {
82 if attr.path().is_ident("plural") {
83 if let Some(Lit::Str(lit_str)) = attr.parse_args::<Lit>().ok() {
84 return Some(lit_str.value());
85 }
86 }
87 }
88 None
89}