Call Validate() functions on nodes if present.
This commit is contained in:
@@ -2,6 +2,7 @@
|
|||||||
<p align="center"><img width="90%" src="kong.png" /></p>
|
<p align="center"><img width="90%" src="kong.png" /></p>
|
||||||
|
|
||||||
# Kong is a command-line parser for Go
|
# Kong is a command-line parser for Go
|
||||||
|
|
||||||
[](http://godoc.org/github.com/alecthomas/kong) [](https://circleci.com/gh/alecthomas/kong) [](https://goreportcard.com/report/github.com/alecthomas/kong) [](https://gophers.slack.com/messages/CN9DS8YF3)
|
[](http://godoc.org/github.com/alecthomas/kong) [](https://circleci.com/gh/alecthomas/kong) [](https://goreportcard.com/report/github.com/alecthomas/kong) [](https://gophers.slack.com/messages/CN9DS8YF3)
|
||||||
|
|
||||||
<!-- TOC depthFrom:2 updateOnSave:true withLinks:true -->
|
<!-- TOC depthFrom:2 updateOnSave:true withLinks:true -->
|
||||||
@@ -24,6 +25,7 @@
|
|||||||
- [Supported tags](#supported-tags)
|
- [Supported tags](#supported-tags)
|
||||||
- [Plugins](#plugins)
|
- [Plugins](#plugins)
|
||||||
- [Variable interpolation](#variable-interpolation)
|
- [Variable interpolation](#variable-interpolation)
|
||||||
|
- [Validation](#validation)
|
||||||
- [Modifying Kong's behaviour](#modifying-kongs-behaviour)
|
- [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)
|
- [`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)
|
- [`Configuration(loader, paths...)` - load defaults from configuration files](#configurationloader-paths---load-defaults-from-configuration-files)
|
||||||
@@ -39,7 +41,8 @@
|
|||||||
|
|
||||||
Kong aims to support arbitrarily complex command-line structures with as little developer effort as possible.
|
Kong aims to support arbitrarily complex command-line structures with as little developer effort as possible.
|
||||||
|
|
||||||
To achieve that, command-lines are expressed as Go types, with the structure and tags directing how the command line is mapped onto the struct.
|
To achieve that, command-lines are expressed as Go types, with the structure and tags directing how the command line is
|
||||||
|
mapped onto the struct.
|
||||||
|
|
||||||
For example, the following command-line:
|
For example, the following command-line:
|
||||||
|
|
||||||
@@ -54,32 +57,33 @@ package main
|
|||||||
import "github.com/alecthomas/kong"
|
import "github.com/alecthomas/kong"
|
||||||
|
|
||||||
var CLI struct {
|
var CLI struct {
|
||||||
Rm struct {
|
Rm struct {
|
||||||
Force bool `help:"Force removal."`
|
Force bool `help:"Force removal."`
|
||||||
Recursive bool `help:"Recursively remove files."`
|
Recursive bool `help:"Recursively remove files."`
|
||||||
|
|
||||||
Paths []string `arg name:"path" help:"Paths to remove." type:"path"`
|
Paths []string `arg name:"path" help:"Paths to remove." type:"path"`
|
||||||
} `cmd help:"Remove files."`
|
} `cmd help:"Remove files."`
|
||||||
|
|
||||||
Ls struct {
|
Ls struct {
|
||||||
Paths []string `arg optional name:"path" help:"Paths to list." type:"path"`
|
Paths []string `arg optional name:"path" help:"Paths to list." type:"path"`
|
||||||
} `cmd help:"List paths."`
|
} `cmd help:"List paths."`
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
ctx := kong.Parse(&CLI)
|
ctx := kong.Parse(&CLI)
|
||||||
switch ctx.Command() {
|
switch ctx.Command() {
|
||||||
case "rm <path>":
|
case "rm <path>":
|
||||||
case "ls":
|
case "ls":
|
||||||
default:
|
default:
|
||||||
panic(ctx.Command())
|
panic(ctx.Command())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Help
|
## Help
|
||||||
|
|
||||||
Help is automatically generated. With no other arguments provided, help will display a full summary of all available commands.
|
Help is automatically generated. With no other arguments provided, help will display a full summary of all available
|
||||||
|
commands.
|
||||||
|
|
||||||
eg.
|
eg.
|
||||||
|
|
||||||
@@ -117,9 +121,8 @@ eg.
|
|||||||
-f, --force Force removal.
|
-f, --force Force removal.
|
||||||
-r, --recursive Recursively remove files.
|
-r, --recursive Recursively remove files.
|
||||||
|
|
||||||
For flags with associated environment variables, the variable `${env}` can be
|
For flags with associated environment variables, the variable `${env}` can be interpolated into the help string. In the
|
||||||
interpolated into the help string. In the absence of this variable in the help,
|
absence of this variable in the help,
|
||||||
|
|
||||||
|
|
||||||
## Command handling
|
## Command handling
|
||||||
|
|
||||||
@@ -127,7 +130,9 @@ There are two ways to handle commands in Kong.
|
|||||||
|
|
||||||
### Switch on the command string
|
### 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:
|
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:
|
||||||
|
|
||||||
There's an example of this pattern [here](https://github.com/alecthomas/kong/blob/master/_examples/shell/main.go).
|
There's an example of this pattern [here](https://github.com/alecthomas/kong/blob/master/_examples/shell/main.go).
|
||||||
|
|
||||||
@@ -139,30 +144,31 @@ package main
|
|||||||
import "github.com/alecthomas/kong"
|
import "github.com/alecthomas/kong"
|
||||||
|
|
||||||
var CLI struct {
|
var CLI struct {
|
||||||
Rm struct {
|
Rm struct {
|
||||||
Force bool `help:"Force removal."`
|
Force bool `help:"Force removal."`
|
||||||
Recursive bool `help:"Recursively remove files."`
|
Recursive bool `help:"Recursively remove files."`
|
||||||
|
|
||||||
Paths []string `arg name:"path" help:"Paths to remove." type:"path"`
|
Paths []string `arg name:"path" help:"Paths to remove." type:"path"`
|
||||||
} `cmd help:"Remove files."`
|
} `cmd help:"Remove files."`
|
||||||
|
|
||||||
Ls struct {
|
Ls struct {
|
||||||
Paths []string `arg optional name:"path" help:"Paths to list." type:"path"`
|
Paths []string `arg optional name:"path" help:"Paths to list." type:"path"`
|
||||||
} `cmd help:"List paths."`
|
} `cmd help:"List paths."`
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
ctx := kong.Parse(&CLI)
|
ctx := kong.Parse(&CLI)
|
||||||
switch ctx.Command() {
|
switch ctx.Command() {
|
||||||
case "rm <path>":
|
case "rm <path>":
|
||||||
case "ls":
|
case "ls":
|
||||||
default:
|
default:
|
||||||
panic(ctx.Command())
|
panic(ctx.Command())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
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.
|
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
|
### Attach a `Run(...) error` method to each command
|
||||||
|
|
||||||
@@ -173,69 +179,71 @@ A more robust approach is to break each command out into their own structs:
|
|||||||
3. Call `kong.Kong.Parse()` to obtain a `kong.Context`.
|
3. Call `kong.Kong.Parse()` to obtain a `kong.Context`.
|
||||||
4. Call `kong.Context.Run(bindings...)` to call the selected parsed command.
|
4. Call `kong.Context.Run(bindings...)` to call the selected parsed command.
|
||||||
|
|
||||||
Once a command node is selected by Kong it will search from that node back to the root. Each
|
Once a command node is selected by Kong it will search from that node back to the root. Each encountered command node
|
||||||
encountered command node with a `Run(...) error` will be called in reverse order. This allows
|
with a `Run(...) error` will be called in reverse order. This allows sub-trees to be re-used fairly conveniently.
|
||||||
sub-trees to be re-used fairly conveniently.
|
|
||||||
|
|
||||||
In addition to values bound with the `kong.Bind(...)` option, any values
|
In addition to values bound with the `kong.Bind(...)` option, any values passed through to `kong.Context.Run(...)` are
|
||||||
passed through to `kong.Context.Run(...)` are also bindable to the target's
|
also bindable to the target's
|
||||||
`Run()` arguments.
|
`Run()` arguments.
|
||||||
|
|
||||||
Finally, hooks can also contribute bindings via `kong.Context.Bind()` and `kong.Context.BindTo()`.
|
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).
|
There's a full example emulating part of the Docker
|
||||||
|
CLI [here](https://github.com/alecthomas/kong/tree/master/_examples/docker).
|
||||||
|
|
||||||
eg.
|
eg.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
type Context struct {
|
type Context struct {
|
||||||
Debug bool
|
Debug bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type RmCmd struct {
|
type RmCmd struct {
|
||||||
Force bool `help:"Force removal."`
|
Force bool `help:"Force removal."`
|
||||||
Recursive bool `help:"Recursively remove files."`
|
Recursive bool `help:"Recursively remove files."`
|
||||||
|
|
||||||
Paths []string `arg name:"path" help:"Paths to remove." type:"path"`
|
Paths []string `arg name:"path" help:"Paths to remove." type:"path"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RmCmd) Run(ctx *Context) error {
|
func (r *RmCmd) Run(ctx *Context) error {
|
||||||
fmt.Println("rm", r.Paths)
|
fmt.Println("rm", r.Paths)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type LsCmd struct {
|
type LsCmd struct {
|
||||||
Paths []string `arg optional name:"path" help:"Paths to list." type:"path"`
|
Paths []string `arg optional name:"path" help:"Paths to list." type:"path"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *LsCmd) Run(ctx *Context) error {
|
func (l *LsCmd) Run(ctx *Context) error {
|
||||||
fmt.Println("ls", l.Paths)
|
fmt.Println("ls", l.Paths)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var cli struct {
|
var cli struct {
|
||||||
Debug bool `help:"Enable debug mode."`
|
Debug bool `help:"Enable debug mode."`
|
||||||
|
|
||||||
Rm RmCmd `cmd help:"Remove files."`
|
Rm RmCmd `cmd help:"Remove files."`
|
||||||
Ls LsCmd `cmd help:"List paths."`
|
Ls LsCmd `cmd help:"List paths."`
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
ctx := kong.Parse(&cli)
|
ctx := kong.Parse(&cli)
|
||||||
// Call the Run() method of the selected parsed command.
|
// Call the Run() method of the selected parsed command.
|
||||||
err := ctx.Run(&Context{Debug: cli.Debug})
|
err := ctx.Run(&Context{Debug: cli.Debug})
|
||||||
ctx.FatalIfErrorf(err)
|
ctx.FatalIfErrorf(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Hooks: BeforeResolve(), BeforeApply(), AfterApply() and the Bind() option
|
## 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.
|
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.
|
The `--help` flag is implemented with a `BeforeApply` hook.
|
||||||
|
|
||||||
Arguments to hooks are provided via the `Run(...)` method or `Bind(...)` option. `*Kong`, `*Context` and `*Path` are also bound and finally, hooks can also contribute bindings via `kong.Context.Bind()` and `kong.Context.BindTo()`.
|
Arguments to hooks are provided via the `Run(...)` method or `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.
|
eg.
|
||||||
|
|
||||||
@@ -244,33 +252,34 @@ eg.
|
|||||||
type debugFlag bool
|
type debugFlag bool
|
||||||
|
|
||||||
func (d debugFlag) BeforeApply(logger *log.Logger) error {
|
func (d debugFlag) BeforeApply(logger *log.Logger) error {
|
||||||
logger.SetOutput(os.Stdout)
|
logger.SetOutput(os.Stdout)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var cli struct {
|
var cli struct {
|
||||||
Debug debugFlag `help:"Enable debug logging."`
|
Debug debugFlag `help:"Enable debug logging."`
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// Debug logger going to discard.
|
// Debug logger going to discard.
|
||||||
logger := log.New(ioutil.Discard, "", log.LstdFlags)
|
logger := log.New(ioutil.Discard, "", log.LstdFlags)
|
||||||
|
|
||||||
ctx := kong.Parse(&cli, kong.Bind(logger))
|
ctx := kong.Parse(&cli, kong.Bind(logger))
|
||||||
|
|
||||||
// ...
|
// ...
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Flags
|
## 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.
|
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.
|
||||||
|
|
||||||
eg. The command-line `app [--flag="foo"]` can be represented by the following.
|
eg. The command-line `app [--flag="foo"]` can be represented by the following.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
type CLI struct {
|
type CLI struct {
|
||||||
Flag string
|
Flag string
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -282,12 +291,12 @@ eg. The following struct represents the CLI structure `command [--flag="str"] su
|
|||||||
|
|
||||||
```go
|
```go
|
||||||
type CLI struct {
|
type CLI struct {
|
||||||
Command struct {
|
Command struct {
|
||||||
Flag string
|
Flag string
|
||||||
|
|
||||||
SubCommand struct {
|
SubCommand struct {
|
||||||
} `cmd`
|
} `cmd`
|
||||||
} `cmd`
|
} `cmd`
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -297,7 +306,9 @@ If a sub-command is tagged with `default:"1"` it will be selected if there are n
|
|||||||
|
|
||||||
In addition to sub-commands, structs can also be configured as branching positional arguments.
|
In addition to sub-commands, structs can also be configured as branching positional arguments.
|
||||||
|
|
||||||
This is achieved by tagging an [unmapped](#mapper---customising-how-the-command-line-is-mapped-to-go-values) nested struct field with `arg`, then including a positional argument field inside that struct _with the same name_. For example, the following command structure:
|
This is achieved by tagging an [unmapped](#mapper---customising-how-the-command-line-is-mapped-to-go-values) nested
|
||||||
|
struct field with `arg`, then including a positional argument field inside that struct _with the same name_. For
|
||||||
|
example, the following command structure:
|
||||||
|
|
||||||
app rename <name> to <name>
|
app rename <name> to <name>
|
||||||
|
|
||||||
@@ -305,16 +316,16 @@ Can be represented with the following:
|
|||||||
|
|
||||||
```go
|
```go
|
||||||
var CLI struct {
|
var CLI struct {
|
||||||
Rename struct {
|
Rename struct {
|
||||||
Name struct {
|
Name struct {
|
||||||
Name string `arg` // <-- NOTE: identical name to enclosing struct field.
|
Name string `arg` // <-- NOTE: identical name to enclosing struct field.
|
||||||
To struct {
|
To struct {
|
||||||
Name struct {
|
Name struct {
|
||||||
Name string `arg`
|
Name string `arg`
|
||||||
} `arg`
|
} `arg`
|
||||||
} `cmd`
|
} `cmd`
|
||||||
} `arg`
|
} `arg`
|
||||||
} `cmd`
|
} `cmd`
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -322,13 +333,16 @@ This looks a little verbose in this contrived example, but typically this will n
|
|||||||
|
|
||||||
## Terminating positional arguments
|
## 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 [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.
|
If a positional argument is a slice, all remaining arguments will be appended to that slice.
|
||||||
|
|
||||||
## Slices
|
## Slices
|
||||||
|
|
||||||
Slice values are treated specially. First the input is split on the `sep:"<rune>"` 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.
|
Slice values are treated specially. First the input is split on the `sep:"<rune>"` 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.
|
||||||
|
|
||||||
To represent the following command-line:
|
To represent the following command-line:
|
||||||
|
|
||||||
@@ -338,15 +352,16 @@ You would use the following:
|
|||||||
|
|
||||||
```go
|
```go
|
||||||
var CLI struct {
|
var CLI struct {
|
||||||
Ls struct {
|
Ls struct {
|
||||||
Files []string `arg type:"existingfile"`
|
Files []string `arg type:"existingfile"`
|
||||||
} `cmd`
|
} `cmd`
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Maps
|
## 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 `=`.
|
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 `=`.
|
||||||
|
|
||||||
To represent the following command-line:
|
To represent the following command-line:
|
||||||
|
|
||||||
@@ -356,21 +371,21 @@ You would use the following:
|
|||||||
|
|
||||||
```go
|
```go
|
||||||
var CLI struct {
|
var CLI struct {
|
||||||
Config struct {
|
Config struct {
|
||||||
Set struct {
|
Set struct {
|
||||||
Config map[string]float64 `arg type:"file:"`
|
Config map[string]float64 `arg type:"file:"`
|
||||||
} `cmd`
|
} `cmd`
|
||||||
} `cmd`
|
} `cmd`
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
For flags, multiple key+value pairs should be separated by `mapsep:"rune"` tag (defaults to `;`) eg. `--set="key1=value1;key2=value2"`.
|
For flags, multiple key+value pairs should be separated by `mapsep:"rune"` tag (defaults to `;`)
|
||||||
|
eg. `--set="key1=value1;key2=value2"`.
|
||||||
|
|
||||||
## Custom named decoders
|
## Custom named decoders
|
||||||
|
|
||||||
Kong includes a number of builtin custom type mappers. These can be used by
|
Kong includes a number of builtin custom type mappers. These can be used by specifying the tag `type:"<type>"`. They are
|
||||||
specifying the tag `type:"<type>"`. They are registered with the option
|
registered with the option function `NamedMapper(name, mapper)`.
|
||||||
function `NamedMapper(name, mapper)`.
|
|
||||||
|
|
||||||
| Name | Description
|
| Name | Description
|
||||||
|-------------------|---------------------------------------------------
|
|-------------------|---------------------------------------------------
|
||||||
@@ -379,19 +394,16 @@ function `NamedMapper(name, mapper)`.
|
|||||||
| `existingdir` | An existing directory. ~ expansion is applied.
|
| `existingdir` | An existing directory. ~ expansion is applied.
|
||||||
| `counter` | Increment a numeric field. Useful for `-vvv`. Can accept `-s`, `--long` or `--long=N`.
|
| `counter` | Increment a numeric field. Useful for `-vvv`. Can accept `-s`, `--long` or `--long=N`.
|
||||||
|
|
||||||
|
Slices and maps treat type tags specially. For slices, the `type:""` tag specifies the element type. For maps, the tag
|
||||||
Slices and maps treat type tags specially. For slices, the `type:""` tag
|
has the format
|
||||||
specifies the element type. For maps, the tag has the format
|
|
||||||
`tag:"[<key>]:[<value>]"` where either may be omitted.
|
`tag:"[<key>]:[<value>]"` where either may be omitted.
|
||||||
|
|
||||||
## Supported field types
|
## Supported field types
|
||||||
|
|
||||||
|
|
||||||
## Custom decoders (mappers)
|
## Custom decoders (mappers)
|
||||||
|
|
||||||
|
Any field implementing `encoding.TextUnmarshaler` or `json.Unmarshaler` will use those interfaces for decoding values.
|
||||||
Any field implementing `encoding.TextUnmarshaler` or `json.Unmarshaler` will use those interfaces
|
Kong also includes builtin support for many common Go types:
|
||||||
for decoding values. Kong also includes builtin support for many common Go types:
|
|
||||||
|
|
||||||
| Type | Description
|
| Type | Description
|
||||||
|---------------------|--------------------------------------------
|
|---------------------|--------------------------------------------
|
||||||
@@ -441,18 +453,19 @@ Tag | Description
|
|||||||
|
|
||||||
## Plugins
|
## Plugins
|
||||||
|
|
||||||
Kong CLI's can be extended by embedding the `kong.Plugin` type and populating it with pointers to Kong annotated structs. For example:
|
Kong CLI's can be extended by embedding the `kong.Plugin` type and populating it with pointers to Kong annotated
|
||||||
|
structs. For example:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
var pluginOne struct {
|
var pluginOne struct {
|
||||||
PluginOneFlag string
|
PluginOneFlag string
|
||||||
}
|
}
|
||||||
var pluginTwo struct {
|
var pluginTwo struct {
|
||||||
PluginTwoFlag string
|
PluginTwoFlag string
|
||||||
}
|
}
|
||||||
var cli struct {
|
var cli struct {
|
||||||
BaseFlag string
|
BaseFlag string
|
||||||
kong.Plugins
|
kong.Plugins
|
||||||
}
|
}
|
||||||
cli.Plugins = kong.Plugins{&pluginOne, &pluginTwo}
|
cli.Plugins = kong.Plugins{&pluginOne, &pluginTwo}
|
||||||
```
|
```
|
||||||
@@ -461,23 +474,20 @@ Additionally if an interface type is embedded, it can also be populated with a K
|
|||||||
|
|
||||||
## Variable interpolation
|
## Variable interpolation
|
||||||
|
|
||||||
Kong supports limited variable interpolation into help strings, enum lists and
|
Kong supports limited variable interpolation into help strings, enum lists and default values.
|
||||||
default values.
|
|
||||||
|
|
||||||
Variables are in the form:
|
Variables are in the form:
|
||||||
|
|
||||||
${<name>}
|
${<name>}
|
||||||
${<name>=<default>}
|
${<name>=<default>}
|
||||||
|
|
||||||
Variables are set with the `Vars{"key": "value", ...}` option. Undefined
|
Variables are set with the `Vars{"key": "value", ...}` option. Undefined variable references in the grammar without a
|
||||||
variable references in the grammar without a default will result in an error at
|
default will result in an error at construction time.
|
||||||
construction time.
|
|
||||||
|
|
||||||
Variables can also be set via the `set:"K=V"` tag. In this case, those variables will be available for that
|
Variables can also be set via the `set:"K=V"` tag. In this case, those variables will be available for that node and all
|
||||||
node and all children. This is useful for composition by allowing the same struct to be reused.
|
children. This is useful for composition by allowing the same struct to be reused.
|
||||||
|
|
||||||
When interpolating into flag or argument help strings, some extra variables
|
When interpolating into flag or argument help strings, some extra variables are defined from the value itself:
|
||||||
are defined from the value itself:
|
|
||||||
|
|
||||||
${default}
|
${default}
|
||||||
${enum}
|
${enum}
|
||||||
@@ -486,17 +496,32 @@ eg.
|
|||||||
|
|
||||||
```go
|
```go
|
||||||
type cli struct {
|
type cli struct {
|
||||||
Config string `type:"path" default:"${config_file}"`
|
Config string `type:"path" default:"${config_file}"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
kong.Parse(&cli,
|
kong.Parse(&cli,
|
||||||
kong.Vars{
|
kong.Vars{
|
||||||
"config_file": "~/.app.conf",
|
"config_file": "~/.app.conf",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Validation
|
||||||
|
|
||||||
|
Kong does validation on the structure of a command-line, but also supports
|
||||||
|
extensible validation. Any node in the tree may implement the following
|
||||||
|
interface:
|
||||||
|
|
||||||
|
```go
|
||||||
|
type Validatable interface {
|
||||||
|
Validate() error
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
If one of these nodes is in the active command-line it will be called during
|
||||||
|
normal validation.
|
||||||
|
|
||||||
## Modifying Kong's behaviour
|
## Modifying Kong's behaviour
|
||||||
|
|
||||||
Each Kong parser can be configured via functional options passed to `New(cli interface{}, options...Option)`.
|
Each Kong parser can be configured via functional options passed to `New(cli interface{}, options...Option)`.
|
||||||
@@ -513,7 +538,8 @@ As with all help in Kong, text will be wrapped to the terminal.
|
|||||||
|
|
||||||
### `Configuration(loader, paths...)` - load defaults from configuration files
|
### `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.
|
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.
|
||||||
|
|
||||||
eg.
|
eg.
|
||||||
|
|
||||||
@@ -521,11 +547,14 @@ eg.
|
|||||||
kong.Parse(&cli, kong.Configuration(kong.JSON, "/etc/myapp.json", "~/.myapp.json"))
|
kong.Parse(&cli, kong.Configuration(kong.JSON, "/etc/myapp.json", "~/.myapp.json"))
|
||||||
```
|
```
|
||||||
|
|
||||||
[See the tests](https://github.com/alecthomas/kong/blob/master/resolver_test.go#L103) for an example of how the JSON file is structured.
|
[See the tests](https://github.com/alecthomas/kong/blob/master/resolver_test.go#L103) for an example of how the JSON
|
||||||
|
file is structured.
|
||||||
|
|
||||||
### `Resolver(...)` - support for default values from external sources
|
### `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.
|
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).
|
Example resolvers can be found in [resolver.go](https://github.com/alecthomas/kong/blob/master/resolver.go).
|
||||||
|
|
||||||
@@ -544,7 +573,8 @@ type Mapper interface {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
All builtin Go types (as well as a bunch of useful stdlib types like `time.Time`) have mappers registered by default. Mappers for custom types can be added using `kong.??Mapper(...)` options. Mappers are applied to fields in four ways:
|
All builtin Go types (as well as a bunch of useful stdlib types like `time.Time`) have mappers registered by default.
|
||||||
|
Mappers for custom types can be added using `kong.??Mapper(...)` options. Mappers are applied to fields in four ways:
|
||||||
|
|
||||||
1. `NamedMapper(string, Mapper)` and using the tag key `type:"<name>"`.
|
1. `NamedMapper(string, Mapper)` and using the tag key `type:"<name>"`.
|
||||||
2. `KindMapper(reflect.Kind, Mapper)`.
|
2. `KindMapper(reflect.Kind, Mapper)`.
|
||||||
@@ -555,9 +585,12 @@ All builtin Go types (as well as a bunch of useful stdlib types like `time.Time`
|
|||||||
|
|
||||||
The default help output is usually sufficient, but if not there are two solutions.
|
The default help output is usually sufficient, but if not there are two solutions.
|
||||||
|
|
||||||
1. Use `ConfigureHelp(HelpOptions)` to configure how help is formatted (see [HelpOptions](https://godoc.org/github.com/alecthomas/kong#HelpOptions) for details).
|
1. Use `ConfigureHelp(HelpOptions)` to configure how help is formatted (
|
||||||
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.
|
see [HelpOptions](https://godoc.org/github.com/alecthomas/kong#HelpOptions) for details).
|
||||||
3. Use `HelpFormatter(HelpValueFormatter)` if you want to just customize the help text that is accompanied by flags and arguments.
|
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.
|
||||||
|
3. Use `HelpFormatter(HelpValueFormatter)` if you want to just customize the help text that is accompanied by flags and
|
||||||
|
arguments.
|
||||||
|
|
||||||
### `Bind(...)` - bind values for callback hooks and Run() methods
|
### `Bind(...)` - bind values for callback hooks and Run() methods
|
||||||
|
|
||||||
|
|||||||
+78
-4
@@ -44,6 +44,27 @@ func (p *Path) Node() *Node {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Visitable returns the Visitable for this path element.
|
||||||
|
func (p *Path) Visitable() Visitable {
|
||||||
|
switch {
|
||||||
|
case p.App != nil:
|
||||||
|
return p.App
|
||||||
|
|
||||||
|
case p.Argument != nil:
|
||||||
|
return p.Argument
|
||||||
|
|
||||||
|
case p.Command != nil:
|
||||||
|
return p.Command
|
||||||
|
|
||||||
|
case p.Flag != nil:
|
||||||
|
return p.Flag
|
||||||
|
|
||||||
|
case p.Positional != nil:
|
||||||
|
return p.Positional
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Context contains the current parse context.
|
// Context contains the current parse context.
|
||||||
type Context struct {
|
type Context struct {
|
||||||
*Kong
|
*Kong
|
||||||
@@ -136,10 +157,19 @@ func (c *Context) Empty() bool {
|
|||||||
// Validate the current context.
|
// Validate the current context.
|
||||||
func (c *Context) Validate() error { // nolint: gocyclo
|
func (c *Context) Validate() error { // nolint: gocyclo
|
||||||
err := Visit(c.Model, func(node Visitable, next Next) error {
|
err := Visit(c.Model, func(node Visitable, next Next) error {
|
||||||
if value, ok := node.(*Value); ok {
|
switch node := node.(type) {
|
||||||
_, ok := os.LookupEnv(value.Tag.Env)
|
case *Value:
|
||||||
if value.Enum != "" && (!value.Required || value.Default != "" || (value.Tag.Env != "" && ok)) {
|
_, ok := os.LookupEnv(node.Tag.Env)
|
||||||
if err := checkEnum(value, value.Target); err != nil {
|
if node.Enum != "" && (!node.Required || node.Default != "" || (node.Tag.Env != "" && ok)) {
|
||||||
|
if err := checkEnum(node, node.Target); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case *Flag:
|
||||||
|
_, ok := os.LookupEnv(node.Tag.Env)
|
||||||
|
if node.Enum != "" && (!node.Required || node.Default != "" || (node.Tag.Env != "" && ok)) {
|
||||||
|
if err := checkEnum(node.Value, node.Target); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -149,6 +179,35 @@ func (c *Context) Validate() error { // nolint: gocyclo
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
for _, el := range c.Path {
|
||||||
|
var (
|
||||||
|
value reflect.Value
|
||||||
|
desc string
|
||||||
|
)
|
||||||
|
switch node := el.Visitable().(type) {
|
||||||
|
case *Value:
|
||||||
|
value = node.Target
|
||||||
|
desc = node.ShortSummary()
|
||||||
|
|
||||||
|
case *Flag:
|
||||||
|
value = node.Target
|
||||||
|
desc = node.ShortSummary()
|
||||||
|
|
||||||
|
case *Application:
|
||||||
|
value = node.Target
|
||||||
|
desc = node.Name
|
||||||
|
|
||||||
|
case *Node:
|
||||||
|
value = node.Target
|
||||||
|
desc = node.Path()
|
||||||
|
}
|
||||||
|
if validate := isValidatable(value); validate != nil {
|
||||||
|
err := validate.Validate()
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, desc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
for _, resolver := range c.combineResolvers() {
|
for _, resolver := range c.combineResolvers() {
|
||||||
if err := resolver.Validate(c.Model); err != nil {
|
if err := resolver.Validate(c.Model); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -787,3 +846,18 @@ func findPotentialCandidates(needle string, haystack []string, format string, ar
|
|||||||
}
|
}
|
||||||
return fmt.Errorf("%s", prefix)
|
return fmt.Errorf("%s", prefix)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type validatable interface{ Validate() error }
|
||||||
|
|
||||||
|
func isValidatable(v reflect.Value) validatable {
|
||||||
|
if !v.IsValid() || (v.Kind() == reflect.Ptr || v.Kind() == reflect.Slice || v.Kind() == reflect.Map) && v.IsNil() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if validate, ok := v.Interface().(validatable); ok {
|
||||||
|
return validate
|
||||||
|
}
|
||||||
|
if v.CanAddr() {
|
||||||
|
return isValidatable(v.Addr())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/alecthomas/kong"
|
"github.com/alecthomas/kong"
|
||||||
@@ -887,3 +888,51 @@ func TestPlugins(t *testing.T) {
|
|||||||
require.Equal(t, "one", pluginOne.One)
|
require.Equal(t, "one", pluginOne.One)
|
||||||
require.Equal(t, "two", pluginTwo.Two)
|
require.Equal(t, "two", pluginTwo.Two)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type validateCmd struct{}
|
||||||
|
|
||||||
|
func (v *validateCmd) Validate() error { return errors.New("cmd error") }
|
||||||
|
|
||||||
|
type validateCli struct {
|
||||||
|
Cmd validateCmd `cmd:""`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *validateCli) Validate() error { return errors.New("app error") }
|
||||||
|
|
||||||
|
type validateFlag string
|
||||||
|
|
||||||
|
func (v *validateFlag) Validate() error { return errors.New("flag error") }
|
||||||
|
|
||||||
|
func TestValidateApp(t *testing.T) {
|
||||||
|
cli := validateCli{}
|
||||||
|
p := mustNew(t, &cli)
|
||||||
|
_, err := p.Parse([]string{})
|
||||||
|
require.EqualError(t, err, "test: app error")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateCmd(t *testing.T) {
|
||||||
|
cli := struct {
|
||||||
|
Cmd validateCmd `cmd:""`
|
||||||
|
}{}
|
||||||
|
p := mustNew(t, &cli)
|
||||||
|
_, err := p.Parse([]string{"cmd"})
|
||||||
|
require.EqualError(t, err, "cmd: cmd error")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateFlag(t *testing.T) {
|
||||||
|
cli := struct {
|
||||||
|
Flag validateFlag
|
||||||
|
}{}
|
||||||
|
p := mustNew(t, &cli)
|
||||||
|
_, err := p.Parse([]string{"--flag=one"})
|
||||||
|
require.EqualError(t, err, "--flag: flag error")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateArg(t *testing.T) {
|
||||||
|
cli := struct {
|
||||||
|
Arg validateFlag `arg:""`
|
||||||
|
}{}
|
||||||
|
p := mustNew(t, &cli)
|
||||||
|
_, err := p.Parse([]string{"one"})
|
||||||
|
require.EqualError(t, err, "<arg>: flag error")
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user