Skip to content

Commit

Permalink
feat: add support for Provide*() (<type>, error) methods on commands
Browse files Browse the repository at this point in the history
  • Loading branch information
alecthomas committed Dec 7, 2024
1 parent 87ee7dc commit 388ba35
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 39 deletions.
80 changes: 41 additions & 39 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

<!-- TOC depthfrom:2 depthto:3 -->

- [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)

<!-- /TOC -->
- [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

Expand Down Expand Up @@ -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<Type>() error` methods on the command structure.

### Other options

Expand Down
13 changes: 13 additions & 0 deletions context.go
Original file line number Diff line number Diff line change
Expand Up @@ -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})
Expand Down
25 changes: 25 additions & 0 deletions kong_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

0 comments on commit 388ba35

Please sign in to comment.