Last update November 6, 2006

Mariano Cecowski /
Command Line Arguments



This is a function for command line parameters handling. Uncomment the main function for testing it.

Documentation and unit tests included, though the function could probably improved, if not elswere, in speed. (My first steps in D...)

Please, leave comments at my home page. MarianoCecowski November 6, 2006 14:50 CET

/**
 * command line parameter handling function.
 *
 *
 * Copyright:	Public Domain
 * Author:	Mariano Cecowski
 * Date: November 4, 2006
 * Copyright: Public Domain
 * Bugs:
 *<ul>
 * <li>Doesn't handle the format of dmd's "-run" parameter.
 * That is, you can't link a command with more than one argument (unless you connect them putting them in ""s).</li>
 * <li>If a command expecting an argment in the following parameter is the last of the parameters, no error is issued.</li>
 *<ul>
 */

module parseargs;

private import std.string;          // find
private import std.stdio;           // writefln

// Example of dmd's parameters, except for '-run', wich only works with the following parameter encosed in ""s.
/*
int main(char[][] args)
{
    static char[][] p_fixed = ["-c", "-cov", "-D", "-d", "-debug", "-g", "-gc", "-H", "--help", "-inline", "-nofloat", "-O", "-o-", "-op", "-profile", "-quiet", "-release", "-unittest", "-v", "-w"];
    static char[][] p_param = ["-Dd", "Df", "-Df", "-debug=", "Hd", "Hf", "-I", "-L", "-od", "-of", "-version="];
    static char[][] p_spaced= ["-run"];

//  args[1..length] to avoid seeing the program's name
	char[][][char[]] parsed = parseArgs(args[1..length], p_fixed, p_param, p_spaced,
                                        "\tParameter '%s' not currently supported.",
                                        "\tMissing argument for parameter %s.", "-+");

	foreach(key, bucket;parsed)
	{
	    writefln("%s:%d", key, parsed[key].length);
        foreach(str;bucket)
            writefln("\t:"~str);
	}
    return 0;
}
*/

/***********************************
 * <br />
 * parseArgs takes the <i>args[][]</i> list of arguments and parses them against the lists of three diferent types of valid commands.
 * It produces an associative array for convenient search of valid parameters.
 *
 * Params:
 *	arguments =	the array if command-line options.
 *  p_fixed = array of valid parameter-less commands (e.i. <i>--help</i>, <i>-verbose</i>, <i>-Wall</i>, etc.)
 *	p_param = array of valid parameterized commands (e.i. <i>-level=3</i>, <i>-version=Windows</i>, <i>-Ic:\dm\include</i>, etc.)
 *  p_spaced = array of valid parametrized commands that are not glued to the command (e.i. <i>-o halo.exe</i>, <i>-B "c:\Program Files"</i>, <i>-run command</i>, etc.)
 *  invalidMsg = String with the error message to be shown on unknown command (not fitting any of the three models) It can contain a '%s' to show the unknown command.
 *  missingParam = String with the error message to be shown on when a command expecting an argument in the following parameter didn't find it. It can contain a '%s' to show the unknown command.
 *  Returns: an associative array with the commands as keys, with arrays of strings for the arguments found for that command. Commandless arguments have "" as their key, while argument-less commands have "" as their value.
 */

/++++++++++++++++++++++++
  + Example:
  + For example, for the parameters' format used by the dmd compiler we could use the following:
  + --------------------------
  +  static char[][] p_fixed = ["-c", "-cov", "-D", "-d", "-debug", "-g", "-gc", "-H",
                                "--help", "-inline", "-nofloat", "-O", "-o-", "-op",
                                "-profile", "-quiet", "-release", "-unittest", "-v", "-w"];
  +  static char[][] p_param = ["-Dd", "Df", "-Df", "-debug=", "Hd", "Hf", "-I", "-L", "-od", "-of", "-version="];
  +  static char[][] p_spaced= ["-run"];
  +  char[][][char[]] parsed = parseArgs(args[1..length], p_fixed, p_param, p_spaced,
  +                                      "\tParameter '%s' not currently supported.",
  +                                      "\tMissing argument for parameter %s.", "-");
  +  foreach(key, bucket;parsed)
  +  {
  +     writefln("%s:%d", key, parsed[key].length);
  +     foreach(str;bucket)
  +         writefln("\t:"~str);
  +  }
  + --------------------------
  + You can then check for the presence of a parameter:
  + --------------------------
  + if("-debug" in parsed) { writefln("debugging...")}
  + --------------------------
  + the value of a non repetitive parameter (or if consider only one of them)
  + --------------------------
  + char[] version = ("-version=" in parsed) ? parsed["-version"][0] : "unknown";
  + --------------------------
  + or all the values of a repetitive parameter
  + --------------------------
  + char[][] includes = ("-I" in parsed) ? parsed["-I"] : null; // null or [""]
  + --------------------------
+/

char[][][char[]] parseArgs( char[][] arguments,
                            char[][] p_fixed,
                            char[][] p_param,
                            char[][] p_spaced=null,
                            lazy char[] invalidMsg=null,
                            lazy char[] missingParam=null,
                            char[] ParameterStarts="-")
in
{
// not really much to assert here. actually, not even this is necesary as it would fail in std.format
//    assert(invalidMsg !is null && find(invalidMsg, "%s")<0 );
//    assert(missingParam !is null && find(missingParam, "%s")<0 );
}
out(result)
{
    assert(arguments.length >= result.length);
    // It would be hard to prove the correct functionality here without duplicating the already bug-prove code of the funtion
}
body
{
    char[][][char[]] result;        // collection of parameters, keyed by prefix ("" for prefix-less argumets)
    char[] lookahead;               // saved prefix for spaced parameters

    // each entry in the args[][] vectors
    foreach(param; arguments)
    {

        bool done=false;

        foreach(test; p_fixed)
        {
            if(param==test)
            {
                result[param]~="";
                if(lookahead)
                {
                    if(missingParam)    writefln(missingParam, param);
                    lookahead=null;
                }
                done=true;
                break;
            }
        }
        if(done) continue;

        foreach(test; p_param)
        {
            int pos=find(param, test);

            if(pos==0)
            {
                result[param[0..test.length]]~=param[test.length..param.length];
                if(lookahead)
                {
                    if(missingParam)    writefln(missingParam, param);
                    lookahead=null;
                }
                done=true;
                break;
            }
        }
        if(done) continue;

        foreach(test; p_spaced)
        {
            if(param==test)
            {
                if(lookahead && missingParam)
                    writefln(missingParam, param);
                lookahead=param;
                done=true;
                break;
            }
        }
        if(done) continue;

        if(find(ParameterStarts, param[0])>=0)
        {
            if(lookahead && missingParam)
                writefln(missingParam, param);
            if(invalidMsg)
                writefln(invalidMsg, param);
        }
        else
        {
            if(lookahead)
            {
                result[lookahead]~=param;
                lookahead=null;
            }
            else
                result[""]~=param;
        }

    }
        return result;
}
unittest
{
    static char[][] args=["program.exe", "-spaced", "+bogus", "-fix", "halo.d", "--wrong", "-wrong", "-spaced", "--double", "-valueSo far",
                          "-equal=equalizer", "--double", "+bogus", "-follows", "followed", "-spaced", "-textTest", "-valueSo good"];
    static char[][] p_fixed = ["-fixed", "-fix", "--double"];
    static char[][] p_param = ["-value=", "-value", "-equal=", "-text"];
    static char[][] p_spaced= ["-spaced", "-follows"];
//    writefln(args);

	char[][][char[]] parsed = parseArgs(args, p_fixed, p_param, p_spaced, null, null, "-+");

// actually, they needn't be in that order...
    assert(parsed[""].length == 2);
    assert(parsed[""][0] == "program.exe");
    assert(parsed[""][1] == "halo.d");

    assert("-spaced" in parsed is null);

    assert("+bogus" in parsed is null);

    assert(parsed["-fix"].length == 1);
    assert("-fix" in parsed );

	assert("--wrong" in parsed is null);

	assert(parsed["--double"].length == 2);
	assert("--double" in parsed);

	// actually, they needn't be in that order...
	assert(parsed["-value"].length == 2);
	assert(parsed["-value"][0] == "So far");
    assert(parsed["-value"][1] == "So good");

    assert(parsed["-equal="].length == 1);
    assert(parsed["-equal="][0] == "equalizer");

    assert(parsed["-follows"].length == 1);
    assert(parsed["-follows"][0] == "followed");

    assert(parsed["-text"].length == 1);
    assert(parsed["-text"][0] == "Test");
    //writefln("\npassed unit test.");
}

FrontPage | News | TestPage | MessageBoard | Search | Contributors | Folders | Index | Help | Preferences | Edit

Edit text of this page (date of last change: November 6, 2006 15:44 (diff))