comparison dang/OptParse.d @ 211:9e9f3e7e342b default tip

Added dang folder and Module in ast.
author Anders Johnsen <skabet@gmail.com>
date Tue, 12 Aug 2008 20:07:35 +0200
parents 2168f4cb73f1
children
comparison
equal deleted inserted replaced
210:f4149d4f6896 211:9e9f3e7e342b
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