tabled_derive/
parse.rs

1use proc_macro2::{Ident, Span};
2use syn::{
3    parenthesized, parse::Parse, punctuated::Punctuated, token, Attribute, LitBool, LitInt, LitStr,
4    Token,
5};
6
7pub fn parse_attributes(
8    attributes: &[Attribute],
9) -> impl Iterator<Item = syn::Result<impl Iterator<Item = TabledAttr>>> + '_ {
10    attributes
11        .iter()
12        .filter(|attr| attr.path.is_ident("tabled"))
13        .map(|attr| attr.parse_args_with(Punctuated::<TabledAttr, Token![,]>::parse_terminated))
14        .map(|result| result.map(IntoIterator::into_iter))
15}
16
17pub struct TabledAttr {
18    pub ident: Ident,
19    pub kind: TabledAttrKind,
20}
21
22impl TabledAttr {
23    pub fn new(ident: Ident, kind: TabledAttrKind) -> Self {
24        Self { ident, kind }
25    }
26}
27
28#[derive(Clone)]
29pub enum TabledAttrKind {
30    Skip(LitBool),
31    Inline(LitBool, Option<LitStr>),
32    Rename(LitStr),
33    RenameAll(LitStr),
34    DisplayWith(LitStr, bool),
35    Order(LitInt),
36}
37
38impl Parse for TabledAttr {
39    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
40        use TabledAttrKind::*;
41
42        let name: Ident = input.parse()?;
43        let name_str = name.to_string();
44
45        if input.peek(Token![=]) {
46            let assign_token = input.parse::<Token![=]>()?;
47
48            if input.peek(LitStr) {
49                let lit = input.parse::<LitStr>()?;
50
51                match name_str.as_str() {
52                    "rename" => return Ok(Self::new(name, Rename(lit))),
53                    "rename_all" => return Ok(Self::new(name, RenameAll(lit))),
54                    "display_with" => return Ok(Self::new(name, DisplayWith(lit, false))),
55                    _ => {}
56                }
57            }
58
59            if input.peek(LitBool) {
60                let lit = input.parse::<LitBool>()?;
61
62                match name_str.as_str() {
63                    "skip" => return Ok(Self::new(name, Skip(lit))),
64                    "inline" => return Ok(Self::new(name, Inline(lit, None))),
65                    _ => {}
66                }
67            }
68
69            if input.peek(LitInt) {
70                let lit = input.parse::<LitInt>()?;
71
72                if let "order" = name_str.as_str() {
73                    return Ok(Self::new(name, Order(lit)));
74                }
75            }
76
77            return Err(syn::Error::new(
78                assign_token.span,
79                "expected `string literal` or `expression` after `=`",
80            ));
81        }
82
83        if input.peek(token::Paren) {
84            let nested;
85            let _paren = parenthesized!(nested in input);
86
87            if nested.peek(LitStr) {
88                let lit = nested.parse::<LitStr>()?;
89
90                match name_str.as_str() {
91                    "display_with" => {
92                        let use_self = if nested.peek(Token![,]) {
93                            let _comma = nested.parse::<Token![,]>()?;
94                            if nested.peek(syn::Ident) {
95                                let ident = nested.parse::<syn::Ident>()?;
96                                ident == "args"
97                            } else {
98                                false
99                            }
100                        } else {
101                            false
102                        };
103
104                        return Ok(Self::new(name, DisplayWith(lit, use_self)));
105                    }
106                    "inline" => {
107                        return Ok(Self::new(
108                            name,
109                            Inline(LitBool::new(true, Span::call_site()), Some(lit)),
110                        ))
111                    }
112                    _ => {}
113                }
114            }
115
116            return Err(syn::Error::new(
117                _paren.span,
118                "expected a `string literal` in parenthesis",
119            ));
120        }
121
122        match name_str.as_str() {
123            "skip" => return Ok(Self::new(name, Skip(LitBool::new(true, Span::call_site())))),
124            "inline" => {
125                return Ok(Self::new(
126                    name,
127                    Inline(LitBool::new(true, Span::call_site()), None),
128                ))
129            }
130            _ => {}
131        }
132
133        Err(syn::Error::new(
134            name.span(),
135            format!("unexpected attribute: {}", name_str),
136        ))
137    }
138}