package wand import ( "reflect" "code.squareroundforest.org/arpio/bind" "io" "time" "strings" ) func filter[T any](list []T, predicate func(T) bool) []T { var filtered []T for _, item := range list { if predicate(item) { filtered = append(filtered, item) } } return filtered } func not[T any](p func(T) bool) func(T) bool { return func(v T) bool { return !p(v) } } func and[T any](p ...func(T) bool) func(T) bool { return func(v T) bool { for _, pi := range p { if !pi(v) { return false } } return true } } func or[T any](p ...func(T) bool) func(T) bool { return func(v T) bool { for _, pi := range p { if pi(v) { return true } } return false } } func unpackTypeChecked(visited map[reflect.Type]bool, t reflect.Type) reflect.Type { if t == nil { return t } if visited[t] { return t } if visited == nil { visited = make(map[reflect.Type]bool) } visited[t] = true switch t.Kind() { case reflect.Pointer, reflect.Slice: return unpackTypeChecked(visited, t.Elem()) default: return t } } func unpackType(t reflect.Type) reflect.Type { return unpackTypeChecked(nil, t) } func unpackValueChecked(visited map[uintptr]bool, v reflect.Value) reflect.Value { if !v.IsValid() { return v } switch v.Kind() { case reflect.Pointer: p := v.Pointer() if visited[p] { return v } if visited == nil { visited = make(map[uintptr]bool) } visited[p] = true return unpackValueChecked(visited, v.Elem()) case reflect.Interface: if v.IsNil() { return v } return unpackValueChecked(visited, v.Elem()) default: return v } } func unpackValue(v reflect.Value) reflect.Value { return unpackValueChecked(nil, v) } func isFunc(v any) bool { r := reflect.ValueOf(v) r = unpackValue(r) return r.Kind() == reflect.Func } func isTime(t reflect.Type) bool { if t == nil { return false } return t.ConvertibleTo(reflect.TypeFor[time.Time]()) } func isStruct(t reflect.Type) bool { if t == nil { return false } t = unpackType(t) return !isTime(t) && t.Kind() == reflect.Struct } func isReader(t reflect.Type) bool { if t == nil || t.Kind() != reflect.Interface { return false } return t.NumMethod() == 1 && t.Implements(reflect.TypeFor[io.Reader]()) } func isWriter(t reflect.Type) bool { if t == nil || t.Kind() != reflect.Interface { return false } return t.NumMethod() == 1 && t.Implements(reflect.TypeFor[io.Writer]()) } func compatibleTypes(t ...bind.Scalar) bool { if len(t) == 0 { return false } if len(t) == 1 { return true } switch t[0] { case bind.Any: return compatibleTypes(t[1:]...) default: return t[0] == t[1] && compatibleTypes(t[1:]...) } } func parameters(f any) []reflect.Type { r := reflect.ValueOf(f) r = unpackValue(r) if r.Kind() != reflect.Func { return nil } var p []reflect.Type t := r.Type() for i := 0; i < t.NumIn(); i++ { p = append(p, t.In(i)) } return p } func structParameters(f any) []reflect.Type { return filter(parameters(f), isStruct) } func structFields(s reflect.Type) []bind.Field { s = unpackType(s) v := reflect.Zero(s) return bind.FieldValues(v) } func fields(f any) []bind.Field { var fields []bind.Field s := structParameters(fields) for _, si := range s { fields = append(fields, structFields(si)...) } return fields } func mapFields(f any) map[string][]bind.Field { fields := fields(fields) m := make(map[string][]bind.Field) for _, fi := range fields { m[fi.Name()] = append(m[fi.Name()], fi) } return m } func boolFields(f []bind.Field) []bind.Field { return filter( fields(f), func(f bind.Field) bool { return f.Type() == bind.Bool }, ) } func positional(f any) ([]reflect.Type, bool) { p := filter( parameters(f), not(or(isReader, isWriter, isStruct)), ) r := reflect.ValueOf(f) r = unpackValue(r) t := r.Type() return p, t.IsVariadic() } func positionalIndices(f any) []int { r := reflect.ValueOf(f) r = unpackValue(r) if r.Kind() != reflect.Func { return nil } var indices []int t := r.Type() for i := 0; i < t.NumIn(); i++ { p := t.In(i) if isTime(p) || isStruct(p) || isReader(p) || isWriter(p) { continue } indices = append(indices, i) } return indices } func ioParameters(f any) ([]reflect.Type, []reflect.Type) { p := parameters(f) return filter(p, isReader), filter(p, isWriter) } func bindable(t reflect.Type) bool { if t == nil { return false } if isTime(t) { return true } if isStruct(t) { return true } switch t.Kind() { case reflect.Bool, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64, reflect.String: return true case reflect.Interface: return t.NumMethod() == 0 case reflect.Slice: return bindable(t.Elem()) default: return false } } func scalarTypeString(t bind.Scalar) string { r := reflect.TypeOf(t) p := strings.Split(r.Name(), ".") n := p[len(p) - 1] n = strings.ToLower(n) return n } func canScan(t bind.Scalar, v any) bool { switch t { case bind.Any: return true case bind.Bool: _, ok := bind.BindScalarCreate[bool](v) return ok case bind.Int: _, ok := bind.BindScalarCreate[int](v) return ok case bind.Uint: _, ok := bind.BindScalarCreate[uint](v) return ok case bind.Float: _, ok := bind.BindScalarCreate[float64](v) return ok case bind.String: _, ok := bind.BindScalarCreate[string](v) return ok case bind.Duration: _, ok := bind.BindScalarCreate[time.Duration](v) return ok case bind.Time: _, ok := bind.BindScalarCreate[time.Time](v) return ok default: return false } } func canScanType(t reflect.Type, v any) bool { if t == nil { return false } r := reflect.Zero(t) return bind.BindScalar(r.Interface(), v) } func allocate(t reflect.Type) reflect.Value { switch t.Kind() { case reflect.Pointer: et := t.Elem() v := allocate(et) p := reflect.New(et) p.Elem().Set(v) return p case reflect.Slice: v := allocate(t.Elem()) s := reflect.MakeSlice(t, 1, 1) s.Index(0).Set(v) return s default: return reflect.Zero(t) } } func bindScalar(receiver reflect.Value, value any) { bind.BindScalar(receiver.Interface(), value) } func bindFields(receiver reflect.Value, values map[string][]any) []string { var f []bind.Field for name, value := range values { f = append(f, bind.NamedValue(name, value)) } unmapped := bind.BindFields(receiver, f...) var names []string for _, um := range unmapped { names = append(names, um.Name()) } return names }