{"id":25127872,"url":"https://github.com/remora/remora.commands","last_synced_at":"2025-04-06T22:06:05.828Z","repository":{"id":38443112,"uuid":"307157518","full_name":"Remora/Remora.Commands","owner":"Remora","description":"A platform-agnostic command parsing and execution library. Uses *nix getopts style syntax.","archived":false,"fork":false,"pushed_at":"2025-02-13T13:05:21.000Z","size":872,"stargazers_count":21,"open_issues_count":5,"forks_count":8,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-06T06:38:01.192Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"lgpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Remora.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-10-25T17:48:45.000Z","updated_at":"2025-03-27T18:35:21.000Z","dependencies_parsed_at":"2024-11-06T22:25:53.618Z","dependency_job_id":"f1ab8435-be49-444c-86c1-8f5864f52578","html_url":"https://github.com/Remora/Remora.Commands","commit_stats":null,"previous_names":[],"tags_count":61,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Remora%2FRemora.Commands","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Remora%2FRemora.Commands/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Remora%2FRemora.Commands/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Remora%2FRemora.Commands/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Remora","download_url":"https://codeload.github.com/Remora/Remora.Commands/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247557767,"owners_count":20958047,"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":[],"created_at":"2025-02-08T11:19:40.667Z","updated_at":"2025-04-06T22:06:05.801Z","avatar_url":"https://github.com/Remora.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"Remora.Commands\n===============\n\nRemora.Commands is a platform-agnostic command library that handles parsing and\ndispatch of typical *nix getopts-style command invocations - that is, \nRemora.Commands turns this:\n\n```\n!things add new-thing --description \"My thing!\" --enable-indexing\n```\n\ninto a call to this:\n\n```cs\n[Group(\"things\")]\npublic class ThingCommands : CommandGroup\n{\n    [Command(\"add\")]\n    public Task\u003cIResult\u003e AddThingAsync\n    (\n        string name, // = \"new-thing\"\n        [Option(\"description\") string description, // = \"My thing!\"\n        [Switch(\"enable-indexing\") bool enableIndexing = false // = true\n    )\n    {\n        return _thingService.AddThing(name, description, enableIndexing);\n    }\n}\n```\n\n## Familiar Syntax\nInspired by and closely following *nix-style getopts syntax, Remora.Commands \nallows you to define commands in a variety of ways, leveraging familiar and \nwidespread principles of command-line tooling. Currently, the library supports \nthe following option syntax types:\n\n  * Positional options\n    - `T value`\n  * Named options\n    - `[Option('v')] T value` (short)\n    - `[Option(\"value\")] T value` (long)\n    - `[Option('v', \"value\")] T value` (short and long)\n  * Switches\n    - `[Switch('e')] bool value = false` (short)\n    - `[Switch(\"enable\")] bool value = false` (long)\n    - `[Switch('e', \"enable\")] bool value = false` (short and long)\n  * Collections\n    - `IEnumerable\u003cT\u003e values` (positional)\n    - `[Option('v', \"values\")] IEnumerable\u003cT\u003e values` (named, as above)\n    - `[Range(Min = 1, Max = 2)] IEnumerable\u003cT\u003e values` (constrained)\n  * Verbs\n\nShort names are specified using a single dash (`-`), and long options by two \ndashes (`--`). As an added bonus, you may combine short-name switches, similar\nto what's supported by GNU `tar`. You can even place a normal named option at \nthe end, followed by a value.\n\nThat is, both of the invocations below are valid (consider `x`, `v` and `z` as \nswitches, and `f` as a named option).\n```\nmy-command -xvz\nmy-command -xvf file.bin\n```\n\nThe library also supports \"greedy\" options, which can simplify usage in certain\ncases, allowing users to omit quotes. A greedy option treats multiple subsequent\nvalues as one combined value, concatenating them automatically. Concatenation is\ndone with a single space in between each value. \n\nMaking an option greedy is a simple matter of applying the `Greedy` attribute, \nand it can be combined with `Option` for both named and positional greedy \nparameters. Collections and switches, for which the greedy behaviour makes \nlittle sense, simply ignore the attribute.\n\n```\n[Greedy] T value\n```\n\n## Ease of use\nIt's dead easy to get started with Remora.Commands.\n\n  1. Declare a command group. Groups may be nested to form verbs, or chains of \n    prefixes to a command.\n        ```cs\n        [Group(\"my-prefix\")]\n        public class MyCommands : CommandGroup\n        {\n        }\n        ```\n  2. Declare a command\n        ```cs\n        [Group(\"my-prefix\")]\n        public class MyCommands : CommandGroup\n        {\n            [Command(\"my-name\")]\n            public Task\u003cIResult\u003e MyCommand()\n            {\n                // ...\n            }\n        }\n        ```\n  3. Set up the command service with dependency injection\n        ```cs\n        var services = new ServiceCollection()\n            .AddCommands()\n            .AddCommandTree()\n                .WithCommandGroup\u003cMyCommands\u003e()\n                .Finish()\n            .BuildServiceProvider();\n        ```\n  4. From any input source, parse and execute!\n        ```cs\n        private readonly CommandService _commandService;\n\n        public async Task\u003cIResult\u003e MyInputHandler\n        (\n            string userInput, \n            CancellationToken ct\n        )\n        {\n            var executionResult = await _commandService.TryExecuteAsync\n            (\n                userInput,\n                ct: ct\n            );\n\n            if (executionResult.IsSuccess)\n            {\n                return executionResult;\n            }\n\n            _logger.Error(\"Oh no!\");\n            _logger.Error(\"Anyway\");\n        }\n        ```\n\n## Flexibility\nCommand groups can be nested and combined in countless ways - registering \nmultiple groups with the same name merges them under the same prefix, nameless\ngroups merge their commands with their outer level, and completely different\ncommand group classes can share their prefixes unhindered.\n\nFor example, the structure below, when registered...\n\n```cs\n[Group(\"commands\"]\npublic class MyFirstGroup : CommandGroup \n{\n    [Command(\"do-thing\")]\n    public Task\u003cIResult\u003e MyCommand() { }\n}\n\n[Group(\"commands\"]\npublic class MySecondGroup : CommandGroup \n{\n    [Group(\"subcommands\")\n    public class MyThirdGroup : CommandGroup\n    {\n        [Command(\"do-thing\")]\n        public Task\u003cIResult\u003e MyCommand() { }\n    }\n}\n```\n\nproduces the following set of available commands:\n\n```\ncommands do-thing\ncommands subcommands do-thing\n```\n\nGenerally, types return `Task\u003cIResult\u003e`, but you can use both `ValueTask\u003cT\u003e` and \n`Task\u003cT\u003e`, as long as `T` implements `IResult`.\n\nCommands themselves can be overloaded using normal C# syntax, and the various\nargument syntax variants (that is, positional, named, switches, and collections)\ncan easily be mixed and matched.\n\nThe list of types recognized and parsed by Remora.Commands can be extended using \n`AbstractTypeParser\u003cTType\u003e` - if you can turn a string into an instance of your\ntype, Remora.Commands will be able to parse it.\n\n```cs\npublic class MyParser : AbstractTypeParser\u003cMyType\u003e\n{\n    public override ValueTask\u003cResult\u003cMyType\u003e\u003e TryParseAsync\n    (\n        string value, \n        CancellationToken ct\n    )\n    {\n        return new ValueTask\u003cResult\u003cMyType\u003e\u003e\n        (\n            !MyType.TryParse(value, out var result)\n            ? Result\u003cMyType\u003e.FromError\n              (\n                  $\"Failed to parse \\\"{value}\\\" as an instance of MyType.\"\n              )\n            : Result\u003cMyType\u003e.FromSuccess(result)\n        );\n    }\n}\n```\n\n```cs\nvar services = new ServiceCollection()\n    .AddCommands()\n    .AddCommandTree()\n        .WithCommandGroup\u003cMyCommands\u003e()\n        .Finish()\n    .AddSingletonParser\u003cMyParser\u003e()\n    .BuildServiceProvider();\n```\n\nAnd, since parsers are instantiated with dependency injection, you can even\ncreate parsers that fetch entities from a database, that look things up online,\nthat integrate with the rest of your application seamlessly... the possibilities\nare endless!\n\nBy default, Remora.Commands provides built-in parsers for the following types:\n  * `string`\n  * `char`\n  * `bool`\n  * `byte`\n  * `sbyte`\n  * `ushort`\n  * `short`\n  * `uint`\n  * `int`\n  * `ulong`\n  * `long`\n  * `float`\n  * `double`\n  * `decimal`\n  * `BigInteger`\n  * `TimeSpan`\n  * `DateTime`\n  * `DateTimeOffset`\n\nRemora.Commands can also parse any enum without the need for a custom parser!\n\n## Multiple trees\nIf your application requires different sets of commands for different contexts, \nyou can register multiple separate trees and selectively execute commands from\nthem. This is excellent for things where you might have a single application\nserving multiple users or groups thereof.\n\n```cs\nvar services = new ServiceCollection()\n    .AddCommands()\n    .AddCommandTree()\n        .WithCommandGroup\u003cMyCommands\u003e()\n        .Finish()\n    .AddCommandTree(\"myothertree\")\n        .WithCommandGroup\u003cMyOtherCommands\u003e()\n        .Finish()\n    .BuildServiceProvider();\n```\n\nThese trees can then be accessed using the `CommandTreeAccessor` service. \nIf you don't need multiple trees, or you want to expose a set of default \ncommands, there's an unnamed tree available by default (accessed by either \n`null` or `Constants.DefaultTreeName` as the tree name). This is also the tree\naccessed by not providing a name to `AddCommandTree`.\n\n```cs\nvar accessor = services.GetRequiredService\u003cCommandTreeAccessor\u003e();\nif (accessor.TryGetNamedTree(\"myothertree\", out var tree))\n{\n    ...\n}\n\nif (accessor.TryGetNamedTree(null, out var defaultTree))\n{\n    ...\n}\n```\n\nThe effects of each `AddCommandTree` call are cumulative, so if you want to \nconfigure the groups that are part of a tree from multiple locations (such as \nplugins), simply call `AddCommandTree` again with the same name.\n\n```cs\nservices\n    .AddCommands()\n    .AddCommandTree(\"myothertree\")\n        .WithCommandGroup\u003cMyOtherCommands\u003e();\n\n// elsewhere...\n\nservices\n    .AddCommandTree(\"myothertree\")\n        .WithCommandGroup\u003cMoreCommands\u003e();\n```\n\nThis would result in a tree with both `MyOtherCommands` and `MoreCommands` \navailable.\n\nTo then access the different trees, pass the desired name when attempting to \nexecute a command.\n\n```cs\nprivate readonly CommandService _commandService;\n\npublic async Task\u003cIResult\u003e MyInputHandler\n(\n    string userInput, \n    CancellationToken ct\n)\n{\n    var executionResult = await _commandService.TryExecuteAsync\n    (\n        userInput,\n        treeName: \"myothertree\",\n        ct: ct\n    );\n\n    if (executionResult.IsSuccess)\n    {\n        return executionResult;\n    }\n\n    _logger.Error(\"Oh no!\");\n    _logger.Error(\"Anyway\");\n}\n```\n\n## Installation\nGet it on [NuGet][1]!\n\n## Thanks\nHeavily inspired by [CommandLineParser][2], a great library for parsing *nix\ngetopts-style arguments from the command line itself.\n\nIcon by [Twemoji][3], licensed under CC-BY 4.0.\n\n[1]: http://nuget.org/packages/Remora.Commands\n[2]: https://github.com/commandlineparser/commandline\n[3]: https://twemoji.twitter.com/\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fremora%2Fremora.commands","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fremora%2Fremora.commands","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fremora%2Fremora.commands/lists"}