Mercurial > projects > ldc
diff tango/tango/util/ArgParser.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/ArgParser.d Fri Jan 11 17:57:40 2008 +0100 @@ -0,0 +1,434 @@ +/******************************************************************************* + + copyright: Copyright (c) 2005-2006 Lars Ivar Igesund, + Eric Anderton. All rights reserved + + license: BSD style: $(LICENSE) + + version: Initial release: December 2005 + + author: Lars Ivar Igesund, Eric Anderton + +*******************************************************************************/ + +module tango.util.ArgParser; + +private import tango.core.Exception; + +/** + An alias to a delegate taking a char[] as a parameter. The value + parameter will hold any chars immediately + following the argument. +*/ +alias void delegate (char[] value) ArgParserCallback; + +/** + An alias to a delegate taking a char[] as a parameter. The value + parameter will hold any chars immediately + following the argument. + + The ordinal argument represents which default argument this is for + the given stream of arguments. The first default argument will + be ordinal=0 with each successive call to this callback having + ordinal values of 1, 2, 3 and so forth. This can be reset to zero + in new calls to parse. +*/ +alias void delegate (char[] value,uint ordinal) DefaultArgParserCallback; + +/** + An alias to a delegate taking no parameters +*/ +alias void delegate () ArgParserSimpleCallback; + + +/** + A struct that represents a "{Prefix}{Identifier}" string. +*/ +struct Argument { + char[] prefix; + char[] identifier; + + /** + Creates a new Argument instance with given prefix and identifier. + */ + static Argument opCall ( char[] prefix, char[] identifier ) { + Argument result; + + result.prefix = prefix; + result.identifier = identifier; + + return result; + } +} + +/** + Alias for for the lazy people. +*/ +alias Argument Arg; + + +/** + A utility class to parse and handle your command line arguments. +*/ +class ArgParser{ + + /** + A helper struct containing a callback and an id, corresponding to + the argId passed to one of the bind methods. + */ + protected struct PrefixCallback { + char[] id; + ArgParserCallback cb; + } + + protected PrefixCallback[][char[]] bindings; + protected DefaultArgParserCallback[char[]] defaultBindings; + protected uint[char[]] prefixOrdinals; + protected char[][] prefixSearchOrder; + protected DefaultArgParserCallback defaultbinding; + private uint defaultOrdinal = 0; + + protected void addBinding(PrefixCallback pcb, char[] argPrefix){ + if (!(argPrefix in bindings)) { + prefixSearchOrder ~= argPrefix; + } + bindings[argPrefix] ~= pcb; + } + + /** + Binds a delegate callback to argument with a prefix and + a argId. + + Params: + argPrefix = the prefix of the argument, e.g. a dash '-'. + argId = the name of the argument, what follows the prefix + cb = the delegate that should be called when this argument is found + */ + public void bind(char[] argPrefix, char[] argId, ArgParserCallback cb){ + PrefixCallback pcb; + pcb.id = argId; + pcb.cb = cb; + addBinding(pcb, argPrefix); + } + + /** + The constructor, creates an empty ArgParser instance. + */ + public this(){ + defaultbinding = null; + } + + /** + The constructor, creates an ArgParser instance with a defined default callback. + */ + public this(DefaultArgParserCallback callback){ + defaultbinding = callback; + } + + protected class SimpleCallbackAdapter{ + ArgParserSimpleCallback callback; + public this(ArgParserSimpleCallback callback){ + this.callback = callback; + } + + public void adapterCallback(char[] value){ + callback(); + } + } + + /** + Binds a delegate callback to argument with a prefix and + a argId. + + Params: + argPrefix = the prefix of the argument, e.g. a dash '-'. + argId = the name of the argument, what follows the prefix + cb = the delegate that should be called when this argument is found + */ + public void bind(char[] argPrefix, char[] argId, ArgParserSimpleCallback cb){ + SimpleCallbackAdapter adapter = new SimpleCallbackAdapter(cb); + PrefixCallback pcb; + pcb.id = argId; + pcb.cb = &adapter.adapterCallback; + addBinding(pcb, argPrefix); + } + + /** + Binds a delegate callback to all arguments with prefix argPrefix, but that + do not conform to an argument bound in a call to bind(). + + Params: + argPrefix = the prefix for the callback + callback = the default callback + */ + public void bindDefault(char[] argPrefix, DefaultArgParserCallback callback){ + defaultBindings[argPrefix] = callback; + prefixOrdinals[argPrefix] = 0; + if (!(argPrefix in bindings)) { + prefixSearchOrder ~= argPrefix; + } + } + + /** + Binds a delegate callback to all arguments not conforming to an + argument bound in a call to bind(). These arguments will be passed to the + delegate without having any matching prefixes removed. + + Params: + callback = the default callback + */ + public void bindDefault(DefaultArgParserCallback callback){ + defaultbinding = callback; + } + + /** + Binds a delegate callback to an argument. + + Params: + argument = argument to respond to + callback = the delegate that should be called when the argument is found + */ + public void bind (Argument argument, ArgParserCallback callback) { + bind(argument.prefix, argument.identifier, callback); + } + + /** + Binds a delegate callback to any number of arguments. + + Params: + arguments = an array of Argument struct instances + callback = the delegate that should be called when one of the arguments is found + */ + public void bind ( Argument[] arguments, void delegate(char[]) callback ) { + foreach (argument; arguments) { bind(argument, callback); } + } + + /** + Binds a delegate callback to an identifier with Posix-like prefixes. This means, + it binds for both prefixes "-" and "--", as well as identifier with, and + without a delimiting "=" between identifier and value. + + Params: + identifier = argument identifier + callback = the delegate that should be called when one of the arguments is found + */ + public void bindPosix ( char[] identifier, ArgParserCallback callback ) { + bind([ Argument("-", identifier ~ "="), Argument("-", identifier), + Argument("--", identifier ~ "="), Argument("--", identifier) ], callback); + } + + /** + Binds a delegate callback to any number of identifiers with Posix-like prefixes. + See bindPosix(identifier, callback). + + Params: + arguments = an array of argument identifiers + callback = the delegate that should be called when one of the arguments is found + */ + public void bindPosix ( char[][] identifiers, ArgParserCallback callback ) { + foreach (identifier; identifiers) { bindPosix(identifier, callback); } + } + + /** + Parses the arguments provided by the parameter. The bound callbacks are called as + arguments are recognized. If two arguments have the same prefix, and start with + the same characters (e.g.: --open, --opened), the longest matching bound callback + is called. + + Params: + arguments = the command line arguments from the application + resetOrdinals = if true, all ordinal counts will be set to zero + */ + public void parse(char[][] arguments, bool resetOrdinals = false){ + if (bindings.length == 0) return; + + if (resetOrdinals) { + defaultOrdinal = 0; + foreach (key; prefixOrdinals.keys) { + prefixOrdinals[key] = 0; + } + } + + foreach (char[] arg; arguments) { + char[] argData = arg; + char[] argOrig = argData; + bool found = false; + + foreach (char[] prefix; prefixSearchOrder) { + if(argData.length < prefix.length) continue; + + if(argData[0..prefix.length] != prefix) continue; + else argData = argData[prefix.length..$]; + + if (prefix in bindings) { + PrefixCallback[] candidates; + + foreach (PrefixCallback cb; bindings[prefix]) { + if (argData.length < cb.id.length) continue; + + uint cbil = cb.id.length; + + if (cb.id == argData[0..cbil]) { + found = true; + candidates ~= cb; + } + } + + if (found) { + // Find the longest matching callback identifier from the candidates. + uint indexLongestMatch = 0; + + if (candidates.length > 1) { + foreach (i, candidate; candidates) { + if (candidate.id.length > candidates[indexLongestMatch].id.length) { + indexLongestMatch = i; + } + } + } + + // Call the best matching callback. + with(candidates[indexLongestMatch]) { cb(argData[id.length..$]); } + } + } + if (found) { + break; + } + else if (prefix in defaultBindings){ + defaultBindings[prefix](argData,prefixOrdinals[prefix]); + prefixOrdinals[prefix]++; + found = true; + break; + } + argData = argOrig; + } + if (!found) { + if (defaultbinding !is null) { + defaultbinding(argData,defaultOrdinal); + defaultOrdinal++; + } + else { + throw new IllegalArgumentException("Illegal argument "~ argData); + } + } + } + } +} + +debug (UnitTest) { + import Integer = tango.text.convert.Integer; + + //void main() {} + +unittest { + + ArgParser parser = new ArgParser(); + bool h = false; + bool h2 = false; + bool b = false; + bool bb = false; + bool boolean = false; + int n = -1; + int dashOrdinalCount = -1; + int ordinalCount = -1; + + parser.bind("--", "h2", delegate void(){ + h2 = true; + }); + + parser.bind("-", "h", delegate void(){ + h = true; + }); + + parser.bind("-", "bb", delegate void(){ + bb = true; + }); + + parser.bind("-", "bool", delegate void(char[] value){ + assert(value.length == 5); + assert(value[0] == '='); + if (value[1..5] == "true") { + boolean = true; + } + else { + assert(false); + } + }); + + parser.bind("-", "b", delegate void(){ + b = true; + }); + + parser.bind("-", "n", delegate void(char[] value){ + assert(value[0] == '='); + n = cast(int) Integer.parse(value[1..5]); + assert(n == 4349); + }); + + parser.bindDefault(delegate void(char[] value, uint ordinal){ + ordinalCount = ordinal; + if (ordinal == 0) { + assert(value == "ordinalTest1"); + } + else if (ordinal == 1) { + assert(value == "ordinalTest2"); + } + }); + + parser.bindDefault("-", delegate void(char[] value, uint ordinal){ + dashOrdinalCount = ordinal; + if (ordinal == 0) { + assert(value == "dashTest1"); + } + else if (ordinal == 1) { + assert(value == "dashTest2"); + } + }); + + parser.bindDefault("@", delegate void(char[] value, uint ordinal){ + assert (value == "atTest"); + }); + + static char[][] test1 = ["--h2", "-h", "-bb", "-b", "-n=4349", "-bool=true", "ordinalTest1", "ordinalTest2", "-dashTest1", "-dashTest2", "@atTest"]; + + parser.parse(test1); + assert(h2); + assert(h); + assert(b); + assert(bb); + assert(n == 4349); + assert(ordinalCount == 1); + assert(dashOrdinalCount == 1); + + h = h2 = b = bb = false; + boolean = false; + n = ordinalCount = dashOrdinalCount = -1; + + static char[][] test2 = ["-n=4349", "ordinalTest1", "@atTest", "--h2", "-b", "-bb", "-h", "-dashTest1", "-dashTest2", "ordinalTest2", "-bool=true"]; + + parser.parse(test2, true); + assert(h2 && h && b && bb && boolean && (n ==4349)); + assert(ordinalCount == 1); + assert(dashOrdinalCount == 1); + + h = h2 = b = bb = false; + boolean = false; + n = ordinalCount = dashOrdinalCount = -1; + + static char[][] test3 = ["-n=4349", "ordinalTest1", "@atTest", "--h2", "-b", "-bb", "-h", "-dashTest1", "-dashTest2", "ordinalTest2", "-bool=true"]; + + parser.parse(test3, true); + assert(h2 && h && b && bb && boolean && (n ==4349)); + assert((ordinalCount == 1) && (dashOrdinalCount == 1)); + + ordinalCount = dashOrdinalCount = -1; + + static char[][] test4 = ["ordinalTest1", "ordinalTest2", "ordinalTest3", "ordinalTest4"]; + static char[][] test5 = ["-dashTest1", "-dashTest2", "-dashTest3"]; + + parser.parse(test4, true); + assert(ordinalCount == 3); + + parser.parse(test5, true); + assert(dashOrdinalCount == 2); +} +}