diff --git a/README.md b/README.md
index 5033807..b10c39c 100644
--- a/README.md
+++ b/README.md
@@ -2,6 +2,7 @@

# 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)
@@ -24,6 +25,7 @@
- [Supported tags](#supported-tags)
- [Plugins](#plugins)
- [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)
@@ -39,7 +41,8 @@
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:
@@ -54,32 +57,33 @@ package main
import "github.com/alecthomas/kong"
var CLI struct {
- Rm struct {
- Force bool `help:"Force removal."`
- Recursive bool `help:"Recursively remove files."`
+ Rm struct {
+ Force bool `help:"Force removal."`
+ Recursive bool `help:"Recursively remove files."`
- Paths []string `arg name:"path" help:"Paths to remove." type:"path"`
- } `cmd help:"Remove files."`
+ Paths []string `arg name:"path" help:"Paths to remove." type:"path"`
+ } `cmd help:"Remove files."`
- Ls struct {
- Paths []string `arg optional name:"path" help:"Paths to list." type:"path"`
- } `cmd help:"List paths."`
+ Ls struct {
+ Paths []string `arg optional name:"path" help:"Paths to list." type:"path"`
+ } `cmd help:"List paths."`
}
func main() {
- ctx := kong.Parse(&CLI)
- switch ctx.Command() {
- case "rm ":
- case "ls":
- default:
- panic(ctx.Command())
- }
+ ctx := kong.Parse(&CLI)
+ switch ctx.Command() {
+ case "rm ":
+ case "ls":
+ default:
+ panic(ctx.Command())
+ }
}
```
## 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.
@@ -117,9 +121,8 @@ eg.
-f, --force Force removal.
-r, --recursive Recursively remove files.
-For flags with associated environment variables, the variable `${env}` can be
-interpolated into the help string. In the absence of this variable in the help,
-
+For flags with associated environment variables, the variable `${env}` can be interpolated into the help string. In the
+absence of this variable in the help,
## Command handling
@@ -127,7 +130,9 @@ 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:
+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).
@@ -139,30 +144,31 @@ package main
import "github.com/alecthomas/kong"
var CLI struct {
- Rm struct {
- Force bool `help:"Force removal."`
- Recursive bool `help:"Recursively remove files."`
+ Rm struct {
+ Force bool `help:"Force removal."`
+ Recursive bool `help:"Recursively remove files."`
- Paths []string `arg name:"path" help:"Paths to remove." type:"path"`
- } `cmd help:"Remove files."`
+ Paths []string `arg name:"path" help:"Paths to remove." type:"path"`
+ } `cmd help:"Remove files."`
- Ls struct {
- Paths []string `arg optional name:"path" help:"Paths to list." type:"path"`
- } `cmd help:"List paths."`
+ Ls struct {
+ Paths []string `arg optional name:"path" help:"Paths to list." type:"path"`
+ } `cmd help:"List paths."`
}
func main() {
- ctx := kong.Parse(&CLI)
- switch ctx.Command() {
- case "rm ":
- case "ls":
- default:
- panic(ctx.Command())
- }
+ ctx := kong.Parse(&CLI)
+ switch ctx.Command() {
+ case "rm ":
+ case "ls":
+ default:
+ 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
@@ -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`.
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
-encountered command node with a `Run(...) error` will be called in reverse order. This allows
-sub-trees to be re-used fairly conveniently.
+Once a command node is selected by Kong it will search from that node back to the root. Each encountered command node
+with a `Run(...) error` will be called in reverse order. This allows sub-trees to be re-used fairly conveniently.
-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
+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).
+There's a full example emulating part of the Docker
+CLI [here](https://github.com/alecthomas/kong/tree/master/_examples/docker).
eg.
```go
type Context struct {
- Debug bool
+Debug bool
}
type RmCmd struct {
- Force bool `help:"Force removal."`
- Recursive bool `help:"Recursively remove files."`
+Force bool `help:"Force removal."`
+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 {
- fmt.Println("rm", r.Paths)
- return nil
+fmt.Println("rm", r.Paths)
+return nil
}
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 {
- fmt.Println("ls", l.Paths)
- return nil
+fmt.Println("ls", l.Paths)
+return nil
}
var cli struct {
- Debug bool `help:"Enable debug mode."`
+Debug bool `help:"Enable debug mode."`
- Rm RmCmd `cmd help:"Remove files."`
- Ls LsCmd `cmd help:"List paths."`
+Rm RmCmd `cmd help:"Remove files."`
+Ls LsCmd `cmd help:"List paths."`
}
func main() {
- ctx := kong.Parse(&cli)
- // Call the Run() method of the selected parsed command.
- err := ctx.Run(&Context{Debug: cli.Debug})
- ctx.FatalIfErrorf(err)
+ctx := kong.Parse(&cli)
+// Call the Run() method of the selected parsed command.
+err := ctx.Run(&Context{Debug: cli.Debug})
+ctx.FatalIfErrorf(err)
}
```
## 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.
-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.
@@ -244,33 +252,34 @@ eg.
type debugFlag bool
func (d debugFlag) BeforeApply(logger *log.Logger) error {
- logger.SetOutput(os.Stdout)
- return nil
+logger.SetOutput(os.Stdout)
+return nil
}
var cli struct {
- Debug debugFlag `help:"Enable debug logging."`
+Debug debugFlag `help:"Enable debug logging."`
}
func main() {
- // Debug logger going to discard.
- logger := log.New(ioutil.Discard, "", log.LstdFlags)
+// Debug logger going to discard.
+logger := log.New(ioutil.Discard, "", log.LstdFlags)
- ctx := kong.Parse(&cli, kong.Bind(logger))
+ctx := kong.Parse(&cli, kong.Bind(logger))
- // ...
+// ...
}
```
## 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.
```go
type CLI struct {
- Flag string
+Flag string
}
```
@@ -282,12 +291,12 @@ eg. The following struct represents the CLI structure `command [--flag="str"] su
```go
type CLI struct {
- Command struct {
- Flag string
+Command struct {
+Flag string
- SubCommand struct {
- } `cmd`
- } `cmd`
+SubCommand struct {
+} `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.
-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 to
@@ -305,16 +316,16 @@ Can be represented with the following:
```go
var CLI struct {
- Rename struct {
- Name struct {
- Name string `arg` // <-- NOTE: identical name to enclosing struct field.
- To struct {
- Name struct {
- Name string `arg`
- } `arg`
- } `cmd`
- } `arg`
- } `cmd`
+Rename struct {
+Name struct {
+Name string `arg` // <-- NOTE: identical name to enclosing struct field.
+To struct {
+Name struct {
+Name string `arg`
+} `arg`
+} `cmd`
+} `arg`
+} `cmd`
}
```
@@ -322,13 +333,16 @@ This looks a little verbose in this contrived example, but typically this will n
## 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.
## 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.
+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.
To represent the following command-line:
@@ -338,15 +352,16 @@ You would use the following:
```go
var CLI struct {
- Ls struct {
- Files []string `arg type:"existingfile"`
- } `cmd`
+Ls struct {
+Files []string `arg type:"existingfile"`
+} `cmd`
}
```
## 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:
@@ -356,21 +371,21 @@ You would use the following:
```go
var CLI struct {
- Config struct {
- Set struct {
- Config map[string]float64 `arg type:"file:"`
- } `cmd`
- } `cmd`
+Config struct {
+Set struct {
+Config map[string]float64 `arg type:"file:"`
+} `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
-Kong includes a number of builtin custom type mappers. These can be used by
-specifying the tag `type:""`. They are registered with the option
-function `NamedMapper(name, mapper)`.
+Kong includes a number of builtin custom type mappers. These can be used by specifying the tag `type:""`. They are
+registered with the option function `NamedMapper(name, mapper)`.
| Name | Description
|-------------------|---------------------------------------------------
@@ -379,19 +394,16 @@ function `NamedMapper(name, mapper)`.
| `existingdir` | An existing directory. ~ expansion is applied.
| `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 has the format
+Slices and maps treat type tags specially. For slices, the `type:""` tag specifies the element type. For maps, the tag
+has the format
`tag:"[]:[]"` where either may be omitted.
## Supported field types
-
## Custom decoders (mappers)
-
-Any field implementing `encoding.TextUnmarshaler` or `json.Unmarshaler` will use those interfaces
-for decoding values. Kong also includes builtin support for many common Go types:
+Any field implementing `encoding.TextUnmarshaler` or `json.Unmarshaler` will use those interfaces for decoding values.
+Kong also includes builtin support for many common Go types:
| Type | Description
|---------------------|--------------------------------------------
@@ -441,18 +453,19 @@ Tag | Description
## 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
var pluginOne struct {
- PluginOneFlag string
+PluginOneFlag string
}
var pluginTwo struct {
- PluginTwoFlag string
+PluginTwoFlag string
}
var cli struct {
- BaseFlag string
- kong.Plugins
+BaseFlag string
+kong.Plugins
}
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
-Kong supports limited variable interpolation into help strings, enum lists and
-default values.
+Kong supports limited variable interpolation into help strings, enum lists and default values.
Variables are in the form:
${}
${=}
-Variables are set with the `Vars{"key": "value", ...}` option. Undefined
-variable references in the grammar without a default will result in an error at
-construction time.
+Variables are set with the `Vars{"key": "value", ...}` option. Undefined variable references in the grammar without a
+default will result in an error at construction time.
-Variables can also be set via the `set:"K=V"` tag. In this case, those variables will be available for that
-node and all children. This is useful for composition by allowing the same struct to be reused.
+Variables can also be set via the `set:"K=V"` tag. In this case, those variables will be available for that node and all
+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
-are defined from the value itself:
+When interpolating into flag or argument help strings, some extra variables are defined from the value itself:
${default}
${enum}
@@ -486,17 +496,32 @@ eg.
```go
type cli struct {
- Config string `type:"path" default:"${config_file}"`
+Config string `type:"path" default:"${config_file}"`
}
func main() {
- kong.Parse(&cli,
- kong.Vars{
- "config_file": "~/.app.conf",
- })
+kong.Parse(&cli,
+kong.Vars{
+"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
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
-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.
@@ -521,11 +547,14 @@ eg.
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
-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).
@@ -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:""`.
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.
-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.
-3. Use `HelpFormatter(HelpValueFormatter)` if you want to just customize the help text that is accompanied by flags and arguments.
+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.
+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
diff --git a/context.go b/context.go
index 244c672..f07f537 100644
--- a/context.go
+++ b/context.go
@@ -44,6 +44,27 @@ func (p *Path) Node() *Node {
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.
type Context struct {
*Kong
@@ -136,10 +157,19 @@ func (c *Context) Empty() bool {
// Validate the current context.
func (c *Context) Validate() error { // nolint: gocyclo
err := Visit(c.Model, func(node Visitable, next Next) error {
- if value, ok := node.(*Value); ok {
- _, ok := os.LookupEnv(value.Tag.Env)
- if value.Enum != "" && (!value.Required || value.Default != "" || (value.Tag.Env != "" && ok)) {
- if err := checkEnum(value, value.Target); err != nil {
+ switch node := node.(type) {
+ case *Value:
+ _, ok := os.LookupEnv(node.Tag.Env)
+ 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
}
}
@@ -149,6 +179,35 @@ func (c *Context) Validate() error { // nolint: gocyclo
if err != nil {
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() {
if err := resolver.Validate(c.Model); err != nil {
return err
@@ -787,3 +846,18 @@ func findPotentialCandidates(needle string, haystack []string, format string, ar
}
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
+}
diff --git a/kong_test.go b/kong_test.go
index 8105641..3703f4d 100644
--- a/kong_test.go
+++ b/kong_test.go
@@ -6,6 +6,7 @@ import (
"strings"
"testing"
+ "github.com/pkg/errors"
"github.com/stretchr/testify/require"
"github.com/alecthomas/kong"
@@ -887,3 +888,51 @@ func TestPlugins(t *testing.T) {
require.Equal(t, "one", pluginOne.One)
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, ": flag error")
+}