telety_impl/
options.rs

1use quote::{format_ident, quote, ToTokens};
2use syn::{
3    parse::Parse, parse2, parse_quote, punctuated::Punctuated, spanned::Spanned as _,
4    visit_mut::VisitMut as _, 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}
16
17impl Options {
18    pub fn from_attrs(attrs: &[Attribute]) -> syn::Result<Self> {
19        let mut args = None;
20        for attr in attrs {
21            let mut segments_iter = attr.path().segments.iter();
22            if let (Some(attr_name), None) = (segments_iter.next(), segments_iter.next()) {
23                if attr_name.ident == "telety"
24                    && 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        visitor::Crateify::new().visit_path_mut(&mut containing_path);
47
48        containing_path
49    }
50
51    pub fn unique_ident(&self, ident: &Ident) -> Ident {
52        let mut iter = self.module_path.segments.iter();
53        let mut unique_ident = iter
54            .next()
55            .expect("Path must have at least one segment")
56            .ident
57            .clone();
58        for segment in iter {
59            let i = &segment.ident;
60            unique_ident = format_ident!("{unique_ident}_{i}");
61        }
62        format_ident!("{unique_ident}_{ident}")
63    }
64
65    pub fn telety_path(&self) -> Path {
66        self.telety_path
67            .clone()
68            .unwrap_or_else(|| parse_quote!(::telety))
69    }
70}
71
72impl Parse for Options {
73    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
74        let mut module_path: Path = input.parse()?;
75        visitor::Decrateify::new().visit_path_mut(&mut module_path);
76
77        let mut telety_path = None;
78        let mut macro_ident = None;
79        let mut visibility = None;
80
81        if let Some(_comma) = input.parse::<Option<Token![,]>>()? {
82            let named_args: Punctuated<MetaNameValue, Token![,]> =
83                Punctuated::parse_terminated(input)?;
84            for named_arg in named_args {
85                if let Some(ident) = named_arg.path.get_ident() {
86                    let Expr::Lit(ExprLit {
87                        lit: Lit::Str(value),
88                        ..
89                    }) = &named_arg.value
90                    else {
91                        return Err(syn::Error::new(
92                            named_arg.value.span(),
93                            "Expected a string literal",
94                        ));
95                    };
96
97                    if ident == "telety_path" {
98                        telety_path = Some(value.parse()?);
99                    } else if ident == "macro_ident" {
100                        macro_ident = Some(value.parse()?);
101                    } else if ident == "visibility" {
102                        visibility = Some(value.parse()?);
103                    } else {
104                        return Err(syn::Error::new(
105                            named_arg.path.span(),
106                            "Invalid parameter name",
107                        ));
108                    }
109                } else {
110                    return Err(syn::Error::new(
111                        named_arg.path.span(),
112                        "Expected a parameter name",
113                    ));
114                }
115            }
116        }
117
118        Ok(Self {
119            module_path,
120            telety_path,
121            macro_ident,
122            visibility,
123        })
124    }
125}
126
127impl ToTokens for Options {
128    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
129        let Self {
130            module_path,
131            telety_path,
132            macro_ident,
133            visibility,
134        } = self;
135
136        // Convert to string literals
137        let telety_path = telety_path
138            .as_ref()
139            .map(ToTokens::to_token_stream)
140            .as_ref()
141            .map(ToString::to_string)
142            .into_iter();
143        let macro_ident = macro_ident
144            .as_ref()
145            .map(ToTokens::to_token_stream)
146            .as_ref()
147            .map(ToString::to_string)
148            .into_iter();
149        let visibility = visibility
150            .as_ref()
151            .map(ToTokens::to_token_stream)
152            .as_ref()
153            .map(ToString::to_string)
154            .into_iter();
155
156        // module_path.to_tokens(tokens);
157        // <Token![,]>::default().to_tokens(tokens);
158
159        quote!(
160            #module_path,
161            #(telety_path = #telety_path,)*
162            #(macro_ident = #macro_ident,)*
163            #(visibility = #visibility,)*
164        )
165        .to_tokens(tokens);
166        // if let Some(telety_path) = telety_path {
167        //     quote!(telety_path = stringify!(#telety_path),).to_tokens(tokens);
168        // }
169        // if let Some(macro_ident) = macro_ident {
170        //     quote!(telety_path = stringify!(#telety_path),).to_tokens(tokens);
171        // }
172        // if let Some(visibility) = visibility {
173        //     visibility.to_tokens(tokens);
174        //     <Token![,]>::default().to_tokens(tokens);
175        // }
176    }
177}