1 /// Contains a type to generate help text for a command.
2 module jaster.cli.commandhelptext;
3 
4 import std.array;
5 import jaster.cli.infogen, jaster.cli.result, jaster.cli.helptext, jaster.cli.binder;
6 
7 /++
8  + A helper struct that will generate help text for a given command.
9  +
10  + Description:
11  +  This struct will construct a `HelpTextBuilderSimple` (via `toBuilder`, or a string via `toString`)
12  +  that is populated via the information provided by the arguments found within `CommandT`, and also the information
13  +  attached to `CommandT` itself.
14  +
15  +  Here is an example of a fully-featured piece of help text generated by this struct:
16  +
17  +  ```
18  +  Usage: mytool MyCommand <InputFile> <OutputFile> <CompressionLevel> [-v|--verbose] [--encoding]
19  +
20  +  Description:
21  +      This is a command that transforms the InputFile into an OutputFile
22  +
23  +  Positional Args:
24  +      InputFile                    - The input file.
25  +      OutputFile                   - The output file.
26  +
27  +  Named Args:
28  +      -v,--verbose                 - Verbose output
29  +
30  +  Utility:
31  +      Utility arguments used to modify the output.
32  +
33  +      CompressionLevel             - How much to compress the file.
34  +      --encoding                   - Sets the encoding to use.
35  +  ```
36  +
37  + The following UDAs are taken into account when generating the help text:
38  +
39  +  * `Command`
40  +
41  +  * `CommandNamedArg`
42  +
43  +  * `CommandPositionalArg`
44  +
45  +  * `CommandArgGroup`
46  +
47  + Furthermore, certain aspects such as whether an argument is nullable or not are reflected within the help text output.
48  +
49  + Params:
50  +  CommandT          = The command to create the help text for.
51  +  ArgBinderInstance = An instance of `ArgBinder`. Currently this is unused, but in the future this may be useful.
52  + ++/
53 struct CommandHelpText(alias CommandT, alias ArgBinderInstance = ArgBinder!())
54 {
55     /// The `CommandInfo` for the `CommandT`, `ArgBinderInstance` combo.
56     enum Info = getCommandInfoFor!(CommandT, ArgBinderInstance);
57 
58     /++
59      + Creates a `HelpTextBuilderSimple` which is populated with all the information available from `CommandT`.
60      +
61      + Params:
62      +  appName = The name of your application, this is displayed within the help text's "usage" string.
63      +
64      + Returns:
65      +  A `HelpTextBuilderSimple` which you can then either further customise, or call `.toString` on.
66      + ++/
67     HelpTextBuilderSimple toBuilder(string appName) const
68     {
69         auto builder = new HelpTextBuilderSimple();
70 
71         void handleGroup(CommandArgGroup uda)
72         {
73             if(uda.isNull)
74                 return;
75 
76             builder.setGroupDescription(uda.name, uda.description);
77         }
78 
79         foreach(arg; Info.namedArgs)
80         {
81             builder.addNamedArg(
82                 (arg.group.isNull) ? null : arg.group.name,
83                 arg.uda.pattern.byEach.array,
84                 arg.uda.description,
85                 cast(ArgIsOptional)((arg.existence & CommandArgExistence.optional) > 0)
86             );
87             handleGroup(arg.group);
88         }
89 
90         foreach(arg; Info.positionalArgs)
91         {
92             builder.addPositionalArg(
93                 (arg.group.isNull) ? null : arg.group.name,
94                 arg.uda.position,
95                 arg.uda.description,
96                 cast(ArgIsOptional)((arg.existence & CommandArgExistence.optional) > 0),
97                 arg.uda.name
98             );
99             handleGroup(arg.group);
100         }
101 
102         builder.commandName = appName ~ " " ~ Info.pattern.defaultPattern;
103         builder.description = Info.description;
104 
105         return builder;
106     }
107 
108     /// Returns: The result of `toBuilder(appName).toString()`.
109     string toString(string appName) const
110     {
111         return this.toBuilder(appName).toString();
112     }
113 }
114 
115 // To get around a limiation of not being able to use Nullable in ArgumentInfo
116 private bool isNull(CommandArgGroup group)
117 {
118     return group == CommandArgGroup.init;
119 }