1 /// Contains the UDAs used and recognised by infogen, and any systems built on top of it.
2 module jaster.cli.infogen.udas;
3 
4 import std.meta : staticMap, Filter;
5 import std.traits;
6 import jaster.cli.infogen, jaster.cli.udas;
7 
8 /++
9  + Attach this to any struct/class that represents the default command.
10  +
11  + See_Also:
12  +  `jaster.cli
13  + ++/
14 struct CommandDefault
15 {
16     /// The command's description.
17     string description = "N/A";
18 }
19 
20 /++
21  + Attach this to any struct/class that represents a command.
22  +
23  + See_Also:
24  +  `jaster.cli.core.CommandLineInterface` for more details.
25  + +/
26 struct Command
27 {
28     /// The pattern used to match against this command. Can contain spaces.
29     Pattern pattern;
30 
31     /// The command's description.
32     string description;
33 
34     ///
35     this(string pattern, string description = "N/A")
36     {
37         this.pattern = Pattern(pattern);
38         this.description = description;
39     }
40 }
41 
42 /++
43  + Attach this to any member field to mark it as a named argument.
44  +
45  + See_Also:
46  +  `jaster.cli.core.CommandLineInterface` for more details.
47  + +/
48 struct CommandNamedArg
49 {
50     /// The pattern used to match against this argument. Cannot contain spaces.
51     Pattern pattern;
52 
53     /// The argument's description.
54     string description;
55 
56     ///
57     this(string pattern, string description = "N/A")
58     {
59         this.pattern = Pattern(pattern);
60         this.description = description;
61     }
62 }
63 
64 /++
65  + Attach this to any member field to mark it as a positional argument.
66  +
67  + See_Also:
68  +  `jaster.cli.core.CommandLineInterface` for more details.
69  + +/
70 struct CommandPositionalArg
71 {
72     /// The position that this argument appears at.
73     size_t position;
74 
75     /// The name of this argument. Used during help-text generation.
76     string name = "VALUE";
77 
78     /// The description of this argument.
79     string description = "N/A";
80 }
81 
82 /++
83  + Attach this to any member field to add it to a help text group.
84  +
85  + See_Also:
86  +  `jaster.cli.core.CommandLineInterface` for more details.
87  + +/
88 struct CommandArgGroup
89 {
90     /// The name of the group to put the arg under.
91     string name;
92 
93     /++
94      + The description of the group.
95      +
96      + Notes:
97      +  The intended usage of this UDA is to apply it to a group of args at the same time, instead of attaching it onto
98      +  singular args:
99      +
100      +  ```
101      +  @CommandArgGroup("group1", "Some description")
102      +  {
103      +      @CommandPositionalArg...
104      +  }
105      +  ```
106      + ++/
107     string description;
108 }
109 
110 /++
111  + Attach this onto a `string[]` member field to mark it as the "raw arg list".
112  +
113  + TLDR; Given the command `"tool.exe command value1 value2 --- value3 value4 value5"`, the member field this UDA is attached to
114  + will be populated as `["value3", "value4", "value5"]`
115  + ++/
116 struct CommandRawListArg {}
117 
118 // Legacy, keep undocumented.
119 alias CommandRawArg = CommandRawListArg;
120 
121 enum isSomeCommand(alias CommandT) = hasUDA!(CommandT, Command) || hasUDA!(CommandT, CommandDefault);
122 
123 enum isSymbol(alias ArgT) = __traits(compiles, __traits(getAttributes, ArgT));
124 
125 enum isRawListArgument(alias ArgT)    = isSymbol!ArgT && hasUDA!(ArgT, CommandRawListArg); // Don't include in isSomeArgument
126 enum isNamedArgument(alias ArgT)      = isSymbol!ArgT && hasUDA!(ArgT, CommandNamedArg);
127 enum isPositionalArgument(alias ArgT) = isSymbol!ArgT && hasUDA!(ArgT, CommandPositionalArg);
128 enum isSomeArgument(alias ArgT)       = isNamedArgument!ArgT || isPositionalArgument!ArgT;
129 
130 package template getCommandArguments(alias CommandT)
131 {
132     static assert(is(CommandT == struct) || is(CommandT == class), "Only classes or structs can be used as commands.");
133     static assert(isSomeCommand!CommandT, "Type "~CommandT.stringof~" is not marked with @Command or @CommandDefault.");
134 
135     alias toSymbol(string name) = __traits(getMember, CommandT, name);
136     alias Members = staticMap!(toSymbol, __traits(allMembers, CommandT));
137     alias getCommandArguments = Filter!(isSomeArgument, Members);
138 }
139 ///
140 unittest
141 {
142     @CommandDefault
143     static struct C 
144     {
145         @CommandNamedArg int a;
146         @CommandPositionalArg int b;
147         int c;
148     }
149 
150     static assert(getCommandArguments!C.length == 2);
151     static assert(getNamedArguments!C.length == 1);
152     static assert(getPositionalArguments!C.length == 1);
153 }
154 
155 package alias getNamedArguments(alias CommandT) = Filter!(isNamedArgument, getCommandArguments!CommandT);
156 package alias getPositionalArguments(alias CommandT) = Filter!(isPositionalArgument, getCommandArguments!CommandT);