From 31fe51f9d8803cbcba0bd5ea862d0168251e580d Mon Sep 17 00:00:00 2001 From: Alec Thomas Date: Thu, 17 May 2018 20:16:12 +1000 Subject: [PATCH] Support cumulative positional arguments. --- build.go | 1 + decoders.go | 25 ++++++++++++++++++------- kong_test.go | 14 +++++++++++++- model.go | 1 + scanner.go | 16 ++++++++++++++++ 5 files changed, 49 insertions(+), 8 deletions(-) diff --git a/build.go b/build.go index 63a301f..ef6798f 100644 --- a/build.go +++ b/build.go @@ -100,6 +100,7 @@ func buildNode(v reflect.Value) *Node { if arg { node.Positional = append(node.Positional, &value) } else { + value.Flag = true node.Flags = append(node.Flags, &Flag{ Value: value, Short: short, diff --git a/decoders.go b/decoders.go index d1b2c10..4cbb1f1 100644 --- a/decoders.go +++ b/decoders.go @@ -181,18 +181,20 @@ func timeDecoder(ctx *DecoderContext, scan *Scanner, target reflect.Value) error } func intDecoder(ctx *DecoderContext, scan *Scanner, target reflect.Value) error { - n, err := strconv.ParseInt(scan.PopValue("int"), 10, 64) + value := scan.PopValue("int") + n, err := strconv.ParseInt(value, 10, 64) if err != nil { - return err + return fmt.Errorf("invalid int %q", value) } target.SetInt(n) return nil } func uintDecoder(ctx *DecoderContext, scan *Scanner, target reflect.Value) error { - n, err := strconv.ParseUint(scan.PopValue("uint"), 10, 64) + value := scan.PopValue("uint") + n, err := strconv.ParseUint(value, 10, 64) if err != nil { - return err + return fmt.Errorf("invalid uint %q", value) } target.SetUint(n) return nil @@ -200,9 +202,10 @@ func uintDecoder(ctx *DecoderContext, scan *Scanner, target reflect.Value) error func floatDecoder(bits int) DecoderFunc { return func(ctx *DecoderContext, scan *Scanner, target reflect.Value) error { - n, err := strconv.ParseFloat(scan.PopValue("float"), bits) + value := scan.PopValue("float") + n, err := strconv.ParseFloat(value, bits) if err != nil { - return err + return fmt.Errorf("invalid float %q", value) } target.SetFloat(n) return nil @@ -215,7 +218,15 @@ func sliceDecoder(ctx *DecoderContext, scan *Scanner, target reflect.Value) erro if !ok { sep = "," } - childScanner := Scan(strings.Split(scan.PopValue("list"), sep)...) + var childScanner *Scanner + if ctx.Value.Flag { + childScanner = Scan(strings.Split(scan.PopValue("list"), sep)...) + } else { + tokens := scan.PopUntil(func(t Token) bool { + return !t.IsValue() || (t.Type == UntypedToken && strings.HasPrefix(t.Value, "-")) + }) + childScanner = Scan(tokens...) + } childDecoder := DecoderForType(el) if childDecoder == nil { return fmt.Errorf("no decoder for element type of %s", target.Type()) diff --git a/kong_test.go b/kong_test.go index 2f3d96b..10e4894 100644 --- a/kong_test.go +++ b/kong_test.go @@ -76,7 +76,7 @@ func TestResetWithDefaults(t *testing.T) { require.Equal(t, "default", cli.FlagWithDefault) } -func TestSlice(t *testing.T) { +func TestFlagSlice(t *testing.T) { var cli struct { Slice []int `help:""` } @@ -86,6 +86,18 @@ func TestSlice(t *testing.T) { require.Equal(t, []int{1, 2, 3}, cli.Slice) } +func TestArgSlice(t *testing.T) { + var cli struct { + Slice []int `help:"" arg:""` + Flag bool `help:""` + } + parser := mustNew(t, &cli) + _, err := parser.Parse([]string{"1", "2", "3", "--flag"}) + require.NoError(t, err) + require.Equal(t, []int{1, 2, 3}, cli.Slice) + require.Equal(t, true, cli.Flag) +} + func TestUnsupportedfieldErrors(t *testing.T) { var cli struct { Keys map[string]string `help:""` diff --git a/model.go b/model.go index 51c12d9..2583c00 100644 --- a/model.go +++ b/model.go @@ -21,6 +21,7 @@ type Node struct { } type Value struct { + Flag bool // True if flag, false if positional argument. Name string Help string Decoder Decoder diff --git a/scanner.go b/scanner.go index e93b717..9c7d86d 100644 --- a/scanner.go +++ b/scanner.go @@ -86,6 +86,22 @@ func (s *Scanner) PopValue(context string) string { return t.Value } +// PopWhile predicate returns true. +func (s *Scanner) PopWhile(predicate func(Token) bool) (values []string) { + for predicate(s.Peek()) { + values = append(values, s.Pop().Value) + } + return +} + +// PopUntil predicate returns true. +func (s *Scanner) PopUntil(predicate func(Token) bool) (values []string) { + for !predicate(s.Peek()) { + values = append(values, s.Pop().Value) + } + return +} + func (s *Scanner) Peek() Token { if len(s.args) == 0 { return Token{Type: EOLToken}