mirror of
https://github.com/dlang/phobos.git
synced 2025-05-03 00:20:26 +03:00

std.contracts: added functions pointsTo() std.numeric: minor unittest fixes. std.bitmanip: fixed code bloat issue, reintroduced FloatRep and DoubleRep. std.conv: minor simplification of implementation. std.regexp: added reference to ECMA standard in the documentation. std.getopt: changed return type from bool to void, error is signaled by use of exceptions. std.functional: added unaryFun, binaryFun, adjoin. std.string: updated documentation, changed code to compile with warnings enabled. std.traits: changed FieldTypeTuple; added RepresentationTypeTuple, hasAliasing; fixed bug 1826; added call to flush() from within write; fixed unlisted bug in lines(). std.algorithm: added map, reduce, filter, inPlace, move, swap, overwriteAdjacent, find, findRange, findBoyerMoore, findAdjacent, findAmong, findAmongSorted, canFind, canFindAmong, canFindAmongSorted, count, equal, overlap, min, max, mismatch, EditOp, none, substitute, insert, remove, levenshteinDistance, levenshteinDistanceAndPath, copy, copyIf, iterSwap, swapRanges, reverse, rotate, SwapStrategy, Unstable, Semistable, Stable, eliminate, partition, nthElement, sort, schwartzSort, partialSort, isSorted, makeIndex, schwartzMakeIndex, lowerBound, upperBound, equalRange, canFindSorted. std.thread: fixed so it compiles with warnings enabled. std.file: made getSize() faster under Linux. std.random: fixed so it compiles with warnings enabled; improved function uniform so it deduces type generated from its arguments. std.format: added fixes to make formatting work with const data. std.path: minor documentation changes.
545 lines
18 KiB
D
545 lines
18 KiB
D
|
|
// Written in the D programming language.
|
|
|
|
/**
|
|
* Processing of command line options.
|
|
*
|
|
* The getopt module implements a $(D_PARAM 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.
|
|
*
|
|
Author:
|
|
|
|
$(WEB erdani.org, Andrei Alexandrescu)
|
|
*
|
|
* Credits:
|
|
*
|
|
* This module and its documentation are inspired by Perl's
|
|
* $(LINK2 http://perldoc.perl.org/Getopt/Long.html,Getopt::Long) module. The
|
|
* syntax of D's $(D_PARAM getopt) is simplified because $(D_PARAM
|
|
* getopt) infers the expected parameter types from the static types
|
|
* of the passed-in pointers.
|
|
*
|
|
* Macros:
|
|
* WIKI = Phobos/StdGetopt
|
|
*/
|
|
|
|
/* Author:
|
|
* Andrei Alexandrescu, www.erdani.org
|
|
*
|
|
* This software is provided 'as-is', without any express or implied
|
|
* warranty. In no event will the authors be held liable for any damages
|
|
* arising from the use of this software.
|
|
*
|
|
* Permission is granted to anyone to use this software for any purpose,
|
|
* including commercial applications, and to alter it and redistribute it
|
|
* freely, subject to the following restrictions:
|
|
*
|
|
* o The origin of this software must not be misrepresented; you must not
|
|
* claim that you wrote the original software. If you use this software
|
|
* in a product, an acknowledgment in the product documentation would be
|
|
* appreciated but is not required.
|
|
* o Altered source versions must be plainly marked as such, and must not
|
|
* be misrepresented as being the original software.
|
|
* o This notice may not be removed or altered from any source
|
|
* distribution.
|
|
*/
|
|
|
|
module std.getopt;
|
|
import std.string, std.conv, std.traits;
|
|
|
|
import std.stdio; // for testing only
|
|
|
|
/**
|
|
Synopsis:
|
|
|
|
---------
|
|
import std.getopt;
|
|
|
|
string data = "file.dat";
|
|
int length = 24;
|
|
bool verbose;
|
|
|
|
void main(string[] args)
|
|
{
|
|
getopt(
|
|
args,
|
|
"length", &length, // numeric
|
|
"file", &data, // string
|
|
"verbose", &verbose); // flag
|
|
...
|
|
}
|
|
---------
|
|
|
|
The $(D_PARAM getopt) function takes a reference to the command line
|
|
(as received by $(D_PARAM 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 pointed-to by the pointer to its
|
|
right (the "bound" pointer). The option string in the call to
|
|
$(D_PARAM getopt) should not start with a dash.
|
|
|
|
In all cases, the command-line options that were parsed and used by
|
|
$(D_PARAM getopt) are removed from $(D_PARAM args). Whatever in the
|
|
arguments did not look like an option is left in $(D_PARAM 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 $(D_PARAM 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 $(D_PARAM ConvError) exception is thrown.
|
|
|
|
Depending on the type of the pointer being bound, $(D_PARAM getopt)
|
|
recognizes the following kinds of options:
|
|
|
|
$(OL $(LI $(I Boolean options). These are the simplest options; all
|
|
they do is set a Boolean to $(D_PARAM true):
|
|
|
|
---------
|
|
bool verbose, debugging;
|
|
getopt(args, "verbose", &verbose, "debug", &debugging);
|
|
---------
|
|
|
|
$(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);
|
|
---------
|
|
|
|
Invoking the program with "--timeout=5" or "--timeout 5" will set
|
|
$(D_PARAM timeout) to 5.)
|
|
|
|
$(UL $(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_PARAM 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 $(D_PARAM paranoid) to 42;
|
|
instead, $(D_PARAM paranoid) is set to 2 and "42" is not considered
|
|
as part of the program options.))
|
|
|
|
$(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 $(D_PARAM 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 $(D_PARAM
|
|
outputFiles) to [ "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 $(D_PARAM tuningParms) to [ "alpha" : 0.5, "beta" : 0.6 ].)
|
|
In general, keys and values can be of any parsable types.
|
|
|
|
$(LI $(I Delegate options.) An option can be bound to a delegate with
|
|
the signature $(D_PARAM void delegate(string option)) or $(D_PARAM
|
|
void delegate(string option, string value)).
|
|
|
|
$(UL $(LI In the $(D_PARAM void delegate(string option)) case, the
|
|
option string (without the leading dash(es)) is passed to the
|
|
delegate. 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 In the $(D_PARAM void delegate(string option, string value))
|
|
case, the option string is handled as an option with one argument,
|
|
and parsed accordingly. The option and its value are passed to the
|
|
delegate. After that, whatever was passed to the delegate is
|
|
considered handled and removed from the list.)
|
|
|
|
---------
|
|
void main(string[] args)
|
|
{
|
|
uint verbosityLevel = 1;
|
|
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 :
|
|
writeln(stderr, "Dunno how verbose you want me to be by saying ",
|
|
value);
|
|
exit(1);
|
|
}
|
|
}
|
|
getopt(args, "verbosity", &myHandler);
|
|
}
|
|
---------
|
|
))))
|
|
|
|
$(B 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);
|
|
---------
|
|
|
|
$(B Case)
|
|
|
|
By default options are case-insensitive. You can change that behavior
|
|
by passing $(D_PARAM getopt) the $(D_PARAM caseSensitive) directive
|
|
like this:
|
|
|
|
---------
|
|
bool foo, bar;
|
|
getopt(args,
|
|
std.getopt.config.caseSensitive,
|
|
"foo", &foo,
|
|
"bar", &bar);
|
|
---------
|
|
|
|
In the example above, "--foo", "--bar", "--FOo", "--bAr" etc. are recognized. The directive is active til the end of $(D_PARAM getopt), or until the converse directive $(D_PARAM 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_PARAM
|
|
std.getopt.config.caseSensitive), but not "--Bar", "--bAr"
|
|
etc. because the directive $(D_PARAM
|
|
std.getopt.config.caseInsensitive) turned sensitivity off before
|
|
option "bar" was parsed.
|
|
|
|
$(B Bundling)
|
|
|
|
Single-letter options can be bundled together, i.e. "-abc" is the same as "-a -b -c". By default, this confusing option is turned off. You can turn it on with the $(D_PARAM 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 $(D_PARAM std.getopt.config.noBundling).
|
|
|
|
$(B Passing unrecognized options through)
|
|
|
|
If an application needs to do its own processing of whichever arguments $(D_PARAM getopt) did not understand, it can pass the $(D_PARAM std.getopt.config.passThrough) directive to $(D_PARAM 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 $(D_PARAM args) after $(D_PARAM getopt) returns.
|
|
|
|
$(B Options Terminator)
|
|
|
|
A lonesome double-dash terminates $(D_PARAM 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 "--foo -- --bar" parses foo but leaves "--bar" in $(D_PARAM args). The double-dash itself is removed from the argument array.
|
|
*/
|
|
|
|
void getopt(T...)(ref string[] args, T opts) {
|
|
configuration cfg;
|
|
return getoptImpl(args, cfg, opts);
|
|
}
|
|
|
|
/**
|
|
* Configuration options for $(D_PARAM getopt). You can pass them to
|
|
* $(D_PARAM getopt) in any position, except in between an option
|
|
* string and its bound pointer.
|
|
*/
|
|
|
|
enum config {
|
|
/// Turns case sensitivity on
|
|
caseSensitive,
|
|
/// Turns case sensitivity off
|
|
caseInsensitive,
|
|
/// Turns bundling on
|
|
bundling,
|
|
/// Turns bundling off
|
|
noBundling,
|
|
/// Pass unrecognized arguments through
|
|
passThrough,
|
|
/// Signal unrecognized arguments as errors
|
|
noPassThrough,
|
|
};
|
|
|
|
private void getoptImpl(T...)(ref string[] args,
|
|
ref configuration cfg, T opts)
|
|
{
|
|
static if (opts.length) {
|
|
static if (is(typeof(opts[0]) : config))
|
|
{
|
|
switch (opts[0])
|
|
{
|
|
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;
|
|
default: assert(false); break;
|
|
}
|
|
return getoptImpl(args, cfg, opts[1 .. $]);
|
|
}
|
|
else
|
|
{
|
|
string option = to!(string)(opts[0]);
|
|
auto receiver = opts[1];
|
|
bool incremental;
|
|
if (option.length && option[$ - 1] == '+')
|
|
{
|
|
option = option[0 .. $ - 1];
|
|
incremental = true;
|
|
}
|
|
for (size_t i = 1; i != args.length; ) {
|
|
auto a = args[i];
|
|
if (a == endOfOptions) break; // end of options
|
|
string val;
|
|
if (!optMatch(a, option, val, cfg))
|
|
{
|
|
++i;
|
|
continue;
|
|
}
|
|
// found it
|
|
|
|
static if (is(typeof(receiver) : bool*)) {
|
|
*receiver = true;
|
|
args = args[0 .. i] ~ args[i + 1 .. $];
|
|
break;
|
|
} else {
|
|
static const isDelegateWithOneParameter =
|
|
is(typeof(receiver("")) : void);
|
|
// non-boolean option, which might include an argument
|
|
if (val || incremental || isDelegateWithOneParameter) {
|
|
args = args[0 .. i] ~ args[i + 1 .. $];
|
|
} else {
|
|
val = args[i + 1];
|
|
args = args[0 .. i] ~ args[i + 2 .. $];
|
|
}
|
|
static if (is(typeof(*receiver) : real)) {
|
|
if (incremental) ++*receiver;
|
|
else *receiver = to!(typeof(*receiver))(val);
|
|
} else static if (is(typeof(receiver) : string*)) {
|
|
*receiver = to!(string)(val);
|
|
} else static if (is(typeof(receiver) == delegate)) {
|
|
static if (is(typeof(receiver("", "")) : void))
|
|
{
|
|
// option with argument
|
|
receiver(option, val);
|
|
}
|
|
else
|
|
{
|
|
static assert(is(typeof(receiver("")) : void));
|
|
// boolean-style receiver
|
|
receiver(option);
|
|
}
|
|
} else static if (isArray!(typeof(*receiver))) {
|
|
*receiver ~= [ to!(typeof(*receiver[0]))(val) ];
|
|
} else static if (isAssociativeArray!(typeof(*receiver))) {
|
|
alias typeof(receiver.keys[0]) K;
|
|
alias typeof(receiver.values[0]) V;
|
|
auto j = find(val, '=');
|
|
auto key = val[0 .. j], value = val[j + 1 .. $];
|
|
(*receiver)[to!(K)(key)] = to!(V)(value);
|
|
} else {
|
|
static assert(false, "Dunno how to deal with type " ~
|
|
typeof(receiver).stringof);
|
|
}
|
|
}
|
|
}
|
|
return getoptImpl(args, cfg, opts[2 .. $]);
|
|
}
|
|
} else {
|
|
foreach (a ; args[1 .. $]) {
|
|
if (!a.length || a[0] != '-') continue; // not an option
|
|
if (a == "--") break; // end of options
|
|
if (!cfg.passThrough)
|
|
{
|
|
throw new Exception("Unrecognized option "~a);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const
|
|
optChar = '-',
|
|
assignChar = '=',
|
|
endOfOptions = "--";
|
|
|
|
private struct configuration
|
|
{
|
|
bool caseSensitive = false;
|
|
bool bundling = false;
|
|
bool passThrough = false;
|
|
}
|
|
|
|
private bool optMatch(string arg, string optPattern, ref string value,
|
|
configuration cfg)
|
|
{
|
|
if (!arg.length || arg[0] != optChar) return false;
|
|
arg = arg[1 .. $];
|
|
const isLong = arg.length > 1 && arg[0] == optChar;
|
|
if (isLong) arg = arg[1 .. $];
|
|
const eqPos = find(arg, assignChar);
|
|
if (eqPos >= 0) {
|
|
value = arg[eqPos + 1 .. $];
|
|
arg = arg[0 .. eqPos];
|
|
} else {
|
|
value = null;
|
|
}
|
|
//writeln("Arg: ", arg, " pattern: ", optPattern, " value: ", value);
|
|
// Split the option
|
|
const variants = split(optPattern, "|");
|
|
foreach (v ; variants) {
|
|
if (arg == v || !cfg.caseSensitive && toupper(arg) == toupper(v))
|
|
return true;
|
|
if (cfg.bundling && !isLong && v.length == 1 && find(arg, v) >= 0)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
unittest
|
|
{
|
|
uint paranoid = 2;
|
|
string[] args = (["program.name",
|
|
"--paranoid", "--paranoid", "--paranoid"]).dup;
|
|
getopt(args, "paranoid+", ¶noid);
|
|
assert(paranoid == 5, to!(string)(paranoid));
|
|
|
|
string data = "file.dat";
|
|
int length = 24;
|
|
bool verbose = true;
|
|
args = (["program.name", "--length=5",
|
|
"--file", "dat.file", "--verbose"]).dup;
|
|
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"]).dup;
|
|
getopt(args, "output", &outputFiles);
|
|
assert(outputFiles.length == 2
|
|
&& outputFiles[0] == "myfile.txt" && outputFiles[0] == "myfile.txt");
|
|
|
|
args = (["program.name", "--tune=alpha=0.5",
|
|
"--tune", "beta=0.6"]).dup;
|
|
double[string] tuningParms;
|
|
getopt(args, "tune", &tuningParms);
|
|
assert(args.length == 1);
|
|
assert(tuningParms.length == 2);
|
|
assert(tuningParms["alpha"] == 0.5);
|
|
assert(tuningParms["beta"] == 0.6);
|
|
|
|
uint verbosityLevel = 1;
|
|
void myHandler(string option)
|
|
{
|
|
if (option == "quiet")
|
|
{
|
|
verbosityLevel = 0;
|
|
}
|
|
else
|
|
{
|
|
assert(option == "verbose");
|
|
verbosityLevel = 2;
|
|
}
|
|
}
|
|
args = (["program.name", "--quiet"]).dup;
|
|
getopt(args, "verbose", &myHandler, "quiet", &myHandler);
|
|
assert(verbosityLevel == 0);
|
|
args = (["program.name", "--verbose"]).dup;
|
|
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"]).dup;
|
|
getopt(args, "verbose", &myHandler2);
|
|
assert(verbosityLevel == 2);
|
|
|
|
bool foo, bar;
|
|
args = (["program.name", "--foo", "--bAr"]).dup;
|
|
getopt(args,
|
|
std.getopt.config.caseSensitive,
|
|
std.getopt.config.passThrough,
|
|
"foo", &foo,
|
|
"bar", &bar);
|
|
assert(args[1] == "--bAr");
|
|
}
|