1mod struct_field;
5mod struct_impl;
6
7mod utils;
8
9extern crate proc_macro as pm;
10
11use crate::struct_field::{FieldFormatting, FieldMenuInit};
12use crate::struct_impl::MenuInit;
13pub(crate) use utils::*;
14
15use proc_macro2::TokenStream;
16use proc_macro_error::{abort, abort_call_site, proc_macro_error};
17use quote::quote;
18use syn::{
19 parse_macro_input, Attribute, Data, DataEnum, DataStruct, DeriveInput, Fields, FieldsNamed,
20 Ident,
21};
22
23#[cfg(feature = "parsed")]
31#[proc_macro_attribute]
32#[proc_macro_error]
33pub fn parsed(_attr: pm::TokenStream, ts: pm::TokenStream) -> pm::TokenStream {
34 let input = parse_macro_input!(ts as DeriveInput);
35 if let Data::Enum(e) = &input.data {
36 build_parsed_enum(&input, e)
37 } else {
38 abort!(
39 input,
40 "ezmenu_derive::ezmenu::parsed macro attribute only works on unit enums."
41 )
42 }
43 .into()
44}
45
46fn build_parsed_enum(input: &DeriveInput, data: &DataEnum) -> TokenStream {
47 let ident = &input.ident;
48
49 let inputs = data
50 .variants
51 .iter()
52 .map(|var| var.ident.to_string().to_lowercase());
53 let outputs = data.variants.iter().map(|var| &var.ident);
54
55 quote! {
56 #input
57 impl ::std::str::FromStr for #ident {
58 type Err = ::ezmenu::lib::MenuError;
59
60 fn from_str(s: &str) -> Result<Self, Self::Err> {
61 match s.to_lowercase().as_str() {
62 #(#inputs => Ok(Self::#outputs),)*
63 _ => Err(::ezmenu::lib::MenuError::Other(
64 Box::new(format!("unrecognized input for `{}`", s))))
66 }
67 }
68 }
69 }
70}
71
72#[cfg(feature = "derive")]
73#[proc_macro_derive(Menu, attributes(menu))]
74#[proc_macro_error]
75pub fn build_menu(ts: pm::TokenStream) -> pm::TokenStream {
76 let input = parse_macro_input!(ts as DeriveInput);
77 match input.data {
78 Data::Enum(_e) => todo!("derive on enum soon"),
79 Data::Struct(DataStruct {
80 fields: Fields::Named(fields),
81 ..
82 }) => build_struct(input.ident, input.attrs, fields),
83 _ => abort_call_site!("Menu derive supports only non-tuple structs and unit-like enums."),
84 }
85 .into()
86}
87
88fn def_init(menu_desc: MenuInit) -> TokenStream {
89 let fields = &menu_desc.fields;
90 let fields_instance = fields.clone().into_iter().map(|f| f.kind);
91 quote! {
92 pub fn from_menu_safe() -> ::ezmenu::lib::MenuResult<Self> {
93 use ::ezmenu::lib::Menu;
94 let mut menu = ::ezmenu::lib::ValueMenu::from([#(#fields),*])
95 #menu_desc;
96 Ok(Self {#(
97 #fields_instance
98 )*})
99 }
100
101 pub fn from_menu() -> Self {
102 Self::from_menu_safe()
103 .expect("An error occurred while processing menu")
104 }
105 }
106}
107
108fn build_struct(name: Ident, attrs: Vec<Attribute>, fields: FieldsNamed) -> TokenStream {
109 let struct_attr = get_meta_attr(attrs);
111 let fields = fields.named.into_iter().map(FieldMenuInit::from).collect();
113
114 let init = def_init(MenuInit::new(struct_attr, fields));
115
116 quote! {
117 impl #name {
118 #init
119 }
120 }
121}