1use std::fmt::{Display, Formatter, Write};
2use std::ops::Index;
3use std::slice::SliceIndex;
4
5use syn::{
6 AttrStyle, Attribute, AttributeArgs, Lit
7};
8
9use crate::{NameValue, NameValueAttribute, NameValueError};
10use crate::visitor::{AttributeArgsVisitor, join_path_to_string};
11
12#[derive(Debug, Clone, Eq, PartialEq, Hash)]
16pub struct MacroAttribute {
17 pub(crate) path: String,
18 pub(crate) args: Vec<MetaItem>,
19 pub(crate) style: Option<AttrStyle>,
20}
21
22impl MacroAttribute {
23 pub fn new(attribute: Attribute) -> syn::Result<Self> {
25 let path = join_path_to_string(&attribute.path);
26 let attr_args = get_attribute_args(&attribute)?;
27 let args = AttributeArgsVisitor::visit(attr_args);
28 let style = Some(attribute.style);
29
30 Ok(MacroAttribute { path, args, style })
31 }
32
33 pub fn from_attribute_args(
35 path: &str,
36 attribute_args: AttributeArgs,
37 style: AttrStyle,
38 ) -> Self {
39 let args = AttributeArgsVisitor::visit(attribute_args);
40 MacroAttribute {
41 path: path.to_string(),
42 args,
43 style: Some(style),
44 }
45 }
46
47 pub fn path(&self) -> &str {
51 self.path.as_str()
52 }
53
54 pub fn style(&self) -> Option<&AttrStyle> {
56 self.style.as_ref()
57 }
58
59 pub fn args(&self) -> &[MetaItem] {
63 self.args.as_slice()
64 }
65
66 pub fn len(&self) -> usize {
68 self.args.len()
69 }
70
71 pub fn is_empty(&self) -> bool {
73 self.args.is_empty()
74 }
75
76 pub fn get(&self, index: usize) -> Option<&MetaItem> {
78 self.args.get(index)
79 }
80
81 pub fn iter(&self) -> impl Iterator<Item = &MetaItem> {
83 self.args.iter()
84 }
85
86 pub fn into_name_values(self) -> Result<NameValueAttribute, NameValueError> {
88 NameValueAttribute::new(self.path.as_str(), self.args, self.style.unwrap())
89 }
90
91 pub fn into_inner(self) -> Vec<MetaItem> {
93 self.args
94 }
95}
96
97impl<I: SliceIndex<[MetaItem]>> Index<I> for MacroAttribute {
98 type Output = I::Output;
99
100 fn index(&self, index: I) -> &Self::Output {
101 self.args.index(index)
102 }
103}
104
105impl<'a> IntoIterator for &'a MacroAttribute {
106 type Item = &'a MetaItem;
107 type IntoIter = std::slice::Iter<'a, MetaItem>;
108
109 fn into_iter(self) -> Self::IntoIter {
110 self.args.iter()
111 }
112}
113
114impl IntoIterator for MacroAttribute {
115 type Item = MetaItem;
116 type IntoIter = std::vec::IntoIter<MetaItem>;
117
118 fn into_iter(self) -> Self::IntoIter {
119 self.args.into_iter()
120 }
121}
122
123impl Display for MacroAttribute {
124 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
125 write!(
126 f,
127 "{}",
128 attribute_to_string(self.style.as_ref(), self.path(), self.args.as_slice())
129 )
130 }
131}
132
133#[derive(Debug, Clone, Eq, PartialEq, Hash)]
135pub enum MetaItem {
136 Path(String),
138 Literal(Lit),
140 NameValue(NameValue),
142 Nested(MacroAttribute),
144}
145
146impl MetaItem {
147 pub fn is_path(&self) -> bool {
149 matches!(self, MetaItem::Path(_))
150 }
151
152 pub fn is_literal(&self) -> bool {
154 matches!(self, MetaItem::Literal(_))
155 }
156
157 pub fn is_name_value(&self) -> bool {
159 matches!(self, MetaItem::NameValue(_))
160 }
161
162 pub fn is_nested(&self) -> bool {
164 matches!(self, MetaItem::Nested(_))
165 }
166
167 pub fn into_path(self) -> Option<String> {
169 match self {
170 MetaItem::Path(x) => Some(x),
171 _ => None,
172 }
173 }
174
175 pub fn into_literal(self) -> Option<Lit> {
177 match self {
178 MetaItem::Literal(x) => Some(x),
179 _ => None,
180 }
181 }
182
183 pub fn into_name_value(self) -> Option<NameValue> {
185 match self {
186 MetaItem::NameValue(x) => Some(x),
187 _ => None,
188 }
189 }
190
191 pub fn into_nested(self) -> Option<MacroAttribute> {
193 match self {
194 MetaItem::Nested(x) => Some(x),
195 _ => None,
196 }
197 }
198
199 pub fn as_path(&self) -> Option<&str> {
201 match self {
202 MetaItem::Path(x) => Some(x.as_str()),
203 _ => None,
204 }
205 }
206
207 pub fn as_literal(&self) -> Option<&Lit> {
209 match self {
210 MetaItem::Literal(x) => Some(x),
211 _ => None,
212 }
213 }
214
215 pub fn as_name_value(&self) -> Option<&NameValue> {
217 match self {
218 MetaItem::NameValue(x) => Some(x),
219 _ => None,
220 }
221 }
222
223 pub fn as_nested(&self) -> Option<&MacroAttribute> {
225 match self {
226 MetaItem::Nested(x) => Some(x),
227 _ => None,
228 }
229 }
230}
231
232impl Display for MetaItem {
233 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
234 match self {
235 MetaItem::Path(s) => write!(f, "{}", s),
236 MetaItem::Literal(lit) => display_lit(f, lit),
237 MetaItem::NameValue(name_value) => write!(f, "{}", name_value),
238 MetaItem::Nested(nested) => {
239 assert!(
240 nested.style.is_none(),
241 "Nested macro attributes cannot have an style"
242 );
243 write!(
244 f,
245 "{}",
246 attribute_to_string(None, nested.path(), nested.args.as_slice())
247 )
248 }
249 }
250 }
251}
252
253fn get_attribute_args(attr: &Attribute) -> syn::Result<AttributeArgs> {
254 let mut token_tree = attr.tokens.clone().into_iter();
256
257 if let Some(proc_macro2::TokenTree::Group(group)) = token_tree.next() {
259 use syn::parse_macro_input::ParseMacroInput;
260 let tokens = group.stream();
261 syn::parse::Parser::parse2(AttributeArgs::parse, tokens)
262 } else {
263 Ok(AttributeArgs::new())
265 }
266}
267
268#[doc(hidden)]
269pub fn lit_to_string(lit: &Lit) -> String {
270 match lit {
271 Lit::Str(s) => s.value(),
272 Lit::ByteStr(s) => unsafe { String::from_utf8_unchecked(s.value()) },
273 Lit::Byte(s) => s.value().to_string(),
274 Lit::Char(s) => s.value().to_string(),
275 Lit::Int(s) => s.to_string(),
276 Lit::Float(s) => s.to_string(),
277 Lit::Bool(s) => s.value.to_string(),
278 Lit::Verbatim(s) => s.to_string(),
279 }
280}
281
282#[doc(hidden)]
283pub fn display_lit<W: Write>(f: &mut W, lit: &Lit) -> std::fmt::Result {
284 match lit {
285 Lit::Str(s) => write!(f, "\"{}\"{}", s.value(), s.suffix()),
286 Lit::ByteStr(s) => write!(
287 f,
288 "b{:?}{}",
289 unsafe { String::from_utf8_unchecked(s.value()) },
290 s.suffix()
291 ),
292 Lit::Byte(s) => write!(
293 f,
294 "b\'{}\'{}",
295 std::char::from_u32(s.value() as u32).unwrap(),
296 s.suffix()
297 ),
298 Lit::Char(s) => write!(f, "\'{}\'{}", s.value(), s.suffix()),
299 Lit::Int(s) => write!(f, "{}{}", s.base10_digits(), s.suffix()),
300 Lit::Float(s) => write!(f, "{}{}", s.base10_digits(), s.suffix()),
301 Lit::Bool(s) => write!(f, "{}", s.value),
302 Lit::Verbatim(s) => write!(f, "{}", s),
303 }
304}
305
306#[doc(hidden)]
307pub fn attribute_to_string<'a, I>(style: Option<&AttrStyle>, path: &str, meta_items: I) -> String
308where
309 I: IntoIterator<Item = &'a MetaItem>,
310{
311 let meta = meta_items
312 .into_iter()
313 .map(|s| s.to_string())
314 .collect::<Vec<String>>();
315
316 if let Some(style) = style {
317 let style = if matches!(style, AttrStyle::Outer) {
318 "#".to_owned()
319 } else {
320 "#!".to_owned()
321 };
322
323 if meta.is_empty() {
324 format!("{}[{}]", style, path)
325 } else {
326 format!("{}[{}({})]", style, path, meta.join(", "))
327 }
328 } else {
329 format!("{}({})", path, meta.join(", "))
330 }
331}
332
333#[cfg(test)]
334mod tests {
335 use proc_macro2::TokenStream;
336 use quote::*;
337 use syn::parse_quote::ParseQuote;
338
339 use super::*;
340
341 fn parse_attr(tokens: TokenStream) -> Attribute {
342 syn::parse::Parser::parse2(Attribute::parse, tokens).expect("invalid attribute")
343 }
344
345 #[test]
346 fn new_macro_attr_test() {
347 let tokens = quote! { #[person(name="Kaori", age=20, job(salary=200.0))] };
348 let attr = MacroAttribute::new(parse_attr(tokens)).unwrap();
349
350 assert_eq!(attr.path, "person".to_owned());
351 assert_eq!(attr.len(), 3);
352 assert!(attr[0].is_name_value());
353 assert!(attr[1].is_name_value());
354 assert!(attr[2].is_nested());
355 }
356
357 #[test]
358 fn path_and_nested_test() {
359 let tokens = quote! { #[attribute(path, nested())] };
360 let attr = MacroAttribute::new(parse_attr(tokens)).unwrap();
361
362 assert_eq!(attr.path, "attribute".to_owned());
363 assert_eq!(attr.len(), 2);
364 assert!(attr[0].is_path());
365 assert!(attr[1].is_nested());
366 }
367
368 #[test]
369 fn to_string_test() {
370 let tokens = quote! {
371 #[attribute(
372 path,
373 nested(name="Alan"),
374 empty(),
375 string="string",
376 byte_str= b"byte_string",
377 int=100usize,
378 float=0.5,
379 byte=b'a',
380 boolean=true,
381 character='z',
382 )]
383 };
384 let attr = MacroAttribute::new(parse_attr(tokens)).unwrap();
385
386 assert_eq!(
387 attr.to_string().as_str(),
388 "#[attribute(\
389 path, \
390 nested(name=\"Alan\"), \
391 empty(), \
392 string=\"string\", \
393 byte_str=b\"byte_string\", \
394 int=100usize, \
395 float=0.5, \
396 byte=b'a', \
397 boolean=true, \
398 character='z'\
399 )]"
400 )
401 }
402
403 #[test]
404 fn invalid_attribute_test() {
405 let tokens = quote! { #[attribute(let x = 10)] };
406 let attr = parse_attr(tokens);
407 let attribute = MacroAttribute::new(attr);
408 assert!(attribute.is_err());
409 }
410}