From 1d67f0703cbdc947e01a1eb5f9cda549e7803126 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 3 Aug 2025 20:14:28 +0000 Subject: [PATCH] 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] --- go.mod | 2 +- go.sum | 4 +- .../github.com/alecthomas/kong/.golangci.yml | 5 +- vendor/github.com/alecthomas/kong/README.md | 65 ++++++-- vendor/github.com/alecthomas/kong/build.go | 22 ++- .../github.com/alecthomas/kong/callbacks.go | 150 ++++++++++++++---- vendor/github.com/alecthomas/kong/context.go | 79 ++++++--- vendor/github.com/alecthomas/kong/error.go | 11 +- vendor/github.com/alecthomas/kong/exit.go | 32 ++++ .../github.com/alecthomas/kong/guesswidth.go | 3 +- .../alecthomas/kong/guesswidth_unix.go | 3 +- vendor/github.com/alecthomas/kong/help.go | 10 +- vendor/github.com/alecthomas/kong/hooks.go | 6 + vendor/github.com/alecthomas/kong/kong.go | 60 ++++--- .../github.com/alecthomas/kong/lefthook.yml | 11 ++ .../github.com/alecthomas/kong/levenshtein.go | 4 +- vendor/github.com/alecthomas/kong/mapper.go | 4 +- vendor/github.com/alecthomas/kong/model.go | 3 + vendor/github.com/alecthomas/kong/options.go | 72 ++++++++- vendor/github.com/alecthomas/kong/resolver.go | 2 +- vendor/github.com/alecthomas/kong/scanner.go | 18 ++- vendor/modules.txt | 2 +- 22 files changed, 456 insertions(+), 112 deletions(-) create mode 100644 vendor/github.com/alecthomas/kong/exit.go create mode 100644 vendor/github.com/alecthomas/kong/lefthook.yml diff --git a/go.mod b/go.mod index babd0ee6..03f377e1 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index 98e45f82..620b3a2b 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/vendor/github.com/alecthomas/kong/.golangci.yml b/vendor/github.com/alecthomas/kong/.golangci.yml index 844092f9..1eb0b92e 100644 --- a/vendor/github.com/alecthomas/kong/.golangci.yml +++ b/vendor/github.com/alecthomas/kong/.golangci.yml @@ -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: diff --git a/vendor/github.com/alecthomas/kong/README.md b/vendor/github.com/alecthomas/kong/README.md index 4a86251c..a9763327 100644 --- a/vendor/github.com/alecthomas/kong/README.md +++ b/vendor/github.com/alecthomas/kong/README.md @@ -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: diff --git a/vendor/github.com/alecthomas/kong/build.go b/vendor/github.com/alecthomas/kong/build.go index 5d17f53f..63afcd4c 100644 --- a/vendor/github.com/alecthomas/kong/build.go +++ b/vendor/github.com/alecthomas/kong/build.go @@ -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. diff --git a/vendor/github.com/alecthomas/kong/callbacks.go b/vendor/github.com/alecthomas/kong/callbacks.go index c1fac817..6096a260 100644 --- a/vendor/github.com/alecthomas/kong/callbacks.go +++ b/vendor/github.com/alecthomas/kong/callbacks.go @@ -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 + } + + 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)...) } } - // Search all receivers for methods - var methods []reflect.Value - for _, receiver := range receivers { - if method := getMethod(receiver, name); method.IsValid() { - methods = append(methods, method) - } - } - return methods + 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)) diff --git a/vendor/github.com/alecthomas/kong/context.go b/vendor/github.com/alecthomas/kong/context.go index b6a56e33..6a4989f6 100644 --- a/vendor/github.com/alecthomas/kong/context.go +++ b/vendor/github.com/alecthomas/kong/context.go @@ -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 @@ -496,9 +522,10 @@ func (c *Context) trace(node *Node) (err error) { //nolint: gocyclo if branch.Type == CommandNode && branch.Name == token.Value { c.scan.Pop() c.Path = append(c.Path, &Path{ - Parent: node, - Command: branch, - Flags: branch.Flags, + Parent: node, + Command: branch, + Flags: branch.Flags, + remainder: c.scan.PeekAll(), }) return c.trace(branch) } @@ -510,9 +537,10 @@ func (c *Context) trace(node *Node) (err error) { //nolint: gocyclo arg := branch.Argument if err := arg.Parse(c.scan, c.getValue(arg)); err == nil { c.Path = append(c.Path, &Path{ - Parent: node, - Argument: branch, - Flags: branch.Flags, + Parent: node, + Argument: branch, + Flags: branch.Flags, + remainder: c.scan.PeekAll(), }) return c.trace(branch) } @@ -523,9 +551,10 @@ func (c *Context) trace(node *Node) (err error) { //nolint: gocyclo // matches, take the branch of the default command if node.DefaultCmd != nil && node.DefaultCmd.Tag.Default == "withargs" { c.Path = append(c.Path, &Path{ - Parent: node, - Command: node.DefaultCmd, - Flags: node.DefaultCmd.Flags, + Parent: node, + Command: node.DefaultCmd, + Flags: node.DefaultCmd.Flags, + remainder: c.scan.PeekAll(), }) return c.trace(node.DefaultCmd) } @@ -538,19 +567,25 @@ 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 } } if node.DefaultCmd != nil { c.Path = append(c.Path, &Path{ - Parent: node.DefaultCmd, - Command: node.DefaultCmd, - Flags: node.DefaultCmd.Flags, + Parent: node.DefaultCmd, + Command: node.DefaultCmd, + Flags: node.DefaultCmd.Flags, + remainder: c.scan.PeekAll(), }) } return nil @@ -595,8 +630,9 @@ func (c *Context) Resolve() error { return err } inserted = append(inserted, &Path{ - Flag: flag, - Resolved: true, + 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) } } diff --git a/vendor/github.com/alecthomas/kong/error.go b/vendor/github.com/alecthomas/kong/error.go index 18225ef5..e79a15dc 100644 --- a/vendor/github.com/alecthomas/kong/error.go +++ b/vendor/github.com/alecthomas/kong/error.go @@ -5,8 +5,17 @@ package kong // It contains the parse Context that triggered the error. type ParseError struct { error - Context *Context + 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 +} diff --git a/vendor/github.com/alecthomas/kong/exit.go b/vendor/github.com/alecthomas/kong/exit.go new file mode 100644 index 00000000..4925f483 --- /dev/null +++ b/vendor/github.com/alecthomas/kong/exit.go @@ -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 +} diff --git a/vendor/github.com/alecthomas/kong/guesswidth.go b/vendor/github.com/alecthomas/kong/guesswidth.go index dfdc3f51..2c19cac7 100644 --- a/vendor/github.com/alecthomas/kong/guesswidth.go +++ b/vendor/github.com/alecthomas/kong/guesswidth.go @@ -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 diff --git a/vendor/github.com/alecthomas/kong/guesswidth_unix.go b/vendor/github.com/alecthomas/kong/guesswidth_unix.go index 0170055a..7fc5d025 100644 --- a/vendor/github.com/alecthomas/kong/guesswidth_unix.go +++ b/vendor/github.com/alecthomas/kong/guesswidth_unix.go @@ -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 diff --git a/vendor/github.com/alecthomas/kong/help.go b/vendor/github.com/alecthomas/kong/help.go index 6363ea21..8da15557 100644 --- a/vendor/github.com/alecthomas/kong/help.go +++ b/vendor/github.com/alecthomas/kong/help.go @@ -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]) diff --git a/vendor/github.com/alecthomas/kong/hooks.go b/vendor/github.com/alecthomas/kong/hooks.go index 9fdf24c1..e95d21ba 100644 --- a/vendor/github.com/alecthomas/kong/hooks.go +++ b/vendor/github.com/alecthomas/kong/hooks.go @@ -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. diff --git a/vendor/github.com/alecthomas/kong/kong.go b/vendor/github.com/alecthomas/kong/kong.go index b85e1452..2334a8a8 100644 --- a/vendor/github.com/alecthomas/kong/kong.go +++ b/vendor/github.com/alecthomas/kong/kong.go @@ -56,21 +56,24 @@ type Kong struct { registry *Registry ignoreFields []*regexp.Regexp - noDefaultHelp bool - usageOnError usageOnError - help HelpPrinter - shortHelp HelpPrinter - helpFormatter HelpValueFormatter - helpOptions HelpOptions - helpFlag *Flag - groups []Group - vars Vars - flagNamer func(string) string + noDefaultHelp bool + allowHyphenated bool + usageOnError usageOnError + help HelpPrinter + shortHelp HelpPrinter + helpFormatter HelpValueFormatter + helpOptions HelpOptions + helpFlag *Flag + groups []Group + vars Vars + flagNamer func(string) string // Set temporarily by Options. These are applied after build(). postBuildOptions []Option embedded []embedded dynamicCommands []*dynamicCommand + + 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). diff --git a/vendor/github.com/alecthomas/kong/lefthook.yml b/vendor/github.com/alecthomas/kong/lefthook.yml new file mode 100644 index 00000000..28ba9ad9 --- /dev/null +++ b/vendor/github.com/alecthomas/kong/lefthook.yml @@ -0,0 +1,11 @@ +output: + - success + - failure +pre-push: + parallel: true + jobs: + - name: test + run: go test -v ./... + + - name: lint + run: golangci-lint run diff --git a/vendor/github.com/alecthomas/kong/levenshtein.go b/vendor/github.com/alecthomas/kong/levenshtein.go index 6837d6c3..fe117457 100644 --- a/vendor/github.com/alecthomas/kong/levenshtein.go +++ b/vendor/github.com/alecthomas/kong/levenshtein.go @@ -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) diff --git a/vendor/github.com/alecthomas/kong/mapper.go b/vendor/github.com/alecthomas/kong/mapper.go index db3f24ec..7e97836c 100644 --- a/vendor/github.com/alecthomas/kong/mapper.go +++ b/vendor/github.com/alecthomas/kong/mapper.go @@ -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) } diff --git a/vendor/github.com/alecthomas/kong/model.go b/vendor/github.com/alecthomas/kong/model.go index 065fcdd0..33a6f333 100644 --- a/vendor/github.com/alecthomas/kong/model.go +++ b/vendor/github.com/alecthomas/kong/model.go @@ -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 diff --git a/vendor/github.com/alecthomas/kong/options.go b/vendor/github.com/alecthomas/kong/options.go index 6263202b..5792c127 100644 --- a/vendor/github.com/alecthomas/kong/options.go +++ b/vendor/github.com/alecthomas/kong/options.go @@ -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 :"". 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 */) }) } diff --git a/vendor/github.com/alecthomas/kong/resolver.go b/vendor/github.com/alecthomas/kong/resolver.go index 29be1b91..3e37ca73 100644 --- a/vendor/github.com/alecthomas/kong/resolver.go +++ b/vendor/github.com/alecthomas/kong/resolver.go @@ -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:] } diff --git a/vendor/github.com/alecthomas/kong/scanner.go b/vendor/github.com/alecthomas/kong/scanner.go index 262d16f1..511bf8f0 100644 --- a/vendor/github.com/alecthomas/kong/scanner.go +++ b/vendor/github.com/alecthomas/kong/scanner.go @@ -111,7 +111,8 @@ func (t Token) IsValue() bool { // // [{FlagToken, "foo"}, {FlagValueToken, "bar"}] type Scanner struct { - args []Token + allowHyphenated bool + args []Token } // ScanAsType creates a new Scanner from args with the given type. @@ -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 value but got " 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}) diff --git a/vendor/modules.txt b/vendor/modules.txt index 686595b1..07864c8c 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -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