Skip to content

Releases: mayuki/Cocona

v2.2.0

27 Mar 13:25
6e64076
Compare
Choose a tag to compare

What's Changed

New Contributors

Full Changelog: v2.1.0...v2.2.0

v2.1.0

08 Dec 06:45
e6db630
Compare
Choose a tag to compare

What's Changed

New Contributors

Full Changelog: v2.0.3...v2.1.0

v2.0.3

16 Feb 22:40
2937849
Compare
Choose a tag to compare

What's Changed

New Contributors

Full Changelog: v2.0.2...v2.0.3

v2.0.2

13 Jan 15:39
628d243
Compare
Choose a tag to compare

What's Changed

New Contributors

Full Changelog: v2.0.1...v2.0.2

v2.0.1

11 Jan 11:07
b6e230e
Compare
Choose a tag to compare

What's Changed

  • Add Korean (Republic of Korea; 韓國語) messages by @dahlia in #58

New Contributors

Full Changelog: v2.0.0...v2.0.1

v2.0.0

11 Jan 00:34
3f3f3ee
Compare
Choose a tag to compare

Features

Introduce Minimal API (#43, #45)

This release introduces ASP.NET Core-like Minimal API. It's an enhancement for .NET 6 and C# 10.

Minimal API supports most of the expressions that were possible with the previous Class-based style.
Please refer to the updated documentation for more details and see CoconaSample.MinimalApi.InAction.

CoconaApp.Run and CoconaApp.RunAsync

Run and RunAsync is a shortcut that makes it easy to build command line applications.

await CoconaApp.RunAsync((string? name) => Console.WriteLine($"Hello {(name ?? "Guest")}!"));

This is equivalent to the following code using Minimal API Builder.

var builder = CoconaApp.CreateBuilder(args);
var app = builder.Build();
app.AddCommand((string? name) => Console.WriteLine($"Hello {(name ?? "Guest")}!"));
app.Run();

CoconaApp.CreateBuilder() and CoconaApp.Create()

CreateBuilder and Create provide builders to configure services and register/configure multiple commands.

var builder = CoconaApp.CreateBuilder();
builder.Services.AddTransient<IMyService, MyService>();
builder.Configuration.AddJsonFile("path/to/additonalsettings.json");

var app = builder.Build();
app.AddCommand("hello", (string? name) => Console.WriteLine($"Hello {(name ?? "Guest")}!"));
app.AddSubCommand("server", x =>
{
    x.UseFilter(new RequirePrivilege());
    x.AddCommand("start", () => Console.WriteLine("Start"));
    x.AddCommand("stop", () => Console.WriteLine("Stop"));
});
app.AddSubCommand("client", x =>
{
    x.AddCommand("connect", () => Console.WriteLine("Connect"));
    x.AddCommand("disconnect", () => Console.WriteLine("Disconnect"));
});
app.AddComands<CommandType>();

await app.RunAsync();

Add a command and configure using the builder

var app = CoconaApp.Create(); // Shorthand for CoconaApp.CreateBuilder().Build();

// Add a command and configure.
app.AddCommand("hello", (string? name) => Console.WriteLine($"Hello {(name ?? "Guest")}!"))
    // Sets a description to the command.
    .WithDescription("Say hello");
    // Sets aliases to the command.
    .WithAliases(new[] { "hey" })
    // Sets a metadata (attribute) to the command.
    .WithMetadata(new HiddenAttribute());

// Use a user-defined command filter.
app.UseFilter(new MyFilterAttribute());

// Configure filters per command.
app.AddCommand("konnichiwa", (string name) => Console.WriteLine($"Konnichiwa {name}!"))
    .WithFilter(new MyFilterAttribute())
    .WithFilter(async (ctx, next) =>
    {
        if (Environment.UserName != "Administrator")
        {
            throw new CommandExitedException("Insufficient Permissions");
        }
        return next(ctx);
    })
    .CommandLikeOption(x =>
    {
        // CommandLikeOption: konnichiwa --info
        x.Add("info", () => Console.WriteLine("Show Information"));
    });
New APIs
  • CoconaAppBuilder
    • Services
    • Build()
  • CoconaApp (ICoconaCommandsBuilder)
    • Static methods
      • Create: Shorthand for CoconaApp.CreateBuilder().Build()
      • CreateBuilder: Creates CoconaAppBuilder instance.
      • CreateHostBuilder: Creates CoconaAppHostBuilder instance. (former CoconaApp.Create())
    • Instance methods
      • AddCommand(Delegate)
      • AddCommand(string, Delegate)
      • AddCommands(Type)
      • AddCommands<T>()
      • AddSubCommand(string, Action<ICoconaCommandsBuilder>)
      • UseFilter
      • Run / RunAsync

Introduce [Option(StopParsingOptions = true)] (#36)

[Option(StopParsingOptions = true)] enables to stop parsing options after a option on a command line.

public void A([Option]int a, [Option(StopParsingOptions = true)]string b, [Argument]string arg0, [Argument]string[] args)
{
   // $ ./myapp --a 123 --b valueB -c -d --e f
   // a = 123
   // b = "valueB"
   // arg0 = "-c"
   // args = new [] { "d", "--e", "f" }
}

Handle shutdown timeout (#57)

Cocona waits for a timeout before shutting down when canceling with Ctrl+C. This will allow you to perform a graceful shutdown.

Applied to Cocona

var builder = CoconaApp.CreateBuilder();
builder.Services.Configure<HostOptions>(options =>
{
     options.ShutdownTimeout = TimeSpan.FromSeconds(15);
});

Applied to Cocona.Lite

CoconaLiteApp.Create(options =>
{
    options.ShutdownTimeout = TimeSpan.FromSecond(15);
});

Localization (#44)

This release introduces localization mechanism for user-defined commands and add built-in ja-jp localization of messages.

image

New APIs

  • ICoconaLocalizer interface: Provides a localized description of the command. (see CoconaSample.Advanced.Localization)

Help Wanted: Contributions are welcome! 🤝

  • English: Since I (@mayuki) am not a native English speaker, this project has some problems such as lack of documentation and grammatical errors. Contributions of XML Doc comments, exception messages and README improvements would be welcome!
  • Non-English: Contributions of non-English language (ja-jp, ko-kr, ...) resources are also welcome!

Breaking changes

Disable support for shell completion by default (#50)

Support for Shell completion feature is now disabled by default.
If you want to continue to enable it, set the EnableShellCompletionSupport option to true.

CoconaApp Host APIs (#43)

  • Rename CoconaApp.Create() -> CoconaApp.CreateHostBuilder()
  • Rename IHostBuilder.UseCocona -> IHostBuilder.ConfigureCocona
  • Remove CommandTypes from CoconaAppOptions and CoconaLiteAppOptions

Changes

Improvements

  • Adopt to .NET 6 (#41)
  • Add scope support for ServiceProvider (#56)
  • Handle static methods marked as commands. (#35)

Fixes

  • Update NuGet package info (#55)
  • Throw exceptions in Run when building the application (#51)
  • Throw an exception if a sub-command name is duplicated (#52)

Full Changelog: v1.6.0...v2.0.0

v1.6.0

05 Aug 01:03
be855a6
Compare
Choose a tag to compare

Features

Introduce ParameterSet (#31)

Introduce a mechanism called Parameter set that defines common parameters for multiple commands.
For example, if every command receives a user name, host name, etc., it would be annoying to define them in a method for each command.

A class or record implements the ICommandParameterSet interface and treats it as a Parameter set.

By parameterized constructor (includes record class)

If a class (or record class) has a parameterized constructor, it is treated as part of the definition of a command method.

public record CommonParameters(
    [Option('t', Description = "Specifies the remote host to connect.")]
    string Host,
    [Option('p', Description = "Port to connect to on the remote host.")]
    int Port,
    [Option('u', Description = "Specifies the user to log in as on the remote host.")]
    string User = "root",
    [Option('f', Description = "Perform without user confirmation.")]
    bool Force = false
) : ICommandParameterSet;

public void Add(CommonParameters commonParams, [Argument] string from, [Argument] string to)
    => Console.WriteLine($"Add: {commonParams.User}@{commonParams.Host}:{commonParams.Port} {(commonParams.Force ? " (Force)" : "")}");

public void Update(CommonParameters commonParams, [Option('r', Description = "Traverse recursively to perform.")] bool recursive, [Argument] string path)
    => Console.WriteLine($"Update: {commonParams.User}@{commonParams.Host}:{commonParams.Port} {(commonParams.Force ? " (Force)" : "")}");

By properties (parameter-less constructor)

If a class has a parameter-less constructor, you can mark the public property as Option or Argument.

NOTE: Option defined as a property is treated as required by default. If you want a non-required Option to have a default value, mark it with HasDefaultValue attribute.

public class CommonParameters : ICommandParameterSet
{
    [Option('t', Description = "Specifies the remote host to connect.")]
    public string Host { get; set; }

    [Option('p', Description = "Port to connect to on the remote host.")]
    public int Port { get; set; }

    [Option('u', Description = "Specifies the user to log in as on the remote host.")]
    [HasDefaultValue]
    public string User  { get; set; } = "root";

    [Option('f', Description = "Perform without user confirmation.")]
    public bool Force  { get; set; } = false;
}

public void Add(CommonParameters commonParams, [Argument] string from, [Argument] string to)
    => Console.WriteLine($"Add: {commonParams.User}@{commonParams.Host}:{commonParams.Port} {(commonParams.Force ? " (Force)" : "")}");

public void Update(CommonParameters commonParams, [Option('r', Description = "Traverse recursively to perform.")] bool recursive, [Argument] string path)
    => Console.WriteLine($"Update: {commonParams.User}@{commonParams.Host}:{commonParams.Port} {(commonParams.Force ? " (Force)" : "")}");

Improvements

  • Adpto to .NET 5 (#30)

Fixes

  • Preserve the long named help option (#32)

v1.5.0

12 Jul 09:35
Compare
Choose a tag to compare

Features

Introduce HelpMessageBuilder (#22, #23)

Introduce HelpMessageBuilder, which makes is easy to help messages can be displayed from user code.

// Show help for commands. (same as `./HelpOnDemand --help`)
public void ForContext(bool optionA, [FromService] ICoconaHelpMessageBuilder helpMessageBuilder)
{
    Console.WriteLine(helpMessageBuilder.BuildAndRenderForCurrentContext());
}

// Show help for this command. (same as `./HelpOnDemand for-command --help`)
public void ForCommand(bool optionA, [FromService] ICoconaHelpMessageBuilder helpMessageBuilder)
{
    Console.WriteLine(helpMessageBuilder.BuildAndRenderForCurrentCommand());
}

v1.4.0

01 Jun 00:38
Compare
Choose a tag to compare

Features

Shell command-line completion support (#14, #15, #18)

Cocona provides support for shell command-line completion (also known as tab completion).

Currently, The supported shells are bash and zsh.

Tab shell completion

Cocona generates a shell script for command-line completion from a command definition and allows users to use command-line completion by loading it. The --completion built-in option is used to specify the name of a shell to generate a script.

$ source <(./myapp --completion bash)
or
% ./myapp --completion zsh > ~/.zsh/functions

This feature is enabled by default, or you can set the EnableShellCompletionSupport option to false if you don't need it.

It is also possible to dynamically generate command-line completion candidates and to prepare candidates at script generation time. Please see the sample below for more details.

Option-like commands (#17)

The option-like command is a way to achieve an independent command that at first glance, looks like an option in a command.

For example, easy to understand examples like --version and --help.
These are the options of a command, but they behave as a command when specified.

[OptionLikeCommand("hello", new[] {'f'}, typeof(Program), nameof(Hello))]
public void Execute()
    => Console.WriteLine("Execute");

private void Hello([Argument]string name)
    => Console.WriteLine($"Hello {name}!");
$ ./myapp
Execute

$ ./myapp --hello Alice
Hello Alice!

Limitations

  • Any previous options or arguments specified by OptionLikeCommand will be ignored.
    • Example: If --foo --bar --optionlikecommand --baz arg0 and --optionlikecommand is an Option-like command, the command will be passed --baz arg0.
  • Arguments are not displayed in help.

CommandMethodForwardedTo attribute (#19)

The CommandMethodForwardedTo attribute allows you to specify that the substance of the specified command method is a different method and that the operation should be forwarded.
If this attribute is given to a command method, the destination's attribute and its implementation are used. Excepts for the Command and Hidden attributes specified by the method.

For example, it can be used if the command implementation is defined in an external assembly or to call a built-in command (such as help) or compatibility purposes.

[CommandMethodForwardedTo(typeof(BuiltInOptionLikeCommands), nameof(BuiltInOptionLikeCommands.ShowHelp))]
public void MyHelp()
    => throw new NotSupportedException(); // NOTE: The method body and parameters used is BuiltInOptionLikeCommands.ShowHelp.

IgnoreUnknownOptions attribute

Cocona treats unknown options as errors by default.
Now, you can set the IgnoreUnknownOptions attribute to ignore unknown options.

Improvements

  • #16: Share ServiceCollectionExtensions code between Cocona and Cocona.Lite.

Fixes

  • #20: Capture stack trace of exception thrown during command execution.

v1.3.0

10 Feb 01:16
b090523
Compare
Choose a tag to compare

Improvements

#13: Nested sub-commands

Added nested sub-commands support. You can implement nested sub-commands in your applications.
Specify the class that has nested sub-commands using HasSubCommands attribute.

[HasSubCommands(typeof(Server), Description = "Server commands")]
[HasSubCommands(typeof(Client), Description = "Client commands")]
class Program
{
    static void Main(string[] args) => CoconaApp.Run<Program>(args);

    // ./myapp info
    public void Info() => Console.WriteLine("Show information");
}

// ./myapp server [command]
class Server
{
    public void Start() => Console.WriteLine("Start");
    public void Stop() => Console.WriteLine("Stop");
}

// ./myapp client [command]
class Client
{
    public void Connect() => Console.WriteLine("Connect");
    public void Disconnect() => Console.WriteLine("Disconnect");
}
$ ./SubCommandApp
Usage: SubCommandApp [command]
Usage: SubCommandApp [--help] [--version]

SubCommandApp

Commands:
  info
  server    Server commands
  client    Client commands

Options:
  -h, --help    Show help message
  --version     Show version

$ ./SubCommandApp
Usage: SubCommandApp server [command]
Usage: SubCommandApp server [--help]

SubCommandApp

Commands:
  start
  stop

Options:
  -h, --help    Show help message

Fixes

  • #12: Moved the lowercasing of commands after overload processing (@faviann)