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 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}