1 /**
2  * Copyright: Copyright Jason White, 2016
3  * License:   MIT
4  * Authors:   Jason White
5  *
6  * Description:
7  * All command line interface options.
8  */
9 module button.cli.options;
10 
11 import std.meta : AliasSeq;
12 
13 import darg;
14 
15 import button.exceptions;
16 
17 struct Command
18 {
19     string name;
20 }
21 
22 struct Description
23 {
24     string description;
25 }
26 
27 struct GlobalOptions
28 {
29     @Option("help")
30     @Help("Prints help on command line usage.")
31     OptionFlag help;
32 
33     @Option("version")
34     @Help("Prints version information.")
35     OptionFlag version_;
36 
37     @Argument("command", Multiplicity.optional)
38     string command;
39 
40     @Argument("args", Multiplicity.zeroOrMore)
41     const(string)[] args;
42 }
43 
44 // Generate usage and help strings at compile-time.
45 immutable globalUsage = usageString!GlobalOptions("button");
46 immutable globalHelp  = helpString!GlobalOptions();
47 
48 @Command("help")
49 @Description("Displays help on a given command.")
50 struct HelpOptions
51 {
52     @Argument("command", Multiplicity.optional)
53     @Help("Command to get help on.")
54     string command;
55 }
56 
57 @Command("version")
58 @Description("Prints the current version of the program.")
59 struct VersionOptions
60 {
61 }
62 
63 @Command("build")
64 @Description("Runs a build.")
65 struct BuildOptions
66 {
67     @Option("file", "f")
68     @Help("Path to the build description.")
69     string path;
70 
71     @Option("dryrun", "n")
72     @Help("Don't make any functional changes. Just print what might happen.")
73     OptionFlag dryRun;
74 
75     @Option("threads", "j")
76     @Help("The number of threads to use. Default is the number of logical
77             cores.")
78     @MetaVar("N")
79     size_t threads;
80 
81     @Option("color")
82     @Help("When to colorize the output.")
83     @MetaVar("{auto,never,always}")
84     string color = "auto";
85 
86     @Option("verbose", "v")
87     @Help("Display additional information such as how long each task took to"~
88           " complete.")
89     OptionFlag verbose;
90 
91     @Option("autopilot")
92     @Help("After building, continue watching for changes to inputs and"~
93           " building again as necessary.")
94     OptionFlag autopilot;
95 
96     @Option("watchdir")
97     @Help("Used with `--autopilot`. Directory to watch for changes in. Since"~
98           " FUSE does not work with inotify, this is useful to use when"~
99           " building in a union file system.")
100     string watchDir = ".";
101 
102     @Option("delay")
103     @Help("Used with `--autopilot`. The number of milliseconds to wait for"~
104           " additional changes after receiving a change event before starting"~
105           " a build.")
106     size_t delay = 50;
107 }
108 
109 @Command("graph")
110 @Description("Generates a graph for input into GraphViz.")
111 struct GraphOptions
112 {
113     import button.edgedata : EdgeType;
114 
115     @Option("file", "f")
116     @Help("Path to the build description.")
117     string path;
118 
119     @Option("changes", "C")
120     @Help("Only display the subgraph that will be traversed on an update.")
121     OptionFlag changes;
122 
123     @Option("cached")
124     @Help("Display the cached graph from the previous build.")
125     OptionFlag cached;
126 
127     @Option("full")
128     @Help("Display the full name of each vertex.")
129     OptionFlag full;
130 
131     @Option("edges", "e")
132     @MetaVar("{explicit,implicit,both}")
133     @Help("Type of edges to show.")
134     EdgeType edges = EdgeType.explicit;
135 
136     @Option("threads", "j")
137     @Help("The number of threads to use. Default is the number of logical
138             cores.")
139     @MetaVar("N")
140     size_t threads;
141 }
142 
143 @Command("status")
144 @Description("Prints the status of the build. That is, which files have been
145         modified and which tasks are pending.")
146 struct StatusOptions
147 {
148     @Option("file", "f")
149     @Help("Path to the build description.")
150     string path;
151 
152     @Option("cached")
153     @Help("Display the cached graph from the previous build.")
154     OptionFlag cached;
155 
156     @Option("color")
157     @Help("When to colorize the output.")
158     @MetaVar("{auto,never,always}")
159     string color = "auto";
160 
161     @Option("threads", "j")
162     @Help("The number of threads to use. Default is the number of logical
163             cores.")
164     @MetaVar("N")
165     size_t threads;
166 }
167 
168 @Command("clean")
169 @Description("Deletes all build outputs.")
170 struct CleanOptions
171 {
172     @Option("file", "f")
173     @Help("Path to the build description.")
174     string path;
175 
176     @Option("dryrun", "n")
177     @Help("Don't make any functional changes. Just print what might happen.")
178     OptionFlag dryRun;
179 
180     @Option("threads", "j")
181     @Help("The number of threads to use. Default is the number of logical
182             cores.")
183     @MetaVar("N")
184     size_t threads;
185 
186     @Option("color")
187     @Help("When to colorize the output.")
188     @MetaVar("{auto,never,always}")
189     string color = "auto";
190 
191     @Option("purge")
192     @Help("Delete the build state too.")
193     OptionFlag purge;
194 }
195 
196 @Command("init")
197 @Description("Initializes a directory with an initial build description.")
198 struct InitOptions
199 {
200     @Argument("dir", Multiplicity.optional)
201     @Help("Directory to initialize")
202     string dir = ".";
203 }
204 
205 enum ConvertFormat
206 {
207     bash,
208 }
209 
210 @Command("convert")
211 @Description("Converts the build description to another format for other build systems.")
212 struct ConvertOptions
213 {
214     @Option("file", "f")
215     @Help("Path to the build description.")
216     string path;
217 
218     @Option("format")
219     @Help("Format of build description to convert to. Default is 'bash'.")
220     @MetaVar("{bash}")
221     ConvertFormat type;
222 
223     @Argument("output")
224     @Help("Path to the output file.")
225     @MetaVar("FILE")
226     string output;
227 }
228 
229 @Command("gc")
230 @Description("EXPERIMENTAL")
231 struct GCOptions
232 {
233     @Option("file", "f")
234     @Help("Path to the build description.")
235     string path;
236 
237     @Option("dryrun", "n")
238     @Help("Don't make any functional changes. Just print what might happen.")
239     OptionFlag dryRun;
240 
241     @Option("color")
242     @Help("When to colorize the output.")
243     @MetaVar("{auto,never,always}")
244     string color = "auto";
245 }
246 
247 /**
248  * List of all options structs.
249  */
250 alias OptionsList = AliasSeq!(
251         HelpOptions,
252         VersionOptions,
253         BuildOptions,
254         GraphOptions,
255         StatusOptions,
256         CleanOptions,
257         InitOptions,
258         GCOptions,
259         ConvertOptions,
260         );
261 
262 /**
263  * Using the list of command functions, runs a command from the specified
264  * string.
265  *
266  * Throws: InvalidCommand if the given command name is not valid.
267  */
268 int runCommand(Funcs...)(string name, GlobalOptions opts)
269 {
270     import std.traits : Parameters, getUDAs;
271     import std.format : format;
272 
273     foreach (F; Funcs)
274     {
275         alias Options = Parameters!F[0];
276 
277         alias Commands = getUDAs!(Options, Command);
278 
279         foreach (C; Commands)
280         {
281             if (C.name == name)
282                 return F(parseArgs!Options(opts.args), opts);
283         }
284     }
285 
286     throw new InvalidCommand("button: '%s' is not a valid command. See 'button help'."
287             .format(name));
288 }