diff --git a/README.md b/README.md
index bb791e5..b5ba8a9 100644
--- a/README.md
+++ b/README.md
@@ -32,7 +32,6 @@
-
## Introduction
Kong aims to support arbitrarily complex command-line structures with as little developer effort as possible.
@@ -75,7 +74,6 @@ func main() {
}
```
-
## Help
Help is automatically generated. With no other arguments provided, help will display a full summary of all available commands.
@@ -116,12 +114,10 @@ eg.
-f, --force Force removal.
-r, --recursive Recursively remove files.
-
## Command handling
There are two ways to handle commands in Kong.
-
### Switch on the command string
When you call `kong.Parse()` it will return a unique string representation of the command. Each command branch in the hierarchy will be a bare word and each branching argument or required positional argument will be the name surrounded by angle brackets. Here's an example:
@@ -161,7 +157,6 @@ func main() {
This has the advantage that it is convenient, but the downside that if you modify your CLI structure, the strings may change. This can be fragile.
-
### Attach a `Run(...) error` method to each command
A more robust approach is to break each command out into their own structs:
@@ -175,6 +170,8 @@ In addition to values bound with the `kong.Bind(...)` option, any values
passed through to `kong.Context.Run(...)` are also bindable to the target's
`Run()` arguments.
+Finally, hooks can also contribute bindings via `kong.Context.Bind()` and `kong.Context.BindTo()`.
+
There's a full example emulating part of the Docker CLI [here](https://github.com/alecthomas/kong/tree/master/_examples/docker).
eg.
@@ -217,14 +214,13 @@ func main() {
```
-
## Hooks: BeforeResolve(), BeforeApply(), AfterApply() and the Bind() option
If a node in the grammar has a `BeforeResolve(...)`, `BeforeApply(...) error` and/or `AfterApply(...) error` method, those methods will be called before validation/assignment and after validation/assignment, respectively.
The `--help` flag is implemented with a `BeforeApply` hook.
-Arguments to hooks are provided via the `Bind(...)` option. `*Kong`, `*Context` and `*Path` are also bound.
+Arguments to hooks are provided via the `Bind(...)` option. `*Kong`, `*Context` and `*Path` are also bound and finally, hooks can also contribute bindings via `kong.Context.Bind()` and `kong.Context.BindTo()`.
eg.
@@ -251,7 +247,6 @@ func main() {
}
```
-
## Flags
Any [mapped](#mapper---customising-how-the-command-line-is-mapped-to-go-values) field in the command structure *not* tagged with `cmd` or `arg` will be a flag. Flags are optional by default.
@@ -264,7 +259,6 @@ type CLI struct {
}
```
-
## Commands and sub-commands
Sub-commands are specified by tagging a struct field with `cmd`. Kong supports arbitrarily nested commands.
@@ -282,7 +276,6 @@ type CLI struct {
}
```
-
## Branching positional arguments
In addition to sub-commands, structs can also be configured as branching positional arguments.
@@ -310,14 +303,12 @@ var CLI struct {
This looks a little verbose in this contrived example, but typically this will not be the case.
-
## Terminating positional arguments
If a [mapped type](#mapper---customising-how-the-command-line-is-mapped-to-go-values) is tagged with `arg` it will be treated as the final positional values to be parsed on the command line.
If a positional argument is a slice, all remaining arguments will be appended to that slice.
-
## Slices
Slice values are treated specially. First the input is split on the `sep:""` tag (defaults to `,`), then each element is parsed by the slice element type and appended to the slice. If the same value is encountered multiple times, elements continue to be appended.
@@ -336,7 +327,6 @@ var CLI struct {
}
```
-
## Maps
Maps are similar to slices except that only one key/value pair can be assigned per value, and the `sep` tag denotes the assignment character and defaults to `=`.
@@ -359,7 +349,6 @@ var CLI struct {
For flags, multiple key+value pairs should be separated by `;` eg. `--set="key1=value1;key2=value2"`.
-
## Custom named decoders
Kong includes a number of builtin custom type mappers. These can be used by
@@ -378,13 +367,11 @@ specifies the element type. For maps, the tag has the format
`tag:"[]:[]"` where either may be omitted.
-
## Custom decoders (mappers)
If a field implements the [MapperValue](https://godoc.org/github.com/alecthomas/kong#MapperValue)
interface it will be used to decode arguments into the field.
-
## Supported tags
Tags can be in two forms:
@@ -416,7 +403,6 @@ Tag | Description
`set:"K=V"` | Set a variable for expansion by child elements. Multiples can occur.
`embed` | If present, this field's children will be embedded in the parent. Useful for composition.
-
## Variable interpolation
Kong supports limited variable interpolation into help strings, enum lists and
@@ -454,14 +440,12 @@ func main() {
}
```
-
## Modifying Kong's behaviour
Each Kong parser can be configured via functional options passed to `New(cli interface{}, options...Option)`.
The full set of options can be found [here](https://godoc.org/github.com/alecthomas/kong#Option).
-
### `Name(help)` and `Description(help)` - set the application name description
Set the application name and/or description.
@@ -470,7 +454,6 @@ The name of the application will default to the binary name, but can be overridd
As with all help in Kong, text will be wrapped to the terminal.
-
### `Configuration(loader, paths...)` - load defaults from configuration files
This option provides Kong with support for loading defaults from a set of configuration files. Each file is opened, if possible, and the loader called to create a resolver for that file.
@@ -481,14 +464,12 @@ eg.
kong.Parse(&cli, kong.Configuration(kong.JSON, "/etc/myapp.json", "~/.myapp.json"))
```
-
### `Resolver(...)` - support for default values from external sources
Resolvers are Kong's extension point for providing default values from external sources. As an example, support for environment variables via the `env` tag is provided by a resolver. There's also a builtin resolver for JSON configuration files.
Example resolvers can be found in [resolver.go](https://github.com/alecthomas/kong/blob/master/resolver.go).
-
### `*Mapper(...)` - customising how the command-line is mapped to Go values
Command-line arguments are mapped to Go values via the Mapper interface:
@@ -511,7 +492,6 @@ All builtin Go types (as well as a bunch of useful stdlib types like `time.Time`
3. `TypeMapper(reflect.Type, Mapper)`.
4. `ValueMapper(interface{}, Mapper)`, passing in a pointer to a field of the grammar.
-
### `ConfigureHelp(HelpOptions)` and `Help(HelpFunc)` - customising help
The default help output is usually sufficient, but if not there are two solutions.
@@ -519,12 +499,10 @@ The default help output is usually sufficient, but if not there are two solution
1. Use `ConfigureHelp(HelpOptions)` to configure how help is formatted (see [HelpOptions](https://godoc.org/github.com/alecthomas/kong#HelpOptions) for details).
2. Custom help can be wired into Kong via the `Help(HelpFunc)` option. The `HelpFunc` is passed a `Context`, which contains the parsed context for the current command-line. See the implementation of `PrintHelp` for an example.
-
### `Bind(...)` - bind values for callback hooks and Run() methods
See the [section on hooks](#BeforeApply-AfterApply-and-the-bind-option) for details.
-
### Other options
The full set of options can be found [here](https://godoc.org/github.com/alecthomas/kong#Option).
diff --git a/callbacks.go b/callbacks.go
index 351ee48..112d35a 100644
--- a/callbacks.go
+++ b/callbacks.go
@@ -23,6 +23,12 @@ func (b bindings) clone() bindings {
return out
}
+func (b bindings) merge(other bindings) {
+ for k, v := range other {
+ b[k] = v
+ }
+}
+
func getMethod(value reflect.Value, name string) reflect.Value {
method := value.MethodByName(name)
if !method.IsValid() {
diff --git a/context.go b/context.go
index 7f67199..201ce16 100644
--- a/context.go
+++ b/context.go
@@ -51,7 +51,8 @@ type Context struct {
Error error
values map[*Value]reflect.Value // Temporary values during tracing.
- resolvers []Resolver // Extra context-specific resolvers.
+ bindings bindings
+ resolvers []Resolver // Extra context-specific resolvers.
scan *Scanner
}
@@ -68,8 +69,9 @@ func Trace(k *Kong, args []string) (*Context, error) {
Path: []*Path{
{App: k.Model, Flags: k.Model.Flags},
},
- values: map[*Value]reflect.Value{},
- scan: Scan(args...),
+ values: map[*Value]reflect.Value{},
+ scan: Scan(args...),
+ bindings: bindings{},
}
c.Error = c.trace(c.Model.Node)
err := c.reset(c.Model.Node)
@@ -80,6 +82,16 @@ func Trace(k *Kong, args []string) (*Context, error) {
return c, nil
}
+// Bind adds bindings to the Context.
+func (c *Context) Bind(args ...interface{}) {
+ c.bindings.add(args...)
+}
+
+// BindTo adds a binding to the Context.
+func (c *Context) BindTo(impl, iface interface{}) {
+ c.bindings[reflect.TypeOf(iface).Elem()] = reflect.ValueOf(impl)
+}
+
// Value returns the value for a particular path element.
func (c *Context) Value(path *Path) reflect.Value {
switch {
diff --git a/kong.go b/kong.go
index 920e966..408cf44 100644
--- a/kong.go
+++ b/kong.go
@@ -239,7 +239,10 @@ func (k *Kong) applyHook(ctx *Context, name string) error {
if !method.IsValid() {
continue
}
- binds := k.bindings.clone().add(ctx, trace).add(trace.Node().Vars().CloneWith(k.vars))
+ binds := k.bindings.clone()
+ binds.add(ctx, trace)
+ binds.add(trace.Node().Vars().CloneWith(k.vars))
+ binds.merge(ctx.bindings)
if err := callMethod(name, value, method, binds); err != nil {
return err
}
diff --git a/kong_test.go b/kong_test.go
index 5278c54..d8218bd 100644
--- a/kong_test.go
+++ b/kong_test.go
@@ -662,3 +662,29 @@ func TestEnum(t *testing.T) {
_, err := mustNew(t, &cli).Parse([]string{"--flag", "d"})
require.EqualError(t, err, "--flag=STRING must be one of a,b,c but got \"\"")
}
+
+type commandWithHook struct {
+ value string
+}
+
+func (c *commandWithHook) AfterApply(cli *cliWithHook) error {
+ c.value = cli.Flag
+ return nil
+}
+
+type cliWithHook struct {
+ Flag string
+ Command commandWithHook `cmd:""`
+}
+
+func (c *cliWithHook) AfterApply(ctx *kong.Context) error {
+ ctx.Bind(c)
+ return nil
+}
+
+func TestParentBindings(t *testing.T) {
+ cli := &cliWithHook{}
+ _, err := mustNew(t, cli).Parse([]string{"command", "--flag=foo"})
+ require.NoError(t, err)
+ require.Equal(t, "foo", cli.Command.value)
+}