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