1use proc_macro2::{Ident, Span, TokenStream};
4use quote::{quote, quote_spanned, ToTokens};
5use syn::{
6 meta::ParseNestedMeta, parse::Parse, punctuated::Punctuated, spanned::Spanned, token::Comma,
7 Token, Variant,
8};
9
10pub trait ParseNestedMetaItem {
11 fn get_name(&self) -> &'static str;
12 fn get_found(&self) -> bool;
13 fn get_required(&self) -> bool;
14 fn parse_nested(&mut self, meta: &ParseNestedMeta) -> Option<syn::Result<()>>;
15}
16
17#[derive(Default)]
18pub struct NestedMetaItem<T> {
19 pub name: &'static str,
20 pub value_required: bool,
21 pub found: bool,
22 pub required: bool,
23 pub value: Option<T>,
24}
25
26impl<T: Parse + ToTokens> std::fmt::Debug for NestedMetaItem<T> {
27 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
28 f.debug_struct("NestedMetaItem")
29 .field("name", &self.name)
30 .field("required", &self.required)
31 .field("value_required", &self.value_required)
32 .field("found", &self.found)
33 .field("value", &self.value.as_ref().map(|v| quote!(#v)))
34 .finish()
35 }
36}
37
38impl<T: Parse> NestedMetaItem<T> {
39 pub const fn new(name: &'static str) -> Self {
40 Self {
41 required: false,
42 name,
43 found: false,
44 value_required: false,
45 value: None,
46 }
47 }
48 pub fn required(mut self) -> Self {
49 self.required = true;
50 self
51 }
52 pub const fn value_required(mut self) -> Self {
55 self.value_required = true;
56 self
57 }
58 pub const fn value_optional(mut self) -> Self {
59 self.value_required = false;
60 self
61 }
62 fn parse_nested_forced(&mut self, meta: &ParseNestedMeta) -> syn::Result<()> {
63 if self.value_required || meta.input.peek(Token![=]) {
64 let _eq: Token![=] = meta.input.parse()?;
65 self.value = Some(meta.input.parse()?);
66 }
67 Ok(())
68 }
69}
70impl<T: Parse> ParseNestedMetaItem for NestedMetaItem<T> {
71 fn get_name(&self) -> &'static str {
72 self.name
73 }
74 fn parse_nested(&mut self, meta: &ParseNestedMeta) -> Option<syn::Result<()>> {
75 if meta.path.is_ident(self.name) {
76 self.found = true;
77 Some(self.parse_nested_forced(meta))
78 } else {
79 None
80 }
81 }
82 fn get_found(&self) -> bool {
83 self.found
84 }
85 fn get_required(&self) -> bool {
86 self.required
87 }
88}
89
90pub fn check_meta_items(span: Span, items: &mut [&mut dyn ParseNestedMetaItem]) -> syn::Result<()> {
91 let mut err: Option<syn::Error> = None;
92 for item in &mut *items {
93 if item.get_required() && !item.get_found() {
94 let nerr = syn::Error::new(
95 span,
96 format!("attribute `{}` must be specified", item.get_name()),
97 );
98 if let Some(ref mut err) = err {
99 err.combine(nerr);
100 } else {
101 err = Some(nerr);
102 }
103 }
104 }
105 match err {
106 Some(err) => Err(err),
107 None => Ok(()),
108 }
109}
110fn parse_nested_meta_items_from_fn(
111 parse_nested_meta: impl FnOnce(
112 &mut dyn FnMut(ParseNestedMeta) -> syn::Result<()>,
113 ) -> syn::Result<()>,
114 items: &mut [&mut dyn ParseNestedMetaItem],
115) -> syn::Result<()> {
116 parse_nested_meta(&mut |meta| {
117 for item in &mut *items {
118 if let Some(res) = item.parse_nested(&meta) {
119 return res;
120 }
121 }
122 Err(meta.error(format!(
123 "unknown attribute `{}`. Possible attributes are {}",
124 meta.path.get_ident().unwrap(),
125 items
126 .iter()
127 .map(|i| format!("`{}`", i.get_name()))
128 .collect::<Vec<_>>()
129 .join(", ")
130 )))
131 })?;
132 Ok(())
133}
134
135pub fn parse_nested_meta_items_from_stream(
136 input: TokenStream,
137 items: &mut [&mut dyn ParseNestedMetaItem],
138) -> syn::Result<()> {
139 parse_nested_meta_items_from_fn(
140 |f| {
141 let p = syn::meta::parser(f);
142 syn::parse::Parser::parse(p, input.into())
143 },
144 items,
145 )?;
146 check_meta_items(Span::call_site(), items)
147}
148
149pub fn parse_nested_meta_items<'a>(
150 attrs: impl IntoIterator<Item = &'a syn::Attribute>,
151 attr_name: &str,
152 items: &mut [&mut dyn ParseNestedMetaItem],
153) -> syn::Result<Option<&'a syn::Attribute>> {
154 let attr = attrs
155 .into_iter()
156 .find(|attr| attr.path().is_ident(attr_name));
157 if let Some(attr) = attr {
158 parse_nested_meta_items_from_fn(|x| attr.parse_nested_meta(x), items)?;
159 check_meta_items(attr.span(), items)?;
160 Ok(Some(attr))
161 } else {
162 Ok(None)
163 }
164}
165
166pub fn parse_optional_nested_meta_items<'a>(
167 attrs: impl IntoIterator<Item = &'a syn::Attribute>,
168 attr_name: &str,
169 items: &mut [&mut dyn ParseNestedMetaItem],
170) -> syn::Result<Option<&'a syn::Attribute>> {
171 let attr = attrs
172 .into_iter()
173 .find(|attr| attr.path().is_ident(attr_name));
174 if let Some(attr) = attr {
175 if let syn::Meta::Path(_) = attr.meta {
176 Ok(Some(attr))
177 } else {
178 parse_nested_meta_items_from_fn(|x| attr.parse_nested_meta(x), items)?;
179 check_meta_items(attr.span(), items)?;
180 Ok(Some(attr))
181 }
182 } else {
183 Ok(None)
184 }
185}
186
187#[cfg(feature = "proc_macro_crate")]
188pub fn crate_ident_new() -> TokenStream {
189 use proc_macro_crate::{crate_name, FoundCrate};
190
191 match crate_name("glib") {
192 Ok(FoundCrate::Name(name)) => Some(name),
193 Ok(FoundCrate::Itself) => Some("glib".to_string()),
194 Err(_) => None,
195 }
196 .map(|s| {
197 let glib = Ident::new(&s, Span::call_site());
198 quote!(#glib)
199 })
200 .unwrap_or_else(|| {
201 let glib = Ident::new("glib", Span::call_site());
213 quote!(#glib)
214 })
215}
216
217#[cfg(not(feature = "proc_macro_crate"))]
218pub fn crate_ident_new() -> TokenStream {
219 let glib = Ident::new("glib", Span::call_site());
220 quote!(#glib)
221}
222
223pub fn gen_enum_from_glib(
230 enum_name: &Ident,
231 enum_variants: &Punctuated<Variant, Comma>,
232) -> TokenStream {
233 let recurse = enum_variants.iter().map(|v| {
235 let name = &v.ident;
236 quote_spanned! { v.span() =>
237 if value == #enum_name::#name as i32 {
238 return ::core::option::Option::Some(#enum_name::#name);
239 }
240 }
241 });
242 quote! {
243 #(#recurse)*
244 ::core::option::Option::None
245 }
246}
247
248#[cfg(test)]
251mod tests {
252 use syn::{parse_quote, DeriveInput};
253
254 use super::*;
255
256 fn boxed_stub() -> DeriveInput {
257 parse_quote!(
258 #[boxed_type(name = "Author")]
259 struct Author {
260 name: String,
261 }
262 )
263 }
264
265 #[test]
266 fn check_attr_found() {
267 let input = boxed_stub();
268 let found = parse_nested_meta_items(&input.attrs, "boxed_type", &mut []);
269 matches!(found, Ok(Some(_)));
270 }
271 #[test]
272 fn required_name_present() {
273 let input = boxed_stub();
274 let mut gtype_name = NestedMetaItem::<syn::LitStr>::new("name")
275 .required()
276 .value_required();
277 let _ = parse_nested_meta_items(&input.attrs, "boxed_type", &mut [&mut gtype_name]);
278 assert!(gtype_name.get_found());
279 assert_eq!(
280 gtype_name.value.map(|x| x.value()),
281 Some("Author".to_string())
282 );
283 }
284 #[test]
285 fn required_name_none() {
286 let input: DeriveInput = parse_quote!(
287 #[boxed_type(name)]
288 struct Author {
289 name: String,
290 }
291 );
292 let mut gtype_name = NestedMetaItem::<syn::LitStr>::new("name")
293 .required()
294 .value_required();
295 let found = parse_nested_meta_items(&input.attrs, "boxed_type", &mut [&mut gtype_name]);
296 assert!(found.is_err());
298 assert!(gtype_name.value.is_none());
299
300 assert!(gtype_name.get_found());
302 }
303}