1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107
extern crate proc_macro; use proc_macro::TokenStream; use proc_macro2::Span; use quote::quote; use regex::Regex; use std::convert::identity; use syn::{ parse_macro_input, Data::Struct, DataStruct, DeriveInput, Fields, Ident, Lit, Meta, NestedMeta, }; #[proc_macro_derive(Recap, attributes(recap))] pub fn derive_recap(item: TokenStream) -> TokenStream { let item = parse_macro_input!(item as DeriveInput); let regex = extract_regex(&item).expect( r#"Unable to resolve recap regex. Make sure your structure has declared an attribute in the form: #[derive(Deserialize, Recap)] #[recap(regex ="your-pattern-here")] struct YourStruct { ... } "#, ); validate(&item, ®ex); let item_ident = &item.ident; let (impl_generics, ty_generics, where_clause) = item.generics.split_for_impl(); let impl_inner = quote! { impl #impl_generics std::str::FromStr for #item_ident #ty_generics #where_clause { type Err = recap::Error; fn from_str(s: &str) -> Result<Self, Self::Err> { recap::lazy_static! { static ref RE: recap::Regex = recap::Regex::new(#regex) .expect("Failed to compile regex"); } Ok(recap::from_captures(&RE, s)?) } } }; let injector = Ident::new( &format!("IMPL_FROMSTR_FOR_{}", item.ident.to_string()), Span::call_site(), ); let out = quote! { const #injector: () = { extern crate recap; #impl_inner }; }; out.into() } fn validate( item: &DeriveInput, regex: &str, ) { let regex = Regex::new(®ex).unwrap_or_else(|err| { panic!( "Invalid regular expression provided for `{}`\n{}", &item.ident, err ) }); let caps = regex.capture_names().filter_map(identity).count(); let fields = match &item.data { Struct(DataStruct { fields: Fields::Named(fs), .. }) => fs.named.len(), _ => panic!("Recap regex can only be applied to Structs with named fields"), }; if caps != fields { panic!( "Recap could not derive a `FromStr` impl for `{}`.\n\t\t > Expected regex with {} named capture groups to align with struct fields but found {}", item.ident, fields, caps ); } } fn extract_regex(item: &DeriveInput) -> Option<String> { item.attrs .iter() .flat_map(syn::Attribute::parse_meta) .filter_map(|x| match x { Meta::List(y) => Some(y), _ => None, }) .filter(|x| x.ident == "recap") .flat_map(|x| x.nested.into_iter()) .filter_map(|x| match x { NestedMeta::Meta(y) => Some(y), _ => None, }) .filter_map(|x| match x { Meta::NameValue(y) => Some(y), _ => None, }) .find(|x| x.ident == "regex") .and_then(|x| match x.lit { Lit::Str(y) => Some(y.value()), _ => None, }) }