1*760c2415Smrg // Written in the D programming language.
2*760c2415Smrg 
3*760c2415Smrg /**
4*760c2415Smrg Processing of command line options.
5*760c2415Smrg 
6*760c2415Smrg The getopt module implements a $(D getopt) function, which adheres to
7*760c2415Smrg the POSIX syntax for command line options. GNU extensions are
8*760c2415Smrg supported in the form of long options introduced by a double dash
9*760c2415Smrg ("--"). Support for bundling of command line options, as was the case
10*760c2415Smrg with the more traditional single-letter approach, is provided but not
11*760c2415Smrg enabled by default.
12*760c2415Smrg 
13*760c2415Smrg Copyright: Copyright Andrei Alexandrescu 2008 - 2015.
14*760c2415Smrg License:   $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
15*760c2415Smrg Authors:   $(HTTP erdani.org, Andrei Alexandrescu)
16*760c2415Smrg Credits:   This module and its documentation are inspired by Perl's $(HTTP
17*760c2415Smrg            perldoc.perl.org/Getopt/Long.html, Getopt::Long) module. The syntax of
18*760c2415Smrg            D's $(D getopt) is simpler than its Perl counterpart because $(D
19*760c2415Smrg            getopt) infers the expected parameter types from the static types of
20*760c2415Smrg            the passed-in pointers.
21*760c2415Smrg Source:    $(PHOBOSSRC std/_getopt.d)
22*760c2415Smrg */
23*760c2415Smrg /*
24*760c2415Smrg          Copyright Andrei Alexandrescu 2008 - 2015.
25*760c2415Smrg Distributed under the Boost Software License, Version 1.0.
26*760c2415Smrg    (See accompanying file LICENSE_1_0.txt or copy at
27*760c2415Smrg          http://www.boost.org/LICENSE_1_0.txt)
28*760c2415Smrg */
29*760c2415Smrg module std.getopt;
30*760c2415Smrg 
31*760c2415Smrg import std.exception;  // basicExceptionCtors
32*760c2415Smrg import std.traits;
33*760c2415Smrg 
34*760c2415Smrg /**
35*760c2415Smrg Thrown on one of the following conditions:
36*760c2415Smrg $(UL
37*760c2415Smrg   $(LI An unrecognized command-line argument is passed, and
38*760c2415Smrg        $(D std.getopt.config.passThrough) was not present.)
39*760c2415Smrg   $(LI A command-line option was not found, and
40*760c2415Smrg        $(D std.getopt.config.required) was present.)
41*760c2415Smrg )
42*760c2415Smrg */
43*760c2415Smrg class GetOptException : Exception
44*760c2415Smrg {
45*760c2415Smrg     mixin basicExceptionCtors;
46*760c2415Smrg }
47*760c2415Smrg 
48*760c2415Smrg static assert(is(typeof(new GetOptException("message"))));
49*760c2415Smrg static assert(is(typeof(new GetOptException("message", Exception.init))));
50*760c2415Smrg 
51*760c2415Smrg /**
52*760c2415Smrg    Parse and remove command line options from a string array.
53*760c2415Smrg 
54*760c2415Smrg    Synopsis:
55*760c2415Smrg 
56*760c2415Smrg ---------
57*760c2415Smrg import std.getopt;
58*760c2415Smrg 
59*760c2415Smrg string data = "file.dat";
60*760c2415Smrg int length = 24;
61*760c2415Smrg bool verbose;
62*760c2415Smrg enum Color { no, yes };
63*760c2415Smrg Color color;
64*760c2415Smrg 
65*760c2415Smrg void main(string[] args)
66*760c2415Smrg {
67*760c2415Smrg   auto helpInformation = getopt(
68*760c2415Smrg     args,
69*760c2415Smrg     "length",  &length,    // numeric
70*760c2415Smrg     "file",    &data,      // string
71*760c2415Smrg     "verbose", &verbose,   // flag
72*760c2415Smrg     "color", "Information about this color", &color);    // enum
73*760c2415Smrg   ...
74*760c2415Smrg 
75*760c2415Smrg   if (helpInformation.helpWanted)
76*760c2415Smrg   {
77*760c2415Smrg     defaultGetoptPrinter("Some information about the program.",
78*760c2415Smrg       helpInformation.options);
79*760c2415Smrg   }
80*760c2415Smrg }
81*760c2415Smrg ---------
82*760c2415Smrg 
83*760c2415Smrg  The $(D getopt) function takes a reference to the command line
84*760c2415Smrg  (as received by $(D main)) as its first argument, and an
85*760c2415Smrg  unbounded number of pairs of strings and pointers. Each string is an
86*760c2415Smrg  option meant to "fill" the value referenced by the pointer to its
87*760c2415Smrg  right (the "bound" pointer). The option string in the call to
88*760c2415Smrg  $(D getopt) should not start with a dash.
89*760c2415Smrg 
90*760c2415Smrg  In all cases, the command-line options that were parsed and used by
91*760c2415Smrg  $(D getopt) are removed from $(D args). Whatever in the
92*760c2415Smrg  arguments did not look like an option is left in $(D args) for
93*760c2415Smrg  further processing by the program. Values that were unaffected by the
94*760c2415Smrg  options are not touched, so a common idiom is to initialize options
95*760c2415Smrg  to their defaults and then invoke $(D getopt). If a
96*760c2415Smrg  command-line argument is recognized as an option with a parameter and
97*760c2415Smrg  the parameter cannot be parsed properly (e.g., a number is expected
98*760c2415Smrg  but not present), a $(D ConvException) exception is thrown.
99*760c2415Smrg  If $(D std.getopt.config.passThrough) was not passed to $(D getopt)
100*760c2415Smrg  and an unrecognized command-line argument is found, a $(D GetOptException)
101*760c2415Smrg  is thrown.
102*760c2415Smrg 
103*760c2415Smrg  Depending on the type of the pointer being bound, $(D getopt)
104*760c2415Smrg  recognizes the following kinds of options:
105*760c2415Smrg 
106*760c2415Smrg  $(OL
107*760c2415Smrg     $(LI $(I Boolean options). A lone argument sets the option to $(D true).
108*760c2415Smrg     Additionally $(B true) or $(B false) can be set within the option separated
109*760c2415Smrg     with an "=" sign:
110*760c2415Smrg 
111*760c2415Smrg ---------
112*760c2415Smrg   bool verbose = false, debugging = true;
113*760c2415Smrg   getopt(args, "verbose", &verbose, "debug", &debugging);
114*760c2415Smrg ---------
115*760c2415Smrg 
116*760c2415Smrg     To set $(D verbose) to $(D true), invoke the program with either
117*760c2415Smrg     $(D --verbose) or $(D --verbose=true).
118*760c2415Smrg 
119*760c2415Smrg     To set $(D debugging) to $(D false), invoke the program with
120*760c2415Smrg     $(D --debugging=false).
121*760c2415Smrg     )
122*760c2415Smrg 
123*760c2415Smrg     $(LI $(I Numeric options.) If an option is bound to a numeric type, a
124*760c2415Smrg     number is expected as the next option, or right within the option separated
125*760c2415Smrg     with an "=" sign:
126*760c2415Smrg 
127*760c2415Smrg ---------
128*760c2415Smrg   uint timeout;
129*760c2415Smrg   getopt(args, "timeout", &timeout);
130*760c2415Smrg ---------
131*760c2415Smrg 
132*760c2415Smrg     To set $(D timeout) to $(D 5), invoke the program with either
133*760c2415Smrg     $(D --timeout=5) or $(D --timeout 5).
134*760c2415Smrg     )
135*760c2415Smrg 
136*760c2415Smrg     $(LI $(I Incremental options.) If an option name has a "+" suffix and is
137*760c2415Smrg     bound to a numeric type, then the option's value tracks the number of times
138*760c2415Smrg     the option occurred on the command line:
139*760c2415Smrg 
140*760c2415Smrg ---------
141*760c2415Smrg   uint paranoid;
142*760c2415Smrg   getopt(args, "paranoid+", &paranoid);
143*760c2415Smrg ---------
144*760c2415Smrg 
145*760c2415Smrg     Invoking the program with "--paranoid --paranoid --paranoid" will set $(D
146*760c2415Smrg     paranoid) to 3. Note that an incremental option never expects a parameter,
147*760c2415Smrg     e.g., in the command line "--paranoid 42 --paranoid", the "42" does not set
148*760c2415Smrg     $(D paranoid) to 42; instead, $(D paranoid) is set to 2 and "42" is not
149*760c2415Smrg     considered as part of the normal program arguments.
150*760c2415Smrg     )
151*760c2415Smrg 
152*760c2415Smrg     $(LI $(I Enum options.) If an option is bound to an enum, an enum symbol as
153*760c2415Smrg     a string is expected as the next option, or right within the option
154*760c2415Smrg     separated with an "=" sign:
155*760c2415Smrg 
156*760c2415Smrg ---------
157*760c2415Smrg   enum Color { no, yes };
158*760c2415Smrg   Color color; // default initialized to Color.no
159*760c2415Smrg   getopt(args, "color", &color);
160*760c2415Smrg ---------
161*760c2415Smrg 
162*760c2415Smrg     To set $(D color) to $(D Color.yes), invoke the program with either
163*760c2415Smrg     $(D --color=yes) or $(D --color yes).
164*760c2415Smrg     )
165*760c2415Smrg 
166*760c2415Smrg     $(LI $(I String options.) If an option is bound to a string, a string is
167*760c2415Smrg     expected as the next option, or right within the option separated with an
168*760c2415Smrg     "=" sign:
169*760c2415Smrg 
170*760c2415Smrg ---------
171*760c2415Smrg string outputFile;
172*760c2415Smrg getopt(args, "output", &outputFile);
173*760c2415Smrg ---------
174*760c2415Smrg 
175*760c2415Smrg     Invoking the program with "--output=myfile.txt" or "--output myfile.txt"
176*760c2415Smrg     will set $(D outputFile) to "myfile.txt". If you want to pass a string
177*760c2415Smrg     containing spaces, you need to use the quoting that is appropriate to your
178*760c2415Smrg     shell, e.g. --output='my file.txt'.
179*760c2415Smrg     )
180*760c2415Smrg 
181*760c2415Smrg     $(LI $(I Array options.) If an option is bound to an array, a new element
182*760c2415Smrg     is appended to the array each time the option occurs:
183*760c2415Smrg 
184*760c2415Smrg ---------
185*760c2415Smrg string[] outputFiles;
186*760c2415Smrg getopt(args, "output", &outputFiles);
187*760c2415Smrg ---------
188*760c2415Smrg 
189*760c2415Smrg     Invoking the program with "--output=myfile.txt --output=yourfile.txt" or
190*760c2415Smrg     "--output myfile.txt --output yourfile.txt" will set $(D outputFiles) to
191*760c2415Smrg     $(D [ "myfile.txt", "yourfile.txt" ]).
192*760c2415Smrg 
193*760c2415Smrg     Alternatively you can set $(LREF arraySep) as the element separator:
194*760c2415Smrg 
195*760c2415Smrg ---------
196*760c2415Smrg string[] outputFiles;
197*760c2415Smrg arraySep = ",";  // defaults to "", separation by whitespace
198*760c2415Smrg getopt(args, "output", &outputFiles);
199*760c2415Smrg ---------
200*760c2415Smrg 
201*760c2415Smrg     With the above code you can invoke the program with
202*760c2415Smrg     "--output=myfile.txt,yourfile.txt", or "--output myfile.txt,yourfile.txt".)
203*760c2415Smrg 
204*760c2415Smrg     $(LI $(I Hash options.) If an option is bound to an associative array, a
205*760c2415Smrg     string of the form "name=value" is expected as the next option, or right
206*760c2415Smrg     within the option separated with an "=" sign:
207*760c2415Smrg 
208*760c2415Smrg ---------
209*760c2415Smrg double[string] tuningParms;
210*760c2415Smrg getopt(args, "tune", &tuningParms);
211*760c2415Smrg ---------
212*760c2415Smrg 
213*760c2415Smrg     Invoking the program with e.g. "--tune=alpha=0.5 --tune beta=0.6" will set
214*760c2415Smrg     $(D tuningParms) to [ "alpha" : 0.5, "beta" : 0.6 ].
215*760c2415Smrg 
216*760c2415Smrg     Alternatively you can set $(LREF arraySep) as the element separator:
217*760c2415Smrg 
218*760c2415Smrg ---------
219*760c2415Smrg double[string] tuningParms;
220*760c2415Smrg arraySep = ",";  // defaults to "", separation by whitespace
221*760c2415Smrg getopt(args, "tune", &tuningParms);
222*760c2415Smrg ---------
223*760c2415Smrg 
224*760c2415Smrg     With the above code you can invoke the program with
225*760c2415Smrg     "--tune=alpha=0.5,beta=0.6", or "--tune alpha=0.5,beta=0.6".
226*760c2415Smrg 
227*760c2415Smrg     In general, the keys and values can be of any parsable types.
228*760c2415Smrg     )
229*760c2415Smrg 
230*760c2415Smrg     $(LI $(I Callback options.) An option can be bound to a function or
231*760c2415Smrg     delegate with the signature $(D void function()), $(D void function(string
232*760c2415Smrg     option)), $(D void function(string option, string value)), or their
233*760c2415Smrg     delegate equivalents.
234*760c2415Smrg 
235*760c2415Smrg     $(UL
236*760c2415Smrg         $(LI If the callback doesn't take any arguments, the callback is
237*760c2415Smrg         invoked whenever the option is seen.
238*760c2415Smrg         )
239*760c2415Smrg 
240*760c2415Smrg         $(LI If the callback takes one string argument, the option string
241*760c2415Smrg         (without the leading dash(es)) is passed to the callback.  After that,
242*760c2415Smrg         the option string is considered handled and removed from the options
243*760c2415Smrg         array.
244*760c2415Smrg 
245*760c2415Smrg ---------
246*760c2415Smrg void main(string[] args)
247*760c2415Smrg {
248*760c2415Smrg   uint verbosityLevel = 1;
249*760c2415Smrg   void myHandler(string option)
250*760c2415Smrg   {
251*760c2415Smrg     if (option == "quiet")
252*760c2415Smrg     {
253*760c2415Smrg       verbosityLevel = 0;
254*760c2415Smrg     }
255*760c2415Smrg     else
256*760c2415Smrg     {
257*760c2415Smrg       assert(option == "verbose");
258*760c2415Smrg       verbosityLevel = 2;
259*760c2415Smrg     }
260*760c2415Smrg   }
261*760c2415Smrg   getopt(args, "verbose", &myHandler, "quiet", &myHandler);
262*760c2415Smrg }
263*760c2415Smrg ---------
264*760c2415Smrg 
265*760c2415Smrg         )
266*760c2415Smrg 
267*760c2415Smrg         $(LI If the callback takes two string arguments, the option string is
268*760c2415Smrg         handled as an option with one argument, and parsed accordingly. The
269*760c2415Smrg         option and its value are passed to the callback. After that, whatever
270*760c2415Smrg         was passed to the callback is considered handled and removed from the
271*760c2415Smrg         list.
272*760c2415Smrg 
273*760c2415Smrg ---------
274*760c2415Smrg int main(string[] args)
275*760c2415Smrg {
276*760c2415Smrg   uint verbosityLevel = 1;
277*760c2415Smrg   bool handlerFailed = false;
278*760c2415Smrg   void myHandler(string option, string value)
279*760c2415Smrg   {
280*760c2415Smrg     switch (value)
281*760c2415Smrg     {
282*760c2415Smrg       case "quiet": verbosityLevel = 0; break;
283*760c2415Smrg       case "verbose": verbosityLevel = 2; break;
284*760c2415Smrg       case "shouting": verbosityLevel = verbosityLevel.max; break;
285*760c2415Smrg       default :
286*760c2415Smrg         stderr.writeln("Unknown verbosity level ", value);
287*760c2415Smrg         handlerFailed = true;
288*760c2415Smrg         break;
289*760c2415Smrg     }
290*760c2415Smrg   }
291*760c2415Smrg   getopt(args, "verbosity", &myHandler);
292*760c2415Smrg   return handlerFailed ? 1 : 0;
293*760c2415Smrg }
294*760c2415Smrg ---------
295*760c2415Smrg         )
296*760c2415Smrg     ))
297*760c2415Smrg )
298*760c2415Smrg 
299*760c2415Smrg Options_with_multiple_names:
300*760c2415Smrg Sometimes option synonyms are desirable, e.g. "--verbose",
301*760c2415Smrg "--loquacious", and "--garrulous" should have the same effect. Such
302*760c2415Smrg alternate option names can be included in the option specification,
303*760c2415Smrg using "|" as a separator:
304*760c2415Smrg 
305*760c2415Smrg ---------
306*760c2415Smrg bool verbose;
307*760c2415Smrg getopt(args, "verbose|loquacious|garrulous", &verbose);
308*760c2415Smrg ---------
309*760c2415Smrg 
310*760c2415Smrg Case:
311*760c2415Smrg By default options are case-insensitive. You can change that behavior
312*760c2415Smrg by passing $(D getopt) the $(D caseSensitive) directive like this:
313*760c2415Smrg 
314*760c2415Smrg ---------
315*760c2415Smrg bool foo, bar;
316*760c2415Smrg getopt(args,
317*760c2415Smrg     std.getopt.config.caseSensitive,
318*760c2415Smrg     "foo", &foo,
319*760c2415Smrg     "bar", &bar);
320*760c2415Smrg ---------
321*760c2415Smrg 
322*760c2415Smrg In the example above, "--foo" and "--bar" are recognized, but "--Foo", "--Bar",
323*760c2415Smrg "--FOo", "--bAr", etc. are rejected.
324*760c2415Smrg The directive is active until the end of $(D getopt), or until the
325*760c2415Smrg converse directive $(D caseInsensitive) is encountered:
326*760c2415Smrg 
327*760c2415Smrg ---------
328*760c2415Smrg bool foo, bar;
329*760c2415Smrg getopt(args,
330*760c2415Smrg     std.getopt.config.caseSensitive,
331*760c2415Smrg     "foo", &foo,
332*760c2415Smrg     std.getopt.config.caseInsensitive,
333*760c2415Smrg     "bar", &bar);
334*760c2415Smrg ---------
335*760c2415Smrg 
336*760c2415Smrg The option "--Foo" is rejected due to $(D
337*760c2415Smrg std.getopt.config.caseSensitive), but not "--Bar", "--bAr"
338*760c2415Smrg etc. because the directive $(D
339*760c2415Smrg std.getopt.config.caseInsensitive) turned sensitivity off before
340*760c2415Smrg option "bar" was parsed.
341*760c2415Smrg 
342*760c2415Smrg Short_versus_long_options:
343*760c2415Smrg Traditionally, programs accepted single-letter options preceded by
344*760c2415Smrg only one dash (e.g. $(D -t)). $(D getopt) accepts such parameters
345*760c2415Smrg seamlessly. When used with a double-dash (e.g. $(D --t)), a
346*760c2415Smrg single-letter option behaves the same as a multi-letter option. When
347*760c2415Smrg used with a single dash, a single-letter option is accepted. If the
348*760c2415Smrg option has a parameter, that must be "stuck" to the option without
349*760c2415Smrg any intervening space or "=":
350*760c2415Smrg 
351*760c2415Smrg ---------
352*760c2415Smrg uint timeout;
353*760c2415Smrg getopt(args, "timeout|t", &timeout);
354*760c2415Smrg ---------
355*760c2415Smrg 
356*760c2415Smrg To set $(D timeout) to $(D 5), use either of the following: $(D --timeout=5),
357*760c2415Smrg $(D --timeout 5), $(D --t=5), $(D --t 5), or $(D -t5). Forms such as $(D -t 5)
358*760c2415Smrg and $(D -timeout=5) will be not accepted.
359*760c2415Smrg 
360*760c2415Smrg For more details about short options, refer also to the next section.
361*760c2415Smrg 
362*760c2415Smrg Bundling:
363*760c2415Smrg Single-letter options can be bundled together, i.e. "-abc" is the same as
364*760c2415Smrg $(D "-a -b -c"). By default, this option is turned off. You can turn it on
365*760c2415Smrg with the $(D std.getopt.config.bundling) directive:
366*760c2415Smrg 
367*760c2415Smrg ---------
368*760c2415Smrg bool foo, bar;
369*760c2415Smrg getopt(args,
370*760c2415Smrg     std.getopt.config.bundling,
371*760c2415Smrg     "foo|f", &foo,
372*760c2415Smrg     "bar|b", &bar);
373*760c2415Smrg ---------
374*760c2415Smrg 
375*760c2415Smrg In case you want to only enable bundling for some of the parameters,
376*760c2415Smrg bundling can be turned off with $(D std.getopt.config.noBundling).
377*760c2415Smrg 
378*760c2415Smrg Required:
379*760c2415Smrg An option can be marked as required. If that option is not present in the
380*760c2415Smrg arguments an exception will be thrown.
381*760c2415Smrg 
382*760c2415Smrg ---------
383*760c2415Smrg bool foo, bar;
384*760c2415Smrg getopt(args,
385*760c2415Smrg     std.getopt.config.required,
386*760c2415Smrg     "foo|f", &foo,
387*760c2415Smrg     "bar|b", &bar);
388*760c2415Smrg ---------
389*760c2415Smrg 
390*760c2415Smrg Only the option directly following $(D std.getopt.config.required) is
391*760c2415Smrg required.
392*760c2415Smrg 
393*760c2415Smrg Passing_unrecognized_options_through:
394*760c2415Smrg If an application needs to do its own processing of whichever arguments
395*760c2415Smrg $(D getopt) did not understand, it can pass the
396*760c2415Smrg $(D std.getopt.config.passThrough) directive to $(D getopt):
397*760c2415Smrg 
398*760c2415Smrg ---------
399*760c2415Smrg bool foo, bar;
400*760c2415Smrg getopt(args,
401*760c2415Smrg     std.getopt.config.passThrough,
402*760c2415Smrg     "foo", &foo,
403*760c2415Smrg     "bar", &bar);
404*760c2415Smrg ---------
405*760c2415Smrg 
406*760c2415Smrg An unrecognized option such as "--baz" will be found untouched in
407*760c2415Smrg $(D args) after $(D getopt) returns.
408*760c2415Smrg 
409*760c2415Smrg Help_Information_Generation:
410*760c2415Smrg If an option string is followed by another string, this string serves as a
411*760c2415Smrg description for this option. The $(D getopt) function returns a struct of type
412*760c2415Smrg $(D GetoptResult). This return value contains information about all passed options
413*760c2415Smrg as well a $(D bool GetoptResult.helpWanted) flag indicating whether information
414*760c2415Smrg about these options was requested. The $(D getopt) function always adds an option for
415*760c2415Smrg `--help|-h` to set the flag if the option is seen on the command line.
416*760c2415Smrg 
417*760c2415Smrg Options_Terminator:
418*760c2415Smrg A lone double-dash terminates $(D getopt) gathering. It is used to
419*760c2415Smrg separate program options from other parameters (e.g., options to be passed
420*760c2415Smrg to another program). Invoking the example above with $(D "--foo -- --bar")
421*760c2415Smrg parses foo but leaves "--bar" in $(D args). The double-dash itself is
422*760c2415Smrg removed from the argument array unless the $(D std.getopt.config.keepEndOfOptions)
423*760c2415Smrg directive is given.
424*760c2415Smrg */
getopt(T...)425*760c2415Smrg GetoptResult getopt(T...)(ref string[] args, T opts)
426*760c2415Smrg {
427*760c2415Smrg     import std.exception : enforce;
428*760c2415Smrg     enforce(args.length,
429*760c2415Smrg             "Invalid arguments string passed: program name missing");
430*760c2415Smrg     configuration cfg;
431*760c2415Smrg     GetoptResult rslt;
432*760c2415Smrg 
433*760c2415Smrg     GetOptException excep;
434*760c2415Smrg     void[][string] visitedLongOpts, visitedShortOpts;
435*760c2415Smrg     getoptImpl(args, cfg, rslt, excep, visitedLongOpts, visitedShortOpts, opts);
436*760c2415Smrg 
437*760c2415Smrg     if (!rslt.helpWanted && excep !is null)
438*760c2415Smrg     {
439*760c2415Smrg         throw excep;
440*760c2415Smrg     }
441*760c2415Smrg 
442*760c2415Smrg     return rslt;
443*760c2415Smrg }
444*760c2415Smrg 
445*760c2415Smrg ///
446*760c2415Smrg @system unittest
447*760c2415Smrg {
448*760c2415Smrg     auto args = ["prog", "--foo", "-b"];
449*760c2415Smrg 
450*760c2415Smrg     bool foo;
451*760c2415Smrg     bool bar;
452*760c2415Smrg     auto rslt = getopt(args, "foo|f", "Some information about foo.", &foo, "bar|b",
453*760c2415Smrg         "Some help message about bar.", &bar);
454*760c2415Smrg 
455*760c2415Smrg     if (rslt.helpWanted)
456*760c2415Smrg     {
457*760c2415Smrg         defaultGetoptPrinter("Some information about the program.",
458*760c2415Smrg             rslt.options);
459*760c2415Smrg     }
460*760c2415Smrg }
461*760c2415Smrg 
462*760c2415Smrg /**
463*760c2415Smrg    Configuration options for $(D getopt).
464*760c2415Smrg 
465*760c2415Smrg    You can pass them to $(D getopt) in any position, except in between an option
466*760c2415Smrg    string and its bound pointer.
467*760c2415Smrg */
468*760c2415Smrg enum config {
469*760c2415Smrg     /// Turn case sensitivity on
470*760c2415Smrg     caseSensitive,
471*760c2415Smrg     /// Turn case sensitivity off (default)
472*760c2415Smrg     caseInsensitive,
473*760c2415Smrg     /// Turn bundling on
474*760c2415Smrg     bundling,
475*760c2415Smrg     /// Turn bundling off (default)
476*760c2415Smrg     noBundling,
477*760c2415Smrg     /// Pass unrecognized arguments through
478*760c2415Smrg     passThrough,
479*760c2415Smrg     /// Signal unrecognized arguments as errors (default)
480*760c2415Smrg     noPassThrough,
481*760c2415Smrg     /// Stop at first argument that does not look like an option
482*760c2415Smrg     stopOnFirstNonOption,
483*760c2415Smrg     /// Do not erase the endOfOptions separator from args
484*760c2415Smrg     keepEndOfOptions,
485*760c2415Smrg     /// Make the next option a required option
486*760c2415Smrg     required
487*760c2415Smrg }
488*760c2415Smrg 
489*760c2415Smrg /** The result of the $(D getopt) function.
490*760c2415Smrg 
491*760c2415Smrg $(D helpWanted) is set if the option `--help` or `-h` was passed to the option parser.
492*760c2415Smrg */
493*760c2415Smrg struct GetoptResult {
494*760c2415Smrg     bool helpWanted; /// Flag indicating if help was requested
495*760c2415Smrg     Option[] options; /// All possible options
496*760c2415Smrg }
497*760c2415Smrg 
498*760c2415Smrg /** Information about an option.
499*760c2415Smrg */
500*760c2415Smrg struct Option {
501*760c2415Smrg     string optShort; /// The short symbol for this option
502*760c2415Smrg     string optLong; /// The long symbol for this option
503*760c2415Smrg     string help; /// The description of this option
504*760c2415Smrg     bool required; /// If a option is required, not passing it will result in an error
505*760c2415Smrg }
506*760c2415Smrg 
splitAndGet(string opt)507*760c2415Smrg private pure Option splitAndGet(string opt) @trusted nothrow
508*760c2415Smrg {
509*760c2415Smrg     import std.array : split;
510*760c2415Smrg     auto sp = split(opt, "|");
511*760c2415Smrg     Option ret;
512*760c2415Smrg     if (sp.length > 1)
513*760c2415Smrg     {
514*760c2415Smrg         ret.optShort = "-" ~ (sp[0].length < sp[1].length ?
515*760c2415Smrg             sp[0] : sp[1]);
516*760c2415Smrg         ret.optLong = "--" ~ (sp[0].length > sp[1].length ?
517*760c2415Smrg             sp[0] : sp[1]);
518*760c2415Smrg     }
519*760c2415Smrg     else if (sp[0].length > 1)
520*760c2415Smrg     {
521*760c2415Smrg         ret.optLong = "--" ~ sp[0];
522*760c2415Smrg     }
523*760c2415Smrg     else
524*760c2415Smrg     {
525*760c2415Smrg         ret.optShort = "-" ~ sp[0];
526*760c2415Smrg     }
527*760c2415Smrg 
528*760c2415Smrg     return ret;
529*760c2415Smrg }
530*760c2415Smrg 
531*760c2415Smrg @safe unittest
532*760c2415Smrg {
533*760c2415Smrg     auto oshort = splitAndGet("f");
534*760c2415Smrg     assert(oshort.optShort == "-f");
535*760c2415Smrg     assert(oshort.optLong == "");
536*760c2415Smrg 
537*760c2415Smrg     auto olong = splitAndGet("foo");
538*760c2415Smrg     assert(olong.optShort == "");
539*760c2415Smrg     assert(olong.optLong == "--foo");
540*760c2415Smrg 
541*760c2415Smrg     auto oshortlong = splitAndGet("f|foo");
542*760c2415Smrg     assert(oshortlong.optShort == "-f");
543*760c2415Smrg     assert(oshortlong.optLong == "--foo");
544*760c2415Smrg 
545*760c2415Smrg     auto olongshort = splitAndGet("foo|f");
546*760c2415Smrg     assert(olongshort.optShort == "-f");
547*760c2415Smrg     assert(olongshort.optLong == "--foo");
548*760c2415Smrg }
549*760c2415Smrg 
550*760c2415Smrg /*
551*760c2415Smrg This function verifies that the variadic parameters passed in getOpt
552*760c2415Smrg follow this pattern:
553*760c2415Smrg 
554*760c2415Smrg   [config override], option, [description], receiver,
555*760c2415Smrg 
556*760c2415Smrg  - config override: a config value, optional
557*760c2415Smrg  - option:          a string or a char
558*760c2415Smrg  - description:     a string, optional
559*760c2415Smrg  - receiver:        a pointer or a callable
560*760c2415Smrg */
optionValidator(A...)561*760c2415Smrg private template optionValidator(A...)
562*760c2415Smrg {
563*760c2415Smrg     import std.format : format;
564*760c2415Smrg     import std.typecons : staticIota;
565*760c2415Smrg 
566*760c2415Smrg     enum fmt = "getopt validator: %s (at position %d)";
567*760c2415Smrg     enum isReceiver(T) = isPointer!T || (is(T == function)) || (is(T == delegate));
568*760c2415Smrg     enum isOptionStr(T) = isSomeString!T || isSomeChar!T;
569*760c2415Smrg 
570*760c2415Smrg     auto validator()
571*760c2415Smrg     {
572*760c2415Smrg         string msg;
573*760c2415Smrg         static if (A.length > 0)
574*760c2415Smrg         {
575*760c2415Smrg             static if (isReceiver!(A[0]))
576*760c2415Smrg             {
577*760c2415Smrg                 msg = format(fmt, "first argument must be a string or a config", 0);
578*760c2415Smrg             }
579*760c2415Smrg             else static if (!isOptionStr!(A[0]) && !is(A[0] == config))
580*760c2415Smrg             {
581*760c2415Smrg                 msg = format(fmt, "invalid argument type: " ~ A[0].stringof, 0);
582*760c2415Smrg             }
583*760c2415Smrg             else foreach (i; staticIota!(1, A.length))
584*760c2415Smrg             {
585*760c2415Smrg                 static if (!isReceiver!(A[i]) && !isOptionStr!(A[i]) &&
586*760c2415Smrg                     !(is(A[i] == config)))
587*760c2415Smrg                 {
588*760c2415Smrg                     msg = format(fmt, "invalid argument type: " ~ A[i].stringof, i);
589*760c2415Smrg                     break;
590*760c2415Smrg                 }
591*760c2415Smrg                 else static if (isReceiver!(A[i]) && !isOptionStr!(A[i-1]))
592*760c2415Smrg                 {
593*760c2415Smrg                     msg = format(fmt, "a receiver can not be preceeded by a receiver", i);
594*760c2415Smrg                     break;
595*760c2415Smrg                 }
596*760c2415Smrg                 else static if (i > 1 && isOptionStr!(A[i]) && isOptionStr!(A[i-1])
597*760c2415Smrg                     && isSomeString!(A[i-2]))
598*760c2415Smrg                 {
599*760c2415Smrg                     msg = format(fmt, "a string can not be preceeded by two strings", i);
600*760c2415Smrg                     break;
601*760c2415Smrg                 }
602*760c2415Smrg             }
603*760c2415Smrg             static if (!isReceiver!(A[$-1]) && !is(A[$-1] == config))
604*760c2415Smrg             {
605*760c2415Smrg                 msg = format(fmt, "last argument must be a receiver or a config",
606*760c2415Smrg                     A.length -1);
607*760c2415Smrg             }
608*760c2415Smrg         }
609*760c2415Smrg         return msg;
610*760c2415Smrg     }
611*760c2415Smrg     enum message = validator;
612*760c2415Smrg     alias optionValidator = message;
613*760c2415Smrg }
614*760c2415Smrg 
615*760c2415Smrg @safe pure unittest
616*760c2415Smrg {
617*760c2415Smrg     alias P = void*;
618*760c2415Smrg     alias S = string;
619*760c2415Smrg     alias A = char;
620*760c2415Smrg     alias C = config;
621*760c2415Smrg     alias F = void function();
622*760c2415Smrg 
623*760c2415Smrg     static assert(optionValidator!(S,P) == "");
624*760c2415Smrg     static assert(optionValidator!(S,F) == "");
625*760c2415Smrg     static assert(optionValidator!(A,P) == "");
626*760c2415Smrg     static assert(optionValidator!(A,F) == "");
627*760c2415Smrg 
628*760c2415Smrg     static assert(optionValidator!(C,S,P) == "");
629*760c2415Smrg     static assert(optionValidator!(C,S,F) == "");
630*760c2415Smrg     static assert(optionValidator!(C,A,P) == "");
631*760c2415Smrg     static assert(optionValidator!(C,A,F) == "");
632*760c2415Smrg 
633*760c2415Smrg     static assert(optionValidator!(C,S,S,P) == "");
634*760c2415Smrg     static assert(optionValidator!(C,S,S,F) == "");
635*760c2415Smrg     static assert(optionValidator!(C,A,S,P) == "");
636*760c2415Smrg     static assert(optionValidator!(C,A,S,F) == "");
637*760c2415Smrg 
638*760c2415Smrg     static assert(optionValidator!(C,S,S,P) == "");
639*760c2415Smrg     static assert(optionValidator!(C,S,S,P,C,S,F) == "");
640*760c2415Smrg     static assert(optionValidator!(C,S,P,C,S,S,F) == "");
641*760c2415Smrg 
642*760c2415Smrg     static assert(optionValidator!(C,A,P,A,S,F) == "");
643*760c2415Smrg     static assert(optionValidator!(C,A,P,C,A,S,F) == "");
644*760c2415Smrg 
645*760c2415Smrg     static assert(optionValidator!(P,S,S) != "");
646*760c2415Smrg     static assert(optionValidator!(P,P,S) != "");
647*760c2415Smrg     static assert(optionValidator!(P,F,S,P) != "");
648*760c2415Smrg     static assert(optionValidator!(C,C,S) != "");
649*760c2415Smrg     static assert(optionValidator!(S,S,P,S,S,P,S) != "");
650*760c2415Smrg     static assert(optionValidator!(S,S,P,P) != "");
651*760c2415Smrg     static assert(optionValidator!(S,S,S,P) != "");
652*760c2415Smrg 
653*760c2415Smrg     static assert(optionValidator!(C,A,S,P,C,A,F) == "");
654*760c2415Smrg     static assert(optionValidator!(C,A,P,C,A,S,F) == "");
655*760c2415Smrg }
656*760c2415Smrg 
657*760c2415Smrg @system unittest // bugzilla 15914
658*760c2415Smrg {
659*760c2415Smrg     bool opt;
660*760c2415Smrg     string[] args = ["program", "-a"];
661*760c2415Smrg     getopt(args, config.passThrough, 'a', &opt);
662*760c2415Smrg     assert(opt);
663*760c2415Smrg     opt = false;
664*760c2415Smrg     args = ["program", "-a"];
665*760c2415Smrg     getopt(args, 'a', &opt);
666*760c2415Smrg     assert(opt);
667*760c2415Smrg     opt = false;
668*760c2415Smrg     args = ["program", "-a"];
669*760c2415Smrg     getopt(args, 'a', "help string", &opt);
670*760c2415Smrg     assert(opt);
671*760c2415Smrg     opt = false;
672*760c2415Smrg     args = ["program", "-a"];
673*760c2415Smrg     getopt(args, config.caseSensitive, 'a', "help string", &opt);
674*760c2415Smrg     assert(opt);
675*760c2415Smrg 
676*760c2415Smrg     assertThrown(getopt(args, "", "forgot to put a string", &opt));
677*760c2415Smrg }
678*760c2415Smrg 
getoptImpl(T...)679*760c2415Smrg private void getoptImpl(T...)(ref string[] args, ref configuration cfg,
680*760c2415Smrg     ref GetoptResult rslt, ref GetOptException excep,
681*760c2415Smrg     void[][string] visitedLongOpts, void[][string] visitedShortOpts, T opts)
682*760c2415Smrg {
683*760c2415Smrg     enum validationMessage = optionValidator!T;
684*760c2415Smrg     static assert(validationMessage == "", validationMessage);
685*760c2415Smrg 
686*760c2415Smrg     import std.algorithm.mutation : remove;
687*760c2415Smrg     import std.conv : to;
688*760c2415Smrg     static if (opts.length)
689*760c2415Smrg     {
690*760c2415Smrg         static if (is(typeof(opts[0]) : config))
691*760c2415Smrg         {
692*760c2415Smrg             // it's a configuration flag, act on it
693*760c2415Smrg             setConfig(cfg, opts[0]);
694*760c2415Smrg             return getoptImpl(args, cfg, rslt, excep, visitedLongOpts,
695*760c2415Smrg                 visitedShortOpts, opts[1 .. $]);
696*760c2415Smrg         }
697*760c2415Smrg         else
698*760c2415Smrg         {
699*760c2415Smrg             // it's an option string
700*760c2415Smrg             auto option = to!string(opts[0]);
701*760c2415Smrg             if (option.length == 0)
702*760c2415Smrg             {
703*760c2415Smrg                 excep = new GetOptException("An option name may not be an empty string", excep);
704*760c2415Smrg                 return;
705*760c2415Smrg             }
706*760c2415Smrg             Option optionHelp = splitAndGet(option);
707*760c2415Smrg             optionHelp.required = cfg.required;
708*760c2415Smrg 
709*760c2415Smrg             if (optionHelp.optLong.length)
710*760c2415Smrg             {
711*760c2415Smrg                 assert(optionHelp.optLong !in visitedLongOpts,
712*760c2415Smrg                     "Long option " ~ optionHelp.optLong ~ " is multiply defined");
713*760c2415Smrg 
714*760c2415Smrg                 visitedLongOpts[optionHelp.optLong] = [];
715*760c2415Smrg             }
716*760c2415Smrg 
717*760c2415Smrg             if (optionHelp.optShort.length)
718*760c2415Smrg             {
719*760c2415Smrg                 assert(optionHelp.optShort !in visitedShortOpts,
720*760c2415Smrg                     "Short option " ~ optionHelp.optShort
721*760c2415Smrg                     ~ " is multiply defined");
722*760c2415Smrg 
723*760c2415Smrg                 visitedShortOpts[optionHelp.optShort] = [];
724*760c2415Smrg             }
725*760c2415Smrg 
726*760c2415Smrg             static if (is(typeof(opts[1]) : string))
727*760c2415Smrg             {
728*760c2415Smrg                 auto receiver = opts[2];
729*760c2415Smrg                 optionHelp.help = opts[1];
730*760c2415Smrg                 immutable lowSliceIdx = 3;
731*760c2415Smrg             }
732*760c2415Smrg             else
733*760c2415Smrg             {
734*760c2415Smrg                 auto receiver = opts[1];
735*760c2415Smrg                 immutable lowSliceIdx = 2;
736*760c2415Smrg             }
737*760c2415Smrg 
738*760c2415Smrg             rslt.options ~= optionHelp;
739*760c2415Smrg 
740*760c2415Smrg             bool incremental;
741*760c2415Smrg             // Handle options of the form --blah+
742*760c2415Smrg             if (option.length && option[$ - 1] == autoIncrementChar)
743*760c2415Smrg             {
744*760c2415Smrg                 option = option[0 .. $ - 1];
745*760c2415Smrg                 incremental = true;
746*760c2415Smrg             }
747*760c2415Smrg 
748*760c2415Smrg             bool optWasHandled = handleOption(option, receiver, args, cfg, incremental);
749*760c2415Smrg 
750*760c2415Smrg             if (cfg.required && !optWasHandled)
751*760c2415Smrg             {
752*760c2415Smrg                 excep = new GetOptException("Required option "
753*760c2415Smrg                     ~ option ~ " was not supplied", excep);
754*760c2415Smrg             }
755*760c2415Smrg             cfg.required = false;
756*760c2415Smrg 
757*760c2415Smrg             getoptImpl(args, cfg, rslt, excep, visitedLongOpts,
758*760c2415Smrg                 visitedShortOpts, opts[lowSliceIdx .. $]);
759*760c2415Smrg         }
760*760c2415Smrg     }
761*760c2415Smrg     else
762*760c2415Smrg     {
763*760c2415Smrg         // no more options to look for, potentially some arguments left
764*760c2415Smrg         for (size_t i = 1; i < args.length;)
765*760c2415Smrg         {
766*760c2415Smrg             auto a = args[i];
767*760c2415Smrg             if (endOfOptions.length && a == endOfOptions)
768*760c2415Smrg             {
769*760c2415Smrg                 // Consume the "--" if keepEndOfOptions is not specified
770*760c2415Smrg                 if (!cfg.keepEndOfOptions)
771*760c2415Smrg                     args = args.remove(i);
772*760c2415Smrg                 break;
773*760c2415Smrg             }
774*760c2415Smrg             if (!a.length || a[0] != optionChar)
775*760c2415Smrg             {
776*760c2415Smrg                 // not an option
777*760c2415Smrg                 if (cfg.stopOnFirstNonOption) break;
778*760c2415Smrg                 ++i;
779*760c2415Smrg                 continue;
780*760c2415Smrg             }
781*760c2415Smrg             if (a == "--help" || a == "-h")
782*760c2415Smrg             {
783*760c2415Smrg                 rslt.helpWanted = true;
784*760c2415Smrg                 args = args.remove(i);
785*760c2415Smrg                 continue;
786*760c2415Smrg             }
787*760c2415Smrg             if (!cfg.passThrough)
788*760c2415Smrg             {
789*760c2415Smrg                 throw new GetOptException("Unrecognized option "~a, excep);
790*760c2415Smrg             }
791*760c2415Smrg             ++i;
792*760c2415Smrg         }
793*760c2415Smrg 
794*760c2415Smrg         Option helpOpt;
795*760c2415Smrg         helpOpt.optShort = "-h";
796*760c2415Smrg         helpOpt.optLong = "--help";
797*760c2415Smrg         helpOpt.help = "This help information.";
798*760c2415Smrg         rslt.options ~= helpOpt;
799*760c2415Smrg     }
800*760c2415Smrg }
801*760c2415Smrg 
handleOption(R)802*760c2415Smrg private bool handleOption(R)(string option, R receiver, ref string[] args,
803*760c2415Smrg     ref configuration cfg, bool incremental)
804*760c2415Smrg {
805*760c2415Smrg     import std.algorithm.iteration : map, splitter;
806*760c2415Smrg     import std.ascii : isAlpha;
807*760c2415Smrg     import std.conv : text, to;
808*760c2415Smrg     // Scan arguments looking for a match for this option
809*760c2415Smrg     bool ret = false;
810*760c2415Smrg     for (size_t i = 1; i < args.length; )
811*760c2415Smrg     {
812*760c2415Smrg         auto a = args[i];
813*760c2415Smrg         if (endOfOptions.length && a == endOfOptions) break;
814*760c2415Smrg         if (cfg.stopOnFirstNonOption && (!a.length || a[0] != optionChar))
815*760c2415Smrg         {
816*760c2415Smrg             // first non-option is end of options
817*760c2415Smrg             break;
818*760c2415Smrg         }
819*760c2415Smrg         // Unbundle bundled arguments if necessary
820*760c2415Smrg         if (cfg.bundling && a.length > 2 && a[0] == optionChar &&
821*760c2415Smrg                 a[1] != optionChar)
822*760c2415Smrg         {
823*760c2415Smrg             string[] expanded;
824*760c2415Smrg             foreach (j, dchar c; a[1 .. $])
825*760c2415Smrg             {
826*760c2415Smrg                 // If the character is not alpha, stop right there. This allows
827*760c2415Smrg                 // e.g. -j100 to work as "pass argument 100 to option -j".
828*760c2415Smrg                 if (!isAlpha(c))
829*760c2415Smrg                 {
830*760c2415Smrg                     if (c == '=')
831*760c2415Smrg                         j++;
832*760c2415Smrg                     expanded ~= a[j + 1 .. $];
833*760c2415Smrg                     break;
834*760c2415Smrg                 }
835*760c2415Smrg                 expanded ~= text(optionChar, c);
836*760c2415Smrg             }
837*760c2415Smrg             args = args[0 .. i] ~ expanded ~ args[i + 1 .. $];
838*760c2415Smrg             continue;
839*760c2415Smrg         }
840*760c2415Smrg 
841*760c2415Smrg         string val;
842*760c2415Smrg         if (!optMatch(a, option, val, cfg))
843*760c2415Smrg         {
844*760c2415Smrg             ++i;
845*760c2415Smrg             continue;
846*760c2415Smrg         }
847*760c2415Smrg 
848*760c2415Smrg         ret = true;
849*760c2415Smrg 
850*760c2415Smrg         // found it
851*760c2415Smrg         // from here on, commit to eat args[i]
852*760c2415Smrg         // (and potentially args[i + 1] too, but that comes later)
853*760c2415Smrg         args = args[0 .. i] ~ args[i + 1 .. $];
854*760c2415Smrg 
855*760c2415Smrg         static if (is(typeof(*receiver) == bool))
856*760c2415Smrg         {
857*760c2415Smrg             if (val.length)
858*760c2415Smrg             {
859*760c2415Smrg                 // parse '--b=true/false'
860*760c2415Smrg                 *receiver = to!(typeof(*receiver))(val);
861*760c2415Smrg             }
862*760c2415Smrg             else
863*760c2415Smrg             {
864*760c2415Smrg                 // no argument means set it to true
865*760c2415Smrg                 *receiver = true;
866*760c2415Smrg             }
867*760c2415Smrg         }
868*760c2415Smrg         else
869*760c2415Smrg         {
870*760c2415Smrg             import std.exception : enforce;
871*760c2415Smrg             // non-boolean option, which might include an argument
872*760c2415Smrg             //enum isCallbackWithOneParameter = is(typeof(receiver("")) : void);
873*760c2415Smrg             enum isCallbackWithLessThanTwoParameters =
874*760c2415Smrg                 (is(typeof(receiver) == delegate) || is(typeof(*receiver) == function)) &&
875*760c2415Smrg                 !is(typeof(receiver("", "")));
876*760c2415Smrg             if (!isCallbackWithLessThanTwoParameters && !(val.length) && !incremental)
877*760c2415Smrg             {
878*760c2415Smrg                 // Eat the next argument too.  Check to make sure there's one
879*760c2415Smrg                 // to be eaten first, though.
880*760c2415Smrg                 enforce(i < args.length,
881*760c2415Smrg                     "Missing value for argument " ~ a ~ ".");
882*760c2415Smrg                 val = args[i];
883*760c2415Smrg                 args = args[0 .. i] ~ args[i + 1 .. $];
884*760c2415Smrg             }
885*760c2415Smrg             static if (is(typeof(*receiver) == enum))
886*760c2415Smrg             {
887*760c2415Smrg                 *receiver = to!(typeof(*receiver))(val);
888*760c2415Smrg             }
889*760c2415Smrg             else static if (is(typeof(*receiver) : real))
890*760c2415Smrg             {
891*760c2415Smrg                 // numeric receiver
892*760c2415Smrg                 if (incremental) ++*receiver;
893*760c2415Smrg                 else *receiver = to!(typeof(*receiver))(val);
894*760c2415Smrg             }
895*760c2415Smrg             else static if (is(typeof(*receiver) == string))
896*760c2415Smrg             {
897*760c2415Smrg                 // string receiver
898*760c2415Smrg                 *receiver = to!(typeof(*receiver))(val);
899*760c2415Smrg             }
900*760c2415Smrg             else static if (is(typeof(receiver) == delegate) ||
901*760c2415Smrg                             is(typeof(*receiver) == function))
902*760c2415Smrg             {
903*760c2415Smrg                 static if (is(typeof(receiver("", "")) : void))
904*760c2415Smrg                 {
905*760c2415Smrg                     // option with argument
906*760c2415Smrg                     receiver(option, val);
907*760c2415Smrg                 }
908*760c2415Smrg                 else static if (is(typeof(receiver("")) : void))
909*760c2415Smrg                 {
910*760c2415Smrg                     static assert(is(typeof(receiver("")) : void));
911*760c2415Smrg                     // boolean-style receiver
912*760c2415Smrg                     receiver(option);
913*760c2415Smrg                 }
914*760c2415Smrg                 else
915*760c2415Smrg                 {
916*760c2415Smrg                     static assert(is(typeof(receiver()) : void));
917*760c2415Smrg                     // boolean-style receiver without argument
918*760c2415Smrg                     receiver();
919*760c2415Smrg                 }
920*760c2415Smrg             }
921*760c2415Smrg             else static if (isArray!(typeof(*receiver)))
922*760c2415Smrg             {
923*760c2415Smrg                 // array receiver
924*760c2415Smrg                 import std.range : ElementEncodingType;
925*760c2415Smrg                 alias E = ElementEncodingType!(typeof(*receiver));
926*760c2415Smrg 
927*760c2415Smrg                 if (arraySep == "")
928*760c2415Smrg                 {
929*760c2415Smrg                     *receiver ~= to!E(val);
930*760c2415Smrg                 }
931*760c2415Smrg                 else
932*760c2415Smrg                 {
933*760c2415Smrg                     foreach (elem; val.splitter(arraySep).map!(a => to!E(a))())
934*760c2415Smrg                         *receiver ~= elem;
935*760c2415Smrg                 }
936*760c2415Smrg             }
937*760c2415Smrg             else static if (isAssociativeArray!(typeof(*receiver)))
938*760c2415Smrg             {
939*760c2415Smrg                 // hash receiver
940*760c2415Smrg                 alias K = typeof(receiver.keys[0]);
941*760c2415Smrg                 alias V = typeof(receiver.values[0]);
942*760c2415Smrg 
943*760c2415Smrg                 import std.range : only;
944*760c2415Smrg                 import std.string : indexOf;
945*760c2415Smrg                 import std.typecons : Tuple, tuple;
946*760c2415Smrg 
947*760c2415Smrg                 static Tuple!(K, V) getter(string input)
948*760c2415Smrg                 {
949*760c2415Smrg                     auto j = indexOf(input, assignChar);
950*760c2415Smrg                     enforce!GetOptException(j != -1, "Could not find '"
951*760c2415Smrg                         ~ to!string(assignChar) ~ "' in argument '" ~ input ~ "'.");
952*760c2415Smrg                     auto key = input[0 .. j];
953*760c2415Smrg                     auto value = input[j + 1 .. $];
954*760c2415Smrg                     return tuple(to!K(key), to!V(value));
955*760c2415Smrg                 }
956*760c2415Smrg 
957*760c2415Smrg                 static void setHash(Range)(R receiver, Range range)
958*760c2415Smrg                 {
959*760c2415Smrg                     foreach (k, v; range.map!getter)
960*760c2415Smrg                         (*receiver)[k] = v;
961*760c2415Smrg                 }
962*760c2415Smrg 
963*760c2415Smrg                 if (arraySep == "")
964*760c2415Smrg                     setHash(receiver, val.only);
965*760c2415Smrg                 else
966*760c2415Smrg                     setHash(receiver, val.splitter(arraySep));
967*760c2415Smrg             }
968*760c2415Smrg             else
969*760c2415Smrg                 static assert(false, "getopt does not know how to handle the type " ~ typeof(receiver).stringof);
970*760c2415Smrg         }
971*760c2415Smrg     }
972*760c2415Smrg 
973*760c2415Smrg     return ret;
974*760c2415Smrg }
975*760c2415Smrg 
976*760c2415Smrg // 17574
977*760c2415Smrg @system unittest
978*760c2415Smrg {
979*760c2415Smrg     import std.algorithm.searching : startsWith;
980*760c2415Smrg 
981*760c2415Smrg     try
982*760c2415Smrg     {
983*760c2415Smrg         string[string] mapping;
984*760c2415Smrg         immutable as = arraySep;
985*760c2415Smrg         arraySep = ",";
986*760c2415Smrg         scope (exit)
987*760c2415Smrg             arraySep = as;
988*760c2415Smrg         string[] args = ["testProgram", "-m", "a=b,c=\"d,e,f\""];
989*760c2415Smrg         args.getopt("m", &mapping);
990*760c2415Smrg         assert(false, "Exception not thrown");
991*760c2415Smrg     }
992*760c2415Smrg     catch (GetOptException goe)
993*760c2415Smrg         assert(goe.msg.startsWith("Could not find"));
994*760c2415Smrg }
995*760c2415Smrg 
996*760c2415Smrg // 5316 - arrays with arraySep
997*760c2415Smrg @system unittest
998*760c2415Smrg {
999*760c2415Smrg     import std.conv;
1000*760c2415Smrg 
1001*760c2415Smrg     arraySep = ",";
1002*760c2415Smrg     scope (exit) arraySep = "";
1003*760c2415Smrg 
1004*760c2415Smrg     string[] names;
1005*760c2415Smrg     auto args = ["program.name", "-nfoo,bar,baz"];
1006*760c2415Smrg     getopt(args, "name|n", &names);
1007*760c2415Smrg     assert(names == ["foo", "bar", "baz"], to!string(names));
1008*760c2415Smrg 
1009*760c2415Smrg     names = names.init;
1010*760c2415Smrg     args = ["program.name", "-n", "foo,bar,baz"];
1011*760c2415Smrg     getopt(args, "name|n", &names);
1012*760c2415Smrg     assert(names == ["foo", "bar", "baz"], to!string(names));
1013*760c2415Smrg 
1014*760c2415Smrg     names = names.init;
1015*760c2415Smrg     args = ["program.name", "--name=foo,bar,baz"];
1016*760c2415Smrg     getopt(args, "name|n", &names);
1017*760c2415Smrg     assert(names == ["foo", "bar", "baz"], to!string(names));
1018*760c2415Smrg 
1019*760c2415Smrg     names = names.init;
1020*760c2415Smrg     args = ["program.name", "--name", "foo,bar,baz"];
1021*760c2415Smrg     getopt(args, "name|n", &names);
1022*760c2415Smrg     assert(names == ["foo", "bar", "baz"], to!string(names));
1023*760c2415Smrg }
1024*760c2415Smrg 
1025*760c2415Smrg // 5316 - associative arrays with arraySep
1026*760c2415Smrg @system unittest
1027*760c2415Smrg {
1028*760c2415Smrg     import std.conv;
1029*760c2415Smrg 
1030*760c2415Smrg     arraySep = ",";
1031*760c2415Smrg     scope (exit) arraySep = "";
1032*760c2415Smrg 
1033*760c2415Smrg     int[string] values;
1034*760c2415Smrg     values = values.init;
1035*760c2415Smrg     auto args = ["program.name", "-vfoo=0,bar=1,baz=2"];
1036*760c2415Smrg     getopt(args, "values|v", &values);
1037*760c2415Smrg     assert(values == ["foo":0, "bar":1, "baz":2], to!string(values));
1038*760c2415Smrg 
1039*760c2415Smrg     values = values.init;
1040*760c2415Smrg     args = ["program.name", "-v", "foo=0,bar=1,baz=2"];
1041*760c2415Smrg     getopt(args, "values|v", &values);
1042*760c2415Smrg     assert(values == ["foo":0, "bar":1, "baz":2], to!string(values));
1043*760c2415Smrg 
1044*760c2415Smrg     values = values.init;
1045*760c2415Smrg     args = ["program.name", "--values=foo=0,bar=1,baz=2"];
1046*760c2415Smrg     getopt(args, "values|t", &values);
1047*760c2415Smrg     assert(values == ["foo":0, "bar":1, "baz":2], to!string(values));
1048*760c2415Smrg 
1049*760c2415Smrg     values = values.init;
1050*760c2415Smrg     args = ["program.name", "--values", "foo=0,bar=1,baz=2"];
1051*760c2415Smrg     getopt(args, "values|v", &values);
1052*760c2415Smrg     assert(values == ["foo":0, "bar":1, "baz":2], to!string(values));
1053*760c2415Smrg }
1054*760c2415Smrg 
1055*760c2415Smrg /**
1056*760c2415Smrg    The option character (default '-').
1057*760c2415Smrg 
1058*760c2415Smrg    Defaults to '-' but it can be assigned to prior to calling $(D getopt).
1059*760c2415Smrg  */
1060*760c2415Smrg dchar optionChar = '-';
1061*760c2415Smrg 
1062*760c2415Smrg /**
1063*760c2415Smrg    The string that conventionally marks the end of all options (default '--').
1064*760c2415Smrg 
1065*760c2415Smrg    Defaults to "--" but can be assigned to prior to calling $(D getopt). Assigning an
1066*760c2415Smrg    empty string to $(D endOfOptions) effectively disables it.
1067*760c2415Smrg  */
1068*760c2415Smrg string endOfOptions = "--";
1069*760c2415Smrg 
1070*760c2415Smrg /**
1071*760c2415Smrg    The assignment character used in options with parameters (default '=').
1072*760c2415Smrg 
1073*760c2415Smrg    Defaults to '=' but can be assigned to prior to calling $(D getopt).
1074*760c2415Smrg  */
1075*760c2415Smrg dchar assignChar = '=';
1076*760c2415Smrg 
1077*760c2415Smrg /**
1078*760c2415Smrg    The string used to separate the elements of an array or associative array
1079*760c2415Smrg    (default is "" which means the elements are separated by whitespace).
1080*760c2415Smrg 
1081*760c2415Smrg    Defaults to "" but can be assigned to prior to calling $(D getopt).
1082*760c2415Smrg  */
1083*760c2415Smrg string arraySep = "";
1084*760c2415Smrg 
1085*760c2415Smrg private enum autoIncrementChar = '+';
1086*760c2415Smrg 
1087*760c2415Smrg private struct configuration
1088*760c2415Smrg {
1089*760c2415Smrg     import std.bitmanip : bitfields;
1090*760c2415Smrg     mixin(bitfields!(
1091*760c2415Smrg                 bool, "caseSensitive",  1,
1092*760c2415Smrg                 bool, "bundling", 1,
1093*760c2415Smrg                 bool, "passThrough", 1,
1094*760c2415Smrg                 bool, "stopOnFirstNonOption", 1,
1095*760c2415Smrg                 bool, "keepEndOfOptions", 1,
1096*760c2415Smrg                 bool, "required", 1,
1097*760c2415Smrg                 ubyte, "", 2));
1098*760c2415Smrg }
1099*760c2415Smrg 
optMatch(string arg,string optPattern,ref string value,configuration cfg)1100*760c2415Smrg private bool optMatch(string arg, string optPattern, ref string value,
1101*760c2415Smrg     configuration cfg) @safe
1102*760c2415Smrg {
1103*760c2415Smrg     import std.array : split;
1104*760c2415Smrg     import std.string : indexOf;
1105*760c2415Smrg     import std.uni : toUpper;
1106*760c2415Smrg     //writeln("optMatch:\n  ", arg, "\n  ", optPattern, "\n  ", value);
1107*760c2415Smrg     //scope(success) writeln("optMatch result: ", value);
1108*760c2415Smrg     if (arg.length < 2 || arg[0] != optionChar) return false;
1109*760c2415Smrg     // yank the leading '-'
1110*760c2415Smrg     arg = arg[1 .. $];
1111*760c2415Smrg     immutable isLong = arg.length > 1 && arg[0] == optionChar;
1112*760c2415Smrg     //writeln("isLong: ", isLong);
1113*760c2415Smrg     // yank the second '-' if present
1114*760c2415Smrg     if (isLong) arg = arg[1 .. $];
1115*760c2415Smrg     immutable eqPos = indexOf(arg, assignChar);
1116*760c2415Smrg     if (isLong && eqPos >= 0)
1117*760c2415Smrg     {
1118*760c2415Smrg         // argument looks like --opt=value
1119*760c2415Smrg         value = arg[eqPos + 1 .. $];
1120*760c2415Smrg         arg = arg[0 .. eqPos];
1121*760c2415Smrg     }
1122*760c2415Smrg     else
1123*760c2415Smrg     {
1124*760c2415Smrg         if (!isLong && eqPos == 1)
1125*760c2415Smrg         {
1126*760c2415Smrg             // argument looks like -o=value
1127*760c2415Smrg             value = arg[2 .. $];
1128*760c2415Smrg             arg = arg[0 .. 1];
1129*760c2415Smrg         }
1130*760c2415Smrg         else
1131*760c2415Smrg         if (!isLong && !cfg.bundling)
1132*760c2415Smrg         {
1133*760c2415Smrg             // argument looks like -ovalue and there's no bundling
1134*760c2415Smrg             value = arg[1 .. $];
1135*760c2415Smrg             arg = arg[0 .. 1];
1136*760c2415Smrg         }
1137*760c2415Smrg         else
1138*760c2415Smrg         {
1139*760c2415Smrg             // argument looks like --opt, or -oxyz with bundling
1140*760c2415Smrg             value = null;
1141*760c2415Smrg         }
1142*760c2415Smrg     }
1143*760c2415Smrg     //writeln("Arg: ", arg, " pattern: ", optPattern, " value: ", value);
1144*760c2415Smrg     // Split the option
1145*760c2415Smrg     const variants = split(optPattern, "|");
1146*760c2415Smrg     foreach (v ; variants)
1147*760c2415Smrg     {
1148*760c2415Smrg         //writeln("Trying variant: ", v, " against ", arg);
1149*760c2415Smrg         if (arg == v || !cfg.caseSensitive && toUpper(arg) == toUpper(v))
1150*760c2415Smrg             return true;
1151*760c2415Smrg         if (cfg.bundling && !isLong && v.length == 1
1152*760c2415Smrg                 && indexOf(arg, v) >= 0)
1153*760c2415Smrg         {
1154*760c2415Smrg             //writeln("success");
1155*760c2415Smrg             return true;
1156*760c2415Smrg         }
1157*760c2415Smrg     }
1158*760c2415Smrg     return false;
1159*760c2415Smrg }
1160*760c2415Smrg 
setConfig(ref configuration cfg,config option)1161*760c2415Smrg private void setConfig(ref configuration cfg, config option) @safe pure nothrow @nogc
1162*760c2415Smrg {
1163*760c2415Smrg     final switch (option)
1164*760c2415Smrg     {
1165*760c2415Smrg     case config.caseSensitive: cfg.caseSensitive = true; break;
1166*760c2415Smrg     case config.caseInsensitive: cfg.caseSensitive = false; break;
1167*760c2415Smrg     case config.bundling: cfg.bundling = true; break;
1168*760c2415Smrg     case config.noBundling: cfg.bundling = false; break;
1169*760c2415Smrg     case config.passThrough: cfg.passThrough = true; break;
1170*760c2415Smrg     case config.noPassThrough: cfg.passThrough = false; break;
1171*760c2415Smrg     case config.required: cfg.required = true; break;
1172*760c2415Smrg     case config.stopOnFirstNonOption:
1173*760c2415Smrg         cfg.stopOnFirstNonOption = true; break;
1174*760c2415Smrg     case config.keepEndOfOptions:
1175*760c2415Smrg         cfg.keepEndOfOptions = true; break;
1176*760c2415Smrg     }
1177*760c2415Smrg }
1178*760c2415Smrg 
1179*760c2415Smrg @system unittest
1180*760c2415Smrg {
1181*760c2415Smrg     import std.conv;
1182*760c2415Smrg     import std.math;
1183*760c2415Smrg 
1184*760c2415Smrg     uint paranoid = 2;
1185*760c2415Smrg     string[] args = ["program.name", "--paranoid", "--paranoid", "--paranoid"];
1186*760c2415Smrg     getopt(args, "paranoid+", &paranoid);
1187*760c2415Smrg     assert(paranoid == 5, to!(string)(paranoid));
1188*760c2415Smrg 
1189*760c2415Smrg     enum Color { no, yes }
1190*760c2415Smrg     Color color;
1191*760c2415Smrg     args = ["program.name", "--color=yes",];
1192*760c2415Smrg     getopt(args, "color", &color);
1193*760c2415Smrg     assert(color, to!(string)(color));
1194*760c2415Smrg 
1195*760c2415Smrg     color = Color.no;
1196*760c2415Smrg     args = ["program.name", "--color", "yes",];
1197*760c2415Smrg     getopt(args, "color", &color);
1198*760c2415Smrg     assert(color, to!(string)(color));
1199*760c2415Smrg 
1200*760c2415Smrg     string data = "file.dat";
1201*760c2415Smrg     int length = 24;
1202*760c2415Smrg     bool verbose = false;
1203*760c2415Smrg     args = ["program.name", "--length=5", "--file", "dat.file", "--verbose"];
1204*760c2415Smrg     getopt(
1205*760c2415Smrg         args,
1206*760c2415Smrg         "length",  &length,
1207*760c2415Smrg         "file",    &data,
1208*760c2415Smrg         "verbose", &verbose);
1209*760c2415Smrg     assert(args.length == 1);
1210*760c2415Smrg     assert(data == "dat.file");
1211*760c2415Smrg     assert(length == 5);
1212*760c2415Smrg     assert(verbose);
1213*760c2415Smrg 
1214*760c2415Smrg     //
1215*760c2415Smrg     string[] outputFiles;
1216*760c2415Smrg     args = ["program.name", "--output=myfile.txt", "--output", "yourfile.txt"];
1217*760c2415Smrg     getopt(args, "output", &outputFiles);
1218*760c2415Smrg     assert(outputFiles.length == 2
1219*760c2415Smrg            && outputFiles[0] == "myfile.txt" && outputFiles[1] == "yourfile.txt");
1220*760c2415Smrg 
1221*760c2415Smrg     outputFiles = [];
1222*760c2415Smrg     arraySep = ",";
1223*760c2415Smrg     args = ["program.name", "--output", "myfile.txt,yourfile.txt"];
1224*760c2415Smrg     getopt(args, "output", &outputFiles);
1225*760c2415Smrg     assert(outputFiles.length == 2
1226*760c2415Smrg            && outputFiles[0] == "myfile.txt" && outputFiles[1] == "yourfile.txt");
1227*760c2415Smrg     arraySep = "";
1228*760c2415Smrg 
foreach(testArgs;)1229*760c2415Smrg     foreach (testArgs;
1230*760c2415Smrg         [["program.name", "--tune=alpha=0.5", "--tune", "beta=0.6"],
1231*760c2415Smrg          ["program.name", "--tune=alpha=0.5,beta=0.6"],
1232*760c2415Smrg          ["program.name", "--tune", "alpha=0.5,beta=0.6"]])
1233*760c2415Smrg     {
1234*760c2415Smrg         arraySep = ",";
1235*760c2415Smrg         double[string] tuningParms;
1236*760c2415Smrg         getopt(testArgs, "tune", &tuningParms);
1237*760c2415Smrg         assert(testArgs.length == 1);
1238*760c2415Smrg         assert(tuningParms.length == 2);
1239*760c2415Smrg         assert(approxEqual(tuningParms["alpha"], 0.5));
1240*760c2415Smrg         assert(approxEqual(tuningParms["beta"], 0.6));
1241*760c2415Smrg         arraySep = "";
1242*760c2415Smrg     }
1243*760c2415Smrg 
1244*760c2415Smrg     uint verbosityLevel = 1;
myHandler(string option)1245*760c2415Smrg     void myHandler(string option)
1246*760c2415Smrg     {
1247*760c2415Smrg         if (option == "quiet")
1248*760c2415Smrg         {
1249*760c2415Smrg             verbosityLevel = 0;
1250*760c2415Smrg         }
1251*760c2415Smrg         else
1252*760c2415Smrg         {
1253*760c2415Smrg             assert(option == "verbose");
1254*760c2415Smrg             verbosityLevel = 2;
1255*760c2415Smrg         }
1256*760c2415Smrg     }
1257*760c2415Smrg     args = ["program.name", "--quiet"];
1258*760c2415Smrg     getopt(args, "verbose", &myHandler, "quiet", &myHandler);
1259*760c2415Smrg     assert(verbosityLevel == 0);
1260*760c2415Smrg     args = ["program.name", "--verbose"];
1261*760c2415Smrg     getopt(args, "verbose", &myHandler, "quiet", &myHandler);
1262*760c2415Smrg     assert(verbosityLevel == 2);
1263*760c2415Smrg 
1264*760c2415Smrg     verbosityLevel = 1;
myHandler2(string option,string value)1265*760c2415Smrg     void myHandler2(string option, string value)
1266*760c2415Smrg     {
1267*760c2415Smrg         assert(option == "verbose");
1268*760c2415Smrg         verbosityLevel = 2;
1269*760c2415Smrg     }
1270*760c2415Smrg     args = ["program.name", "--verbose", "2"];
1271*760c2415Smrg     getopt(args, "verbose", &myHandler2);
1272*760c2415Smrg     assert(verbosityLevel == 2);
1273*760c2415Smrg 
1274*760c2415Smrg     verbosityLevel = 1;
myHandler3()1275*760c2415Smrg     void myHandler3()
1276*760c2415Smrg     {
1277*760c2415Smrg         verbosityLevel = 2;
1278*760c2415Smrg     }
1279*760c2415Smrg     args = ["program.name", "--verbose"];
1280*760c2415Smrg     getopt(args, "verbose", &myHandler3);
1281*760c2415Smrg     assert(verbosityLevel == 2);
1282*760c2415Smrg 
1283*760c2415Smrg     bool foo, bar;
1284*760c2415Smrg     args = ["program.name", "--foo", "--bAr"];
1285*760c2415Smrg     getopt(args,
1286*760c2415Smrg         std.getopt.config.caseSensitive,
1287*760c2415Smrg         std.getopt.config.passThrough,
1288*760c2415Smrg         "foo", &foo,
1289*760c2415Smrg         "bar", &bar);
1290*760c2415Smrg     assert(args[1] == "--bAr");
1291*760c2415Smrg 
1292*760c2415Smrg     // test stopOnFirstNonOption
1293*760c2415Smrg 
1294*760c2415Smrg     args = ["program.name", "--foo", "nonoption", "--bar"];
1295*760c2415Smrg     foo = bar = false;
1296*760c2415Smrg     getopt(args,
1297*760c2415Smrg         std.getopt.config.stopOnFirstNonOption,
1298*760c2415Smrg         "foo", &foo,
1299*760c2415Smrg         "bar", &bar);
1300*760c2415Smrg     assert(foo && !bar && args[1] == "nonoption" && args[2] == "--bar");
1301*760c2415Smrg 
1302*760c2415Smrg     args = ["program.name", "--foo", "nonoption", "--zab"];
1303*760c2415Smrg     foo = bar = false;
1304*760c2415Smrg     getopt(args,
1305*760c2415Smrg         std.getopt.config.stopOnFirstNonOption,
1306*760c2415Smrg         "foo", &foo,
1307*760c2415Smrg         "bar", &bar);
1308*760c2415Smrg     assert(foo && !bar && args[1] == "nonoption" && args[2] == "--zab");
1309*760c2415Smrg 
1310*760c2415Smrg     args = ["program.name", "--fb1", "--fb2=true", "--tb1=false"];
1311*760c2415Smrg     bool fb1, fb2;
1312*760c2415Smrg     bool tb1 = true;
1313*760c2415Smrg     getopt(args, "fb1", &fb1, "fb2", &fb2, "tb1", &tb1);
1314*760c2415Smrg     assert(fb1 && fb2 && !tb1);
1315*760c2415Smrg 
1316*760c2415Smrg     // test keepEndOfOptions
1317*760c2415Smrg 
1318*760c2415Smrg     args = ["program.name", "--foo", "nonoption", "--bar", "--", "--baz"];
1319*760c2415Smrg     getopt(args,
1320*760c2415Smrg         std.getopt.config.keepEndOfOptions,
1321*760c2415Smrg         "foo", &foo,
1322*760c2415Smrg         "bar", &bar);
1323*760c2415Smrg     assert(args == ["program.name", "nonoption", "--", "--baz"]);
1324*760c2415Smrg 
1325*760c2415Smrg     // Ensure old behavior without the keepEndOfOptions
1326*760c2415Smrg 
1327*760c2415Smrg     args = ["program.name", "--foo", "nonoption", "--bar", "--", "--baz"];
1328*760c2415Smrg     getopt(args,
1329*760c2415Smrg         "foo", &foo,
1330*760c2415Smrg         "bar", &bar);
1331*760c2415Smrg     assert(args == ["program.name", "nonoption", "--baz"]);
1332*760c2415Smrg 
1333*760c2415Smrg     // test function callbacks
1334*760c2415Smrg 
1335*760c2415Smrg     static class MyEx : Exception
1336*760c2415Smrg     {
this()1337*760c2415Smrg         this() { super(""); }
this(string option)1338*760c2415Smrg         this(string option) { this(); this.option = option; }
this(string option,string value)1339*760c2415Smrg         this(string option, string value) { this(option); this.value = value; }
1340*760c2415Smrg 
1341*760c2415Smrg         string option;
1342*760c2415Smrg         string value;
1343*760c2415Smrg     }
1344*760c2415Smrg 
myStaticHandler1()1345*760c2415Smrg     static void myStaticHandler1() { throw new MyEx(); }
1346*760c2415Smrg     args = ["program.name", "--verbose"];
1347*760c2415Smrg     try { getopt(args, "verbose", &myStaticHandler1); assert(0); }
catch(MyEx ex)1348*760c2415Smrg     catch (MyEx ex) { assert(ex.option is null && ex.value is null); }
1349*760c2415Smrg 
myStaticHandler2(string option)1350*760c2415Smrg     static void myStaticHandler2(string option) { throw new MyEx(option); }
1351*760c2415Smrg     args = ["program.name", "--verbose"];
1352*760c2415Smrg     try { getopt(args, "verbose", &myStaticHandler2); assert(0); }
catch(MyEx ex)1353*760c2415Smrg     catch (MyEx ex) { assert(ex.option == "verbose" && ex.value is null); }
1354*760c2415Smrg 
myStaticHandler3(string option,string value)1355*760c2415Smrg     static void myStaticHandler3(string option, string value) { throw new MyEx(option, value); }
1356*760c2415Smrg     args = ["program.name", "--verbose", "2"];
1357*760c2415Smrg     try { getopt(args, "verbose", &myStaticHandler3); assert(0); }
catch(MyEx ex)1358*760c2415Smrg     catch (MyEx ex) { assert(ex.option == "verbose" && ex.value == "2"); }
1359*760c2415Smrg }
1360*760c2415Smrg 
1361*760c2415Smrg @safe unittest // @safe std.getopt.config option use
1362*760c2415Smrg {
1363*760c2415Smrg     long x = 0;
1364*760c2415Smrg     string[] args = ["program", "--inc-x", "--inc-x"];
1365*760c2415Smrg     getopt(args,
1366*760c2415Smrg            std.getopt.config.caseSensitive,
1367*760c2415Smrg            "inc-x", "Add one to x", delegate void() { x++; });
1368*760c2415Smrg     assert(x == 2);
1369*760c2415Smrg }
1370*760c2415Smrg 
1371*760c2415Smrg @system unittest
1372*760c2415Smrg {
1373*760c2415Smrg     // From bugzilla 2142
1374*760c2415Smrg     bool f_linenum, f_filename;
1375*760c2415Smrg     string[] args = [ "", "-nl" ];
1376*760c2415Smrg     getopt
1377*760c2415Smrg         (
1378*760c2415Smrg             args,
1379*760c2415Smrg             std.getopt.config.bundling,
1380*760c2415Smrg             //std.getopt.config.caseSensitive,
1381*760c2415Smrg             "linenum|l", &f_linenum,
1382*760c2415Smrg             "filename|n", &f_filename
1383*760c2415Smrg         );
1384*760c2415Smrg     assert(f_linenum);
1385*760c2415Smrg     assert(f_filename);
1386*760c2415Smrg }
1387*760c2415Smrg 
1388*760c2415Smrg @system unittest
1389*760c2415Smrg {
1390*760c2415Smrg     // From bugzilla 6887
1391*760c2415Smrg     string[] p;
1392*760c2415Smrg     string[] args = ["", "-pa"];
1393*760c2415Smrg     getopt(args, "p", &p);
1394*760c2415Smrg     assert(p.length == 1);
1395*760c2415Smrg     assert(p[0] == "a");
1396*760c2415Smrg }
1397*760c2415Smrg 
1398*760c2415Smrg @system unittest
1399*760c2415Smrg {
1400*760c2415Smrg     // From bugzilla 6888
1401*760c2415Smrg     int[string] foo;
1402*760c2415Smrg     auto args = ["", "-t", "a=1"];
1403*760c2415Smrg     getopt(args, "t", &foo);
1404*760c2415Smrg     assert(foo == ["a":1]);
1405*760c2415Smrg }
1406*760c2415Smrg 
1407*760c2415Smrg @system unittest
1408*760c2415Smrg {
1409*760c2415Smrg     // From bugzilla 9583
1410*760c2415Smrg     int opt;
1411*760c2415Smrg     auto args = ["prog", "--opt=123", "--", "--a", "--b", "--c"];
1412*760c2415Smrg     getopt(args, "opt", &opt);
1413*760c2415Smrg     assert(args == ["prog", "--a", "--b", "--c"]);
1414*760c2415Smrg }
1415*760c2415Smrg 
1416*760c2415Smrg @system unittest
1417*760c2415Smrg {
1418*760c2415Smrg     string foo, bar;
1419*760c2415Smrg     auto args = ["prog", "-thello", "-dbar=baz"];
1420*760c2415Smrg     getopt(args, "t", &foo, "d", &bar);
1421*760c2415Smrg     assert(foo == "hello");
1422*760c2415Smrg     assert(bar == "bar=baz");
1423*760c2415Smrg 
1424*760c2415Smrg     // From bugzilla 5762
1425*760c2415Smrg     string a;
1426*760c2415Smrg     args = ["prog", "-a-0x12"];
1427*760c2415Smrg     getopt(args, config.bundling, "a|addr", &a);
1428*760c2415Smrg     assert(a == "-0x12", a);
1429*760c2415Smrg     args = ["prog", "--addr=-0x12"];
1430*760c2415Smrg     getopt(args, config.bundling, "a|addr", &a);
1431*760c2415Smrg     assert(a == "-0x12");
1432*760c2415Smrg 
1433*760c2415Smrg     // From https://d.puremagic.com/issues/show_bug.cgi?id=11764
1434*760c2415Smrg     args = ["main", "-test"];
1435*760c2415Smrg     bool opt;
1436*760c2415Smrg     args.getopt(config.passThrough, "opt", &opt);
1437*760c2415Smrg     assert(args == ["main", "-test"]);
1438*760c2415Smrg 
1439*760c2415Smrg     // From https://issues.dlang.org/show_bug.cgi?id=15220
1440*760c2415Smrg     args = ["main", "-o=str"];
1441*760c2415Smrg     string o;
1442*760c2415Smrg     args.getopt("o", &o);
1443*760c2415Smrg     assert(o == "str");
1444*760c2415Smrg 
1445*760c2415Smrg     args = ["main", "-o=str"];
1446*760c2415Smrg     o = null;
1447*760c2415Smrg     args.getopt(config.bundling, "o", &o);
1448*760c2415Smrg     assert(o == "str");
1449*760c2415Smrg }
1450*760c2415Smrg 
1451*760c2415Smrg @system unittest // 5228
1452*760c2415Smrg {
1453*760c2415Smrg     import std.conv;
1454*760c2415Smrg     import std.exception;
1455*760c2415Smrg 
1456*760c2415Smrg     auto args = ["prog", "--foo=bar"];
1457*760c2415Smrg     int abc;
1458*760c2415Smrg     assertThrown!GetOptException(getopt(args, "abc", &abc));
1459*760c2415Smrg 
1460*760c2415Smrg     args = ["prog", "--abc=string"];
1461*760c2415Smrg     assertThrown!ConvException(getopt(args, "abc", &abc));
1462*760c2415Smrg }
1463*760c2415Smrg 
1464*760c2415Smrg @system unittest // From bugzilla 7693
1465*760c2415Smrg {
1466*760c2415Smrg     import std.exception;
1467*760c2415Smrg 
1468*760c2415Smrg     enum Foo {
1469*760c2415Smrg         bar,
1470*760c2415Smrg         baz
1471*760c2415Smrg     }
1472*760c2415Smrg 
1473*760c2415Smrg     auto args = ["prog", "--foo=barZZZ"];
1474*760c2415Smrg     Foo foo;
1475*760c2415Smrg     assertThrown(getopt(args, "foo", &foo));
1476*760c2415Smrg     args = ["prog", "--foo=bar"];
1477*760c2415Smrg     assertNotThrown(getopt(args, "foo", &foo));
1478*760c2415Smrg     args = ["prog", "--foo", "barZZZ"];
1479*760c2415Smrg     assertThrown(getopt(args, "foo", &foo));
1480*760c2415Smrg     args = ["prog", "--foo", "baz"];
1481*760c2415Smrg     assertNotThrown(getopt(args, "foo", &foo));
1482*760c2415Smrg }
1483*760c2415Smrg 
1484*760c2415Smrg @system unittest // same bug as 7693 only for bool
1485*760c2415Smrg {
1486*760c2415Smrg     import std.exception;
1487*760c2415Smrg 
1488*760c2415Smrg     auto args = ["prog", "--foo=truefoobar"];
1489*760c2415Smrg     bool foo;
1490*760c2415Smrg     assertThrown(getopt(args, "foo", &foo));
1491*760c2415Smrg     args = ["prog", "--foo"];
1492*760c2415Smrg     getopt(args, "foo", &foo);
1493*760c2415Smrg     assert(foo);
1494*760c2415Smrg }
1495*760c2415Smrg 
1496*760c2415Smrg @system unittest
1497*760c2415Smrg {
1498*760c2415Smrg     bool foo;
1499*760c2415Smrg     auto args = ["prog", "--foo"];
1500*760c2415Smrg     getopt(args, "foo", &foo);
1501*760c2415Smrg     assert(foo);
1502*760c2415Smrg }
1503*760c2415Smrg 
1504*760c2415Smrg @system unittest
1505*760c2415Smrg {
1506*760c2415Smrg     bool foo;
1507*760c2415Smrg     bool bar;
1508*760c2415Smrg     auto args = ["prog", "--foo", "-b"];
1509*760c2415Smrg     getopt(args, config.caseInsensitive,"foo|f", "Some foo", &foo,
1510*760c2415Smrg         config.caseSensitive, "bar|b", "Some bar", &bar);
1511*760c2415Smrg     assert(foo);
1512*760c2415Smrg     assert(bar);
1513*760c2415Smrg }
1514*760c2415Smrg 
1515*760c2415Smrg @system unittest
1516*760c2415Smrg {
1517*760c2415Smrg     bool foo;
1518*760c2415Smrg     bool bar;
1519*760c2415Smrg     auto args = ["prog", "-b", "--foo", "-z"];
1520*760c2415Smrg     getopt(args, config.caseInsensitive, config.required, "foo|f", "Some foo",
1521*760c2415Smrg         &foo, config.caseSensitive, "bar|b", "Some bar", &bar,
1522*760c2415Smrg         config.passThrough);
1523*760c2415Smrg     assert(foo);
1524*760c2415Smrg     assert(bar);
1525*760c2415Smrg }
1526*760c2415Smrg 
1527*760c2415Smrg @system unittest
1528*760c2415Smrg {
1529*760c2415Smrg     import std.exception;
1530*760c2415Smrg 
1531*760c2415Smrg     bool foo;
1532*760c2415Smrg     bool bar;
1533*760c2415Smrg     auto args = ["prog", "-b", "-z"];
1534*760c2415Smrg     assertThrown(getopt(args, config.caseInsensitive, config.required, "foo|f",
1535*760c2415Smrg         "Some foo", &foo, config.caseSensitive, "bar|b", "Some bar", &bar,
1536*760c2415Smrg         config.passThrough));
1537*760c2415Smrg }
1538*760c2415Smrg 
1539*760c2415Smrg @system unittest
1540*760c2415Smrg {
1541*760c2415Smrg     import std.exception;
1542*760c2415Smrg 
1543*760c2415Smrg     bool foo;
1544*760c2415Smrg     bool bar;
1545*760c2415Smrg     auto args = ["prog", "--foo", "-z"];
1546*760c2415Smrg     assertNotThrown(getopt(args, config.caseInsensitive, config.required,
1547*760c2415Smrg         "foo|f", "Some foo", &foo, config.caseSensitive, "bar|b", "Some bar",
1548*760c2415Smrg         &bar, config.passThrough));
1549*760c2415Smrg     assert(foo);
1550*760c2415Smrg     assert(!bar);
1551*760c2415Smrg }
1552*760c2415Smrg 
1553*760c2415Smrg @system unittest
1554*760c2415Smrg {
1555*760c2415Smrg     bool foo;
1556*760c2415Smrg     auto args = ["prog", "-f"];
1557*760c2415Smrg     auto r = getopt(args, config.caseInsensitive, "help|f", "Some foo", &foo);
1558*760c2415Smrg     assert(foo);
1559*760c2415Smrg     assert(!r.helpWanted);
1560*760c2415Smrg }
1561*760c2415Smrg 
1562*760c2415Smrg @safe unittest // implicit help option without config.passThrough
1563*760c2415Smrg {
1564*760c2415Smrg     string[] args = ["program", "--help"];
1565*760c2415Smrg     auto r = getopt(args);
1566*760c2415Smrg     assert(r.helpWanted);
1567*760c2415Smrg }
1568*760c2415Smrg 
1569*760c2415Smrg // Issue 13316 - std.getopt: implicit help option breaks the next argument
1570*760c2415Smrg @system unittest
1571*760c2415Smrg {
1572*760c2415Smrg     string[] args = ["program", "--help", "--", "something"];
1573*760c2415Smrg     getopt(args);
1574*760c2415Smrg     assert(args == ["program", "something"]);
1575*760c2415Smrg 
1576*760c2415Smrg     args = ["program", "--help", "--"];
1577*760c2415Smrg     getopt(args);
1578*760c2415Smrg     assert(args == ["program"]);
1579*760c2415Smrg 
1580*760c2415Smrg     bool b;
1581*760c2415Smrg     args = ["program", "--help", "nonoption", "--option"];
1582*760c2415Smrg     getopt(args, config.stopOnFirstNonOption, "option", &b);
1583*760c2415Smrg     assert(args == ["program", "nonoption", "--option"]);
1584*760c2415Smrg }
1585*760c2415Smrg 
1586*760c2415Smrg // Issue 13317 - std.getopt: endOfOptions broken when it doesn't look like an option
1587*760c2415Smrg @system unittest
1588*760c2415Smrg {
1589*760c2415Smrg     auto endOfOptionsBackup = endOfOptions;
1590*760c2415Smrg     scope(exit) endOfOptions = endOfOptionsBackup;
1591*760c2415Smrg     endOfOptions = "endofoptions";
1592*760c2415Smrg     string[] args = ["program", "endofoptions", "--option"];
1593*760c2415Smrg     bool b = false;
1594*760c2415Smrg     getopt(args, "option", &b);
1595*760c2415Smrg     assert(!b);
1596*760c2415Smrg     assert(args == ["program", "--option"]);
1597*760c2415Smrg }
1598*760c2415Smrg 
1599*760c2415Smrg /** This function prints the passed $(D Option)s and text in an aligned manner on $(D stdout).
1600*760c2415Smrg 
1601*760c2415Smrg The passed text will be printed first, followed by a newline, then the short
1602*760c2415Smrg and long version of every option will be printed. The short and long version
1603*760c2415Smrg will be aligned to the longest option of every $(D Option) passed. If the option
1604*760c2415Smrg is required, then "Required:" will be printed after the long version of the
1605*760c2415Smrg $(D Option). If a help message is present it will be printed next. The format is
1606*760c2415Smrg illustrated by this code:
1607*760c2415Smrg 
1608*760c2415Smrg ------------
1609*760c2415Smrg foreach (it; opt)
1610*760c2415Smrg {
1611*760c2415Smrg     writefln("%*s %*s%s%s", lengthOfLongestShortOption, it.optShort,
1612*760c2415Smrg         lengthOfLongestLongOption, it.optLong,
1613*760c2415Smrg         it.required ? " Required: " : " ", it.help);
1614*760c2415Smrg }
1615*760c2415Smrg ------------
1616*760c2415Smrg 
1617*760c2415Smrg Params:
1618*760c2415Smrg     text = The text to printed at the beginning of the help output.
1619*760c2415Smrg     opt = The $(D Option) extracted from the $(D getopt) parameter.
1620*760c2415Smrg */
defaultGetoptPrinter(string text,Option[]opt)1621*760c2415Smrg void defaultGetoptPrinter(string text, Option[] opt)
1622*760c2415Smrg {
1623*760c2415Smrg     import std.stdio : stdout;
1624*760c2415Smrg 
1625*760c2415Smrg     defaultGetoptFormatter(stdout.lockingTextWriter(), text, opt);
1626*760c2415Smrg }
1627*760c2415Smrg 
1628*760c2415Smrg /** This function writes the passed text and $(D Option) into an output range
1629*760c2415Smrg in the manner described in the documentation of function
1630*760c2415Smrg $(D defaultGetoptPrinter).
1631*760c2415Smrg 
1632*760c2415Smrg Params:
1633*760c2415Smrg     output = The output range used to write the help information.
1634*760c2415Smrg     text = The text to print at the beginning of the help output.
1635*760c2415Smrg     opt = The $(D Option) extracted from the $(D getopt) parameter.
1636*760c2415Smrg */
defaultGetoptFormatter(Output)1637*760c2415Smrg void defaultGetoptFormatter(Output)(Output output, string text, Option[] opt)
1638*760c2415Smrg {
1639*760c2415Smrg     import std.algorithm.comparison : min, max;
1640*760c2415Smrg     import std.format : formattedWrite;
1641*760c2415Smrg 
1642*760c2415Smrg     output.formattedWrite("%s\n", text);
1643*760c2415Smrg 
1644*760c2415Smrg     size_t ls, ll;
1645*760c2415Smrg     bool hasRequired = false;
1646*760c2415Smrg     foreach (it; opt)
1647*760c2415Smrg     {
1648*760c2415Smrg         ls = max(ls, it.optShort.length);
1649*760c2415Smrg         ll = max(ll, it.optLong.length);
1650*760c2415Smrg 
1651*760c2415Smrg         hasRequired = hasRequired || it.required;
1652*760c2415Smrg     }
1653*760c2415Smrg 
1654*760c2415Smrg     string re = " Required: ";
1655*760c2415Smrg 
1656*760c2415Smrg     foreach (it; opt)
1657*760c2415Smrg     {
1658*760c2415Smrg         output.formattedWrite("%*s %*s%*s%s\n", ls, it.optShort, ll, it.optLong,
1659*760c2415Smrg             hasRequired ? re.length : 1, it.required ? re : " ", it.help);
1660*760c2415Smrg     }
1661*760c2415Smrg }
1662*760c2415Smrg 
1663*760c2415Smrg @system unittest
1664*760c2415Smrg {
1665*760c2415Smrg     import std.conv;
1666*760c2415Smrg 
1667*760c2415Smrg     import std.array;
1668*760c2415Smrg     import std.string;
1669*760c2415Smrg     bool a;
1670*760c2415Smrg     auto args = ["prog", "--foo"];
1671*760c2415Smrg     auto t = getopt(args, "foo|f", "Help", &a);
1672*760c2415Smrg     string s;
1673*760c2415Smrg     auto app = appender!string();
1674*760c2415Smrg     defaultGetoptFormatter(app, "Some Text", t.options);
1675*760c2415Smrg 
1676*760c2415Smrg     string helpMsg = app.data;
1677*760c2415Smrg     //writeln(helpMsg);
1678*760c2415Smrg     assert(helpMsg.length);
1679*760c2415Smrg     assert(helpMsg.count("\n") == 3, to!string(helpMsg.count("\n")) ~ " "
1680*760c2415Smrg         ~ helpMsg);
1681*760c2415Smrg     assert(helpMsg.indexOf("--foo") != -1);
1682*760c2415Smrg     assert(helpMsg.indexOf("-f") != -1);
1683*760c2415Smrg     assert(helpMsg.indexOf("-h") != -1);
1684*760c2415Smrg     assert(helpMsg.indexOf("--help") != -1);
1685*760c2415Smrg     assert(helpMsg.indexOf("Help") != -1);
1686*760c2415Smrg 
1687*760c2415Smrg     string wanted = "Some Text\n-f  --foo Help\n-h --help This help "
1688*760c2415Smrg         ~ "information.\n";
1689*760c2415Smrg     assert(wanted == helpMsg);
1690*760c2415Smrg }
1691*760c2415Smrg 
1692*760c2415Smrg @system unittest
1693*760c2415Smrg {
1694*760c2415Smrg     import std.array ;
1695*760c2415Smrg     import std.conv;
1696*760c2415Smrg     import std.string;
1697*760c2415Smrg     bool a;
1698*760c2415Smrg     auto args = ["prog", "--foo"];
1699*760c2415Smrg     auto t = getopt(args, config.required, "foo|f", "Help", &a);
1700*760c2415Smrg     string s;
1701*760c2415Smrg     auto app = appender!string();
1702*760c2415Smrg     defaultGetoptFormatter(app, "Some Text", t.options);
1703*760c2415Smrg 
1704*760c2415Smrg     string helpMsg = app.data;
1705*760c2415Smrg     //writeln(helpMsg);
1706*760c2415Smrg     assert(helpMsg.length);
1707*760c2415Smrg     assert(helpMsg.count("\n") == 3, to!string(helpMsg.count("\n")) ~ " "
1708*760c2415Smrg         ~ helpMsg);
1709*760c2415Smrg     assert(helpMsg.indexOf("Required:") != -1);
1710*760c2415Smrg     assert(helpMsg.indexOf("--foo") != -1);
1711*760c2415Smrg     assert(helpMsg.indexOf("-f") != -1);
1712*760c2415Smrg     assert(helpMsg.indexOf("-h") != -1);
1713*760c2415Smrg     assert(helpMsg.indexOf("--help") != -1);
1714*760c2415Smrg     assert(helpMsg.indexOf("Help") != -1);
1715*760c2415Smrg 
1716*760c2415Smrg     string wanted = "Some Text\n-f  --foo Required: Help\n-h --help "
1717*760c2415Smrg         ~ "          This help information.\n";
1718*760c2415Smrg     assert(wanted == helpMsg, helpMsg ~ wanted);
1719*760c2415Smrg }
1720*760c2415Smrg 
1721*760c2415Smrg @system unittest // Issue 14724
1722*760c2415Smrg {
1723*760c2415Smrg     bool a;
1724*760c2415Smrg     auto args = ["prog", "--help"];
1725*760c2415Smrg     GetoptResult rslt;
1726*760c2415Smrg     try
1727*760c2415Smrg     {
1728*760c2415Smrg         rslt = getopt(args, config.required, "foo|f", "bool a", &a);
1729*760c2415Smrg     }
catch(Exception e)1730*760c2415Smrg     catch (Exception e)
1731*760c2415Smrg     {
1732*760c2415Smrg         enum errorMsg = "If the request for help was passed required options" ~
1733*760c2415Smrg                 "must not be set.";
1734*760c2415Smrg         assert(false, errorMsg);
1735*760c2415Smrg     }
1736*760c2415Smrg 
1737*760c2415Smrg     assert(rslt.helpWanted);
1738*760c2415Smrg }
1739*760c2415Smrg 
1740*760c2415Smrg // throw on duplicate options
1741*760c2415Smrg @system unittest
1742*760c2415Smrg {
1743*760c2415Smrg     import core.exception;
1744*760c2415Smrg     auto args = ["prog", "--abc", "1"];
1745*760c2415Smrg     int abc, def;
1746*760c2415Smrg     assertThrown!AssertError(getopt(args, "abc", &abc, "abc", &abc));
1747*760c2415Smrg     assertThrown!AssertError(getopt(args, "abc|a", &abc, "def|a", &def));
1748*760c2415Smrg     assertNotThrown!AssertError(getopt(args, "abc", &abc, "def", &def));
1749*760c2415Smrg }
1750*760c2415Smrg 
1751*760c2415Smrg @system unittest // Issue 17327 repeated option use
1752*760c2415Smrg {
1753*760c2415Smrg     long num = 0;
1754*760c2415Smrg 
1755*760c2415Smrg     string[] args = ["program", "--num", "3"];
1756*760c2415Smrg     getopt(args, "n|num", &num);
1757*760c2415Smrg     assert(num == 3);
1758*760c2415Smrg 
1759*760c2415Smrg     args = ["program", "--num", "3", "--num", "5"];
1760*760c2415Smrg     getopt(args, "n|num", &num);
1761*760c2415Smrg     assert(num == 5);
1762*760c2415Smrg 
1763*760c2415Smrg     args = ["program", "--n", "3", "--num", "5", "-n", "-7"];
1764*760c2415Smrg     getopt(args, "n|num", &num);
1765*760c2415Smrg     assert(num == -7);
1766*760c2415Smrg 
add1()1767*760c2415Smrg     void add1() { num++; }
add2(string option)1768*760c2415Smrg     void add2(string option) { num += 2; }
addN(string option,string value)1769*760c2415Smrg     void addN(string option, string value)
1770*760c2415Smrg     {
1771*760c2415Smrg         import std.conv : to;
1772*760c2415Smrg         num += value.to!long;
1773*760c2415Smrg     }
1774*760c2415Smrg 
1775*760c2415Smrg     num = 0;
1776*760c2415Smrg     args = ["program", "--add1", "--add2", "--add1", "--add", "5", "--add2", "--add", "10"];
1777*760c2415Smrg     getopt(args,
1778*760c2415Smrg            "add1", "Add 1 to num", &add1,
1779*760c2415Smrg            "add2", "Add 2 to num", &add2,
1780*760c2415Smrg            "add", "Add N to num", &addN,);
1781*760c2415Smrg     assert(num == 21);
1782*760c2415Smrg 
1783*760c2415Smrg     bool flag = false;
1784*760c2415Smrg     args = ["program", "--flag"];
1785*760c2415Smrg     getopt(args, "f|flag", "Boolean", &flag);
1786*760c2415Smrg     assert(flag);
1787*760c2415Smrg 
1788*760c2415Smrg     flag = false;
1789*760c2415Smrg     args = ["program", "-f", "-f"];
1790*760c2415Smrg     getopt(args, "f|flag", "Boolean", &flag);
1791*760c2415Smrg     assert(flag);
1792*760c2415Smrg 
1793*760c2415Smrg     flag = false;
1794*760c2415Smrg     args = ["program", "--flag=true", "--flag=false"];
1795*760c2415Smrg     getopt(args, "f|flag", "Boolean", &flag);
1796*760c2415Smrg     assert(!flag);
1797*760c2415Smrg 
1798*760c2415Smrg     flag = false;
1799*760c2415Smrg     args = ["program", "--flag=true", "--flag=false", "-f"];
1800*760c2415Smrg     getopt(args, "f|flag", "Boolean", &flag);
1801*760c2415Smrg     assert(flag);
1802*760c2415Smrg }
1803*760c2415Smrg 
1804*760c2415Smrg @safe unittest  // Delegates as callbacks
1805*760c2415Smrg {
1806*760c2415Smrg     alias TwoArgOptionHandler = void delegate(string option, string value) @safe;
1807*760c2415Smrg 
makeAddNHandler(ref long dest)1808*760c2415Smrg     TwoArgOptionHandler makeAddNHandler(ref long dest)
1809*760c2415Smrg     {
1810*760c2415Smrg         void addN(ref long dest, string n)
1811*760c2415Smrg         {
1812*760c2415Smrg             import std.conv : to;
1813*760c2415Smrg             dest += n.to!long;
1814*760c2415Smrg         }
1815*760c2415Smrg 
1816*760c2415Smrg         return (option, value) => addN(dest, value);
1817*760c2415Smrg     }
1818*760c2415Smrg 
1819*760c2415Smrg     long x = 0;
1820*760c2415Smrg     long y = 0;
1821*760c2415Smrg 
1822*760c2415Smrg     string[] args =
1823*760c2415Smrg         ["program", "--x-plus-1", "--x-plus-1", "--x-plus-5", "--x-plus-n", "10",
1824*760c2415Smrg          "--y-plus-n", "25", "--y-plus-7", "--y-plus-n", "15", "--y-plus-3"];
1825*760c2415Smrg 
1826*760c2415Smrg     getopt(args,
1827*760c2415Smrg            "x-plus-1", "Add one to x", delegate void() { x += 1; },
1828*760c2415Smrg            "x-plus-5", "Add five to x", delegate void(string option) { x += 5; },
1829*760c2415Smrg            "x-plus-n", "Add NUM to x", makeAddNHandler(x),
1830*760c2415Smrg            "y-plus-7", "Add seven to y", delegate void() { y += 7; },
1831*760c2415Smrg            "y-plus-3", "Add three to y", delegate void(string option) { y += 3; },
1832*760c2415Smrg            "y-plus-n", "Add NUM to x", makeAddNHandler(y),);
1833*760c2415Smrg 
1834*760c2415Smrg     assert(x == 17);
1835*760c2415Smrg     assert(y == 50);
1836*760c2415Smrg }
1837*760c2415Smrg 
1838*760c2415Smrg @system unittest // Hyphens at the start of option values; Issue 17650
1839*760c2415Smrg {
1840*760c2415Smrg     auto args = ["program", "-m", "-5", "-n", "-50", "-c", "-", "-f", "-"];
1841*760c2415Smrg 
1842*760c2415Smrg     int m;
1843*760c2415Smrg     int n;
1844*760c2415Smrg     char c;
1845*760c2415Smrg     string f;
1846*760c2415Smrg 
1847*760c2415Smrg     getopt(args,
1848*760c2415Smrg            "m|mm", "integer", &m,
1849*760c2415Smrg            "n|nn", "integer", &n,
1850*760c2415Smrg            "c|cc", "character", &c,
1851*760c2415Smrg            "f|file", "filename or hyphen for stdin", &f);
1852*760c2415Smrg 
1853*760c2415Smrg     assert(m == -5);
1854*760c2415Smrg     assert(n == -50);
1855*760c2415Smrg     assert(c == '-');
1856*760c2415Smrg     assert(f == "-");
1857*760c2415Smrg }
1858