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:
2 packages are needed:
- DeclarativeCommandLine: Contains attributes used to decorate commands, options and arguments;
- DeclarativeCommandLine.Generator: The source generator that actually constructs the System.CommandLine client code. Only used on compile time.
<ItemGroup>
<PackageReferenceInclude="DeclarativeCommandLine"Version="2.0.4" />
<PackageReferenceInclude="DeclarativeCommandLine.Generator"Version="2.0.4" />
<PackageReferenceInclude="Microsoft.Extensions.DependencyInjection"Version="9.0.9"/>
</ItemGroup>usingDeclarativeCommandLine;usingMicrosoft.Extensions.DependencyInjection;namespaceMyApp;[Command(Description="Math commands")]publicclassAppRootCommand{}[Command(Description="Add 2 numbers",Parent=typeof(AppRootCommand))]publicclassAddCommand:ICommand{[Option(Required=true)]publicintValueA{get;set;}[Option(Required=true)]publicintValueB{get;set;}publicvoidExecute(){Console.WriteLine($"A={ValueA} + {ValueB} = {ValueA+ValueB}");}}publicstaticclassProgram{publicstaticintMain(string[]args){varp=newServiceCollection().AddTransient<AppRootCommand>().AddTransient<AddCommand>().BuildServiceProvider();returnnewCommandBuilder().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 = 42This is what the source generator has written, based on the attribute-annotated classes:
/// <auto-generated/>usingDeclarativeCommandLine;usingSystem;usingSystem.CommandLine;namespaceMyApp{publicpartialclassCommandBuilder{publicvirtualRootCommandBuild(Func<Type,object>serviceProvider){varcmd1=newRootCommand();cmd1.Hidden=false;// global::MyApp.AddCommand{varcmd2=newCommand("add");cmd1.Add(cmd2);cmd2.Hidden=false;// Option --value-avaropt3=newOption<Int32>("--value-a");{cmd2.Add(opt3);opt3.Description="";opt3.Hidden=false;opt3.Required=true;}// Option --value-bvaropt4=newOption<Int32>("--value-b");{cmd2.Add(opt4);opt4.Description="";opt4.Hidden=false;opt4.Required=true;}cmd2.SetAction(async(parseResult,ct)=>{varcmd2Inst=(global::MyApp.AddCommand)serviceProvider(typeof(global::MyApp.AddCommand));cmd2Inst.ValueA=parseResult.GetValue(opt3);cmd2Inst.ValueB=parseResult.GetValue(opt4);if(cmd2InstisIAsyncCommandWithParseResultcmd2001){awaitcmd2001.ExecuteAsync(parseResult,ct).ConfigureAwait(false);}if(cmd2InstisIAsyncCommandcmd2002){awaitcmd2002.ExecuteAsync(ct).ConfigureAwait(false);}if(cmd2InstisICommandcmd2003){cmd2003.Execute();}});}returncmd1;}}}- Action
- Aliases
- Arguments
- Description
- Hidden
- Name
- Options
- Subcommands
- Completions
- TreatUnmatchedTokensAsErrors
- Validators
- AcceptOnlyFromAmong
- Default
- Description
- Name
- AcceptLegalFileNamesOnly
- AcceptLegalFilePathsOnly
- Arity
- Completions
- HelpName
- Hidden
- Validators
- dir.Description
- dir.Hidden
- dir.Name
- opt.AcceptOnlyFromAmong
- opt.Aliases
- opt.DefaultValueFactory
- opt.Description
- opt.Hidden
- opt.Name
- opt.Required
- opt.AcceptLegalFileNamesOnly
- opt.AcceptLegalFilePathsOnly
- opt.AllowMultipleArgumentsPerToken
- opt.Arity
- opt.Completions
- opt.HelpName
- opt.Recursive
- opt.Validators