* feat: Support singleton providers
This change adds support for provider functions that are
not reinvoked even if requested by multiple other providers.
Instead, their value is cached and reused between invocations.
To make this possible, we change how bindings are stored:
instead of just a function reference, we now store a binding object
which records whether the binding is a singleton,
and records the resolved singleton value (if any).
Resolves#500
* refac(bindings): hide singleton status
Don't require callAnyFunction to be aware of
whether a binding is a singleton or not.
* hooks: Recursively search embedded fields for methods
Follow up to #493 and 840220c
Kong currently supports hooks on embedded fields of a parsed node,
but only at the first level of embedding:
```
type mainCmd struct {
FooOptions
}
type FooOptions struct {
BarOptions
}
func (f *FooOptions) BeforeApply() error {
// this will be called
}
type BarOptions struct {
}
func (b *BarOptions) BeforeApply() error {
// this will not be called
}
```
This change adds support for hooks to be defined
on embedded fields of embedded fields so that the above
example would work as expected.
Per #493, the definition of "embedded" field is adjusted to mean:
- Any anonymous (Go-embedded) field that is exported
- Any non-anonymous field that is tagged with `embed:""`
*Testing*:
Includes a test case for embedding an anonymous field in an `embed:""`
and an `embed:""` field in an anonymous field.
* Use recursion to build up the list of receivers
The 'receivers' parameter helps avoid constant memory allocation
as the backing storage for the slice is reused across recursive calls.
Relates to 840220c (#90)
This change adds support for hooks to be called on fields
that are tagged with `embed:""`.
### Use case
If a command has several subcommands,
many (but not all) of which need the same external resource,
this allows defining the flag-level inputs for that resource centrally,
and then using `embed:""` in any command that needs that resource.
For example, imagine:
```go
type githubClientProvider struct {
Token string `name:"github-token" env:"GITHUB_TOKEN"`
URL string `name:"github-url" env:"GITHUB_URL"`
}
func (g *githubClientProvider) BeforeApply(kctx *kong.Context) error {
return kctx.BindToProvider(func() (*github.Client, error) {
return github.NewClient(...), nil
})
}
```
Then, any command that needs GitHub client will add this field,
any other resource providers it needs,
and add parameters to its `Run` method to accept those resources:
```go
type listUsersCmd struct {
GitHub githubClientProvider `embed:""`
S3 s3ClientProvider `embed:""`
}
func (l *listUsersCmd) Run(gh *github.Client, s3 *s3.Client) error {
...
}
```
### Alternatives
It is possible to do the same today if the `*Provider` struct above
is actually a Go embed instead of a Kong embed, *and* it is exported.
```
type GitHubClientProvider struct{ ... }
type listUsersCmd struct {
GithubClientProvider
S3ClientProvider
}
```
The difference is whether the struct defining the flags
is required to be exported or not.
This allows provider functions to accept parameters that are injected by other
bindings or binding providers, eg. call the provider function with the root CLI
struct (which is automatically bound by Kong):
kong.BindToProvider(func(cli *CLI) (*Injected, error) { ... })
The golangci-lint being used was quite dated.
This change upgrades to the latest version.
Adds and updates exclusions based on new failures.
Note on revive:
I've included an opt-out for unused parameters for revive
because turning `newThing(required bool)` to `newThing(_ bool)`
is a loss of useful information.
The changes to the Go files are to fix the following issues:
```
camelcase.go:16: File is not `gofmt`-ed with `-s` (gofmt)
config_test.go:50:18: directive `//nolint: gosec` is unused for linter "gosec" (nolintlint)
defaults_test.go:28:25: G601: Implicit memory aliasing in for loop. (gosec)
doc.go:5: File is not `gofmt`-ed with `-s` (gofmt)
kong.go:446:18: directive `//nolint: gosec` is unused for linter "gosec" (nolintlint)
kong_test.go:503:20: G601: Implicit memory aliasing in for loop. (gosec)
model.go:493:10: composites: reflect.ValueError struct literal uses unkeyed fields (govet)
scanner.go:112: File is not `gofmt`-ed with `-s` (gofmt)
```
And to address broken nolint directives reported as follows by
golangci-lint.
```
[.. skipped .. ]
tag.go:65:1: directive `// nolint:gocyclo` should be written without leading space as `//nolint:gocyclo` (nolintlint)
tag.go:206:51: directive `// nolint: gocyclo` should be written without leading space as `//nolint: gocyclo` (nolintlint)
util_test.go:23:43: directive `// nolint: errcheck` should be written without leading space as `//nolint: errcheck` (nolintlint)
util_test.go:51:22: directive `// nolint: errcheck` should be written without leading space as `//nolint: errcheck` (nolintlint)
```
The former allows arbitrary structs to be embedded in the root of the
CLI, with optional tags.
The latter allows an arbitrary function to be called using Kong's
binding functionality.
* Switch to the standard errors API, that was introduced in 1.13
* Fix linter errors 🤦
* Remove withStackError
* fix: freeze go version to 1.17, since 1.18 introduced generics, which are not supported by linters yet
* fix: freeze go version to 1.17, since 1.18 introduced generics, which are not supported by linters yet
This is useful for situations where the initialisation of some object
should be deferred, eg. when there are distinct "setup" and "use" phases
to a tools lifecycle.