1use proc_macro::TokenStream;
2use quote::quote;
3use syn::{parse_macro_input, Data, DeriveInput, Fields, Path, Type, TypePath};
4
5#[proc_macro_derive(Parser, attributes(arg))]
6pub fn parser(input: TokenStream) -> TokenStream {
7 let input = parse_macro_input!(input as DeriveInput);
8 let help = generate_help(&input);
9 let ident = input.ident;
10
11 let mut matched_fields = Vec::new();
12 let mut fields_constructor = Vec::new();
13 let mut fields_checker = Vec::new();
14 let mut init = Vec::new();
15
16 if let Data::Struct(data_struct) = input.data {
17 if let Fields::Named(fields_named) = data_struct.fields {
18 for field in fields_named.named {
19 let name = field.ident.unwrap();
20 let matcher = name.to_string();
21 matched_fields.push(quote! {
22 Long(#matcher) => {#name = Some(parser.value()?.parse()?)}
23 });
24
25 fields_constructor.push(quote! {
26 let mut #name = None;
27 });
28
29 if !type_is_option(field.ty) {
30 fields_checker.push(quote! {
31 let #name = #name.ok_or(::lexopt::Error::MissingValue {
32 option: Some(#matcher.to_string()),
33 })?;
34 });
35 }
36
37 init.push(quote! {
38 #name,
39 })
40 }
41 } else {
42 unimplemented!() }
44 } else {
45 unimplemented!() }
47
48 quote! {
49 impl #ident {
50 const HELP: &str = #help;
51
52 pub fn parse<A>(args: A, help: &str) -> Result<Self, ::lexopt::Error>
53 where
54 A: IntoIterator,
55 A::Item: Into<::std::ffi::OsString>,
56 {
57 #(#fields_constructor)*
58
59 use ::lexopt::prelude::*;
60 let mut parser = ::lexopt::Parser::from_iter(args);
61
62 while let Some(arg) = parser.next()? {
63 match arg {
64 Long("help") | Short('h') => {
65 println!("{help}"); ::std::process::exit(0)
68 }
69 #(#matched_fields)*
70 _ => return Err(arg.unexpected()),
71 }
72 }
73
74 #(#fields_checker)*
75
76 Ok(Self { #(#init)* })
77 }
78 }
79 }
80 .into()
81}
82
83fn generate_help(input: &DeriveInput) -> String {
84 "This is a sample generated help message".to_string() }
86
87fn type_is_option(path: Type) -> bool {
88 if let Type::Path(TypePath { qself: _, path }) = path {
89 path.segments.len() == 1 && path.segments.iter().next().unwrap().ident == "Option"
90 } else {
91 false
92 }
93}