1 use proc_macro2::{Span, TokenStream};
2 use syn::{
3     parenthesized,
4     parse::{Parse, ParseStream},
5     punctuated::Punctuated,
6     spanned::Spanned,
7     Attribute, DeriveInput, Ident, LitBool, LitStr, Path, Token, Variant, Visibility,
8 };
9 
10 use super::case_style::CaseStyle;
11 
12 pub mod kw {
13     use syn::custom_keyword;
14 
15     // enum metadata
16     custom_keyword!(serialize_all);
17 
18     // enum discriminant metadata
19     custom_keyword!(derive);
20     custom_keyword!(name);
21     custom_keyword!(vis);
22 
23     // variant metadata
24     custom_keyword!(message);
25     custom_keyword!(detailed_message);
26     custom_keyword!(serialize);
27     custom_keyword!(to_string);
28     custom_keyword!(disabled);
29     custom_keyword!(default);
30     custom_keyword!(props);
31     custom_keyword!(ascii_case_insensitive);
32 }
33 
34 pub enum EnumMeta {
35     SerializeAll {
36         kw: kw::serialize_all,
37         case_style: CaseStyle,
38     },
39     AsciiCaseInsensitive(kw::ascii_case_insensitive),
40 }
41 
42 impl Parse for EnumMeta {
parse(input: ParseStream) -> syn::Result<Self>43     fn parse(input: ParseStream) -> syn::Result<Self> {
44         let lookahead = input.lookahead1();
45         if lookahead.peek(kw::serialize_all) {
46             let kw = input.parse::<kw::serialize_all>()?;
47             input.parse::<Token![=]>()?;
48             let case_style = input.parse()?;
49             Ok(EnumMeta::SerializeAll { kw, case_style })
50         } else if lookahead.peek(kw::ascii_case_insensitive) {
51             let kw = input.parse()?;
52             Ok(EnumMeta::AsciiCaseInsensitive(kw))
53         } else {
54             Err(lookahead.error())
55         }
56     }
57 }
58 
59 impl Spanned for EnumMeta {
span(&self) -> Span60     fn span(&self) -> Span {
61         match self {
62             EnumMeta::SerializeAll { kw, .. } => kw.span(),
63             EnumMeta::AsciiCaseInsensitive(kw) => kw.span(),
64         }
65     }
66 }
67 
68 pub enum EnumDiscriminantsMeta {
69     Derive { kw: kw::derive, paths: Vec<Path> },
70     Name { kw: kw::name, name: Ident },
71     Vis { kw: kw::vis, vis: Visibility },
72     Other { path: Path, nested: TokenStream },
73 }
74 
75 impl Parse for EnumDiscriminantsMeta {
parse(input: ParseStream) -> syn::Result<Self>76     fn parse(input: ParseStream) -> syn::Result<Self> {
77         if input.peek(kw::derive) {
78             let kw = input.parse()?;
79             let content;
80             parenthesized!(content in input);
81             let paths = content.parse_terminated::<_, Token![,]>(Path::parse)?;
82             Ok(EnumDiscriminantsMeta::Derive {
83                 kw,
84                 paths: paths.into_iter().collect(),
85             })
86         } else if input.peek(kw::name) {
87             let kw = input.parse()?;
88             let content;
89             parenthesized!(content in input);
90             let name = content.parse()?;
91             Ok(EnumDiscriminantsMeta::Name { kw, name })
92         } else if input.peek(kw::vis) {
93             let kw = input.parse()?;
94             let content;
95             parenthesized!(content in input);
96             let vis = content.parse()?;
97             Ok(EnumDiscriminantsMeta::Vis { kw, vis })
98         } else {
99             let path = input.parse()?;
100             let content;
101             parenthesized!(content in input);
102             let nested = content.parse()?;
103             Ok(EnumDiscriminantsMeta::Other { path, nested })
104         }
105     }
106 }
107 
108 impl Spanned for EnumDiscriminantsMeta {
span(&self) -> Span109     fn span(&self) -> Span {
110         match self {
111             EnumDiscriminantsMeta::Derive { kw, .. } => kw.span,
112             EnumDiscriminantsMeta::Name { kw, .. } => kw.span,
113             EnumDiscriminantsMeta::Vis { kw, .. } => kw.span,
114             EnumDiscriminantsMeta::Other { path, .. } => path.span(),
115         }
116     }
117 }
118 
119 pub trait DeriveInputExt {
120     /// Get all the strum metadata associated with an enum.
get_metadata(&self) -> syn::Result<Vec<EnumMeta>>121     fn get_metadata(&self) -> syn::Result<Vec<EnumMeta>>;
122 
123     /// Get all the strum_discriminants metadata associated with an enum.
get_discriminants_metadata(&self) -> syn::Result<Vec<EnumDiscriminantsMeta>>124     fn get_discriminants_metadata(&self) -> syn::Result<Vec<EnumDiscriminantsMeta>>;
125 }
126 
127 impl DeriveInputExt for DeriveInput {
get_metadata(&self) -> syn::Result<Vec<EnumMeta>>128     fn get_metadata(&self) -> syn::Result<Vec<EnumMeta>> {
129         get_metadata_inner("strum", &self.attrs)
130     }
131 
get_discriminants_metadata(&self) -> syn::Result<Vec<EnumDiscriminantsMeta>>132     fn get_discriminants_metadata(&self) -> syn::Result<Vec<EnumDiscriminantsMeta>> {
133         get_metadata_inner("strum_discriminants", &self.attrs)
134     }
135 }
136 
137 pub enum VariantMeta {
138     Message {
139         kw: kw::message,
140         value: LitStr,
141     },
142     DetailedMessage {
143         kw: kw::detailed_message,
144         value: LitStr,
145     },
146     Serialize {
147         kw: kw::serialize,
148         value: LitStr,
149     },
150     ToString {
151         kw: kw::to_string,
152         value: LitStr,
153     },
154     Disabled(kw::disabled),
155     Default(kw::default),
156     AsciiCaseInsensitive {
157         kw: kw::ascii_case_insensitive,
158         value: bool,
159     },
160     Props {
161         kw: kw::props,
162         props: Vec<(LitStr, LitStr)>,
163     },
164 }
165 
166 impl Parse for VariantMeta {
parse(input: ParseStream) -> syn::Result<Self>167     fn parse(input: ParseStream) -> syn::Result<Self> {
168         let lookahead = input.lookahead1();
169         if lookahead.peek(kw::message) {
170             let kw = input.parse()?;
171             let _: Token![=] = input.parse()?;
172             let value = input.parse()?;
173             Ok(VariantMeta::Message { kw, value })
174         } else if lookahead.peek(kw::detailed_message) {
175             let kw = input.parse()?;
176             let _: Token![=] = input.parse()?;
177             let value = input.parse()?;
178             Ok(VariantMeta::DetailedMessage { kw, value })
179         } else if lookahead.peek(kw::serialize) {
180             let kw = input.parse()?;
181             let _: Token![=] = input.parse()?;
182             let value = input.parse()?;
183             Ok(VariantMeta::Serialize { kw, value })
184         } else if lookahead.peek(kw::to_string) {
185             let kw = input.parse()?;
186             let _: Token![=] = input.parse()?;
187             let value = input.parse()?;
188             Ok(VariantMeta::ToString { kw, value })
189         } else if lookahead.peek(kw::disabled) {
190             Ok(VariantMeta::Disabled(input.parse()?))
191         } else if lookahead.peek(kw::default) {
192             Ok(VariantMeta::Default(input.parse()?))
193         } else if lookahead.peek(kw::ascii_case_insensitive) {
194             let kw = input.parse()?;
195             let value = if input.peek(Token![=]) {
196                 let _: Token![=] = input.parse()?;
197                 input.parse::<LitBool>()?.value
198             } else {
199                 true
200             };
201             Ok(VariantMeta::AsciiCaseInsensitive { kw, value })
202         } else if lookahead.peek(kw::props) {
203             let kw = input.parse()?;
204             let content;
205             parenthesized!(content in input);
206             let props = content.parse_terminated::<_, Token![,]>(Prop::parse)?;
207             Ok(VariantMeta::Props {
208                 kw,
209                 props: props
210                     .into_iter()
211                     .map(|Prop(k, v)| (LitStr::new(&k.to_string(), k.span()), v))
212                     .collect(),
213             })
214         } else {
215             Err(lookahead.error())
216         }
217     }
218 }
219 
220 struct Prop(Ident, LitStr);
221 
222 impl Parse for Prop {
parse(input: ParseStream) -> syn::Result<Self>223     fn parse(input: ParseStream) -> syn::Result<Self> {
224         use syn::ext::IdentExt;
225 
226         let k = Ident::parse_any(&input)?;
227         let _: Token![=] = input.parse()?;
228         let v = input.parse()?;
229 
230         Ok(Prop(k, v))
231     }
232 }
233 
234 impl Spanned for VariantMeta {
span(&self) -> Span235     fn span(&self) -> Span {
236         match self {
237             VariantMeta::Message { kw, .. } => kw.span,
238             VariantMeta::DetailedMessage { kw, .. } => kw.span,
239             VariantMeta::Serialize { kw, .. } => kw.span,
240             VariantMeta::ToString { kw, .. } => kw.span,
241             VariantMeta::Disabled(kw) => kw.span,
242             VariantMeta::Default(kw) => kw.span,
243             VariantMeta::AsciiCaseInsensitive { kw, .. } => kw.span,
244             VariantMeta::Props { kw, .. } => kw.span,
245         }
246     }
247 }
248 
249 pub trait VariantExt {
250     /// Get all the metadata associated with an enum variant.
get_metadata(&self) -> syn::Result<Vec<VariantMeta>>251     fn get_metadata(&self) -> syn::Result<Vec<VariantMeta>>;
252 }
253 
254 impl VariantExt for Variant {
get_metadata(&self) -> syn::Result<Vec<VariantMeta>>255     fn get_metadata(&self) -> syn::Result<Vec<VariantMeta>> {
256         get_metadata_inner("strum", &self.attrs)
257     }
258 }
259 
get_metadata_inner<'a, T: Parse + Spanned>( ident: &str, it: impl IntoIterator<Item = &'a Attribute>, ) -> syn::Result<Vec<T>>260 fn get_metadata_inner<'a, T: Parse + Spanned>(
261     ident: &str,
262     it: impl IntoIterator<Item = &'a Attribute>,
263 ) -> syn::Result<Vec<T>> {
264     it.into_iter()
265         .filter(|attr| attr.path.is_ident(ident))
266         .try_fold(Vec::new(), |mut vec, attr| {
267             vec.extend(attr.parse_args_with(Punctuated::<T, Token![,]>::parse_terminated)?);
268             Ok(vec)
269         })
270 }
271