1extern crate proc_macro;
2use proc_macro::TokenStream;
3use quote::ToTokens;
4use syn::{parse_macro_input, Attribute, Data, DataEnum, DeriveInput, Error, Ident, LitStr, Result};
5
6#[proc_macro_derive(Description, attributes(description))]
7pub fn derive_description(input: TokenStream) -> TokenStream {
8 let input = parse_macro_input!(input as DeriveInput);
9
10 match try_expand(&input) {
11 Ok(expanded) => expanded,
12 Err(error) => error.to_compile_error().into(),
13 }
14}
15
16fn try_expand(input: &DeriveInput) -> Result<TokenStream> {
17 match &input.data {
18 Data::Enum(e) => Ok(impl_enum(&input.ident, e)?),
19 _ => Err(Error::new_spanned(input, "Description cannot be implemented on structs or unions"))
20 }
21}
22
23fn impl_enum(ident: &Ident, input: &DataEnum) -> Result<TokenStream> {
24 let mut vec = Vec::with_capacity(input.variants.len());
25 for variant in &input.variants {
26 let attr = variant.attrs.iter()
27 .find(|x| x.path().is_ident("description"))
28 .ok_or(syn::Error::new_spanned(variant, "Missing 'description' attribute"))?;
29
30 let result: Result<LitStr> = attr.parse_args();
31
32 let var = parse_args(result, attr)?;
33 let ident = &variant.ident;
34 vec.push(quote::quote! { Self::#ident => #var, });
35 }
36
37 Ok(quote::quote! {
38 impl ::description::Description for #ident {
39 fn description(&self) -> &'static str {
40 match self {
41 #(#vec)*
42 }
43 }
44 }
45 }.into())
46}
47
48fn parse_args(result: Result<LitStr>, attr: &Attribute) -> Result<proc_macro2::TokenStream> {
49
50 let format = if let Ok(v) = result.clone() {
51 v.value().contains('{')
52 } else {true};
54
55 if format {
56 #[cfg(feature = "format")]
57 {
58 let args: proc_macro2::TokenStream = attr.parse_args()?;
59 Ok(quote::quote! {
60 ::const_format::formatcp!(#args)
61 })
62 }
63
64 #[cfg(not(feature = "format"))]
65 {
66 let args: proc_macro2::TokenStream = attr.parse_args()?;
68 let str = args.into_iter()
69 .next().map(|s| s.to_string().contains('{'));
70
71 match str {
72 Some(true) => Err(Error::new_spanned(attr, "You need the 'format' feature to use format arguments")),
73 _ => Err(result.err().unwrap())
74 }
75 }
76 } else {
78 let segment = result.unwrap();
79 Ok(segment.to_token_stream())
80 }
81}
82
83#[proc_macro_derive(OptionalDescription, attributes(description))]
84pub fn derive_optional_description(input: TokenStream) -> TokenStream {
85 let input = parse_macro_input!(input as DeriveInput);
86
87 match try_expand_optional(&input) {
88 Ok(expanded) => expanded,
89 Err(error) => error.to_compile_error().into(),
90 }
91}
92
93fn try_expand_optional(input: &DeriveInput) -> Result<TokenStream> {
94 match &input.data {
95 Data::Enum(e) => Ok(impl_enum_optional(&input.ident, e)?),
96 _ => Err(Error::new_spanned(input, "Description cannot be implemented on structs or unions"))
97 }
98}
99
100fn impl_enum_optional(ident: &Ident, input: &DataEnum) -> Result<TokenStream> {
101 let mut vec = Vec::with_capacity(input.variants.len());
102
103 for variant in &input.variants {
104 let attr = variant.attrs.iter()
105 .find(|x| x.path().is_ident("description"));
106
107 let segment: Option<Result<LitStr>> = attr.map(|x| x.parse_args());
108
109 let ident = &variant.ident;
110 match segment {
111 Some(result) => {
112 let var = parse_args(result, attr.unwrap())?;
113 let ident = &variant.ident;
114 vec.push(quote::quote! { Self::#ident => Some(#var), });
115 },
116 None => {
117 vec.push(quote::quote! {
118 Self::#ident => None,
119 });
120 }
121 }
122 }
123
124 Ok(quote::quote! {
125 impl ::description::OptionalDescription for #ident {
126 fn description(&self) -> Option<&'static str> {
127 match self {
128 #(#vec)*
129 }
130 }
131 }
132 }.into())
133}