Kong tag PR fixes (#12)

* Removed bubbling errors in favour of panic
* Added a test for Parse with a bad build and an arg.
* Removed is() function in favour of a switch with strings
* Collect key and value in parseCSV, reducing complexity.
* Fix in global to not use parser instance on error.
This commit is contained in:
Gerald Kaszuba
2018-05-22 23:18:08 +10:00
committed by Alec Thomas
parent 31e1ffaa3b
commit ecd369e622
5 changed files with 80 additions and 59 deletions
+1 -5
View File
@@ -61,11 +61,7 @@ func buildNode(v reflect.Value, seenFlags map[string]bool, cmd bool) *Node {
name = strings.ToLower(dashedString(ft.Name)) name = strings.ToLower(dashedString(ft.Name))
} }
tag, err := parseTag(fv, ft.Tag.Get("kong")) tag := parseTag(fv, ft.Tag.Get("kong"))
if err != nil {
fail("%s", err)
}
decoder := DecoderForField(tag.Type, ft) decoder := DecoderForField(tag.Type, ft)
if !cmd { if !cmd {
+6 -2
View File
@@ -1,11 +1,15 @@
package kong package kong
import "os" import (
"os"
)
// Parse constructs a new parser and parses the default command-line. // Parse constructs a new parser and parses the default command-line.
func Parse(cli interface{}, options ...Option) string { func Parse(cli interface{}, options ...Option) string {
parser, err := New(cli, options...) parser, err := New(cli, options...)
parser.FatalIfErrorf(err) if err != nil {
panic(err)
}
cmd, err := parser.Parse(os.Args[1:]) cmd, err := parser.Parse(os.Args[1:])
parser.FatalIfErrorf(err) parser.FatalIfErrorf(err)
return cmd return cmd
+31
View File
@@ -0,0 +1,31 @@
package kong
import (
"os"
"testing"
"github.com/stretchr/testify/require"
)
func TestParseHandlingBadBuild(t *testing.T) {
var cli struct {
Enabled bool `kong:"unknown"`
}
args := os.Args
defer func() {
os.Args = args
}()
os.Args = []string{os.Args[0], "hi"}
defer func() {
if r := recover(); r != nil {
require.Equal(t, Error{msg: "unknown is an unknown kong key"}, r)
}
}()
Parse(&cli, ExitFunction(func(_ int) { panic("exiting") }))
require.Fail(t, "we were expecting a panic")
}
+2 -5
View File
@@ -51,6 +51,7 @@ func New(ast interface{}, options ...Option) (*Kong, error) {
for _, option := range options { for _, option := range options {
option(k) option(k)
} }
return k, nil return k, nil
} }
@@ -75,11 +76,7 @@ func (k *Kong) Parse(args []string) (command string, err error) {
} }
func (k *Kong) Errorf(format string, args ...interface{}) { func (k *Kong) Errorf(format string, args ...interface{}) {
if k.Model != nil { fmt.Fprintf(os.Stderr, k.Model.Name+": "+format, args...)
fmt.Fprintf(os.Stderr, k.Model.Name+": "+format, args...)
} else {
fmt.Fprintf(os.Stderr, format, args...)
}
} }
func (k *Kong) FatalIfErrorf(err error, args ...interface{}) { func (k *Kong) FatalIfErrorf(err error, args ...interface{}) {
+40 -47
View File
@@ -1,7 +1,6 @@
package kong package kong
import ( import (
"fmt"
"reflect" "reflect"
"strings" "strings"
"unicode/utf8" "unicode/utf8"
@@ -21,19 +20,21 @@ type Tag struct {
Short rune Short rune
} }
func parseCSV(s string) ([]string, error) { func parseCSV(s string) map[string]string {
num := 0 d := map[string]string{}
parts := []string{}
current := []rune{} key := []rune{}
value := []rune{}
quotes := false
inKey := true
add := func() { add := func() {
parts = append(parts, string(current)) d[string(key)] = string(value)
current = []rune{} key = []rune{}
num++ value = []rune{}
inKey = true
} }
quotes := false
runes := []rune(s) runes := []rune(s)
for idx := 0; idx < len(runes); idx++ { for idx := 0; idx < len(runes); idx++ {
r := runes[idx] r := runes[idx]
@@ -48,6 +49,10 @@ func parseCSV(s string) ([]string, error) {
add() add()
continue continue
} }
if r == '=' && inKey {
inKey = false
continue
}
if r == '\\' { if r == '\\' {
if next == '\'' { if next == '\'' {
idx++ idx++
@@ -59,72 +64,60 @@ func parseCSV(s string) ([]string, error) {
if next == ',' || eof { if next == ',' || eof {
continue continue
} }
return parts, fmt.Errorf("%v has an unexpected char at pos %v", s, idx) fail("%v has an unexpected char at pos %v", s, idx)
} else { } else {
quotes = true quotes = true
continue continue
} }
} }
current = append(current, r) if inKey {
key = append(key, r)
} else {
value = append(value, r)
}
} }
if quotes { if quotes {
return parts, fmt.Errorf("%v is not quoted properly", s) fail("%v is not quoted properly", s)
} }
add() add()
return parts, nil return d
} }
func parseTag(fv reflect.Value, s string) (*Tag, error) { func parseTag(fv reflect.Value, s string) *Tag {
t := &Tag{} t := &Tag{}
if s == "" { if s == "" {
return t, nil return t
} }
parts, err := parseCSV(s) for k, v := range parseCSV(s) {
if err != nil { switch k {
return t, err case "cmd":
}
for _, part := range parts {
is := func(m string) bool { return part == m }
value := func(m string) (string, bool) {
split := strings.SplitN(part, "=", 2)
if split[0] != m {
return "", false
}
if len(split) == 1 {
return "", true
}
return split[1], true
}
if is("cmd") {
t.Cmd = true t.Cmd = true
} else if is("arg") { case "arg":
t.Arg = true t.Arg = true
} else if is("required") { case "required":
t.Required = true t.Required = true
} else if is("optional") { case "optional":
t.Optional = true t.Optional = true
} else if v, ok := value("default"); ok { case "default":
t.Default = v t.Default = v
} else if v, ok := value("help"); ok { case "help":
t.Help = v t.Help = v
} else if v, ok := value("type"); ok { case "type":
t.Type = v t.Type = v
} else if v, ok := value("placeholder"); ok { case "placeholder":
t.Placeholder = v t.Placeholder = v
} else if v, ok := value("env"); ok { case "env":
t.Env = v t.Env = v
} else if v, ok := value("rune"); ok { case "rune":
t.Short, _ = utf8.DecodeRuneInString(v) t.Short, _ = utf8.DecodeRuneInString(v)
if t.Short == utf8.RuneError { if t.Short == utf8.RuneError {
t.Short = 0 t.Short = 0
} }
} else { default:
return t, fmt.Errorf("%v is an unknown kong key", part) fail("%v is an unknown kong key", k)
} }
} }
@@ -132,5 +125,5 @@ func parseTag(fv reflect.Value, s string) (*Tag, error) {
t.Placeholder = strings.ToUpper(dashedString(fv.Type().Name())) t.Placeholder = strings.ToUpper(dashedString(fv.Type().Name()))
} }
return t, nil return t
} }