Introduction
One of the first things I tend to write whenever I try something new in a programming language, are console test applications. These test applications tend to start turning into complete test harnesses - quickly start outgrowing the original functionality. Typically they also start handling all of the special cases that I uncover as I develop.
Eventually, I end up having to supply switches on the command line to change the functionality of the application. What I always wanted was a clever command-line parser to minimize the amount of coding I needed to do to add such functionality.
This article shows my current (and work in progress) command line parser based upon .net's attributes and using regular expressions to split the sections apart.
At the end of this article I've included a wish list (for myself) and will continue this class as an ongoing development. Updates and change-lists to be added to the original article.
Reading the Command Line
As most developers know, there are many ways to look at the command line supplied arguments for a running application. The two typical methods are:
-
The Main function takes an array of strings representing each of the white space separated arguments (or including white space if quoted correctly).
public static int Main( string[] cmdLine )
{
Console.WriteLine("Command-line strings are:");
foreach( string s in cmdLine )
Console.WriteLine(s);
return 0;
}
-
The second method involves looking at the environment command-line.
public static int Main( string[] cmdLine )
{
Console.WriteLine("Command-line is: {0}",
System.Environment.CommandLine);
return 0;
}
Both of the above methods have pros and cons when trying to pass in command line arguments. The first is very easy to walk through when, for example, individual files-names have been supplied. The latter method allows complex switches, e.g. ones taking optional parameters, to be parsed.
In most cases, a programmer needs to walk through the command line switches looking for a textual match for their intended switch. This tends to make code look like a big series of if
statements or a huge switch
statement.
It was my intention to remove this clutter and constant rewriting of similar code from my test applications, indeed, very minimal coding is now required.
Registering Switches
The parser class has two different ways of being made aware of command line switches. Programmatically added switches or automatically added switches.
Programmatic Switches
This is the typical way of adding something to a class. After construction of the parser class (but prior to running the parser), switches may be added. Currently this is achieved by calling the member functions (notice the overloading in the example below) called AddSwitch.
Parser parser = new Parser( System.Environment.CommandLine);
parser.AddSwitch( "Wibble", "Do something silly" );
parser.AddSwitch(new string[] { "help", @"\?" }, "show help");
parser.AddSwitch(new string[] { "a", "b", "c", "d", "e", "f" }, "Early alphabet");
parser.Parse();
In the example above, there are two examples of adding aliases to switches. For example, "help" may be addressed with either help or /?. The reason that the question mark is quoted belays the underlying use of Regular Expressions. It is necessary to quote the strings in such a way to prevent causing an exception in the regular expression handler.
Note: Currently, it is only possible to register a Boolean type switch. The AddSwitch function is likely to change in the next revision to take a type argument.
After the parser has been executed, it is possible to see if the switch has been set by calling the following:
if ( parser["help"] != null )
Console.WriteLine("Request for help = {0}", parser["help"]);
else
Console.WriteLine("Request for help has no associated value.");
Automatic Switches
Using the wonderful attributes and reflection mechanisms available in C# and .net, it is possible to make the above code even simpler. Consider when you develop a class, typically you create properties for the different things your application does. Let's for example have a Boolean to specify whether or not the user wishes to see some help.
public class Application
{
private bool m_ShowHelp = false;
public bool ShowHelp
{
get { return m_ShowHelp; }
set { m_ShowHelp = value; }
}
}
Rather than adding the programmatic code we would normally have done, consider the following adaptation:
public class Application
{
private bool m_ShowHelp = false;
[CommandLineSwitch("help","Show the help")]
public bool ShowHelp
{
get { return m_ShowHelp; }
set { m_ShowHelp = value; }
}
}
The attribute CommandLineSwitchAttribute
(that may be shortened to CommandLineSwitch
when used as above) assigned to the get/set methods of the property indicating that ShowHelp is a command line switch of "help". The only different thing we need to do is to pass the instance of the class into the constructor of the parser.
public class Application
{
private bool m_ShowHelp = false;
[CommandLineSwitch("help","Show the help")]
public bool ShowHelp
{
get { return m_ShowHelp; }
set { m_ShowHelp = value; }
}
private void Run()
{
Console.WriteLine("Show help defaults to FALSE. " +
"Currently it is : " + ShowHelp);
Parser parser = new Parser( System.Environment.CommandLine, this );
parser.Parse();
Console.WriteLine("The current value of ShowHelp : " + ShowHelp);
}
public static int Main( string[] cmdLine );
{
Application a = new Application();
a.Run();
return 0;
}
}
The parser class will now walk through the methods and properties of the Application class (as passed as second parameter in the Parser class constructor) looking for any CommandLineSwitchAttributes. In the event of finding a match when parsing the command-line, the internal value of ShowHelp will be modified directly, without any need to look at the results of the parser class.
C:\> test
Show help defaults to FALSE. Currently it is : false
The current value of ShowHelp : false
C:\> test /help
Show help defaults to FALSE. Currently it is : false
The current value of ShowHelp : true
C:\> test --help
Show help defaults to FALSE. Currently it is : false
The current value of ShowHelp : true
Switches
There are currently three types of switches handled by the parser (NOTE: the programmatically added switches only support Boolean types so far).
- Boolean
- String
- Integer
- Enumerations (** new **)
Using the Switches
All switches may be used with any of the following escapes, see below for examples of the "help" switch. The types may currently be intermingled within the same command-line.
- /help Single forward slash
- -help Single hyphen
- -help Double hyphen
Boolean Switches
These switches are the most basic, supported by both the programmatic and automatic mechanisms. An occurrence of the switch toggles its state (this is important for the automatic switches, since they could default to true). Continuing using "help" as an example, the following switch directives may be given:
- /help Toggle the state. When enrolled programmatically, this will set the state to true.
- /help+ Set the Boolean to true (irrespective to its old state)
- /help- Set the Boolean to false (irrespective to its old state)
Note that the single and double hyphen prefixes are still legal. So --help+ is identical to /help+.
String Switches
Currently string type switches are only implemented with the automatically enrolled switches.
To introduce a new example switch, say we have a user-name in our application defined as:
private string m_UserName = "Unknown";
[CommandLineSwitch("user","Set the user name")]
public bool UserName
{
get { return m_UserName; }
set { m_UserName = value; }
}
We can supply a new user-name to the application in one of the following ways:
- /user:new-user-name
- /user new-user-name
- /user:"user name with spaces"
- /user "user name with spaces"
Note that the single and double hyphen prefixes are still legal. So --user:fred + is identical to -user:fred
Enumerated Type Switches (** new **)
Imagine if you had an enumerated type for the days of the week, you would implement something akin to the following.
public enum DaysOfWeek
{
Sun,
Mon,
Tue,
Wed,
Thu,
Fri,
Sat
};
Should it prove necessary for a command line switch to use this enumeration, this is now supported. Placing the attribute type on an enumeration will allow the command line to accept enumerations (similar to how it accepts strings). Conside the following:
[CommandLineSwitch("Day","Day of the week")]
public DaysOfWeek DoW { ... }
Thus, the following will be legal on the command line:
- /day:Mon
- /day:tue (note that it is not case sensitive)
- /day sun
The following are illegal uses of the switch
- /day tomorrow
- /day:thur (because it should be thu)
If a Set method is provided for the property, the value will be correctly set to the approprate value.
Accessing the remainder of the command-line
After all of the command-line switches, what about accessing any non-switch parameters? Well, nothing too complex. After all of the switches have been stripped from the command-line, what remains is split (by the parser) into white-space delimited (or quoted) strings, returned by the property Parameters.
Console.WriteLine("Non-switch Params : {0}", parser.Parameters.Length);
for ( int j=0; j<parser.Parameters.Length; j++ )
Console.WriteLine("{0} : {1}",j,parser.Parameters[j]);
Wish List
The list that follows is basically my wish list, should you think of anything and tell me, I might add your wishes to it too. :-)
- Expand the article about the use of regular expressions within the parser.
- Expand the article about the use of attributes.
- Complete the commenting of the source.
- Add more switch types.
- Reimplement the
AddSwitch
function to take all switch types.
- Allow a switch-file to be parsed (as well as the command line), e.g. if the file config contains switches for the run-time application to use (not new switches, but settings for existing ones), then putting
@config
on the command line will include those settings too.
Licence
I don't want to get too heavy with licences, so here it is simply. I retain copyright of the code, I offer no warrenties for its suitability for your job. If you include this implementation or a derivative in your code/article, credit me as the original author. If you include it in commerical software, put my name and a reference to codeproject in your about box/help information.
Oh yeah... send me an email or attach a comment below saying where you're using it.
History
Date |
Version |
Change Description |
25 March 2003 |
0.1 |
Initial Public Release |
26 March 2003 |
0.2 |
- Update to article.
- Code updated to support using Enum types.
- Utility function added to retrieve parsed switches, for error handling.
|