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"
|
||||
)
|
||||
|
||||
func build(ast interface{}, extraFlags []*Flag) (app *Application, err error) {
|
||||
func build(k *Kong, ast interface{}) (app *Application, err error) {
|
||||
defer catch(&err)
|
||||
v := reflect.ValueOf(ast)
|
||||
iv := reflect.Indirect(v)
|
||||
@@ -15,11 +15,12 @@ func build(ast interface{}, extraFlags []*Flag) (app *Application, err error) {
|
||||
}
|
||||
|
||||
app = &Application{}
|
||||
extraFlags := k.extraFlags()
|
||||
seenFlags := map[string]bool{}
|
||||
for _, flag := range extraFlags {
|
||||
seenFlags[flag.Name] = true
|
||||
}
|
||||
node := buildNode(iv, ApplicationNode, seenFlags)
|
||||
node := buildNode(k, iv, ApplicationNode, seenFlags)
|
||||
if len(node.Positional) > 0 && len(node.Children) > 0 {
|
||||
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), "-")
|
||||
}
|
||||
|
||||
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{
|
||||
Type: typ,
|
||||
Target: v,
|
||||
@@ -57,9 +58,9 @@ func buildNode(v reflect.Value, typ NodeType, seenFlags map[string]bool) *Node {
|
||||
if tag.Arg {
|
||||
typ = ArgumentNode
|
||||
}
|
||||
buildChild(node, typ, v, ft, fv, tag, name, seenFlags)
|
||||
buildChild(k, node, typ, v, ft, fv, tag, name, seenFlags)
|
||||
} 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
|
||||
}
|
||||
|
||||
func buildChild(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)
|
||||
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(k, fv, typ, seenFlags)
|
||||
child.Parent = node
|
||||
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) {
|
||||
decoder := DecoderForField(tag.Type, ft)
|
||||
if decoder == nil {
|
||||
fail("no decoder for %s.%s (of type %s)", v.Type(), ft.Name, ft.Type)
|
||||
func buildField(k *Kong, node *Node, v reflect.Value, ft reflect.StructField, fv reflect.Value, tag *Tag, name string, seenFlags map[string]bool) {
|
||||
mapper := k.registry.ForNamedType(tag.Type, fv)
|
||||
if mapper == nil {
|
||||
fail("no mapper for %s.%s (of type %s)", v.Type(), ft.Name, ft.Type)
|
||||
}
|
||||
|
||||
flag := !tag.Arg
|
||||
@@ -131,7 +132,7 @@ func buildField(node *Node, v reflect.Value, ft reflect.StructField, fv reflect.
|
||||
Flag: flag,
|
||||
Help: tag.Help,
|
||||
Default: tag.Default,
|
||||
Decoder: decoder,
|
||||
Mapper: mapper,
|
||||
Tag: tag,
|
||||
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:
|
||||
// .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 {
|
||||
merged := map[string]interface{}{
|
||||
"App": ctx.App,
|
||||
|
||||
@@ -8,7 +8,8 @@ import (
|
||||
"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.
|
||||
type Error struct{ msg string }
|
||||
@@ -38,7 +39,8 @@ type Kong struct {
|
||||
Stdout io.Writer
|
||||
Stderr io.Writer
|
||||
|
||||
hooks map[reflect.Value]HookFunction
|
||||
before map[reflect.Value]Before
|
||||
registry *Registry
|
||||
noDefaultHelp bool
|
||||
}
|
||||
|
||||
@@ -47,13 +49,18 @@ type Kong struct {
|
||||
// See the README (https://github.com/alecthomas/kong) for usage instructions.
|
||||
func New(grammar interface{}, options ...Option) (*Kong, error) {
|
||||
k := &Kong{
|
||||
Exit: os.Exit,
|
||||
Stdout: os.Stdout,
|
||||
Stderr: os.Stderr,
|
||||
hooks: map[reflect.Value]HookFunction{},
|
||||
Exit: os.Exit,
|
||||
Stdout: os.Stdout,
|
||||
Stderr: os.Stderr,
|
||||
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 {
|
||||
return k, err
|
||||
}
|
||||
@@ -72,13 +79,14 @@ func (k *Kong) extraFlags() []*Flag {
|
||||
return nil
|
||||
}
|
||||
helpValue := false
|
||||
value := reflect.ValueOf(&helpValue).Elem()
|
||||
helpFlag := &Flag{
|
||||
Value: Value{
|
||||
Name: "help",
|
||||
Help: "Show context-sensitive help.",
|
||||
Flag: true,
|
||||
Value: reflect.ValueOf(&helpValue).Elem(),
|
||||
Decoder: kindDecoders[reflect.Bool],
|
||||
Name: "help",
|
||||
Help: "Show context-sensitive help.",
|
||||
Flag: true,
|
||||
Value: value,
|
||||
Mapper: k.registry.ForValue(value),
|
||||
},
|
||||
}
|
||||
hook := Hook(&helpValue, Help(defaultHelpTemplate, nil))
|
||||
@@ -133,7 +141,7 @@ func (k *Kong) applyHooks(ctx *Context) error {
|
||||
if key.IsValid() {
|
||||
key = key.Addr()
|
||||
}
|
||||
if hook := k.hooks[key]; hook != nil {
|
||||
if hook := k.before[key]; hook != nil {
|
||||
if err := hook(ctx, trace); err != nil {
|
||||
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
|
||||
Help string
|
||||
Default string
|
||||
Decoder Decoder
|
||||
Mapper Mapper
|
||||
Tag *Tag
|
||||
Value reflect.Value
|
||||
Required bool
|
||||
@@ -144,13 +144,16 @@ func (v *Value) IsCumulative() bool {
|
||||
}
|
||||
|
||||
func (v *Value) IsBool() bool {
|
||||
if m, ok := v.Mapper.(BoolMapper); ok && m.IsBool() {
|
||||
return true
|
||||
}
|
||||
return v.Value.Kind() == reflect.Bool
|
||||
}
|
||||
|
||||
// Parse tokens into value, parse, and validate, but do not write to the field.
|
||||
func (v *Value) Parse(scan *Scanner) (reflect.Value, error) {
|
||||
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 {
|
||||
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.
|
||||
func Description(description string) Option {
|
||||
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.
|
||||
func Hook(ptr interface{}, hook HookFunction) Option {
|
||||
// Hook to aply before a command, flag or positional argument is encountered.
|
||||
//
|
||||
// "ptr" is a pointer to a field of the grammar.
|
||||
func Hook(ptr interface{}, hook Before) Option {
|
||||
key := reflect.ValueOf(ptr)
|
||||
if key.Kind() != reflect.Ptr {
|
||||
panic("expected a pointer")
|
||||
}
|
||||
return func(k *Kong) {
|
||||
k.hooks[key] = hook
|
||||
k.before[key] = hook
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user