telety_impl/
options.rs

1use quote::{quote, ToTokens};
2use syn::{
3    parse::Parse, parse2, parse_quote, punctuated::Punctuated, spanned::Spanned as _,
4    Attribute, Expr, ExprLit, Ident, Lit, MetaNameValue, Path, Token,
5    Visibility,
6};
7
8use crate::visitor;
9
10pub struct Options {
11    pub module_path: Path,
12    pub telety_path: Option<Path>,
13    pub macro_ident: Option<Ident>,
14    pub visibility: Option<Visibility>,
15    pub proxy: Option<Path>,
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
76        if let Some(_comma) = input.parse::<Option<Token![,]>>()? {
77            let named_args: Punctuated<MetaNameValue, Token![,]> =
78                Punctuated::parse_terminated(input)?;
79            for named_arg in named_args {
80                if let Some(ident) = named_arg.path.get_ident() {
81                    let Expr::Lit(ExprLit {
82                        lit: Lit::Str(value),
83                        ..
84                    }) = &named_arg.value
85                    else {
86                        return Err(syn::Error::new(
87                            named_arg.value.span(),
88                            "Expected a string literal",
89                        ));
90                    };
91
92                    if ident == "telety_path" {
93                        telety_path = Some(value.parse()?);
94                    } else if ident == "macro_ident" {
95                        macro_ident = Some(value.parse()?);
96                    } else if ident == "visibility" {
97                        visibility = Some(value.parse()?);
98                    } else if ident == "proxy" {
99                        proxy = Some(value.parse()?);
100                    } else {
101                        return Err(syn::Error::new(
102                            named_arg.path.span(),
103                            "Invalid parameter name",
104                        ));
105                    }
106                } else {
107                    return Err(syn::Error::new(
108                        named_arg.path.span(),
109                        "Expected a parameter name",
110                    ));
111                }
112            }
113        }
114
115        Ok(Self {
116            module_path,
117            telety_path,
118            macro_ident,
119            visibility,
120            proxy,
121        })
122    }
123}
124
125impl ToTokens for Options {
126    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
127        let Self {
128            module_path,
129            telety_path,
130            macro_ident,
131            visibility,
132            proxy,
133        } = self;
134
135        // Convert to string literals
136        let telety_path = telety_path
137            .as_ref()
138            .map(ToTokens::to_token_stream)
139            .as_ref()
140            .map(ToString::to_string)
141            .into_iter();
142        let macro_ident = macro_ident
143            .as_ref()
144            .map(ToTokens::to_token_stream)
145            .as_ref()
146            .map(ToString::to_string)
147            .into_iter();
148        let visibility = visibility
149            .as_ref()
150            .map(ToTokens::to_token_stream)
151            .as_ref()
152            .map(ToString::to_string)
153            .into_iter();
154        let proxy = proxy
155            .as_ref()
156            .map(ToTokens::to_token_stream)
157            .as_ref()
158            .map(ToString::to_string)
159            .into_iter();
160
161        quote!(
162            #module_path
163            #(, telety_path = #telety_path)*
164            #(, macro_ident = #macro_ident)*
165            #(, visibility = #visibility)*
166            #(, proxy = #proxy)*
167        )
168        .to_tokens(tokens);
169    }
170}