https://github.com/flyingpie/declarative-command-line
Attribute-driven layer on top of System.CommandLine to make the most common use cases easier to set up.
https://github.com/flyingpie/declarative-command-line
command-line dotnet
Last synced: 6 months ago
JSON representation
Attribute-driven layer on top of System.CommandLine to make the most common use cases easier to set up.
- Host: GitHub
- URL: https://github.com/flyingpie/declarative-command-line
- Owner: flyingpie
- License: mit
- Created: 2023-01-13T22:14:40.000Z (over 3 years ago)
- Default Branch: master
- Last Pushed: 2025-12-25T12:05:53.000Z (6 months ago)
- Last Synced: 2025-12-27T00:09:02.155Z (6 months ago)
- Topics: command-line, dotnet
- Language: C#
- Homepage:
- Size: 154 KB
- Stars: 1
- Watchers: 1
- Forks: 0
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# Declarative Command Line
[](https://github.com/flyingpie/declarative-command-line/actions/workflows/ci.yml)
[](https://nuget.org/packages/DeclarativeCommandLine)
Attribute-driven layer on top of [System.CommandLine](https://github.com/dotnet/command-line-api) to make the most common use cases easier to set up.
## Minimalistic Example
A minimal example, using DI to instantiate command objects:
### Add NuGet Packages
2 packages are needed:
- [DeclarativeCommandLine](https://www.nuget.org/packages/DeclarativeCommandLine): Contains attributes used to decorate commands, options and arguments;
- [DeclarativeCommandLine.Generator](https://www.nuget.org/packages/DeclarativeCommandLine.Generator): The source generator that actually constructs the System.CommandLine client code. Only used on compile time.
```xml
```
### Program.cs
```cs
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()
.AddTransient()
.BuildServiceProvider();
return new CommandBuilder()
.Build(t => p.GetRequiredService(t))
.Parse(args)
.Invoke();
}
}
```
### Result
```bash
$ ./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
```
```bash
$ ./myapp add
Option '--value-a' is required.
Option '--value-b' is required.
Description:
Usage:
myapp add [options]
Options:
--value-a (REQUIRED)
--value-b (REQUIRED)
-?, -h, --help Show help and usage information
```
```bash
$ ./myapp add --value-a 20 --value-b 22
A=20 + 22 = 42
```
### Generated
This is what the source generator has written, based on the attribute-annotated classes:
```cs
///
using DeclarativeCommandLine;
using System;
using System.CommandLine;
namespace MyApp
{
public partial class CommandBuilder
{
public virtual RootCommand Build(Func 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("--value-a");
{
cmd2.Add(opt3);
opt3.Description = "";
opt3.Hidden = false;
opt3.Required = true;
}
// Option --value-b
var opt4 = new Option("--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;
}
}
}
```
## Progress
### Command
- [x] Action
- [x] Aliases
- [x] Arguments
- [x] Description
- [x] Hidden
- [x] Name
- [x] Options
- [x] Subcommands
- [ ] Completions
- [ ] TreatUnmatchedTokensAsErrors
- [ ] Validators
### Arguments
- [x] AcceptOnlyFromAmong
- [x] Default
- [x] Description
- [x] Name
- [ ] AcceptLegalFileNamesOnly
- [ ] AcceptLegalFilePathsOnly
- [ ] Arity
- [ ] Completions
- [ ] HelpName
- [ ] Hidden
- [ ] Validators
### Directives
- [ ] dir.Description
- [ ] dir.Hidden
- [ ] dir.Name
### Option
- [x] opt.AcceptOnlyFromAmong
- [x] opt.Aliases
- [x] opt.DefaultValueFactory
- [x] opt.Description
- [x] opt.Hidden
- [x] opt.Name
- [x] opt.Required
- [ ] opt.AcceptLegalFileNamesOnly
- [ ] opt.AcceptLegalFilePathsOnly
- [ ] opt.AllowMultipleArgumentsPerToken
- [ ] opt.Arity
- [ ] opt.Completions
- [ ] opt.HelpName
- [ ] opt.Recursive
- [ ] opt.Validators