bf0cbf5d7c
The former allows arbitrary structs to be embedded in the root of the CLI, with optional tags. The latter allows an arbitrary function to be called using Kong's binding functionality.
138 lines
3.2 KiB
Go
138 lines
3.2 KiB
Go
package kong
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"strings"
|
|
)
|
|
|
|
type bindings map[reflect.Type]func() (reflect.Value, error)
|
|
|
|
func (b bindings) String() string {
|
|
out := []string{}
|
|
for k := range b {
|
|
out = append(out, k.String())
|
|
}
|
|
return "bindings{" + strings.Join(out, ", ") + "}"
|
|
}
|
|
|
|
func (b bindings) add(values ...interface{}) bindings {
|
|
for _, v := range values {
|
|
v := v
|
|
b[reflect.TypeOf(v)] = func() (reflect.Value, error) { return reflect.ValueOf(v), nil }
|
|
}
|
|
return b
|
|
}
|
|
|
|
func (b bindings) addTo(impl, iface interface{}) {
|
|
valueOf := reflect.ValueOf(impl)
|
|
b[reflect.TypeOf(iface).Elem()] = func() (reflect.Value, error) { return valueOf, nil }
|
|
}
|
|
|
|
func (b bindings) addProvider(provider interface{}) error {
|
|
pv := reflect.ValueOf(provider)
|
|
t := pv.Type()
|
|
if t.Kind() != reflect.Func || t.NumIn() != 0 || t.NumOut() != 2 || t.Out(1) != reflect.TypeOf((*error)(nil)).Elem() {
|
|
return fmt.Errorf("%T must be a function with the signature func()(T, error)", provider)
|
|
}
|
|
rt := pv.Type().Out(0)
|
|
b[rt] = func() (reflect.Value, error) {
|
|
out := pv.Call(nil)
|
|
errv := out[1]
|
|
var err error
|
|
if !errv.IsNil() {
|
|
err = errv.Interface().(error) // nolint
|
|
}
|
|
return out[0], err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Clone and add values.
|
|
func (b bindings) clone() bindings {
|
|
out := make(bindings, len(b))
|
|
for k, v := range b {
|
|
out[k] = v
|
|
}
|
|
return out
|
|
}
|
|
|
|
func (b bindings) merge(other bindings) bindings {
|
|
for k, v := range other {
|
|
b[k] = v
|
|
}
|
|
return b
|
|
}
|
|
|
|
func getMethod(value reflect.Value, name string) reflect.Value {
|
|
method := value.MethodByName(name)
|
|
if !method.IsValid() {
|
|
if value.CanAddr() {
|
|
method = value.Addr().MethodByName(name)
|
|
}
|
|
}
|
|
return method
|
|
}
|
|
|
|
func callFunction(f reflect.Value, bindings bindings) error {
|
|
if f.Kind() != reflect.Func {
|
|
return fmt.Errorf("expected function, got %s", f.Type())
|
|
}
|
|
in := []reflect.Value{}
|
|
t := f.Type()
|
|
if t.NumOut() != 1 || !t.Out(0).Implements(callbackReturnSignature) {
|
|
return fmt.Errorf("return value of %s must implement \"error\"", t)
|
|
}
|
|
for i := 0; i < t.NumIn(); i++ {
|
|
pt := t.In(i)
|
|
if argf, ok := bindings[pt]; ok {
|
|
argv, err := argf()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
in = append(in, argv)
|
|
} else {
|
|
return fmt.Errorf("couldn't find binding of type %s for parameter %d of %s(), use kong.Bind(%s)", pt, i, t, pt)
|
|
}
|
|
}
|
|
out := f.Call(in)
|
|
if out[0].IsNil() {
|
|
return nil
|
|
}
|
|
return out[0].Interface().(error) // nolint
|
|
}
|
|
|
|
func callAnyFunction(f reflect.Value, bindings bindings) (out []any, err error) {
|
|
if f.Kind() != reflect.Func {
|
|
return nil, fmt.Errorf("expected function, got %s", f.Type())
|
|
}
|
|
in := []reflect.Value{}
|
|
t := f.Type()
|
|
for i := 0; i < t.NumIn(); i++ {
|
|
pt := t.In(i)
|
|
if argf, ok := bindings[pt]; ok {
|
|
argv, err := argf()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
in = append(in, argv)
|
|
} else {
|
|
return nil, fmt.Errorf("couldn't find binding of type %s for parameter %d of %s(), use kong.Bind(%s)", pt, i, t, pt)
|
|
}
|
|
}
|
|
outv := f.Call(in)
|
|
out = make([]any, len(outv))
|
|
for i, v := range outv {
|
|
out[i] = v.Interface()
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func callMethod(name string, v, f reflect.Value, bindings bindings) error {
|
|
err := callFunction(f, bindings)
|
|
if err != nil {
|
|
return fmt.Errorf("%s.%s(): %w", v.Type(), name, err)
|
|
}
|
|
return nil
|
|
}
|