255 lines
5.2 KiB
Go
255 lines
5.2 KiB
Go
package wand
|
|
|
|
import (
|
|
"github.com/iancoleman/strcase"
|
|
"io"
|
|
"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)
|
|
}
|
|
}
|
|
}
|
|
|
|
func createStructArg(t reflect.Type, shortForms []string, c config, e env, o []option) (reflect.Value, bool) {
|
|
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)
|
|
}
|
|
|
|
var foundConfig []string
|
|
for n := range c.values {
|
|
if fn[n] {
|
|
foundConfig = append(foundConfig, n)
|
|
}
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|
|
|
|
if len(foundConfig) == 0 && len(foundEnv) == 0 && len(foundOptions) == 0 {
|
|
return reflect.Zero(t), false
|
|
}
|
|
|
|
p := reflect.New(tup)
|
|
for _, n := range foundConfig {
|
|
var v []value
|
|
for _, vi := range c.values[n] {
|
|
v = append(v, stringValue(vi))
|
|
}
|
|
|
|
setField(p.Elem(), n, v)
|
|
}
|
|
|
|
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 {
|
|
if t.Kind() == reflect.Interface {
|
|
return reflect.ValueOf(v)
|
|
}
|
|
|
|
tup := unpack(t)
|
|
sv := reflect.ValueOf(scan(tup, v))
|
|
return pack(sv, t)
|
|
}
|
|
|
|
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 = unpack(v)
|
|
t := v.Type()
|
|
args := createArgs(stdin, stdout, t, cmd.shortForms, c, e, cl)
|
|
out := v.Call(args)
|
|
return processResults(t, out)
|
|
}
|