1 // Take a look at the license at the top of the repository in the LICENSE file.
2 
3 use anyhow::{bail, Result};
4 use proc_macro2::{Ident, Span, TokenStream};
5 use proc_macro_crate::crate_name;
6 use quote::{quote, quote_spanned};
7 use syn::{
8     punctuated::Punctuated, spanned::Spanned, token::Comma, Attribute, DeriveInput, Lit, Meta,
9     MetaList, NestedMeta, Variant,
10 };
11 
12 // find the #[@attr_name] attribute in @attrs
find_attribute_meta(attrs: &[Attribute], attr_name: &str) -> Result<Option<MetaList>>13 pub fn find_attribute_meta(attrs: &[Attribute], attr_name: &str) -> Result<Option<MetaList>> {
14     let meta = match attrs.iter().find(|a| a.path.is_ident(attr_name)) {
15         Some(a) => a.parse_meta(),
16         _ => return Ok(None),
17     };
18     match meta? {
19         Meta::List(n) => Ok(Some(n)),
20         _ => bail!("wrong meta type"),
21     }
22 }
23 
24 // parse a single meta like: ident = "value"
parse_attribute(meta: &NestedMeta) -> Result<(String, String)>25 fn parse_attribute(meta: &NestedMeta) -> Result<(String, String)> {
26     let meta = match &meta {
27         NestedMeta::Meta(m) => m,
28         _ => bail!("wrong meta type"),
29     };
30     let meta = match meta {
31         Meta::NameValue(n) => n,
32         _ => bail!("wrong meta type"),
33     };
34     let value = match &meta.lit {
35         Lit::Str(s) => s.value(),
36         _ => bail!("wrong meta type"),
37     };
38 
39     let ident = match meta.path.get_ident() {
40         None => bail!("missing ident"),
41         Some(ident) => ident,
42     };
43 
44     Ok((ident.to_string(), value))
45 }
46 
47 #[derive(Debug)]
48 pub enum EnumAttribute {
49     TypeName(String),
50 }
51 
parse_enum_attribute(meta: &NestedMeta) -> Result<EnumAttribute>52 pub fn parse_enum_attribute(meta: &NestedMeta) -> Result<EnumAttribute> {
53     let (ident, v) = parse_attribute(meta)?;
54 
55     match ident.as_ref() {
56         "type_name" => Ok(EnumAttribute::TypeName(v)),
57         s => bail!("Unknown enum meta {}", s),
58     }
59 }
60 
find_nested_meta<'a>(meta: &'a MetaList, name: &str) -> Option<&'a NestedMeta>61 pub fn find_nested_meta<'a>(meta: &'a MetaList, name: &str) -> Option<&'a NestedMeta> {
62     meta.nested.iter().find(|n| match n {
63         NestedMeta::Meta(m) => m.path().is_ident(name),
64         _ => false,
65     })
66 }
67 
68 // Parse attribute such as:
69 // #[genum(type_name = "TestAnimalType")]
parse_type_name(input: &DeriveInput, attr_name: &str) -> Result<String>70 pub fn parse_type_name(input: &DeriveInput, attr_name: &str) -> Result<String> {
71     let meta = match find_attribute_meta(&input.attrs, attr_name)? {
72         Some(meta) => meta,
73         _ => bail!("Missing '{}' attribute", attr_name),
74     };
75 
76     let meta = match find_nested_meta(&meta, "type_name") {
77         Some(meta) => meta,
78         _ => bail!("Missing meta 'type_name'"),
79     };
80 
81     match parse_enum_attribute(meta)? {
82         EnumAttribute::TypeName(n) => Ok(n),
83     }
84 }
85 
86 #[derive(Debug)]
87 pub enum ErrorDomainAttribute {
88     Name(String),
89 }
90 
parse_error_attribute(meta: &NestedMeta) -> Result<ErrorDomainAttribute>91 pub fn parse_error_attribute(meta: &NestedMeta) -> Result<ErrorDomainAttribute> {
92     let (ident, v) = parse_attribute(meta)?;
93 
94     match ident.as_ref() {
95         "name" => Ok(ErrorDomainAttribute::Name(v)),
96         s => bail!("Unknown enum meta {}", s),
97     }
98 }
99 
100 // Parse attribute such as:
101 // #[gerror_domain(name = "MyError")]
parse_name(input: &DeriveInput, attr_name: &str) -> Result<String>102 pub fn parse_name(input: &DeriveInput, attr_name: &str) -> Result<String> {
103     let meta = match find_attribute_meta(&input.attrs, attr_name)? {
104         Some(meta) => meta,
105         _ => bail!("Missing '{}' attribute", attr_name),
106     };
107 
108     let meta = match find_nested_meta(&meta, "name") {
109         Some(meta) => meta,
110         _ => bail!("Missing meta 'name'"),
111     };
112 
113     match parse_error_attribute(meta)? {
114         ErrorDomainAttribute::Name(n) => Ok(n),
115     }
116 }
117 
118 #[derive(Debug)]
119 pub enum ItemAttribute {
120     Name(String),
121     Nick(String),
122 }
123 
parse_item_attribute(meta: &NestedMeta) -> Result<ItemAttribute>124 fn parse_item_attribute(meta: &NestedMeta) -> Result<ItemAttribute> {
125     let (ident, v) = parse_attribute(meta)?;
126 
127     match ident.as_ref() {
128         "name" => Ok(ItemAttribute::Name(v)),
129         "nick" => Ok(ItemAttribute::Nick(v)),
130         s => bail!("Unknown item meta {}", s),
131     }
132 }
133 
134 // Parse optional enum item attributes such as:
135 // #[genum(name = "My Name", nick = "my-nick")]
parse_item_attributes(attr_name: &str, attrs: &[Attribute]) -> Result<Vec<ItemAttribute>>136 pub fn parse_item_attributes(attr_name: &str, attrs: &[Attribute]) -> Result<Vec<ItemAttribute>> {
137     let meta = find_attribute_meta(attrs, attr_name)?;
138 
139     let v = match meta {
140         Some(meta) => meta
141             .nested
142             .iter()
143             .map(|m| parse_item_attribute(m))
144             .collect::<Result<Vec<_>, _>>()?,
145         None => Vec::new(),
146     };
147 
148     Ok(v)
149 }
150 
crate_ident_new() -> TokenStream151 pub fn crate_ident_new() -> TokenStream {
152     use proc_macro_crate::FoundCrate;
153 
154     match crate_name("glib") {
155         Ok(FoundCrate::Name(name)) => Some(name),
156         Ok(FoundCrate::Itself) => Some("glib".to_string()),
157         Err(_) => None,
158     }
159     .map(|s| {
160         let glib = Ident::new(&s, Span::call_site());
161         quote!(#glib)
162     })
163     .unwrap_or_else(|| {
164         // We couldn't find the glib crate (renamed or not) so let's just hope it's in scope!
165         //
166         // We will be able to have this information once this code is stable:
167         //
168         // ```
169         // let span = Span::call_site();
170         // let source = span.source_file();
171         // let file_path = source.path();
172         // ```
173         //
174         // Then we can use proc_macro to parse the file and check if glib is imported somehow.
175         let glib = Ident::new("glib", Span::call_site());
176         quote!(#glib)
177     })
178 }
179 
180 // Generate i32 to enum mapping, used to implement
181 // glib::translate::TryFromGlib<i32>, such as:
182 //
183 //   if value == Animal::Goat as i32 {
184 //       return Some(Animal::Goat);
185 //   }
gen_enum_from_glib( enum_name: &Ident, enum_variants: &Punctuated<Variant, Comma>, ) -> TokenStream186 pub fn gen_enum_from_glib(
187     enum_name: &Ident,
188     enum_variants: &Punctuated<Variant, Comma>,
189 ) -> TokenStream {
190     // FIXME: can we express this with a match()?
191     let recurse = enum_variants.iter().map(|v| {
192         let name = &v.ident;
193         quote_spanned! { v.span() =>
194             if value == #enum_name::#name as i32 {
195                 return Some(#enum_name::#name);
196             }
197         }
198     });
199     quote! {
200         #(#recurse)*
201         None
202     }
203 }
204