Support hooks on embed:"" fields (#493)
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 commit is contained in:
+13
-1
@@ -80,7 +80,19 @@ func getMethods(value reflect.Value, name string) []reflect.Value {
|
||||
for i := 0; i < value.NumField(); i++ {
|
||||
field := value.Field(i)
|
||||
fieldType := t.Field(i)
|
||||
if fieldType.IsExported() && fieldType.Anonymous {
|
||||
if !fieldType.IsExported() {
|
||||
continue
|
||||
}
|
||||
|
||||
// Hooks on exported embedded fields should be called.
|
||||
if fieldType.Anonymous {
|
||||
receivers = append(receivers, field)
|
||||
continue
|
||||
}
|
||||
|
||||
// Hooks on exported fields that are not exported,
|
||||
// but are tagged with `embed:""` should be called.
|
||||
if _, ok := fieldType.Tag.Lookup("embed"); ok {
|
||||
receivers = append(receivers, field)
|
||||
}
|
||||
}
|
||||
|
||||
+14
-1
@@ -2413,9 +2413,19 @@ func (e *EmbeddedCallback) AfterApply() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type taggedEmbeddedCallback struct {
|
||||
Tagged bool
|
||||
}
|
||||
|
||||
func (e *taggedEmbeddedCallback) AfterApply() error {
|
||||
e.Tagged = true
|
||||
return nil
|
||||
}
|
||||
|
||||
type EmbeddedRoot struct {
|
||||
EmbeddedCallback
|
||||
Root bool
|
||||
Tagged taggedEmbeddedCallback `embed:""`
|
||||
Root bool
|
||||
}
|
||||
|
||||
func (e *EmbeddedRoot) AfterApply() error {
|
||||
@@ -2432,6 +2442,9 @@ func TestEmbeddedCallbacks(t *testing.T) {
|
||||
EmbeddedCallback: EmbeddedCallback{
|
||||
Embedded: true,
|
||||
},
|
||||
Tagged: taggedEmbeddedCallback{
|
||||
Tagged: true,
|
||||
},
|
||||
Root: true,
|
||||
}
|
||||
assert.Equal(t, expected, actual)
|
||||
|
||||
Reference in New Issue
Block a user