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
github.com/AlecAivazis/survey/v2 v2.3.7
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/containerd/platforms v1.0.0-rc.1
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/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/kong v1.6.1 h1:/7bVimARU3uxPD0hbryPE8qWrS3Oz3kPQoxA/H2NKG8=
github.com/alecthomas/kong v1.6.1/go.mod h1:p2vqieVMeTAnaC83txKtXe8FLke2X07aruPWXyMPQrU=
github.com/alecthomas/kong v1.12.1 h1:iq6aMJDcFYP9uFrLdsiZQ2ZMmcshduyGv4Pek0MQPW0=
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/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=

View File

@@ -12,7 +12,6 @@ linters:
- wsl
- funlen
- gocognit
- gomnd
- goprintffuncname
- paralleltest
- nlreturn
@@ -36,12 +35,14 @@ linters:
- nilnil
- depguard # nothing to guard against yet
- tagalign # hurts readability of kong tags
- tenv # deprecated since v1.64, but not removed yet
- mnd
- perfsprint
- err113
- copyloopvar
- intrange
- execinquery
- nakedret
- recvcheck # value receivers are intentionally used for copies
linters-settings:
govet:

View File

@@ -13,7 +13,8 @@
- [Command handling](#command-handling)
- [Switch on the command string](#switch-on-the-command-string)
- [Attach a `Run(...) error` method to each command](#attach-a-run-error-method-to-each-command)
- [Hooks: BeforeReset(), BeforeResolve(), BeforeApply(), AfterApply() and the Bind() option](#hooks-beforereset-beforeresolve-beforeapply-afterapply-and-the-bind-option)
- [Hooks: BeforeReset(), BeforeResolve(), BeforeApply(), AfterApply()](#hooks-beforereset-beforeresolve-beforeapply-afterapply)
- [The Bind() option](#the-bind-option)
- [Flags](#flags)
- [Commands and sub-commands](#commands-and-sub-commands)
- [Branching positional arguments](#branching-positional-arguments)
@@ -180,7 +181,7 @@ Flags:
#### 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**
@@ -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
(...) error`, `BeforeApply(...) error` and/or `AfterApply(...) error` method, those
methods will be called before values are reset, before validation/assignment,
and after validation/assignment, respectively.
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 will be called as Kong
resets, resolves, validates, and assigns values to the node.
| 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.
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.
```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
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
Kong supports limited variable interpolation into help strings, enum lists and
default values.
Kong supports limited variable interpolation into help strings, placeholder strings,
enum lists and default values.
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 {
return out, nil
}
ignored := map[string]bool{}
for i := 0; i < v.NumField(); i++ {
ft := v.Type().Field(i)
fv := v.Field(i)
@@ -61,7 +62,8 @@ func flattenedFields(v reflect.Value, ptag *Tag) (out []flattenedField, err erro
if err != nil {
return nil, err
}
if tag.Ignored {
if tag.Ignored || ignored[ft.Name] {
ignored[ft.Name] = true
continue
}
// 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 = removeIgnored(out, ignored)
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.
//
// "v" is the value to create the node from, "typ" is the output Node type.

View File

@@ -6,10 +6,59 @@ import (
"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.
//
// 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 {
out := []string{}
@@ -21,24 +70,34 @@ func (b bindings) String() string {
func (b bindings) add(values ...any) bindings {
for _, v := range values {
v := v
b[reflect.TypeOf(v)] = func() (any, error) { return v, nil }
val := reflect.ValueOf(v)
b[val.Type()] = newValueBinding(val)
}
return b
}
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)
t := pv.Type()
if t.Kind() != reflect.Func || t.NumOut() != 2 || t.Out(1) != reflect.TypeOf((*error)(nil)).Elem() {
return fmt.Errorf("%T must be a function with the signature func(...)(T, error)", provider)
if t.Kind() != reflect.Func {
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)
b[rt] = provider
b[rt] = newFunctionBinding(pv, singleton)
return nil
}
@@ -68,31 +127,48 @@ func getMethod(value reflect.Value, name string) reflect.Value {
return method
}
// Get methods from the given value and any embedded fields.
func getMethods(value reflect.Value, name string) []reflect.Value {
// Collect all possible receivers
receivers := []reflect.Value{value}
// 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.Kind() == reflect.Struct {
t := value.Type()
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 !value.IsValid() {
return
}
}
}
// Search all receivers for methods
var methods []reflect.Value
for _, receiver := range receivers {
if method := getMethod(receiver, name); method.IsValid() {
if method := getMethod(value, name); method.IsValid() {
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 {
@@ -122,19 +198,29 @@ func callAnyFunction(f reflect.Value, bindings bindings) (out []any, err error)
t := f.Type()
for i := 0; i < t.NumIn(); i++ {
pt := t.In(i)
argf, ok := bindings[pt]
binding, ok := bindings[pt]
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)
}
// 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(reflect.ValueOf(argf), bindings)
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.IsNil() {
if ferrv := reflect.ValueOf(argv[len(argv)-1]); ferrv.IsValid() && ferrv.Type().Implements(callbackReturnSignature) && !ferrv.IsNil() {
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)
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.
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.
@@ -64,6 +67,15 @@ func (p *Path) Visitable() Visitable {
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.
type Context struct {
*Kong
@@ -87,14 +99,15 @@ type Context struct {
// This just constructs a new trace. To fully apply the trace you must call Reset(), Resolve(),
// Validate() and Apply().
func Trace(k *Kong, args []string) (*Context, error) {
s := Scan(args...).AllowHyphenPrefixedParameters(k.allowHyphenated)
c := &Context{
Kong: k,
Args: args,
Path: []*Path{
{App: k.Model, Flags: k.Model.Flags},
{App: k.Model, Flags: k.Model.Flags, remainder: s.PeekAll()},
},
values: map[*Value]reflect.Value{},
scan: Scan(args...),
scan: s,
bindings: bindings{},
}
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
// 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 {
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.
@@ -465,6 +490,7 @@ func (c *Context) trace(node *Node) (err error) { //nolint: gocyclo
c.Path = append(c.Path, &Path{
Parent: node,
Positional: arg,
remainder: c.scan.PeekAll(),
})
positional++
break
@@ -499,6 +525,7 @@ func (c *Context) trace(node *Node) (err error) { //nolint: gocyclo
Parent: node,
Command: branch,
Flags: branch.Flags,
remainder: c.scan.PeekAll(),
})
return c.trace(branch)
}
@@ -513,6 +540,7 @@ func (c *Context) trace(node *Node) (err error) { //nolint: gocyclo
Parent: node,
Argument: branch,
Flags: branch.Flags,
remainder: c.scan.PeekAll(),
})
return c.trace(branch)
}
@@ -526,6 +554,7 @@ func (c *Context) trace(node *Node) (err error) { //nolint: gocyclo
Parent: node,
Command: node.DefaultCmd,
Flags: node.DefaultCmd.Flags,
remainder: c.scan.PeekAll(),
})
return c.trace(node.DefaultCmd)
}
@@ -538,11 +567,16 @@ func (c *Context) trace(node *Node) (err error) { //nolint: gocyclo
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,
// otherwise we'd only ever display the help for the default command.
func (c *Context) maybeSelectDefault(flags []*Flag, node *Node) error {
for _, flag := range flags {
if flag.Name == "help" && flag.Set {
if _, ok := flag.Target.Interface().(IgnoreDefault); ok && flag.Set {
return nil
}
}
@@ -551,6 +585,7 @@ func (c *Context) maybeSelectDefault(flags []*Flag, node *Node) error {
Parent: node.DefaultCmd,
Command: node.DefaultCmd,
Flags: node.DefaultCmd.Flags,
remainder: c.scan.PeekAll(),
})
}
return nil
@@ -597,6 +632,7 @@ func (c *Context) Resolve() error {
inserted = append(inserted, &Path{
Flag: flag,
Resolved: true,
remainder: c.scan.PeekAll(),
})
}
}
@@ -740,7 +776,10 @@ func (c *Context) parseFlag(flags []*Flag, match string) (err error) {
}
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 &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)
if strings.HasPrefix(methodt.Name, "Provide") {
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)
}
}

View File

@@ -6,7 +6,16 @@ package kong
type ParseError struct {
error
Context *Context
exitCode int
}
// Unwrap returns the original cause of the 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)
// +build appengine !linux,!freebsd,!darwin,!dragonfly,!netbsd,!openbsd
//go:build tinygo || appengine || (!linux && !freebsd && !darwin && !dragonfly && !netbsd && !openbsd)
package kong

View File

@@ -1,5 +1,4 @@
//go:build (!appengine && linux) || freebsd || darwin || dragonfly || netbsd || openbsd
// +build !appengine,linux freebsd darwin dragonfly netbsd openbsd
//go:build !tinygo && ((!appengine && linux) || freebsd || darwin || dragonfly || netbsd || openbsd)
package kong

View File

@@ -14,9 +14,11 @@ const (
)
// 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.Summary = false
err := ctx.Kong.help(options, ctx)
@@ -415,7 +417,7 @@ func (h *helpWriter) Write(w io.Writer) error {
func (h *helpWriter) Wrap(text string) {
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") {
h.Print(line)
}
@@ -470,7 +472,7 @@ func writeTwoColumns(w *helpWriter, rows [][2]string) {
for _, row := range rows {
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")
line := fmt.Sprintf("%-*s", leftSize, row[0])

View File

@@ -1,5 +1,11 @@
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.
type BeforeResolve interface {
// This is not the correct signature - see README for details.

View File

@@ -57,6 +57,7 @@ type Kong struct {
ignoreFields []*regexp.Regexp
noDefaultHelp bool
allowHyphenated bool
usageOnError usageOnError
help HelpPrinter
shortHelp HelpPrinter
@@ -71,6 +72,8 @@ type Kong struct {
postBuildOptions []Option
embedded []embedded
dynamicCommands []*dynamicCommand
hooks map[string][]reflect.Value
}
// New creates a new Kong parser on grammar.
@@ -84,6 +87,7 @@ func New(grammar any, options ...Option) (*Kong, error) {
registry: NewRegistry().RegisterDefaults(),
vars: Vars{},
bindings: bindings{},
hooks: make(map[string][]reflect.Value),
helpFormatter: DefaultHelpValueFormatter,
ignoreFields: make([]*regexp.Regexp, 0),
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 {
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)
if err != nil {
@@ -283,7 +292,7 @@ func (k *Kong) extraFlags() []*Flag {
if k.noDefaultHelp {
return nil
}
var helpTarget helpValue
var helpTarget helpFlag
value := reflect.ValueOf(&helpTarget).Elem()
helpFlag := &Flag{
Short: 'h',
@@ -311,11 +320,11 @@ func (k *Kong) extraFlags() []*Flag {
// invalid one, which will report a normal error).
func (k *Kong) Parse(args []string) (ctx *Context, err error) {
ctx, err = Trace(k, args)
if err != nil {
return nil, err
if err != nil { // Trace is not expected to return an err
return nil, &ParseError{error: err, Context: ctx, exitCode: exitUsageError}
}
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 {
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 {
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}
}
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 {
return nil, &ParseError{error: err, Context: ctx}
@@ -361,7 +370,7 @@ func (k *Kong) applyHook(ctx *Context, name string) error {
default:
panic("unsupported Path")
}
for _, method := range getMethods(value, name) {
for _, method := range k.getMethods(value, name) {
binds := k.bindings.clone()
binds.add(ctx, trace)
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)
}
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.
func (k *Kong) applyHookToDefaultFlags(ctx *Context, node *Node, name string) error {
if node == nil {
@@ -428,13 +447,15 @@ func (k *Kong) Errorf(format string, args ...any) *Kong {
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) {
k.Errorf(format, args...)
k.Exit(1)
}
// 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) {
if err == nil {
return
@@ -455,7 +476,8 @@ func (k *Kong) FatalIfErrorf(err error, args ...any) {
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).

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"
// https://en.wikibooks.org/wiki/Algorithm_Implementation/Strings/Levenshtein_distance#Go
// License: https://creativecommons.org/licenses/by-sa/3.0/
// Copied from https://github.com/daviddengcn/go-algs/blob/fe23fabd9d0670e4675326040ba7c285c7117b4c/ed/ed.go#L31
// License: https://github.com/daviddengcn/go-algs/blob/fe23fabd9d0670e4675326040ba7c285c7117b4c/LICENSE
func levenshtein(a, b string) int {
f := make([]int, utf8.RuneCountInString(b)+1)

View File

@@ -387,7 +387,7 @@ func intDecoder(bits int) MapperFunc { //nolint: dupl
default:
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 {
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:
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 {
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...)
}
for _, flag := range allFlags {
if _, ok := flag.Target.Interface().(helpFlag); ok {
continue
}
if !flag.Required {
summary += " [flags]"
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 {
strct any
tags []string
@@ -89,10 +99,6 @@ type dynamicCommand struct {
// "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 {
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{
name: name,
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.
func Name(name string) Option {
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.
//
// 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
// not all be initialisable from the main() function.
func BindToProvider(provider any) Option {
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 {
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:]
}

View File

@@ -111,6 +111,7 @@ func (t Token) IsValue() bool {
//
// [{FlagToken, "foo"}, {FlagValueToken, "bar"}]
type Scanner struct {
allowHyphenated bool
args []Token
}
@@ -133,6 +134,14 @@ func ScanFromTokens(tokens ...Token) *Scanner {
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.
func (s *Scanner) Len() int {
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>"
func (s *Scanner) PopValue(context string) (Token, error) {
t := s.Pop()
if !t.IsValue() {
if !s.allowHyphenated && !t.IsValue() {
return t, &expectedError{context, t}
}
return t, nil
@@ -203,6 +212,11 @@ func (s *Scanner) Peek() Token {
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.
func (s *Scanner) Push(arg any) *Scanner {
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
## explicit
github.com/agext/levenshtein
# github.com/alecthomas/kong v1.6.1
# github.com/alecthomas/kong v1.12.1
## explicit; go 1.20
github.com/alecthomas/kong
# github.com/andybalholm/cascadia v1.3.2