1extern crate proc_macro;
2
3use proc_macro::TokenStream;
4use quote::quote;
5use syn::{parse_macro_input, Item, ItemEnum, DeriveInput};
6
7#[proc_macro_derive(DisplayDescription, attributes(description))]
8pub fn display_description(input: TokenStream) -> TokenStream {
9 let input = parse_macro_input!(input as DeriveInput);
11 let item: Item = input.into();
12
13 if let Item::Enum(e) = item {
14 let expanded = generate_display_impl(&e);
16
17 TokenStream::from(expanded)
19 } else {
20 panic!("Only Enums are supported for DisplayDescription!");
21 }
22}
23
24fn generate_display_impl(enum_data: &ItemEnum) -> TokenStream {
25 let name: &syn::Ident = &enum_data.ident;
26 let variants: &syn::punctuated::Punctuated<syn::Variant, syn::token::Comma> = &enum_data.variants;
27 let variant_names = variants.into_iter().map(|v| v.ident.clone());
28 let variant_descriptions = variants
29 .into_iter()
30 .map(|v| {
31 let desc_attribute = v.attrs.iter().find(|a| a.path.is_ident("description"));
32 match desc_attribute {
33 Some(a) => a.tokens.to_string().replace("=", "").replace("\"", "").trim().to_string(),
34 _ => v.ident.to_string().to_lowercase(),
35 }
36 });
37
38 (quote! {
39 impl std::fmt::Display for #name {
40 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41 let description = match self {
42 #(
43 #name::#variant_names => #variant_descriptions,
44 )*
45 };
46 write!(f, "{}", description)
47 }
48 }
49 }).into()
50}
51
52#[proc_macro_derive(FromValue)]
53pub fn from_value(input: TokenStream) -> TokenStream {
54 let input = parse_macro_input!(input as DeriveInput);
56 let item: Item = input.into();
57
58 if let Item::Enum(e) = item {
59 let expanded = generate_from_value_impl(&e);
61
62 TokenStream::from(expanded)
64 } else {
65 panic!("Only Enums are supported for DisplayDescription!");
66 }
67}
68
69fn generate_from_value_impl(enum_data: &ItemEnum) -> TokenStream {
70 let name: &syn::Ident = &enum_data.ident;
71 let variants: &syn::punctuated::Punctuated<syn::Variant, syn::token::Comma> = &enum_data.variants;
72 let variant_names = variants.into_iter().map(|v| v.ident.clone());
73 let default_variant_name = variant_names.clone().last().clone().unwrap();
74 let variant_values = variants.into_iter().map(|v| match &v.discriminant {
75 Some((_, expr)) => match expr {
76 syn::Expr::Lit(value) => match &value.lit {
77 syn::Lit::Int(i) => i.base10_parse().unwrap_or(254u8),
78 _ => 253u8,
79 },
80 _ => 252u8
81 },
82 None => 251u8,
83 });
84
85 (quote! {
86 impl std::convert::From<u8> for #name {
87 fn from(value: u8) -> Self {
88 match value {
89 #(
90 #variant_values => #name::#variant_names,
91 )*
92 _ => #name::#default_variant_name
93 }
94 }
95 }
96 }).into()
97}
98
99#[proc_macro_derive(ToParameter, attributes(name, abbrev, unit))]
100pub fn parameter_attributes(input: TokenStream) -> TokenStream {
101 let input = parse_macro_input!(input as DeriveInput);
103 let item: Item = input.into();
104
105 if let Item::Enum(e) = item {
106 let expanded = generate_parameter_attributes(&e);
108
109 TokenStream::from(expanded)
111 } else {
112 panic!("Only Enums are supported for DisplayDescription!");
113 }
114}
115
116fn generate_parameter_attributes(enum_data: &ItemEnum) -> TokenStream {
117 let name: &syn::Ident = &enum_data.ident;
118 let variants: &syn::punctuated::Punctuated<syn::Variant, syn::token::Comma> = &enum_data.variants;
119 let variant_names_first = variants.into_iter().map(|v| v.ident.clone());
120 let variant_names_second = variants.into_iter().map(|v| v.ident.clone());
121 let variant_names_third = variants.into_iter().map(|v| v.ident.clone());
122 let variant_names = variants
123 .into_iter()
124 .map(|v| {
125 let unit_attribute = v.attrs.iter().find(|a| a.path.is_ident("name"));
126 match unit_attribute {
127 Some(a) => a.tokens.to_string().replace("=", "").replace("\"", "").trim().to_string(),
128 _ => v.ident.to_string().to_lowercase(),
129 }
130 });
131 let variant_abbreviations = variants
132 .into_iter()
133 .map(|v| {
134 let abbrev_attribute = v.attrs.iter().find(|a| a.path.is_ident("abbrev"));
135 match abbrev_attribute {
136 Some(a) => a.tokens.to_string().replace("=", "").replace("\"", "").trim().to_string(),
137 _ => v.ident.to_string().to_lowercase(),
138 }
139 });
140 let variant_units = variants
141 .into_iter()
142 .map(|v| {
143 let unit_attribute = v.attrs.iter().find(|a| a.path.is_ident("unit"));
144 match unit_attribute {
145 Some(a) => a.tokens.to_string().replace("=", "").replace("\"", "").trim().to_string(),
146 _ => "".to_string(),
147 }
148 });
149
150 (quote! {
151 impl #name {
152 pub fn name(&self) -> &str {
153 match self {
154 #(
155 #name::#variant_names_first => #variant_names,
156 )*
157 }
158 }
159
160 pub fn abbrev(&self) -> &str {
161 match self {
162 #(
163 #name::#variant_names_second => #variant_abbreviations,
164 )*
165 }
166 }
167
168 pub fn unit(&self) -> &str {
169 match self {
170 #(
171 #name::#variant_names_third => #variant_units,
172 )*
173 }
174 }
175 }
176
177 impl std::convert::From<#name> for Parameter {
178 fn from(value: #name) -> Parameter {
179 Parameter {
180 name: value.name().to_string(),
181 unit: value.unit().to_string(),
182 abbrev: value.abbrev().to_string(),
183 }
184 }
185 }
186 }).into()
187}