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