1use convert_case::{Case, Casing};
2use proc_macro2::TokenStream;
3use proc_macro_error::{abort, proc_macro_error};
4use quote::quote;
5use syn::{parse_macro_input, Fields, Ident, Item, ItemEnum, ItemStruct, Type, TypePath};
6
7use crate::parse::{DescriptorFieldAttr, DescriptorStructAttr};
8
9mod parse;
10
11#[derive(Clone)]
12struct StructField {
13 ident: Ident,
14 typ: Type,
15 field_name: String,
16 attr: DescriptorFieldAttr,
17}
18
19#[proc_macro_derive(Descriptor, attributes(descriptor))]
20#[proc_macro_error]
21pub fn descriptor(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
22 let input = parse_macro_input!(input as Item);
23 match input {
24 Item::Enum(item) => generate_enum_decriptor(item),
25 Item::Struct(item) => generate_struct_decriptor(item),
26 _ => abort! {input, "not implemented for kind of input"},
27 }
28}
29
30fn generate_struct_decriptor(input: ItemStruct) -> proc_macro::TokenStream {
43 let name = &input.ident;
44
45 let decriptor_struct_attributes = parse::extract_struct_attributes(&input.attrs);
46 let fields = extract_field(&input);
47
48 let describe = describe_method_for_struct(&fields, &decriptor_struct_attributes);
49 let default_headers = default_headers_for_struct(&fields, &decriptor_struct_attributes);
50 let headers = headers_for_struct(&fields, &decriptor_struct_attributes);
51 let header_name_func = rename_headers_for_struct(&fields, &decriptor_struct_attributes);
52 let to_field = to_field_for_struct(&fields, &decriptor_struct_attributes);
53 let pad_struct = pad_struct(&fields);
54
55 generate_trait(
56 name,
57 describe,
58 to_field,
59 Some(pad_struct),
60 Some(default_headers),
61 Some(headers),
62 Some(header_name_func),
63 )
64 .into()
65}
66
67fn pad_struct(fields: &[StructField]) -> TokenStream {
68 let pad = match fields
69 .iter()
70 .map(|field| field.field_name.to_case(Case::Title).len())
71 .max()
72 {
73 None => 0,
74 Some(x) => x + 1,
75 };
76
77 let mut max_pad = quote! {
78 let pad = #pad;
79 };
80
81 for field in fields {
82 if field.attr.flatten {
83 let typ = &field.typ;
84 max_pad.extend(quote! {
85 let pad = pad.max(#typ::struct_pad());
86 })
87 }
88 }
89
90 quote! {
91 #max_pad
92 pad
93 }
94}
95
96fn extract_field(input: &ItemStruct) -> Vec<StructField> {
97 match &input.fields {
98 Fields::Named(named) => named
99 .named
100 .iter()
101 .map(|field| {
102 let ident = match field.ident.as_ref() {
103 None => abort! {input.ident, "no identifier on field"},
104 Some(ident) => ident,
105 };
106
107 StructField {
108 ident: ident.clone(),
109 typ: field.ty.clone(),
110 field_name: ident.to_string(),
111 attr: parse::extract_field_attributes(&field.attrs),
112 }
113 })
114 .filter(|x| !x.attr.skip)
115 .collect::<Vec<StructField>>(),
116 _ => abort! {input.ident, "not implemented for unnamed struct"},
117 }
118}
119
120fn to_field_for_struct(fields: &[StructField], struct_attributes: &DescriptorStructAttr) -> TokenStream {
122 let mut match_to_field = quote!();
123
124 fields
125 .iter()
126 .map(|field| {
127 let field_name = &field.field_name;
128
129 let value = field_getter(
130 &field,
131 quote! {
132 to_field(_child)
133 },
134 );
135
136 quote! {
137 #field_name => {#value},
138 }
139 })
140 .for_each(|ts| match_to_field.extend(ts));
141
142 let fallback = if let Some(extra_fields) = &struct_attributes.extra_fields {
143 quote! {
144 _ => {
145 Into::<#extra_fields>::into(self).to_field(field_name)
146 },
147 }
148 } else {
149 quote! {
150 _ => "field not found".to_string(),
151 }
152 };
153
154 let return_value = if let Some(map) = &struct_attributes.map {
155 quote! {
156 #map(&self, value)
157 }
158 } else {
159 quote! {
160 value
161 }
162 };
163
164 let func = quote! {
165 let (field, _child) = descriptor::get_keys(field_name);
166
167 let value = match field {
168 #match_to_field
169 #fallback
170 };
171
172 #return_value
173 };
174
175 func
176}
177
178fn rename_headers_for_struct(
180 fields: &[StructField],
181 struct_attributes: &DescriptorStructAttr,
182) -> TokenStream {
183 let mut rename_headers = quote!();
184
185 fields
186 .iter()
187 .map(|field| {
188 let typ = &field.typ;
189 let field_name = &field.field_name;
190
191 match &field.attr.rename_header {
192 Some(rename) => quote! {
193 #field_name => Some(#rename.to_string()),
194 },
195 None => {
196 if let Some(into) = &field.attr.into {
197 quote! {
198 #field_name => <#into>::header_name(_child),
199 }
200 } else {
201 quote! {
202 #field_name => <#typ>::header_name(_child),
203 }
204 }
205 }
206 }
207 })
208 .for_each(|ts| rename_headers.extend(ts));
209
210 if let Some(extra_fields) = &struct_attributes.extra_fields {
211 rename_headers.extend(quote! {
212 stringify!(#extra_fields) => <#extra_fields>::header_name(_child),
213 });
214 }
215
216 let func = quote! {
217 let (header, _child) = descriptor::get_keys(header);
218 match header {
219 #rename_headers
220 _ => None,
221 }
222 };
223 func
224}
225
226fn headers_for_struct(fields: &[StructField], struct_attributes: &DescriptorStructAttr) -> TokenStream {
228 let mut headers = quote! {
229 let mut headers = Vec::new();
230 };
231
232 for field in fields.iter() {
233 let typ = &field.typ;
234 let field_name = &field.field_name;
235
236 headers.extend(if let Some(into) = &field.attr.into {
237 quote! {
238 let mut fields = <#into>::default_headers()
239 }
240 } else {
241 quote! {
242 let mut fields = <#typ>::default_headers()
243 }
244 });
245
246 headers.extend(quote! {
247 .into_iter()
248 .map(|x| format!("{}.{}", #field_name, x).to_string())
249 .collect::<Vec<String>>();
250
251 if fields.is_empty() {
252 headers.push(#field_name.to_string());
253 } else {
254 headers.append(&mut fields);
255 }
256 });
257 }
258
259 if let Some(extra_fields) = &struct_attributes.extra_fields {
260 headers.extend(quote! {
261 let mut fields = <#extra_fields>::default_headers();
262 headers.append(&mut fields);
263 })
264 }
265
266 headers.extend(quote! {
267 headers
269 });
270
271 headers
272}
273
274fn default_headers_for_struct(
276 fields: &[StructField],
277 struct_attributes: &DescriptorStructAttr,
278) -> TokenStream {
279 match struct_attributes.headers.as_ref() {
280 Some(headers) => {
281 quote! {
282 #headers.iter().map(|x| x.to_string()).collect::<Vec<String>>()
283 }
284 }
285 None => {
286 let mut slice = quote! {};
287
288 fields
289 .iter()
290 .filter(|x| x.attr.skip_header)
291 .map(|x| x.field_name.to_string())
292 .for_each(|x| {
293 slice.extend(quote! {
294 #x,
295 })
296 });
297
298 quote! {
299 const SKIP : &'static[&'static str] = &[#slice];
300 Self::headers()
301 .into_iter()
302 .filter(|x| !SKIP.contains(&x.as_str()))
303 .map(|x|x.to_string())
304 .collect::<Vec<_>>()
305 }
306 }
307 }
308}
309
310fn describe_method_for_struct(
312 fields: &[StructField],
313 struct_attributes: &DescriptorStructAttr,
314) -> TokenStream {
315 match struct_attributes.into.as_ref() {
316 Some(into) => {
317 quote! {
318 Into::<#into>::into(self).describe(writer, ctx.clone())
319 }
320 }
321 None => {
322 let mut describe = quote!();
323
324 fields
325 .iter()
326 .filter(|x| !x.attr.skip_description)
327 .enumerate()
328 .map(|(i, x)| describe_field(x, i == 0))
329 .for_each(|value| describe.extend(value));
330
331 if let Some(extra_fields) = &struct_attributes.extra_fields {
332 describe.extend(quote! {
333 Into::<#extra_fields>::into(self).describe(writer, ctx.pad(Self::struct_pad()))?;
334 })
335 }
336
337 describe.extend(quote!(Ok(())));
338 describe
339 }
340 }
341}
342
343fn describe_field(field: &StructField, first_field: bool) -> TokenStream {
345 let title_name = field.field_name.to_case(Case::Title);
346 let ident = &field.ident;
347
348 if field.attr.flatten {
349 quote! {
350 self.#ident.describe(writer, ctx.pad(Self::struct_pad()))?;
351 }
352 } else {
353 let title = quote! {
354 ctx.write_title(writer, #title_name, #first_field)?;
355 };
356
357 let value = if field.attr.output_table {
358 quote! {
359 ctx.describe_table(&self.#ident, writer)?;
360 }
361 } else {
362 field_getter(
363 &field,
364 quote! {
365 describe(writer, ctx.indent(Self::struct_pad(), #title_name.len()))?;
366 },
367 )
368 };
369
370 quote! {
371 #title
372 #value
373 }
374 }
375}
376
377fn field_getter(field: &StructField, method: TokenStream) -> TokenStream {
380 let ident = &field.ident;
381
382 let value = match (&field.attr.map, &field.attr.into) {
383 (Some(func), _) => {
384 quote! {
385 #func(#ident)
386 }
387 }
388 (_, Some(into)) => {
389 quote! {
390 Into::<#into>::into(#ident)
391 }
392 }
393 (_, _) => {
394 quote! {
395 #ident
396 }
397 }
398 };
399
400 if path_is_option(&field.typ) && field.attr.resolve_option {
401 quote! {
402 if let Some(#ident) = &self.#ident {
403 #value.#method
404 } else {
405 self.#ident.#method
406 }
407 }
408 } else {
409 quote! {
410 let #ident = &self.#ident;
411 #value.#method
412 }
413 }
414}
415
416fn generate_enum_decriptor(input: ItemEnum) -> proc_macro::TokenStream {
418 let enum_name = &input.ident;
419
420 let mut match_fields = quote! {};
421
422 for variant in input.variants {
423 let name = variant.ident;
424 let field_attributes = parse::extract_field_attributes(&variant.attrs);
425
426 let value = if let Some(rename) = field_attributes.rename_description {
427 quote!(#rename)
428 } else {
429 quote!(stringify!(#name))
430 };
431
432 match_fields.extend(quote! {
433 #enum_name::#name => #value.to_string(),
434 })
435 }
436
437 let to_field = quote! {
438 match self {
439 #match_fields
440 }
441 };
442
443 let describe = quote! {
444 ctx.write_value(writer, self.to_field(""))
445 };
446 generate_trait(&input.ident, describe, to_field, None, None, None, None).into()
447}
448
449fn generate_trait(
450 name: &Ident,
451 describe: TokenStream,
452 to_field: TokenStream,
453 pad: Option<TokenStream>,
454 default_headers: Option<TokenStream>,
455 headers: Option<TokenStream>,
456 header_name: Option<TokenStream>,
457) -> TokenStream {
458 let default_headers = match &default_headers {
459 None => quote! {},
460 Some(headers) => quote! {
461 fn default_headers() -> Vec<String> {
462 #headers
463 }
464 },
465 };
466 let headers = match &headers {
467 None => quote! {},
468 Some(headers) => quote! {
469 fn headers() -> Vec<String> {
470 #headers
471 }
472 },
473 };
474
475 let header_name = match &header_name {
476 None => quote! {},
477 Some(header_name) => quote! {
478 fn header_name(header: &str) -> Option<String> {
479 #header_name
480 }
481 },
482 };
483
484 let pad = match &pad {
485 None => quote! {},
486 Some(pad) => quote! {
487 fn struct_pad() -> usize {
488 #pad
489 }
490 },
491 };
492
493 quote! {
494 impl descriptor::Describe for #name {
495 fn describe<W>(&self, writer: &mut W, ctx: descriptor::Context) -> std::io::Result<()>
496 where
497 W: std::io::Write,
498 {
499 #describe
500 }
501
502 #header_name
503 #headers
504 #default_headers
505 #pad
506
507 fn to_field(&self, field_name: &str) -> String {
508 #to_field
509 }
510 }
511 }
512}
513
514fn path_is_option(ty: &Type) -> bool {
515 match ty {
516 Type::Path(TypePath { path, .. }) => {
517 path.leading_colon.is_none()
518 && path.segments.len() == 1
519 && path.segments.iter().next().unwrap().ident == "Option"
520 }
521 _ => false,
522 }
523}