root/misc/optimpl.d

Revision 130, 30.4 kB (checked in by KirkMcDonald, 7 years ago)

Split optparse into two modules, one with the command-line interface, one allowing the use of optparse outside of the command-line.

Line 
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 2007-08-15 - Added changes to make GDC happy, submitted by Tim Burrell.
24 */
25 /**
26  * Command-line option parsing, in the style of Python's optparse.
27  *
28  * Refer to the complete docs for more information.
29  */
30 module optimpl;
31
32 version (Tango) {
33     import tango.text.Util : find = locate, locatePrior;
34     import tango.text.Ascii : toupper = toUpper;
35     import tango.stdc.stdlib : exit, EXIT_FAILURE, EXIT_SUCCESS;
36     import tango.text.convert.Integer : parse, toInt, toString = toUtf8;
37     import tango.text.convert.Utf : toUTF8 = toUtf8, toUTF32 = toUtf32;
38     int getNotFound(char[] s) {
39         return s.length;
40     }
41     //alias char[] string;
42 } else {
43     import std.string : find, toupper, toString;
44     import std.c.stdlib : exit, EXIT_FAILURE, EXIT_SUCCESS;
45     import std.conv : toInt, ConvError;
46     import std.path : getBaseName;
47     import std.utf : toUTF8, toUTF32;
48     int getNotFound(char[] s) {
49         return -1;
50     }
51 }
52
53 /*
54 Options may be in two forms: long and short. Short options start with a single
55 dash and are one letter long. Long options start with two dashes and may
56 consist of any number of characters (so long as they don't start with a dash,
57 though they may contain dashes). Options are case-sensitive.
58
59 Short options may be combined. The following are equivalent:
60
61 $ myapp -a -b -c
62 $ myapp -abc
63 $ myapp -ab -c
64
65 If -f and --file are aliases of the same option, which accepts an argument,
66 the following are equivalent:
67
68 $ myapp -f somefile.txt
69 $ myapp -fsomefile.txt
70 $ myapp --file somefile.txt
71 $ myapp --file=somefile.txt
72
73 The following are also valid:
74
75 $ myapp -abcf somefile.txt
76 $ myapp -abcfsomefile.txt
77 $ myapp -abc --file somefile.txt
78
79 If an option occurs multiple times, the last one is the one recorded:
80
81 $ myapp -f somefile.txt --file otherfile.txt
82
83 Matches 'otherfile.txt'.
84 */
85
86 bool startswith(char[] s, char[] start) {
87     if (s.length < start.length) return false;
88     return s[0 .. start.length] == start;
89 }
90 bool endswith(char[] s, char[] end) {
91     if (s.length < end.length) return false;
92     return s[$ - end.length .. $] == end;
93 }
94
95 /// Thrown if client code tries to set up an improper option.
96 class OptionError : Exception {
97     this(char[] msg) { super(msg); }
98 }
99 /// Thrown if client code tries to extract the wrong type from an option.
100 class OptionTypeError : Exception {
101     this(char[] msg) { super(msg); }
102 }
103 /// Thrown if there is a problem while parsing the command-line.
104 class OptionParsingError : Exception {
105     this(char[] msg) { super(msg); }
106 }
107
108 /++
109 This class represents the results after parsing the command-line.
110 +/
111 class Options {
112     char[][][char[]] opts;
113     int[char[]] counted_opts;
114     /// By default, leftover arguments are placed in this array.
115     char[][] args;
116
117     /// Retrieves the results of the Store and StoreConst actions.
118     char[] opIndex(char[] opt) {
119         char[][]* o = opt in opts;
120         if (o) {
121             return (*o)[0];
122         } else {
123             return "";
124         }
125     }
126     /// Retrieves the results of the Store action, when the type is Integer.
127     int value(char[] opt) {
128         char[][]* o = opt in opts;
129         if (o) {
130             return toInt((*o)[0]);
131         } else {
132             return 0;
133         }
134     }
135     /// Retrieves the results of the Append and AppendConst actions.
136     char[][] list(char[] opt) {
137         char[][]* o = opt in opts;
138         if (o) {
139             return *o;
140         } else {
141             return null;
142         }
143     }
144     /// Retrieves the results of the Append action, when the type is Integer.
145     int[] valueList(char[] opt) {
146         char[][]* o = opt in opts;
147         int[] l;
148         if (o) {
149             l.length = (*o).length;
150             foreach (i, s; *o) {
151                 l[i] = toInt(s);
152             }
153         }
154         return l;
155     }
156     /// Retrieves the results of the Count action.
157     int count(char[] opt) {
158         int* c = opt in counted_opts;
159         if (c) {
160             return *c;
161         } else {
162             return 0;
163         }
164     }
165     /// Retrieves the results of the SetTrue and SetFalse actions.
166     bool flag(char[] opt) {
167         char[][]* o = opt in opts;
168         if (o) {
169             return (*o)[0] == "1";
170         } else {
171             return false;
172         }
173     }
174     private {
175         void storeArg(char[] name, char[] arg) {
176             opts[name] = [arg];
177         }
178         void storeArg(char[] name, bool arg) {
179             if (arg) {
180                 opts[name] = ["1"];
181             } else {
182                 opts[name] = ["0"];
183             }
184         }
185         void appendArg(char[] name, char[] arg) {
186             opts[name] ~= arg;
187         }
188         void increment(char[] name) {
189             ++counted_opts[name];
190         }
191         bool hasName(char[] name) {
192             return name in opts || name in counted_opts;
193         }
194     }
195 }
196
197 // Options, args, this opt's index in args, name[, arg]
198 ///
199 alias void delegate(Options, inout char[][], inout int, char[], char[]) OptionCallbackFancyArg;
200 ///
201 alias void delegate(Options, inout char[][], inout int, char[], int)    OptionCallbackFancyInt;
202 ///
203 alias void delegate(Options, inout char[][], inout int, char[])         OptionCallbackFancy;
204
205 ///
206 alias void delegate(char[]) OptionCallbackArg;
207 ///
208 alias void delegate(int)    OptionCallbackInt;
209 ///
210 alias void delegate()       OptionCallback;
211
212 ///
213 enum Action { /+++/Store, /+++/StoreConst, /+++/Append, /+++/AppendConst, /+++/Count, /+++/SetTrue, /+++/SetFalse, /+++/Callback, /+++/CallbackFancy, /+++/Help /+++/}
214 ///
215 enum ArgType { /+/+++/None,+/ /+++/String, /+++/Integer, /+++/Bool /+++/}
216
217 /++
218 This class represents a single command-line option.
219 +/
220 abstract class Option {
221     char[][] shortopts, longopts;
222     char[] name, argname;
223     char[] helptext;
224     this(char[][] options, char[] name) {
225         dchar[] opt;
226         foreach (_opt; options) {
227             // (Unicode note: We convert to dchar[] so the length checks work
228             // out in the event of a short opt with a >127 character.)
229             opt = toUTF32(_opt);
230             if (opt.length < 2) {
231                 throw new OptionError(
232                     "invalid option string '" ~ _opt ~ "': must be at least two characters long"
233                 );
234             } else if (opt.length > 2) {
235                 if (opt[0 .. 2] != "--" || opt[2] == '-')
236                     throw new OptionError(
237                         "invalid long option string '" ~ _opt ~ "': must start with --, followed by non-dash"
238                     );
239                 longopts ~= _opt;
240             } else {
241                 if (opt[0] != '-' || opt[1] == '-')
242                     throw new OptionError(
243                         "invalid short option string '" ~ _opt ~ "': must be of the form -x, where x is non-dash"
244                     );
245                 shortopts ~= _opt;
246             }
247         }
248         if (name is null) {
249             // (Unicode note: We know '-' is a single code unit, so these
250             // slices are okay.)
251             if (longopts.length > 0)
252                 this.name = longopts[0][2 .. $];
253             else if (shortopts.length > 0)
254                 this.name = shortopts[0][1 .. 2];
255             else
256                 throw new OptionError(
257                     "No options provided to addOption!"
258                 );
259         } else {
260             this.name = name;
261         }
262         argname = toupper(this.name.dup);
263     }
264     char[] toString() {
265         int optCount = this.shortopts.length + this.longopts.length;
266         char[] result;
267         //bool printed_arg = false;
268         foreach(i, opt; this.shortopts ~ this.longopts) {
269             result ~= opt;
270             if (i < optCount-1) {
271                 result ~= ", ";
272             } else if (this.hasArg()) {
273                 result ~= "=" ~ toupper(this.argname.dup);
274             }
275         }
276         return result;
277     }
278     //enum Action { Store, StoreConst, Append, AppendConst, Count, SetTrue, SetFalse, Callback, CallbackFancy, Help }
279     bool supports_default() {
280         return false;
281     }
282     ArgType def_type() {
283         throw new OptionError("Option "~name~" does not support default arguments.");
284     }
285     bool has_default() {
286         throw new OptionError("Option "~name~" does not support default arguments.");
287     }
288     void issue_default(Options results) {
289         return;
290     }
291     /// Does whatever this option is supposed to do.
292     abstract void performAction(OptionParser parser, Options results, inout char[][] args, inout int idx, char[] arg);
293     /// Returns whether this option accepts an argument.
294     bool hasArg() {
295         return false;
296     }
297     /// Sets the help text for this option.
298     Option help(char[] help) {
299         this.helptext = help;
300         return this;
301     }
302     /// Sets the name of this option's argument, if it has one.
303     Option argName(char[] argname) {
304         this.argname = argname;
305         return this;
306     }
307     Option def(char[] val) {
308         throw new OptionError("Cannot specify string default for non-string option '"~this.name~"'");
309     }
310     Option def(int val) {
311         throw new OptionError("Cannot specify integer default for non-integer option '"~this.name~"'");
312     }
313     Option def(bool val) {
314         throw new OptionError("Cannot specify boolean default for non-flag option '"~this.name~"'");
315     }
316     // Returns true if the passed option string matches this option.
317     bool matches(char[] _arg) {
318         dchar[] arg = toUTF32(_arg);
319         if (
320             arg.length < 2 ||
321             arg.length == 2 && (arg[0] != '-' || arg[1] == '-') ||
322             arg.length > 2 && (arg[0 .. 2] != "--" || arg[2] == '-')
323         ) {
324             return false;
325         }
326         if (arg.length == 2) {
327             foreach (opt; shortopts) {
328                 if (_arg == opt) {
329                     return true;
330                 }
331             }
332         } else {
333             foreach (opt; longopts) {
334                 if (_arg == opt) {
335                     return true;
336                 }
337             }
338         }
339         return false;
340     }
341 }
342
343 abstract class ArgOption : Option {
344     static const ArgType default_type = ArgType.String;
345     ArgType type;
346     bool _has_default = false;
347     char[] default_string;
348     this(char[][] options, char[] name, ArgType type) {
349         super(options, name);
350         if (type != ArgType.Integer && type != ArgType.String) {
351             throw new OptionError("Argument type for Store and Append must be Integer or String.");
352         }
353         this.type = type;
354     }
355     override void performAction(OptionParser parser, Options results, inout char[][] args, inout int idx, char[] arg) {
356         if (this.type == ArgType.Integer) {
357             // Verify that it's an int.
358             int i = parser.toOptInt(arg);
359         }
360     }
361     override ArgType def_type() {
362         return this.type;
363     }
364     override Option def(char[] val) {
365         if (this.type != ArgType.String)
366             super.def(val);
367         this._has_default = true;
368         this.default_string = val;
369         return this;
370     }
371     override Option def(int val) {
372         if (this.type != ArgType.Integer)
373             super.def(val);
374         this._has_default = true;
375         this.default_string = .toString(val);
376         return this;
377     }
378     override bool supports_default() {
379         return true;
380     }
381     override bool has_default() {
382         return _has_default;
383     }
384     override void issue_default(Options results) {
385         if (_has_default) results.storeArg(this.name, this.default_string);
386     }
387     override bool hasArg() {
388         return true;
389     }
390 }
391
392 class Store : ArgOption {
393     this(char[][] options, char[] name, ArgType type=Store.default_type) {
394         super(options, name, type);
395     }
396     override void performAction(OptionParser parser, Options results, inout char[][] args, inout int idx, char[] arg) {
397         super.performAction(parser, results, args, idx, arg);
398         results.storeArg(this.name, arg);
399     }
400 }
401 class Append : ArgOption {
402     this(char[][] options, char[] name, ArgType type=Append.default_type) {
403         super(options, name, type);
404     }
405     override void performAction(OptionParser parser, Options results, inout char[][] args, inout int idx, char[] arg) {
406         super.performAction(parser, results, args, idx, arg);
407         results.appendArg(this.name, arg);
408     }
409 }
410 abstract class ConstOption : Option {
411     bool _has_default = false;
412     char[] default_value;
413     char[] const_value;
414     this(char[][] options, char[] name, char[] const_value) {
415         super(options, name);
416         this.const_value = const_value;
417     }
418     override Option def(char[] val) {
419         this._has_default = true;
420         this.default_value = val;
421         return this;
422     }
423     override ArgType def_type() {
424         return ArgType.String;
425     }
426     override bool supports_default() {
427         return true;
428     }
429     override bool has_default() {
430         return _has_default;
431     }
432     override void issue_default(Options results) {
433         if (_has_default) results.storeArg(this.name, this.default_value);
434     }
435 }
436 class StoreConst : ConstOption {
437     this(char[][] options, char[] name, char[] const_value) {
438         super(options, name, const_value);
439     }
440     override void performAction(OptionParser parser, Options results, inout char[][] args, inout int idx, char[] arg) {
441         results.storeArg(this.name, this.const_value);
442     }
443 }
444 class AppendConst : ConstOption {
445     this(char[][] options, char[] name, char[] const_value) {
446         super(options, name, const_value);
447     }
448     override void performAction(OptionParser parser, Options results, inout char[][] args, inout int idx, char[] arg) {
449         results.appendArg(this.name, this.const_value);
450     }
451 }
452 abstract class SetBool : Option {
453     bool _has_default = false;
454     bool default_flag = false;
455     this(char[][] options, char[] name) {
456         super(options, name);
457     }
458     override Option def(bool val) {
459         this._has_default = true;
460         this.default_flag = val;
461         return this;
462     }
463     override ArgType def_type() {
464         return ArgType.Bool;
465     }
466     override bool supports_default() {
467         return true;
468     }
469     override bool has_default() {
470         return _has_default;
471     }
472     override void issue_default(Options results) {
473         if (_has_default) results.storeArg(this.name, this.default_flag);
474     }
475 }
476 class SetTrue : SetBool {
477     this(char[][] options, char[] name) {
478         super(options, name);
479     }
480     override void performAction(OptionParser parser, Options results, inout char[][] args, inout int idx, char[] arg) {
481         results.storeArg(this.name, true);
482     }
483 }
484 class SetFalse : SetBool {
485     this(char[][] options, char[] name) {
486         super(options, name);
487     }
488     override void performAction(OptionParser parser, Options results, inout char[][] args, inout int idx, char[] arg) {
489         results.storeArg(this.name, false);
490     }
491 }
492 class Count : Option {
493     this(char[][] options, char[] name) {
494         super(options, name);
495     }
496     override void performAction(OptionParser parser, Options results, inout char[][] args, inout int idx, char[] arg) {
497         results.increment(this.name);
498     }
499 }
500 class Callback : Option {
501     OptionCallback cb;
502     this(char[][] options, OptionCallback cb) {
503         super(options, null);
504         this.cb = cb;
505     }
506     override void performAction(OptionParser parser, Options results, inout char[][] args, inout int idx, char[] arg) {
507         this.cb();
508     }
509 }
510 class CallbackArg : Option {
511     OptionCallbackArg cb;
512     this(char[][] options, OptionCallbackArg cb) {
513         super(options, null);
514         this.cb = cb;
515     }
516     override void performAction(OptionParser parser, Options results, inout char[][] args, inout int idx, char[] arg) {
517         this.cb(arg);
518     }
519     override bool hasArg() {
520         return true;
521     }
522 }
523 class CallbackInt : Option {
524     OptionCallbackInt cb;
525     this(char[][] options, OptionCallbackInt cb) {
526         super(options, null);
527         this.cb = cb;
528     }
529     override void performAction(OptionParser parser, Options results, inout char[][] args, inout int idx, char[] arg) {
530         int i = parser.toOptInt(arg);
531         this.cb(i);
532     }
533     override bool hasArg() {
534         return true;
535     }
536 }
537 class FancyCallback : Option {
538     OptionCallbackFancy cb;
539     this(char[][] options, char[] name, OptionCallbackFancy cb) {
540         super(options, name);
541         this.cb = cb;
542     }
543     override void performAction(OptionParser parser, Options results, inout char[][] args, inout int idx, char[] arg) {
544         this.cb(results, args, idx, this.name);
545     }
546 }
547 class FancyCallbackArg : Option {
548     OptionCallbackFancyArg cb;
549     this(char[][] options, char[] name, OptionCallbackFancyArg cb) {
550         super(options, name);
551         this.cb = cb;
552     }
553     override void performAction(OptionParser parser, Options results, inout char[][] args, inout int idx, char[] arg) {
554         this.cb(results, args, idx, this.name, arg);
555     }
556     override bool hasArg() {
557         return true;
558     }
559 }
560 class FancyCallbackInt : Option {
561     OptionCallbackFancyInt cb;
562     this(char[][] options, char[] name, OptionCallbackFancyInt cb) {
563         super(options, name);
564         this.cb = cb;
565     }
566     override void performAction(OptionParser parser, Options results, inout char[][] args, inout int idx, char[] arg) {
567         int i = parser.toOptInt(arg);
568         this.cb(results, args, idx, this.name, i);
569     }
570     override bool hasArg() {
571         return true;
572     }
573 }
574 class Help : Option {
575     this(char[][] options) {
576         super(options, null);
577     }
578     override void performAction(OptionParser parser, Options results, inout char[][] args, inout int idx, char[] arg) {
579         parser.helpText();
580         exit(EXIT_SUCCESS);
581     }
582 }
583
584 /++
585 This class is used to define a set of options, and parse the command-line
586 arguments.
587 +/
588 class OptionParser {
589     OptionCallbackArg leftover_cb;
590     /// An array of all of the options known by this parser.
591     Option[] options;
592     char[] name, desc;
593     /// The description of the programs arguments, as used in the Help action.
594     char[] argdesc;
595     private void delegate(char[]) error_callback;
596
597     this(char[] desc="") {
598         this.name = "";
599         this.desc = desc;
600         this.argdesc = "[options] args...";
601     }
602
603     /// Sets a callback, to override the default error behavior.
604     void setErrorCallback(void delegate(char[]) dg) {
605         error_callback = dg;
606     }
607     void unknownOptError(char[] opt) {
608         error("Unknown argument '"~opt~"'");
609     }
610     void expectedArgError(char[] opt) {
611         error("'"~opt~"' option expects an argument.");
612     }
613     /// Displays an error message and terminates the program.
614     void error(char[] err) {
615         if (error_callback !is null) {
616             error_callback(err);
617         } else {
618             this.helpText();
619         }
620         throw new OptionParsingError(err);
621     }
622     version (Tango) {
623         int toOptInt(char[] s) {
624             uint ate;
625             int i = cast(int)(.parse(s, 10u, &ate));
626             if (ate != s.length)
627                 error("Could not convert '"~s~"' to an integer.");
628             return i;
629         }
630     } else {
631         int toOptInt(char[] s) {
632             int i;
633             try {
634                 i = toInt(s);
635             } catch (ConvError e) {
636                 error("Could not convert '"~s~"' to an integer.");
637             }
638             return i;
639         }
640     }
641
642     /+
643     Returns an array of arrays of strings with  useful "help" information about the program's
644     options.
645
646     The array looks something like this:
647     [
648         ['option', 'help text'],
649         ['option', 'help text'],
650         ...
651     ]
652     +/
653     char[][][] helpText() {
654         char[][][] ret;
655         foreach(i, opt; options) {
656             ret ~= [opt.toString(), opt.helptext];
657         }
658         return ret;
659     }
660    
661     // Checks the passed arg against all the options in the parser.
662     // Returns null if no match is found.
663     Option matches(char[] arg) {
664         foreach(o; options) {
665             if (o.matches(arg)) {
666                 return o;
667             }
668         }
669         return null;
670     }
671     version (Tango) {
672         char[] getProgramName(char[] path) {
673             version(Windows) {
674                 char delimiter = '\\';
675             } else {
676                 char delimiter = '/';
677             }
678             uint idx = locatePrior(path, delimiter);
679             if (idx == path.length) return path;
680             return path[idx+1 .. $];
681         }
682     } else {
683         char[] getProgramName(char[] path) {
684             version(Windows) {
685                 // (Unicode note: ".exe" only contains 4 code units, so this slice
686                 // should Just Work.) (Although it remains to be seen how robust
687                 // this code actually is.)
688                 assert(path[$-4 .. $] == ".exe");
689                 path = path[0 .. $-4];
690             }
691             return getBaseName(path);
692         }
693     }
694     /// Parses the passed command-line arguments and returns the results.
695     Options parse(char[][] args) {
696         this.name = getProgramName(args[0]);
697         args = args[1 .. $];
698         Options options = new Options;
699         /*
700         The issue is this:
701
702         $ myapp -abc
703
704         This might be three short opts, or one or two opts, the last of which
705         accepts an argument. In the three-opt case, we want to get:
706
707         $ myapp -a -b -c
708
709         In the one-opt case, we want:
710
711         $ myapp -a bc
712
713         In the two-opt case, we want:
714
715         $ myapp -a -b c
716
717         We also want to parse apart "--file=somefile" into "--file somefile"
718         */
719         char[] opt, newopt, arg;
720         dchar[] opt32;
721         int idx;
722         Option match;
723
724         foreach (o; this.options) {
725             o.issue_default(options);
726         }
727
728         for (int i=0; i<args.length; ++i) {
729             opt = args[i];
730             // -- ends the option list, the remainder is dumped into args
731             if (opt == "--") {
732                 if (this.leftover_cb !is null) {
733                     foreach(a; args[i+1 .. $]) {
734                         this.leftover_cb(a);
735                     }
736                 } else {
737                     options.args ~= args[i+1 .. $];
738                 }
739                 i = args.length;
740             } else if (opt.startswith("--")) {
741                 idx = find(opt, '=');
742                 if (idx != getNotFound(opt)) {
743                     newopt = opt[0 .. idx];
744                     // Stitch out the old arg, stitch in the newopt, arg pair.
745                     // (Unicode note: idx+1 works, since we know '=' is a
746                     // single code unit.)
747                     args = args[0 .. i] ~ [newopt, opt[idx+1 .. $]] ~ args[i+1 .. $];
748                 } else {
749                     newopt = opt;
750                 }
751                 match = matches(newopt);
752                 if (match is null) {
753                     unknownOptError(newopt);
754                 }
755                 if (match.hasArg) {
756                     if (i == args.length-1) expectedArgError(match.name);
757                     arg = args[i+1];
758                     ++i;
759                 } else {
760                     arg = null;
761                 }
762                 match.performAction(this, options, args, i, arg);
763             } else if (opt.startswith("-")) {
764                 if (opt.length >= 2) {
765                     opt32 = toUTF32(opt[1 .. $]);
766                     foreach (j, c; opt32) {
767                         newopt = toUTF8("-" ~ [c]);
768                         match = matches(newopt);
769                         if (match is null) {
770                             unknownOptError(newopt);
771                         }
772                         if (match.hasArg) {
773                             // This is the last char in the group, look to the
774                             // next element of args for the arg.
775                             if (j == opt32.length-1) {
776                                 if (i == args.length-1) expectedArgError(match.name);
777                                 arg = args[i+1];
778                                 ++i;
779                             // Otherwise, consume the rest of this group for
780                             // the arg.
781                             } else {
782                                 arg = toUTF8(opt32[j+1 .. $]);
783                                 match.performAction(this, options, args, i, arg);
784                                 break;
785                             }
786                         } else {
787                             arg = null;
788                         }
789                         match.performAction(this, options, args, i, arg);
790                     }
791                 } else {
792                     unknownOptError(opt);
793                 }
794             } else {
795                 if (this.leftover_cb is null) {
796                     options.args ~= opt;
797                 } else {
798                     this.leftover_cb(opt);
799                 }
800             }
801         }
802         return options;
803     }
804
805     /++
806     Overrides the default behavior of leftover arguments, calling this callback
807     with them instead of adding them an array.
808     +/
809     void leftoverCallback(OptionCallbackArg dg) {
810         this.leftover_cb = dg;
811     }
812     private void wrong_args(char[][] options) {
813         if (options.length > 0) {
814             throw new OptionError(
815                 "Wrong arguments to addOption for option '"
816                 ~options[0]~"'."
817             );
818         } else {
819             throw new OptionError("Found an empty option!");
820         }
821     }
822     ///
823     Option addOption(char[][] options ...) {
824         return addOption(new Store(options, null));
825     }
826     ///
827     Option addOption(char[][] options, char[] name) {
828         return addOption(new Store(options, name));
829     }
830     ///
831     Option addOption(char[][] options, Action action) {
832         Option o;
833         switch (action) {
834             case Action.Store, Action.Append:
835                 return addOption(options, null, action, ArgOption.default_type);
836             case Action.Count:
837                 return addOption(options, null, action);
838             case Action.Help:
839                 return addOption(new Help(options));
840             default:
841                 wrong_args(options);
842                 break;
843         }
844         return addOption(o);
845     }
846     ///
847     Option addOption(char[][] options, ArgType type) {
848         return addOption(new Store(options, null, type));
849     }
850     ///
851     Option addOption(char[][] options, Action action, ArgType type) {
852         return addOption(options, null, action, type);
853     }
854     ///
855     Option addOption(char[][] options, char[] name, Action action) {
856         Option o;
857         switch (action) {
858             case Action.Store, Action.Append:
859                 return addOption(options, name, action, ArgOption.default_type);
860             case Action.Count:
861                 o = new Count(options, name);
862                 break;
863             case Action.SetTrue:
864                 o = new SetTrue(options, name);
865                 break;
866             case Action.SetFalse:
867                 o = new SetFalse(options, name);
868                 break;
869             default:
870                 wrong_args(options);
871                 break;
872         }
873         return addOption(o);
874     }
875     ///
876     Option addOption(char[][] options, char[] name, Action action, ArgType type) {
877         Option o;
878         switch (action) {
879             case Action.Store:
880                 o = new Store(options, name, type);
881                 break;
882             case Action.Append:
883                 o = new Append(options, name, type);
884                 break;
885             default:
886                 wrong_args(options);
887                 break;
888         }
889         return addOption(o);
890     }
891     ///
892     Option addOption(char[][] options, Action action, char[] const_value) {
893         return addOption(options, null, action, const_value);
894     }
895     ///
896     Option addOption(char[][] options, char[] name, char[] const_value) {
897         return addOption(options, name, Action.StoreConst, const_value);
898     }
899     ///
900     Option addOption(char[][] options, char[] name, Action action, char[] const_value) {
901         Option o;
902         switch (action) {
903             case Action.StoreConst:
904                 o = new StoreConst(options, name, const_value);
905                 break;
906             case Action.AppendConst:
907                 o = new AppendConst(options, name, const_value);
908                 break;
909             default:
910                 wrong_args(options);
911                 break;
912         }
913         return addOption(o);
914     }
915     ///
916     Option addOption(char[][] options, OptionCallback dg) {
917         return addOption(new Callback(options, dg));
918     }
919     ///
920     Option addOption(char[][] options, OptionCallbackArg dg) {
921         return addOption(new CallbackArg(options, dg));
922     }
923     ///
924     Option addOption(char[][] options, OptionCallbackInt dg) {
925         return addOption(new CallbackInt(options, dg));
926     }
927     ///
928     Option addOption(char[][] options, OptionCallbackFancy dg) {
929         return addOption(new FancyCallback(options, null, dg));
930     }
931     ///
932     Option addOption(char[][] options, OptionCallbackFancyArg dg) {
933         return addOption(new FancyCallbackArg(options, null, dg));
934     }
935     ///
936     Option addOption(char[][] options, OptionCallbackFancyInt dg) {
937         return addOption(new FancyCallbackInt(options, null, dg));
938     }
939     ///
940     Option addOption(char[][] options, char[] name, OptionCallbackFancy dg) {
941         return addOption(new FancyCallback(options, name, dg));
942     }
943     ///
944     Option addOption(char[][] options, char[] name, OptionCallbackFancyArg dg) {
945         return addOption(new FancyCallbackArg(options, name, dg));
946     }
947     ///
948     Option addOption(char[][] options, char[] name, OptionCallbackFancyInt dg) {
949         return addOption(new FancyCallbackInt(options, name, dg));
950     }
951     // Although users certainly /can/ call this, all those overloads are there
952     // for a reason.
953     Option addOption(Option option) {
954         this.options ~= option;
955         return option;
956     }
957 }
Note: See TracBrowser for help on using the browser.