{"id":13410686,"url":"https://github.com/ukautz/clif","last_synced_at":"2025-08-30T20:13:59.379Z","repository":{"id":57487941,"uuid":"36570417","full_name":"ukautz/clif","owner":"ukautz","description":"Another CLI framework for Go. It works on my machine.","archived":false,"fork":false,"pushed_at":"2019-02-18T14:43:25.000Z","size":131,"stargazers_count":129,"open_issues_count":3,"forks_count":15,"subscribers_count":3,"default_branch":"v1","last_synced_at":"2025-04-01T18:56:09.771Z","etag":null,"topics":["cli","command-line","framework","go","golang"],"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/ukautz.png","metadata":{"files":{"readme":"README.md","changelog":"changelog.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2015-05-30T18:30:08.000Z","updated_at":"2024-08-22T06:17:36.000Z","dependencies_parsed_at":"2022-08-29T11:22:06.747Z","dependency_job_id":null,"html_url":"https://github.com/ukautz/clif","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/ukautz/clif","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ukautz%2Fclif","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ukautz%2Fclif/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ukautz%2Fclif/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ukautz%2Fclif/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ukautz","download_url":"https://codeload.github.com/ukautz/clif/tar.gz/refs/heads/v1","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ukautz%2Fclif/sbom","scorecard":{"id":907729,"data":{"date":"2025-08-11","repo":{"name":"github.com/ukautz/clif","commit":"df36acc242048b34c40bd8dd8c2acc460b4fc1c4"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3.2,"checks":[{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Code-Review","score":1,"reason":"Found 3/27 approved changesets -- score normalized to 1","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'v1'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 6 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-24T17:56:16.513Z","repository_id":57487941,"created_at":"2025-08-24T17:56:16.513Z","updated_at":"2025-08-24T17:56:16.513Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":272900164,"owners_count":25012034,"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","status":"online","status_checked_at":"2025-08-30T02:00:09.474Z","response_time":77,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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","framework","go","golang"],"created_at":"2024-07-30T20:01:08.423Z","updated_at":"2025-08-30T20:13:59.357Z","avatar_url":"https://github.com/ukautz.png","language":"Go","funding_links":[],"categories":["Command Line","命令行","命令行工具","Build Automation","命令行工具### 标准 CLI`用于创建一个标准命令行应用程序的库`","[Go](https://go.dev/)","\u003cspan id=\"命令行-command-line\"\u003e命令行 Command Line\u003c/span\u003e"],"sub_categories":["Standard CLI","标准CLI","標準命令行交互","标准 CLI","Useful awesome list for Dotnet cli","标准命令行交互"],"readme":"[![Build Status](https://travis-ci.org/ukautz/clif.svg?branch=master)](https://travis-ci.org/ukautz/clif)\n[![GoDoc](https://godoc.org/gopkg.in/ukautz/clif.v1?status.svg)](http://godoc.org/gopkg.in/ukautz/clif.v1)\n\n![logo-large](https://cloud.githubusercontent.com/assets/600604/11909504/c6e25a6e-a5e7-11e5-8eaf-f69fa577b744.png)\n\nCommand line interface framework\n================================\n\nGo framework for rapid command line application development.\n\nExample\n-------\n\n![demo](https://cloud.githubusercontent.com/assets/600604/8886731/c0a517e0-326f-11e5-8349-6ebee2cb8de5.gif)\n\n```go\npackage main\n\nimport \"gopkg.in/ukautz/clif.v1\"\n\nfunc main() {\n\tclif.New(\"My App\", \"1.0.0\", \"An example application\").\n\t\tNew(\"hello\", \"The obligatory hello world\", func(out clif.Output) {\n\t\t\tout.Printf(\"Hello World\\n\")\n\t\t}).\n\t\tRun()\n}\n```\n\n- - -\n\n\u003c!-- TOC START --\u003e\n\n* [Example](#example)\n* [Install](#install)\n* [Getting started](#getting-started)\n* [Commands](#commands)\n  * [Callback functions](#callback-functions)\n    * [Named](#named)\n    * [Default objects](#default-objects)\n  * [Arguments and Options](#arguments-and-options)\n    * [Arguments](#arguments)\n    * [Options](#options)\n      * [Flags](#flags)\n    * [Validation \u0026amp; (Parsing | Transformation)](#validation--parsing--transformation)\n    * [Environment variables \u0026amp; default](#environment-variables--default)\n    * [Default options](#default-options)\n* [Input \u0026amp; Output](#input--output)\n  * [Input](#input)\n    * [Ask \u0026amp; AskRegex](#ask--askregex)\n    * [Confirm](#confirm)\n    * [Choose](#choose)\n  * [Output \u0026amp; formatting](#output--formatting)\n    * [Output themes](#output-themes)\n    * [Styles](#styles)\n    * [Table](#table)\n    * [Progress bar](#progress-bar)\n* [Real-life example](#real-life-example)\n* [See also](#see-also)\n\n\u003c!-- TOC END --\u003e\n\n- - -\n\nInstall\n-------\n\n``` bash\n$ go get gopkg.in/ukautz/clif.v1\n```\n\nGetting started\n---------------\n\nOn the one side, CLIF's *builder*-like API can be easily used for rapid development of small, single purpose tools. On the other side, CLIF is designed with complex console applications in mind.\n\nCommands\n--------\n\nCommands must have a unique name and can have additional arguments and options.\n\n``` go\ncmd1 := clif.NewCommand(\"name\", \"A description\", callBackFunction)\ncmd2 := clif.NewCommand(\"other\", \"Another description\", callBackFunction2)\n```\n\nThe `name` is used from the command line to call the command:\n\n```bash\n$ ./app name\n$ ./app other\n```\n\n### Callback functions\n\nCallback functions can have arbitrary parameters. CLIF uses a small, built-in (signatur) injection container which allows you to register any kind of object (`struct` or `interface`) beforehand.\n\nSo you can register any object (interface{}, struct{} .. and anything else, see [below](#named)) in your bootstrap and then \"require\" those instances by simply putting them in the command callback signature:\n\n```go\n// Some type definition\ntype MyFoo struct {\n    X int\n}\n\nfunc main() {\n    // init cli\n    cli := clif.New(\"My App\", \"1.0.0\", \"An example application\")\n\n    // register object instance with container\n    foo := \u0026MyFoo{X: 123}\n    cli.Register(foo)\n\n    // Create command with callback using the peviously registered instance\n    cli.NewCommand(\"foo\", \"Call foo\", func (foo *MyFoo) {\n        // do something with foo\n    })\n\n    cli.Run()\n}\n```\n\nUsing interfaces is possible as well, but a bit less elegant:\n\n```go\n// Some interface\ntype MyBar interface {\n    Bar() string\n}\n\n// Some type\ntype MyFoo struct {\n}\n\n// implement interface\nfunc (m *MyFoo) Bar() string {\n    return \"bar\"\n}\n\nfunc main() {\n    // init cli\n    cli := clif.New(\"My App\", \"1.0.0\", \"An example application\")\n\n    // create object, which implements MyBar:\n    foo := \u0026MyFoo{}\n    t := reflect.TypeOf((*MyBar)(nil)).Elem()\n    cli.RegisterAs(t.String(), foo)\n\n    // Register command with callback using the type\n    cli.NewCommand(\"bar\", \"Call bar\", func (bar MyBar) {\n        // do something with bar\n    })\n\n    cli.Run()\n}\n```\n\n#### Named\n\nEverything works great if you only have a single instance of any object of a specific type.\nHowever, if you need more than one instance (which might often be the case for primitive\ntypes, such as `int` or `string`) you can use named registering:\n\n```go\n// Register abitrary objects under unique name\ncli.RegisterNamed(\"foo\", new(MyFoo)).\n    RegisterNamed(\"bar\", 123).\n    RegisterNamed(\"baz\", \"bla\")\n\n// Register command with callback named container\ncli.NewCommand(\"bar\", \"Call bar\", func (named clif.NamedParameters) {\n    asMap := map[string]interface{}(named)\n    fmt.Println(asMap[\"baz\"].(string))\n})\n```\n\n**Note**: If you want to use the named feature, you cannot `Register()` any `NamedParameters`\ninstance yourself, since \"normally\" registered objects are evaluated before named.\n\n#### Default objects\n\nCLIF pre-populates the dependency container with a couple of built-in objects:\n\n* The `Output` (formatted output helper, see below), eg `func (out clif.Output) { .. }`\n* The `Input` (input helper, see below), eg `func (in clif.Input) { .. }`\n* The `*Cli` instance itself, eg `func (c *clif.Cli) { .. }`\n* The current `*Command` instance, eg `func (o *clif.Command) { .. }`\n\n### Arguments and Options\n\nCLIF can deal with arguments and options. The difference being:\n\n* **Arguments** come after the command name. They are identified by their position.\n* **Options** have no fixed position. They are identified by their `--opt-name` (or alias, eg `-O`)\n\nOf course you can use arguments and options at the same time..\n\n#### Arguments\n\nArguments are additional command line parameters which come after the command name itself.\n\n``` go\ncmd := clif.NewCommand(\"hello\", \"A description\", callBackFunction)\n\t.NewArgument(\"name\", \"Name for greeting\", \"\", true, false)\n\narg := cmd.NewAgument(\"other\", \"Something ..\", \"default\", false, true)\ncmd.AddArgument(arg)\n```\n\nArguments consist of a *name*, a *description*, an optional *default* value a *required* flag and a *multiple* flag.\n\n``` bash\n$ ./my-app hello the-name other1 other2 other3\n#            ^      ^       ^       ^     ^\n#            |      |       |       |     |\n#            |      |       |       | third \"other\" arg\n#            |      |       |  second \"other\" arg\n#            |      |  first \"other\" arg\n#            |  the \"name\" arg\n#        command name\n```\n\nPosition of arguments matters. Make sure you add them in the right order. And: **required** arguments must come before optional arguments (makes sense, right?). There can be only one **multiple** argument at all and, of course, it must be the last (think: variadic).\n\nYou can access the arguments by injecting the command instance `*clif.Command` into the callback and calling the `Argument()` method. You can choose to interpret the argument as `String()`, `Int()`, `Float()`, `Bool()`, `Time()` or `Json()`. Multiple arguments can be accessed with `Strings()`, `Ints()` .. and so on. `Count()` gives the amount of (provided) multiple arguments and `Provided()` returns bool for optional arguments. Please see [parameter.go](parameter.go) for more.\n\n``` go\nfunc callbackFunctionI(c *clif.Command) {\n\t// a single\n\tname := c.Argument(\"name\").String()\n\n\t// a multiple\n\tothers := c.Argument(\"other\").Strings()\n\n\t// .. do something ..\n}\n```\n\n#### Options\n\nOptions have no fixed position, meaning `./app --foo --bar` and `./app --bar --foo` are equivalent. Options are referenced by their name (eg `--name`) or alias (eg `-n`). Unless the option is a flag (see below) it must have a value. The value must immediately follow the option. Valid forms are: `--name value`, `--name=value`, `-n value` and `-n=value`.\n\nOptions must come before the command, unless they use the `=` separator. For example: `./app command --opt value` is valid, `./app --opt=value command` is valid but `./app --opt value command` is not valid (since it becomes impossible to distinguish between command and value).\n\n``` go\ncmd := clif.NewCommand(\"hello\", \"A description\", callBackFunction)\n\t.NewOption(\"name\", \"n\", \"Name for greeting\", \"\", true, false)\n\narg := cmd.NewOption(\"other\", \"O\", \"Something ..\", \"default\", false, true)\ncmd.AddOption(arg)\n```\n\nNow:\n\n``` bash\n$ ./my-app hello --other bar -n Me -O foo\n#                       ^       ^    ^\n#                       |       |    |\n#                       |       |  second other opt with value\n#                       |   name opt with value\n#                  first other opt with value\n```\n\nYou can access options the same way as arguments, just use `Option()` instead.\n\n``` go\nfunc callbackFunctionI(c *clif.Command) {\n\tname := c.Option(\"name\").String()\n\tothers := c.Option(\"other\").Strings()\n\t// .. do something ..\n}\n```\n\n##### Flags\n\nThere is a special kind of option, which does not expect a parameter: the flag. As options, their position is arbitrary.\n\n``` go\n// shorthand\nflag := clif.NewFlag(\"my-flag\", \"f\", \"Something ..\", false)\n// which would just do:\nflag = clif.NewOption(\"my-flag\", \"f\", \"Something ..\", \"\", false, false).IsFlag()\ncmd := clif.NewCommand(\"hello\", \"A description\", callBackFunction).AddOption(flag)\n```\n\nWhen using the option, you dont need to (nor can you) provide an argument:\n\n```bash\n$ ./my-app hello --my-flag\n```\n\nYou want to use `Bool()` to check if a flag is provided:\n\n``` go\nfunc callbackFunctionI(c *clif.Command) {\n\tif c.Option(\"my-flag\").Bool() {\n\t\t// ..\n\t}\n}\n```\n\n#### Validation \u0026 (Parsing | Transformation)\n\nYou can validate/parse/transform the input using the `Parse` attribute of options or arguments. It can be (later on)\nset using the `SetParse()` method:\n\n``` go\n// Validation example\narg := clif.NewArgument(\"my-int\", \"An integer\", \"\", true, false).\n    SetParse(func(name, value string) (string, error) {\n        if _, err := strconv.Atoi(value); err != nil {\n            return \"\", fmt.Errorf(\"Oops: %s is not an integer: %s\", name, err)\n        } else {\n            return value, nil\n        }\n    })\n\n// Transformation example\nopt := clif.NewOption(\"client-id\", \"c\", \"The client ID\", \"\", true, false).\n    SetParse(func(name, value string) (string, error) {\n        if strings.Index(value, \"#\") != 0 {\n            return fmt.Sprintf(\"#%s\", value), nil\n        } else {\n            return value, nil\n        }\n    })\n```\n\nThere are a few built-in validators you can use out of the box:\n\n* `clif.IsInt` - Checks for integer, eg `clif.NewOption(..).SetParse(clif.IsInt)`\n* `clif.IsFloat` - Checks for float, eg `clif.NewOption(..).SetParse(clif.IsFloat)`\n\nSee [validators.go](validators.go).\n\n#### Environment variables \u0026 default\n\nThe argument and option constructors (`NewArgument`, `NewOption`) already allow you to set a default. In addition you can set\nthe name of an environment variable, which will be used, if the parameter is not provided.\n\n``` go\nopt := clif.NewOption(\"client-id\", \"c\", \"The client ID\", \"\", true, false).SetEnv(\"CLIENT_ID\")\n```\n\nThe order is:\n\n1. Provided, eg `--config /path/to/config`\n2. Environment variable, eg `CONFIG_FILE`\n3. Default value, as provided in constructor or set via `SetDefault()`\n\n**Note**: A *required* parameter must have a value, but it does not care whether it came from input, via environment variable or as a default value.\n\n#### Default options\n\nOften you need one or multiple options on every or most commands. The usual `--verbose` or `--config /path..` are common examples.\nCLIF provides two ways to deal with those.\n\n1. Modifying/extending `clif.DefaultOptions` (it's pre-filled with the `--help` option, which is `clif.DefaultHelpOption`)\n2. Calling `AddDefaultOptions()` or `NewDefaultOption()` on an instance of `clif.Cli`\n\nThe former is global (for any instance of `clif.Cli`) and assigned to any new command (created by the `NewCommand` constructor). The latter is applied when `Run()` is called and is in the scope of a single `clif.Cli` instance.\n\n**Note**: A helpful patterns is combining default options and the injection container/registry. Following an example parsing a config file, which can be set on any command with `--config /path..` or as an environment variable and has a default path.\n\n```go\n\ntype Conf struct {\n    Foo string\n    Bar string\n}\n\nfunc() main {\n\n    // init new cli app\n    cli := clif.New(\"my-app\", \"1.2.3\", \"My app that does something\")\n\n    // register default option, which fills injection container with config instance\n    configOpt := clif.NewOption(\"config\", \"c\", \"Path to config file\", \"/default/config/path.json\", true, false).\n        SetEnv(\"MY_APP_CONFIG\").\n        SetParse(function(name, value string) (string, error) {\n            conf := new(Conf)\n            if raw, err := ioutil.ReadFile(value); err != nil {\n                return \"\", fmt.Errorf(\"Could not read config file %s: %s\", value, err)\n            } else if err = json.Unmarshal(raw, conf); err != nil {\n                return \"\", fmt.Errorf(\"Could not unmarshal config file %s: %s\", value, err)\n            } else if conf.Foo == \"\" {\n                return \"\", fmt.Errorf(\"Config %s is missing \\\"foo\\\"\", value)\n            } else {\n                // register *Conf\n                cli.Register(conf)\n                return value, nil\n            }\n        })\n    cli.AddDefaultOptions(configOpt)\n\n    // Since *Conf was registered it can be used in any callback\n    cli.New(\"anything\", \"Does anything\", func(conf *Conf) {\n        // do something with conf\n    })\n\n    cli.Run()\n}\n```\n\nInput \u0026 Output\n--------------\n\nOf course, you can just use `fmt` and `os.Stdin`, but for convenience (and fancy output) there are `clif.Output` and `clif.Input`.\n\n### Input\n\nYou can inject an instance of the `clif.Input` interface into your command callback. It provides small set of often used tools.\n\n![input](https://cloud.githubusercontent.com/assets/600604/8886968/378a2668-3273-11e5-8bda-51b2b5cd127b.png)\n\n#### Ask \u0026 AskRegex\n\nJust ask the user a question then read \u0026 check the input. The question will be asked until the check/requirement is satisfied (or the user exits out with `ctrl+c`):\n\n``` go\nfunc callbackFunctionI(in clif.Input) {\n\t// Any input is OK\n\tfoo := in.Ask(\"What is a foo\", nil)\n\n\t// Validate input\n\tname := in.Ask(\"Who are you? \", func(v string) error {\n\t\tif len(v) \u003e 0 {\n\t\t\treturn nil\n\t\t} else {\n\t\t\treturn fmt.Errorf(\"Didn't catch that\")\n\t\t}\n\t})\n\n\t// Shorthand for regex validation\n\tcount := in.AskRegex(\"How many? \", regexp.MustCompile(`^[0-9]+$`))\n\n\t// ..\n}\n```\n\n*See `clif.RenderAskQuestion` for customization.*\n\n#### Confirm\n\n`Confirm()` ask the user a question until it is answered with `yes` (or `y`) or `no` (or `n`) and returns the response as `bool`.\n\n``` go\nfunc callbackFunctionI(in clif.Input) {\n\tif in.Confirm(\"Let's do it?\") {\n\t\t// ..\n\t}\n}\n```\n\n*See `clif.ConfirmRejection`, `clif.ConfirmYesRegex` and `clif.ConfirmNoRegex` for customization.*\n\n#### Choose\n\n`Choose()` is like a select in HTML and provides a list of options with descriptions to the user. The user then must choose (type in) one of the options. The choices will be presented to the user until a valid choice (one of the options) is provided.\n\n``` go\nfunc callbackFunctionI(in clif.Input) {\n\tfather := in.Choose(\"Who is your father?\", map[string]string{\n\t\t\"yoda\":  \"The small, green guy\",\n\t\t\"darth\": \"The one with the smoker voice and the dark cape!\",\n\t\t\"obi\":   \"The old man with the light thingy\",\n\t})\n\n\tif father == \"darth\" {\n\t\t// ..\n\t}\n}\n```\n\n*See `clif.RenderChooseQuestion`, `clif.RenderChooseOption` and `clif.RenderChooseQuery` for customization.*\n\n### Output \u0026 formatting\n\nThe `clif.Output` interface can be injected into any callback. It relies on a `clif.Formatter`, which does the actual formatting (eg colorizing) of the text.\n\n#### Output themes\n\nPer default, the `clif.DefaultInput` via `clif.NewColorOutput()` is used. It uses `clif.DefaultStyles`, which look like the screenshots you are seeing in this readme.\n\nYou can change the output like so:\n\n``` go\ncli := clif.New(..)\ncli.SetOutput(clif.NewColorOutput().\n    SetFormatter(clif.NewDefaultFormatter(clif.SunburnStyles))\n```\n\n#### Styles\n\nStyles are applied by parsing (replacing) tokens like `\u003cerror\u003e`, which would be substitude with `\\033[31;1m` (using the default styles) resulting in a red coloring. Another example is `\u003creset\u003e`, which is replaced with `\\033[0m` leading to reset all colorings \u0026 styles.\n\nThere three built-in color styles (of course, you can extend them or add your own):\n\n1. `DefaultStyles` - as you can see on this page\n1. `SunburnStyles` - more yellow'ish\n1. `WinterStyles` - more blue'ish\n\n\n#### Table\n\nTable rendering is a neat tool for CLIs. CLIF supports tables out of the box using the `Output` interface.\n\n**Features:**\n\n* Multi-line columns\n* Column auto fit\n* Formatting (color) within columns\n* Automatic stretch to max size (unless specifcied otherwise)\n\n**Example:**\n\n``` go\nvar (\n    headers := []string{\"Name\", \"Age\", \"Force\"}\n    rows = [][]string{\n        {\n            \"\u003cimportant\u003eYoda\u003creset\u003e\",\n            \"Very, very old\",\n            \"Like the uber guy\",\n        },\n        {\n            \"\u003cimportant\u003eLuke Skywalker\u003creset\u003e\",\n            \"Not that old\",\n            \"A bit, but not that much\",\n        },\n        {\n            \"\u003cimportant\u003eAnakin Skywalker\u003creset\u003e\",\n            \"Old dude\",\n            \"He is Lukes father! Was kind of stronger in 1-3, but still failed to\" +\n                \" kill Jar Jar Binks. Not even tried, though. What's with that?\",\n        },\n\t}\n)\n\nfunc callbackFunction(out clif.Output) {\n\ttable := out.Table(headers)\n\ttable.AddRows(rows)\n\tfmt.Println(table.Render())\n}\n```\n\nWould print the following:\n\n![table-1](https://cloud.githubusercontent.com/assets/600604/11908046/37aa3578-a5d9-11e5-99a3-cc66937b965c.gif)\n\nThere are currently to styles available: `ClosedTableStyle` (above), `ClosedTableStyleLight`, `OpenTableStyle` (below) and `OpenTableStyleLight`:\n\n``` go\nfunc callbackFunction(out clif.Output) {\n\ttable := out.Table(headers, clif.OpenTableStyle)\n\ttable.AddRows(rows)\n\tfmt.Println(table.Render())\n}\n```\n\nWould print the following:\n\n![table-2](https://cloud.githubusercontent.com/assets/600604/11908047/37ac10f0-a5d9-11e5-9f95-b7ae59d26ac9.gif)\n\n#### Progress bar\n\nAnother often required tool is the progress bar. Hence CLIF provides one out of the box:\n\n```go\nfunc cmdProgress(out clif.Output) error {\n\tpbs := out.ProgressBars()\n\tpb, _ := pbs.Init(\"default\", 200)\n\tpbs.Start()\n\tvar wg sync.WaitGroup\n\twg.Add(1)\n\tgo func(b clif.ProgressBar) {\n\t\tdefer wg.Done()\n\t\tfor i := 0; i \u003c 200; i++ {\n\t\t\tb.Increment()\n\t\t\t\u003c-time.After(time.Millisecond * 100)\n\t\t}\n\t}(pb)\n\twg.Wait()\n\t\u003c-pbs.Finish()\n}\n```\n\nWould output this:\n\n![progress-1](https://cloud.githubusercontent.com/assets/600604/11908868/796206c4-a5e0-11e5-9b5b-ffae60f19b99.gif)\n\nMultiple bars are also possible, thanks to [Greg Osuri's library](github.com/gosuri/uilive):\n\n```go\nfunc cmdProgress(out clif.Output) error {\n\tpbs := out.ProgressBars()\n\tpbs.Start()\n\tvar wg sync.WaitGroup\n\tfor i := 0; i \u003c 3; i++ {\n\t\twg.Add(1)\n\t\tpb, _ := pbs.Init(fmt.Sprintf(\"bar-%d\", i+1), 200)\n\t\tgo func(b clif.ProgressBar, ii int) {\n\t\t\tdefer wg.Done()\n\t\t\tfor i := 0; i \u003c 200; i++ {\n\t\t\t\tb.Increment()\n\t\t\t\t\u003c-time.After(time.Millisecond * time.Duration(100 * ii))\n\t\t\t}\n\t\t}(pb, i)\n\t}\n\twg.Wait()\n\t\u003c-pbs.Finish()\n}\n```\n\nWould output this:\n\n![progress-2](https://cloud.githubusercontent.com/assets/600604/11908867/795f1338-a5e0-11e5-8d2c-aa2d8f2802e5.gif)\n\nYou prefer those ASCII arrows? Just set `pbs.SetStyle(clif.ProgressBarStyleAscii)` and:\n\n![progress-3](https://cloud.githubusercontent.com/assets/600604/11908964/ac90e83e-a5e1-11e5-8f8a-d3ebaa8c5903.gif)\n\n\nReal-life example\n-----------------\n\nTo provide you a usful'ish example, I've written a small CLI application called [repos](https://github.com/ukautz/repos).\n\nSee also\n--------\n\nThere are a lot of [other approaches](https://github.com/avelino/awesome-go#command-line) you should have a look at.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fukautz%2Fclif","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fukautz%2Fclif","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fukautz%2Fclif/lists"}