summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEuAndreh <eu@euandre.org>2024-05-16 11:58:57 -0300
committerEuAndreh <eu@euandre.org>2024-05-16 14:17:41 -0300
commit6291643ff88e50579bdbd0823b2b1846c9fd6d09 (patch)
tree90e7a2a516f70d1c30c80e2a4bcf80f17c684d4b
parentInitial empty commit (diff)
downloadgobang-6291643ff88e50579bdbd0823b2b1846c9fd6d09.tar.gz
gobang-6291643ff88e50579bdbd0823b2b1846c9fd6d09.tar.xz
Initial implementation: include code from papod
-rw-r--r--.gitignore2
-rw-r--r--Makefile104
-rw-r--r--go.mod3
-rw-r--r--src/cmd/main.go7
-rw-r--r--src/export_test.go3
-rw-r--r--src/lib.go283
-rwxr-xr-xtests/cli-opts.sh4
-rw-r--r--tests/lib_test.go53
8 files changed, 459 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..85a298a
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+/*.bin
+/tests/*.bin
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..fdf1423
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,104 @@
+.POSIX:
+DATE = 1970-01-01
+VERSION = 0.1.0
+NAME = gobang
+NAME_UC = $(NAME)
+LANGUAGES = en
+## Installation prefix. Defaults to "/usr".
+PREFIX = /usr
+BINDIR = $(PREFIX)/bin
+LIBDIR = $(PREFIX)/lib
+INCLUDEDIR = $(PREFIX)/include
+LIBDDIR = $(PREFIX)/lib/$(NAME)
+SRCDIR = $(PREFIX)/src/$(NAME)
+SHAREDIR = $(PREFIX)/share
+LOCALEDIR = $(SHAREDIR)/locale
+MANDIR = $(SHAREDIR)/man
+EXEC = ./
+## Where to store the installation. Empty by default.
+DESTDIR =
+LDLIBS =
+
+
+
+.SUFFIXES:
+.SUFFIXES: .go .bin
+
+
+
+sources = \
+ src/lib.go \
+ src/cmd/main.go \
+
+
+derived-assets = \
+ $(NAME).bin \
+ tests/lib_test.bin \
+
+side-assets = \
+
+
+
+## Default target. Builds all artifacts required for testing
+## and installation.
+all: $(derived-assets)
+
+
+$(NAME).bin: src/lib.go src/cmd/main.go Makefile
+ go build -o $@ -v src/cmd/main.go
+
+tests/lib_test.bin: src/lib.go tests/lib_test.go Makefile
+ go test -c -o $@ -v $*.go
+
+
+
+check-unit: tests/lib_test.bin
+ ./tests/lib_test.bin
+
+
+integration-tests = \
+ tests/cli-opts.sh \
+
+$(integration-tests): $(NAME).bin ALWAYS
+ sh $@ $(EXEC)$(NAME).bin
+
+check-integration: $(integration-tests)
+
+
+## Run all tests. Each test suite is isolated, so that a parallel
+## build can run tests at the same time. The required artifacts
+## are created if missing.
+check: check-unit check-integration
+
+
+
+## Remove *all* derived artifacts produced during the build.
+## A dedicated test asserts that this is always true.
+clean:
+ rm -rf $(derived-assets) $(side-assets)
+
+
+## Installs into $(DESTDIR)$(PREFIX). Its dependency target
+## ensures that all installable artifacts are crafted beforehand.
+install: all
+ mkdir -p \
+ '$(DESTDIR)$(BINDIR)' \
+
+ cp $(NAME).bin '$(DESTDIR)$(BINDIR)'/$(NAME)
+ for f in $(sources); do \
+ dir='$(DESTDIR)$(SRCDIR)'/"`dirname "$${f#src/}"`"; \
+ mkdir -p "$$dir"; \
+ cp -P "$$f" "$$dir"; \
+ done
+
+## Uninstalls from $(DESTDIR)$(PREFIX). This is a perfect mirror
+## of the "install" target, and removes *all* that was installed.
+## A dedicated test asserts that this is always true.
+uninstall:
+ rm -rf \
+ '$(DESTDIR)$(BINDIR)'/$(NAME) \
+ '$(DESTDIR)$(LIBDDIR)' \
+ '$(DESTDIR)$(SRCDIR)' \
+
+
+ALWAYS:
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..c5ea785
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,3 @@
+module euandre.org/gobang
+
+go 1.21.5
diff --git a/src/cmd/main.go b/src/cmd/main.go
new file mode 100644
index 0000000..d5f1346
--- /dev/null
+++ b/src/cmd/main.go
@@ -0,0 +1,7 @@
+package main
+
+import "euandre.org/gobang/src"
+
+func main() {
+ gobang.Main()
+}
diff --git a/src/export_test.go b/src/export_test.go
new file mode 100644
index 0000000..6bb0416
--- /dev/null
+++ b/src/export_test.go
@@ -0,0 +1,3 @@
+package gobang
+
+var Mmain = mmain
diff --git a/src/lib.go b/src/lib.go
new file mode 100644
index 0000000..8b60853
--- /dev/null
+++ b/src/lib.go
@@ -0,0 +1,283 @@
+package gobang
+
+import (
+ "crypto/rand"
+ "encoding/hex"
+ "fmt"
+ "io"
+ "log/slog"
+ "math/big"
+ "os"
+ "runtime/debug"
+ "sync"
+ "syscall"
+ "time"
+)
+
+
+// FIXME: finish rewriting
+//
+// lastV7time is the last time we returned stored as:
+//
+// 52 bits of time in milliseconds since epoch
+// 12 bits of (fractional nanoseconds) >> 8
+var lastV7Time int64
+var timeMu sync.Mutex
+// getV7Time returns the time in milliseconds and nanoseconds / 256.
+// The returned (milli << (12 + seq)) is guaranteed to be greater than
+// (milli << (12 + seq)) returned by any previous call to getV7Time.
+// `seq` Sequence number is between 0 and 3906 (nanoPerMilli >> 8)
+func getV7Time(nano int64) (int64, int64) {
+ const nanoPerMilli = 1000 * 1000
+
+ milli := nano / nanoPerMilli
+ seq := (nano - (milli * nanoPerMilli)) >> 8
+ now := milli << (12 + seq)
+
+ timeMu.Lock()
+ defer timeMu.Unlock()
+ if now <= lastV7Time {
+ now = lastV7Time + 1
+ milli = now >> 12
+ seq = now & 0xfff
+ }
+ lastV7Time = now
+ return milli, seq
+}
+
+const lengthUUID = 16
+
+type UUID struct {
+ bytes [lengthUUID]byte
+}
+
+func NewUUID() UUID {
+ var buf [lengthUUID]byte
+ _, err := io.ReadFull(rand.Reader, buf[7:])
+ if err != nil {
+ panic(err)
+ }
+
+ buf[6] = (buf[6] & 0x0f) | 0x40 // Version 4
+ buf[8] = (buf[8] & 0x3f) | 0x80 // Variant is 10
+
+ t, s := getV7Time(time.Now().UnixNano())
+
+ buf[0] = byte(t >> 40)
+ buf[1] = byte(t >> 32)
+ buf[2] = byte(t >> 24)
+ buf[3] = byte(t >> 16)
+ buf[4] = byte(t >> 8)
+ buf[5] = byte(t >> 0)
+
+ buf[6] = 0x70 | (0x0f & byte(s >> 8))
+ buf[7] = byte(s)
+ return UUID { bytes: buf }
+}
+
+func (uuid UUID) ToString() string {
+ const dashCount = 4
+ const encodedLength = (lengthUUID * 2) + dashCount
+ dst := [encodedLength]byte {
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ '-',
+ 0, 0, 0, 0,
+ '-',
+ 0, 0, 0, 0,
+ '-',
+ 0, 0, 0, 0,
+ '-',
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ }
+
+ hex.Encode(dst[ 0:8], uuid.bytes[0:4])
+ hex.Encode(dst[ 9:13], uuid.bytes[4:6])
+ hex.Encode(dst[14:18], uuid.bytes[6:8])
+ hex.Encode(dst[19:23], uuid.bytes[8:10])
+ hex.Encode(dst[24:36], uuid.bytes[10:])
+
+ return string(dst[:])
+}
+
+
+
+
+func Debug(message string, type_ string, args ...any) {
+ slog.Debug(
+ message,
+ append(
+ []any {
+ "id", NewUUID().ToString(),
+ "kind", "log",
+ "type", type_,
+ },
+ args...,
+ )...,
+ )
+}
+
+func Info(message string, type_ string, args ...any) {
+ slog.Info(
+ message,
+ append(
+ []any {
+ "id", NewUUID().ToString(),
+ "kind", "log",
+ "type", type_,
+ },
+ args...,
+ )...,
+ )
+}
+
+func Warning(message string, type_ string, args ...any) {
+ slog.Warn(
+ message,
+ append(
+ []any {
+ "id", NewUUID().ToString(),
+ "kind", "log",
+ "type", type_,
+ },
+ args...,
+ )...,
+ )
+}
+
+func Error(message string, type_ string, args ...any) {
+ slog.Error(
+ message,
+ append(
+ []any {
+ "id", NewUUID().ToString(),
+ "kind", "log",
+ "type", type_,
+ },
+ args...,
+ )...,
+ )
+}
+
+func Metric(type_ string, label string, args ...any) {
+ slog.Info(
+ "_",
+ append(
+ []any {
+ "id", NewUUID().ToString(),
+ "kind", "metric",
+ "type", type_,
+ "label", label,
+ },
+ args...,
+ )...,
+ )
+}
+
+type Gauge struct {
+ Inc func(...any)
+ Dec func(...any)
+}
+
+var zero = big.NewInt(0)
+var one = big.NewInt(1)
+func MakeGauge(label string, staticArgs ...any) Gauge {
+ count := big.NewInt(0)
+ emitGauge := func(dynamicArgs ...any) {
+ if count.Cmp(zero) == -1 {
+ Error(
+ "Gauge went negative",
+ "process-metric",
+ append(
+ []any { "value", count },
+ append(
+ staticArgs,
+ dynamicArgs...,
+ )...,
+ )...,
+ )
+ return // avoid wrong metrics being emitted
+ }
+ Metric(
+ "gauge", label,
+ // TODO: we'll have slices.Concat on Go 1.22
+ append(
+ []any { "value", count },
+ append(
+ 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 MakeCounter(label string) func(...any) {
+ return func(args ...any) {
+ Metric(
+ "counter", label,
+ append([]any { "value", 1 }, args...)...,
+ )
+ }
+}
+
+func SetLoggerOutput(w io.Writer) {
+ slog.SetDefault(slog.New(slog.NewJSONHandler(w, &slog.HandlerOptions {
+ AddSource: true,
+ })).With(
+ slog.Group(
+ "info",
+ "pid", os.Getpid(),
+ "ppid", os.Getppid(),
+ "puuid", NewUUID().ToString(),
+ ),
+ ))
+}
+
+func SetTraceback() {
+ if os.Getenv("GOTRACEBACK") == "" {
+ debug.SetTraceback("crash")
+ }
+}
+
+func Init() {
+ SetLoggerOutput(os.Stdout)
+ SetTraceback()
+}
+
+func Fatal(err error) {
+ Error(
+ "Fatal error", "fatal-error",
+ "error", err,
+ "stack", string(debug.Stack()),
+ )
+ syscall.Kill(os.Getpid(), syscall.SIGABRT)
+ os.Exit(3)
+}
+
+func FatalIf(err error) {
+ if err != nil {
+ Fatal(err)
+ }
+}
+
+
+func Main() {
+ fmt.Println(NewUUID().ToString())
+}
+
+func mmain() {
+ fmt.Println(NewUUID().ToString())
+}
diff --git a/tests/cli-opts.sh b/tests/cli-opts.sh
new file mode 100755
index 0000000..f06f8a6
--- /dev/null
+++ b/tests/cli-opts.sh
@@ -0,0 +1,4 @@
+#!/bin/sh
+set -eu
+
+"$1" | grep -qE '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$'
diff --git a/tests/lib_test.go b/tests/lib_test.go
new file mode 100644
index 0000000..797bb0f
--- /dev/null
+++ b/tests/lib_test.go
@@ -0,0 +1,53 @@
+package gobang_test
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "log/slog"
+ "reflect"
+ "testing"
+
+ "euandre.org/gobang/src"
+)
+
+
+func errorIf(t *testing.T, err error) {
+ if err != nil {
+ t.Errorf("Unexpected error: %#v\n", err)
+ }
+}
+
+func assertEqual(t *testing.T, given any, expected any) {
+ if !reflect.DeepEqual(given, expected) {
+ t.Errorf("given != expected")
+ t.Errorf("given: %#v\n", given)
+ t.Errorf("expected: %#v\n", expected)
+ }
+}
+
+func TestSetLoggerOutput(t *testing.T) {
+ return
+ type entry struct {
+ msg string `json:"msg"`
+ aKey string `json:"a-key"`
+ }
+ var e entry
+ var buf bytes.Buffer
+ slog.Error("the message", "a-key", "a-value")
+
+ s := buf.String()
+ // fmt.Println(s)
+ // fmt.Println(e)
+ err := json.Unmarshal([]byte(s), &e)
+ if err != nil {
+ t.Fail()
+ // gobang.Mmain()
+ gobang.Main()
+ }
+ if e.msg != "the message" {
+ t.Fail()
+ }
+ fmt.Println(1)
+ // fmt.Println(e)
+}