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