Mercurial > projects > ldc
diff tango/tango/util/Arguments.d @ 132:1700239cab2e trunk
[svn r136] MAJOR UNSTABLE UPDATE!!!
Initial commit after moving to Tango instead of Phobos.
Lots of bugfixes...
This build is not suitable for most things.
author | lindquist |
---|---|
date | Fri, 11 Jan 2008 17:57:40 +0100 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tango/tango/util/Arguments.d Fri Jan 11 17:57:40 2008 +0100 @@ -0,0 +1,834 @@ +/******************************************************************************* + copyright: Copyright (c) 2007 Darryl Bleau. All rights reserved. + + license: BSD style: $(LICENSE) + + version: Oct2007 + author: Darryl B, Jeff D + + History: + --- + Date Who What + Sep2006 Darryl Bleau Original C module + Sep2007 Jeff Davey Ported to D, added comments. + Oct2007 Darryl Bleau Added validation delegates/functions, more comments. + --- +*******************************************************************************/ + +/******************************************************************************* + + This module is used to parse arguments, and give easy access to them. + +*******************************************************************************/ + +module tango.util.Arguments; + +private import tango.core.Exception : TracedException; + +/*********************************************************************** + + This exception is thrown during argument validation. + +***********************************************************************/ + +public class ArgumentException : TracedException +{ + /*********************************************************************** + + The reason the exception was thrown + + ***********************************************************************/ + + enum ExceptionReason + { + /*********************************************************************** + An invalid parameter was passed + ***********************************************************************/ + + INVALID_PARAMETER, + /*********************************************************************** + A parameter wasn't passed to an argument when it was expected + ***********************************************************************/ + + MISSING_PARAMETER, + /*********************************************************************** + An argument was missing + ***********************************************************************/ + + MISSING_ARGUMENT + } + + private char[] _name; + private char[] _parameter; + private ExceptionReason _reason; + + /*********************************************************************** + + The name of the specific argument + + ***********************************************************************/ + + char[] name() { return _name; } + + /*********************************************************************** + + The parameter to the argument + + ***********************************************************************/ + + char[] parameter() { return _parameter; } + + /*********************************************************************** + + The enum to the reason it failed + + ***********************************************************************/ + + ExceptionReason reason() { return _reason; } + + this(char[] msg, char[] name, char[] parameter, ExceptionReason reason) + { + _name = name; + _parameter = parameter; + _reason = reason; + super(msg); + } +} + +/*********************************************************************** + + The Arguments class is used to parse arguments and encapsulate the + parameters passed by an application user. + + The command line arguments into an array of found arguments and their + respective parameters (if any). + + Arguments can be short (-), long (--), or implicit. Arguments can + optionally be passed parameters. For example, this module + parses command lines such as: "-a -b -c --long --argument=parameter" + + Example: + --- + char[][] arguments = [ "programname", "-a:on", "-abc:on", "--this=good", "-x:on" ]; + Arguments args = new Arguments(arguments); + if (args) + { + args.addValidation("b", true, false); + args.addValidation("c", true, true); + args.addValidation("x", false, true); + args.addValidation("this", false, true); + args.addValidation("this", (char[] a) { (return a.length < 5); }); + try + { + args.validate(); + return Test.Status.Success; + } + catch (ArgumentException ex) + { + messages ~= Stdout.layout.convert("{}: {} - {}", ex.name, ex.msg, ex.reason == ArgumentException.ExceptionReason.INVALID_PARAMETER ? "invalid parameter" : ex.reason == ArgumentException.ExceptionReason.MISSING_PARAMETER ? "missing parameter" : "missing argument"); + } + } + --- + + Syntax: + --- + Short Argument - -[x][:=]?[ parameter]... + Long Argument - --[long][:=]?[ parameter]... + Implicit Arguments - [implicitName]... Multiple implicit arguments are allowed. + --- + + Usage: + + Short options can be grouped in a single dash. The following are equivalent. + --- + - "myprogram -a -b -c" + - "myprogram -abc" + --- + + Arguments can be passed with space, '=', or ':'. The following are equivalent. + --- + - "myprogram -c arg" + - "myprogram -c=arg" + - "myprogram -c:arg" + --- + + As are these. + --- + - "myprogram --long arg" + - "myprogram --long=arg" + - "myprogram --long:arg" + --- + + Arguments can contain either '=' or ':', but not both. For example. + the following results in the argument 'long' being set to 'arg=other' + and 'arg:other', respectively. + --- + - "myprogram --long:arg=other" + - "myprogram --long=arg:other" + --- + + Blank dashes are ignored. The following are all equivalent. + --- + - "myprogram -c -- -a" + - "myprogram -c - -a" + - "myprogram - - - -a -- -c" + --- + + In the absence of implicit arguments, short options can be infered when + they come first. Given no implicit arguments, the following are equivalent. + --- + - "myprogram abc" + - "myprogram -abc" + - "myprogram -a -b -c" + --- + + Short options are case sensitive, while long options are not. The following + are equivalent. + --- + - "myprogram -a -A -LONG" + - "myprogram -a -A -Long" + - "myprogram -a -A -long" + --- + + In the event of multiple definitions of an argument, any parameters given + will be concatenated. The following are equivalent. + --- + - "myprogram -a one two three" + - "myprogram -a one -a two -a three" + - "myprogram -a:one two -a=three" + --- + + Multiple parameters can be iterated through using via the opIndex operator. + For example, given: + --- + - "myprogram -collect one two three '4 5 6'" + --- + args["collect"] will return a char[][] array ["one", "two", "three", "4 5 6"]. + + Implicit arguments can be defined by passing in an implicit arguments array, + which may look something like: ["first", "second"]. + Given implicit arguments, any non-argument command line parameters will be + automatically assigned to the implicit arguments, + in the order they were given. + For example, given the implicit arguments ["first", "second"] and command line: + --- + - "myprogram hello there bob" + --- + The argument 'first' will be assigned 'hello', and the argument 'second' will + be assigned both 'there' and 'bob'. + + Any intervening arguments will end the assignment. For example, given: + --- + - "myprogram hello there bob -a how are you" + --- + 'first' is assigned 'hello', 'second' is assigned 'there' and 'bob', and 'a' + is assigned 'how', 'are', and 'you'. + + Implicit arguments also allows programs to support non-option arguments, + given implicit arguments ["actions"], and a command line such as: + --- + - "myprogram mop sweep get_coffee -time:now" + --- + args["actions"] will contain ["mop", "sweep", "get_coffee"], and + args["time"] will contain "now". + +***********************************************************************/ + +public class Arguments +{ + /// Function used to validate multiple parameters at once. + alias bool function(char[][] params, inout char[] invalidParam) validationFunctionMulti; + /// Delegate used to validate multiple parameters at once. + alias bool delegate(char[][] params, inout char[] invalidParam) validationDelegateMulti; + /// Function used to validate single parameters at a time. + alias bool function(char[] param) validationFunction; + /// Delegate used to validate single parameters at a time. + alias bool delegate(char[] param) validationDelegate; + + private char[][][char[]] _args; + private char[] _program; + private struct validation + { + validationFunction[] validF; + validationDelegate[] validD; + validationFunctionMulti[] validFM; + validationDelegateMulti[] validDM; + bool required; + bool paramRequired; + } + private validation[char[]] _validations; + + private char[] parseLongArgument(char[] arg) + { + char[] rtn; + + int locate(char[] arg, char c) { + foreach (i, a; arg) + if (a is c) + return i; + return arg.length; + } + + + if (arg) + { + int equalDelim = locate(arg, '='); + int colonDelim = locate(arg, ':'); + int paramPos = ((equalDelim != arg.length) && (colonDelim != arg.length)) ? (equalDelim < colonDelim ? equalDelim : colonDelim) : (equalDelim != arg.length) ? equalDelim : colonDelim; + if (paramPos != arg.length) + { + char[] argName = arg[0 .. paramPos]; + char[] value = arg[(paramPos + 1) .. arg.length]; + setArg(argName, value); + rtn = argName; + } + else + { + setArg(arg, null); + rtn = arg; + } + } + return rtn; + } + + private void setArg(char[] arg, char[] value) + { + if (arg) + { + if ((arg in _args) || (value !is null)) + _args[arg] ~= value; + else + _args[arg] = null; + } + } + + private char[] parseShortArgument(char[] arg) + { + char[] rtn; + + if (arg) + { + char[] argName; + uint i; + for (i = 0; i < arg.length; i++) + { + if (arg[i] != '=' && arg[i] != ':') + { + argName = arg[i .. i + 1]; + setArg(argName, null); + } + else if (((arg.length) - i) > 1) + { + setArg(argName, arg[i+1 .. arg.length]); + break; + } + } + rtn = argName; + } + return rtn; + } + + /*********************************************************************** + + Allows external argument assignment, works the same as command line + in that it appends to any values already assigned to the given key. + + Params: + value = assigned value + key = key to assign to + + ***********************************************************************/ + + void opIndexAssign(char[] value, char[] key) + { + setArg(key, value); + } + + /*********************************************************************** + + Allows removal of keys from the arguments. Useful if you want to replace + values for a given key rather than to append to them. + + Params: + key = key to remove values from. + + ***********************************************************************/ + + void remove(char[] key) + { + _args[key] = null; + } + + /*********************************************************************** + + Directly access an argument's parameters via opIndex operator as an + array. + This is to cover something like: param1 "parm with space" param2 + + ***********************************************************************/ + + char[][] opIndex(char[] key) + { + char[][] rtn = null; + if (key && (key in _args)) + rtn = _args[key]; + return rtn; + } + + /*********************************************************************** + + Operator is used to check if the argument exists + + ***********************************************************************/ + + bool opIn_r(char[] key) + { + bool rtn = false; + if (key) + rtn = (key in _args) != null; + return rtn; + } + + /*********************************************************************** + + Adds a validation to the arguments + + Params: + argument = the argument name + required = specifies if this argument is required + paramRequired = specifies if this argument requires a parameter + + ***********************************************************************/ + + void addValidation(char[] argument, bool required, bool paramRequired) + { + if (argument) + { + validation* val = getValidation(argument); + if (val !is null) + { + val.required = required; + val.paramRequired = paramRequired; + } + } + } + + /*********************************************************************** + + Adds a validation to the arguments + + Params: + argument = the argument name + validF = a validation function for single parameters + + ***********************************************************************/ + + void addValidation(char[] argument, validationFunction validF) + { + if (argument && validF) + { + validation* val = getValidation(argument); + if (val !is null) + val.validF ~= validF; + } + } + + /*********************************************************************** + + Adds a validation to the arguments + + Params: + argument = the argument name + validD = a validation delegate for single parameters + + ***********************************************************************/ + + void addValidation(char[] argument, validationDelegate validD) + { + if (argument && validD) + { + validation* val = getValidation(argument); + if (val !is null) + val.validD ~= validD; + } + } + + /*********************************************************************** + + Adds a validation to the arguments + + Params: + argument = the argument name + validF = a validation function for multiple parameters + + ***********************************************************************/ + + void addValidation(char[] argument, validationFunctionMulti validFM) + { + if (argument && validFM) + { + validation* val = getValidation(argument); + if (val !is null) + val.validFM ~= validFM; + } + } + + /*********************************************************************** + + Adds a validation to the arguments + + Params: + argument = the argument name + validD = a validation delegate for multiple parameters + + ***********************************************************************/ + + void addValidation(char[] argument, validationDelegateMulti validDM) + { + if (argument && validDM) + { + validation* val = getValidation(argument); + if (val !is null) + val.validDM ~= validDM; + } + } + + private validation* getValidation(char[] argument) + { + validation* rtn = null; + if (!(argument in _validations)) + { + validation newValidation; + _validations[argument] = newValidation; + } + if (argument in _validations) + rtn = &(_validations[argument]); + return rtn; + } + + /*********************************************************************** + + Validates the parsed arguments. + + Throws ArgumentException if it finds something wrong. + + ***********************************************************************/ + + void validate() + { + foreach(char[] argument, validation val; _validations) + { + if (val.required && !(argument in _args)) + throw new ArgumentException("Argument required.", argument, null, ArgumentException.ExceptionReason.MISSING_ARGUMENT); + if (val.paramRequired && (argument in _args) && (_args[argument].length == 0)) + throw new ArgumentException("Parameter required.", argument, null, ArgumentException.ExceptionReason.MISSING_PARAMETER); + if ((argument in _args) && (_args[argument].length > 0)) + { + char[] invalidParameter = null; + foreach(validationFunctionMulti validFM; val.validFM) + if (!validFM(_args[argument], invalidParameter)) + break; + if (invalidParameter is null) + { + foreach(validationDelegateMulti validDM; val.validDM) + if (!validDM(_args[argument], invalidParameter)) + break; + if (invalidParameter is null) + { + foreach(char[] arg; _args[argument]) + { + foreach(validationFunction validF; val.validF) + { + if (!validF(arg)) + { + invalidParameter = arg; + break; + } + } + if (invalidParameter is null) + { + foreach(validationDelegate validD; val.validD) + { + if (!validD(arg)) + { + invalidParameter = arg; + break; + } + } + } + } + } + } + if (invalidParameter !is null) + throw new ArgumentException("Invalid parameter.", argument, invalidParameter, ArgumentException.ExceptionReason.INVALID_PARAMETER); + } + } + } + + /*********************************************************************** + + Parse the arguments according to the passed implicitArg list + and aliases. + + ***********************************************************************/ + + + void parse(char[][] arguments, char[][] implicitArgs, char[][][] aliases) + { + char[] lastArgumentSet; + uint currentImplicitArg = 0; + for (uint i = 1; i < arguments.length; i++) + { + char[] currentArgument = arguments[i]; + if (currentArgument) + { + if (currentArgument[0] == '-') + { + if (currentArgument.length > 1) + { + if (currentArgument[1] == '-') + { + if (currentArgument.length > 2) + lastArgumentSet = parseLongArgument(currentArgument[2 .. currentArgument.length]); // long argument + } + else + lastArgumentSet = parseShortArgument(currentArgument[1 .. currentArgument.length]); // short argument + } + } + else + { + char[] argName; + // implicit argument / previously set argument + if (implicitArgs && (currentImplicitArg < implicitArgs.length)) + lastArgumentSet = argName = implicitArgs[currentImplicitArg++]; + else + argName = lastArgumentSet; + + if (argName) + setArg(argName, currentArgument); + else + lastArgumentSet = parseShortArgument(currentArgument); + } + } + } + + if (aliases) + { + for (uint i = 0; i < aliases.length; i++) + { + bool foundOne = false; + char[][] currentValues; + for (uint j = 0; j < aliases[i].length; j++) + { + if (aliases[i][j] in _args) + { + foundOne = true; + currentValues ~= _args[aliases[i][j]]; + } + } + + if (foundOne) + { + for (uint j = 0; j < aliases[i].length; j++) + _args[aliases[i][j]] = currentValues; + } + } + } + } + + /*********************************************************************** + + Constructor that supports all features + + Params: + arguments = the list of arguments (usually from main) + implicitArgs = assigns values using these keys in order from the arguments array. + aliases = aliases specific arguments to each other to concat parameters. looks like aliases[0] = [ "alias1", "alias2", "alias3" ]; (which groups all these arguments together) + + ***********************************************************************/ + + this(char[][] arguments, char[][] implicitArgs, char[][][] aliases) + { + _program = arguments[0]; + this.parse(arguments, implicitArgs, aliases); + } + + /*********************************************************************** + + Basic constructor which only deals with arguments + + Params: + arguments = array usually from main() + + ***********************************************************************/ + + this(char[][] arguments) + { + this(arguments, null, null); + } + + + /*********************************************************************** + + This constructor allows implicitArgs to be set as well + + Params: + arguments = array usually from main() + implicitArgs = the implicit arguments + + ***********************************************************************/ + + this(char[][] arguments, char[][] implicitArgs) + { + this(arguments, implicitArgs, null); + } + + /*********************************************************************** + + This constructor allows aliases + + Params: + arguments = array usually from main + aliases = the array of arguments to alias + + ***********************************************************************/ + + this(char[][] arguments, char[][][] aliases) + { + this(arguments, null, aliases); + } +} + +/+ + +TODO: Either rewrite this test to not use the Test class, or resolve ticket +749 to include Test in Tango. + +version(UnitTest) +{ + import tango.util.Test; + import tango.text.Util; + import tango.io.Stdout; + + unittest + { + Test.Status parseTest(inout char[][] messages) + { + char[][] arguments = [ "ignoreprogramname", "--accumulate", "one", "-x", "on", "--accumulate:two", "-y", "off", "--accumulate=three", "-abc" ]; + Arguments args = new Arguments(arguments); + if (args) + { + if (!("ignoreprogramname" in args)) + { + if (join(args["accumulate"], " ") == "one two three") + { + if (args["x"][0] == "on") + { + if (("a" in args) && ("b" in args) && ("c" in args)) + return Test.Status.Success; + } + } + } + } + return Test.Status.Failure; + } + + Test.Status implicitParseTest(inout char[][] messages) + { + char[][] arguments = ["ignoreprogramname", "-r", "zero", "one two three", "four five", "-s", "six"]; + char[][] implicitArgs = [ "first", "second" ]; + Arguments args = new Arguments(arguments, implicitArgs); + if (args) + { + if (!("ignoreprogramname" in args)) + { + if (("r" in args) && !args["r"]) + { + if (args["first"][0] == "zero") + { + if (join(args["second"], " ") == "one two three four five") + { + if (args["second"] == ["one two three", "four five"]) + { + if (args["s"][0] == "six") + return Test.Status.Success; + } + } + } + } + } + } + return Test.Status.Failure; + } + + Test.Status aliasParseTest(inout char[][] messages) + { + char[][] arguments = [ "ignoreprogramname", "abc", "-d", "-e=eee", "--eff=f" ]; + char[][][4] aliases; + aliases[0] = [ "lettera", "a" ]; + aliases[1] = [ "letterbc", "c", "b" ]; + aliases[2] = [ "letterd", "d" ]; + aliases[3] = [ "lettere", "eff", "e" ]; + Arguments args = new Arguments(arguments, aliases); + if (args) + { + if (!("ignoreprogramname" in args) && ("letterbc" in args) && ("a" in args) && ("b" in args) && + ("c" in args) && ("d" in args) && ("eff" in args)) + { + if (("lettera" in args) && ("letterbc" in args) && ("letterd" in args)) + { + if (join(args["eff"], " ") == "f eee") + { + if (args["eff"] == ["f", "eee"]) + return Test.Status.Success; + } + } + } + } + return Test.Status.Failure; + } + + bool testRan = false; + bool testValidation(char[] arg) + { + bool rtn = true; + if (arg.length > 5) + rtn = false; + testRan = true; + return rtn; + } + + Test.Status validationTest(inout char[][] messages) + { + char[][] arguments = [ "programname", "-a:on", "-abc:on", "--this=good", "-x:on" ]; + Arguments args = new Arguments(arguments); + if (args) + { + args.addValidation("b", true, false); + args.addValidation("c", true, true); + args.addValidation("x", false, true); + args.addValidation("this", false, true); + args.addValidation("this", &testValidation); + try + { + args.validate(); + if (testRan) + return Test.Status.Success; + } + catch (ArgumentException ex) + { + messages ~= Stdout.layout.convert("{}: {} - {} ({})", ex.name, ex.msg, ex.reason == ArgumentException.ExceptionReason.INVALID_PARAMETER ? "invalid parameter" : ex.reason == ArgumentException.ExceptionReason.MISSING_PARAMETER ? "missing parameter" : "missing argument", ex.parameter); + } + } + return Test.Status.Failure; + } + + Test argTest = new Test("tetra.util.Arguments"); + argTest["Normal"] = &parseTest; + argTest["Implicit"] = &implicitParseTest; + argTest["Alias"] = &aliasParseTest; + argTest["Validation"] = &validationTest; + argTest.run; + } +} ++/