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