1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
use quote::ToTokens;
use syn::{Attribute, Lit, Meta, NestedMeta};

use super::super::super::create_path_string_from_lit_str;
use crate::{panic, Trait};

#[derive(Debug, Clone)]
pub enum FieldAttributeName {
    Default,
    Custom(String),
}

impl FieldAttributeName {
    pub fn into_option_string(self) -> Option<String> {
        match self {
            FieldAttributeName::Default => None,
            FieldAttributeName::Custom(s) => Some(s),
        }
    }
}

#[derive(Debug, Clone)]
pub struct FieldAttribute {
    pub name:          FieldAttributeName,
    pub ignore:        bool,
    pub format_method: Option<String>,
    pub format_trait:  Option<String>,
}

#[derive(Debug, Clone)]
pub struct FieldAttributeBuilder {
    pub name:          FieldAttributeName,
    pub enable_name:   bool,
    pub enable_ignore: bool,
    pub enable_impl:   bool,
}

impl FieldAttributeBuilder {
    #[allow(clippy::wrong_self_convention)]
    pub fn from_debug_meta(&self, meta: &Meta) -> FieldAttribute {
        let mut name = self.name.clone();

        let mut ignore = false;

        let mut format_method = None;
        let mut format_trait = None;

        let correct_usage_for_debug_attribute = {
            let mut usage = vec![];

            if self.enable_name {
                usage.push(stringify!(#[educe(Debug = "new_name")]));
                usage.push(stringify!(#[educe(Debug("new_name"))]));
            }

            if self.enable_ignore {
                usage.push(stringify!(#[educe(Debug = false)]));
                usage.push(stringify!(#[educe(Debug(false))]));
            }

            usage
        };

        let correct_usage_for_name = {
            let usage = vec![
                stringify!(#[educe(Debug(name = "new_name"))]),
                stringify!(#[educe(Debug(name("new_name")))]),
            ];

            usage
        };

        let correct_usage_for_ignore = {
            let usage = vec![stringify!(#[educe(Debug(ignore))])];

            usage
        };

        let correct_usage_for_impl = {
            let usage = vec![
                stringify!(#[educe(Debug(method = "path_to_method"))]),
                stringify!(#[educe(Debug(trait = "path_to_trait"))]),
                stringify!(#[educe(Debug(trait = "path_to_trait", method = "path_to_method_in_trait"))]),
                stringify!(#[educe(Debug(method("path_to_method")))]),
                stringify!(#[educe(Debug(trait("path_to_trait")))]),
                stringify!(#[educe(Debug(trait("path_to_trait"), method("path_to_method_in_trait")))]),
            ];

            usage
        };

        match meta {
            Meta::List(list) => {
                let mut name_is_set = false;
                let mut ignore_is_set = false;

                for p in list.nested.iter() {
                    match p {
                        NestedMeta::Meta(meta) => {
                            let meta_name = meta.path().into_token_stream().to_string();

                            match meta_name.as_str() {
                                "name" | "rename" => {
                                    if !self.enable_name {
                                        panic::unknown_parameter("Debug", meta_name.as_str());
                                    }

                                    match meta {
                                        Meta::List(list) => {
                                            for p in list.nested.iter() {
                                                match p {
                                                    NestedMeta::Lit(lit) => match lit {
                                                        Lit::Str(s) => {
                                                            if name_is_set {
                                                                panic::reset_parameter(
                                                                    meta_name.as_str(),
                                                                );
                                                            }

                                                            name_is_set = true;

                                                            let s =
                                                                create_path_string_from_lit_str(s);

                                                            name = match s {
                                                                Some(s) => {
                                                                    FieldAttributeName::Custom(s)
                                                                },
                                                                None => {
                                                                    panic::disable_named_field_name(
                                                                    )
                                                                },
                                                            };
                                                        },
                                                        Lit::Bool(s) => {
                                                            if name_is_set {
                                                                panic::reset_parameter(
                                                                    meta_name.as_str(),
                                                                );
                                                            }

                                                            name_is_set = true;

                                                            if s.value {
                                                                name = FieldAttributeName::Default;
                                                            } else {
                                                                panic::disable_named_field_name();
                                                            }
                                                        },
                                                        _ => panic::parameter_incorrect_format(
                                                            meta_name.as_str(),
                                                            &correct_usage_for_name,
                                                        ),
                                                    },
                                                    _ => panic::parameter_incorrect_format(
                                                        meta_name.as_str(),
                                                        &correct_usage_for_name,
                                                    ),
                                                }
                                            }
                                        },
                                        Meta::NameValue(named_value) => {
                                            let lit = &named_value.lit;

                                            match lit {
                                                Lit::Str(s) => {
                                                    if name_is_set {
                                                        panic::reset_parameter(meta_name.as_str());
                                                    }

                                                    name_is_set = true;

                                                    let s = create_path_string_from_lit_str(s);

                                                    name = match s {
                                                        Some(s) => FieldAttributeName::Custom(s),
                                                        None => panic::disable_named_field_name(),
                                                    };
                                                },
                                                Lit::Bool(s) => {
                                                    if name_is_set {
                                                        panic::reset_parameter(meta_name.as_str());
                                                    }

                                                    name_is_set = true;

                                                    if s.value {
                                                        name = FieldAttributeName::Default;
                                                    } else {
                                                        panic::disable_named_field_name();
                                                    }
                                                },
                                                _ => panic::parameter_incorrect_format(
                                                    meta_name.as_str(),
                                                    &correct_usage_for_name,
                                                ),
                                            }
                                        },
                                        _ => panic::parameter_incorrect_format(
                                            meta_name.as_str(),
                                            &correct_usage_for_name,
                                        ),
                                    }
                                },
                                "ignore" => {
                                    if !self.enable_ignore {
                                        panic::unknown_parameter("Debug", meta_name.as_str());
                                    }

                                    match meta {
                                        Meta::Path(_) => {
                                            if ignore_is_set {
                                                panic::reset_parameter(meta_name.as_str());
                                            }

                                            ignore_is_set = true;

                                            ignore = true;
                                        },
                                        _ => panic::parameter_incorrect_format(
                                            meta_name.as_str(),
                                            &correct_usage_for_ignore,
                                        ),
                                    }
                                },
                                "method" => {
                                    if !self.enable_impl {
                                        panic::unknown_parameter("Debug", meta_name.as_str());
                                    }

                                    match meta {
                                        Meta::List(list) => {
                                            for p in list.nested.iter() {
                                                match p {
                                                    NestedMeta::Lit(Lit::Str(s)) => {
                                                        if format_method.is_some() {
                                                            panic::reset_parameter(
                                                                meta_name.as_str(),
                                                            );
                                                        }

                                                        let s = create_path_string_from_lit_str(s);

                                                        if let Some(s) = s {
                                                            format_method = Some(s);
                                                        } else {
                                                            panic::empty_parameter(
                                                                meta_name.as_str(),
                                                            );
                                                        }
                                                    },
                                                    _ => panic::parameter_incorrect_format(
                                                        meta_name.as_str(),
                                                        &correct_usage_for_impl,
                                                    ),
                                                }
                                            }
                                        },
                                        Meta::NameValue(named_value) => {
                                            let lit = &named_value.lit;

                                            match lit {
                                                Lit::Str(s) => {
                                                    if format_method.is_some() {
                                                        panic::reset_parameter(meta_name.as_str());
                                                    }

                                                    let s = create_path_string_from_lit_str(s);

                                                    if let Some(s) = s {
                                                        format_method = Some(s);
                                                    } else {
                                                        panic::empty_parameter(meta_name.as_str());
                                                    }
                                                },
                                                _ => panic::parameter_incorrect_format(
                                                    meta_name.as_str(),
                                                    &correct_usage_for_impl,
                                                ),
                                            }
                                        },
                                        _ => panic::parameter_incorrect_format(
                                            meta_name.as_str(),
                                            &correct_usage_for_impl,
                                        ),
                                    }
                                },
                                "trait" => {
                                    if !self.enable_impl {
                                        panic::unknown_parameter("Debug", meta_name.as_str());
                                    }

                                    match meta {
                                        Meta::List(list) => {
                                            for p in list.nested.iter() {
                                                match p {
                                                    NestedMeta::Lit(Lit::Str(s)) => {
                                                        if format_trait.is_some() {
                                                            panic::reset_parameter(
                                                                meta_name.as_str(),
                                                            );
                                                        }

                                                        let s = create_path_string_from_lit_str(s);

                                                        if let Some(s) = s {
                                                            format_trait = Some(s);
                                                        } else {
                                                            panic::empty_parameter(
                                                                meta_name.as_str(),
                                                            );
                                                        }
                                                    },
                                                    _ => panic::parameter_incorrect_format(
                                                        meta_name.as_str(),
                                                        &correct_usage_for_impl,
                                                    ),
                                                }
                                            }
                                        },
                                        Meta::NameValue(named_value) => {
                                            let lit = &named_value.lit;

                                            match lit {
                                                Lit::Str(s) => {
                                                    if format_trait.is_some() {
                                                        panic::reset_parameter(meta_name.as_str());
                                                    }

                                                    let s = create_path_string_from_lit_str(s);

                                                    if let Some(s) = s {
                                                        format_trait = Some(s);
                                                    } else {
                                                        panic::empty_parameter(meta_name.as_str());
                                                    }
                                                },
                                                _ => panic::parameter_incorrect_format(
                                                    meta_name.as_str(),
                                                    &correct_usage_for_impl,
                                                ),
                                            }
                                        },
                                        _ => panic::parameter_incorrect_format(
                                            meta_name.as_str(),
                                            &correct_usage_for_impl,
                                        ),
                                    }
                                },
                                _ => panic::unknown_parameter("Debug", meta_name.as_str()),
                            }
                        },
                        NestedMeta::Lit(lit) => match lit {
                            Lit::Str(s) => {
                                if !self.enable_name {
                                    panic::attribute_incorrect_format(
                                        "Debug",
                                        &correct_usage_for_debug_attribute,
                                    )
                                }

                                if name_is_set {
                                    panic::reset_parameter("name");
                                }

                                name_is_set = true;

                                let s = create_path_string_from_lit_str(s);

                                name = match s {
                                    Some(s) => FieldAttributeName::Custom(s),
                                    None => panic::disable_named_field_name(),
                                };
                            },
                            Lit::Bool(b) => {
                                if !self.enable_ignore {
                                    panic::attribute_incorrect_format(
                                        "Debug",
                                        &correct_usage_for_debug_attribute,
                                    )
                                }

                                if ignore_is_set {
                                    panic::reset_parameter("ignore");
                                }

                                ignore_is_set = true;

                                ignore = !b.value;
                            },
                            _ => panic::attribute_incorrect_format(
                                "Debug",
                                &correct_usage_for_debug_attribute,
                            ),
                        },
                    }
                }
            },
            Meta::NameValue(named_value) => {
                let lit = &named_value.lit;

                match lit {
                    Lit::Str(s) => {
                        if !self.enable_name {
                            panic::attribute_incorrect_format(
                                "Debug",
                                &correct_usage_for_debug_attribute,
                            )
                        }

                        let s = create_path_string_from_lit_str(s);

                        name = match s {
                            Some(s) => FieldAttributeName::Custom(s),
                            None => panic::disable_named_field_name(),
                        };
                    },
                    Lit::Bool(b) => {
                        if !self.enable_ignore {
                            panic::attribute_incorrect_format(
                                "Debug",
                                &correct_usage_for_debug_attribute,
                            )
                        }

                        ignore = !b.value;
                    },
                    _ => panic::attribute_incorrect_format(
                        "Debug",
                        &correct_usage_for_debug_attribute,
                    ),
                }
            },
            _ => panic::attribute_incorrect_format("Debug", &correct_usage_for_debug_attribute),
        }

        if format_trait.is_some() && format_method.is_none() {
            format_method = Some("fmt".to_string());
        }

        FieldAttribute {
            name,
            ignore,
            format_method,
            format_trait,
        }
    }

    #[allow(clippy::wrong_self_convention)]
    pub fn from_attributes(self, attributes: &[Attribute], traits: &[Trait]) -> FieldAttribute {
        let mut result = None;

        for attribute in attributes.iter() {
            if attribute.path.is_ident("educe") {
                let meta = attribute.parse_meta().unwrap();

                match meta {
                    Meta::List(list) => {
                        for p in list.nested.iter() {
                            match p {
                                NestedMeta::Meta(meta) => {
                                    let meta_name = meta.path().into_token_stream().to_string();

                                    let t = Trait::from_str(meta_name);

                                    if traits.binary_search(&t).is_err() {
                                        panic::trait_not_used(t);
                                    }

                                    if t == Trait::Debug {
                                        if result.is_some() {
                                            panic::reuse_a_trait(t);
                                        }

                                        result = Some(self.from_debug_meta(meta));
                                    }
                                },
                                _ => panic::educe_format_incorrect(),
                            }
                        }
                    },
                    _ => panic::educe_format_incorrect(),
                }
            }
        }

        result.unwrap_or(FieldAttribute {
            name:          self.name,
            ignore:        false,
            format_method: None,
            format_trait:  None,
        })
    }
}