chore(deps): bump github.com/alecthomas/kong from 1.6.1 to 1.12.1

Bumps [github.com/alecthomas/kong](https://github.com/alecthomas/kong) from 1.6.1 to 1.12.1.
- [Commits](https://github.com/alecthomas/kong/compare/v1.6.1...v1.12.1)

---
updated-dependencies:
- dependency-name: github.com/alecthomas/kong
  dependency-version: 1.12.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
This commit is contained in:
dependabot[bot]
2025-08-03 20:14:28 +00:00
committed by GitHub
parent 0cb1e60c9c
commit 1d67f0703c
22 changed files with 456 additions and 112 deletions

2
go.mod
View File

@@ -6,7 +6,7 @@ require (
dario.cat/mergo v1.0.2 dario.cat/mergo v1.0.2
github.com/AlecAivazis/survey/v2 v2.3.7 github.com/AlecAivazis/survey/v2 v2.3.7
github.com/PaulSonOfLars/gotgbot/v2 v2.0.0-rc.33 github.com/PaulSonOfLars/gotgbot/v2 v2.0.0-rc.33
github.com/alecthomas/kong v1.6.1 github.com/alecthomas/kong v1.12.1
github.com/bmatcuk/doublestar/v3 v3.0.0 github.com/bmatcuk/doublestar/v3 v3.0.0
github.com/containerd/platforms v1.0.0-rc.1 github.com/containerd/platforms v1.0.0-rc.1
github.com/containers/image/v5 v5.36.0 github.com/containers/image/v5 v5.36.0

4
go.sum
View File

@@ -24,8 +24,8 @@ github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7l
github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
github.com/alecthomas/kong v1.6.1 h1:/7bVimARU3uxPD0hbryPE8qWrS3Oz3kPQoxA/H2NKG8= github.com/alecthomas/kong v1.12.1 h1:iq6aMJDcFYP9uFrLdsiZQ2ZMmcshduyGv4Pek0MQPW0=
github.com/alecthomas/kong v1.6.1/go.mod h1:p2vqieVMeTAnaC83txKtXe8FLke2X07aruPWXyMPQrU= github.com/alecthomas/kong v1.12.1/go.mod h1:p2vqieVMeTAnaC83txKtXe8FLke2X07aruPWXyMPQrU=
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=

View File

@@ -12,7 +12,6 @@ linters:
- wsl - wsl
- funlen - funlen
- gocognit - gocognit
- gomnd
- goprintffuncname - goprintffuncname
- paralleltest - paralleltest
- nlreturn - nlreturn
@@ -36,12 +35,14 @@ linters:
- nilnil - nilnil
- 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 - mnd
- perfsprint - perfsprint
- err113 - err113
- copyloopvar - copyloopvar
- intrange - intrange
- execinquery - nakedret
- recvcheck # value receivers are intentionally used for copies
linters-settings: linters-settings:
govet: govet:

View File

@@ -13,7 +13,8 @@
- [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)
@@ -180,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**
@@ -305,17 +306,21 @@ func main() {
``` ```
## Hooks: BeforeReset(), BeforeResolve(), BeforeApply(), AfterApply() and the Bind() option ## Hooks: BeforeReset(), BeforeResolve(), BeforeApply(), AfterApply()
If a node in the CLI, or any of its embedded fields, has a `BeforeReset(...) error`, `BeforeResolve If a node in the CLI, or any of its embedded fields, implements a `BeforeReset(...) error`, `BeforeResolve
(...) error`, `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
@@ -341,6 +346,44 @@ func main() {
} }
``` ```
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
type CLI struct {
Debug bool `help:"Enable debug mode."`
Rm RmCmd `cmd:"" help:"Remove files."`
Ls LsCmd `cmd:"" help:"List paths."`
}
type AuthorName string
// ...
func (l *LsCmd) Run(cli *CLI) error {
// use cli.Debug here !!
return nil
}
func (r *RmCmD) Run(author AuthorName) error{
// use binded author here
return nil
}
func main() {
var cli CLI
ctx := kong.Parse(&cli, Bind(AuthorName("penguin")))
err := ctx.Run()
```
## Flags ## Flags
Any [mapped](#mapper---customising-how-the-command-line-is-mapped-to-go-values) field in the command structure _not_ tagged with `cmd` or `arg` will be a flag. Flags are optional by default. Any [mapped](#mapper---customising-how-the-command-line-is-mapped-to-go-values) field in the command structure _not_ tagged with `cmd` or `arg` will be a flag. Flags are optional by default.
@@ -587,8 +630,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:

View File

@@ -54,6 +54,7 @@ func flattenedFields(v reflect.Value, ptag *Tag) (out []flattenedField, err erro
if v.Kind() != reflect.Struct { if v.Kind() != reflect.Struct {
return out, nil 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)
@@ -61,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.
@@ -106,9 +108,27 @@ 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.

View File

@@ -6,10 +6,59 @@ import (
"strings" "strings"
) )
// 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. // 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. // The function should have the signature func(...) (T, error). Arguments are recursively resolved.
type bindings map[reflect.Type]any type bindings map[reflect.Type]*binding
func (b bindings) String() string { func (b bindings) String() string {
out := []string{} out := []string{}
@@ -21,24 +70,34 @@ func (b bindings) String() string {
func (b bindings) add(values ...any) 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() (any, error) { return v, nil } b[val.Type()] = newValueBinding(val)
} }
return b return b
} }
func (b bindings) addTo(impl, iface any) { func (b bindings) addTo(impl, iface any) {
b[reflect.TypeOf(iface).Elem()] = func() (any, error) { return impl, nil } val := reflect.ValueOf(impl)
b[reflect.TypeOf(iface).Elem()] = newValueBinding(val)
} }
func (b bindings) addProvider(provider any) 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.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] = provider b[rt] = newFunctionBinding(pv, singleton)
return nil return nil
} }
@@ -68,31 +127,48 @@ func getMethod(value reflect.Value, name string) reflect.Value {
return method return method
} }
// Get methods from the given value and any embedded fields. // getMethods gets all methods with the given name from the given value
func getMethods(value reflect.Value, name string) []reflect.Value { // and any embedded fields.
// Collect all possible receivers //
receivers := []reflect.Value{value} // 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 { if value.Kind() == reflect.Ptr {
value = value.Elem() value = value.Elem()
} }
if value.Kind() == reflect.Struct { if !value.IsValid() {
t := value.Type() return
for i := 0; i < value.NumField(); i++ {
field := value.Field(i)
fieldType := t.Field(i)
if fieldType.IsExported() && fieldType.Anonymous {
receivers = append(receivers, field)
} }
}
} if method := getMethod(value, name); method.IsValid() {
// Search all receivers for methods
var methods []reflect.Value
for _, receiver := range receivers {
if method := getMethod(receiver, name); method.IsValid() {
methods = append(methods, method) methods = append(methods, method)
} }
if value.Kind() != reflect.Struct {
return
} }
return methods // 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 {
@@ -122,19 +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)
argf, ok := bindings[pt] binding, ok := bindings[pt]
if !ok { if !ok {
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. // Recursively resolve binding functions.
argv, err := callAnyFunction(reflect.ValueOf(argf), bindings) argv, err := callAnyFunction(binding.fn, bindings)
if err != nil { if err != nil {
return nil, fmt.Errorf("%s: %w", pt, err) return nil, fmt.Errorf("%s: %w", pt, err)
} }
if ferrv := reflect.ValueOf(argv[len(argv)-1]); ferrv.IsValid() && !ferrv.IsNil() { if ferrv := reflect.ValueOf(argv[len(argv)-1]); ferrv.IsValid() && ferrv.Type().Implements(callbackReturnSignature) && !ferrv.IsNil() {
return nil, ferrv.Interface().(error) //nolint:forcetypeassert return nil, ferrv.Interface().(error) //nolint:forcetypeassert
} }
in = append(in, reflect.ValueOf(argv[0]))
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))

View File

@@ -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)
@@ -119,8 +132,20 @@ func (c *Context) BindTo(impl, iface any) {
// //
// 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.
//
// "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 { func (c *Context) BindToProvider(provider any) error {
return c.bindings.addProvider(provider) 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.
@@ -465,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
@@ -499,6 +525,7 @@ func (c *Context) trace(node *Node) (err error) { //nolint: gocyclo
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)
} }
@@ -513,6 +540,7 @@ func (c *Context) trace(node *Node) (err error) { //nolint: gocyclo
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)
} }
@@ -526,6 +554,7 @@ func (c *Context) trace(node *Node) (err error) { //nolint: gocyclo
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)
} }
@@ -538,11 +567,16 @@ 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
} }
} }
@@ -551,6 +585,7 @@ func (c *Context) maybeSelectDefault(flags []*Flag, node *Node) error {
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
@@ -597,6 +632,7 @@ func (c *Context) Resolve() error {
inserted = append(inserted, &Path{ inserted = append(inserted, &Path{
Flag: flag, Flag: flag,
Resolved: true, Resolved: true,
remainder: c.scan.PeekAll(),
}) })
} }
} }
@@ -740,7 +776,10 @@ 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 &unknownFlagError{Cause: findPotentialCandidates(match, candidates, "unknown flag %s", match)} return &unknownFlagError{Cause: findPotentialCandidates(match, candidates, "unknown flag %s", match)}
@@ -789,7 +828,7 @@ func (c *Context) RunNode(node *Node, binds ...any) (err error) {
methodt := t.Method(i) methodt := t.Method(i)
if strings.HasPrefix(methodt.Name, "Provide") { if strings.HasPrefix(methodt.Name, "Provide") {
method := p.Method(i) method := p.Method(i)
if err := methodBinds.addProvider(method.Interface()); err != nil { if err := methodBinds.addProvider(method.Interface(), false /* singleton */); err != nil {
return fmt.Errorf("%s.%s: %w", t.Name(), methodt.Name, err) return fmt.Errorf("%s.%s: %w", t.Name(), methodt.Name, err)
} }
} }

View File

@@ -6,7 +6,16 @@ package kong
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
}

32
vendor/github.com/alecthomas/kong/exit.go generated vendored Normal file
View File

@@ -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
}

View File

@@ -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

View File

@@ -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

View File

@@ -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)
@@ -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])

View File

@@ -1,5 +1,11 @@
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.

View File

@@ -57,6 +57,7 @@ type Kong struct {
ignoreFields []*regexp.Regexp ignoreFields []*regexp.Regexp
noDefaultHelp bool noDefaultHelp bool
allowHyphenated bool
usageOnError usageOnError usageOnError usageOnError
help HelpPrinter help HelpPrinter
shortHelp HelpPrinter shortHelp HelpPrinter
@@ -71,6 +72,8 @@ type Kong struct {
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.
@@ -84,6 +87,7 @@ func New(grammar any, 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 {
@@ -270,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 {
@@ -283,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',
@@ -311,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}
@@ -332,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}
@@ -361,7 +370,7 @@ func (k *Kong) applyHook(ctx *Context, name string) error {
default: default:
panic("unsupported Path") panic("unsupported Path")
} }
for _, method := range getMethods(value, name) { for _, method := range k.getMethods(value, name) {
binds := k.bindings.clone() binds := k.bindings.clone()
binds.add(ctx, trace) binds.add(ctx, trace)
binds.add(trace.Node().Vars().CloneWith(k.vars)) binds.add(trace.Node().Vars().CloneWith(k.vars))
@@ -375,6 +384,16 @@ func (k *Kong) applyHook(ctx *Context, name string) error {
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 {
@@ -428,13 +447,15 @@ func (k *Kong) Errorf(format string, args ...any) *Kong {
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 ...any) { 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.
// 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) { func (k *Kong) FatalIfErrorf(err error, args ...any) {
if err == nil { if err == nil {
return return
@@ -455,7 +476,8 @@ func (k *Kong) FatalIfErrorf(err error, args ...any) {
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).

11
vendor/github.com/alecthomas/kong/lefthook.yml generated vendored Normal file
View File

@@ -0,0 +1,11 @@
output:
- success
- failure
pre-push:
parallel: true
jobs:
- name: test
run: go test -v ./...
- name: lint
run: golangci-lint run

View File

@@ -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)

View File

@@ -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)
} }

View File

@@ -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

View File

@@ -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
@@ -89,10 +99,6 @@ type dynamicCommand struct {
// "tags" is a list of extra tag strings to parse, in the form <key>:"<value>". // "tags" is a list of extra tag strings to parse, in the form <key>:"<value>".
func DynamicCommand(name, help, group string, cmd any, 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,
@@ -123,6 +129,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 {
@@ -210,15 +250,33 @@ func BindTo(impl, iface any) Option {
// BindToProvider binds an injected value to a provider function. // BindToProvider binds an injected value to a provider function.
// //
// The provider function must have the signature: // The provider function must have one of the following signatures:
// //
// func() (any, error) // 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 any) 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 */)
}) })
} }

View File

@@ -63,6 +63,6 @@ func JSON(r io.Reader) (Resolver, error) {
} }
func snakeCase(name string) string { func snakeCase(name string) string {
name = strings.Join(strings.Split(strings.Title(name), "-"), "") 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:]
} }

View File

@@ -111,6 +111,7 @@ func (t Token) IsValue() bool {
// //
// [{FlagToken, "foo"}, {FlagValueToken, "bar"}] // [{FlagToken, "foo"}, {FlagValueToken, "bar"}]
type Scanner struct { type Scanner struct {
allowHyphenated bool
args []Token args []Token
} }
@@ -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
@@ -203,6 +212,11 @@ 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 any) *Scanner { func (s *Scanner) Push(arg any) *Scanner {
s.PushToken(Token{Value: arg}) s.PushToken(Token{Value: arg})

2
vendor/modules.txt vendored
View File

@@ -34,7 +34,7 @@ github.com/PuerkitoBio/goquery
# github.com/agext/levenshtein v1.2.3 # github.com/agext/levenshtein v1.2.3
## explicit ## explicit
github.com/agext/levenshtein github.com/agext/levenshtein
# github.com/alecthomas/kong v1.6.1 # github.com/alecthomas/kong v1.12.1
## explicit; go 1.20 ## explicit; go 1.20
github.com/alecthomas/kong github.com/alecthomas/kong
# github.com/andybalholm/cascadia v1.3.2 # github.com/andybalholm/cascadia v1.3.2