wand/apply.go
2025-09-01 04:10:35 +02:00

128 lines
3.0 KiB
Go

package wand
import (
"io"
"reflect"
)
func bindKeyVals(receiver reflect.Value, keyVals map[string][]string) bool {
v := make(map[string][]any)
for name, values := range keyVals {
for _, vi := range values {
v[name] = append(v[name], vi)
}
}
u := bindFields(receiver, v)
return len(v) > 0 && len(u) < len(v)
}
func bindOptions(receiver reflect.Value, shortForms []string, o []option) bool {
ms := make(map[string]string)
for i := 0; i < len(shortForms); i += 2 {
ms[shortForms[i]] = shortForms[i+1]
}
v := make(map[string][]any)
for _, oi := range o {
n := oi.name
if oi.shortForm {
n = ms[n]
}
var val any
if oi.value.isBool {
val = oi.value.boolean
} else {
val = oi.value.str
}
v[n] = append(v[n], val)
}
u := bindFields(receiver, v)
return len(v) > 0 && len(u) < len(v)
}
func createStructArg(t reflect.Type, shortForms []string, c config, e env, o []option) (reflect.Value, bool) {
r := allocate(reflect.PointerTo(t))
hasConfigMatches := bindKeyVals(r, c.values)
hasEnvMatches := bindKeyVals(r, e.values)
hasOptionMatches := bindOptions(r, shortForms, o)
return r.Elem(), hasConfigMatches || hasEnvMatches || hasOptionMatches
}
func createPositional(t reflect.Type, v string) reflect.Value {
r := allocate(reflect.PointerTo(t))
bindScalar(r, v)
return r.Elem()
}
func createArgs(stdin io.Reader, stdout io.Writer, t reflect.Type, shortForms []string, c config, e env, cl commandLine) []reflect.Value {
var args []reflect.Value
positional := cl.positional
for i := 0; i < t.NumIn(); i++ {
ti := t.In(i)
structure := isStruct(ti)
variadic := t.IsVariadic() && i == t.NumIn()-1
ior := isReader(ti)
iow := isWriter(ti)
switch {
case ior:
args = append(args, reflect.ValueOf(stdin))
case iow:
args = append(args, reflect.ValueOf(stdout))
case structure && variadic:
if arg, ok := createStructArg(ti, shortForms, c, e, cl.options); ok {
args = append(args, arg)
}
case structure:
arg, _ := createStructArg(ti, shortForms, c, e, cl.options)
args = append(args, arg)
case variadic:
for _, p := range positional {
args = append(args, createPositional(ti.Elem(), p))
}
default:
var p string
p, positional = positional[0], positional[1:]
args = append(args, createPositional(ti, p))
}
}
return args
}
func processResults(t reflect.Type, out []reflect.Value) ([]any, error) {
if len(out) == 0 {
return nil, nil
}
var err error
last := len(out) - 1
isErrorType := t.Out(last) == reflect.TypeFor[error]()
if isErrorType && !out[last].IsZero() {
err = out[last].Interface().(error)
}
if isErrorType {
out = out[:last]
}
var values []any
for _, o := range out {
values = append(values, o.Interface())
}
return values, err
}
func apply(stdin io.Reader, stdout io.Writer, cmd Cmd, c config, e env, cl commandLine) ([]any, error) {
v := reflect.ValueOf(cmd.impl)
v = unpackValue(v)
t := v.Type()
args := createArgs(stdin, stdout, t, cmd.shortForms, c, e, cl)
out := v.Call(args)
return processResults(t, out)
}