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 { 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.TypeOf(err) 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) }