~ knotless Search/
↑↓ move↵ open⇧↵ all results
0
0
main
145 lines · 2.5 KB Raw History
package baosync

import (
	"encoding/json"
	"fmt"
	"slices"
	"sort"
	"strconv"
	"strings"
	"time"
)

func Drifted(want, got map[string]any, skip ...string) string {
	keys := make([]string, 0, len(want))
	for k := range want {
		if !slices.Contains(skip, k) {
			keys = append(keys, k)
		}
	}
	sort.Strings(keys)
	for _, k := range keys {
		if !same(want[k], got[k]) {
			return k
		}
	}
	return ""
}

func empty(v any) bool {
	switch x := v.(type) {
	case nil:
		return true
	case string:
		return x == ""
	case []string:
		return len(x) == 0
	case []any:
		return len(x) == 0
	case bool:
		return !x
	case int:
		return x == 0
	case float64:
		return x == 0
	}
	return false
}

func number(v any) (float64, bool) {
	switch x := v.(type) {
	case float64:
		return x, true
	case int:
		return float64(x), true
	case int64:
		return float64(x), true
	case json.Number:
		f, err := x.Float64()
		return f, err == nil
	}
	return 0, false
}

func strs(v any) ([]string, bool) {
	switch x := v.(type) {
	case []string:
		return x, true
	case []any:
		out := make([]string, 0, len(x))
		for _, e := range x {
			out = append(out, fmt.Sprint(e))
		}
		return out, true
	case string:
		if x == "" {
			return nil, true
		}
		parts := strings.Split(x, ",")
		for i := range parts {
			parts[i] = strings.TrimSpace(parts[i])
		}
		return parts, true
	}
	return nil, false
}

func sameSet(a, b []string) bool {
	x, y := slices.Clone(a), slices.Clone(b)
	for i := range x {
		x[i] = strings.ToLower(x[i])
	}
	for i := range y {
		y[i] = strings.ToLower(y[i])
	}
	sort.Strings(x)
	sort.Strings(y)
	return slices.Equal(slices.Compact(x), slices.Compact(y))
}

func seconds(s string) (float64, bool) {
	if s == "" {
		return 0, true
	}
	if n, err := strconv.ParseFloat(s, 64); err == nil {
		return n, true
	}
	d, err := time.ParseDuration(s)
	if err != nil {
		return 0, false
	}
	return d.Seconds(), true
}

func same(want, got any) bool {
	if got == nil {
		return empty(want)
	}
	switch w := want.(type) {
	case string:
		switch g := got.(type) {
		case string:
			return strings.TrimSpace(w) == strings.TrimSpace(g)
		case bool:
			return w == strconv.FormatBool(g)
		case []any:
			ws, _ := strs(w)
			gs, _ := strs(g)
			return sameSet(ws, gs)
		}
		if n, ok := number(got); ok {
			s, ok := seconds(w)
			return ok && s == n
		}
	case []string:
		gs, ok := strs(got)
		return ok && sameSet(w, gs)
	case bool:
		g, ok := got.(bool)
		return ok && g == w
	case int:
		n, ok := number(got)
		return ok && n == float64(w)
	}
	return fmt.Sprint(want) == fmt.Sprint(got)
}