1use crate::ident::GetIdent;
2#[cfg(feature = "parsing")]
3use crate::meta::{self, MetaExt};
4use crate::meta::{Meta1, MetaList1};
5use syn::{parse_quote, Attribute, Ident};
6#[cfg(feature = "parsing")]
7use syn::{punctuated::Punctuated, token::Paren, Result};
8
9impl GetIdent for Attribute {
10 fn get_ident(&self) -> Option<&Ident> {
12 self.path().get_ident()
13 }
14}
15
16#[cfg(feature = "parsing")]
18pub trait AttributeExt {
19 fn from_meta<M>(meta: M) -> Self
21 where
22 M: IntoAttribute;
23
24 fn parse_meta(&self) -> Result<Meta1>;
27
28 fn try_meta_mut<F, R>(&mut self, f: F) -> Result<R>
37 where
38 F: FnOnce(&mut Meta1) -> Result<R>;
39
40 fn promoted_list(&self) -> Result<MetaList1>;
46
47 fn try_promoted_list_mut<F, R>(&mut self, paren: Paren, f: F) -> Result<R>
55 where
56 F: FnOnce(&mut MetaList1) -> Result<R>;
57}
58
59#[cfg(feature = "parsing")]
60impl AttributeExt for Attribute {
61 fn from_meta<M>(meta: M) -> Self
62 where
63 M: IntoAttribute,
64 {
65 meta.into_attribute()
66 }
67
68 fn parse_meta(&self) -> Result<Meta1> {
69 use std::convert::TryInto;
70 self.meta.clone().try_into()
71 }
72
73 fn try_meta_mut<F, R>(&mut self, f: F) -> Result<R>
74 where
75 F: FnOnce(&mut Meta1) -> Result<R>,
76 {
77 let mut meta = self.parse_meta()?;
78 let result = f(&mut meta);
79 *self = Self::from_meta(meta);
80 result
81 }
82
83 fn promoted_list(&self) -> Result<MetaList1> {
84 match self.parse_meta()? {
85 Meta1::Path(path) => Ok(MetaList1 {
86 path,
87 paren_token: Default::default(),
88 nested: Punctuated::new(),
89 }),
90 Meta1::List(metalist) => Ok(metalist),
91 other => Err(meta::err_promote_to_list(&other)),
92 }
93 }
94
95 fn try_promoted_list_mut<F, R>(&mut self, paren: Paren, f: F) -> Result<R>
96 where
97 F: FnOnce(&mut MetaList1) -> Result<R>,
98 {
99 self.try_meta_mut(|meta| {
100 let metalist = meta.promote_to_list(paren)?;
101 f(metalist)
102 })
103 }
104}
105
106#[cfg(feature = "parsing")]
108pub trait AttributeIteratorExt {
109 fn doc(self) -> Option<String>;
112
113 }
120
121#[cfg(feature = "parsing")]
122impl<'a, I> AttributeIteratorExt for I
123where
124 I: std::iter::IntoIterator<Item = &'a Attribute>,
125{
126 fn doc(self) -> Option<String> {
127 let items: Vec<_> = self
128 .into_iter()
129 .filter_map(|attr| attr.parse_meta().ok().and_then(|m| m.doc().ok()))
130 .collect();
131 if items.is_empty() {
132 None
133 } else {
134 Some(items.join("\n"))
135 }
136 }
137
138 }
153
154#[cfg(test)]
155mod test {
156 use super::*;
157 use crate::assert_quote_eq;
158 use quote::quote;
159 use Meta1 as Meta;
160
161 #[cfg(feature = "parsing")]
162 fn test_meta_round_trip(attr: Attribute) -> Result<()> {
163 let meta = attr.parse_meta()?;
164 let created = Attribute::from_meta(meta);
165 assert_eq!(
166 quote! { #attr }.to_string(),
167 quote! { #created }.to_string()
168 );
169 Ok(())
170 }
171
172 #[cfg(feature = "parsing")]
173 #[test]
174 fn run_test_meta_round_trip() {
175 use syn::parse_quote;
176 test_meta_round_trip(parse_quote! { #[cfg(test)] }).unwrap();
177 test_meta_round_trip(parse_quote! { #[feature = "full"] }).unwrap();
178 test_meta_round_trip(parse_quote! { #[cfg(all(a,b,any(c,d)))] }).unwrap();
179 test_meta_round_trip(parse_quote! { #[a(b="1",d)] }).unwrap();
180 test_meta_round_trip(parse_quote! { #[abc::de::ef] }).unwrap();
181 }
182
183 #[cfg(feature = "parsing")]
184 #[test]
185 fn test_try_meta_mut() {
186 let mut attr: Attribute = parse_quote! { #[cfg(test)] };
187 attr.try_meta_mut(|meta| match meta {
188 Meta::List(metalist) => {
189 metalist.path = parse_quote! { newcfg };
190 Ok(())
191 }
192 _ => unreachable!(),
193 })
194 .unwrap();
195 let expected: Attribute = parse_quote! { #[newcfg(test)] };
196 assert_quote_eq!(attr, expected);
197
198 attr.try_meta_mut(|meta| match meta {
199 Meta::List(metalist) => {
200 metalist.nested.pop();
201 metalist.nested.push(parse_quote!(a));
202 metalist.nested.push(parse_quote!(b = "c"));
203 metalist.nested.push(parse_quote!("d"));
204 Ok(())
205 }
206 _ => unreachable!(),
207 })
208 .unwrap();
209 let expected: Attribute = parse_quote! { #[newcfg(a, b="c", "d")] };
210 assert_quote_eq!(attr, expected);
211 }
212
213 #[test]
214 #[cfg(feature = "parsing")]
215 fn test_promoted_list() {
216 let attr: Attribute = parse_quote! { #[derive] };
217 let list = attr.promoted_list().unwrap();
218 assert_quote_eq!(attr.path(), list.path);
219 assert!(list.nested.is_empty());
220 }
221
222 #[cfg(all(feature = "parsing", feature = "full"))]
223 #[test]
224 fn test_doc() {
225 let func: syn::ItemFn = parse_quote! {
226 #[derive]
227 #[test]
229 #[doc = "doc line 2"]
230 #[cfg]
231 fn f() {}
232 };
233 let doc = func.attrs.doc().unwrap();
234 assert_eq!(doc, "doc line 1\ndoc line 2");
235 }
236}
237
238pub trait IntoAttribute {
239 fn into_attribute(self) -> Attribute;
240}
241
242impl IntoAttribute for Meta1 {
243 fn into_attribute(self) -> Attribute {
244 parse_quote!( #[ #self ] )
245 }
246}
247
248impl IntoAttribute for MetaList1 {
249 fn into_attribute(self) -> Attribute {
250 Meta1::List(self).into_attribute()
251 }
252}