1
|
1 /*
|
|
2 Copyright (c) 2007 Kirk McDonald
|
|
3
|
|
4 Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
5 this software and associated documentation files (the "Software"), to deal in
|
|
6 the Software without restriction, including without limitation the rights to
|
|
7 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
|
8 of the Software, and to permit persons to whom the Software is furnished to do
|
|
9 so, subject to the following conditions:
|
|
10
|
|
11 The above copyright notice and this permission notice shall be included in all
|
|
12 copies or substantial portions of the Software.
|
|
13
|
|
14 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
20 SOFTWARE.
|
|
21 */
|
|
22 /**
|
|
23 * Command-line option parsing, in the style of Python's optparse.
|
|
24 *
|
|
25 * Refer to the complete docs for more information.
|
|
26 */
|
|
27 module dang.OptParse;
|
|
28
|
|
29 import tango.io.Stdout;
|
|
30 import tango.text.Util : locate, locatePrior;
|
|
31 import tango.text.Ascii : toUpper;
|
|
32 import tango.stdc.stdlib : exit, EXIT_FAILURE, EXIT_SUCCESS;
|
|
33 import tango.text.convert.Integer : parse, toInt, toString = toString;
|
|
34 import tango.text.convert.Utf : toString, toString32;
|
|
35
|
|
36 /*
|
|
37 Options may be in two forms: long and short. Short options start with a single
|
|
38 dash and are one letter long. Long options start with two dashes and may
|
|
39 consist of any number of characters (so long as they don't start with a dash,
|
|
40 though they may contain dashes). Options are case-sensitive.
|
|
41
|
|
42 Short options may be combined. The following are equivalent:
|
|
43
|
|
44 $ myapp -a -b -c
|
|
45 $ myapp -abc
|
|
46 $ myapp -ab -c
|
|
47
|
|
48 If -f and --file are aliases of the same option, which accepts an argument,
|
|
49 the following are equivalent:
|
|
50
|
|
51 $ myapp -f somefile.txt
|
|
52 $ myapp -fsomefile.txt
|
|
53 $ myapp --file somefile.txt
|
|
54 $ myapp --file=somefile.txt
|
|
55
|
|
56 The following are also valid:
|
|
57
|
|
58 $ myapp -abcf somefile.txt
|
|
59 $ myapp -abcfsomefile.txt
|
|
60 $ myapp -abc --file somefile.txt
|
|
61
|
|
62 If an option occurs multiple times, the last one is the one recorded:
|
|
63
|
|
64 $ myapp -f somefile.txt --file otherfile.txt
|
|
65
|
|
66 Matches 'otherfile.txt'.
|
|
67 */
|
|
68
|
|
69 bool startswith(char[] s, char[] start) {
|
|
70 if (s.length < start.length) return false;
|
|
71 return s[0 .. start.length] == start;
|
|
72 }
|
|
73 bool endswith(char[] s, char[] end) {
|
|
74 if (s.length < end.length) return false;
|
|
75 return s[$ - end.length .. $] == end;
|
|
76 }
|
|
77
|
|
78 /// Thrown if client code tries to set up an improper option.
|
|
79 class OptionError : Exception {
|
|
80 this(char[] msg) { super(msg); }
|
|
81 }
|
|
82 // Thrown if client code tries to extract the wrong type from an option.
|
|
83 class OptionTypeError : Exception {
|
|
84 this(char[] msg) { super(msg); }
|
|
85 }
|
|
86
|
|
87 /++
|
|
88 This class represents the results after parsing the command-line.
|
|
89 +/
|
|
90 class Options {
|
|
91 char[][][char[]] opts;
|
|
92 int[char[]] counted_opts;
|
|
93 /// By default, leftover arguments are placed in this array.
|
|
94 char[][] args;
|
|
95
|
|
96 /// Retrieves the results of the Store and StoreConst actions.
|
|
97 char[] opIndex(char[] opt) {
|
|
98 char[][]* o = opt in opts;
|
|
99 if (o) {
|
|
100 return (*o)[0];
|
|
101 } else {
|
|
102 return "";
|
|
103 }
|
|
104 }
|
|
105 /// Retrieves the results of the Store action, when the type is Integer.
|
|
106 int value(char[] opt) {
|
|
107 char[][]* o = opt in opts;
|
|
108 if (o) {
|
|
109 return toInt((*o)[0]);
|
|
110 } else {
|
|
111 return 0;
|
|
112 }
|
|
113 }
|
|
114 /// Retrieves the results of the Append and AppendConst actions.
|
|
115 char[][] list(char[] opt) {
|
|
116 char[][]* o = opt in opts;
|
|
117 if (o) {
|
|
118 return *o;
|
|
119 } else {
|
|
120 return null;
|
|
121 }
|
|
122 }
|
|
123 /// Retrieves the results of the Append action, when the type is Integer.
|
|
124 int[] valueList(char[] opt) {
|
|
125 char[][]* o = opt in opts;
|
|
126 int[] l;
|
|
127 if (o) {
|
|
128 l.length = (*o).length;
|
|
129 foreach (i, s; *o) {
|
|
130 l[i] = toInt(s);
|
|
131 }
|
|
132 }
|
|
133 return l;
|
|
134 }
|
|
135 /// Retrieves the results of the Count action.
|
|
136 int count(char[] opt) {
|
|
137 int* c = opt in counted_opts;
|
|
138 if (c) {
|
|
139 return *c;
|
|
140 } else {
|
|
141 return 0;
|
|
142 }
|
|
143 }
|
|
144 /// Retrieves the results of the SetTrue and SetFalse actions.
|
|
145 bool flag(char[] opt) {
|
|
146 char[][]* o = opt in opts;
|
|
147 if (o) {
|
|
148 return (*o)[0] == "1";
|
|
149 } else {
|
|
150 return false;
|
|
151 }
|
|
152 }
|
|
153 }
|
|
154
|
|
155 // Options, args, this opt's index in args, name[, arg]
|
|
156 ///
|
|
157 alias void delegate(Options, inout char[][], inout int, char[], char[]) OptionCallbackFancyArg;
|
|
158 ///
|
|
159 alias void delegate(Options, inout char[][], inout int, char[], int) OptionCallbackFancyInt;
|
|
160 ///
|
|
161 alias void delegate(Options, inout char[][], inout int, char[]) OptionCallbackFancy;
|
|
162
|
|
163 ///
|
|
164 alias void delegate(char[]) OptionCallbackArg;
|
|
165 ///
|
|
166 alias void delegate(int) OptionCallbackInt;
|
|
167 ///
|
|
168 alias void delegate() OptionCallback;
|
|
169
|
|
170 /*
|
|
171 Actions:
|
|
172 * Store: name
|
|
173 * StoreConst: name, const_value
|
|
174 * Append: name
|
|
175 * AppendConst: name, const_value
|
|
176 * Count: name
|
|
177 * CallbackArg: dga
|
|
178 * CallbackVoid: dg
|
|
179 */
|
|
180 ///
|
|
181 enum Action { /+++/Store, /+++/StoreConst, /+++/Append, /+++/AppendConst, /+++/Count, /+++/SetTrue, /+++/SetFalse, /+++/Callback, /+++/CallbackFancy, /+++/Help /+++/}
|
|
182 ///
|
|
183 enum ArgType { /+++/None, /+++/String, /+++/Integer /+++/}
|
|
184
|
|
185 ArgType defaultType(Action action) {
|
|
186 switch (action) {
|
|
187 case Action.Store, Action.Append, Action.Callback, Action.CallbackFancy:
|
|
188 return ArgType.String;
|
|
189 break;
|
|
190 default:
|
|
191 return ArgType.None;
|
|
192 break;
|
|
193 }
|
|
194 }
|
|
195
|
|
196 /++
|
|
197 This class represents a single command-line option.
|
|
198 +/
|
|
199 class Option {
|
|
200 char[][] shortopts, longopts;
|
|
201 Action action;
|
|
202 ArgType type;
|
|
203 char[] name, argname;
|
|
204 char[] const_value;
|
|
205
|
|
206 char[] default_string;
|
|
207 int default_value;
|
|
208 bool default_flag, has_default;
|
|
209
|
|
210 OptionCallbackArg callback;
|
|
211 OptionCallbackInt int_callback;
|
|
212 OptionCallback void_callback;
|
|
213
|
|
214 OptionCallbackFancyArg fancy_callback;
|
|
215 OptionCallbackFancyInt fancy_int_callback;
|
|
216 OptionCallbackFancy fancy_void_callback;
|
|
217 char[] helptext;
|
|
218 this(
|
|
219 char[][] shorts, char[][] longs, ArgType type,
|
|
220 Action act, char[] name, char[] const_value,
|
|
221 OptionCallbackArg dga, OptionCallback dg,
|
|
222 OptionCallbackInt dgi,
|
|
223 OptionCallbackFancyArg fdga,
|
|
224 OptionCallbackFancyInt fdgi,
|
|
225 OptionCallbackFancy fdg
|
|
226 ) {
|
|
227 this.shortopts = shorts;
|
|
228 this.longopts = longs;
|
|
229 this.action = act;
|
|
230 this.type = type;
|
|
231 this.name = name;
|
|
232 this.argname = toUpper(name.dup);
|
|
233 this.default_string = "";
|
|
234 this.default_value = 0;
|
|
235 this.default_flag = false;
|
|
236
|
|
237 // Perform sanity checks.
|
|
238 assert (name !is null);
|
|
239 switch (act) {
|
|
240 case Action.Store, Action.Append:
|
|
241 assert(type != ArgType.None);
|
|
242 break;
|
|
243 case Action.StoreConst, Action.AppendConst:
|
|
244 assert(type == ArgType.None);
|
|
245 assert(const_value !is null);
|
|
246 break;
|
|
247 case Action.Callback:
|
|
248 //assert(type != ArgType.None);
|
|
249 switch (type) {
|
|
250 case ArgType.String:
|
|
251 assert(dga !is null);
|
|
252 break;
|
|
253 case ArgType.Integer:
|
|
254 assert(dgi !is null);
|
|
255 break;
|
|
256 case ArgType.None:
|
|
257 assert(dg !is null);
|
|
258 break;
|
|
259 }
|
|
260 break;
|
|
261 case Action.CallbackFancy:
|
|
262 switch (type) {
|
|
263 case ArgType.String:
|
|
264 assert(fdga !is null);
|
|
265 break;
|
|
266 case ArgType.Integer:
|
|
267 assert(fdgi !is null);
|
|
268 break;
|
|
269 case ArgType.None:
|
|
270 assert(fdg !is null);
|
|
271 break;
|
|
272 }
|
|
273 default:
|
|
274 break;
|
|
275 }
|
|
276 this.const_value = const_value;
|
|
277 this.callback = dga;
|
|
278 this.int_callback = dgi;
|
|
279 this.void_callback = dg;
|
|
280 this.fancy_callback = fdga;
|
|
281 this.fancy_int_callback = fdgi;
|
|
282 this.fancy_void_callback = fdg;
|
|
283 }
|
|
284 char[] toString() {
|
|
285 int optCount = this.shortopts.length + this.longopts.length;
|
|
286 char[] result;
|
|
287 bool printed_arg = false;
|
|
288 foreach(i, opt; this.shortopts ~ this.longopts) {
|
|
289 result ~= opt;
|
|
290 if (i < optCount-1) {
|
|
291 result ~= ", ";
|
|
292 } else if (this.hasArg()) {
|
|
293 result ~= "=" ~ toUpper(this.argname.dup);
|
|
294 }
|
|
295 }
|
|
296 return result;
|
|
297 }
|
|
298 //enum Action { Store, StoreConst, Append, AppendConst, Count, SetTrue, SetFalse, Callback, CallbackFancy, Help }
|
|
299 void issue_default(Options results) {
|
|
300 // Only set the default if the option doesn't already have a value.
|
|
301 char[][]* val = this.name in results.opts;
|
|
302 switch (this.action) {
|
|
303 case Action.Store, Action.Append:
|
|
304 if (val !is null) return;
|
|
305 if (this.type == ArgType.String) {
|
|
306 results.opts[name] = [default_string];
|
|
307 } else {
|
|
308 results.opts[name] = [.toString(default_value)];
|
|
309 }
|
|
310 break;
|
|
311 case Action.StoreConst, Action.AppendConst:
|
|
312 if (val !is null) return;
|
|
313 results.opts[name] = [default_string];
|
|
314 break;
|
|
315 case Action.SetTrue, Action.SetFalse:
|
|
316 if (val !is null) return;
|
|
317 if (default_flag) {
|
|
318 results.opts[name] = ["1"];
|
|
319 } else {
|
|
320 results.opts[name] = ["0"];
|
|
321 }
|
|
322 break;
|
|
323 default:
|
|
324 return;
|
|
325 }
|
|
326 }
|
|
327 // Does whatever this option is supposed to do.
|
|
328 void performAction(OptionParser parser, Options results, inout char[][] args, inout int idx, char[] arg) {
|
|
329 int i;
|
|
330 if (this.type == ArgType.Integer) {
|
|
331 // Verify that it's an int.
|
|
332 i = parser.toOptInt(arg);
|
|
333 }
|
|
334 switch (this.action) {
|
|
335 case Action.Store:
|
|
336 results.opts[name] = [arg];
|
|
337 break;
|
|
338 case Action.Append:
|
|
339 results.opts[name] ~= arg;
|
|
340 break;
|
|
341 case Action.StoreConst:
|
|
342 assert(arg is null, "Got unexpected argument for '"~name~"' option.");
|
|
343 results.opts[name] = [const_value];
|
|
344 break;
|
|
345 case Action.AppendConst:
|
|
346 assert(arg is null, "Got unexpected argument for '"~name~"' option.");
|
|
347 results.opts[name] ~= const_value;
|
|
348 break;
|
|
349 case Action.Count:
|
|
350 assert(arg is null, "Got unexpected argument for '"~name~"' option.");
|
|
351 ++results.counted_opts[name];
|
|
352 break;
|
|
353 case Action.SetTrue:
|
|
354 results.opts[name] = ["1"];
|
|
355 break;
|
|
356 case Action.SetFalse:
|
|
357 results.opts[name] = ["0"];
|
|
358 break;
|
|
359 case Action.Callback:
|
|
360 switch (type) {
|
|
361 case ArgType.String:
|
|
362 callback(arg);
|
|
363 break;
|
|
364 case ArgType.Integer:
|
|
365 int_callback(i);
|
|
366 break;
|
|
367 case ArgType.None:
|
|
368 void_callback();
|
|
369 break;
|
|
370 }
|
|
371 break;
|
|
372 case Action.CallbackFancy:
|
|
373 switch (type) {
|
|
374 case ArgType.String:
|
|
375 fancy_callback(results, args, idx, name, arg);
|
|
376 break;
|
|
377 case ArgType.Integer:
|
|
378 fancy_int_callback(results, args, idx, name, i);
|
|
379 break;
|
|
380 case ArgType.None:
|
|
381 fancy_void_callback(results, args, idx, name);
|
|
382 break;
|
|
383 }
|
|
384 break;
|
|
385 case Action.Help:
|
|
386 parser.helpText();
|
|
387 exit(EXIT_SUCCESS);
|
|
388 break;
|
|
389 }
|
|
390 }
|
|
391 /// Returns whether this option accepts an argument.
|
|
392 bool hasArg() {
|
|
393 return this.type != ArgType.None;
|
|
394 }
|
|
395 /// Sets the help text for this option.
|
|
396 Option help(char[] help) {
|
|
397 this.helptext = help;
|
|
398 return this;
|
|
399 }
|
|
400 /// Sets the name of this option's argument, if it has one.
|
|
401 Option argName(char[] argname) {
|
|
402 this.argname = argname;
|
|
403 return this;
|
|
404 }
|
|
405 Option def(char[] val) {
|
|
406 if (
|
|
407 (this.type != ArgType.String || (this.action != Action.Store && this.action != Action.Append)) &&
|
|
408 this.action != Action.StoreConst && this.action != Action.AppendConst
|
|
409 )
|
|
410 throw new OptionError("Cannot specify string default for non-string option '"~this.name~"'");
|
|
411 this.has_default = true;
|
|
412 this.default_string = val;
|
|
413 return this;
|
|
414 }
|
|
415 Option def(int val) {
|
|
416 if (this.type != ArgType.Integer || (this.action != Action.Store && this.action != Action.Append))
|
|
417 throw new OptionError("Cannot specify integer default for non-integer option '"~this.name~"'");
|
|
418 this.has_default = true;
|
|
419 this.default_value = val;
|
|
420 return this;
|
|
421 }
|
|
422 Option def(bool val) {
|
|
423 if (this.action != Action.SetTrue && this.action != Action.SetFalse)
|
|
424 throw new OptionError("Cannot specify boolean default for non-flag option '"~this.name~"'");
|
|
425 this.has_default = true;
|
|
426 this.default_flag = val;
|
|
427 return this;
|
|
428 }
|
|
429 // Returns true if the passed option string matches this option.
|
|
430 bool matches(char[] _arg) {
|
|
431 dchar[] arg = toString32(_arg);
|
|
432 if (
|
|
433 arg.length < 2 ||
|
|
434 arg.length == 2 && (arg[0] != '-' || arg[1] == '-') ||
|
|
435 arg.length > 2 && (arg[0 .. 2] != "--" || arg[2] == '-')
|
|
436 ) {
|
|
437 return false;
|
|
438 }
|
|
439 if (arg.length == 2) {
|
|
440 foreach (opt; shortopts) {
|
|
441 if (_arg == opt) {
|
|
442 return true;
|
|
443 }
|
|
444 }
|
|
445 } else {
|
|
446 foreach (opt; longopts) {
|
|
447 if (_arg == opt) {
|
|
448 return true;
|
|
449 }
|
|
450 }
|
|
451 }
|
|
452 return false;
|
|
453 }
|
|
454 }
|
|
455
|
|
456 /++
|
|
457 This class is used to define a set of options, and parse the command-line
|
|
458 arguments.
|
|
459 +/
|
|
460 class OptionParser {
|
|
461 OptionCallbackArg leftover_cb;
|
|
462 /// An array of all of the options known by this parser.
|
|
463 Option[] options;
|
|
464 char[] name, desc;
|
|
465 /// The description of the programs arguments, as used in the Help action.
|
|
466 char[] argdesc;
|
|
467 private void delegate(char[]) error_callback;
|
|
468
|
|
469 this(char[] desc="") {
|
|
470 this.name = "";
|
|
471 this.desc = desc;
|
|
472 this.argdesc = "[options] args...";
|
|
473 }
|
|
474
|
|
475 /// Sets a callback, to override the default error behavior.
|
|
476 void setErrorCallback(void delegate(char[]) dg) {
|
|
477 error_callback = dg;
|
|
478 }
|
|
479 void unknownOptError(char[] opt) {
|
|
480 error("Unknown argument '"~opt~"'");
|
|
481 }
|
|
482 void expectedArgError(char[] opt) {
|
|
483 error("'"~opt~"' option expects an argument.");
|
|
484 }
|
|
485 /// Displays an error message and terminates the program.
|
|
486 void error(char[] err) {
|
|
487 if (error_callback !is null) {
|
|
488 error_callback(err);
|
|
489 } else {
|
|
490 this.helpText();
|
|
491 Stdout.formatln(err);
|
|
492 }
|
|
493 exit(EXIT_FAILURE);
|
|
494 }
|
|
495 int toOptInt(char[] s) {
|
|
496 int i;
|
|
497 uint ate;
|
|
498 i = .parse(s, 10u, &ate);
|
|
499 if (ate != s.length)
|
|
500 error("Could not convert '"~s~"' to an integer.");
|
|
501 return i;
|
|
502 }
|
|
503
|
|
504 /// Displays useful "help" information about the program's options.
|
|
505 void helpText() {
|
|
506 int optWidth;
|
|
507 char[][] optStrs;
|
|
508 typedef char spacechar = ' ';
|
|
509 spacechar[] padding;
|
|
510 // Calculate the maximum width of the option lists.
|
|
511 foreach(i, opt; options) {
|
|
512 optStrs ~= opt.toString();
|
|
513 if (optStrs[i].length > optWidth) {
|
|
514 optWidth = optStrs[i].length;
|
|
515 }
|
|
516 }
|
|
517 Stdout.formatln("Usage: {0} {1}", this.name, this.argdesc);
|
|
518 if (this.desc !is null && this.desc != "") Stdout.formatln(this.desc);
|
|
519 Stdout.formatln("\nOptions:");
|
|
520 foreach(i, opt; options) {
|
|
521 padding.length = optWidth - optStrs[i].length;
|
|
522 Stdout.formatln(" {0}{1} {2}", optStrs[i], cast(char[])padding, opt.helptext);
|
|
523 }
|
|
524 }
|
|
525
|
|
526 // Checks the passed arg against all the options in the parser.
|
|
527 // Returns null if no match is found.
|
|
528 Option matches(char[] arg) {
|
|
529 foreach(o; options) {
|
|
530 if (o.matches(arg)) {
|
|
531 return o;
|
|
532 }
|
|
533 }
|
|
534 return null;
|
|
535 }
|
|
536 char[] getBaseName(char[] path) {
|
|
537 version(Windows) {
|
|
538 char delimiter = '\\';
|
|
539 } else {
|
|
540 char delimiter = '/';
|
|
541 }
|
|
542 uint idx = locatePrior(path, delimiter);
|
|
543 if (idx == path.length) return path;
|
|
544 return path[idx+1 .. $];
|
|
545 }
|
|
546 char[] getProgramName(char[] path) {
|
|
547 version(Windows) {
|
|
548 // (Unicode note: ".exe" only contains 4 code units, so this slice
|
|
549 // should Just Work.) (Although it remains to be seen how robust
|
|
550 // this code actually is.)
|
|
551 //Stdout.formatln(path);
|
|
552 //assert(path[$-4 .. $] == ".exe");
|
|
553 //path = path[0 .. $-4];
|
|
554 }
|
|
555 return getBaseName(path);
|
|
556 }
|
|
557 /// Parses the passed command-line arguments and returns the results.
|
|
558 Options parse(char[][] args) {
|
|
559 this.name = getProgramName(args[0]);
|
|
560 args = args[1 .. $];
|
|
561 Options options = new Options;
|
|
562 /*
|
|
563 The issue is this:
|
|
564
|
|
565 $ myapp -abc
|
|
566
|
|
567 This might be three short opts, or one or two opts, the last of which
|
|
568 accepts an argument. In the three-opt case, we want to get:
|
|
569
|
|
570 $ myapp -a -b -c
|
|
571
|
|
572 In the one-opt case, we want:
|
|
573
|
|
574 $ myapp -a bc
|
|
575
|
|
576 In the two-opt case, we want:
|
|
577
|
|
578 $ myapp -a -b c
|
|
579
|
|
580 We also want to parse apart "--file=somefile" into "--file somefile"
|
|
581 */
|
|
582 char[] opt, newopt, arg;
|
|
583 dchar[] opt32;
|
|
584 int idx;
|
|
585 Option match;
|
|
586
|
|
587 for (int i=0; i<args.length; ++i) {
|
|
588 opt = args[i];
|
|
589 // -- ends the option list, the remainder is dumped into args
|
|
590 if (opt == "--") {
|
|
591 if (this.leftover_cb !is null) {
|
|
592 foreach(a; args[i+1 .. $]) {
|
|
593 this.leftover_cb(a);
|
|
594 }
|
|
595 } else {
|
|
596 options.args ~= args[i+1 .. $];
|
|
597 }
|
|
598 i = args.length;
|
|
599 } else if (opt.startswith("--")) {
|
|
600 idx = locate(opt, '=');
|
|
601 if (idx != opt.length) {
|
|
602 newopt = opt[0 .. idx];
|
|
603 // Stitch out the old arg, stitch in the newopt, arg pair.
|
|
604 // (Unicode note: idx+1 works, since we know '=' is a
|
|
605 // single code unit.)
|
|
606 args = args[0 .. i] ~ [newopt, opt[idx+1 .. $]] ~ args[i+1 .. $];
|
|
607 } else {
|
|
608 newopt = opt;
|
|
609 }
|
|
610 match = matches(newopt);
|
|
611 if (match is null) {
|
|
612 unknownOptError(newopt);
|
|
613 }
|
|
614 if (match.hasArg) {
|
|
615 if (i == args.length-1) expectedArgError(match.name);
|
|
616 arg = args[i+1];
|
|
617 ++i;
|
|
618 } else {
|
|
619 arg = null;
|
|
620 }
|
|
621 match.performAction(this, options, args, i, arg);
|
|
622 } else if (opt.startswith("-")) {
|
|
623 if (opt.length >= 2) {
|
|
624 opt32 = toString32(opt[1 .. $]);
|
|
625 foreach (j, c; opt32) {
|
|
626 newopt = .toString("-" ~ [c]);
|
|
627 match = matches(newopt);
|
|
628 if (match is null) {
|
|
629 unknownOptError(newopt);
|
|
630 }
|
|
631 if (match.hasArg) {
|
|
632 // This is the last char in the group, look to the
|
|
633 // next element of args for the arg.
|
|
634 if (j == opt32.length-1) {
|
|
635 if (i == args.length-1) expectedArgError(match.name);
|
|
636 arg = args[i+1];
|
|
637 ++i;
|
|
638 // Otherwise, consume the rest of this group for
|
|
639 // the arg.
|
|
640 } else {
|
|
641 arg = .toString(opt32[j+1 .. $]);
|
|
642 match.performAction(this, options, args, i, arg);
|
|
643 break;
|
|
644 }
|
|
645 } else {
|
|
646 arg = null;
|
|
647 }
|
|
648 match.performAction(this, options, args, i, arg);
|
|
649 }
|
|
650 } else {
|
|
651 unknownOptError(opt);
|
|
652 }
|
|
653 } else {
|
|
654 if (this.leftover_cb is null) {
|
|
655 options.args ~= opt;
|
|
656 } else {
|
|
657 this.leftover_cb(opt);
|
|
658 }
|
|
659 }
|
|
660 }
|
|
661 foreach (o; this.options) {
|
|
662 o.issue_default(options);
|
|
663 }
|
|
664 return options;
|
|
665 }
|
|
666
|
|
667 /++
|
|
668 Overrides the default behavior of leftover arguments, calling this callback
|
|
669 with them instead of adding them an array.
|
|
670 +/
|
|
671 void leftoverCallback(OptionCallbackArg dg) {
|
|
672 this.leftover_cb = dg;
|
|
673 }
|
|
674 ///
|
|
675 Option addOption(Option option) {
|
|
676 this.options ~= option;
|
|
677 return option;
|
|
678 }
|
|
679 // options action name type const_value dga dgv dgi fdga fdgi fdg
|
|
680 ///
|
|
681 Option addOption(char[][] options ...) {
|
|
682 return addOption(options, Action.Store, null, defaultType(Action.Store), null, null, null, null, null, null, null);
|
|
683 }
|
|
684 ///
|
|
685 Option addOption(char[][] options, char[] name) {
|
|
686 return addOption(options, Action.Store, name, defaultType(Action.Store), null, null, null, null, null, null, null);
|
|
687 }
|
|
688 ///
|
|
689 Option addOption(char[][] options, Action action) {
|
|
690 return addOption(options, action, null, defaultType(action), null, null, null, null, null, null, null);
|
|
691 }
|
|
692 ///
|
|
693 Option addOption(char[][] options, ArgType type) {
|
|
694 return addOption(options, Action.Store, null, type, null, null, null, null, null, null, null);
|
|
695 }
|
|
696 ///
|
|
697 Option addOption(char[][] options, Action action, ArgType type) {
|
|
698 return addOption(options, action, null, type, null, null, null, null, null, null, null);
|
|
699 }
|
|
700 ///
|
|
701 Option addOption(char[][] options, char[] name, Action action) {
|
|
702 return addOption(options, action, name, defaultType(action), null, null, null, null, null, null, null);
|
|
703 }
|
|
704 ///
|
|
705 Option addOption(char[][] options, char[] name, Action action, ArgType type) {
|
|
706 return addOption(options, action, name, type, null, null, null, null, null, null, null);
|
|
707 }
|
|
708 ///
|
|
709 Option addOption(char[][] options, Action action, char[] const_value) {
|
|
710 return addOption(options, action, null, defaultType(action), const_value, null, null, null, null, null, null);
|
|
711 }
|
|
712 ///
|
|
713 Option addOption(char[][] options, char[] name, char[] const_value) {
|
|
714 return addOption(options, Action.StoreConst, name, defaultType(Action.Store), const_value, null, null, null, null, null, null);
|
|
715 }
|
|
716 ///
|
|
717 Option addOption(char[][] options, char[] name, Action action, char[] const_value) {
|
|
718 return addOption(options, action, name, defaultType(action), const_value, null, null, null, null, null, null);
|
|
719 }
|
|
720 ///
|
|
721 Option addOption(char[][] options, OptionCallbackArg dg) {
|
|
722 return addOption(options, Action.Callback, null, ArgType.String, null, dg, null, null, null, null, null);
|
|
723 }
|
|
724 ///
|
|
725 Option addOption(char[][] options, OptionCallback dg) {
|
|
726 return addOption(options, Action.Callback, null, ArgType.None, null, null, dg, null, null, null, null);
|
|
727 }
|
|
728 ///
|
|
729 Option addOption(char[][] options, OptionCallbackInt dg) {
|
|
730 return addOption(options, Action.Callback, null, ArgType.Integer, null, null, null, dg, null, null, null);
|
|
731 }
|
|
732 ///
|
|
733 Option addOption(char[][] options, OptionCallbackFancyArg dg) {
|
|
734 return addOption(options, Action.CallbackFancy, null, ArgType.String, null, null, null, null, dg, null, null);
|
|
735 }
|
|
736 ///
|
|
737 Option addOption(char[][] options, OptionCallbackFancy dg) {
|
|
738 return addOption(options, Action.CallbackFancy, null, ArgType.None, null, null, null, null, null, null, dg);
|
|
739 }
|
|
740 ///
|
|
741 Option addOption(char[][] options, OptionCallbackFancyInt dg) {
|
|
742 return addOption(options, Action.CallbackFancy, null, ArgType.Integer, null, null, null, null, null, dg, null);
|
|
743 }
|
|
744 ///
|
|
745 Option addOption(char[][] options, char[] name, OptionCallbackFancyArg dg) {
|
|
746 return addOption(options, Action.CallbackFancy, name, ArgType.String, null, null, null, null, dg, null, null);
|
|
747 }
|
|
748 ///
|
|
749 Option addOption(char[][] options, char[] name, OptionCallbackFancy dg) {
|
|
750 return addOption(options, Action.CallbackFancy, name, ArgType.None, null, null, null, null, null, null, dg);
|
|
751 }
|
|
752 ///
|
|
753 Option addOption(char[][] options, char[] name, OptionCallbackFancyInt dg) {
|
|
754 return addOption(options, Action.CallbackFancy, name, ArgType.Integer, null, null, null, null, null, dg, null);
|
|
755 }
|
|
756 // Although users certainly /can/ call this, all those overloads are there
|
|
757 // for a reason.
|
|
758 Option addOption(
|
|
759 char[][] options,
|
|
760 Action action, char[] name, ArgType type, char[] const_value,
|
|
761 OptionCallbackArg callback, OptionCallback vcall,
|
|
762 OptionCallbackInt icall,
|
|
763 OptionCallbackFancyArg fdga,
|
|
764 OptionCallbackFancyInt fdgi,
|
|
765 OptionCallbackFancy fdg
|
|
766 ) {
|
|
767 char[][] shortopts;
|
|
768 char[][] longopts;
|
|
769 dchar[] opt;
|
|
770 Option option;
|
|
771 foreach (_opt; options) {
|
|
772 // (Unicode note: We convert to dchar[] so the length checks work
|
|
773 // out in the event of a short opt with a >127 character.)
|
|
774 opt = toString32(_opt);
|
|
775 if (opt.length < 2) {
|
|
776 throw new OptionError(
|
|
777 "invalid option string '" ~ _opt ~ "': must be at least two characters long"
|
|
778 );
|
|
779 } else if (opt.length > 2) {
|
|
780 if (opt[0 .. 2] != "--" || opt[2] == '-')
|
|
781 throw new OptionError(
|
|
782 "invalid long option string '" ~ _opt ~ "': must start with --, followed by non-dash"
|
|
783 );
|
|
784 longopts ~= _opt;
|
|
785 } else {
|
|
786 if (opt[0] != '-' || opt[1] == '-')
|
|
787 throw new OptionError(
|
|
788 "invalid short option string '" ~ _opt ~ "': must be of the form -x, where x is non-dash"
|
|
789 );
|
|
790 shortopts ~= _opt;
|
|
791 }
|
|
792 }
|
|
793 if (name is null) {
|
|
794 // (Unicode note: We know '-' is a single code unit, so these
|
|
795 // slices are okay.)
|
|
796 if (longopts.length > 0)
|
|
797 name = longopts[0][2 .. $];
|
|
798 else if (shortopts.length > 0)
|
|
799 name = shortopts[0][1 .. 2];
|
|
800 else
|
|
801 throw new OptionError(
|
|
802 "No options provided to addOption!"
|
|
803 );
|
|
804 }
|
|
805 option = new Option(shortopts, longopts, type, action, name, const_value, callback, vcall, icall, fdga, fdgi, fdg);
|
|
806 this.options ~= option;
|
|
807 return option;
|
|
808 }
|
|
809 }
|
|
810
|