userprompt_derive/
lib.rs

1use proc_macro::TokenStream;
2use syn::{DeriveInput, Ident};
3
4#[cfg(feature = "egui")]
5fn build_enum_variant_builder(v: &syn::Variant) -> proc_macro2::TokenStream {
6    let sident = proc_macro2::Ident::new("Self", proc_macro2::Span::call_site());
7    match &v.fields {
8        syn::Fields::Named(f) => {
9            let mut def: proc_macro2::TokenStream = proc_macro2::TokenStream::new();
10            let mut tokens: proc_macro2::TokenStream = proc_macro2::TokenStream::new();
11            for f in f.named.iter() {
12                tokens.extend([proc_macro2::TokenTree::Ident(
13                    f.ident.as_ref().unwrap().clone(),
14                )]);
15                let ftype = &f.ty;
16                let val = quote::quote!(<#ftype as core::default::Default>::default());
17                tokens.extend([proc_macro2::TokenTree::Punct(proc_macro2::Punct::new(
18                    ':',
19                    proc_macro2::Spacing::Alone,
20                ))]);
21                tokens.extend(val);
22                tokens.extend([proc_macro2::TokenTree::Punct(proc_macro2::Punct::new(
23                    ',',
24                    proc_macro2::Spacing::Alone,
25                ))]);
26            }
27            def.extend(tokens);
28
29            let mut tokens: proc_macro2::TokenStream = proc_macro2::TokenStream::new();
30            tokens.extend([proc_macro2::TokenTree::Ident(sident.clone())]);
31            tokens.extend([
32                proc_macro2::TokenTree::Punct(proc_macro2::Punct::new(
33                    ':',
34                    proc_macro2::Spacing::Joint,
35                )),
36                proc_macro2::TokenTree::Punct(proc_macro2::Punct::new(
37                    ':',
38                    proc_macro2::Spacing::Alone,
39                )),
40            ]);
41            tokens.extend([proc_macro2::TokenTree::Ident(v.ident.clone())]);
42            tokens.extend([proc_macro2::TokenTree::Group(proc_macro2::Group::new(
43                proc_macro2::Delimiter::Brace,
44                def,
45            ))]);
46            tokens
47        }
48        syn::Fields::Unnamed(f) => {
49            let mut def: proc_macro2::TokenStream = proc_macro2::TokenStream::new();
50            let mut tokens: proc_macro2::TokenStream = proc_macro2::TokenStream::new();
51            for (i, f) in f.unnamed.iter().enumerate() {
52                let ty = &f.ty;
53                tokens.extend(quote::quote!(<#ty as core::default::Default>::default()));
54                tokens.extend([proc_macro2::TokenTree::Punct(proc_macro2::Punct::new(
55                    ',',
56                    proc_macro2::Spacing::Alone,
57                ))]);
58            }
59            def.extend(tokens);
60
61            let mut tokens: proc_macro2::TokenStream = proc_macro2::TokenStream::new();
62            tokens.extend([proc_macro2::TokenTree::Ident(sident.clone())]);
63            tokens.extend([
64                proc_macro2::TokenTree::Punct(proc_macro2::Punct::new(
65                    ':',
66                    proc_macro2::Spacing::Joint,
67                )),
68                proc_macro2::TokenTree::Punct(proc_macro2::Punct::new(
69                    ':',
70                    proc_macro2::Spacing::Alone,
71                )),
72            ]);
73            tokens.extend([proc_macro2::TokenTree::Ident(v.ident.clone())]);
74            tokens.extend([proc_macro2::TokenTree::Group(proc_macro2::Group::new(
75                proc_macro2::Delimiter::Parenthesis,
76                def,
77            ))]);
78            tokens
79        }
80        syn::Fields::Unit => {
81            let mut tokens: proc_macro2::TokenStream = proc_macro2::TokenStream::new();
82            tokens.extend([proc_macro2::TokenTree::Ident(sident.clone())]);
83            tokens.extend([
84                proc_macro2::TokenTree::Punct(proc_macro2::Punct::new(
85                    ':',
86                    proc_macro2::Spacing::Joint,
87                )),
88                proc_macro2::TokenTree::Punct(proc_macro2::Punct::new(
89                    ':',
90                    proc_macro2::Spacing::Alone,
91                )),
92            ]);
93            tokens.extend([proc_macro2::TokenTree::Ident(v.ident.clone())]);
94            tokens
95        }
96    }
97}
98
99/// Builds a match pair for a given enum variant to help convert an enum to a string
100#[cfg(feature = "egui")]
101fn build_enum_variant_to_fields(v: &syn::Variant) -> (proc_macro2::TokenStream, Vec<&syn::Field>) {
102    let sident = proc_macro2::Ident::new("Self", proc_macro2::Span::call_site());
103    let mut fields = Vec::new();
104    let q: proc_macro2::TokenStream = match &v.fields {
105        syn::Fields::Named(f) => {
106            let mut def: proc_macro2::TokenStream = proc_macro2::TokenStream::new();
107            let mut tokens: proc_macro2::TokenStream = proc_macro2::TokenStream::new();
108            for f in f.named.iter() {
109                tokens.extend([proc_macro2::TokenTree::Ident(
110                    f.ident.as_ref().unwrap().clone(),
111                )]);
112                tokens.extend([proc_macro2::TokenTree::Punct(proc_macro2::Punct::new(
113                    ',',
114                    proc_macro2::Spacing::Alone,
115                ))]);
116                fields.push(f);
117            }
118            def.extend(tokens);
119
120            let mut tokens: proc_macro2::TokenStream = proc_macro2::TokenStream::new();
121            tokens.extend([proc_macro2::TokenTree::Ident(sident.clone())]);
122            tokens.extend([
123                proc_macro2::TokenTree::Punct(proc_macro2::Punct::new(
124                    ':',
125                    proc_macro2::Spacing::Joint,
126                )),
127                proc_macro2::TokenTree::Punct(proc_macro2::Punct::new(
128                    ':',
129                    proc_macro2::Spacing::Alone,
130                )),
131            ]);
132            tokens.extend([proc_macro2::TokenTree::Ident(v.ident.clone())]);
133            tokens.extend([proc_macro2::TokenTree::Group(proc_macro2::Group::new(
134                proc_macro2::Delimiter::Brace,
135                def,
136            ))]);
137            tokens
138        }
139        syn::Fields::Unnamed(f) => {
140            let mut tokens: proc_macro2::TokenStream = proc_macro2::TokenStream::new();
141            tokens.extend([proc_macro2::TokenTree::Ident(sident.clone())]);
142            tokens.extend([
143                proc_macro2::TokenTree::Punct(proc_macro2::Punct::new(
144                    ':',
145                    proc_macro2::Spacing::Joint,
146                )),
147                proc_macro2::TokenTree::Punct(proc_macro2::Punct::new(
148                    ':',
149                    proc_macro2::Spacing::Alone,
150                )),
151            ]);
152            tokens.extend([proc_macro2::TokenTree::Ident(v.ident.clone())]);
153            let mut tokens2: proc_macro2::TokenStream = proc_macro2::TokenStream::new();
154            for (i, f) in f.unnamed.iter().enumerate() {
155                let varname = quote::format_ident!("a_{}", i);
156                tokens2.extend([proc_macro2::TokenTree::Ident(varname)]);
157                tokens2.extend([proc_macro2::TokenTree::Punct(proc_macro2::Punct::new(
158                    ',',
159                    proc_macro2::Spacing::Alone,
160                ))]);
161                fields.push(f);
162            }
163            quote::quote! {#tokens (#tokens2)}
164        }
165        syn::Fields::Unit => {
166            let mut tokens: proc_macro2::TokenStream = proc_macro2::TokenStream::new();
167            tokens.extend([proc_macro2::TokenTree::Ident(sident.clone())]);
168            tokens.extend([
169                proc_macro2::TokenTree::Punct(proc_macro2::Punct::new(
170                    ':',
171                    proc_macro2::Spacing::Joint,
172                )),
173                proc_macro2::TokenTree::Punct(proc_macro2::Punct::new(
174                    ':',
175                    proc_macro2::Spacing::Alone,
176                )),
177            ]);
178            tokens.extend([proc_macro2::TokenTree::Ident(v.ident.clone())]);
179            tokens
180        }
181    };
182    (q, fields)
183}
184
185/// Builds a match pair for a given enum variant to help convert an enum to a string
186#[cfg(feature = "egui")]
187fn build_enum_variant_to_string(v: &syn::Variant) -> (proc_macro2::TokenStream, String) {
188    let sident = proc_macro2::Ident::new("Self", proc_macro2::Span::call_site());
189    let text2 = v.ident.to_string();
190    let q: proc_macro2::TokenStream = match &v.fields {
191        syn::Fields::Named(f) => {
192            let mut def: proc_macro2::TokenStream = proc_macro2::TokenStream::new();
193            let mut tokens: proc_macro2::TokenStream = proc_macro2::TokenStream::new();
194            for f in f.named.iter() {
195                tokens.extend([proc_macro2::TokenTree::Ident(
196                    f.ident.as_ref().unwrap().clone(),
197                )]);
198                tokens.extend([proc_macro2::TokenTree::Punct(proc_macro2::Punct::new(
199                    ',',
200                    proc_macro2::Spacing::Alone,
201                ))]);
202            }
203            def.extend(tokens);
204
205            let mut tokens: proc_macro2::TokenStream = proc_macro2::TokenStream::new();
206            tokens.extend([proc_macro2::TokenTree::Ident(sident.clone())]);
207            tokens.extend([
208                proc_macro2::TokenTree::Punct(proc_macro2::Punct::new(
209                    ':',
210                    proc_macro2::Spacing::Joint,
211                )),
212                proc_macro2::TokenTree::Punct(proc_macro2::Punct::new(
213                    ':',
214                    proc_macro2::Spacing::Alone,
215                )),
216            ]);
217            tokens.extend([proc_macro2::TokenTree::Ident(v.ident.clone())]);
218            tokens.extend([proc_macro2::TokenTree::Group(proc_macro2::Group::new(
219                proc_macro2::Delimiter::Brace,
220                def,
221            ))]);
222            tokens
223        }
224        syn::Fields::Unnamed(f) => {
225            let mut tokens: proc_macro2::TokenStream = proc_macro2::TokenStream::new();
226            tokens.extend([proc_macro2::TokenTree::Ident(sident.clone())]);
227            tokens.extend([
228                proc_macro2::TokenTree::Punct(proc_macro2::Punct::new(
229                    ':',
230                    proc_macro2::Spacing::Joint,
231                )),
232                proc_macro2::TokenTree::Punct(proc_macro2::Punct::new(
233                    ':',
234                    proc_macro2::Spacing::Alone,
235                )),
236            ]);
237            tokens.extend([proc_macro2::TokenTree::Ident(v.ident.clone())]);
238            let mut tokens2: proc_macro2::TokenStream = proc_macro2::TokenStream::new();
239            for (i, _f) in f.unnamed.iter().enumerate() {
240                let varname = quote::format_ident!("a_{}", i);
241                tokens2.extend([proc_macro2::TokenTree::Ident(varname)]);
242                tokens2.extend([proc_macro2::TokenTree::Punct(proc_macro2::Punct::new(
243                    ',',
244                    proc_macro2::Spacing::Alone,
245                ))]);
246            }
247            quote::quote! {#tokens (#tokens2)}
248        }
249        syn::Fields::Unit => {
250            let mut tokens: proc_macro2::TokenStream = proc_macro2::TokenStream::new();
251            tokens.extend([proc_macro2::TokenTree::Ident(sident.clone())]);
252            tokens.extend([
253                proc_macro2::TokenTree::Punct(proc_macro2::Punct::new(
254                    ':',
255                    proc_macro2::Spacing::Joint,
256                )),
257                proc_macro2::TokenTree::Punct(proc_macro2::Punct::new(
258                    ':',
259                    proc_macro2::Spacing::Alone,
260                )),
261            ]);
262            tokens.extend([proc_macro2::TokenTree::Ident(v.ident.clone())]);
263            tokens
264        }
265    };
266    (q, text2)
267}
268
269/// This macro is used to drive the EguiPrompting trait for custom types.
270/// The macro attribute PromptComment is used to give direction to the user for each field that the user enters.
271#[cfg(feature = "egui")]
272#[proc_macro_derive(EguiPrompting, attributes(PromptComment))]
273pub fn derive_egui_prompting(input: TokenStream) -> TokenStream {
274    use std::any::Any;
275
276    let input = syn::parse_macro_input!(input as DeriveInput);
277    let sident = input.ident;
278    let expanded: TokenStream;
279
280    match &input.data {
281        syn::Data::Enum(e) => {
282            let mut field_stuff: proc_macro2::TokenStream = proc_macro2::TokenStream::new();
283            let q: proc_macro2::TokenStream = quote::quote! {
284                let combobox = if let Some(name) = name {
285                    let mut s = "Select a ".to_string();
286                    s.push_str(&name);
287                    egui::ComboBox::from_label(s)
288                } else {
289                    egui::ComboBox::from_label("Select")
290                };
291            };
292            field_stuff.extend(q);
293
294            let mut user_info: proc_macro2::TokenStream = proc_macro2::TokenStream::new();
295            for v in &e.variants {
296                let a = get_comment_from_attrs(&v.attrs);
297                if let Some(a) = a {
298                    let (_q, t) = build_enum_variant_to_string(v);
299                    user_info.extend(quote::quote! {
300                        ui.label(format!("{} - {}", #t, #a));
301                    });
302                }
303            }
304            field_stuff.extend(user_info);
305
306            let mut match_stuff: proc_macro2::TokenStream = proc_macro2::TokenStream::new();
307            for v in &e.variants {
308                let (q, t) = build_enum_variant_to_string(v);
309                match_stuff.extend(quote::quote! {
310                    #q => #t,
311                });
312            }
313
314            let q_start: proc_macro2::TokenStream = quote::quote! {
315                let val = match self {
316                    #match_stuff
317                };
318            };
319            field_stuff.extend(q_start);
320
321            let mut combo_stuff: proc_macro2::TokenStream = proc_macro2::TokenStream::new();
322            for v in &e.variants {
323                let text = v.ident.to_string();
324                let assign = build_enum_variant_builder(v);
325                let t = v.type_id();
326                let q: proc_macro2::TokenStream = quote::quote! {
327                    if ui.selectable_label(false, #text).clicked() {
328                        *self = #assign;
329                    }
330                };
331                combo_stuff.extend(q);
332            }
333
334            let mut option_prompt = proc_macro2::TokenStream::new();
335            let mut checks = proc_macro2::TokenStream::new();
336            for v in &e.variants {
337                let (q, f) = build_enum_variant_to_fields(v);
338                let a = get_comment_from_attrs(&v.attrs);
339                let mut option_code: proc_macro2::TokenStream = proc_macro2::TokenStream::new();
340                let mut check_item = proc_macro2::TokenStream::new();
341                if let Some(a) = a {
342                    let q = quote::quote! {
343                        ui.label(#a);
344                    };
345                    option_code.extend(q);
346                }
347                if !f.is_empty() {
348                    for (i, f) in f.iter().enumerate() {
349                        let a = get_comment(f);
350                        if let Some(ident) = &f.ident {
351                            let varname = quote::format_ident!("{}", ident);
352                            let text = ident.to_string();
353                            let q = match a {
354                                Some(a) => quote::quote! {
355                                    let subname = format!("{}/{}", name.unwrap_or(""), #text);
356                                    #varname.build_gui(ui, Some(&subname), Some(#a))?;
357                                    ui.separator();
358                                },
359                                None => quote::quote! {
360                                    let subname = format!("{}/{}", name.unwrap_or(""), #text);
361                                    #varname.build_gui(ui, Some(&subname), None)?;
362                                    ui.separator();
363                                },
364                            };
365                            option_code.extend(q);
366
367                            let q2 = quote::quote! {
368                                let subname = format!("{}/{}", name.unwrap_or(""), #text);
369                                #varname.check(Some(&subname))?;
370                            };
371                            check_item.extend(q2);
372                        } else {
373                            let varname = quote::format_ident!("a_{}", i);
374                            let text = format!("{}", i);
375                            let q = match a {
376                                Some(a) => quote::quote! {
377                                    let subname = format!("{}/{}", name.unwrap_or(""), #text);
378                                    #varname.build_gui(ui, Some(&subname), Some(#a))?;
379                                    ui.separator();
380                                },
381                                None => quote::quote! {
382                                    let subname = format!("{}/{}", name.unwrap_or(""), #text);
383                                    #varname.build_gui(ui, Some(&subname), None)?;
384                                    ui.separator();
385                                },
386                            };
387                            option_code.extend(q);
388
389                            let q2 = quote::quote! {
390                                let subname = format!("{}/{}", name.unwrap_or(""), #text);
391                                #varname.check(Some(&subname))?;
392                            };
393                            check_item.extend(q2);
394                        }
395                    }
396                    option_prompt.extend(quote::quote! {
397                        #q => { #option_code },
398                    });
399                    checks.extend(quote::quote! {
400                        #q => { #check_item },
401                    });
402                }
403            }
404
405            expanded = quote::quote! {
406                impl userprompt::EguiPrompting for #sident {
407                    fn build_gui(&mut self, ui: &mut egui::Ui, name: Option<&str>, comment: Option<&str>) -> Result<(), String> {
408                        #field_stuff
409                        combobox.selected_text(val)
410                            .show_ui(ui, |ui| { #combo_stuff });
411                        match self {
412                            #option_prompt
413                            _ => {}
414                        }
415                        self.check(name)
416                    }
417
418                    fn check(&self, name: Option<&str>) -> Result<(), String> {
419                        match self {
420                            #checks
421                            _ => {}
422                        }
423                        Ok(())
424                    }
425                }
426            }
427            .into();
428        }
429        syn::Data::Struct(s) => {
430            let fields = &s.fields;
431            let mut field_stuff: proc_macro2::TokenStream = proc_macro2::TokenStream::new();
432            let mut checks = proc_macro2::TokenStream::new();
433
434            let q: proc_macro2::TokenStream = quote::quote! {
435                if let Some(name) = name {
436                    ui.label(name);
437                }
438            };
439            field_stuff.extend(q);
440
441            if let syn::Fields::Named(n) = fields {
442                for n in n.named.iter() {
443                    let a = get_comment(n);
444                    if let Some(ident) = &n.ident {
445                        let text = ident.to_string();
446                        let varname = quote::format_ident!("{}", ident);
447                        let q = match a {
448                            Some(a) => quote::quote! {
449                                let subname = format!("{}/{}", name.unwrap_or(""), #text);
450                                self.#varname.build_gui(ui, Some(&subname), Some(#a))?;
451                                ui.separator();
452                            },
453                            None => quote::quote! {
454                                let subname = format!("{}/{}", name.unwrap_or(""), #text);
455                                self.#varname.build_gui(ui, Some(&subname), None)?;
456                                ui.separator();
457                            },
458                        };
459                        field_stuff.extend(q);
460                        let q2 = quote::quote! {
461                            let subname = format!("{}/{}", name.unwrap_or(""), #text);
462                            self.#varname.check(Some(&subname))?;
463                        };
464                        checks.extend(q2);
465                    }
466                }
467
468                let mut q2s: proc_macro2::TokenStream = proc_macro2::TokenStream::new();
469
470                for (i, n) in n.named.iter().enumerate() {
471                    if let Some(ident) = &n.ident {
472                        let name = Ident::new(&format!("a_{}", i), proc_macro2::Span::call_site());
473                        let q2: proc_macro2::TokenStream = quote::quote! {
474                            #ident: #name,
475                        };
476                        q2s.extend(q2);
477                    }
478                }
479
480                let q: proc_macro2::TokenStream = quote::quote! {
481                    Ok(())
482                };
483                field_stuff.extend(q);
484            }
485            expanded = quote::quote! {
486                impl userprompt::EguiPrompting for #sident {
487                    fn build_gui(&mut self, ui: &mut egui::Ui, name: Option<&str>, comment: Option<&str>) -> Result<(), String> {
488                        #field_stuff
489                    }
490
491                    fn check(&self, name: Option<&str>) -> Result<(), String> {
492                        #checks
493                        Ok(())
494                    }
495                }                
496            }
497            .into();
498        }
499        _ => panic!("Unhandled object type"),
500    };
501    TokenStream::from(expanded)
502}
503
504/// Retrieves the comment attribute from the field
505fn get_comment(field: &syn::Field) -> Option<syn::Expr> {
506    get_comment_from_attrs(&field.attrs)
507}
508
509/// Retrieves the comment attribute from a list of attributes
510fn get_comment_from_attrs(attrs: &Vec<syn::Attribute>) -> Option<syn::Expr> {
511    attrs
512        .iter()
513        .filter(|p| p.path().is_ident("PromptComment"))
514        .take(1)
515        .next()
516        .map(|a| a.meta.require_name_value().unwrap().value.clone())
517}
518
519/// This macro is used to drive the Prompting trait for custom types.
520/// The macro attribute PromptComment is used to give direction to the user for each field that the user enters.
521#[proc_macro_derive(Prompting, attributes(PromptComment))]
522pub fn derive_prompting(input: TokenStream) -> TokenStream {
523    let input = syn::parse_macro_input!(input as DeriveInput);
524    let sident = input.ident;
525    let expanded: TokenStream;
526    match &input.data {
527        syn::Data::Enum(e) => {
528            let mut field_stuff: proc_macro2::TokenStream = proc_macro2::TokenStream::new();
529            let q: proc_macro2::TokenStream = quote::quote! {
530                if let Some(name) = name {
531                    println!("[{}]", name);
532                }
533            };
534            field_stuff.extend(q);
535
536            let q: proc_macro2::TokenStream = quote::quote! {
537                println!("Enter the variant type, valid options are listed below");
538            };
539            field_stuff.extend(q);
540
541            for v in &e.variants {
542                let text = v.ident.to_string();
543                let a = get_comment_from_attrs(&v.attrs);
544                let q = match a {
545                    Some(a) => quote::quote! {
546                        println!("\t{} - {}", #text, #a);
547                    },
548                    None => quote::quote! {
549                        println!("\t{}", #text);
550                    },
551                };
552                field_stuff.extend(q);
553            }
554
555            let name = Ident::new(&format!("a"), proc_macro2::Span::call_site());
556            let q: proc_macro2::TokenStream = quote::quote! {
557                let #name = <String as userprompt::Prompting>::prompt(None, None)?;
558            };
559            field_stuff.extend(q);
560
561            let mut match_stuff: proc_macro2::TokenStream = proc_macro2::TokenStream::new();
562            for v in &e.variants {
563                let text2 = v.ident.to_string();
564                let q: proc_macro2::TokenStream = match &v.fields {
565                    syn::Fields::Named(f) => {
566                        let mut def: proc_macro2::TokenStream = proc_macro2::TokenStream::new();
567                        let mut tokens: proc_macro2::TokenStream = proc_macro2::TokenStream::new();
568                        for (i, f) in f.named.iter().enumerate() {
569                            if i != 0 {
570                                tokens.extend([proc_macro2::TokenTree::Punct(
571                                    proc_macro2::Punct::new(',', proc_macro2::Spacing::Alone),
572                                )]);
573                            }
574                            let a = get_comment(f);
575                            tokens.extend([proc_macro2::TokenTree::Ident(
576                                f.ident.as_ref().unwrap().clone(),
577                            )]);
578                            let ftype = &f.ty;
579                            let text = f.ident.as_ref().unwrap().to_string();
580                            let val = match a {
581                                Some(a) => {
582                                    quote::quote!(<#ftype as userprompt::Prompting>::prompt(Some(#text), Some(#a))?)
583                                }
584                                None => {
585                                    quote::quote!(<#ftype as userprompt::Prompting>::prompt(Some(#text), None)?)
586                                }
587                            };
588                            tokens.extend([proc_macro2::TokenTree::Punct(
589                                proc_macro2::Punct::new(':', proc_macro2::Spacing::Alone),
590                            )]);
591                            tokens.extend(val);
592                        }
593                        def.extend(tokens);
594
595                        let mut tokens: proc_macro2::TokenStream = proc_macro2::TokenStream::new();
596                        tokens.extend([proc_macro2::TokenTree::Ident(sident.clone())]);
597                        tokens.extend([
598                            proc_macro2::TokenTree::Punct(proc_macro2::Punct::new(
599                                ':',
600                                proc_macro2::Spacing::Joint,
601                            )),
602                            proc_macro2::TokenTree::Punct(proc_macro2::Punct::new(
603                                ':',
604                                proc_macro2::Spacing::Alone,
605                            )),
606                        ]);
607                        tokens.extend([proc_macro2::TokenTree::Ident(v.ident.clone())]);
608                        tokens.extend([proc_macro2::TokenTree::Group(proc_macro2::Group::new(
609                            proc_macro2::Delimiter::Brace,
610                            def,
611                        ))]);
612                        quote::quote! {
613                            #text2 => { return Ok(#tokens); }
614                        }
615                    }
616                    syn::Fields::Unnamed(f) => {
617                        let mut def: proc_macro2::TokenStream = proc_macro2::TokenStream::new();
618                        let mut tokens: proc_macro2::TokenStream = proc_macro2::TokenStream::new();
619                        for (i, f) in f.unnamed.iter().enumerate() {
620                            if i != 0 {
621                                tokens.extend([proc_macro2::TokenTree::Punct(
622                                    proc_macro2::Punct::new(',', proc_macro2::Spacing::Alone),
623                                )]);
624                            }
625                            tokens.extend([proc_macro2::TokenTree::Literal(
626                                proc_macro2::Literal::usize_unsuffixed(i),
627                            )]);
628                            let a = get_comment(f);
629                            let ftype = &f.ty;
630                            let val = match a {
631                                Some(a) => {
632                                    quote::quote!(<#ftype as userprompt::Prompting>::prompt(Some(&format!("{}", #i)), Some(#a))?)
633                                }
634                                None => {
635                                    quote::quote!(<#ftype as userprompt::Prompting>::prompt(Some(&format!("{}", #i)), None)?)
636                                }
637                            };
638                            tokens.extend([proc_macro2::TokenTree::Punct(
639                                proc_macro2::Punct::new(':', proc_macro2::Spacing::Alone),
640                            )]);
641                            tokens.extend(val);
642                        }
643                        def.extend(tokens);
644
645                        let mut tokens: proc_macro2::TokenStream = proc_macro2::TokenStream::new();
646                        tokens.extend([proc_macro2::TokenTree::Ident(sident.clone())]);
647                        tokens.extend([
648                            proc_macro2::TokenTree::Punct(proc_macro2::Punct::new(
649                                ':',
650                                proc_macro2::Spacing::Joint,
651                            )),
652                            proc_macro2::TokenTree::Punct(proc_macro2::Punct::new(
653                                ':',
654                                proc_macro2::Spacing::Alone,
655                            )),
656                        ]);
657                        tokens.extend([proc_macro2::TokenTree::Ident(v.ident.clone())]);
658                        tokens.extend([proc_macro2::TokenTree::Group(proc_macro2::Group::new(
659                            proc_macro2::Delimiter::Brace,
660                            def,
661                        ))]);
662                        quote::quote! {
663                            #text2 => { return Ok(#tokens); }
664                        }
665                    }
666                    syn::Fields::Unit => {
667                        let mut tokens: proc_macro2::TokenStream = proc_macro2::TokenStream::new();
668                        tokens.extend([proc_macro2::TokenTree::Ident(sident.clone())]);
669                        tokens.extend([
670                            proc_macro2::TokenTree::Punct(proc_macro2::Punct::new(
671                                ':',
672                                proc_macro2::Spacing::Joint,
673                            )),
674                            proc_macro2::TokenTree::Punct(proc_macro2::Punct::new(
675                                ':',
676                                proc_macro2::Spacing::Alone,
677                            )),
678                        ]);
679                        tokens.extend([proc_macro2::TokenTree::Ident(v.ident.clone())]);
680                        quote::quote! {
681                            #text2 => { return Ok(#tokens); }
682                        }
683                    }
684                };
685                match_stuff.extend(q);
686            }
687
688            let match_else: proc_macro2::TokenStream =
689                quote::quote!(_ => println!("Invalid option"),);
690
691            let q_start: proc_macro2::TokenStream = quote::quote! {
692                match a.as_str() {
693                    #match_stuff
694                    #match_else
695                }
696            };
697            field_stuff.extend(q_start);
698
699            expanded = quote::quote! {
700                impl userprompt::Prompting for #sident {
701                    fn prompt(name: Option<&str>, comment: Option<&str>) -> Result<Self, userprompt::Error> {
702                        loop {
703                            #field_stuff
704                        }
705                    }
706                }
707            }
708            .into();
709        }
710        syn::Data::Struct(s) => {
711            let fields = &s.fields;
712            let mut field_stuff: proc_macro2::TokenStream = proc_macro2::TokenStream::new();
713
714            let q: proc_macro2::TokenStream = quote::quote! {
715                if let Some(name) = name {
716                    println!("[{}]", name);
717                }
718            };
719            field_stuff.extend(q);
720
721            if let syn::Fields::Named(n) = fields {
722                for (i, n) in n.named.iter().enumerate() {
723                    let ftype = &n.ty;
724                    if let Some(ident) = &n.ident {
725                        let name = Ident::new(&format!("a_{}", i), proc_macro2::Span::call_site());
726                        let text = ident.to_string();
727                        let a = get_comment_from_attrs(&n.attrs);
728                        let q: proc_macro2::TokenStream = match a {
729                            Some(a) => {
730                                quote::quote! {
731                                    let #name = <#ftype as userprompt::Prompting>::prompt(Some(#text), Some(#a))?;
732                                }
733                            }
734                            None => {
735                                quote::quote! {
736                                    let #name = <#ftype as userprompt::Prompting>::prompt(Some(#text), None)?;
737                                }
738                            }
739                        };
740                        field_stuff.extend(q);
741                    }
742                }
743
744                let mut q2s: proc_macro2::TokenStream = proc_macro2::TokenStream::new();
745
746                for (i, n) in n.named.iter().enumerate() {
747                    if let Some(ident) = &n.ident {
748                        let name = Ident::new(&format!("a_{}", i), proc_macro2::Span::call_site());
749                        let q2: proc_macro2::TokenStream = quote::quote! {
750                            #ident: #name,
751                        };
752                        q2s.extend(q2);
753                    }
754                }
755
756                let q: proc_macro2::TokenStream = quote::quote! {
757                    Ok(Self {
758                        #q2s
759                    })
760                };
761                field_stuff.extend(q);
762            }
763            expanded = quote::quote! {
764                impl userprompt::Prompting for #sident {
765                    fn prompt(name: Option<&str>, comment: Option<&str>) -> Result<Self, userprompt::Error> {
766                        #field_stuff
767                    }
768                }
769            }
770            .into();
771        }
772        _ => panic!("Unhandled object type"),
773    };
774    TokenStream::from(expanded)
775}