1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
extern crate proc_macro;
use std::fmt::Write;
use proc_macro::TokenStream;
use proc_macro2::Span;
use quote::quote;
use syn::spanned::Spanned;
use syn::{Data, Ident, Variant};
#[proc_macro_derive(FromInputValue)]
pub fn from_input_value_derive(input: TokenStream) -> TokenStream {
let ast = syn::parse(input).unwrap();
derive_impl(ast)
}
fn derive_impl(ast: syn::DeriveInput) -> TokenStream {
let name = &ast.ident;
let generics = &ast.generics;
if generics.type_params().next().is_some() {
return compile_error(
generics.span(),
"The FromInputValue derive macro currently doesn't support generics",
);
}
match ast.data {
Data::Struct(s) => compile_error(
s.struct_token.span(),
"The FromInputValue derive macro only supports enums, not structs",
),
Data::Union(u) => compile_error(
u.union_token.span(),
"The FromInputValue derive macro only supports enums, not unions",
),
Data::Enum(e) => {
let variants: Vec<Variant> = e.variants.into_iter().collect();
let len = variants.len();
for v in &variants {
if v.fields.iter().next().is_some() {
return compile_error(
v.fields.span(),
"The FromInputValue derive macro doesn't support \
variants with fields",
);
}
}
let idents_strs: Vec<String> = variants
.iter()
.map(|v| {
let mut s = format!("{}", v.ident);
s.make_ascii_lowercase();
s
})
.collect();
let mut ident_strs_concat =
variants.iter().enumerate().fold(String::new(), |mut acc, (i, v)| {
if i == 0 {
write!(&mut acc, "{}", v.ident).unwrap();
} else if i < len - 1 {
write!(&mut acc, ", {}", v.ident).unwrap();
} else {
write!(&mut acc, " or {}", v.ident).unwrap();
}
acc
});
ident_strs_concat.make_ascii_lowercase();
let idents: Vec<Ident> = variants.into_iter().map(|v| v.ident).collect();
let gen = quote! {
impl parkour::FromInputValue for #name {
type Context = ();
fn from_input_value(value: &str, _: &Self::Context) -> Result<Self, parkour::Error> {
match value {
#(
v if v.eq_ignore_ascii_case(#idents_strs) => Ok(#name::#idents),
)*
v => Err(parkour::Error::unexpected_value(v, #ident_strs_concat)),
}
}
}
};
gen.into()
}
}
}
fn compile_error(span: Span, message: impl std::fmt::Display) -> TokenStream {
syn::Error::new(span, message).to_compile_error().into()
}