Introduction
I love console applications. They're quick to write, and effective when trying out new code or testing code snippets. They're also invaluable when writing utility functions that you may possibly want to script or run unattended. I find that I write any number of them in a day. What one does tend to discover though, is that you end up writing the same pieces of code over and over, and that code is invariably for command line parsing.
This project started as a few modest snippets of helper code, and gradually grew into the library it is today. It has become immeasurably valuable to me, and I am hoping that someone else may find a use for it too.
Why Another Parser?
There are many command line parsers out there, many of them on CodeProject itself. I did not intentionally set out to develop this library – it kind of ‘evolved’. Now that it has developed to a certain level of maturity, I have compared it to some of the others out there, and found mine to be simpler to use, more expressive, and far neater in source. Anyway, try it out. You may just like it!
Using the Code
Once the library has been ‘linked’ into your project (more on that later), it’s simply a matter of decorating the start up class with a few attributes, and the library does the rest. I have defined two attributes, those being ApplicationAttribute
and OptionAttribute
.
ApplicationAttribute
The ApplicationAttribute
defines how the application, in general, will react to command line options, for example:
[Application(
HelpOptions=new string[] {"?", "help"},
OptionPrefixes=new char[] {'/', '-'},
ThrowParsingExceptions=false)]
public class Program : CommandLineBase
{
static void Main(string[] args) { new Program(args); }
private Program(string[] args) : base(args)
{
if ((Options.Count == 0) &&
!ShowingHelp)
ShowHelp();
if (ParseErrors)
WriteLine("Errors occurred!");
}
}
HelpOptions
is a string array containing the list of ‘triggers’ that will invoke the help screen. OptionPrefixies
are the forward slashes or dashes (or both) that are expected to precede each command line argument. Using the example above, entering a /? or -help or any other combination of the two will invoke the help screen.
ThrowParsingExceptions
will determine whether or not an exception is to be thrown when a command line could not be parsed. This will vary depending on how you want to handle an invalid command line. If you choose to switch the throwing of exceptions off, you can test whether errors occurred by looking at the ParseErrors
property.
The example also shows the use of the ShowingHelp
property (determine whether the help screen was invoked), the ShowHelp
method (force the help screen to display), and the WriteLine
method, which will emit the specified text. It is possible to redirect output from the standard console to another source. This is done by providing a TextWriter
instance to the base constructor, so it is always advisable to use the base class’s WriteLine
rather than a Console.WriteLine
.
OptionAttribute
The snippet above is pretty useless, as no command line options are recognized. We use the OptionAttribute
for that. Have a look at the next example.
[Application(
HelpOptions=new string[] {"?", "help"},
OptionPrefixes=new char[] {'/', '-'},
ThrowParsingExceptions=false)]
[Option("Confirm",
FormatPattern="y|n",
FormatDisplay="y/n",
ShortDescription="Confirmation",
LongDescription="Choose either 'y' or 'n' to confirm or deny.",
ValuePresence=OptionAttributeValuePresence.MustHaveValue,
IsOptional=true,
Value="y")]
[Option("TheFile",
IsAnonymous=true,
ShortDescription="FileName",
LongDescription="The file to process",
ValuePresence = OptionAttributeValuePresence.MustHaveValue,
IsOptional = false)]
public class Program : CommandLineBase
{
static void Main(string[] args) { new Program(args); }
private Program(string[] args) : base(args)
{
if (ParseErrors)
{
base.WriteLine("Errors occurred!");
return;
}
if ((Options.Count == 0) &&
!base.ShowingHelp)
ShowHelp();
if (!ShowingHelp)
WriteLine(
"Must we do it to file name '{0}'? {1}!",
Options["TheFile"].Value,
Options["Confirm"].Value);
}
Now, it looks a little more interesting! We accept two command line arguments. One of them is a file name, the other some kind of confirmation switch. Each option is identified by a name ("Confirm
" and "TheFile
", in this example). If you have a look at the configuration, the file has IsAnonymous
set to true
, meaning it doesn't need any prefix or switch before it. You just enter it as-is on the command line. The "Confirm
" option does require its switch. It also uses a Regular Expression to determine what values are permissible. An appropriate command line for this configuration would be:
If we invoke the Help screen now, we now see that the library has pulled all the relevant information together and displays it to us.
The named parameters on the OptionAttribute
attribute are:
The key by which the option is identified, both in code and on the command line.
Used both to read the value that was entered on the command line, and to set the default value that should be assigned, should this option not appear on the command line.
If the same option appears multiple times, an array of values is built up. The Value
property always returns the first value found.
Friendly descriptions of what the switch does, to be used on the help screen.
A Regular Expression pattern to validate the value. If the validation fails, an error occurs (which will result in a CommandLineParsingException
being thrown if the ThrowParsingExceptions
parameter is set to true
on the ApplicationAttribute
).
The text to display on the help screen, as Regular Expressions are generally not very meaningful to the average user. This will likely be some form of 'shorthand' to carry across the general intent of the pattern specified.
Whether or not the option should appear on the command. If the option is not optional, an error occurs if it is missing.
If an option is anonymous, it is expected that the option name will not precede it. This is generally used when specifying file names or paths on the command line.
This can be one of three values. MustNotHaveValue
means that the option name must appear on its own, without a value. MayHaveValue
is used when a value may or may not accompany the option name, and MustHaveValue
when you always want a value to be provided.
This is used in code to detect whether a value was given, generally when ValuePresence
is set to MayHaveValue
.
The numeric order in which the options will appear on the help screen.
Test whether an option was entered on the command line.
Specify whether multiple copies of the option can be entered on the same command line.
Name
Value
Values
ShortDescription
and LongDescription
FormatPattern
FormatDisplay
IsOptional
IsAnonymous
ValuePresence
HasValue
Order
IsPresent
AllowMultiple
Different Exceptions
Take note! The library can throw two different types of exceptions. The first, CommandLineParsingException
, is thrown when the user enters an 'invalid' sequence of command line options. A command line is invalid if a mandatory option was not given, a format pattern validation failed, an option that was not supposed to have a value did have one,... you get the idea. You can override the throwing of this exception with the ThrowParsingExceptions
parameter on the ApplicationAttribute
.
The other exception, a CommandLineParserBaseException
, is thrown when you, the developer, specify a conflicting or incorrect combination of OptionAttribute
parameters. This one will always be thrown, regardless of the state of ThrowParsingExceptions
.
In a sense (using a lot of literary license here), view the CommandLineParsingException
as a runtime exception and CommandLineParserBaseException
as a compiler exception.
Suggestions
I have found it quite helpful to create partial classes when writing command line apps. One class will contain the long list of attributes defining all the options, and the other class will contain the actual application code. This way, I don't clutter up my code.
Using the Library
Right! Now you're ready to use the library. If you reference the CommandLineBase
DLL from your application, you will need to distribute both a DLL and your EXE, which may not always be desirable. There are two ways to get around this.
The first is to use the fabulous IL Merge tool. This combines both the EXE and the DLL (and any other referenced libraries for that matter) into a single assembly. It does take some fiddling around with to get it working, but it does work well.
The second option is the 'cut-and-paste' option. Basically, just include all the source files right into your application project.
Both options have pros and cons, and each is suited to different environments. I have used both approaches in various instances, and both work well.
There you have it! The best way to get to know the library is to try it out for yourself. Download it and have fun! I have included both a VS 2008 project file, and a Build.cmd file, in case you don't have Visual Studio.
History
- 1st September, 2008: Initial version