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+", ¶noid);
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+", ¶noid);
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