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"?
This commit is contained in:
Alec Thomas
2019-06-21 10:23:09 +10:00
parent 1076f5ee1f
commit 95de7d2f0d
4 changed files with 28 additions and 14 deletions
+5
View File
@@ -5,6 +5,8 @@ import (
"reflect" "reflect"
"strconv" "strconv"
"strings" "strings"
"github.com/pkg/errors"
) )
// Path records the nodes and parsed values from the current command-line. // 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() c.scan.Pop()
err := flag.Parse(c.scan, c.getValue(flag.Value)) err := flag.Parse(c.scan, c.getValue(flag.Value))
if err != nil { 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 return err
} }
c.Path = append(c.Path, &Path{Flag: flag}) c.Path = append(c.Path, &Path{Flag: flag})
+1 -1
View File
@@ -715,7 +715,7 @@ func TestNumericParamErrors(t *testing.T) {
} }
parser := mustNew(t, &cli) parser := mustNew(t, &cli)
_, err := parser.Parse([]string{"--name", "-10"}) _, 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) { func TestDefaultValueIsHyphen(t *testing.T) {
+4 -2
View File
@@ -7,6 +7,8 @@ import (
"reflect" "reflect"
"strconv" "strconv"
"strings" "strings"
"github.com/pkg/errors"
) )
// A Visitable component in the model. // 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 { if rerr := recover(); rerr != nil {
switch rerr := rerr.(type) { switch rerr := rerr.(type) {
case Error: case Error:
err = fmt.Errorf("%s: %s", v.ShortSummary(), rerr) err = errors.Wrap(rerr, v.ShortSummary())
default: default:
panic(fmt.Sprintf("mapper %T failed to apply to %s: %s", v.Mapper, v.Summary(), rerr)) 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) err = v.Mapper.Decode(&DecodeContext{Value: v, Scan: scan}, target)
if err != nil { if err != nil {
return fmt.Errorf("%s: %s", v.ShortSummary(), err) return errors.Wrap(err, v.ShortSummary())
} }
v.Set = true v.Set = true
return nil return nil
+18 -11
View File
@@ -3,8 +3,6 @@ package kong
import ( import (
"fmt" "fmt"
"strings" "strings"
"github.com/pkg/errors"
) )
// TokenType is the type of a token. // TokenType is the type of a token.
@@ -14,11 +12,11 @@ type TokenType int
const ( const (
UntypedToken TokenType = iota UntypedToken TokenType = iota
EOLToken EOLToken
FlagToken // --<flag> FlagToken // --<flag>
FlagValueToken // =<value> FlagValueToken // =<value>
ShortFlagToken // -<short>[<tail] ShortFlagToken // -<short>[<tail]
ShortFlagTailToken // <tail> ShortFlagTailToken // <tail>
PositionalArgumentToken // <arg> PositionalArgumentToken // <arg>
) )
func (t TokenType) String() string { func (t TokenType) String() string {
@@ -142,13 +140,22 @@ func (s *Scanner) Pop() Token {
return arg 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. // 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 <context> value but got <type>" // "context" is used to assist the user if the value can not be popped, eg. "expected <context> value but got <type>"
func (s *Scanner) PopValue(context string) (Token, error) { func (s *Scanner) PopValue(context string) (Token, error) {
t := s.Pop() t := s.Pop()
if !t.IsValue() { 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 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 <context> value but got <type>" // "context" is used to assist the user if the value can not be popped, eg. "expected <context> value but got <type>"
func (s *Scanner) PopValueInto(context string, target interface{}) error { func (s *Scanner) PopValueInto(context string, target interface{}) error {
t := s.Pop() t, err := s.PopValue(context)
if !t.IsValue() { if err != nil {
return errors.Errorf("expected %s value but got %q (%s)", context, t, t.InferredType()) return err
} }
return jsonTranscode(t.Value, target) return jsonTranscode(t.Value, target)
} }