from_str_sequential_derive/
lib.rs1use proc_macro::{self, TokenStream};
2use quote::quote;
3use syn::{Data, DataEnum, DeriveInput, Fields, Ident, Type};
4
5#[proc_macro_derive(FromStrSequential)]
6pub fn from_str_sequential_derive(input: TokenStream) -> TokenStream {
7 let DeriveInput { ident, data, .. } = syn::parse(input).unwrap();
10
11 impl_from_str_sequential(ident, data)
13}
14
15fn impl_from_str_sequential(ident: Ident, data: Data) -> TokenStream {
16 let Data::Enum(data_enum) = data else {panic!("Only enums are supported")};
17 let fields = fields_ident(data_enum);
18 let ([first_field], other_fields) = fields.split_at(1) else {panic!("Enum need to have at least one variant")};
19 let sequenced_fom_str: proc_macro2::TokenStream = format!(
20 "{}{}",
21 first_field.to_token_stream(),
22 other_fields
23 .iter()
24 .map(|f| format!(".or_else(|_| {})", f.to_token_stream()))
25 .collect::<String>()
26 )
27 .parse()
28 .unwrap();
29 let gen = quote! {
30 impl FromStrSequential for #ident {
31 type Err = String;
32 fn from_str_sequential(__str: &str) -> Result<Self, Self::Err> {
33 #sequenced_fom_str
34 }
35 }
36 };
37 gen.into()
38}
39
40enum CrateVariant {
42 Unit(Ident),
43 Unnamed { ident: Ident, ty: Type },
44}
45
46impl CrateVariant {
47 fn to_token_stream(&self) -> TokenStream {
48 match self {
49 Self::Unit(ident) => {
50 quote! {
51 if __str.to_ascii_lowercase() == stringify!(#ident).to_ascii_lowercase() {
52 Ok(Self::#ident)
53 } else {
54 Err("String not matching variant name (case-insensitive)".to_string())
55 }
56 }
57 .into()
58 }
59 Self::Unnamed { ident, ty } => quote! {
60 <#ty as ::std::str::FromStr>::from_str(__str).map(Self::#ident).map_err(|e| e.to_string())
61 }
62 .into(),
63 }
64 }
65}
66
67fn fields_ident(data_enum: DataEnum) -> Vec<CrateVariant> {
68 let mut fields_ident = Vec::new();
69 for variant in data_enum.variants {
70 match variant.fields {
71 Fields::Unnamed(ref unnamed_fields) if unnamed_fields.unnamed.len() == 1 => {
72 fields_ident.push(CrateVariant::Unnamed {
73 ident: variant.ident,
74 ty: unnamed_fields
75 .unnamed
76 .first()
77 .expect("Unnamed fields should have at least one member")
78 .ty
79 .clone(),
80 })
81 }
82 Fields::Unit => fields_ident.push(CrateVariant::Unit(variant.ident)),
83 _ => panic!(""),
84 }
85 }
86 fields_ident
87}