Open EifelMono opened 5 years ago
Thanks, @EifelMono! It's great to see people experimenting with the API.
Your fluent interface falls into the category of app models that build on the core System.CommandLine
library. Enabling people to build APIs like this is a goal of ours, but we're not currently including them in the this repo.
@KathleenDollard and I would be particularly interested to hear about your experience building on the core API.
Another option is using configuration callbacks combined with fluent builders. Topshelf is a good example of this in the .net world, and kotlin builders are essentially this with some syntactic sugar.
How would the team feel about just returning "this" from each of the Command methods to allow easy chaining? For example:
var root = new RootCommand()
.AddCommand(
new Command("foo")
.AddArgument(...)
.AddOption(...))
.AddCommand(
new Command("bar")
....)
);
That alone would make the API much more fluent-able than it currently is, since everything is void right now...
FWIW, it's probably not obvious enough that you can also use the existing API in a more declarative construction style:
var root = new RootCommand
{
new Command("add")
{
new Argument<string>("--name", () => config.GetString("cli", "name")!, "The name"),
new Option<long?>(new[] { "--size", "-s" }, () => config.GetNumber("cli", "size")!, "Default size"),
new Option<bool?>(new[] { "--enabled", "-e" }, () => config.GetBoolean("cli", "enabled"), "Enable?")
},
new Command("remove")
{
// more args/options
}
};
it's probably not obvious enough that you can also use the existing API in a more declarative construction style:
One cannot provide a CommandHandler
alongside some arguments or options using this declarative approach:.
static readonly RootCommand RootCommand = new()
{
Handler = CommandHandler.Create(Add),
Arguments = { new Argument<int>("number") }
// Error: IReadOnlyList<Argument> does not contain a definition for Add
};
static readonly RootCommand RootCommand = new()
{
Handler = CommandHandler.Create(Add),
Arguments = new[] { new Argument<int>("number") }
// Error: Property Arguments cannot be assigned to — it is read-only
};
Is it possible to add Command
and RootCommand
constructors allowing initializing of the Handler
property?
It is possible, but as we work out how to make an easy-to-use source generator, these two things might not go well together.
Here's the current state of the source generator: https://github.com/dotnet/command-line-api/pull/1498
It lets you write code like this and generates the plumbing to bind the arguments:
void Execute(string fullnameOrNickname, IConsole console, int age)
{
boundName = fullnameOrNickname;
boundConsole = console;
boundAge = age;
}
var nameArgument = new Argument<string>();
var ageOption = new Option<int>("--age");
var command = new Command("command")
{
nameArgument,
ageOption
};
command.SetHandler(Execute, nameArgument, ageOption);
You wouldn't be able to pass in the relevant arguments and options if the handler were in the constructor and you were also using collection initializer syntax.
This might be a reason to have the SetHandler
methods which the generator targets return the command in a fluent style.
@Keboo
The other thing to consider with this is where else you might need access to those arguments/options that get added to the command.
We have recently been moving the string-based matching to the delegate parameters into its own library. This had a performance hit as well as the source of confusion for many people. For the core library, the primary way to retrieve the values that were passed on the command line is to use the ParseResult.ValueFor*
methods and pass in the Argument/Option.
With the source generator, it uses the SetHandler
method as a trigger to know what to generate. Because the types need to be known at compile-time, the current implementation would not work well with a fluent syntax (will need to think more on how that might work).
What I suspect, is that a fluent style syntax is likely something that would end up being similar to the string-based naming convention, or source generator, where it is an additional library on top of the core library. I would imagine the critical pieces will be figuring out the link between a command's version child symbols (arguments/options) and the command's handler. The examples above appear to rely on the string-based matching (which is fine). The other thing we have been playing around with are ways to make declaring the parser (the commands + options + arguments) in a simpler way (sort of side stepping the request here). One implementation you can see is DragonFruit (which has its own set of limitations as well).
Hello everybody,
I have written a fluent interface for the System.CommandLine
Below is the sample from your wiki in fluent form.
More doc, code, tests and samples can be found on my github side
I would like to bring this in these project if you think it is useful for others and fits in this project