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:
- Parameters of the form: -flag, --flag, /flag, -flag=value, --flag=value, /flag=value, -flag:value, --flag:value, /flag:value, -flag value, --flag value, /flag value.
- "boolean" parameters of the form: -flag, --flag, and /flag. Boolean parameters can have a `+' or `-' appended to explicitly enable or disable the flag (in the same fashion as mcs -debug+). For boolean callbacks, the provided value is non-null for enabled, and null for disabled.
- "value" parameters with a required value (append `=' to the option name) or an optional value (append `:' to the option name). The option value can either be in the current option (--opt=value) or in the following parameter (--opt value). The actual value is provided as the parameter to the callback delegate, unless it's (1) optional and (2) missing, in which case null is passed.
- "bundled" parameters which must start with a single `-' and consists of only single characters. In this manner, -abc would be a shorthand for -a -b -c.
- Option processing is disabled when -- is encountered.
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).
blog comments powered by Disqus