From 388ba35f1a32c1cf97108a7777b8a36a84dbc1a4 Mon Sep 17 00:00:00 2001 From: Alec Thomas Date: Sat, 7 Dec 2024 17:08:31 +1100 Subject: [PATCH] feat: add support for `Provide*() (, error)` methods on commands --- README.md | 80 +++++++++++++++++++++++++++------------------------- context.go | 13 +++++++++ kong_test.go | 25 ++++++++++++++++ 3 files changed, 79 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index 10b6128..ee4befe 100644 --- a/README.md +++ b/README.md @@ -5,43 +5,40 @@ [![](https://godoc.org/github.com/alecthomas/kong?status.svg)](http://godoc.org/github.com/alecthomas/kong) [![CircleCI](https://img.shields.io/circleci/project/github/alecthomas/kong.svg)](https://circleci.com/gh/alecthomas/kong) [![Go Report Card](https://goreportcard.com/badge/github.com/alecthomas/kong)](https://goreportcard.com/report/github.com/alecthomas/kong) [![Slack chat](https://img.shields.io/static/v1?logo=slack&style=flat&label=slack&color=green&message=gophers)](https://gophers.slack.com/messages/CN9DS8YF3) - - -- [Version 1.0.0 Release](#version-100-release) -- [Introduction](#introduction) -- [Help](#help) - - [Help as a user of a Kong application](#help-as-a-user-of-a-kong-application) - - [Defining help in Kong](#defining-help-in-kong) -- [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) -- [Flags](#flags) -- [Commands and sub-commands](#commands-and-sub-commands) -- [Branching positional arguments](#branching-positional-arguments) -- [Positional arguments](#positional-arguments) -- [Slices](#slices) -- [Maps](#maps) -- [Pointers](#pointers) -- [Nested data structure](#nested-data-structure) -- [Custom named decoders](#custom-named-decoders) -- [Supported field types](#supported-field-types) -- [Custom decoders mappers](#custom-decoders-mappers) -- [Supported tags](#supported-tags) -- [Plugins](#plugins) -- [Dynamic Commands](#dynamic-commands) -- [Variable interpolation](#variable-interpolation) -- [Validation](#validation) -- [Modifying Kong's behaviour](#modifying-kongs-behaviour) - - [Namehelp and Descriptionhelp - set the application name description](#namehelp-and-descriptionhelp---set-the-application-name-description) - - [Configurationloader, paths... - load defaults from configuration files](#configurationloader-paths---load-defaults-from-configuration-files) - - [Resolver... - support for default values from external sources](#resolver---support-for-default-values-from-external-sources) - - [\*Mapper... - customising how the command-line is mapped to Go values](#mapper---customising-how-the-command-line-is-mapped-to-go-values) - - [ConfigureHelpHelpOptions and HelpHelpFunc - customising help](#configurehelphelpoptions-and-helphelpfunc---customising-help) - - [Bind... - bind values for callback hooks and Run methods](#bind---bind-values-for-callback-hooks-and-run-methods) - - [Other options](#other-options) - - +- [Kong is a command-line parser for Go](#kong-is-a-command-line-parser-for-go) + - [Version 1.0.0 Release](#version-100-release) + - [Introduction](#introduction) + - [Help](#help) + - [Help as a user of a Kong application](#help-as-a-user-of-a-kong-application) + - [Defining help in Kong](#defining-help-in-kong) + - [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) + - [Flags](#flags) + - [Commands and sub-commands](#commands-and-sub-commands) + - [Branching positional arguments](#branching-positional-arguments) + - [Positional arguments](#positional-arguments) + - [Slices](#slices) + - [Maps](#maps) + - [Pointers](#pointers) + - [Nested data structure](#nested-data-structure) + - [Custom named decoders](#custom-named-decoders) + - [Supported field types](#supported-field-types) + - [Custom decoders (mappers)](#custom-decoders-mappers) + - [Supported tags](#supported-tags) + - [Plugins](#plugins) + - [Dynamic Commands](#dynamic-commands) + - [Variable interpolation](#variable-interpolation) + - [Validation](#validation) + - [Modifying Kong's behaviour](#modifying-kongs-behaviour) + - [`Name(help)` and `Description(help)` - set the application name description](#namehelp-and-descriptionhelp---set-the-application-name-description) + - [`Configuration(loader, paths...)` - load defaults from configuration files](#configurationloader-paths---load-defaults-from-configuration-files) + - [`Resolver(...)` - support for default values from external sources](#resolver---support-for-default-values-from-external-sources) + - [`*Mapper(...)` - customising how the command-line is mapped to Go values](#mapper---customising-how-the-command-line-is-mapped-to-go-values) + - [`ConfigureHelp(HelpOptions)` and `Help(HelpFunc)` - customising help](#configurehelphelpoptions-and-helphelpfunc---customising-help) + - [`Bind(...)` - bind values for callback hooks and Run() methods](#bind---bind-values-for-callback-hooks-and-run-methods) + - [Other options](#other-options) ## Version 1.0.0 Release @@ -755,9 +752,14 @@ The default help output is usually sufficient, but if not there are two solution 3. Use `ValueFormatter(HelpValueFormatter)` if you want to just customize the help text that is accompanied by flags and arguments. 4. Use `Groups([]Group)` if you want to customize group titles or add a header. -### `Bind(...)` - bind values for callback hooks and Run() methods +### Injecting values into `Run()` methods -See the [section on hooks](#hooks-beforereset-beforeresolve-beforeapply-afterapply-and-the-bind-option) for details. +There are several ways to inject values into `Run()` methods: + +1. Use `Bind()` to bind values directly. +2. Use `BindTo()` to bind values to an interface type. +3. Use `BindToProvider()` to bind values to a function that provides the value. +4. Implement `Provide() error` methods on the command structure. ### Other options diff --git a/context.go b/context.go index b339f6b..fd168fa 100644 --- a/context.go +++ b/context.go @@ -782,6 +782,19 @@ func (c *Context) RunNode(node *Node, binds ...interface{}) (err error) { methodBinds = methodBinds.clone() for p := node; p != nil; p = p.Parent { methodBinds = methodBinds.add(p.Target.Addr().Interface()) + // Try value and pointer to value. + for _, p := range []reflect.Value{p.Target, p.Target.Addr()} { + t := p.Type() + for i := 0; i < p.NumMethod(); i++ { + methodt := t.Method(i) + if strings.HasPrefix(methodt.Name, "Provide") { + method := p.Method(i) + if err := methodBinds.addProvider(method.Interface()); err != nil { + return fmt.Errorf("%s.%s: %w", t.Name(), methodt.Name, err) + } + } + } + } } if method.IsValid() { methods = append(methods, targetMethod{node, method, methodBinds}) diff --git a/kong_test.go b/kong_test.go index bd47185..2b52758 100644 --- a/kong_test.go +++ b/kong_test.go @@ -2381,3 +2381,28 @@ func TestAfterRun(t *testing.T) { assert.NoError(t, err) assert.Equal(t, afterRunCLI{runCalled: true, afterRunCalled: true}, cli) } + +type ProvidedString string + +type providerCLI struct { + Sub providerSubCommand `cmd:""` +} + +type providerSubCommand struct{} + +func (p *providerCLI) ProvideFoo() (ProvidedString, error) { + return ProvidedString("foo"), nil +} + +func (p *providerSubCommand) Run(t *testing.T, ps ProvidedString) error { + assert.Equal(t, ProvidedString("foo"), ps) + return nil +} + +func TestProviderMethods(t *testing.T) { + k := mustNew(t, &providerCLI{}) + kctx, err := k.Parse([]string{"sub"}) + assert.NoError(t, err) + err = kctx.Run(t) + assert.NoError(t, err) +}