DragonFruit and System.CommandLine is a new way to think about .NET Console apps
There's some interesting stuff quietly happening in the "Console App" world within open source .NET Core right now. Within the https://github.com/dotnet/command-line-api repository are three packages:
- System.CommandLine.Experimental
- System.CommandLine.DragonFruit
- System.CommandLine.Rendering
These are interesting experiments and directions that are exploring how to make Console apps easier to write, more compelling, and more useful.
The one I am the most infatuated with is DragonFruit.
Historically Console apps in classic C look like this:
#include <stdio.h>
int main(int argc, char *argv[])
{
printf("Hello, World!\n");
return 0;
}
That first argument argc is the count of the number of arguments you've passed in, and argv is an array of pointers to 'strings,' essentially. The actual parsing of the command line arguments and the semantic meaning of the args you've decided on are totally on you.
C# has done it this way, since always.
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
It's a pretty straight conceptual port from C to C#, right? It's an array of strings. Argc is gone because you can just args.Length.
If you want to make an app that does a bunch of different stuff, you've got a lot of string parsing before you get to DO the actual stuff you're app is supposed to do. In my experience, a simple console app with real proper command line arg validation can end up with half the code parsing crap and half doing stuff.
myapp.com someCommand --param:value --verbose
The larger question - one that DragonFruit tries to answer - is why doesn't .NET do the boring stuff for you in an easy and idiomatic way?
From their docs, what if you could declare a strongly-typed Main
method? This was the question that led to the creation of the experimental app model called "DragonFruit", which allows you to create an entry point with multiple parameters of various types and using default values, like this:
static void Main(int intOption = 42, bool boolOption = false, FileInfo fileOption = null) { Console.WriteLine($"The value of intOption is: {intOption}"); Console.WriteLine($"The value of boolOption is: {boolOption}"); Console.WriteLine($"The value of fileOption is: {fileOption?.FullName ?? "null"}"); }
In this concept, the Main method - the entry point - is an interface that can be used to infer options and apply defaults.
using System;
namespace DragonFruit
{
class Program
{
/// <summary>
/// DragonFruit simple example program
/// </summary>
/// <param name="verbose">Show verbose output</param>
/// <param name="flavor">Which flavor to use</param>
/// <param name="count">How many smoothies?</param>
static int Main(
bool verbose,
string flavor = "chocolate",
int count = 1)
{
if (verbose)
{
Console.WriteLine("Running in verbose mode");
}
Console.WriteLine($"Creating {count} banana {(count == 1 ? "smoothie" : "smoothies")} with {flavor}");
return 0;
}
}
}
I can run it like this:
> dotnet run --flavor Vanilla --count 3
Creating 3 banana smoothies with Vanilla
The way DragonFruit does this is super clever. During the build process, DragonFruit changes this public strongly typed Main to a private (so it's not seen from the outside - .NET won't consider it an entry point. It's then replaced with a Main like this, but you'll never see it as it's in the compiled/generated artifact.
public static async Task<int> Main(string[] args)
{
return await CommandLine.ExecuteAssemblyAsync(typeof(AutoGeneratedProgram).Assembly, args, "");
}
So DragonFruit has swapped your Main for its smarter Main and the magic happens! You'll even get free auto-generated help!
DragonFruit:
DragonFruit simple example program
Usage:
DragonFruit [options]
Options:
--verbose Show verbose output
--flavor <flavor> Which flavor to use
--count <count> How many smoothies?
--version Display version information
If you want less magic and more power, you can use the same APIs DragonFruit uses to make very sophisticated behaviors. Check out the Wiki and Repository for more and perhaps get involved in this open source project!
I really like this idea and I'd love to see it taken further! Have you used DragonFruit on a project? Or are you using another command line argument parser?
Sponsor: Ossum unifies agile planning, version control, and continuous integration into a smart platform that saves 3x the time and effort so your team can focus on building their next great product. Sign up free.
About Scott
Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.
About Newsletter
I really like it. It sort of creates an object of your parameters and you don't have to do any of the parsing yourself.
It supports verbs in addition to just options and is well supported. Give it a try.
In my experience, a simple console app with real proper command line arg validation can end up with half the code parsing crap and half doing stuff.Boy, can it. That's one of the things I always really loved about PowerShell cmdlets; much of the parameter system is handled for you.
How do you make commands like dotnet add with DragonFruit?
I find the verb mapping to strongly typed parameter classes approach pretty intuitive.
This looks interesting, but it seems to serve a smaller set of usecases, for example, it doesn't appear like you could write something larger like the Git command line, or the dotnet command line with this, as it doesn't support
[exe] [action] [parameters]style invocations like
git add -f ."
Which you can do with ManyConsole / CommandLineParser.
If they can somehow add support for accepting strongly typed verb classes as parameters and parsing that way, Dragonfruit would be truly excellent I think!
It has suited me very well for many years and I always recommend it to other developers who I find looking for a command line parser. It hasn't been updated for a while but it supports .NET core and has all the features that I have needed, so if something is feature complete, why change it?
1. Microsoft.Extensions.CommandLineUtils https://msdn.microsoft.com/en-us/magazine/mt763239.aspx
2. System.CommandLine (I guess, DragonFruit is built on top of it) https://msdn.microsoft.com/en-us/magazine/mt833289.aspx.
It would be interesting to see an official guidance from MS regarding command line applications and frameworks (and whether they will converge at some point).
void Main(string[] args)
{
var ic = new InstallContext(null,args);
...
And so, I've never had the need to write parsing code of my own.
Now, with the move to using dashes, or double dashes, I for sure will not write it myself. Time to go and check out what's out there.
2. Need to see example of how to limit command arguments to well known subsets of the data type for the command argument. Consider these command lines:
compress -Level 5 x.txt -Output x.compressed
Where -Level can have values from 1 to 9
compress -Method GroupIV a.png -Output a.tiff
Where -Method can be LZW, GroupIV, Jbig
And an example of compound multi-argument validation where "-Level X -Rate 21" is allowed only if -Level and -Rate are supplied but -Level by itself is not allowed and -Rate by itself is not allowed.
Comments are closed.
The only problem with these types of frameworks is that they are often not extensible enough to allow you to replace the default text rendering with one that uses Colorful.Console or some other custom text.
This does look cool for a quick app though.