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