{"id":15037485,"url":"https://github.com/mayuki/cocona","last_synced_at":"2025-05-13T19:09:48.131Z","repository":{"id":37404856,"uuid":"233205869","full_name":"mayuki/Cocona","owner":"mayuki","description":"Micro-framework for .NET console application. Cocona makes it easy and fast to build console applications on .NET.","archived":false,"fork":false,"pushed_at":"2024-08-13T15:28:41.000Z","size":1375,"stargazers_count":3384,"open_issues_count":70,"forks_count":90,"subscribers_count":30,"default_branch":"master","last_synced_at":"2025-04-27T04:45:35.025Z","etag":null,"topics":["cli","command-line","console","csharp","dotnet","dotnet-core"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mayuki.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-01-11T09:18:04.000Z","updated_at":"2025-04-26T21:53:04.000Z","dependencies_parsed_at":"2024-05-28T22:01:21.550Z","dependency_job_id":"f38364be-cbdb-4110-8bcf-f0311253ed60","html_url":"https://github.com/mayuki/Cocona","commit_stats":null,"previous_names":[],"tags_count":19,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mayuki%2FCocona","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mayuki%2FCocona/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mayuki%2FCocona/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mayuki%2FCocona/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mayuki","download_url":"https://codeload.github.com/mayuki/Cocona/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251089408,"owners_count":21534512,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cli","command-line","console","csharp","dotnet","dotnet-core"],"created_at":"2024-09-24T20:34:46.095Z","updated_at":"2025-04-27T04:45:46.646Z","avatar_url":"https://github.com/mayuki.png","language":"C#","readme":"# ![Cocona](https://raw.githubusercontent.com/mayuki/Cocona/master/docs/assets/logo.svg)\nMicro-framework for .NET **Co**re **con**sole **a**pplication. Cocona makes it easy and fast to build console applications on .NET.🚀\n\n[![Build Status](https://dev.azure.com/misuzilla/Cocona/_apis/build/status/Cocona?branchName=master)](https://dev.azure.com/misuzilla/Cocona/_build/latest?definitionId=18\u0026branchName=master) [![NuGet Package: Cocona](https://img.shields.io/nuget/vpre/Cocona?label=NuGet%3A%20Cocona)](https://www.nuget.org/packages/Cocona) [![NuGet Package: Cocona.Lite](https://img.shields.io/nuget/vpre/Cocona.Lite?label=NuGet%3A%20Cocona.Lite)](https://www.nuget.org/packages/Cocona.Lite)\n\n### ⏱ Create a console application with Cocona in seconds.\n```csharp\nCoconaApp.Run((string? name, bool hey) =\u003e\n    Console.WriteLine($\"{(hey ? \"Hey\" :\"Hello\")} {(name ?? \"Guest\")}!\"));\n```\n![](https://raw.githubusercontent.com/mayuki/Cocona/master/docs/assets/intro-in-seconds.gif)\n\n## Feature\n- 🚀 **Make it easy to build console applications on .NET.**\n    - ASP.NET Core-like Minimal API\n    - `public` method as a command\n    - Provides ASP.NET Core MVC-like development experience to console application development.\n- ✨ **Command-line option semantics like UNIX tools standard. (`getopt`/`getopt_long` like options)**\n    - Your app can handle both `-rf /` and `-r -f /` :-)\n    - Support single command and multiple commands style\n        - `myapp --foo --bar -n arg0 \"arg1\"` (e.g. `dir`, `cp`, `ls` ...)\n        - `myapp server -m \"Hello world!\"` (e.g. `dotnet`, `git`, `kubectl` ...)\n- ❓ **Built-in help documentation support.**\n    - You want to see a help message; you type `-h` or `--help`.\n    - Built-in similar commands suggestion\n    - Shell command-line completion support for `bash` and `zsh`\n- 🛠 **Highly modulable/customizable CLI framework.**\n    - Cocona built on top of `Microsoft.Extensions.*` framework. Cocona natively supports Logging, DI, Configuration and ConsoleLifetime.\n    - Don't you need `Microsoft.Extensions.*`? [then you can use a lightweight version of Cocona (named Cocona.Lite)](#performance--coconalite).\n\n[You can find sample code for various features.](samples)\n\n## Table of contents\n- [Installing](#installing)\n- [Requirements](#requirements)\n- [Getting Started](#getting-started)\n- [Command-line handling basics](#command-line-handling-basics)\n    - [Command](#command)\n    - [Options](#options)\n    - [Arguments](#arguments)\n    - [Sub-commands](#sub-commands)\n    - [Option-like commands](#option-like-commands)\n- [Cocona in action](#cocona-in-action)\n    - [Parameter set](#parameter-set)\n    - [Exit code](#exit-code)\n    - [Validation](#validation)\n    - [Shutdown event handling](#shutdown-event-handling)\n    - [Command filter](#command-filter)\n    - [Dependency Injection](#dependency-injection)\n    - [Configuration](#configuration)\n    - [Logging](#logging)\n    - [Shell command-line completion](#shell-command-line-completion)\n- [Performance \u0026 Cocona.Lite](#performance--coconalite)\n- [Advanced](#advanced)\n    - [Localization](#localization)\n    - [Hide command from help](#hide-command-from-help)\n    - [Help customization](#help-customization)\n    - [CommandMethodForwardedTo attribute](#commandmethodforwardedto-attribute)\n    - [IgnoreUnknownOptions attribute](#ignoreunknownoptions-attribute)\n    - [GenericHost integration](#generichost-integration) \n- [Related projects](#related-projects)\n- [License](#license)\n\n## Installing\nInstall NuGet package from NuGet.org\n\n```sh\n$ dotnet add package Cocona\n\n# A lightweight version is also available if you prefer less dependency.\n$ dotnet add package Cocona.Lite\n```\n\n## Requirements\n- .NET 6 (Required to use Minimal API)\n- .NET 5\n- .NET Standard 2.0, 2.1\n\n## Getting Started\n\n```csharp\nusing Cocona;\nCoconaApp.Run((string name) =\u003e\n{\n    Console.WriteLine($\"Hello {name}\");\n})\n```\n\n\u003cdetails\u003e\u003csummary\u003eClass-based style (for .NET Standard / .NET 5)\u003c/summary\u003e\n\n```csharp\nusing Cocona;\nclass Program\n{\n    static void Main(string[] args)\n    {\n        // Cocona parses command-line and executes a command.\n        CoconaApp.Run\u003cProgram\u003e(args);\n    }\n\n    // public method as a command ™\n    public void Hello(string name)\n    {\n        Console.WriteLine($\"Hello {name}\");\n    }\n}\n```\n\u003c/details\u003e\n\n### Try to run!\n```sh\n$ dotnet run\nUsage: ConsoleAppSample [--name \u003cString\u003e]\n\nOptions:\n  --name \u003cString\u003e    (Required)\n  -h, --help         Show help message\n  --version          Show version\n\n$ dotnet run -- --name Cocona\nHello Cocona\n```\n\n### Extra: Publish the application as a single-file executable\nIf 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](https://docs.microsoft.com/en-us/dotnet/core/whats-new/dotnet-core-3-0#single-file-executables))\n```sh\nPS\u003e dotnet publish -r win-x64 -p:PublishSingleFile=true\nPS\u003e app.exe --name Cocona\n\n$ dotnet publish -r linux-x64 -p:PublishSingleFile=true\n$ ./app --name Cocona\n```\n\n## Command-line handling basics\n### Command\n#### Minimal API style\n\nIf your application has a single command, you can easily define and run it with `CoconaApp.Run`.\n\n```csharp\nCoconaApp.Run((string name, int age) =\u003e { ... });\n```\n\nThis is equivalent to the following code using the Minimal API Builder.\n\n```csharp\nvar builder = CoconaApp.CreateBuilder();\nvar app = builder.Build();\n\napp.AddCommand((string name, int age) =\u003e { ... });\n\napp.Run();\n```\n\nIf you want your application to have more than one command, you can add named commands. See [Sub commands](#sub-commands) for details.\n\n```csharp\nvar app = CoconaApp.Create(); // is a shorthand for `CoconaApp.CreateBuilder().Build()`\n\napp.AddCommand(\"list\", () =\u003e { ... });\napp.AddCommand(\"add\", () =\u003e { ... });\napp.AddCommand(\"delete\", () =\u003e { ... });\n\napp.Run();\n```\n\nYou can add (classic) Class-based style commands with the `AddCommands\u003cT\u003e` method.\n\n```csharp\napp.AddCommands\u003cMyCommand\u003e();\n```\n\n#### Public method as a command (Class-based style)\nBy default, Cocona treats `public` methods as commands.\n\nIf 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](#sub-commands))\n\n```csharp\n// Treats a method name as a command name. (Below method is named `command`)\npublic void Command() { ... }\n\n// Specify a command name using CommandAttribute.\n[Command(\"commandname\")]\npublic void Command() { ... }\n\n// Cocona will ignore this method.\n[Ignore]\npublic void Ignored() { ... }\n```\n\nIf you want to specify a method as a command manually, set `false` to `TreatPublicMethodsAsCommands` option at startup. All command methods require `CommandAttribute`.\n\n```csharp\nCoconaApp.Run\u003cProgram\u003e(args, options =\u003e\n{\n    // If the option value is `false`, All command methods require `CommandAttribute`.\n    options.TreatPublicMethodsAsCommands = false;\n});\n```\n\n### Options\nCocona exposes method parameters as command-line options (also known as flags).\n\n```csharp\n// This command accepts `--name \u003cstring\u003e` and `--hey` options.\napp.AddCommand((string name, bool hey) =\u003e { ... });\n```\n\n\u003cdetails\u003e\u003csummary\u003eClass-based style (for .NET Standard / .NET 5)\u003c/summary\u003e\n\n```csharp\n// This command accepts `--name \u003cstring\u003e` and `--hey` options.\npublic void Hello(string name, bool hey) { ... }\n```\n\u003c/details\u003e\n\nIf the parameter of a method is defined as nullable, Cocona will treat them as non-mandatory option for a command. (That is, the parameters are treated as **required option** by default excepts boolean).\nIf a parameter is boolean, it's assumed that `false` default value is specified.\n\n```csharp\n// `--name` is non-mandatory option.\n// If the user runs the application without this option, the parameter will be `null`.\napp.AddCommand((string? name) =\u003e { ... });\n```\n\n\u003cdetails\u003e\u003csummary\u003eOptional with default value (Class-based style)\u003c/summary\u003e\n\nIf method parameters are [optional argument](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/named-and-optional-arguments#optional-arguments), Cocona treats those as optional command options. (That is, the parameters are treated as **required option** by default excepts boolean).\nIf a parameter is boolean, it's assumed that `false` default value is specified.\n\n```csharp\n// `--name \"default user\"` is specified implicity.\npublic void Hello(string name = \"default user\") { ... }\n```\n\u003c/details\u003e\n\nDo you want to use short-name option `-f` instead of `--force`?\nYou can specify short-name to an option using `OptionAttribute`.\n\n```csharp\n// The command accepts `-f` or `--force` option.\n// Cocona's command-line parser accepts getopt-like styles. See below.\n// $ remove --force --recursive\n// $ remove -r -f\n// $ remove -rf\napp.AddCommand(([Option('f')]bool force, [Option('r')]bool recursive) =\u003e { ... });\n```\n\n\u003cdetails\u003e\u003csummary\u003eClass-based style (for .NET Standard / .NET 5))\u003c/summary\u003e\n\n```csharp\n// The command accepts `-f` or `--force` option.\n// Cocona's command-line parser accepts getopt-like styles. See below.\n// $ remove --force --recursive\n// $ remove -r -f\n// $ remove -rf\npublic void Remove([Option('f')]bool force, [Option('r')]bool recursive) { ... }\n```\n\u003c/details\u003e\n\nIf a parameter is `T[]` or `IEnumerable\u003cT\u003e`, a command accepts one or more options by the same name.\n\n```csharp\n// $ compile -I../path/to/foo.h -I/usr/include/bar.h -I/usr/include/baz.h nantoka.c\n// include = new [] { \"../path/to/foo.h\", \"/usr/include/bar.h\", \"/usr/include/baz.h\" };\napp.AddCommand(([Option('I')]string[] include, [Argument]string file) =\u003e { ... });\n```\n\n\u003cdetails\u003e\u003csummary\u003eClass-based style (for .NET Standard / .NET 5))\u003c/summary\u003e\n\n```csharp\n// $ compile -I../path/to/foo.h -I/usr/include/bar.h -I/usr/include/baz.h nantoka.c\n// include = new [] { \"../path/to/foo.h\", \"/usr/include/bar.h\", \"/usr/include/baz.h\" };\npublic void Compile([Option('I')]string[] include, [Argument]string file) { ... }\n```\n\u003c/details\u003e\n\nYou can also specify a description for options that appear in the help.\n\n```csharp\napp.AddCommand((\n    [Option(Description = \"Description of the option\")] int value,\n    [Argument(Description = \"Description of the argument\")]string arg\n) =\u003e { ... });\n```\n\n\u003cdetails\u003e\u003csummary\u003eClass-based style (for .NET Standard / .NET 5))\u003c/summary\u003e\n\n```csharp\npublic void HasDescription([Option(Description = \"Description of the option\")] int value, [Argument(Description = \"Description of the argument\")]string arg) { ... }\n```\n\u003c/details\u003e\n\n```\nUsage: CoconaSample.InAction.CommandOptions has-description [--value \u003cInt32\u003e] [--help] arg\n\nArguments:\n  0: arg    Description of the argument (Required)\n\nOptions:\n  --value \u003cInt32\u003e    Description of the option (Required)\n  -h, --help         Show help message\n```\n\n- See also: [CoconaSample.InAction.CommandOptions](samples/InAction.CommandOptions)\n\n### Arguments\nCommand-line arguments are defined as method parameters as same as options.\n\n```csharp\n// ./app alice karen\napp.AddCommand(([Argument]string from, [Argument]string to) =\u003e { ... });\n```\n\u003cdetails\u003e\u003csummary\u003eClass-based style (for .NET Standard / .NET 5)\u003c/summary\u003e\n\n```csharp\n// ./app alice karen\npublic void Hello([Argument]string from, [Argument]string to) { ... }\n```\n\u003c/details\u003e\n\nYou 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`).\n\n```csharp\n// ./copy file1 file2 file3 dest\napp.AddCommand(([Argument]string[] src, [Argument]string dest) =\u003e { ... });\n```\n\n\u003cdetails\u003e\u003csummary\u003eClass-based style (for .NET Standard / .NET 5)\u003c/summary\u003e\n\n```csharp\n// ./copy file1 file2 file3 dest\npublic void Copy([Argument]string[] src, [Argument]string dest) { ... }\n```\n\u003c/details\u003e\n\n- See also: [CoconaSample.InAction.ManyArguments](samples/InAction.ManyArguments)\n\n### Sub-commands\n\nYou can add multiple commands with names and expose them as sub-commands. You can implement an application that has sub-commands similar to `dotnet`, `git`, `kubectl` etc...\n\n```csharp\nvar app = CoconaApp.Create();\napp.AddCommand(\"hello\", ([Argument]string name) =\u003e Console.WriteLine($\"Hello {name}!\"))\n    .WithDescription(\"Say hello\");\napp.AddCommand(\"bye\", ([Argument]string name) =\u003e Console.WriteLine($\"Goodbye {name}!\"))\n    .WithDescription(\"Say goodbye\");\napp.Run();\n```\n\n\u003cdetails\u003e\u003csummary\u003eClass-based style (for .NET Standard / .NET 5)\u003c/summary\u003e\n\nIf 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...\n\n```csharp\nstatic void Main(string[] args)\n{\n    CoconaApp.Run\u003cProgram\u003e(args);\n}\n\n[Command(Description = \"Say hello\")]\npublic void Hello([Argument]string name)\n{\n    Console.WriteLine($\"Hello {name}!\");\n}\n\n[Command(Description = \"Say goodbye\")]\npublic void Bye([Argument]string name)\n{\n    Console.WriteLine($\"Goodbye {name}!\");\n}\n```\n\u003c/details\u003e\n\n```bash\n$ ./SubCommandApp\nUsage: SubCommandApp [command]\nUsage: SubCommandApp [--help] [--version]\n\nSubCommandApp\n\nCommands:\n  hello    Say hello\n  bye      Say goodbye\n\nOptions:\n  -h, --help    Show help message\n  --version     Show version\n```\n\nWhen a user mistypes a command, Cocona prints command autogenerated suggestions.\n\n```bash\n$ ./SubCommandApp hell\nError: 'hell' is not a command. See '--help' for usage.\n\nSimilar commands:\n  hello\n```\n\n- See also: [CoconaSample.GettingStarted.SubCommandApp](samples/GettingStarted.SubCommandApp)\n\n##### Nested sub-commands\n\nCocona also supports nested sub-commands. Specify the class that has nested sub-commands using `AddSubCommand` method.\n\n```csharp\nvar app = CoconaApp.Create();\n// ./myapp info\napp.AddCommand(\"info\", () =\u003e Console.WriteLine(\"Show information\"));\n\n// ./myapp server [command]\napp.AddSubCommand(\"server\", x =\u003e\n{\n    x.AddCommand(\"start\", () =\u003e Console.WriteLine(\"Start\"));\n    x.AddCommand(\"stop\", () =\u003e Console.WriteLine(\"Stop\"));\n})\n.WithDescription(\"Server commands\");\n\n// ./myapp client [command]\napp.AddSubCommand(\"client\", x =\u003e\n{\n    x.AddCommand(\"connect\", () =\u003e Console.WriteLine(\"Connect\"));\n    x.AddCommand(\"disconnect\", () =\u003e Console.WriteLine(\"Disconnect\"));\n})\n.WithDescription(\"Client commands\");\n\napp.Run();\n```\n\n\u003cdetails\u003e\u003csummary\u003eClass-based style (for .NET Standard / .NET 5)\u003c/summary\u003e\n\nCocona also supports nested sub-commands. Specify the class that has nested sub-commands using `HasSubCommands` attribute.\n\n```csharp\n[HasSubCommands(typeof(Server), Description = \"Server commands\")]\n[HasSubCommands(typeof(Client), Description = \"Client commands\")]\nclass Program\n{\n    static void Main(string[] args) =\u003e CoconaApp.Run\u003cProgram\u003e(args);\n\n    // ./myapp info\n    public void Info() =\u003e Console.WriteLine(\"Show information\");\n}\n\n// ./myapp server [command]\nclass Server\n{\n    public void Start() =\u003e Console.WriteLine(\"Start\");\n    public void Stop() =\u003e Console.WriteLine(\"Stop\");\n}\n\n// ./myapp client [command]\nclass Client\n{\n    public void Connect() =\u003e Console.WriteLine(\"Connect\");\n    public void Disconnect() =\u003e Console.WriteLine(\"Disconnect\");\n}\n```\n\u003c/details\u003e\n\n```bash\n$ ./SubCommandApp\nUsage: SubCommandApp [command]\nUsage: SubCommandApp [--help] [--version]\n\nSubCommandApp\n\nCommands:\n  info\n  server    Server commands\n  client    Client commands\n\nOptions:\n  -h, --help    Show help message\n  --version     Show version\n\n$ ./SubCommandApp server\nUsage: SubCommandApp server [command]\nUsage: SubCommandApp server [--help]\n\nSubCommandApp\n\nCommands:\n  start\n  stop\n\nOptions:\n  -h, --help    Show help message\n```\n\n#### PrimaryCommand\n```csharp\nvar app = CoconaApp.Create();\napp.AddCommand((bool foo, string bar) =\u003e { ... }); // Primary command\n\napp.AddCommand(\"hello\", () =\u003e { ... });\napp.AddCommand(\"goodbye\", () =\u003e { ... });\napp.Run();\n```\n\n\u003cdetails\u003e\u003csummary\u003eClass-based style (for .NET Standard / .NET 5)\u003c/summary\u003e\n\n```csharp\n[PrimaryCommand]\npublic void Primary(bool foo, string bar) { ... }\n\n[Command]\npublic void Hello() { ... }\n\n[Command]\npublic void Goodbye() { ... }\n```\n\u003c/details\u003e\n\n### Option-like commands\nThe option-like command is a way to achieve an independent command that at first glance, looks like an option in a command.\n\nFor example, easy to understand examples like `--version` and `--help`.\nThese are the options of a command, but they behave as a command when specified.\n\n```csharp\nvar app = CoconaApp.Create();\napp.AddCommand(() =\u003e Console.WriteLine(\"Execute\"))\n    .OptionLikeCommand(x =\u003e\n    {\n        x.AddCommand(\"hello\", ([Argument]string name) =\u003e Console.WriteLine($\"Hello {name}!\"))\n            .WithAliases('f');\n    });\napp.Run();\n```\n\n\u003cdetails\u003e\u003csummary\u003eClass-based style (for .NET Standard / .NET 5)\u003c/summary\u003e\n\n```csharp\n[OptionLikeCommand(\"hello\", new[] {'f'}, typeof(Program), nameof(Hello))]\npublic void Execute()\n    =\u003e Console.WriteLine(\"Execute\");\n\nprivate void Hello([Argument]string name)\n    =\u003e Console.WriteLine($\"Hello {name}!\");\n```\n\u003c/details\u003e\n\n```bash\n$ ./myapp\nExecute\n\n$ ./myapp --hello Alice\nHello Alice!\n```\n\n- See: [samples/Advanced.OptionLikeCommand](samples/Advanced.OptionLikeCommand)\n\n##### Limitations\n- Any previous options or arguments specified by OptionLikeCommand will be ignored.\n    - Example: If `--foo --bar --optionlikecommand --baz arg0` and `--optionlikecommand` is an Option-like command, the command will be passed `--baz arg0`.\n- Arguments are not displayed in help.\n\n## Cocona in action\n\n### Parameter set\nCocona has a mechanism called Parameter set that defines common parameters for multiple commands.\nFor example, if every command receives a user name, host name, etc., it would be annoying to define them in a method for each command.\n\nA class or `record` implements the `ICommandParameterSet` interface and treats it as a Parameter set.\n\n- See: [samples/InAction.ParameterSet](samples/InAction.ParameterSet)\n\n#### By parameterized constructor (includes record class)\nIf a class (or record class) has a parameterized constructor, it is treated as part of the definition of a command method.\n\n```csharp\npublic record CommonParameters(\n    [Option('t', Description = \"Specifies the remote host to connect.\")]\n    string Host,\n    [Option('p', Description = \"Port to connect to on the remote host.\")]\n    int Port,\n    [Option('u', Description = \"Specifies the user to log in as on the remote host.\")]\n    string User = \"root\",\n    [Option('f', Description = \"Perform without user confirmation.\")]\n    bool Force = false\n) : ICommandParameterSet;\n\npublic void Add(CommonParameters commonParams, [Argument] string from, [Argument] string to)\n    =\u003e Console.WriteLine($\"Add: {commonParams.User}@{commonParams.Host}:{commonParams.Port} {(commonParams.Force ? \" (Force)\" : \"\")}\");\n\npublic void Update(CommonParameters commonParams, [Option('r', Description = \"Traverse recursively to perform.\")] bool recursive, [Argument] string path)\n    =\u003e Console.WriteLine($\"Update: {commonParams.User}@{commonParams.Host}:{commonParams.Port} {(commonParams.Force ? \" (Force)\" : \"\")}\");\n```\n\n#### By properties (parameter-less constructor)\nIf a class has a parameter-less constructor, you can mark the public property as `Option` or `Argument`.\n\n**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.**\n\n```csharp\npublic class CommonParameters : ICommandParameterSet\n{\n    [Option('t', Description = \"Specifies the remote host to connect.\")]\n    public string Host { get; set; }\n\n    [Option('p', Description = \"Port to connect to on the remote host.\")]\n    public int Port { get; set; }\n\n    [Option('u', Description = \"Specifies the user to log in as on the remote host.\")]\n    [HasDefaultValue]\n    public string User  { get; set; } = \"root\";\n\n    [Option('f', Description = \"Perform without user confirmation.\")]\n    public bool Force  { get; set; } = false;\n}\n\npublic void Add(CommonParameters commonParams, [Argument] string from, [Argument] string to)\n    =\u003e Console.WriteLine($\"Add: {commonParams.User}@{commonParams.Host}:{commonParams.Port} {(commonParams.Force ? \" (Force)\" : \"\")}\");\n\npublic void Update(CommonParameters commonParams, [Option('r', Description = \"Traverse recursively to perform.\")] bool recursive, [Argument] string path)\n    =\u003e Console.WriteLine($\"Update: {commonParams.User}@{commonParams.Host}:{commonParams.Port} {(commonParams.Force ? \" (Force)\" : \"\")}\");\n```\n\n\n### Exit code\n```csharp\n// Exit Code: 0\npublic void NoReturn() { }\n\n// Exit Code: 123\npublic int Return() { return 123; }\n\n// Exit Code: 255\npublic async Task\u003cint\u003e ReturnAsync() { return 255; }\n\n// Exit Code: -1\npublic async ValueTask\u003cint\u003e ReturnValueTaskAsync() { return -1; }\n\n// Exit Code: 128\npublic void Throw() { throw new CommandExitedException(128); }\n```\n\n- See also: [CoconaSample.InAction.ExitCode](samples/InAction.ExitCode)\n\n### Validation\nCocona can use attributes to validate options and arguments. It is similar to ASP.NET Core MVC. \n\n.NET BCL (`System.ComponentModel.DataAnnotations`) has some pre-defined attributes:\n\n- `RangeAttribute`\n- `MaxLengthAttribute`\n- `MinLengthAttribute`\n- ...\n\nIf you want to implement custom validation attribute, it should inherit `System.ComponentModel.DataAnnotations.ValidationAttribute` attribute. \n\n```csharp\nclass Program\n{\n    static void Main(string[] args)\n    {\n        CoconaApp.Run\u003cProgram\u003e(args);\n    }\n\n    public void Run([Range(1, 128)]int width, [Range(1, 128)]int height, [Argument][PathExists]string filePath)\n    {\n        Console.WriteLine($\"Size: {width}x{height}\");\n        Console.WriteLine($\"Path: {filePath}\");\n    }\n}\n\nclass PathExistsAttribute : ValidationAttribute\n{\n    protected override ValidationResult IsValid(object value, ValidationContext validationContext)\n    {\n        if (value is string path \u0026\u0026 (Directory.Exists(path) || Directory.Exists(path)))\n        {\n            return ValidationResult.Success;\n        }\n        return new ValidationResult($\"The path '{value}' is not found.\");\n    }\n}\n```\n\n- See also: [CoconaSample.InAction.Validation](samples/InAction.Validation)\n\n### Shutdown event handling\n\n```csharp\napp.AddCommand(async (CoconaAppContext ctx) =\u003e\n{\n    while (!ctx.CancellationToken.IsCancellationRequested)\n    {\n        await Task.Delay(100);\n    }\n});\n```\n\n\u003cdetails\u003e\u003csummary\u003eClass-based style (for .NET Standard / .NET 5)\u003c/summary\u003e\n\n```csharp\nclass Program : CoconaConsoleAppBase\n{\n    ...\n    public async Task RunAsync()\n    {\n        while (!Context.CancellationToken.IsCancellationRequested)\n        {\n            await Task.Delay(100);\n        }\n    }\n}\n```\n\nAlternatively, you can use `ICoconaAppContextAccessor` and `CoconaAppContext` to access `CancellationToken`.\n\n```csharp\npublic async Task RunAsync([FromService]ICoconaAppContextAccessor contextAccessor)\n{\n    var ctx = contextAccessor.Current ?? throw new InvalidOperationException();\n    while (!ctx.CancellationToken.IsCancellationRequested)\n    {\n        await Task.Delay(100);\n    }\n}\n```\n\u003c/details\u003e\n\n- See also: [CoconaSample.InAction.HandleShutdownSignal](samples/InAction.HandleShutdownSignal)\n\n### Command filter\nCocona has filter mechanism like ASP.NET Core's action filter. Filters allow custom processing before or after you run a command.\n\n- `ICommandFilter` interface\n- `CommandFilterAttribute` attribute\n- `IFilterProvider` interface\n- `IFilterMetadata` interface\n\n```csharp\nvar app = CoconaApp.Create();\n\n// Add a command with command filters.\napp.AddCommand(() =\u003e\n    {\n        Console.WriteLine($\"Hello Konnichiwa\");\n    })\n    .WithFilter(new SampleCommandFilter())\n    .WithFilter(async (ctx, next) =\u003e\n    {\n        // You can declare and apply a filter using a delegate.\n        return await next(ctx);\n    });\n\n// Add a command filter and apply it to commands after this call.\napp.UseFilter(new MyFilter());\n\nclass SampleCommandFilterAttribute : CommandFilterAttribute\n{\n    public override async ValueTask\u003cint\u003e OnCommandExecutionAsync(CoconaCommandExecutingContext ctx, CommandExecutionDelegate next)\n    {\n        Console.WriteLine($\"Before Command: {ctx.Command.Name}\");\n        try\n        {\n            return await next(ctx);\n        }\n        finally\n        {\n            Console.WriteLine($\"End Command: {ctx.Command.Name}\");\n        }\n    }\n}\n```\n\n\u003cdetails\u003e\u003csummary\u003eClass-based style (for .NET Standard / .NET 5)\u003c/summary\u003e\n\n```csharp\nclass Program\n{\n    static void Main(string[] args)\n    {\n        CoconaApp.Run\u003cProgram\u003e(args);\n    }\n\n    [SampleCommandFilter]\n    public void Hello()\n    {\n        Console.WriteLine($\"Hello Konnichiwa\");\n    }\n}\n\nclass SampleCommandFilterAttribute : CommandFilterAttribute\n{\n    public override async ValueTask\u003cint\u003e OnCommandExecutionAsync(CoconaCommandExecutingContext ctx, CommandExecutionDelegate next)\n    {\n        Console.WriteLine($\"Before Command: {ctx.Command.Name}\");\n        try\n        {\n            return await next(ctx);\n        }\n        finally\n        {\n            Console.WriteLine($\"End Command: {ctx.Command.Name}\");\n        }\n    }\n}\n```\n\u003c/details\u003e\n\n- See also: [CoconaSample.InAction.CommandFilter](samples/InAction.CommandFilter)\n\n### Dependency Injection\nIf a constructor has parameters, Cocona injects an instance obtained from IServiceProvider into the parameter. \n\n```csharp\nvar builder = CoconaApp.CreateBuilder();\nbuilder.Services.AddTransient\u003cMyService\u003e();\n\nvar app = builder.Build();\napp.AddCommand((MyService myService) =\u003e\n{\n    myService.Hello(\"Hello Konnichiwa!\");\n});\napp.Run();\n\nclass MyService\n{\n    private readonly ILogger _logger;\n\n    public MyService(ILogger\u003cMyService\u003e logger)\n    {\n        _logger = logger;\n    }\n\n    public void Hello(string message)\n    {\n        _logger.LogInformation(message);\n    }\n}\n```\n\n\u003cdetails\u003e\u003csummary\u003eClass-based style (for .NET Standard / .NET 5)\u003c/summary\u003e\n\nIf 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]`.\n\n```csharp\nclass Program\n{\n    public Program(ILogger\u003cProgram\u003e logger)\n    {\n        logger.LogInformation(\"Create Instance\");\n    }\n\n    static void Main(string[] args)\n    {\n        CoconaApp.Create()\n            .ConfigureServices(services =\u003e\n            {\n                services.AddTransient\u003cMyService\u003e();\n            })\n            .Run\u003cProgram\u003e(args);\n    }\n\n    public void Hello([FromService]MyService myService)\n    {\n        myService.Hello(\"Hello Konnichiwa!\");\n    }\n}\n\nclass MyService\n{\n    private readonly ILogger _logger;\n\n    public MyService(ILogger\u003cMyService\u003e logger)\n    {\n        _logger = logger;\n    }\n\n    public void Hello(string message)\n    {\n        _logger.LogInformation(message);\n    }\n}\n```\n\u003c/details\u003e\n\n- See also: [CoconaSample.InAction.DependencyInjection](samples/InAction.DependencyInjection)\n\n### Configuration\n- See also: [CoconaSample.InAction.AppConfiguration](samples/InAction.AppConfiguration)\n\n### Logging\n\n```csharp\nvar builder = CoconaApp.CreateBuilder();\nbuilder.Logging.AddDebug();\n\nvar app = builder.Build();\n\napp.AddCommand((ILogger\u003cProgram\u003e logger) =\u003e logger.LogInformation(\"Hello Konnichiwa!\"));\n\napp.Run();\n```\n\n\u003cdetails\u003e\u003csummary\u003eClass-based style (for .NET Standard / .NET 5)\u003c/summary\u003e\n\n```csharp\nclass Program : CoconaConsoleAppBase\n{\n    static void Main(string[] args)\n    {\n        CoconaApp.Create()\n            .ConfigureLogging(logging =\u003e\n            {\n                logging.AddDebug();\n            })\n            .Run\u003cProgram\u003e(args);\n    }\n\n    public async Task RunAsync()\n    {\n        Context.Logger.LogInformation(\"Hello Konnichiwa!\");\n    }\n}\n```\n\u003c/details\u003e\n\n### Shell command-line completion\nCocona provides support for shell command-line completion (also known as tab completion).\n\n![Tab shell completion](https://user-images.githubusercontent.com/9012/83354785-effcd400-a395-11ea-8226-c21e114c746f.gif)\n\nCocona 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.\n\n```sh\n$ source \u003c(./myapp --completion bash)\nor\n% ./myapp --completion zsh \u003e ~/.zsh/functions\n```\n\nCurrently, The supported shells are `bash` and `zsh`.\n\nThis feature is **disabled** by default, or you can set the `EnableShellCompletionSupport` option to `true` if you need it.\n\nIt 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.\n\n- See: [samples/Advanced.ShellCompletionCandidates](samples/Advanced.ShellCompletionCandidates)\n\n## Performance \u0026 Cocona.Lite\n`Microsoft.Extensions.*` are powerful but little heavy libraries. If you don't need`Microsoft.Extensions.*`, you can use a lightweight version of Cocona. (named [Cocona.Lite](https://www.nuget.org/packages/Cocona.Lite/))\n\n### Feature \u0026 Limitation\n- Almost the same features and APIs as Cocona (command-line, help, etc.)\n- No `Microsoft.Extensions.*` dependencies\n    - No Logging, DI, Configuration are provided\n- Fewer overheads\n- The minimal Dependency Injection function\n\n### Installing \u0026 How to use\nJust install NuGet package `Cocona.Lite` instead of `Cocona`.\n\n```sh\n$ dotnet add package Cocona.Lite\n```\n\nThen in your source code, use `CoconaLiteApp` class instead of `CoconaApp` class.\n\n```csharp\nCoconaLiteApp.Run(() =\u003e { ... });\n```\n\n```csharp\nvar app = CoconaLiteApp.Create();\napp.AddCommand(() =\u003e { ... });\napp.Run();\n```\n\n```csharp\nstatic void Main(string[] args)\n{\n    CoconaLiteApp.Run\u003cProgram\u003e(args);\n}\n```\n\n## Advanced\n\n### Localization\nMicrosoft.Extensions.Localization can be used to localize your application. Please refer to the sample code for details.\n\n```csharp\n// Register Microsoft.Extensions.Localization and ICoconaLocalizer services\n// Cocona uses `ICoconaLocalizer` to localize command descriptions.\nvar builder = CoconaApp.CreateBuilder();\nbuilder.Services.AddLocalization(options =\u003e\n{\n    options.ResourcesPath = \"Resources\";\n});\n\n// `MicrosoftExtensionLocalizationCoconaLocalizer` is not included in Cocona core library.\nbuilder.Services.TryAddTransient\u003cICoconaLocalizer, MicrosoftExtensionLocalizationCoconaLocalizer\u003e();\n\nvar app = builder.Build();\napp.AddCommand(\"hello\", ([Argument(Description = \"Name\")]string name, IStringLocalizer\u003cProgram\u003e localizer) =\u003e\n    {\n        // Get a localized text from Microsoft.Extensions.Localization.IStringLocalizer (same as ASP.NET Core)\n        Console.WriteLine(localizer.GetString(\"Hello {0}!\", name));\n    })\n    .WithDescription(\"Say Hello\");\napp.Run();\n```\n\n- See also: [CoconaSample.Advanced.Localization](samples/Advanced.Localization)\n\n\n### Hide command from help\n\n```csharp\nvar app = CoconaApp.Create();\napp.AddCommand(\"hello\", (string name) =\u003e\n    {\n        Console.WriteLine(\"Hello {0}!\", name);\n    });\napp.AddCommand(\"secret-command\", (string name) =\u003e\n    {\n        Console.WriteLine(\"🙊\");\n    })\n    .WithMetadata(new HiddenAttribute());\napp.Run();\n```\n\n### Help customization\n- See also: [CoconaSample.Advanced.HelpTransformer](samples/Advanced.HelpTransformer)\n\n### CommandMethodForwardedTo attribute\nThe `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.\nIf 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.\n\nFor 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.\n\n```csharp\n[CommandMethodForwardedTo(typeof(BuiltInOptionLikeCommands), nameof(BuiltInOptionLikeCommands.ShowHelp))]\npublic void MyHelp()\n    =\u003e throw new NotSupportedException(); // NOTE: The method body and parameters used is BuiltInOptionLikeCommands.ShowHelp.\n```\n\n- See: [samples/Advanced.CommandMethodForwarding](samples/Advanced.CommandMethodForwarding)\n\n### IgnoreUnknownOptions attribute\nCocona treats unknown options as errors by default.\nNow, you can set the IgnoreUnknownOptions attribute to ignore unknown options.\n\n### GenericHost integration\n\nCocona can be integrated with GenericHost of Microsoft.Extensions.Hosting.\nYou can register the services with `UseCocona` extension method.\n\n```csharp\nclass Program\n{\n    static async Task Main(string[] args)\n    {\n        await Host.CreateDefaultBuilder()\n            .ConfigureCocona(args, new[] { typeof(Program) })\n            .Build()\n            .RunAsync();\n    }\n\n    public void Hello()\n    {\n        Console.WriteLine($\"Hello Konnichiwa!\");\n    }\n}\n```\n\n- See: [samples/Advanced.GenericHost](samples/Advanced.GenericHost)\n\n## Related projects\n- [Cysharp/ConsoleAppFramework](https://github.com/Cysharp/ConsoleAppFramework): ConsoleAppFramework heavily inspired Cocona.\n\n## License\nMIT License\n```\nCopyright © 2020-present Mayuki Sawatari \u003cmayuki@misuzilla.org\u003e\n```\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmayuki%2Fcocona","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmayuki%2Fcocona","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmayuki%2Fcocona/lists"}