Display usage information on error.
This commit is contained in:
@@ -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.
|
||||
|
||||
|
||||
@@ -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
@@ -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, ", "))
|
||||
}
|
||||
|
||||
|
||||
@@ -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 }
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user