from_regex_macros/
lib.rs

1use proc_macro_error::{abort, proc_macro_error};
2use quote::quote;
3use quote::ToTokens;
4use syn::spanned::Spanned;
5
6mod captures;
7mod impl_enum;
8mod impl_struct;
9
10// TODO: for unit structs/variants, don't require a named capture to
11// capture an entire string?
12
13// TODO: us from_str instead of .into in conversions?
14
15/// # Derive FromRegex
16///
17/// ## Implementation Notes
18///
19/// - Default implementations of `from_regex` will only match if the *entire string* is matched
20///
21/// ## Usage with Structs
22///
23/// ### Item Level Attributes
24///
25///
26///
27/// ## Usage with Enums
28///
29/// ### Item Level Attributes
30///
31/// - Match Mode: TODO
32///
33///
34#[proc_macro_error]
35#[proc_macro_derive(FromRegex, attributes(from_regex))]
36pub fn derive_regex(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
37    let input = syn::parse_macro_input!(input as syn::DeriveInput);
38    Item::from(&input).into_token_stream().into()
39}
40
41/// Generates the same code as [`FromRegex`], but also adds a `std::str::FromStr` implementation (with `()` error type)
42#[proc_macro_error]
43#[proc_macro_derive(FromStr, attributes(from_regex))]
44pub fn derive_str(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
45    let input = syn::parse_macro_input!(input as syn::DeriveInput);
46    let ident = input.ident.clone();
47    let item = Item::from(&input);
48
49    let stream = quote! {
50        #item
51        impl std::str::FromStr for #ident {
52            type Err = ();
53            fn from_str(s: &str) -> Result<Self, Self::Err> {
54                Self::from_regex(s).ok_or(())
55            }
56        }
57    };
58
59    stream.into()
60}
61
62const ATTRIBUTE: &str = "from_regex";
63
64enum Item<'a> {
65    Enum(impl_enum::Item<'a>),
66    Struct(impl_struct::Item<'a>),
67}
68
69impl<'a> From<&'a syn::DeriveInput> for Item<'a> {
70    fn from(input: &'a syn::DeriveInput) -> Self {
71        let syn::DeriveInput {
72            data, attrs, ident, ..
73        } = input;
74        match data {
75            syn::Data::Enum(data_enum) => Item::Enum(impl_enum::Item::new(
76                ident,
77                &attrs,
78                data_enum.variants.iter(),
79            )),
80            syn::Data::Struct(syn::DataStruct { fields, .. }) => {
81                Item::Struct(impl_struct::Item::new(ident, &attrs, &fields))
82            }
83            syn::Data::Union(syn::DataUnion { union_token, .. }) => {
84                abort!(union_token.span(), "Unsupported item type")
85            }
86        }
87    }
88}
89
90impl<'a> ToTokens for Item<'a> {
91    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
92        match self {
93            Item::Enum(item) => item.to_tokens(tokens),
94            Item::Struct(item) => item.to_tokens(tokens),
95        }
96    }
97}
98
99// Iterator representing crate specific attribtes
100struct Attributes<'a> {
101    iter: core::slice::Iter<'a, syn::Attribute>,
102    list: Option<syn::punctuated::IntoIter<syn::NestedMeta>>,
103}
104impl<'a> From<&'a [syn::Attribute]> for Attributes<'a> {
105    fn from(attrs: &'a [syn::Attribute]) -> Self {
106        Self {
107            iter: attrs.iter(),
108            list: None,
109        }
110    }
111}
112impl<'a> Iterator for Attributes<'a> {
113    type Item = syn::NestedMeta;
114    fn next(&mut self) -> Option<Self::Item> {
115        // Consume the existing list first
116        if let Some(list) = &mut self.list {
117            if let Some(nested) = list.next() {
118                return Some(nested);
119            }
120        }
121
122        loop {
123            // Then return to the base iterator
124            let next = self.iter.next()?;
125            if let syn::Meta::List(list) = next.parse_meta().expect("failed to parse attr meta") {
126                if list.path.is_ident(ATTRIBUTE) {
127                    // If we have a list of attrs, return the first meta
128                    // and hold on to the rest
129                    let mut list = list.nested.into_iter();
130                    if let Some(next) = list.next() {
131                        self.list = Some(list);
132                        return Some(next);
133                    }
134                }
135            }
136        }
137    }
138}