Parsing Command Args in .NET
All your commands are belong to us
2017-06-18
The Command Line - Learn to Love It
I like the web. I like GUIs but there is something about just 'talking to the computer' in a low level way without a lot ceremony - the command line interface. I think this is a result of my first experience with computers playing Orgeon Trail, drawing with Logo and trying 'logic' with the goto Basic in our classroom on the Apple ][. I seem to have a couple of posts relating to command lines in one form or another.
There are so many tools in the software industry that are command line interface (cli) based. Despite the efforts of various tools the true power of git is best realized when on the commandline. The node.js ecosystem is steeped deeply in the cli. Ever do any build or deployment automation? Chances are you've had to write script or two to get things working to your liking. GitHub makes a nice little utility called Hub that enables quick access to some commonly used features that you would normally have to use the site for.
In .NET the console application project type is a way to build your own command line interface (CLI) and the string array called args
is your little bit of magic that gets bound automatically for you to use in your application. You can a number of things by consuming this array and using those values for various arguments and switches to control the "GOTOs" in your application. This works for a short while but you find over time that you'll have a collection of helper methods to try to wrangle these strings. Then you need to build cli for another purpose, then what?
Buy Instead of Build
There are a number of command line argument parsers that I've used over the years. Honestly they all have just have different ways to setting up what you want your arguments to look like - read as: they all enable friction differently. The one I usually come back to is Mono.Options but I've also visited Fluent Command Line Parser and commandline. There is also CLAP and OptGet. Tharga Console is an interesting one in that there is an interactive mode you can 'trap' the user in your application to repeat shorter commands with specific context.
Now Shipping with .NET
If you bring in the CommandLineUtils package in to your console application project you'll have yet another way to parse those arguments for your application logic to consume.
You start off by creating a application (context) to work from
const string Help = "-? | -h | --help";
var app = new CommandLineApplication(throwOnUnexpectedArg: false) {
Name = "motocli"
};
app.HelpOption(Help);
app.Execute(args);
This is the core of the application that wires up the help system.
» .\motocli -h
Usage: motocli [options]
Options:
-? | -h | --help Show help information
When you start to add application commands
var brosweCommand = app.Command("browse", config =>
{
config.Description = "launches the default browser to motowiliams.com";
config.HelpOption(Help);
});
More of the help system lights up with information on how to use the utility.
» .\motocli --help
Usage: motocli [options]
Options:
-? | -h | --help Show help information
You can also chain more commands together by adding another command to an existing command.
var brosweCommand = app.Command("browse", config =>
{
config.Description = "launches the default browser to motowiliams.com";
config.HelpOption(Help);
});
brosweCommand.Command("paypal", config =>
{
config.Description = "launches the default browser to Eric's donation page";
config.HelpOption(Help);
});
This will enable the execution of this block by issuing motocli browse pay-pal
and it can also show its "sub-help"
» .\motocli browse --help
Usage: motocli browse [options] [command]
Options:
-? | -h | --help Show help information
Commands:
paypal launches the default browser to Eric's donation page
Use "browse [command] --help" for more information about a command.
Speaking of executing, nothing is really happening at this point. We have an application that show cli help and nothing more. When you add a OnExecute to your configuration then you get a hook to actually execute some code.
If we update our browse command
var brosweCommand = app.Command("browse", config =>
{
config.Description = "launches the default browser to motowiliams.com";
config.HelpOption(Help);
config.OnExecute(() =>
{
Console.WriteLine("Spinning up the Http Hyper-Drive. Destination motowilliams.com");
return 0;
});
});
Changes the output to
» .\motocli browse
Spinning up the Http Hyper-Drive. Destination motowilliams.com
The Command Option class is where you enable dash dash options
to your cli arguments. These could be no value such as a --debug
, a single value like --connectionString database=foo;server=bar
or multiple values, --tags foo --tags bar
In this case, adding the debugOption
configuration options with a shortname will let use us access more things that can be set in the command line.
var brosweCommand = app.Command("browse", config =>
{
config.Description = "launches the default browser to motowiliams.com";
config.HelpOption(Help);
var debugOption = config.Option("--debug", "debug flag to show more output", CommandOptionType.NoValue);
debugOption.ShortName = "d";
config.OnExecute(() =>
{
Console.WriteLine("Spinning up the Http Hyper-Drive. Destination motowilliams.com");
if (debugOption.HasValue())
{
System.Console.WriteLine($" - taking off");
System.Console.WriteLine($" - plotting course");
System.Console.WriteLine($" - ... punch it!");
}
return 0;
});
});
Again without the -d
flag
» .\motocli browse
Spinning up the Http Hyper-Drive. Destination motowilliams.com
and with the -d
flag we get more detail to our travel log
» .\motocli browse -d
Spinning up the Http Hyper-Drive. Destination motowilliams.com
- taking off
- plotting course
- ... punch it!
Finding the right feel for your arguments is, probably, the most challenging part. You want to use the arguments and options in the right balance. Look at other cli tools and across platforms to see what are the common threads.
I hope you found this useful.