regex_parse_proc_macro/
lib.rs1use 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 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}