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