diff --git a/decoders.go b/decoders.go index 4cbb1f1..a5428ec 100644 --- a/decoders.go +++ b/decoders.go @@ -13,7 +13,12 @@ type DecoderContext struct { Value *Value } +// A Decoder knows how to decode text into a Go value. type Decoder interface { + // Decode scan into target. + // + // "ctx" contains context about the value being decoded that may be useful + // to some decoders. Decode(ctx *DecoderContext, scan *Scanner, target reflect.Value) error } @@ -61,6 +66,12 @@ func (k *kindDecoder) Kind() reflect.Kind { return k.kind } var _ KindDecoder = &kindDecoder{} +// A NamedDecoder will be used if the value field has a "type" tag matching Name(). +// +// eg. +// +// Field string `type:"colour"` +// kong.RegisterDecoder(kong.NewNamedDecoder("colour", ...)) type NamedDecoder interface { Name() string Decoder @@ -98,7 +109,7 @@ func DecoderForField(field reflect.StructField) Decoder { return DecoderForType(field.Type) } -// DecoderForType finds a decoder via a type or kind. +// DecoderForType finds a decoder from a type or kind. // // Will return nil if a decoder can not be determined. func DecoderForType(typ reflect.Type) Decoder { @@ -125,7 +136,7 @@ func RegisterDecoder(decoders ...Decoder) { case NamedDecoder: namedDecoders[decoder.Name()] = decoder default: - fail("unsupported decoder type " + reflect.TypeOf(decoder).String()) + fail("unsupported decoder type %T", decoder) } } } @@ -220,11 +231,10 @@ func sliceDecoder(ctx *DecoderContext, scan *Scanner, target reflect.Value) erro } var childScanner *Scanner if ctx.Value.Flag { + // If decoding a flag, we need an argument. 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, "-")) - }) + tokens := scan.PopUntil(func(t Token) bool { return !t.IsValue() }) childScanner = Scan(tokens...) } childDecoder := DecoderForType(el) diff --git a/kong.go b/kong.go index c382b40..5c06d55 100644 --- a/kong.go +++ b/kong.go @@ -61,7 +61,7 @@ func (k *Kong) reset(node *Node) { for _, flag := range node.Flags { flag.Value.Value.Set(reflect.Zero(flag.Value.Value.Type())) if flag.Default != "" { - flag.Decoder.Decode(&DecoderContext{Value: &flag.Value}, Scan(flag.Default), flag.Value.Value) + flag.Decode(Scan(flag.Default)) } } for _, pos := range node.Positional { @@ -144,7 +144,7 @@ func (k *Kong) applyNode(scan *Scanner, node *Node) (command []string, err error // Ensure we've consumed all positional arguments. if positional < len(node.Positional) { arg := node.Positional[positional] - err := arg.Decoder.Decode(&DecoderContext{Value: arg}, scan, arg.Value) + err := arg.Decode(scan) if err != nil { return nil, err } @@ -169,7 +169,7 @@ func (k *Kong) applyNode(scan *Scanner, node *Node) (command []string, err error case branch.Argument != nil: arg := branch.Argument.Argument - if err := arg.Decoder.Decode(&DecoderContext{Value: arg}, scan, arg.Value); err == nil { + if err := arg.Decode(scan); err == nil { command = append(command, "<"+arg.Name+">") cmd, err := k.applyNode(scan, &branch.Argument.Node) if err != nil { @@ -200,7 +200,7 @@ func matchFlags(flags []*Flag, token Token, scan *Scanner, matcher func(f *Flag) for _, flag := range flags { // Found a matching flag. if flag.Name == token.Value { - err := flag.Decoder.Decode(&DecoderContext{Value: &flag.Value}, scan, flag.Value.Value) + err := flag.Decode(scan) if err != nil { return err } diff --git a/model.go b/model.go index 2583c00..50be9c2 100644 --- a/model.go +++ b/model.go @@ -31,6 +31,10 @@ type Value struct { Format string // Formatting directive, if applicable. } +func (v *Value) Decode(scan *Scanner) error { + return v.Decoder.Decode(&DecoderContext{Value: v}, scan, v.Value) +} + type Positional = Value type Argument struct { diff --git a/scanner.go b/scanner.go index 9c7d86d..e653988 100644 --- a/scanner.go +++ b/scanner.go @@ -2,6 +2,7 @@ package kong import ( "strconv" + "strings" ) //go:generate stringer -type=TokenType @@ -48,8 +49,12 @@ func (t Token) IsAny(types ...TokenType) bool { return false } +// IsValue returns true if token is usable as a parseable value. +// +// A parseable value is either a value typed token, or an untyped token NOT starting with a hyphen. func (t Token) IsValue() bool { - return t.IsAny(FlagValueToken, ShortFlagTailToken, PositionalArgumentToken, UntypedToken) + return t.IsAny(FlagValueToken, ShortFlagTailToken, PositionalArgumentToken) || + (t.Type == UntypedToken && !strings.HasPrefix(t.Value, "-")) } type Scanner struct { @@ -77,7 +82,7 @@ func (s *Scanner) Pop() Token { return arg } -// PopValue or panic with Error. +// PopValue token, or panic with Error. func (s *Scanner) PopValue(context string) string { t := s.Pop() if !t.IsValue() {