With Command Line Utils we are up for an exception.
I am going to break my self-imposed rules for code that I use. It passes the NuGet package filter but there is no project page to get from the package. That would be an automatic “thanks, but no, thanks” but I was curious enough to see what it might come from a Microsoft owned repository that I bit the proverbial bullet.
This a Microsoft.* extension, but there is no project page, no documentation, no intellisense comments and a single test. Am I totally out of my mind to even consider this as a contender?
I might well be, but when trying to find where it lived I bumped into this article in MSDN magazine the caught my attention.
As opposed to majority of the frameworks seen so far, it does not use the concept of member annotations to generate the commands, options and arguments. It uses a “proper” object model (although there are hints of DSL-nesh in the language used to define options and arguments) with classes (
CommandOption) that can be instantiated and configured with good-old methods or we can go the inheritance route to encapsulate command behavior as much as possible.
In any case, this different take and the fact that it comes from Microsoft and seems to be used by themselves (at least an incarnation of it) in their
dotnet cli is enough for me to look at despite its raw state.
Implementing our example
To show both styles of use, we have encapsulated the something command in its own class. Describe the command via properties and add options via the
.Option() method (the help option is a special one used to display detailed help):
Dispatching is simple: create a instance of
CommandLineApplication (or a subclass), add commands to the
.Commands collection (for the self-contained ones) or describe them using the
.Command() method (as we have done for the something-else command) and dispatch the execution with the
Not being attribute-driven we can easily tweak the messages to whichever language needs to be used in an easier manner than when attributes are used.
One thing to note is that there are two concepts: arguments or options. Think of options as the information that comes after a named parameter and think of arguments as the information to be supplied for the command without the need of a named argument. We have used an argument for something-else’s locations argument, whereas we have used an option for something’s location argument. The difference is the need to specify the
–l shorthand) when using options.
There is no framework built-in concept of a mandatory argument as far as I can see. Which means that the burden of checking for its existence is laid upon the framework user. I have sketched how one could do it in the
Something class, but I think the framework should provide support for that scenario .
Unmapped arguments will make an error appear but the behavior can be changed.
Another area where the framework falls very short is the conversion of textual argument values into typed values. Again, I included a very pedestrian way of doing in the
Something class, but I am very disappointed the user is forced to do that sort of plumbing code.
Same history with default values.
Multi-valued arguments are supported by declaring the option as
CommandOptionType.MultipleValue or the argument with the
.Multiple property set to
For more complete help (samples, remarks,…) the
.ExtendedHelpText can be populated.
Running the program with the
-–help option (which we have defined with the
.HelpOption() method) a list of supported commands:
And using the
--help option on a command, drills down to the command-level documentation:
Commands behavior can be defined using the
.OnExecute() method that takes the piece of code (synchronous or async) to be executed when the command is specified.
There does not seem to be a hook to get into the command object creation, but dependencies could be “manually” injected when creating command instances.
Command Line Utils offers a new object-oriented take on the panorama of cli frameworks. Despite coming straight from the horse’s mouth, it is extremely rough on the edges (and beyond them) but has the advantage of netcore portability. As a matter of fact, it is a parameter I have not evaluated so far but I guess that if no other nicer framework is portable, one might consider this one knowing that one is pretty much on his own (or can use and contribute to CliHelpers) and sleeves are going to be rolled
Let’s wait and see how it evolves, but I would not hold my breath with this one.