package gobang import ( "cmp" "crypto/rand" "errors" "fmt" "io" "log/slog" "math/big" "os" "reflect" "regexp" "runtime" "runtime/debug" "slices" "strings" "time" "guuid" ) type LogLevel int8 const ( LevelNone LogLevel = 0 LevelError LogLevel = 1 LevelWarning LogLevel = 2 LevelInfo LogLevel = 3 LevelDebug LogLevel = 4 ) type SetT[T comparable] struct{ data map[T]struct{} } type PairT[A any, B any] struct{ L A R B } type Gauge struct { Inc func(...any) Dec func(...any) } type CopyResult struct { Written int64 Err error Label string } const ( SQLiteNow = "strftime('%Y-%m-%dT%H:%M:%f000000Z', 'now')" ) var ( level LogLevel = LevelInfo emitMetric = true hostname string testOutput io.Writer = os.Stderr testExitFn = os.Exit randomReader = rand.Reader SourceInfoSkip = 3 ErrBadSQLTablePrefix = errors.New("Invalid table prefix") ) func SetOf[T comparable](values ...T) SetT[T] { s := SetT[T]{ data: map[T]struct{}{ }, } for _, value := range values { s.data[value] = struct{}{} } return s } func Contains[T comparable](set SetT[T], value T) bool { _, ok := set.data[value] return ok } func MapIndexed[A any, B any](fn func(A, int) B, coll []A) []B { out := make([]B, len(coll)) for i, x := range coll { out[i] = fn(x, i) } return out } func Map[A any, B any](fn func(A) B, coll []A) []B { return MapIndexed(func(x A, _ int) B { return fn(x) }, coll) } func Filter[A any](fn func(A) bool, coll []A) []A { out := []A{} for _, x := range coll { if fn(x) { out = append(out, x) } } return out } func PanicIf(err error) { if err != nil { panic(err) } } func Must[T any](x T, err error) T { PanicIf(err) return x } func Clamp[T cmp.Ordered](n T, minimum T, maximum T) T { return min(maximum, max(minimum, n)) } var _SQLTablePrefixRE = regexp.MustCompilePOSIX("^[a-zA-Z][_a-zA-z0-9]*$") func ValidateSQLTablePrefix(prefix string) error { if !_SQLTablePrefixRE.MatchString(prefix) { return ErrBadSQLTablePrefix } return nil } func WrapErrors(errs ...error) error { slices.Reverse(errs) var out error for _, err := range errs { if err != nil { if out == nil { out = err } else { out = fmt.Errorf( "error %w on top of %w", err, out, ) } } } return out } func SomeError(errs ...error) error { for _, err := range errs { if err != nil { return err } } return nil } func SomeFnError(fns ...func() error) error { errs := make([]error, len(fns)) for i, fn := range fns { if fn != nil { errs[i] = fn() } } return SomeError(errs...) } func Random(length int) ([]byte, error) { buffer := make([]byte, length) _, err := io.ReadFull(randomReader, buffer) if err != nil { return nil, err } return buffer, nil } func sourceInfo(skip int) slog.Attr { pc := make([]uintptr, 10) n := runtime.Callers(skip, pc) if n == 0 { return slog.Group( "src", "file", "UNAVAILABLE", "function", "UNAVAILABLE", "line", "UNAVAILABLE", ) } pc = pc[:n] frames := runtime.CallersFrames(pc) frame, _ := frames.Next() return slog.Group( "src", "file", frame.File, "function", frame.Function, "line", frame.Line, ) } func logArgs(type_ string) []string { return []string { "id", guuid.New().String(), "kind", "log", "type", type_, } } func anyArr[S ~[]E, E any](arr S) []any { ret := make([]any, len(arr)) for i , el := range arr { ret[i] = el } return ret } func Debug(message string, type_ string, args ...any) { if level < LevelDebug { return } slog.Debug( message, slices.Concat( anyArr(logArgs(type_)), []any { sourceInfo(SourceInfoSkip) }, args, )..., ) } func Info(message string, type_ string, args ...any) { if level < LevelInfo { return } slog.Info( message, slices.Concat( anyArr(logArgs(type_)), []any { sourceInfo(SourceInfoSkip) }, args, )..., ) } func Warning(message string, type_ string, args ...any) { if level < LevelWarning { return } slog.Warn( message, slices.Concat( anyArr(logArgs(type_)), []any { sourceInfo(SourceInfoSkip) }, args, )..., ) } func Error(message string, type_ string, args ...any) { if level < LevelError { return } slog.Error( message, slices.Concat( anyArr(logArgs(type_)), []any { sourceInfo(SourceInfoSkip) }, args, )..., ) } func metric(type_ string, label string, args ...any) { if !emitMetric { return } slog.Info( "_", slices.Concat( []any { "id", guuid.New().String(), "kind", "metric", "type", type_, "label", label, }, []any { sourceInfo(SourceInfoSkip) }, args, )..., ) } func Timed(label string, thunk func(), args ...any) { var ( start time.Time end time.Time ) { start = time.Now() thunk() end = time.Now() } duration := end.Sub(start) metric( "timer", label, slices.Concat( []any{ "start", start, "end", end, "duration", duration, }, args, )..., ) } func MakeCounter(label string, staticArgs ...any) func(...any) { return func(dynamicArgs ...any) { metric( "counter", label, slices.Concat( []any { "value", 1 }, staticArgs, dynamicArgs, )..., ) } } func MakeGauge(label string, staticArgs ...any) Gauge { var zero = big.NewInt(0) var one = big.NewInt(1) count := big.NewInt(0) emitGauge := func(dynamicArgs ...any) { if count.Cmp(zero) == -1 { Error( "Gauge went negative", "process-metric", slices.Concat( []any { "value", count }, staticArgs, dynamicArgs, )..., ) return // avoid wrong metrics being emitted } metric( "gauge", label, slices.Concat( []any { "value", count }, staticArgs, dynamicArgs, )..., ) } return Gauge { Inc: func(dynamicArgs ...any) { count.Add(count, one) emitGauge(dynamicArgs...) }, Dec: func(dynamicArgs ...any) { count.Sub(count, one) emitGauge(dynamicArgs...) }, } } func showColour() bool { return os.Getenv("NO_COLOUR") == "" } func TestStart(name string) { fmt.Fprintf(testOutput, "%s:\n", name) } func Testing(message string, body func()) { if showColour() { fmt.Fprintf( testOutput, "\033[0;33mtesting\033[0m: %s... ", message, ) body() fmt.Fprint(testOutput, "\033[0;32mOK\033[0m.\n") } else { fmt.Fprintf(testOutput, "testing: %s...", message) body() fmt.Fprint(testOutput, " OK.\n") } } func terr() { if showColour() { fmt.Fprintf(testOutput, "\033[0;31mERR\033[0m") } else { fmt.Fprintf(testOutput, "ERR") } _, file, line, ok := runtime.Caller(2) if ok { fmt.Fprintf(testOutput, " (%s:%d)", file, line) } fmt.Fprintf(testOutput, ".\n") } func TAssertEqual(given any, expected any) { if !reflect.DeepEqual(given, expected) { terr() fmt.Fprintf(testOutput, "given != expected\n") fmt.Fprintf(testOutput, "given: %#v\n", given) fmt.Fprintf(testOutput, "expected: %#v\n", expected) testExitFn(100) } } func TAssertEqualS(given any, expected any, message string) { if !reflect.DeepEqual(given, expected) { terr() fmt.Fprintf(testOutput, "message: %s\n", message) fmt.Fprintf(testOutput, "given != expected\n") fmt.Fprintf(testOutput, "given: %#v\n", given) fmt.Fprintf(testOutput, "expected: %#v\n", expected) testExitFn(100) } } func TAssertEqualI[T any](givenarr []T, expectedarr []T) { givenlen := len(givenarr) expectedlen := len(expectedarr) if givenlen < expectedlen { terr() fmt.Fprintf( testOutput, "expected has %d more elements:\n%#v\n", expectedlen - givenlen, expectedarr[givenlen:], ) testExitFn(100) return } if givenlen > expectedlen { terr() fmt.Fprintf( testOutput, "given has %d more elements:\n%#v\n", givenlen - expectedlen, givenarr[expectedlen:], ) testExitFn(100) return } for i, _ := range givenarr { given := givenarr[i] expected := expectedarr[i] if !reflect.DeepEqual(given, expected) { terr() fmt.Fprintf( testOutput, "given != expected (i = %d)\n", i, ) fmt.Fprintf(testOutput, "given: %#v\n", given) fmt.Fprintf(testOutput, "expected: %#v\n", expected) testExitFn(100) } } } func TErrorIf(err error) { if err != nil { terr() fmt.Fprintf(testOutput, "Unexpected error: %#v\n", err) testExitFn(100) } } func TErrorNil(err error) { if err == nil { terr() fmt.Fprintf(testOutput, "Expected error, got nil\n") testExitFn(100) } } var unfilteringLevel = new(slog.LevelVar) func SetLoggerOutput(w io.Writer, args ...any) { unfilteringLevel.Set(slog.LevelDebug) slog.SetDefault(slog.New(slog.NewJSONHandler(w, &slog.HandlerOptions { Level: unfilteringLevel, })).With( slog.Group( "info", "pid", os.Getpid(), "ppid", os.Getppid(), "puuid", guuid.New().String(), ), ).With(args...)) } func levelFromString(name string, fallback LogLevel) LogLevel { switch strings.ToUpper(name) { case "NONE": return LevelNone case "ERROR": return LevelError case "WARNING": return LevelWarning case "INFO": return LevelInfo case "DEBUG": return LevelDebug default: return fallback } } func setLogLevel() { level = levelFromString(os.Getenv("LOG_LEVEL"), level) } func SetLevel(l LogLevel) { level = l } func setMetric() { if os.Getenv("NO_METRIC") != "" { emitMetric = false } } func setTraceback() { if os.Getenv("GOTRACEBACK") == "" { debug.SetTraceback("crash") } } func Fatal(err error) { Error( "Fatal error", "fatal-error", "error", err, "stack", string(debug.Stack()), ) panic(err) } func FatalIf(err error) { if err != nil { Fatal(err) } } func Assert(condition bool, message string) { if !condition { Fatal(errors.New("assertion failed: " + message)) } } func Unreachable() { Assert(false, "Unreachable code was reached") } func setHostname() { var err error hostname, err = os.Hostname() FatalIf(err) } func Init(args ...any) { SetLoggerOutput(os.Stdout, args...) setLogLevel() setMetric() setTraceback() setHostname() }