telety_impl/
options.rs

1use quote::{ToTokens, quote};
2use syn::{
3    Attribute, Expr, ExprLit, Ident, Lit, MetaNameValue, Path, Token, Visibility, parse::Parse,
4    parse_quote, parse2, punctuated::Punctuated, spanned::Spanned as _,
5};
6
7use crate::visitor;
8
9pub struct Options {
10    pub module_path: Path,
11    pub telety_path: Option<Path>,
12    pub macro_ident: Option<Ident>,
13    pub visibility: Option<Visibility>,
14    pub proxy: Option<Path>,
15    pub alias_traits: Option<bool>,
16}
17
18impl Options {
19    pub fn from_attrs(attrs: &[Attribute]) -> syn::Result<Self> {
20        let mut args = None;
21        for attr in attrs {
22            if attr.path().is_ident("telety") {
23                #[allow(clippy::collapsible_if, reason = "separate mutating if for clarity")]
24                if args
25                    .replace(parse2(attr.meta.require_list()?.tokens.clone())?)
26                    .is_some()
27                {
28                    return Err(syn::Error::new(
29                        attr.span(),
30                        "Only one 'telety' attribute is allowed",
31                    ));
32                }
33            }
34        }
35
36        args.ok_or_else(|| {
37            syn::Error::new(
38                attrs.first().span(),
39                "'telety' attribute not found (aliasing the attribute is not supported)",
40            )
41        })
42    }
43
44    pub fn converted_containing_path(&self) -> Path {
45        let mut containing_path = self.module_path.clone();
46        directed_visit::visit_mut(
47            &mut directed_visit::syn::direct::FullDefault,
48            &mut visitor::Crateify::new(),
49            &mut containing_path,
50        );
51
52        containing_path
53    }
54
55    pub fn telety_path(&self) -> Path {
56        self.telety_path
57            .clone()
58            .unwrap_or_else(|| parse_quote!(::telety))
59    }
60}
61
62impl Parse for Options {
63    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
64        let mut module_path: Path = input.parse()?;
65        directed_visit::visit_mut(
66            &mut directed_visit::syn::direct::FullDefault,
67            &mut visitor::Decrateify::new(),
68            &mut module_path,
69        );
70
71        let mut telety_path = None;
72        let mut macro_ident = None;
73        let mut visibility = None;
74        let mut proxy = None;
75        let mut alias_traits = None;
76
77        if let Some(_comma) = input.parse::<Option<Token![,]>>()? {
78            let named_args: Punctuated<MetaNameValue, Token![,]> =
79                Punctuated::parse_terminated(input)?;
80            for named_arg in named_args {
81                if let Some(ident) = named_arg.path.get_ident() {
82                    let Expr::Lit(ExprLit {
83                        lit: Lit::Str(value),
84                        ..
85                    }) = &named_arg.value
86                    else {
87                        return Err(syn::Error::new(
88                            named_arg.value.span(),
89                            "Expected a string literal",
90                        ));
91                    };
92
93                    if ident == "telety_path" {
94                        telety_path = Some(value.parse()?);
95                    } else if ident == "macro_ident" {
96                        macro_ident = Some(value.parse()?);
97                    } else if ident == "visibility" {
98                        visibility = Some(value.parse()?);
99                    } else if ident == "proxy" {
100                        proxy = Some(value.parse()?);
101                    } else if ident == "alias_traits" {
102                        if value.value() == "always" {
103                            alias_traits = Some(true);
104                        } else if value.value() == "never" {
105                            alias_traits = Some(false);
106                        } else {
107                            return Err(syn::Error::new(
108                                value.span(),
109                                "Expected \"always\" or \"never\"",
110                            ));
111                        }
112                    } else {
113                        return Err(syn::Error::new(
114                            named_arg.path.span(),
115                            "Invalid parameter name",
116                        ));
117                    }
118                } else {
119                    return Err(syn::Error::new(
120                        named_arg.path.span(),
121                        "Expected a parameter name",
122                    ));
123                }
124            }
125        }
126
127        Ok(Self {
128            module_path,
129            telety_path,
130            macro_ident,
131            visibility,
132            proxy,
133            alias_traits,
134        })
135    }
136}
137
138impl ToTokens for Options {
139    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
140        let Self {
141            module_path,
142            telety_path,
143            macro_ident,
144            visibility,
145            proxy,
146            alias_traits,
147        } = self;
148
149        // Convert to string literals
150        let telety_path = telety_path
151            .as_ref()
152            .map(ToTokens::to_token_stream)
153            .as_ref()
154            .map(ToString::to_string)
155            .into_iter();
156        let macro_ident = macro_ident
157            .as_ref()
158            .map(ToTokens::to_token_stream)
159            .as_ref()
160            .map(ToString::to_string)
161            .into_iter();
162        let visibility = visibility
163            .as_ref()
164            .map(ToTokens::to_token_stream)
165            .as_ref()
166            .map(ToString::to_string)
167            .into_iter();
168        let proxy = proxy
169            .as_ref()
170            .map(ToTokens::to_token_stream)
171            .as_ref()
172            .map(ToString::to_string)
173            .into_iter();
174        let alias_traits = alias_traits
175            .as_ref()
176            .map(|always| if *always { "always" } else { "never" })
177            .into_iter();
178
179        quote!(
180            #module_path
181            #(, telety_path = #telety_path)*
182            #(, macro_ident = #macro_ident)*
183            #(, visibility = #visibility)*
184            #(, proxy = #proxy)*
185            #(, alias_traits = #alias_traits)*
186        )
187        .to_tokens(tokens);
188    }
189}