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 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 quote!(
160 #module_path,
161 #(telety_path = #telety_path,)*
162 #(macro_ident = #macro_ident,)*
163 #(visibility = #visibility,)*
164 )
165 .to_tokens(tokens);
166 }
177}