wand/apply.go

255 lines
5.2 KiB
Go
Raw Normal View History

2025-08-18 14:24:31 +02:00
package wand
import (
"github.com/iancoleman/strcase"
2025-08-24 01:45:25 +02:00
"io"
2025-08-18 14:24:31 +02:00
"reflect"
"strings"
)
func ensurePointerAllocation(p reflect.Value, n int) {
if p.IsNil() {
p.Set(reflect.New(p.Type().Elem()))
}
ensureAllocation(p.Elem(), n)
}
func ensureSliceAllocation(s reflect.Value, n int) {
if s.Len() < n {
a := reflect.MakeSlice(s.Type(), n-s.Len(), n-s.Len())
a = reflect.AppendSlice(s, a)
s.Set(a)
}
if s.Len() > n {
a := s.Slice(0, n)
s.Set(a)
}
for i := 0; i < s.Len(); i++ {
ensureAllocation(s.Index(i), 1)
}
}
func ensureAllocation(v reflect.Value, n int) {
switch v.Type().Kind() {
case reflect.Pointer:
ensurePointerAllocation(v, n)
case reflect.Slice:
ensureSliceAllocation(v, n)
}
}
func setPointerValue(p reflect.Value, v []value) {
setFieldValue(p.Elem(), v)
}
func setSliceValue(s reflect.Value, v []value) {
for i := 0; i < s.Len(); i++ {
setFieldValue(s.Index(i), v[i:i+1])
}
}
func setValue(f reflect.Value, v value) {
if v.isBool {
f.Set(reflect.ValueOf(v.boolean))
return
}
f.Set(reflect.ValueOf(scan(f.Type(), v.str)))
}
func setFieldValue(field reflect.Value, v []value) {
switch field.Kind() {
case reflect.Pointer:
setPointerValue(field, v)
case reflect.Slice:
setSliceValue(field, v)
default:
setValue(field, v[0])
}
}
func setField(s reflect.Value, name string, v []value) {
for i := 0; i < s.Type().NumField(); i++ {
fs := s.Type().Field(i)
fname := strcase.ToKebab(fs.Name)
ft := fs.Type
ftup := unpack(ft)
fv := s.Field(i)
switch {
case !fs.Anonymous && fname == name:
ensureAllocation(fv, len(v))
setFieldValue(fv, v)
case !fs.Anonymous && ftup.Kind() == reflect.Struct:
prefix := fname + "-"
if strings.HasPrefix(name, prefix) {
ensureAllocation(fv, len(v))
setField(unpack(fv), name[len(prefix):], v)
}
case fs.Anonymous:
ensureAllocation(fv, 1)
setField(unpack(fv), name, v)
}
}
}
2025-08-24 01:45:25 +02:00
func createStructArg(t reflect.Type, shortForms []string, c config, e env, o []option) (reflect.Value, bool) {
2025-08-18 14:24:31 +02:00
tup := unpack(t)
f := fields(tup)
fn := make(map[string]bool)
for _, fi := range f {
fn[fi.name] = true
}
ms := make(map[string]string)
for i := 0; i < len(shortForms); i += 2 {
l, s := shortForms[i], shortForms[i+1]
ms[s] = l
}
om := make(map[string][]option)
for _, oi := range o {
n := oi.name
if l, ok := ms[n]; ok && oi.shortForm {
n = l
}
om[n] = append(om[n], oi)
}
2025-08-24 01:45:25 +02:00
var foundConfig []string
for n := range c.values {
if fn[n] {
foundConfig = append(foundConfig, n)
}
}
2025-08-18 14:24:31 +02:00
var foundEnv []string
for n := range e.values {
if fn[n] {
foundEnv = append(foundEnv, n)
}
}
var foundOptions []string
for n := range om {
if fn[n] {
foundOptions = append(foundOptions, n)
}
}
2025-08-24 01:45:25 +02:00
if len(foundConfig) == 0 && len(foundEnv) == 0 && len(foundOptions) == 0 {
2025-08-18 14:24:31 +02:00
return reflect.Zero(t), false
}
p := reflect.New(tup)
2025-08-24 01:45:25 +02:00
for _, n := range foundConfig {
var v []value
for _, vi := range c.values[n] {
v = append(v, stringValue(vi))
}
setField(p.Elem(), n, v)
}
2025-08-18 14:24:31 +02:00
for _, n := range foundEnv {
var v []value
for _, vi := range e.values[n] {
v = append(v, stringValue(vi))
}
setField(p.Elem(), n, v)
}
for _, n := range foundOptions {
var v []value
for _, oi := range om[n] {
v = append(v, oi.value)
}
setField(p.Elem(), n, v)
}
return pack(p.Elem(), t), true
}
func createPositional(t reflect.Type, v string) reflect.Value {
2025-08-26 03:21:35 +02:00
if t.Kind() == reflect.Interface {
return reflect.ValueOf(v)
}
2025-08-18 14:24:31 +02:00
tup := unpack(t)
sv := reflect.ValueOf(scan(tup, v))
return pack(sv, t)
}
2025-08-24 01:45:25 +02:00
func createArgs(stdin io.Reader, stdout io.Writer, t reflect.Type, shortForms []string, c config, e env, cl commandLine) []reflect.Value {
2025-08-18 14:24:31 +02:00
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:
2025-08-24 01:45:25 +02:00
args = append(args, reflect.ValueOf(stdin))
2025-08-18 14:24:31 +02:00
case iow:
2025-08-24 01:45:25 +02:00
args = append(args, reflect.ValueOf(stdout))
2025-08-18 14:24:31 +02:00
case structure && variadic:
2025-08-24 01:45:25 +02:00
if arg, ok := createStructArg(ti, shortForms, c, e, cl.options); ok {
2025-08-18 14:24:31 +02:00
args = append(args, arg)
}
case structure:
2025-08-24 01:45:25 +02:00
arg, _ := createStructArg(ti, shortForms, c, e, cl.options)
2025-08-18 14:24:31 +02:00
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
2025-08-26 03:21:35 +02:00
isErrorType := t.Out(last) == reflect.TypeFor[error]()
2025-08-18 14:24:31 +02:00
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
}
2025-08-24 01:45:25 +02:00
func apply(stdin io.Reader, stdout io.Writer, cmd Cmd, c config, e env, cl commandLine) ([]any, error) {
2025-08-18 14:24:31 +02:00
v := reflect.ValueOf(cmd.impl)
v = unpack(v)
t := v.Type()
2025-08-24 01:45:25 +02:00
args := createArgs(stdin, stdout, t, cmd.shortForms, c, e, cl)
2025-08-18 14:24:31 +02:00
out := v.Call(args)
return processResults(t, out)
}