attr_parser_fn/
lib.rs

1use std::{collections::HashSet, marker::PhantomData};
2
3use args::ParseRequiredArgs;
4use meta::ParseMeta;
5use opt_args::ParseOptionalArgs;
6use proc_macro2::{TokenStream, TokenTree};
7use quote::ToTokens;
8use rest_args::ParseRestArgs;
9use syn::{
10    buffer::Cursor,
11    parse::{ParseStream, Parser},
12    Attribute, Error, Meta, Result, Token,
13};
14
15pub mod args;
16pub mod find_attr;
17pub mod meta;
18pub mod opt_args;
19pub mod rest_args;
20
21pub trait ParseAttrTrait: Sized {
22    type Output;
23    fn parse(self, input: ParseStream) -> Result<Self::Output>;
24
25    fn parse_attr(self, input: &Attribute) -> Result<Self::Output> {
26        (|input: ParseStream| self.parse(input)).parse2(match &input.meta {
27            Meta::Path(_) => TokenStream::new(),
28            Meta::List(list) => list.tokens.clone(),
29            Meta::NameValue(meta) => {
30                return Err(Error::new_spanned(
31                    meta,
32                    "expect `path` or `list(...)`, found `key = value`",
33                ))
34            }
35        })
36    }
37
38    fn parse_concat_attrs<'r, I>(self, input: I) -> Result<Self::Output>
39    where
40        I: Iterator<Item = &'r Attribute>,
41    {
42        let parser = |input: ParseStream| self.parse(input);
43        let mut concatenated = TokenStream::new();
44
45        for attr in input {
46            let tokens = attr.meta.require_list()?.tokens.clone();
47            if tokens.is_empty() {
48                continue;
49            }
50
51            let mut trail_comma = false;
52            concatenated.extend(tokens.into_iter().map(|token| {
53                trail_comma = matches!(&token, TokenTree::Punct(p) if p.as_char() == ',');
54                token
55            }));
56
57            if !trail_comma {
58                <Token![,]>::default().to_tokens(&mut concatenated);
59            }
60        }
61
62        parser.parse2(concatenated)
63    }
64}
65
66pub struct Marker<T>(PhantomData<T>);
67
68impl<ReqArgs, OptArgs, RestArgs, Meta> ParseAttrTrait
69    for ParseArgs<Marker<ReqArgs>, Marker<OptArgs>, Marker<RestArgs>, Meta>
70where
71    ReqArgs: ParseRequiredArgs,
72    OptArgs: ParseOptionalArgs,
73    RestArgs: ParseRestArgs,
74    Meta: ParseMeta,
75{
76    type Output = ParseArgs<ReqArgs::Output, OptArgs::Output, RestArgs, Meta::Output>;
77
78    fn parse(mut self, input: ParseStream) -> Result<Self::Output> {
79        Ok(ParseArgs {
80            args: ReqArgs::parse(input)?,
81            opt_args: OptArgs::parse(input)?,
82            rest_args: RestArgs::parse(input)?,
83            meta: {
84                let mut specified_paths = HashSet::new();
85                let cursor = input.cursor();
86                syn::meta::parser(|nested| {
87                    let id = nested.path.require_ident()?.to_string();
88
89                    if specified_paths.contains(&id) {
90                        return Err(Error::new_spanned(
91                            nested.path,
92                            format!("path `{id}` has been specified",),
93                        ));
94                    }
95
96                    if !self.meta.parse(&nested)? {
97                        return Err(Error::new_spanned(
98                            nested.path,
99                            format!("attribute `{id}` is not expected, or the calling form is not compliant"),
100                        ));
101                    }
102
103                    specified_paths.insert(id);
104                    Ok(())
105                })
106                .parse2(cursor.token_stream())?;
107
108                // set input buffer to empty
109                input.step(|_| Ok(((), Cursor::empty()))).unwrap();
110
111                self.meta.finish()?
112            },
113        })
114    }
115}
116
117#[derive(Debug)]
118pub struct ParseArgs<ReqArgs, OptArgs, RestArgs, Meta> {
119    pub args: ReqArgs,
120    pub opt_args: OptArgs,
121    pub rest_args: RestArgs,
122    pub meta: Meta,
123}
124
125impl ParseArgs<Marker<()>, Marker<()>, Marker<()>, ()> {
126    pub fn new() -> Self {
127        ParseArgs {
128            args: marker(),
129            opt_args: marker(),
130            rest_args: marker(),
131            meta: (),
132        }
133    }
134}
135
136impl<ReqArgs, OptArgs, RestArgs, Meta> ParseArgs<ReqArgs, OptArgs, RestArgs, Meta> {
137    pub fn args<T: ParseRequiredArgs>(self) -> ParseArgs<Marker<T>, OptArgs, RestArgs, Meta> {
138        ParseArgs {
139            args: marker(),
140            opt_args: self.opt_args,
141            rest_args: self.rest_args,
142            meta: self.meta,
143        }
144    }
145
146    pub fn opt_args<T: ParseOptionalArgs>(self) -> ParseArgs<ReqArgs, Marker<T>, RestArgs, Meta> {
147        ParseArgs {
148            args: self.args,
149            opt_args: marker(),
150            rest_args: self.rest_args,
151            meta: self.meta,
152        }
153    }
154
155    pub fn rest_args<T: ParseRestArgs>(self) -> ParseArgs<ReqArgs, OptArgs, Marker<T>, Meta> {
156        ParseArgs {
157            args: self.args,
158            opt_args: self.opt_args,
159            rest_args: marker(),
160            meta: self.meta,
161        }
162    }
163
164    pub fn meta<T: ParseMeta>(self, meta: T) -> ParseArgs<ReqArgs, OptArgs, RestArgs, T> {
165        ParseArgs {
166            args: self.args,
167            opt_args: self.opt_args,
168            rest_args: self.rest_args,
169            meta,
170        }
171    }
172}
173
174fn with_comma(input: ParseStream) -> Result<()> {
175    if !input.is_empty() {
176        input.parse::<Token![,]>()?;
177    }
178
179    Ok(())
180}
181
182fn marker<T>() -> Marker<T> {
183    Marker(PhantomData)
184}