educe/trait_handlers/debug/models/
type_attribute.rs

1use quote::{quote, ToTokens};
2use syn::{
3    punctuated::Punctuated, token::Comma, Attribute, GenericParam, Ident, Lit, Meta, NestedMeta,
4    WherePredicate,
5};
6
7use super::super::super::{
8    create_path_string_from_lit_str, create_where_predicates_from_generic_parameters,
9    create_where_predicates_from_lit_str,
10};
11use crate::{panic, Trait};
12
13#[derive(Debug, Clone)]
14pub enum TypeAttributeName {
15    Disable,
16    Default,
17    Custom(String),
18}
19
20impl TypeAttributeName {
21    pub fn into_string_by_ident(self, ident: &Ident) -> String {
22        match self {
23            TypeAttributeName::Disable => String::new(),
24            TypeAttributeName::Default => ident.to_string(),
25            TypeAttributeName::Custom(s) => s,
26        }
27    }
28}
29
30#[derive(Clone)]
31pub enum TypeAttributeBound {
32    None,
33    Auto,
34    Custom(Punctuated<WherePredicate, Comma>),
35}
36
37impl TypeAttributeBound {
38    pub fn into_punctuated_where_predicates_by_generic_parameters(
39        self,
40        params: &Punctuated<GenericParam, Comma>,
41    ) -> Punctuated<WherePredicate, Comma> {
42        match self {
43            TypeAttributeBound::None => Punctuated::new(),
44            TypeAttributeBound::Auto => create_where_predicates_from_generic_parameters(
45                params,
46                &syn::parse2(quote!(core::fmt::Debug)).unwrap(),
47            ),
48            TypeAttributeBound::Custom(where_predicates) => where_predicates,
49        }
50    }
51}
52
53#[derive(Clone)]
54pub struct TypeAttribute {
55    pub flag:        bool,
56    pub name:        TypeAttributeName,
57    pub named_field: bool,
58    pub bound:       TypeAttributeBound,
59}
60
61#[derive(Debug, Clone)]
62pub struct TypeAttributeBuilder {
63    pub enable_flag:        bool,
64    pub name:               TypeAttributeName,
65    pub enable_name:        bool,
66    pub named_field:        bool,
67    pub enable_named_field: bool,
68    pub enable_bound:       bool,
69}
70
71impl TypeAttributeBuilder {
72    #[allow(clippy::wrong_self_convention)]
73    pub fn from_debug_meta(&self, meta: &Meta) -> TypeAttribute {
74        let mut flag = false;
75        let mut name = self.name.clone();
76        let mut named_field = self.named_field;
77        let mut bound = TypeAttributeBound::None;
78
79        let correct_usage_for_debug_attribute = {
80            let mut usage = vec![];
81
82            if self.enable_flag {
83                usage.push(stringify!(#[educe(Default)]));
84            }
85
86            if self.enable_name {
87                usage.push(stringify!(#[educe(Debug = "new_name")]));
88                usage.push(stringify!(#[educe(Debug("new_name"))]));
89            }
90
91            if self.enable_bound {
92                usage.push(stringify!(#[educe(Debug(ignore))]));
93            }
94
95            usage
96        };
97
98        let correct_usage_for_name = {
99            let mut usage = vec![
100                stringify!(#[educe(Debug(name = "new_name"))]),
101                stringify!(#[educe(Debug(name("new_name")))]),
102            ];
103
104            if let TypeAttributeName::Disable = &name {
105                usage.push(stringify!(#[educe(Debug(name = true))]));
106                usage.push(stringify!(#[educe(Debug(name(true)))]));
107            } else {
108                usage.push(stringify!(#[educe(Debug(name = false))]));
109                usage.push(stringify!(#[educe(Debug(name(false)))]));
110            }
111
112            usage
113        };
114
115        let correct_usage_for_named_field = {
116            let mut usage = vec![];
117
118            if !self.named_field {
119                usage.push(stringify!(#[educe(Debug(named_field = true))]));
120                usage.push(stringify!(#[educe(Debug(named_field(true)))]));
121            } else {
122                usage.push(stringify!(#[educe(Debug(named_field = false))]));
123                usage.push(stringify!(#[educe(Debug(named_field(false)))]));
124            }
125
126            usage
127        };
128
129        let correct_usage_for_bound = {
130            let usage = vec![
131                stringify!(#[educe(Debug(bound))]),
132                stringify!(#[educe(Debug(bound = "where_predicates"))]),
133                stringify!(#[educe(Debug(bound("where_predicates")))]),
134            ];
135
136            usage
137        };
138
139        match meta {
140            Meta::List(list) => {
141                let mut name_is_set = false;
142                let mut named_field_is_set = false;
143                let mut bound_is_set = false;
144
145                for p in list.nested.iter() {
146                    match p {
147                        NestedMeta::Meta(meta) => {
148                            let meta_name = meta.path().into_token_stream().to_string();
149
150                            match meta_name.as_str() {
151                                "name" | "rename" => {
152                                    if !self.enable_name {
153                                        panic::unknown_parameter("Debug", meta_name.as_str());
154                                    }
155
156                                    match meta {
157                                        Meta::List(list) => {
158                                            for p in list.nested.iter() {
159                                                match p {
160                                                    NestedMeta::Lit(lit) => match lit {
161                                                        Lit::Str(s) => {
162                                                            if name_is_set {
163                                                                panic::reset_parameter(
164                                                                    meta_name.as_str(),
165                                                                );
166                                                            }
167
168                                                            name_is_set = true;
169
170                                                            let s =
171                                                                create_path_string_from_lit_str(s);
172
173                                                            name = match s {
174                                                                Some(s) => {
175                                                                    TypeAttributeName::Custom(s)
176                                                                },
177                                                                None => TypeAttributeName::Disable,
178                                                            };
179                                                        },
180                                                        Lit::Bool(s) => {
181                                                            if name_is_set {
182                                                                panic::reset_parameter(
183                                                                    meta_name.as_str(),
184                                                                );
185                                                            }
186
187                                                            name_is_set = true;
188
189                                                            if s.value {
190                                                                name = TypeAttributeName::Default;
191                                                            } else {
192                                                                name = TypeAttributeName::Disable;
193                                                            }
194                                                        },
195                                                        _ => panic::parameter_incorrect_format(
196                                                            meta_name.as_str(),
197                                                            &correct_usage_for_name,
198                                                        ),
199                                                    },
200                                                    _ => panic::parameter_incorrect_format(
201                                                        meta_name.as_str(),
202                                                        &correct_usage_for_name,
203                                                    ),
204                                                }
205                                            }
206                                        },
207                                        Meta::NameValue(named_value) => {
208                                            let lit = &named_value.lit;
209
210                                            match lit {
211                                                Lit::Str(s) => {
212                                                    if name_is_set {
213                                                        panic::reset_parameter(meta_name.as_str());
214                                                    }
215
216                                                    name_is_set = true;
217
218                                                    let s = create_path_string_from_lit_str(s);
219
220                                                    name = match s {
221                                                        Some(s) => TypeAttributeName::Custom(s),
222                                                        None => TypeAttributeName::Disable,
223                                                    };
224                                                },
225                                                Lit::Bool(s) => {
226                                                    if name_is_set {
227                                                        panic::reset_parameter(meta_name.as_str());
228                                                    }
229
230                                                    name_is_set = true;
231
232                                                    if s.value {
233                                                        name = TypeAttributeName::Default;
234                                                    } else {
235                                                        name = TypeAttributeName::Disable;
236                                                    }
237                                                },
238                                                _ => panic::parameter_incorrect_format(
239                                                    meta_name.as_str(),
240                                                    &correct_usage_for_name,
241                                                ),
242                                            }
243                                        },
244                                        _ => panic::parameter_incorrect_format(
245                                            meta_name.as_str(),
246                                            &correct_usage_for_name,
247                                        ),
248                                    }
249                                },
250                                "named_field" => {
251                                    if !self.enable_named_field {
252                                        panic::unknown_parameter("Debug", meta_name.as_str());
253                                    }
254
255                                    match meta {
256                                        Meta::List(list) => {
257                                            for p in list.nested.iter() {
258                                                match p {
259                                                    NestedMeta::Lit(Lit::Bool(s)) => {
260                                                        if named_field_is_set {
261                                                            panic::reset_parameter(
262                                                                meta_name.as_str(),
263                                                            );
264                                                        }
265
266                                                        named_field_is_set = true;
267
268                                                        named_field = s.value;
269                                                    },
270                                                    _ => panic::parameter_incorrect_format(
271                                                        meta_name.as_str(),
272                                                        &correct_usage_for_named_field,
273                                                    ),
274                                                }
275                                            }
276                                        },
277                                        Meta::NameValue(named_value) => {
278                                            let lit = &named_value.lit;
279
280                                            match lit {
281                                                Lit::Bool(s) => {
282                                                    if named_field_is_set {
283                                                        panic::reset_parameter(meta_name.as_str());
284                                                    }
285
286                                                    named_field_is_set = true;
287
288                                                    named_field = s.value;
289                                                },
290                                                _ => panic::parameter_incorrect_format(
291                                                    meta_name.as_str(),
292                                                    &correct_usage_for_named_field,
293                                                ),
294                                            }
295                                        },
296                                        _ => panic::parameter_incorrect_format(
297                                            meta_name.as_str(),
298                                            &correct_usage_for_named_field,
299                                        ),
300                                    }
301                                },
302                                "bound" => {
303                                    if !self.enable_bound {
304                                        panic::unknown_parameter("Debug", meta_name.as_str());
305                                    }
306
307                                    match meta {
308                                        Meta::List(list) => {
309                                            for p in list.nested.iter() {
310                                                match p {
311                                                    NestedMeta::Lit(Lit::Str(s)) => {
312                                                        if bound_is_set {
313                                                            panic::reset_parameter(
314                                                                meta_name.as_str(),
315                                                            );
316                                                        }
317
318                                                        bound_is_set = true;
319
320                                                        let where_predicates =
321                                                            create_where_predicates_from_lit_str(s);
322
323                                                        bound = match where_predicates {
324                                                            Some(where_predicates) => {
325                                                                TypeAttributeBound::Custom(
326                                                                    where_predicates,
327                                                                )
328                                                            },
329                                                            None => panic::empty_parameter(
330                                                                meta_name.as_str(),
331                                                            ),
332                                                        };
333                                                    },
334                                                    _ => panic::parameter_incorrect_format(
335                                                        meta_name.as_str(),
336                                                        &correct_usage_for_bound,
337                                                    ),
338                                                }
339                                            }
340                                        },
341                                        Meta::NameValue(named_value) => {
342                                            let lit = &named_value.lit;
343
344                                            match lit {
345                                                Lit::Str(s) => {
346                                                    if bound_is_set {
347                                                        panic::reset_parameter(meta_name.as_str());
348                                                    }
349
350                                                    bound_is_set = true;
351
352                                                    let where_predicates =
353                                                        create_where_predicates_from_lit_str(s);
354
355                                                    bound = match where_predicates {
356                                                        Some(where_predicates) => {
357                                                            TypeAttributeBound::Custom(
358                                                                where_predicates,
359                                                            )
360                                                        },
361                                                        None => panic::empty_parameter(
362                                                            meta_name.as_str(),
363                                                        ),
364                                                    };
365                                                },
366                                                _ => panic::parameter_incorrect_format(
367                                                    meta_name.as_str(),
368                                                    &correct_usage_for_bound,
369                                                ),
370                                            }
371                                        },
372                                        Meta::Path(_) => {
373                                            if bound_is_set {
374                                                panic::reset_parameter(meta_name.as_str());
375                                            }
376
377                                            bound_is_set = true;
378
379                                            bound = TypeAttributeBound::Auto;
380                                        },
381                                    }
382                                },
383                                _ => panic::unknown_parameter("Debug", meta_name.as_str()),
384                            }
385                        },
386                        NestedMeta::Lit(lit) => match lit {
387                            Lit::Str(s) => {
388                                if !self.enable_name {
389                                    panic::attribute_incorrect_format(
390                                        "Debug",
391                                        &correct_usage_for_debug_attribute,
392                                    )
393                                }
394
395                                if name_is_set {
396                                    panic::reset_parameter("name");
397                                }
398
399                                name_is_set = true;
400
401                                let s = create_path_string_from_lit_str(s);
402
403                                name = match s {
404                                    Some(s) => TypeAttributeName::Custom(s),
405                                    None => TypeAttributeName::Disable,
406                                };
407                            },
408                            _ => panic::attribute_incorrect_format(
409                                "Debug",
410                                &correct_usage_for_debug_attribute,
411                            ),
412                        },
413                    }
414                }
415            },
416            Meta::NameValue(named_value) => {
417                let lit = &named_value.lit;
418
419                match lit {
420                    Lit::Str(s) => {
421                        if !self.enable_name {
422                            panic::attribute_incorrect_format(
423                                "Debug",
424                                &correct_usage_for_debug_attribute,
425                            )
426                        }
427
428                        let s = create_path_string_from_lit_str(s);
429
430                        name = match s {
431                            Some(s) => TypeAttributeName::Custom(s),
432                            None => TypeAttributeName::Disable,
433                        };
434                    },
435                    _ => panic::attribute_incorrect_format(
436                        "Debug",
437                        &correct_usage_for_debug_attribute,
438                    ),
439                }
440            },
441            Meta::Path(_) => {
442                if !self.enable_flag {
443                    panic::attribute_incorrect_format("Debug", &correct_usage_for_debug_attribute);
444                }
445
446                flag = true;
447            },
448        }
449
450        TypeAttribute {
451            flag,
452            name,
453            named_field,
454            bound,
455        }
456    }
457
458    #[allow(clippy::wrong_self_convention)]
459    pub fn from_attributes(self, attributes: &[Attribute], traits: &[Trait]) -> TypeAttribute {
460        let mut result = None;
461
462        for attribute in attributes.iter() {
463            if attribute.path.is_ident("educe") {
464                let meta = attribute.parse_meta().unwrap();
465
466                match meta {
467                    Meta::List(list) => {
468                        for p in list.nested.iter() {
469                            match p {
470                                NestedMeta::Meta(meta) => {
471                                    let meta_name = meta.path().into_token_stream().to_string();
472
473                                    let t = Trait::from_str(meta_name);
474
475                                    if traits.binary_search(&t).is_err() {
476                                        panic::trait_not_used(t);
477                                    }
478
479                                    if t == Trait::Debug {
480                                        if result.is_some() {
481                                            panic::reuse_a_trait(t);
482                                        }
483
484                                        result = Some(self.from_debug_meta(meta));
485                                    }
486                                },
487                                _ => panic::educe_format_incorrect(),
488                            }
489                        }
490                    },
491                    _ => panic::educe_format_incorrect(),
492                }
493            }
494        }
495
496        result.unwrap_or(TypeAttribute {
497            flag:        false,
498            name:        self.name,
499            named_field: self.named_field,
500            bound:       TypeAttributeBound::None,
501        })
502    }
503}