diff --git a/go.mod b/go.mod
index 16e19fb1..b2b49581 100644
--- a/go.mod
+++ b/go.mod
@@ -10,7 +10,7 @@ require (
github.com/Azure/go-autorest/autorest v0.11.18
github.com/DopplerHQ/cli v0.0.0-20210309042056-414bede8a50e
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38
- github.com/alecthomas/kong v0.2.15
+ github.com/alecthomas/kong v0.9.0
github.com/aws/aws-sdk-go-v2 v1.24.1
github.com/aws/aws-sdk-go-v2/config v1.26.5
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.26.2
@@ -68,7 +68,7 @@ require (
github.com/aead/argon2 v0.0.0-20180111183520-a87724528b07 // indirect
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect
github.com/alecthomas/colour v0.1.0 // indirect
- github.com/alecthomas/repr v0.0.0-20201120212035-bb82daffcca2 // indirect
+ github.com/alecthomas/repr v0.4.0 // indirect
github.com/alessio/shellescape v1.4.1 // indirect
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da // indirect
github.com/atotto/clipboard v0.1.4 // indirect
diff --git a/go.sum b/go.sum
index d4bc7073..ae1e1a91 100644
--- a/go.sum
+++ b/go.sum
@@ -116,12 +116,13 @@ github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmH
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 h1:smF2tmSOzy2Mm+0dGI2AIUHY+w0BUc+4tn40djz7+6U=
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI=
+github.com/alecthomas/assert/v2 v2.6.0 h1:o3WJwILtexrEUk3cUVal3oiQY2tfgr/FHWiz/v2n4FU=
github.com/alecthomas/colour v0.1.0 h1:nOE9rJm6dsZ66RGWYSFrXw461ZIt9A6+nHgL7FRrDUk=
github.com/alecthomas/colour v0.1.0/go.mod h1:QO9JBoKquHd+jz9nshCh40fOfO+JzsoXy8qTHF68zU0=
-github.com/alecthomas/kong v0.2.15 h1:HP3K1XuFn0wGSWFGVW67V+65tXw/Ht8FDYiLNAuX2Ug=
-github.com/alecthomas/kong v0.2.15/go.mod h1:kQOmtJgV+Lb4aj+I2LEn40cbtawdWJ9Y8QLq+lElKxE=
-github.com/alecthomas/repr v0.0.0-20201120212035-bb82daffcca2 h1:G5TeG64Ox4OWq2YwlsxS7nOedU8vbGgNRTRDAjGvDCk=
-github.com/alecthomas/repr v0.0.0-20201120212035-bb82daffcca2/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8=
+github.com/alecthomas/kong v0.9.0 h1:G5diXxc85KvoV2f0ZRVuMsi45IrBgx9zDNGNj165aPA=
+github.com/alecthomas/kong v0.9.0/go.mod h1:Y47y5gKfHp1hDc7CH7OeXgLIpp+Q2m1Ni0L5s3bI8Os=
+github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
+github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
@@ -552,6 +553,7 @@ github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKe
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
github.com/heroku/heroku-go/v5 v5.2.1 h1:5g379GyHuOI3qhb1ujFwQ13Kjt96M+KMkV8s7omg+do=
github.com/heroku/heroku-go/v5 v5.2.1/go.mod h1:d+1QrZyjbnQJG1f8xIoVvMQRFLt3XRVZOdlm26Sr73U=
+github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174 h1:WlZsjVhE8Af9IcZDGgJGQpNflI3+MJSBhsgT5PCtzBQ=
github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174/go.mod h1:DqJ97dSdRW1W22yXSB90986pcOyQ7r45iio1KN2ez1A=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
diff --git a/vendor/github.com/alecthomas/kong/.gitignore b/vendor/github.com/alecthomas/kong/.gitignore
index ba077a40..e69de29b 100644
--- a/vendor/github.com/alecthomas/kong/.gitignore
+++ b/vendor/github.com/alecthomas/kong/.gitignore
@@ -1 +0,0 @@
-bin
diff --git a/vendor/github.com/alecthomas/kong/.golangci.yml b/vendor/github.com/alecthomas/kong/.golangci.yml
index af552572..e8980bfc 100644
--- a/vendor/github.com/alecthomas/kong/.golangci.yml
+++ b/vendor/github.com/alecthomas/kong/.golangci.yml
@@ -15,17 +15,53 @@ linters:
- gocognit
- gomnd
- goprintffuncname
+ - paralleltest
+ - nlreturn
+ - goerr113
+ - ifshort
+ - testpackage
+ - wrapcheck
+ - exhaustivestruct
+ - forbidigo
+ - gci
+ - godot
+ - gofumpt
+ - cyclop
+ - errorlint
+ - nestif
+ - golint
+ - scopelint
+ - interfacer
+ - tagliatelle
+ - thelper
+ - godox
+ - goconst
+ - varnamelen
+ - ireturn
+ - exhaustruct
+ - nonamedreturns
+ - nilnil
+ - nosnakecase # deprecated since v1.48.1
+ - structcheck # deprecated since v1.49.0
+ - deadcode # deprecated since v1.49.0
+ - varcheck # deprecated since v1.49.0
+ - depguard # nothing to guard against yet
+ - tagalign # hurts readability of kong tags
linters-settings:
govet:
check-shadowing: true
+ # These govet checks are disabled by default, but they're useful.
+ enable:
+ - niliness
+ - sortslice
+ - unusedwrite
dupl:
threshold: 100
- goconst:
- min-len: 5
- min-occurrences: 3
gocyclo:
min-complexity: 20
+ exhaustive:
+ default-signifies-exhaustive: true
issues:
max-per-linter: 0
@@ -40,3 +76,14 @@ issues:
- 'bad syntax for struct tag key'
- 'bad syntax for struct tag pair'
- 'result .* \(error\) is always nil'
+
+ exclude-rules:
+ # Don't warn on unused parameters.
+ # Parameter names are useful for documentation.
+ # Replacing them with '_' hides useful information.
+ - linters: [revive]
+ text: 'unused-parameter: parameter \S+ seems to be unused, consider removing or renaming it as _'
+
+ # Duplicate words are okay in tests.
+ - linters: [dupword]
+ path: _test\.go
diff --git a/vendor/github.com/alecthomas/kong/README.md b/vendor/github.com/alecthomas/kong/README.md
index f0b7a30b..22e00f06 100644
--- a/vendor/github.com/alecthomas/kong/README.md
+++ b/vendor/github.com/alecthomas/kong/README.md
@@ -2,39 +2,43 @@

# Kong is a command-line parser for Go
+
[](http://godoc.org/github.com/alecthomas/kong) [](https://circleci.com/gh/alecthomas/kong) [](https://goreportcard.com/report/github.com/alecthomas/kong) [](https://gophers.slack.com/messages/CN9DS8YF3)
-
+
- [Introduction](#introduction)
- [Help](#help)
- - [Help as a user of a Kong application](#help-as-a-user-of-a-kong-application)
- - [Defining help in Kong](#defining-help-in-kong)
+ - [Help as a user of a Kong application](#help-as-a-user-of-a-kong-application)
+ - [Defining help in Kong](#defining-help-in-kong)
- [Command handling](#command-handling)
- - [Switch on the command string](#switch-on-the-command-string)
- - [Attach a Run... error method to each command](#attach-a-run-error-method-to-each-command)
-- [Hooks: BeforeResolve, BeforeApply, AfterApply and the Bind option](#hooks-beforeresolve-beforeapply-afterapply-and-the-bind-option)
+ - [Switch on the command string](#switch-on-the-command-string)
+ - [Attach a Run... error method to each command](#attach-a-run-error-method-to-each-command)
+- [Hooks: BeforeReset, BeforeResolve, BeforeApply, AfterApply and the Bind option](#hooks-beforereset-beforeresolve-beforeapply-afterapply-and-the-bind-option)
- [Flags](#flags)
- [Commands and sub-commands](#commands-and-sub-commands)
- [Branching positional arguments](#branching-positional-arguments)
-- [Terminating positional arguments](#terminating-positional-arguments)
+- [Positional arguments](#positional-arguments)
- [Slices](#slices)
- [Maps](#maps)
+- [Pointers](#pointers)
+- [Nested data structure](#nested-data-structure)
- [Custom named decoders](#custom-named-decoders)
- [Supported field types](#supported-field-types)
- [Custom decoders mappers](#custom-decoders-mappers)
- [Supported tags](#supported-tags)
- [Plugins](#plugins)
+- [Dynamic Commands](#dynamic-commands)
- [Variable interpolation](#variable-interpolation)
- [Validation](#validation)
- [Modifying Kong's behaviour](#modifying-kongs-behaviour)
- - [Namehelp and Descriptionhelp - set the application name description](#namehelp-and-descriptionhelp---set-the-application-name-description)
- - [Configurationloader, paths... - load defaults from configuration files](#configurationloader-paths---load-defaults-from-configuration-files)
- - [Resolver... - support for default values from external sources](#resolver---support-for-default-values-from-external-sources)
- - [*Mapper... - customising how the command-line is mapped to Go values](#mapper---customising-how-the-command-line-is-mapped-to-go-values)
- - [ConfigureHelpHelpOptions and HelpHelpFunc - customising help](#configurehelphelpoptions-and-helphelpfunc---customising-help)
- - [Bind... - bind values for callback hooks and Run methods](#bind---bind-values-for-callback-hooks-and-run-methods)
- - [Other options](#other-options)
+ - [Namehelp and Descriptionhelp - set the application name description](#namehelp-and-descriptionhelp---set-the-application-name-description)
+ - [Configurationloader, paths... - load defaults from configuration files](#configurationloader-paths---load-defaults-from-configuration-files)
+ - [Resolver... - support for default values from external sources](#resolver---support-for-default-values-from-external-sources)
+ - [\*Mapper... - customising how the command-line is mapped to Go values](#mapper---customising-how-the-command-line-is-mapped-to-go-values)
+ - [ConfigureHelpHelpOptions and HelpHelpFunc - customising help](#configurehelphelpoptions-and-helphelpfunc---customising-help)
+ - [Bind... - bind values for callback hooks and Run methods](#bind---bind-values-for-callback-hooks-and-run-methods)
+ - [Other options](#other-options)
@@ -61,12 +65,12 @@ var CLI struct {
Force bool `help:"Force removal."`
Recursive bool `help:"Recursively remove files."`
- Paths []string `arg name:"path" help:"Paths to remove." type:"path"`
- } `cmd help:"Remove files."`
+ Paths []string `arg:"" name:"path" help:"Paths to remove." type:"path"`
+ } `cmd:"" help:"Remove files."`
Ls struct {
- Paths []string `arg optional name:"path" help:"Paths to list." type:"path"`
- } `cmd help:"List paths."`
+ Paths []string `arg:"" optional:"" name:"path" help:"Paths to list." type:"path"`
+ } `cmd:"" help:"List paths."`
}
func main() {
@@ -128,9 +132,67 @@ Help is automatically generated from the command-line structure itself,
including `help:""` and other tags. [Variables](#variable-interpolation) will
also be interpolated into the help string.
-Finally, any command, argument, or flag type implementing the interface
-`Help() string` will have this function called to retrieve the help string.
-This allows for much more descriptive text than can fit in Go tags.
+Finally, any command, or argument type implementing the interface
+`Help() string` will have this function called to retrieve more detail to
+augment the help tag. This allows for much more descriptive text than can
+fit in Go tags. [See \_examples/shell/help](./_examples/shell/help)
+
+#### Showing the _command_'s detailed help
+
+A command's additional help text is _not_ shown from top-level help, but can be displayed within contextual help:
+
+**Top level help**
+
+```bash
+ $ go run ./_examples/shell/help --help
+Usage: help
+
+An app demonstrating HelpProviders
+
+Flags:
+ -h, --help Show context-sensitive help.
+ --flag Regular flag help
+
+Commands:
+ echo Regular command help
+```
+
+**Contextual**
+
+```bash
+ $ go run ./_examples/shell/help echo --help
+Usage: help echo
+
+Regular command help
+
+🚀 additional command help
+
+Arguments:
+ Regular argument help
+
+Flags:
+ -h, --help Show context-sensitive help.
+ --flag Regular flag help
+```
+
+#### Showing an _argument_'s detailed help
+
+Custom help will only be shown for _positional arguments with named fields_ ([see the README section on positional arguments for more details on what that means](../../../README.md#branching-positional-arguments))
+
+**Contextual argument help**
+
+```bash
+ $ go run ./_examples/shell/help msg --help
+Usage: help echo
+
+Regular argument help
+
+📣 additional argument help
+
+Flags:
+ -h, --help Show context-sensitive help.
+ --flag Regular flag help
+```
## Command handling
@@ -140,7 +202,7 @@ There are two ways to handle commands in Kong.
When you call `kong.Parse()` it will return a unique string representation of the command. Each command branch in the hierarchy will be a bare word and each branching argument or required positional argument will be the name surrounded by angle brackets. Here's an example:
-There's an example of this pattern [here](https://github.com/alecthomas/kong/blob/master/_examples/shell/main.go).
+There's an example of this pattern [here](https://github.com/alecthomas/kong/blob/master/_examples/shell/commandstring/main.go).
eg.
@@ -154,12 +216,12 @@ var CLI struct {
Force bool `help:"Force removal."`
Recursive bool `help:"Recursively remove files."`
- Paths []string `arg name:"path" help:"Paths to remove." type:"path"`
- } `cmd help:"Remove files."`
+ Paths []string `arg:"" name:"path" help:"Paths to remove." type:"path"`
+ } `cmd:"" help:"Remove files."`
Ls struct {
- Paths []string `arg optional name:"path" help:"Paths to list." type:"path"`
- } `cmd help:"List paths."`
+ Paths []string `arg:"" optional:"" name:"path" help:"Paths to list." type:"path"`
+ } `cmd:"" help:"List paths."`
}
func main() {
@@ -207,7 +269,7 @@ type RmCmd struct {
Force bool `help:"Force removal."`
Recursive bool `help:"Recursively remove files."`
- Paths []string `arg name:"path" help:"Paths to remove." type:"path"`
+ Paths []string `arg:"" name:"path" help:"Paths to remove." type:"path"`
}
func (r *RmCmd) Run(ctx *Context) error {
@@ -216,7 +278,7 @@ func (r *RmCmd) Run(ctx *Context) error {
}
type LsCmd struct {
- Paths []string `arg optional name:"path" help:"Paths to list." type:"path"`
+ Paths []string `arg:"" optional:"" name:"path" help:"Paths to list." type:"path"`
}
func (l *LsCmd) Run(ctx *Context) error {
@@ -227,8 +289,8 @@ func (l *LsCmd) Run(ctx *Context) error {
var cli struct {
Debug bool `help:"Enable debug mode."`
- Rm RmCmd `cmd help:"Remove files."`
- Ls LsCmd `cmd help:"List paths."`
+ Rm RmCmd `cmd:"" help:"Remove files."`
+ Ls LsCmd `cmd:"" help:"List paths."`
}
func main() {
@@ -240,11 +302,14 @@ func main() {
```
-## Hooks: BeforeResolve(), BeforeApply(), AfterApply() and the Bind() option
+## Hooks: BeforeReset(), BeforeResolve(), BeforeApply(), AfterApply() and the Bind() option
-If a node in the grammar has a `BeforeResolve(...)`, `BeforeApply(...) error` and/or `AfterApply(...) error` method, those methods will be called before validation/assignment and after validation/assignment, respectively.
+If a node in the grammar has a `BeforeReset(...)`, `BeforeResolve
+(...)`, `BeforeApply(...) error` and/or `AfterApply(...) error` method, those
+methods will be called before values are reset, before validation/assignment,
+and after validation/assignment, respectively.
-The `--help` flag is implemented with a `BeforeApply` hook.
+The `--help` flag is implemented with a `BeforeReset` hook.
Arguments to hooks are provided via the `Run(...)` method or `Bind(...)` option. `*Kong`, `*Context` and `*Path` are also bound and finally, hooks can also contribute bindings via `kong.Context.Bind()` and `kong.Context.BindTo()`.
@@ -265,7 +330,7 @@ var cli struct {
func main() {
// Debug logger going to discard.
- logger := log.New(ioutil.Discard, "", log.LstdFlags)
+ logger := log.New(io.Discard, "", log.LstdFlags)
ctx := kong.Parse(&cli, kong.Bind(logger))
@@ -273,9 +338,43 @@ func main() {
}
```
+Another example of using hooks is load the env-file:
+
+```go
+package main
+
+import (
+ "fmt"
+ "github.com/alecthomas/kong"
+ "github.com/joho/godotenv"
+)
+
+type EnvFlag string
+
+// BeforeResolve loads env file.
+func (c EnvFlag) BeforeReset(ctx *kong.Context, trace *kong.Path) error {
+ path := string(ctx.FlagValue(trace.Flag).(EnvFlag)) // nolint
+ path = kong.ExpandPath(path)
+ if err := godotenv.Load(path); err != nil {
+ return err
+ }
+ return nil
+}
+
+var CLI struct {
+ EnvFile EnvFlag
+ Flag `env:"FLAG"`
+}
+
+func main() {
+ _ = kong.Parse(&CLI)
+ fmt.Println(CLI.Flag)
+}
+```
+
## Flags
-Any [mapped](#mapper---customising-how-the-command-line-is-mapped-to-go-values) field in the command structure *not* tagged with `cmd` or `arg` will be a flag. Flags are optional by default.
+Any [mapped](#mapper---customising-how-the-command-line-is-mapped-to-go-values) field in the command structure _not_ tagged with `cmd` or `arg` will be a flag. Flags are optional by default.
eg. The command-line `app [--flag="foo"]` can be represented by the following.
@@ -302,7 +401,7 @@ type CLI struct {
}
```
-If a sub-command is tagged with `default:"1"` it will be selected if there are no further arguments.
+If a sub-command is tagged with `default:"1"` it will be selected if there are no further arguments. If a sub-command is tagged with `default:"withargs"` it will be selected even if there are further arguments or flags and those arguments or flags are valid for the sub-command. This allows the user to omit the sub-command name on the CLI if its arguments/flags are not ambiguous with the sibling commands or flags.
## Branching positional arguments
@@ -331,11 +430,14 @@ var CLI struct {
This looks a little verbose in this contrived example, but typically this will not be the case.
-## Terminating positional arguments
+## Positional arguments
-If a [mapped type](#mapper---customising-how-the-command-line-is-mapped-to-go-values) is tagged with `arg` it will be treated as the final positional values to be parsed on the command line.
+If a field is tagged with `arg:""` it will be treated as the final positional
+value to be parsed on the command line. By default positional arguments are
+required, but specifying `optional:""` will alter this.
-If a positional argument is a slice, all remaining arguments will be appended to that slice.
+If a positional argument is a slice, all remaining arguments will be appended
+to that slice.
## Slices
@@ -350,7 +452,7 @@ You would use the following:
```go
var CLI struct {
Ls struct {
- Files []string `arg type:"existingfile"`
+ Files []string `arg:"" type:"existingfile"`
} `cmd`
}
```
@@ -369,7 +471,7 @@ You would use the following:
var CLI struct {
Config struct {
Set struct {
- Config map[string]float64 `arg type:"file:"`
+ Config map[string]float64 `arg:"" type:"file:"`
} `cmd`
} `cmd`
}
@@ -377,19 +479,48 @@ var CLI struct {
For flags, multiple key+value pairs should be separated by `mapsep:"rune"` tag (defaults to `;`) eg. `--set="key1=value1;key2=value2"`.
+## Pointers
+
+Pointers work like the underlying type, except that you can differentiate between the presence of the zero value and no value being supplied.
+
+For example:
+
+```go
+var CLI struct {
+ Foo *int
+}
+```
+
+Would produce a nil value for `Foo` if no `--foo` argument is supplied, but would have a pointer to the value 0 if the argument `--foo=0` was supplied.
+
+## Nested data structure
+
+Kong support a nested data structure as well with `embed:""`. You can combine `embed:""` with `prefix:""`:
+
+```go
+var CLI struct {
+ Logging struct {
+ Level string `enum:"debug,info,warn,error" default:"info"`
+ Type string `enum:"json,console" default:"console"`
+ } `embed:"" prefix:"logging."`
+}
+```
+
+This configures Kong to accept flags `--logging.level` and `--logging.type`.
+
## Custom named decoders
Kong includes a number of builtin custom type mappers. These can be used by
specifying the tag `type:""`. They are registered with the option
function `NamedMapper(name, mapper)`.
-| Name | Description
-|-------------------|---------------------------------------------------
-| `path` | A path. ~ expansion is applied.
-| `existingfile` | An existing file. ~ expansion is applied. `-` is accepted for stdin.
-| `existingdir` | An existing directory. ~ expansion is applied.
-| `counter` | Increment a numeric field. Useful for `-vvv`. Can accept `-s`, `--long` or `--long=N`.
-
+| Name | Description |
+| -------------- | ---------------------------------------------------------------------------------------------------------------------- |
+| `path` | A path. ~ expansion is applied. `-` is accepted for stdout, and will be passed unaltered. |
+| `existingfile` | An existing file. ~ expansion is applied. `-` is accepted for stdin, and will be passed unaltered. |
+| `existingdir` | An existing directory. ~ expansion is applied. |
+| `counter` | Increment a numeric field. Useful for `-vvv`. Can accept `-s`, `--long` or `--long=N`. |
+| `filecontent` | Read the file at path into the field. ~ expansion is applied. `-` is accepted for stdin, and will be passed unaltered. |
Slices and maps treat type tags specially. For slices, the `type:""` tag
specifies the element type. For maps, the tag has the format
@@ -397,19 +528,17 @@ specifies the element type. For maps, the tag has the format
## Supported field types
-
## Custom decoders (mappers)
-
Any field implementing `encoding.TextUnmarshaler` or `json.Unmarshaler` will use those interfaces
for decoding values. Kong also includes builtin support for many common Go types:
-| Type | Description
-|---------------------|--------------------------------------------
-| `time.Duration` | Populated using `time.ParseDuration()`.
-| `time.Time` | Populated using `time.Parse()`. Format defaults to RFC3339 but can be overridden with the `format:"X"` tag.
-| `*os.File` | Path to a file that will be opened, or `-` for `os.Stdin`. File must be closed by the user.
-| `*url.URL` | Populated with `url.Parse()`.
+| Type | Description |
+| --------------- | ----------------------------------------------------------------------------------------------------------- |
+| `time.Duration` | Populated using `time.ParseDuration()`. |
+| `time.Time` | Populated using `time.Parse()`. Format defaults to RFC3339 but can be overridden with the `format:"X"` tag. |
+| `*os.File` | Path to a file that will be opened, or `-` for `os.Stdin`. File must be closed by the user. |
+| `*url.URL` | Populated with `url.Parse()`. |
For more fine-grained control, if a field implements the
[MapperValue](https://godoc.org/github.com/alecthomas/kong#MapperValue)
@@ -420,36 +549,40 @@ interface it will be used to decode arguments into the field.
Tags can be in two forms:
1. Standard Go syntax, eg. `kong:"required,name='foo'"`.
-2. Bare tags, eg. `required name:"foo"`
+2. Bare tags, eg. `required:"" name:"foo"`
Both can coexist with standard Tag parsing.
-Tag | Description
------------------------| -------------------------------------------
-`cmd` | If present, struct is a command.
-`arg` | If present, field is an argument.
-`env:"X"` | Specify envar to use for default value.
-`name:"X"` | Long name, for overriding field name.
-`help:"X"` | Help text.
-`type:"X"` | Specify [named types](#custom-named-decoders) to use.
-`placeholder:"X"` | Placeholder text.
-`default:"X"` | Default value.
-`default:"1"` | On a command, make it the default.
-`short:"X"` | Short name, if flag.
-`aliases:"X,Y"` | One or more aliases (for cmd).
-`required` | If present, flag/arg is required.
-`optional` | If present, flag/arg is optional.
-`hidden` | If present, command or flag is hidden.
-`format:"X"` | Format for parsing input, if supported.
-`sep:"X"` | Separator for sequences (defaults to ","). May be `none` to disable splitting.
-`mapsep:"X"` | Separator for maps (defaults to ";"). May be `none` to disable splitting.
-`enum:"X,Y,..."` | Set of valid values allowed for this flag.
-`group:"X"` | Logical group for a flag or command.
-`xor:"X"` | Exclusive OR group for flags. Only one flag in the group can be used which is restricted within the same command.
-`prefix:"X"` | Prefix for all sub-flags.
-`set:"K=V"` | Set a variable for expansion by child elements. Multiples can occur.
-`embed` | If present, this field's children will be embedded in the parent. Useful for composition.
-`-` | Ignore the field. Useful for adding non-CLI fields to a configuration struct.
+| Tag | Description |
+| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
+| `cmd:""` | If present, struct is a command. |
+| `arg:""` | If present, field is an argument. Required by default. |
+| `env:"X,Y,..."` | Specify envars to use for default value. The envs are resolved in the declared order. The first value found is used. |
+| `name:"X"` | Long name, for overriding field name. |
+| `help:"X"` | Help text. |
+| `type:"X"` | Specify [named types](#custom-named-decoders) to use. |
+| `placeholder:"X"` | Placeholder text. |
+| `default:"X"` | Default value. |
+| `default:"1"` | On a command, make it the default. |
+| `default:"withargs"` | On a command, make it the default and allow args/flags from that command |
+| `short:"X"` | Short name, if flag. |
+| `aliases:"X,Y"` | One or more aliases (for cmd or flag). |
+| `required:""` | If present, flag/arg is required. |
+| `optional:""` | If present, flag/arg is optional. |
+| `hidden:""` | If present, command or flag is hidden. |
+| `negatable:""` | If present on a `bool` field, supports prefixing a flag with `--no-` to invert the default value |
+| `format:"X"` | Format for parsing input, if supported. |
+| `sep:"X"` | Separator for sequences (defaults to ","). May be `none` to disable splitting. |
+| `mapsep:"X"` | Separator for maps (defaults to ";"). May be `none` to disable splitting. |
+| `enum:"X,Y,..."` | Set of valid values allowed for this flag. An enum field must be `required` or have a valid `default`. |
+| `group:"X"` | Logical group for a flag or command. |
+| `xor:"X,Y,..."` | Exclusive OR groups for flags. Only one flag in the group can be used which is restricted within the same command. When combined with `required`, at least one of the `xor` group will be required. |
+| `prefix:"X"` | Prefix for all sub-flags. |
+| `envprefix:"X"` | Envar prefix for all sub-flags. |
+| `set:"K=V"` | Set a variable for expansion by child elements. Multiples can occur. |
+| `embed:""` | If present, this field's children will be embedded in the parent. Useful for composition. |
+| `passthrough:""` | If present on a positional argument, it stops flag parsing when encountered, as if `--` was processed before. Useful for external command wrappers, like `exec`. On a command it requires that the command contains only one argument of type `[]string` which is then filled with everything following the command, unparsed. |
+| `-` | Ignore the field. Useful for adding non-CLI fields to a configuration struct. e.g `` `kong:"-"` `` |
## Plugins
@@ -471,6 +604,11 @@ cli.Plugins = kong.Plugins{&pluginOne, &pluginTwo}
Additionally if an interface type is embedded, it can also be populated with a Kong annotated struct.
+## Dynamic Commands
+
+While plugins give complete control over extending command-line interfaces, Kong
+also supports dynamically adding commands via `kong.DynamicCommand()`.
+
## Variable interpolation
Kong supports limited variable interpolation into help strings, enum lists and
@@ -523,10 +661,10 @@ interface:
type Validatable interface {
Validate() error
}
- ```
+```
-+If one of these nodes is in the active command-line it will be called during
-+normal validation.
+If one of these nodes is in the active command-line it will be called during
+normal validation.
## Modifying Kong's behaviour
@@ -552,7 +690,14 @@ eg.
kong.Parse(&cli, kong.Configuration(kong.JSON, "/etc/myapp.json", "~/.myapp.json"))
```
-[See the tests](https://github.com/alecthomas/kong/blob/master/resolver_test.go#L103) for an example of how the JSON file is structured.
+[See the tests](https://github.com/alecthomas/kong/blob/master/resolver_test.go#L206) for an example of how the JSON file is structured.
+
+#### List of Configuration Loaders
+
+- [YAML](https://github.com/alecthomas/kong-yaml)
+- [HCL](https://github.com/alecthomas/kong-hcl)
+- [TOML](https://github.com/alecthomas/kong-toml)
+- [JSON](https://github.com/alecthomas/kong)
### `Resolver(...)` - support for default values from external sources
@@ -565,13 +710,14 @@ Example resolvers can be found in [resolver.go](https://github.com/alecthomas/ko
Command-line arguments are mapped to Go values via the Mapper interface:
```go
-// A Mapper knows how to map command-line input to Go.
+// A Mapper represents how a field is mapped from command-line values to Go.
+//
+// Mappers can be associated with concrete fields via pointer, reflect.Type, reflect.Kind, or via a "type" tag.
+//
+// Additionally, if a type implements the MapperValue interface, it will be used.
type Mapper interface {
- // Decode scan into target.
- //
- // "ctx" contains context about the value being decoded that may be useful
- // to some mappers.
- Decode(ctx *MapperContext, scan *Scanner, target reflect.Value) error
+ // Decode ctx.Value with ctx.Scanner into target.
+ Decode(ctx *DecodeContext, target reflect.Value) error
}
```
@@ -588,7 +734,7 @@ The default help output is usually sufficient, but if not there are two solution
1. Use `ConfigureHelp(HelpOptions)` to configure how help is formatted (see [HelpOptions](https://godoc.org/github.com/alecthomas/kong#HelpOptions) for details).
2. Custom help can be wired into Kong via the `Help(HelpFunc)` option. The `HelpFunc` is passed a `Context`, which contains the parsed context for the current command-line. See the implementation of `PrintHelp` for an example.
-3. Use `HelpFormatter(HelpValueFormatter)` if you want to just customize the help text that is accompanied by flags and arguments.
+3. Use `ValueFormatter(HelpValueFormatter)` if you want to just customize the help text that is accompanied by flags and arguments.
4. Use `Groups([]Group)` if you want to customize group titles or add a header.
### `Bind(...)` - bind values for callback hooks and Run() methods
diff --git a/vendor/github.com/alecthomas/kong/build.go b/vendor/github.com/alecthomas/kong/build.go
index 0e3bc5f7..a52d90fa 100644
--- a/vendor/github.com/alecthomas/kong/build.go
+++ b/vendor/github.com/alecthomas/kong/build.go
@@ -12,7 +12,6 @@ import (
type Plugins []interface{}
func build(k *Kong, ast interface{}) (app *Application, err error) {
- defer catch(&err)
v := reflect.ValueOf(ast)
iv := reflect.Indirect(v)
if v.Kind() != reflect.Ptr || iv.Kind() != reflect.Struct {
@@ -25,7 +24,11 @@ func build(k *Kong, ast interface{}) (app *Application, err error) {
for _, flag := range extraFlags {
seenFlags[flag.Name] = true
}
- node := buildNode(k, iv, ApplicationNode, seenFlags)
+
+ node, err := buildNode(k, iv, ApplicationNode, newEmptyTag(), seenFlags)
+ if err != nil {
+ return nil, err
+ }
if len(node.Positional) > 0 && len(node.Children) > 0 {
return nil, fmt.Errorf("can't mix positional arguments and branching arguments on %T", ast)
}
@@ -46,18 +49,36 @@ type flattenedField struct {
tag *Tag
}
-func flattenedFields(v reflect.Value) (out []flattenedField) {
+func flattenedFields(v reflect.Value, ptag *Tag) (out []flattenedField, err error) {
v = reflect.Indirect(v)
for i := 0; i < v.NumField(); i++ {
ft := v.Type().Field(i)
fv := v.Field(i)
- tag := parseTag(fv, ft)
+ tag, err := parseTag(v, ft)
+ if err != nil {
+ return nil, err
+ }
if tag.Ignored {
continue
}
+ // Assign group if it's not already set.
+ if tag.Group == "" {
+ tag.Group = ptag.Group
+ }
+ // Accumulate prefixes.
+ tag.Prefix = ptag.Prefix + tag.Prefix
+ tag.EnvPrefix = ptag.EnvPrefix + tag.EnvPrefix
+ // Combine parent vars.
+ tag.Vars = ptag.Vars.CloneWith(tag.Vars)
+ // Command and embedded structs can be pointers, so we hydrate them now.
+ if (tag.Cmd || tag.Embed) && ft.Type.Kind() == reflect.Ptr {
+ fv = reflect.New(ft.Type.Elem()).Elem()
+ v.FieldByIndex(ft.Index).Set(fv.Addr())
+ }
if !ft.Anonymous && !tag.Embed {
if fv.CanSet() {
- out = append(out, flattenedField{field: ft, value: fv, tag: tag})
+ field := flattenedField{field: ft, value: fv, tag: tag}
+ out = append(out, field)
}
continue
}
@@ -67,77 +88,125 @@ func flattenedFields(v reflect.Value) (out []flattenedField) {
fv = fv.Elem()
} else if fv.Type() == reflect.TypeOf(Plugins{}) {
for i := 0; i < fv.Len(); i++ {
- out = append(out, flattenedFields(fv.Index(i).Elem())...)
+ fields, ferr := flattenedFields(fv.Index(i).Elem(), tag)
+ if ferr != nil {
+ return nil, ferr
+ }
+ out = append(out, fields...)
}
continue
}
- sub := flattenedFields(fv)
- for _, subf := range sub {
- // Assign parent if it's not already set.
- if subf.tag.Group == "" {
- subf.tag.Group = tag.Group
- }
- // Accumulate prefixes.
- subf.tag.Prefix = tag.Prefix + subf.tag.Prefix
- // Combine parent vars.
- subf.tag.Vars = tag.Vars.CloneWith(subf.tag.Vars)
+ sub, err := flattenedFields(fv, tag)
+ if err != nil {
+ return nil, err
}
out = append(out, sub...)
}
- return out
+ return out, nil
}
-func buildNode(k *Kong, v reflect.Value, typ NodeType, seenFlags map[string]bool) *Node {
+// Build a Node in the Kong data model.
+//
+// "v" is the value to create the node from, "typ" is the output Node type.
+func buildNode(k *Kong, v reflect.Value, typ NodeType, tag *Tag, seenFlags map[string]bool) (*Node, error) {
node := &Node{
Type: typ,
Target: v,
- Tag: newEmptyTag(),
+ Tag: tag,
}
- for _, field := range flattenedFields(v) {
+ fields, err := flattenedFields(v, tag)
+ if err != nil {
+ return nil, err
+ }
+
+MAIN:
+ for _, field := range fields {
+ for _, r := range k.ignoreFields {
+ if r.MatchString(v.Type().Name() + "." + field.field.Name) {
+ continue MAIN
+ }
+ }
+
ft := field.field
fv := field.value
tag := field.tag
name := tag.Name
if name == "" {
- name = tag.Prefix + strings.ToLower(dashedString(ft.Name))
+ name = tag.Prefix + k.flagNamer(ft.Name)
} else {
name = tag.Prefix + name
}
+ if len(tag.Envs) != 0 {
+ for i := range tag.Envs {
+ tag.Envs[i] = tag.EnvPrefix + tag.Envs[i]
+ }
+ }
+
// Nested structs are either commands or args, unless they implement the Mapper interface.
- if ft.Type.Kind() == reflect.Struct && (tag.Cmd || tag.Arg) && k.registry.ForValue(fv) == nil {
+ if field.value.Kind() == reflect.Struct && (tag.Cmd || tag.Arg) && k.registry.ForValue(fv) == nil {
typ := CommandNode
if tag.Arg {
typ = ArgumentNode
}
- buildChild(k, node, typ, v, ft, fv, tag, name, seenFlags)
+ err = buildChild(k, node, typ, v, ft, fv, tag, name, seenFlags)
} else {
- buildField(k, node, v, ft, fv, tag, name, seenFlags)
+ err = buildField(k, node, v, ft, fv, tag, name, seenFlags)
+ }
+ if err != nil {
+ return nil, err
}
}
+ // Validate if there are no duplicate names
+ if err := checkDuplicateNames(node, v); err != nil {
+ return nil, err
+ }
+
// "Unsee" flags.
for _, flag := range node.Flags {
- delete(seenFlags, flag.Name)
+ delete(seenFlags, "--"+flag.Name)
+ if flag.Short != 0 {
+ delete(seenFlags, "-"+string(flag.Short))
+ }
}
- // Scan through argument positionals to ensure optional is never before a required.
- last := true
- for i, p := range node.Positional {
- if !last && p.Required {
- fail("argument %q can not be required after an optional", p.Name)
+ if err := validatePositionalArguments(node); err != nil {
+ return nil, err
+ }
+
+ return node, nil
+}
+
+func validatePositionalArguments(node *Node) error {
+ var last *Value
+ for i, curr := range node.Positional {
+ if last != nil {
+ // Scan through argument positionals to ensure optional is never before a required.
+ if !last.Required && curr.Required {
+ return fmt.Errorf("%s: required %q cannot come after optional %q", node.FullPath(), curr.Name, last.Name)
+ }
+
+ // Cumulative argument needs to be last.
+ if last.IsCumulative() {
+ return fmt.Errorf("%s: argument %q cannot come after cumulative %q", node.FullPath(), curr.Name, last.Name)
+ }
}
- last = p.Required
- p.Position = i
+ last = curr
+ curr.Position = i
}
- return node
+ return nil
}
-func buildChild(k *Kong, node *Node, typ NodeType, v reflect.Value, ft reflect.StructField, fv reflect.Value, tag *Tag, name string, seenFlags map[string]bool) {
- child := buildNode(k, fv, typ, seenFlags)
+func buildChild(k *Kong, node *Node, typ NodeType, v reflect.Value, ft reflect.StructField, fv reflect.Value, tag *Tag, name string, seenFlags map[string]bool) error {
+ child, err := buildNode(k, fv, typ, newEmptyTag(), seenFlags)
+ if err != nil {
+ return err
+ }
+ child.Name = name
child.Tag = tag
child.Parent = node
child.Help = tag.Help
@@ -153,48 +222,67 @@ func buildChild(k *Kong, node *Node, typ NodeType, v reflect.Value, ft reflect.S
// a positional argument is provided to the child, and move it to the branching argument field.
if tag.Arg {
if len(child.Positional) == 0 {
- fail("positional branch %s.%s must have at least one child positional argument named %q",
- v.Type().Name(), ft.Name, name)
+ return failField(v, ft, "positional branch must have at least one child positional argument named %q", name)
+ }
+ if child.Positional[0].Name != name {
+ return failField(v, ft, "first field in positional branch must have the same name as the parent field (%s).", child.Name)
}
- value := child.Positional[0]
+ child.Argument = child.Positional[0]
child.Positional = child.Positional[1:]
if child.Help == "" {
- child.Help = value.Help
+ child.Help = child.Argument.Help
}
-
- child.Name = value.Name
- if child.Name != name {
- fail("first field in positional branch %s.%s must have the same name as the parent field (%s).",
- v.Type().Name(), ft.Name, child.Name)
- }
-
- child.Argument = value
} else {
- child.Name = name
+ if tag.HasDefault {
+ if node.DefaultCmd != nil {
+ return failField(v, ft, "can't have more than one default command under %s", node.Summary())
+ }
+ if tag.Default != "withargs" && (len(child.Children) > 0 || len(child.Positional) > 0) {
+ return failField(v, ft, "default command %s must not have subcommands or arguments", child.Summary())
+ }
+ node.DefaultCmd = child
+ }
+ if tag.Passthrough {
+ if len(child.Children) > 0 || len(child.Flags) > 0 {
+ return failField(v, ft, "passthrough command %s must not have subcommands or flags", child.Summary())
+ }
+ if len(child.Positional) != 1 {
+ return failField(v, ft, "passthrough command %s must contain exactly one positional argument", child.Summary())
+ }
+ if !checkPassthroughArg(child.Positional[0].Target) {
+ return failField(v, ft, "passthrough command %s must contain exactly one positional argument of []string type", child.Summary())
+ }
+ child.Passthrough = true
+ }
}
node.Children = append(node.Children, child)
if len(child.Positional) > 0 && len(child.Children) > 0 {
- fail("can't mix positional arguments and branching arguments on %s.%s", v.Type().Name(), ft.Name)
+ return failField(v, ft, "can't mix positional arguments and branching arguments")
}
+
+ return nil
}
-func buildField(k *Kong, node *Node, v reflect.Value, ft reflect.StructField, fv reflect.Value, tag *Tag, name string, seenFlags map[string]bool) {
+func buildField(k *Kong, node *Node, v reflect.Value, ft reflect.StructField, fv reflect.Value, tag *Tag, name string, seenFlags map[string]bool) error {
mapper := k.registry.ForNamedValue(tag.Type, fv)
if mapper == nil {
- fail("unsupported field type %s.%s (of type %s), perhaps missing a cmd:\"\" tag?", v.Type(), ft.Name, ft.Type)
+ return failField(v, ft, "unsupported field type %s, perhaps missing a cmd:\"\" tag?", ft.Type)
}
value := &Value{
Name: name,
Help: tag.Help,
+ OrigHelp: tag.Help,
+ HasDefault: tag.HasDefault,
Default: tag.Default,
DefaultValue: reflect.New(fv.Type()).Elem(),
Mapper: mapper,
Tag: tag,
Target: fv,
Enum: tag.Enum,
+ Passthrough: tag.Passthrough,
// Flags are optional by default, and args are required by default.
Required: (!tag.Arg && tag.Required) || (tag.Arg && !tag.Optional),
@@ -204,15 +292,29 @@ func buildField(k *Kong, node *Node, v reflect.Value, ft reflect.StructField, fv
if tag.Arg {
node.Positional = append(node.Positional, value)
} else {
- if seenFlags[value.Name] {
- fail("duplicate flag --%s", value.Name)
+ if seenFlags["--"+value.Name] {
+ return failField(v, ft, "duplicate flag --%s", value.Name)
+ }
+ seenFlags["--"+value.Name] = true
+ for _, alias := range tag.Aliases {
+ aliasFlag := "--" + alias
+ if seenFlags[aliasFlag] {
+ return failField(v, ft, "duplicate flag %s", aliasFlag)
+ }
+ seenFlags[aliasFlag] = true
+ }
+ if tag.Short != 0 {
+ if seenFlags["-"+string(tag.Short)] {
+ return failField(v, ft, "duplicate short flag -%c", tag.Short)
+ }
+ seenFlags["-"+string(tag.Short)] = true
}
- seenFlags[value.Name] = true
flag := &Flag{
Value: value,
+ Aliases: tag.Aliases,
Short: tag.Short,
PlaceHolder: tag.PlaceHolder,
- Env: tag.Env,
+ Envs: tag.Envs,
Group: buildGroupForKey(k, tag.Group),
Xor: tag.Xor,
Hidden: tag.Hidden,
@@ -220,6 +322,7 @@ func buildField(k *Kong, node *Node, v reflect.Value, ft reflect.StructField, fv
value.Flag = flag
node.Flags = append(node.Flags, flag)
}
+ return nil
}
func buildGroupForKey(k *Kong, key string) *Group {
@@ -238,3 +341,20 @@ func buildGroupForKey(k *Kong, key string) *Group {
Title: key,
}
}
+
+func checkDuplicateNames(node *Node, v reflect.Value) error {
+ seenNames := make(map[string]struct{})
+ for _, node := range node.Children {
+ if _, ok := seenNames[node.Name]; ok {
+ name := v.Type().Name()
+ if name == "" {
+ name = ""
+ }
+ return fmt.Errorf("duplicate command name %q in command %q", node.Name, name)
+ }
+
+ seenNames[node.Name] = struct{}{}
+ }
+
+ return nil
+}
diff --git a/vendor/github.com/alecthomas/kong/callbacks.go b/vendor/github.com/alecthomas/kong/callbacks.go
index 139a83bf..45ef0d25 100644
--- a/vendor/github.com/alecthomas/kong/callbacks.go
+++ b/vendor/github.com/alecthomas/kong/callbacks.go
@@ -24,6 +24,30 @@ func (b bindings) add(values ...interface{}) bindings {
return b
}
+func (b bindings) addTo(impl, iface interface{}) {
+ valueOf := reflect.ValueOf(impl)
+ b[reflect.TypeOf(iface).Elem()] = func() (reflect.Value, error) { return valueOf, nil }
+}
+
+func (b bindings) addProvider(provider interface{}) error {
+ pv := reflect.ValueOf(provider)
+ t := pv.Type()
+ if t.Kind() != reflect.Func || t.NumIn() != 0 || t.NumOut() != 2 || t.Out(1) != reflect.TypeOf((*error)(nil)).Elem() {
+ return fmt.Errorf("%T must be a function with the signature func()(T, error)", provider)
+ }
+ rt := pv.Type().Out(0)
+ b[rt] = func() (reflect.Value, error) {
+ out := pv.Call(nil)
+ errv := out[1]
+ var err error
+ if !errv.IsNil() {
+ err = errv.Interface().(error) //nolint
+ }
+ return out[0], err
+ }
+ return nil
+}
+
// Clone and add values.
func (b bindings) clone() bindings {
out := make(bindings, len(b))
@@ -50,11 +74,14 @@ func getMethod(value reflect.Value, name string) reflect.Value {
return method
}
-func callMethod(name string, v, f reflect.Value, bindings bindings) error {
+func callFunction(f reflect.Value, bindings bindings) error {
+ if f.Kind() != reflect.Func {
+ return fmt.Errorf("expected function, got %s", f.Type())
+ }
in := []reflect.Value{}
t := f.Type()
- if t.NumOut() != 1 || t.Out(0) != callbackReturnSignature {
- return fmt.Errorf("return value of %T.%s() must be exactly \"error\"", v.Type(), name)
+ if t.NumOut() != 1 || !t.Out(0).Implements(callbackReturnSignature) {
+ return fmt.Errorf("return value of %s must implement \"error\"", t)
}
for i := 0; i < t.NumIn(); i++ {
pt := t.In(i)
@@ -65,12 +92,38 @@ func callMethod(name string, v, f reflect.Value, bindings bindings) error {
}
in = append(in, argv)
} else {
- return fmt.Errorf("couldn't find binding of type %s for parameter %d of %s.%s(), use kong.Bind(%s)", pt, i, v.Type(), name, pt)
+ return fmt.Errorf("couldn't find binding of type %s for parameter %d of %s(), use kong.Bind(%s)", pt, i, t, pt)
}
}
out := f.Call(in)
if out[0].IsNil() {
return nil
}
- return out[0].Interface().(error)
+ return out[0].Interface().(error) //nolint
+}
+
+func callAnyFunction(f reflect.Value, bindings bindings) (out []any, err error) {
+ if f.Kind() != reflect.Func {
+ return nil, fmt.Errorf("expected function, got %s", f.Type())
+ }
+ in := []reflect.Value{}
+ t := f.Type()
+ for i := 0; i < t.NumIn(); i++ {
+ pt := t.In(i)
+ if argf, ok := bindings[pt]; ok {
+ argv, err := argf()
+ if err != nil {
+ return nil, err
+ }
+ in = append(in, argv)
+ } else {
+ return nil, fmt.Errorf("couldn't find binding of type %s for parameter %d of %s(), use kong.Bind(%s)", pt, i, t, pt)
+ }
+ }
+ outv := f.Call(in)
+ out = make([]any, len(outv))
+ for i, v := range outv {
+ out[i] = v.Interface()
+ }
+ return out, nil
}
diff --git a/vendor/github.com/alecthomas/kong/camelcase.go b/vendor/github.com/alecthomas/kong/camelcase.go
index acf29f75..a955b16b 100644
--- a/vendor/github.com/alecthomas/kong/camelcase.go
+++ b/vendor/github.com/alecthomas/kong/camelcase.go
@@ -13,37 +13,37 @@ import (
//
// Examples
//
-// "" => [""]
-// "lowercase" => ["lowercase"]
-// "Class" => ["Class"]
-// "MyClass" => ["My", "Class"]
-// "MyC" => ["My", "C"]
-// "HTML" => ["HTML"]
-// "PDFLoader" => ["PDF", "Loader"]
-// "AString" => ["A", "String"]
-// "SimpleXMLParser" => ["Simple", "XML", "Parser"]
-// "vimRPCPlugin" => ["vim", "RPC", "Plugin"]
-// "GL11Version" => ["GL", "11", "Version"]
-// "99Bottles" => ["99", "Bottles"]
-// "May5" => ["May", "5"]
-// "BFG9000" => ["BFG", "9000"]
-// "BöseÜberraschung" => ["Böse", "Überraschung"]
-// "Two spaces" => ["Two", " ", "spaces"]
-// "BadUTF8\xe2\xe2\xa1" => ["BadUTF8\xe2\xe2\xa1"]
+// "" => [""]
+// "lowercase" => ["lowercase"]
+// "Class" => ["Class"]
+// "MyClass" => ["My", "Class"]
+// "MyC" => ["My", "C"]
+// "HTML" => ["HTML"]
+// "PDFLoader" => ["PDF", "Loader"]
+// "AString" => ["A", "String"]
+// "SimpleXMLParser" => ["Simple", "XML", "Parser"]
+// "vimRPCPlugin" => ["vim", "RPC", "Plugin"]
+// "GL11Version" => ["GL", "11", "Version"]
+// "99Bottles" => ["99", "Bottles"]
+// "May5" => ["May", "5"]
+// "BFG9000" => ["BFG", "9000"]
+// "BöseÜberraschung" => ["Böse", "Überraschung"]
+// "Two spaces" => ["Two", " ", "spaces"]
+// "BadUTF8\xe2\xe2\xa1" => ["BadUTF8\xe2\xe2\xa1"]
//
// Splitting rules
//
-// 1) If string is not valid UTF-8, return it without splitting as
+// 1. If string is not valid UTF-8, return it without splitting as
// single item array.
-// 2) Assign all unicode characters into one of 4 sets: lower case
+// 2. Assign all unicode characters into one of 4 sets: lower case
// letters, upper case letters, numbers, and all other characters.
-// 3) Iterate through characters of string, introducing splits
+// 3. Iterate through characters of string, introducing splits
// between adjacent characters that belong to different sets.
-// 4) Iterate through array of split strings, and if a given string
+// 4. Iterate through array of split strings, and if a given string
// is upper case:
-// if subsequent string is lower case:
-// move last character of upper case string to beginning of
-// lower case string
+// if subsequent string is lower case:
+// move last character of upper case string to beginning of
+// lower case string
func camelCase(src string) (entries []string) {
// don't split invalid utf8
if !utf8.ValidString(src) {
diff --git a/vendor/github.com/alecthomas/kong/context.go b/vendor/github.com/alecthomas/kong/context.go
index 4d241613..b2bfea69 100644
--- a/vendor/github.com/alecthomas/kong/context.go
+++ b/vendor/github.com/alecthomas/kong/context.go
@@ -1,14 +1,13 @@
package kong
import (
+ "errors"
"fmt"
"os"
"reflect"
"sort"
"strconv"
"strings"
-
- "github.com/pkg/errors"
)
// Path records the nodes and parsed values from the current command-line.
@@ -111,10 +110,17 @@ func (c *Context) Bind(args ...interface{}) {
//
// This will typically have to be called like so:
//
-// BindTo(impl, (*MyInterface)(nil))
+// BindTo(impl, (*MyInterface)(nil))
func (c *Context) BindTo(impl, iface interface{}) {
- valueOf := reflect.ValueOf(impl)
- c.bindings[reflect.TypeOf(iface).Elem()] = func() (reflect.Value, error) { return valueOf, nil }
+ c.bindings.addTo(impl, iface)
+}
+
+// BindToProvider allows binding of provider functions.
+//
+// This is useful when the Run() function of different commands require different values that may
+// not all be initialisable from the main() function.
+func (c *Context) BindToProvider(provider interface{}) error {
+ return c.bindings.addProvider(provider)
}
// Value returns the value for a particular path element.
@@ -155,20 +161,20 @@ func (c *Context) Empty() bool {
}
// Validate the current context.
-func (c *Context) Validate() error { // nolint: gocyclo
+func (c *Context) Validate() error { //nolint: gocyclo
err := Visit(c.Model, func(node Visitable, next Next) error {
switch node := node.(type) {
case *Value:
- _, ok := os.LookupEnv(node.Tag.Env)
- if node.Enum != "" && (!node.Required || node.Default != "" || (node.Tag.Env != "" && ok)) {
+ ok := atLeastOneEnvSet(node.Tag.Envs)
+ if node.Enum != "" && (!node.Required || node.HasDefault || (len(node.Tag.Envs) != 0 && ok)) {
if err := checkEnum(node, node.Target); err != nil {
return err
}
}
case *Flag:
- _, ok := os.LookupEnv(node.Tag.Env)
- if node.Enum != "" && (!node.Required || node.Default != "" || (node.Tag.Env != "" && ok)) {
+ ok := atLeastOneEnvSet(node.Tag.Envs)
+ if node.Enum != "" && (!node.Required || node.HasDefault || (len(node.Tag.Envs) != 0 && ok)) {
if err := checkEnum(node.Value, node.Target); err != nil {
return err
}
@@ -195,16 +201,18 @@ func (c *Context) Validate() error { // nolint: gocyclo
case *Application:
value = node.Target
- desc = node.Name
+ desc = ""
case *Node:
value = node.Target
desc = node.Path()
}
if validate := isValidatable(value); validate != nil {
- err := validate.Validate()
- if err != nil {
- return errors.Wrap(err, desc)
+ if err := validate.Validate(); err != nil {
+ if desc != "" {
+ return fmt.Errorf("%s: %w", desc, err)
+ }
+ return err
}
}
}
@@ -324,14 +332,41 @@ func (c *Context) Reset() error {
})
}
-func (c *Context) trace(node *Node) (err error) { // nolint: gocyclo
+func (c *Context) endParsing() {
+ args := []string{}
+ for {
+ token := c.scan.Pop()
+ if token.Type == EOLToken {
+ break
+ }
+ args = append(args, token.String())
+ }
+ // Note: tokens must be pushed in reverse order.
+ for i := range args {
+ c.scan.PushTyped(args[len(args)-1-i], PositionalArgumentToken)
+ }
+}
+
+func (c *Context) trace(node *Node) (err error) { //nolint: gocyclo
positional := 0
+ node.Active = true
flags := []*Flag{}
- for _, group := range node.AllFlags(false) {
+ flagNode := node
+ if node.DefaultCmd != nil && node.DefaultCmd.Tag.Default == "withargs" {
+ // Add flags of the default command if the current node has one
+ // and that default command allows args / flags without explicitly
+ // naming the command on the CLI.
+ flagNode = node.DefaultCmd
+ }
+ for _, group := range flagNode.AllFlags(false) {
flags = append(flags, group...)
}
+ if node.Passthrough {
+ c.endParsing()
+ }
+
for !c.scan.Peek().IsEOL() {
token := c.scan.Peek()
switch token.Type {
@@ -342,25 +377,14 @@ func (c *Context) trace(node *Node) (err error) { // nolint: gocyclo
switch {
case v == "-":
fallthrough
- default: // nolint
+ default: //nolint
c.scan.Pop()
c.scan.PushTyped(token.Value, PositionalArgumentToken)
// Indicates end of parsing. All remaining arguments are treated as positional arguments only.
case v == "--":
c.scan.Pop()
- args := []string{}
- for {
- token = c.scan.Pop()
- if token.Type == EOLToken {
- break
- }
- args = append(args, token.String())
- }
- // Note: tokens must be pushed in reverse order.
- for i := range args {
- c.scan.PushTyped(args[len(args)-1-i], PositionalArgumentToken)
- }
+ c.endParsing()
// Long flag.
case strings.HasPrefix(v, "--"):
@@ -413,6 +437,12 @@ func (c *Context) trace(node *Node) (err error) { // nolint: gocyclo
// Ensure we've consumed all positional arguments.
if positional < len(node.Positional) {
arg := node.Positional[positional]
+
+ if arg.Passthrough {
+ c.endParsing()
+ }
+
+ arg.Active = true
err := arg.Parse(c.scan, c.getValue(arg))
if err != nil {
return err
@@ -474,6 +504,17 @@ func (c *Context) trace(node *Node) (err error) { // nolint: gocyclo
}
}
+ // If there is a default command that allows args and nothing else
+ // matches, take the branch of the default command
+ if node.DefaultCmd != nil && node.DefaultCmd.Tag.Default == "withargs" {
+ c.Path = append(c.Path, &Path{
+ Parent: node,
+ Command: node.DefaultCmd,
+ Flags: node.DefaultCmd.Flags,
+ })
+ return c.trace(node.DefaultCmd)
+ }
+
return findPotentialCandidates(token.String(), candidates, "unexpected argument %s", token)
default:
return fmt.Errorf("unexpected token %s", token)
@@ -490,21 +531,12 @@ func (c *Context) maybeSelectDefault(flags []*Flag, node *Node) error {
return nil
}
}
- var defaultNode *Path
- for _, child := range node.Children {
- if child.Type == CommandNode && child.Tag.Default != "" {
- if defaultNode != nil {
- return fmt.Errorf("can't have more than one default command under %s", node.Summary())
- }
- defaultNode = &Path{
- Parent: child,
- Command: child,
- Flags: child.Flags,
- }
- }
- }
- if defaultNode != nil {
- c.Path = append(c.Path, defaultNode)
+ if node.DefaultCmd != nil {
+ c.Path = append(c.Path, &Path{
+ Parent: node.DefaultCmd,
+ Command: node.DefaultCmd,
+ Flags: node.DefaultCmd.Flags,
+ })
}
return nil
}
@@ -529,7 +561,7 @@ func (c *Context) Resolve() error {
for _, resolver := range resolvers {
s, err := resolver.Resolve(c, path, flag)
if err != nil {
- return errors.Wrap(err, flag.ShortSummary())
+ return fmt.Errorf("%s: %w", flag.ShortSummary(), err)
}
if s == nil {
continue
@@ -553,7 +585,7 @@ func (c *Context) Resolve() error {
})
}
}
- c.Path = append(inserted, c.Path...)
+ c.Path = append(c.Path, inserted...)
return nil
}
@@ -576,6 +608,7 @@ func (c *Context) getValue(value *Value) reflect.Value {
v.Set(reflect.MakeSlice(v.Type(), 0, 0))
case reflect.Map:
v.Set(reflect.MakeMap(v.Type()))
+ default:
}
c.values[value] = v
}
@@ -633,34 +666,78 @@ func (c *Context) Apply() (string, error) {
return strings.Join(path, " "), nil
}
+func flipBoolValue(value reflect.Value) error {
+ if value.Kind() == reflect.Bool {
+ value.SetBool(!value.Bool())
+ return nil
+ }
+
+ if value.Kind() == reflect.Ptr {
+ if !value.IsNil() {
+ return flipBoolValue(value.Elem())
+ }
+ return nil
+ }
+
+ return fmt.Errorf("cannot negate a value of %s", value.Type().String())
+}
+
func (c *Context) parseFlag(flags []*Flag, match string) (err error) {
- defer catch(&err)
candidates := []string{}
+
for _, flag := range flags {
long := "--" + flag.Name
- short := "-" + string(flag.Short)
+ matched := long == match
candidates = append(candidates, long)
if flag.Short != 0 {
+ short := "-" + string(flag.Short)
+ matched = matched || (short == match)
candidates = append(candidates, short)
}
- if short != match && long != match {
+ for _, alias := range flag.Aliases {
+ alias = "--" + alias
+ matched = matched || (alias == match)
+ candidates = append(candidates, alias)
+ }
+
+ neg := "--no-" + flag.Name
+ if !matched && !(match == neg && flag.Tag.Negatable) {
continue
}
// Found a matching flag.
c.scan.Pop()
+ if match == neg && flag.Tag.Negatable {
+ flag.Negated = true
+ }
err := flag.Parse(c.scan, c.getValue(flag.Value))
if err != nil {
- if e, ok := errors.Cause(err).(*expectedError); ok && e.token.InferredType().IsAny(FlagToken, ShortFlagToken) {
- return errors.Errorf("%s; perhaps try %s=%q?", err, flag.ShortSummary(), e.token)
+ var expected *expectedError
+ if errors.As(err, &expected) && expected.token.InferredType().IsAny(FlagToken, ShortFlagToken) {
+ return fmt.Errorf("%s; perhaps try %s=%q?", err.Error(), flag.ShortSummary(), expected.token)
}
return err
}
+ if flag.Negated {
+ value := c.getValue(flag.Value)
+ err := flipBoolValue(value)
+ if err != nil {
+ return err
+ }
+ flag.Value.Apply(value)
+ }
c.Path = append(c.Path, &Path{Flag: flag})
return nil
}
return findPotentialCandidates(match, candidates, "unknown flag %s", match)
}
+// Call an arbitrary function filling arguments with bound values.
+func (c *Context) Call(fn any, binds ...interface{}) (out []interface{}, err error) {
+ fv := reflect.ValueOf(fn)
+ bindings := c.Kong.bindings.clone().add(binds...).add(c).merge(c.bindings) //nolint:govet
+ return callAnyFunction(fv, bindings)
+}
+
// RunNode calls the Run() method on an arbitrary node.
//
// This is useful in conjunction with Visit(), for dynamically running commands.
@@ -694,7 +771,7 @@ func (c *Context) RunNode(node *Node, binds ...interface{}) (err error) {
}
for _, method := range methods {
- if err = callMethod("Run", method.node.Target, method.method, method.binds); err != nil {
+ if err = callFunction(method.method, method.binds); err != nil {
return err
}
}
@@ -706,9 +783,17 @@ func (c *Context) RunNode(node *Node, binds ...interface{}) (err error) {
// Any passed values will be bindable to arguments of the target Run() method. Additionally,
// all parent nodes in the command structure will be bound.
func (c *Context) Run(binds ...interface{}) (err error) {
- defer catch(&err)
node := c.Selected()
if node == nil {
+ if len(c.Path) > 0 {
+ selected := c.Path[0].Node()
+ if selected.Type == ApplicationNode {
+ method := getMethod(selected.Target, "Run")
+ if method.IsValid() {
+ return c.RunNode(selected, binds...)
+ }
+ }
+ }
return fmt.Errorf("no command selected")
}
return c.RunNode(node, binds...)
@@ -724,17 +809,41 @@ func (c *Context) PrintUsage(summary bool) error {
}
func checkMissingFlags(flags []*Flag) error {
+ xorGroupSet := map[string]bool{}
+ xorGroup := map[string][]string{}
missing := []string{}
for _, flag := range flags {
+ if flag.Set {
+ for _, xor := range flag.Xor {
+ xorGroupSet[xor] = true
+ }
+ }
if !flag.Required || flag.Set {
continue
}
- missing = append(missing, flag.Summary())
+ if len(flag.Xor) > 0 {
+ for _, xor := range flag.Xor {
+ if xorGroupSet[xor] {
+ continue
+ }
+ xorGroup[xor] = append(xorGroup[xor], flag.Summary())
+ }
+ } else {
+ missing = append(missing, flag.Summary())
+ }
}
+ for xor, flags := range xorGroup {
+ if !xorGroupSet[xor] && len(flags) > 1 {
+ missing = append(missing, strings.Join(flags, " or "))
+ }
+ }
+
if len(missing) == 0 {
return nil
}
+ sort.Strings(missing)
+
return fmt.Errorf("missing flags: %s", strings.Join(missing, ", "))
}
@@ -751,7 +860,6 @@ func checkMissingChildren(node *Node) error {
missing = append(missing, strconv.Quote(strings.Join(missingArgs, " ")))
}
- haveDefault := 0
for _, child := range node.Children {
if child.Hidden {
continue
@@ -762,19 +870,10 @@ func checkMissingChildren(node *Node) error {
}
missing = append(missing, strconv.Quote(child.Summary()))
} else {
- if child.Tag.Default != "" {
- if len(child.Children) > 0 {
- return fmt.Errorf("default command %s must not have subcommands or arguments", child.Summary())
- }
- haveDefault++
- }
missing = append(missing, strconv.Quote(child.Name))
}
}
- if haveDefault > 1 {
- return fmt.Errorf("more than one default command found under %s", node.Summary())
- }
- if len(missing) == 0 || haveDefault > 0 {
+ if len(missing) == 0 {
return nil
}
@@ -801,7 +900,17 @@ func checkMissingPositionals(positional int, values []*Value) error {
missing := []string{}
for ; positional < len(values); positional++ {
- missing = append(missing, "<"+values[positional].Name+">")
+ arg := values[positional]
+ // TODO(aat): Fix hardcoding of these env checks all over the place :\
+ if len(arg.Tag.Envs) != 0 {
+ if atLeastOneEnvSet(arg.Tag.Envs) {
+ continue
+ }
+ }
+ missing = append(missing, "<"+arg.Name+">")
+ }
+ if len(missing) == 0 {
+ return nil
}
return fmt.Errorf("missing positional arguments %s", strings.Join(missing, " "))
}
@@ -817,23 +926,37 @@ func checkEnum(value *Value, target reflect.Value) error {
return nil
case reflect.Map, reflect.Struct:
- return errors.Errorf("enum can only be applied to a slice or value")
+ return errors.New("enum can only be applied to a slice or value")
- default:
- enumMap := value.EnumMap()
- v := fmt.Sprintf("%v", target)
- if enumMap[v] {
+ case reflect.Ptr:
+ if target.IsNil() {
return nil
}
+ return checkEnum(value, target.Elem())
+ default:
+ enumSlice := value.EnumSlice()
+ v := fmt.Sprintf("%v", target)
enums := []string{}
- for enum := range enumMap {
+ for _, enum := range enumSlice {
+ if enum == v {
+ return nil
+ }
enums = append(enums, fmt.Sprintf("%q", enum))
}
- sort.Strings(enums)
return fmt.Errorf("%s must be one of %s but got %q", value.ShortSummary(), strings.Join(enums, ","), target.Interface())
}
}
+func checkPassthroughArg(target reflect.Value) bool {
+ typ := target.Type()
+ switch typ.Kind() {
+ case reflect.Slice:
+ return typ.Elem().Kind() == reflect.String
+ default:
+ return false
+ }
+}
+
func checkXorDuplicates(paths []*Path) error {
for _, path := range paths {
seen := map[string]*Flag{}
@@ -841,13 +964,12 @@ func checkXorDuplicates(paths []*Path) error {
if !flag.Set {
continue
}
- if flag.Xor == "" {
- continue
- }
- if seen[flag.Xor] != nil {
- return fmt.Errorf("--%s and --%s can't be used together", seen[flag.Xor].Name, flag.Name)
+ for _, xor := range flag.Xor {
+ if seen[xor] != nil {
+ return fmt.Errorf("--%s and --%s can't be used together", seen[xor].Name, flag.Name)
+ }
+ seen[xor] = flag
}
- seen[flag.Xor] = flag
}
}
return nil
@@ -886,3 +1008,12 @@ func isValidatable(v reflect.Value) validatable {
}
return nil
}
+
+func atLeastOneEnvSet(envs []string) bool {
+ for _, env := range envs {
+ if _, ok := os.LookupEnv(env); ok {
+ return true
+ }
+ }
+ return false
+}
diff --git a/vendor/github.com/alecthomas/kong/doc.go b/vendor/github.com/alecthomas/kong/doc.go
index 78c4d110..7e53da7f 100644
--- a/vendor/github.com/alecthomas/kong/doc.go
+++ b/vendor/github.com/alecthomas/kong/doc.go
@@ -2,31 +2,31 @@
//
// Here's an example:
//
-// shell rm [-f] [-r] ...
-// shell ls [ ...]
+// shell rm [-f] [-r] ...
+// shell ls [ ...]
//
// This can be represented by the following command-line structure:
//
-// package main
+// package main
//
-// import "github.com/alecthomas/kong"
+// import "github.com/alecthomas/kong"
//
-// var CLI struct {
-// Rm struct {
-// Force bool `short:"f" help:"Force removal."`
-// Recursive bool `short:"r" help:"Recursively remove files."`
+// var CLI struct {
+// Rm struct {
+// Force bool `short:"f" help:"Force removal."`
+// Recursive bool `short:"r" help:"Recursively remove files."`
//
-// Paths []string `arg help:"Paths to remove." type:"path"`
-// } `cmd help:"Remove files."`
+// Paths []string `arg help:"Paths to remove." type:"path"`
+// } `cmd help:"Remove files."`
//
-// Ls struct {
-// Paths []string `arg optional help:"Paths to list." type:"path"`
-// } `cmd help:"List paths."`
-// }
+// Ls struct {
+// Paths []string `arg optional help:"Paths to list." type:"path"`
+// } `cmd help:"List paths."`
+// }
//
-// func main() {
-// kong.Parse(&CLI)
-// }
+// func main() {
+// kong.Parse(&CLI)
+// }
//
// See https://github.com/alecthomas/kong for details.
package kong
diff --git a/vendor/github.com/alecthomas/kong/error.go b/vendor/github.com/alecthomas/kong/error.go
index 30b88589..18225ef5 100644
--- a/vendor/github.com/alecthomas/kong/error.go
+++ b/vendor/github.com/alecthomas/kong/error.go
@@ -8,5 +8,5 @@ type ParseError struct {
Context *Context
}
-// Cause returns the original cause of the error.
-func (p *ParseError) Cause() error { return p.error }
+// Unwrap returns the original cause of the error.
+func (p *ParseError) Unwrap() error { return p.error }
diff --git a/vendor/github.com/alecthomas/kong/guesswidth.go b/vendor/github.com/alecthomas/kong/guesswidth.go
index 46768e68..dfdc3f51 100644
--- a/vendor/github.com/alecthomas/kong/guesswidth.go
+++ b/vendor/github.com/alecthomas/kong/guesswidth.go
@@ -1,3 +1,4 @@
+//go:build appengine || (!linux && !freebsd && !darwin && !dragonfly && !netbsd && !openbsd)
// +build appengine !linux,!freebsd,!darwin,!dragonfly,!netbsd,!openbsd
package kong
diff --git a/vendor/github.com/alecthomas/kong/guesswidth_unix.go b/vendor/github.com/alecthomas/kong/guesswidth_unix.go
index 41d54e64..0170055a 100644
--- a/vendor/github.com/alecthomas/kong/guesswidth_unix.go
+++ b/vendor/github.com/alecthomas/kong/guesswidth_unix.go
@@ -1,3 +1,4 @@
+//go:build (!appengine && linux) || freebsd || darwin || dragonfly || netbsd || openbsd
// +build !appengine,linux freebsd darwin dragonfly netbsd openbsd
package kong
@@ -26,9 +27,9 @@ func guessWidth(w io.Writer) int {
if _, _, err := syscall.Syscall6(
syscall.SYS_IOCTL,
- uintptr(fd), // nolint: unconvert
+ uintptr(fd), //nolint: unconvert
uintptr(syscall.TIOCGWINSZ),
- uintptr(unsafe.Pointer(&dimensions)), // nolint: gas
+ uintptr(unsafe.Pointer(&dimensions)), //nolint: gas
0, 0, 0,
); err == 0 {
if dimensions[1] == 0 {
diff --git a/vendor/github.com/alecthomas/kong/help.go b/vendor/github.com/alecthomas/kong/help.go
index e1077240..cf5a912c 100644
--- a/vendor/github.com/alecthomas/kong/help.go
+++ b/vendor/github.com/alecthomas/kong/help.go
@@ -16,7 +16,7 @@ const (
// Help flag.
type helpValue bool
-func (h helpValue) BeforeApply(ctx *Context) error {
+func (h helpValue) BeforeReset(ctx *Context) error {
options := ctx.Kong.helpOptions
options.Summary = false
err := ctx.Kong.help(options, ctx)
@@ -41,10 +41,21 @@ type HelpOptions struct {
// Tree writes command chains in a tree structure instead of listing them separately.
Tree bool
+ // Place the flags after the commands listing.
+ FlagsLast bool
+
// Indenter modulates the given prefix for the next layer in the tree view.
// The following exported templates can be used: kong.SpaceIndenter, kong.LineIndenter, kong.TreeIndenter
// The kong.SpaceIndenter will be used by default.
Indenter HelpIndenter
+
+ // Don't show the help associated with subcommands
+ NoExpandSubcommands bool
+
+ // Clamp the help wrap width to a value smaller than the terminal width.
+ // If this is set to a non-positive number, the terminal width is used; otherwise,
+ // the min of this value or the terminal width is used.
+ WrapUpperBound int
}
// Apply options to Kong as a configuration option.
@@ -59,6 +70,11 @@ type HelpProvider interface {
Help() string
}
+// PlaceHolderProvider can be implemented by mappers to provide custom placeholder text.
+type PlaceHolderProvider interface {
+ PlaceHolder(flag *Flag) string
+}
+
// HelpIndenter is used to indent new layers in the help tree.
type HelpIndenter func(prefix string) string
@@ -70,10 +86,10 @@ type HelpValueFormatter func(value *Value) string
// DefaultHelpValueFormatter is the default HelpValueFormatter.
func DefaultHelpValueFormatter(value *Value) string {
- if value.Tag.Env == "" {
+ if len(value.Tag.Envs) == 0 || HasInterpolatedVar(value.OrigHelp, "env") {
return value.Help
}
- suffix := "($" + value.Tag.Env + ")"
+ suffix := "(" + formatEnvs(value.Tag.Envs) + ")"
switch {
case strings.HasSuffix(value.Help, "."):
return value.Help[:len(value.Help)-1] + " " + suffix + "."
@@ -84,6 +100,21 @@ func DefaultHelpValueFormatter(value *Value) string {
}
}
+// DefaultShortHelpPrinter is the default HelpPrinter for short help on error.
+func DefaultShortHelpPrinter(options HelpOptions, ctx *Context) error {
+ w := newHelpWriter(ctx, options)
+ cmd := ctx.Selected()
+ app := ctx.Model
+ if cmd == nil {
+ w.Printf("Usage: %s%s", app.Name, app.Summary())
+ w.Printf(`Run "%s --help" for more information.`, app.Name)
+ } else {
+ w.Printf("Usage: %s %s", app.Name, cmd.Summary())
+ w.Printf(`Run "%s --help" for more information.`, cmd.FullPath())
+ }
+ return w.Write(ctx.Stdout)
+}
+
// DefaultHelpPrinter is the default HelpPrinter.
func DefaultHelpPrinter(options HelpOptions, ctx *Context) error {
if ctx.Empty() {
@@ -143,21 +174,31 @@ func printNodeDetail(w *helpWriter, node *Node, hide bool) {
w.Print("Arguments:")
writePositionals(w.Indent(), node.Positional)
}
- if flags := node.AllFlags(true); len(flags) > 0 {
- groupedFlags := collectFlagGroups(flags)
- for _, group := range groupedFlags {
- w.Print("")
- if group.Metadata.Title != "" {
- w.Wrap(group.Metadata.Title)
- }
- if group.Metadata.Description != "" {
- w.Indent().Wrap(group.Metadata.Description)
+ printFlags := func() {
+ if flags := node.AllFlags(true); len(flags) > 0 {
+ groupedFlags := collectFlagGroups(flags)
+ for _, group := range groupedFlags {
w.Print("")
+ if group.Metadata.Title != "" {
+ w.Wrap(group.Metadata.Title)
+ }
+ if group.Metadata.Description != "" {
+ w.Indent().Wrap(group.Metadata.Description)
+ w.Print("")
+ }
+ writeFlags(w.Indent(), group.Flags)
}
- writeFlags(w.Indent(), group.Flags)
}
}
- cmds := node.Leaves(hide)
+ if !w.FlagsLast {
+ printFlags()
+ }
+ var cmds []*Node
+ if w.NoExpandSubcommands {
+ cmds = node.Children
+ } else {
+ cmds = node.Leaves(hide)
+ }
if len(cmds) > 0 {
iw := w.Indent()
if w.Tree {
@@ -184,6 +225,9 @@ func printNodeDetail(w *helpWriter, node *Node, hide bool) {
}
}
}
+ if w.FlagsLast {
+ printFlags()
+ }
}
func writeCommandList(cmds []*Node, iw *helpWriter) {
@@ -328,9 +372,13 @@ type helpWriter struct {
func newHelpWriter(ctx *Context, options HelpOptions) *helpWriter {
lines := []string{}
+ wrapWidth := guessWidth(ctx.Stdout)
+ if options.WrapUpperBound > 0 && wrapWidth > options.WrapUpperBound {
+ wrapWidth = options.WrapUpperBound
+ }
w := &helpWriter{
indent: "",
- width: guessWidth(ctx.Stdout),
+ width: wrapWidth,
lines: &lines,
helpFormatter: ctx.Kong.helpFormatter,
HelpOptions: options,
@@ -442,16 +490,29 @@ func formatFlag(haveShort bool, flag *Flag) string {
flagString := ""
name := flag.Name
isBool := flag.IsBool()
+ isCounter := flag.IsCounter()
if flag.Short != 0 {
- flagString += fmt.Sprintf("-%c, --%s", flag.Short, name)
+ if isBool && flag.Tag.Negatable {
+ flagString += fmt.Sprintf("-%c, --[no-]%s", flag.Short, name)
+ } else {
+ flagString += fmt.Sprintf("-%c, --%s", flag.Short, name)
+ }
} else {
- if haveShort {
- flagString += fmt.Sprintf(" --%s", name)
+ if isBool && flag.Tag.Negatable {
+ if haveShort {
+ flagString = fmt.Sprintf(" --[no-]%s", name)
+ } else {
+ flagString = fmt.Sprintf("--[no-]%s", name)
+ }
} else {
- flagString += fmt.Sprintf("--%s", name)
+ if haveShort {
+ flagString += fmt.Sprintf(" --%s", name)
+ } else {
+ flagString += fmt.Sprintf("--%s", name)
+ }
}
}
- if !isBool {
+ if !isBool && !isCounter {
flagString += fmt.Sprintf("=%s", flag.FormatPlaceHolder())
}
return flagString
@@ -463,6 +524,9 @@ func (h *HelpOptions) CommandTree(node *Node, prefix string) (rows [][2]string)
switch node.Type {
default:
nodeName += prefix + node.Name
+ if len(node.Aliases) != 0 {
+ nodeName += fmt.Sprintf(" (%s)", strings.Join(node.Aliases, ","))
+ }
case ArgumentNode:
nodeName += prefix + "<" + node.Name + ">"
}
@@ -476,6 +540,9 @@ func (h *HelpOptions) CommandTree(node *Node, prefix string) (rows [][2]string)
rows = append(rows, [2]string{prefix + arg.Summary(), arg.Help})
}
for _, subCmd := range node.Children {
+ if subCmd.Hidden {
+ continue
+ }
rows = append(rows, h.CommandTree(subCmd, prefix)...)
}
return
@@ -501,3 +568,12 @@ func TreeIndenter(prefix string) string {
}
return "|" + strings.Repeat(" ", defaultIndent) + prefix
}
+
+func formatEnvs(envs []string) string {
+ formatted := make([]string, len(envs))
+ for i := range envs {
+ formatted[i] = "$" + envs[i]
+ }
+
+ return strings.Join(formatted, ", ")
+}
diff --git a/vendor/github.com/alecthomas/kong/interpolate.go b/vendor/github.com/alecthomas/kong/interpolate.go
index 8c6f7424..e811632d 100644
--- a/vendor/github.com/alecthomas/kong/interpolate.go
+++ b/vendor/github.com/alecthomas/kong/interpolate.go
@@ -5,7 +5,18 @@ import (
"regexp"
)
-var interpolationRegex = regexp.MustCompile(`((?:\${([[:alpha:]_][[:word:]]*))(?:=([^}]+))?})|(\$)|([^$]+)`)
+var interpolationRegex = regexp.MustCompile(`(\$\$)|((?:\${([[:alpha:]_][[:word:]]*))(?:=([^}]+))?})|(\$)|([^$]+)`)
+
+// HasInterpolatedVar returns true if the variable "v" is interpolated in "s".
+func HasInterpolatedVar(s string, v string) bool {
+ matches := interpolationRegex.FindAllStringSubmatch(s, -1)
+ for _, match := range matches {
+ if name := match[3]; name == v {
+ return true
+ }
+ }
+ return false
+}
// Interpolate variables from vars into s for substrings in the form ${var} or ${var=default}.
func interpolate(s string, vars Vars, updatedVars map[string]string) (string, error) {
@@ -21,14 +32,16 @@ func interpolate(s string, vars Vars, updatedVars map[string]string) (string, er
}
}
for _, match := range matches {
- if name := match[2]; name != "" {
+ if dollar := match[1]; dollar != "" {
+ out += "$"
+ } else if name := match[3]; name != "" {
value, ok := vars[name]
if !ok {
// No default value.
- if match[3] == "" {
+ if match[4] == "" {
return "", fmt.Errorf("undefined variable ${%s}", name)
}
- value = match[3]
+ value = match[4]
}
out += value
} else {
diff --git a/vendor/github.com/alecthomas/kong/kong.go b/vendor/github.com/alecthomas/kong/kong.go
index 9b67db93..76eaefe7 100644
--- a/vendor/github.com/alecthomas/kong/kong.go
+++ b/vendor/github.com/alecthomas/kong/kong.go
@@ -1,11 +1,13 @@
package kong
import (
+ "errors"
"fmt"
"io"
"os"
"path/filepath"
"reflect"
+ "regexp"
"strings"
)
@@ -13,13 +15,12 @@ var (
callbackReturnSignature = reflect.TypeOf((*error)(nil)).Elem()
)
-// Error reported by Kong.
-type Error struct{ msg string }
-
-func (e Error) Error() string { return e.msg }
-
-func fail(format string, args ...interface{}) {
- panic(Error{msg: fmt.Sprintf(format, args...)})
+func failField(parent reflect.Value, field reflect.StructField, format string, args ...interface{}) error {
+ name := parent.Type().Name()
+ if name == "" {
+ name = ""
+ }
+ return fmt.Errorf("%s.%s: %s", name, field.Name, fmt.Sprintf(format, args...))
}
// Must creates a new Parser or panics if there is an error.
@@ -31,6 +32,13 @@ func Must(ast interface{}, options ...Option) *Kong {
return k
}
+type usageOnError int
+
+const (
+ shortUsage usageOnError = iota + 1
+ fullUsage
+)
+
// Kong is the main parser type.
type Kong struct {
// Grammar model.
@@ -42,22 +50,27 @@ type Kong struct {
Stdout io.Writer
Stderr io.Writer
- bindings bindings
- loader ConfigurationLoader
- resolvers []Resolver
- registry *Registry
+ bindings bindings
+ loader ConfigurationLoader
+ resolvers []Resolver
+ registry *Registry
+ ignoreFields []*regexp.Regexp
noDefaultHelp bool
- usageOnError bool
+ usageOnError usageOnError
help HelpPrinter
+ shortHelp HelpPrinter
helpFormatter HelpValueFormatter
helpOptions HelpOptions
helpFlag *Flag
groups []Group
vars Vars
+ flagNamer func(string) string
// Set temporarily by Options. These are applied after build().
postBuildOptions []Option
+ embedded []embedded
+ dynamicCommands []*dynamicCommand
}
// New creates a new Kong parser on grammar.
@@ -72,6 +85,10 @@ func New(grammar interface{}, options ...Option) (*Kong, error) {
vars: Vars{},
bindings: bindings{},
helpFormatter: DefaultHelpValueFormatter,
+ ignoreFields: make([]*regexp.Regexp, 0),
+ flagNamer: func(s string) string {
+ return strings.ToLower(dashedString(s))
+ },
}
options = append(options, Bind(k))
@@ -86,6 +103,10 @@ func New(grammar interface{}, options ...Option) (*Kong, error) {
k.help = DefaultHelpPrinter
}
+ if k.shortHelp == nil {
+ k.shortHelp = DefaultShortHelpPrinter
+ }
+
model, err := build(k, grammar)
if err != nil {
return k, err
@@ -94,6 +115,45 @@ func New(grammar interface{}, options ...Option) (*Kong, error) {
k.Model = model
k.Model.HelpFlag = k.helpFlag
+ // Embed any embedded structs.
+ for _, embed := range k.embedded {
+ tag, err := parseTagString(strings.Join(embed.tags, " ")) //nolint:govet
+ if err != nil {
+ return nil, err
+ }
+ tag.Embed = true
+ v := reflect.Indirect(reflect.ValueOf(embed.strct))
+ node, err := buildNode(k, v, CommandNode, tag, map[string]bool{})
+ if err != nil {
+ return nil, err
+ }
+ for _, child := range node.Children {
+ child.Parent = k.Model.Node
+ k.Model.Children = append(k.Model.Children, child)
+ }
+ k.Model.Flags = append(k.Model.Flags, node.Flags...)
+ }
+
+ // Synthesise command nodes.
+ for _, dcmd := range k.dynamicCommands {
+ tag, terr := parseTagString(strings.Join(dcmd.tags, " "))
+ if terr != nil {
+ return nil, terr
+ }
+ tag.Name = dcmd.name
+ tag.Help = dcmd.help
+ tag.Group = dcmd.group
+ tag.Cmd = true
+ v := reflect.Indirect(reflect.ValueOf(dcmd.cmd))
+ err = buildChild(k, k.Model.Node, CommandNode, reflect.Value{}, reflect.StructField{
+ Name: dcmd.name,
+ Type: v.Type(),
+ }, v, tag, dcmd.name, map[string]bool{})
+ if err != nil {
+ return nil, err
+ }
+ }
+
for _, option := range k.postBuildOptions {
if err = option.Apply(k); err != nil {
return nil, err
@@ -148,16 +208,37 @@ func (k *Kong) interpolateValue(value *Value, vars Vars) (err error) {
if len(value.Tag.Vars) > 0 {
vars = vars.CloneWith(value.Tag.Vars)
}
+ if varsContributor, ok := value.Mapper.(VarsContributor); ok {
+ vars = vars.CloneWith(varsContributor.Vars(value))
+ }
+
+ if value.Enum, err = interpolate(value.Enum, vars, nil); err != nil {
+ return fmt.Errorf("enum for %s: %s", value.Summary(), err)
+ }
+
+ updatedVars := map[string]string{
+ "default": value.Default,
+ "enum": value.Enum,
+ }
if value.Default, err = interpolate(value.Default, vars, nil); err != nil {
return fmt.Errorf("default value for %s: %s", value.Summary(), err)
}
if value.Enum, err = interpolate(value.Enum, vars, nil); err != nil {
return fmt.Errorf("enum value for %s: %s", value.Summary(), err)
}
- value.Help, err = interpolate(value.Help, vars, map[string]string{
- "default": value.Default,
- "enum": value.Enum,
- })
+ if value.Flag != nil {
+ for i, env := range value.Flag.Envs {
+ if value.Flag.Envs[i], err = interpolate(env, vars, nil); err != nil {
+ return fmt.Errorf("env value for %s: %s", value.Summary(), err)
+ }
+ }
+ value.Tag.Envs = value.Flag.Envs
+ updatedVars["env"] = ""
+ if len(value.Flag.Envs) != 0 {
+ updatedVars["env"] = value.Flag.Envs[0]
+ }
+ }
+ value.Help, err = interpolate(value.Help, vars, updatedVars)
if err != nil {
return fmt.Errorf("help for %s: %s", value.Summary(), err)
}
@@ -176,6 +257,7 @@ func (k *Kong) extraFlags() []*Flag {
Value: &Value{
Name: "help",
Help: "Show context-sensitive help.",
+ OrigHelp: "Show context-sensitive help.",
Target: value,
Tag: &Tag{},
Mapper: k.registry.ForValue(value),
@@ -195,7 +277,6 @@ func (k *Kong) extraFlags() []*Flag {
// Will return a ParseError if a *semantically* invalid command-line is encountered (as opposed to a syntactically
// invalid one, which will report a normal error).
func (k *Kong) Parse(args []string) (ctx *Context, err error) {
- defer catch(&err)
ctx, err = Trace(k, args)
if err != nil {
return nil, err
@@ -203,6 +284,9 @@ func (k *Kong) Parse(args []string) (ctx *Context, err error) {
if ctx.Error != nil {
return nil, &ParseError{error: ctx.Error, Context: ctx}
}
+ if err = k.applyHook(ctx, "BeforeReset"); err != nil {
+ return nil, &ParseError{error: err, Context: ctx}
+ }
if err = ctx.Reset(); err != nil {
return nil, &ParseError{error: err, Context: ctx}
}
@@ -252,7 +336,7 @@ func (k *Kong) applyHook(ctx *Context, name string) error {
binds.add(ctx, trace)
binds.add(trace.Node().Vars().CloneWith(k.vars))
binds.merge(ctx.bindings)
- if err := callMethod(name, value, method, binds); err != nil {
+ if err := callFunction(method, binds); err != nil {
return err
}
}
@@ -272,7 +356,7 @@ func (k *Kong) applyHookToDefaultFlags(ctx *Context, node *Node, name string) er
}
binds := k.bindings.clone().add(ctx).add(node.Vars().CloneWith(k.vars))
for _, flag := range node.Flags {
- if flag.Default == "" || ctx.values[flag.Value].IsValid() || !flag.Target.IsValid() {
+ if !flag.HasDefault || ctx.values[flag.Value].IsValid() || !flag.Target.IsValid() {
continue
}
method := getMethod(flag.Target, name)
@@ -280,7 +364,7 @@ func (k *Kong) applyHookToDefaultFlags(ctx *Context, node *Node, name string) er
continue
}
path := &Path{Flag: flag}
- if err := callMethod(name, flag.Target, method, binds.clone().add(path)); err != nil {
+ if err := callFunction(method, binds.clone().add(path)); err != nil {
return next(err)
}
}
@@ -328,13 +412,19 @@ func (k *Kong) FatalIfErrorf(err error, args ...interface{}) {
}
msg := err.Error()
if len(args) > 0 {
- msg = fmt.Sprintf(args[0].(string), args[1:]...) + ": " + err.Error()
+ msg = fmt.Sprintf(args[0].(string), args[1:]...) + ": " + err.Error() //nolint
}
// Maybe display usage information.
- if err, ok := err.(*ParseError); ok && k.usageOnError {
- options := k.helpOptions
- _ = k.help(options, err.Context)
- fmt.Fprintln(k.Stdout)
+ var parseErr *ParseError
+ if errors.As(err, &parseErr) {
+ switch k.usageOnError {
+ case fullUsage:
+ _ = k.help(k.helpOptions, parseErr.Context)
+ fmt.Fprintln(k.Stdout)
+ case shortUsage:
+ _ = k.shortHelp(k.helpOptions, parseErr.Context)
+ fmt.Fprintln(k.Stdout)
+ }
}
k.Fatalf("%s", msg)
}
@@ -349,7 +439,7 @@ func (k *Kong) LoadConfig(path string) (Resolver, error) {
if err != nil {
return nil, err
}
- r, err := os.Open(path) // nolint: gas
+ r, err := os.Open(path) //nolint: gas
if err != nil {
return nil, err
}
@@ -357,12 +447,3 @@ func (k *Kong) LoadConfig(path string) (Resolver, error) {
return k.loader(r)
}
-
-func catch(err *error) {
- msg := recover()
- if test, ok := msg.(Error); ok {
- *err = test
- } else if msg != nil {
- panic(msg)
- }
-}
diff --git a/vendor/github.com/alecthomas/kong/mapper.go b/vendor/github.com/alecthomas/kong/mapper.go
index 183ec09b..584bb006 100644
--- a/vendor/github.com/alecthomas/kong/mapper.go
+++ b/vendor/github.com/alecthomas/kong/mapper.go
@@ -3,8 +3,9 @@ package kong
import (
"encoding"
"encoding/json"
+ "errors"
"fmt"
- "io/ioutil"
+ "io"
"math/bits"
"net/url"
"os"
@@ -12,13 +13,11 @@ import (
"strconv"
"strings"
"time"
-
- "github.com/pkg/errors"
)
var (
mapperValueType = reflect.TypeOf((*MapperValue)(nil)).Elem()
- boolMapperType = reflect.TypeOf((*BoolMapper)(nil)).Elem()
+ boolMapperValueType = reflect.TypeOf((*BoolMapperValue)(nil)).Elem()
jsonUnmarshalerType = reflect.TypeOf((*json.Unmarshaler)(nil)).Elem()
textUnmarshalerType = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem()
binaryUnmarshalerType = reflect.TypeOf((*encoding.BinaryUnmarshaler)(nil)).Elem()
@@ -43,19 +42,26 @@ func (r *DecodeContext) WithScanner(scan *Scanner) *DecodeContext {
}
// MapperValue may be implemented by fields in order to provide custom mapping.
+// Mappers may additionally implement PlaceHolderProvider to provide custom placeholder text.
type MapperValue interface {
Decode(ctx *DecodeContext) error
}
+// BoolMapperValue may be implemented by fields in order to provide custom mappings for boolean values.
+type BoolMapperValue interface {
+ MapperValue
+ IsBool() bool
+}
+
type mapperValueAdapter struct {
isBool bool
}
func (m *mapperValueAdapter) Decode(ctx *DecodeContext, target reflect.Value) error {
if target.Type().Implements(mapperValueType) {
- return target.Interface().(MapperValue).Decode(ctx)
+ return target.Interface().(MapperValue).Decode(ctx) //nolint
}
- return target.Addr().Interface().(MapperValue).Decode(ctx)
+ return target.Addr().Interface().(MapperValue).Decode(ctx) //nolint
}
func (m *mapperValueAdapter) IsBool() bool {
@@ -71,9 +77,9 @@ func (m *textUnmarshalerAdapter) Decode(ctx *DecodeContext, target reflect.Value
return err
}
if target.Type().Implements(textUnmarshalerType) {
- return target.Interface().(encoding.TextUnmarshaler).UnmarshalText([]byte(value))
+ return target.Interface().(encoding.TextUnmarshaler).UnmarshalText([]byte(value)) //nolint
}
- return target.Addr().Interface().(encoding.TextUnmarshaler).UnmarshalText([]byte(value))
+ return target.Addr().Interface().(encoding.TextUnmarshaler).UnmarshalText([]byte(value)) //nolint
}
type binaryUnmarshalerAdapter struct{}
@@ -84,10 +90,10 @@ func (m *binaryUnmarshalerAdapter) Decode(ctx *DecodeContext, target reflect.Val
if err != nil {
return err
}
- if target.Type().Implements(textUnmarshalerType) {
- return target.Interface().(encoding.BinaryUnmarshaler).UnmarshalBinary([]byte(value))
+ if target.Type().Implements(binaryUnmarshalerType) {
+ return target.Interface().(encoding.BinaryUnmarshaler).UnmarshalBinary([]byte(value)) //nolint
}
- return target.Addr().Interface().(encoding.BinaryUnmarshaler).UnmarshalBinary([]byte(value))
+ return target.Addr().Interface().(encoding.BinaryUnmarshaler).UnmarshalBinary([]byte(value)) //nolint
}
type jsonUnmarshalerAdapter struct{}
@@ -99,9 +105,9 @@ func (j *jsonUnmarshalerAdapter) Decode(ctx *DecodeContext, target reflect.Value
return err
}
if target.Type().Implements(jsonUnmarshalerType) {
- return target.Interface().(json.Unmarshaler).UnmarshalJSON([]byte(value))
+ return target.Interface().(json.Unmarshaler).UnmarshalJSON([]byte(value)) //nolint
}
- return target.Addr().Interface().(json.Unmarshaler).UnmarshalJSON([]byte(value))
+ return target.Addr().Interface().(json.Unmarshaler).UnmarshalJSON([]byte(value)) //nolint
}
// A Mapper represents how a field is mapped from command-line values to Go.
@@ -114,17 +120,29 @@ type Mapper interface {
Decode(ctx *DecodeContext, target reflect.Value) error
}
+// VarsContributor can be implemented by a Mapper to contribute Vars during interpolation.
+type VarsContributor interface {
+ Vars(ctx *Value) Vars
+}
+
// A BoolMapper is a Mapper to a value that is a boolean.
//
// This is used solely for formatting help.
type BoolMapper interface {
+ Mapper
IsBool() bool
}
+// BoolMapperExt allows a Mapper to dynamically determine if a value is a boolean.
+type BoolMapperExt interface {
+ Mapper
+ IsBoolFromValue(v reflect.Value) bool
+}
+
// A MapperFunc is a single function that complies with the Mapper interface.
type MapperFunc func(ctx *DecodeContext, target reflect.Value) error
-func (m MapperFunc) Decode(ctx *DecodeContext, target reflect.Value) error { // nolint: golint
+func (m MapperFunc) Decode(ctx *DecodeContext, target reflect.Value) error { //nolint: revive
return m(ctx, target)
}
@@ -181,7 +199,8 @@ func (r *Registry) ForType(typ reflect.Type) Mapper {
// Check if the type implements MapperValue.
for _, impl := range []reflect.Type{typ, reflect.PtrTo(typ)} {
if impl.Implements(mapperValueType) {
- return &mapperValueAdapter{impl.Implements(boolMapperType)}
+ // FIXME: This should pass in the bool mapper.
+ return &mapperValueAdapter{impl.Implements(boolMapperValueType)}
}
}
// Next, try explicitly registered types.
@@ -218,8 +237,8 @@ func (r *Registry) RegisterKind(kind reflect.Kind, mapper Mapper) *Registry {
//
// eg.
//
-// Mapper string `kong:"type='colour'`
-// registry.RegisterName("colour", ...)
+// Mapper string `kong:"type='colour'`
+// registry.RegisterName("colour", ...)
func (r *Registry) RegisterName(name string, mapper Mapper) *Registry {
r.names[name] = mapper
return r
@@ -257,8 +276,7 @@ func (r *Registry) RegisterDefaults() *Registry {
RegisterKind(reflect.Float32, floatDecoder(32)).
RegisterKind(reflect.Float64, floatDecoder(64)).
RegisterKind(reflect.String, MapperFunc(func(ctx *DecodeContext, target reflect.Value) error {
- err := ctx.Scan.PopValueInto("string", target.Addr().Interface())
- return err
+ return ctx.Scan.PopValueInto("string", target.Addr().Interface())
})).
RegisterKind(reflect.Bool, boolMapper{}).
RegisterKind(reflect.Slice, sliceDecoder(r)).
@@ -270,7 +288,9 @@ func (r *Registry) RegisterDefaults() *Registry {
RegisterName("path", pathMapper(r)).
RegisterName("existingfile", existingFileMapper(r)).
RegisterName("existingdir", existingDirMapper(r)).
- RegisterName("counter", counterMapper())
+ RegisterName("counter", counterMapper()).
+ RegisterName("filecontent", fileContentMapper(r)).
+ RegisterKind(reflect.Ptr, ptrMapper{r})
}
type boolMapper struct{}
@@ -289,14 +309,14 @@ func (boolMapper) Decode(ctx *DecodeContext, target reflect.Value) error {
target.SetBool(false)
default:
- return errors.Errorf("bool value must be true, 1, yes, false, 0 or no but got %q", v)
+ return fmt.Errorf("bool value must be true, 1, yes, false, 0 or no but got %q", v)
}
case bool:
target.SetBool(v)
default:
- return errors.Errorf("expected bool but got %q (%T)", token.Value, token.Value)
+ return fmt.Errorf("expected bool but got %q (%T)", token.Value, token.Value)
}
} else {
target.SetBool(true)
@@ -307,15 +327,23 @@ func (boolMapper) IsBool() bool { return true }
func durationDecoder() MapperFunc {
return func(ctx *DecodeContext, target reflect.Value) error {
- var value string
- if err := ctx.Scan.PopValueInto("duration", &value); err != nil {
+ t, err := ctx.Scan.PopValue("duration")
+ if err != nil {
return err
}
- r, err := time.ParseDuration(value)
- if err != nil {
- return errors.Errorf("expected duration but got %q: %s", value, err)
+ var d time.Duration
+ switch v := t.Value.(type) {
+ case string:
+ d, err = time.ParseDuration(v)
+ if err != nil {
+ return fmt.Errorf("expected duration but got %q: %v", v, err)
+ }
+ case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64:
+ d = reflect.ValueOf(v).Convert(reflect.TypeOf(time.Duration(0))).Interface().(time.Duration) //nolint: forcetypeassert
+ default:
+ return fmt.Errorf("expected duration but got %q", v)
}
- target.Set(reflect.ValueOf(r))
+ target.Set(reflect.ValueOf(d))
return nil
}
}
@@ -339,7 +367,7 @@ func timeDecoder() MapperFunc {
}
}
-func intDecoder(bits int) MapperFunc { // nolint: dupl
+func intDecoder(bits int) MapperFunc { //nolint: dupl
return func(ctx *DecodeContext, target reflect.Value) error {
t, err := ctx.Scan.PopValue("int")
if err != nil {
@@ -350,22 +378,25 @@ func intDecoder(bits int) MapperFunc { // nolint: dupl
case string:
sv = v
- case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64:
+ case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
sv = fmt.Sprintf("%v", v)
+ case float32, float64:
+ sv = fmt.Sprintf("%0.f", v)
+
default:
- return errors.Errorf("expected an int but got %q (%T)", t, t.Value)
+ return fmt.Errorf("expected an int but got %q (%T)", t, t.Value)
}
n, err := strconv.ParseInt(sv, 10, bits)
if err != nil {
- return errors.Errorf("expected a valid %d bit int but got %q", bits, sv)
+ return fmt.Errorf("expected a valid %d bit int but got %q", bits, sv)
}
target.SetInt(n)
return nil
}
}
-func uintDecoder(bits int) MapperFunc { // nolint: dupl
+func uintDecoder(bits int) MapperFunc { //nolint: dupl
return func(ctx *DecodeContext, target reflect.Value) error {
t, err := ctx.Scan.PopValue("uint")
if err != nil {
@@ -376,15 +407,18 @@ func uintDecoder(bits int) MapperFunc { // nolint: dupl
case string:
sv = v
- case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64:
+ case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
sv = fmt.Sprintf("%v", v)
+ case float32, float64:
+ sv = fmt.Sprintf("%0.f", v)
+
default:
- return errors.Errorf("expected an int but got %q (%T)", t, t.Value)
+ return fmt.Errorf("expected an int but got %q (%T)", t, t.Value)
}
n, err := strconv.ParseUint(sv, 10, bits)
if err != nil {
- return errors.Errorf("expected a valid %d bit uint but got %q", bits, sv)
+ return fmt.Errorf("expected a valid %d bit uint but got %q", bits, sv)
}
target.SetUint(n)
return nil
@@ -401,7 +435,7 @@ func floatDecoder(bits int) MapperFunc {
case string:
n, err := strconv.ParseFloat(v, bits)
if err != nil {
- return errors.Errorf("expected a float but got %q (%T)", t, t.Value)
+ return fmt.Errorf("expected a float but got %q (%T)", t, t.Value)
}
target.SetFloat(n)
@@ -415,7 +449,7 @@ func floatDecoder(bits int) MapperFunc {
target.Set(reflect.ValueOf(v))
default:
- return errors.Errorf("expected an int but got %q (%T)", t, t.Value)
+ return fmt.Errorf("expected an int but got %q (%T)", t, t.Value)
}
return nil
}
@@ -427,23 +461,23 @@ func mapDecoder(r *Registry) MapperFunc {
target.Set(reflect.MakeMap(target.Type()))
}
el := target.Type()
- sep := ctx.Value.Tag.MapSep
+ mapsep := ctx.Value.Tag.MapSep
var childScanner *Scanner
if ctx.Value.Flag != nil {
t := ctx.Scan.Pop()
- // If decoding a flag, we need an argument.
+ // If decoding a flag, we need an value.
if t.IsEOL() {
- return errors.Errorf("unexpected EOL")
+ return fmt.Errorf("missing value, expecting \"=%c...\"", mapsep)
}
switch v := t.Value.(type) {
case string:
- childScanner = Scan(SplitEscaped(v, sep)...)
+ childScanner = ScanAsType(t.Type, SplitEscaped(v, mapsep)...)
case []map[string]interface{}:
for _, m := range v {
err := jsonTranscode(m, target.Addr().Interface())
if err != nil {
- return errors.WithStack(err)
+ return err
}
}
return nil
@@ -452,7 +486,7 @@ func mapDecoder(r *Registry) MapperFunc {
return jsonTranscode(v, target.Addr().Interface())
default:
- return errors.Errorf("invalid map value %q (of type %T)", t, t.Value)
+ return fmt.Errorf("invalid map value %q (of type %T)", t, t.Value)
}
} else {
tokens := ctx.Scan.PopWhile(func(t Token) bool { return t.IsValue() })
@@ -466,7 +500,7 @@ func mapDecoder(r *Registry) MapperFunc {
}
parts := strings.SplitN(token, "=", 2)
if len(parts) != 2 {
- return errors.Errorf("expected \"=\" but got %q", token)
+ return fmt.Errorf("expected \"=\" but got %q", token)
}
key, value := parts[0], parts[1]
@@ -474,23 +508,23 @@ func mapDecoder(r *Registry) MapperFunc {
if typ := ctx.Value.Tag.Type; typ != "" {
parts := strings.Split(typ, ":")
if len(parts) != 2 {
- return errors.Errorf("type:\"\" on map field must be in the form \"[]:[]\"")
+ return errors.New("type:\"\" on map field must be in the form \"[]:[]\"")
}
keyTypeName, valueTypeName = parts[0], parts[1]
}
- keyScanner := Scan(key)
+ keyScanner := ScanAsType(FlagValueToken, key)
keyDecoder := r.ForNamedType(keyTypeName, el.Key())
keyValue := reflect.New(el.Key()).Elem()
if err := keyDecoder.Decode(ctx.WithScanner(keyScanner), keyValue); err != nil {
- return errors.Errorf("invalid map key %q", key)
+ return fmt.Errorf("invalid map key %q", key)
}
- valueScanner := Scan(value)
+ valueScanner := ScanAsType(FlagValueToken, value)
valueDecoder := r.ForNamedType(valueTypeName, el.Elem())
valueValue := reflect.New(el.Elem()).Elem()
if err := valueDecoder.Decode(ctx.WithScanner(valueScanner), valueValue); err != nil {
- return errors.Errorf("invalid map value %q", value)
+ return fmt.Errorf("invalid map value %q", value)
}
target.SetMapIndex(keyValue, valueValue)
@@ -506,13 +540,13 @@ func sliceDecoder(r *Registry) MapperFunc {
var childScanner *Scanner
if ctx.Value.Flag != nil {
t := ctx.Scan.Pop()
- // If decoding a flag, we need an argument.
+ // If decoding a flag, we need a value.
if t.IsEOL() {
- return errors.Errorf("unexpected EOL")
+ return fmt.Errorf("missing value, expecting \"%c...\"", sep)
}
switch v := t.Value.(type) {
case string:
- childScanner = Scan(SplitEscaped(v, sep)...)
+ childScanner = ScanAsType(t.Type, SplitEscaped(v, sep)...)
case []interface{}:
return jsonTranscode(v, target.Addr().Interface())
@@ -527,13 +561,13 @@ func sliceDecoder(r *Registry) MapperFunc {
}
childDecoder := r.ForNamedType(ctx.Value.Tag.Type, el)
if childDecoder == nil {
- return errors.Errorf("no mapper for element type of %s", target.Type())
+ return fmt.Errorf("no mapper for element type of %s", target.Type())
}
for !childScanner.Peek().IsEOL() {
childValue := reflect.New(el).Elem()
err := childDecoder.Decode(ctx.WithScanner(childScanner), childValue)
if err != nil {
- return errors.WithStack(err)
+ return err
}
target.Set(reflect.Append(target, childValue))
}
@@ -546,15 +580,23 @@ func pathMapper(r *Registry) MapperFunc {
if target.Kind() == reflect.Slice {
return sliceDecoder(r)(ctx, target)
}
+ if target.Kind() == reflect.Ptr && target.Elem().Kind() == reflect.String {
+ if target.IsNil() {
+ return nil
+ }
+ target = target.Elem()
+ }
if target.Kind() != reflect.String {
- return errors.Errorf("\"path\" type must be applied to a string not %s", target.Type())
+ return fmt.Errorf("\"path\" type must be applied to a string not %s", target.Type())
}
var path string
err := ctx.Scan.PopValueInto("file", &path)
if err != nil {
return err
}
- path = ExpandPath(path)
+ if path != "-" {
+ path = ExpandPath(path)
+ }
target.SetString(path)
return nil
}
@@ -575,7 +617,7 @@ func fileMapper(r *Registry) MapperFunc {
file = os.Stdin
} else {
path = ExpandPath(path)
- file, err = os.Open(path) // nolint: gosec
+ file, err = os.Open(path) //nolint: gosec
if err != nil {
return err
}
@@ -591,13 +633,21 @@ func existingFileMapper(r *Registry) MapperFunc {
return sliceDecoder(r)(ctx, target)
}
if target.Kind() != reflect.String {
- return errors.Errorf("\"existingfile\" type must be applied to a string not %s", target.Type())
+ return fmt.Errorf("\"existingfile\" type must be applied to a string not %s", target.Type())
}
var path string
err := ctx.Scan.PopValueInto("file", &path)
if err != nil {
return err
}
+
+ if !ctx.Value.Active || (ctx.Value.Set && ctx.Value.Target.Type() == target.Type()) {
+ // early return to avoid checking extra files that may not exist;
+ // this hack only works because the value provided on the cli is
+ // checked before the default value is checked (if default is set).
+ return nil
+ }
+
if path != "-" {
path = ExpandPath(path)
stat, err := os.Stat(path)
@@ -605,7 +655,7 @@ func existingFileMapper(r *Registry) MapperFunc {
return err
}
if stat.IsDir() {
- return errors.Errorf("%q exists but is a directory", path)
+ return fmt.Errorf("%q exists but is a directory", path)
}
}
target.SetString(path)
@@ -619,26 +669,106 @@ func existingDirMapper(r *Registry) MapperFunc {
return sliceDecoder(r)(ctx, target)
}
if target.Kind() != reflect.String {
- return errors.Errorf("\"existingdir\" must be applied to a string not %s", target.Type())
+ return fmt.Errorf("\"existingdir\" must be applied to a string not %s", target.Type())
}
var path string
err := ctx.Scan.PopValueInto("file", &path)
if err != nil {
return err
}
+
+ if !ctx.Value.Active || (ctx.Value.Set && ctx.Value.Target.Type() == target.Type()) {
+ // early return to avoid checking extra dirs that may not exist;
+ // this hack only works because the value provided on the cli is
+ // checked before the default value is checked (if default is set).
+ return nil
+ }
+
path = ExpandPath(path)
stat, err := os.Stat(path)
if err != nil {
return err
}
if !stat.IsDir() {
- return errors.Errorf("%q exists but is not a directory", path)
+ return fmt.Errorf("%q exists but is not a directory", path)
}
target.SetString(path)
return nil
}
}
+func fileContentMapper(r *Registry) MapperFunc {
+ return func(ctx *DecodeContext, target reflect.Value) error {
+ if target.Kind() != reflect.Slice && target.Elem().Kind() != reflect.Uint8 {
+ return fmt.Errorf("\"filecontent\" must be applied to []byte not %s", target.Type())
+ }
+ var path string
+ err := ctx.Scan.PopValueInto("file", &path)
+ if err != nil {
+ return err
+ }
+
+ if !ctx.Value.Active || ctx.Value.Set {
+ // early return to avoid checking extra dirs that may not exist;
+ // this hack only works because the value provided on the cli is
+ // checked before the default value is checked (if default is set).
+ return nil
+ }
+
+ var data []byte
+ if path != "-" {
+ path = ExpandPath(path)
+ data, err = os.ReadFile(path) //nolint:gosec
+ } else {
+ data, err = io.ReadAll(os.Stdin)
+ }
+ if err != nil {
+ if info, statErr := os.Stat(path); statErr == nil && info.IsDir() {
+ return fmt.Errorf("%q exists but is a directory: %w", path, err)
+ }
+ return err
+ }
+ target.SetBytes(data)
+ return nil
+ }
+}
+
+type ptrMapper struct {
+ r *Registry
+}
+
+var _ BoolMapperExt = (*ptrMapper)(nil)
+
+// IsBoolFromValue implements BoolMapperExt
+func (p ptrMapper) IsBoolFromValue(target reflect.Value) bool {
+ elem := reflect.New(target.Type().Elem()).Elem()
+ nestedMapper := p.r.ForValue(elem)
+ if nestedMapper == nil {
+ return false
+ }
+ if bm, ok := nestedMapper.(BoolMapper); ok && bm.IsBool() {
+ return true
+ }
+ if bm, ok := nestedMapper.(BoolMapperExt); ok && bm.IsBoolFromValue(target) {
+ return true
+ }
+ return target.Kind() == reflect.Ptr && target.Type().Elem().Kind() == reflect.Bool
+}
+
+func (p ptrMapper) Decode(ctx *DecodeContext, target reflect.Value) error {
+ elem := reflect.New(target.Type().Elem()).Elem()
+ nestedMapper := p.r.ForValue(elem)
+ if nestedMapper == nil {
+ return fmt.Errorf("cannot find mapper for %v", target.Type().Elem().String())
+ }
+ err := nestedMapper.Decode(ctx, elem)
+ if err != nil {
+ return err
+ }
+ target.Set(elem.Addr())
+ return nil
+}
+
func counterMapper() MapperFunc {
return func(ctx *DecodeContext, target reflect.Value) error {
if ctx.Scan.Peek().Type == FlagValueToken {
@@ -650,7 +780,7 @@ func counterMapper() MapperFunc {
case string:
n, err := strconv.ParseInt(v, 10, 64)
if err != nil {
- return errors.Errorf("expected a counter but got %q (%T)", t, t.Value)
+ return fmt.Errorf("expected a counter but got %q (%T)", t, t.Value)
}
target.SetInt(n)
@@ -658,7 +788,7 @@ func counterMapper() MapperFunc {
target.Set(reflect.ValueOf(v))
default:
- return errors.Errorf("expected a counter but got %q (%T)", t, t.Value)
+ return fmt.Errorf("expected a counter but got %q (%T)", t, t.Value)
}
return nil
}
@@ -674,7 +804,7 @@ func counterMapper() MapperFunc {
target.SetFloat(target.Float() + 1)
default:
- return errors.Errorf("type:\"counter\" must be used with a numeric field")
+ return fmt.Errorf("type:\"counter\" must be used with a numeric field")
}
return nil
}
@@ -689,7 +819,7 @@ func urlMapper() MapperFunc {
}
url, err := url.Parse(urlStr)
if err != nil {
- return errors.WithStack(err)
+ return err
}
target.Set(reflect.ValueOf(url))
return nil
@@ -700,19 +830,22 @@ func urlMapper() MapperFunc {
//
// It differs from strings.Split() in that the separator can exist in a field by escaping it with a \. eg.
//
-// SplitEscaped(`hello\,there,bob`, ',') == []string{"hello,there", "bob"}
+// SplitEscaped(`hello\,there,bob`, ',') == []string{"hello,there", "bob"}
func SplitEscaped(s string, sep rune) (out []string) {
if sep == -1 {
return []string{s}
}
escaped := false
token := ""
- for _, ch := range s {
+ for i, ch := range s {
switch {
case escaped:
+ if ch != sep {
+ token += `\`
+ }
token += string(ch)
escaped = false
- case ch == '\\':
+ case ch == '\\' && i < len(s)-1:
escaped = true
case ch == sep && !escaped:
out = append(out, token)
@@ -730,11 +863,11 @@ func SplitEscaped(s string, sep rune) (out []string) {
// JoinEscaped joins a slice of strings on sep, but also escapes any instances of sep in the fields with \. eg.
//
-// JoinEscaped([]string{"hello,there", "bob"}, ',') == `hello\,there,bob`
+// JoinEscaped([]string{"hello,there", "bob"}, ',') == `hello\,there,bob`
func JoinEscaped(s []string, sep rune) string {
escaped := []string{}
for _, e := range s {
- escaped = append(escaped, strings.Replace(e, string(sep), `\`+string(sep), -1))
+ escaped = append(escaped, strings.ReplaceAll(e, string(sep), `\`+string(sep)))
}
return strings.Join(escaped, string(sep))
}
@@ -745,7 +878,7 @@ type NamedFileContentFlag struct {
Contents []byte
}
-func (f *NamedFileContentFlag) Decode(ctx *DecodeContext) error { // nolint: golint
+func (f *NamedFileContentFlag) Decode(ctx *DecodeContext) error { //nolint: revive
var filename string
err := ctx.Scan.PopValueInto("filename", &filename)
if err != nil {
@@ -757,9 +890,9 @@ func (f *NamedFileContentFlag) Decode(ctx *DecodeContext) error { // nolint: gol
return nil
}
filename = ExpandPath(filename)
- data, err := ioutil.ReadFile(filename) // nolint: gosec
+ data, err := os.ReadFile(filename) //nolint: gosec
if err != nil {
- return errors.Errorf("failed to open %q: %s", filename, err)
+ return fmt.Errorf("failed to open %q: %v", filename, err)
}
f.Contents = data
f.Filename = filename
@@ -769,7 +902,7 @@ func (f *NamedFileContentFlag) Decode(ctx *DecodeContext) error { // nolint: gol
// FileContentFlag is a flag value that loads a file's contents into its value.
type FileContentFlag []byte
-func (f *FileContentFlag) Decode(ctx *DecodeContext) error { // nolint: golint
+func (f *FileContentFlag) Decode(ctx *DecodeContext) error { //nolint: revive
var filename string
err := ctx.Scan.PopValueInto("filename", &filename)
if err != nil {
@@ -781,9 +914,9 @@ func (f *FileContentFlag) Decode(ctx *DecodeContext) error { // nolint: golint
return nil
}
filename = ExpandPath(filename)
- data, err := ioutil.ReadFile(filename) // nolint: gosec
+ data, err := os.ReadFile(filename) //nolint: gosec
if err != nil {
- return errors.Errorf("failed to open %q: %s", filename, err)
+ return fmt.Errorf("failed to open %q: %v", filename, err)
}
*f = data
return nil
@@ -792,7 +925,10 @@ func (f *FileContentFlag) Decode(ctx *DecodeContext) error { // nolint: golint
func jsonTranscode(in, out interface{}) error {
data, err := json.Marshal(in)
if err != nil {
- return errors.WithStack(err)
+ return err
}
- return errors.Wrapf(json.Unmarshal(data, out), "%#v -> %T", in, out)
+ if err = json.Unmarshal(data, out); err != nil {
+ return fmt.Errorf("%#v -> %T: %v", in, out, err)
+ }
+ return nil
}
diff --git a/vendor/github.com/alecthomas/kong/model.go b/vendor/github.com/alecthomas/kong/model.go
index c64cab3e..8d1f82f9 100644
--- a/vendor/github.com/alecthomas/kong/model.go
+++ b/vendor/github.com/alecthomas/kong/model.go
@@ -7,8 +7,6 @@ import (
"reflect"
"strconv"
"strings"
-
- "github.com/pkg/errors"
)
// A Visitable component in the model.
@@ -41,19 +39,22 @@ const (
// Node is a branch in the CLI. ie. a command or positional argument.
type Node struct {
- Type NodeType
- Parent *Node
- Name string
- Help string // Short help displayed in summaries.
- Detail string // Detailed help displayed when describing command/arg alone.
- Group *Group
- Hidden bool
- Flags []*Flag
- Positional []*Positional
- Children []*Node
- Target reflect.Value // Pointer to the value in the grammar that this Node is associated with.
- Tag *Tag
- Aliases []string
+ Type NodeType
+ Parent *Node
+ Name string
+ Help string // Short help displayed in summaries.
+ Detail string // Detailed help displayed when describing command/arg alone.
+ Group *Group
+ Hidden bool
+ Flags []*Flag
+ Positional []*Positional
+ Children []*Node
+ DefaultCmd *Node
+ Target reflect.Value // Pointer to the value in the grammar that this Node is associated with.
+ Tag *Tag
+ Aliases []string
+ Passthrough bool // Set to true to stop flag parsing when encountered.
+ Active bool // Denotes the node is part of an active branch in the CLI.
Argument *Value // Populated when Type is ArgumentNode.
}
@@ -98,6 +99,7 @@ func (n *Node) AllFlags(hide bool) (out [][]*Flag) {
group := []*Flag{}
for _, flag := range n.Flags {
if !hide || !flag.Hidden {
+ flag.Active = true
group = append(group, flag)
}
}
@@ -146,14 +148,30 @@ func (n *Node) Summary() string {
summary += " " + flags
}
args := []string{}
+ optional := 0
for _, arg := range n.Positional {
- args = append(args, arg.Summary())
+ argSummary := arg.Summary()
+ if arg.Tag.Optional {
+ optional++
+ argSummary = strings.TrimRight(argSummary, "]")
+ }
+ args = append(args, argSummary)
}
if len(args) != 0 {
- summary += " " + strings.Join(args, " ")
+ summary += " " + strings.Join(args, " ") + strings.Repeat("]", optional)
} else if len(n.Children) > 0 {
summary += " "
}
+ allFlags := n.Flags
+ if n.Parent != nil {
+ allFlags = append(allFlags, n.Parent.Flags...)
+ }
+ for _, flag := range allFlags {
+ if !flag.Required {
+ summary += " [flags]"
+ break
+ }
+ }
return summary
}
@@ -197,8 +215,12 @@ func (n *Node) Path() (out string) {
switch n.Type {
case CommandNode:
out += " " + n.Name
+ if len(n.Aliases) > 0 {
+ out += fmt.Sprintf(" (%s)", strings.Join(n.Aliases, ","))
+ }
case ArgumentNode:
out += " " + "<" + n.Name + ">"
+ default:
}
return strings.TrimSpace(out)
}
@@ -220,6 +242,8 @@ type Value struct {
Flag *Flag // Nil if positional argument.
Name string
Help string
+ OrigHelp string // Original help string, without interpolated variables.
+ HasDefault bool
Default string
DefaultValue reflect.Value
Enum string
@@ -230,6 +254,8 @@ type Value struct {
Set bool // Set to true when this value is set through some mechanism.
Format string // Formatting directive, if applicable.
Position int // Position (for positional arguments).
+ Passthrough bool // Set to true to stop flag parsing when encountered.
+ Active bool // Denotes the value is part of an active branch in the CLI.
}
// EnumMap returns a map of the enums in this value.
@@ -242,6 +268,16 @@ func (v *Value) EnumMap() map[string]bool {
return out
}
+// EnumSlice returns a slice of the enums in this value.
+func (v *Value) EnumSlice() []string {
+ parts := strings.Split(v.Enum, ",")
+ out := make([]string, len(parts))
+ for i, part := range parts {
+ out[i] = strings.TrimSpace(part)
+ }
+ return out
+}
+
// ShortSummary returns a human-readable summary of the value, not including any placeholders/defaults.
func (v *Value) ShortSummary() string {
if v.Flag != nil {
@@ -292,27 +328,28 @@ func (v *Value) IsMap() bool {
// IsBool returns true if the underlying value is a boolean.
func (v *Value) IsBool() bool {
+ if m, ok := v.Mapper.(BoolMapperExt); ok && m.IsBoolFromValue(v.Target) {
+ return true
+ }
if m, ok := v.Mapper.(BoolMapper); ok && m.IsBool() {
return true
}
return v.Target.Kind() == reflect.Bool
}
+// IsCounter returns true if the value is a counter.
+func (v *Value) IsCounter() bool {
+ return v.Tag.Type == "counter"
+}
+
// Parse tokens into value, parse, and validate, but do not write to the field.
func (v *Value) Parse(scan *Scanner, target reflect.Value) (err error) {
- defer func() {
- if rerr := recover(); rerr != nil {
- switch rerr := rerr.(type) {
- case Error:
- err = errors.Wrap(rerr, v.ShortSummary())
- default:
- panic(fmt.Sprintf("mapper %T failed to apply to %s: %s", v.Mapper, v.Summary(), rerr))
- }
- }
- }()
+ if target.Kind() == reflect.Ptr && target.IsNil() {
+ target.Set(reflect.New(target.Type().Elem()))
+ }
err = v.Mapper.Decode(&DecodeContext{Value: v, Scan: scan}, target)
if err != nil {
- return errors.Wrap(err, v.ShortSummary())
+ return fmt.Errorf("%s: %w", v.ShortSummary(), err)
}
v.Set = true
return nil
@@ -339,17 +376,20 @@ func (v *Value) ApplyDefault() error {
// Does not include resolvers.
func (v *Value) Reset() error {
v.Target.Set(reflect.Zero(v.Target.Type()))
- if v.Tag.Env != "" {
- envar := os.Getenv(v.Tag.Env)
- if envar != "" {
- err := v.Parse(ScanFromTokens(Token{Type: FlagValueToken, Value: envar}), v.Target)
- if err != nil {
- return fmt.Errorf("%s (from envar %s=%q)", err, v.Tag.Env, envar)
+ if len(v.Tag.Envs) != 0 {
+ for _, env := range v.Tag.Envs {
+ envar, ok := os.LookupEnv(env)
+ // Parse the first non-empty ENV in the list
+ if ok {
+ err := v.Parse(ScanFromTokens(Token{Type: FlagValueToken, Value: envar}), v.Target)
+ if err != nil {
+ return fmt.Errorf("%s (from envar %s=%q)", err, env, envar)
+ }
+ return nil
}
- return nil
}
}
- if v.Default != "" {
+ if v.HasDefault {
return v.Parse(ScanFromTokens(Token{Type: FlagValueToken, Value: v.Default}), v.Target)
}
return nil
@@ -364,11 +404,13 @@ type Positional = Value
type Flag struct {
*Value
Group *Group // Logical grouping when displaying. May also be used by configuration loaders to group options logically.
- Xor string
+ Xor []string
PlaceHolder string
- Env string
+ Envs []string
+ Aliases []string
Short rune
Hidden bool
+ Negated bool
}
func (f *Flag) String() string {
@@ -376,7 +418,7 @@ func (f *Flag) String() string {
if f.Short != 0 {
out = fmt.Sprintf("-%c, %s", f.Short, out)
}
- if !f.IsBool() {
+ if !f.IsBool() && !f.IsCounter() {
out += "=" + f.FormatPlaceHolder()
}
return out
@@ -384,25 +426,32 @@ func (f *Flag) String() string {
// FormatPlaceHolder formats the placeholder string for a Flag.
func (f *Flag) FormatPlaceHolder() string {
+ placeholderHelper, ok := f.Value.Mapper.(PlaceHolderProvider)
+ if ok {
+ return placeholderHelper.PlaceHolder(f)
+ }
tail := ""
if f.Value.IsSlice() && f.Value.Tag.Sep != -1 {
tail += string(f.Value.Tag.Sep) + "..."
}
- if f.Default != "" {
+ if f.PlaceHolder != "" {
+ return f.PlaceHolder + tail
+ }
+ if f.HasDefault {
if f.Value.Target.Kind() == reflect.String {
return strconv.Quote(f.Default) + tail
}
return f.Default + tail
}
- if f.PlaceHolder != "" {
- return f.PlaceHolder + tail
- }
if f.Value.IsMap() {
if f.Value.Tag.MapSep != -1 {
tail = string(f.Value.Tag.MapSep) + "..."
}
return "KEY=VALUE" + tail
}
+ if f.Tag != nil && f.Tag.TypeName != "" {
+ return strings.ToUpper(dashedString(f.Tag.TypeName)) + tail
+ }
return strings.ToUpper(f.Name) + tail
}
@@ -452,6 +501,9 @@ func reflectValueIsZero(v reflect.Value) bool {
default:
// This should never happens, but will act as a safeguard for
// later, as a default value doesn't makes sense here.
- panic(&reflect.ValueError{"reflect.Value.IsZero", v.Kind()})
+ panic(&reflect.ValueError{
+ Method: "reflect.Value.IsZero",
+ Kind: v.Kind(),
+ })
}
}
diff --git a/vendor/github.com/alecthomas/kong/options.go b/vendor/github.com/alecthomas/kong/options.go
index bdb57f63..d01aeec2 100644
--- a/vendor/github.com/alecthomas/kong/options.go
+++ b/vendor/github.com/alecthomas/kong/options.go
@@ -1,14 +1,15 @@
package kong
import (
+ "errors"
+ "fmt"
"io"
"os"
"os/user"
"path/filepath"
"reflect"
+ "regexp"
"strings"
-
- "github.com/pkg/errors"
)
// An Option applies optional changes to the Kong application.
@@ -19,7 +20,7 @@ type Option interface {
// OptionFunc is function that adheres to the Option interface.
type OptionFunc func(k *Kong) error
-func (o OptionFunc) Apply(k *Kong) error { return o(k) } // nolint: golint
+func (o OptionFunc) Apply(k *Kong) error { return o(k) } //nolint: revive
// Vars sets the variables to use for interpolation into help strings and default values.
//
@@ -54,6 +55,51 @@ func Exit(exit func(int)) Option {
})
}
+type embedded struct {
+ strct any
+ tags []string
+}
+
+// Embed a struct into the root of the CLI.
+//
+// "strct" must be a pointer to a structure.
+func Embed(strct any, tags ...string) Option {
+ t := reflect.TypeOf(strct)
+ if t.Kind() != reflect.Ptr || t.Elem().Kind() != reflect.Struct {
+ panic("kong: Embed() must be called with a pointer to a struct")
+ }
+ return OptionFunc(func(k *Kong) error {
+ k.embedded = append(k.embedded, embedded{strct, tags})
+ return nil
+ })
+}
+
+type dynamicCommand struct {
+ name string
+ help string
+ group string
+ tags []string
+ cmd interface{}
+}
+
+// DynamicCommand registers a dynamically constructed command with the root of the CLI.
+//
+// This is useful for command-line structures that are extensible via user-provided plugins.
+//
+// "tags" is a list of extra tag strings to parse, in the form :"".
+func DynamicCommand(name, help, group string, cmd interface{}, tags ...string) Option {
+ return OptionFunc(func(k *Kong) error {
+ k.dynamicCommands = append(k.dynamicCommands, &dynamicCommand{
+ name: name,
+ help: help,
+ group: group,
+ cmd: cmd,
+ tags: tags,
+ })
+ return nil
+ })
+}
+
// NoDefaultHelp disables the default help flags.
func NoDefaultHelp() Option {
return OptionFunc(func(k *Kong) error {
@@ -137,8 +183,8 @@ func Writers(stdout, stderr io.Writer) Option {
//
// There are two hook points:
//
-// BeforeApply(...) error
-// AfterApply(...) error
+// BeforeApply(...) error
+// AfterApply(...) error
//
// Called before validation/assignment, and immediately after validation/assignment, respectively.
func Bind(args ...interface{}) Option {
@@ -150,11 +196,10 @@ func Bind(args ...interface{}) Option {
// BindTo allows binding of implementations to interfaces.
//
-// BindTo(impl, (*iface)(nil))
+// BindTo(impl, (*iface)(nil))
func BindTo(impl, iface interface{}) Option {
return OptionFunc(func(k *Kong) error {
- valueOf := reflect.ValueOf(impl)
- k.bindings[reflect.TypeOf(iface).Elem()] = func() (reflect.Value, error) { return valueOf, nil }
+ k.bindings.addTo(impl, iface)
return nil
})
}
@@ -165,22 +210,7 @@ func BindTo(impl, iface interface{}) Option {
// not all be initialisable from the main() function.
func BindToProvider(provider interface{}) Option {
return OptionFunc(func(k *Kong) error {
- pv := reflect.ValueOf(provider)
- t := pv.Type()
- if t.Kind() != reflect.Func || t.NumIn() != 0 || t.NumOut() != 2 || t.Out(1) != reflect.TypeOf((*error)(nil)).Elem() {
- return errors.Errorf("%T must be a function with the signature func()(T, error)", provider)
- }
- rt := pv.Type().Out(0)
- k.bindings[rt] = func() (reflect.Value, error) {
- out := pv.Call(nil)
- errv := out[1]
- var err error
- if !errv.IsNil() {
- err = errv.Interface().(error)
- }
- return out[0], err
- }
- return nil
+ return k.bindings.addProvider(provider)
})
}
@@ -192,7 +222,20 @@ func Help(help HelpPrinter) Option {
})
}
+// ShortHelp configures the short usage message.
+//
+// It should be used together with kong.ShortUsageOnError() to display a
+// custom short usage message on errors.
+func ShortHelp(shortHelp HelpPrinter) Option {
+ return OptionFunc(func(k *Kong) error {
+ k.shortHelp = shortHelp
+ return nil
+ })
+}
+
// HelpFormatter configures how the help text is formatted.
+//
+// Deprecated: Use ValueFormatter() instead.
func HelpFormatter(helpFormatter HelpValueFormatter) Option {
return OptionFunc(func(k *Kong) error {
k.helpFormatter = helpFormatter
@@ -200,6 +243,14 @@ func HelpFormatter(helpFormatter HelpValueFormatter) Option {
})
}
+// ValueFormatter configures how the help text is formatted.
+func ValueFormatter(helpFormatter HelpValueFormatter) Option {
+ return OptionFunc(func(k *Kong) error {
+ k.helpFormatter = helpFormatter
+ return nil
+ })
+}
+
// ConfigureHelp sets the HelpOptions to use for printing help.
func ConfigureHelp(options HelpOptions) Option {
return OptionFunc(func(k *Kong) error {
@@ -208,6 +259,21 @@ func ConfigureHelp(options HelpOptions) Option {
})
}
+// AutoGroup automatically assigns groups to flags.
+func AutoGroup(format func(parent Visitable, flag *Flag) *Group) Option {
+ return PostBuild(func(kong *Kong) error {
+ parents := []Visitable{kong.Model}
+ return Visit(kong.Model, func(node Visitable, next Next) error {
+ if flag, ok := node.(*Flag); ok && flag.Group == nil {
+ flag.Group = format(parents[len(parents)-1], flag)
+ }
+ parents = append(parents, node)
+ defer func() { parents = parents[:len(parents)-1] }()
+ return next(nil)
+ })
+ })
+}
+
// Groups associates `group` field tags with group metadata.
//
// This option is used to simplify Kong tags while providing
@@ -221,7 +287,7 @@ func ConfigureHelp(options HelpOptions) Option {
// See also ExplicitGroups for a more structured alternative.
type Groups map[string]string
-func (g Groups) Apply(k *Kong) error { // nolint: golint
+func (g Groups) Apply(k *Kong) error { //nolint: revive
for key, info := range g {
lines := strings.Split(info, "\n")
title := strings.TrimSpace(lines[0])
@@ -251,7 +317,17 @@ func ExplicitGroups(groups []Group) Option {
// UsageOnError configures Kong to display context-sensitive usage if FatalIfErrorf is called with an error.
func UsageOnError() Option {
return OptionFunc(func(k *Kong) error {
- k.usageOnError = true
+ k.usageOnError = fullUsage
+ return nil
+ })
+}
+
+// ShortUsageOnError configures Kong to display context-sensitive short
+// usage if FatalIfErrorf is called with an error. The default short
+// usage message can be overridden with kong.ShortHelp(...).
+func ShortUsageOnError() Option {
+ return OptionFunc(func(k *Kong) error {
+ k.usageOnError = shortUsage
return nil
})
}
@@ -272,6 +348,31 @@ func Resolvers(resolvers ...Resolver) Option {
})
}
+// IgnoreFields will cause kong.New() to skip field names that match any
+// of the provided regex patterns. This is useful if you are not able to add a
+// kong="-" struct tag to a struct/element before the call to New.
+//
+// Example: When referencing protoc generated structs, you will likely want to
+// ignore/skip XXX_* fields.
+func IgnoreFields(regexes ...string) Option {
+ return OptionFunc(func(k *Kong) error {
+ for _, r := range regexes {
+ if r == "" {
+ return errors.New("regex input cannot be empty")
+ }
+
+ re, err := regexp.Compile(r)
+ if err != nil {
+ return fmt.Errorf("unable to compile regex: %v", err)
+ }
+
+ k.ignoreFields = append(k.ignoreFields, re)
+ }
+
+ return nil
+ })
+}
+
// ConfigurationLoader is a function that builds a resolver from a file.
type ConfigurationLoader func(r io.Reader) (Resolver, error)
@@ -286,12 +387,19 @@ func Configuration(loader ConfigurationLoader, paths ...string) Option {
return OptionFunc(func(k *Kong) error {
k.loader = loader
for _, path := range paths {
- if _, err := os.Stat(ExpandPath(path)); os.IsNotExist(err) {
- continue
+ f, err := os.Open(ExpandPath(path))
+ if err != nil {
+ if os.IsNotExist(err) || os.IsPermission(err) {
+ continue
+ }
+
+ return err
}
+ f.Close()
+
resolver, err := k.LoadConfig(path)
if err != nil {
- return errors.Wrap(err, path)
+ return fmt.Errorf("%s: %v", path, err)
}
if resolver != nil {
k.resolvers = append(k.resolvers, resolver)
@@ -321,3 +429,65 @@ func ExpandPath(path string) string {
}
return abspath
}
+
+func siftStrings(ss []string, filter func(s string) bool) []string {
+ i := 0
+ ss = append([]string(nil), ss...)
+ for _, s := range ss {
+ if filter(s) {
+ ss[i] = s
+ i++
+ }
+ }
+ return ss[0:i]
+}
+
+// DefaultEnvars option inits environment names for flags.
+// The name will not generate if tag "env" is "-".
+// Predefined environment variables are skipped.
+//
+// For example:
+//
+// --some.value -> PREFIX_SOME_VALUE
+func DefaultEnvars(prefix string) Option {
+ processFlag := func(flag *Flag) {
+ switch env := flag.Envs; {
+ case flag.Name == "help":
+ return
+ case len(env) == 1 && env[0] == "-":
+ flag.Envs = nil
+ return
+ case len(env) > 0:
+ return
+ }
+ replacer := strings.NewReplacer("-", "_", ".", "_")
+ names := append([]string{prefix}, camelCase(replacer.Replace(flag.Name))...)
+ names = siftStrings(names, func(s string) bool { return !(s == "_" || strings.TrimSpace(s) == "") })
+ name := strings.ToUpper(strings.Join(names, "_"))
+ flag.Envs = append(flag.Envs, name)
+ flag.Value.Tag.Envs = append(flag.Value.Tag.Envs, name)
+ }
+
+ var processNode func(node *Node)
+ processNode = func(node *Node) {
+ for _, flag := range node.Flags {
+ processFlag(flag)
+ }
+ for _, node := range node.Children {
+ processNode(node)
+ }
+ }
+
+ return PostBuild(func(k *Kong) error {
+ processNode(k.Model.Node)
+ return nil
+ })
+}
+
+// FlagNamer allows you to override the default kebab-case automated flag name generation.
+func FlagNamer(namer func(fieldName string) string) Option {
+ return OptionFunc(func(k *Kong) error {
+ k.flagNamer = namer
+ return nil
+ })
+}
diff --git a/vendor/github.com/alecthomas/kong/renovate.json5 b/vendor/github.com/alecthomas/kong/renovate.json5
new file mode 100644
index 00000000..561d59fd
--- /dev/null
+++ b/vendor/github.com/alecthomas/kong/renovate.json5
@@ -0,0 +1,18 @@
+{
+ $schema: "https://docs.renovatebot.com/renovate-schema.json",
+ extends: [
+ "config:recommended",
+ ":semanticCommits",
+ ":semanticCommitTypeAll(chore)",
+ ":semanticCommitScope(deps)",
+ "group:allNonMajor",
+ "schedule:earlyMondays", // Run once a week.
+ ],
+ packageRules: [
+ {
+ "matchPackageNames": ["golangci-lint"],
+ "matchManagers": ["hermit"],
+ "enabled": false
+ },
+ ]
+}
diff --git a/vendor/github.com/alecthomas/kong/resolver.go b/vendor/github.com/alecthomas/kong/resolver.go
index 8ef764dd..05be7f68 100644
--- a/vendor/github.com/alecthomas/kong/resolver.go
+++ b/vendor/github.com/alecthomas/kong/resolver.go
@@ -22,14 +22,14 @@ type ResolverFunc func(context *Context, parent *Path, flag *Flag) (interface{},
var _ Resolver = ResolverFunc(nil)
-func (r ResolverFunc) Resolve(context *Context, parent *Path, flag *Flag) (interface{}, error) { // nolint: golint
+func (r ResolverFunc) Resolve(context *Context, parent *Path, flag *Flag) (interface{}, error) { //nolint: revive
return r(context, parent, flag)
}
-func (r ResolverFunc) Validate(app *Application) error { return nil } // nolint: golint
+func (r ResolverFunc) Validate(app *Application) error { return nil } //nolint: revive
// JSON returns a Resolver that retrieves values from a JSON source.
//
-// Hyphens in flag names are replaced with underscores.
+// Flag names are used as JSON keys indirectly, by tring snake_case and camelCase variants.
func JSON(r io.Reader) (Resolver, error) {
values := map[string]interface{}{}
err := json.NewDecoder(r).Decode(&values)
@@ -37,13 +37,32 @@ func JSON(r io.Reader) (Resolver, error) {
return nil, err
}
var f ResolverFunc = func(context *Context, parent *Path, flag *Flag) (interface{}, error) {
- name := strings.Replace(flag.Name, "-", "_", -1)
+ name := strings.ReplaceAll(flag.Name, "-", "_")
+ snakeCaseName := snakeCase(flag.Name)
raw, ok := values[name]
- if !ok {
- return nil, nil
+ if ok {
+ return raw, nil
+ } else if raw, ok = values[snakeCaseName]; ok {
+ return raw, nil
+ }
+ raw = values
+ for _, part := range strings.Split(name, ".") {
+ if values, ok := raw.(map[string]interface{}); ok {
+ raw, ok = values[part]
+ if !ok {
+ return nil, nil
+ }
+ } else {
+ return nil, nil
+ }
}
return raw, nil
}
return f, nil
}
+
+func snakeCase(name string) string {
+ name = strings.Join(strings.Split(strings.Title(name), "-"), "") //nolint: staticcheck
+ return strings.ToLower(name[:1]) + name[1:]
+}
diff --git a/vendor/github.com/alecthomas/kong/scanner.go b/vendor/github.com/alecthomas/kong/scanner.go
index 11b78c38..c8a8bd60 100644
--- a/vendor/github.com/alecthomas/kong/scanner.go
+++ b/vendor/github.com/alecthomas/kong/scanner.go
@@ -82,7 +82,7 @@ func (t Token) InferredType() TokenType {
return t.Type
}
if v, ok := t.Value.(string); ok {
- if strings.HasPrefix(v, "--") { // nolint: gocritic
+ if strings.HasPrefix(v, "--") { //nolint: gocritic
return FlagToken
} else if v == "-" {
return PositionalArgumentToken
@@ -109,20 +109,25 @@ func (t Token) IsValue() bool {
//
// For example, the token "--foo=bar" will be split into the following by the parser:
//
-// [{FlagToken, "foo"}, {FlagValueToken, "bar"}]
+// [{FlagToken, "foo"}, {FlagValueToken, "bar"}]
type Scanner struct {
args []Token
}
-// Scan creates a new Scanner from args with untyped tokens.
-func Scan(args ...string) *Scanner {
+// ScanAsType creates a new Scanner from args with the given type.
+func ScanAsType(ttype TokenType, args ...string) *Scanner {
s := &Scanner{}
for _, arg := range args {
- s.args = append(s.args, Token{Value: arg})
+ s.args = append(s.args, Token{Value: arg, Type: ttype})
}
return s
}
+// Scan creates a new Scanner from args with untyped tokens.
+func Scan(args ...string) *Scanner {
+ return ScanAsType(UntypedToken, args...)
+}
+
// ScanFromTokens creates a new Scanner from a slice of tokens.
func ScanFromTokens(tokens ...Token) *Scanner {
return &Scanner{args: tokens}
diff --git a/vendor/github.com/alecthomas/kong/tag.go b/vendor/github.com/alecthomas/kong/tag.go
index b780000c..3e37c194 100644
--- a/vendor/github.com/alecthomas/kong/tag.go
+++ b/vendor/github.com/alecthomas/kong/tag.go
@@ -1,6 +1,7 @@
package kong
import (
+ "errors"
"fmt"
"reflect"
"strconv"
@@ -18,45 +19,76 @@ type Tag struct {
Name string
Help string
Type string
+ TypeName string
+ HasDefault bool
Default string
Format string
PlaceHolder string
- Env string
+ Envs []string
Short rune
Hidden bool
Sep rune
MapSep rune
Enum string
Group string
- Xor string
+ Xor []string
Vars Vars
Prefix string // Optional prefix on anonymous structs. All sub-flags will have this prefix.
+ EnvPrefix string
Embed bool
Aliases []string
+ Negatable bool
+ Passthrough bool
// Storage for all tag keys for arbitrary lookups.
items map[string][]string
}
+func (t *Tag) String() string {
+ out := []string{}
+ for key, list := range t.items {
+ for _, value := range list {
+ out = append(out, fmt.Sprintf("%s:%q", key, value))
+ }
+ }
+ return strings.Join(out, " ")
+}
+
type tagChars struct {
sep, quote, assign rune
+ needsUnquote bool
}
-var kongChars = tagChars{sep: ',', quote: '\'', assign: '='}
-var bareChars = tagChars{sep: ' ', quote: '"', assign: ':'}
+var kongChars = tagChars{sep: ',', quote: '\'', assign: '=', needsUnquote: false}
+var bareChars = tagChars{sep: ' ', quote: '"', assign: ':', needsUnquote: true}
-func parseTagItems(tagString string, chr tagChars) map[string][]string {
+//nolint:gocyclo
+func parseTagItems(tagString string, chr tagChars) (map[string][]string, error) {
d := map[string][]string{}
key := []rune{}
value := []rune{}
quotes := false
inKey := true
- add := func() {
- d[string(key)] = append(d[string(key)], string(value))
+ add := func() error {
+ // Bare tags are quoted, therefore we need to unquote them in the same fashion reflect.Lookup() (implicitly)
+ // unquotes "kong tags".
+ s := string(value)
+
+ if chr.needsUnquote && s != "" {
+ if unquoted, err := strconv.Unquote(fmt.Sprintf(`"%s"`, s)); err == nil {
+ s = unquoted
+ } else {
+ return fmt.Errorf("unquoting tag value `%s`: %w", s, err)
+ }
+ }
+
+ d[string(key)] = append(d[string(key)], s)
key = []rune{}
value = []rune{}
inKey = true
+
+ return nil
}
runes := []rune(tagString)
@@ -70,7 +102,10 @@ func parseTagItems(tagString string, chr tagChars) map[string][]string {
eof = true
}
if !quotes && r == chr.sep {
- add()
+ if err := add(); err != nil {
+ return nil, err
+ }
+
continue
}
if r == chr.assign && inKey {
@@ -80,6 +115,12 @@ func parseTagItems(tagString string, chr tagChars) map[string][]string {
if r == '\\' {
if next == chr.quote {
idx++
+
+ // We need to keep the backslashes, otherwise subsequent unquoting cannot work
+ if chr.needsUnquote {
+ value = append(value, r)
+ }
+
r = chr.quote
}
} else if r == chr.quote {
@@ -88,11 +129,10 @@ func parseTagItems(tagString string, chr tagChars) map[string][]string {
if next == chr.sep || eof {
continue
}
- fail("%v has an unexpected char at pos %v", tagString, idx)
- } else {
- quotes = true
- continue
+ return nil, fmt.Errorf("%v has an unexpected char at pos %v", tagString, idx)
}
+ quotes = true
+ continue
}
if inKey {
key = append(key, r)
@@ -101,12 +141,14 @@ func parseTagItems(tagString string, chr tagChars) map[string][]string {
}
}
if quotes {
- fail("%v is not quoted properly", tagString)
+ return nil, fmt.Errorf("%v is not quoted properly", tagString)
}
- add()
+ if err := add(); err != nil {
+ return nil, err
+ }
- return d
+ return d, nil
}
func getTagInfo(ft reflect.StructField) (string, tagChars) {
@@ -122,63 +164,123 @@ func newEmptyTag() *Tag {
return &Tag{items: map[string][]string{}}
}
-func parseTag(fv reflect.Value, ft reflect.StructField) *Tag {
+func tagSplitFn(r rune) bool {
+ return r == ',' || r == ' '
+}
+
+func parseTagString(s string) (*Tag, error) {
+ items, err := parseTagItems(s, bareChars)
+ if err != nil {
+ return nil, err
+ }
+ t := &Tag{
+ items: items,
+ }
+ err = hydrateTag(t, nil)
+ if err != nil {
+ return nil, fmt.Errorf("%s: %s", s, err)
+ }
+ return t, nil
+}
+
+func parseTag(parent reflect.Value, ft reflect.StructField) (*Tag, error) {
if ft.Tag.Get("kong") == "-" {
t := newEmptyTag()
t.Ignored = true
- return t
+ return t, nil
+ }
+ items, err := parseTagItems(getTagInfo(ft))
+ if err != nil {
+ return nil, err
}
t := &Tag{
- items: parseTagItems(getTagInfo(ft)),
+ items: items,
}
+ err = hydrateTag(t, ft.Type)
+ if err != nil {
+ return nil, failField(parent, ft, "%s", err)
+ }
+ return t, nil
+}
+
+func hydrateTag(t *Tag, typ reflect.Type) error { //nolint: gocyclo
+ var typeName string
+ var isBool bool
+ var isBoolPtr bool
+ if typ != nil {
+ typeName = typ.Name()
+ isBool = typ.Kind() == reflect.Bool
+ isBoolPtr = typ.Kind() == reflect.Ptr && typ.Elem().Kind() == reflect.Bool
+ }
+ var err error
t.Cmd = t.Has("cmd")
t.Arg = t.Has("arg")
required := t.Has("required")
optional := t.Has("optional")
if required && optional {
- fail("can't specify both required and optional")
+ return fmt.Errorf("can't specify both required and optional")
}
t.Required = required
t.Optional = optional
+ t.HasDefault = t.Has("default")
t.Default = t.Get("default")
// Arguments with defaults are always optional.
- if t.Arg && t.Default != "" {
+ if t.Arg && t.HasDefault {
t.Optional = true
+ } else if t.Arg && !optional { // Arguments are required unless explicitly made optional.
+ t.Required = true
}
t.Name = t.Get("name")
t.Help = t.Get("help")
t.Type = t.Get("type")
- t.Env = t.Get("env")
- t.Short, _ = t.GetRune("short")
+ t.TypeName = typeName
+ for _, env := range t.GetAll("env") {
+ t.Envs = append(t.Envs, strings.FieldsFunc(env, tagSplitFn)...)
+ }
+ t.Short, err = t.GetRune("short")
+ if err != nil && t.Get("short") != "" {
+ return fmt.Errorf("invalid short flag name %q: %s", t.Get("short"), err)
+ }
t.Hidden = t.Has("hidden")
t.Format = t.Get("format")
t.Sep, _ = t.GetSep("sep", ',')
t.MapSep, _ = t.GetSep("mapsep", ';')
t.Group = t.Get("group")
- t.Xor = t.Get("xor")
+ for _, xor := range t.GetAll("xor") {
+ t.Xor = append(t.Xor, strings.FieldsFunc(xor, tagSplitFn)...)
+ }
t.Prefix = t.Get("prefix")
+ t.EnvPrefix = t.Get("envprefix")
t.Embed = t.Has("embed")
- splitFn := func(r rune) bool {
- return r == ',' || r == ' '
+ negatable := t.Has("negatable")
+ if negatable && !isBool && !isBoolPtr {
+ return fmt.Errorf("negatable can only be set on booleans")
}
+ t.Negatable = negatable
aliases := t.Get("aliases")
if len(aliases) > 0 {
- t.Aliases = append(t.Aliases, strings.FieldsFunc(aliases, splitFn)...)
+ t.Aliases = append(t.Aliases, strings.FieldsFunc(aliases, tagSplitFn)...)
}
t.Vars = Vars{}
for _, set := range t.GetAll("set") {
parts := strings.SplitN(set, "=", 2)
if len(parts) == 0 {
- fail("set should be in the form key=value but got %q", set)
+ return fmt.Errorf("set should be in the form key=value but got %q", set)
}
t.Vars[parts[0]] = parts[1]
}
t.PlaceHolder = t.Get("placeholder")
- if t.PlaceHolder == "" {
- t.PlaceHolder = strings.ToUpper(dashedString(fv.Type().Name()))
- }
t.Enum = t.Get("enum")
- return t
+ scalarType := typ == nil || !(typ.Kind() == reflect.Slice || typ.Kind() == reflect.Map || typ.Kind() == reflect.Ptr)
+ if t.Enum != "" && !(t.Required || t.HasDefault) && scalarType {
+ return fmt.Errorf("enum value is only valid if it is either required or has a valid default value")
+ }
+ passthrough := t.Has("passthrough")
+ if passthrough && !t.Arg && !t.Cmd {
+ return fmt.Errorf("passthrough only makes sense for positional arguments or commands")
+ }
+ t.Passthrough = passthrough
+ return nil
}
// Has returns true if the tag contained the given key.
@@ -220,9 +322,10 @@ func (t *Tag) GetInt(k string) (int64, error) {
// GetRune parses the given tag as a rune.
func (t *Tag) GetRune(k string) (rune, error) {
- r, _ := utf8.DecodeRuneInString(t.Get(k))
- if r == utf8.RuneError {
- return 0, fmt.Errorf("%v has a rune error", t.Get(k))
+ value := t.Get(k)
+ r, size := utf8.DecodeRuneInString(value)
+ if r == utf8.RuneError || size < len(value) {
+ return 0, errors.New("invalid rune")
}
return r, nil
}
diff --git a/vendor/github.com/alecthomas/kong/util.go b/vendor/github.com/alecthomas/kong/util.go
index 154aebcc..8b706642 100644
--- a/vendor/github.com/alecthomas/kong/util.go
+++ b/vendor/github.com/alecthomas/kong/util.go
@@ -2,6 +2,8 @@ package kong
import (
"fmt"
+ "os"
+ "reflect"
)
// ConfigFlag uses the configured (via kong.Configuration(loader)) configuration loader to load configuration
@@ -15,7 +17,7 @@ func (c ConfigFlag) BeforeResolve(kong *Kong, ctx *Context, trace *Path) error {
if kong.loader == nil {
return fmt.Errorf("kong must be configured with kong.Configuration(...)")
}
- path := string(ctx.FlagValue(trace.Flag).(ConfigFlag))
+ path := string(ctx.FlagValue(trace.Flag).(ConfigFlag)) //nolint
resolver, err := kong.LoadConfig(path)
if err != nil {
return err
@@ -27,9 +29,29 @@ func (c ConfigFlag) BeforeResolve(kong *Kong, ctx *Context, trace *Path) error {
// VersionFlag is a flag type that can be used to display a version number, stored in the "version" variable.
type VersionFlag bool
-// BeforeApply writes the version variable and terminates with a 0 exit status.
-func (v VersionFlag) BeforeApply(app *Kong, vars Vars) error {
+// BeforeReset writes the version variable and terminates with a 0 exit status.
+func (v VersionFlag) BeforeReset(app *Kong, vars Vars) error {
fmt.Fprintln(app.Stdout, vars["version"])
app.Exit(0)
return nil
}
+
+// ChangeDirFlag changes the current working directory to a path specified by a flag
+// early in the parsing process, changing how other flags resolve relative paths.
+//
+// Use this flag to provide a "git -C" like functionality.
+//
+// It is not compatible with custom named decoders, e.g., existingdir.
+type ChangeDirFlag string
+
+// Decode is used to create a side effect of changing the current working directory.
+func (c ChangeDirFlag) Decode(ctx *DecodeContext) error {
+ var path string
+ err := ctx.Scan.PopValueInto("string", &path)
+ if err != nil {
+ return err
+ }
+ path = ExpandPath(path)
+ ctx.Value.Target.Set(reflect.ValueOf(ChangeDirFlag(path)))
+ return os.Chdir(path)
+}
diff --git a/vendor/github.com/alecthomas/repr/renovate.json5 b/vendor/github.com/alecthomas/repr/renovate.json5
new file mode 100644
index 00000000..897864b8
--- /dev/null
+++ b/vendor/github.com/alecthomas/repr/renovate.json5
@@ -0,0 +1,11 @@
+{
+ $schema: "https://docs.renovatebot.com/renovate-schema.json",
+ extends: [
+ "config:recommended",
+ ":semanticCommits",
+ ":semanticCommitTypeAll(chore)",
+ ":semanticCommitScope(deps)",
+ "group:allNonMajor",
+ "schedule:earlyMondays", // Run once a week.
+ ],
+}
diff --git a/vendor/github.com/alecthomas/repr/repr.go b/vendor/github.com/alecthomas/repr/repr.go
index 6bc21511..c03eb067 100644
--- a/vendor/github.com/alecthomas/repr/repr.go
+++ b/vendor/github.com/alecthomas/repr/repr.go
@@ -12,6 +12,7 @@ import (
"os"
"reflect"
"sort"
+ "strings"
"time"
"unsafe"
)
@@ -44,6 +45,7 @@ var (
}
goStringerType = reflect.TypeOf((*fmt.GoStringer)(nil)).Elem()
+ anyType = reflect.TypeOf((*any)(nil)).Elem()
byteSliceType = reflect.TypeOf([]byte{})
)
@@ -69,13 +71,20 @@ func ExplicitTypes(ok bool) Option { return func(o *Printer) { o.explicitTypes =
// IgnoreGoStringer disables use of the .GoString() method.
func IgnoreGoStringer() Option { return func(o *Printer) { o.ignoreGoStringer = true } }
-// Hide excludes the given types from representation, instead just printing the name of the type.
-func Hide(ts ...interface{}) Option {
+// IgnorePrivate disables private field members from output.
+func IgnorePrivate() Option { return func(o *Printer) { o.ignorePrivate = true } }
+
+// ScalarLiterals forces the use of literals for scalars, rather than a string representation if available.
+//
+// For example, `time.Hour` will be printed as `time.Duration(3600000000000)` rather than `time.Duration(1h0m0s)`.
+func ScalarLiterals() Option { return func(o *Printer) { o.useLiterals = true } }
+
+// Hide excludes fields of the given type from representation.
+func Hide[T any]() Option {
return func(o *Printer) {
- for _, t := range ts {
- rt := reflect.Indirect(reflect.ValueOf(t)).Type()
- o.exclude[rt] = true
- }
+ t := (*T)(nil) // A bit of skulduggery so we can Hide() interfaces.
+ rt := reflect.TypeOf(t).Elem()
+ o.exclude[rt] = true
}
}
@@ -87,10 +96,12 @@ type Printer struct {
indent string
omitEmpty bool
ignoreGoStringer bool
+ ignorePrivate bool
alwaysIncludeType bool
explicitTypes bool
exclude map[reflect.Type]bool
w io.Writer
+ useLiterals bool
}
// New creates a new Printer on w with the given Options.
@@ -122,27 +133,28 @@ func (p *Printer) thisIndent(indent string) string {
}
// Print the values.
-func (p *Printer) Print(vs ...interface{}) {
+func (p *Printer) Print(vs ...any) {
for i, v := range vs {
if i > 0 {
fmt.Fprint(p.w, " ")
}
- p.reprValue(map[reflect.Value]bool{}, reflect.ValueOf(v), "", true)
+ p.reprValue(map[reflect.Value]bool{}, reflect.ValueOf(v), "", true, false)
}
}
// Println prints each value on a new line.
-func (p *Printer) Println(vs ...interface{}) {
+func (p *Printer) Println(vs ...any) {
for i, v := range vs {
if i > 0 {
fmt.Fprint(p.w, " ")
}
- p.reprValue(map[reflect.Value]bool{}, reflect.ValueOf(v), "", true)
+ p.reprValue(map[reflect.Value]bool{}, reflect.ValueOf(v), "", true, false)
}
fmt.Fprintln(p.w)
}
-func (p *Printer) reprValue(seen map[reflect.Value]bool, v reflect.Value, indent string, showType bool) { // nolint: gocyclo
+// showType is true if struct types should be shown. isAnyValue is true if the containing value is an "any" type.
+func (p *Printer) reprValue(seen map[reflect.Value]bool, v reflect.Value, indent string, showStructType bool, isAnyValue bool) { // nolint: gocyclo
if seen[v] {
fmt.Fprint(p.w, "...")
return
@@ -154,10 +166,6 @@ func (p *Printer) reprValue(seen map[reflect.Value]bool, v reflect.Value, indent
fmt.Fprint(p.w, "nil")
return
}
- if p.exclude[v.Type()] {
- fmt.Fprintf(p.w, "%s...", v.Type().Name())
- return
- }
t := v.Type()
if t == byteSliceType {
@@ -173,7 +181,7 @@ func (p *Printer) reprValue(seen map[reflect.Value]bool, v reflect.Value, indent
}
}
// Attempt to use fmt.GoStringer interface.
- if !p.ignoreGoStringer && t.Implements(goStringerType) {
+ if !p.ignoreGoStringer && t.Implements(goStringerType) && v.CanInterface() {
fmt.Fprint(p.w, v.Interface().(fmt.GoStringer).GoString())
return
}
@@ -181,10 +189,7 @@ func (p *Printer) reprValue(seen map[reflect.Value]bool, v reflect.Value, indent
ni := p.nextIndent(indent)
switch v.Kind() {
case reflect.Slice, reflect.Array:
- if p.omitEmpty && v.Len() == 0 {
- return
- }
- fmt.Fprintf(p.w, "%s{", v.Type())
+ fmt.Fprintf(p.w, "%s{", substAny(v.Type()))
if v.Len() == 0 {
fmt.Fprint(p.w, "}")
} else {
@@ -194,7 +199,7 @@ func (p *Printer) reprValue(seen map[reflect.Value]bool, v reflect.Value, indent
for i := 0; i < v.Len(); i++ {
e := v.Index(i)
fmt.Fprintf(p.w, "%s", ni)
- p.reprValue(seen, e, ni, p.alwaysIncludeType || p.explicitTypes)
+ p.reprValue(seen, e, ni, p.alwaysIncludeType || p.explicitTypes, v.Type().Elem() == anyType)
if p.indent != "" {
fmt.Fprintf(p.w, ",\n")
} else if i < v.Len()-1 {
@@ -206,11 +211,11 @@ func (p *Printer) reprValue(seen map[reflect.Value]bool, v reflect.Value, indent
case reflect.Chan:
fmt.Fprintf(p.w, "make(")
- fmt.Fprintf(p.w, "%s", v.Type())
+ fmt.Fprintf(p.w, "%s", substAny(v.Type()))
fmt.Fprintf(p.w, ", %d)", v.Cap())
case reflect.Map:
- fmt.Fprintf(p.w, "%s{", v.Type())
+ fmt.Fprintf(p.w, "%s{", substAny(v.Type()))
if p.indent != "" && v.Len() != 0 {
fmt.Fprintf(p.w, "\n")
}
@@ -221,9 +226,9 @@ func (p *Printer) reprValue(seen map[reflect.Value]bool, v reflect.Value, indent
for i, k := range keys {
kv := v.MapIndex(k)
fmt.Fprintf(p.w, "%s", ni)
- p.reprValue(seen, k, ni, p.alwaysIncludeType || p.explicitTypes)
+ p.reprValue(seen, k, ni, p.alwaysIncludeType || p.explicitTypes, v.Type().Key() == anyType)
fmt.Fprintf(p.w, ": ")
- p.reprValue(seen, kv, ni, true)
+ p.reprValue(seen, kv, ni, true, v.Type().Elem() == anyType)
if p.indent != "" {
fmt.Fprintf(p.w, ",\n")
} else if i < v.Len()-1 {
@@ -233,29 +238,67 @@ func (p *Printer) reprValue(seen map[reflect.Value]bool, v reflect.Value, indent
fmt.Fprintf(p.w, "%s}", in)
case reflect.Struct:
- if td, ok := v.Interface().(time.Time); ok {
+ if td, ok := asTime(v); ok {
timeToGo(p.w, td)
} else {
- if showType {
- fmt.Fprintf(p.w, "%s{", v.Type())
+ if showStructType {
+ fmt.Fprintf(p.w, "%s{", substAny(v.Type()))
} else {
fmt.Fprint(p.w, "{")
}
if p.indent != "" && v.NumField() != 0 {
fmt.Fprintf(p.w, "\n")
}
+ previous := false
for i := 0; i < v.NumField(); i++ {
t := v.Type().Field(i)
+ if p.exclude[t.Type] {
+ continue
+ }
f := v.Field(i)
- if p.omitEmpty && isZero(f) {
+ ft := f.Type()
+ // skip private fields
+ if p.ignorePrivate && !f.CanInterface() {
continue
}
+ if p.omitEmpty && (f.IsZero() ||
+ ft.Kind() == reflect.Slice && f.Len() == 0 ||
+ ft.Kind() == reflect.Map && f.Len() == 0) {
+ continue
+ }
+ if previous && p.indent == "" {
+ fmt.Fprintf(p.w, ", ")
+ }
+ previous = true
fmt.Fprintf(p.w, "%s%s: ", ni, t.Name)
- p.reprValue(seen, f, ni, true)
+ p.reprValue(seen, f, ni, true, t.Type == anyType)
+
+ // if private fields should be ignored, look up if a public
+ // field need to be displayed and breaks at the first public
+ // field found preventing from looping over all remaining
+ // fields.
+ //
+ // If no other field need to be displayed, continue and do
+ // not print a comma.
+ //
+ // This prevents from having a trailing comma if a private
+ // field ends a structure.
+ if p.ignorePrivate {
+ nc := false
+ for j := i + 1; j < v.NumField(); j++ {
+ if v.Field(j).CanInterface() {
+ nc = true
+ // exit for j loop
+ break
+ }
+ }
+ // Skip comma display if no remaining public field found.
+ if !nc {
+ continue
+ }
+ }
if p.indent != "" {
fmt.Fprintf(p.w, ",\n")
- } else if i < v.NumField()-1 {
- fmt.Fprintf(p.w, ", ")
}
}
fmt.Fprintf(p.w, "%s}", indent)
@@ -265,10 +308,10 @@ func (p *Printer) reprValue(seen map[reflect.Value]bool, v reflect.Value, indent
fmt.Fprintf(p.w, "nil")
return
}
- if showType {
+ if showStructType {
fmt.Fprintf(p.w, "&")
}
- p.reprValue(seen, v.Elem(), indent, showType)
+ p.reprValue(seen, v.Elem(), indent, showStructType, false)
case reflect.String:
if t.Name() != "string" || p.alwaysIncludeType {
@@ -279,22 +322,37 @@ func (p *Printer) reprValue(seen map[reflect.Value]bool, v reflect.Value, indent
case reflect.Interface:
if v.IsNil() {
- fmt.Fprintf(p.w, "interface {}(nil)")
+ fmt.Fprintf(p.w, "%s(nil)", substAny(v.Type()))
} else {
- p.reprValue(seen, v.Elem(), indent, true)
+ p.reprValue(seen, v.Elem(), indent, true, true)
}
+ case reflect.Func:
+ fmt.Fprint(p.w, substAny(v.Type()))
+
default:
- if t.Name() != realKindName[t.Kind()] || p.alwaysIncludeType {
- fmt.Fprintf(p.w, "%s(%v)", t, v)
+ value := fmt.Sprintf("%v", v)
+ if p.useLiterals {
+ value = fmt.Sprintf("%#v", v)
+ }
+ if t.Name() != realKindName[t.Kind()] || p.alwaysIncludeType || isAnyValue {
+ fmt.Fprintf(p.w, "%s(%s)", t, value)
} else {
- fmt.Fprintf(p.w, "%v", v)
+ fmt.Fprintf(p.w, "%s", value)
}
}
}
+func asTime(v reflect.Value) (time.Time, bool) {
+ if !v.CanInterface() {
+ return time.Time{}, false
+ }
+ t, ok := v.Interface().(time.Time)
+ return t, ok
+}
+
// String returns a string representing v.
-func String(v interface{}, options ...Option) string {
+func String(v any, options ...Option) string {
w := bytes.NewBuffer(nil)
options = append([]Option{NoIndent()}, options...)
p := New(w, options...)
@@ -302,7 +360,7 @@ func String(v interface{}, options ...Option) string {
return w.String()
}
-func extractOptions(vs ...interface{}) (args []interface{}, options []Option) {
+func extractOptions(vs ...any) (args []any, options []Option) {
for _, v := range vs {
if o, ok := v.(Option); ok {
options = append(options, o)
@@ -314,35 +372,17 @@ func extractOptions(vs ...interface{}) (args []interface{}, options []Option) {
}
// Println prints v to os.Stdout, one per line.
-func Println(vs ...interface{}) {
+func Println(vs ...any) {
args, options := extractOptions(vs...)
New(os.Stdout, options...).Println(args...)
}
// Print writes a representation of v to os.Stdout, separated by spaces.
-func Print(vs ...interface{}) {
+func Print(vs ...any) {
args, options := extractOptions(vs...)
New(os.Stdout, options...).Print(args...)
}
-func isZero(v reflect.Value) bool {
- switch v.Kind() {
- case reflect.Array, reflect.String:
- return v.Len() == 0
- case reflect.Bool:
- return !v.Bool()
- case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
- return v.Int() == 0
- case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
- return v.Uint() == 0
- case reflect.Float32, reflect.Float64:
- return v.Float() == 0
- case reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
- return v.IsNil()
- }
- return false
-}
-
func timeToGo(w io.Writer, t time.Time) {
if t.IsZero() {
fmt.Fprint(w, "time.Time{}")
@@ -364,3 +404,39 @@ func timeToGo(w io.Writer, t time.Time) {
y, m, d := t.Date()
fmt.Fprintf(w, `time.Date(%d, %d, %d, %d, %d, %d, %d, %s)`, y, m, d, t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), zone)
}
+
+// Replace "interface {}" with "any"
+func substAny(t reflect.Type) string {
+ switch t.Kind() {
+ case reflect.Array:
+ return fmt.Sprintf("[%d]%s", t.Len(), substAny(t.Elem()))
+
+ case reflect.Slice:
+ return "[]" + substAny(t.Elem())
+
+ case reflect.Map:
+ return "map[" + substAny(t.Key()) + "]" + substAny(t.Elem())
+
+ case reflect.Chan:
+ return fmt.Sprintf("%s %s", t.ChanDir(), substAny(t.Elem()))
+
+ case reflect.Func:
+ in := []string{}
+ out := []string{}
+ for i := 0; i < t.NumIn(); i++ {
+ in = append(in, substAny(t.In(i)))
+ }
+ for i := 0; i < t.NumOut(); i++ {
+ out = append(out, substAny(t.Out(i)))
+ }
+ if len(out) == 0 {
+ return "func" + t.Name() + "(" + strings.Join(in, ", ") + ")"
+ }
+ return "func" + t.Name() + "(" + strings.Join(in, ", ") + ") (" + strings.Join(out, ", ") + ")"
+ }
+
+ if t == anyType {
+ return "any"
+ }
+ return t.String()
+}
diff --git a/vendor/modules.txt b/vendor/modules.txt
index e3a268a8..d6af1e99 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -130,11 +130,11 @@ github.com/alecthomas/assert
# github.com/alecthomas/colour v0.1.0
## explicit
github.com/alecthomas/colour
-# github.com/alecthomas/kong v0.2.15
-## explicit; go 1.13
+# github.com/alecthomas/kong v0.9.0
+## explicit; go 1.18
github.com/alecthomas/kong
-# github.com/alecthomas/repr v0.0.0-20201120212035-bb82daffcca2
-## explicit; go 1.15
+# github.com/alecthomas/repr v0.4.0
+## explicit; go 1.18
github.com/alecthomas/repr
# github.com/alessio/shellescape v1.4.1
## explicit; go 1.14