strum_macros/helpers/
metadata.rs

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