Display usage information on error.

This commit is contained in:
Alec Thomas
2018-06-21 10:08:01 +10:00
parent d9c0dd25b1
commit cdcdf49f67
7 changed files with 53 additions and 16 deletions
+2 -2
View File
@@ -19,7 +19,7 @@
1. [`Configuration(loader, paths...)` - load defaults from configuration files](#configurationloader-paths---load-defaults-from-configuration-files)
1. [`Resolver(...)` - support for default values from external sources](#resolver---support-for-default-values-from-external-sources)
1. [`*Mapper(...)` - customising how the command-line is mapped to Go values](#mapper---customising-how-the-command-line-is-mapped-to-go-values)
1. [`Help(HelpFunc)` - customising help](#helphelpfunc---customising-help)
1. [`HelpOptions(...HelpOption)` and `Help(HelpFunc)` - customising help](#helpoptionshelpoption-and-helphelpfunc---customising-help)
1. [`Hook(&field, HookFunc)` - callback hooks to execute when the command-line is parsed](#hookfield-hookfunc---callback-hooks-to-execute-when-the-command-line-is-parsed)
1. [Other options](#other-options)
@@ -279,7 +279,7 @@ 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.
### `Help(HelpFunc)` - customising help
### `HelpOptions(...HelpOption)` and `Help(HelpFunc)` - customising help
The default help output is usually sufficient, but if it's not, there are two solutions.
+1 -1
View File
@@ -14,7 +14,7 @@ var cli struct {
Force bool `help:"Force removal." short:"f"`
Recursive bool `help:"Recursively remove files." short:"r"`
Paths []string `arg:"" help:"Paths to remove." type:"path"`
Paths []string `arg:"" help:"Paths to remove." type:"path" name:"path"`
} `cmd:"" help:"Remove files."`
Ls struct {
+7 -4
View File
@@ -43,11 +43,11 @@ func (p *Path) Node() *Node {
// Context contains the current parse context.
type Context struct {
App *Kong
Path []*Path // A trace through parsed nodes.
Error error // Error that occurred during trace, if any.
Path []*Path // A trace through parsed nodes.
Args []string // Original command-line arguments.
Error error // Error that occurred during trace, if any.
values map[*Value]reflect.Value // Temporary values during tracing.
args []string
scan *Scanner
}
@@ -86,7 +86,7 @@ func (c *Context) Selected() *Node {
func Trace(k *Kong, args []string) (*Context, error) {
c := &Context{
App: k,
args: args,
Args: args,
Path: []*Path{
{App: k.Model, Flags: k.Model.Flags},
},
@@ -461,6 +461,9 @@ func checkMissingChildren(node *Node) error {
if len(missing) == 1 {
return fmt.Errorf("%q should be followed by %s", node.Path(), missing[0])
}
if len(missing) > 5 {
missing = append(missing[:5], "...")
}
return fmt.Errorf("%q should be followed by one of %s", node.Path(), strings.Join(missing, ", "))
}
+12
View File
@@ -0,0 +1,12 @@
package kong
// ParseError is the error type returned by Kong.Parse().
//
// It contains the parse Context that triggered the error.
type ParseError struct {
error
Context *Context
}
// Cause returns the original cause of the error.
func (p *ParseError) Cause() error { return p.error }
+18 -9
View File
@@ -38,12 +38,13 @@ type Kong struct {
Stdout io.Writer
Stderr io.Writer
before map[reflect.Value]HookFunc
resolvers []ResolverFunc
registry *Registry
noDefaultHelp bool
help func(*Context) error
helpOptions []HelpOption
before map[reflect.Value]HookFunc
resolvers []ResolverFunc
registry *Registry
noDefaultHelp bool
noUsageOnError bool
help func(*Context) error
helpOptions []HelpOption
// Set temporarily by Options. These are applied after build().
postBuildOptions []Option
@@ -133,6 +134,9 @@ func (k *Kong) Help(args []string) error {
//
// The returned "command" is a space separated path to the final selected command, if any. Commands appear as
// the command name while positional arguments are the argument name surrounded by "<argument>".
//
// Will return a ParseError if a *semantically* invalid command-line is encountered (as opposed to a syntactically
// invalid one, which will report a normal error).
func (k *Kong) Parse(args []string) (command string, err error) {
defer catch(&err)
ctx, err := Trace(k, args)
@@ -140,13 +144,13 @@ func (k *Kong) Parse(args []string) (command string, err error) {
return "", err
}
if err = k.applyHooks(ctx); err != nil {
return "", err
return "", &ParseError{error: err, Context: ctx}
}
if ctx.Error != nil {
return "", ctx.Error
return "", &ParseError{error: ctx.Error, Context: ctx}
}
if err = ctx.Validate(); err != nil {
return "", err
return "", &ParseError{error: err, Context: ctx}
}
return ctx.Apply()
}
@@ -210,6 +214,11 @@ func (k *Kong) FatalIfErrorf(err error, args ...interface{}) {
msg = fmt.Sprintf(args[0].(string), args[1:]...) + ": " + err.Error()
}
k.Errorf("%s", msg)
// Maybe display usage information.
if err, ok := err.(*ParseError); ok && !k.noUsageOnError {
fmt.Fprintln(k.Stdout)
_ = k.help(err.Context)
}
k.Exit(1)
}
+8
View File
@@ -132,6 +132,14 @@ func HelpOptions(options ...HelpOption) Option {
}
}
// NoUsageOnError configures Kong to NOT display context-sensitive usage if FatalIfErrorf is called with an error.
func NoUsageOnError() Option {
return func(k *Kong) error {
k.noUsageOnError = true
return nil
}
}
// ClearResolvers clears all existing resolvers.
func ClearResolvers() Option {
return func(k *Kong) error {
+5
View File
@@ -23,6 +23,7 @@ type Tag struct {
Short rune
Hidden bool
Sep rune
Enum map[string]bool
// Storage for all tag keys for arbitrary lookups.
items map[string]string
@@ -112,6 +113,7 @@ func parseTag(fv reflect.Value, ft reflect.StructField) *Tag {
s, chars := getTagInfo(ft)
t := &Tag{
items: parseTagItems(s, chars),
Enum: map[string]bool{},
}
t.Cmd = t.Has("cmd")
t.Arg = t.Has("arg")
@@ -141,6 +143,9 @@ func parseTag(fv reflect.Value, ft reflect.StructField) *Tag {
if t.PlaceHolder == "" {
t.PlaceHolder = strings.ToUpper(dashedString(fv.Type().Name()))
}
for _, part := range strings.Split(t.Get("enum"), ",") {
t.Enum[part] = true
}
return t
}