Open WojciechNagorski opened 5 years ago
There isn't an equivalent of the UnParser
that this leverages, although we should be able to build one using the upcoming binding infrastructure (#390).
Would you imagine a common way to model examples? A common way to ask a program for examples (analogous to / related to -h
)?
A common way to ask a program for examples is -h
or --help
. It should be presented like this:
Program description.
Usage:
naval_fate.exe ship new <name>...
naval_fate.exe ship <name> move <x> <y> [--speed=<kn>]
naval_fate.exe ship shoot <x> <y>
naval_fate.exe mine (set|remove) <x> <y> [--moored | --drifting]
naval_fate.exe (-h | --help)
naval_fate.exe --version
Options:
-h --help Show this screen.
--version Show version.
--speed=<kn> Speed in knots [default: 10].
--moored Moored (anchored) mine.
--drifting Drifting mine.
docopt did great job. You have to see video about docopt on the page http://docopt.org/ if you didn't see yet. In docopt
you just write help message (in string) like above and then this text is parsed.
namespace NavalFate
{
internal class Program
{
private const string usage = @"Naval Fate.
Usage:
naval_fate.exe ship new <name>...
naval_fate.exe ship <name> move <x> <y> [--speed=<kn>]
naval_fate.exe ship shoot <x> <y>
naval_fate.exe mine (set|remove) <x> <y> [--moored | --drifting]
naval_fate.exe (-h | --help)
naval_fate.exe --version
Options:
-h --help Show this screen.
--version Show version.
--speed=<kn> Speed in knots [default: 10].
--moored Moored (anchored) mine.
--drifting Drifting mine.
";
private static void Main(string[] args)
{
var arguments = new Docopt().Apply(usage, args, version: "Naval Fate 2.0", exit: true);
foreach (var argument in arguments)
{
Console.WriteLine("{0} = {1}", argument.Key, argument.Value);
}
}
}
}
I think that this is simple and great idea. System.CommandLine
should have the same layer:
namespace NavalFate
{
internal class Program
{
private const string usage = @"Naval Fate.
Usage:
naval_fate.exe ship new <name>...
naval_fate.exe ship <name> move <x> <y> [--speed=<kn>]
naval_fate.exe ship shoot <x> <y>
naval_fate.exe mine (set|remove) <x> <y> [--moored | --drifting]
naval_fate.exe (-h | --help)
naval_fate.exe --version
Options:
-h --help Show this screen.
--version Show version.
--speed=<kn> Speed in knots [default: 10].
--moored Moored (anchored) mine.
--drifting Drifting mine.
";
private static void Main(string[] args)
{
var parser = new CommandLineBuilder()
.ConfigureFromUsage(usage)
.UseParseDirective()
.UseDebugDirective()
.UseSuggestDirective()
.RegisterWithDotnetSuggest()
.UseTypoCorrections()
.UseParseErrorReporting()
.UseExceptionHandler()
.CancelOnProcessTermination()
.Build();
var arguments = new parser.Parse(args);
foreach (var argument in arguments)
{
Console.WriteLine("{0} = {1}", argument.Key, argument.Value);
}
}
}
}
Or/And even strong type approach, like @KathleenDollard did in Jackfruit
https://github.com/dotnet/command-line-api/pull/363 :
namespace NavalFate
{
public class CommandLineHandler
{
public class Ship
{
public async Task<int> New(string name) => await Task.FromResult(42); // actually do stuff
public async Task<int> Move(string name, int x, int y, dobule speed) => await Task.FromResult(42); // actually do stuff
public async Task<int> Shoot(string name, int x, int y) => await Task.FromResult(42); // actually do stuff
}
public class Mine
{
[...]
}
[...]
}
internal class Program
{
private const string usage = @"Naval Fate.
Usage:
naval_fate.exe ship new <name>...
naval_fate.exe ship <name> move <x> <y> [--speed=<kn>]
naval_fate.exe ship shoot <x> <y>
naval_fate.exe mine (set|remove) <x> <y> [--moored | --drifting]
naval_fate.exe (-h | --help)
naval_fate.exe --version
Options:
-h --help Show this screen.
--version Show version.
--speed=<kn> Speed in knots [default: 10].
--moored Moored (anchored) mine.
--drifting Drifting mine.
";
private static Task<int> Main(string[] args)
{
var parser = new CommandLineBuilder()
.ConfigureFromUsage(usage)
.UseParseDirective()
.UseDebugDirective()
.UseSuggestDirective()
.RegisterWithDotnetSuggest()
.UseTypoCorrections()
.UseParseErrorReporting()
.UseExceptionHandler()
.CancelOnProcessTermination()
.Build();
return await parser.Parse<CommandLineHandler>(args);
}
}
}
Hmm nice, also does nuget.exe use a version of this yet? Also I provide my own command parser for my newsmake however I would like to see if I can in fact move it over to this and not loose anything that my custom one provides (like how I register commands) But how I can register command groups should in fact be different completely.
@AraHaan If you try switching your parser, please let us know how the experience goes and what missing features you might run into.
I am also switching my parser currently from CommandLineParser
to this one, mostly because I need an easy way to have multiple layers of subcommands, which is not easily supported in CommandLineParser
.
The biggest thing I am missing here is exactly the Example/Usage support for the help.
Since I see there is not progress in that since the past two years, I guess it won't be coming soon?
Don't get me wrong, I am not complaining, your library is great. I am just honestly asking if there is some plan to add this.
@jonsequitur Welp I moved my newsmake to System.CommandLine, however it seems currently that a few things are missing:
I usually take security diagnostics on the analyzer seriously so I wold like a way for these above to happen would be nice, or at least a place where we can set in the case of if an exception handler gets triggered the error code we want and if the library sees it as non-zero return that code instead of the code returned from the command handler(s).
To get help out directly, you can do this:
using System.CommandLine;
using System.CommandLine.IO;
using System.CommandLine.Help;
var command = new RootCommand
{
new Option<int>("-i", "An option"),
new Option<string[]>("some-args", "An argument"),
};
var console = new TestConsole();
new HelpBuilder(console).Write(command);
console.Out.ToString()
which outputs, e.g.:
Microsoft.DotNet.Interactive.App:
myapp
Usage:
Microsoft.DotNet.Interactive.App [options]
Options:
-i <i> An option
some-args <some-args> An argument
To get help out directly, you can do this:
using System.CommandLine; using System.CommandLine.IO; using System.CommandLine.Help; var command = new RootCommand { new Option<int>("-i", "An option"), new Option<string[]>("some-args", "An argument"), }; var console = new TestConsole(); new HelpBuilder(console).Write(command); console.Out.ToString()
which outputs, e.g.:
Microsoft.DotNet.Interactive.App: myapp Usage: Microsoft.DotNet.Interactive.App [options] Options: -i <i> An option some-args <some-args> An argument
From within the commad's handler?
Yes. And in the handler you would probably want to use an IConsole
parameter if you don't want to have to use the TestConsole
.
Yes. And in the handler you would probably want to use an
IConsole
parameter if you don't want to have to use theTestConsole
.
And I can get the command
instance of the command
that the handler is invoked for too?
Yes.
using System.CommandLine;
using System.CommandLine.Help;
using System.CommandLine.Parsing;
using System.CommandLine.Invocation;
var subcommandOne = new Command("one");
var subcommandTwo = new Command("two");
var rootCommand = new RootCommand
{
subcommandOne,
subcommandTwo
};
var handler = CommandHandler.Create<InvocationContext, IConsole>((ctx, console) => {
var helpBuilder = new HelpBuilder(console);
helpBuilder.Write(ctx.ParseResult.CommandResult.Command);
});
subcommandOne.Handler = handler;
subcommandTwo.Handler = handler;
await rootCommand.InvokeAsync("one");
CommandLineParser
supports of printing examples of program usage: There is examples ot this feature in BenchmarkDotNet:https://github.com/dotnet/BenchmarkDotNet/blob/adde64cbbcde01938d6b2772066852c4f6c0e88d/src/BenchmarkDotNet/ConsoleArguments/CommandLineOptions.cs#L151-L178
Is there an equivalent of this function in `System.Command Line'?