dotnet / dotnet/command-line-api

Fluent syntax for command creation

Open
#2,494 1 comment 0 reactions 0 assignees View on GitHub
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.