CommandLineParser - .NET Arg Parsing Made Easy!
I’m a huge fan of command-line utilities (aka, console apps). In general, I find console apps to have much lower barriers to use than traditional window-based systems: in many respects, they’re much easier to build than other alternatives (such as Windows Forms and Windows Presentation Foundation apps), with generally lower barriers to entry. That said, I hate writing argument parsers. It’s not that it’s super-difficult, but it’s tedious, and for such a common task, I’d really like to use a library. I’m aware of a few, and found what is easily my favorite recently, CommandLineParser. In this post, I’ll explore what my preferences are, and why I’ve recently selected this one as the default when starting new .NET projects. I’ll also explore how I go about selecting new dependencies for projects that I’m working on.
Picking Dependencies
When working with data, we usually define simple classes that are serialized from some source, such as a database, web request, or other source. We could write our own parsers for such formats, but it’s not a good use of developer time. In general, if you’re repeating yourself in your code, it’s probably time to abstract. Parsing command-line arguments is no different. I personally want an abstraction that allows me to:
- Define an options class, just like any other DTO I would create for database integration or web development (potentially using Attributes)
- Have a simple API for converting my
string
arguments to my options object - Provide a simple, preferably integrated, convention for providing help text
Obviously, my stated objectives require Reflection. You could have an API, alternatively, that consists of a matching function and a callback to modify some program state. My issue with such a convention is that it’s really not abstracting very much of the activity of parsing my arguments, and I’d just as soon do it myself in a loop.
I was hunting around NuGet a few weeks ago for projects that might provide the things I was looking for in a single package. I employed some supplemental selection criteria as I was browsing the feed:
- Does the package have a project URL?
- Is the project hosted on Github?
- Is the project active?
- Do other people use this package?
- How is the project licensed?
- How good is the documentation?
Github has, for better or worse, become the de facto standard for hosting a ton of open-source projects. Even when a project is not actively developed on Github, it’s not uncommon for the project to have a Git mirror on Github for accessing the source code, as is the case for much of Android, as well as Git itself. I would never take away points from a project for choosing to use a different hosting provider for hosting source code, such as Gitlab, but I do like to see some activity on Github as I’m making a selection. Finally, if a project isn’t hosted on Github, that’s fine with me, but if there’s no URL available for me to do some investigation, I’m probably not going to take a dependency on the project.
Project activity is an important detail for me, because the majority of the work I do is for enterprise customers. In reality, if I’m taking a dependency on your project, my employer is very likely to depend on that project for much, much longer than I’ll be employed by them. That’s not to say that I plan on leaving my employers with any degree of relative frequency, but the reality is that software systems tend to be pretty sticky: once you’ve got a bunch of data in a database, and a software system has reached a large enough scale, it’s difficult to replace. The reality is that my decisions today will affect the organization in the future, and I want them to have options. This is part of why we choose dependencies over building ourselves in the first place.
Whether or not other people use a package is an important detail when I’m evaluating a package I’ve never used before. This is essentially how I vet new dependencies. This isn’t a non-starter, but I do find it re-assuring when I’m able to easily verify that others have already decided to use a package. The project’s license is less fluffy for me: if the project does not employ a permissive license, I’m not likely to use it.
The last piece of crtieria I mentioned is documentation. I want to access source code for a number of reasons, but learning how to use a dependency is simply not one of them. Documentation is actually critical, and I will not adopt a project if I feel the documentation is not good enough to use it. Once again, I work for enterprise customers, and have to prioritize the interests of the firm when I’m evaluating dependencies. For my own projects, I’m perfectly happy to have less documentation, and endure a certain amount of pain, but in many work environments, you have to contend with several additional factors:
- Minimum and average degree of experience on the team
- Developer turnover rate
I have no idea how long some of my projects will be around for. I’ve maintained and replaced systems that were 20 years old, and spent plenty of time reading VB6 just to support existing production systems. Hell, I even found that one of the dependencies used in a project I worked on had changed corporate ownership four times between when the project started and when I finally started managing it. This experience is why I try to be prudent in selecting dependencies.
CommandLineParser Benefits
As I was “shopping” around on NuGet, I found a few packages that looked really appealing to me, but ultimately settled on CommandLineParser as being the best fit for my needs. It met all of my stated requirements for a new package, as well as all my specific ideas about what a parsing package might provide. It also has a bunch of other features I wasn’t looking for, but certainly look attractive to me. Here’s a short list of additional things I was happy with:
- There’s a fairly functional API surface
- The package provides support for sub-commands (similar to Git)
Overall, I found this project to be a really good fit for all my use-cases. I won’t spend any time on presenting examples here, but that’s because I think their documentation is pretty great. If you’re looking to solve the problem of parsing arguments in your code, I’d highly recommend giving this project a run. It’s clean, the documentation is great, it’s been around for a long time, and is realy awesome to use.
It is worth noting that the dotnet team is working to add standard support to the framework to provide better parsing support. For more information, visit the command-line-api project.
Wrapping Up
This wasn’t the longest post I could’ve made on this topic, and in particular, I’m sorry that it wasn’t very focused. Instead of highlighting all the benefits of the CommandLineParser project, I spent a lot of time describing how I evaluate dependencies for my projects. I’ve decided to post this anyway, because I think it may be valuable for others on each side of the NuGet package table (producers and consumers), and I hope it’s provided some useful insight. As always, thanks for reading!
- Brian