Argopt: Command line argument parsing in .NET

This article was originally published in my blog (affectionately referred to as blargh) on . The original blog no longer exists as I've migrated everything to this wiki.

The original URL of this post was at https://tmont.com/blargh/2011/5/argopt-command-line-argument-parsing-in-net. Hopefully that link redirects back to this page.

So, I was writing a console app in C# the other day, and I needed to parse some command line arguments. A quick google search gave me NDesk.Options, which I've used before. However, it's quite old. Whenever I see a .NET project that talks about using "new" features like lambda expressions, I get a little wary. It also doesn't help that the author and maintainer of the project claims it is UNSTABLE, and hasn't been updated since 2008.

So, I wrote my own.

This isn't new territory for me: I wrote one in PHP before the dawn of PHP 5.3 and platform-independent getopt. And I thought it would be fun.

I also didn't really like the API of NDesk: too many magic strings, and the code was very imperative. I want objects and generics and attributes and angle brackets, dammit.

Introducing Argopt, another command line argument parsing library. And shut up, I like the name. It sounds like a Greek warrior.

What you do is:

  1. define the command line arguments in a class (a contract)
  2. annotate the properties in that class with some special attributes
  3. Argopt uses that class definition to figure out how to parse things and returns an instance of that class with the command line values injected

Here's the example from the main site:

C♯
using System;
using Argopt;

// this will handle things like:
//   greeting.exe --names=Bill,Hillary,Barack,Michelle -r 1 "What a nice day"
//   greeting.exe -n Bill,Hillary,Barack,Michelle --repeat=1 "What a nice day "
//   greeting.exe /Names:Bill,Hillary,Barack,Michelle /r 1 "What a nice day"
public class MyContract {
	[ValueProperty]
	[Description("The greeting to display")]
	public string Greeting { get; set; }
	
	[Alias("r")]
	[Description("The number of times to repeat the greeting", ValueName = "times")]
	public int Repeat { get; set; }
	
	[Delimited(","), Alias("n")]
	[Description("The names of the people to greet", Required = true, ValueName = "name1,name2...")]
	public string[] Names { get; set; }
}

class Program {
	static void Main(string[] args) {
		var result = OptionParser.Parse<MyContract>(args);
		if (!result.IsValid) {
			foreach (var error in result.Errors) {
				Console.WriteLine(error.ThrownException);
			}
			
			return;
		}
		
		var contract = result.Contract; // this is an instance of MyContract
		
		if (contract.Names.Length == 0 || string.IsNullOrEmpty(contract.Greeting)) {
			// print usage
			Console.WriteLine(OptionParser.GetDescription<MyContract>());
			return;
		}
		
		foreach (var name in contract.Names) {
			for (var i = 0; i < contract.Repeat; i++) {
				Console.WriteLine(string.Format("{0}, {1}!", contract.Greeting, name));
			}
		}
	}
}

More information can be found at the website: argopt.tmont.com.