Merge remote-tracking branch 'upstream/master'
This commit is contained in:
@@ -13,7 +13,7 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
# These are the release channels.
|
# These are the release channels.
|
||||||
# Hermit will handle installing the right patch.
|
# Hermit will handle installing the right patch.
|
||||||
go: ["1.20", "1.21"]
|
go: ["1.23", "1.24"]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
@@ -35,7 +35,7 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
# These are versions for GitHub's setup-go.
|
# These are versions for GitHub's setup-go.
|
||||||
# '.x' will pick the latest patch release.
|
# '.x' will pick the latest patch release.
|
||||||
go: ["1.20.x", "1.21.x"]
|
go: ["1.23.x", "1.24.x"]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|||||||
+9
-13
@@ -7,21 +7,16 @@ output:
|
|||||||
linters:
|
linters:
|
||||||
enable-all: true
|
enable-all: true
|
||||||
disable:
|
disable:
|
||||||
- maligned
|
|
||||||
- lll
|
- lll
|
||||||
- gochecknoglobals
|
- gochecknoglobals
|
||||||
- wsl
|
- wsl
|
||||||
- funlen
|
- funlen
|
||||||
- gocognit
|
- gocognit
|
||||||
- gomnd
|
|
||||||
- goprintffuncname
|
- goprintffuncname
|
||||||
- paralleltest
|
- paralleltest
|
||||||
- nlreturn
|
- nlreturn
|
||||||
- goerr113
|
|
||||||
- ifshort
|
|
||||||
- testpackage
|
- testpackage
|
||||||
- wrapcheck
|
- wrapcheck
|
||||||
- exhaustivestruct
|
|
||||||
- forbidigo
|
- forbidigo
|
||||||
- gci
|
- gci
|
||||||
- godot
|
- godot
|
||||||
@@ -29,9 +24,6 @@ linters:
|
|||||||
- cyclop
|
- cyclop
|
||||||
- errorlint
|
- errorlint
|
||||||
- nestif
|
- nestif
|
||||||
- golint
|
|
||||||
- scopelint
|
|
||||||
- interfacer
|
|
||||||
- tagliatelle
|
- tagliatelle
|
||||||
- thelper
|
- thelper
|
||||||
- godox
|
- godox
|
||||||
@@ -41,16 +33,19 @@ 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
|
||||||
|
- tenv # deprecated since v1.64, but not removed yet
|
||||||
|
- mnd
|
||||||
|
- perfsprint
|
||||||
|
- err113
|
||||||
|
- copyloopvar
|
||||||
|
- intrange
|
||||||
|
- nakedret
|
||||||
|
- recvcheck # value receivers are intentionally used for copies
|
||||||
|
|
||||||
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 +71,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.
|
||||||
|
|||||||
@@ -5,16 +5,16 @@
|
|||||||
|
|
||||||
[](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)
|
[](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)
|
||||||
|
|
||||||
<!-- TOC depthfrom:2 depthto:3 -->
|
- [Version 1.0.0 Release](#version-100-release)
|
||||||
|
|
||||||
- [Introduction](#introduction)
|
- [Introduction](#introduction)
|
||||||
- [Help](#help)
|
- [Help](#help)
|
||||||
- [Help as a user of a Kong application](#help-as-a-user-of-a-kong-application)
|
- [Help as a user of a Kong application](#help-as-a-user-of-a-kong-application)
|
||||||
- [Defining help in Kong](#defining-help-in-kong)
|
- [Defining help in Kong](#defining-help-in-kong)
|
||||||
- [Command handling](#command-handling)
|
- [Command handling](#command-handling)
|
||||||
- [Switch on the command string](#switch-on-the-command-string)
|
- [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)
|
- [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)
|
- [Hooks: BeforeReset(), BeforeResolve(), BeforeApply(), AfterApply()](#hooks-beforereset-beforeresolve-beforeapply-afterapply)
|
||||||
|
- [The Bind() option](#the-bind-option)
|
||||||
- [Flags](#flags)
|
- [Flags](#flags)
|
||||||
- [Commands and sub-commands](#commands-and-sub-commands)
|
- [Commands and sub-commands](#commands-and-sub-commands)
|
||||||
- [Branching positional arguments](#branching-positional-arguments)
|
- [Branching positional arguments](#branching-positional-arguments)
|
||||||
@@ -25,22 +25,26 @@
|
|||||||
- [Nested data structure](#nested-data-structure)
|
- [Nested data structure](#nested-data-structure)
|
||||||
- [Custom named decoders](#custom-named-decoders)
|
- [Custom named decoders](#custom-named-decoders)
|
||||||
- [Supported field types](#supported-field-types)
|
- [Supported field types](#supported-field-types)
|
||||||
- [Custom decoders mappers](#custom-decoders-mappers)
|
- [Custom decoders (mappers)](#custom-decoders-mappers)
|
||||||
- [Supported tags](#supported-tags)
|
- [Supported tags](#supported-tags)
|
||||||
- [Plugins](#plugins)
|
- [Plugins](#plugins)
|
||||||
- [Dynamic Commands](#dynamic-commands)
|
- [Dynamic Commands](#dynamic-commands)
|
||||||
- [Variable interpolation](#variable-interpolation)
|
- [Variable interpolation](#variable-interpolation)
|
||||||
- [Validation](#validation)
|
- [Validation](#validation)
|
||||||
- [Modifying Kong's behaviour](#modifying-kongs-behaviour)
|
- [Modifying Kong's behaviour](#modifying-kongs-behaviour)
|
||||||
- [Namehelp and Descriptionhelp - set the application name description](#namehelp-and-descriptionhelp---set-the-application-name-description)
|
- [`Name(help)` and `Description(help)` - 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)
|
- [`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)
|
- [`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)
|
- [`*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)
|
- [`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)
|
- [Injecting values into `Run()` methods](#injecting-values-into-run-methods)
|
||||||
- [Other options](#other-options)
|
- [Other options](#other-options)
|
||||||
|
|
||||||
<!-- /TOC -->
|
## Version 1.0.0 Release
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
@@ -177,7 +181,7 @@ Flags:
|
|||||||
|
|
||||||
#### Showing an _argument_'s detailed 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))
|
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](#branching-positional-arguments))
|
||||||
|
|
||||||
**Contextual argument help**
|
**Contextual argument help**
|
||||||
|
|
||||||
@@ -302,17 +306,21 @@ func main() {
|
|||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Hooks: BeforeReset(), BeforeResolve(), BeforeApply(), AfterApply() and the Bind() option
|
## Hooks: BeforeReset(), BeforeResolve(), BeforeApply(), AfterApply()
|
||||||
|
|
||||||
If a node in the grammar has a `BeforeReset(...)`, `BeforeResolve
|
If a node in the CLI, or any of its embedded fields, implements a `BeforeReset(...) error`, `BeforeResolve
|
||||||
(...)`, `BeforeApply(...) error` and/or `AfterApply(...) error` method, those
|
(...) error`, `BeforeApply(...) error` and/or `AfterApply(...) error` method, those will be called as Kong
|
||||||
methods will be called before values are reset, before validation/assignment,
|
resets, resolves, validates, and assigns values to the node.
|
||||||
and after validation/assignment, respectively.
|
|
||||||
|
| Hook | Description |
|
||||||
|
| --------------- | ----------------------------------------------------------------------------------------------- |
|
||||||
|
| `BeforeReset` | Invoked before values are reset to their defaults (as defined by the grammar) or to zero values |
|
||||||
|
| `BeforeResolve` | Invoked before resolvers are applied to a node |
|
||||||
|
| `BeforeApply` | Invoked before the traced command line arguments are applied to the grammar |
|
||||||
|
| `AfterApply` | Invoked after command line arguments are applied to the grammar **and validated**` |
|
||||||
|
|
||||||
The `--help` flag is implemented with a `BeforeReset` 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()`.
|
|
||||||
|
|
||||||
eg.
|
eg.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
@@ -338,38 +346,42 @@ func main() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Another example of using hooks is load the env-file:
|
It's also possible to register these hooks with the functional options
|
||||||
|
`kong.WithBeforeReset`, `kong.WithBeforeResolve`, `kong.WithBeforeApply`, and
|
||||||
|
`kong.WithAfterApply`.
|
||||||
|
|
||||||
|
## The Bind() option
|
||||||
|
|
||||||
|
Arguments to hooks are provided via the `Run(...)` method or `Bind(...)` option. `*Kong`, `*Context`, `*Path` and parent commands are also bound and finally, hooks can also contribute bindings via `kong.Context.Bind()` and `kong.Context.BindTo()`.
|
||||||
|
|
||||||
|
eg:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
package main
|
type CLI struct {
|
||||||
|
Debug bool `help:"Enable debug mode."`
|
||||||
|
|
||||||
import (
|
Rm RmCmd `cmd:"" help:"Remove files."`
|
||||||
"fmt"
|
Ls LsCmd `cmd:"" help:"List paths."`
|
||||||
"git.company.lan/gopkg/kong"
|
}
|
||||||
"github.com/joho/godotenv"
|
|
||||||
)
|
|
||||||
|
|
||||||
type EnvFlag string
|
type AuthorName string
|
||||||
|
|
||||||
// BeforeResolve loads env file.
|
// ...
|
||||||
func (c EnvFlag) BeforeReset(ctx *kong.Context, trace *kong.Path) error {
|
func (l *LsCmd) Run(cli *CLI) error {
|
||||||
path := string(ctx.FlagValue(trace.Flag).(EnvFlag)) // nolint
|
// use cli.Debug here !!
|
||||||
path = kong.ExpandPath(path)
|
|
||||||
if err := godotenv.Load(path); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var CLI struct {
|
func (r *RmCmD) Run(author AuthorName) error{
|
||||||
EnvFile EnvFlag
|
// use binded author here
|
||||||
Flag `env:"FLAG"`
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
_ = kong.Parse(&CLI)
|
var cli CLI
|
||||||
fmt.Println(CLI.Flag)
|
|
||||||
}
|
ctx := kong.Parse(&cli, Bind(AuthorName("penguin")))
|
||||||
|
err := ctx.Run()
|
||||||
```
|
```
|
||||||
|
|
||||||
## Flags
|
## Flags
|
||||||
@@ -553,36 +565,44 @@ Tags can be in two forms:
|
|||||||
|
|
||||||
Both can coexist with standard Tag parsing.
|
Both can coexist with standard Tag parsing.
|
||||||
|
|
||||||
| Tag | Description |
|
| Tag | Description |
|
||||||
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||||
| `cmd:""` | If present, struct is a command. |
|
| `cmd:""` | If present, struct is a command. |
|
||||||
| `arg:""` | If present, field is an argument. Required by default. |
|
| `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. |
|
| `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. |
|
| `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 |
|
||||||
| `format:"X"` | Format for parsing input, if supported. |
|
| `negatable:"X"` | If present on a `bool` field, supports `--X` to invert the default value |
|
||||||
| `sep:"X"` | Separator for sequences (defaults to ","). May be `none` to disable splitting. |
|
| `format:"X"` | Format for parsing input, if supported. |
|
||||||
| `mapsep:"X"` | Separator for maps (defaults to ";"). May be `none` to disable splitting. |
|
| `sep:"X"` | Separator for sequences (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`. |
|
| `mapsep:"X"` | Separator for maps (defaults to ";"). May be `none` to disable splitting. |
|
||||||
| `group:"X"` | Logical group for a flag or command. |
|
| `enum:"X,Y,..."` | Set of valid values allowed for this flag. An enum field must be `required` or have a valid `default`. |
|
||||||
| `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. |
|
| `group:"X"` | Logical group for a flag or command. |
|
||||||
| `prefix:"X"` | Prefix for all sub-flags. |
|
| `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. |
|
||||||
| `envprefix:"X"` | Envar prefix for all sub-flags. |
|
| `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. |
|
||||||
| `set:"K=V"` | Set a variable for expansion by child elements. Multiples can occur. |
|
| `prefix:"X"` | Prefix for all sub-flags. |
|
||||||
| `embed:""` | If present, this field's children will be embedded in the parent. Useful for composition. |
|
| `envprefix:"X"` | Envar prefix for all sub-flags. |
|
||||||
| `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. |
|
| `xorprefix:"X"` | Prefix for all sub-flags in XOR/AND groups. |
|
||||||
| `-` | Ignore the field. Useful for adding non-CLI fields to a configuration struct. e.g `` `kong:"-"` `` |
|
| `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:"<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:"-"` `` |
|
||||||
|
|
||||||
|
[^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
|
||||||
|
|
||||||
@@ -611,8 +631,8 @@ also supports dynamically adding commands via `kong.DynamicCommand()`.
|
|||||||
|
|
||||||
## Variable interpolation
|
## Variable interpolation
|
||||||
|
|
||||||
Kong supports limited variable interpolation into help strings, enum lists and
|
Kong supports limited variable interpolation into help strings, placeholder strings,
|
||||||
default values.
|
enum lists and default values.
|
||||||
|
|
||||||
Variables are in the form:
|
Variables are in the form:
|
||||||
|
|
||||||
@@ -654,8 +674,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,12 +682,18 @@ 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.
|
||||||
|
|
||||||
## Modifying Kong's behaviour
|
## Modifying Kong's behaviour
|
||||||
|
|
||||||
Each Kong parser can be configured via functional options passed to `New(cli interface{}, options...Option)`.
|
Each Kong parser can be configured via functional options passed to `New(cli any, options...Option)`.
|
||||||
|
|
||||||
The full set of options can be found [here](https://godoc.org/github.com/alecthomas/kong#Option).
|
The full set of options can be found [here](https://godoc.org/github.com/alecthomas/kong#Option).
|
||||||
|
|
||||||
@@ -726,20 +751,25 @@ All builtin Go types (as well as a bunch of useful stdlib types like `time.Time`
|
|||||||
1. `NamedMapper(string, Mapper)` and using the tag key `type:"<name>"`.
|
1. `NamedMapper(string, Mapper)` and using the tag key `type:"<name>"`.
|
||||||
2. `KindMapper(reflect.Kind, Mapper)`.
|
2. `KindMapper(reflect.Kind, Mapper)`.
|
||||||
3. `TypeMapper(reflect.Type, Mapper)`.
|
3. `TypeMapper(reflect.Type, Mapper)`.
|
||||||
4. `ValueMapper(interface{}, Mapper)`, passing in a pointer to a field of the grammar.
|
4. `ValueMapper(any, Mapper)`, passing in a pointer to a field of the grammar.
|
||||||
|
|
||||||
### `ConfigureHelp(HelpOptions)` and `Help(HelpFunc)` - customising help
|
### `ConfigureHelp(HelpOptions)` and `Help(HelpFunc)` - customising help
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
|
|||||||
+12
-4
@@ -1,17 +1,25 @@
|
|||||||
module kong_server
|
module kong_server
|
||||||
|
|
||||||
go 1.13
|
go 1.23.0
|
||||||
|
|
||||||
|
toolchain go1.24.4
|
||||||
|
|
||||||
require (
|
require (
|
||||||
git.company.lan/gopkg/kong v0.0.0-00010101000000-000000000000
|
git.company.lan/gopkg/kong v0.0.0-00010101000000-000000000000
|
||||||
github.com/alecthomas/colour v0.1.0
|
github.com/alecthomas/colour v0.1.0
|
||||||
github.com/chzyer/readline v1.5.1
|
github.com/chzyer/readline v1.5.1
|
||||||
github.com/gliderlabs/ssh v0.3.7
|
github.com/gliderlabs/ssh v0.3.8
|
||||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
|
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
|
||||||
github.com/kr/pty v1.1.8
|
github.com/kr/pty v1.1.8
|
||||||
|
golang.org/x/term v0.32.0
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect
|
||||||
|
github.com/creack/pty v1.1.7 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.12 // indirect
|
github.com/mattn/go-isatty v0.0.12 // indirect
|
||||||
golang.org/x/crypto v0.21.0 // indirect
|
golang.org/x/crypto v0.39.0 // indirect
|
||||||
golang.org/x/term v0.18.0
|
golang.org/x/sys v0.33.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
replace git.company.lan/gopkg/kong => ../../
|
replace git.company.lan/gopkg/kong => ../../
|
||||||
|
|||||||
+10
-52
@@ -1,5 +1,5 @@
|
|||||||
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/colour v0.1.0 h1:nOE9rJm6dsZ66RGWYSFrXw461ZIt9A6+nHgL7FRrDUk=
|
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/colour v0.1.0/go.mod h1:QO9JBoKquHd+jz9nshCh40fOfO+JzsoXy8qTHF68zU0=
|
||||||
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
|
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
|
||||||
@@ -14,8 +14,8 @@ github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04=
|
|||||||
github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
|
github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
|
||||||
github.com/creack/pty v1.1.7 h1:6pwm8kMQKCmgUg0ZHTm5+/YvRK0s3THD/28+T6/kk4A=
|
github.com/creack/pty v1.1.7 h1:6pwm8kMQKCmgUg0ZHTm5+/YvRK0s3THD/28+T6/kk4A=
|
||||||
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
||||||
github.com/gliderlabs/ssh v0.3.7 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE=
|
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
|
||||||
github.com/gliderlabs/ssh v0.3.7/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8=
|
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
|
||||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
|
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
|
||||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
|
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
|
||||||
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
||||||
@@ -24,53 +24,11 @@ github.com/kr/pty v1.1.8 h1:AkaSdXYQOWeaO3neb8EM634ahkXXe3jYbVh/F9lq+GI=
|
|||||||
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
|
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
|
||||||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
|
||||||
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
|
||||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
|
||||||
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
|
|
||||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
|
||||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
|
||||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
|
||||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
|
||||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
|
||||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
|
||||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
|
||||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
|
||||||
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
|
|
||||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
|
||||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
|
||||||
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
|
|
||||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
|
||||||
golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
|
|
||||||
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
|
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
|
||||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
|
||||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
|
||||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
|
||||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
|
|||||||
@@ -7,14 +7,16 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var cli struct {
|
var cli struct {
|
||||||
Flag flagWithHelp `help:"Regular flag help"`
|
Flag flagWithHelp `help:"${flag_help}"`
|
||||||
Echo commandWithHelp `cmd:"" help:"Regular command help"`
|
Echo commandWithHelp `cmd:"" help:"Regular command help"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type flagWithHelp bool
|
type flagWithHelp bool
|
||||||
|
|
||||||
func (f *flagWithHelp) Help() string {
|
// See https://github.com/alecthomas/kong?tab=readme-ov-file#variable-interpolation
|
||||||
return "🏁 additional flag help"
|
var vars = kong.Vars{
|
||||||
|
"flag_help": "Extended flag help that might be too long for directly " +
|
||||||
|
"including in the struct tag field",
|
||||||
}
|
}
|
||||||
|
|
||||||
type commandWithHelp struct {
|
type commandWithHelp struct {
|
||||||
@@ -41,7 +43,8 @@ func main() {
|
|||||||
kong.ConfigureHelp(kong.HelpOptions{
|
kong.ConfigureHelp(kong.HelpOptions{
|
||||||
Compact: true,
|
Compact: true,
|
||||||
Summary: false,
|
Summary: false,
|
||||||
}))
|
}),
|
||||||
|
vars)
|
||||||
switch ctx.Command() {
|
switch ctx.Command() {
|
||||||
case "echo <msg>":
|
case "echo <msg>":
|
||||||
fmt.Println(cli.Echo.Msg)
|
fmt.Println(cli.Echo.Msg)
|
||||||
|
|||||||
Symlink
+1
@@ -0,0 +1 @@
|
|||||||
|
hermit
|
||||||
+1
-1
@@ -1 +1 @@
|
|||||||
.golangci-lint-1.55.2.pkg
|
.golangci-lint-1.64.5.pkg
|
||||||
Symlink
+1
@@ -0,0 +1 @@
|
|||||||
|
.lefthook-1.11.13.pkg
|
||||||
@@ -9,9 +9,9 @@ import (
|
|||||||
// Plugins are dynamically embedded command-line structures.
|
// Plugins are dynamically embedded command-line structures.
|
||||||
//
|
//
|
||||||
// Each element in the Plugins list *must* be a pointer to a structure.
|
// Each element in the Plugins list *must* be a pointer to a structure.
|
||||||
type Plugins []interface{}
|
type Plugins []any
|
||||||
|
|
||||||
func build(k *Kong, ast interface{}) (app *Application, err error) {
|
func build(k *Kong, ast any) (app *Application, err error) {
|
||||||
v := reflect.ValueOf(ast)
|
v := reflect.ValueOf(ast)
|
||||||
iv := reflect.Indirect(v)
|
iv := reflect.Indirect(v)
|
||||||
if v.Kind() != reflect.Ptr || iv.Kind() != reflect.Struct {
|
if v.Kind() != reflect.Ptr || iv.Kind() != reflect.Struct {
|
||||||
@@ -51,6 +51,10 @@ 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
|
||||||
|
}
|
||||||
|
ignored := map[string]bool{}
|
||||||
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)
|
||||||
@@ -58,7 +62,8 @@ func flattenedFields(v reflect.Value, ptag *Tag) (out []flattenedField, err erro
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if tag.Ignored {
|
if tag.Ignored || ignored[ft.Name] {
|
||||||
|
ignored[ft.Name] = true
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// Assign group if it's not already set.
|
// Assign group if it's not already set.
|
||||||
@@ -68,6 +73,7 @@ func flattenedFields(v reflect.Value, ptag *Tag) (out []flattenedField, err erro
|
|||||||
// Accumulate prefixes.
|
// Accumulate prefixes.
|
||||||
tag.Prefix = ptag.Prefix + tag.Prefix
|
tag.Prefix = ptag.Prefix + tag.Prefix
|
||||||
tag.EnvPrefix = ptag.EnvPrefix + tag.EnvPrefix
|
tag.EnvPrefix = ptag.EnvPrefix + tag.EnvPrefix
|
||||||
|
tag.XorPrefix = ptag.XorPrefix + tag.XorPrefix
|
||||||
// Combine parent vars.
|
// Combine parent vars.
|
||||||
tag.Vars = ptag.Vars.CloneWith(tag.Vars)
|
tag.Vars = ptag.Vars.CloneWith(tag.Vars)
|
||||||
// Command and embedded structs can be pointers, so we hydrate them now.
|
// Command and embedded structs can be pointers, so we hydrate them now.
|
||||||
@@ -102,13 +108,31 @@ func flattenedFields(v reflect.Value, ptag *Tag) (out []flattenedField, err erro
|
|||||||
}
|
}
|
||||||
out = append(out, sub...)
|
out = append(out, sub...)
|
||||||
}
|
}
|
||||||
|
out = removeIgnored(out, ignored)
|
||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func removeIgnored(fields []flattenedField, ignored map[string]bool) []flattenedField {
|
||||||
|
j := 0
|
||||||
|
for i := 0; i < len(fields); i++ {
|
||||||
|
if ignored[fields[i].field.Name] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if i != j {
|
||||||
|
fields[j] = fields[i]
|
||||||
|
}
|
||||||
|
j++
|
||||||
|
}
|
||||||
|
if j != len(fields) {
|
||||||
|
fields = fields[:j]
|
||||||
|
}
|
||||||
|
return fields
|
||||||
|
}
|
||||||
|
|
||||||
// Build a Node in the Kong data model.
|
// Build a Node in the Kong data model.
|
||||||
//
|
//
|
||||||
// "v" is the value to create the node from, "typ" is the output Node type.
|
// "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) {
|
func buildNode(k *Kong, v reflect.Value, typ NodeType, tag *Tag, seenFlags map[string]bool) (*Node, error) { //nolint:gocyclo
|
||||||
node := &Node{
|
node := &Node{
|
||||||
Type: typ,
|
Type: typ,
|
||||||
Target: v,
|
Target: v,
|
||||||
@@ -144,6 +168,18 @@ MAIN:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(tag.Xor) != 0 {
|
||||||
|
for i := range tag.Xor {
|
||||||
|
tag.Xor[i] = tag.XorPrefix + tag.Xor[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(tag.And) != 0 {
|
||||||
|
for i := range tag.And {
|
||||||
|
tag.And[i] = tag.XorPrefix + tag.And[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Nested structs are either commands or args, unless they implement the Mapper interface.
|
// Nested structs are either commands or args, unless they implement the Mapper interface.
|
||||||
if field.value.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
|
typ := CommandNode
|
||||||
@@ -170,6 +206,9 @@ 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 {
|
for _, aflag := range flag.Aliases {
|
||||||
delete(seenFlags, "--"+aflag)
|
delete(seenFlags, "--"+aflag)
|
||||||
}
|
}
|
||||||
@@ -275,17 +314,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),
|
||||||
@@ -312,6 +352,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,
|
||||||
@@ -320,6 +367,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
|
||||||
|
|||||||
+143
-41
@@ -6,7 +6,59 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type bindings map[reflect.Type]func() (reflect.Value, error)
|
// binding is a single binding registered with Kong.
|
||||||
|
type binding struct {
|
||||||
|
// fn is a function that returns a value of the target type.
|
||||||
|
fn reflect.Value
|
||||||
|
|
||||||
|
// val is a value of the target type.
|
||||||
|
// Must be set if done and singleton are true.
|
||||||
|
val reflect.Value
|
||||||
|
|
||||||
|
// singleton indicates whether the binding is a singleton.
|
||||||
|
// If true, the binding will be resolved once and cached.
|
||||||
|
singleton bool
|
||||||
|
|
||||||
|
// done indicates whether a singleton binding has been resolved.
|
||||||
|
// If singleton is false, this field is ignored.
|
||||||
|
done bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// newValueBinding builds a binding with an already resolved value.
|
||||||
|
func newValueBinding(v reflect.Value) *binding {
|
||||||
|
return &binding{val: v, done: true, singleton: true}
|
||||||
|
}
|
||||||
|
|
||||||
|
// newFunctionBinding builds a binding with a function
|
||||||
|
// that will return a value of the target type.
|
||||||
|
//
|
||||||
|
// The function signature must be func(...) (T, error) or func(...) T
|
||||||
|
// where parameters are recursively resolved.
|
||||||
|
func newFunctionBinding(f reflect.Value, singleton bool) *binding {
|
||||||
|
return &binding{fn: f, singleton: singleton}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns the pre-resolved value for the binding,
|
||||||
|
// or false if the binding is not resolved.
|
||||||
|
func (b *binding) Get() (v reflect.Value, ok bool) {
|
||||||
|
return b.val, b.done
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set sets the value of the binding to the given value,
|
||||||
|
// marking it as resolved.
|
||||||
|
//
|
||||||
|
// If the binding is not a singleton, this method does nothing.
|
||||||
|
func (b *binding) Set(v reflect.Value) {
|
||||||
|
if b.singleton {
|
||||||
|
b.val = v
|
||||||
|
b.done = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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]*binding
|
||||||
|
|
||||||
func (b bindings) String() string {
|
func (b bindings) String() string {
|
||||||
out := []string{}
|
out := []string{}
|
||||||
@@ -16,35 +68,36 @@ func (b bindings) String() string {
|
|||||||
return "bindings{" + strings.Join(out, ", ") + "}"
|
return "bindings{" + strings.Join(out, ", ") + "}"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b bindings) add(values ...interface{}) bindings {
|
func (b bindings) add(values ...any) bindings {
|
||||||
for _, v := range values {
|
for _, v := range values {
|
||||||
v := v
|
val := reflect.ValueOf(v)
|
||||||
b[reflect.TypeOf(v)] = func() (reflect.Value, error) { return reflect.ValueOf(v), nil }
|
b[val.Type()] = newValueBinding(val)
|
||||||
}
|
}
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b bindings) addTo(impl, iface interface{}) {
|
func (b bindings) addTo(impl, iface any) {
|
||||||
valueOf := reflect.ValueOf(impl)
|
val := reflect.ValueOf(impl)
|
||||||
b[reflect.TypeOf(iface).Elem()] = func() (reflect.Value, error) { return valueOf, nil }
|
b[reflect.TypeOf(iface).Elem()] = newValueBinding(val)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b bindings) addProvider(provider interface{}) error {
|
func (b bindings) addProvider(provider any, singleton bool) 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 {
|
||||||
return fmt.Errorf("%T must be a function with the signature func()(T, error)", provider)
|
return fmt.Errorf("%T must be a function", provider)
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.NumOut() == 0 {
|
||||||
|
return fmt.Errorf("%T must be a function with the signature func(...)(T, error) or func(...) T", provider)
|
||||||
|
}
|
||||||
|
if t.NumOut() == 2 {
|
||||||
|
if t.Out(1) != reflect.TypeOf((*error)(nil)).Elem() {
|
||||||
|
return fmt.Errorf("missing error; %T must be a function with the signature func(...)(T, error) or func(...) T", provider)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
rt := pv.Type().Out(0)
|
rt := pv.Type().Out(0)
|
||||||
b[rt] = func() (reflect.Value, error) {
|
b[rt] = newFunctionBinding(pv, singleton)
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,32 +127,67 @@ func getMethod(value reflect.Value, name string) reflect.Value {
|
|||||||
return method
|
return method
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getMethods gets all methods with the given name from the given value
|
||||||
|
// and any embedded fields.
|
||||||
|
//
|
||||||
|
// Returns a slice of bound methods that can be called directly.
|
||||||
|
func getMethods(value reflect.Value, name string) (methods []reflect.Value) {
|
||||||
|
if value.Kind() == reflect.Ptr {
|
||||||
|
value = value.Elem()
|
||||||
|
}
|
||||||
|
if !value.IsValid() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if method := getMethod(value, name); method.IsValid() {
|
||||||
|
methods = append(methods, method)
|
||||||
|
}
|
||||||
|
|
||||||
|
if value.Kind() != reflect.Struct {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// If the current value is a struct, also consider embedded fields.
|
||||||
|
// Two kinds of embedded fields are considered if they're exported:
|
||||||
|
//
|
||||||
|
// - standard Go embedded fields
|
||||||
|
// - fields tagged with `embed:""`
|
||||||
|
t := value.Type()
|
||||||
|
for i := 0; i < value.NumField(); i++ {
|
||||||
|
fieldValue := value.Field(i)
|
||||||
|
field := t.Field(i)
|
||||||
|
|
||||||
|
if !field.IsExported() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Consider a field embedded if it's actually embedded
|
||||||
|
// or if it's tagged with `embed:""`.
|
||||||
|
_, isEmbedded := field.Tag.Lookup("embed")
|
||||||
|
isEmbedded = isEmbedded || field.Anonymous
|
||||||
|
if isEmbedded {
|
||||||
|
methods = append(methods, getMethods(fieldValue, name)...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func callFunction(f reflect.Value, bindings bindings) error {
|
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 +198,29 @@ 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 {
|
binding, 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Don't need to call the function if the value is already resolved.
|
||||||
|
if val, ok := binding.Get(); ok {
|
||||||
|
in = append(in, val)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recursively resolve binding functions.
|
||||||
|
argv, err := callAnyFunction(binding.fn, bindings)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%s: %w", pt, err)
|
||||||
|
}
|
||||||
|
if ferrv := reflect.ValueOf(argv[len(argv)-1]); ferrv.IsValid() && ferrv.Type().Implements(callbackReturnSignature) && !ferrv.IsNil() {
|
||||||
|
return nil, ferrv.Interface().(error) //nolint:forcetypeassert
|
||||||
|
}
|
||||||
|
|
||||||
|
val := reflect.ValueOf(argv[0])
|
||||||
|
binding.Set(val)
|
||||||
|
in = append(in, val)
|
||||||
}
|
}
|
||||||
outv := f.Call(in)
|
outv := f.Call(in)
|
||||||
out = make([]any, len(outv))
|
out = make([]any, len(outv))
|
||||||
|
|||||||
+6
-9
@@ -15,12 +15,10 @@ func TestMultipleConfigLoading(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cli.Flag = "first"
|
cli.Flag = "first"
|
||||||
first, cleanFirst := makeConfig(t, &cli)
|
first := makeConfig(t, &cli)
|
||||||
defer cleanFirst()
|
|
||||||
|
|
||||||
cli.Flag = ""
|
cli.Flag = ""
|
||||||
second, cleanSecond := makeConfig(t, &cli)
|
second := makeConfig(t, &cli)
|
||||||
defer cleanSecond()
|
|
||||||
|
|
||||||
p := mustNew(t, &cli, kong.Configuration(kong.JSON, first, second))
|
p := mustNew(t, &cli, kong.Configuration(kong.JSON, first, second))
|
||||||
_, err := p.Parse(nil)
|
_, err := p.Parse(nil)
|
||||||
@@ -34,20 +32,19 @@ func TestConfigValidation(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cli.Flag = "invalid"
|
cli.Flag = "invalid"
|
||||||
conf, cleanConf := makeConfig(t, &cli)
|
conf := makeConfig(t, &cli)
|
||||||
defer cleanConf()
|
|
||||||
|
|
||||||
p := mustNew(t, &cli, kong.Configuration(kong.JSON, conf))
|
p := mustNew(t, &cli, kong.Configuration(kong.JSON, conf))
|
||||||
_, err := p.Parse(nil)
|
_, err := p.Parse(nil)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeConfig(t *testing.T, config interface{}) (path string, cleanup func()) {
|
func makeConfig(t *testing.T, config any) (path string) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
w, err := os.CreateTemp("", "")
|
w, err := os.CreateTemp(t.TempDir(), "")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
defer w.Close()
|
defer w.Close()
|
||||||
err = json.NewEncoder(w).Encode(config)
|
err = json.NewEncoder(w).Encode(config)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
return w.Name(), func() { os.Remove(w.Name()) }
|
return w.Name()
|
||||||
}
|
}
|
||||||
|
|||||||
+220
-56
@@ -26,6 +26,9 @@ type Path struct {
|
|||||||
|
|
||||||
// True if this Path element was created as the result of a resolver.
|
// True if this Path element was created as the result of a resolver.
|
||||||
Resolved bool
|
Resolved bool
|
||||||
|
|
||||||
|
// Remaining tokens after this node
|
||||||
|
remainder []Token
|
||||||
}
|
}
|
||||||
|
|
||||||
// Node returns the Node associated with this Path, or nil if Path is a non-Node.
|
// Node returns the Node associated with this Path, or nil if Path is a non-Node.
|
||||||
@@ -64,6 +67,15 @@ func (p *Path) Visitable() Visitable {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remainder returns the remaining unparsed args after this Path element.
|
||||||
|
func (p *Path) Remainder() []string {
|
||||||
|
args := []string{}
|
||||||
|
for _, token := range p.remainder {
|
||||||
|
args = append(args, token.String())
|
||||||
|
}
|
||||||
|
return args
|
||||||
|
}
|
||||||
|
|
||||||
// Context contains the current parse context.
|
// Context contains the current parse context.
|
||||||
type Context struct {
|
type Context struct {
|
||||||
*Kong
|
*Kong
|
||||||
@@ -87,14 +99,15 @@ type Context struct {
|
|||||||
// This just constructs a new trace. To fully apply the trace you must call Reset(), Resolve(),
|
// This just constructs a new trace. To fully apply the trace you must call Reset(), Resolve(),
|
||||||
// Validate() and Apply().
|
// Validate() and Apply().
|
||||||
func Trace(k *Kong, args []string) (*Context, error) {
|
func Trace(k *Kong, args []string) (*Context, error) {
|
||||||
|
s := Scan(args...).AllowHyphenPrefixedParameters(k.allowHyphenated)
|
||||||
c := &Context{
|
c := &Context{
|
||||||
Kong: k,
|
Kong: k,
|
||||||
Args: args,
|
Args: args,
|
||||||
Path: []*Path{
|
Path: []*Path{
|
||||||
{App: k.Model, Flags: k.Model.Flags},
|
{App: k.Model, Flags: k.Model.Flags, remainder: s.PeekAll()},
|
||||||
},
|
},
|
||||||
values: map[*Value]reflect.Value{},
|
values: map[*Value]reflect.Value{},
|
||||||
scan: Scan(args...),
|
scan: s,
|
||||||
bindings: bindings{},
|
bindings: bindings{},
|
||||||
}
|
}
|
||||||
c.Error = c.trace(c.Model.Node)
|
c.Error = c.trace(c.Model.Node)
|
||||||
@@ -102,7 +115,7 @@ func Trace(k *Kong, args []string) (*Context, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Bind adds bindings to the Context.
|
// Bind adds bindings to the Context.
|
||||||
func (c *Context) Bind(args ...interface{}) {
|
func (c *Context) Bind(args ...any) {
|
||||||
c.bindings.add(args...)
|
c.bindings.add(args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,7 +124,7 @@ func (c *Context) Bind(args ...interface{}) {
|
|||||||
// This will typically have to be called like so:
|
// This will typically have to be called like so:
|
||||||
//
|
//
|
||||||
// BindTo(impl, (*MyInterface)(nil))
|
// BindTo(impl, (*MyInterface)(nil))
|
||||||
func (c *Context) BindTo(impl, iface interface{}) {
|
func (c *Context) BindTo(impl, iface any) {
|
||||||
c.bindings.addTo(impl, iface)
|
c.bindings.addTo(impl, iface)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,8 +132,20 @@ func (c *Context) BindTo(impl, iface interface{}) {
|
|||||||
//
|
//
|
||||||
// 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.
|
||||||
func (c *Context) BindToProvider(provider interface{}) error {
|
//
|
||||||
return c.bindings.addProvider(provider)
|
// "provider" must be a function with the signature func(...) (T, error) or func(...) T,
|
||||||
|
// where ... will be recursively injected with bound values.
|
||||||
|
func (c *Context) BindToProvider(provider any) error {
|
||||||
|
return c.bindings.addProvider(provider, false /* singleton */)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BindSingletonProvider allows binding of provider functions.
|
||||||
|
// The provider will be called once and the result cached.
|
||||||
|
//
|
||||||
|
// "provider" must be a function with the signature func(...) (T, error) or func(...) T,
|
||||||
|
// where ... will be recursively injected with bound values.
|
||||||
|
func (c *Context) BindSingletonProvider(provider any) error {
|
||||||
|
return c.bindings.addProvider(provider, true /* singleton */)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Value returns the value for a particular path element.
|
// Value returns the value for a particular path element.
|
||||||
@@ -208,7 +233,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 +284,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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -306,7 +331,7 @@ func (c *Context) AddResolver(resolver Resolver) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// FlagValue returns the set value of a flag if it was encountered and exists, or its default value.
|
// FlagValue returns the set value of a flag if it was encountered and exists, or its default value.
|
||||||
func (c *Context) FlagValue(flag *Flag) interface{} {
|
func (c *Context) FlagValue(flag *Flag) any {
|
||||||
for _, trace := range c.Path {
|
for _, trace := range c.Path {
|
||||||
if trace.Flag == flag {
|
if trace.Flag == flag {
|
||||||
v, ok := c.values[trace.Flag.Value]
|
v, ok := c.values[trace.Flag.Value]
|
||||||
@@ -347,6 +372,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 +409,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 +450,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:
|
||||||
@@ -450,6 +490,7 @@ func (c *Context) trace(node *Node) (err error) { //nolint: gocyclo
|
|||||||
c.Path = append(c.Path, &Path{
|
c.Path = append(c.Path, &Path{
|
||||||
Parent: node,
|
Parent: node,
|
||||||
Positional: arg,
|
Positional: arg,
|
||||||
|
remainder: c.scan.PeekAll(),
|
||||||
})
|
})
|
||||||
positional++
|
positional++
|
||||||
break
|
break
|
||||||
@@ -481,9 +522,10 @@ func (c *Context) trace(node *Node) (err error) { //nolint: gocyclo
|
|||||||
if branch.Type == CommandNode && branch.Name == token.Value {
|
if branch.Type == CommandNode && branch.Name == token.Value {
|
||||||
c.scan.Pop()
|
c.scan.Pop()
|
||||||
c.Path = append(c.Path, &Path{
|
c.Path = append(c.Path, &Path{
|
||||||
Parent: node,
|
Parent: node,
|
||||||
Command: branch,
|
Command: branch,
|
||||||
Flags: branch.Flags,
|
Flags: branch.Flags,
|
||||||
|
remainder: c.scan.PeekAll(),
|
||||||
})
|
})
|
||||||
return c.trace(branch)
|
return c.trace(branch)
|
||||||
}
|
}
|
||||||
@@ -495,9 +537,10 @@ func (c *Context) trace(node *Node) (err error) { //nolint: gocyclo
|
|||||||
arg := branch.Argument
|
arg := branch.Argument
|
||||||
if err := arg.Parse(c.scan, c.getValue(arg)); err == nil {
|
if err := arg.Parse(c.scan, c.getValue(arg)); err == nil {
|
||||||
c.Path = append(c.Path, &Path{
|
c.Path = append(c.Path, &Path{
|
||||||
Parent: node,
|
Parent: node,
|
||||||
Argument: branch,
|
Argument: branch,
|
||||||
Flags: branch.Flags,
|
Flags: branch.Flags,
|
||||||
|
remainder: c.scan.PeekAll(),
|
||||||
})
|
})
|
||||||
return c.trace(branch)
|
return c.trace(branch)
|
||||||
}
|
}
|
||||||
@@ -508,9 +551,10 @@ func (c *Context) trace(node *Node) (err error) { //nolint: gocyclo
|
|||||||
// matches, take the branch of the default command
|
// matches, take the branch of the default command
|
||||||
if node.DefaultCmd != nil && node.DefaultCmd.Tag.Default == "withargs" {
|
if node.DefaultCmd != nil && node.DefaultCmd.Tag.Default == "withargs" {
|
||||||
c.Path = append(c.Path, &Path{
|
c.Path = append(c.Path, &Path{
|
||||||
Parent: node,
|
Parent: node,
|
||||||
Command: node.DefaultCmd,
|
Command: node.DefaultCmd,
|
||||||
Flags: node.DefaultCmd.Flags,
|
Flags: node.DefaultCmd.Flags,
|
||||||
|
remainder: c.scan.PeekAll(),
|
||||||
})
|
})
|
||||||
return c.trace(node.DefaultCmd)
|
return c.trace(node.DefaultCmd)
|
||||||
}
|
}
|
||||||
@@ -523,19 +567,25 @@ func (c *Context) trace(node *Node) (err error) { //nolint: gocyclo
|
|||||||
return c.maybeSelectDefault(flags, node)
|
return c.maybeSelectDefault(flags, node)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IgnoreDefault can be implemented by flags that want to be applied before any default commands.
|
||||||
|
type IgnoreDefault interface {
|
||||||
|
IgnoreDefault()
|
||||||
|
}
|
||||||
|
|
||||||
// End of the line, check for a default command, but only if we're not displaying help,
|
// End of the line, check for a default command, but only if we're not displaying help,
|
||||||
// otherwise we'd only ever display the help for the default command.
|
// otherwise we'd only ever display the help for the default command.
|
||||||
func (c *Context) maybeSelectDefault(flags []*Flag, node *Node) error {
|
func (c *Context) maybeSelectDefault(flags []*Flag, node *Node) error {
|
||||||
for _, flag := range flags {
|
for _, flag := range flags {
|
||||||
if flag.Name == "help" && flag.Set {
|
if _, ok := flag.Target.Interface().(IgnoreDefault); ok && flag.Set {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if node.DefaultCmd != nil {
|
if node.DefaultCmd != nil {
|
||||||
c.Path = append(c.Path, &Path{
|
c.Path = append(c.Path, &Path{
|
||||||
Parent: node.DefaultCmd,
|
Parent: node.DefaultCmd,
|
||||||
Command: node.DefaultCmd,
|
Command: node.DefaultCmd,
|
||||||
Flags: node.DefaultCmd.Flags,
|
Flags: node.DefaultCmd.Flags,
|
||||||
|
remainder: c.scan.PeekAll(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -557,7 +607,7 @@ func (c *Context) Resolve() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Pick the last resolved value.
|
// Pick the last resolved value.
|
||||||
var selected interface{}
|
var selected any
|
||||||
for _, resolver := range resolvers {
|
for _, resolver := range resolvers {
|
||||||
s, err := resolver.Resolve(c, path, flag)
|
s, err := resolver.Resolve(c, path, flag)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -580,8 +630,9 @@ func (c *Context) Resolve() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
inserted = append(inserted, &Path{
|
inserted = append(inserted, &Path{
|
||||||
Flag: flag,
|
Flag: flag,
|
||||||
Resolved: true,
|
Resolved: true,
|
||||||
|
remainder: c.scan.PeekAll(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -700,13 +751,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))
|
||||||
@@ -725,16 +776,29 @@ func (c *Context) parseFlag(flags []*Flag, match string) (err error) {
|
|||||||
}
|
}
|
||||||
flag.Value.Apply(value)
|
flag.Value.Apply(value)
|
||||||
}
|
}
|
||||||
c.Path = append(c.Path, &Path{Flag: flag})
|
c.Path = append(c.Path, &Path{
|
||||||
|
Flag: flag,
|
||||||
|
remainder: c.scan.PeekAll(),
|
||||||
|
})
|
||||||
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 ...any) (out []any, 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -744,7 +808,7 @@ func (c *Context) Call(fn any, binds ...interface{}) (out []interface{}, err err
|
|||||||
//
|
//
|
||||||
// Any passed values will be bindable to arguments of the target Run() method. Additionally,
|
// Any passed values will be bindable to arguments of the target Run() method. Additionally,
|
||||||
// all parent nodes in the command structure will be bound.
|
// all parent nodes in the command structure will be bound.
|
||||||
func (c *Context) RunNode(node *Node, binds ...interface{}) (err error) {
|
func (c *Context) RunNode(node *Node, binds ...any) (err error) {
|
||||||
type targetMethod struct {
|
type targetMethod struct {
|
||||||
node *Node
|
node *Node
|
||||||
method reflect.Value
|
method reflect.Value
|
||||||
@@ -757,6 +821,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(), false /* singleton */); 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})
|
||||||
@@ -765,11 +842,6 @@ func (c *Context) RunNode(node *Node, binds ...interface{}) (err error) {
|
|||||||
if len(methods) == 0 {
|
if len(methods) == 0 {
|
||||||
return fmt.Errorf("no Run() method found in hierarchy of %s", c.Selected().Summary())
|
return fmt.Errorf("no Run() method found in hierarchy of %s", c.Selected().Summary())
|
||||||
}
|
}
|
||||||
_, err = c.Apply()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, method := range methods {
|
for _, method := range methods {
|
||||||
if err = callFunction(method.method, method.binds); err != nil {
|
if err = callFunction(method.method, method.binds); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -782,21 +854,27 @@ func (c *Context) RunNode(node *Node, binds ...interface{}) (err error) {
|
|||||||
//
|
//
|
||||||
// Any passed values will be bindable to arguments of the target Run() method. Additionally,
|
// Any passed values will be bindable to arguments of the target Run() method. Additionally,
|
||||||
// all parent nodes in the command structure will be bound.
|
// all parent nodes in the command structure will be bound.
|
||||||
func (c *Context) Run(binds ...interface{}) (err error) {
|
func (c *Context) Run(binds ...any) (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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return fmt.Errorf("no command selected")
|
|
||||||
|
if node == nil {
|
||||||
|
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 +889,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 +927,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 +942,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 +990,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 +1050,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 +1064,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,7 +1096,39 @@ func checkXorDuplicates(paths []*Path) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func findPotentialCandidates(needle string, haystack []string, format string, args ...interface{}) error {
|
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 ...any) error {
|
||||||
if len(haystack) == 0 {
|
if len(haystack) == 0 {
|
||||||
return fmt.Errorf(format, args...)
|
return fmt.Errorf(format, args...)
|
||||||
}
|
}
|
||||||
@@ -995,12 +1148,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() {
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
package kong
|
package kong
|
||||||
|
|
||||||
// ApplyDefaults if they are not already set.
|
// ApplyDefaults if they are not already set.
|
||||||
func ApplyDefaults(target interface{}, options ...Option) error {
|
func ApplyDefaults(target any, options ...Option) error {
|
||||||
app, err := New(target, options...)
|
app, err := New(target, options...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -5,8 +5,17 @@ package kong
|
|||||||
// It contains the parse Context that triggered the error.
|
// It contains the parse Context that triggered the error.
|
||||||
type ParseError struct {
|
type ParseError struct {
|
||||||
error
|
error
|
||||||
Context *Context
|
Context *Context
|
||||||
|
exitCode int
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unwrap returns the original cause of the error.
|
// Unwrap returns the original cause of the error.
|
||||||
func (p *ParseError) Unwrap() error { return p.error }
|
func (p *ParseError) Unwrap() error { return p.error }
|
||||||
|
|
||||||
|
// ExitCode returns the status that Kong should exit with if it fails with a ParseError.
|
||||||
|
func (p *ParseError) ExitCode() int {
|
||||||
|
if p.exitCode == 0 {
|
||||||
|
return exitNotOk
|
||||||
|
}
|
||||||
|
return p.exitCode
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package kong
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
const (
|
||||||
|
exitOk = 0
|
||||||
|
exitNotOk = 1
|
||||||
|
|
||||||
|
// Semantic exit codes from https://github.com/square/exit?tab=readme-ov-file#about
|
||||||
|
exitUsageError = 80
|
||||||
|
)
|
||||||
|
|
||||||
|
// ExitCoder is an interface that may be implemented by an error value to
|
||||||
|
// provide an integer exit code. The method ExitCode should return an integer
|
||||||
|
// that is intended to be used as the exit code for the application.
|
||||||
|
type ExitCoder interface {
|
||||||
|
ExitCode() int
|
||||||
|
}
|
||||||
|
|
||||||
|
// exitCodeFromError returns the exit code for the given error.
|
||||||
|
// If err implements the exitCoder interface, the ExitCode method is called.
|
||||||
|
// Otherwise, exitCodeFromError returns 0 if err is nil, and 1 if it is not.
|
||||||
|
func exitCodeFromError(err error) int {
|
||||||
|
var e ExitCoder
|
||||||
|
if errors.As(err, &e) {
|
||||||
|
return e.ExitCode()
|
||||||
|
} else if err == nil {
|
||||||
|
return exitOk
|
||||||
|
}
|
||||||
|
|
||||||
|
return exitNotOk
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Parse constructs a new parser and parses the default command-line.
|
// Parse constructs a new parser and parses the default command-line.
|
||||||
func Parse(cli interface{}, options ...Option) *Context {
|
func Parse(cli any, options ...Option) *Context {
|
||||||
parser, err := New(cli, options...)
|
parser, err := New(cli, options...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
module git.company.lan/gopkg/kong
|
module git.company.lan/gopkg/kong
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/alecthomas/assert/v2 v2.6.0
|
github.com/alecthomas/assert/v2 v2.11.0
|
||||||
github.com/alecthomas/repr v0.4.0
|
github.com/alecthomas/repr v0.4.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require github.com/hexops/gotextdiff v1.0.3 // indirect
|
require github.com/hexops/gotextdiff v1.0.3 // indirect
|
||||||
|
|
||||||
go 1.18
|
go 1.20
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
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/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/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
||||||
|
|||||||
+1
-2
@@ -1,5 +1,4 @@
|
|||||||
//go:build appengine || (!linux && !freebsd && !darwin && !dragonfly && !netbsd && !openbsd)
|
//go:build tinygo || appengine || (!linux && !freebsd && !darwin && !dragonfly && !netbsd && !openbsd)
|
||||||
// +build appengine !linux,!freebsd,!darwin,!dragonfly,!netbsd,!openbsd
|
|
||||||
|
|
||||||
package kong
|
package kong
|
||||||
|
|
||||||
|
|||||||
+1
-2
@@ -1,5 +1,4 @@
|
|||||||
//go:build (!appengine && linux) || freebsd || darwin || dragonfly || netbsd || openbsd
|
//go:build !tinygo && ((!appengine && linux) || freebsd || darwin || dragonfly || netbsd || openbsd)
|
||||||
// +build !appengine,linux freebsd darwin dragonfly netbsd openbsd
|
|
||||||
|
|
||||||
package kong
|
package kong
|
||||||
|
|
||||||
|
|||||||
@@ -14,9 +14,11 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Help flag.
|
// Help flag.
|
||||||
type helpValue bool
|
type helpFlag bool
|
||||||
|
|
||||||
func (h helpValue) BeforeReset(ctx *Context) error {
|
func (h helpFlag) IgnoreDefault() {}
|
||||||
|
|
||||||
|
func (h helpFlag) BeforeReset(ctx *Context) error {
|
||||||
options := ctx.Kong.helpOptions
|
options := ctx.Kong.helpOptions
|
||||||
options.Summary = false
|
options.Summary = false
|
||||||
err := ctx.Kong.help(options, ctx)
|
err := ctx.Kong.help(options, ctx)
|
||||||
@@ -386,7 +388,7 @@ func newHelpWriter(ctx *Context, options HelpOptions) *helpWriter {
|
|||||||
return w
|
return w
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *helpWriter) Printf(format string, args ...interface{}) {
|
func (h *helpWriter) Printf(format string, args ...any) {
|
||||||
h.Print(fmt.Sprintf(format, args...))
|
h.Print(fmt.Sprintf(format, args...))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -415,7 +417,7 @@ func (h *helpWriter) Write(w io.Writer) error {
|
|||||||
|
|
||||||
func (h *helpWriter) Wrap(text string) {
|
func (h *helpWriter) Wrap(text string) {
|
||||||
w := bytes.NewBuffer(nil)
|
w := bytes.NewBuffer(nil)
|
||||||
doc.ToText(w, strings.TrimSpace(text), "", " ", h.width)
|
doc.ToText(w, strings.TrimSpace(text), "", " ", h.width) //nolint:staticcheck // cross-package links not possible
|
||||||
for _, line := range strings.Split(strings.TrimSpace(w.String()), "\n") {
|
for _, line := range strings.Split(strings.TrimSpace(w.String()), "\n") {
|
||||||
h.Print(line)
|
h.Print(line)
|
||||||
}
|
}
|
||||||
@@ -470,7 +472,7 @@ func writeTwoColumns(w *helpWriter, rows [][2]string) {
|
|||||||
|
|
||||||
for _, row := range rows {
|
for _, row := range rows {
|
||||||
buf := bytes.NewBuffer(nil)
|
buf := bytes.NewBuffer(nil)
|
||||||
doc.ToText(buf, row[1], "", strings.Repeat(" ", defaultIndent), w.width-leftSize-defaultColumnPadding)
|
doc.ToText(buf, row[1], "", strings.Repeat(" ", defaultIndent), w.width-leftSize-defaultColumnPadding) //nolint:staticcheck // cross-package links not possible
|
||||||
lines := strings.Split(strings.TrimRight(buf.String(), "\n"), "\n")
|
lines := strings.Split(strings.TrimRight(buf.String(), "\n"), "\n")
|
||||||
|
|
||||||
line := fmt.Sprintf("%-*s", leftSize, row[0])
|
line := fmt.Sprintf("%-*s", leftSize, row[0])
|
||||||
@@ -491,27 +493,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())
|
||||||
}
|
}
|
||||||
|
|||||||
+24
-19
@@ -51,7 +51,7 @@ func TestHelpOptionalArgs(t *testing.T) {
|
|||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
})
|
})
|
||||||
assert.True(t, exited)
|
assert.True(t, exited)
|
||||||
expected := `Usage: test-app [<one> [<two>]] [flags]
|
expected := `Usage: test-app [<one> [<two>]]
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
[<one>] One optional arg.
|
[<one>] One optional arg.
|
||||||
@@ -71,6 +71,7 @@ func TestHelp(t *testing.T) {
|
|||||||
Map map[string]int `help:"A map of strings to ints."`
|
Map map[string]int `help:"A map of strings to ints."`
|
||||||
Required bool `required help:"A required flag."`
|
Required bool `required help:"A required flag."`
|
||||||
Sort bool `negatable short:"s" help:"Is sortable or not."`
|
Sort bool `negatable short:"s" help:"Is sortable or not."`
|
||||||
|
Approve bool `negatable:"deny" help:"Approve or deny message."`
|
||||||
|
|
||||||
One struct {
|
One struct {
|
||||||
Flag string `help:"Nested flag."`
|
Flag string `help:"Nested flag."`
|
||||||
@@ -82,8 +83,7 @@ func TestHelp(t *testing.T) {
|
|||||||
|
|
||||||
Three threeArg `arg help:"Sub-sub-arg."`
|
Three threeArg `arg help:"Sub-sub-arg."`
|
||||||
|
|
||||||
Four struct {
|
Four struct{} `cmd help:"Sub-sub-command."`
|
||||||
} `cmd help:"Sub-sub-command."`
|
|
||||||
} `cmd help:"Another subcommand."`
|
} `cmd help:"Another subcommand."`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,6 +118,7 @@ Flags:
|
|||||||
--map=KEY=VALUE;... A map of strings to ints.
|
--map=KEY=VALUE;... A map of strings to ints.
|
||||||
--required A required flag.
|
--required A required flag.
|
||||||
-s, --[no-]sort Is sortable or not.
|
-s, --[no-]sort Is sortable or not.
|
||||||
|
--approve/deny Approve or deny message.
|
||||||
|
|
||||||
Commands:
|
Commands:
|
||||||
one --required [flags]
|
one --required [flags]
|
||||||
@@ -159,6 +160,7 @@ Flags:
|
|||||||
--map=KEY=VALUE;... A map of strings to ints.
|
--map=KEY=VALUE;... A map of strings to ints.
|
||||||
--required A required flag.
|
--required A required flag.
|
||||||
-s, --[no-]sort Is sortable or not.
|
-s, --[no-]sort Is sortable or not.
|
||||||
|
--approve/deny Approve or deny message.
|
||||||
|
|
||||||
--flag=STRING Nested flag under two.
|
--flag=STRING Nested flag under two.
|
||||||
--required-two
|
--required-two
|
||||||
@@ -189,8 +191,7 @@ func TestFlagsLast(t *testing.T) {
|
|||||||
|
|
||||||
Three threeArg `arg help:"Sub-sub-arg."`
|
Three threeArg `arg help:"Sub-sub-arg."`
|
||||||
|
|
||||||
Four struct {
|
Four struct{} `cmd help:"Sub-sub-command."`
|
||||||
} `cmd help:"Sub-sub-command."`
|
|
||||||
} `cmd help:"Another subcommand."`
|
} `cmd help:"Another subcommand."`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -293,8 +294,7 @@ func TestHelpTree(t *testing.T) {
|
|||||||
Two struct {
|
Two struct {
|
||||||
Three threeArg `arg help:"Sub-sub-arg."`
|
Three threeArg `arg help:"Sub-sub-arg."`
|
||||||
|
|
||||||
Four struct {
|
Four struct{} `cmd help:"Sub-sub-command." aliases:"for,fore"`
|
||||||
} `cmd help:"Sub-sub-command." aliases:"for,fore"`
|
|
||||||
} `cmd help:"Another subcommand."`
|
} `cmd help:"Another subcommand."`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -320,7 +320,7 @@ func TestHelpTree(t *testing.T) {
|
|||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
})
|
})
|
||||||
assert.True(t, exited)
|
assert.True(t, exited)
|
||||||
expected := `Usage: test-app <command> [flags]
|
expected := `Usage: test-app <command>
|
||||||
|
|
||||||
A test app.
|
A test app.
|
||||||
|
|
||||||
@@ -353,7 +353,7 @@ Run "test-app <command> --help" for more information on a command.
|
|||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
})
|
})
|
||||||
assert.True(t, exited)
|
assert.True(t, exited)
|
||||||
expected := `Usage: test-app one (un,uno) <command> [flags]
|
expected := `Usage: test-app one (un,uno) <command>
|
||||||
|
|
||||||
subcommand one
|
subcommand one
|
||||||
|
|
||||||
@@ -387,8 +387,7 @@ func TestHelpCompactNoExpand(t *testing.T) {
|
|||||||
Two struct {
|
Two struct {
|
||||||
Three threeArg `arg help:"Sub-sub-arg."`
|
Three threeArg `arg help:"Sub-sub-arg."`
|
||||||
|
|
||||||
Four struct {
|
Four struct{} `cmd help:"Sub-sub-command." aliases:"for,fore"`
|
||||||
} `cmd help:"Sub-sub-command." aliases:"for,fore"`
|
|
||||||
} `cmd help:"Another subcommand."`
|
} `cmd help:"Another subcommand."`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -414,7 +413,7 @@ func TestHelpCompactNoExpand(t *testing.T) {
|
|||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
})
|
})
|
||||||
assert.True(t, exited)
|
assert.True(t, exited)
|
||||||
expected := `Usage: test-app <command> [flags]
|
expected := `Usage: test-app <command>
|
||||||
|
|
||||||
A test app.
|
A test app.
|
||||||
|
|
||||||
@@ -443,7 +442,7 @@ Run "test-app <command> --help" for more information on a command.
|
|||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
})
|
})
|
||||||
assert.True(t, exited)
|
assert.True(t, exited)
|
||||||
expected := `Usage: test-app one (un,uno) <command> [flags]
|
expected := `Usage: test-app one (un,uno) <command>
|
||||||
|
|
||||||
subcommand one
|
subcommand one
|
||||||
|
|
||||||
@@ -600,7 +599,7 @@ func TestAutoGroup(t *testing.T) {
|
|||||||
if node, ok := parent.(*kong.Node); ok {
|
if node, ok := parent.(*kong.Node); ok {
|
||||||
return &kong.Group{
|
return &kong.Group{
|
||||||
Key: node.Name,
|
Key: node.Name,
|
||||||
Title: strings.Title(node.Name) + " flags:", //nolint
|
Title: strings.Title(node.Name) + " flags:", //nolint:staticcheck // strings.Title in test is okay
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -787,16 +786,17 @@ func TestUsageOnError(t *testing.T) {
|
|||||||
Flag string `help:"A required flag." required`
|
Flag string `help:"A required flag." required`
|
||||||
}
|
}
|
||||||
w := &strings.Builder{}
|
w := &strings.Builder{}
|
||||||
|
exitCode := -1
|
||||||
p := mustNew(t, &cli,
|
p := mustNew(t, &cli,
|
||||||
kong.Writers(w, w),
|
kong.Writers(w, w),
|
||||||
kong.Description("Some description."),
|
kong.Description("Some description."),
|
||||||
kong.Exit(func(int) {}),
|
kong.Exit(func(code int) { exitCode = code }),
|
||||||
kong.UsageOnError(),
|
kong.UsageOnError(),
|
||||||
)
|
)
|
||||||
_, err := p.Parse([]string{})
|
_, err := p.Parse([]string{})
|
||||||
p.FatalIfErrorf(err)
|
p.FatalIfErrorf(err)
|
||||||
|
|
||||||
expected := `Usage: test --flag=STRING [flags]
|
expected := `Usage: test --flag=STRING
|
||||||
|
|
||||||
Some description.
|
Some description.
|
||||||
|
|
||||||
@@ -807,6 +807,7 @@ Flags:
|
|||||||
test: error: missing flags: --flag=STRING
|
test: error: missing flags: --flag=STRING
|
||||||
`
|
`
|
||||||
assert.Equal(t, expected, w.String())
|
assert.Equal(t, expected, w.String())
|
||||||
|
assert.Equal(t, 80, exitCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShortUsageOnError(t *testing.T) {
|
func TestShortUsageOnError(t *testing.T) {
|
||||||
@@ -814,22 +815,24 @@ func TestShortUsageOnError(t *testing.T) {
|
|||||||
Flag string `help:"A required flag." required`
|
Flag string `help:"A required flag." required`
|
||||||
}
|
}
|
||||||
w := &strings.Builder{}
|
w := &strings.Builder{}
|
||||||
|
exitCode := -1
|
||||||
p := mustNew(t, &cli,
|
p := mustNew(t, &cli,
|
||||||
kong.Writers(w, w),
|
kong.Writers(w, w),
|
||||||
kong.Description("Some description."),
|
kong.Description("Some description."),
|
||||||
kong.Exit(func(int) {}),
|
kong.Exit(func(code int) { exitCode = code }),
|
||||||
kong.ShortUsageOnError(),
|
kong.ShortUsageOnError(),
|
||||||
)
|
)
|
||||||
_, err := p.Parse([]string{})
|
_, err := p.Parse([]string{})
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
p.FatalIfErrorf(err)
|
p.FatalIfErrorf(err)
|
||||||
|
|
||||||
expected := `Usage: test --flag=STRING [flags]
|
expected := `Usage: test --flag=STRING
|
||||||
Run "test --help" for more information.
|
Run "test --help" for more information.
|
||||||
|
|
||||||
test: error: missing flags: --flag=STRING
|
test: error: missing flags: --flag=STRING
|
||||||
`
|
`
|
||||||
assert.Equal(t, expected, w.String())
|
assert.Equal(t, expected, w.String())
|
||||||
|
assert.Equal(t, 80, exitCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCustomShortUsageOnError(t *testing.T) {
|
func TestCustomShortUsageOnError(t *testing.T) {
|
||||||
@@ -841,10 +844,11 @@ func TestCustomShortUsageOnError(t *testing.T) {
|
|||||||
fmt.Fprintln(ctx.Stdout, "🤷 wish I could help")
|
fmt.Fprintln(ctx.Stdout, "🤷 wish I could help")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
exitCode := -1
|
||||||
p := mustNew(t, &cli,
|
p := mustNew(t, &cli,
|
||||||
kong.Writers(w, w),
|
kong.Writers(w, w),
|
||||||
kong.Description("Some description."),
|
kong.Description("Some description."),
|
||||||
kong.Exit(func(int) {}),
|
kong.Exit(func(code int) { exitCode = code }),
|
||||||
kong.ShortHelp(shortHelp),
|
kong.ShortHelp(shortHelp),
|
||||||
kong.ShortUsageOnError(),
|
kong.ShortUsageOnError(),
|
||||||
)
|
)
|
||||||
@@ -857,4 +861,5 @@ func TestCustomShortUsageOnError(t *testing.T) {
|
|||||||
test: error: missing flags: --flag=STRING
|
test: error: missing flags: --flag=STRING
|
||||||
`
|
`
|
||||||
assert.Equal(t, expected, w.String())
|
assert.Equal(t, expected, w.String())
|
||||||
|
assert.Equal(t, 80, exitCode)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,32 @@
|
|||||||
package kong
|
package kong
|
||||||
|
|
||||||
|
// BeforeReset is a documentation-only interface describing hooks that run before defaults values are applied.
|
||||||
|
type BeforeReset interface {
|
||||||
|
// This is not the correct signature - see README for details.
|
||||||
|
BeforeReset(args ...any) error
|
||||||
|
}
|
||||||
|
|
||||||
// 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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ var (
|
|||||||
callbackReturnSignature = reflect.TypeOf((*error)(nil)).Elem()
|
callbackReturnSignature = reflect.TypeOf((*error)(nil)).Elem()
|
||||||
)
|
)
|
||||||
|
|
||||||
func failField(parent reflect.Value, field reflect.StructField, format string, args ...interface{}) error {
|
func failField(parent reflect.Value, field reflect.StructField, format string, args ...any) error {
|
||||||
name := parent.Type().Name()
|
name := parent.Type().Name()
|
||||||
if name == "" {
|
if name == "" {
|
||||||
name = "<anonymous struct>"
|
name = "<anonymous struct>"
|
||||||
@@ -24,7 +24,7 @@ func failField(parent reflect.Value, field reflect.StructField, format string, a
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Must creates a new Parser or panics if there is an error.
|
// Must creates a new Parser or panics if there is an error.
|
||||||
func Must(ast interface{}, options ...Option) *Kong {
|
func Must(ast any, options ...Option) *Kong {
|
||||||
k, err := New(ast, options...)
|
k, err := New(ast, options...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@@ -56,27 +56,30 @@ type Kong struct {
|
|||||||
registry *Registry
|
registry *Registry
|
||||||
ignoreFields []*regexp.Regexp
|
ignoreFields []*regexp.Regexp
|
||||||
|
|
||||||
noDefaultHelp bool
|
noDefaultHelp bool
|
||||||
usageOnError usageOnError
|
allowHyphenated bool
|
||||||
help HelpPrinter
|
usageOnError usageOnError
|
||||||
shortHelp HelpPrinter
|
help HelpPrinter
|
||||||
helpFormatter HelpValueFormatter
|
shortHelp HelpPrinter
|
||||||
helpOptions HelpOptions
|
helpFormatter HelpValueFormatter
|
||||||
helpFlag *Flag
|
helpOptions HelpOptions
|
||||||
groups []Group
|
helpFlag *Flag
|
||||||
vars Vars
|
groups []Group
|
||||||
flagNamer func(string) string
|
vars Vars
|
||||||
|
flagNamer func(string) string
|
||||||
|
|
||||||
// Set temporarily by Options. These are applied after build().
|
// Set temporarily by Options. These are applied after build().
|
||||||
postBuildOptions []Option
|
postBuildOptions []Option
|
||||||
embedded []embedded
|
embedded []embedded
|
||||||
dynamicCommands []*dynamicCommand
|
dynamicCommands []*dynamicCommand
|
||||||
|
|
||||||
|
hooks map[string][]reflect.Value
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new Kong parser on grammar.
|
// New creates a new Kong parser on grammar.
|
||||||
//
|
//
|
||||||
// See the README (https://git.company.lan/gopkg/kong) for usage instructions.
|
// See the README (https://git.company.lan/gopkg/kong) for usage instructions.
|
||||||
func New(grammar interface{}, options ...Option) (*Kong, error) {
|
func New(grammar any, options ...Option) (*Kong, error) {
|
||||||
k := &Kong{
|
k := &Kong{
|
||||||
Exit: os.Exit,
|
Exit: os.Exit,
|
||||||
Stdout: os.Stdout,
|
Stdout: os.Stdout,
|
||||||
@@ -84,6 +87,7 @@ func New(grammar interface{}, options ...Option) (*Kong, error) {
|
|||||||
registry: NewRegistry().RegisterDefaults(),
|
registry: NewRegistry().RegisterDefaults(),
|
||||||
vars: Vars{},
|
vars: Vars{},
|
||||||
bindings: bindings{},
|
bindings: bindings{},
|
||||||
|
hooks: make(map[string][]reflect.Value),
|
||||||
helpFormatter: DefaultHelpValueFormatter,
|
helpFormatter: DefaultHelpValueFormatter,
|
||||||
ignoreFields: make([]*regexp.Regexp, 0),
|
ignoreFields: make([]*regexp.Regexp, 0),
|
||||||
flagNamer: func(s string) string {
|
flagNamer: func(s string) string {
|
||||||
@@ -117,7 +121,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 +171,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 +253,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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -237,6 +274,11 @@ func (k *Kong) interpolateValue(value *Value, vars Vars) (err error) {
|
|||||||
if len(value.Flag.Envs) != 0 {
|
if len(value.Flag.Envs) != 0 {
|
||||||
updatedVars["env"] = value.Flag.Envs[0]
|
updatedVars["env"] = value.Flag.Envs[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
value.Flag.PlaceHolder, err = interpolate(value.Flag.PlaceHolder, vars, updatedVars)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("placeholder value for %s: %s", value.Summary(), err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
value.Help, err = interpolate(value.Help, vars, updatedVars)
|
value.Help, err = interpolate(value.Help, vars, updatedVars)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -250,7 +292,7 @@ func (k *Kong) extraFlags() []*Flag {
|
|||||||
if k.noDefaultHelp {
|
if k.noDefaultHelp {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
var helpTarget helpValue
|
var helpTarget helpFlag
|
||||||
value := reflect.ValueOf(&helpTarget).Elem()
|
value := reflect.ValueOf(&helpTarget).Elem()
|
||||||
helpFlag := &Flag{
|
helpFlag := &Flag{
|
||||||
Short: 'h',
|
Short: 'h',
|
||||||
@@ -278,11 +320,11 @@ func (k *Kong) extraFlags() []*Flag {
|
|||||||
// invalid one, which will report a normal error).
|
// invalid one, which will report a normal error).
|
||||||
func (k *Kong) Parse(args []string) (ctx *Context, err error) {
|
func (k *Kong) Parse(args []string) (ctx *Context, err error) {
|
||||||
ctx, err = Trace(k, args)
|
ctx, err = Trace(k, args)
|
||||||
if err != nil {
|
if err != nil { // Trace is not expected to return an err
|
||||||
return nil, err
|
return nil, &ParseError{error: err, Context: ctx, exitCode: exitUsageError}
|
||||||
}
|
}
|
||||||
if ctx.Error != nil {
|
if ctx.Error != nil {
|
||||||
return nil, &ParseError{error: ctx.Error, Context: ctx}
|
return nil, &ParseError{error: ctx.Error, Context: ctx, exitCode: exitUsageError}
|
||||||
}
|
}
|
||||||
if err = k.applyHook(ctx, "BeforeReset"); err != nil {
|
if err = k.applyHook(ctx, "BeforeReset"); err != nil {
|
||||||
return nil, &ParseError{error: err, Context: ctx}
|
return nil, &ParseError{error: err, Context: ctx}
|
||||||
@@ -299,11 +341,11 @@ func (k *Kong) Parse(args []string) (ctx *Context, err error) {
|
|||||||
if err = k.applyHook(ctx, "BeforeApply"); err != nil {
|
if err = k.applyHook(ctx, "BeforeApply"); err != nil {
|
||||||
return nil, &ParseError{error: err, Context: ctx}
|
return nil, &ParseError{error: err, Context: ctx}
|
||||||
}
|
}
|
||||||
if _, err = ctx.Apply(); err != nil {
|
if _, err = ctx.Apply(); err != nil { // Apply is not expected to return an err
|
||||||
return nil, &ParseError{error: err, Context: ctx}
|
return nil, &ParseError{error: err, Context: ctx}
|
||||||
}
|
}
|
||||||
if err = ctx.Validate(); err != nil {
|
if err = ctx.Validate(); err != nil {
|
||||||
return nil, &ParseError{error: err, Context: ctx}
|
return nil, &ParseError{error: err, Context: ctx, exitCode: exitUsageError}
|
||||||
}
|
}
|
||||||
if err = k.applyHook(ctx, "AfterApply"); err != nil {
|
if err = k.applyHook(ctx, "AfterApply"); err != nil {
|
||||||
return nil, &ParseError{error: err, Context: ctx}
|
return nil, &ParseError{error: err, Context: ctx}
|
||||||
@@ -328,22 +370,30 @@ func (k *Kong) applyHook(ctx *Context, name string) error {
|
|||||||
default:
|
default:
|
||||||
panic("unsupported Path")
|
panic("unsupported Path")
|
||||||
}
|
}
|
||||||
method := getMethod(value, name)
|
for _, method := range k.getMethods(value, name) {
|
||||||
if !method.IsValid() {
|
binds := k.bindings.clone()
|
||||||
continue
|
binds.add(ctx, trace)
|
||||||
}
|
binds.add(trace.Node().Vars().CloneWith(k.vars))
|
||||||
binds := k.bindings.clone()
|
binds.merge(ctx.bindings)
|
||||||
binds.add(ctx, trace)
|
if err := callFunction(method, binds); err != nil {
|
||||||
binds.add(trace.Node().Vars().CloneWith(k.vars))
|
return err
|
||||||
binds.merge(ctx.bindings)
|
}
|
||||||
if err := callFunction(method, binds); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Path[0] will always be the app root.
|
// Path[0] will always be the app root.
|
||||||
return k.applyHookToDefaultFlags(ctx, ctx.Path[0].Node(), name)
|
return k.applyHookToDefaultFlags(ctx, ctx.Path[0].Node(), name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (k *Kong) getMethods(value reflect.Value, name string) []reflect.Value {
|
||||||
|
return append(
|
||||||
|
// Identify callbacks by reflecting on value
|
||||||
|
getMethods(value, name),
|
||||||
|
|
||||||
|
// Identify callbacks that were registered with a kong.Option
|
||||||
|
k.hooks[name]...,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// Call hook on any unset flags with default values.
|
// Call hook on any unset flags with default values.
|
||||||
func (k *Kong) applyHookToDefaultFlags(ctx *Context, node *Node, name string) error {
|
func (k *Kong) applyHookToDefaultFlags(ctx *Context, node *Node, name string) error {
|
||||||
if node == nil {
|
if node == nil {
|
||||||
@@ -359,21 +409,19 @@ func (k *Kong) applyHookToDefaultFlags(ctx *Context, node *Node, name string) er
|
|||||||
if !flag.HasDefault || ctx.values[flag.Value].IsValid() || !flag.Target.IsValid() {
|
if !flag.HasDefault || ctx.values[flag.Value].IsValid() || !flag.Target.IsValid() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
method := getMethod(flag.Target, name)
|
for _, method := range getMethods(flag.Target, name) {
|
||||||
if !method.IsValid() {
|
path := &Path{Flag: flag}
|
||||||
continue
|
if err := callFunction(method, binds.clone().add(path)); err != nil {
|
||||||
}
|
return next(err)
|
||||||
path := &Path{Flag: flag}
|
}
|
||||||
if err := callFunction(method, binds.clone().add(path)); err != nil {
|
|
||||||
return next(err)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return next(nil)
|
return next(nil)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func formatMultilineMessage(w io.Writer, leaders []string, format string, args ...interface{}) {
|
func formatMultilineMessage(w io.Writer, leaders []string, format string, args ...any) {
|
||||||
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 == "" {
|
||||||
@@ -388,25 +436,27 @@ func formatMultilineMessage(w io.Writer, leaders []string, format string, args .
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Printf writes a message to Kong.Stdout with the application name prefixed.
|
// Printf writes a message to Kong.Stdout with the application name prefixed.
|
||||||
func (k *Kong) Printf(format string, args ...interface{}) *Kong {
|
func (k *Kong) Printf(format string, args ...any) *Kong {
|
||||||
formatMultilineMessage(k.Stdout, []string{k.Model.Name}, format, args...)
|
formatMultilineMessage(k.Stdout, []string{k.Model.Name}, format, args...)
|
||||||
return k
|
return k
|
||||||
}
|
}
|
||||||
|
|
||||||
// Errorf writes a message to Kong.Stderr with the application name prefixed.
|
// Errorf writes a message to Kong.Stderr with the application name prefixed.
|
||||||
func (k *Kong) Errorf(format string, args ...interface{}) *Kong {
|
func (k *Kong) Errorf(format string, args ...any) *Kong {
|
||||||
formatMultilineMessage(k.Stderr, []string{k.Model.Name, "error"}, format, args...)
|
formatMultilineMessage(k.Stderr, []string{k.Model.Name, "error"}, format, args...)
|
||||||
return k
|
return k
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fatalf writes a message to Kong.Stderr with the application name prefixed then exits with a non-zero status.
|
// Fatalf writes a message to Kong.Stderr with the application name prefixed then exits with status 1.
|
||||||
func (k *Kong) Fatalf(format string, args ...interface{}) {
|
func (k *Kong) Fatalf(format string, args ...any) {
|
||||||
k.Errorf(format, args...)
|
k.Errorf(format, args...)
|
||||||
k.Exit(1)
|
k.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FatalIfErrorf terminates with an error message if err != nil.
|
// FatalIfErrorf terminates with an error message if err != nil.
|
||||||
func (k *Kong) FatalIfErrorf(err error, args ...interface{}) {
|
// If the error implements the ExitCoder interface, the ExitCode() method is called and
|
||||||
|
// the application exits with that status. Otherwise, the application exits with status 1.
|
||||||
|
func (k *Kong) FatalIfErrorf(err error, args ...any) {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -426,7 +476,8 @@ func (k *Kong) FatalIfErrorf(err error, args ...interface{}) {
|
|||||||
fmt.Fprintln(k.Stdout)
|
fmt.Fprintln(k.Stdout)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
k.Fatalf("%s", msg)
|
k.Errorf("%s", msg)
|
||||||
|
k.Exit(exitCodeFromError(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadConfig from path using the loader configured via Configuration(loader).
|
// LoadConfig from path using the loader configured via Configuration(loader).
|
||||||
|
|||||||
+727
-50
@@ -4,6 +4,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@@ -13,7 +14,7 @@ import (
|
|||||||
"git.company.lan/gopkg/kong"
|
"git.company.lan/gopkg/kong"
|
||||||
)
|
)
|
||||||
|
|
||||||
func mustNew(t *testing.T, cli interface{}, options ...kong.Option) *kong.Kong {
|
func mustNew(t *testing.T, cli any, options ...kong.Option) *kong.Kong {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
options = append([]kong.Option{
|
options = append([]kong.Option{
|
||||||
kong.Name("test"),
|
kong.Name("test"),
|
||||||
@@ -47,6 +48,25 @@ func TestPositionalArguments(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRemainderReturnsUnparsedArgs(t *testing.T) {
|
||||||
|
var cli struct {
|
||||||
|
User struct {
|
||||||
|
Create struct {
|
||||||
|
ID int `kong:"arg"`
|
||||||
|
First string `kong:"arg"`
|
||||||
|
Last string `kong:"arg"`
|
||||||
|
} `kong:"cmd"`
|
||||||
|
} `kong:"cmd"`
|
||||||
|
}
|
||||||
|
p := mustNew(t, &cli)
|
||||||
|
args := []string{"user", "create", "10", "Alec", "Thomas"}
|
||||||
|
ctx, err := p.Parse(args)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
for i, x := range ctx.Path {
|
||||||
|
assert.Equal(t, strings.Join(args[i:], " "), strings.Join(x.Remainder(), " "))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestBranchingArgument(t *testing.T) {
|
func TestBranchingArgument(t *testing.T) {
|
||||||
/*
|
/*
|
||||||
app user create <id> <first> <last>
|
app user create <id> <first> <last>
|
||||||
@@ -356,8 +376,9 @@ func TestTraceErrorPartiallySucceeds(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type commandWithNegatableFlag struct {
|
type commandWithNegatableFlag struct {
|
||||||
Flag bool `kong:"default='true',negatable"`
|
Flag bool `kong:"default='true',negatable"`
|
||||||
ran bool
|
Custom bool `kong:"default='true',negatable='standard'"`
|
||||||
|
ran bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *commandWithNegatableFlag) Run() error {
|
func (c *commandWithNegatableFlag) Run() error {
|
||||||
@@ -367,34 +388,64 @@ func (c *commandWithNegatableFlag) Run() error {
|
|||||||
|
|
||||||
func TestNegatableFlag(t *testing.T) {
|
func TestNegatableFlag(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
args []string
|
args []string
|
||||||
expected bool
|
expectedFlag bool
|
||||||
|
expectedCustom bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "no flag",
|
name: "no flag",
|
||||||
args: []string{"cmd"},
|
args: []string{"cmd"},
|
||||||
expected: true,
|
expectedFlag: true,
|
||||||
|
expectedCustom: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "boolean flag",
|
name: "boolean flag",
|
||||||
args: []string{"cmd", "--flag"},
|
args: []string{"cmd", "--flag"},
|
||||||
expected: true,
|
expectedFlag: true,
|
||||||
|
expectedCustom: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "inverted boolean flag",
|
name: "custom boolean flag",
|
||||||
args: []string{"cmd", "--flag=false"},
|
args: []string{"cmd", "--custom"},
|
||||||
expected: false,
|
expectedFlag: true,
|
||||||
|
expectedCustom: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "negated boolean flag",
|
name: "inverted boolean flag",
|
||||||
args: []string{"cmd", "--no-flag"},
|
args: []string{"cmd", "--flag=false"},
|
||||||
expected: false,
|
expectedFlag: false,
|
||||||
|
expectedCustom: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "inverted negated boolean flag",
|
name: "custom inverted boolean flag",
|
||||||
args: []string{"cmd", "--no-flag=false"},
|
args: []string{"cmd", "--custom=false"},
|
||||||
expected: true,
|
expectedFlag: true,
|
||||||
|
expectedCustom: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "negated boolean flag",
|
||||||
|
args: []string{"cmd", "--no-flag"},
|
||||||
|
expectedFlag: false,
|
||||||
|
expectedCustom: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "custom negated boolean flag",
|
||||||
|
args: []string{"cmd", "--standard"},
|
||||||
|
expectedFlag: true,
|
||||||
|
expectedCustom: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "inverted negated boolean flag",
|
||||||
|
args: []string{"cmd", "--no-flag=false"},
|
||||||
|
expectedFlag: true,
|
||||||
|
expectedCustom: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "inverted custom negated boolean flag",
|
||||||
|
args: []string{"cmd", "--standard=false"},
|
||||||
|
expectedFlag: true,
|
||||||
|
expectedCustom: true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
@@ -407,16 +458,47 @@ func TestNegatableFlag(t *testing.T) {
|
|||||||
p := mustNew(t, &cli)
|
p := mustNew(t, &cli)
|
||||||
kctx, err := p.Parse(tt.args)
|
kctx, err := p.Parse(tt.args)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, tt.expected, cli.Cmd.Flag)
|
assert.Equal(t, tt.expectedFlag, cli.Cmd.Flag)
|
||||||
|
assert.Equal(t, tt.expectedCustom, cli.Cmd.Custom)
|
||||||
|
|
||||||
err = kctx.Run()
|
err = kctx.Run()
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, tt.expected, cli.Cmd.Flag)
|
assert.Equal(t, tt.expectedFlag, cli.Cmd.Flag)
|
||||||
|
assert.Equal(t, tt.expectedCustom, cli.Cmd.Custom)
|
||||||
assert.True(t, cli.Cmd.ran)
|
assert.True(t, cli.Cmd.ran)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDuplicateNegatableLong(t *testing.T) {
|
||||||
|
cli2 := struct {
|
||||||
|
NoFlag bool
|
||||||
|
Flag bool `negatable:""` // negation duplicates NoFlag
|
||||||
|
}{}
|
||||||
|
_, err := kong.New(&cli2)
|
||||||
|
assert.EqualError(t, err, "<anonymous struct>.Flag: duplicate negation flag --no-flag")
|
||||||
|
|
||||||
|
cli3 := struct {
|
||||||
|
One bool
|
||||||
|
Two bool `negatable:"one"` // negation duplicates Flag2
|
||||||
|
}{}
|
||||||
|
_, err = kong.New(&cli3)
|
||||||
|
assert.EqualError(t, err, "<anonymous struct>.Two: duplicate negation flag --one")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDuplicateNegatableFlagsInSubcommands(t *testing.T) {
|
||||||
|
cli2 := struct {
|
||||||
|
Sub struct {
|
||||||
|
Negated bool `negatable:"nope-"`
|
||||||
|
} `cmd:""`
|
||||||
|
Sub2 struct {
|
||||||
|
Negated bool `negatable:"nope-"`
|
||||||
|
} `cmd:""`
|
||||||
|
}{}
|
||||||
|
_, err := kong.New(&cli2)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
func TestExistingNoFlag(t *testing.T) {
|
func TestExistingNoFlag(t *testing.T) {
|
||||||
var cli struct {
|
var cli struct {
|
||||||
Cmd struct {
|
Cmd struct {
|
||||||
@@ -506,6 +588,65 @@ func TestHooks(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGlobalHooks(t *testing.T) {
|
||||||
|
var cli struct {
|
||||||
|
One struct {
|
||||||
|
Two string `kong:"arg,optional"`
|
||||||
|
Three string
|
||||||
|
} `cmd:""`
|
||||||
|
}
|
||||||
|
|
||||||
|
called := []string{}
|
||||||
|
log := func(name string) any {
|
||||||
|
return func(value *kong.Path) error {
|
||||||
|
switch {
|
||||||
|
case value.App != nil:
|
||||||
|
called = append(called, fmt.Sprintf("%s (app)", name))
|
||||||
|
|
||||||
|
case value.Positional != nil:
|
||||||
|
called = append(called, fmt.Sprintf("%s (arg) %s", name, value.Positional.Name))
|
||||||
|
|
||||||
|
case value.Flag != nil:
|
||||||
|
called = append(called, fmt.Sprintf("%s (flag) %s", name, value.Flag.Name))
|
||||||
|
|
||||||
|
case value.Argument != nil:
|
||||||
|
called = append(called, fmt.Sprintf("%s (arg) %s", name, value.Argument.Name))
|
||||||
|
|
||||||
|
case value.Command != nil:
|
||||||
|
called = append(called, fmt.Sprintf("%s (cmd) %s", name, value.Command.Name))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p := mustNew(t, &cli,
|
||||||
|
kong.WithBeforeReset(log("BeforeReset")),
|
||||||
|
kong.WithBeforeResolve(log("BeforeResolve")),
|
||||||
|
kong.WithBeforeApply(log("BeforeApply")),
|
||||||
|
kong.WithAfterApply(log("AfterApply")),
|
||||||
|
)
|
||||||
|
|
||||||
|
_, err := p.Parse([]string{"one", "two", "--three=THREE"})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, []string{
|
||||||
|
"BeforeReset (app)",
|
||||||
|
"BeforeReset (cmd) one",
|
||||||
|
"BeforeReset (arg) two",
|
||||||
|
"BeforeReset (flag) three",
|
||||||
|
"BeforeResolve (app)",
|
||||||
|
"BeforeResolve (cmd) one",
|
||||||
|
"BeforeResolve (arg) two",
|
||||||
|
"BeforeResolve (flag) three",
|
||||||
|
"BeforeApply (app)",
|
||||||
|
"BeforeApply (cmd) one",
|
||||||
|
"BeforeApply (arg) two",
|
||||||
|
"BeforeApply (flag) three",
|
||||||
|
"AfterApply (app)",
|
||||||
|
"AfterApply (cmd) one",
|
||||||
|
"AfterApply (arg) two",
|
||||||
|
"AfterApply (flag) three",
|
||||||
|
}, called)
|
||||||
|
}
|
||||||
|
|
||||||
func TestShort(t *testing.T) {
|
func TestShort(t *testing.T) {
|
||||||
var cli struct {
|
var cli struct {
|
||||||
Bool bool `short:"b"`
|
Bool bool `short:"b"`
|
||||||
@@ -593,11 +734,24 @@ func TestSliceWithDisabledSeparator(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestMultilineMessage(t *testing.T) {
|
func TestMultilineMessage(t *testing.T) {
|
||||||
w := &bytes.Buffer{}
|
tests := []struct {
|
||||||
var cli struct{}
|
name string
|
||||||
p := mustNew(t, &cli, kong.Writers(w, w))
|
text string
|
||||||
p.Printf("hello\nworld")
|
want string
|
||||||
assert.Equal(t, "test: hello\n world\n", w.String())
|
}{
|
||||||
|
{"Simple", "hello\nworld", "test: hello\n world\n"},
|
||||||
|
{"WithNewline", "hello\nworld\n", "test: hello\n world\n"},
|
||||||
|
}
|
||||||
|
for _, test := range tests {
|
||||||
|
test := test
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
w := &bytes.Buffer{}
|
||||||
|
var cli struct{}
|
||||||
|
p := mustNew(t, &cli, kong.Writers(w, w))
|
||||||
|
p.Printf("%s", test.text)
|
||||||
|
assert.Equal(t, test.want, w.String())
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type cmdWithRun struct {
|
type cmdWithRun struct {
|
||||||
@@ -671,8 +825,8 @@ func TestPassesThroughOriginalCommandError(t *testing.T) {
|
|||||||
|
|
||||||
func TestInterpolationIntoModel(t *testing.T) {
|
func TestInterpolationIntoModel(t *testing.T) {
|
||||||
var cli struct {
|
var cli struct {
|
||||||
Flag string `default:"${default_value}" help:"Help, I need ${somebody}" enum:"${enum}"`
|
Flag string `default:"${default_value}" help:"Help, I need ${somebody}" enum:"${enum}" placeholder:"${enum}"`
|
||||||
EnumRef string `enum:"a,b" required:"" help:"One of ${enum}"`
|
EnumRef string `enum:"a,b" required:"" help:"One of ${enum}" placeholder:"${enum}"`
|
||||||
EnvRef string `env:"${env}" help:"God ${env}"`
|
EnvRef string `env:"${env}" help:"God ${env}"`
|
||||||
}
|
}
|
||||||
_, err := kong.New(&cli)
|
_, err := kong.New(&cli)
|
||||||
@@ -692,7 +846,9 @@ func TestInterpolationIntoModel(t *testing.T) {
|
|||||||
assert.Equal(t, "Help, I need chickens!", flag.Help)
|
assert.Equal(t, "Help, I need chickens!", flag.Help)
|
||||||
assert.Equal(t, map[string]bool{"a": true, "b": true, "c": true, "d": true}, flag.EnumMap())
|
assert.Equal(t, map[string]bool{"a": true, "b": true, "c": true, "d": true}, flag.EnumMap())
|
||||||
assert.Equal(t, []string{"a", "b", "c", "d"}, flag.EnumSlice())
|
assert.Equal(t, []string{"a", "b", "c", "d"}, flag.EnumSlice())
|
||||||
|
assert.Equal(t, "a,b,c,d", flag.PlaceHolder)
|
||||||
assert.Equal(t, "One of a,b", flag2.Help)
|
assert.Equal(t, "One of a,b", flag2.Help)
|
||||||
|
assert.Equal(t, "a,b", flag2.PlaceHolder)
|
||||||
assert.Equal(t, []string{"SAVE_THE_QUEEN"}, flag3.Envs)
|
assert.Equal(t, []string{"SAVE_THE_QUEEN"}, flag3.Envs)
|
||||||
assert.Equal(t, "God SAVE_THE_QUEEN", flag3.Help)
|
assert.Equal(t, "God SAVE_THE_QUEEN", flag3.Help)
|
||||||
}
|
}
|
||||||
@@ -780,6 +936,43 @@ func TestExcludedField(t *testing.T) {
|
|||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestExcludeEmbeddedField(t *testing.T) {
|
||||||
|
type Embedded struct {
|
||||||
|
Flag string
|
||||||
|
Excluded string
|
||||||
|
}
|
||||||
|
type Embedded2 struct {
|
||||||
|
Flag2 string
|
||||||
|
Excluded string
|
||||||
|
}
|
||||||
|
var cli struct {
|
||||||
|
Embedded
|
||||||
|
Excluded string `kong:"-"`
|
||||||
|
Embedded2
|
||||||
|
}
|
||||||
|
var cli2 struct {
|
||||||
|
Embedded Embedded `kong:"embed"`
|
||||||
|
Excluded string `kong:"-"`
|
||||||
|
Embedded2 Embedded2 `kong:"embed"`
|
||||||
|
}
|
||||||
|
|
||||||
|
p := mustNew(t, &cli)
|
||||||
|
_, err := p.Parse([]string{"--flag=foo"})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
_, err = p.Parse([]string{"--flag-2=foo"})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
_, err = p.Parse([]string{"--excluded=foo"})
|
||||||
|
assert.Error(t, err)
|
||||||
|
|
||||||
|
p = mustNew(t, &cli2)
|
||||||
|
_, err = p.Parse([]string{"--flag=foo"})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
_, err = p.Parse([]string{"--flag-2=foo"})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
_, err = p.Parse([]string{"--excluded=foo"})
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
func TestUnnamedFieldEmbeds(t *testing.T) {
|
func TestUnnamedFieldEmbeds(t *testing.T) {
|
||||||
type Embed struct {
|
type Embed struct {
|
||||||
Flag string
|
Flag string
|
||||||
@@ -850,15 +1043,6 @@ func TestParentBindings(t *testing.T) {
|
|||||||
assert.Equal(t, "foo", cli.Command.value)
|
assert.Equal(t, "foo", cli.Command.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNumericParamErrors(t *testing.T) {
|
|
||||||
var cli struct {
|
|
||||||
Name string
|
|
||||||
}
|
|
||||||
parser := mustNew(t, &cli)
|
|
||||||
_, err := parser.Parse([]string{"--name", "-10"})
|
|
||||||
assert.EqualError(t, err, `--name: expected string value but got "-10" (short flag); perhaps try --name="-10"?`)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDefaultValueIsHyphen(t *testing.T) {
|
func TestDefaultValueIsHyphen(t *testing.T) {
|
||||||
var cli struct {
|
var cli struct {
|
||||||
Flag string `default:"-"`
|
Flag string `default:"-"`
|
||||||
@@ -879,14 +1063,12 @@ func TestDefaultEnumValidated(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestEnvarEnumValidated(t *testing.T) {
|
func TestEnvarEnumValidated(t *testing.T) {
|
||||||
restore := tempEnv(map[string]string{
|
|
||||||
"FLAG": "invalid",
|
|
||||||
})
|
|
||||||
defer restore()
|
|
||||||
var cli struct {
|
var cli struct {
|
||||||
Flag string `env:"FLAG" required:"" enum:"valid"`
|
Flag string `env:"FLAG" required:"" enum:"valid"`
|
||||||
}
|
}
|
||||||
p := mustNew(t, &cli)
|
p := newEnvParser(t, &cli, envMap{
|
||||||
|
"FLAG": "invalid",
|
||||||
|
})
|
||||||
_, err := p.Parse(nil)
|
_, err := p.Parse(nil)
|
||||||
assert.EqualError(t, err, "--flag must be one of \"valid\" but got \"invalid\"")
|
assert.EqualError(t, err, "--flag must be one of \"valid\" but got \"invalid\"")
|
||||||
}
|
}
|
||||||
@@ -906,6 +1088,21 @@ func TestXor(t *testing.T) {
|
|||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAnd(t *testing.T) {
|
||||||
|
var cli struct {
|
||||||
|
Hello bool `and:"another"`
|
||||||
|
One bool `and:"group"`
|
||||||
|
Two string `and:"group"`
|
||||||
|
}
|
||||||
|
p := mustNew(t, &cli)
|
||||||
|
_, err := p.Parse([]string{"--hello", "--one"})
|
||||||
|
assert.EqualError(t, err, "--one and --two must be used together")
|
||||||
|
|
||||||
|
p = mustNew(t, &cli)
|
||||||
|
_, err = p.Parse([]string{"--one", "--two=hi", "--hello"})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
func TestXorChild(t *testing.T) {
|
func TestXorChild(t *testing.T) {
|
||||||
var cli struct {
|
var cli struct {
|
||||||
One bool `xor:"group"`
|
One bool `xor:"group"`
|
||||||
@@ -923,6 +1120,23 @@ func TestXorChild(t *testing.T) {
|
|||||||
assert.Error(t, err, "--two and --three can't be used together")
|
assert.Error(t, err, "--two and --three can't be used together")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAndChild(t *testing.T) {
|
||||||
|
var cli struct {
|
||||||
|
One bool `and:"group"`
|
||||||
|
Cmd struct {
|
||||||
|
Two string `and:"group"`
|
||||||
|
Three string `and:"group"`
|
||||||
|
} `cmd`
|
||||||
|
}
|
||||||
|
p := mustNew(t, &cli)
|
||||||
|
_, err := p.Parse([]string{"--one", "cmd", "--two=hi", "--three=hello"})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
p = mustNew(t, &cli)
|
||||||
|
_, err = p.Parse([]string{"--two=hi", "cmd"})
|
||||||
|
assert.Error(t, err, "--two and --three must be used together")
|
||||||
|
}
|
||||||
|
|
||||||
func TestMultiXor(t *testing.T) {
|
func TestMultiXor(t *testing.T) {
|
||||||
var cli struct {
|
var cli struct {
|
||||||
Hello bool `xor:"one,two"`
|
Hello bool `xor:"one,two"`
|
||||||
@@ -939,6 +1153,57 @@ func TestMultiXor(t *testing.T) {
|
|||||||
assert.EqualError(t, err, "--hello and --two can't be used together")
|
assert.EqualError(t, err, "--hello and --two can't be used together")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMultiAnd(t *testing.T) {
|
||||||
|
var cli struct {
|
||||||
|
Hello bool `and:"one,two"`
|
||||||
|
One bool `and:"one"`
|
||||||
|
Two string `and:"two"`
|
||||||
|
}
|
||||||
|
|
||||||
|
p := mustNew(t, &cli)
|
||||||
|
_, err := p.Parse([]string{"--hello"})
|
||||||
|
// Split and combine error so messages always will be in the same order
|
||||||
|
// when testing
|
||||||
|
missingMsgs := strings.Split(err.Error(), ", ")
|
||||||
|
sort.Strings(missingMsgs)
|
||||||
|
err = fmt.Errorf("%s", strings.Join(missingMsgs, ", "))
|
||||||
|
assert.EqualError(t, err, "--hello and --one must be used together, --hello and --two must be used together")
|
||||||
|
|
||||||
|
p = mustNew(t, &cli)
|
||||||
|
_, err = p.Parse([]string{"--two=foo"})
|
||||||
|
assert.EqualError(t, err, "--hello and --two must be used together")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestXorAnd(t *testing.T) {
|
||||||
|
var cli struct {
|
||||||
|
Hello bool `xor:"one" and:"two"`
|
||||||
|
One bool `xor:"one"`
|
||||||
|
Two string `and:"two"`
|
||||||
|
}
|
||||||
|
|
||||||
|
p := mustNew(t, &cli)
|
||||||
|
_, err := p.Parse([]string{"--hello"})
|
||||||
|
assert.EqualError(t, err, "--hello and --two must be used together")
|
||||||
|
|
||||||
|
p = mustNew(t, &cli)
|
||||||
|
_, err = p.Parse([]string{"--one"})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
p = mustNew(t, &cli)
|
||||||
|
_, err = p.Parse([]string{"--hello", "--one"})
|
||||||
|
assert.EqualError(t, err, "--hello and --one can't be used together, --hello and --two must be used together")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOverLappingXorAnd(t *testing.T) {
|
||||||
|
var cli struct {
|
||||||
|
Hello bool `xor:"one" and:"two"`
|
||||||
|
One bool `xor:"one" and:"two"`
|
||||||
|
Two string `xor:"one" and:"two"`
|
||||||
|
}
|
||||||
|
_, err := kong.New(&cli)
|
||||||
|
assert.EqualError(t, err, "invalid xor and combination, one and two overlap with more than one: [hello one two]")
|
||||||
|
}
|
||||||
|
|
||||||
func TestXorRequired(t *testing.T) {
|
func TestXorRequired(t *testing.T) {
|
||||||
var cli struct {
|
var cli struct {
|
||||||
One bool `xor:"one,two" required:""`
|
One bool `xor:"one,two" required:""`
|
||||||
@@ -959,6 +1224,26 @@ func TestXorRequired(t *testing.T) {
|
|||||||
assert.EqualError(t, err, "missing flags: --four, --one or --three, --one or --two")
|
assert.EqualError(t, err, "missing flags: --four, --one or --three, --one or --two")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAndRequired(t *testing.T) {
|
||||||
|
var cli struct {
|
||||||
|
One bool `and:"one,two" required:""`
|
||||||
|
Two bool `and:"one" required:""`
|
||||||
|
Three bool `and:"two"`
|
||||||
|
Four bool `required:""`
|
||||||
|
}
|
||||||
|
p := mustNew(t, &cli)
|
||||||
|
_, err := p.Parse([]string{"--one", "--two", "--three"})
|
||||||
|
assert.EqualError(t, err, "missing flags: --four")
|
||||||
|
|
||||||
|
p = mustNew(t, &cli)
|
||||||
|
_, err = p.Parse([]string{"--four"})
|
||||||
|
assert.EqualError(t, err, "missing flags: --one and --three, --one and --two")
|
||||||
|
|
||||||
|
p = mustNew(t, &cli)
|
||||||
|
_, err = p.Parse([]string{})
|
||||||
|
assert.EqualError(t, err, "missing flags: --four, --one and --three, --one and --two")
|
||||||
|
}
|
||||||
|
|
||||||
func TestXorRequiredMany(t *testing.T) {
|
func TestXorRequiredMany(t *testing.T) {
|
||||||
var cli struct {
|
var cli struct {
|
||||||
One bool `xor:"one" required:""`
|
One bool `xor:"one" required:""`
|
||||||
@@ -978,6 +1263,21 @@ func TestXorRequiredMany(t *testing.T) {
|
|||||||
assert.EqualError(t, err, "missing flags: --one or --two or --three")
|
assert.EqualError(t, err, "missing flags: --one or --two or --three")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAndRequiredMany(t *testing.T) {
|
||||||
|
var cli struct {
|
||||||
|
One bool `and:"one" required:""`
|
||||||
|
Two bool `and:"one" required:""`
|
||||||
|
Three bool `and:"one" required:""`
|
||||||
|
}
|
||||||
|
p := mustNew(t, &cli)
|
||||||
|
_, err := p.Parse([]string{})
|
||||||
|
assert.EqualError(t, err, "missing flags: --one and --two and --three")
|
||||||
|
|
||||||
|
p = mustNew(t, &cli)
|
||||||
|
_, err = p.Parse([]string{"--three"})
|
||||||
|
assert.EqualError(t, err, "missing flags: --one and --two")
|
||||||
|
}
|
||||||
|
|
||||||
func TestEnumSequence(t *testing.T) {
|
func TestEnumSequence(t *testing.T) {
|
||||||
var cli struct {
|
var cli struct {
|
||||||
State []string `enum:"a,b,c" default:"a"`
|
State []string `enum:"a,b,c" default:"a"`
|
||||||
@@ -1040,10 +1340,9 @@ func TestIssue153(t *testing.T) {
|
|||||||
Ls LsCmd `cmd help:"List paths."`
|
Ls LsCmd `cmd help:"List paths."`
|
||||||
}
|
}
|
||||||
|
|
||||||
p, revert := newEnvParser(t, &cli, envMap{
|
p := newEnvParser(t, &cli, envMap{
|
||||||
"CMD_PATHS": "hello",
|
"CMD_PATHS": "hello",
|
||||||
})
|
})
|
||||||
defer revert()
|
|
||||||
_, err := p.Parse([]string{"ls"})
|
_, err := p.Parse([]string{"ls"})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, []string{"hello"}, cli.Ls.Paths)
|
assert.Equal(t, []string{"hello"}, cli.Ls.Paths)
|
||||||
@@ -1272,6 +1571,19 @@ func TestValidateArg(t *testing.T) {
|
|||||||
assert.EqualError(t, err, "<arg>: flag error")
|
assert.EqualError(t, err, "<arg>: flag error")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type extendedValidateFlag string
|
||||||
|
|
||||||
|
func (v *extendedValidateFlag) Validate(kctx *kong.Context) error { return errors.New("flag error") }
|
||||||
|
|
||||||
|
func TestExtendedValidateFlag(t *testing.T) {
|
||||||
|
cli := struct {
|
||||||
|
Flag extendedValidateFlag
|
||||||
|
}{}
|
||||||
|
p := mustNew(t, &cli)
|
||||||
|
_, err := p.Parse([]string{"--flag=one"})
|
||||||
|
assert.EqualError(t, err, "--flag: flag error")
|
||||||
|
}
|
||||||
|
|
||||||
func TestPointers(t *testing.T) {
|
func TestPointers(t *testing.T) {
|
||||||
cli := struct {
|
cli := struct {
|
||||||
Mapped *mappedValue
|
Mapped *mappedValue
|
||||||
@@ -1297,6 +1609,12 @@ func (d *dynamicCommand) Run() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type commandFunc func() error
|
||||||
|
|
||||||
|
func (cf commandFunc) Run() error {
|
||||||
|
return cf()
|
||||||
|
}
|
||||||
|
|
||||||
func TestDynamicCommands(t *testing.T) {
|
func TestDynamicCommands(t *testing.T) {
|
||||||
cli := struct {
|
cli := struct {
|
||||||
One struct{} `cmd:"one"`
|
One struct{} `cmd:"one"`
|
||||||
@@ -1304,9 +1622,12 @@ func TestDynamicCommands(t *testing.T) {
|
|||||||
help := &strings.Builder{}
|
help := &strings.Builder{}
|
||||||
two := &dynamicCommand{}
|
two := &dynamicCommand{}
|
||||||
three := &dynamicCommand{}
|
three := &dynamicCommand{}
|
||||||
|
fourRan := false
|
||||||
|
four := commandFunc(func() error { fourRan = true; return nil })
|
||||||
p := mustNew(t, &cli,
|
p := mustNew(t, &cli,
|
||||||
kong.DynamicCommand("two", "", "", &two),
|
kong.DynamicCommand("two", "", "", &two),
|
||||||
kong.DynamicCommand("three", "", "", three, "hidden"),
|
kong.DynamicCommand("three", "", "", three, "hidden"),
|
||||||
|
kong.DynamicCommand("four", "", "", &four),
|
||||||
kong.Writers(help, help),
|
kong.Writers(help, help),
|
||||||
kong.Exit(func(int) {}))
|
kong.Exit(func(int) {}))
|
||||||
kctx, err := p.Parse([]string{"two", "--flag=flag"})
|
kctx, err := p.Parse([]string{"two", "--flag=flag"})
|
||||||
@@ -1317,8 +1638,15 @@ func TestDynamicCommands(t *testing.T) {
|
|||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.True(t, two.ran)
|
assert.True(t, two.ran)
|
||||||
|
|
||||||
|
kctx, err = p.Parse([]string{"four"})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.False(t, fourRan)
|
||||||
|
err = kctx.Run()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.True(t, fourRan)
|
||||||
|
|
||||||
_, err = p.Parse([]string{"--help"})
|
_, err = p.Parse([]string{"--help"})
|
||||||
assert.EqualError(t, err, `expected one of "one", "two"`)
|
assert.EqualError(t, err, `expected one of "one", "two", "four"`)
|
||||||
assert.NotContains(t, help.String(), "three", help.String())
|
assert.NotContains(t, help.String(), "three", help.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1460,7 +1788,7 @@ func TestOptionReturnsErr(t *testing.T) {
|
|||||||
func TestEnumValidation(t *testing.T) {
|
func TestEnumValidation(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
cli interface{}
|
cli any
|
||||||
fail bool
|
fail bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
@@ -1526,6 +1854,89 @@ func TestEnumValidation(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPassthroughArgs(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args []string
|
||||||
|
flag string
|
||||||
|
cmdArgs []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"NoArgs",
|
||||||
|
[]string{},
|
||||||
|
"",
|
||||||
|
[]string(nil),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"RecognizedFlagAndArgs",
|
||||||
|
[]string{"--flag", "foobar", "something"},
|
||||||
|
"foobar",
|
||||||
|
[]string{"something"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"DashDashBetweenArgs",
|
||||||
|
[]string{"foo", "--", "bar"},
|
||||||
|
"",
|
||||||
|
[]string{"foo", "--", "bar"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"DashDash",
|
||||||
|
[]string{"--", "--flag", "foobar"},
|
||||||
|
"",
|
||||||
|
[]string{"--", "--flag", "foobar"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"UnrecognizedFlagAndArgs",
|
||||||
|
[]string{"--unrecognized-flag", "something"},
|
||||||
|
"",
|
||||||
|
[]string{"--unrecognized-flag", "something"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range tests {
|
||||||
|
test := test
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
var cli struct {
|
||||||
|
Flag string
|
||||||
|
Args []string `arg:"" optional:"" passthrough:""`
|
||||||
|
}
|
||||||
|
p := mustNew(t, &cli)
|
||||||
|
_, err := p.Parse(test.args)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, test.flag, cli.Flag)
|
||||||
|
assert.Equal(t, test.cmdArgs, cli.Args)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPassthroughPartial(t *testing.T) {
|
||||||
|
var cli struct {
|
||||||
|
Flag string
|
||||||
|
Args []string `arg:"" optional:"" passthrough:"partial"`
|
||||||
|
}
|
||||||
|
p := mustNew(t, &cli)
|
||||||
|
_, err := p.Parse([]string{"--flag", "foobar", "something"})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "foobar", cli.Flag)
|
||||||
|
assert.Equal(t, []string{"something"}, cli.Args)
|
||||||
|
_, err = p.Parse([]string{"--invalid", "foobar", "something"})
|
||||||
|
assert.EqualError(t, err, "unknown flag --invalid")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPassthroughAll(t *testing.T) {
|
||||||
|
var cli struct {
|
||||||
|
Flag string
|
||||||
|
Args []string `arg:"" optional:"" passthrough:"all"`
|
||||||
|
}
|
||||||
|
p := mustNew(t, &cli)
|
||||||
|
_, err := p.Parse([]string{"--flag", "foobar", "something"})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "foobar", cli.Flag)
|
||||||
|
assert.Equal(t, []string{"something"}, cli.Args)
|
||||||
|
_, err = p.Parse([]string{"--invalid", "foobar", "something"})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, []string{"--invalid", "foobar", "something"}, cli.Args)
|
||||||
|
}
|
||||||
|
|
||||||
func TestPassthroughCmd(t *testing.T) {
|
func TestPassthroughCmd(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@@ -1651,7 +2062,7 @@ func TestVersionFlagShouldStillWork(t *testing.T) {
|
|||||||
func TestSliceDecoderHelpfulErrorMsg(t *testing.T) {
|
func TestSliceDecoderHelpfulErrorMsg(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
cli interface{}
|
cli any
|
||||||
args []string
|
args []string
|
||||||
err string
|
err string
|
||||||
}{
|
}{
|
||||||
@@ -1701,7 +2112,7 @@ func TestSliceDecoderHelpfulErrorMsg(t *testing.T) {
|
|||||||
func TestMapDecoderHelpfulErrorMsg(t *testing.T) {
|
func TestMapDecoderHelpfulErrorMsg(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
cli interface{}
|
cli any
|
||||||
args []string
|
args []string
|
||||||
expected string
|
expected string
|
||||||
}{
|
}{
|
||||||
@@ -2027,3 +2438,269 @@ func TestEnumPtrOmittedNoDefault(t *testing.T) {
|
|||||||
assert.NotZero(t, ctx)
|
assert.NotZero(t, ctx)
|
||||||
assert.Zero(t, cli.X)
|
assert.Zero(t, cli.X)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIntEnum(t *testing.T) {
|
||||||
|
var cli struct {
|
||||||
|
Enum int `enum:"1,2,3" default:"1"`
|
||||||
|
}
|
||||||
|
k, err := kong.New(&cli)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
_, err = k.Parse([]string{"--enum=123"})
|
||||||
|
assert.EqualError(t, err, `--enum must be one of "1","2","3" but got "123"`)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRecursiveVariableExpansion(t *testing.T) {
|
||||||
|
var cli struct {
|
||||||
|
Config string `type:"path" default:"${config_file}" help:"Default: ${default}"`
|
||||||
|
}
|
||||||
|
k := mustNew(t, &cli, kong.Vars{"config_file": "/etc/config"}, kong.Exit(func(int) {}))
|
||||||
|
w := &strings.Builder{}
|
||||||
|
k.Stderr = w
|
||||||
|
k.Stdout = w
|
||||||
|
_, err := k.Parse([]string{"--help"})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Contains(t, w.String(), "Default: /etc/config")
|
||||||
|
}
|
||||||
|
|
||||||
|
type afterRunCLI struct {
|
||||||
|
runCalled bool `kong:"-"`
|
||||||
|
afterRunCalled bool `kong:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *afterRunCLI) Run() error {
|
||||||
|
c.runCalled = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *afterRunCLI) AfterRun() error {
|
||||||
|
c.afterRunCalled = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAfterRun(t *testing.T) {
|
||||||
|
var cli afterRunCLI
|
||||||
|
k := mustNew(t, &cli)
|
||||||
|
kctx, err := k.Parse([]string{})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
err = kctx.Run()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, afterRunCLI{runCalled: true, afterRunCalled: true}, cli)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProvidedString string
|
||||||
|
|
||||||
|
type providerCLI struct {
|
||||||
|
Sub providerSubCommand `cmd:""`
|
||||||
|
}
|
||||||
|
|
||||||
|
type providerSubCommand struct{}
|
||||||
|
|
||||||
|
func (p *providerCLI) ProvideFoo() (ProvidedString, error) {
|
||||||
|
return ProvidedString("foo"), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *providerSubCommand) Run(t *testing.T, ps ProvidedString) error {
|
||||||
|
assert.Equal(t, ProvidedString("foo"), ps)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProviderMethods(t *testing.T) {
|
||||||
|
k := mustNew(t, &providerCLI{})
|
||||||
|
kctx, err := k.Parse([]string{"sub"})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
err = kctx.Run(t)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
type EmbeddedCallback struct {
|
||||||
|
Nested NestedCallback `embed:""`
|
||||||
|
|
||||||
|
Embedded bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *EmbeddedCallback) AfterApply() error {
|
||||||
|
e.Embedded = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type taggedEmbeddedCallback struct {
|
||||||
|
NestedCallback
|
||||||
|
|
||||||
|
Tagged bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *taggedEmbeddedCallback) AfterApply() error {
|
||||||
|
e.Tagged = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type NestedCallback struct {
|
||||||
|
nested bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NestedCallback) AfterApply() error {
|
||||||
|
n.nested = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type EmbeddedRoot struct {
|
||||||
|
EmbeddedCallback
|
||||||
|
Tagged taggedEmbeddedCallback `embed:""`
|
||||||
|
Root bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *EmbeddedRoot) AfterApply() error {
|
||||||
|
e.Root = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEmbeddedCallbacks(t *testing.T) {
|
||||||
|
actual := &EmbeddedRoot{}
|
||||||
|
k := mustNew(t, actual)
|
||||||
|
_, err := k.Parse(nil)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
expected := &EmbeddedRoot{
|
||||||
|
EmbeddedCallback: EmbeddedCallback{
|
||||||
|
Embedded: true,
|
||||||
|
Nested: NestedCallback{
|
||||||
|
nested: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Tagged: taggedEmbeddedCallback{
|
||||||
|
Tagged: true,
|
||||||
|
NestedCallback: NestedCallback{
|
||||||
|
nested: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Root: true,
|
||||||
|
}
|
||||||
|
assert.Equal(t, expected, actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
type applyCalledOnce struct {
|
||||||
|
Dev bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *applyCalledOnce) AfterApply() error {
|
||||||
|
c.Dev = false
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c applyCalledOnce) Run() error {
|
||||||
|
if c.Dev {
|
||||||
|
return fmt.Errorf("--dev should not be set")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestApplyCalledOnce(t *testing.T) {
|
||||||
|
cli := &applyCalledOnce{}
|
||||||
|
kctx, err := mustNew(t, cli).Parse([]string{"--dev"})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
err = kctx.Run()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCustomTypeNoEllipsis(t *testing.T) {
|
||||||
|
type CLI struct {
|
||||||
|
Flag []byte `type:"existingfile"`
|
||||||
|
}
|
||||||
|
var cli CLI
|
||||||
|
p := mustNew(t, &cli, kong.Exit(func(int) {}))
|
||||||
|
w := &strings.Builder{}
|
||||||
|
p.Stderr = w
|
||||||
|
p.Stdout = w
|
||||||
|
_, err := p.Parse([]string{"--help"})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
help := w.String()
|
||||||
|
assert.NotContains(t, help, "...")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPrefixXorIssue343(t *testing.T) {
|
||||||
|
type DBConfig struct {
|
||||||
|
Password string `help:"Password" xor:"password" optional:""`
|
||||||
|
PasswordFile string `help:"File which content will be used for a password" xor:"password" optional:""`
|
||||||
|
PasswordCommand string `help:"Command to run to retrieve password" xor:"password" optional:""`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SourceTargetConfig struct {
|
||||||
|
Source DBConfig `help:"Database config of source to be copied from" prefix:"source-" xorprefix:"source-" embed:""`
|
||||||
|
Target DBConfig `help:"Database config of source to be copied from" prefix:"target-" xorprefix:"target-" embed:""`
|
||||||
|
}
|
||||||
|
|
||||||
|
cli := SourceTargetConfig{}
|
||||||
|
kctx := mustNew(t, &cli)
|
||||||
|
_, err := kctx.Parse([]string{"--source-password=foo", "--target-password=bar"})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
_, err = kctx.Parse([]string{"--source-password-file=foo", "--source-password=bar"})
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIssue483EmptyRootNodeNoRun(t *testing.T) {
|
||||||
|
var emptyCLI struct{}
|
||||||
|
parser, err := kong.New(&emptyCLI)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
kctx, err := parser.Parse([]string{})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
err = kctx.Run()
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Contains(t, err.Error(), "no command selected")
|
||||||
|
}
|
||||||
|
|
||||||
|
type providerWithoutErrorCLI struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *providerWithoutErrorCLI) Run(name string) error {
|
||||||
|
if name == "Bob" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("name %s is not Bob", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProviderWithoutError(t *testing.T) {
|
||||||
|
k := mustNew(t, &providerWithoutErrorCLI{})
|
||||||
|
kctx, err := k.Parse(nil)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
err = kctx.BindToProvider(func() string { return "Bob" })
|
||||||
|
assert.NoError(t, err)
|
||||||
|
err = kctx.Run()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseHyphenParameter(t *testing.T) {
|
||||||
|
type shortFlag struct {
|
||||||
|
Flag string `short:"f"`
|
||||||
|
Other string `short:"o"`
|
||||||
|
Numeric int `short:"n"`
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("ShortFlag", func(t *testing.T) {
|
||||||
|
actual := &shortFlag{}
|
||||||
|
_, err := mustNew(t, actual, kong.WithHyphenPrefixedParameters(true)).Parse([]string{"-f", "-foo"})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, &shortFlag{Flag: "-foo"}, actual)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("LongFlag", func(t *testing.T) {
|
||||||
|
actual := &shortFlag{}
|
||||||
|
_, err := mustNew(t, actual, kong.WithHyphenPrefixedParameters(true)).Parse([]string{"--flag", "-foo"})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, &shortFlag{Flag: "-foo"}, actual)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("ParamMatchesFlag", func(t *testing.T) {
|
||||||
|
actual := &shortFlag{}
|
||||||
|
_, err := mustNew(t, actual, kong.WithHyphenPrefixedParameters(true)).Parse([]string{"--flag", "-oo"})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, &shortFlag{Flag: "-oo"}, actual)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("NegativeNumber", func(t *testing.T) {
|
||||||
|
actual := &shortFlag{}
|
||||||
|
_, err := mustNew(t, actual, kong.WithHyphenPrefixedParameters(true)).Parse([]string{"--numeric", "-10"})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, &shortFlag{Numeric: -10}, actual)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
output:
|
||||||
|
- success
|
||||||
|
- failure
|
||||||
|
pre-push:
|
||||||
|
parallel: true
|
||||||
|
jobs:
|
||||||
|
- name: test
|
||||||
|
run: go test -v ./...
|
||||||
|
|
||||||
|
- name: lint
|
||||||
|
run: golangci-lint run
|
||||||
+3
-3
@@ -2,8 +2,8 @@ package kong
|
|||||||
|
|
||||||
import "unicode/utf8"
|
import "unicode/utf8"
|
||||||
|
|
||||||
// https://en.wikibooks.org/wiki/Algorithm_Implementation/Strings/Levenshtein_distance#Go
|
// Copied from https://github.com/daviddengcn/go-algs/blob/fe23fabd9d0670e4675326040ba7c285c7117b4c/ed/ed.go#L31
|
||||||
// License: https://creativecommons.org/licenses/by-sa/3.0/
|
// License: https://github.com/daviddengcn/go-algs/blob/fe23fabd9d0670e4675326040ba7c285c7117b4c/LICENSE
|
||||||
func levenshtein(a, b string) int {
|
func levenshtein(a, b string) int {
|
||||||
f := make([]int, utf8.RuneCountInString(b)+1)
|
f := make([]int, utf8.RuneCountInString(b)+1)
|
||||||
|
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -251,7 +251,7 @@ func (r *Registry) RegisterType(typ reflect.Type, mapper Mapper) *Registry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RegisterValue registers a Mapper by pointer to the field value.
|
// RegisterValue registers a Mapper by pointer to the field value.
|
||||||
func (r *Registry) RegisterValue(ptr interface{}, mapper Mapper) *Registry {
|
func (r *Registry) RegisterValue(ptr any, mapper Mapper) *Registry {
|
||||||
key := reflect.ValueOf(ptr)
|
key := reflect.ValueOf(ptr)
|
||||||
if key.Kind() != reflect.Ptr {
|
if key.Kind() != reflect.Ptr {
|
||||||
panic("expected a pointer")
|
panic("expected a pointer")
|
||||||
@@ -387,7 +387,7 @@ func intDecoder(bits int) MapperFunc { //nolint: dupl
|
|||||||
default:
|
default:
|
||||||
return fmt.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)
|
n, err := strconv.ParseInt(sv, 0, bits)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.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)
|
||||||
}
|
}
|
||||||
@@ -416,7 +416,7 @@ func uintDecoder(bits int) MapperFunc { //nolint: dupl
|
|||||||
default:
|
default:
|
||||||
return fmt.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)
|
n, err := strconv.ParseUint(sv, 0, bits)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.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)
|
||||||
}
|
}
|
||||||
@@ -473,7 +473,7 @@ func mapDecoder(r *Registry) MapperFunc {
|
|||||||
case string:
|
case string:
|
||||||
childScanner = ScanAsType(t.Type, SplitEscaped(v, mapsep)...)
|
childScanner = ScanAsType(t.Type, SplitEscaped(v, mapsep)...)
|
||||||
|
|
||||||
case []map[string]interface{}:
|
case []map[string]any:
|
||||||
for _, m := range v {
|
for _, m := range v {
|
||||||
err := jsonTranscode(m, target.Addr().Interface())
|
err := jsonTranscode(m, target.Addr().Interface())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -482,7 +482,7 @@ func mapDecoder(r *Registry) MapperFunc {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
case map[string]interface{}:
|
case map[string]any:
|
||||||
return jsonTranscode(v, target.Addr().Interface())
|
return jsonTranscode(v, target.Addr().Interface())
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@@ -548,11 +548,11 @@ func sliceDecoder(r *Registry) MapperFunc {
|
|||||||
case string:
|
case string:
|
||||||
childScanner = ScanAsType(t.Type, SplitEscaped(v, sep)...)
|
childScanner = ScanAsType(t.Type, SplitEscaped(v, sep)...)
|
||||||
|
|
||||||
case []interface{}:
|
case []any:
|
||||||
return jsonTranscode(v, target.Addr().Interface())
|
return jsonTranscode(v, target.Addr().Interface())
|
||||||
|
|
||||||
default:
|
default:
|
||||||
v = []interface{}{v}
|
v = []any{v}
|
||||||
return jsonTranscode(v, target.Addr().Interface())
|
return jsonTranscode(v, target.Addr().Interface())
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -922,7 +922,7 @@ func (f *FileContentFlag) Decode(ctx *DecodeContext) error { //nolint: revive
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func jsonTranscode(in, out interface{}) error {
|
func jsonTranscode(in, out any) error {
|
||||||
data, err := json.Marshal(in)
|
data, err := json.Marshal(in)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
+55
-4
@@ -268,9 +268,8 @@ func TestFileContentFlag(t *testing.T) {
|
|||||||
var cli struct {
|
var cli struct {
|
||||||
File kong.FileContentFlag
|
File kong.FileContentFlag
|
||||||
}
|
}
|
||||||
f, err := os.CreateTemp("", "")
|
f, err := os.CreateTemp(t.TempDir(), "")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
defer os.Remove(f.Name())
|
|
||||||
fmt.Fprint(f, "hello world")
|
fmt.Fprint(f, "hello world")
|
||||||
f.Close()
|
f.Close()
|
||||||
_, err = mustNew(t, &cli).Parse([]string{"--file", f.Name()})
|
_, err = mustNew(t, &cli).Parse([]string{"--file", f.Name()})
|
||||||
@@ -282,9 +281,8 @@ func TestNamedFileContentFlag(t *testing.T) {
|
|||||||
var cli struct {
|
var cli struct {
|
||||||
File kong.NamedFileContentFlag
|
File kong.NamedFileContentFlag
|
||||||
}
|
}
|
||||||
f, err := os.CreateTemp("", "")
|
f, err := os.CreateTemp(t.TempDir(), "")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
defer os.Remove(f.Name())
|
|
||||||
fmt.Fprint(f, "hello world")
|
fmt.Fprint(f, "hello world")
|
||||||
f.Close()
|
f.Close()
|
||||||
_, err = mustNew(t, &cli).Parse([]string{"--file", f.Name()})
|
_, err = mustNew(t, &cli).Parse([]string{"--file", f.Name()})
|
||||||
@@ -397,6 +395,31 @@ func TestNumbers(t *testing.T) {
|
|||||||
I64: math.MinInt64,
|
I64: math.MinInt64,
|
||||||
}, cli)
|
}, cli)
|
||||||
})
|
})
|
||||||
|
t.Run("Integer literals", func(t *testing.T) {
|
||||||
|
integerLiterals := "10_0"
|
||||||
|
|
||||||
|
_, err := p.Parse([]string{
|
||||||
|
fmt.Sprintf("--i-8=%s", integerLiterals),
|
||||||
|
fmt.Sprintf("--i-16=%s", integerLiterals),
|
||||||
|
fmt.Sprintf("--i-32=%s", integerLiterals),
|
||||||
|
fmt.Sprintf("--i-64=%s", integerLiterals),
|
||||||
|
fmt.Sprintf("--u-8=%s", integerLiterals),
|
||||||
|
fmt.Sprintf("--u-16=%s", integerLiterals),
|
||||||
|
fmt.Sprintf("--u-32=%s", integerLiterals),
|
||||||
|
fmt.Sprintf("--u-64=%s", integerLiterals),
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, CLI{
|
||||||
|
I8: int8(100),
|
||||||
|
I16: int16(100),
|
||||||
|
I32: int32(100),
|
||||||
|
I64: int64(100),
|
||||||
|
U8: uint8(100),
|
||||||
|
U16: uint16(100),
|
||||||
|
U32: uint32(100),
|
||||||
|
U64: uint64(100),
|
||||||
|
}, cli)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestJSONLargeNumber(t *testing.T) {
|
func TestJSONLargeNumber(t *testing.T) {
|
||||||
@@ -756,3 +779,31 @@ func TestValuesThatLookLikeFlags(t *testing.T) {
|
|||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, map[string]string{"-foo": "-bar"}, cli.Map)
|
assert.Equal(t, map[string]string{"-foo": "-bar"}, cli.Map)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DecodeCLI struct {
|
||||||
|
Foo DecodeFoo `env:"FOO"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DecodeFoo struct {
|
||||||
|
Bar string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f DecodeFoo) Decode(ctx *kong.DecodeContext) error {
|
||||||
|
ctx.Value.Target.Set(reflect.ValueOf(struct {
|
||||||
|
Bar string
|
||||||
|
}{"baz"}))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDecode(t *testing.T) {
|
||||||
|
c := &DecodeCLI{}
|
||||||
|
parser, err := kong.New(c)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
t.Setenv("FOO", "foo")
|
||||||
|
|
||||||
|
_, err = parser.Parse([]string{})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, c.Foo.Bar, "baz")
|
||||||
|
}
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ func (n *Node) Leaf() bool {
|
|||||||
// Find a command/argument/flag by pointer to its field.
|
// Find a command/argument/flag by pointer to its field.
|
||||||
//
|
//
|
||||||
// Returns nil if not found. Panics if ptr is not a pointer.
|
// Returns nil if not found. Panics if ptr is not a pointer.
|
||||||
func (n *Node) Find(ptr interface{}) *Node {
|
func (n *Node) Find(ptr any) *Node {
|
||||||
key := reflect.ValueOf(ptr)
|
key := reflect.ValueOf(ptr)
|
||||||
if key.Kind() != reflect.Ptr {
|
if key.Kind() != reflect.Ptr {
|
||||||
panic("expected a pointer")
|
panic("expected a pointer")
|
||||||
@@ -167,6 +167,9 @@ func (n *Node) Summary() string {
|
|||||||
allFlags = append(allFlags, n.Parent.Flags...)
|
allFlags = append(allFlags, n.Parent.Flags...)
|
||||||
}
|
}
|
||||||
for _, flag := range allFlags {
|
for _, flag := range allFlags {
|
||||||
|
if _, ok := flag.Target.Interface().(helpFlag); ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if !flag.Required {
|
if !flag.Required {
|
||||||
summary += " [flags]"
|
summary += " [flags]"
|
||||||
break
|
break
|
||||||
@@ -239,23 +242,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 +409,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
|
||||||
@@ -431,7 +436,7 @@ func (f *Flag) FormatPlaceHolder() string {
|
|||||||
return placeholderHelper.PlaceHolder(f)
|
return placeholderHelper.PlaceHolder(f)
|
||||||
}
|
}
|
||||||
tail := ""
|
tail := ""
|
||||||
if f.Value.IsSlice() && f.Value.Tag.Sep != -1 {
|
if f.Value.IsSlice() && f.Value.Tag.Sep != -1 && f.Tag.Type == "" {
|
||||||
tail += string(f.Value.Tag.Sep) + "..."
|
tail += string(f.Value.Tag.Sep) + "..."
|
||||||
}
|
}
|
||||||
if f.PlaceHolder != "" {
|
if f.PlaceHolder != "" {
|
||||||
@@ -444,7 +449,7 @@ func (f *Flag) FormatPlaceHolder() string {
|
|||||||
return f.Default + tail
|
return f.Default + tail
|
||||||
}
|
}
|
||||||
if f.Value.IsMap() {
|
if f.Value.IsMap() {
|
||||||
if f.Value.Tag.MapSep != -1 {
|
if f.Value.Tag.MapSep != -1 && f.Tag.Type == "" {
|
||||||
tail = string(f.Value.Tag.MapSep) + "..."
|
tail = string(f.Value.Tag.MapSep) + "..."
|
||||||
}
|
}
|
||||||
return "KEY=VALUE" + tail
|
return "KEY=VALUE" + tail
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package kong_test
|
package kong_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/alecthomas/assert/v2"
|
"github.com/alecthomas/assert/v2"
|
||||||
@@ -70,3 +71,22 @@ func TestFlagString(t *testing.T) {
|
|||||||
assert.Equal(t, want, flag.String())
|
assert.Equal(t, want, flag.String())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIgnoreHelpInUsage(t *testing.T) {
|
||||||
|
var cli struct {
|
||||||
|
One string `required:""`
|
||||||
|
}
|
||||||
|
|
||||||
|
k := mustNew(t, &cli)
|
||||||
|
w := &bytes.Buffer{}
|
||||||
|
k.Stdout = w
|
||||||
|
k.Exit = func(code int) {}
|
||||||
|
_, err := k.Parse([]string{"--help"})
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Equal(t, `Usage: test --one=STRING
|
||||||
|
|
||||||
|
Flags:
|
||||||
|
-h, --help Show context-sensitive help.
|
||||||
|
--one=STRING
|
||||||
|
`, w.String())
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
+78
-8
@@ -55,6 +55,16 @@ func Exit(exit func(int)) Option {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithHyphenPrefixedParameters enables or disables hyphen-prefixed parameters.
|
||||||
|
//
|
||||||
|
// These are disabled by default.
|
||||||
|
func WithHyphenPrefixedParameters(enable bool) Option {
|
||||||
|
return OptionFunc(func(k *Kong) error {
|
||||||
|
k.allowHyphenated = enable
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
type embedded struct {
|
type embedded struct {
|
||||||
strct any
|
strct any
|
||||||
tags []string
|
tags []string
|
||||||
@@ -79,7 +89,7 @@ type dynamicCommand struct {
|
|||||||
help string
|
help string
|
||||||
group string
|
group string
|
||||||
tags []string
|
tags []string
|
||||||
cmd interface{}
|
cmd any
|
||||||
}
|
}
|
||||||
|
|
||||||
// DynamicCommand registers a dynamically constructed command with the root of the CLI.
|
// DynamicCommand registers a dynamically constructed command with the root of the CLI.
|
||||||
@@ -87,8 +97,12 @@ type dynamicCommand struct {
|
|||||||
// This is useful for command-line structures that are extensible via user-provided plugins.
|
// 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 <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 any, 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,
|
||||||
@@ -119,6 +133,40 @@ func PostBuild(fn func(*Kong) error) Option {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithBeforeReset registers a hook to run before fields values are reset to their defaults
|
||||||
|
// (as specified in the grammar) or to zero values.
|
||||||
|
func WithBeforeReset(fn any) Option {
|
||||||
|
return withHook("BeforeReset", fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithBeforeResolve registers a hook to run before resolvers are applied.
|
||||||
|
func WithBeforeResolve(fn any) Option {
|
||||||
|
return withHook("BeforeResolve", fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithBeforeApply registers a hook to run before command line arguments are applied to the grammar.
|
||||||
|
func WithBeforeApply(fn any) Option {
|
||||||
|
return withHook("BeforeApply", fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithAfterApply registers a hook to run after values are applied to the grammar and validated.
|
||||||
|
func WithAfterApply(fn any) Option {
|
||||||
|
return withHook("AfterApply", fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// withHook registers a named hook.
|
||||||
|
func withHook(name string, fn any) Option {
|
||||||
|
value := reflect.ValueOf(fn)
|
||||||
|
if value.Kind() != reflect.Func {
|
||||||
|
panic(fmt.Errorf("expected function, got %s", value.Type()))
|
||||||
|
}
|
||||||
|
|
||||||
|
return OptionFunc(func(k *Kong) error {
|
||||||
|
k.hooks[name] = append(k.hooks[name], value)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Name overrides the application name.
|
// Name overrides the application name.
|
||||||
func Name(name string) Option {
|
func Name(name string) Option {
|
||||||
return PostBuild(func(k *Kong) error {
|
return PostBuild(func(k *Kong) error {
|
||||||
@@ -152,7 +200,7 @@ func KindMapper(kind reflect.Kind, mapper Mapper) Option {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ValueMapper registers a mapper to a field value.
|
// ValueMapper registers a mapper to a field value.
|
||||||
func ValueMapper(ptr interface{}, mapper Mapper) Option {
|
func ValueMapper(ptr any, mapper Mapper) Option {
|
||||||
return OptionFunc(func(k *Kong) error {
|
return OptionFunc(func(k *Kong) error {
|
||||||
k.registry.RegisterValue(ptr, mapper)
|
k.registry.RegisterValue(ptr, mapper)
|
||||||
return nil
|
return nil
|
||||||
@@ -187,7 +235,7 @@ func Writers(stdout, stderr io.Writer) Option {
|
|||||||
// AfterApply(...) error
|
// AfterApply(...) error
|
||||||
//
|
//
|
||||||
// Called before validation/assignment, and immediately after validation/assignment, respectively.
|
// Called before validation/assignment, and immediately after validation/assignment, respectively.
|
||||||
func Bind(args ...interface{}) Option {
|
func Bind(args ...any) Option {
|
||||||
return OptionFunc(func(k *Kong) error {
|
return OptionFunc(func(k *Kong) error {
|
||||||
k.bindings.add(args...)
|
k.bindings.add(args...)
|
||||||
return nil
|
return nil
|
||||||
@@ -197,20 +245,42 @@ func Bind(args ...interface{}) Option {
|
|||||||
// BindTo allows binding of implementations to interfaces.
|
// BindTo allows binding of implementations to interfaces.
|
||||||
//
|
//
|
||||||
// BindTo(impl, (*iface)(nil))
|
// BindTo(impl, (*iface)(nil))
|
||||||
func BindTo(impl, iface interface{}) Option {
|
func BindTo(impl, iface any) Option {
|
||||||
return OptionFunc(func(k *Kong) error {
|
return OptionFunc(func(k *Kong) error {
|
||||||
k.bindings.addTo(impl, iface)
|
k.bindings.addTo(impl, iface)
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// BindToProvider allows binding of provider functions.
|
// BindToProvider binds an injected value to a provider function.
|
||||||
|
//
|
||||||
|
// The provider function must have one of the following signatures:
|
||||||
|
//
|
||||||
|
// func(...) (T, error)
|
||||||
|
// func(...) T
|
||||||
|
//
|
||||||
|
// Where arguments to the function are injected by Kong.
|
||||||
//
|
//
|
||||||
// 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.
|
||||||
func BindToProvider(provider interface{}) Option {
|
func BindToProvider(provider any) Option {
|
||||||
return OptionFunc(func(k *Kong) error {
|
return OptionFunc(func(k *Kong) error {
|
||||||
return k.bindings.addProvider(provider)
|
return k.bindings.addProvider(provider, false /* singleton */)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// BindSingletonProvider binds an injected value to a provider function.
|
||||||
|
// The provider function must have the signature:
|
||||||
|
//
|
||||||
|
// func(...) (T, error)
|
||||||
|
// func(...) T
|
||||||
|
//
|
||||||
|
// Unlike [BindToProvider], the provider function will only be called
|
||||||
|
// at most once, and the result will be cached and reused
|
||||||
|
// across multiple recipients of the injected value.
|
||||||
|
func BindSingletonProvider(provider any) Option {
|
||||||
|
return OptionFunc(func(k *Kong) error {
|
||||||
|
return k.bindings.addProvider(provider, true /* singleton */)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+62
-1
@@ -89,11 +89,13 @@ func TestCallbackCustomError(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type bindToProviderCLI struct {
|
type bindToProviderCLI struct {
|
||||||
|
Filled bool `default:"true"`
|
||||||
Called bool
|
Called bool
|
||||||
Cmd bindToProviderCmd `cmd:""`
|
Cmd bindToProviderCmd `cmd:""`
|
||||||
}
|
}
|
||||||
|
|
||||||
type boundThing struct {
|
type boundThing struct {
|
||||||
|
Filled bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type bindToProviderCmd struct{}
|
type bindToProviderCmd struct{}
|
||||||
@@ -105,7 +107,10 @@ func (*bindToProviderCmd) Run(cli *bindToProviderCLI, b *boundThing) error {
|
|||||||
|
|
||||||
func TestBindToProvider(t *testing.T) {
|
func TestBindToProvider(t *testing.T) {
|
||||||
var cli bindToProviderCLI
|
var cli bindToProviderCLI
|
||||||
app, err := New(&cli, BindToProvider(func() (*boundThing, error) { return &boundThing{}, nil }))
|
app, err := New(&cli, BindToProvider(func(cli *bindToProviderCLI) (*boundThing, error) {
|
||||||
|
assert.True(t, cli.Filled, "CLI struct should have already been populated by Kong")
|
||||||
|
return &boundThing{Filled: cli.Filled}, nil
|
||||||
|
}))
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
ctx, err := app.Parse([]string{"cmd"})
|
ctx, err := app.Parse([]string{"cmd"})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
@@ -114,6 +119,43 @@ func TestBindToProvider(t *testing.T) {
|
|||||||
assert.True(t, cli.Called)
|
assert.True(t, cli.Called)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBindSingletonProvider(t *testing.T) {
|
||||||
|
type (
|
||||||
|
Connection struct{}
|
||||||
|
ClientA struct{ conn *Connection }
|
||||||
|
ClientB struct{ conn *Connection }
|
||||||
|
)
|
||||||
|
|
||||||
|
var numConnections int
|
||||||
|
newConnection := func() *Connection {
|
||||||
|
numConnections++
|
||||||
|
return &Connection{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var cli struct{}
|
||||||
|
app, err := New(&cli,
|
||||||
|
BindSingletonProvider(newConnection),
|
||||||
|
BindToProvider(func(conn *Connection) *ClientA {
|
||||||
|
return &ClientA{conn: conn}
|
||||||
|
}),
|
||||||
|
BindToProvider(func(conn *Connection) *ClientB {
|
||||||
|
return &ClientB{conn: conn}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
ctx, err := app.Parse([]string{})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
_, err = ctx.Call(func(a *ClientA, b *ClientB) {
|
||||||
|
assert.NotZero(t, a.conn)
|
||||||
|
assert.NotZero(t, b.conn)
|
||||||
|
|
||||||
|
assert.Equal(t, 1, numConnections, "expected newConnection to be called only once")
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
func TestFlagNamer(t *testing.T) {
|
func TestFlagNamer(t *testing.T) {
|
||||||
var cli struct {
|
var cli struct {
|
||||||
SomeFlag string
|
SomeFlag string
|
||||||
@@ -122,3 +164,22 @@ func TestFlagNamer(t *testing.T) {
|
|||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, "SOMEFLAG", app.Model.Flags[1].Name)
|
assert.Equal(t, "SOMEFLAG", app.Model.Flags[1].Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type npError string
|
||||||
|
|
||||||
|
func (e npError) Error() string {
|
||||||
|
return "ERROR: " + string(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCallbackNonPointerError(t *testing.T) {
|
||||||
|
method := func() error {
|
||||||
|
return npError("failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
var cli struct{}
|
||||||
|
|
||||||
|
p, err := New(&cli)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
err = callFunction(reflect.ValueOf(method), p.bindings)
|
||||||
|
assert.EqualError(t, err, "ERROR: failed")
|
||||||
|
}
|
||||||
|
|||||||
+7
-7
@@ -14,15 +14,15 @@ type Resolver interface {
|
|||||||
Validate(app *Application) error
|
Validate(app *Application) error
|
||||||
|
|
||||||
// Resolve the value for a Flag.
|
// Resolve the value for a Flag.
|
||||||
Resolve(context *Context, parent *Path, flag *Flag) (interface{}, error)
|
Resolve(context *Context, parent *Path, flag *Flag) (any, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResolverFunc is a convenience type for non-validating Resolvers.
|
// ResolverFunc is a convenience type for non-validating Resolvers.
|
||||||
type ResolverFunc func(context *Context, parent *Path, flag *Flag) (interface{}, error)
|
type ResolverFunc func(context *Context, parent *Path, flag *Flag) (any, error)
|
||||||
|
|
||||||
var _ Resolver = ResolverFunc(nil)
|
var _ Resolver = ResolverFunc(nil)
|
||||||
|
|
||||||
func (r ResolverFunc) Resolve(context *Context, parent *Path, flag *Flag) (interface{}, error) { //nolint: revive
|
func (r ResolverFunc) Resolve(context *Context, parent *Path, flag *Flag) (any, error) { //nolint: revive
|
||||||
return r(context, parent, flag)
|
return r(context, parent, flag)
|
||||||
}
|
}
|
||||||
func (r ResolverFunc) Validate(app *Application) error { return nil } //nolint: revive
|
func (r ResolverFunc) Validate(app *Application) error { return nil } //nolint: revive
|
||||||
@@ -31,12 +31,12 @@ func (r ResolverFunc) Validate(app *Application) error { return nil } //nolint:
|
|||||||
//
|
//
|
||||||
// Flag names are used as JSON keys indirectly, by tring snake_case and camelCase variants.
|
// Flag names are used as JSON keys indirectly, by tring snake_case and camelCase variants.
|
||||||
func JSON(r io.Reader) (Resolver, error) {
|
func JSON(r io.Reader) (Resolver, error) {
|
||||||
values := map[string]interface{}{}
|
values := map[string]any{}
|
||||||
err := json.NewDecoder(r).Decode(&values)
|
err := json.NewDecoder(r).Decode(&values)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
var f ResolverFunc = func(context *Context, parent *Path, flag *Flag) (interface{}, error) {
|
var f ResolverFunc = func(context *Context, parent *Path, flag *Flag) (any, error) {
|
||||||
name := strings.ReplaceAll(flag.Name, "-", "_")
|
name := strings.ReplaceAll(flag.Name, "-", "_")
|
||||||
snakeCaseName := snakeCase(flag.Name)
|
snakeCaseName := snakeCase(flag.Name)
|
||||||
raw, ok := values[name]
|
raw, ok := values[name]
|
||||||
@@ -47,7 +47,7 @@ func JSON(r io.Reader) (Resolver, error) {
|
|||||||
}
|
}
|
||||||
raw = values
|
raw = values
|
||||||
for _, part := range strings.Split(name, ".") {
|
for _, part := range strings.Split(name, ".") {
|
||||||
if values, ok := raw.(map[string]interface{}); ok {
|
if values, ok := raw.(map[string]any); ok {
|
||||||
raw, ok = values[part]
|
raw, ok = values[part]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
@@ -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), "-"), "") //nolint:staticcheck // Unicode punctuation not an issue
|
||||||
return strings.ToLower(name[:1]) + name[1:]
|
return strings.ToLower(name[:1]) + name[1:]
|
||||||
}
|
}
|
||||||
|
|||||||
+25
-47
@@ -2,7 +2,6 @@ package kong_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"os"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -13,23 +12,13 @@ import (
|
|||||||
|
|
||||||
type envMap map[string]string
|
type envMap map[string]string
|
||||||
|
|
||||||
func tempEnv(env envMap) func() {
|
func newEnvParser(t *testing.T, cli any, env envMap, options ...kong.Option) *kong.Kong {
|
||||||
for k, v := range env {
|
|
||||||
os.Setenv(k, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
return func() {
|
|
||||||
for k := range env {
|
|
||||||
os.Unsetenv(k)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func newEnvParser(t *testing.T, cli interface{}, env envMap, options ...kong.Option) (*kong.Kong, func()) {
|
|
||||||
t.Helper()
|
t.Helper()
|
||||||
restoreEnv := tempEnv(env)
|
for name, value := range env {
|
||||||
|
t.Setenv(name, value)
|
||||||
|
}
|
||||||
parser := mustNew(t, cli, options...)
|
parser := mustNew(t, cli, options...)
|
||||||
return parser, restoreEnv
|
return parser
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEnvarsFlagBasic(t *testing.T) {
|
func TestEnvarsFlagBasic(t *testing.T) {
|
||||||
@@ -39,7 +28,7 @@ func TestEnvarsFlagBasic(t *testing.T) {
|
|||||||
Interp string `env:"${kongInterp}"`
|
Interp string `env:"${kongInterp}"`
|
||||||
}
|
}
|
||||||
kongInterpEnv := "KONG_INTERP"
|
kongInterpEnv := "KONG_INTERP"
|
||||||
parser, unsetEnvs := newEnvParser(t, &cli,
|
parser := newEnvParser(t, &cli,
|
||||||
envMap{
|
envMap{
|
||||||
"KONG_STRING": "bye",
|
"KONG_STRING": "bye",
|
||||||
"KONG_SLICE": "5,2,9",
|
"KONG_SLICE": "5,2,9",
|
||||||
@@ -49,7 +38,6 @@ func TestEnvarsFlagBasic(t *testing.T) {
|
|||||||
"kongInterp": kongInterpEnv,
|
"kongInterp": kongInterpEnv,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
defer unsetEnvs()
|
|
||||||
|
|
||||||
_, err := parser.Parse([]string{})
|
_, err := parser.Parse([]string{})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
@@ -63,14 +51,13 @@ func TestEnvarsFlagMultiple(t *testing.T) {
|
|||||||
FirstENVPresent string `env:"KONG_TEST1_1,KONG_TEST1_2"`
|
FirstENVPresent string `env:"KONG_TEST1_1,KONG_TEST1_2"`
|
||||||
SecondENVPresent string `env:"KONG_TEST2_1,KONG_TEST2_2"`
|
SecondENVPresent string `env:"KONG_TEST2_1,KONG_TEST2_2"`
|
||||||
}
|
}
|
||||||
parser, unsetEnvs := newEnvParser(t, &cli,
|
parser := newEnvParser(t, &cli,
|
||||||
envMap{
|
envMap{
|
||||||
"KONG_TEST1_1": "value1.1",
|
"KONG_TEST1_1": "value1.1",
|
||||||
"KONG_TEST1_2": "value1.2",
|
"KONG_TEST1_2": "value1.2",
|
||||||
"KONG_TEST2_2": "value2.2",
|
"KONG_TEST2_2": "value2.2",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
defer unsetEnvs()
|
|
||||||
|
|
||||||
_, err := parser.Parse([]string{})
|
_, err := parser.Parse([]string{})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
@@ -82,8 +69,7 @@ func TestEnvarsFlagOverride(t *testing.T) {
|
|||||||
var cli struct {
|
var cli struct {
|
||||||
Flag string `env:"KONG_FLAG"`
|
Flag string `env:"KONG_FLAG"`
|
||||||
}
|
}
|
||||||
parser, restoreEnv := newEnvParser(t, &cli, envMap{"KONG_FLAG": "bye"})
|
parser := newEnvParser(t, &cli, envMap{"KONG_FLAG": "bye"})
|
||||||
defer restoreEnv()
|
|
||||||
|
|
||||||
_, err := parser.Parse([]string{"--flag=hello"})
|
_, err := parser.Parse([]string{"--flag=hello"})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
@@ -94,8 +80,7 @@ func TestEnvarsTag(t *testing.T) {
|
|||||||
var cli struct {
|
var cli struct {
|
||||||
Slice []int `env:"KONG_NUMBERS"`
|
Slice []int `env:"KONG_NUMBERS"`
|
||||||
}
|
}
|
||||||
parser, restoreEnv := newEnvParser(t, &cli, envMap{"KONG_NUMBERS": "5,2,9"})
|
parser := newEnvParser(t, &cli, envMap{"KONG_NUMBERS": "5,2,9"})
|
||||||
defer restoreEnv()
|
|
||||||
|
|
||||||
_, err := parser.Parse([]string{})
|
_, err := parser.Parse([]string{})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
@@ -109,8 +94,7 @@ func TestEnvarsEnvPrefix(t *testing.T) {
|
|||||||
var cli struct {
|
var cli struct {
|
||||||
Anonymous `envprefix:"KONG_"`
|
Anonymous `envprefix:"KONG_"`
|
||||||
}
|
}
|
||||||
parser, restoreEnv := newEnvParser(t, &cli, envMap{"KONG_NUMBERS": "1,2,3"})
|
parser := newEnvParser(t, &cli, envMap{"KONG_NUMBERS": "1,2,3"})
|
||||||
defer restoreEnv()
|
|
||||||
|
|
||||||
_, err := parser.Parse([]string{})
|
_, err := parser.Parse([]string{})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
@@ -125,8 +109,7 @@ func TestEnvarsEnvPrefixMultiple(t *testing.T) {
|
|||||||
var cli struct {
|
var cli struct {
|
||||||
Anonymous `envprefix:"KONG_"`
|
Anonymous `envprefix:"KONG_"`
|
||||||
}
|
}
|
||||||
parser, restoreEnv := newEnvParser(t, &cli, envMap{"KONG_NUMBERS1_1": "1,2,3", "KONG_NUMBERS2_2": "5,6,7"})
|
parser := newEnvParser(t, &cli, envMap{"KONG_NUMBERS1_1": "1,2,3", "KONG_NUMBERS2_2": "5,6,7"})
|
||||||
defer restoreEnv()
|
|
||||||
|
|
||||||
_, err := parser.Parse([]string{})
|
_, err := parser.Parse([]string{})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
@@ -144,8 +127,7 @@ func TestEnvarsNestedEnvPrefix(t *testing.T) {
|
|||||||
var cli struct {
|
var cli struct {
|
||||||
Anonymous `envprefix:"KONG_"`
|
Anonymous `envprefix:"KONG_"`
|
||||||
}
|
}
|
||||||
parser, restoreEnv := newEnvParser(t, &cli, envMap{"KONG_ANON_STRING": "abc"})
|
parser := newEnvParser(t, &cli, envMap{"KONG_ANON_STRING": "abc"})
|
||||||
defer restoreEnv()
|
|
||||||
|
|
||||||
_, err := parser.Parse([]string{})
|
_, err := parser.Parse([]string{})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
@@ -156,15 +138,13 @@ func TestEnvarsWithDefault(t *testing.T) {
|
|||||||
var cli struct {
|
var cli struct {
|
||||||
Flag string `env:"KONG_FLAG" default:"default"`
|
Flag string `env:"KONG_FLAG" default:"default"`
|
||||||
}
|
}
|
||||||
parser, restoreEnv := newEnvParser(t, &cli, envMap{})
|
parser := newEnvParser(t, &cli, envMap{})
|
||||||
defer restoreEnv()
|
|
||||||
|
|
||||||
_, err := parser.Parse(nil)
|
_, err := parser.Parse(nil)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, "default", cli.Flag)
|
assert.Equal(t, "default", cli.Flag)
|
||||||
|
|
||||||
parser, restoreEnv = newEnvParser(t, &cli, envMap{"KONG_FLAG": "moo"})
|
parser = newEnvParser(t, &cli, envMap{"KONG_FLAG": "moo"})
|
||||||
defer restoreEnv()
|
|
||||||
_, err = parser.Parse(nil)
|
_, err = parser.Parse(nil)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, "moo", cli.Flag)
|
assert.Equal(t, "moo", cli.Flag)
|
||||||
@@ -194,7 +174,7 @@ func TestEnv(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// With the prefix
|
// With the prefix
|
||||||
parser, unsetEnvs := newEnvParser(t, &cli, envMap{
|
parser := newEnvParser(t, &cli, envMap{
|
||||||
"KONG_ONE_FLAG": "one",
|
"KONG_ONE_FLAG": "one",
|
||||||
"KONG_TWO_FLAG": "two",
|
"KONG_TWO_FLAG": "two",
|
||||||
"KONG_THREE_FLAG": "three",
|
"KONG_THREE_FLAG": "three",
|
||||||
@@ -202,14 +182,13 @@ func TestEnv(t *testing.T) {
|
|||||||
"KONG_FIVE": "true",
|
"KONG_FIVE": "true",
|
||||||
"KONG_SIX": "true",
|
"KONG_SIX": "true",
|
||||||
}, kong.DefaultEnvars("KONG"))
|
}, kong.DefaultEnvars("KONG"))
|
||||||
defer unsetEnvs()
|
|
||||||
|
|
||||||
_, err := parser.Parse(nil)
|
_, err := parser.Parse(nil)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, expected, cli)
|
assert.Equal(t, expected, cli)
|
||||||
|
|
||||||
// Without the prefix
|
// Without the prefix
|
||||||
parser, unsetEnvs = newEnvParser(t, &cli, envMap{
|
parser = newEnvParser(t, &cli, envMap{
|
||||||
"ONE_FLAG": "one",
|
"ONE_FLAG": "one",
|
||||||
"TWO_FLAG": "two",
|
"TWO_FLAG": "two",
|
||||||
"THREE_FLAG": "three",
|
"THREE_FLAG": "three",
|
||||||
@@ -217,7 +196,6 @@ func TestEnv(t *testing.T) {
|
|||||||
"FIVE": "true",
|
"FIVE": "true",
|
||||||
"SIX": "true",
|
"SIX": "true",
|
||||||
}, kong.DefaultEnvars(""))
|
}, kong.DefaultEnvars(""))
|
||||||
defer unsetEnvs()
|
|
||||||
|
|
||||||
_, err = parser.Parse(nil)
|
_, err = parser.Parse(nil)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
@@ -281,10 +259,10 @@ func TestResolversWithMappers(t *testing.T) {
|
|||||||
Flag string `env:"KONG_MOO" type:"upper"`
|
Flag string `env:"KONG_MOO" type:"upper"`
|
||||||
}
|
}
|
||||||
|
|
||||||
restoreEnv := tempEnv(envMap{"KONG_MOO": "meow"})
|
t.Setenv("KONG_MOO", "meow")
|
||||||
defer restoreEnv()
|
|
||||||
|
|
||||||
parser := mustNew(t, &cli,
|
parser := newEnvParser(t, &cli,
|
||||||
|
envMap{"KONG_MOO": "meow"},
|
||||||
kong.NamedMapper("upper", testUppercaseMapper{}),
|
kong.NamedMapper("upper", testUppercaseMapper{}),
|
||||||
)
|
)
|
||||||
_, err := parser.Parse([]string{})
|
_, err := parser.Parse([]string{})
|
||||||
@@ -297,7 +275,7 @@ func TestResolverWithBool(t *testing.T) {
|
|||||||
Bool bool
|
Bool bool
|
||||||
}
|
}
|
||||||
|
|
||||||
var resolver kong.ResolverFunc = func(context *kong.Context, parent *kong.Path, flag *kong.Flag) (interface{}, error) {
|
var resolver kong.ResolverFunc = func(context *kong.Context, parent *kong.Path, flag *kong.Flag) (any, error) {
|
||||||
if flag.Name == "bool" {
|
if flag.Name == "bool" {
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
@@ -316,14 +294,14 @@ func TestLastResolverWins(t *testing.T) {
|
|||||||
Int []int
|
Int []int
|
||||||
}
|
}
|
||||||
|
|
||||||
var first kong.ResolverFunc = func(context *kong.Context, parent *kong.Path, flag *kong.Flag) (interface{}, error) {
|
var first kong.ResolverFunc = func(context *kong.Context, parent *kong.Path, flag *kong.Flag) (any, error) {
|
||||||
if flag.Name == "int" {
|
if flag.Name == "int" {
|
||||||
return 1, nil
|
return 1, nil
|
||||||
}
|
}
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var second kong.ResolverFunc = func(context *kong.Context, parent *kong.Path, flag *kong.Flag) (interface{}, error) {
|
var second kong.ResolverFunc = func(context *kong.Context, parent *kong.Path, flag *kong.Flag) (any, error) {
|
||||||
if flag.Name == "int" {
|
if flag.Name == "int" {
|
||||||
return 2, nil
|
return 2, nil
|
||||||
}
|
}
|
||||||
@@ -340,7 +318,7 @@ func TestResolverSatisfiesRequired(t *testing.T) {
|
|||||||
var cli struct {
|
var cli struct {
|
||||||
Int int `required`
|
Int int `required`
|
||||||
}
|
}
|
||||||
var resolver kong.ResolverFunc = func(context *kong.Context, parent *kong.Path, flag *kong.Flag) (interface{}, error) {
|
var resolver kong.ResolverFunc = func(context *kong.Context, parent *kong.Path, flag *kong.Flag) (any, error) {
|
||||||
if flag.Name == "int" {
|
if flag.Name == "int" {
|
||||||
return 1, nil
|
return 1, nil
|
||||||
}
|
}
|
||||||
@@ -358,7 +336,7 @@ func TestResolverTriggersHooks(t *testing.T) {
|
|||||||
Flag hookValue
|
Flag hookValue
|
||||||
}
|
}
|
||||||
|
|
||||||
var first kong.ResolverFunc = func(context *kong.Context, parent *kong.Path, flag *kong.Flag) (interface{}, error) {
|
var first kong.ResolverFunc = func(context *kong.Context, parent *kong.Path, flag *kong.Flag) (any, error) {
|
||||||
if flag.Name == "flag" {
|
if flag.Name == "flag" {
|
||||||
return "one", nil
|
return "one", nil
|
||||||
}
|
}
|
||||||
@@ -377,7 +355,7 @@ type validatingResolver struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (v *validatingResolver) Validate(app *kong.Application) error { return v.err }
|
func (v *validatingResolver) Validate(app *kong.Application) error { return v.err }
|
||||||
func (v *validatingResolver) Resolve(context *kong.Context, parent *kong.Path, flag *kong.Flag) (interface{}, error) {
|
func (v *validatingResolver) Resolve(context *kong.Context, parent *kong.Path, flag *kong.Flag) (any, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+20
-6
@@ -41,7 +41,7 @@ func (t TokenType) String() string {
|
|||||||
|
|
||||||
// Token created by Scanner.
|
// Token created by Scanner.
|
||||||
type Token struct {
|
type Token struct {
|
||||||
Value interface{}
|
Value any
|
||||||
Type TokenType
|
Type TokenType
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,7 +111,8 @@ func (t Token) IsValue() bool {
|
|||||||
//
|
//
|
||||||
// [{FlagToken, "foo"}, {FlagValueToken, "bar"}]
|
// [{FlagToken, "foo"}, {FlagValueToken, "bar"}]
|
||||||
type Scanner struct {
|
type Scanner struct {
|
||||||
args []Token
|
allowHyphenated bool
|
||||||
|
args []Token
|
||||||
}
|
}
|
||||||
|
|
||||||
// ScanAsType creates a new Scanner from args with the given type.
|
// ScanAsType creates a new Scanner from args with the given type.
|
||||||
@@ -133,6 +134,14 @@ func ScanFromTokens(tokens ...Token) *Scanner {
|
|||||||
return &Scanner{args: tokens}
|
return &Scanner{args: tokens}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AllowHyphenPrefixedParameters enables or disables hyphen-prefixed flag parameters on this Scanner.
|
||||||
|
//
|
||||||
|
// Disabled by default.
|
||||||
|
func (s *Scanner) AllowHyphenPrefixedParameters(enable bool) *Scanner {
|
||||||
|
s.allowHyphenated = enable
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
// Len returns the number of input arguments.
|
// Len returns the number of input arguments.
|
||||||
func (s *Scanner) Len() int {
|
func (s *Scanner) Len() int {
|
||||||
return len(s.args)
|
return len(s.args)
|
||||||
@@ -162,7 +171,7 @@ func (e *expectedError) Error() string {
|
|||||||
// "context" is used to assist the user if the value can not be popped, eg. "expected <context> value but got <type>"
|
// "context" is used to assist the user if the value can not be popped, eg. "expected <context> value but got <type>"
|
||||||
func (s *Scanner) PopValue(context string) (Token, error) {
|
func (s *Scanner) PopValue(context string) (Token, error) {
|
||||||
t := s.Pop()
|
t := s.Pop()
|
||||||
if !t.IsValue() {
|
if !s.allowHyphenated && !t.IsValue() {
|
||||||
return t, &expectedError{context, t}
|
return t, &expectedError{context, t}
|
||||||
}
|
}
|
||||||
return t, nil
|
return t, nil
|
||||||
@@ -171,7 +180,7 @@ func (s *Scanner) PopValue(context string) (Token, error) {
|
|||||||
// PopValueInto pops a value token into target or returns an error.
|
// PopValueInto pops a value token into target or returns an error.
|
||||||
//
|
//
|
||||||
// "context" is used to assist the user if the value can not be popped, eg. "expected <context> value but got <type>"
|
// "context" is used to assist the user if the value can not be popped, eg. "expected <context> value but got <type>"
|
||||||
func (s *Scanner) PopValueInto(context string, target interface{}) error {
|
func (s *Scanner) PopValueInto(context string, target any) error {
|
||||||
t, err := s.PopValue(context)
|
t, err := s.PopValue(context)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -203,14 +212,19 @@ func (s *Scanner) Peek() Token {
|
|||||||
return s.args[0]
|
return s.args[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PeekAll remaining tokens
|
||||||
|
func (s *Scanner) PeekAll() []Token {
|
||||||
|
return s.args
|
||||||
|
}
|
||||||
|
|
||||||
// Push an untyped Token onto the front of the Scanner.
|
// Push an untyped Token onto the front of the Scanner.
|
||||||
func (s *Scanner) Push(arg interface{}) *Scanner {
|
func (s *Scanner) Push(arg any) *Scanner {
|
||||||
s.PushToken(Token{Value: arg})
|
s.PushToken(Token{Value: arg})
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
// PushTyped pushes a typed token onto the front of the Scanner.
|
// PushTyped pushes a typed token onto the front of the Scanner.
|
||||||
func (s *Scanner) PushTyped(arg interface{}, typ TokenType) *Scanner {
|
func (s *Scanner) PushTyped(arg any, typ TokenType) *Scanner {
|
||||||
s.PushToken(Token{Value: arg, Type: typ})
|
s.PushToken(Token{Value: arg, Type: typ})
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,36 +9,51 @@ 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
|
XorPrefix string // Optional prefix on XOR/AND groups.
|
||||||
Negatable bool
|
Embed bool
|
||||||
Passthrough bool
|
Aliases []string
|
||||||
|
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 +264,23 @@ 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.XorPrefix = t.Get("xorprefix")
|
||||||
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 +304,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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-2
@@ -16,9 +16,8 @@ func TestConfigFlag(t *testing.T) {
|
|||||||
Flag string
|
Flag string
|
||||||
}
|
}
|
||||||
|
|
||||||
w, err := os.CreateTemp("", "")
|
w, err := os.CreateTemp(t.TempDir(), "")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
defer os.Remove(w.Name())
|
|
||||||
w.WriteString(`{"flag": "hello world"}`) //nolint: errcheck
|
w.WriteString(`{"flag": "hello world"}`) //nolint: errcheck
|
||||||
w.Close()
|
w.Close()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user