Skip to main content

promkit_derive/
lib.rs

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}