All Projects → mayuki → Cocona

mayuki / Cocona

Licence: mit
Micro-framework for .NET Core console application. Cocona makes it easy and fast to build console applications on .NET Core.

Programming Languages

csharp
926 projects

Projects that are alternatives of or similar to Cocona

Word Wrap
Wrap words to a specified length.
Stars: ✭ 107 (-73.12%)
Mutual labels:  cli, command-line, console
Cointop
A fast and lightweight interactive terminal based UI application for tracking cryptocurrencies 🚀
Stars: ✭ 2,912 (+631.66%)
Mutual labels:  cli, command-line, console
Clrcli
CLRCLI is an event-driven library for building line-art user interfaces in C#/.Net command-line applications.
Stars: ✭ 124 (-68.84%)
Mutual labels:  cli, command-line, console
Window Size
Reliable way to to get the height and width of the terminal/console in a node.js environment.
Stars: ✭ 79 (-80.15%)
Mutual labels:  cli, command-line, console
Jquery.terminal
jQuery Terminal Emulator - JavaScript library for creating web-based terminals with custom commands
Stars: ✭ 2,623 (+559.05%)
Mutual labels:  cli, command-line, console
Tooling
Advancing Node.js as a framework for writing great tools
Stars: ✭ 98 (-75.38%)
Mutual labels:  cli, command-line, console
Mandown
man-page inspired Markdown viewer
Stars: ✭ 173 (-56.53%)
Mutual labels:  cli, command-line, console
Nnn
n³ The unorthodox terminal file manager
Stars: ✭ 13,138 (+3201.01%)
Mutual labels:  cli, command-line, console
Ascii
👾 ASCII Roulette :: ascii art video chat on the cli
Stars: ✭ 202 (-49.25%)
Mutual labels:  cli, command-line, console
Sharprompt
Interactive command line interface toolkit for C#
Stars: ✭ 197 (-50.5%)
Mutual labels:  cli, command-line, dotnet-core
Rang
A Minimal, Header only Modern c++ library for terminal goodies 💄✨
Stars: ✭ 1,080 (+171.36%)
Mutual labels:  cli, command-line, console
Whatspup
🔳 WhatsApp chat from commandline/console/cli using GoogleChrome puppeteer
Stars: ✭ 310 (-22.11%)
Mutual labels:  cli, command-line, console
Crossline
A small, self-contained, zero-config, MIT licensed, cross-platform, readline and libedit replacement.
Stars: ✭ 53 (-86.68%)
Mutual labels:  cli, command-line, console
Simple Console
Add an elegant command-line interface to any page
Stars: ✭ 107 (-73.12%)
Mutual labels:  cli, command-line, console
Wonders
🌈 Declarative JavaScript framework to build command-line applications.
Stars: ✭ 34 (-91.46%)
Mutual labels:  cli, command-line, console
Gcli
🖥 Go CLI application, tool library, running CLI commands, support console color, user interaction, progress display, data formatting display, generate bash/zsh completion add more features. Go的命令行应用,工具库,运行CLI命令,支持命令行色彩,用户交互,进度显示,数据格式化显示,生成bash/zsh命令补全脚本
Stars: ✭ 188 (-52.76%)
Mutual labels:  cli, command-line, console
Laravel Zero
A PHP framework for console artisans
Stars: ✭ 2,821 (+608.79%)
Mutual labels:  cli, command-line, console
Php Console
🖥 PHP CLI application library, provide console argument parse, console controller/command run, color style, user interactive, format information show and more. 功能全面的PHP命令行应用库。提供控制台参数解析, 命令运行,颜色风格输出, 用户信息交互, 特殊格式信息显示
Stars: ✭ 310 (-22.11%)
Mutual labels:  cli, command-line, console
Laravel Desktop Notifier
💻 Send notifications to your desktop from your Laravel Artisan Commands. An JoliNotif wrapper for Laravel.
Stars: ✭ 333 (-16.33%)
Mutual labels:  command-line, console
Go Tea
Tea provides an Elm inspired functional framework for interactive command-line programs.
Stars: ✭ 329 (-17.34%)
Mutual labels:  cli, command-line

Cocona

Micro-framework for .NET Core console application. Cocona makes it easy and fast to build console applications on .NET Core.🚀

Build Status NuGet Package: Cocona NuGet Package: Cocona.Lite

⏱ Create a console application with Cocona in seconds.

Feature

  • 🚀 Make it easy to build console applications on .NET Core.
    • public method as a command ™
    • Provides ASP.NET Core MVC-like development experience to console application development.
  • Command-line option semantics like UNIX tools standard. (getopt/getopt_long like options)
    • Your app can handle both -rf / and -r -f / :-)
    • Support single command and multiple commands style
      • myapp --foo --bar -n arg0 "arg1" (e.g. dir, cp, ls ...)
      • myapp server -m "Hello world!" (e.g. dotnet, git, kubectl ...)
  • Built-in help documentation support.
    • You want to see a help message; you type -h or --help.
    • Built-in similar commands suggestion
    • Shell command-line completion support for bash and zsh
  • 🛠 Highly modulable/customizable CLI framework.

You can find sample code for various features.

Table of contents

Installing

Install NuGet package from NuGet.org

$ dotnet add package Cocona
PS> Install-Package Cocona

Getting Started

using Cocona;
class Program
{
    static void Main(string[] args)
    {
        // Cocona parses command-line and executes a command.
        CoconaApp.Run<Program>(args);
    }

    // public method as a command ™
    public void Hello(string name)
    {
        Console.WriteLine($"Hello {name}");
    }
}

Try to run!

$ dotnet run
Usage: ConsoleAppSample [--name <String>]

Options:
  --name <String>    (Required)
  -h, --help         Show help message
  --version          Show version

$ dotnet run -- --name Cocona
Hello Cocona

Extra: Publish the application as a single-file executable

If your application runs on .NET Core 3.0 or later, you can publish the app as a single-file executable. (see. What's new in .NET Core 3.0)

PS> dotnet publish -r win-x64 -p:PublishSingleFile=true
PS> app.exe --name Cocona

$ dotnet publish -r linux-x64 -p:PublishSingleFile=true
$ ./app --name Cocona

Command-line handling basics

Command

Public method as a command ™

By default, Cocona treats public methods as commands.

If an application has one public method, Cocona calls it on startup. If there are more than one, they are treated as sub-commands. (see also Sub commands)

// Treats a method name as a command name. (Below method is named `command`)
public void Command() { ... }

// Specify a command name using CommandAttribute.
[Command("commandname")]
public void Command() { ... }

// Cocona will ignore this method.
[Ignore]
public void Ignored() { ... }

If you want to specify a method as a command manually, set false to TreatPublicMethodsAsCommands option at startup. All command methods require CommandAttribute.

CoconaApp.Run<Program>(args, options =>
{
    // If the option value is `false`, All command methods require `CommandAttribute`.
    options.TreatPublicMethodsAsCommands = false;
});

Options

Cocona exposes method parameters as command-line options (also known as flags).

// This command accepts `--name <string>` and `--hey` options.
public void Hello(string name, bool hey) { ... }

If method parameters are optional argument, Cocona treats those as optional command options. (That is, the parameters are treated as required option by default excepts boolean). If a parameter is boolean, it's assumed that false default value is specified.

// `--name "default user"` is specified implicity.
public void Hello(string name = "default user") { ... }

Do you want to use short-name option -f instead of --force? You can specify short-name to an option using OptionAttribute.

// The command accepts `-f` or `--force` option.
// Cocona's command-line parser accepts getopt-like styles. See below.
// $ remove --force --recursive
// $ remove -r -f
// $ remove -rf
public void Remove([Option('f')]bool force, [Option('r')]bool recursive) { ... }

If a parameter is T[] or IEnumerable<T>, a command accepts one or more options by the same name.

// $ compile -I../path/to/foo.h -I/usr/include/bar.h -I/usr/include/baz.h nantoka.c
// include = new [] { "../path/to/foo.h", "/usr/include/bar.h", "/usr/include/baz.h" };
public void Compile([Option('I')]string[] include, [Argument]string file) { ... }

You can also specify a description for options that appear in the help.

public void HasDescription([Option(Description = "Description of the option")] int value, [Argument(Description = "Description of the argument")]string arg) { ... }
Usage: CoconaSample.InAction.CommandOptions has-description [--value <Int32>] [--help] arg

Arguments:
  0: arg    Description of the argument (Required)

Options:
  --value <Int32>    Description of the option (Required)
  -h, --help         Show help message

Arguments

Command-line arguments are defined as method parameters as same as options.

// ./app alice karen
public void Hello([Argument]string from, [Argument]string to) { ... }

You can define a parameter as T[]. It allows defining cp-like command which accepts many file paths and one destination path (cp file1 file2 file3 dest).

// ./copy file1 file2 file3 dest
public void Copy([Argument]string[] src, [Argument]string dest) { ... }

Sub-commands

If a command type has more than one public method or [Command], those commands are exposed as sub-commands. You can implement an application that has sub-commands similar to dotnet, git, kubectl etc...

static void Main(string[] args)
{
    CoconaApp.Run<Program>(args);
}

[Command(Description = "Say hello")]
public void Hello([Argument]string name)
{
    Console.WriteLine($"Hello {name}!");
}

[Command(Description = "Say goodbye")]
public void Bye([Argument]string name)
{
    Console.WriteLine($"Goodbye {name}!");
}
$ ./SubCommandApp
Usage: SubCommandApp [command]
Usage: SubCommandApp [--help] [--version]

SubCommandApp

Commands:
  hello    Say hello
  bye      Say goodbye

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

When a user mistypes a command, Cocona prints command autogenerated suggestions.

$ ./SubCommandApp hell
Error: 'hell' is not a command. See '--help' for usage.

Similar commands:
  hello
Nested sub-commands

Cocona also supports nested sub-commands. 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

PrimaryCommand

[PrimaryCommand]
public void Primary(bool foo, string bar) { ... }

[Command]
public void Hello() { ... }

[Command]
public void Goodbye() { ... }

Option-like commands

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.

Cocona in action

Exit code

// Exit Code: 0
public void NoReturn() { }

// Exit Code: 123
public int Return() { return 123; }

// Exit Code: 255
public async Task<int> ReturnAsync() { return 255; }

// Exit Code: -1
public async ValueTask<int> ReturnValueTaskAsync() { return -1; }

// Exit Code: 128
public void Throw() { throw new CommandExitedException(128); }

Validation

Cocona can use attributes to validate options and arguments. It is similar to ASP.NET Core MVC.

.NET Core (System.ComponentModel.DataAnnotations) has some pre-defined attributes:

  • RangeAttribute
  • MaxLangeAttribute
  • MinLengthAttribute
  • ...

If you want to implement custom validation attribute, it should inherit System.ComponentModel.DataAnnotations.ValidationAttribute attribute.

class Program
{
    static void Main(string[] args)
    {
        CoconaApp.Run<Program>(args);
    }

    public void Run([Range(1, 128)]int width, [Range(1, 128)]int height, [Argument][PathExists]string filePath)
    {
        Console.WriteLine($"Size: {width}x{height}");
        Console.WriteLine($"Path: {filePath}");
    }
}

class PathExistsAttribute : ValidationAttribute
{
    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        if (value is string path && (Directory.Exists(path) || Directory.Exists(path)))
        {
            return ValidationResult.Success;
        }
        return new ValidationResult($"The path '{value}' is not found.");
    }
}

Shutdown event handling

class Program : CoconaConsoleAppBase
{
    ...
    public async Task RunAsync()
    {
        while (!Context.CancellationToken.IsCancellationRequested)
        {
            await Task.Delay(100);
        }
    }
}

Command filter

Cocona has filter mechanism like ASP.NET Core's action filter. Filters allow custom processing before or after you run a command.

  • ICommandFilter interface
  • CommandFilterAttribute attribute
  • IFilterProvider interface
  • IFilterMetadata interface
class Program
{
    static void Main(string[] args)
    {
        CoconaApp.Run<Program>(args);
    }

    [SampleCommandFilter]
    public void Hello()
    {
        Console.WriteLine($"Hello Konnichiwa");
    }
}

class SampleCommandFilterAttribute : CommandFilterAttribute
{
    public override async ValueTask<int> OnCommandExecutionAsync(CoconaCommandExecutingContext ctx, CommandExecutionDelegate next)
    {
        Console.WriteLine($"Before Command: {ctx.Command.Name}");
        try
        {
            return await next(ctx);
        }
        finally
        {
            Console.WriteLine($"End Command: {ctx.Command.Name}");
        }
    }
}

Dependency Injection

If a constructor has parameters, Cocona injects an instance obtained from IServiceProvider into the parameter. Cocona will also inject an instance into the parameter if a command method parameter is marked as [FromService].

class Program
{
    public Program(ILogger<Program> logger)
    {
        logger.LogInformation("Create Instance");
    }

    static void Main(string[] args)
    {
        CoconaApp.Create()
            .ConfigureServices(services =>
            {
                services.AddTransient<MyService>();
            })
            .Run<Program>(args);
    }

    public void Hello([FromService]MyService myService)
    {
        myService.Hello("Hello Konnichiwa!");
    }
}

class MyService
{
    private readonly ILogger _logger;

    public MyService(ILogger<MyService> logger)
    {
        _logger = logger;
    }

    public void Hello(string message)
    {
        _logger.LogInformation(message);
    }
}

Configuration

Logging

class Program : CoconaConsoleAppBase
{
    static void Main(string[] args)
    {
        CoconaApp.Create()
            .ConfigureLogging(logging =>
            {
                logging.AddDebug();
            })
            .Run<Program>(args);
    }

    public async Task RunAsync()
    {
        Context.Logger.LogInformation("Hello Konnichiwa!");
    }
}

Shell command-line completion

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

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

Currently, The supported shells are bash and zsh.

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.

Performance & Cocona.Lite

Microsoft.Extensions.* are powerful but little heavy libraries. If you don't needMicrosoft.Extensions.*, you can use a lightweight version of Cocona. (named Cocona.Lite)

Feature & Limitation

  • Almost the same features and APIs as Cocona (command-line, help, etc.)
  • No Microsoft.Extensions.* dependencies
    • No Logging, DI, Configuration are provided
  • Fewer overheads
  • The minimal Dependency Injection function

Installing & How to use

Just install NuGet package Cocona.Lite instead of Cocona.

$ dotnet add package Cocona.Lite

Then in your source code, use CoconaLiteApp class instead of CoconaApp class.

static void Main(string[] args)
{
    CoconaLiteApp.Run<Program>(args);
}

Benchmark

BenchmarkDotNet=v0.12.0, OS=Windows 10.0.18363
Intel Core i7-8650U CPU 1.90GHz (Kaby Lake R), 1 CPU, 8 logical and 4 physical cores
.NET Core SDK=3.1.101
  [Host]     : .NET Core 3.1.1 (CoreCLR 4.700.19.60701, CoreFX 4.700.19.60801), X64 RyuJIT
  DefaultJob : .NET Core 3.1.1 (CoreCLR 4.700.19.60701, CoreFX 4.700.19.60801), X64 RyuJIT
Method Mean Error StdDev Ratio RatioSD Rank
Cocona.Lite 50.15 us 0.952 us 1.058 us 1.00 0.00 1
CommandLineParser 51.22 us 1.004 us 1.157 us 1.02 0.03 1
CliFx 57.73 us 1.128 us 1.427 us 1.15 0.03 2
McMaster.Extensions.CommandLineUtils 188.98 us 3.707 us 5.316 us 3.76 0.13 3
Clipr 203.84 us 4.706 us 13.350 us 3.90 0.19 4
System.CommandLine 239.53 us 4.762 us 8.827 us 4.73 0.25 5
PowerArgs 352.29 us 7.681 us 13.043 us 6.96 0.28 6
Cocona 1,836.93 us 35.555 us 50.992 us 36.90 1.44 7

Advanced

Help customization

CommandMethodForwardedTo attribute

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.

Related projects

License

MIT License

Copyright © 2020-present Mayuki Sawatari <[email protected]>
Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].