Scott Hanselman

DragonFruit and System.CommandLine is a new way to think about .NET Console apps

July 17, 2019 Comment on this post [17] Posted in DotNetCore
Sponsored By

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.

facebook twitter subscribe
About   Newsletter
Hosting By
Hosted in an Azure App Service
July 18, 2019 12:07
We like colourful ASCII art text in our Console apps right? Well, Colorful.Console implements that for you.

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.
July 18, 2019 12:50
I've been using Mono.Options for going on a decade now, with brief detours to the F# PowerPack parser and the PowerShell command-line parser.
July 18, 2019 13:37
I think Nate McMaster's library has evolved into the most natural, yet complete CLI parsing libraries today: https://github.com/natemcmaster/CommandLineUtils/blob/master/README.md#attribute-api
July 18, 2019 13:49
I always use ManyConsole, which uses Mono.Options.
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.
July 18, 2019 16:11
I've used CommandLine many times.

It supports verbs in addition to just options and is well supported. Give it a try.
July 18, 2019 16:36
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.
July 18, 2019 19:38
The best approach I saw so far is Python's Fire.

How do you make commands like dotnet add with DragonFruit?
July 18, 2019 22:22
I’ve been looking for exactly this! Anybody have any other good recommendations? I find it very hard to search for because googling “.net cli” comes with all the wrong things.
July 19, 2019 3:49
What if options are coming from a config file; is there some duplication of effort with the generic host model config?
July 19, 2019 5:05
I've had good success using the previously mentioned ManyConsole for older .NET Framework console apps, and lately with CommandLineParser for .NET Standard / .NET Core support.

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!
July 19, 2019 11:49
Just to add my recommendation to the list, I use CLAP - the .NET Command-Line Auto-Parser.

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?
July 19, 2019 12:05
Check out Konsole for some animated CLI goodness.
July 19, 2019 13:51
While I find DragonFruit a very cool project, we already have 2 command line processing frameworks for .Net written by Microsoft:
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).
July 20, 2019 0:01
I've used CommandLineParser for a few years. It works pretty well, but I think DragonFruit or the commandlineapi looks pretty cool and clean. Thanks for sharing!
July 20, 2019 17:48
For parsing switches and key-value pairs with forward slashes, System.Configuration.InstallContext has always sufficed for me.

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.
July 22, 2019 19:27
PowerArgs has always been my goto package for console apps. I'll be having a look at these new projects.
July 27, 2019 0:23
1. Really need an example showing how to parse command line arguments for a medium sized command such as the "cmd.exe Dir" command
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.
Bob

Comments are closed.

Disclaimer: The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.