1use proc_macro2::Span;
4use quote::ToTokens;
5use syn::{
6 parse::{Parse, ParseStream},
7 punctuated::Punctuated,
8 Attribute, DeriveInput, Error, Field, Fields, Ident, LitBool, LitStr, Meta, Result, Token,
9 Type,
10};
11
12mod kw {
14 syn::custom_keyword!(file);
16 syn::custom_keyword!(resource);
17 syn::custom_keyword!(string);
18 syn::custom_keyword!(allow_template_child_without_attribute);
19
20 syn::custom_keyword!(id);
22 syn::custom_keyword!(internal);
23}
24
25pub struct Template {
27 pub source: TemplateSource,
28 pub allow_template_child_without_attribute: bool,
29}
30
31impl Parse for Template {
32 fn parse(input: ParseStream) -> Result<Self> {
33 let mut source = None;
34 let mut allow_template_child_without_attribute = false;
35
36 while !input.is_empty() {
37 let lookahead = input.lookahead1();
38 if lookahead.peek(kw::file) {
39 let keyword: kw::file = input.parse()?;
40 let _: Token![=] = input.parse()?;
41 let value: LitStr = input.parse()?;
42
43 if source.is_some() {
44 return Err(Error::new_spanned(
45 keyword,
46 "Specify only one of 'file', 'resource', or 'string'",
47 ));
48 }
49
50 source = Some(TemplateSource::File(value.value()));
51 } else if lookahead.peek(kw::resource) {
52 let keyword: kw::resource = input.parse()?;
53 let _: Token![=] = input.parse()?;
54 let value: LitStr = input.parse()?;
55
56 if source.is_some() {
57 return Err(Error::new_spanned(
58 keyword,
59 "Specify only one of 'file', 'resource', or 'string'",
60 ));
61 }
62
63 source = Some(TemplateSource::Resource(value.value()));
64 } else if lookahead.peek(kw::string) {
65 let keyword: kw::string = input.parse()?;
66 let _: Token![=] = input.parse()?;
67 let value: LitStr = input.parse()?;
68
69 if source.is_some() {
70 return Err(Error::new_spanned(
71 keyword,
72 "Specify only one of 'file', 'resource', or 'string'",
73 ));
74 }
75
76 source = Some(
77 TemplateSource::from_string_source(value.value())
78 .ok_or_else(|| Error::new_spanned(value, "Unknown language"))?,
79 );
80 } else if lookahead.peek(kw::allow_template_child_without_attribute) {
81 let keyword: kw::allow_template_child_without_attribute = input.parse()?;
82
83 if allow_template_child_without_attribute {
84 return Err(Error::new_spanned(
85 keyword,
86 "Duplicate 'allow_template_child_without_attribute'",
87 ));
88 }
89
90 allow_template_child_without_attribute = true;
91 } else {
92 return Err(lookahead.error());
93 }
94
95 if !input.is_empty() {
96 let _: Token![,] = input.parse()?;
97 }
98 }
99
100 let Some(source) = source else {
101 return Err(Error::new(
102 Span::call_site(),
103 "Invalid meta, specify one of 'file', 'resource', or 'string'",
104 ));
105 };
106
107 Ok(Template {
108 source,
109 allow_template_child_without_attribute,
110 })
111 }
112}
113
114pub enum TemplateSource {
116 File(String),
117 Resource(String),
118 Xml(String),
119 #[cfg(feature = "blueprint")]
120 Blueprint(String),
121}
122
123impl TemplateSource {
124 fn from_string_source(value: String) -> Option<Self> {
125 for c in value.chars() {
126 #[cfg(feature = "blueprint")]
127 if c.is_ascii_alphabetic() {
128 return Some(Self::Blueprint(value));
130 } else if c == '<' {
131 return Some(Self::Xml(value));
133 }
134 #[cfg(not(feature = "blueprint"))]
135 if c == '<' {
136 return Some(Self::Xml(value));
138 }
139 }
140
141 None
142 }
143}
144
145pub fn parse_template_source(input: &DeriveInput) -> Result<Template> {
146 let Some(attr) = input
147 .attrs
148 .iter()
149 .find(|attr| attr.path().is_ident("template"))
150 else {
151 return Err(Error::new(
152 Span::call_site(),
153 "Missing 'template' attribute",
154 ));
155 };
156
157 attr.parse_args::<Template>()
158}
159
160pub enum FieldAttributeArg {
162 #[allow(dead_code)]
163 Id(String, Span),
165 Internal(bool),
166}
167
168impl FieldAttributeArg {
169 fn from_template_child_meta(meta: &TemplateChildAttributeMeta) -> Self {
170 match meta {
171 TemplateChildAttributeMeta::Id { value, .. } => Self::Id(value.value(), value.span()),
172 TemplateChildAttributeMeta::Internal { value, .. } => Self::Internal(value.value()),
173 }
174 }
175}
176
177#[derive(Debug)]
179pub enum FieldAttributeType {
180 TemplateChild,
181}
182
183pub struct FieldAttribute {
185 pub ty: FieldAttributeType,
186 pub args: Vec<FieldAttributeArg>,
187}
188
189pub struct AttributedField {
191 pub ident: Ident,
192 pub ty: Type,
193 pub attr: FieldAttribute,
194}
195
196impl AttributedField {
197 pub fn id(&self) -> String {
198 let mut name = None;
199 for arg in &self.attr.args {
200 if let FieldAttributeArg::Id(value, _) = arg {
201 name = Some(value)
202 }
203 }
204 name.cloned().unwrap_or_else(|| self.ident.to_string())
205 }
206
207 #[cfg(feature = "xml_validation")]
208 pub fn id_span(&self) -> Span {
209 for arg in &self.attr.args {
210 if let FieldAttributeArg::Id(_, span) = arg {
211 return *span;
212 }
213 }
214 self.ident.span()
215 }
216}
217
218enum TemplateChildAttributeMeta {
220 Id {
221 keyword: kw::id,
222 value: LitStr,
223 },
224 Internal {
225 keyword: kw::internal,
226 value: LitBool,
227 },
228}
229
230impl Parse for TemplateChildAttributeMeta {
231 fn parse(input: ParseStream) -> Result<Self> {
232 let lookahead = input.lookahead1();
233 if lookahead.peek(kw::id) {
234 let keyword = input.parse()?;
235 let _: Token![=] = input.parse()?;
236 let value = input.parse()?;
237 Ok(Self::Id { keyword, value })
238 } else if lookahead.peek(kw::internal) {
239 let keyword = input.parse()?;
240 let _: Token![=] = input.parse()?;
241 let value = input.parse()?;
242 Ok(Self::Internal { keyword, value })
243 } else {
244 Err(lookahead.error())
245 }
246 }
247}
248
249impl ToTokens for TemplateChildAttributeMeta {
250 fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
251 match self {
252 Self::Id { keyword, .. } => keyword.to_tokens(tokens),
253 Self::Internal { keyword, .. } => keyword.to_tokens(tokens),
254 }
255 }
256}
257
258fn parse_field_attr_args(ty: FieldAttributeType, attr: &Attribute) -> Result<FieldAttribute> {
259 let mut args = Vec::new();
260
261 if matches!(ty, FieldAttributeType::TemplateChild) && !matches!(attr.meta, Meta::Path(_)) {
262 let meta_list = attr.parse_args_with(
263 Punctuated::<TemplateChildAttributeMeta, Token![,]>::parse_terminated,
264 )?;
265
266 for meta in meta_list {
267 let new_arg = FieldAttributeArg::from_template_child_meta(&meta);
268
269 if args.iter().any(|arg| {
270 std::mem::discriminant(arg) == std::mem::discriminant(&new_arg)
272 }) {
273 return Err(Error::new_spanned(
274 meta,
275 "two instances of the same attribute \
276 argument, each argument must be specified only once",
277 ));
278 }
279
280 args.push(new_arg);
281 }
282 }
283
284 Ok(FieldAttribute { ty, args })
285}
286
287fn parse_field(field: &Field) -> Result<Option<AttributedField>> {
288 let Some(ident) = &field.ident else {
289 return Err(Error::new_spanned(field, "expected identifier"));
290 };
291
292 let mut attr_field = None;
293
294 for attr in &field.attrs {
295 let ty = if attr.path().is_ident("template_child") {
296 FieldAttributeType::TemplateChild
297 } else {
298 continue;
299 };
300
301 let field_attr = parse_field_attr_args(ty, attr)?;
302
303 if attr_field.is_some() {
304 return Err(Error::new_spanned(
305 attr,
306 "multiple attributes on the same field are not supported",
307 ));
308 }
309
310 attr_field = Some(AttributedField {
311 ident: ident.clone(),
312 ty: field.ty.clone(),
313 attr: field_attr,
314 })
315 }
316
317 Ok(attr_field)
318}
319
320fn path_is_template_child(path: &syn::Path) -> bool {
321 if path.leading_colon.is_none()
322 && path.segments.len() == 1
323 && matches!(
324 &path.segments[0].arguments,
325 syn::PathArguments::AngleBracketed(_)
326 )
327 && path.segments[0].ident == "TemplateChild"
328 {
329 return true;
330 }
331 if path.segments.len() == 2
332 && (path.segments[0].ident == "gtk" || path.segments[0].ident == "gtk4")
333 && matches!(
334 &path.segments[1].arguments,
335 syn::PathArguments::AngleBracketed(_)
336 )
337 && path.segments[1].ident == "TemplateChild"
338 {
339 return true;
340 }
341 false
342}
343
344pub fn parse_fields(
345 fields: &Fields,
346 allow_missing_attribute: bool,
347) -> Result<Vec<AttributedField>> {
348 let mut attributed_fields = Vec::new();
349
350 for field in fields {
351 let mut has_attr = false;
352 if !field.attrs.is_empty() {
353 if let Some(attributed_field) = parse_field(field)? {
354 attributed_fields.push(attributed_field);
355 has_attr = true;
356 }
357 }
358 if !has_attr && !allow_missing_attribute {
359 if let syn::Type::Path(syn::TypePath { path, .. }) = &field.ty {
360 if path_is_template_child(path) {
361 return Err(Error::new_spanned(
362 field,
363 format!("field `{}` with type `TemplateChild` possibly missing #[template_child] attribute. Use a meta attribute on the struct to suppress this error: '#[template(string|file|resource = \"...\", allow_template_child_without_attribute)]'",
364 field.ident.as_ref().unwrap())
365 ));
366 }
367 }
368 }
369 }
370
371 Ok(attributed_fields)
372}