enum_utils/
from_str.rs

1use std::collections::BTreeMap;
2
3use failure::format_err;
4use proc_macro2::TokenStream;
5use quote::quote;
6
7use crate::attr::{Enum, ErrorList};
8use enum_utils_from_str::{Case, StrMapFunc};
9
10struct FromStrImpl {
11    nocase: bool,
12    enum_name: syn::Ident,
13    variants: BTreeMap<String, syn::Ident>,
14}
15
16impl FromStrImpl {
17    pub fn parse(input: &syn::DeriveInput) -> Result<Self, ErrorList> {
18        let Enum { name, attrs: enum_attrs, variants, .. } = Enum::parse(input)?;
19
20        let mut errors = ErrorList::default();
21        let mut name_map = BTreeMap::default();
22        for (v, attrs) in variants.iter() {
23            if attrs.skip {
24                continue;
25            }
26
27            if v.fields != syn::Fields::Unit {
28                errors.push_back(format_err!("An (unskipped) variant cannot have fields"));
29            }
30
31            if let Some(name) = &attrs.rename {
32                name_map.insert(name.clone(), v.ident.clone());
33            } else if let Some(rename_rule) = &enum_attrs.rename_rule {
34                let s = v.ident.to_string();
35                name_map.insert(rename_rule.apply_to_variant(&*s), v.ident.clone());
36            } else {
37                let s = v.ident.to_string();
38                name_map.insert(s, v.ident.clone());
39            }
40
41            for alias in &attrs.aliases {
42                name_map.insert(alias.clone(), v.ident.clone());
43            }
44        }
45
46        if !errors.is_empty() {
47            return Err(errors);
48        }
49
50        Ok(FromStrImpl {
51            nocase: enum_attrs.nocase,
52            enum_name: name.clone(),
53            variants: name_map,
54        })
55    }
56}
57
58pub fn derive(ast: &syn::DeriveInput) -> Result<TokenStream, ErrorList> {
59    let FromStrImpl { nocase, enum_name, variants } = FromStrImpl::parse(ast)?;
60
61    let mut trie = StrMapFunc::new("_parse", &enum_name.to_string());
62    let case = if nocase { Case::Insensitive } else { Case::Sensitive };
63    trie.case(case);
64
65    for (alias, variant) in variants {
66        let path = quote!(#enum_name::#variant);
67        trie.entry(alias.as_str(), path);
68    }
69
70    Ok(quote!{
71        impl ::std::str::FromStr for #enum_name {
72            type Err = ();
73
74            fn from_str(s: &str) -> Result<Self, Self::Err> {
75                #trie
76                _parse(s.as_bytes()).ok_or(())
77            }
78        }
79    })
80}