regex_parse_proc_macro/
lib.rs

1use darling::{ast::NestedMeta, Error, FromField, FromMeta};
2use proc_macro::TokenStream;
3use quote::quote;
4use syn::{Ident, Path, Type};
5
6#[derive(FromField, Debug)]
7#[darling(attributes(regex))]
8struct RegexSubArgs {
9    ident: Option<Ident>,
10    ty: Type,
11    method: Option<Ident>,
12}
13
14#[derive(FromMeta)]
15struct RegexArgs {
16    regex: String,
17}
18
19#[proc_macro_attribute]
20pub fn regex(args: TokenStream, stream: TokenStream) -> TokenStream {
21    let attr_args = match NestedMeta::parse_meta_list(args.into()) {
22        Ok(v) => v,
23        Err(e) => return TokenStream::from(Error::from(e).write_errors()),
24    };
25
26    let mut ast = syn::parse(stream).expect("macro input stream should be valid");
27
28    // Generate the arguments in the form of a struct
29    let args = match RegexArgs::from_list(attr_args.as_ref()) {
30        Ok(v) => v,
31        Err(e) => return TokenStream::from(e.write_errors()),
32    };
33
34    impl_macro(&args, &mut ast)
35}
36
37fn impl_macro(attrs: &RegexArgs, ast: &mut syn::ItemStruct) -> TokenStream {
38    let result = ast.fields.clone().into_iter().map(|x| {
39        let args = RegexSubArgs::from_field(&x).expect("field should be parsable");
40
41        let ty = args.ty.clone();
42
43        let ident = args.ident.clone();
44
45        let ident_string = args
46            .ident
47            .clone()
48            .expect("Ident should be some")
49            .to_string();
50
51        let code = match args.method {
52            Some(t) => quote! {{
53                let item = match matches.name(#ident_string) {
54                    Some(item) => {
55                        item.as_str()
56                    },
57                    None => return Err(format!("Item with name {} not found", #ident_string).into())
58                };
59                #t(item)?
60            }},
61            None => quote! {{
62                type T = #ty;
63                let item = match matches.name(#ident_string) {
64                    Some(item) => {
65                        item.as_str()
66                    },
67                    None => return Err(format!("Item with name {} not found", #ident_string).into())
68                };
69                T::parse(item)?
70            }},
71        };
72
73        quote! {
74            #ident: #code,
75        }
76    });
77
78    let ident = ast.ident.clone();
79    let regex = attrs.regex.clone();
80
81    ast.fields.iter_mut().for_each(|x| {
82        x.attrs
83            .retain(|x| x.path() != &Path::from_string("regex").expect("regex should be path"))
84    });
85
86    quote! {
87        #ast
88
89        impl RegexParse for #ident {
90            fn parse(text: &str) -> Result<Self, Box<dyn Error>> {
91                let regex = Regex::new(#regex)?;
92
93                let matches = regex.captures(text);
94
95                match matches {
96                    Some(matches) => Ok(Self{
97                        #(#result)*
98                    }),
99                    None => {
100                        Err("Failed to parse text for regex".into())
101                    }
102                }
103
104            }
105        }
106    }
107    .into()
108}