From 95de7d2f0dd4cc5b9a74133e7117507bb3724a6e Mon Sep 17 00:00:00 2001 From: Alec Thomas Date: Fri, 21 Jun 2019 10:23:09 +1000 Subject: [PATCH] Produce a more useful error when flag-like values are used for flag values. eg. myapp: error: --log-level: expected string value but got "--foo" (long flag); perhaps try --log-level="--foo"? --- context.go | 5 +++++ kong_test.go | 2 +- model.go | 6 ++++-- scanner.go | 29 ++++++++++++++++++----------- 4 files changed, 28 insertions(+), 14 deletions(-) diff --git a/context.go b/context.go index d4761dd..4861f00 100644 --- a/context.go +++ b/context.go @@ -5,6 +5,8 @@ import ( "reflect" "strconv" "strings" + + "github.com/pkg/errors" ) // Path records the nodes and parsed values from the current command-line. @@ -504,6 +506,9 @@ func (c *Context) parseFlag(flags []*Flag, match string) (err error) { c.scan.Pop() err := flag.Parse(c.scan, c.getValue(flag.Value)) if err != nil { + if e, ok := errors.Cause(err).(*expectedError); ok && e.token.InferredType().IsAny(FlagToken, ShortFlagToken) { + return errors.Errorf("%s; perhaps try %s=%q?", err, flag.ShortSummary(), e.token) + } return err } c.Path = append(c.Path, &Path{Flag: flag}) diff --git a/kong_test.go b/kong_test.go index a94a3c1..e6c3858 100644 --- a/kong_test.go +++ b/kong_test.go @@ -715,7 +715,7 @@ func TestNumericParamErrors(t *testing.T) { } parser := mustNew(t, &cli) _, err := parser.Parse([]string{"--name", "-10"}) - require.EqualError(t, err, `--name: expected string value but got "-10" (short flag)`) + require.EqualError(t, err, `--name: expected string value but got "-10" (short flag); perhaps try --name="-10"?`) } func TestDefaultValueIsHyphen(t *testing.T) { diff --git a/model.go b/model.go index 5e72723..0fda877 100644 --- a/model.go +++ b/model.go @@ -7,6 +7,8 @@ import ( "reflect" "strconv" "strings" + + "github.com/pkg/errors" ) // A Visitable component in the model. @@ -289,7 +291,7 @@ func (v *Value) Parse(scan *Scanner, target reflect.Value) (err error) { if rerr := recover(); rerr != nil { switch rerr := rerr.(type) { case Error: - err = fmt.Errorf("%s: %s", v.ShortSummary(), rerr) + err = errors.Wrap(rerr, v.ShortSummary()) default: panic(fmt.Sprintf("mapper %T failed to apply to %s: %s", v.Mapper, v.Summary(), rerr)) } @@ -297,7 +299,7 @@ func (v *Value) Parse(scan *Scanner, target reflect.Value) (err error) { }() err = v.Mapper.Decode(&DecodeContext{Value: v, Scan: scan}, target) if err != nil { - return fmt.Errorf("%s: %s", v.ShortSummary(), err) + return errors.Wrap(err, v.ShortSummary()) } v.Set = true return nil diff --git a/scanner.go b/scanner.go index c8c3636..d913d07 100644 --- a/scanner.go +++ b/scanner.go @@ -3,8 +3,6 @@ package kong import ( "fmt" "strings" - - "github.com/pkg/errors" ) // TokenType is the type of a token. @@ -14,11 +12,11 @@ type TokenType int const ( UntypedToken TokenType = iota EOLToken - FlagToken // -- - FlagValueToken // = - ShortFlagToken // -[ - PositionalArgumentToken // + FlagToken // -- + FlagValueToken // = + ShortFlagToken // -[ + PositionalArgumentToken // ) func (t TokenType) String() string { @@ -142,13 +140,22 @@ func (s *Scanner) Pop() Token { return arg } +type expectedError struct { + context string + token Token +} + +func (e *expectedError) Error() string { + return fmt.Sprintf("expected %s value but got %q (%s)", e.context, e.token, e.token.InferredType()) +} + // PopValue pops a value token, or returns an error. // // "context" is used to assist the user if the value can not be popped, eg. "expected value but got " func (s *Scanner) PopValue(context string) (Token, error) { t := s.Pop() if !t.IsValue() { - return t, errors.Errorf("expected %s value but got %q (%s)", context, t, t.InferredType()) + return t, &expectedError{context, t} } return t, nil } @@ -157,9 +164,9 @@ func (s *Scanner) PopValue(context string) (Token, error) { // // "context" is used to assist the user if the value can not be popped, eg. "expected value but got " func (s *Scanner) PopValueInto(context string, target interface{}) error { - t := s.Pop() - if !t.IsValue() { - return errors.Errorf("expected %s value but got %q (%s)", context, t, t.InferredType()) + t, err := s.PopValue(context) + if err != nil { + return err } return jsonTranscode(t.Value, target) }