Introduction
A quick glance at this site shows that there are several flavors of command line parsers available. Several of them are quite good. Command line parsing is the type of thing that most of us programmers do without a second thought - you've already started coding your console test application when you realize you can simplify things by passing an option or two from the command line - and the next thing you know you're typing away...
foreach (string arg in args) { if (arg.StartsWith ("-")) { blah, blah, blah... } }
Most command line programs are put together to test a library or do a quick proof of concept. If the program is more permanent in nature, or even a GUI-based program or service that offers startup arguments, you might consider putting a framework in place that allows structured maintenance and addition of command line options. The ArgumentParser
class described here fits this requirement nicely.
Background
A few weeks ago, I wrote the ArgumentParser
library for use with a program I was working on. I had another program that parsed arguments manually, and while that code was clean enough, it continued to grow as I added arguments, until ultimately the largest part of the console application was the handling of the arguments! This is not necessarily unusual, since a test rig might have a pretty slim interface anyway. Still, I found myself doing some of the same things - checking for valid integers, building messages, etc. In short, I was underwhelmed.
Several things are handled by the library, rather than by the client program:
- Catching syntax errors, such as an expected integer with an alpha character in it
- Catching argument errors, such as a missing argument after the token
- Tracking the occurrence of errors during argument parsing
- Keeping the text of all errors and warnings during parsing
- Allowing the client program to add warnings or error messages
Using the Code
The namespace AsAbove.Command.Arguments
(of which ArgumentParser
is a member) contains six delegate
definitions. The delegate
s define the data types supported by the library:
public delegate string ArgumentString (string val);
public delegate string ArgumentInteger (int val);
public delegate string ArgumentDouble (double val);
public delegate string ArgumentBool (bool val);
public delegate string ArgumentDate (DateTime val);
public delegate string ArgumentSimple ();
For instance, consider a program that takes two arguments - a string
and a bool
ean type. The purpose of the program is to indicate a change in active work status for a person, using the person's social security number. The command line for such a program might look like:
setactive -ssn 505-30-2773 -active-
The components of the command are the program name, setactive
, the social security number token -ssn
followed by the number, and the boolean argument -active-
to indicate "inactive". Note that boolean arguments must always be terminated with a +
or -
sign. If the token is terminated with +
, it evaluates to true
, and if it is terminated with -
, it evaluates to false
.
The data types string
, integer
, double
, and date
must always be followed by an argument of that type; these correspond to the delegates shown above with that data type in the name. The delegate ArgumentSimple
does not require a following argument - it is used in cases when the presence of the token on the command line is enough to trigger the action. For instance, this delegate
would be useful for requesting help with -h
or -?
.
To add ArgumentParser
support to a program, the first thing to do is to implement the delegate
methods that are to be called for each program argument. For the setactive
program, we would define two delegate
s:
private string SSNDelegate (string val)
{
this.ssn_ = val;
return "";
}
private string ActiveDelegate (bool val)
{
this.active_ = val;
return "";
}
Note that all delegate
s return a string
. If the client program needs to add a message to the program output, it can return the message from the delegate
.
Argument Validation by ArgumentParser
By the time the delegate
is called, the argument has been validated for correct syntax. If the argument is missing or incorrect, the delegate
is not called, and the error is appended to the message string
contained in the ArgumentParser
object. For instance, an integer argument that contains a non-digit, or a boolean argument that is not terminated with +
or -
.
Argument Validation by Client Delegates
The delegate
s have an opportunity to validate the arguments too. If a message should be given, it can be returned in a string
:
private string ActiveDelegate (bool val)
{
this.active_ = val;
return "SSN set to " + val ? "active." : "inactive.";
}
If the delegate
discovers an error condition, it can throw an exception. The ArgumentParser
object catches the exception and appends the message to the message string
.
private string SSNDelegate (string val)
{
if (false == IsCorrectFormattedForSSN (val))
{
throw new ArgumentException ("Bad SSN format " + val);
}
this.ssn_ = val;
return "";
}
After the client delegate
s are implemented, the next thing to do is to declare the Argument
array. The Argument
class stores token names for matching, the client delegate
, and provides the internal methods for the syntax validation based on data type.
public Argument[] GetArgumentDefinitions
{
get
{
Argument[] arguments =
{
new Argument ("ssn", new ArgumentString (this.SSNDelegate)),
new Argument ("active", new ArgumentBool (this.ActiveDelegate))
};
return arguments;
}
}
Finally, the client program puts it all together inside of the Main
method. The following code snippet is abbreviated just to show the juicy parts:
class ArgTest
{
[STAThread]
static void Main(string[] args)
{
ArgTest a = new ArgTest ();
ArgumentParser p = new ArgumentParser (a.GetArgumentDefinitions, args, 0);
}
}
Points of Interest
The source code with this article contains a complete test program that demonstrates the use of each delegate
type, determines if errors were found, and displays messages to the console. A help document is also included in the project.
For more information, see the readme.txt file in the Command directory.
History
- 9th March, 2006: Initial post