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