dotnet / dotnet/command-line-api
Fluent syntax for command creation
- Dominant language
- C#
- Stars
- 3.7k
- Forks
- 428
- PR merge metrics
- No merged PRs in 30d
Description
System.CommandLine is a nice library that convers a lot of edge cases, but in my opinion creating commands (adding handlers) is quite verbose and adds a lot of boilerplate code to the main function.
What I was thinking is that maybe it could be possible to add some extension methods to make creating commands more simple. For example, in my project I have something like this:
```cs
private static async Task Main(string[] args)
{
var rootCommand = new RootCommand
{
new CommandBuilder("do-something", "Does something")
.WithOption("--type", CommandLineUtils.GetType)
.WithOption("--run-migrations")
.Build(DoSomethingAsync),
new CommandBuilder("do-another-thing", "Does another thing")
.WithArgument()
.Build(DoAnotherThingAsync),
};
return await rootCommand.InvokeAsync(args)
.ConfigureAwait(false);
}
private static async Task DoSomethingAsync(SomeType type, bool runMigrations)
{
await using var services = BuildServiceProvider();
if (runMigrations)
services.RunMigrations();
await services.GetRequiredService()
.DoSomethingAsync(type);
}
private static async Task DoAnotherThingAsync(string arg)
{
await using var services = BuildServiceProvider();
await services.GetRequiredService()
.DoSomethingElseAsync(arg);
}
```
The `CommandBuilder` is a class that encapsulated the boiler plate code. For example:
```cs
namespace System.CommandLine;
public sealed class CommandBuilder
{
internal readonly string Name;
internal readonly string Description;
public CommandBuilder(string name, string description)
{
Name = name;
Description = description;
}
public CommandHandler1Builder WithOption(in string name, ParseArgument parseArgument) =>
new(this, name, ArgType.Option, parseArgument);
}
```
And `CommandHandler1Builder`, `CommandHandler2Builder`, `CommandHandler3Builder`, etc. are used to create handlers, arguments and options. For example:
```cs
using System.CommandLine.Binding;
namespace System.CommandLine;
public sealed class CommandHandler1Builder : CommandHandlerBuilderBase
{
internal readonly CommandBuilder CommandBuilder;
private readonly string _name;
private readonly ParseArgument? _parseArgument;
internal CommandHandler1Builder(in CommandBuilder commandBuilder, in string name, in ArgType argType, in ParseArgument? parseArgument = null)
: base(argType)
{
CommandBuilder = commandBuilder;
_name = name;
_parseArgument = parseArgument;
}
public Command Build(Func handler)
{
var command = new Command(CommandBuilder.Name, CommandBuilder.Description);
var value = AppendValueDescriptor(command);
command.SetHandler(handler, value);
return command;
}
public CommandHandler2Builder WithOption(in string name) =>
new(this, name, ArgType.Option);
public CommandHandler2Builder WithArgument(in string name) =>
new(this, name, ArgType.Argument);
internal IValueDescriptor AppendValueDescriptor(in Command command) =>
AppendValueDescriptor(command, _name, _parseArgument);
}
```
```cs
using System.CommandLine.Binding;
namespace System.CommandLine;
public sealed class CommandHandler2Builder : CommandHandlerBuilderBase
{
private readonly CommandHandler1Builder _commandBuilder;
private readonly string _name;
internal CommandHandler2Builder(CommandHandler1Builder commandBuilder, string name, ArgType argType)
: base(argType)
{
_commandBuilder = commandBuilder;
_name = name;
}
public Command Build(Func handler)
{
var command = new Command(_commandBuilder.CommandBuilder.Name, _commandBuilder.CommandBuilder.Description);
var value1 = _commandBuilder.AppendValueDescriptor(command);
var option2 = AppendValueDescriptor(command);
command.SetHandler(handler, value1, option2);
return command;
}
private IValueDescriptor AppendValueDescriptor(in Command command) =>
AppendValueDescriptor(command, _name);
}
```
Would it be something that you can be interested in? If yes, I might work on this feature and prepare a PR.
Contributor guide
Assessment
This issue has not been assessed yet.