{"id":17837437,"url":"https://github.com/ndaba1/gommander","last_synced_at":"2025-03-19T21:30:42.208Z","repository":{"id":37203951,"uuid":"487677740","full_name":"ndaba1/gommander","owner":"ndaba1","description":"A commander package for creating CLIs in golang","archived":false,"fork":false,"pushed_at":"2023-11-06T14:18:07.000Z","size":616,"stargazers_count":25,"open_issues_count":1,"forks_count":2,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-03-17T14:49:27.021Z","etag":null,"topics":["argument-parsing","cli","command-line","command-line-arguments-parser","command-line-parser","command-line-tool","commander","go","golang","golang-cli","golang-package","gommander","positional-arguments"],"latest_commit_sha":null,"homepage":"","language":"Go","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/ndaba1.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-05-02T00:50:48.000Z","updated_at":"2024-02-24T15:17:44.000Z","dependencies_parsed_at":"2024-06-19T01:55:12.383Z","dependency_job_id":"c2a483cb-2514-44f2-a26f-46e873dcd224","html_url":"https://github.com/ndaba1/gommander","commit_stats":null,"previous_names":[],"tags_count":10,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ndaba1%2Fgommander","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ndaba1%2Fgommander/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ndaba1%2Fgommander/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ndaba1%2Fgommander/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ndaba1","download_url":"https://codeload.github.com/ndaba1/gommander/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244507847,"owners_count":20463689,"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":["argument-parsing","cli","command-line","command-line-arguments-parser","command-line-parser","command-line-tool","commander","go","golang","golang-cli","golang-package","gommander","positional-arguments"],"created_at":"2024-10-27T20:47:10.467Z","updated_at":"2025-03-19T21:30:41.933Z","avatar_url":"https://github.com/ndaba1.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Gommander (go-commander)\n\n\u003cp align=\"center\"\u003e\n\u003cimg alt=\"GitHub Workflow Status\" src=\"https://img.shields.io/github/workflow/status/ndaba1/gommander/gommander-ci-workflow?label=CI\u0026logo=github%20actions\u0026logoColor=fff\"\u003e\n\u003cimg alt=\"Codecov\" src=\"https://img.shields.io/codecov/c/gh/ndaba1/gommander?color=orange\u0026logo=codecov\"\u003e\n\u003cimg alt=\"Go report card\", src=\"https://goreportcard.com/badge/github.com/ndaba1/gommander\"\u003e\n\u003cimg alt=\"Go reference\", src=\"https://pkg.go.dev/badge/github.com/ndaba1/gommander.svg\"\u003e\n\u003c/p\u003e\n\nA commander package for creating CLIs in Go. This package aims to be a complete solution for command-line argument parsing by providing you with an easy-to-use and extensible api but without compromising speed.\n\nFeatures of this package include:\n\n- Support for POSIX-compliant flags\n- Color printing\n- Easily extensible API\n- Fast and lightweight parser\n- Subcommands nesting\n- Automatic help generation\n\n## API Overview\n\n\u003cimg src=\"./assets/overview.png\" alt=\"Overview\"\u003e\n\n## Index\n\n- [Gommander](#gommander-go-commander)\n  - [Installation](#installation)\n  - [Quick Start](#quick-start)\n  - [Subcommands](#subcommands)\n  - [Arguments](#arguments)\n  - [Flags](#flags)\n  - [Options](#options)\n  - [App Settings and Events](#settings-and-events)\n  - [App Themes and UI](#themes-and-ui)\n  - [Command Callbacks](#command-callbacks)\n  - [Error Handling](#error-handling)\n\n## Installation\n\nTo add the package to your go project, execute the following command:\n\n```bash\ngo get -u github.com/ndaba1/gommander\n```\n\n## Quick Start\n\nGettting started with gommander is quite easy. Create an instance of `gommander.App()` which is the internal representation of your CLI program to which you add [subcommands](#subcommands), [arguments](#arguments), [flags](#flags) and [options](#options). The following is a quick example:\n\n```go\npackage main\n\nimport (\n  \"fmt\"\n\n  \"github.com/ndaba1/gommander\"\n)\n\nfunc main() {\n  app := gommander.App()\n\n  app.Author(\"vndaba\").\n      Version(\"0.1.0\").\n      Help(\"A demo usage of gommander\").\n      Name(\"demo\")\n\n  app.Subcommand(\"greet\").\n      Argument(\"\u003cname\u003e\", \"The name to greet\").\n      Help(\"A simple command that greets the provided name\").\n      Option(\"-c --count \u003cint:number\u003e\", \"The number of times to greet the name\")\n\n  app.Parse()\n}\n\n```\n\nWhen the help flag is invoked on the above example, the following output gets printed out:\n\n```bash\n\nA demo usage of gommander\n\nUsage:\n    demo [FLAGS] \u003cSUBCOMMAND\u003e\n\nFlags:\n    -h, --help        Print out help information\n    -v, --version     Print out version information\n\nSubcommands:\n    greet             A simple cmd that greets provided name\n\n```\n\n## Subcommands\n\nCommands and subcommands are the gist of the package. The program's entry point is itself a command to which more subcommands may be nested. A new command can be created by the `gommander.NewCommand()` method, but you will rarely have to work with this method directly. Instead, the convention is first to create an app via the `gommander.App()` method, which is also a command marked as the program's entry point (the root cmd), then chain further subcommands to it.\n\n```go\n//...package declaration and imports\nfunc main() {\n    app := gommander.App()\n\n    app.SubCommand(\"basic\")\n}\n//...\n```\n\nThe `.SubCommand()` method returns the newly created subcommand for further manipulation and customization such as adding [arguments](#arguments), [flags](#flags), [options](#options) or updating command metadata such as it description, etc.\n\nYou can also manipulate the fields of the root command, such as the program's author and the version, and even set the program's name to be different from the name of its binary. (This doesn't change the name of the actual binary, it only changes the name displayed to users when printing out help information).\n\n```go\n// ...\nfunc main() {\n    app := gommander.App()\n\n    app.Help(\"A simple CLI app\").\n        Version(\"0.1.0\").\n        Author(\"vndaba\")\n}\n// ...\n```\n\nSubcommands can have multiple aliases which are hidden by default, but you can this behavior can be modified easily, as shown:\n\n```go\n// ...\nfunc main() {\n    app := gommander.App()\n\n    app.SubCommand(\"image\").\n        Help(\"A test subcmd\").\n        Alias(\"img\").\n        Alias(\"images\")\n\n    // to display the aliases\n    app.Set(gommander.ShowCommandAliases, true)\n}\n// ...\n```\n\nThe package also supports Subcommand Nesting. You can chain as many subcommands as you would like. The following is an example:\n\n```go\n// ...\nfunc main() {\n    app := gommander.App()\n\n    image := app.SubCommand(\"image\").Help(\"Manage images\")\n\n    image.SubCommand(\"ls\").Help(\"List available images\")\n    image.SubCommand(\"build\").Help(\"Build a new image\")\n\n    // ...\n}\n// ...\n```\n\nThe `.SubCommand()` method is ergonomic and reduces function parameters nesting in your program. However, you could also use the `.AddSubCommand()` method to add a new subcommand. It is similar to the subcommand method, but instead of taking the name of the new command as input, it makes it an instance of an already created command, and instead of returning the newly created subcommand, it returns the command to which it is chained as so:\n\n```go\n// ...\nfunc main() {\n    app := gommander.App()\n\n    app.AddSubCommand(\n        gommander.NewCommand(\"first\").Help(\"A basic subcommand\"),\n    ).AddSubCommand(\n        gommander.NewCommand(\"second\").Help(\"Another basic subcommand\"),\n    )\n}\n// ...\n```\n\nYou could keep chaining subcommands using this method as shown. The `.SubCommand()` method invokes this one internally.\n\nThroughout the package, you will also notice a similar pattern to other methods. For instance, you can create flags via the `.Flag()` method, but there also exists a `.AddFlag()` method which follows the same rules as the subcommand method counterpart. The same also applies for [options](#options) and [arguments](#arguments).\n\n### Subcommand Groups\n\nYou can also add subcommands to groups. This may be done to change how they are printed when showing help. For this, the `.AddToGroup()` method may be used. An example of this is shown in the [subcommands](./examples/subcommands/subcommands.go) example.\n\n## Arguments\n\nArguments are values passed to a command as input. You can also pass them to an option. For instance, in the demo example in the [quick start section](#quick-start), the greet subcommand takes in a name as input. The program is therefore expected to be run as follows:\n\n```bash\n./demo.exe greet John\n```\n\nHere, `John` is a value that is an instance of the `\u003cname\u003e` argument. Adding arguments to commands is fairly simple. You could either use the [.Argument()](#argument-method) method or the [.AddArgument()](#addargument-method) one.\n\n### **Argument() method**\n\n```go\n// ...\nfunc main() {\n    app := gommander.App()\n\n    app.Argument(\"\u003cbasic\u003e\", \"Some basic argument\")\n}\n// ...\n```\n\nHere, we see the value of the argument enclosed between angle brackets. **This means that the argument is required** and therefore, an error will be thrown if one is not passed to the program. Optional arguments are represented by square brackets: `[arg]`. The argument is marked as optional if neither the square or angle brackets are provided.\nThe `.Argument()` method takes in the value of the argument and its help string/description. Here are the acceptable forms for the argument value:\n| Value | Semantics|\n|:-----|:--------|\n| `\u003carg\u003e` | Argument is required |\n| `\u003carg...\u003e` | Argument is required and is variadic|\n| `[arg]` | Argument is optional|\n| `[arg...]` | Argument is optional but variadic if provided|\n| `\u003cint:arg\u003e` | Required integer argument\n| `\u003cuint:arg\u003e` | Required unsigned integer argument\n| `\u003cfloat:arg\u003e` | Required float argument\n| `\u003cbool:arg\u003e` | Argument value should be `true` or `false`\n| `\u003cstr:arg\u003e` | Required string arg (all args are strings by default)\n| `\u003cfile:arg\u003e` | The provided arg must be an existing file or path\n\n### **AddArgument() method**\n\n```go\n// ...\nfunc main() {\n    app := gommander.App()\n\n    app.AddArgument(\n        gommander.NewArgument(\"language\").\n            Required(true).\n            Variadic(false).\n            ValidateWith([]string{\"ENGLISH\", \"SPANISH\", \"FRENCH\", \"SWAHILI\"}).\n            Default(\"ENGLISH\").\n            Help(\"The language to use\"),\n    )\n}\n// ...\n```\n\n- The `.AddArgument()` method, while more verbose, provides more flexibility in defining arguments. It ought to be used when defining more complex arguments. The `gommander.NewArgument()` returns an instance of an Argument to which you can chain more methods. Most of the methods are axiomatic and you can deduce their functionality from their names.\n- The `.ValidateWith()` method sets valid_values for an argument. If the value passed is not one of those values, a well-described error is thrown by the program and printed out.\n- The `.ValidatorFunc()` method is similar to the `ValidateWith()` method but instead takes in a function that accepts a string as the input to perform custom validation on and returns an error instance or nil depending on the value.\n- The `.Default()` method sets a default value for an argument. The default value is used if the argument is required, but the user passed no value.\n- The `.ValidatorRegex()` method receives a string containing the regex to be used for validating arguments. If the string is invalid regex, the program panics.\n\nAn example of the above-discussed methods is shown below:\n\n```go\n// ...\nfunc main() {\n    app := gommander.App()\n\n    app.AddArgument(\n        gommander.\n            NewArgument(\"\u003clanguage\u003e\").\n            ValidateWith([]string{\"ENGLISH\", \"SPANISH\", \"SWAHILI\"}).\n            Default(\"ENGLISH\"),\n    ).AddArgument(\n        gommander.\n            NewArgument(\"\u003cversion\u003e\").\n            ValidatorRegex(`^v*`).\n            Default(\"v0.1.0\"),\n    ).AddArgument(\n        gommander.\n            NewArgument(\"\u003crandom\u003e\").\n            ValidatorFunc(func(s string) error {\n                if strings.HasPrefix(s, \"X\") {\n                    return errors.New(\"values cannot begin with X\")\n                }\n                return nil\n            }),\n    )\n}\n// ...\n```\n\nNormally, you will use either the `ValidatorFunc` or `ValidatorRegex` or `ValidValues` method. If you use more tha one, **One of them will take precedence over the other:** (`ValidValues` \u003e `ValidatorFunc` \u003e `ValidatorRegex`)\n\n### Adding types to arguments\n\nArguments can also be typed meaning that values that are not of a given type will fail the validation. This is achieved as shown below:\n\n```go\n// ...\nfunc main() {\n    app := gommander.App()\n\n    app.Argument(\"\u003cint:count\u003e\", \"Pass a count\").\n        Argument(\"\u003cbool:verbose\u003e\", \"Verbosity\").\n        Argument(\"\u003cfloat:percentage\u003e\", \"Some percentage\")\n\n    // ...\n\n    app.Parse()\n}\n\n// ...\n```\n\nWhen a type is provided to an argument, a validator function is automatically added to the argument that checks if the value provided at runtime matches the arg type. If not, an error is displayed to the user.\nAll available types are: `int`, `float`, `bool`, `str`. It is redudant to declare an argument as `str` since it it the default type.\n\nArguments can also be passed to options. This is discussed in depth in the [options](#options) section\n\n## Flags\n\nFlags are values prefixed with either a `-` in their short form or `--` in their long form. Adding a flag to an instance of command is simple. You can achieve it in one of two ways:\n\n- The `.Flag()` method:\n\n```go\n// ...\nfunc main() {\n    app := gommander.App()\n\n    app.Flag(\"-V --verbose\", \"A flag for verbosity\")\n}\n// ...\n```\n\n- The `.AddFlag()` method:\n\n```go\n// ...\nfunc main() {\n    app := gommander.App()\n\n    app.AddFlag(\n        gommander.NewFlag(\"verbose\").\n            Short('V').\n            Help(\"A flag for verbosity\").\n            Global(true),\n    )\n}\n// ...\n```\n\nWhen a flag is set as global, it will propagate to all the app subcommands.\nThe parser also supports POSIX flag syntax; therefore, if a command contains flags, say `-i`, `-t`, `-d`, instead of passing the flags individually to the program, users can combine the flags as `itd`.\n\n## Options\n\nOptions are simply flags that take in a value as input. There are also two ways to declare options:\n\n- The `.Option()` method\n\n```go\n// ...\nfunc main() {\n    app := gommander.App()\n\n    app.Option(\"-p --port \u003cint:port-number\u003e\", \"The port number to use\")\n}\n// ...\n```\n\n- The `.AddOption()` method:\n\n```go\n// ...\nfunc main() {\n    app := gommander.App()\n\n    app.AddOption(\n        gommander.NewOption(\"port\").\n            Short('p').\n            AddArgument(\n                gommander.NewArgument(\"\u003cint:port-number\u003e\").\n                    Default(\"9000\"),\n            ),\n    )\n}\n// ...\n```\n\nWhen adding an argument, you can either use the `.Argument()` method or the `.AddArgument()` one.\nSupported option syntaxes are:\n\n- `-p 80`\n- `--port 80`\n- `--port=80`\n\n## Settings and Events\n\nThe default behavior of the program can be easily modified or even overridden. You can achieve this through settings and events.\nThe program settings are straightforward and can be accessed via the `Command.Set()` method. Settings configured via this method are simple boolean values and include:\n\n```go\n// ...\nfunc main() {\n    app := gommander.App()\n\n    // .... app logic\n\n    app.Set(gommander.IncludeHelpSubcommand, true).\n        Set(gommander.DisableVersionFlag, true).\n        Set(gommander.IgnoreAllErrors, false).\n        Set(gommander.ShowHelpOnAllErrors, true).\n        Set(gommander.ShowCommandAliases, true).\n        Set(gommander.OverrideAllDefaultListeners, false).\n        Set(gommander.AllowNegativeNumbers, true)\n\n    app.Parse()\n}\n// ...\n```\n\nThe package also has the concept of events that are emitted by the app and can be reacted to by adding new listeners or even overriding the default listeners.\n\n```go\n// ...\nfunc main() {\n    app := gommander.App()\n\n    // .... app logic\n\n    app.On(gommander.OutputVersion, func(ec *gommander.EventConfig) {\n        app := ec.GetApp()\n\n       fmt.Printf(\"You are using version: %v of %v which was authored by: %v\", app.GetVersion(), app.GetName(), app.GetAuthor())\n    })\n}\n// ...\n```\n\nThe `.On()` method is used to register a new callback for a given event. This callback is defined as: `type EventCallback = func(*gommander.EventConfig)`. The `.On()` method adds the new callback after the default ones. If you wish to override the default behavior, use the `.Override()` method to register the callback.\n\nOther event-related methods include:\n\n- `Command.BeforeAll()`\n- `Command.BeforeHelp()`\n- `Command.AfterAll()`\n- `Command.AfterHelp()`\n\n## Themes and UI\n\nThemes control the color palette used by the program. You can define your own theme or use predefined ones. The package uses `github.com/fatih/color` as a dependency for color functionality.\nThe package uses the concept of designations for theme functionality, i.e. `Keywords` are assigned one color, `Descriptions` another, `Errors` another and so forth.\n\nUsing a predefined theme is as shown below:\n\n```go\n// ...\nfunc main() {\n    app := gommander.App()\n\n    // .... app logic\n\n    app.UsePredefinedTheme(gommander.PlainTheme)\n}\n// ...\n```\n\nYou can also create your theme. You will need to import the `github.com/fatih/color` package.\n\n```go\napp.Theme(\n    gommander.NewTheme(color.FgGreen, color.FgBlue, color.FgRed, color.FgWhite, color.FgWhite),\n)\n```\n\nThe `NewTheme` method takes in values of type `ColorAttribute` defined in the `fatih/color` package. Here are examples of the said themes:\n\n### The default app theme:\n\n\u003cimg src=\"./assets/default_theme.png\"\u003e\n\n### The plain theme:\n\nYou can configure the app to use the plain theme as follows:\n\n```go\n// ...\n    app.UsePredefineTheme(gommander.PlainTheme)\n// ...\n```\n\n\u003cimg src=\"./assets/plain_theme.png\"\u003e\n\n### The colorful theme:\n\n```go\n// ...\n    app.UsePredefineTheme(gommander.ColorfulTheme)\n// ...\n```\n\n\u003cimg src=\"./assets/colorful_theme.png\"\u003e\n\n### Custom-defined themes:\n\nYou can easily define your custom theme as shown below:\n\n```go\npackage main\n\nimport (\n    \"github.com/ndaba1/gommander\"\n    \"github.com/fatih/color\"\n)\n\nfunc main() {\n    app := gommander.App()\n\n    app.Theme(\n        gommander.\n           NewTheme(color.FgYellow, color.FgCyan, color.FgMagenta, color.FgRed, color.FgWhite),\n    )\n}\n\n```\n\n\u003cimg src=\"./assets/custom_theme.png\"\u003e\n\n## Command Callbacks\n\nThe package only serves one purpose, to parse command-line arguments. Command callbacks are defined to define what to do with the parsed arguments. There are simply functions of the type: `func(*gommander.ParserMatches)` that get invoked when a command is matched. If a callback is not defined for a subcommand and the subcommand gets checked, help information gets printed out as the fallback behavior.\nThe `Command.Action()` function defines these functions.\n\nSee an example of this [here](./examples/demo/demo.go).\n\n## Error handling\n\nErrors are handled directly by the package. When an error is encountered, the program `emits` an event corresponding to this error which the set event listeners then catch. The program has pre-defined error listeners out of the box, but they can be overriden if you so choose and handle the error in a custom way. However, the error handling is sufficient by default. This is how errors are printed out by default:\n\n\u003cimg src=\"./assets/errors.png\"\u003e\n\nYou can configure the program to print out help information when an error is encountered by setting the said setting to true as shown:\n\n```go\n// ...\nfunc main() {\n    app := gommander.App()\n\n    // ...\n\n    app.Set(gommander.ShowHelpOnAllErrors, true)\n}\n```\n\nThis will in turn cause errors to be displayed as follows:\n\n\u003cimg src=\"./assets/errors_help.png\"\u003e\n\nWhen you define a custom event-listener for an error-event, the function takes in an `EventConfig` corresponding to the specified event. You can get specific information from this config. The following is an example:\n\n```go\n// ...\nfunc main() {\n    app := gommander.App()\n\n    app.On(gommander.MissingRequiredArgument, func(ec *gommander.EventConfig) {\n        args := ec.GetArgs()\n        fmt.Printf(\"\\nError: You are missing the following required argument: %v\\n\", args[0])\n        os.Exit(ec.GetExitCode())\n    })\n}\n// ...\n```\n\nThere are a few things to note about the above example:\n\n- The `EventConfig.GetArgs()` method returns a slice of different strings depending on the error event that was emitted, various events have been documented accordingly. In this case, there was only one string in the slice, which was the name of the missing argument\n- When defining custom-listeners, the `Command.On()` method does not remove the default listener, it only adds a new one, which will get invoked after the default ones. If you wish to override the default listener completely, use the `Command.Override()` method.\n- Different events have different exit codes that can be accessed via the `EventConfig.GetExitCode()` method.\n- You can add multiple listeners for a single event\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fndaba1%2Fgommander","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fndaba1%2Fgommander","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fndaba1%2Fgommander/lists"}