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);
+}
+}