0
0
Fork 0
mirror of https://github.com/crazy-max/diun.git synced 2025-03-18 13:12:54 +00:00

Merge pull request from crazy-max/dependabot/go_modules/github.com/alecthomas/kong-1.6.0

chore(deps): bump github.com/alecthomas/kong from 0.9.0 to 1.6.0
This commit is contained in:
CrazyMax 2024-12-15 15:17:42 +01:00 committed by GitHub
commit c9d62874f2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 461 additions and 214 deletions

2
go.mod
View file

@ -5,7 +5,7 @@ go 1.23.0
require ( require (
github.com/AlecAivazis/survey/v2 v2.3.7 github.com/AlecAivazis/survey/v2 v2.3.7
github.com/PaulSonOfLars/gotgbot/v2 v2.0.0-rc.30 github.com/PaulSonOfLars/gotgbot/v2 v2.0.0-rc.30
github.com/alecthomas/kong v0.9.0 github.com/alecthomas/kong v1.6.0
github.com/bmatcuk/doublestar/v3 v3.0.0 github.com/bmatcuk/doublestar/v3 v3.0.0
github.com/containerd/platforms v0.2.1 github.com/containerd/platforms v0.2.1
github.com/containers/image/v5 v5.33.0 github.com/containers/image/v5 v5.33.0

8
go.sum
View file

@ -20,10 +20,10 @@ github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAc
github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ= github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ=
github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo=
github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
github.com/alecthomas/assert/v2 v2.6.0 h1:o3WJwILtexrEUk3cUVal3oiQY2tfgr/FHWiz/v2n4FU= github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
github.com/alecthomas/assert/v2 v2.6.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
github.com/alecthomas/kong v0.9.0 h1:G5diXxc85KvoV2f0ZRVuMsi45IrBgx9zDNGNj165aPA= github.com/alecthomas/kong v1.6.0 h1:mwOzbdMR7uv2vul9J0FU3GYxE7ls/iX1ieMg5WIM6gE=
github.com/alecthomas/kong v0.9.0/go.mod h1:Y47y5gKfHp1hDc7CH7OeXgLIpp+Q2m1Ni0L5s3bI8Os= github.com/alecthomas/kong v1.6.0/go.mod h1:p2vqieVMeTAnaC83txKtXe8FLke2X07aruPWXyMPQrU=
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= 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/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=

View file

@ -7,7 +7,6 @@ output:
linters: linters:
enable-all: true enable-all: true
disable: disable:
- maligned
- lll - lll
- gochecknoglobals - gochecknoglobals
- wsl - wsl
@ -17,11 +16,8 @@ linters:
- goprintffuncname - goprintffuncname
- paralleltest - paralleltest
- nlreturn - nlreturn
- goerr113
- ifshort
- testpackage - testpackage
- wrapcheck - wrapcheck
- exhaustivestruct
- forbidigo - forbidigo
- gci - gci
- godot - godot
@ -29,9 +25,6 @@ linters:
- cyclop - cyclop
- errorlint - errorlint
- nestif - nestif
- golint
- scopelint
- interfacer
- tagliatelle - tagliatelle
- thelper - thelper
- godox - godox
@ -41,16 +34,17 @@ linters:
- exhaustruct - exhaustruct
- nonamedreturns - nonamedreturns
- nilnil - 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 - depguard # nothing to guard against yet
- tagalign # hurts readability of kong tags - tagalign # hurts readability of kong tags
- mnd
- perfsprint
- err113
- copyloopvar
- intrange
- execinquery
linters-settings: linters-settings:
govet: govet:
check-shadowing: true
# These govet checks are disabled by default, but they're useful. # These govet checks are disabled by default, but they're useful.
enable: enable:
- niliness - niliness
@ -76,6 +70,7 @@ issues:
- 'bad syntax for struct tag key' - 'bad syntax for struct tag key'
- 'bad syntax for struct tag pair' - 'bad syntax for struct tag pair'
- 'result .* \(error\) is always nil' - 'result .* \(error\) is always nil'
- 'Error return value of `fmt.Fprintln` is not checked'
exclude-rules: exclude-rules:
# Don't warn on unused parameters. # Don't warn on unused parameters.

View file

@ -5,42 +5,46 @@
[![](https://godoc.org/github.com/alecthomas/kong?status.svg)](http://godoc.org/github.com/alecthomas/kong) [![CircleCI](https://img.shields.io/circleci/project/github/alecthomas/kong.svg)](https://circleci.com/gh/alecthomas/kong) [![Go Report Card](https://goreportcard.com/badge/github.com/alecthomas/kong)](https://goreportcard.com/report/github.com/alecthomas/kong) [![Slack chat](https://img.shields.io/static/v1?logo=slack&style=flat&label=slack&color=green&message=gophers)](https://gophers.slack.com/messages/CN9DS8YF3) [![](https://godoc.org/github.com/alecthomas/kong?status.svg)](http://godoc.org/github.com/alecthomas/kong) [![CircleCI](https://img.shields.io/circleci/project/github/alecthomas/kong.svg)](https://circleci.com/gh/alecthomas/kong) [![Go Report Card](https://goreportcard.com/badge/github.com/alecthomas/kong)](https://goreportcard.com/report/github.com/alecthomas/kong) [![Slack chat](https://img.shields.io/static/v1?logo=slack&style=flat&label=slack&color=green&message=gophers)](https://gophers.slack.com/messages/CN9DS8YF3)
<!-- TOC depthfrom:2 depthto:3 --> - [Kong is a command-line parser for Go](#kong-is-a-command-line-parser-for-go)
- [Version 1.0.0 Release](#version-100-release)
- [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)
- [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: 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)
- [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)
- [`Name(help)` and `Description(help)` - set the application name description](#namehelp-and-descriptionhelp---set-the-application-name-description)
- [`Configuration(loader, 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)
- [`ConfigureHelp(HelpOptions)` and `Help(HelpFunc)` - 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)
- [Introduction](#introduction) ## Version 1.0.0 Release
- [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)
- [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: 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)
- [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)
<!-- /TOC --> Kong has been stable for a long time, so it seemed appropriate to cut a 1.0 release.
There is one breaking change, [#436](https://github.com/alecthomas/kong/pull/436), which should effect relatively few users.
## Introduction ## Introduction
@ -561,29 +565,35 @@ Both can coexist with standard Tag parsing.
| `name:"X"` | Long name, for overriding field name. | | `name:"X"` | Long name, for overriding field name. |
| `help:"X"` | Help text. | | `help:"X"` | Help text. |
| `type:"X"` | Specify [named types](#custom-named-decoders) to use. | | `type:"X"` | Specify [named types](#custom-named-decoders) to use. |
| `placeholder:"X"` | Placeholder text. | | `placeholder:"X"` | Placeholder input, if flag. e.g. `` `placeholder:"<the-placeholder>"` `` will show `--flag-name=<the-placeholder>` when displaying help. |
| `default:"X"` | Default value. | | `default:"X"` | Default value. |
| `default:"1"` | On a command, make it the default. | | `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 | | `default:"withargs"` | On a command, make it the default and allow args/flags from that command |
| `short:"X"` | Short name, if flag. | | `short:"X"` | Short name, if flag. |
| `aliases:"X,Y"` | One or more aliases (for cmd or flag). | | `aliases:"X,Y"` | One or more aliases (for cmd or flag). |
| `required:""` | If present, flag/arg is required. | | `required:""` | If present, flag/arg is required. |
| `optional:""` | If present, flag/arg is optional. | | `optional:""` | If present, flag/arg is optional. |
| `hidden:""` | If present, command or flag is hidden. | | `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 | | `negatable:""` | If present on a `bool` field, supports prefixing a flag with `--no-` to invert the default value |
| `negatable:"X"` | If present on a `bool` field, supports `--X` to invert the default value |
| `format:"X"` | Format for parsing input, if supported. | | `format:"X"` | Format for parsing input, if supported. |
| `sep:"X"` | Separator for sequences (defaults to ","). May be `none` to disable splitting. | | `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. | | `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`. | | `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. | | `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. | | `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. |
| `and:"X,Y,..."` | AND groups for flags. All flags in the group must be used in the same command. When combined with `required`, all flags in the group will be required. |
| `prefix:"X"` | Prefix for all sub-flags. | | `prefix:"X"` | Prefix for all sub-flags. |
| `envprefix:"X"` | Envar 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. | | `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. | | `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. | | `passthrough:"<mode>"`[^1] | 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:"-"` `` | | `-` | Ignore the field. Useful for adding non-CLI fields to a configuration struct. e.g `` `kong:"-"` `` |
[^1]: `<mode>` can be `partial` or `all` (the default). `all` will pass through all arguments including flags, including
flags. `partial` will validate flags until the first positional argument is encountered, then pass through all remaining
positional arguments.
## Plugins ## Plugins
Kong CLI's can be extended by embedding the `kong.Plugin` type and populating it with pointers to Kong annotated structs. For example: Kong CLI's can be extended by embedding the `kong.Plugin` type and populating it with pointers to Kong annotated structs. For example:
@ -654,8 +664,7 @@ func main() {
## Validation ## Validation
Kong does validation on the structure of a command-line, but also supports Kong does validation on the structure of a command-line, but also supports
extensible validation. Any node in the tree may implement the following extensible validation. Any node in the tree may implement either of the following interfaces:
interface:
```go ```go
type Validatable interface { type Validatable interface {
@ -663,6 +672,12 @@ type Validatable interface {
} }
``` ```
```go
type Validatable interface {
Validate(kctx *kong.Context) error
}
```
If one of these nodes is in the active command-line it will be called during If one of these nodes is in the active command-line it will be called during
normal validation. normal validation.
@ -733,13 +748,18 @@ All builtin Go types (as well as a bunch of useful stdlib types like `time.Time`
The default help output is usually sufficient, but if not there are two solutions. The default help output is usually sufficient, but if not there are two solutions.
1. Use `ConfigureHelp(HelpOptions)` to configure how help is formatted (see [HelpOptions](https://godoc.org/github.com/alecthomas/kong#HelpOptions) for details). 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. 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 `DefaultHelpPrinter` for an example.
3. Use `ValueFormatter(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. 4. Use `Groups([]Group)` if you want to customize group titles or add a header.
### `Bind(...)` - bind values for callback hooks and Run() methods ### Injecting values into `Run()` methods
See the [section on hooks](#hooks-beforeresolve-beforeapply-afterapply-and-the-bind-option) for details. There are several ways to inject values into `Run()` methods:
1. Use `Bind()` to bind values directly.
2. Use `BindTo()` to bind values to an interface type.
3. Use `BindToProvider()` to bind values to a function that provides the value.
4. Implement `Provide<Type>() error` methods on the command structure.
### Other options ### Other options

View file

@ -51,6 +51,9 @@ type flattenedField struct {
func flattenedFields(v reflect.Value, ptag *Tag) (out []flattenedField, err error) { func flattenedFields(v reflect.Value, ptag *Tag) (out []flattenedField, err error) {
v = reflect.Indirect(v) v = reflect.Indirect(v)
if v.Kind() != reflect.Struct {
return out, nil
}
for i := 0; i < v.NumField(); i++ { for i := 0; i < v.NumField(); i++ {
ft := v.Type().Field(i) ft := v.Type().Field(i)
fv := v.Field(i) fv := v.Field(i)
@ -170,6 +173,12 @@ MAIN:
if flag.Short != 0 { if flag.Short != 0 {
delete(seenFlags, "-"+string(flag.Short)) delete(seenFlags, "-"+string(flag.Short))
} }
if negFlag := negatableFlagName(flag.Name, flag.Tag.Negatable); negFlag != "" {
delete(seenFlags, negFlag)
}
for _, aflag := range flag.Aliases {
delete(seenFlags, "--"+aflag)
}
} }
if err := validatePositionalArguments(node); err != nil { if err := validatePositionalArguments(node); err != nil {
@ -272,17 +281,18 @@ func buildField(k *Kong, node *Node, v reflect.Value, ft reflect.StructField, fv
} }
value := &Value{ value := &Value{
Name: name, Name: name,
Help: tag.Help, Help: tag.Help,
OrigHelp: tag.Help, OrigHelp: tag.Help,
HasDefault: tag.HasDefault, HasDefault: tag.HasDefault,
Default: tag.Default, Default: tag.Default,
DefaultValue: reflect.New(fv.Type()).Elem(), DefaultValue: reflect.New(fv.Type()).Elem(),
Mapper: mapper, Mapper: mapper,
Tag: tag, Tag: tag,
Target: fv, Target: fv,
Enum: tag.Enum, Enum: tag.Enum,
Passthrough: tag.Passthrough, Passthrough: tag.Passthrough,
PassthroughMode: tag.PassthroughMode,
// Flags are optional by default, and args are required by default. // Flags are optional by default, and args are required by default.
Required: (!tag.Arg && tag.Required) || (tag.Arg && !tag.Optional), Required: (!tag.Arg && tag.Required) || (tag.Arg && !tag.Optional),
@ -309,6 +319,13 @@ func buildField(k *Kong, node *Node, v reflect.Value, ft reflect.StructField, fv
} }
seenFlags["-"+string(tag.Short)] = true seenFlags["-"+string(tag.Short)] = true
} }
if tag.Negatable != "" {
negFlag := negatableFlagName(value.Name, tag.Negatable)
if seenFlags[negFlag] {
return failField(v, ft, "duplicate negation flag %s", negFlag)
}
seenFlags[negFlag] = true
}
flag := &Flag{ flag := &Flag{
Value: value, Value: value,
Aliases: tag.Aliases, Aliases: tag.Aliases,
@ -317,6 +334,7 @@ func buildField(k *Kong, node *Node, v reflect.Value, ft reflect.StructField, fv
Envs: tag.Envs, Envs: tag.Envs,
Group: buildGroupForKey(k, tag.Group), Group: buildGroupForKey(k, tag.Group),
Xor: tag.Xor, Xor: tag.Xor,
And: tag.And,
Hidden: tag.Hidden, Hidden: tag.Hidden,
} }
value.Flag = flag value.Flag = flag

View file

@ -6,7 +6,10 @@ import (
"strings" "strings"
) )
type bindings map[reflect.Type]func() (reflect.Value, error) // A map of type to function that returns a value of that type.
//
// The function should have the signature func(...) (T, error). Arguments are recursively resolved.
type bindings map[reflect.Type]any
func (b bindings) String() string { func (b bindings) String() string {
out := []string{} out := []string{}
@ -19,32 +22,23 @@ func (b bindings) String() string {
func (b bindings) add(values ...interface{}) bindings { func (b bindings) add(values ...interface{}) bindings {
for _, v := range values { for _, v := range values {
v := v v := v
b[reflect.TypeOf(v)] = func() (reflect.Value, error) { return reflect.ValueOf(v), nil } b[reflect.TypeOf(v)] = func() (any, error) { return v, nil }
} }
return b return b
} }
func (b bindings) addTo(impl, iface interface{}) { func (b bindings) addTo(impl, iface interface{}) {
valueOf := reflect.ValueOf(impl) b[reflect.TypeOf(iface).Elem()] = func() (any, error) { return impl, nil }
b[reflect.TypeOf(iface).Elem()] = func() (reflect.Value, error) { return valueOf, nil }
} }
func (b bindings) addProvider(provider interface{}) error { func (b bindings) addProvider(provider interface{}) error {
pv := reflect.ValueOf(provider) pv := reflect.ValueOf(provider)
t := pv.Type() t := pv.Type()
if t.Kind() != reflect.Func || t.NumIn() != 0 || t.NumOut() != 2 || t.Out(1) != reflect.TypeOf((*error)(nil)).Elem() { if t.Kind() != reflect.Func || 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) return fmt.Errorf("%T must be a function with the signature func(...)(T, error)", provider)
} }
rt := pv.Type().Out(0) rt := pv.Type().Out(0)
b[rt] = func() (reflect.Value, error) { b[rt] = provider
out := pv.Call(nil)
errv := out[1]
var err error
if !errv.IsNil() {
err = errv.Interface().(error) //nolint
}
return out[0], err
}
return nil return nil
} }
@ -78,28 +72,19 @@ func callFunction(f reflect.Value, bindings bindings) error {
if f.Kind() != reflect.Func { if f.Kind() != reflect.Func {
return fmt.Errorf("expected function, got %s", f.Type()) return fmt.Errorf("expected function, got %s", f.Type())
} }
in := []reflect.Value{}
t := f.Type() t := f.Type()
if t.NumOut() != 1 || !t.Out(0).Implements(callbackReturnSignature) { if t.NumOut() != 1 || !t.Out(0).Implements(callbackReturnSignature) {
return fmt.Errorf("return value of %s must implement \"error\"", t) return fmt.Errorf("return value of %s must implement \"error\"", t)
} }
for i := 0; i < t.NumIn(); i++ { out, err := callAnyFunction(f, bindings)
pt := t.In(i) if err != nil {
if argf, ok := bindings[pt]; ok { return err
argv, err := argf()
if err != nil {
return err
}
in = append(in, argv)
} else {
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) ferr := out[0]
if out[0].IsNil() { if ferrv := reflect.ValueOf(ferr); !ferrv.IsValid() || ((ferrv.Kind() == reflect.Interface || ferrv.Kind() == reflect.Pointer) && ferrv.IsNil()) {
return nil return nil
} }
return out[0].Interface().(error) //nolint return ferr.(error) //nolint:forcetypeassert
} }
func callAnyFunction(f reflect.Value, bindings bindings) (out []any, err error) { func callAnyFunction(f reflect.Value, bindings bindings) (out []any, err error) {
@ -110,15 +95,19 @@ func callAnyFunction(f reflect.Value, bindings bindings) (out []any, err error)
t := f.Type() t := f.Type()
for i := 0; i < t.NumIn(); i++ { for i := 0; i < t.NumIn(); i++ {
pt := t.In(i) pt := t.In(i)
if argf, ok := bindings[pt]; ok { argf, ok := bindings[pt]
argv, err := argf() if !ok {
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) return nil, fmt.Errorf("couldn't find binding of type %s for parameter %d of %s(), use kong.Bind(%s)", pt, i, t, pt)
} }
// Recursively resolve binding functions.
argv, err := callAnyFunction(reflect.ValueOf(argf), bindings)
if err != nil {
return nil, fmt.Errorf("%s: %w", pt, err)
}
if ferrv := reflect.ValueOf(argv[len(argv)-1]); ferrv.IsValid() && !ferrv.IsNil() {
return nil, ferrv.Interface().(error) //nolint:forcetypeassert
}
in = append(in, reflect.ValueOf(argv[0]))
} }
outv := f.Call(in) outv := f.Call(in)
out = make([]any, len(outv)) out = make([]any, len(outv))

View file

@ -208,7 +208,7 @@ func (c *Context) Validate() error { //nolint: gocyclo
desc = node.Path() desc = node.Path()
} }
if validate := isValidatable(value); validate != nil { if validate := isValidatable(value); validate != nil {
if err := validate.Validate(); err != nil { if err := validate.Validate(c); err != nil {
if desc != "" { if desc != "" {
return fmt.Errorf("%s: %w", desc, err) return fmt.Errorf("%s: %w", desc, err)
} }
@ -259,7 +259,7 @@ func (c *Context) Validate() error { //nolint: gocyclo
if err := checkMissingPositionals(positionals, node.Positional); err != nil { if err := checkMissingPositionals(positionals, node.Positional); err != nil {
return err return err
} }
if err := checkXorDuplicates(c.Path); err != nil { if err := checkXorDuplicatedAndAndMissing(c.Path); err != nil {
return err return err
} }
@ -347,6 +347,7 @@ func (c *Context) endParsing() {
} }
} }
//nolint:maintidx
func (c *Context) trace(node *Node) (err error) { //nolint: gocyclo func (c *Context) trace(node *Node) (err error) { //nolint: gocyclo
positional := 0 positional := 0
node.Active = true node.Active = true
@ -383,9 +384,13 @@ func (c *Context) trace(node *Node) (err error) { //nolint: gocyclo
// Indicates end of parsing. All remaining arguments are treated as positional arguments only. // Indicates end of parsing. All remaining arguments are treated as positional arguments only.
case v == "--": case v == "--":
c.scan.Pop()
c.endParsing() c.endParsing()
// Pop the -- token unless the next positional argument accepts passthrough arguments.
if !(positional < len(node.Positional) && node.Positional[positional].Passthrough) {
c.scan.Pop()
}
// Long flag. // Long flag.
case strings.HasPrefix(v, "--"): case strings.HasPrefix(v, "--"):
c.scan.Pop() c.scan.Pop()
@ -420,12 +425,22 @@ func (c *Context) trace(node *Node) (err error) { //nolint: gocyclo
case FlagToken: case FlagToken:
if err := c.parseFlag(flags, token.String()); err != nil { if err := c.parseFlag(flags, token.String()); err != nil {
return err if isUnknownFlagError(err) && positional < len(node.Positional) && node.Positional[positional].PassthroughMode == PassThroughModeAll {
c.scan.Pop()
c.scan.PushTyped(token.String(), PositionalArgumentToken)
} else {
return err
}
} }
case ShortFlagToken: case ShortFlagToken:
if err := c.parseFlag(flags, token.String()); err != nil { if err := c.parseFlag(flags, token.String()); err != nil {
return err if isUnknownFlagError(err) && positional < len(node.Positional) && node.Positional[positional].PassthroughMode == PassThroughModeAll {
c.scan.Pop()
c.scan.PushTyped(token.String(), PositionalArgumentToken)
} else {
return err
}
} }
case FlagValueToken: case FlagValueToken:
@ -700,13 +715,13 @@ func (c *Context) parseFlag(flags []*Flag, match string) (err error) {
candidates = append(candidates, alias) candidates = append(candidates, alias)
} }
neg := "--no-" + flag.Name neg := negatableFlagName(flag.Name, flag.Tag.Negatable)
if !matched && !(match == neg && flag.Tag.Negatable) { if !matched && match != neg {
continue continue
} }
// Found a matching flag. // Found a matching flag.
c.scan.Pop() c.scan.Pop()
if match == neg && flag.Tag.Negatable { if match == neg && flag.Tag.Negatable != "" {
flag.Negated = true flag.Negated = true
} }
err := flag.Parse(c.scan, c.getValue(flag.Value)) err := flag.Parse(c.scan, c.getValue(flag.Value))
@ -728,13 +743,23 @@ func (c *Context) parseFlag(flags []*Flag, match string) (err error) {
c.Path = append(c.Path, &Path{Flag: flag}) c.Path = append(c.Path, &Path{Flag: flag})
return nil return nil
} }
return findPotentialCandidates(match, candidates, "unknown flag %s", match) return &unknownFlagError{Cause: findPotentialCandidates(match, candidates, "unknown flag %s", match)}
} }
func isUnknownFlagError(err error) bool {
var unknown *unknownFlagError
return errors.As(err, &unknown)
}
type unknownFlagError struct{ Cause error }
func (e *unknownFlagError) Unwrap() error { return e.Cause }
func (e *unknownFlagError) Error() string { return e.Cause.Error() }
// Call an arbitrary function filling arguments with bound values. // Call an arbitrary function filling arguments with bound values.
func (c *Context) Call(fn any, binds ...interface{}) (out []interface{}, err error) { func (c *Context) Call(fn any, binds ...interface{}) (out []interface{}, err error) {
fv := reflect.ValueOf(fn) fv := reflect.ValueOf(fn)
bindings := c.Kong.bindings.clone().add(binds...).add(c).merge(c.bindings) //nolint:govet bindings := c.Kong.bindings.clone().add(binds...).add(c).merge(c.bindings)
return callAnyFunction(fv, bindings) return callAnyFunction(fv, bindings)
} }
@ -757,6 +782,19 @@ func (c *Context) RunNode(node *Node, binds ...interface{}) (err error) {
methodBinds = methodBinds.clone() methodBinds = methodBinds.clone()
for p := node; p != nil; p = p.Parent { for p := node; p != nil; p = p.Parent {
methodBinds = methodBinds.add(p.Target.Addr().Interface()) methodBinds = methodBinds.add(p.Target.Addr().Interface())
// Try value and pointer to value.
for _, p := range []reflect.Value{p.Target, p.Target.Addr()} {
t := p.Type()
for i := 0; i < p.NumMethod(); i++ {
methodt := t.Method(i)
if strings.HasPrefix(methodt.Name, "Provide") {
method := p.Method(i)
if err := methodBinds.addProvider(method.Interface()); err != nil {
return fmt.Errorf("%s.%s: %w", t.Name(), methodt.Name, err)
}
}
}
}
} }
if method.IsValid() { if method.IsValid() {
methods = append(methods, targetMethod{node, method, methodBinds}) methods = append(methods, targetMethod{node, method, methodBinds})
@ -785,18 +823,22 @@ func (c *Context) RunNode(node *Node, binds ...interface{}) (err error) {
func (c *Context) Run(binds ...interface{}) (err error) { func (c *Context) Run(binds ...interface{}) (err error) {
node := c.Selected() node := c.Selected()
if node == nil { if node == nil {
if len(c.Path) > 0 { if len(c.Path) == 0 {
selected := c.Path[0].Node() return fmt.Errorf("no command selected")
if selected.Type == ApplicationNode { }
method := getMethod(selected.Target, "Run") selected := c.Path[0].Node()
if method.IsValid() { if selected.Type == ApplicationNode {
return c.RunNode(selected, binds...) method := getMethod(selected.Target, "Run")
} if method.IsValid() {
} node = selected
}
} else {
return fmt.Errorf("no command selected")
} }
return fmt.Errorf("no command selected")
} }
return c.RunNode(node, binds...) runErr := c.RunNode(node, binds...)
err = c.Kong.applyHook(c, "AfterRun")
return errors.Join(runErr, err)
} }
// PrintUsage to Kong's stdout. // PrintUsage to Kong's stdout.
@ -811,23 +853,35 @@ func (c *Context) PrintUsage(summary bool) error {
func checkMissingFlags(flags []*Flag) error { func checkMissingFlags(flags []*Flag) error {
xorGroupSet := map[string]bool{} xorGroupSet := map[string]bool{}
xorGroup := map[string][]string{} xorGroup := map[string][]string{}
andGroupSet := map[string]bool{}
andGroup := map[string][]string{}
missing := []string{} missing := []string{}
andGroupRequired := getRequiredAndGroupMap(flags)
for _, flag := range flags { for _, flag := range flags {
for _, and := range flag.And {
flag.Required = andGroupRequired[and]
}
if flag.Set { if flag.Set {
for _, xor := range flag.Xor { for _, xor := range flag.Xor {
xorGroupSet[xor] = true xorGroupSet[xor] = true
} }
for _, and := range flag.And {
andGroupSet[and] = true
}
} }
if !flag.Required || flag.Set { if !flag.Required || flag.Set {
continue continue
} }
if len(flag.Xor) > 0 { if len(flag.Xor) > 0 || len(flag.And) > 0 {
for _, xor := range flag.Xor { for _, xor := range flag.Xor {
if xorGroupSet[xor] { if xorGroupSet[xor] {
continue continue
} }
xorGroup[xor] = append(xorGroup[xor], flag.Summary()) xorGroup[xor] = append(xorGroup[xor], flag.Summary())
} }
for _, and := range flag.And {
andGroup[and] = append(andGroup[and], flag.Summary())
}
} else { } else {
missing = append(missing, flag.Summary()) missing = append(missing, flag.Summary())
} }
@ -837,6 +891,11 @@ func checkMissingFlags(flags []*Flag) error {
missing = append(missing, strings.Join(flags, " or ")) missing = append(missing, strings.Join(flags, " or "))
} }
} }
for _, flags := range andGroup {
if len(flags) > 1 {
missing = append(missing, strings.Join(flags, " and "))
}
}
if len(missing) == 0 { if len(missing) == 0 {
return nil return nil
@ -847,6 +906,18 @@ func checkMissingFlags(flags []*Flag) error {
return fmt.Errorf("missing flags: %s", strings.Join(missing, ", ")) return fmt.Errorf("missing flags: %s", strings.Join(missing, ", "))
} }
func getRequiredAndGroupMap(flags []*Flag) map[string]bool {
andGroupRequired := map[string]bool{}
for _, flag := range flags {
for _, and := range flag.And {
if flag.Required {
andGroupRequired[and] = true
}
}
}
return andGroupRequired
}
func checkMissingChildren(node *Node) error { func checkMissingChildren(node *Node) error {
missing := []string{} missing := []string{}
@ -883,7 +954,7 @@ func checkMissingChildren(node *Node) error {
if len(missing) == 1 { if len(missing) == 1 {
return fmt.Errorf("expected %s", missing[0]) return fmt.Errorf("expected %s", missing[0])
} }
return fmt.Errorf("expected one of %s", strings.Join(missing, ", ")) return fmt.Errorf("expected one of %s", strings.Join(missing, ", "))
} }
// If we're missing any positionals and they're required, return an error. // If we're missing any positionals and they're required, return an error.
@ -943,7 +1014,7 @@ func checkEnum(value *Value, target reflect.Value) error {
} }
enums = append(enums, fmt.Sprintf("%q", enum)) enums = append(enums, fmt.Sprintf("%q", enum))
} }
return fmt.Errorf("%s must be one of %s but got %q", value.ShortSummary(), strings.Join(enums, ","), target.Interface()) return fmt.Errorf("%s must be one of %s but got %q", value.ShortSummary(), strings.Join(enums, ","), fmt.Sprintf("%v", target.Interface()))
} }
} }
@ -957,6 +1028,20 @@ func checkPassthroughArg(target reflect.Value) bool {
} }
} }
func checkXorDuplicatedAndAndMissing(paths []*Path) error {
errs := []string{}
if err := checkXorDuplicates(paths); err != nil {
errs = append(errs, err.Error())
}
if err := checkAndMissing(paths); err != nil {
errs = append(errs, err.Error())
}
if len(errs) > 0 {
return errors.New(strings.Join(errs, ", "))
}
return nil
}
func checkXorDuplicates(paths []*Path) error { func checkXorDuplicates(paths []*Path) error {
for _, path := range paths { for _, path := range paths {
seen := map[string]*Flag{} seen := map[string]*Flag{}
@ -975,6 +1060,38 @@ func checkXorDuplicates(paths []*Path) error {
return nil return nil
} }
func checkAndMissing(paths []*Path) error {
for _, path := range paths {
missingMsgs := []string{}
andGroups := map[string][]*Flag{}
for _, flag := range path.Flags {
for _, and := range flag.And {
andGroups[and] = append(andGroups[and], flag)
}
}
for _, flags := range andGroups {
oneSet := false
notSet := []*Flag{}
flagNames := []string{}
for _, flag := range flags {
flagNames = append(flagNames, flag.Name)
if flag.Set {
oneSet = true
} else {
notSet = append(notSet, flag)
}
}
if len(notSet) > 0 && oneSet {
missingMsgs = append(missingMsgs, fmt.Sprintf("--%s must be used together", strings.Join(flagNames, " and --")))
}
}
if len(missingMsgs) > 0 {
return fmt.Errorf("%s", strings.Join(missingMsgs, ", "))
}
}
return nil
}
func findPotentialCandidates(needle string, haystack []string, format string, args ...interface{}) error { func findPotentialCandidates(needle string, haystack []string, format string, args ...interface{}) error {
if len(haystack) == 0 { if len(haystack) == 0 {
return fmt.Errorf(format, args...) return fmt.Errorf(format, args...)
@ -995,12 +1112,23 @@ func findPotentialCandidates(needle string, haystack []string, format string, ar
} }
type validatable interface{ Validate() error } type validatable interface{ Validate() error }
type extendedValidatable interface {
Validate(kctx *Context) error
}
func isValidatable(v reflect.Value) validatable { // Proxy a validatable function to the extendedValidatable interface
type validatableFunc func() error
func (f validatableFunc) Validate(kctx *Context) error { return f() }
func isValidatable(v reflect.Value) extendedValidatable {
if !v.IsValid() || (v.Kind() == reflect.Ptr || v.Kind() == reflect.Slice || v.Kind() == reflect.Map) && v.IsNil() { if !v.IsValid() || (v.Kind() == reflect.Ptr || v.Kind() == reflect.Slice || v.Kind() == reflect.Map) && v.IsNil() {
return nil return nil
} }
if validate, ok := v.Interface().(validatable); ok { if validate, ok := v.Interface().(validatable); ok {
return validatableFunc(validate.Validate)
}
if validate, ok := v.Interface().(extendedValidatable); ok {
return validate return validate
} }
if v.CanAddr() { if v.CanAddr() {

View file

@ -491,27 +491,22 @@ func formatFlag(haveShort bool, flag *Flag) string {
name := flag.Name name := flag.Name
isBool := flag.IsBool() isBool := flag.IsBool()
isCounter := flag.IsCounter() isCounter := flag.IsCounter()
short := ""
if flag.Short != 0 { if flag.Short != 0 {
if isBool && flag.Tag.Negatable { short = "-" + string(flag.Short) + ", "
flagString += fmt.Sprintf("-%c, --[no-]%s", flag.Short, name) } else if haveShort {
} else { short = " "
flagString += fmt.Sprintf("-%c, --%s", flag.Short, name)
}
} else {
if isBool && flag.Tag.Negatable {
if haveShort {
flagString = fmt.Sprintf(" --[no-]%s", name)
} else {
flagString = fmt.Sprintf("--[no-]%s", name)
}
} else {
if haveShort {
flagString += fmt.Sprintf(" --%s", name)
} else {
flagString += fmt.Sprintf("--%s", name)
}
}
} }
if isBool && flag.Tag.Negatable == negatableDefault {
name = "[no-]" + name
} else if isBool && flag.Tag.Negatable != "" {
name += "/" + flag.Tag.Negatable
}
flagString += fmt.Sprintf("%s--%s", short, name)
if !isBool && !isCounter { if !isBool && !isCounter {
flagString += fmt.Sprintf("=%s", flag.FormatPlaceHolder()) flagString += fmt.Sprintf("=%s", flag.FormatPlaceHolder())
} }

View file

@ -3,17 +3,24 @@ package kong
// BeforeResolve is a documentation-only interface describing hooks that run before resolvers are applied. // BeforeResolve is a documentation-only interface describing hooks that run before resolvers are applied.
type BeforeResolve interface { type BeforeResolve interface {
// This is not the correct signature - see README for details. // This is not the correct signature - see README for details.
BeforeResolve(args ...interface{}) error BeforeResolve(args ...any) error
} }
// BeforeApply is a documentation-only interface describing hooks that run before values are set. // BeforeApply is a documentation-only interface describing hooks that run before values are set.
type BeforeApply interface { type BeforeApply interface {
// This is not the correct signature - see README for details. // This is not the correct signature - see README for details.
BeforeApply(args ...interface{}) error BeforeApply(args ...any) error
} }
// AfterApply is a documentation-only interface describing hooks that run after values are set. // AfterApply is a documentation-only interface describing hooks that run after values are set.
type AfterApply interface { type AfterApply interface {
// This is not the correct signature - see README for details. // This is not the correct signature - see README for details.
AfterApply(args ...interface{}) error AfterApply(args ...any) error
}
// AfterRun is a documentation-only interface describing hooks that run after Run() returns.
type AfterRun interface {
// This is not the correct signature - see README for details.
// AfterRun is called after Run() returns.
AfterRun(args ...any) error
} }

View file

@ -117,7 +117,7 @@ func New(grammar interface{}, options ...Option) (*Kong, error) {
// Embed any embedded structs. // Embed any embedded structs.
for _, embed := range k.embedded { for _, embed := range k.embedded {
tag, err := parseTagString(strings.Join(embed.tags, " ")) //nolint:govet tag, err := parseTagString(strings.Join(embed.tags, " "))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -167,9 +167,42 @@ func New(grammar interface{}, options ...Option) (*Kong, error) {
k.bindings.add(k.vars) k.bindings.add(k.vars)
if err = checkOverlappingXorAnd(k); err != nil {
return nil, err
}
return k, nil return k, nil
} }
func checkOverlappingXorAnd(k *Kong) error {
xorGroups := map[string][]string{}
andGroups := map[string][]string{}
for _, flag := range k.Model.Node.Flags {
for _, xor := range flag.Xor {
xorGroups[xor] = append(xorGroups[xor], flag.Name)
}
for _, and := range flag.And {
andGroups[and] = append(andGroups[and], flag.Name)
}
}
for xor, xorSet := range xorGroups {
for and, andSet := range andGroups {
overlappingEntries := []string{}
for _, xorTag := range xorSet {
for _, andTag := range andSet {
if xorTag == andTag {
overlappingEntries = append(overlappingEntries, xorTag)
}
}
}
if len(overlappingEntries) > 1 {
return fmt.Errorf("invalid xor and combination, %s and %s overlap with more than one: %s", xor, and, overlappingEntries)
}
}
}
return nil
}
type varStack []Vars type varStack []Vars
func (v *varStack) head() Vars { return (*v)[len(*v)-1] } func (v *varStack) head() Vars { return (*v)[len(*v)-1] }
@ -216,19 +249,19 @@ func (k *Kong) interpolateValue(value *Value, vars Vars) (err error) {
return fmt.Errorf("enum for %s: %s", value.Summary(), err) 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 { if value.Default, err = interpolate(value.Default, vars, nil); err != nil {
return fmt.Errorf("default value for %s: %s", value.Summary(), err) return fmt.Errorf("default value for %s: %s", value.Summary(), err)
} }
if value.Enum, err = interpolate(value.Enum, vars, nil); err != nil { if value.Enum, err = interpolate(value.Enum, vars, nil); err != nil {
return fmt.Errorf("enum value for %s: %s", value.Summary(), err) return fmt.Errorf("enum value for %s: %s", value.Summary(), err)
} }
updatedVars := map[string]string{
"default": value.Default,
"enum": value.Enum,
}
if value.Flag != nil { if value.Flag != nil {
for i, env := range value.Flag.Envs { for i, env := range value.Flag.Envs {
if value.Flag.Envs[i], err = interpolate(env, vars, nil); err != nil { if value.Flag.Envs[i], err = interpolate(env, vars, updatedVars); err != nil {
return fmt.Errorf("env value for %s: %s", value.Summary(), err) return fmt.Errorf("env value for %s: %s", value.Summary(), err)
} }
} }
@ -373,7 +406,7 @@ func (k *Kong) applyHookToDefaultFlags(ctx *Context, node *Node, name string) er
} }
func formatMultilineMessage(w io.Writer, leaders []string, format string, args ...interface{}) { func formatMultilineMessage(w io.Writer, leaders []string, format string, args ...interface{}) {
lines := strings.Split(fmt.Sprintf(format, args...), "\n") lines := strings.Split(strings.TrimRight(fmt.Sprintf(format, args...), "\n"), "\n")
leader := "" leader := ""
for _, l := range leaders { for _, l := range leaders {
if l == "" { if l == "" {

View file

@ -31,7 +31,7 @@ func levenshtein(a, b string) int {
return f[len(f)-1] return f[len(f)-1]
} }
func min(a, b int) int { func min(a, b int) int { //nolint:predeclared
if a <= b { if a <= b {
return a return a
} }

View file

@ -239,23 +239,24 @@ func (n *Node) ClosestGroup() *Group {
// A Value is either a flag or a variable positional argument. // A Value is either a flag or a variable positional argument.
type Value struct { type Value struct {
Flag *Flag // Nil if positional argument. Flag *Flag // Nil if positional argument.
Name string Name string
Help string Help string
OrigHelp string // Original help string, without interpolated variables. OrigHelp string // Original help string, without interpolated variables.
HasDefault bool HasDefault bool
Default string Default string
DefaultValue reflect.Value DefaultValue reflect.Value
Enum string Enum string
Mapper Mapper Mapper Mapper
Tag *Tag Tag *Tag
Target reflect.Value Target reflect.Value
Required bool Required bool
Set bool // Set to true when this value is set through some mechanism. Set bool // Set to true when this value is set through some mechanism.
Format string // Formatting directive, if applicable. Format string // Formatting directive, if applicable.
Position int // Position (for positional arguments). Position int // Position (for positional arguments).
Passthrough bool // Set to true to stop flag parsing when encountered. Passthrough bool // Deprecated: Use PassthroughMode instead. Set to true to stop flag parsing when encountered.
Active bool // Denotes the value is part of an active branch in the CLI. PassthroughMode PassthroughMode //
Active bool // Denotes the value is part of an active branch in the CLI.
} }
// EnumMap returns a map of the enums in this value. // EnumMap returns a map of the enums in this value.
@ -405,6 +406,7 @@ type Flag struct {
*Value *Value
Group *Group // Logical grouping when displaying. May also be used by configuration loaders to group options logically. Group *Group // Logical grouping when displaying. May also be used by configuration loaders to group options logically.
Xor []string Xor []string
And []string
PlaceHolder string PlaceHolder string
Envs []string Envs []string
Aliases []string Aliases []string

19
vendor/github.com/alecthomas/kong/negatable.go generated vendored Normal file
View file

@ -0,0 +1,19 @@
package kong
// negatableDefault is a placeholder value for the Negatable tag to indicate
// the negated flag is --no-<flag-name>. This is needed as at the time of
// parsing a tag, the field's flag name is not yet known.
const negatableDefault = "_"
// negatableFlagName returns the name of the flag for a negatable field, or
// an empty string if the field is not negatable.
func negatableFlagName(name, negation string) string {
switch negation {
case "":
return ""
case negatableDefault:
return "--no-" + name
default:
return "--" + negation
}
}

View file

@ -89,6 +89,10 @@ type dynamicCommand struct {
// "tags" is a list of extra tag strings to parse, in the form <key>:"<value>". // "tags" is a list of extra tag strings to parse, in the form <key>:"<value>".
func DynamicCommand(name, help, group string, cmd interface{}, tags ...string) Option { func DynamicCommand(name, help, group string, cmd interface{}, tags ...string) Option {
return OptionFunc(func(k *Kong) error { return OptionFunc(func(k *Kong) error {
if run := getMethod(reflect.Indirect(reflect.ValueOf(cmd)), "Run"); !run.IsValid() {
return fmt.Errorf("kong: DynamicCommand %q must be a type with a 'Run' method; got %T", name, cmd)
}
k.dynamicCommands = append(k.dynamicCommands, &dynamicCommand{ k.dynamicCommands = append(k.dynamicCommands, &dynamicCommand{
name: name, name: name,
help: help, help: help,
@ -204,7 +208,11 @@ func BindTo(impl, iface interface{}) Option {
}) })
} }
// BindToProvider allows binding of provider functions. // BindToProvider binds an injected value to a provider function.
//
// The provider function must have the signature:
//
// func() (interface{}, error)
// //
// This is useful when the Run() function of different commands require different values that may // This is useful when the Run() function of different commands require different values that may
// not all be initialisable from the main() function. // not all be initialisable from the main() function.

View file

@ -63,6 +63,6 @@ func JSON(r io.Reader) (Resolver, error) {
} }
func snakeCase(name string) string { func snakeCase(name string) string {
name = strings.Join(strings.Split(strings.Title(name), "-"), "") //nolint: staticcheck name = strings.Join(strings.Split(strings.Title(name), "-"), "")
return strings.ToLower(name[:1]) + name[1:] return strings.ToLower(name[:1]) + name[1:]
} }

View file

@ -9,36 +9,50 @@ import (
"unicode/utf8" "unicode/utf8"
) )
// PassthroughMode indicates how parameters are passed through when "passthrough" is set.
type PassthroughMode int
const (
// PassThroughModeNone indicates passthrough mode is disabled.
PassThroughModeNone PassthroughMode = iota
// PassThroughModeAll indicates that all parameters, including flags, are passed through. It is the default.
PassThroughModeAll
// PassThroughModePartial will validate flags until the first positional argument is encountered, then pass through all remaining positional arguments.
PassThroughModePartial
)
// Tag represents the parsed state of Kong tags in a struct field tag. // Tag represents the parsed state of Kong tags in a struct field tag.
type Tag struct { type Tag struct {
Ignored bool // Field is ignored by Kong. ie. kong:"-" Ignored bool // Field is ignored by Kong. ie. kong:"-"
Cmd bool Cmd bool
Arg bool Arg bool
Required bool Required bool
Optional bool Optional bool
Name string Name string
Help string Help string
Type string Type string
TypeName string TypeName string
HasDefault bool HasDefault bool
Default string Default string
Format string Format string
PlaceHolder string PlaceHolder string
Envs []string Envs []string
Short rune Short rune
Hidden bool Hidden bool
Sep rune Sep rune
MapSep rune MapSep rune
Enum string Enum string
Group string Group string
Xor []string Xor []string
Vars Vars And []string
Prefix string // Optional prefix on anonymous structs. All sub-flags will have this prefix. Vars Vars
EnvPrefix string Prefix string // Optional prefix on anonymous structs. All sub-flags will have this prefix.
Embed bool EnvPrefix string
Aliases []string Embed bool
Negatable bool Aliases []string
Passthrough bool Negatable string
Passthrough bool // Deprecated: use PassthroughMode instead.
PassthroughMode PassthroughMode
// Storage for all tag keys for arbitrary lookups. // Storage for all tag keys for arbitrary lookups.
items map[string][]string items map[string][]string
@ -249,14 +263,22 @@ func hydrateTag(t *Tag, typ reflect.Type) error { //nolint: gocyclo
for _, xor := range t.GetAll("xor") { for _, xor := range t.GetAll("xor") {
t.Xor = append(t.Xor, strings.FieldsFunc(xor, tagSplitFn)...) t.Xor = append(t.Xor, strings.FieldsFunc(xor, tagSplitFn)...)
} }
for _, and := range t.GetAll("and") {
t.And = append(t.And, strings.FieldsFunc(and, tagSplitFn)...)
}
t.Prefix = t.Get("prefix") t.Prefix = t.Get("prefix")
t.EnvPrefix = t.Get("envprefix") t.EnvPrefix = t.Get("envprefix")
t.Embed = t.Has("embed") t.Embed = t.Has("embed")
negatable := t.Has("negatable") if t.Has("negatable") {
if negatable && !isBool && !isBoolPtr { if !isBool && !isBoolPtr {
return fmt.Errorf("negatable can only be set on booleans") return fmt.Errorf("negatable can only be set on booleans")
}
negatable := t.Get("negatable")
if negatable == "" {
negatable = negatableDefault // placeholder for default negation of --no-<flag>
}
t.Negatable = negatable
} }
t.Negatable = negatable
aliases := t.Get("aliases") aliases := t.Get("aliases")
if len(aliases) > 0 { if len(aliases) > 0 {
t.Aliases = append(t.Aliases, strings.FieldsFunc(aliases, tagSplitFn)...) t.Aliases = append(t.Aliases, strings.FieldsFunc(aliases, tagSplitFn)...)
@ -280,6 +302,17 @@ func hydrateTag(t *Tag, typ reflect.Type) error { //nolint: gocyclo
return fmt.Errorf("passthrough only makes sense for positional arguments or commands") return fmt.Errorf("passthrough only makes sense for positional arguments or commands")
} }
t.Passthrough = passthrough t.Passthrough = passthrough
if t.Passthrough {
passthroughMode := t.Get("passthrough")
switch passthroughMode {
case "partial":
t.PassthroughMode = PassThroughModePartial
case "all", "":
t.PassthroughMode = PassThroughModeAll
default:
return fmt.Errorf("invalid passthrough mode %q, must be one of 'partial' or 'all'", passthroughMode)
}
}
return nil return nil
} }

4
vendor/modules.txt vendored
View file

@ -31,8 +31,8 @@ github.com/PuerkitoBio/goquery
# github.com/agext/levenshtein v1.2.3 # github.com/agext/levenshtein v1.2.3
## explicit ## explicit
github.com/agext/levenshtein github.com/agext/levenshtein
# github.com/alecthomas/kong v0.9.0 # github.com/alecthomas/kong v1.6.0
## explicit; go 1.18 ## explicit; go 1.20
github.com/alecthomas/kong github.com/alecthomas/kong
# github.com/andybalholm/cascadia v1.3.2 # github.com/andybalholm/cascadia v1.3.2
## explicit; go 1.16 ## explicit; go 1.16