lexopt_derive/
lib.rs

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!() // TODO: Handle this as an error
43        }
44    } else {
45        unimplemented!() // TODO: Handle this as an error
46    }
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!("{}", Self::HELP);
66                            println!("{help}"); // FIXME: This should use a generate help message
67                            ::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() // TODO: Need to actually generate an help message
85}
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}