mirror of
https://github.com/dlang/phobos.git
synced 2025-04-26 13:10:35 +03:00
2041 lines
60 KiB
D
2041 lines
60 KiB
D
// Written in the D programming language.
|
|
|
|
/**
|
|
Processing of command line options.
|
|
|
|
The getopt module implements a `getopt` function, which adheres to
|
|
the POSIX syntax for command line options. GNU extensions are
|
|
supported in the form of long options introduced by a double dash
|
|
("--"). Support for bundling of command line options, as was the case
|
|
with the more traditional single-letter approach, is provided but not
|
|
enabled by default.
|
|
|
|
Copyright: Copyright Andrei Alexandrescu 2008 - 2015.
|
|
License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
|
|
Authors: $(HTTP erdani.org, Andrei Alexandrescu)
|
|
Credits: This module and its documentation are inspired by Perl's
|
|
$(HTTPS perldoc.perl.org/Getopt/Long.html, Getopt::Long) module. The syntax of
|
|
D's `getopt` is simpler than its Perl counterpart because $(D
|
|
getopt) infers the expected parameter types from the static types of
|
|
the passed-in pointers.
|
|
Source: $(PHOBOSSRC std/getopt.d)
|
|
*/
|
|
/*
|
|
Copyright Andrei Alexandrescu 2008 - 2015.
|
|
Distributed under the Boost Software License, Version 1.0.
|
|
(See accompanying file LICENSE_1_0.txt or copy at
|
|
http://www.boost.org/LICENSE_1_0.txt)
|
|
*/
|
|
module std.getopt;
|
|
|
|
import std.exception : basicExceptionCtors;
|
|
import std.traits;
|
|
|
|
/**
|
|
Thrown on one of the following conditions:
|
|
$(UL
|
|
$(LI An unrecognized command-line argument is passed, and
|
|
`std.getopt.config.passThrough` was not present.)
|
|
$(LI A command-line option was not found, and
|
|
`std.getopt.config.required` was present.)
|
|
$(LI A callback option is missing a value.)
|
|
)
|
|
*/
|
|
class GetOptException : Exception
|
|
{
|
|
mixin basicExceptionCtors;
|
|
}
|
|
|
|
static assert(is(typeof(new GetOptException("message"))));
|
|
static assert(is(typeof(new GetOptException("message", Exception.init))));
|
|
|
|
/**
|
|
Parse and remove command line options from a string array.
|
|
|
|
Synopsis:
|
|
|
|
---------
|
|
import std.getopt;
|
|
|
|
string data = "file.dat";
|
|
int length = 24;
|
|
bool verbose;
|
|
enum Color { no, yes };
|
|
Color color;
|
|
|
|
void main(string[] args)
|
|
{
|
|
auto helpInformation = getopt(
|
|
args,
|
|
"length", &length, // numeric
|
|
"file", &data, // string
|
|
"verbose", &verbose, // flag
|
|
"color", "Information about this color", &color); // enum
|
|
...
|
|
|
|
if (helpInformation.helpWanted)
|
|
{
|
|
defaultGetoptPrinter("Some information about the program.",
|
|
helpInformation.options);
|
|
}
|
|
}
|
|
---------
|
|
|
|
The `getopt` function takes a reference to the command line
|
|
(as received by `main`) as its first argument, and an
|
|
unbounded number of pairs of strings and pointers. Each string is an
|
|
option meant to "fill" the value referenced by the pointer to its
|
|
right (the "bound" pointer). The option string in the call to
|
|
`getopt` should not start with a dash.
|
|
|
|
In all cases, the command-line options that were parsed and used by
|
|
`getopt` are removed from `args`. Whatever in the
|
|
arguments did not look like an option is left in `args` for
|
|
further processing by the program. Values that were unaffected by the
|
|
options are not touched, so a common idiom is to initialize options
|
|
to their defaults and then invoke `getopt`. If a
|
|
command-line argument is recognized as an option with a parameter and
|
|
the parameter cannot be parsed properly (e.g., a number is expected
|
|
but not present), a `ConvException` exception is thrown.
|
|
If `std.getopt.config.passThrough` was not passed to `getopt`
|
|
and an unrecognized command-line argument is found, or if a required
|
|
argument is missing a `GetOptException` is thrown.
|
|
|
|
Depending on the type of the pointer being bound, `getopt`
|
|
recognizes the following kinds of options:
|
|
|
|
$(OL
|
|
$(LI $(I Boolean options). A lone argument sets the option to `true`.
|
|
Additionally $(B true) or $(B false) can be set within the option separated
|
|
with an "=" sign:
|
|
|
|
---------
|
|
bool verbose = false, debugging = true;
|
|
getopt(args, "verbose", &verbose, "debug", &debugging);
|
|
---------
|
|
|
|
To set `verbose` to `true`, invoke the program with either
|
|
`--verbose` or `--verbose=true`.
|
|
|
|
To set `debugging` to `false`, invoke the program with
|
|
`--debugging=false`.
|
|
)
|
|
|
|
$(LI $(I Numeric options.) If an option is bound to a numeric type, a
|
|
number is expected as the next option, or right within the option separated
|
|
with an "=" sign:
|
|
|
|
---------
|
|
uint timeout;
|
|
getopt(args, "timeout", &timeout);
|
|
---------
|
|
|
|
To set `timeout` to `5`, invoke the program with either
|
|
`--timeout=5` or $(D --timeout 5).
|
|
)
|
|
|
|
$(LI $(I Incremental options.) If an option name has a "+" suffix and is
|
|
bound to a numeric type, then the option's value tracks the number of times
|
|
the option occurred on the command line:
|
|
|
|
---------
|
|
uint paranoid;
|
|
getopt(args, "paranoid+", ¶noid);
|
|
---------
|
|
|
|
Invoking the program with "--paranoid --paranoid --paranoid" will set $(D
|
|
paranoid) to 3. Note that an incremental option never expects a parameter,
|
|
e.g., in the command line "--paranoid 42 --paranoid", the "42" does not set
|
|
`paranoid` to 42; instead, `paranoid` is set to 2 and "42" is not
|
|
considered as part of the normal program arguments.
|
|
)
|
|
|
|
$(LI $(I Enum options.) If an option is bound to an enum, an enum symbol as
|
|
a string is expected as the next option, or right within the option
|
|
separated with an "=" sign:
|
|
|
|
---------
|
|
enum Color { no, yes };
|
|
Color color; // default initialized to Color.no
|
|
getopt(args, "color", &color);
|
|
---------
|
|
|
|
To set `color` to `Color.yes`, invoke the program with either
|
|
`--color=yes` or $(D --color yes).
|
|
)
|
|
|
|
$(LI $(I String options.) If an option is bound to a string, a string is
|
|
expected as the next option, or right within the option separated with an
|
|
"=" sign:
|
|
|
|
---------
|
|
string outputFile;
|
|
getopt(args, "output", &outputFile);
|
|
---------
|
|
|
|
Invoking the program with "--output=myfile.txt" or "--output myfile.txt"
|
|
will set `outputFile` to "myfile.txt". If you want to pass a string
|
|
containing spaces, you need to use the quoting that is appropriate to your
|
|
shell, e.g. --output='my file.txt'.
|
|
)
|
|
|
|
$(LI $(I Array options.) If an option is bound to an array, a new element
|
|
is appended to the array each time the option occurs:
|
|
|
|
---------
|
|
string[] outputFiles;
|
|
getopt(args, "output", &outputFiles);
|
|
---------
|
|
|
|
Invoking the program with "--output=myfile.txt --output=yourfile.txt" or
|
|
"--output myfile.txt --output yourfile.txt" will set `outputFiles` to
|
|
$(D [ "myfile.txt", "yourfile.txt" ]).
|
|
|
|
Alternatively you can set $(LREF arraySep) to allow multiple elements in
|
|
one parameter.
|
|
|
|
---------
|
|
string[] outputFiles;
|
|
arraySep = ","; // defaults to "", meaning one element per parameter
|
|
getopt(args, "output", &outputFiles);
|
|
---------
|
|
|
|
With the above code you can invoke the program with
|
|
"--output=myfile.txt,yourfile.txt", or "--output myfile.txt,yourfile.txt".)
|
|
|
|
$(LI $(I Hash options.) If an option is bound to an associative array, a
|
|
string of the form "name=value" is expected as the next option, or right
|
|
within the option separated with an "=" sign:
|
|
|
|
---------
|
|
double[string] tuningParms;
|
|
getopt(args, "tune", &tuningParms);
|
|
---------
|
|
|
|
Invoking the program with e.g. "--tune=alpha=0.5 --tune beta=0.6" will set
|
|
`tuningParms` to [ "alpha" : 0.5, "beta" : 0.6 ].
|
|
|
|
Alternatively you can set $(LREF arraySep) as the element separator:
|
|
|
|
---------
|
|
double[string] tuningParms;
|
|
arraySep = ","; // defaults to "", meaning one element per parameter
|
|
getopt(args, "tune", &tuningParms);
|
|
---------
|
|
|
|
With the above code you can invoke the program with
|
|
"--tune=alpha=0.5,beta=0.6", or "--tune alpha=0.5,beta=0.6".
|
|
|
|
In general, the keys and values can be of any parsable types.
|
|
)
|
|
|
|
$(LI $(I Callback options.) An option can be bound to a function or
|
|
delegate with the signature $(D void function()), $(D void function(string
|
|
option)), $(D void function(string option, string value)), or their
|
|
delegate equivalents.
|
|
|
|
$(UL
|
|
$(LI If the callback doesn't take any arguments, the callback is
|
|
invoked whenever the option is seen.
|
|
)
|
|
|
|
$(LI If the callback takes one string argument, the option string
|
|
(without the leading dash(es)) is passed to the callback. After that,
|
|
the option string is considered handled and removed from the options
|
|
array.
|
|
|
|
---------
|
|
void main(string[] args)
|
|
{
|
|
uint verbosityLevel = 1;
|
|
void myHandler(string option)
|
|
{
|
|
if (option == "quiet")
|
|
{
|
|
verbosityLevel = 0;
|
|
}
|
|
else
|
|
{
|
|
assert(option == "verbose");
|
|
verbosityLevel = 2;
|
|
}
|
|
}
|
|
getopt(args, "verbose", &myHandler, "quiet", &myHandler);
|
|
}
|
|
---------
|
|
|
|
)
|
|
|
|
$(LI If the callback takes two string arguments, the option string is
|
|
handled as an option with one argument, and parsed accordingly. The
|
|
option and its value are passed to the callback. After that, whatever
|
|
was passed to the callback is considered handled and removed from the
|
|
list.
|
|
|
|
---------
|
|
int main(string[] args)
|
|
{
|
|
uint verbosityLevel = 1;
|
|
bool handlerFailed = false;
|
|
void myHandler(string option, string value)
|
|
{
|
|
switch (value)
|
|
{
|
|
case "quiet": verbosityLevel = 0; break;
|
|
case "verbose": verbosityLevel = 2; break;
|
|
case "shouting": verbosityLevel = verbosityLevel.max; break;
|
|
default :
|
|
stderr.writeln("Unknown verbosity level ", value);
|
|
handlerFailed = true;
|
|
break;
|
|
}
|
|
}
|
|
getopt(args, "verbosity", &myHandler);
|
|
return handlerFailed ? 1 : 0;
|
|
}
|
|
---------
|
|
)
|
|
))
|
|
)
|
|
|
|
Options_with_multiple_names:
|
|
Sometimes option synonyms are desirable, e.g. "--verbose",
|
|
"--loquacious", and "--garrulous" should have the same effect. Such
|
|
alternate option names can be included in the option specification,
|
|
using "|" as a separator:
|
|
|
|
---------
|
|
bool verbose;
|
|
getopt(args, "verbose|loquacious|garrulous", &verbose);
|
|
---------
|
|
|
|
Case:
|
|
By default options are case-insensitive. You can change that behavior
|
|
by passing `getopt` the `caseSensitive` directive like this:
|
|
|
|
---------
|
|
bool foo, bar;
|
|
getopt(args,
|
|
std.getopt.config.caseSensitive,
|
|
"foo", &foo,
|
|
"bar", &bar);
|
|
---------
|
|
|
|
In the example above, "--foo" and "--bar" are recognized, but "--Foo", "--Bar",
|
|
"--FOo", "--bAr", etc. are rejected.
|
|
The directive is active until the end of `getopt`, or until the
|
|
converse directive `caseInsensitive` is encountered:
|
|
|
|
---------
|
|
bool foo, bar;
|
|
getopt(args,
|
|
std.getopt.config.caseSensitive,
|
|
"foo", &foo,
|
|
std.getopt.config.caseInsensitive,
|
|
"bar", &bar);
|
|
---------
|
|
|
|
The option "--Foo" is rejected due to $(D
|
|
std.getopt.config.caseSensitive), but not "--Bar", "--bAr"
|
|
etc. because the directive $(D
|
|
std.getopt.config.caseInsensitive) turned sensitivity off before
|
|
option "bar" was parsed.
|
|
|
|
Short_versus_long_options:
|
|
Traditionally, programs accepted single-letter options preceded by
|
|
only one dash (e.g. `-t`). `getopt` accepts such parameters
|
|
seamlessly. When used with a double-dash (e.g. `--t`), a
|
|
single-letter option behaves the same as a multi-letter option. When
|
|
used with a single dash, a single-letter option is accepted.
|
|
|
|
To set `timeout` to `5`, use either of the following: `--timeout=5`,
|
|
`--timeout 5`, `--t=5`, `--t 5`, `-t5`, or `-t 5`. Forms such as
|
|
`-timeout=5` will be not accepted.
|
|
|
|
For more details about short options, refer also to the next section.
|
|
|
|
Bundling:
|
|
Single-letter options can be bundled together, i.e. "-abc" is the same as
|
|
$(D "-a -b -c"). By default, this option is turned off. You can turn it on
|
|
with the `std.getopt.config.bundling` directive:
|
|
|
|
---------
|
|
bool foo, bar;
|
|
getopt(args,
|
|
std.getopt.config.bundling,
|
|
"foo|f", &foo,
|
|
"bar|b", &bar);
|
|
---------
|
|
|
|
In case you want to only enable bundling for some of the parameters,
|
|
bundling can be turned off with `std.getopt.config.noBundling`.
|
|
|
|
Required:
|
|
An option can be marked as required. If that option is not present in the
|
|
arguments an exception will be thrown.
|
|
|
|
---------
|
|
bool foo, bar;
|
|
getopt(args,
|
|
std.getopt.config.required,
|
|
"foo|f", &foo,
|
|
"bar|b", &bar);
|
|
---------
|
|
|
|
Only the option directly following `std.getopt.config.required` is
|
|
required.
|
|
|
|
Passing_unrecognized_options_through:
|
|
If an application needs to do its own processing of whichever arguments
|
|
`getopt` did not understand, it can pass the
|
|
`std.getopt.config.passThrough` directive to `getopt`:
|
|
|
|
---------
|
|
bool foo, bar;
|
|
getopt(args,
|
|
std.getopt.config.passThrough,
|
|
"foo", &foo,
|
|
"bar", &bar);
|
|
---------
|
|
|
|
An unrecognized option such as "--baz" will be found untouched in
|
|
`args` after `getopt` returns.
|
|
|
|
Help_Information_Generation:
|
|
If an option string is followed by another string, this string serves as a
|
|
description for this option. The `getopt` function returns a struct of type
|
|
`GetoptResult`. This return value contains information about all passed options
|
|
as well a $(D bool GetoptResult.helpWanted) flag indicating whether information
|
|
about these options was requested. The `getopt` function always adds an option for
|
|
`--help|-h` to set the flag if the option is seen on the command line.
|
|
|
|
Options_Terminator:
|
|
A lone double-dash terminates `getopt` gathering. It is used to
|
|
separate program options from other parameters (e.g., options to be passed
|
|
to another program). Invoking the example above with $(D "--foo -- --bar")
|
|
parses foo but leaves "--bar" in `args`. The double-dash itself is
|
|
removed from the argument array unless the `std.getopt.config.keepEndOfOptions`
|
|
directive is given.
|
|
*/
|
|
GetoptResult getopt(T...)(ref string[] args, T opts)
|
|
{
|
|
import std.exception : enforce;
|
|
enforce(args.length,
|
|
"Invalid arguments string passed: program name missing");
|
|
configuration cfg;
|
|
GetoptResult rslt;
|
|
|
|
GetOptException excep;
|
|
void[][string] visitedLongOpts, visitedShortOpts;
|
|
getoptImpl(args, cfg, rslt, excep, visitedLongOpts, visitedShortOpts, opts);
|
|
|
|
if (!rslt.helpWanted && excep !is null)
|
|
{
|
|
throw excep;
|
|
}
|
|
|
|
return rslt;
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
auto args = ["prog", "--foo", "-b"];
|
|
|
|
bool foo;
|
|
bool bar;
|
|
auto rslt = getopt(args, "foo|f", "Some information about foo.", &foo, "bar|b",
|
|
"Some help message about bar.", &bar);
|
|
|
|
if (rslt.helpWanted)
|
|
{
|
|
defaultGetoptPrinter("Some information about the program.",
|
|
rslt.options);
|
|
}
|
|
}
|
|
|
|
/**
|
|
Configuration options for `getopt`.
|
|
|
|
You can pass them to `getopt` in any position, except in between an option
|
|
string and its bound pointer.
|
|
*/
|
|
enum config {
|
|
/// Turn case sensitivity on
|
|
caseSensitive,
|
|
/// Turn case sensitivity off (default)
|
|
caseInsensitive,
|
|
/// Turn bundling on
|
|
bundling,
|
|
/// Turn bundling off (default)
|
|
noBundling,
|
|
/// Pass unrecognized arguments through
|
|
passThrough,
|
|
/// Signal unrecognized arguments as errors (default)
|
|
noPassThrough,
|
|
/// Stop at first argument that does not look like an option
|
|
stopOnFirstNonOption,
|
|
/// Do not erase the endOfOptions separator from args
|
|
keepEndOfOptions,
|
|
/// Make the next option a required option
|
|
required
|
|
}
|
|
|
|
/** The result of the `getopt` function.
|
|
|
|
`helpWanted` is set if the option `--help` or `-h` was passed to the option parser.
|
|
*/
|
|
struct GetoptResult {
|
|
bool helpWanted; /// Flag indicating if help was requested
|
|
Option[] options; /// All possible options
|
|
}
|
|
|
|
/** Information about an option.
|
|
*/
|
|
struct Option {
|
|
string optShort; /// The short symbol for this option
|
|
string optLong; /// The long symbol for this option
|
|
string help; /// The description of this option
|
|
bool required; /// If a option is required, not passing it will result in an error
|
|
}
|
|
|
|
private pure Option splitAndGet(string opt) @trusted nothrow
|
|
{
|
|
import std.array : split;
|
|
auto sp = split(opt, "|");
|
|
Option ret;
|
|
if (sp.length > 1)
|
|
{
|
|
ret.optShort = "-" ~ (sp[0].length < sp[1].length ?
|
|
sp[0] : sp[1]);
|
|
ret.optLong = "--" ~ (sp[0].length > sp[1].length ?
|
|
sp[0] : sp[1]);
|
|
}
|
|
else if (sp[0].length > 1)
|
|
{
|
|
ret.optLong = "--" ~ sp[0];
|
|
}
|
|
else
|
|
{
|
|
ret.optShort = "-" ~ sp[0];
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
auto oshort = splitAndGet("f");
|
|
assert(oshort.optShort == "-f");
|
|
assert(oshort.optLong == "");
|
|
|
|
auto olong = splitAndGet("foo");
|
|
assert(olong.optShort == "");
|
|
assert(olong.optLong == "--foo");
|
|
|
|
auto oshortlong = splitAndGet("f|foo");
|
|
assert(oshortlong.optShort == "-f");
|
|
assert(oshortlong.optLong == "--foo");
|
|
|
|
auto olongshort = splitAndGet("foo|f");
|
|
assert(olongshort.optShort == "-f");
|
|
assert(olongshort.optLong == "--foo");
|
|
}
|
|
|
|
/*
|
|
This function verifies that the variadic parameters passed in getOpt
|
|
follow this pattern:
|
|
|
|
[config override], option, [description], receiver,
|
|
|
|
- config override: a config value, optional
|
|
- option: a string or a char
|
|
- description: a string, optional
|
|
- receiver: a pointer or a callable
|
|
*/
|
|
private template optionValidator(A...)
|
|
{
|
|
import std.format : format;
|
|
|
|
enum fmt = "getopt validator: %s (at position %d)";
|
|
enum isReceiver(T) = is(T == U*, U) || (is(T == function)) || (is(T == delegate));
|
|
enum isOptionStr(T) = isSomeString!T || isSomeChar!T;
|
|
|
|
auto validator()
|
|
{
|
|
string msg;
|
|
static if (A.length > 0)
|
|
{
|
|
static if (isReceiver!(A[0]))
|
|
{
|
|
msg = format(fmt, "first argument must be a string or a config", 0);
|
|
}
|
|
else static if (!isOptionStr!(A[0]) && !is(A[0] == config))
|
|
{
|
|
msg = format(fmt, "invalid argument type: " ~ A[0].stringof, 0);
|
|
}
|
|
else
|
|
{
|
|
static foreach (i; 1 .. A.length)
|
|
{
|
|
static if (!isReceiver!(A[i]) && !isOptionStr!(A[i]) &&
|
|
!(is(A[i] == config)))
|
|
{
|
|
msg = format(fmt, "invalid argument type: " ~ A[i].stringof, i);
|
|
goto end;
|
|
}
|
|
else static if (isReceiver!(A[i]) && !isOptionStr!(A[i-1]))
|
|
{
|
|
msg = format(fmt, "a receiver can not be preceeded by a receiver", i);
|
|
goto end;
|
|
}
|
|
else static if (i > 1 && isOptionStr!(A[i]) && isOptionStr!(A[i-1])
|
|
&& isSomeString!(A[i-2]))
|
|
{
|
|
msg = format(fmt, "a string can not be preceeded by two strings", i);
|
|
goto end;
|
|
}
|
|
}
|
|
}
|
|
static if (!isReceiver!(A[$-1]) && !is(A[$-1] == config))
|
|
{
|
|
msg = format(fmt, "last argument must be a receiver or a config",
|
|
A.length -1);
|
|
}
|
|
}
|
|
end:
|
|
return msg;
|
|
}
|
|
enum message = validator;
|
|
alias optionValidator = message;
|
|
}
|
|
|
|
private void handleConversion(R)(string option, string value, R* receiver,
|
|
size_t idx, string file = __FILE__, size_t line = __LINE__)
|
|
{
|
|
import std.conv : to, ConvException;
|
|
import std.format : format;
|
|
try
|
|
{
|
|
*receiver = to!(typeof(*receiver))(value);
|
|
}
|
|
catch (ConvException e)
|
|
{
|
|
throw new ConvException(format("Argument '%s' at position '%u' could "
|
|
~ "not be converted to type '%s' as required by option '%s'.",
|
|
value, idx, R.stringof, option), e, file, line);
|
|
}
|
|
}
|
|
|
|
@safe pure unittest
|
|
{
|
|
alias P = void*;
|
|
alias S = string;
|
|
alias A = char;
|
|
alias C = config;
|
|
alias F = void function();
|
|
|
|
static assert(optionValidator!(S,P) == "");
|
|
static assert(optionValidator!(S,F) == "");
|
|
static assert(optionValidator!(A,P) == "");
|
|
static assert(optionValidator!(A,F) == "");
|
|
|
|
static assert(optionValidator!(C,S,P) == "");
|
|
static assert(optionValidator!(C,S,F) == "");
|
|
static assert(optionValidator!(C,A,P) == "");
|
|
static assert(optionValidator!(C,A,F) == "");
|
|
|
|
static assert(optionValidator!(C,S,S,P) == "");
|
|
static assert(optionValidator!(C,S,S,F) == "");
|
|
static assert(optionValidator!(C,A,S,P) == "");
|
|
static assert(optionValidator!(C,A,S,F) == "");
|
|
|
|
static assert(optionValidator!(C,S,S,P) == "");
|
|
static assert(optionValidator!(C,S,S,P,C,S,F) == "");
|
|
static assert(optionValidator!(C,S,P,C,S,S,F) == "");
|
|
|
|
static assert(optionValidator!(C,A,P,A,S,F) == "");
|
|
static assert(optionValidator!(C,A,P,C,A,S,F) == "");
|
|
|
|
static assert(optionValidator!(P,S,S) != "");
|
|
static assert(optionValidator!(P,P,S) != "");
|
|
static assert(optionValidator!(P,F,S,P) != "");
|
|
static assert(optionValidator!(C,C,S) != "");
|
|
static assert(optionValidator!(S,S,P,S,S,P,S) != "");
|
|
static assert(optionValidator!(S,S,P,P) != "");
|
|
static assert(optionValidator!(S,S,S,P) != "");
|
|
|
|
static assert(optionValidator!(C,A,S,P,C,A,F) == "");
|
|
static assert(optionValidator!(C,A,P,C,A,S,F) == "");
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=15914
|
|
@safe unittest
|
|
{
|
|
import std.exception : assertThrown;
|
|
bool opt;
|
|
string[] args = ["program", "-a"];
|
|
getopt(args, config.passThrough, 'a', &opt);
|
|
assert(opt);
|
|
opt = false;
|
|
args = ["program", "-a"];
|
|
getopt(args, 'a', &opt);
|
|
assert(opt);
|
|
opt = false;
|
|
args = ["program", "-a"];
|
|
getopt(args, 'a', "help string", &opt);
|
|
assert(opt);
|
|
opt = false;
|
|
args = ["program", "-a"];
|
|
getopt(args, config.caseSensitive, 'a', "help string", &opt);
|
|
assert(opt);
|
|
|
|
assertThrown(getopt(args, "", "forgot to put a string", &opt));
|
|
}
|
|
|
|
private void getoptImpl(T...)(ref string[] args, ref configuration cfg,
|
|
ref GetoptResult rslt, ref GetOptException excep,
|
|
void[][string] visitedLongOpts, void[][string] visitedShortOpts, T opts)
|
|
{
|
|
enum validationMessage = optionValidator!T;
|
|
static assert(validationMessage == "", validationMessage);
|
|
|
|
import std.algorithm.mutation : remove;
|
|
import std.conv : to;
|
|
import std.uni : toLower;
|
|
static if (opts.length)
|
|
{
|
|
static if (is(typeof(opts[0]) : config))
|
|
{
|
|
// it's a configuration flag, act on it
|
|
setConfig(cfg, opts[0]);
|
|
return getoptImpl(args, cfg, rslt, excep, visitedLongOpts,
|
|
visitedShortOpts, opts[1 .. $]);
|
|
}
|
|
else
|
|
{
|
|
// it's an option string
|
|
auto option = to!string(opts[0]);
|
|
if (option.length == 0)
|
|
{
|
|
excep = new GetOptException("An option name may not be an empty string", excep);
|
|
return;
|
|
}
|
|
Option optionHelp = splitAndGet(option);
|
|
optionHelp.required = cfg.required;
|
|
|
|
if (optionHelp.optLong.length)
|
|
{
|
|
auto name = optionHelp.optLong;
|
|
if (!cfg.caseSensitive)
|
|
name = name.toLower();
|
|
assert(name !in visitedLongOpts,
|
|
"Long option " ~ optionHelp.optLong ~ " is multiply defined");
|
|
|
|
visitedLongOpts[optionHelp.optLong] = [];
|
|
}
|
|
|
|
if (optionHelp.optShort.length)
|
|
{
|
|
auto name = optionHelp.optShort;
|
|
if (!cfg.caseSensitive)
|
|
name = name.toLower();
|
|
assert(name !in visitedShortOpts,
|
|
"Short option " ~ optionHelp.optShort
|
|
~ " is multiply defined");
|
|
|
|
visitedShortOpts[optionHelp.optShort] = [];
|
|
}
|
|
|
|
static if (is(typeof(opts[1]) : string))
|
|
{
|
|
alias receiver = opts[2];
|
|
optionHelp.help = opts[1];
|
|
immutable lowSliceIdx = 3;
|
|
}
|
|
else
|
|
{
|
|
alias receiver = opts[1];
|
|
immutable lowSliceIdx = 2;
|
|
}
|
|
|
|
rslt.options ~= optionHelp;
|
|
|
|
bool incremental;
|
|
// Handle options of the form --blah+
|
|
if (option.length && option[$ - 1] == autoIncrementChar)
|
|
{
|
|
option = option[0 .. $ - 1];
|
|
incremental = true;
|
|
}
|
|
|
|
bool optWasHandled = handleOption(option, receiver, args, cfg, incremental);
|
|
|
|
if (cfg.required && !optWasHandled)
|
|
{
|
|
excep = new GetOptException("Required option "
|
|
~ option ~ " was not supplied", excep);
|
|
}
|
|
cfg.required = false;
|
|
|
|
getoptImpl(args, cfg, rslt, excep, visitedLongOpts,
|
|
visitedShortOpts, opts[lowSliceIdx .. $]);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// no more options to look for, potentially some arguments left
|
|
for (size_t i = 1; i < args.length;)
|
|
{
|
|
auto a = args[i];
|
|
if (endOfOptions.length && a == endOfOptions)
|
|
{
|
|
// Consume the "--" if keepEndOfOptions is not specified
|
|
if (!cfg.keepEndOfOptions)
|
|
args = args.remove(i);
|
|
break;
|
|
}
|
|
if (a.length < 2 || a[0] != optionChar)
|
|
{
|
|
// not an option
|
|
if (cfg.stopOnFirstNonOption) break;
|
|
++i;
|
|
continue;
|
|
}
|
|
if (a == "--help" || a == "-h")
|
|
{
|
|
rslt.helpWanted = true;
|
|
args = args.remove(i);
|
|
continue;
|
|
}
|
|
if (!cfg.passThrough)
|
|
{
|
|
throw new GetOptException("Unrecognized option "~a, excep);
|
|
}
|
|
++i;
|
|
}
|
|
|
|
Option helpOpt;
|
|
helpOpt.optShort = "-h";
|
|
helpOpt.optLong = "--help";
|
|
helpOpt.help = "This help information.";
|
|
rslt.options ~= helpOpt;
|
|
}
|
|
}
|
|
|
|
private bool handleOption(R)(string option, R receiver, ref string[] args,
|
|
ref configuration cfg, bool incremental)
|
|
{
|
|
import std.algorithm.iteration : map, splitter;
|
|
import std.ascii : isAlpha;
|
|
import std.conv : text, to;
|
|
// Scan arguments looking for a match for this option
|
|
bool ret = false;
|
|
for (size_t i = 1; i < args.length; )
|
|
{
|
|
auto a = args[i];
|
|
if (endOfOptions.length && a == endOfOptions) break;
|
|
if (cfg.stopOnFirstNonOption && (!a.length || a[0] != optionChar))
|
|
{
|
|
// first non-option is end of options
|
|
break;
|
|
}
|
|
// Unbundle bundled arguments if necessary
|
|
if (cfg.bundling && a.length > 2 && a[0] == optionChar &&
|
|
a[1] != optionChar)
|
|
{
|
|
string[] expanded;
|
|
foreach (j, dchar c; a[1 .. $])
|
|
{
|
|
// If the character is not alpha, stop right there. This allows
|
|
// e.g. -j100 to work as "pass argument 100 to option -j".
|
|
if (!isAlpha(c))
|
|
{
|
|
if (c == '=')
|
|
j++;
|
|
expanded ~= a[j + 1 .. $];
|
|
break;
|
|
}
|
|
expanded ~= text(optionChar, c);
|
|
}
|
|
args = args[0 .. i] ~ expanded ~ args[i + 1 .. $];
|
|
continue;
|
|
}
|
|
|
|
string val;
|
|
if (!optMatch(a, option, val, cfg))
|
|
{
|
|
++i;
|
|
continue;
|
|
}
|
|
|
|
ret = true;
|
|
|
|
// found it
|
|
// from here on, commit to eat args[i]
|
|
// (and potentially args[i + 1] too, but that comes later)
|
|
args = args[0 .. i] ~ args[i + 1 .. $];
|
|
|
|
static if (is(typeof(*receiver) == bool))
|
|
{
|
|
if (val.length)
|
|
{
|
|
// parse '--b=true/false'
|
|
handleConversion(option, val, receiver, i);
|
|
}
|
|
else
|
|
{
|
|
// no argument means set it to true
|
|
*receiver = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
import std.exception : enforce;
|
|
// non-boolean option, which might include an argument
|
|
enum isCallbackWithLessThanTwoParameters =
|
|
(is(typeof(receiver) == delegate) || is(typeof(*receiver) == function)) &&
|
|
!is(typeof(receiver("", "")));
|
|
if (!isCallbackWithLessThanTwoParameters && !(val.length) && !incremental)
|
|
{
|
|
// Eat the next argument too. Check to make sure there's one
|
|
// to be eaten first, though.
|
|
enforce!GetOptException(i < args.length,
|
|
"Missing value for argument " ~ a ~ ".");
|
|
val = args[i];
|
|
args = args[0 .. i] ~ args[i + 1 .. $];
|
|
}
|
|
static if (is(typeof(*receiver) == enum) ||
|
|
is(typeof(*receiver) == string))
|
|
{
|
|
handleConversion(option, val, receiver, i);
|
|
}
|
|
else static if (is(typeof(*receiver) : real))
|
|
{
|
|
// numeric receiver
|
|
if (incremental)
|
|
{
|
|
++*receiver;
|
|
}
|
|
else
|
|
{
|
|
handleConversion(option, val, receiver, i);
|
|
}
|
|
}
|
|
else static if (is(typeof(*receiver) == string))
|
|
{
|
|
// string receiver
|
|
*receiver = to!(typeof(*receiver))(val);
|
|
}
|
|
else static if (is(typeof(receiver) == delegate) ||
|
|
is(typeof(*receiver) == function))
|
|
{
|
|
static if (is(typeof(receiver("", "")) : void))
|
|
{
|
|
// option with argument
|
|
receiver(option, val);
|
|
}
|
|
else static if (is(typeof(receiver("")) : void))
|
|
{
|
|
alias RType = typeof(receiver(""));
|
|
static assert(is(RType : void),
|
|
"Invalid receiver return type " ~ RType.stringof);
|
|
// boolean-style receiver
|
|
receiver(option);
|
|
}
|
|
else
|
|
{
|
|
alias RType = typeof(receiver());
|
|
static assert(is(RType : void),
|
|
"Invalid receiver return type " ~ RType.stringof);
|
|
// boolean-style receiver without argument
|
|
receiver();
|
|
}
|
|
}
|
|
else static if (isArray!(typeof(*receiver)))
|
|
{
|
|
// array receiver
|
|
import std.range : ElementEncodingType;
|
|
alias E = ElementEncodingType!(typeof(*receiver));
|
|
|
|
if (arraySep == "")
|
|
{
|
|
E tmp;
|
|
handleConversion(option, val, &tmp, i);
|
|
*receiver ~= tmp;
|
|
}
|
|
else
|
|
{
|
|
foreach (elem; val.splitter(arraySep))
|
|
{
|
|
E tmp;
|
|
handleConversion(option, elem, &tmp, i);
|
|
*receiver ~= tmp;
|
|
}
|
|
}
|
|
}
|
|
else static if (isAssociativeArray!(typeof(*receiver)))
|
|
{
|
|
// hash receiver
|
|
alias K = typeof(receiver.keys[0]);
|
|
alias V = typeof(receiver.values[0]);
|
|
|
|
import std.range : only;
|
|
import std.string : indexOf;
|
|
import std.typecons : Tuple, tuple;
|
|
|
|
static Tuple!(K, V) getter(string input)
|
|
{
|
|
auto j = indexOf(input, assignChar);
|
|
enforce!GetOptException(j != -1, "Could not find '"
|
|
~ to!string(assignChar) ~ "' in argument '" ~ input ~ "'.");
|
|
auto key = input[0 .. j];
|
|
auto value = input[j + 1 .. $];
|
|
|
|
K k;
|
|
handleConversion("", key, &k, 0);
|
|
|
|
V v;
|
|
handleConversion("", value, &v, 0);
|
|
|
|
return tuple(k,v);
|
|
}
|
|
|
|
static void setHash(Range)(R receiver, Range range)
|
|
{
|
|
foreach (k, v; range.map!getter)
|
|
(*receiver)[k] = v;
|
|
}
|
|
|
|
if (arraySep == "")
|
|
setHash(receiver, val.only);
|
|
else
|
|
setHash(receiver, val.splitter(arraySep));
|
|
}
|
|
else
|
|
static assert(false, "getopt does not know how to handle the type " ~ typeof(receiver).stringof);
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=17574
|
|
@safe unittest
|
|
{
|
|
import std.algorithm.searching : startsWith;
|
|
|
|
try
|
|
{
|
|
string[string] mapping;
|
|
immutable as = arraySep;
|
|
arraySep = ",";
|
|
scope (exit)
|
|
arraySep = as;
|
|
string[] args = ["testProgram", "-m", "a=b,c=\"d,e,f\""];
|
|
args.getopt("m", &mapping);
|
|
assert(false, "Exception not thrown");
|
|
}
|
|
catch (GetOptException goe)
|
|
assert(goe.msg.startsWith("Could not find"));
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=5316 - arrays with arraySep
|
|
@safe unittest
|
|
{
|
|
import std.conv;
|
|
|
|
arraySep = ",";
|
|
scope (exit) arraySep = "";
|
|
|
|
string[] names;
|
|
auto args = ["program.name", "-nfoo,bar,baz"];
|
|
getopt(args, "name|n", &names);
|
|
assert(names == ["foo", "bar", "baz"], to!string(names));
|
|
|
|
names = names.init;
|
|
args = ["program.name", "-n", "foo,bar,baz"];
|
|
getopt(args, "name|n", &names);
|
|
assert(names == ["foo", "bar", "baz"], to!string(names));
|
|
|
|
names = names.init;
|
|
args = ["program.name", "--name=foo,bar,baz"];
|
|
getopt(args, "name|n", &names);
|
|
assert(names == ["foo", "bar", "baz"], to!string(names));
|
|
|
|
names = names.init;
|
|
args = ["program.name", "--name", "foo,bar,baz"];
|
|
getopt(args, "name|n", &names);
|
|
assert(names == ["foo", "bar", "baz"], to!string(names));
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=5316 - associative arrays with arraySep
|
|
@safe unittest
|
|
{
|
|
import std.conv;
|
|
|
|
arraySep = ",";
|
|
scope (exit) arraySep = "";
|
|
|
|
int[string] values;
|
|
values = values.init;
|
|
auto args = ["program.name", "-vfoo=0,bar=1,baz=2"];
|
|
getopt(args, "values|v", &values);
|
|
assert(values == ["foo":0, "bar":1, "baz":2], to!string(values));
|
|
|
|
values = values.init;
|
|
args = ["program.name", "-v", "foo=0,bar=1,baz=2"];
|
|
getopt(args, "values|v", &values);
|
|
assert(values == ["foo":0, "bar":1, "baz":2], to!string(values));
|
|
|
|
values = values.init;
|
|
args = ["program.name", "--values=foo=0,bar=1,baz=2"];
|
|
getopt(args, "values|t", &values);
|
|
assert(values == ["foo":0, "bar":1, "baz":2], to!string(values));
|
|
|
|
values = values.init;
|
|
args = ["program.name", "--values", "foo=0,bar=1,baz=2"];
|
|
getopt(args, "values|v", &values);
|
|
assert(values == ["foo":0, "bar":1, "baz":2], to!string(values));
|
|
}
|
|
|
|
/**
|
|
The option character (default '-').
|
|
|
|
Defaults to '-' but it can be assigned to prior to calling `getopt`.
|
|
*/
|
|
dchar optionChar = '-';
|
|
|
|
/**
|
|
The string that conventionally marks the end of all options (default '--').
|
|
|
|
Defaults to "--" but can be assigned to prior to calling `getopt`. Assigning an
|
|
empty string to `endOfOptions` effectively disables it.
|
|
*/
|
|
string endOfOptions = "--";
|
|
|
|
/**
|
|
The assignment character used in options with parameters (default '=').
|
|
|
|
Defaults to '=' but can be assigned to prior to calling `getopt`.
|
|
*/
|
|
dchar assignChar = '=';
|
|
|
|
/**
|
|
When set to "", parameters to array and associative array receivers are
|
|
treated as an individual argument. That is, only one argument is appended or
|
|
inserted per appearance of the option switch. If `arraySep` is set to
|
|
something else, then each parameter is first split by the separator, and the
|
|
individual pieces are treated as arguments to the same option.
|
|
|
|
Defaults to "" but can be assigned to prior to calling `getopt`.
|
|
*/
|
|
string arraySep = "";
|
|
|
|
private enum autoIncrementChar = '+';
|
|
|
|
private struct configuration
|
|
{
|
|
import std.bitmanip : bitfields;
|
|
mixin(bitfields!(
|
|
bool, "caseSensitive", 1,
|
|
bool, "bundling", 1,
|
|
bool, "passThrough", 1,
|
|
bool, "stopOnFirstNonOption", 1,
|
|
bool, "keepEndOfOptions", 1,
|
|
bool, "required", 1,
|
|
ubyte, "", 2));
|
|
}
|
|
|
|
private bool optMatch(string arg, scope string optPattern, ref string value,
|
|
configuration cfg) @safe
|
|
{
|
|
import std.algorithm.iteration : splitter;
|
|
import std.string : indexOf;
|
|
import std.uni : icmp;
|
|
//writeln("optMatch:\n ", arg, "\n ", optPattern, "\n ", value);
|
|
//scope(success) writeln("optMatch result: ", value);
|
|
if (arg.length < 2 || arg[0] != optionChar) return false;
|
|
// yank the leading '-'
|
|
arg = arg[1 .. $];
|
|
immutable isLong = arg.length > 1 && arg[0] == optionChar;
|
|
//writeln("isLong: ", isLong);
|
|
// yank the second '-' if present
|
|
if (isLong) arg = arg[1 .. $];
|
|
immutable eqPos = indexOf(arg, assignChar);
|
|
if (isLong && eqPos >= 0)
|
|
{
|
|
// argument looks like --opt=value
|
|
value = arg[eqPos + 1 .. $];
|
|
arg = arg[0 .. eqPos];
|
|
}
|
|
else
|
|
{
|
|
if (!isLong && eqPos == 1)
|
|
{
|
|
// argument looks like -o=value
|
|
value = arg[2 .. $];
|
|
arg = arg[0 .. 1];
|
|
}
|
|
else
|
|
if (!isLong && !cfg.bundling)
|
|
{
|
|
// argument looks like -ovalue and there's no bundling
|
|
value = arg[1 .. $];
|
|
arg = arg[0 .. 1];
|
|
}
|
|
else
|
|
{
|
|
// argument looks like --opt, or -oxyz with bundling
|
|
value = null;
|
|
}
|
|
}
|
|
//writeln("Arg: ", arg, " pattern: ", optPattern, " value: ", value);
|
|
// Split the option
|
|
foreach (v; splitter(optPattern, "|"))
|
|
{
|
|
//writeln("Trying variant: ", v, " against ", arg);
|
|
if (arg == v || (!cfg.caseSensitive && icmp(arg, v) == 0))
|
|
return true;
|
|
if (cfg.bundling && !isLong && v.length == 1
|
|
&& indexOf(arg, v) >= 0)
|
|
{
|
|
//writeln("success");
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private void setConfig(ref configuration cfg, config option) @safe pure nothrow @nogc
|
|
{
|
|
final switch (option)
|
|
{
|
|
case config.caseSensitive: cfg.caseSensitive = true; break;
|
|
case config.caseInsensitive: cfg.caseSensitive = false; break;
|
|
case config.bundling: cfg.bundling = true; break;
|
|
case config.noBundling: cfg.bundling = false; break;
|
|
case config.passThrough: cfg.passThrough = true; break;
|
|
case config.noPassThrough: cfg.passThrough = false; break;
|
|
case config.required: cfg.required = true; break;
|
|
case config.stopOnFirstNonOption:
|
|
cfg.stopOnFirstNonOption = true; break;
|
|
case config.keepEndOfOptions:
|
|
cfg.keepEndOfOptions = true; break;
|
|
}
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
import std.conv;
|
|
import std.math.operations : isClose;
|
|
|
|
uint paranoid = 2;
|
|
string[] args = ["program.name", "--paranoid", "--paranoid", "--paranoid"];
|
|
getopt(args, "paranoid+", ¶noid);
|
|
assert(paranoid == 5, to!(string)(paranoid));
|
|
|
|
enum Color { no, yes }
|
|
Color color;
|
|
args = ["program.name", "--color=yes",];
|
|
getopt(args, "color", &color);
|
|
assert(color, to!(string)(color));
|
|
|
|
color = Color.no;
|
|
args = ["program.name", "--color", "yes",];
|
|
getopt(args, "color", &color);
|
|
assert(color, to!(string)(color));
|
|
|
|
string data = "file.dat";
|
|
int length = 24;
|
|
bool verbose = false;
|
|
args = ["program.name", "--length=5", "--file", "dat.file", "--verbose"];
|
|
getopt(
|
|
args,
|
|
"length", &length,
|
|
"file", &data,
|
|
"verbose", &verbose);
|
|
assert(args.length == 1);
|
|
assert(data == "dat.file");
|
|
assert(length == 5);
|
|
assert(verbose);
|
|
|
|
//
|
|
string[] outputFiles;
|
|
args = ["program.name", "--output=myfile.txt", "--output", "yourfile.txt"];
|
|
getopt(args, "output", &outputFiles);
|
|
assert(outputFiles.length == 2
|
|
&& outputFiles[0] == "myfile.txt" && outputFiles[1] == "yourfile.txt");
|
|
|
|
outputFiles = [];
|
|
arraySep = ",";
|
|
args = ["program.name", "--output", "myfile.txt,yourfile.txt"];
|
|
getopt(args, "output", &outputFiles);
|
|
assert(outputFiles.length == 2
|
|
&& outputFiles[0] == "myfile.txt" && outputFiles[1] == "yourfile.txt");
|
|
arraySep = "";
|
|
|
|
foreach (testArgs;
|
|
[["program.name", "--tune=alpha=0.5", "--tune", "beta=0.6"],
|
|
["program.name", "--tune=alpha=0.5,beta=0.6"],
|
|
["program.name", "--tune", "alpha=0.5,beta=0.6"]])
|
|
{
|
|
arraySep = ",";
|
|
double[string] tuningParms;
|
|
getopt(testArgs, "tune", &tuningParms);
|
|
assert(testArgs.length == 1);
|
|
assert(tuningParms.length == 2);
|
|
assert(isClose(tuningParms["alpha"], 0.5));
|
|
assert(isClose(tuningParms["beta"], 0.6));
|
|
arraySep = "";
|
|
}
|
|
|
|
uint verbosityLevel = 1;
|
|
void myHandler(string option)
|
|
{
|
|
if (option == "quiet")
|
|
{
|
|
verbosityLevel = 0;
|
|
}
|
|
else
|
|
{
|
|
assert(option == "verbose");
|
|
verbosityLevel = 2;
|
|
}
|
|
}
|
|
args = ["program.name", "--quiet"];
|
|
getopt(args, "verbose", &myHandler, "quiet", &myHandler);
|
|
assert(verbosityLevel == 0);
|
|
args = ["program.name", "--verbose"];
|
|
getopt(args, "verbose", &myHandler, "quiet", &myHandler);
|
|
assert(verbosityLevel == 2);
|
|
|
|
verbosityLevel = 1;
|
|
void myHandler2(string option, string value)
|
|
{
|
|
assert(option == "verbose");
|
|
verbosityLevel = 2;
|
|
}
|
|
args = ["program.name", "--verbose", "2"];
|
|
getopt(args, "verbose", &myHandler2);
|
|
assert(verbosityLevel == 2);
|
|
|
|
verbosityLevel = 1;
|
|
void myHandler3()
|
|
{
|
|
verbosityLevel = 2;
|
|
}
|
|
args = ["program.name", "--verbose"];
|
|
getopt(args, "verbose", &myHandler3);
|
|
assert(verbosityLevel == 2);
|
|
|
|
bool foo, bar;
|
|
args = ["program.name", "--foo", "--bAr"];
|
|
getopt(args,
|
|
std.getopt.config.caseSensitive,
|
|
std.getopt.config.passThrough,
|
|
"foo", &foo,
|
|
"bar", &bar);
|
|
assert(args[1] == "--bAr");
|
|
|
|
// test stopOnFirstNonOption
|
|
|
|
args = ["program.name", "--foo", "nonoption", "--bar"];
|
|
foo = bar = false;
|
|
getopt(args,
|
|
std.getopt.config.stopOnFirstNonOption,
|
|
"foo", &foo,
|
|
"bar", &bar);
|
|
assert(foo && !bar && args[1] == "nonoption" && args[2] == "--bar");
|
|
|
|
args = ["program.name", "--foo", "nonoption", "--zab"];
|
|
foo = bar = false;
|
|
getopt(args,
|
|
std.getopt.config.stopOnFirstNonOption,
|
|
"foo", &foo,
|
|
"bar", &bar);
|
|
assert(foo && !bar && args[1] == "nonoption" && args[2] == "--zab");
|
|
|
|
args = ["program.name", "--fb1", "--fb2=true", "--tb1=false"];
|
|
bool fb1, fb2;
|
|
bool tb1 = true;
|
|
getopt(args, "fb1", &fb1, "fb2", &fb2, "tb1", &tb1);
|
|
assert(fb1 && fb2 && !tb1);
|
|
|
|
// test keepEndOfOptions
|
|
|
|
args = ["program.name", "--foo", "nonoption", "--bar", "--", "--baz"];
|
|
getopt(args,
|
|
std.getopt.config.keepEndOfOptions,
|
|
"foo", &foo,
|
|
"bar", &bar);
|
|
assert(args == ["program.name", "nonoption", "--", "--baz"]);
|
|
|
|
// Ensure old behavior without the keepEndOfOptions
|
|
|
|
args = ["program.name", "--foo", "nonoption", "--bar", "--", "--baz"];
|
|
getopt(args,
|
|
"foo", &foo,
|
|
"bar", &bar);
|
|
assert(args == ["program.name", "nonoption", "--baz"]);
|
|
|
|
// test function callbacks
|
|
|
|
static class MyEx : Exception
|
|
{
|
|
this() { super(""); }
|
|
this(string option) { this(); this.option = option; }
|
|
this(string option, string value) { this(option); this.value = value; }
|
|
|
|
string option;
|
|
string value;
|
|
}
|
|
|
|
static void myStaticHandler1() { throw new MyEx(); }
|
|
args = ["program.name", "--verbose"];
|
|
try { getopt(args, "verbose", &myStaticHandler1); assert(0); }
|
|
catch (MyEx ex) { assert(ex.option is null && ex.value is null); }
|
|
|
|
static void myStaticHandler2(string option) { throw new MyEx(option); }
|
|
args = ["program.name", "--verbose"];
|
|
try { getopt(args, "verbose", &myStaticHandler2); assert(0); }
|
|
catch (MyEx ex) { assert(ex.option == "verbose" && ex.value is null); }
|
|
|
|
static void myStaticHandler3(string option, string value) { throw new MyEx(option, value); }
|
|
args = ["program.name", "--verbose", "2"];
|
|
try { getopt(args, "verbose", &myStaticHandler3); assert(0); }
|
|
catch (MyEx ex) { assert(ex.option == "verbose" && ex.value == "2"); }
|
|
|
|
// check that GetOptException is thrown if the value is missing
|
|
args = ["program.name", "--verbose"];
|
|
try { getopt(args, "verbose", &myStaticHandler3); assert(0); }
|
|
catch (GetOptException e) {}
|
|
catch (Exception e) { assert(0); }
|
|
}
|
|
|
|
@safe unittest // @safe std.getopt.config option use
|
|
{
|
|
long x = 0;
|
|
string[] args = ["program", "--inc-x", "--inc-x"];
|
|
getopt(args,
|
|
std.getopt.config.caseSensitive,
|
|
"inc-x", "Add one to x", delegate void() { x++; });
|
|
assert(x == 2);
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=2142
|
|
@safe unittest
|
|
{
|
|
bool f_linenum, f_filename;
|
|
string[] args = [ "", "-nl" ];
|
|
getopt
|
|
(
|
|
args,
|
|
std.getopt.config.bundling,
|
|
//std.getopt.config.caseSensitive,
|
|
"linenum|l", &f_linenum,
|
|
"filename|n", &f_filename
|
|
);
|
|
assert(f_linenum);
|
|
assert(f_filename);
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=6887
|
|
@safe unittest
|
|
{
|
|
string[] p;
|
|
string[] args = ["", "-pa"];
|
|
getopt(args, "p", &p);
|
|
assert(p.length == 1);
|
|
assert(p[0] == "a");
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=6888
|
|
@safe unittest
|
|
{
|
|
int[string] foo;
|
|
auto args = ["", "-t", "a=1"];
|
|
getopt(args, "t", &foo);
|
|
assert(foo == ["a":1]);
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=9583
|
|
@safe unittest
|
|
{
|
|
int opt;
|
|
auto args = ["prog", "--opt=123", "--", "--a", "--b", "--c"];
|
|
getopt(args, "opt", &opt);
|
|
assert(args == ["prog", "--a", "--b", "--c"]);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
string foo, bar;
|
|
auto args = ["prog", "-thello", "-dbar=baz"];
|
|
getopt(args, "t", &foo, "d", &bar);
|
|
assert(foo == "hello");
|
|
assert(bar == "bar=baz");
|
|
|
|
// From https://issues.dlang.org/show_bug.cgi?id=5762
|
|
string a;
|
|
args = ["prog", "-a-0x12"];
|
|
getopt(args, config.bundling, "a|addr", &a);
|
|
assert(a == "-0x12", a);
|
|
args = ["prog", "--addr=-0x12"];
|
|
getopt(args, config.bundling, "a|addr", &a);
|
|
assert(a == "-0x12");
|
|
|
|
// From https://issues.dlang.org/show_bug.cgi?id=11764
|
|
args = ["main", "-test"];
|
|
bool opt;
|
|
args.getopt(config.passThrough, "opt", &opt);
|
|
assert(args == ["main", "-test"]);
|
|
|
|
// From https://issues.dlang.org/show_bug.cgi?id=15220
|
|
args = ["main", "-o=str"];
|
|
string o;
|
|
args.getopt("o", &o);
|
|
assert(o == "str");
|
|
|
|
args = ["main", "-o=str"];
|
|
o = null;
|
|
args.getopt(config.bundling, "o", &o);
|
|
assert(o == "str");
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=5228
|
|
@safe unittest
|
|
{
|
|
import std.conv;
|
|
import std.exception;
|
|
|
|
auto args = ["prog", "--foo=bar"];
|
|
int abc;
|
|
assertThrown!GetOptException(getopt(args, "abc", &abc));
|
|
|
|
args = ["prog", "--abc=string"];
|
|
assertThrown!ConvException(getopt(args, "abc", &abc));
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=7693
|
|
@safe unittest
|
|
{
|
|
import std.exception;
|
|
|
|
enum Foo {
|
|
bar,
|
|
baz
|
|
}
|
|
|
|
auto args = ["prog", "--foo=barZZZ"];
|
|
Foo foo;
|
|
assertThrown(getopt(args, "foo", &foo));
|
|
args = ["prog", "--foo=bar"];
|
|
assertNotThrown(getopt(args, "foo", &foo));
|
|
args = ["prog", "--foo", "barZZZ"];
|
|
assertThrown(getopt(args, "foo", &foo));
|
|
args = ["prog", "--foo", "baz"];
|
|
assertNotThrown(getopt(args, "foo", &foo));
|
|
}
|
|
|
|
// Same as https://issues.dlang.org/show_bug.cgi?id=7693 only for `bool`
|
|
@safe unittest
|
|
{
|
|
import std.exception;
|
|
|
|
auto args = ["prog", "--foo=truefoobar"];
|
|
bool foo;
|
|
assertThrown(getopt(args, "foo", &foo));
|
|
args = ["prog", "--foo"];
|
|
getopt(args, "foo", &foo);
|
|
assert(foo);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
bool foo;
|
|
auto args = ["prog", "--foo"];
|
|
getopt(args, "foo", &foo);
|
|
assert(foo);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
bool foo;
|
|
bool bar;
|
|
auto args = ["prog", "--foo", "-b"];
|
|
getopt(args, config.caseInsensitive,"foo|f", "Some foo", &foo,
|
|
config.caseSensitive, "bar|b", "Some bar", &bar);
|
|
assert(foo);
|
|
assert(bar);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
bool foo;
|
|
bool bar;
|
|
auto args = ["prog", "-b", "--foo", "-z"];
|
|
getopt(args, config.caseInsensitive, config.required, "foo|f", "Some foo",
|
|
&foo, config.caseSensitive, "bar|b", "Some bar", &bar,
|
|
config.passThrough);
|
|
assert(foo);
|
|
assert(bar);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
import std.exception;
|
|
|
|
bool foo;
|
|
bool bar;
|
|
auto args = ["prog", "-b", "-z"];
|
|
assertThrown(getopt(args, config.caseInsensitive, config.required, "foo|f",
|
|
"Some foo", &foo, config.caseSensitive, "bar|b", "Some bar", &bar,
|
|
config.passThrough));
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
import std.exception;
|
|
|
|
bool foo;
|
|
bool bar;
|
|
auto args = ["prog", "--foo", "-z"];
|
|
assertNotThrown(getopt(args, config.caseInsensitive, config.required,
|
|
"foo|f", "Some foo", &foo, config.caseSensitive, "bar|b", "Some bar",
|
|
&bar, config.passThrough));
|
|
assert(foo);
|
|
assert(!bar);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
bool foo;
|
|
auto args = ["prog", "-f"];
|
|
auto r = getopt(args, config.caseInsensitive, "help|f", "Some foo", &foo);
|
|
assert(foo);
|
|
assert(!r.helpWanted);
|
|
}
|
|
|
|
@safe unittest // implicit help option without config.passThrough
|
|
{
|
|
string[] args = ["program", "--help"];
|
|
auto r = getopt(args);
|
|
assert(r.helpWanted);
|
|
}
|
|
|
|
// std.getopt: implicit help option breaks the next argument
|
|
// https://issues.dlang.org/show_bug.cgi?id=13316
|
|
@safe unittest
|
|
{
|
|
string[] args = ["program", "--help", "--", "something"];
|
|
getopt(args);
|
|
assert(args == ["program", "something"]);
|
|
|
|
args = ["program", "--help", "--"];
|
|
getopt(args);
|
|
assert(args == ["program"]);
|
|
|
|
bool b;
|
|
args = ["program", "--help", "nonoption", "--option"];
|
|
getopt(args, config.stopOnFirstNonOption, "option", &b);
|
|
assert(args == ["program", "nonoption", "--option"]);
|
|
}
|
|
|
|
// std.getopt: endOfOptions broken when it doesn't look like an option
|
|
// https://issues.dlang.org/show_bug.cgi?id=13317
|
|
@safe unittest
|
|
{
|
|
auto endOfOptionsBackup = endOfOptions;
|
|
scope(exit) endOfOptions = endOfOptionsBackup;
|
|
endOfOptions = "endofoptions";
|
|
string[] args = ["program", "endofoptions", "--option"];
|
|
bool b = false;
|
|
getopt(args, "option", &b);
|
|
assert(!b);
|
|
assert(args == ["program", "--option"]);
|
|
}
|
|
|
|
// make std.getopt ready for DIP 1000
|
|
// https://issues.dlang.org/show_bug.cgi?id=20480
|
|
@safe unittest
|
|
{
|
|
string[] args = ["test", "--foo", "42", "--bar", "BAR"];
|
|
int foo;
|
|
string bar;
|
|
getopt(args, "foo", &foo, "bar", "bar help", &bar);
|
|
assert(foo == 42);
|
|
assert(bar == "BAR");
|
|
}
|
|
|
|
/** This function prints the passed `Option`s and text in an aligned manner on `stdout`.
|
|
|
|
The passed text will be printed first, followed by a newline, then the short
|
|
and long version of every option will be printed. The short and long version
|
|
will be aligned to the longest option of every `Option` passed. If the option
|
|
is required, then "Required:" will be printed after the long version of the
|
|
`Option`. If a help message is present it will be printed next. The format is
|
|
illustrated by this code:
|
|
|
|
------------
|
|
foreach (it; opt)
|
|
{
|
|
writefln("%*s %*s%s%s", lengthOfLongestShortOption, it.optShort,
|
|
lengthOfLongestLongOption, it.optLong,
|
|
it.required ? " Required: " : " ", it.help);
|
|
}
|
|
------------
|
|
|
|
Params:
|
|
text = The text to printed at the beginning of the help output.
|
|
opt = The `Option` extracted from the `getopt` parameter.
|
|
*/
|
|
void defaultGetoptPrinter(string text, Option[] opt) @safe
|
|
{
|
|
import std.stdio : stdout;
|
|
// stdout global __gshared is trusted with a locked text writer
|
|
auto w = (() @trusted => stdout.lockingTextWriter())();
|
|
|
|
defaultGetoptFormatter(w, text, opt);
|
|
}
|
|
|
|
/** This function writes the passed text and `Option` into an output range
|
|
in the manner described in the documentation of function
|
|
`defaultGetoptPrinter`, unless the style option is used.
|
|
|
|
Params:
|
|
output = The output range used to write the help information.
|
|
text = The text to print at the beginning of the help output.
|
|
opt = The `Option` extracted from the `getopt` parameter.
|
|
style = The manner in which to display the output of each `Option.`
|
|
*/
|
|
void defaultGetoptFormatter(Output)(Output output, string text, Option[] opt, string style = "%*s %*s%*s%s\n")
|
|
{
|
|
import std.algorithm.comparison : min, max;
|
|
import std.format.write : formattedWrite;
|
|
|
|
output.formattedWrite("%s\n", text);
|
|
|
|
size_t ls, ll;
|
|
bool hasRequired = false;
|
|
foreach (it; opt)
|
|
{
|
|
ls = max(ls, it.optShort.length);
|
|
ll = max(ll, it.optLong.length);
|
|
|
|
hasRequired = hasRequired || it.required;
|
|
}
|
|
|
|
string re = " Required: ";
|
|
|
|
foreach (it; opt)
|
|
{
|
|
output.formattedWrite(style, ls, it.optShort, ll, it.optLong,
|
|
hasRequired ? re.length : 1, it.required ? re : " ", it.help);
|
|
}
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
import std.conv;
|
|
|
|
import std.array;
|
|
import std.string;
|
|
bool a;
|
|
auto args = ["prog", "--foo"];
|
|
auto t = getopt(args, "foo|f", "Help", &a);
|
|
string s;
|
|
auto app = appender!string();
|
|
defaultGetoptFormatter(app, "Some Text", t.options);
|
|
|
|
string helpMsg = app.data;
|
|
//writeln(helpMsg);
|
|
assert(helpMsg.length);
|
|
assert(helpMsg.count("\n") == 3, to!string(helpMsg.count("\n")) ~ " "
|
|
~ helpMsg);
|
|
assert(helpMsg.indexOf("--foo") != -1);
|
|
assert(helpMsg.indexOf("-f") != -1);
|
|
assert(helpMsg.indexOf("-h") != -1);
|
|
assert(helpMsg.indexOf("--help") != -1);
|
|
assert(helpMsg.indexOf("Help") != -1);
|
|
|
|
string wanted = "Some Text\n-f --foo Help\n-h --help This help "
|
|
~ "information.\n";
|
|
assert(wanted == helpMsg);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
import std.array ;
|
|
import std.conv;
|
|
import std.string;
|
|
bool a;
|
|
auto args = ["prog", "--foo"];
|
|
auto t = getopt(args, config.required, "foo|f", "Help", &a);
|
|
string s;
|
|
auto app = appender!string();
|
|
defaultGetoptFormatter(app, "Some Text", t.options);
|
|
|
|
string helpMsg = app.data;
|
|
//writeln(helpMsg);
|
|
assert(helpMsg.length);
|
|
assert(helpMsg.count("\n") == 3, to!string(helpMsg.count("\n")) ~ " "
|
|
~ helpMsg);
|
|
assert(helpMsg.indexOf("Required:") != -1);
|
|
assert(helpMsg.indexOf("--foo") != -1);
|
|
assert(helpMsg.indexOf("-f") != -1);
|
|
assert(helpMsg.indexOf("-h") != -1);
|
|
assert(helpMsg.indexOf("--help") != -1);
|
|
assert(helpMsg.indexOf("Help") != -1);
|
|
|
|
string wanted = "Some Text\n-f --foo Required: Help\n-h --help "
|
|
~ " This help information.\n";
|
|
assert(wanted == helpMsg, helpMsg ~ wanted);
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=14724
|
|
@safe unittest
|
|
{
|
|
bool a;
|
|
auto args = ["prog", "--help"];
|
|
GetoptResult rslt;
|
|
try
|
|
{
|
|
rslt = getopt(args, config.required, "foo|f", "bool a", &a);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
enum errorMsg = "If the request for help was passed required options" ~
|
|
"must not be set.";
|
|
assert(false, errorMsg);
|
|
}
|
|
|
|
assert(rslt.helpWanted);
|
|
}
|
|
|
|
// throw on duplicate options
|
|
@system unittest
|
|
{
|
|
import core.exception : AssertError;
|
|
import std.exception : assertNotThrown, assertThrown;
|
|
auto args = ["prog", "--abc", "1"];
|
|
int abc, def;
|
|
assertThrown!AssertError(getopt(args, "abc", &abc, "abc", &abc));
|
|
assertThrown!AssertError(getopt(args, "abc|a", &abc, "def|a", &def));
|
|
assertNotThrown!AssertError(getopt(args, "abc", &abc, "def", &def));
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=23940
|
|
assertThrown!AssertError(getopt(args,
|
|
"abc", &abc, "ABC", &def));
|
|
assertThrown!AssertError(getopt(args, config.caseInsensitive,
|
|
"abc", &abc, "ABC", &def));
|
|
assertNotThrown!AssertError(getopt(args, config.caseSensitive,
|
|
"abc", &abc, "ABC", &def));
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=17327 repeated option use
|
|
@safe unittest
|
|
{
|
|
long num = 0;
|
|
|
|
string[] args = ["program", "--num", "3"];
|
|
getopt(args, "n|num", &num);
|
|
assert(num == 3);
|
|
|
|
args = ["program", "--num", "3", "--num", "5"];
|
|
getopt(args, "n|num", &num);
|
|
assert(num == 5);
|
|
|
|
args = ["program", "--n", "3", "--num", "5", "-n", "-7"];
|
|
getopt(args, "n|num", &num);
|
|
assert(num == -7);
|
|
|
|
void add1() { num++; }
|
|
void add2(string option) { num += 2; }
|
|
void addN(string option, string value)
|
|
{
|
|
import std.conv : to;
|
|
num += value.to!long;
|
|
}
|
|
|
|
num = 0;
|
|
args = ["program", "--add1", "--add2", "--add1", "--add", "5", "--add2", "--add", "10"];
|
|
getopt(args,
|
|
"add1", "Add 1 to num", &add1,
|
|
"add2", "Add 2 to num", &add2,
|
|
"add", "Add N to num", &addN,);
|
|
assert(num == 21);
|
|
|
|
bool flag = false;
|
|
args = ["program", "--flag"];
|
|
getopt(args, "f|flag", "Boolean", &flag);
|
|
assert(flag);
|
|
|
|
flag = false;
|
|
args = ["program", "-f", "-f"];
|
|
getopt(args, "f|flag", "Boolean", &flag);
|
|
assert(flag);
|
|
|
|
flag = false;
|
|
args = ["program", "--flag=true", "--flag=false"];
|
|
getopt(args, "f|flag", "Boolean", &flag);
|
|
assert(!flag);
|
|
|
|
flag = false;
|
|
args = ["program", "--flag=true", "--flag=false", "-f"];
|
|
getopt(args, "f|flag", "Boolean", &flag);
|
|
assert(flag);
|
|
}
|
|
|
|
@system unittest // Delegates as callbacks
|
|
{
|
|
alias TwoArgOptionHandler = void delegate(string option, string value) @safe;
|
|
|
|
TwoArgOptionHandler makeAddNHandler(ref long dest)
|
|
{
|
|
void addN(ref long dest, string n)
|
|
{
|
|
import std.conv : to;
|
|
dest += n.to!long;
|
|
}
|
|
|
|
return (option, value) => addN(dest, value);
|
|
}
|
|
|
|
long x = 0;
|
|
long y = 0;
|
|
|
|
string[] args =
|
|
["program", "--x-plus-1", "--x-plus-1", "--x-plus-5", "--x-plus-n", "10",
|
|
"--y-plus-n", "25", "--y-plus-7", "--y-plus-n", "15", "--y-plus-3"];
|
|
|
|
getopt(args,
|
|
"x-plus-1", "Add one to x", delegate void() { x += 1; },
|
|
"x-plus-5", "Add five to x", delegate void(string option) { x += 5; },
|
|
"x-plus-n", "Add NUM to x", makeAddNHandler(x),
|
|
"y-plus-7", "Add seven to y", delegate void() { y += 7; },
|
|
"y-plus-3", "Add three to y", delegate void(string option) { y += 3; },
|
|
"y-plus-n", "Add NUM to x", makeAddNHandler(y),);
|
|
|
|
assert(x == 17);
|
|
assert(y == 50);
|
|
}
|
|
|
|
// Hyphens at the start of option values;
|
|
// https://issues.dlang.org/show_bug.cgi?id=17650
|
|
@safe unittest
|
|
{
|
|
auto args = ["program", "-m", "-5", "-n", "-50", "-c", "-", "-f", "-"];
|
|
|
|
int m;
|
|
int n;
|
|
char c;
|
|
string f;
|
|
|
|
getopt(args,
|
|
"m|mm", "integer", &m,
|
|
"n|nn", "integer", &n,
|
|
"c|cc", "character", &c,
|
|
"f|file", "filename or hyphen for stdin", &f);
|
|
|
|
assert(m == -5);
|
|
assert(n == -50);
|
|
assert(c == '-');
|
|
assert(f == "-");
|
|
}
|
|
|
|
// Hyphen at the option value;
|
|
// https://issues.dlang.org/show_bug.cgi?id=22394
|
|
@safe unittest
|
|
{
|
|
auto args = ["program", "-"];
|
|
|
|
getopt(args);
|
|
|
|
assert(args == ["program", "-"]);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
import std.conv;
|
|
|
|
import std.array;
|
|
import std.string;
|
|
bool a;
|
|
auto args = ["prog", "--foo"];
|
|
auto t = getopt(args, "foo|f", "Help", &a);
|
|
string s;
|
|
auto app = appender!string();
|
|
defaultGetoptFormatter(app, "Some Text", t.options, "\t\t%*s %*s%*s\n%s\n");
|
|
|
|
string helpMsg = app.data;
|
|
//writeln(helpMsg);
|
|
assert(helpMsg.length);
|
|
assert(helpMsg.count("\n") == 5, to!string(helpMsg.count("\n")) ~ " "
|
|
~ helpMsg);
|
|
assert(helpMsg.indexOf("--foo") != -1);
|
|
assert(helpMsg.indexOf("-f") != -1);
|
|
assert(helpMsg.indexOf("-h") != -1);
|
|
assert(helpMsg.indexOf("--help") != -1);
|
|
assert(helpMsg.indexOf("Help") != -1);
|
|
|
|
string wanted = "Some Text\n\t\t-f --foo \nHelp\n\t\t-h --help \nThis help "
|
|
~ "information.\n";
|
|
assert(wanted == helpMsg);
|
|
}
|
|
|
|
|
|
@safe unittest
|
|
{
|
|
import std.conv : ConvException;
|
|
import std.string : indexOf;
|
|
|
|
enum UniqueIdentifer {
|
|
a,
|
|
b
|
|
}
|
|
|
|
UniqueIdentifer a;
|
|
|
|
auto args = ["prog", "--foo", "HELLO"];
|
|
try
|
|
{
|
|
auto t = getopt(args, "foo|f", &a);
|
|
assert(false, "Must not be reached, as \"HELLO\" cannot be converted"
|
|
~ " to enum A.");
|
|
}
|
|
catch (ConvException e)
|
|
{
|
|
string str = () @trusted { return e.toString(); }();
|
|
assert(str.indexOf("HELLO") != -1);
|
|
assert(str.indexOf("UniqueIdentifer") != -1);
|
|
assert(str.indexOf("foo") != -1);
|
|
}
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
import std.conv : ConvException;
|
|
import std.string : indexOf;
|
|
|
|
int a;
|
|
|
|
auto args = ["prog", "--foo", "HELLO"];
|
|
try
|
|
{
|
|
auto t = getopt(args, "foo|f", &a);
|
|
assert(false, "Must not be reached, as \"HELLO\" cannot be converted"
|
|
~ " to an int");
|
|
}
|
|
catch (ConvException e)
|
|
{
|
|
string str = () @trusted { return e.toString(); }();
|
|
assert(str.indexOf("HELLO") != -1);
|
|
assert(str.indexOf("int") != -1);
|
|
assert(str.indexOf("foo") != -1);
|
|
}
|
|
|
|
args = ["prog", "--foo", "1337"];
|
|
getopt(args, "foo|f", &a);
|
|
assert(a == 1337);
|
|
}
|