Decoders are now field mappers.
Mappers are responsible for mapping from command-line input to Go. This is typically just decoding, but also includes other information such as if the field is a bool.
This commit is contained in:
committed by
Gerald Kaszuba
parent
c8b487e49c
commit
48af58cefa
@@ -6,7 +6,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func build(ast interface{}, extraFlags []*Flag) (app *Application, err error) {
|
func build(k *Kong, ast interface{}) (app *Application, err error) {
|
||||||
defer catch(&err)
|
defer catch(&err)
|
||||||
v := reflect.ValueOf(ast)
|
v := reflect.ValueOf(ast)
|
||||||
iv := reflect.Indirect(v)
|
iv := reflect.Indirect(v)
|
||||||
@@ -15,11 +15,12 @@ func build(ast interface{}, extraFlags []*Flag) (app *Application, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
app = &Application{}
|
app = &Application{}
|
||||||
|
extraFlags := k.extraFlags()
|
||||||
seenFlags := map[string]bool{}
|
seenFlags := map[string]bool{}
|
||||||
for _, flag := range extraFlags {
|
for _, flag := range extraFlags {
|
||||||
seenFlags[flag.Name] = true
|
seenFlags[flag.Name] = true
|
||||||
}
|
}
|
||||||
node := buildNode(iv, ApplicationNode, seenFlags)
|
node := buildNode(k, iv, ApplicationNode, seenFlags)
|
||||||
if len(node.Positional) > 0 && len(node.Children) > 0 {
|
if len(node.Positional) > 0 && len(node.Children) > 0 {
|
||||||
return nil, fmt.Errorf("can't mix positional arguments and branching arguments on %T", ast)
|
return nil, fmt.Errorf("can't mix positional arguments and branching arguments on %T", ast)
|
||||||
}
|
}
|
||||||
@@ -32,7 +33,7 @@ func dashedString(s string) string {
|
|||||||
return strings.Join(camelCase(s), "-")
|
return strings.Join(camelCase(s), "-")
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildNode(v reflect.Value, typ NodeType, seenFlags map[string]bool) *Node {
|
func buildNode(k *Kong, v reflect.Value, typ NodeType, seenFlags map[string]bool) *Node {
|
||||||
node := &Node{
|
node := &Node{
|
||||||
Type: typ,
|
Type: typ,
|
||||||
Target: v,
|
Target: v,
|
||||||
@@ -57,9 +58,9 @@ func buildNode(v reflect.Value, typ NodeType, seenFlags map[string]bool) *Node {
|
|||||||
if tag.Arg {
|
if tag.Arg {
|
||||||
typ = ArgumentNode
|
typ = ArgumentNode
|
||||||
}
|
}
|
||||||
buildChild(node, typ, v, ft, fv, tag, name, seenFlags)
|
buildChild(k, node, typ, v, ft, fv, tag, name, seenFlags)
|
||||||
} else {
|
} else {
|
||||||
buildField(node, v, ft, fv, tag, name, seenFlags)
|
buildField(k, node, v, ft, fv, tag, name, seenFlags)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,8 +83,8 @@ func buildNode(v reflect.Value, typ NodeType, seenFlags map[string]bool) *Node {
|
|||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildChild(node *Node, typ NodeType, v reflect.Value, ft reflect.StructField, fv reflect.Value, tag *Tag, name string, seenFlags map[string]bool) {
|
func buildChild(k *Kong, node *Node, typ NodeType, v reflect.Value, ft reflect.StructField, fv reflect.Value, tag *Tag, name string, seenFlags map[string]bool) {
|
||||||
child := buildNode(fv, typ, seenFlags)
|
child := buildNode(k, fv, typ, seenFlags)
|
||||||
child.Parent = node
|
child.Parent = node
|
||||||
child.Help = tag.Help
|
child.Help = tag.Help
|
||||||
|
|
||||||
@@ -118,10 +119,10 @@ func buildChild(node *Node, typ NodeType, v reflect.Value, ft reflect.StructFiel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildField(node *Node, v reflect.Value, ft reflect.StructField, fv reflect.Value, tag *Tag, name string, seenFlags map[string]bool) {
|
func buildField(k *Kong, node *Node, v reflect.Value, ft reflect.StructField, fv reflect.Value, tag *Tag, name string, seenFlags map[string]bool) {
|
||||||
decoder := DecoderForField(tag.Type, ft)
|
mapper := k.registry.ForNamedType(tag.Type, fv)
|
||||||
if decoder == nil {
|
if mapper == nil {
|
||||||
fail("no decoder for %s.%s (of type %s)", v.Type(), ft.Name, ft.Type)
|
fail("no mapper for %s.%s (of type %s)", v.Type(), ft.Name, ft.Type)
|
||||||
}
|
}
|
||||||
|
|
||||||
flag := !tag.Arg
|
flag := !tag.Arg
|
||||||
@@ -131,7 +132,7 @@ func buildField(node *Node, v reflect.Value, ft reflect.StructField, fv reflect.
|
|||||||
Flag: flag,
|
Flag: flag,
|
||||||
Help: tag.Help,
|
Help: tag.Help,
|
||||||
Default: tag.Default,
|
Default: tag.Default,
|
||||||
Decoder: decoder,
|
Mapper: mapper,
|
||||||
Tag: tag,
|
Tag: tag,
|
||||||
Value: fv,
|
Value: fv,
|
||||||
|
|
||||||
|
|||||||
-250
@@ -1,250 +0,0 @@
|
|||||||
package kong
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type DecoderContext struct {
|
|
||||||
// Value being decoded into.
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
type DecoderFunc func(ctx *DecoderContext, scan *Scanner, target reflect.Value) error
|
|
||||||
|
|
||||||
func (d DecoderFunc) Decode(ctx *DecoderContext, scan *Scanner, target reflect.Value) error {
|
|
||||||
return d(ctx, scan, target)
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ Decoder = DecoderFunc(nil)
|
|
||||||
|
|
||||||
type TypeDecoder interface {
|
|
||||||
Type() reflect.Type
|
|
||||||
Decoder
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewTypeDecoder(typ reflect.Type, decoder DecoderFunc) TypeDecoder {
|
|
||||||
return &typeDecoder{typ, decoder}
|
|
||||||
}
|
|
||||||
|
|
||||||
type typeDecoder struct {
|
|
||||||
typ reflect.Type
|
|
||||||
DecoderFunc
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *typeDecoder) Type() reflect.Type { return t.typ }
|
|
||||||
|
|
||||||
var _ TypeDecoder = &typeDecoder{}
|
|
||||||
|
|
||||||
type KindDecoder interface {
|
|
||||||
Kind() reflect.Kind
|
|
||||||
Decoder
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewKindDecoder(kind reflect.Kind, decoder DecoderFunc) KindDecoder {
|
|
||||||
return &kindDecoder{kind, decoder}
|
|
||||||
}
|
|
||||||
|
|
||||||
type kindDecoder struct {
|
|
||||||
kind reflect.Kind
|
|
||||||
DecoderFunc
|
|
||||||
}
|
|
||||||
|
|
||||||
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 `kong:"type='colour'`
|
|
||||||
// kong.RegisterDecoder(kong.NewNamedDecoder("colour", ...))
|
|
||||||
type NamedDecoder interface {
|
|
||||||
Name() string
|
|
||||||
Decoder
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewNamedDecoder(name string, decoder DecoderFunc) NamedDecoder {
|
|
||||||
return &namedDecoder{name, decoder}
|
|
||||||
}
|
|
||||||
|
|
||||||
type namedDecoder struct {
|
|
||||||
name string
|
|
||||||
DecoderFunc
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *namedDecoder) Name() string { return n.name }
|
|
||||||
|
|
||||||
var _ NamedDecoder = &namedDecoder{}
|
|
||||||
|
|
||||||
var (
|
|
||||||
namedDecoders = map[string]NamedDecoder{}
|
|
||||||
typeDecoders = map[reflect.Type]TypeDecoder{}
|
|
||||||
kindDecoders = map[reflect.Kind]KindDecoder{}
|
|
||||||
)
|
|
||||||
|
|
||||||
// DecoderForField finds a decoder for a struct field.
|
|
||||||
//
|
|
||||||
// Will return nil if a decoder can not be determined.
|
|
||||||
func DecoderForField(name string, field reflect.StructField) Decoder {
|
|
||||||
if decoder, ok := namedDecoders[name]; ok {
|
|
||||||
return decoder
|
|
||||||
}
|
|
||||||
return DecoderForType(field.Type)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 {
|
|
||||||
var decoder Decoder
|
|
||||||
var ok bool
|
|
||||||
if decoder, ok = typeDecoders[typ]; ok {
|
|
||||||
return decoder
|
|
||||||
} else if decoder, ok = kindDecoders[typ.Kind()]; ok {
|
|
||||||
return decoder
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RegisterDecoder registers decoders.
|
|
||||||
//
|
|
||||||
// Decoders must be one of TypeDecoder, KindDecoder or NamedDecoder.
|
|
||||||
func RegisterDecoder(decoders ...Decoder) {
|
|
||||||
for _, decoder := range decoders {
|
|
||||||
switch decoder := decoder.(type) {
|
|
||||||
case TypeDecoder:
|
|
||||||
typeDecoders[decoder.Type()] = decoder
|
|
||||||
case KindDecoder:
|
|
||||||
kindDecoders[decoder.Kind()] = decoder
|
|
||||||
case NamedDecoder:
|
|
||||||
namedDecoders[decoder.Name()] = decoder
|
|
||||||
default:
|
|
||||||
fail("unsupported decoder type %T", decoder)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
RegisterDecoder(
|
|
||||||
NewKindDecoder(reflect.Int, intDecoder),
|
|
||||||
NewKindDecoder(reflect.Int8, intDecoder),
|
|
||||||
NewKindDecoder(reflect.Int16, intDecoder),
|
|
||||||
NewKindDecoder(reflect.Int32, intDecoder),
|
|
||||||
NewKindDecoder(reflect.Int64, intDecoder),
|
|
||||||
NewKindDecoder(reflect.Uint, uintDecoder),
|
|
||||||
NewKindDecoder(reflect.Uint8, uintDecoder),
|
|
||||||
NewKindDecoder(reflect.Uint16, uintDecoder),
|
|
||||||
NewKindDecoder(reflect.Uint32, uintDecoder),
|
|
||||||
NewKindDecoder(reflect.Uint64, uintDecoder),
|
|
||||||
NewKindDecoder(reflect.Float32, floatDecoder(32)),
|
|
||||||
NewKindDecoder(reflect.Float64, floatDecoder(64)),
|
|
||||||
NewKindDecoder(reflect.String, func(ctx *DecoderContext, scan *Scanner, target reflect.Value) error {
|
|
||||||
target.SetString(scan.PopValue("string"))
|
|
||||||
return nil
|
|
||||||
}),
|
|
||||||
NewKindDecoder(reflect.Bool, func(ctx *DecoderContext, scan *Scanner, target reflect.Value) error {
|
|
||||||
target.SetBool(true)
|
|
||||||
return nil
|
|
||||||
}),
|
|
||||||
NewKindDecoder(reflect.Slice, sliceDecoder),
|
|
||||||
NewTypeDecoder(reflect.TypeOf(time.Time{}), timeDecoder),
|
|
||||||
NewTypeDecoder(reflect.TypeOf(time.Duration(0)), durationDecoder),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func durationDecoder(ctx *DecoderContext, scan *Scanner, target reflect.Value) error {
|
|
||||||
d, err := time.ParseDuration(scan.PopValue("duration"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
target.Set(reflect.ValueOf(d))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func timeDecoder(ctx *DecoderContext, scan *Scanner, target reflect.Value) error {
|
|
||||||
fmt := time.RFC3339
|
|
||||||
if ctx.Value.Format != "" {
|
|
||||||
fmt = ctx.Value.Format
|
|
||||||
}
|
|
||||||
t, err := time.Parse(fmt, scan.PopValue("time"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
target.Set(reflect.ValueOf(t))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func intDecoder(ctx *DecoderContext, scan *Scanner, target reflect.Value) error {
|
|
||||||
value := scan.PopValue("int")
|
|
||||||
n, err := strconv.ParseInt(value, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("invalid int %q", value)
|
|
||||||
}
|
|
||||||
target.SetInt(n)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func uintDecoder(ctx *DecoderContext, scan *Scanner, target reflect.Value) error {
|
|
||||||
value := scan.PopValue("uint")
|
|
||||||
n, err := strconv.ParseUint(value, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("invalid uint %q", value)
|
|
||||||
}
|
|
||||||
target.SetUint(n)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func floatDecoder(bits int) DecoderFunc {
|
|
||||||
return func(ctx *DecoderContext, scan *Scanner, target reflect.Value) error {
|
|
||||||
value := scan.PopValue("float")
|
|
||||||
n, err := strconv.ParseFloat(value, bits)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("invalid float %q", value)
|
|
||||||
}
|
|
||||||
target.SetFloat(n)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func sliceDecoder(ctx *DecoderContext, scan *Scanner, target reflect.Value) error {
|
|
||||||
el := target.Type().Elem()
|
|
||||||
sep, ok := ctx.Value.Tag.Get("sep")
|
|
||||||
if !ok {
|
|
||||||
sep = ","
|
|
||||||
}
|
|
||||||
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() })
|
|
||||||
childScanner = Scan(tokens...)
|
|
||||||
}
|
|
||||||
childDecoder := DecoderForType(el)
|
|
||||||
if childDecoder == nil {
|
|
||||||
return fmt.Errorf("no decoder for element type of %s", target.Type())
|
|
||||||
}
|
|
||||||
for childScanner.Peek().Type != EOLToken {
|
|
||||||
childValue := reflect.New(el).Elem()
|
|
||||||
err := childDecoder.Decode(ctx, childScanner, childValue)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
target.Set(reflect.Append(target, childValue))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
package kong
|
|
||||||
|
|
||||||
import "testing"
|
|
||||||
|
|
||||||
func TestDecoders(t *testing.T) {
|
|
||||||
}
|
|
||||||
@@ -82,11 +82,11 @@ func init() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Help returns a Hook that will display help and exit.
|
// Help returns a Before hook that will display help and exit.
|
||||||
//
|
//
|
||||||
// tmpl receives a context with several top-level values, in addition to those passed through tmplctx:
|
// tmpl receives a context with several top-level values, in addition to those passed through tmplctx:
|
||||||
// .Context which is of type *Context and .Path which is of type *Path.
|
// .Context which is of type *Context and .Path which is of type *Path.
|
||||||
func Help(tmpl *raymond.Template, tmplctx map[string]interface{}) HookFunction {
|
func Help(tmpl *raymond.Template, tmplctx map[string]interface{}) Before {
|
||||||
return func(ctx *Context, path *Path) error {
|
return func(ctx *Context, path *Path) error {
|
||||||
merged := map[string]interface{}{
|
merged := map[string]interface{}{
|
||||||
"App": ctx.App,
|
"App": ctx.App,
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
)
|
)
|
||||||
|
|
||||||
type HookFunction func(ctx *Context, path *Path) error
|
// Before is a callback tied to a field of the grammar, called before a value is applied.
|
||||||
|
type Before func(ctx *Context, path *Path) error
|
||||||
|
|
||||||
// Error reported by Kong.
|
// Error reported by Kong.
|
||||||
type Error struct{ msg string }
|
type Error struct{ msg string }
|
||||||
@@ -38,7 +39,8 @@ type Kong struct {
|
|||||||
Stdout io.Writer
|
Stdout io.Writer
|
||||||
Stderr io.Writer
|
Stderr io.Writer
|
||||||
|
|
||||||
hooks map[reflect.Value]HookFunction
|
before map[reflect.Value]Before
|
||||||
|
registry *Registry
|
||||||
noDefaultHelp bool
|
noDefaultHelp bool
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,10 +52,15 @@ func New(grammar interface{}, options ...Option) (*Kong, error) {
|
|||||||
Exit: os.Exit,
|
Exit: os.Exit,
|
||||||
Stdout: os.Stdout,
|
Stdout: os.Stdout,
|
||||||
Stderr: os.Stderr,
|
Stderr: os.Stderr,
|
||||||
hooks: map[reflect.Value]HookFunction{},
|
before: map[reflect.Value]Before{},
|
||||||
|
registry: NewRegistry().RegisterDefaults(),
|
||||||
}
|
}
|
||||||
|
|
||||||
model, err := build(grammar, k.extraFlags())
|
for _, option := range options {
|
||||||
|
option(k)
|
||||||
|
}
|
||||||
|
|
||||||
|
model, err := build(k, grammar)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return k, err
|
return k, err
|
||||||
}
|
}
|
||||||
@@ -72,13 +79,14 @@ func (k *Kong) extraFlags() []*Flag {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
helpValue := false
|
helpValue := false
|
||||||
|
value := reflect.ValueOf(&helpValue).Elem()
|
||||||
helpFlag := &Flag{
|
helpFlag := &Flag{
|
||||||
Value: Value{
|
Value: Value{
|
||||||
Name: "help",
|
Name: "help",
|
||||||
Help: "Show context-sensitive help.",
|
Help: "Show context-sensitive help.",
|
||||||
Flag: true,
|
Flag: true,
|
||||||
Value: reflect.ValueOf(&helpValue).Elem(),
|
Value: value,
|
||||||
Decoder: kindDecoders[reflect.Bool],
|
Mapper: k.registry.ForValue(value),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
hook := Hook(&helpValue, Help(defaultHelpTemplate, nil))
|
hook := Hook(&helpValue, Help(defaultHelpTemplate, nil))
|
||||||
@@ -133,7 +141,7 @@ func (k *Kong) applyHooks(ctx *Context) error {
|
|||||||
if key.IsValid() {
|
if key.IsValid() {
|
||||||
key = key.Addr()
|
key = key.Addr()
|
||||||
}
|
}
|
||||||
if hook := k.hooks[key]; hook != nil {
|
if hook := k.before[key]; hook != nil {
|
||||||
if err := hook(ctx, trace); err != nil {
|
if err := hook(ctx, trace); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,229 @@
|
|||||||
|
package kong
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DecoderContext struct {
|
||||||
|
// Value being decoded into.
|
||||||
|
Value *Value
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Mapper represents how a field is mapped from command-line values to Go.
|
||||||
|
//
|
||||||
|
// Mappers can be associated with concrete fields via pointer, reflect.Type, reflect.Kind, or via a "type" tag.
|
||||||
|
type Mapper interface {
|
||||||
|
Decode(ctx *DecoderContext, scan *Scanner, target reflect.Value) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// A BoolMapper is a Mapper to a value that is a boolean.
|
||||||
|
type BoolMapper interface {
|
||||||
|
Mapper
|
||||||
|
IsBool() bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type MapperFunc func(ctx *DecoderContext, scan *Scanner, target reflect.Value) error
|
||||||
|
|
||||||
|
func (d MapperFunc) Decode(ctx *DecoderContext, scan *Scanner, target reflect.Value) error {
|
||||||
|
return d(ctx, scan, target)
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Registry encapsulates a set of fields and lookups to resolve them.
|
||||||
|
type Registry struct {
|
||||||
|
names map[string]Mapper
|
||||||
|
types map[reflect.Type]Mapper
|
||||||
|
kinds map[reflect.Kind]Mapper
|
||||||
|
values map[reflect.Value]Mapper
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRegistry() *Registry {
|
||||||
|
return &Registry{
|
||||||
|
names: map[string]Mapper{},
|
||||||
|
types: map[reflect.Type]Mapper{},
|
||||||
|
kinds: map[reflect.Kind]Mapper{},
|
||||||
|
values: map[reflect.Value]Mapper{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ForNamedType finds a mapper for a value with a user-specified type.
|
||||||
|
//
|
||||||
|
// Will return nil if a mapper can not be determined.
|
||||||
|
func (d *Registry) ForNamedType(name string, value reflect.Value) Mapper {
|
||||||
|
if mapper, ok := d.names[name]; ok {
|
||||||
|
return mapper
|
||||||
|
}
|
||||||
|
return d.ForValue(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Registry) ForValue(value reflect.Value) Mapper {
|
||||||
|
if mapper, ok := d.values[value]; ok {
|
||||||
|
return mapper
|
||||||
|
}
|
||||||
|
return d.ForType(value.Type())
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecoderForType finds a mapper from a type or kind.
|
||||||
|
//
|
||||||
|
// Will return nil if a mapper can not be determined.
|
||||||
|
func (d *Registry) ForType(typ reflect.Type) Mapper {
|
||||||
|
var mapper Mapper
|
||||||
|
var ok bool
|
||||||
|
if mapper, ok = d.types[typ]; ok {
|
||||||
|
return mapper
|
||||||
|
} else if mapper, ok = d.kinds[typ.Kind()]; ok {
|
||||||
|
return mapper
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Registry) RegisterKind(kind reflect.Kind, mapper Mapper) *Registry {
|
||||||
|
d.kinds[kind] = mapper
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterName registeres a mapper to be used if the value mapper has a "type" tag matching name.
|
||||||
|
//
|
||||||
|
// eg.
|
||||||
|
//
|
||||||
|
// Mapper string `kong:"type='colour'`
|
||||||
|
// registry.RegisterName("colour", ...)
|
||||||
|
func (d *Registry) RegisterName(name string, mapper Mapper) *Registry {
|
||||||
|
d.names[name] = mapper
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Registry) RegisterType(typ reflect.Type, mapper Mapper) *Registry {
|
||||||
|
d.types[typ] = mapper
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterValue registers a mapper by a pointer to the mapper value.
|
||||||
|
func (d *Registry) RegisterValue(ptr interface{}, mapper Mapper) *Registry {
|
||||||
|
key := reflect.ValueOf(ptr)
|
||||||
|
if key.Kind() != reflect.Ptr {
|
||||||
|
panic("expected a pointer")
|
||||||
|
}
|
||||||
|
key = key.Elem()
|
||||||
|
d.values[key] = mapper
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Registry) RegisterDefaults() *Registry {
|
||||||
|
return d.RegisterKind(reflect.Int, MapperFunc(intDecoder)).
|
||||||
|
RegisterKind(reflect.Int8, MapperFunc(intDecoder)).
|
||||||
|
RegisterKind(reflect.Int16, MapperFunc(intDecoder)).
|
||||||
|
RegisterKind(reflect.Int32, MapperFunc(intDecoder)).
|
||||||
|
RegisterKind(reflect.Int64, MapperFunc(intDecoder)).
|
||||||
|
RegisterKind(reflect.Uint, MapperFunc(uintDecoder)).
|
||||||
|
RegisterKind(reflect.Uint8, MapperFunc(uintDecoder)).
|
||||||
|
RegisterKind(reflect.Uint16, MapperFunc(uintDecoder)).
|
||||||
|
RegisterKind(reflect.Uint32, MapperFunc(uintDecoder)).
|
||||||
|
RegisterKind(reflect.Uint64, MapperFunc(uintDecoder)).
|
||||||
|
RegisterKind(reflect.Float32, floatDecoder(32)).
|
||||||
|
RegisterKind(reflect.Float64, floatDecoder(64)).
|
||||||
|
RegisterKind(reflect.String, MapperFunc(func(ctx *DecoderContext, scan *Scanner, target reflect.Value) error {
|
||||||
|
target.SetString(scan.PopValue("string"))
|
||||||
|
return nil
|
||||||
|
})).
|
||||||
|
RegisterKind(reflect.Bool, boolMapper{}).
|
||||||
|
RegisterType(reflect.TypeOf(time.Time{}), MapperFunc(timeDecoder)).
|
||||||
|
RegisterType(reflect.TypeOf(time.Duration(0)), MapperFunc(durationDecoder)).
|
||||||
|
RegisterKind(reflect.Slice, sliceDecoder(d))
|
||||||
|
}
|
||||||
|
|
||||||
|
type boolMapper struct{}
|
||||||
|
|
||||||
|
func (boolMapper) Decode(ctx *DecoderContext, scan *Scanner, target reflect.Value) error {
|
||||||
|
target.SetBool(true)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (boolMapper) IsBool() bool { return true }
|
||||||
|
|
||||||
|
func durationDecoder(ctx *DecoderContext, scan *Scanner, target reflect.Value) error {
|
||||||
|
d, err := time.ParseDuration(scan.PopValue("duration"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
target.Set(reflect.ValueOf(d))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func timeDecoder(ctx *DecoderContext, scan *Scanner, target reflect.Value) error {
|
||||||
|
fmt := time.RFC3339
|
||||||
|
if ctx.Value.Format != "" {
|
||||||
|
fmt = ctx.Value.Format
|
||||||
|
}
|
||||||
|
t, err := time.Parse(fmt, scan.PopValue("time"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
target.Set(reflect.ValueOf(t))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func intDecoder(ctx *DecoderContext, scan *Scanner, target reflect.Value) error {
|
||||||
|
value := scan.PopValue("int")
|
||||||
|
n, err := strconv.ParseInt(value, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid int %q", value)
|
||||||
|
}
|
||||||
|
target.SetInt(n)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func uintDecoder(ctx *DecoderContext, scan *Scanner, target reflect.Value) error {
|
||||||
|
value := scan.PopValue("uint")
|
||||||
|
n, err := strconv.ParseUint(value, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid uint %q", value)
|
||||||
|
}
|
||||||
|
target.SetUint(n)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func floatDecoder(bits int) MapperFunc {
|
||||||
|
return func(ctx *DecoderContext, scan *Scanner, target reflect.Value) error {
|
||||||
|
value := scan.PopValue("float")
|
||||||
|
n, err := strconv.ParseFloat(value, bits)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid float %q", value)
|
||||||
|
}
|
||||||
|
target.SetFloat(n)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func sliceDecoder(d *Registry) MapperFunc {
|
||||||
|
return func(ctx *DecoderContext, scan *Scanner, target reflect.Value) error {
|
||||||
|
el := target.Type().Elem()
|
||||||
|
sep, ok := ctx.Value.Tag.Get("sep")
|
||||||
|
if !ok {
|
||||||
|
sep = ","
|
||||||
|
}
|
||||||
|
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() })
|
||||||
|
childScanner = Scan(tokens...)
|
||||||
|
}
|
||||||
|
childDecoder := d.ForType(el)
|
||||||
|
if childDecoder == nil {
|
||||||
|
return fmt.Errorf("no mapper for element type of %s", target.Type())
|
||||||
|
}
|
||||||
|
for childScanner.Peek().Type != EOLToken {
|
||||||
|
childValue := reflect.New(el).Elem()
|
||||||
|
err := childDecoder.Decode(ctx, childScanner, childValue)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
target.Set(reflect.Append(target, childValue))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package kong
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestValueMapper(t *testing.T) {
|
||||||
|
var cli struct {
|
||||||
|
Flag string
|
||||||
|
}
|
||||||
|
k := mustNew(t, &cli, ValueMapper(&cli.Flag, testMooMapper{}))
|
||||||
|
_, err := k.Parse(nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, "", cli.Flag)
|
||||||
|
_, err = k.Parse([]string{"--flag"})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, "MOO", cli.Flag)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNamedMapper(t *testing.T) {
|
||||||
|
var cli struct {
|
||||||
|
Flag string `type:"moo"`
|
||||||
|
}
|
||||||
|
k := mustNew(t, &cli, NamedMapper("moo", testMooMapper{}))
|
||||||
|
_, err := k.Parse(nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, "", cli.Flag)
|
||||||
|
_, err = k.Parse([]string{"--flag"})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, "MOO", cli.Flag)
|
||||||
|
}
|
||||||
|
|
||||||
|
type testMooMapper struct{}
|
||||||
|
|
||||||
|
func (testMooMapper) Decode(ctx *DecoderContext, scan *Scanner, target reflect.Value) error {
|
||||||
|
target.SetString("MOO")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (testMooMapper) IsBool() bool { return true }
|
||||||
@@ -130,7 +130,7 @@ type Value struct {
|
|||||||
Name string
|
Name string
|
||||||
Help string
|
Help string
|
||||||
Default string
|
Default string
|
||||||
Decoder Decoder
|
Mapper Mapper
|
||||||
Tag *Tag
|
Tag *Tag
|
||||||
Value reflect.Value
|
Value reflect.Value
|
||||||
Required bool
|
Required bool
|
||||||
@@ -144,13 +144,16 @@ func (v *Value) IsCumulative() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (v *Value) IsBool() bool {
|
func (v *Value) IsBool() bool {
|
||||||
|
if m, ok := v.Mapper.(BoolMapper); ok && m.IsBool() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
return v.Value.Kind() == reflect.Bool
|
return v.Value.Kind() == reflect.Bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse tokens into value, parse, and validate, but do not write to the field.
|
// Parse tokens into value, parse, and validate, but do not write to the field.
|
||||||
func (v *Value) Parse(scan *Scanner) (reflect.Value, error) {
|
func (v *Value) Parse(scan *Scanner) (reflect.Value, error) {
|
||||||
value := reflect.New(v.Value.Type()).Elem()
|
value := reflect.New(v.Value.Type()).Elem()
|
||||||
err := v.Decoder.Decode(&DecoderContext{Value: v}, scan, value)
|
err := v.Mapper.Decode(&DecoderContext{Value: v}, scan, value)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
v.Set = true
|
v.Set = true
|
||||||
}
|
}
|
||||||
|
|||||||
+25
-3
@@ -32,6 +32,26 @@ func Name(name string) Option {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TypeMapper registers a mapper to a type.
|
||||||
|
func TypeMapper(typ reflect.Type, mapper Mapper) Option {
|
||||||
|
return func(k *Kong) { k.registry.RegisterType(typ, mapper) }
|
||||||
|
}
|
||||||
|
|
||||||
|
// KindMapper registers a mapper to a kind.
|
||||||
|
func KindMapper(kind reflect.Kind, mapper Mapper) Option {
|
||||||
|
return func(k *Kong) { k.registry.RegisterKind(kind, mapper) }
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValueMapper registers a mapper to a field value.
|
||||||
|
func ValueMapper(ptr interface{}, mapper Mapper) Option {
|
||||||
|
return func(k *Kong) { k.registry.RegisterValue(ptr, mapper) }
|
||||||
|
}
|
||||||
|
|
||||||
|
// NamedMapper registers a mapper to a name.
|
||||||
|
func NamedMapper(name string, mapper Mapper) Option {
|
||||||
|
return func(k *Kong) { k.registry.RegisterName(name, mapper) }
|
||||||
|
}
|
||||||
|
|
||||||
// Description sets the application description.
|
// Description sets the application description.
|
||||||
func Description(description string) Option {
|
func Description(description string) Option {
|
||||||
return func(k *Kong) {
|
return func(k *Kong) {
|
||||||
@@ -49,13 +69,15 @@ func Writers(stdout, stderr io.Writer) Option {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hook to execute when a command, flag or positional argument is encountered.
|
// Hook to aply before a command, flag or positional argument is encountered.
|
||||||
func Hook(ptr interface{}, hook HookFunction) Option {
|
//
|
||||||
|
// "ptr" is a pointer to a field of the grammar.
|
||||||
|
func Hook(ptr interface{}, hook Before) Option {
|
||||||
key := reflect.ValueOf(ptr)
|
key := reflect.ValueOf(ptr)
|
||||||
if key.Kind() != reflect.Ptr {
|
if key.Kind() != reflect.Ptr {
|
||||||
panic("expected a pointer")
|
panic("expected a pointer")
|
||||||
}
|
}
|
||||||
return func(k *Kong) {
|
return func(k *Kong) {
|
||||||
k.hooks[key] = hook
|
k.before[key] = hook
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user