Attribute-driven layer on top of System.CommandLine to make the most common use cases easier to set up.
A minimal example, using DI to instantiate command objects:
<ItemGroup>
<PackageReference Include="DeclarativeCommandLine" Version="2.0.0-g498c5dd86c" />
<PackageReference Include="DeclarativeCommandLine.Generator" Version="2.0.0-g498c5dd86c" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.9"/>
</ItemGroup>
using DeclarativeCommandLine;
using Microsoft.Extensions.DependencyInjection;
namespace MyApp;
[Command(Description = "Math commands")]
public class AppRootCommand
{
}
[Command(Description = "Add 2 numbers", Parent = typeof(AppRootCommand))]
public class AddCommand : ICommand
{
[Option(Required = true)]
public int ValueA { get; set; }
[Option(Required = true)]
public int ValueB { get; set; }
public void Execute()
{
Console.WriteLine($"A={ValueA} + {ValueB} = {ValueA + ValueB}");
}
}
public static class Program
{
public static int Main(string[] args)
{
var p = new ServiceCollection()
.AddTransient<AppRootCommand>()
.AddTransient<AddCommand>()
.BuildServiceProvider();
return new CommandBuilder()
.Build(t => p.GetRequiredService(t))
.Parse(args)
.Invoke();
}
}
$ ./myapp
Required command was not provided.
Description:
Usage:
myapp [command] [options]
Options:
-?, -h, --help Show help and usage information
--version Show version information
Commands:
add
$ ./myapp add
Option '--value-a' is required.
Option '--value-b' is required.
Description:
Usage:
myapp add [options]
Options:
--value-a <value-a> (REQUIRED)
--value-b <value-b> (REQUIRED)
-?, -h, --help Show help and usage information
$ ./myapp add --value-a 20 --value-b 22
A=20 + 22 = 42
This is what the source generator has written, based on the attribute-annotated classes:
/// <auto-generated/>
using DeclarativeCommandLine;
using System;
using System.CommandLine;
namespace MyApp
{
public partial class CommandBuilder
{
public virtual RootCommand Build(Func<Type, object> serviceProvider)
{
var cmd1 = new RootCommand();
cmd1.Hidden = false;
// global::MyApp.AddCommand
{
var cmd2 = new Command("add");
cmd1.Add(cmd2);
cmd2.Hidden = false;
// Option --value-a
var opt3 = new Option<Int32>("--value-a");
{
cmd2.Add(opt3);
opt3.Description = "";
opt3.Hidden = false;
opt3.Required = true;
}
// Option --value-b
var opt4 = new Option<Int32>("--value-b");
{
cmd2.Add(opt4);
opt4.Description = "";
opt4.Hidden = false;
opt4.Required = true;
}
cmd2.SetAction(async (parseResult, ct) =>
{
var cmd2Inst = (global::MyApp.AddCommand)serviceProvider(typeof(global::MyApp.AddCommand));
cmd2Inst.ValueA = parseResult.GetValue(opt3);
cmd2Inst.ValueB = parseResult.GetValue(opt4);
if (cmd2Inst is IAsyncCommandWithParseResult cmd2001)
{
await cmd2001.ExecuteAsync(parseResult, ct).ConfigureAwait(false);
}
if (cmd2Inst is IAsyncCommand cmd2002)
{
await cmd2002.ExecuteAsync(ct).ConfigureAwait(false);
}
if (cmd2Inst is ICommand cmd2003)
{
cmd2003.Execute();
}
});
}
return cmd1;
}
}
}
- Action
- Arguments
- Description
- Hidden
- Name
- Options
- Subcommands
- Aliases
- Completions
- TreatUnmatchedTokensAsErrors
- Validators
- Description
- Name
- AcceptLegalFileNamesOnly
- AcceptLegalFilePathsOnly
- AcceptOnlyFromAmong
- Arity
- Completions
- Default
- HelpName
- Hidden
- Validators
- dir.Description
- dir.Hidden
- dir.Name
- opt.Description
- opt.Name
- opt.Required
- opt.AcceptLegalFileNamesOnly
- opt.AcceptLegalFilePathsOnly
- opt.AcceptOnlyFromAmong
- opt.Aliases
- opt.AllowMultipleArgumentsPerToken
- opt.Arity
- opt.Completions
- opt.DefaultValueFactory
- opt.HelpName
- opt.Hidden
- opt.Recursive
- opt.Validators