1extern crate proc_macro;
2
3use proc_macro2::TokenStream;
4use quote::{quote, ToTokens};
5use syn::{parse::Error, parse_macro_input, spanned::Spanned, DeriveInput};
6
7#[proc_macro_derive(Promkit, attributes(form))]
8pub fn promkit_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
9 let ast = parse_macro_input!(input as DeriveInput);
10 match impl_promkit_derive(&ast) {
11 Ok(token) => token.into(),
12 Err(e) => e.to_compile_error().into(),
13 }
14}
15
16mod text_editor;
17
18fn impl_promkit_derive(ast: &DeriveInput) -> Result<TokenStream, Error> {
19 let fields = match &ast.data {
20 syn::Data::Struct(s) => match &s.fields {
21 syn::Fields::Named(fields) => &fields.named,
22 syn::Fields::Unnamed(_) => {
23 return Err(Error::new(ast.span(), "Not support tuple structs"))
24 }
25 syn::Fields::Unit => return Err(Error::new(ast.span(), "Not support unit structs")),
26 },
27 syn::Data::Enum(_) => return Err(Error::new(ast.span(), "Not support enums")),
28 syn::Data::Union(_) => return Err(Error::new(ast.span(), "Not support unions")),
29 };
30
31 let mut text_editor_states = Vec::new();
32 let mut field_assignments = Vec::new();
33 let mut field_types = Vec::new();
34
35 for (idx, field) in fields.iter().enumerate() {
36 for attr in field.attrs.iter() {
37 #[allow(clippy::single_match)]
38 match attr.path().get_ident().unwrap().to_string().as_str() {
39 "form" => {
40 let state = text_editor::create_state(attr)?;
41 text_editor_states.push(state);
42
43 let field_ident = field.ident.as_ref().unwrap();
44 let idx_lit = syn::Index::from(idx);
45
46 match &field.ty {
47 syn::Type::Path(typ) => {
48 let last_segment = typ.path.segments.last().unwrap();
49 match last_segment.ident.to_string().as_str() {
50 "Option" => {
51 if let syn::PathArguments::AngleBracketed(args) =
52 &last_segment.arguments
53 {
54 if let Some(syn::GenericArgument::Type(inner_type)) =
55 args.args.first()
56 {
57 field_assignments.push(quote! {
58 self.#field_ident = results[#idx_lit].parse::<#inner_type>().ok();
59 });
60 field_types.push(quote! { Option<#inner_type> });
61 }
62 }
63 }
64 _ => {
65 let ty = &field.ty;
66 field_assignments.push(quote! {
67 self.#field_ident = results[#idx_lit].parse::<#ty>()?;
68 });
69 field_types.push(quote! { #ty });
70 }
71 }
72 }
73 ty => {
74 return Err(Error::new(
75 ty.span(),
76 format!(
77 "Support only Path for field type but got {}",
78 ty.to_token_stream(),
79 ),
80 ))
81 }
82 }
83 }
84 _ => (),
85 }
86 }
87 }
88
89 let name = &ast.ident;
90 let combined_states = quote! {
91 vec![
92 #(#text_editor_states),*
93 ]
94 };
95
96 Ok(quote! {
97 impl #name {
98 pub async fn build(&mut self) -> Result<(), Box<dyn std::error::Error>> {
99 use promkit::Prompt;
100
101 let states = #combined_states;
102 let mut form = promkit::preset::form::Form::new(states);
103 let results = form.run().await?;
104
105 #(#field_assignments)*
106
107 Ok(())
108 }
109 }
110 })
111}