An open API service indexing awesome lists of open source software.

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.

Awesome Lists containing this project

README

          

# Declarative Command Line

[![WTQ CI](https://github.com/flyingpie/declarative-command-line/actions/workflows/ci.yml/badge.svg)](https://github.com/flyingpie/declarative-command-line/actions/workflows/ci.yml)

[![Nuget](https://img.shields.io/nuget/vpre/DeclarativeCommandLine.svg)](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