As Kong traces a sequence of command line arguments, it parses them and appends them to the parsed `Path` sequence. For each element in `Path`, these is a corresponding sequence of unparsed arguments. This change enables `Path` to yield these.
I have a package that uses Kong's hooks to instrument Kong applications (to monitor usage, reliability, etc of internal tools). I would like to instrument the commandline arguments as well.
This change would enable it to work roughly as follows:
```golang
func (Foo) BeforeApply(app *kong.Kong, ctx *kong.Context, t *Tracker) error {
command := []string{ctx.Model.Name}
var args []string
for _, path := range ctx.Path {
if path.Command != nil {
command = append(command, path.Command.Name)
args = path.Remainder()
}
}
app.Exit = t.exit(app.Exit)
t.WithCommand(strings.Join(command, " ")).WithArgs(args)
return nil
}
```
If a field in a struct is ignored with `kong:"-"`, any embedded fields
with the same name are also ignored. This allows an outer struct to
remove flags from an embedded struct by redefining it and adding a kong
ignore tag.
* 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.
In CI, test with Go 1.23 and 1.24,
and upgrade the Hermit-managed Go and golangci-lint to latest versions.
The new golangci-lint had a number of warnings and minor issues
that were either fixed or opted-out of.
* 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.
`passthrough:""` or `passthrough:"all"` (the default) will pass through
all further arguments including unrecognised flags.
`passthrough:"partial"` will validate flags up until the `--` or the
first positional argument, then pass through all subsequent flags and
arguments.
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) { ... })
Given a grammar like this:
```golang
var cli struct {
Args []string `arg:"" optional:"" passthrough:""`
}
```
If Kong parses `cli foo -- bar`, it will populate `Args` with `[]string{"foo", "--", "bar"}` (including "`--`").
However, if Kong parses `cli -- foo bar`, will populate `Args` with `[]string{"foo", "bar"}` (leaving off `"--"`).
This differs from the behavior of a passthrough Command, where `"--"` is included with the args in both cases.
There are 3 places where `c.endParsing()` is called
1. When `node.Passthrough` is true: https://github.com/alecthomas/kong/blob/5f9c5cc822bdb888a3671c44d4688a6f602ecb90/context.go#L366-L368
2. When `arg.Passthrough` is true: https://github.com/alecthomas/kong/blob/5f9c5cc822bdb888a3671c44d4688a6f602ecb90/context.go#L451-L453
3. When `"--"` is encountered: https://github.com/alecthomas/kong/blob/5f9c5cc822bdb888a3671c44d4688a6f602ecb90/context.go#L384-L387
The first two do not also pop any tokens. The third one does.
This commit makes `c.scan.Pop()` conditional, skipping it when the next positional argument is passthrough.
I believe this will cause Kong to behave a little more consistently — and from my perspective, `--` is relevant for args intended to be passed through! — but it will change the behavior of existing projects that use `arg:"" passthrough:""`.