some tinkering

alot more

comments

some more

more doc

getoptX cleanup

tuple -> struct

getoptX -> getoptEx

Required

getoptEx is now inline

GetoptRslt -> GetoptResult

more info

some comment fixes
This commit is contained in:
Robert burner Schadek 2014-03-28 02:11:41 +01:00 committed by Robert burner Schadek
parent 29aaca1200
commit d602a5347a

View file

@ -33,7 +33,7 @@ Distributed under the Boost Software License, Version 1.0.
module std.getopt; module std.getopt;
private import std.array, std.string, std.conv, std.traits, std.bitmanip, private import std.array, std.string, std.conv, std.traits, std.bitmanip,
std.algorithm, std.ascii, std.exception; std.algorithm, std.ascii, std.exception, std.typetuple, std.typecons;
version (unittest) version (unittest)
{ {
@ -70,13 +70,19 @@ Color color;
void main(string[] args) void main(string[] args)
{ {
getopt( auto helpInformation = getopt(
args, args,
"length", &length, // numeric "length", &length, // numeric
"file", &data, // string "file", &data, // string
"verbose", &verbose, // flag "verbose", &verbose, // flag
"color", &color); // enum "color", "Information about this color", &color); // enum
... ...
if (helpInformation.helpWanted)
{
defaultGetoptPrinter("Some information about the program.",
helpInformation.options);
}
} }
--------- ---------
@ -360,6 +366,22 @@ getopt(args,
In case you want to only enable bundling for some of the parameters, In case you want to only enable bundling for some of the parameters,
bundling can be turned off with $(D std.getopt.config.noBundling). bundling can be turned off with $(D std.getopt.config.noBundling).
$(B Required)
An option can be marked as required. If that option is not present in the
arguments an exceptin will be thrown.
---------
bool foo, bar;
getopt(args,
std.getopt.config.required,
"foo|f", &foo,
"bar|b", &bar);
---------
Only the option direclty following $(D std.getopt.config.required) is
required.
$(B Passing unrecognized options through) $(B Passing unrecognized options through)
If an application needs to do its own processing of whichever arguments If an application needs to do its own processing of whichever arguments
@ -377,6 +399,14 @@ getopt(args,
An unrecognized option such as "--baz" will be found untouched in An unrecognized option such as "--baz" will be found untouched in
$(D args) after $(D getopt) returns. $(D args) after $(D getopt) returns.
$(D Help Information Generation)
If an option string is followed by another string, this string serves as an
description for this option. The function $(D getopt) returns a struct of type
$(D GetoptResult). This return value contains information about all passed options
as well a bool indicating if information about these options where required by
the passed arguments.
$(B Options Terminator) $(B Options Terminator)
A lonesome double-dash terminates $(D getopt) gathering. It is used to A lonesome double-dash terminates $(D getopt) gathering. It is used to
@ -385,11 +415,32 @@ to another program). Invoking the example above with $(D "--foo -- --bar")
parses foo but leaves "--bar" in $(D args). The double-dash itself is parses foo but leaves "--bar" in $(D args). The double-dash itself is
removed from the argument array. removed from the argument array.
*/ */
void getopt(T...)(ref string[] args, T opts) { GetoptResult getopt(T...)(ref string[] args, T opts) {
enforce(args.length, enforce(args.length,
"Invalid arguments string passed: program name missing"); "Invalid arguments string passed: program name missing");
configuration cfg; configuration cfg;
return getoptImpl(args, cfg, opts); GetoptResult rslt;
getoptImpl(args, cfg, rslt, opts);
return rslt;
}
///
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);
}
} }
/** /**
@ -413,10 +464,52 @@ enum config {
noPassThrough, noPassThrough,
/// Stop at first argument that does not look like an option /// Stop at first argument that does not look like an option
stopOnFirstNonOption, stopOnFirstNonOption,
/// Makes the next option a required option
required
} }
private void getoptImpl(T...)(ref string[] args, /** The result of the $(D getoptX) function.
ref configuration cfg, T opts)
The $(D GetOptDRslt) contains two members. The first member is a boolean with
the name $(D helpWanted). The second member is an array of $(D Option). The
array is accessable by the name $(D options).
*/
struct GetoptResult {
bool helpWanted; /// Flag indicating if help was requested
Option[] options; /// All possible options
}
/** The result of the $(D getoptHelp) function.
*/
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.
}
pure Option splitAndGet(string opt) @trusted nothrow
{
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
{
ret.optLong = "--" ~ sp[0];
}
return ret;
}
private void getoptImpl(T...)(ref string[] args, ref configuration cfg,
ref GetoptResult rslt, T opts)
{ {
static if (opts.length) static if (opts.length)
{ {
@ -424,13 +517,29 @@ private void getoptImpl(T...)(ref string[] args,
{ {
// it's a configuration flag, act on it // it's a configuration flag, act on it
setConfig(cfg, opts[0]); setConfig(cfg, opts[0]);
return getoptImpl(args, cfg, opts[1 .. $]); return getoptImpl(args, cfg, rslt, opts[1 .. $]);
} }
else else
{ {
// it's an option string // it's an option string
auto option = to!string(opts[0]); auto option = to!string(opts[0]);
Option optionHelp = splitAndGet(option);
optionHelp.required = cfg.required;
static if (is(typeof(opts[1]) : string))
{
auto receiver = opts[2];
optionHelp.help = opts[1];
immutable lowSliceIdx = 3;
}
else
{
auto receiver = opts[1]; auto receiver = opts[1];
immutable lowSliceIdx = 2;
}
rslt.options ~= optionHelp;
bool incremental; bool incremental;
// Handle options of the form --blah+ // Handle options of the form --blah+
if (option.length && option[$ - 1] == autoIncrementChar) if (option.length && option[$ - 1] == autoIncrementChar)
@ -438,8 +547,17 @@ private void getoptImpl(T...)(ref string[] args,
option = option[0 .. $ - 1]; option = option[0 .. $ - 1];
incremental = true; incremental = true;
} }
handleOption(option, receiver, args, cfg, incremental);
return getoptImpl(args, cfg, opts[2 .. $]); bool optWasHandled = handleOption(option, receiver, args, cfg, incremental);
if (cfg.required && !optWasHandled)
{
throw new GetOptException("Required option " ~ option ~
"was not supplied");
}
cfg.required = false;
return getoptImpl(args, cfg, rslt, opts[lowSliceIdx .. $]);
} }
} }
else else
@ -462,14 +580,28 @@ private void getoptImpl(T...)(ref string[] args,
{ {
throw new GetOptException("Unrecognized option "~a); throw new GetOptException("Unrecognized option "~a);
} }
}
if (a == "--help" || a == "-h")
{
rslt.helpWanted = true;
args = args.remove(i + 1);
} }
} }
void handleOption(R)(string option, R receiver, ref string[] args, Option helpOpt;
helpOpt.optShort = "-h";
helpOpt.optLong = "--help";
helpOpt.help = "This help information.";
rslt.options ~= helpOpt;
}
}
bool handleOption(R)(string option, R receiver, ref string[] args,
ref configuration cfg, bool incremental) ref configuration cfg, bool incremental)
{ {
// Scan arguments looking for a match for this option // Scan arguments looking for a match for this option
bool ret = false;
for (size_t i = 1; i < args.length; ) { for (size_t i = 1; i < args.length; ) {
auto a = args[i]; auto a = args[i];
if (endOfOptions.length && a == endOfOptions) break; if (endOfOptions.length && a == endOfOptions) break;
@ -504,6 +636,9 @@ void handleOption(R)(string option, R receiver, ref string[] args,
++i; ++i;
continue; continue;
} }
ret = true;
// found it // found it
// from here on, commit to eat args[i] // from here on, commit to eat args[i]
// (and potentially args[i + 1] too, but that comes later) // (and potentially args[i + 1] too, but that comes later)
@ -624,6 +759,8 @@ void handleOption(R)(string option, R receiver, ref string[] args,
} }
} }
} }
return ret;
} }
// 5316 - arrays with arraySep // 5316 - arrays with arraySep
@ -720,7 +857,8 @@ private struct configuration
bool, "bundling", 1, bool, "bundling", 1,
bool, "passThrough", 1, bool, "passThrough", 1,
bool, "stopOnFirstNonOption", 1, bool, "stopOnFirstNonOption", 1,
ubyte, "", 4)); bool, "required", 1,
ubyte, "", 3));
} }
private bool optMatch(string arg, string optPattern, ref string value, private bool optMatch(string arg, string optPattern, ref string value,
@ -784,6 +922,7 @@ private void setConfig(ref configuration cfg, config option)
case config.noBundling: cfg.bundling = false; break; case config.noBundling: cfg.bundling = false; break;
case config.passThrough: cfg.passThrough = true; break; case config.passThrough: cfg.passThrough = true; break;
case config.noPassThrough: cfg.passThrough = false; break; case config.noPassThrough: cfg.passThrough = false; break;
case config.required: cfg.required = true; break;
case config.stopOnFirstNonOption: case config.stopOnFirstNonOption:
cfg.stopOnFirstNonOption = true; break; cfg.stopOnFirstNonOption = true; break;
default: assert(false); default: assert(false);
@ -1060,3 +1199,177 @@ unittest // same bug as 7693 only for bool
getopt(args, "foo", &foo); getopt(args, "foo", &foo);
assert(foo); assert(foo);
} }
unittest
{
bool foo;
auto args = ["prog", "--foo"];
getopt(args, "foo", &foo);
assert(foo);
}
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);
}
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);
}
unittest
{
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));
}
unittest
{
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);
}
unittest
{
bool foo;
auto args = ["prog", "-f"];
auto r = getopt(args, config.caseInsensitive, "help|f", "Some foo", &foo);
assert(foo);
assert(!r.helpWanted);
}
/** This function prints the passed $(D Option) and text in an aligned manner.
The passed text will be printed first, followed by a newline. Than 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 $(D Option) passed. If a help
message is present it will be printed after the long version of the
$(D Option).
------------
foreach(it; opt) {
writefln("%*s %*s %s", lengthOfLongestShortOption, it.optShort,
lengthOfLongestLongOption, it.optLong, it.help);
}
------------
Params:
text = The text to printed at the beginning of the help output.
opt = The $(D Option) extracted from the $(D getoptX) parameter.
*/
void defaultGetoptPrinter(string text, Option[] opt)
{
import std.stdio : stdout;
defaultGetoptFormatter(stdout.lockingTextWriter(), text, opt);
}
/** This function writes the passed text and $(D Option) into an output range
in the manner, described in the documentation of function
$(D defaultGetoptXPrinter).
Params:
output = The output range used to write the help information.
text = The text to printed at the beginning of the help output.
opt = The $(D Option) extracted from the $(D getoptX) parameter.
*/
void defaultGetoptFormatter(Output)(Output output, string text, Option[] opt) {
import std.format : 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;
}
size_t argLength = ls + ll + 2;
string re = " Required: ";
foreach (it; opt)
{
output.formattedWrite("%*s %*s%*s%s\n", ls, it.optShort, ll, it.optLong,
hasRequired ? re.length : 1, it.required ? re : " ", it.help);
}
}
unittest
{
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);
}
unittest
{
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);
}