1 module jcli.introspect.data;
2 
3 import jcli.core;
4 
5 import std.conv : to;
6 import std.traits;
7 import std.meta;
8 
9 struct ArgumentCommonInfo
10 {
11     // TODO: Support nested structs. For now, no such thing.
12     // This is currently the name of the field, used to get a reference to that field.
13     string identifier;
14     ArgFlags flags;
15     ArgGroup group;
16 }
17 
18 /// Reason: accessing with multiple dots is annoying and prevents easy refactoring.
19 mixin template ArgumentGetters()
20 {
21     string name() const nothrow @nogc pure @safe { return uda.name; }
22 
23     inout nothrow @nogc pure @safe
24     {
25         ref inout(string) description() { return uda.description; }
26         ref inout(string) identifier() { return argument.identifier; }
27         ref inout(ArgFlags) flags() { return argument.flags; }
28         ref inout(ArgGroup) group() { return argument.group; }
29     }
30 }
31 
32 struct NamedArgumentInfo
33 {
34     ArgNamed uda;
35     ArgumentCommonInfo argument;
36 
37     mixin ArgumentGetters;
38     inout(Pattern) pattern() @safe nothrow @nogc inout { return uda.pattern; }
39 }
40 
41 // It's a good idea to encapsulate this logic here.
42 // It can be easily modified later when we add nested structs.
43 template isNameMatch(NamedArgumentInfo namedArgumentInfo)
44 {
45     import std.algorithm;
46     /// Takes a name, like the `aa` part of `-aa`.
47     /// Tries to match the pattern specified as the template argument.
48     bool isNameMatch(string value)
49     {
50         enum caseInsensitive = namedArgumentInfo.flags.has(ArgFlags._caseInsensitiveBit);
51         {
52             bool noMatches = namedArgumentInfo
53                 .pattern
54                 .matches!caseInsensitive(value)
55                 .empty;
56             if (!noMatches)
57                 return true;
58         }
59         static if (namedArgumentInfo.flags.has(ArgFlags._repeatableNameBit))
60         {
61             bool allSame = value.all!(a => a == value[0]);
62             if (!allSame)
63                 return false;
64             bool noMatches = namedArgumentInfo
65                 .pattern
66                 .matches!caseInsensitive(value[0 .. 1])
67                 .empty;
68             return !noMatches;
69         }
70         else
71         {
72             return false;
73         }
74     }
75 }
76 
77 struct PositionalArgumentInfo
78 {
79     ArgPositional uda;
80     ArgumentCommonInfo argument;
81 
82     mixin ArgumentGetters;
83 }
84 
85 // private template FieldType(TCommand, string fieldPath)
86 // {
87 //     mixin("alias FieldType = typeof(TCommand." ~ fieldPath ~ ");");
88 // }
89 
90 
91 private template fieldPathOf(alias argumentInfoOrFieldPath)
92 {
93     static if (is(Unqual!(typeof(argumentInfoOrFieldPath)) == ArgumentCommonInfo)) 
94         enum string fieldPathOf = argumentInfoOrFieldPath.identifier;
95     else static if (is(Unqual!(typeof(argumentInfoOrFieldPath.argument)) == ArgumentCommonInfo)) 
96         enum string fieldPathOf = argumentInfoOrFieldPath.argument.identifier;
97     else static if (is(Unqual!(typeof(argumentInfoOrFieldPath)) == string))
98         enum string fieldPathOf = argumentInfoOrFieldPath;
99     else
100         static assert(false, "Unsupported thing: " ~ Unqual!(typeof(argumentInfoOrFieldPath)).stringof);
101 }
102 
103 /// command.getArgumentFieldRef!argInfo
104 template getArgumentFieldRef(alias argumentInfoOrFieldPath)
105 {
106     ref auto getArgumentFieldRef(TCommand)(ref TCommand command)
107     {
108         mixin("return command." ~ fieldPathOf!argumentInfoOrFieldPath ~ ";");
109     }
110 }
111 template getArgumentFieldSymbol(TCommand, alias argumentInfoOrFieldPath)
112 {
113     mixin("alias getArgumentFieldSymbol = TCommand." ~ fieldPathOf!argumentInfoOrFieldPath ~ ";");
114 }
115 
116 template getCommandUDAs(CommandType)
117 {
118     alias commandUDAs = AliasSeq!(
119         getUDAs!(CommandType, Command),
120         getUDAs!(CommandType, CommandDefault));
121 
122     static if (commandUDAs.length > 0)
123     {
124         alias getCommandUDAs = commandUDAs;
125     }
126     else
127     {
128         enum isValue(alias stringUDA) = is(typeof(stringUDA) : string);
129         alias stringUDAs = Filter!(isValue,
130             getUDAs!(CommandType, string));
131 
132         static if (stringUDAs.length > 0)
133             alias getCommandUDAs = AliasSeq!(stringUDAs[0]);
134         else
135             alias getCommandUDAs = AliasSeq!();
136     }
137 }
138 
139 
140 // NOTE:
141 // It's not clear at all what a good design fot this is.
142 // I only feel esoteric OOP vibes when deciding whether this should be in a struct,
143 // template, whether it should be a getter, and enum, immutable, or an alias.
144 // It feels to me like it's only philosophical at this point.
145 // So I'm just going to do a single thing and call it a day.
146 // Whatever I do feels wrong tho.
147 template CommandInfo(TCommand)
148 {
149     alias CommandType = TCommand;
150     alias Arguments = CommandArgumentsInfo!TCommand;
151 
152     static assert(
153         getCommandUDAs!TCommand.length <= 1,
154         "Only one command UDA is allowed.");
155     alias commandUDAs = getCommandUDAs!TCommand;
156 
157     static if (commandUDAs.length == 0)
158     {
159         enum flags = CommandFlags.noCommandAttribute;
160     }
161     else
162     {
163         alias rawCommandUDA = Alias!(commandUDAs[0]);
164 
165         static if (is(typeof(rawCommandUDA) : string))
166         {
167             enum flags = CommandFlags.stringAttribute | CommandFlags.givenValue;
168             enum string udaValue = rawCommandUDA;
169         }
170         else static if (is(rawCommandUDA == Command))
171         {
172             enum flags = CommandFlags.commandAttribute;
173             immutable udaValue = Command([__traits(identifier, TCommand)], "");
174         }
175         else static if (is(rawCommandUDA == CommandDefault))
176         {
177             enum flags = CommandFlags.commandAttribute | CommandFlags.explicitlyDefault;
178             immutable udaValue = CommandDefault("");
179         }
180         else static if (is(typeof(rawCommandUDA) == Command))
181         {
182             enum flags = CommandFlags.commandAttribute | CommandFlags.givenValue;
183             immutable udaValue = rawCommandUDA;
184         }
185         else static if (is(typeof(rawCommandUDA) == CommandDefault))
186         {
187             enum flags = CommandFlags.commandAttribute | CommandFlags.givenValue | CommandFlags.explicitlyDefault;
188             immutable udaValue = rawCommandUDA;
189         }
190         else static assert(0);
191 
192         // this part is super meh
193         static if (flags.has(CommandFlags.stringAttribute))
194             enum string description = udaValue;
195         else
196             enum string description = udaValue.description;
197     }
198 }
199 
200 template CommandArgumentsInfo(TCommand)
201 {
202     alias CommandType = TCommand;
203 
204     static foreach (field; TCommand.tupleof)
205         static assert(countUDAsOf!(field, ArgNamed, ArgPositional, ArgOverflow, ArgRaw) <= 1);
206 
207     // TODO: 
208     // We should have another member here, with the members that are nested structs.
209     //
210     // Probably say positional arguments are not allowed for nested structs, 
211     // because it would be extremely confusing to implement and nobody will probably ever use them.
212     //
213     // The nested struct can be either named (aka -name.field to give a value to the flag `field`
214     // inside that nested struct argument), or flattened (allow to simply do -field).
215     //
216     // We should create another member which would point to the arguments with the nested types perhaps?
217     // We could get the names of the types by accessing the field identifiers?
218     // ```
219     // immutable NamedArgumentInfo[] nested;
220     // ```
221     // Then make a helper template that would flatten these for the linear iteration, 
222     // for use in the command parser. It really does not need to know about this nesting,
223     // since it would always read the arguments linearly, only matching actual qualified names,
224     // it does not care where in the struct that field is.
225     //
226     // But we still do need all the info for e.g. reading arguments from typical config format,
227     // where nesting is allowed. Like json or xml. There, the user would write out a nested object
228     // instead of assigning the fields individually, which would be kinda hard to implement without
229     // the nesting info (you'd have to reget that info from the stored dots in the idetifiers.
230 
231     // TODO: 
232     // A similar mechanism can be used to get pointers to data received from other commands.
233     // This will be useful when nested commands partially match the given arguments.
234     // Currently not implemented whatsoever.
235 
236     // TODO: These seem to be used only as a compile-time info thing, so these should perhaps be alias seqs.
237 
238     /// Includes the simple string usage, which gets converted to an ArgNamed uda.
239     immutable NamedArgumentInfo[]      named      = getNamedArgumentInfosOf!TCommand;
240     immutable PositionalArgumentInfo[] positional = getPositionalArgumentInfosOf!TCommand;
241 
242     import std.algorithm;
243     enum size_t numRequiredPositionalArguments = positional
244         .filter!((immutable p) => p.flags.has(ArgFlags._requiredBit))
245         .count;
246 
247     private alias fieldsWithOverflow = fieldsWithUDAOf!(TCommand, ArgOverflow);
248     static assert(fieldsWithOverflow.length <= 1, "No more than one overflow argument allowed");
249     enum takesOverflow = fieldsWithOverflow.length == 1;
250     static if (takesOverflow)
251         immutable ArgumentCommonInfo overflow = getCommonArgumentInfo!(fieldsWithOverflow[0], ArgConfig.aggregate);
252 
253     
254     private alias fieldsWithRaw = fieldsWithUDAOf!(TCommand, ArgRaw);
255     static assert(fieldsWithRaw.length <= 1, "No more than one raw argument allowed");
256     enum takesRaw = fieldsWithRaw.length == 1;
257     static if (takesRaw)
258         immutable ArgumentCommonInfo raw = getCommonArgumentInfo!(fieldsWithRaw[0], ArgConfig.aggregate);
259     
260     enum takesSomeArguments = named.length > 0 || positional.length > 0 || takesOverflow || takesRaw;
261 }
262 
263 unittest
264 {
265     {
266         static struct S
267         {
268             @ArgNamed
269             @ArgNamed
270             string a;
271         }
272         static assert(!__traits(compiles, CommandArgumentsInfo!S));
273     }
274     {
275         static struct S
276         {
277             @ArgNamed
278             @ArgPositional
279             string a;
280         }
281         static assert(!__traits(compiles, CommandArgumentsInfo!S));
282     }
283     {
284         static struct S
285         {
286             @ArgPositional
287             @ArgPositional
288             string a;
289         }
290         static assert(!__traits(compiles, CommandArgumentsInfo!S));
291     }
292     {
293         static struct S
294         {
295             @ArgOverflow
296             @ArgOverflow
297             string[] a;
298         }
299         static assert(!__traits(compiles, CommandArgumentsInfo!S));
300     }
301     {
302         static struct S
303         {
304             @ArgOverflow
305             @ArgRaw
306             string[] a;
307         }
308         static assert(!__traits(compiles, CommandArgumentsInfo!S));
309     }
310     {
311         static struct S
312         {
313             @ArgOverflow
314             string[] a;
315 
316             @ArgRaw
317             string[] b;
318         }
319         alias Info = CommandArgumentsInfo!S;
320         assert(Info.takesOverflow);
321         assert(Info.overflow.identifier == "a");
322         assert(Info.takesRaw);
323         assert(Info.raw.identifier == "b");
324         assert(Info.named.length == 0);
325         assert(Info.positional.length == 0);
326     }
327     {
328         static struct S
329         {
330             @ArgPositional
331             string a;
332         }
333         alias Info = CommandArgumentsInfo!S;
334         static assert(!Info.takesOverflow);
335         static assert(!Info.takesRaw);
336         static assert(Info.positional.length == 1);
337         enum positional = Info.positional[0];
338         static assert(positional.identifier == "a");
339         static assert(positional.name == "a");
340         static assert(positional.flags.has(ArgFlags._positionalArgumentBit));
341     }
342     {
343         static struct S
344         {
345             @ArgNamed
346             string a;
347         }
348         alias Info = CommandArgumentsInfo!S;
349         static assert(!Info.takesOverflow);
350         static assert(!Info.takesRaw);
351         static assert(Info.named.length == 1);
352         enum named = Info.named[0];
353         static assert(named.identifier == "a");
354         static assert(named.name == "a");
355         static assert(named.flags.has(ArgFlags._namedArgumentBit));
356     }
357     {
358         static struct S
359         {
360             @("b")
361             string a;
362         }
363         alias Info = CommandArgumentsInfo!S;
364         static assert(!Info.takesOverflow);
365         static assert(!Info.takesRaw);
366         static assert(Info.named.length == 1);
367         enum named = Info.named[0];
368         static assert(named.identifier == "a");
369         static assert(named.name == "a");
370         static assert(named.description == "b");
371         static assert(named.flags.has(ArgFlags._namedArgumentBit));
372     }
373     {
374         static struct S
375         {
376             @("b")
377             @ArgNamed
378             string a;
379         }
380         alias Info = CommandArgumentsInfo!S;
381         static assert(Info.named.length == 1);
382         enum named = Info.named[0];
383         static assert(named.identifier == "a");
384         static assert(named.name == "a");
385         static assert(named.flags.has(ArgFlags._namedArgumentBit));
386 
387         // The description is not applied here, because 
388         // ArgNamed takes precedence over the string.
389         // static assert(named.uda.description == "b");
390     }
391     {
392         static struct S
393         {
394             @("b")
395             string a = "c";
396         }
397         alias Info = CommandArgumentsInfo!S;
398         static assert(Info.named[0].flags.has(ArgFlags._optionalBit));
399     }
400     {
401         static struct S
402         {
403             @ArgNamed
404             string a = "c";
405         }
406         alias Info = CommandArgumentsInfo!S;
407         static assert(Info.named[0].flags.has(ArgFlags._optionalBit));
408     }
409     {
410         static struct S
411         {
412             @ArgPositional
413             @(ArgConfig.optional)
414             string a;
415 
416             @ArgPositional
417             string b;
418         }
419         static assert(!__traits(compiles, CommandArgumentsInfo!S));
420     }
421     {
422         static struct S
423         {
424             @ArgPositional
425             string a = "c";
426 
427             @ArgPositional
428             string b;
429         }
430         alias Info = CommandArgumentsInfo!S;
431         static assert(Info.positional[0].flags.has(ArgFlags._requiredBit));
432         static assert(Info.positional[1].flags.has(ArgFlags._requiredBit));
433     }
434     {
435         static struct S
436         {
437             @ArgPositional
438             string a;
439 
440             @ArgPositional
441             string b;
442         }
443         alias Info = CommandArgumentsInfo!S;
444         static assert(Info.positional.length == 2);
445         {
446             enum a = Info.positional[0];
447             static assert(a.identifier == "a");
448             static assert(a.name == "a");
449             static assert(a.flags.doesNotHave(ArgFlags._optionalBit));
450         }
451         {
452             enum b = Info.positional[1];
453             static assert(b.identifier == "b");
454             static assert(b.name == "b");
455             static assert(b.flags.doesNotHave(ArgFlags._optionalBit));
456         }
457     }
458     {
459         static struct S
460         {
461             @ArgPositional
462             string a;
463 
464             @ArgPositional
465             string b = "c";
466         }
467         alias Info = CommandArgumentsInfo!S;
468         static assert(Info.positional.length == 2);
469         {
470             enum a = Info.positional[0];
471             static assert(a.identifier == "a");
472             static assert(a.name == "a");
473             static assert(a.flags.doesNotHave(ArgFlags._optionalBit));
474         }
475         {
476             enum b = Info.positional[1];
477             static assert(b.identifier == "b");
478             static assert(b.name == "b");
479             static assert(b.flags.has(ArgFlags._optionalBit));
480         }
481     }
482     {
483         static struct S
484         {
485             @ArgPositional
486             @(ArgConfig.parseAsFlag)
487             bool a;
488         }
489         static assert(!__traits(compiles, CommandArgumentsInfo!S));
490     }
491     {
492         static struct S
493         {
494             @ArgNamed
495             @(ArgConfig.parseAsFlag)
496             bool a;
497         }
498         alias Info = CommandArgumentsInfo!S;
499         static assert(Info.named[0].flags.has(ArgFlags._parseAsFlagBit));
500     }
501     {
502         static struct S
503         {
504             @ArgNamed
505             @(ArgConfig.parseAsFlag)
506             bool a = true;
507         }
508         static assert(!__traits(compiles, CommandArgumentsInfo!S));
509     }
510     {
511         // NOTE: 
512         // The previous version inferred that flag.
513         // I think, inferring it could lead to bugs so I say you should specify it explicitly.
514         static struct S
515         {
516             @ArgNamed
517             bool a;
518         }
519         alias Info = CommandArgumentsInfo!S;
520         static assert(Info.named[0].flags.doesNotHave(ArgFlags._parseAsFlagBit));
521     }
522     {
523         static struct S
524         {
525             @ArgPositional
526             bool a;
527         }
528         alias Info = CommandArgumentsInfo!S;
529         static assert(Info.positional[0].flags.doesNotHave(ArgFlags._parseAsFlagBit));
530     }
531     {
532         static struct S
533         {
534             @ArgNamed
535             @(ArgConfig._optionalBit | ArgFlags._requiredBit)
536             string a;
537         }
538         static assert(!__traits(compiles, CommandArgumentsInfo!S));
539     }
540     {
541         static struct S
542         {
543             @ArgNamed
544             Nullable!string a;
545         }
546         alias Info = CommandArgumentsInfo!S;
547         enum a = Info.named[0];
548         static assert(a.flags.has(ArgFlags._optionalBit | ArgFlags._inferedOptionalityBit));
549     }
550     {
551         static struct S
552         {
553             @ArgPositional
554             Nullable!string a;
555         }
556         alias Info = CommandArgumentsInfo!S;
557         enum a = Info.positional[0];
558         static assert(a.flags.has(ArgFlags._optionalBit | ArgFlags._inferedOptionalityBit));
559     }
560     {
561         static struct S
562         {
563             @ArgPositional
564             @(ArgFlags._requiredBit)
565             Nullable!string a;
566         }
567         static assert(!__traits(compiles, CommandArgumentsInfo!S));
568     }
569     {
570         static struct S
571         {
572             @ArgPositional
573             Nullable!string a;
574             @ArgPositional
575             string b;
576         }
577         static assert(!__traits(compiles, CommandArgumentsInfo!S));
578     }
579     {
580         static struct S
581         {
582             @ArgPositional
583             string a;
584             @ArgPositional
585             Nullable!string b;
586         }
587         alias Info = CommandArgumentsInfo!S;
588         enum a = Info.positional[0];
589         static assert(a.flags.has(ArgFlags._requiredBit | ArgFlags._inferedOptionalityBit));
590         enum b = Info.positional[1];
591         static assert(b.flags.has(ArgFlags._optionalBit | ArgFlags._inferedOptionalityBit));
592     }
593     {
594         static struct S
595         {
596             @("Hello")
597             @(ArgConfig.positional)
598             string a;
599 
600             @("World")
601             string b;
602         }
603         alias Info = CommandArgumentsInfo!S;
604 
605         enum a = Info.positional[0];
606         static assert(a.flags.has(ArgFlags._requiredBit | ArgFlags._inferedOptionalityBit));
607         static assert(a.description == "Hello");
608         
609         enum b = Info.named[0];
610         static assert(b.flags.has(ArgFlags._requiredBit | ArgFlags._inferedOptionalityBit));
611         static assert(b.description == "World");
612     }
613 }
614 
615 private:
616 
617 import std.traits;
618 import std.meta;
619 
620 // NOTE:
621 // Passing the udas as a sequence works, but trying to get them within the function does not.
622 // Apparently, it may work if we mark the function static, but afaik that's a compiler bug.
623 // Another NOTE:
624 // We still need the static here?? wtf?
625 static ArgFlags foldArgumentFlags(udas...)()
626 {
627     ArgFlags result;
628     ArgConfig[] highLevelFlags;
629 
630     static foreach (uda; udas)
631     {
632         static if (is(typeof(uda) == ArgConfig))
633         {
634             highLevelFlags ~= uda;
635         }
636         static if (is(typeof(uda) : ArgFlags))
637         {
638             static assert(uda.doesNotHaveEither(
639                 ArgFlags._inferedOptionalityBit | ArgFlags._mayChangeOptionalityWithoutBreakingThingsBit),
640                 "Specifying the `_inferedOptionalityBit` or `_mayChangeOptionalityWithoutBreakingThingsBit`"
641                 ~ " in user code is not allowed. The optionality will be inferred automatically by default, if possible.");
642             result |= uda;
643         }
644     }
645 
646     // Validate only the high level flag combinations.
647     {
648         string validationMessage = getArgumentConfigFlagsIncompatibilityValidationMessage(highLevelFlags);
649         assert(validationMessage is null, validationMessage);
650     }
651 
652     return result;
653 }
654 
655 
656 // This is needed as a workaround for the function below.
657 // Basically, you cannot access `field.init` or get the attributes
658 // of a field symbol in a function context.
659 // template FieldInfo(alias field)
660 // {
661 //     alias FieldType = typeof(field);
662 //     enum FieldType initialValue = field.init;
663 // }
664 
665 // This function is needed as a separate entity as a workaround the compiler quirk that makes
666 // us unable to query attributes of field symbols in templated functions.
667 // I would've inlined it below, but we're forced to use a template, or pass the arguments as an alias seq. 
668 ArgFlags inferOptionalityAndValidate(FieldType)(ArgFlags initialFlags, FieldType fieldDefaultValue)
669 {
670     import std.conv : to;
671     ArgFlags flags = initialFlags;
672 
673     // Validate all flags and try to infer optionality.
674     with (ArgFlags)
675     {
676         auto validationMessage = getArgumentFlagsValidationMessage(flags);
677         assert(validationMessage is null, validationMessage);
678 
679         string messageIfAddedOptional = getArgumentFlagsValidationMessage(flags | _optionalBit);
680 
681         static if (is(FieldType : Nullable!T, T))
682         {
683             assert(messageIfAddedOptional is null,
684                 "Nullable types must be optional.\n" ~ messageIfAddedOptional);
685 
686             if (flags.doesNotHave(_optionalBit))
687                 flags |= _optionalBit | _inferedOptionalityBit;
688         }
689         // Note: These two checks are common for both named arguments and positional arguments. 
690         else if (flags.doesNotHaveEither(_optionalBit | _requiredBit)) // @suppress(dscanner.suspicious.static_if_else)
691         {
692             string messageIfAddedRequired = getArgumentFlagsValidationMessage(flags | _requiredBit);
693             // TODO: 
694             // This rudimentary check fails e.g. for the following: 
695             // `struct A { int a = 0; }`
696             // Aka when the value is given, but is also the default.
697             // Guys from the D server say it's probably impossible to do currently.
698             // NOTE: `is` does bitwise comparison. `!=` would fail for float.nan
699             bool canInferOptional = FieldType.init !is fieldDefaultValue;
700 
701             if (messageIfAddedOptional is null && canInferOptional)
702             {
703                 flags |= _optionalBit;
704                 if (messageIfAddedRequired is null)
705                     flags |= _mayChangeOptionalityWithoutBreakingThingsBit;
706             }
707             else
708             {
709                 assert(messageIfAddedRequired is null,
710                     "The type can be neither optional nor required. This check should have never been hit.\n"
711                     ~ "If we added required: " ~ messageIfAddedRequired ~ "\n"
712                     ~ "If we added optional: " ~ messageIfAddedOptional);
713 
714                 flags |= _requiredBit;
715                 if (messageIfAddedOptional is null)
716                     flags |= _mayChangeOptionalityWithoutBreakingThingsBit;
717             }
718             flags |= _inferedOptionalityBit;
719         }
720     }
721 
722     return flags;
723 }
724 
725 // NOTE: 
726 // We never pass alias to field even to CTFE functions as template parameters,
727 // because the compiler essetially prevents their use there.
728 // Passing to templates is ok, but trying to do anything with them in template functions
729 // makes the compiler complain.
730 enum defaultValueOf(alias field) = __traits(child, __traits(parent, field).init, field);
731 
732 // Gives some basic information associated with the given argument field.
733 public template getCommonArgumentInfo(alias field, ArgFlags initialFlags = ArgFlags.none)
734 {
735     enum flagsBeforeInference = initialFlags | foldArgumentFlags!(__traits(getAttributes, field));
736     enum flagsAfterInference = inferOptionalityAndValidate!(typeof(field))(
737         flagsBeforeInference, defaultValueOf!field);
738     
739     alias groups = getUDAs!(field, ArgGroup);
740     static assert(groups.length <= 1, "Only one group attribute is allowed");
741     static if (groups.length == 1)
742         enum group = groups[0];
743     else
744         enum group = ArgGroup.init;
745 
746     enum identifier = __traits(identifier, field);
747     enum getCommonArgumentInfo = ArgumentCommonInfo(identifier, flagsAfterInference, groups);
748 }
749 
750 template getArgumentInfo(UDAType, alias field)
751 {
752     static foreach (uda; __traits(getAttributes, field))
753     {
754         static if (is(uda == UDAType))
755         {
756             enum getArgumentInfo = UDAType.init;
757         }
758         else static if (is(typeof(uda) == UDAType))
759         {
760             enum getArgumentInfo = uda;
761         }
762     }
763 }
764 
765 
766 template fieldsWithUDAOf(T, UDAType)
767 {
768     enum hasThatUDA(alias field) = hasUDA!(field, UDAType);
769     alias fieldsWithUDAOf = Filter!(hasThatUDA, T.tupleof);
770 }
771 unittest
772 {
773     enum Test;
774     static struct S
775     {
776         @Test string a;
777         @Test string b;
778         string c;
779     }
780     alias fields = fieldsWithUDAOf!(S, Test);
781     static assert(fields.length == 2);
782     static assert(fields[0].stringof == "a");
783     static assert(fields[1].stringof == "b");
784 }
785 
786 template fieldWithUDAOf(T, UDAType)
787 {
788     alias allFields = fieldsWithUDAOf!(T, UDAType);
789     static assert(allFields.length > 0);
790     alias fieldWithUDAOf = allFields[0];
791 }
792 unittest
793 {
794     enum Test;
795     {
796         static struct S
797         {
798             @Test string a;
799             string c;
800         }
801         alias field = fieldWithUDAOf!(S, Test);
802         static assert(field.stringof == "a");
803     }
804     {
805         static struct S
806         {
807             @Test string a;
808             @Test string c;
809         }
810         alias field = fieldWithUDAOf!(S, Test);
811         static assert(field.stringof == "a");
812     }
813     {
814         static struct S
815         {
816             string a;
817             string c;
818         }
819         static assert(!__traits(compiles, fieldWithUDAOf!(S, Test)));
820     }
821 }
822 
823 template UDAInfo(alias uda)
824 {
825     static if (is(typeof(uda)))
826     {
827         alias Type = typeof(uda);
828         enum hasDefaultValue = false;
829         enum value = uda;
830     }
831     else static if (is(typeof(uda.init)))
832     {
833         alias Type = uda;
834         enum hasDefaultValue = true;
835         enum value = uda.init;
836     }
837     else
838     {
839         // Even though calling enums a type is technically wrong.
840         alias Type = uda;
841     }
842 }
843 unittest
844 {
845     struct Data { int a; }
846     enum Enum;
847     {
848         @Data int b;
849 
850         alias Info = UDAInfo!(getUDAs!(b, Data));
851         static assert(Info.hasDefaultValue);
852         static assert(is(Info.Type == Data));
853     }
854     {
855         @Data(1) int b;
856 
857         alias Info = UDAInfo!(getUDAs!(b, Data));
858         static assert(!Info.hasDefaultValue);
859         static assert(Info.value == Data(1));
860         static assert(is(Info.Type == Data));
861     }
862     {
863         @Enum int b;
864 
865         alias Info = UDAInfo!(getUDAs!(b, Enum));
866         static assert(is(Info.Type == Enum));
867     }
868 }
869 
870 template countUDAsOf(alias something, UDATypes...)
871 {
872     enum countUDAsOf =
873     (){
874         size_t result = 0;
875         static foreach (UDAType; UDATypes)
876         {
877             static foreach (uda; __traits(getAttributes, something))
878             {
879                 static if (is(UDAInfo!uda.Type == UDAType))
880                     result++;
881             }
882         }
883         return result;
884     }();
885 }
886 
887 PositionalArgumentInfo[] getPositionalArgumentInfosOf(TCommand)() pure
888 {
889     import std.algorithm;
890     import std.range;
891 
892     // Since this is a static foreach, it will be easy to include type info here.
893     PositionalArgumentInfo[] result;
894 
895     static foreach (field; TCommand.tupleof)
896     {{
897         enum hasPositionalUDA = hasUDA!(field, ArgPositional);
898         // Doing this one is tiny bit costly ...
899         enum foldedArgFlags = foldArgumentFlags!(__traits(getAttributes, field));
900         enum hasPositionalFlag = foldedArgFlags.has(ArgFlags._positionalArgumentBit);
901 
902         static if (hasPositionalUDA)
903         {
904             auto info = getArgumentInfo!(ArgPositional, field);
905             if (info.name == "")
906                 info.name = __traits(identifier, field);
907         }
908         else static if (hasPositionalFlag)
909         {
910             alias stringAttributes = getUDAs!(field, string);
911 
912             // TODO: check for string type attribute.
913             static if (stringAttributes.length > 0)
914                 auto info = ArgPositional(__traits(identifier, field), stringAttributes[0]);
915             else
916                 auto info = ArgPositional(__traits(identifier, field), "");
917         }
918 
919         static if (hasPositionalUDA || hasPositionalFlag)
920         {
921             result ~= PositionalArgumentInfo(
922                 info, getCommonArgumentInfo!(field, ArgFlags._positionalArgumentBit));
923         }
924     }}
925 
926     alias isOptional = p => p.flags.has(ArgFlags._optionalBit);
927     alias isRequired = p => p.flags.has(ArgFlags._requiredBit);
928 
929     // If there is an optional positional argument in between required ones,
930     // we change it to positional, if possible. 
931     
932     int indexOfLastRequired = -1;
933     foreach (index, ref p; result)
934     {
935         if (isRequired(p))
936             indexOfLastRequired = cast(int) index;
937     }
938     if (indexOfLastRequired == -1)
939         return result;
940 
941     result
942         .take(indexOfLastRequired)
943         .filter!(isOptional)
944         .each!((ref PositionalArgumentInfo p)
945         {
946             assert(p.flags.has(ArgFlags._mayChangeOptionalityWithoutBreakingThingsBit), 
947                 "The positional argument " ~ p.identifier 
948                 ~ " cannot be optional, because it is in between two positionals.");
949             p.flags &= ~ArgFlags._mayChangeOptionalityWithoutBreakingThingsBit;
950             p.flags &= ~ArgFlags._optionalBit;
951             p.flags |= ArgFlags._requiredBit;
952         });
953 
954     return result;
955 
956     // const notOptionalAfterOptional = positional
957     //     // after one that isn't optional,
958     //     .find!(p => p.flags.has(ArgFlags._optionalBit))
959     //     // there are no optionals.
960     //     .find!(isNotOptional);
961 
962     // if (!notOptionalAfterOptional.empty)
963     // {
964     //     string message = "Found the following non-optional positional arguments after an optional argument: ";
965         
966     //     message ~= notOptionalAfterOptional
967     //         .filter!isNotOptional
968     //         .map!(p => p.argument.identifier)
969     //         .join(", ");
970 
971     //     // TODO: Having a default value should imply optionality only after this check.
972     //     message ~= ". They must either have a default value and or be marked with the optional attribute.";
973 
974     //     assert(false, message);
975     // }
976 }
977 
978 ArgFlags inferArgumentFlagsSpecificToNamedArguments(FieldType)(ArgFlags flags, FieldType fieldDefaultValue) pure
979 {
980     with (ArgFlags)
981     {
982         if (flags.has(_parseAsFlagBit))
983         {
984             static if (is(FieldType == bool) || is(FieldType : Nullable!bool))
985             {
986                 static if (is(FieldType == bool))
987                     assert(fieldDefaultValue == false, 
988                         "Fields marked `parseAsFlag` must have the default value false.");
989                 else
990                     assert(fieldDefaultValue.isNull,
991                         "Nullable fields marked with `parseAsFlag` must have the default value of null.");
992 
993                 string messageIfAddedOptional = getArgumentFlagsValidationMessage(flags | _optionalBit);
994 
995                 // NOTE: This one should always be covered by the flags validation instead.
996                 assert(messageIfAddedOptional is null, "Should never be hit!!\n" ~ messageIfAddedOptional);
997                 
998                 if (flags.doesNotHave(_optionalBit))
999                     flags |= _optionalBit | _inferedOptionalityBit;
1000             }
1001             else
1002             {
1003                 // TODO: maybe allow flags in the future??
1004                 assert(false, "Fields marked `parseAsFlag` must be boolean.");
1005             }
1006         }
1007     }
1008     return flags;
1009 }
1010 
1011 NamedArgumentInfo[] getNamedArgumentInfosOf(TCommand)() pure
1012 {
1013     import std.traits : hasUDA;
1014     NamedArgumentInfo[] result;
1015     
1016     static foreach (field; TCommand.tupleof)
1017     {{
1018         enum hasNamed = hasUDA!(field, ArgNamed);
1019         enum foldedArgFlags = foldArgumentFlags!(__traits(getAttributes, field));
1020         enum hasPositionalFlag = foldedArgFlags.has(ArgFlags._positionalArgumentBit);
1021         enum hasNamedFlag = foldedArgFlags.has(ArgFlags._namedArgumentBit);
1022 
1023         // This check is getting quite complex, so time to refactor to be honest.
1024         enum isSimple = !hasNamed
1025             && !hasUDA!(field, ArgPositional)
1026             && !hasPositionalFlag
1027             && (hasUDA!(field, string) || hasNamedFlag);
1028 
1029         static if (hasNamed)
1030         {
1031             ArgNamed uda = getArgumentInfo!(ArgNamed, field);
1032             if (uda.pattern == Pattern.init)
1033                 uda.pattern = Pattern([__traits(identifier, field)]);
1034         }
1035         else static if (isSimple)
1036         {
1037             alias stringAttributes = getUDAs!(field, string);
1038             static if (stringAttributes.length > 0)
1039                 ArgNamed uda = ArgNamed(__traits(identifier, field), stringAttributes[0]);
1040             else
1041                 ArgNamed uda = ArgNamed(__traits(identifier, field), "");
1042         }
1043         
1044         static if (hasNamed || isSimple)
1045         {
1046             auto argument  = getCommonArgumentInfo!(field, ArgFlags._namedArgumentBit);
1047             argument.flags = inferArgumentFlagsSpecificToNamedArguments!(typeof(field))(
1048                 argument.flags, defaultValueOf!field);
1049             result ~= NamedArgumentInfo(uda, argument);
1050         }
1051     }}
1052     return result;
1053 }