collapse Blogs I Read
collapse Table of Contents
  1. So you want to parse a command line... - Jonathan Pryor's web log
    1. So you want to parse a command line...

So you want to parse a command line... - Jonathan Pryor's web log

« Announcing Brian Jonathan Pryor | Main | Mono and Mixed Mode Assembly Support »

So you want to parse a command line...

If you develop command-line apps, parsing the command-line is a necessary evil (unless you write software so simple that it doesn't require any options to control its behavior). Consequently, I've written and used several parsing libraries, including Mono.GetOptions, Perl's Getopt::Long library, and some custom written libraries or helpers.

So what's wrong with them? The problem with Mono.GetOptions is that it has high code overhead: in order to parse a command line, you need a new type (which inherits from Mono.GetOptions.Options) and annotate each field or property within the type with an Option attribute, and let Mono.GetOptions map each command-line argument to a field/property within the Options subclass. See monodocer for an example; search for Opts to find the subclass.

The type-reflector parser is similarly code heavy, if only in a different way. The Mono.Fuse, lb, and omgwtf parsers are one-offs, either specific to a particular environment (e.g. integration with the FUSE native library) or not written with any eye toward reuse.

Which leaves Perl's Getopt::Long library, which I've used for a number of projects, and quite like. It's short, concise, requires no object overhead, and allows seeing at a glance all of the options supported by a program:

use Getopt::Long;
my $data    = "file.dat";
my $help    = undef;
my $verbose = 0;

GetOptions (
	"file=s"    => \$data,
	"v|verbose" => sub { ++$verbose; },
	"h|?|help"  => $help
);

The above may be somewhat cryptic at first, but it's short, concise, and lets you know at a glance that it takes three sets of arguments, one of which takes a required string parameter (the file option).

So, says I, what would it take to provide similar support in C#? With C# 3.0 collection initializers and lambda delegates, I can get something that feels rather similar to the above GetOpt::Long code:

string data = null;
bool help   = false;
int verbose = 0;

var p = new Options () {
	{ "file=",      (v) => data = v },
	{ "v|verbose",  (v) => { ++verbose } },
	{ "h|?|help",   (v) => help = v != null },
};
p.Parse (argv).ToArray ();

Options.cs has the goods, plus unit tests and additional examples (via the tests).

Options is both more and less flexible than Getopt::Long. It doesn't support providing references to variables, instead using a delegate to do all variable assignment. In this sense, Options is akin to Getopt::Long while requiring that all options use a sub callback (as the v|verbose option does above).

Options is more flexible in that it isn't restricted to just strings, integers, and floating point numbers. If there is a TypeConverter registered for your type (to perform string->object conversions), then any type can be used as an option value. To do so, merely declare that type within the callback:

int count = 0;

var p = new Options () {
	{ "c|count=", (int v) => count = v },
};

As additional crack, you can provide an (optional) description of the option so that Options can generate help text for you:

var p = new Options () {
	{ "really-long-option", "description", (v) => {} },
	{ "h|?|help", "print out this message and exit", (v) => {} },
};
p.WriteOptionDescriptions (Console.Out);

would generate the text:

      --really-long-option   description
  -h, -?, --help             print out this message and exit

Options currently supports:

All un-handled parameters are returned from the Options.Parse method, which is implemented as an iterator (hence the calls to .ToArray() in the above C# examples, to force processing).

Posted on 07 Jan 2008 | Path: /development/mono/ | Permalink
blog comments powered by Disqus