diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..22bb4f4 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,17 @@ +on: + push: + branches: + - master + pull_request: +name: CI +jobs: + test: + name: Test + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + - name: Init Hermit + run: ./bin/hermit env -r >> $GITHUB_ENV + - name: Test + run: go test ./... diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 9c45bac..0000000 --- a/.travis.yml +++ /dev/null @@ -1,14 +0,0 @@ -sudo: false -language: go -install: go get -t -v ./... -go: - - 1.2.x - - 1.3.x - - 1.4.x - - 1.5.x - - 1.6.x - - 1.7.x - - 1.8.x - - 1.9.x - - 1.10.x - - 1.11.x diff --git a/README.md b/README.md index 73bcedf..932e451 100644 --- a/README.md +++ b/README.md @@ -4,12 +4,11 @@ **Current status.** Kingpin is largely feature stable. There hasn't been a need to add new features in a while, but there are some bugs that should be fixed. -**Why?** I no longer use Kingpin personally (I now use [kong](https://github.com/alecthomas/kong)). Rather than leave the project in a limbo of people filing issues and wondering why they're note being worked on, I believe this notice will more clearly set expectations. - -# Kingpin - A Go (golang) command line and flag parser -[![](https://godoc.org/github.com/alecthomas/kingpin?status.svg)](http://godoc.org/github.com/alecthomas/kingpin) [![Build Status](https://travis-ci.org/alecthomas/kingpin.svg?branch=master)](https://travis-ci.org/alecthomas/kingpin) [![Gitter chat](https://badges.gitter.im/alecthomas.png)](https://gitter.im/alecthomas/Lobby) +**Why?** I no longer use Kingpin personally (I now use [kong](https://github.com/alecthomas/kong)). Rather than leave the project in a limbo of people filing issues and wondering why they're not being worked on, I believe this notice will more clearly set expectations. +## Kingpin - A Go (golang) command line and flag parser +[![](https://godoc.org/github.com/alecthomas/kingpin?status.svg)](http://godoc.org/github.com/alecthomas/kingpin) [![Build Status](https://travis-ci.org/alecthomas/kingpin.svg?branch=master)](https://travis-ci.org/alecthomas/kingpin) [![Gitter chat](https://badges.gitter.im/alecthomas.png)](https://gitter.im/alecthomas/Lobby) @@ -41,7 +40,7 @@ -## Overview +### Overview Kingpin is a [fluent-style](http://en.wikipedia.org/wiki/Fluent_interface), type-safe command-line parser. It supports flags, nested commands, and @@ -49,7 +48,9 @@ positional arguments. Install it with: - $ go get gopkg.in/alecthomas/kingpin.v2 +```bash +go get gopkg.in/alecthomas/kingpin.v2 +``` It looks like this: @@ -72,10 +73,10 @@ important thing a command-line parser does. Kingpin tries to provide detailed contextual help if `--help` is encountered at any point in the command line (excluding after `--`). -## Features +### Features - Help output that isn't as ugly as sin. -- Fully [customisable help](#custom-help), via Go templates. +- Fully [customizable help](#custom-help), via Go templates. - Parsed, type-safe flags (`kingpin.Flag("f", "help").Int()`) - Parsed, type-safe positional arguments (`kingpin.Arg("a", "help").Int()`). - Parsed, type-safe, arbitrarily deep commands (`kingpin.Command("c", "help")`). @@ -87,30 +88,30 @@ contextual help if `--help` is encountered at any point in the command line - Read command-line from files (`@`). - Automatically generate man pages (`--help-man`). -## User-visible changes between v1 and v2 +### User-visible changes between v1 and v2 -### Flags can be used at any point after their definition. +#### Flags can be used at any point after their definition Flags can be specified at any point after their definition, not just *immediately after their associated command*. From the chat example below, the following used to be required: -``` -$ chat --server=chat.server.com:8080 post --image=~/Downloads/owls.jpg pics +```bash +chat --server=chat.server.com:8080 post --image=~/Downloads/owls.jpg pics ``` But the following will now work: -``` -$ chat post --server=chat.server.com:8080 --image=~/Downloads/owls.jpg pics +```bash +chat post --server=chat.server.com:8080 --image=~/Downloads/owls.jpg pics ``` -### Short flags can be combined with their parameters +#### Short flags can be combined with their parameters Previously, if a short flag was used, any argument to that flag would have to be separated by a space. That is no longer the case. -## API changes between v1 and v2 +### API changes between v1 and v2 - `ParseWithFileExpansion()` is gone. The new parser directly supports expanding `@`. - Added `FatalUsage()` and `FatalUsageContext()` for displaying an error + usage and terminating. @@ -122,102 +123,102 @@ be separated by a space. That is no longer the case. 1. `DefaultUsageTemplate` - default template. 2. `CompactUsageTemplate` - compact command template for larger applications. -## Versions +### Versions -Kingpin uses [gopkg.in](https://gopkg.in/alecthomas/kingpin) for versioning. +The current stable version is [github.com/alecthomas/kingpin/v2](https://github.com/alecthomas/kingpin/v2). The previous version, [gopkg.in/alecthomas/kingpin.v1](https://gopkg.in/alecthomas/kingpin.v1), is deprecated and in maintenance mode. -The current stable version is [gopkg.in/alecthomas/kingpin.v2](https://gopkg.in/alecthomas/kingpin.v2). The previous version, [gopkg.in/alecthomas/kingpin.v1](https://gopkg.in/alecthomas/kingpin.v1), is deprecated and in maintenance mode. +### [V2](https://github.com/alecthomas/kingpin/v2) is the current stable version -### [V2](https://gopkg.in/alecthomas/kingpin.v2) is the current stable version +#### [V2](https://gopkg.in/alecthomas/kingpin.v2) is the current stable version Installation: ```sh -$ go get gopkg.in/alecthomas/kingpin.v2 +go get gopkg.in/alecthomas/kingpin.v2 ``` -### [V1](https://gopkg.in/alecthomas/kingpin.v1) is the OLD stable version +#### [V1](https://gopkg.in/alecthomas/kingpin.v1) is the OLD stable version Installation: ```sh -$ go get gopkg.in/alecthomas/kingpin.v1 +go get gopkg.in/alecthomas/kingpin.v1 ``` -## Change History +### Change History - *2015-09-19* -- Stable v2.1.0 release. - - Added `command.Default()` to specify a default command to use if no other - command matches. This allows for convenient user shortcuts. - - Exposed `HelpFlag` and `VersionFlag` for further customisation. - - `Action()` and `PreAction()` added and both now support an arbitrary - number of callbacks. - - `kingpin.SeparateOptionalFlagsUsageTemplate`. - - `--help-long` and `--help-man` (hidden by default) flags. - - Flags are "interspersed" by default, but can be disabled with `app.Interspersed(false)`. - - Added flags for all simple builtin types (int8, uint16, etc.) and slice variants. - - Use `app.Writer(os.Writer)` to specify the default writer for all output functions. - - Dropped `os.Writer` prefix from all printf-like functions. + - Added `command.Default()` to specify a default command to use if no other + command matches. This allows for convenient user shortcuts. + - Exposed `HelpFlag` and `VersionFlag` for further customisation. + - `Action()` and `PreAction()` added and both now support an arbitrary + number of callbacks. + - `kingpin.SeparateOptionalFlagsUsageTemplate`. + - `--help-long` and `--help-man` (hidden by default) flags. + - Flags are "interspersed" by default, but can be disabled with `app.Interspersed(false)`. + - Added flags for all simple builtin types (int8, uint16, etc.) and slice variants. + - Use `app.Writer(os.Writer)` to specify the default writer for all output functions. + - Dropped `os.Writer` prefix from all printf-like functions. - *2015-05-22* -- Stable v2.0.0 release. - - Initial stable release of v2.0.0. - - Fully supports interspersed flags, commands and arguments. - - Flags can be present at any point after their logical definition. - - Application.Parse() terminates if commands are present and a command is not parsed. - - Dispatch() -> Action(). - - Actions are dispatched after all values are populated. - - Override termination function (defaults to os.Exit). - - Override output stream (defaults to os.Stderr). - - Templatised usage help, with default and compact templates. - - Make error/usage functions more consistent. - - Support argument expansion from files by default (with @). - - Fully public data model is available via .Model(). - - Parser has been completely refactored. - - Parsing and execution has been split into distinct stages. - - Use `go generate` to generate repeated flags. - - Support combined short-flag+argument: -fARG. + - Initial stable release of v2.0.0. + - Fully supports interspersed flags, commands and arguments. + - Flags can be present at any point after their logical definition. + - Application.Parse() terminates if commands are present and a command is not parsed. + - Dispatch() -> Action(). + - Actions are dispatched after all values are populated. + - Override termination function (defaults to os.Exit). + - Override output stream (defaults to os.Stderr). + - Templated usage help, with default and compact templates. + - Make error/usage functions more consistent. + - Support argument expansion from files by default (with @file). + - Fully public data model is available via .Model(). + - Parser has been completely refactored. + - Parsing and execution has been split into distinct stages. + - Use `go generate` to generate repeated flags. + - Support combined short-flag+argument: -fARG. - *2015-01-23* -- Stable v1.3.4 release. - - Support "--" for separating flags from positional arguments. - - Support loading flags from files (ParseWithFileExpansion()). Use @FILE as an argument. - - Add post-app and post-cmd validation hooks. This allows arbitrary validation to be added. - - A bunch of improvements to help usage and formatting. - - Support arbitrarily nested sub-commands. + - Support "--" for separating flags from positional arguments. + - Support loading flags from files (ParseWithFileExpansion()). Use @FILE as an argument. + - Add post-app and post-cmd validation hooks. This allows arbitrary validation to be added. + - A bunch of improvements to help usage and formatting. + - Support arbitrarily nested sub-commands. - *2014-07-08* -- Stable v1.2.0 release. - - Pass any value through to `Strings()` when final argument. - Allows for values that look like flags to be processed. - - Allow `--help` to be used with commands. - - Support `Hidden()` flags. - - Parser for [units.Base2Bytes](https://github.com/alecthomas/units) - type. Allows for flags like `--ram=512MB` or `--ram=1GB`. - - Add an `Enum()` value, allowing only one of a set of values - to be selected. eg. `Flag(...).Enum("debug", "info", "warning")`. + - Pass any value through to `Strings()` when final argument. + Allows for values that look like flags to be processed. + - Allow `--help` to be used with commands. + - Support `Hidden()` flags. + - Parser for [units.Base2Bytes](https://github.com/alecthomas/units) + type. Allows for flags like `--ram=512MB` or `--ram=1GB`. + - Add an `Enum()` value, allowing only one of a set of values + to be selected. eg. `Flag(...).Enum("debug", "info", "warning")`. - *2014-06-27* -- Stable v1.1.0 release. - - Bug fixes. - - Always return an error (rather than panicing) when misconfigured. - - `OpenFile(flag, perm)` value type added, for finer control over opening files. - - Significantly improved usage formatting. + - Bug fixes. + - Always return an error (rather than panicking) when misconfigured. + - `OpenFile(flag, perm)` value type added, for finer control over opening files. + - Significantly improved usage formatting. - *2014-06-19* -- Stable v1.0.0 release. - - Support [cumulative positional](#consuming-all-remaining-arguments) arguments. - - Return error rather than panic when there are fatal errors not caught by - the type system. eg. when a default value is invalid. - - Use gokpg.in. + - Support [cumulative positional](#consuming-all-remaining-arguments) arguments. + - Return error rather than panic when there are fatal errors not caught by + the type system. eg. when a default value is invalid. + - Use gokpg.in. - *2014-06-10* -- Place-holder streamlining. - - Renamed `MetaVar` to `PlaceHolder`. - - Removed `MetaVarFromDefault`. Kingpin now uses [heuristics](#place-holders-in-help) - to determine what to display. + - Renamed `MetaVar` to `PlaceHolder`. + - Removed `MetaVarFromDefault`. Kingpin now uses [heuristics](#place-holders-in-help) + to determine what to display. -## Examples +### Examples -### Simple Example +#### Simple Example Kingpin can be used for simple flag+arg applications like so: -``` +```sh $ ping --help usage: ping [] [] @@ -241,12 +242,12 @@ package main import ( "fmt" - "gopkg.in/alecthomas/kingpin.v2" + "github.com/alecthomas/kingpin/v2" ) var ( debug = kingpin.Flag("debug", "Enable debug mode.").Bool() - timeout = kingpin.Flag("timeout", "Timeout waiting for ping.").Default("5s").OverrideDefaultFromEnvar("PING_TIMEOUT").Short('t').Duration() + timeout = kingpin.Flag("timeout", "Timeout waiting for ping.").Default("5s").Envar("PING_TIMEOUT").Short('t').Duration() ip = kingpin.Arg("ip", "IP address to ping.").Required().IP() count = kingpin.Arg("count", "Number of packets to send").Int() ) @@ -258,15 +259,19 @@ func main() { } ``` -#### Reading arguments from a file +##### Reading arguments from a file + Kingpin supports reading arguments from a file. -Creat a file with the corresponding arguments: -``` +Create a file with the corresponding arguments: + +```sh echo -t=5\n > args ``` + And now supply it: -``` -$ ping @args + +```sh +ping @args ``` ### Complex Example @@ -274,7 +279,7 @@ $ ping @args Kingpin can also produce complex command-line applications with global flags, subcommands, and per-subcommand flags, like this: -``` +```sh $ chat --help usage: chat [] [] [ ...] @@ -319,7 +324,7 @@ package main import ( "os" "strings" - "gopkg.in/alecthomas/kingpin.v2" + "github.com/alecthomas/kingpin/v2" ) var ( @@ -362,7 +367,9 @@ information to the user. Error messages look something like this: - : error: +```text +: error: +``` The functions on `Application` are: @@ -473,14 +480,16 @@ The place-holder value for a flag is the value used in the help to describe the value of a non-boolean flag. The value provided to PlaceHolder() is used if provided, then the value -provided by Default() if provided, then finally the capitalised flag name is +provided by Default() if provided, then finally the capitalized flag name is used. Here are some examples of flags with various permutations: - --name=NAME // Flag(...).String() - --name="Harry" // Flag(...).Default("Harry").String() - --name=FULL-NAME // Flag(...).PlaceHolder("FULL-NAME").Default("Harry").String() +```text +--name=NAME // Flag(...).String() +--name="Harry" // Flag(...).Default("Harry").String() +--name=FULL-NAME // Flag(...).PlaceHolder("FULL-NAME").Default("Harry").String() +``` ### Consuming all remaining arguments @@ -488,7 +497,9 @@ A common command-line idiom is to use all remaining arguments for some purpose. eg. The following command accepts an arbitrary number of IP addresses as positional arguments: - ./cmd ping 10.1.1.1 192.168.1.1 +```sh +./cmd ping 10.1.1.1 192.168.1.1 +``` Such arguments are similar to [repeatable flags](#repeatable-flags), but for arguments. Therefore they use the same `IsCumulative() bool` function on the @@ -551,7 +562,7 @@ Fortunately Kingpin makes it easy to generate or source a script for use with end users shells. `./yourtool --completion-script-bash` and `./yourtool --completion-script-zsh` will generate these scripts for you. -**Installation by Package** +#### Installation by Package For the best user experience, you should bundle your pre-created completion script with your CLI tool and install it inside @@ -559,35 +570,35 @@ completion script with your CLI tool and install it inside this as an automated step to your build pipeline, in the implementation is improved for bug fixed. -**Installation by `bash_profile`** +#### Installation by `bash_profile` Alternatively, instruct your users to add an additional statement to their `bash_profile` (or equivalent): -``` +```sh eval "$(your-cli-tool --completion-script-bash)" ``` Or for ZSH -``` +```zsh eval "$(your-cli-tool --completion-script-zsh)" ``` #### Additional API + To provide more flexibility, a completion option API has been exposed for flags to allow user defined completion options, to extend completions further than just EnumVar/Enum. - -**Provide Static Options** +##### Provide Static Options When using an `Enum` or `EnumVar`, users are limited to only the options given. Maybe we wish to hint possible options to the user, but also allow them to provide their own custom option. `HintOptions` gives this functionality to flags. -``` +```go app := kingpin.New("completion", "My application with bash completion.") app.Flag("port", "Provide a port to connect to"). Required(). @@ -595,11 +606,12 @@ app.Flag("port", "Provide a port to connect to"). IntVar(&c.port) ``` -**Provide Dynamic Options** +##### Provide Dynamic Options + Consider the case that you needed to read a local database or a file to provide suggestions. You can dynamically generate the options -``` +```go func listHosts() []string { // Provide a dynamic list of hosts from a hosts file or otherwise // for bash completion. In this example we simply return static slice. @@ -619,12 +631,10 @@ used for bash autocompletion. However, if you wish to provide a subset or different options, you can use `HintOptions` or `HintAction` which will override the default completion options for `Enum`/`EnumVar`. - **Examples** You can see an in depth example of the completion API within `examples/completion/main.go` - ### Supporting -h for help `kingpin.CommandLine.HelpFlag.Short('h')` @@ -633,13 +643,13 @@ Short help is also available when creating a more complicated app: ```go var ( - app = kingpin.New("chat", "A command-line chat application.") + app = kingpin.New("chat", "A command-line chat application.") // ... ) func main() { - app.HelpFlag.Short('h') - switch kingpin.MustParse(app.Parse(os.Args[1:])) { + app.HelpFlag.Short('h') + switch kingpin.MustParse(app.Parse(os.Args[1:])) { // ... } } @@ -647,9 +657,9 @@ func main() { ### Custom help -Kingpin v2 supports templatised help using the text/template library (actually, [a fork](https://github.com/alecthomas/template)). +Kingpin v2 supports templated help using the text/template library (actually, [a fork](https://github.com/alecthomas/template)). -You can specify the template to use with the [Application.UsageTemplate()](http://godoc.org/gopkg.in/alecthomas/kingpin.v2#Application.UsageTemplate) function. +You can specify the template to use with the [Application.UsageTemplate()](http://godoc.org/github.com/alecthomas/kingpin/v2#Application.UsageTemplate) function. There are four included templates: `kingpin.DefaultUsageTemplate` is the default, `kingpin.CompactUsageTemplate` provides a more compact representation for more complex command-line structures, @@ -660,7 +670,7 @@ See the above templates for examples of usage, and the the function [UsageForCon #### Default help template -``` +```bash $ go run ./examples/curl/curl.go --help usage: curl [] [ ...] @@ -688,7 +698,7 @@ Commands: #### Compact help template -``` +```bash $ go run ./examples/curl/curl.go --help usage: curl [] [ ...] diff --git a/_examples/chat1/main.go b/_examples/chat1/main.go index d28192b..ac7a553 100644 --- a/_examples/chat1/main.go +++ b/_examples/chat1/main.go @@ -3,7 +3,7 @@ package main import ( "fmt" - "gopkg.in/alecthomas/kingpin.v2" + "github.com/alecthomas/kingpin/v2" ) var ( diff --git a/_examples/chat2/main.go b/_examples/chat2/main.go index 83891a7..d63c42d 100644 --- a/_examples/chat2/main.go +++ b/_examples/chat2/main.go @@ -4,7 +4,7 @@ import ( "os" "strings" - "gopkg.in/alecthomas/kingpin.v2" + "github.com/alecthomas/kingpin/v2" ) var ( diff --git a/_examples/completion/main.go b/_examples/completion/main.go index 0bbabe3..816d6fc 100644 --- a/_examples/completion/main.go +++ b/_examples/completion/main.go @@ -4,7 +4,7 @@ import ( "fmt" "os" - "gopkg.in/alecthomas/kingpin.v2" + "github.com/alecthomas/kingpin/v2" ) func listHosts() []string { diff --git a/_examples/curl/main.go b/_examples/curl/main.go index a877e7b..0e807b7 100644 --- a/_examples/curl/main.go +++ b/_examples/curl/main.go @@ -9,7 +9,7 @@ import ( "os" "strings" - "gopkg.in/alecthomas/kingpin.v2" + "github.com/alecthomas/kingpin/v2" ) var ( diff --git a/_examples/go.mod b/_examples/go.mod new file mode 100644 index 0000000..b191f58 --- /dev/null +++ b/_examples/go.mod @@ -0,0 +1,13 @@ +module examples + +go 1.19 + +replace github.com/alecthomas/kingpin/v2 => ../ + +require github.com/alecthomas/kingpin/v2 v2.4.0 + +require ( + github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect + github.com/xhit/go-str2duration v1.2.0 // indirect + github.com/xhit/go-str2duration/v2 v2.1.0 // indirect +) diff --git a/_examples/go.sum b/_examples/go.sum new file mode 100644 index 0000000..3b3cdc8 --- /dev/null +++ b/_examples/go.sum @@ -0,0 +1,19 @@ +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAuRjVTiNNhvNRfY2Wxp9nhfyel4rklc= +github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/xhit/go-str2duration v1.2.0 h1:BcV5u025cITWxEQKGWr1URRzrcXtu7uk8+luz3Yuhwc= +github.com/xhit/go-str2duration v1.2.0/go.mod h1:3cPSlfZlUHVlneIVfePFWcJZsuwf+P1v2SRTV4cUmp4= +github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc= +github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/_examples/modular/main.go b/_examples/modular/main.go index 34cfa0b..d15c2e4 100644 --- a/_examples/modular/main.go +++ b/_examples/modular/main.go @@ -4,7 +4,7 @@ import ( "fmt" "os" - "gopkg.in/alecthomas/kingpin.v2" + "github.com/alecthomas/kingpin/v2" ) // Context for "ls" command diff --git a/_examples/ping/main.go b/_examples/ping/main.go index 41ea263..a729bdd 100644 --- a/_examples/ping/main.go +++ b/_examples/ping/main.go @@ -3,7 +3,7 @@ package main import ( "fmt" - "gopkg.in/alecthomas/kingpin.v2" + "github.com/alecthomas/kingpin/v2" ) var ( diff --git a/app.go b/app.go index a4b73b0..44a56bc 100644 --- a/app.go +++ b/app.go @@ -458,6 +458,10 @@ func (a *Application) setDefaults(context *ParseContext) error { if flag.name == "help" { return nil } + + if flag.name == "version" { + return nil + } flagElements[flag.name] = element } } @@ -520,14 +524,18 @@ func (a *Application) validateRequired(context *ParseContext) error { } // Check required flags and set defaults. + var missingFlags []string for _, flag := range context.flags.long { if flagElements[flag.name] == nil { // Check required flags were provided. if flag.needsValue() { - return fmt.Errorf("required flag --%s not provided", flag.name) + missingFlags = append(missingFlags, fmt.Sprintf("'--%s'", flag.name)) } } } + if len(missingFlags) != 0 { + return fmt.Errorf("required flag(s) %s not provided", strings.Join(missingFlags, ", ")) + } for _, arg := range context.arguments.args { if argElements[arg.name] == nil { diff --git a/app_test.go b/app_test.go index b720474..df562f6 100644 --- a/app_test.go +++ b/app_test.go @@ -737,3 +737,14 @@ func TestCmdValidation(t *testing.T) { _, err = c.Parse([]string{"cmd", "--a", "A"}) assert.NoError(t, err) } + +func TestVersion(t *testing.T) { + c := newTestApp() + c.Flag("config", "path to config file").Default("config.yaml").ExistingFile() + c.Version("1.0.0") + + // the pre-action for version should be executed without running validation + // for ExistingFile + _, err := c.Parse([]string{"--version"}) + assert.NoError(t, err) +} diff --git a/bin/.go-1.22.3.pkg b/bin/.go-1.22.3.pkg new file mode 120000 index 0000000..383f451 --- /dev/null +++ b/bin/.go-1.22.3.pkg @@ -0,0 +1 @@ +hermit \ No newline at end of file diff --git a/bin/README.hermit.md b/bin/README.hermit.md new file mode 100644 index 0000000..e889550 --- /dev/null +++ b/bin/README.hermit.md @@ -0,0 +1,7 @@ +# Hermit environment + +This is a [Hermit](https://github.com/cashapp/hermit) bin directory. + +The symlinks in this directory are managed by Hermit and will automatically +download and install Hermit itself as well as packages. These packages are +local to this environment. diff --git a/bin/activate-hermit b/bin/activate-hermit new file mode 100755 index 0000000..fe28214 --- /dev/null +++ b/bin/activate-hermit @@ -0,0 +1,21 @@ +#!/bin/bash +# This file must be used with "source bin/activate-hermit" from bash or zsh. +# You cannot run it directly +# +# THIS FILE IS GENERATED; DO NOT MODIFY + +if [ "${BASH_SOURCE-}" = "$0" ]; then + echo "You must source this script: \$ source $0" >&2 + exit 33 +fi + +BIN_DIR="$(dirname "${BASH_SOURCE[0]:-${(%):-%x}}")" +if "${BIN_DIR}/hermit" noop > /dev/null; then + eval "$("${BIN_DIR}/hermit" activate "${BIN_DIR}/..")" + + if [ -n "${BASH-}" ] || [ -n "${ZSH_VERSION-}" ]; then + hash -r 2>/dev/null + fi + + echo "Hermit environment $("${HERMIT_ENV}"/bin/hermit env HERMIT_ENV) activated" +fi diff --git a/bin/go b/bin/go new file mode 120000 index 0000000..764d174 --- /dev/null +++ b/bin/go @@ -0,0 +1 @@ +.go-1.22.3.pkg \ No newline at end of file diff --git a/bin/gofmt b/bin/gofmt new file mode 120000 index 0000000..764d174 --- /dev/null +++ b/bin/gofmt @@ -0,0 +1 @@ +.go-1.22.3.pkg \ No newline at end of file diff --git a/bin/hermit b/bin/hermit new file mode 100755 index 0000000..7fef769 --- /dev/null +++ b/bin/hermit @@ -0,0 +1,43 @@ +#!/bin/bash +# +# THIS FILE IS GENERATED; DO NOT MODIFY + +set -eo pipefail + +export HERMIT_USER_HOME=~ + +if [ -z "${HERMIT_STATE_DIR}" ]; then + case "$(uname -s)" in + Darwin) + export HERMIT_STATE_DIR="${HERMIT_USER_HOME}/Library/Caches/hermit" + ;; + Linux) + export HERMIT_STATE_DIR="${XDG_CACHE_HOME:-${HERMIT_USER_HOME}/.cache}/hermit" + ;; + esac +fi + +export HERMIT_DIST_URL="${HERMIT_DIST_URL:-https://github.com/cashapp/hermit/releases/download/stable}" +HERMIT_CHANNEL="$(basename "${HERMIT_DIST_URL}")" +export HERMIT_CHANNEL +export HERMIT_EXE=${HERMIT_EXE:-${HERMIT_STATE_DIR}/pkg/hermit@${HERMIT_CHANNEL}/hermit} + +if [ ! -x "${HERMIT_EXE}" ]; then + echo "Bootstrapping ${HERMIT_EXE} from ${HERMIT_DIST_URL}" 1>&2 + INSTALL_SCRIPT="$(mktemp)" + # This value must match that of the install script + INSTALL_SCRIPT_SHA256="180e997dd837f839a3072a5e2f558619b6d12555cd5452d3ab19d87720704e38" + if [ "${INSTALL_SCRIPT_SHA256}" = "BYPASS" ]; then + curl -fsSL "${HERMIT_DIST_URL}/install.sh" -o "${INSTALL_SCRIPT}" + else + # Install script is versioned by its sha256sum value + curl -fsSL "${HERMIT_DIST_URL}/install-${INSTALL_SCRIPT_SHA256}.sh" -o "${INSTALL_SCRIPT}" + # Verify install script's sha256sum + openssl dgst -sha256 "${INSTALL_SCRIPT}" | \ + awk -v EXPECTED="$INSTALL_SCRIPT_SHA256" \ + '$2!=EXPECTED {print "Install script sha256 " $2 " does not match " EXPECTED; exit 1}' + fi + /bin/bash "${INSTALL_SCRIPT}" 1>&2 +fi + +exec "${HERMIT_EXE}" --level=fatal exec "$0" -- "$@" diff --git a/bin/hermit.hcl b/bin/hermit.hcl new file mode 100644 index 0000000..e69de29 diff --git a/cmd/genvalues/main.go b/cmd/genvalues/main.go index 5d22ad0..a2c6fec 100644 --- a/cmd/genvalues/main.go +++ b/cmd/genvalues/main.go @@ -5,8 +5,7 @@ import ( "os" "os/exec" "strings" - - "github.com/alecthomas/template" + "text/template" ) const ( @@ -37,9 +36,9 @@ func (f *{{.|ValueName}}) String() string { return {{.|Format}} } {{if .Help}} // {{.Help}} -{{else}}\ +{{else -}} // {{.|Name}} parses the next command-line value as {{.Type}}. -{{end}}\ +{{end -}} func (p *parserMixin) {{.|Name}}() (target *{{.Type}}) { target = new({{.Type}}) p.{{.|Name}}Var(target) diff --git a/doc.go b/doc.go index cb951a8..8a72729 100644 --- a/doc.go +++ b/doc.go @@ -35,7 +35,7 @@ // // package main // -// import "gopkg.in/alecthomas/kingpin.v2" +// import "github.com/alecthomas/kingpin/v2" // // var ( // debug = kingpin.Flag("debug", "enable debug mode").Default("false").Bool() diff --git a/go.mod b/go.mod index d6ef03b..707edff 100644 --- a/go.mod +++ b/go.mod @@ -1,11 +1,15 @@ module github.com/coveord/kingpin/v2 +go 1.17 + +require ( + github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b + github.com/stretchr/testify v1.9.0 + github.com/xhit/go-str2duration/v2 v2.1.0 +) + require ( - github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc - github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf github.com/davecgh/go-spew v1.1.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/stretchr/testify v1.2.2 + gopkg.in/yaml.v3 v3.0.1 // indirect ) - -go 1.13 diff --git a/go.sum b/go.sum index 5107484..b3c6ff1 100644 --- a/go.sum +++ b/go.sum @@ -1,10 +1,23 @@ -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b h1:mimo19zliBX/vSQ6PWWSL9lK8qwHozUj03+zLoEB8O0= +github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b/go.mod h1:fvzegU4vN3H1qMT+8wDmzjAcDONcgo2/SZ/TyfdUOFs= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc= +github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/renovate.json5 b/renovate.json5 new file mode 100644 index 0000000..6bb4acd --- /dev/null +++ b/renovate.json5 @@ -0,0 +1,15 @@ +{ + $schema: "https://docs.renovatebot.com/renovate-schema.json", + extends: [ + "config:recommended", + ":semanticCommits", + ":semanticCommitTypeAll(chore)", + ":semanticCommitScope(deps)", + "group:allNonMajor", + "schedule:earlyMondays", // Run once a week. + ], + postUpdateOptions: [ + "gomodTidy", + "gomodUpdateImportPaths" + ] +} diff --git a/templates.go b/templates.go index 3b69cec..ddeb0d7 100644 --- a/templates.go +++ b/templates.go @@ -1,182 +1,181 @@ package kingpin -// DefaultUsageTemplate is the default usage template. -var DefaultUsageTemplate = `{{define "FormatCommand"}}\ -{{if .FlagSummary}} {{.FlagSummary}}{{end}}\ -{{range .Args}}{{if not .Hidden}} {{if not .Required}}[{{end}}{{if .PlaceHolder}}{{.PlaceHolder}}{{else}}<{{.Name}}>{{end}}{{if .Value|IsCumulative}}...{{end}}{{if not .Required}}]{{end}}{{end}}{{end}}\ -{{end}}\ +// Default usage template. +var DefaultUsageTemplate = `{{define "FormatCommand" -}} +{{if .FlagSummary}} {{.FlagSummary}}{{end -}} +{{range .Args}}{{if not .Hidden}} {{if not .Required}}[{{end}}{{if .PlaceHolder}}{{.PlaceHolder}}{{else}}<{{.Name}}>{{end}}{{if .Value|IsCumulative}}...{{end}}{{if not .Required}}]{{end}}{{end}}{{end -}} +{{end -}} -{{define "FormatCommands"}}\ -{{range .FlattenedCommands}}\ -{{if not .Hidden}}\ +{{define "FormatCommands" -}} +{{range .FlattenedCommands -}} +{{if not .Hidden -}} {{.FullCommand}}{{if .Default}}*{{end}}{{template "FormatCommand" .}} {{.Help|Wrap 4}} -{{end}}\ -{{end}}\ -{{end}}\ +{{end -}} +{{end -}} +{{end -}} -{{define "FormatUsage"}}\ +{{define "FormatUsage" -}} {{template "FormatCommand" .}}{{if .Commands}} [ ...]{{end}} {{if .Help}} -{{.Help|Wrap 0}}\ -{{end}}\ +{{.Help|Wrap 0 -}} +{{end -}} -{{end}}\ +{{end -}} -{{if .Context.SelectedCommand}}\ +{{if .Context.SelectedCommand -}} usage: {{.App.Name}} {{.Context.SelectedCommand}}{{template "FormatUsage" .Context.SelectedCommand}} -{{else}}\ +{{ else -}} usage: {{.App.Name}}{{template "FormatUsage" .App}} -{{end}}\ -{{if .Context.Flags}}\ +{{end}} +{{if .Context.Flags -}} Flags: {{.Context.Flags|FlagsToTwoColumns|FormatTwoColumns}} -{{end}}\ -{{if .Context.Args}}\ +{{end -}} +{{if .Context.Args -}} Args: {{.Context.Args|ArgsToTwoColumns|FormatTwoColumns}} -{{end}}\ -{{if .Context.SelectedCommand}}\ -{{if len .Context.SelectedCommand.Commands}}\ +{{end -}} +{{if .Context.SelectedCommand -}} +{{if len .Context.SelectedCommand.Commands -}} Subcommands: {{template "FormatCommands" .Context.SelectedCommand}} -{{end}}\ -{{else if .App.Commands}}\ +{{end -}} +{{else if .App.Commands -}} Commands: {{template "FormatCommands" .App}} -{{end}}\ +{{end -}} ` -// SeparateOptionalFlagsUsageTemplate is a usage template where command's optional flags are listed separately -var SeparateOptionalFlagsUsageTemplate = `{{define "FormatCommand"}}\ -{{if .FlagSummary}} {{.FlagSummary}}{{end}}\ -{{range .Args}}{{if not .Hidden}} {{if not .Required}}[{{end}}{{if .PlaceHolder}}{{.PlaceHolder}}{{else}}<{{.Name}}>{{end}}{{if .Value|IsCumulative}}...{{end}}{{if not .Required}}]{{end}}{{end}}{{end}}\ -{{end}}\ +// Usage template where command's optional flags are listed separately +var SeparateOptionalFlagsUsageTemplate = `{{define "FormatCommand" -}} +{{if .FlagSummary}} {{.FlagSummary}}{{end -}} +{{range .Args}}{{if not .Hidden}} {{if not .Required}}[{{end}}{{if .PlaceHolder}}{{.PlaceHolder}}{{else}}<{{.Name}}>{{end}}{{if .Value|IsCumulative}}...{{end}}{{if not .Required}}]{{end}}{{end}}{{end -}} +{{end -}} -{{define "FormatCommands"}}\ -{{range .FlattenedCommands}}\ -{{if not .Hidden}}\ +{{define "FormatCommands" -}} +{{range .FlattenedCommands -}} +{{if not .Hidden -}} {{.FullCommand}}{{if .Default}}*{{end}}{{template "FormatCommand" .}} {{.Help|Wrap 4}} -{{end}}\ -{{end}}\ -{{end}}\ +{{end -}} +{{end -}} +{{end -}} -{{define "FormatUsage"}}\ +{{define "FormatUsage" -}} {{template "FormatCommand" .}}{{if .Commands}} [ ...]{{end}} {{if .Help}} -{{.Help|Wrap 0}}\ -{{end}}\ +{{.Help|Wrap 0 -}} +{{end -}} -{{end}}\ -{{if .Context.SelectedCommand}}\ +{{end -}} +{{if .Context.SelectedCommand -}} usage: {{.App.Name}} {{.Context.SelectedCommand}}{{template "FormatUsage" .Context.SelectedCommand}} -{{else}}\ +{{else -}} usage: {{.App.Name}}{{template "FormatUsage" .App}} -{{end}}\ +{{end -}} -{{if .Context.Flags|RequiredFlags}}\ +{{if .Context.Flags|RequiredFlags -}} Required flags: {{.Context.Flags|RequiredFlags|FlagsToTwoColumns|FormatTwoColumns}} -{{end}}\ -{{if .Context.Flags|OptionalFlags}}\ +{{end -}} +{{if .Context.Flags|OptionalFlags -}} Optional flags: {{.Context.Flags|OptionalFlags|FlagsToTwoColumns|FormatTwoColumns}} -{{end}}\ -{{if .Context.Args}}\ +{{end -}} +{{if .Context.Args -}} Args: {{.Context.Args|ArgsToTwoColumns|FormatTwoColumns}} -{{end}}\ -{{if .Context.SelectedCommand}}\ +{{end -}} +{{if .Context.SelectedCommand -}} Subcommands: -{{if .Context.SelectedCommand.Commands}}\ +{{if .Context.SelectedCommand.Commands -}} {{template "FormatCommands" .Context.SelectedCommand}} -{{end}}\ -{{else if .App.Commands}}\ +{{end -}} +{{else if .App.Commands -}} Commands: {{template "FormatCommands" .App}} -{{end}}\ +{{end -}} ` -// CompactUsageTemplate is the usage template with compactly formatted commands. -var CompactUsageTemplate = `{{define "FormatCommand"}}\ -{{if .FlagSummary}} {{.FlagSummary}}{{end}}\ -{{range .Args}}{{if not .Hidden}} {{if not .Required}}[{{end}}{{if .PlaceHolder}}{{.PlaceHolder}}{{else}}<{{.Name}}>{{end}}{{if .Value|IsCumulative}}...{{end}}{{if not .Required}}]{{end}}{{end}}{{end}}\ -{{end}}\ +// Usage template with compactly formatted commands. +var CompactUsageTemplate = `{{define "FormatCommand" -}} +{{if .FlagSummary}} {{.FlagSummary}}{{end -}} +{{range .Args}}{{if not .Hidden}} {{if not .Required}}[{{end}}{{if .PlaceHolder}}{{.PlaceHolder}}{{else}}<{{.Name}}>{{end}}{{if .Value|IsCumulative}}...{{end}}{{if not .Required}}]{{end}}{{end}}{{end -}} +{{end -}} -{{define "FormatCommandList"}}\ -{{range .}}\ -{{if not .Hidden}}\ +{{define "FormatCommandList" -}} +{{range . -}} +{{if not .Hidden -}} {{.Depth|Indent}}{{.Name}}{{if .Default}}*{{end}}{{template "FormatCommand" .}} -{{end}}\ -{{template "FormatCommandList" .Commands}}\ -{{end}}\ -{{end}}\ +{{end -}} +{{template "FormatCommandList" .Commands -}} +{{end -}} +{{end -}} -{{define "FormatUsage"}}\ +{{define "FormatUsage" -}} {{template "FormatCommand" .}}{{if .Commands}} [ ...]{{end}} {{if .Help}} -{{.Help|Wrap 0}}\ -{{end}}\ +{{.Help|Wrap 0 -}} +{{end -}} -{{end}}\ +{{end -}} -{{if .Context.SelectedCommand}}\ +{{if .Context.SelectedCommand -}} usage: {{.App.Name}} {{.Context.SelectedCommand}}{{template "FormatUsage" .Context.SelectedCommand}} -{{else}}\ +{{else -}} usage: {{.App.Name}}{{template "FormatUsage" .App}} -{{end}}\ -{{if .Context.Flags}}\ +{{end -}} +{{if .Context.Flags -}} Flags: {{.Context.Flags|FlagsToTwoColumns|FormatTwoColumns}} -{{end}}\ -{{if .Context.Args}}\ +{{end -}} +{{if .Context.Args -}} Args: {{.Context.Args|ArgsToTwoColumns|FormatTwoColumns}} -{{end}}\ -{{if .Context.SelectedCommand}}\ -{{if .Context.SelectedCommand.Commands}}\ +{{end -}} +{{if .Context.SelectedCommand -}} +{{if .Context.SelectedCommand.Commands -}} Commands: {{.Context.SelectedCommand}} {{template "FormatCommandList" .Context.SelectedCommand.Commands}} -{{end}}\ -{{else if .App.Commands}}\ +{{end -}} +{{else if .App.Commands -}} Commands: {{template "FormatCommandList" .App.Commands}} -{{end}}\ +{{end -}} ` -// ManPageTemplate is the template used go generate man page. -var ManPageTemplate = `{{define "FormatFlags"}}\ -{{range .Flags}}\ -{{if not .Hidden}}\ +var ManPageTemplate = `{{define "FormatFlags" -}} +{{range .Flags -}} +{{if not .Hidden -}} .TP -\fB{{if .Short}}-{{.Short|Char}}, {{end}}--{{.Name}}{{if not .IsBoolFlag}}={{.FormatPlaceHolder}}{{end}}\\fR +\fB{{if .Short}}-{{.Short|Char}}, {{end}}--{{.Name}}{{if not .IsBoolFlag}}={{.FormatPlaceHolder}}{{end -}}\fR {{.Help}} -{{end}}\ -{{end}}\ -{{end}}\ +{{end -}} +{{end -}} +{{end -}} -{{define "FormatCommand"}}\ -{{if .FlagSummary}} {{.FlagSummary}}{{end}}\ -{{range .Args}}{{if not .Hidden}} {{if not .Required}}[{{end}}{{if .PlaceHolder}}{{.PlaceHolder}}{{else}}<{{.Name}}>{{end}}{{if .Value|IsCumulative}}...{{end}}{{if not .Required}}]{{end}}{{end}}{{end}}\ -{{end}}\ +{{define "FormatCommand" -}} +{{if .FlagSummary}} {{.FlagSummary}}{{end -}} +{{range .Args}}{{if not .Hidden}} {{if not .Required}}[{{end}}{{if .PlaceHolder}}{{.PlaceHolder}}{{else}}<{{.Name}}>{{end}}{{if .Value|IsCumulative}}...{{end}}{{if not .Required}}]{{end}}{{end}}{{end -}} +{{end -}} -{{define "FormatCommands"}}\ -{{range .FlattenedCommands}}\ -{{if not .Hidden}}\ +{{define "FormatCommands" -}} +{{range .FlattenedCommands -}} +{{if not .Hidden -}} .SS -\fB{{.FullCommand}}{{template "FormatCommand" .}}\\fR +\fB{{.FullCommand}}{{template "FormatCommand" . -}}\fR .PP {{.Help}} -{{template "FormatFlags" .}}\ -{{end}}\ -{{end}}\ -{{end}}\ +{{template "FormatFlags" . -}} +{{end -}} +{{end -}} +{{end -}} -{{define "FormatUsage"}}\ -{{template "FormatCommand" .}}{{if .Commands}} [ ...]{{end}}\\fR -{{end}}\ +{{define "FormatUsage" -}} +{{template "FormatCommand" .}}{{if .Commands}} [ ...]{{end -}}\fR +{{end -}} .TH {{.App.Name}} 1 {{.App.Version}} "{{.App.Author}}" .SH "NAME" @@ -187,50 +186,50 @@ var ManPageTemplate = `{{define "FormatFlags"}}\ .SH "DESCRIPTION" {{.App.Help}} .SH "OPTIONS" -{{template "FormatFlags" .App}}\ -{{if .App.Commands}}\ +{{template "FormatFlags" .App -}} +{{if .App.Commands -}} .SH "COMMANDS" -{{template "FormatCommands" .App}}\ -{{end}}\ +{{template "FormatCommands" .App -}} +{{end -}} ` -// LongHelpTemplate is the template used go generate long help. -var LongHelpTemplate = `{{define "FormatCommand"}}\ -{{if .FlagSummary}} {{.FlagSummary}}{{end}}\ -{{range .Args}}{{if not .Hidden}} {{if not .Required}}[{{end}}{{if .PlaceHolder}}{{.PlaceHolder}}{{else}}<{{.Name}}>{{end}}{{if .Value|IsCumulative}}...{{end}}{{if not .Required}}]{{end}}{{end}}{{end}}\ -{{end}}\ +// Default usage template. +var LongHelpTemplate = `{{define "FormatCommand" -}} +{{if .FlagSummary}} {{.FlagSummary}}{{end -}} +{{range .Args}}{{if not .Hidden}} {{if not .Required}}[{{end}}{{if .PlaceHolder}}{{.PlaceHolder}}{{else}}<{{.Name}}>{{end}}{{if .Value|IsCumulative}}...{{end}}{{if not .Required}}]{{end}}{{end}}{{end -}} +{{end -}} -{{define "FormatCommands"}}\ -{{range .FlattenedCommands}}\ -{{if not .Hidden}}\ +{{define "FormatCommands" -}} +{{range .FlattenedCommands -}} +{{if not .Hidden -}} {{.FullCommand}}{{template "FormatCommand" .}} {{.Help|Wrap 4}} {{with .Flags|FlagsToTwoColumns}}{{FormatTwoColumnsWithIndent . 4 2}}{{end}} -{{end}}\ -{{end}}\ -{{end}}\ +{{end -}} +{{end -}} +{{end -}} -{{define "FormatUsage"}}\ +{{define "FormatUsage" -}} {{template "FormatCommand" .}}{{if .Commands}} [ ...]{{end}} {{if .Help}} -{{.Help|Wrap 0}}\ -{{end}}\ +{{.Help|Wrap 0 -}} +{{end -}} -{{end}}\ +{{end -}} usage: {{.App.Name}}{{template "FormatUsage" .App}} -{{if .Context.Flags}}\ +{{if .Context.Flags -}} Flags: {{.Context.Flags|FlagsToTwoColumns|FormatTwoColumns}} -{{end}}\ -{{if .Context.Args}}\ +{{end -}} +{{if .Context.Args -}} Args: {{.Context.Args|ArgsToTwoColumns|FormatTwoColumns}} -{{end}}\ -{{if .App.Commands}}\ +{{end -}} +{{if .App.Commands -}} Commands: {{template "FormatCommands" .App}} -{{end}}\ +{{end -}} ` // BashCompletionTemplate is the template used go generate bash completion. @@ -251,7 +250,7 @@ complete -F _{{.App.Name}}_bash_autocomplete -o default {{.App.Name}} var ZshCompletionTemplate = `#compdef {{.App.Name}} _{{.App.Name}}() { - local matches=($(${words[1]} --completion-bash "${(@)words[1,$CURRENT]}")) + local matches=($(${words[1]} --completion-bash "${(@)words[2,$CURRENT]}")) compadd -a matches if [[ $compstate[nmatches] -eq 0 && $words[$CURRENT] != -* ]]; then diff --git a/usage.go b/usage.go index eccbc73..9b3dd73 100644 --- a/usage.go +++ b/usage.go @@ -6,8 +6,7 @@ import ( "go/doc" "io" "strings" - - "github.com/alecthomas/template" + "text/template" ) var ( @@ -75,13 +74,17 @@ func formatCmdUsage(app *ApplicationModel, cmd *CmdModel) string { func formatFlag(haveShort bool, flag *FlagModel) string { flagString := "" + flagName := flag.Name + if flag.IsBoolFlag() { + flagName = "[no-]" + flagName + } if flag.Short != 0 { - flagString += fmt.Sprintf("-%c, --%s", flag.Short, flag.Name) + flagString += fmt.Sprintf("-%c, --%s", flag.Short, flagName) } else { if haveShort { - flagString += fmt.Sprintf(" --%s", flag.Name) + flagString += fmt.Sprintf(" --%s", flagName) } else { - flagString += fmt.Sprintf("--%s", flag.Name) + flagString += fmt.Sprintf("--%s", flagName) } } if !flag.IsBoolFlag() { diff --git a/usage_test.go b/usage_test.go index cb41a68..a438748 100644 --- a/usage_test.go +++ b/usage_test.go @@ -80,7 +80,7 @@ func TestUsageFuncs(t *testing.T) { func TestCmdClause_HelpLong(t *testing.T) { var buf bytes.Buffer - tpl := `{{define "FormatUsage"}}{{.HelpLong}}{{end}}\ + tpl := `{{define "FormatUsage"}}{{.HelpLong}}{{end -}} {{template "FormatUsage" .Context.SelectedCommand}}` a := New("test", "Test").Writer(&buf).Terminate(nil) diff --git a/values.go b/values.go index af9d3aa..1b61e93 100644 --- a/values.go +++ b/values.go @@ -14,6 +14,7 @@ import ( "time" "github.com/alecthomas/units" + "github.com/xhit/go-str2duration/v2" ) // NOTE: Most of the base type values were lifted from: @@ -138,7 +139,7 @@ func newDurationValue(p *time.Duration) *durationValue { } func (d *durationValue) Set(s string) error { - v, err := time.ParseDuration(s) + v, err := str2duration.ParseDuration(s) *d = durationValue(v) return err }