diff --git a/build.go b/build.go index 13adda2..34a5101 100644 --- a/build.go +++ b/build.go @@ -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, diff --git a/decoders.go b/decoders.go deleted file mode 100644 index 2858889..0000000 --- a/decoders.go +++ /dev/null @@ -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 -} diff --git a/decoders_test.go b/decoders_test.go deleted file mode 100644 index 0eaa188..0000000 --- a/decoders_test.go +++ /dev/null @@ -1,6 +0,0 @@ -package kong - -import "testing" - -func TestDecoders(t *testing.T) { -} diff --git a/help.go b/help.go index 940c4f9..5c54764 100644 --- a/help.go +++ b/help.go @@ -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, diff --git a/kong.go b/kong.go index 95c67c8..f27c463 100644 --- a/kong.go +++ b/kong.go @@ -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 } diff --git a/mapper.go b/mapper.go new file mode 100644 index 0000000..5ee10ce --- /dev/null +++ b/mapper.go @@ -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 + } +} diff --git a/mapper_test.go b/mapper_test.go new file mode 100644 index 0000000..a9f819a --- /dev/null +++ b/mapper_test.go @@ -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 } diff --git a/model.go b/model.go index f94db84..82bcac2 100644 --- a/model.go +++ b/model.go @@ -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 } diff --git a/options.go b/options.go index 1075c58..481b06f 100644 --- a/options.go +++ b/options.go @@ -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 } }