diff --git a/go.mod b/go.mod
index 40cd2eee6..ca13d8233 100644
--- a/go.mod
+++ b/go.mod
@@ -24,7 +24,7 @@ require (
codeberg.org/gruf/go-fastcopy v1.1.3
codeberg.org/gruf/go-ffmpreg v0.6.7
codeberg.org/gruf/go-iotools v0.0.0-20240710125620-934ae9c654cf
- codeberg.org/gruf/go-kv v1.6.5
+ codeberg.org/gruf/go-kv/v2 v2.0.3
codeberg.org/gruf/go-list v0.0.0-20240425093752-494db03d641f
codeberg.org/gruf/go-mempool v0.0.0-20240507125005-cef10d64a760
codeberg.org/gruf/go-mutexes v1.5.2
@@ -99,6 +99,7 @@ require (
code.superseriousbusiness.org/go-jpeg-image-structure/v2 v2.3.0 // indirect
code.superseriousbusiness.org/go-png-image-structure/v2 v2.3.0 // indirect
codeberg.org/gruf/go-fastpath/v2 v2.0.0 // indirect
+ codeberg.org/gruf/go-kv v1.6.5 // indirect
codeberg.org/gruf/go-mangler v1.4.4 // indirect
codeberg.org/gruf/go-maps v1.0.4 // indirect
github.com/Masterminds/goutils v1.1.1 // indirect
diff --git a/go.sum b/go.sum
index cb505dcd6..09b212cd5 100644
--- a/go.sum
+++ b/go.sum
@@ -32,6 +32,8 @@ codeberg.org/gruf/go-iotools v0.0.0-20240710125620-934ae9c654cf h1:84s/ii8N6lYls
codeberg.org/gruf/go-iotools v0.0.0-20240710125620-934ae9c654cf/go.mod h1:zZAICsp5rY7+hxnws2V0ePrWxE0Z2Z/KXcN3p/RQCfk=
codeberg.org/gruf/go-kv v1.6.5 h1:ttPf0NA8F79pDqBttSudPTVCZmGncumeNIxmeM9ztz0=
codeberg.org/gruf/go-kv v1.6.5/go.mod h1:c4PsGqw05bDScvISpK+d31SiDEpBorweCL50hsiK3dc=
+codeberg.org/gruf/go-kv/v2 v2.0.3 h1:Ge4/WFR417EFPwfDdsf8S80XAdKF74RJk5g+VerAg1k=
+codeberg.org/gruf/go-kv/v2 v2.0.3/go.mod h1:mNL6SrBnYGEyrx6Mh4E1tAdhO0+T9/1iBrPJxIwxY24=
codeberg.org/gruf/go-list v0.0.0-20240425093752-494db03d641f h1:Ss6Z+vygy+jOGhj96d/GwsYYDd22QmIcH74zM7/nQkw=
codeberg.org/gruf/go-list v0.0.0-20240425093752-494db03d641f/go.mod h1:F9pl4h34iuVN7kucKam9fLwsItTc+9mmaKt7pNXRd/4=
codeberg.org/gruf/go-loosy v0.0.0-20231007123304-bb910d1ab5c4 h1:IXwfoU7f2whT6+JKIKskNl/hBlmWmnF1vZd84Eb3cyA=
diff --git a/internal/admin/domainperms.go b/internal/admin/domainperms.go
index c54c1f93e..ca2da4a15 100644
--- a/internal/admin/domainperms.go
+++ b/internal/admin/domainperms.go
@@ -22,7 +22,7 @@ import (
"errors"
"time"
- "codeberg.org/gruf/go-kv"
+ "codeberg.org/gruf/go-kv/v2"
"code.superseriousbusiness.org/gotosocial/internal/ap"
"code.superseriousbusiness.org/gotosocial/internal/config"
diff --git a/internal/api/util/errorhandling.go b/internal/api/util/errorhandling.go
index 3341e7399..7ffbbbf88 100644
--- a/internal/api/util/errorhandling.go
+++ b/internal/api/util/errorhandling.go
@@ -26,7 +26,7 @@ import (
"code.superseriousbusiness.org/gotosocial/internal/gtscontext"
"code.superseriousbusiness.org/gotosocial/internal/gtserror"
"code.superseriousbusiness.org/gotosocial/internal/log"
- "codeberg.org/gruf/go-kv"
+ "codeberg.org/gruf/go-kv/v2"
"github.com/gin-gonic/gin"
)
diff --git a/internal/cache/timeline/status_test.go b/internal/cache/timeline/status_test.go
index 93623f3ab..ff6dbe51b 100644
--- a/internal/cache/timeline/status_test.go
+++ b/internal/cache/timeline/status_test.go
@@ -29,8 +29,8 @@ import (
apimodel "code.superseriousbusiness.org/gotosocial/internal/api/model"
"code.superseriousbusiness.org/gotosocial/internal/gtsmodel"
"code.superseriousbusiness.org/gotosocial/internal/id"
- "code.superseriousbusiness.org/gotosocial/internal/log"
"code.superseriousbusiness.org/gotosocial/internal/paging"
+ "codeberg.org/gruf/go-kv/v2"
"codeberg.org/gruf/go-structr"
"github.com/stretchr/testify/assert"
)
@@ -440,7 +440,8 @@ func loadStatusIDsFrom(data []*StatusMeta) func(ids []string) ([]*gtsmodel.Statu
return s.ID == id
})
if i < 0 || i >= len(data) {
- panic(fmt.Sprintf("could not find %s in %v", id, log.VarDump(data)))
+ kv := kv.Field{K: "data", V: data} // use kv.Field for formatting
+ panic(fmt.Sprintf("could not find %s in %v", id, kv))
}
statuses = append(statuses, >smodel.Status{
ID: data[i].ID,
diff --git a/internal/config/config_test.go b/internal/config/config_test.go
index 4e920700e..a23fc20f4 100644
--- a/internal/config/config_test.go
+++ b/internal/config/config_test.go
@@ -24,7 +24,7 @@ import (
"testing"
"code.superseriousbusiness.org/gotosocial/internal/config"
- "codeberg.org/gruf/go-kv"
+ "codeberg.org/gruf/go-kv/v2"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/stretchr/testify/assert"
diff --git a/internal/db/bundb/hook.go b/internal/db/bundb/hook.go
index 798868ccb..f02a1353d 100644
--- a/internal/db/bundb/hook.go
+++ b/internal/db/bundb/hook.go
@@ -22,7 +22,7 @@ import (
"time"
"code.superseriousbusiness.org/gotosocial/internal/log"
- "codeberg.org/gruf/go-kv"
+ "codeberg.org/gruf/go-kv/v2"
"github.com/uptrace/bun"
)
diff --git a/internal/federation/authenticate.go b/internal/federation/authenticate.go
index 2355aaa03..f8515e649 100644
--- a/internal/federation/authenticate.go
+++ b/internal/federation/authenticate.go
@@ -38,7 +38,7 @@ import (
"code.superseriousbusiness.org/gotosocial/internal/gtsmodel"
"code.superseriousbusiness.org/gotosocial/internal/log"
"code.superseriousbusiness.org/httpsig"
- "codeberg.org/gruf/go-kv"
+ "codeberg.org/gruf/go-kv/v2"
)
var (
diff --git a/internal/federation/dereferencing/thread.go b/internal/federation/dereferencing/thread.go
index f9d6adb99..d426ee4bc 100644
--- a/internal/federation/dereferencing/thread.go
+++ b/internal/federation/dereferencing/thread.go
@@ -28,7 +28,7 @@ import (
"code.superseriousbusiness.org/gotosocial/internal/gtserror"
"code.superseriousbusiness.org/gotosocial/internal/gtsmodel"
"code.superseriousbusiness.org/gotosocial/internal/log"
- "codeberg.org/gruf/go-kv"
+ "codeberg.org/gruf/go-kv/v2"
)
// maxIter defines how many iterations of descendants or
diff --git a/internal/federation/federatingactor.go b/internal/federation/federatingactor.go
index 56bc0a416..2a68865aa 100644
--- a/internal/federation/federatingactor.go
+++ b/internal/federation/federatingactor.go
@@ -35,7 +35,7 @@ import (
"code.superseriousbusiness.org/gotosocial/internal/log"
"code.superseriousbusiness.org/gotosocial/internal/uris"
errorsv2 "codeberg.org/gruf/go-errors/v2"
- "codeberg.org/gruf/go-kv"
+ "codeberg.org/gruf/go-kv/v2"
)
// federatingActor wraps the pub.FederatingActor
diff --git a/internal/federation/federatingprotocol.go b/internal/federation/federatingprotocol.go
index 60ee48eaa..e1ec86b32 100644
--- a/internal/federation/federatingprotocol.go
+++ b/internal/federation/federatingprotocol.go
@@ -35,7 +35,7 @@ import (
"code.superseriousbusiness.org/gotosocial/internal/log"
"code.superseriousbusiness.org/gotosocial/internal/uris"
"code.superseriousbusiness.org/gotosocial/internal/util/xslices"
- "codeberg.org/gruf/go-kv"
+ "codeberg.org/gruf/go-kv/v2"
)
type errOtherIRIBlocked struct {
diff --git a/internal/gtscontext/log_hooks.go b/internal/gtscontext/log_hooks.go
index 3e8b166ff..a50f8fffa 100644
--- a/internal/gtscontext/log_hooks.go
+++ b/internal/gtscontext/log_hooks.go
@@ -21,7 +21,7 @@ import (
"context"
"code.superseriousbusiness.org/gotosocial/internal/log"
- "codeberg.org/gruf/go-kv"
+ "codeberg.org/gruf/go-kv/v2"
)
func init() {
diff --git a/internal/id/ulid.go b/internal/id/ulid.go
index b281ef5b8..04afef8f0 100644
--- a/internal/id/ulid.go
+++ b/internal/id/ulid.go
@@ -23,7 +23,7 @@ import (
"time"
"code.superseriousbusiness.org/gotosocial/internal/log"
- "codeberg.org/gruf/go-kv"
+ "codeberg.org/gruf/go-kv/v2"
"github.com/oklog/ulid"
)
diff --git a/internal/log/entry.go b/internal/log/entry.go
index 52e90caed..48059c3bf 100644
--- a/internal/log/entry.go
+++ b/internal/log/entry.go
@@ -21,7 +21,7 @@ import (
"context"
"fmt"
- "codeberg.org/gruf/go-kv"
+ "codeberg.org/gruf/go-kv/v2"
)
type Entry struct {
diff --git a/internal/log/format.go b/internal/log/format.go
deleted file mode 100644
index e7468ed21..000000000
--- a/internal/log/format.go
+++ /dev/null
@@ -1,29 +0,0 @@
-// GoToSocial
-// Copyright (C) GoToSocial Authors admin@gotosocial.org
-// SPDX-License-Identifier: AGPL-3.0-or-later
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Affero General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with this program. If not, see .
-
-package log
-
-import "codeberg.org/gruf/go-kv/format"
-
-// VarDump returns a serialized, useful log / error output of given variable.
-func VarDump(a any) string {
- buf := getBuf()
- format.Appendf(buf, "{:v}", a)
- s := string(buf.B)
- putBuf(buf)
- return s
-}
diff --git a/internal/log/log.go b/internal/log/log.go
index bf2259b50..afb1bcdb8 100644
--- a/internal/log/log.go
+++ b/internal/log/log.go
@@ -26,7 +26,7 @@ import (
"time"
"code.superseriousbusiness.org/gotosocial/internal/util/xslices"
- "codeberg.org/gruf/go-kv"
+ "codeberg.org/gruf/go-kv/v2"
)
var (
diff --git a/internal/media/processingmedia.go b/internal/media/processingmedia.go
index 0bb145a02..bd0c1b7ee 100644
--- a/internal/media/processingmedia.go
+++ b/internal/media/processingmedia.go
@@ -22,7 +22,7 @@ import (
"os"
errorsv2 "codeberg.org/gruf/go-errors/v2"
- "codeberg.org/gruf/go-kv"
+ "codeberg.org/gruf/go-kv/v2"
"codeberg.org/gruf/go-runners"
"code.superseriousbusiness.org/gotosocial/internal/config"
diff --git a/internal/middleware/logger.go b/internal/middleware/logger.go
index 350e7552c..d805e693f 100644
--- a/internal/middleware/logger.go
+++ b/internal/middleware/logger.go
@@ -28,7 +28,7 @@ import (
"code.superseriousbusiness.org/gotosocial/internal/log"
"code.superseriousbusiness.org/gotosocial/internal/util"
"codeberg.org/gruf/go-bytesize"
- "codeberg.org/gruf/go-kv"
+ "codeberg.org/gruf/go-kv/v2"
"github.com/gin-gonic/gin"
)
diff --git a/internal/observability/tracing.go b/internal/observability/tracing.go
index 25d5e27e8..99f426743 100644
--- a/internal/observability/tracing.go
+++ b/internal/observability/tracing.go
@@ -26,7 +26,7 @@ import (
"net/http"
"strconv"
- "codeberg.org/gruf/go-kv"
+ "codeberg.org/gruf/go-kv/v2"
"github.com/gin-gonic/gin"
diff --git a/internal/processing/account/delete.go b/internal/processing/account/delete.go
index f812909c5..717c03fcc 100644
--- a/internal/processing/account/delete.go
+++ b/internal/processing/account/delete.go
@@ -31,7 +31,7 @@ import (
"code.superseriousbusiness.org/gotosocial/internal/log"
"code.superseriousbusiness.org/gotosocial/internal/messages"
"code.superseriousbusiness.org/gotosocial/internal/util"
- "codeberg.org/gruf/go-kv"
+ "codeberg.org/gruf/go-kv/v2"
"github.com/google/uuid"
"golang.org/x/crypto/bcrypt"
)
diff --git a/internal/processing/search/accounts.go b/internal/processing/search/accounts.go
index 9345aaba6..af84abc31 100644
--- a/internal/processing/search/accounts.go
+++ b/internal/processing/search/accounts.go
@@ -29,7 +29,7 @@ import (
"code.superseriousbusiness.org/gotosocial/internal/id"
"code.superseriousbusiness.org/gotosocial/internal/log"
"code.superseriousbusiness.org/gotosocial/internal/util"
- "codeberg.org/gruf/go-kv"
+ "codeberg.org/gruf/go-kv/v2"
)
// Accounts does a partial search for accounts that
diff --git a/internal/processing/search/get.go b/internal/processing/search/get.go
index 2051fb399..2e956b049 100644
--- a/internal/processing/search/get.go
+++ b/internal/processing/search/get.go
@@ -34,7 +34,7 @@ import (
"code.superseriousbusiness.org/gotosocial/internal/log"
"code.superseriousbusiness.org/gotosocial/internal/text"
"code.superseriousbusiness.org/gotosocial/internal/util"
- "codeberg.org/gruf/go-kv"
+ "codeberg.org/gruf/go-kv/v2"
)
const (
diff --git a/internal/processing/search/lookup.go b/internal/processing/search/lookup.go
index 3250f9848..39fddad77 100644
--- a/internal/processing/search/lookup.go
+++ b/internal/processing/search/lookup.go
@@ -28,7 +28,7 @@ import (
"code.superseriousbusiness.org/gotosocial/internal/gtsmodel"
"code.superseriousbusiness.org/gotosocial/internal/log"
"code.superseriousbusiness.org/gotosocial/internal/util"
- "codeberg.org/gruf/go-kv"
+ "codeberg.org/gruf/go-kv/v2"
)
// Lookup does a quick, non-resolving search for accounts that
diff --git a/internal/processing/stream/open.go b/internal/processing/stream/open.go
index bafa277a5..899e26896 100644
--- a/internal/processing/stream/open.go
+++ b/internal/processing/stream/open.go
@@ -24,7 +24,7 @@ import (
"code.superseriousbusiness.org/gotosocial/internal/gtsmodel"
"code.superseriousbusiness.org/gotosocial/internal/log"
"code.superseriousbusiness.org/gotosocial/internal/stream"
- "codeberg.org/gruf/go-kv"
+ "codeberg.org/gruf/go-kv/v2"
)
// Open returns a new Stream for the given account, which will contain a channel for passing messages back to the caller.
diff --git a/internal/processing/workers/fromclientapi.go b/internal/processing/workers/fromclientapi.go
index 32b3d8816..22e7780f6 100644
--- a/internal/processing/workers/fromclientapi.go
+++ b/internal/processing/workers/fromclientapi.go
@@ -36,7 +36,7 @@ import (
"code.superseriousbusiness.org/gotosocial/internal/typeutils"
"code.superseriousbusiness.org/gotosocial/internal/uris"
"code.superseriousbusiness.org/gotosocial/internal/util"
- "codeberg.org/gruf/go-kv"
+ "codeberg.org/gruf/go-kv/v2"
)
// clientAPI wraps processing functions
diff --git a/internal/processing/workers/fromfediapi.go b/internal/processing/workers/fromfediapi.go
index 1a42a04fb..09c1df480 100644
--- a/internal/processing/workers/fromfediapi.go
+++ b/internal/processing/workers/fromfediapi.go
@@ -29,7 +29,7 @@ import (
"code.superseriousbusiness.org/gotosocial/internal/gtscontext"
"code.superseriousbusiness.org/gotosocial/internal/id"
"code.superseriousbusiness.org/gotosocial/internal/uris"
- "codeberg.org/gruf/go-kv"
+ "codeberg.org/gruf/go-kv/v2"
"code.superseriousbusiness.org/gotosocial/internal/gtserror"
"code.superseriousbusiness.org/gotosocial/internal/gtsmodel"
diff --git a/internal/subscriptions/domainperms.go b/internal/subscriptions/domainperms.go
index 0f001620f..a1af8c152 100644
--- a/internal/subscriptions/domainperms.go
+++ b/internal/subscriptions/domainperms.go
@@ -30,7 +30,7 @@ import (
"strings"
"time"
- "codeberg.org/gruf/go-kv"
+ "codeberg.org/gruf/go-kv/v2"
"code.superseriousbusiness.org/gotosocial/internal/admin"
apimodel "code.superseriousbusiness.org/gotosocial/internal/api/model"
diff --git a/testrig/db.go b/testrig/db.go
index 3a5615f01..dd65e0804 100644
--- a/testrig/db.go
+++ b/testrig/db.go
@@ -25,7 +25,7 @@ import (
"code.superseriousbusiness.org/gotosocial/internal/gtsmodel"
"code.superseriousbusiness.org/gotosocial/internal/log"
"code.superseriousbusiness.org/gotosocial/internal/state"
- "codeberg.org/gruf/go-kv"
+ "codeberg.org/gruf/go-kv/v2"
)
var testModels = []interface{}{
diff --git a/testrig/util.go b/testrig/util.go
index b4d80fd4a..bdd18ec29 100644
--- a/testrig/util.go
+++ b/testrig/util.go
@@ -31,8 +31,7 @@ import (
"code.superseriousbusiness.org/gotosocial/internal/messages"
"code.superseriousbusiness.org/gotosocial/internal/processing/workers"
"code.superseriousbusiness.org/gotosocial/internal/state"
- "codeberg.org/gruf/go-byteutil"
- "codeberg.org/gruf/go-kv/format"
+ "codeberg.org/gruf/go-kv/v2"
)
// Starts workers on the provided state using noop processing functions.
@@ -283,7 +282,7 @@ func WaitFor(condition func() bool) bool {
// dump returns debug output of 'v'.
func dump(v any) string {
- var buf byteutil.Buffer
- format.Append(&buf, v)
- return buf.String()
+ var kv kv.Field
+ kv.V = v
+ return kv.Value(false)
}
diff --git a/vendor/codeberg.org/gruf/go-kv/v2/LICENSE b/vendor/codeberg.org/gruf/go-kv/v2/LICENSE
new file mode 100644
index 000000000..b7c4417ac
--- /dev/null
+++ b/vendor/codeberg.org/gruf/go-kv/v2/LICENSE
@@ -0,0 +1,9 @@
+MIT License
+
+Copyright (c) 2021 gruf
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/vendor/codeberg.org/gruf/go-kv/v2/README.md b/vendor/codeberg.org/gruf/go-kv/v2/README.md
new file mode 100644
index 000000000..2815f1c83
--- /dev/null
+++ b/vendor/codeberg.org/gruf/go-kv/v2/README.md
@@ -0,0 +1,5 @@
+# go-kv
+
+This library provides a key-value field structure `kv.Field{}` that plays well with the `"fmt"` package. It gives an easy means of appending key-value fields to log entries, in a more performant manner that also happens to look nice! (it's not far removed from using a `map[string]interface{}`).
+
+The formatting for these key-value fields is handled by the `"fmt"` package by default. If you set the `kvformat` build tag then it will use a custom formatting library found under `format/`.
diff --git a/vendor/codeberg.org/gruf/go-kv/v2/field.go b/vendor/codeberg.org/gruf/go-kv/v2/field.go
new file mode 100644
index 000000000..58399e4a9
--- /dev/null
+++ b/vendor/codeberg.org/gruf/go-kv/v2/field.go
@@ -0,0 +1,103 @@
+package kv
+
+import (
+ "codeberg.org/gruf/go-byteutil"
+)
+
+// bufsize is the default buffer size per field to alloc
+// when calling .AppendFormat() from within .String().
+const bufsize = 64
+
+// Fields is a typedef for a []Field slice to provide
+// slightly more performant string formatting for multiples.
+type Fields []Field
+
+// Get will return the field with given 'key'.
+func (f Fields) Get(key string) (*Field, bool) {
+ for i := 0; i < len(f); i++ {
+ if f[i].K == key {
+ return &f[i], true
+ }
+ }
+ return nil, false
+}
+
+// Set will set an existing field with 'key' to 'value', or append new.
+func (f *Fields) Set(key string, value interface{}) {
+ for i := 0; i < len(*f); i++ {
+ // Update existing value
+ if (*f)[i].K == key {
+ (*f)[i].V = value
+ return
+ }
+ }
+
+ // Append new field
+ *f = append(*f, Field{
+ K: key,
+ V: value,
+ })
+}
+
+// AppendFormat appends a string representation of receiving Field(s) to 'b'.
+func (f Fields) AppendFormat(buf *byteutil.Buffer, vbose bool) {
+ for i := 0; i < len(f); i++ {
+ f[i].AppendFormat(buf, vbose)
+ buf.WriteByte(' ')
+ }
+ if len(f) > 0 {
+ buf.Truncate(1)
+ }
+}
+
+// String returns a string representation of receiving Field(s).
+func (f Fields) String() string {
+ b := make([]byte, 0, bufsize*len(f))
+ buf := byteutil.Buffer{B: b}
+ f.AppendFormat(&buf, false)
+ return buf.String()
+}
+
+// GoString performs .String() but with type prefix.
+func (f Fields) GoString() string {
+ b := make([]byte, 0, bufsize*len(f))
+ buf := byteutil.Buffer{B: b}
+ f.AppendFormat(&buf, true)
+ return "kv.Fields{" + buf.String() + "}"
+}
+
+// Field represents an individual key-value field.
+type Field struct {
+ K string // Field key
+ V interface{} // Field value
+}
+
+// Key returns the formatted key string of this Field.
+func (f Field) Key() string {
+ buf := byteutil.Buffer{B: make([]byte, 0, bufsize/2)}
+ AppendQuoteString(&buf, f.K)
+ return buf.String()
+}
+
+// String will return a string representation of this Field
+// of the form `key=value` where `value` is formatted using
+// fmt package's `%+v` directive. If the .X = true (verbose),
+// then it uses '%#v'. Both key and value are escaped and
+// quoted if necessary to fit on single line.
+//
+// If the `kvformat` build tag is provided, the formatting
+// will be performed by the `kv/format` package.
+func (f Field) String() string {
+ b := make([]byte, 0, bufsize)
+ buf := byteutil.Buffer{B: b}
+ f.AppendFormat(&buf, false)
+ return buf.String()
+}
+
+// GoString performs .String() but with verbose always enabled.
+func (f Field) GoString() string {
+ b := make([]byte, 0, bufsize)
+ buf := byteutil.Buffer{B: b}
+ f.AppendFormat(&buf, true)
+ return buf.String()
+}
diff --git a/vendor/codeberg.org/gruf/go-kv/v2/field_fmt.go b/vendor/codeberg.org/gruf/go-kv/v2/field_fmt.go
new file mode 100644
index 000000000..5d6e77f4b
--- /dev/null
+++ b/vendor/codeberg.org/gruf/go-kv/v2/field_fmt.go
@@ -0,0 +1,63 @@
+//go:build !kvformat
+// +build !kvformat
+
+package kv
+
+import (
+ "fmt"
+ "sync"
+
+ "codeberg.org/gruf/go-byteutil"
+)
+
+// bufPool is a memory pool of byte buffers.
+var bufPool = sync.Pool{
+ New: func() interface{} {
+ return &byteutil.Buffer{B: make([]byte, 0, 512)}
+ },
+}
+
+// AppendFormat will append formatted format of Field to 'buf'. See .String() for details.
+func (f Field) AppendFormat(buf *byteutil.Buffer, vbose bool) {
+ var fmtstr string
+ if vbose /* verbose */ {
+ fmtstr = `%#v`
+ } else /* regular */ {
+ fmtstr = `%+v`
+ }
+ AppendQuoteString(buf, f.K)
+ buf.WriteByte('=')
+ appendValuef(buf, fmtstr, f.V)
+}
+
+// Value returns the formatted value string of this Field.
+func (f Field) Value(vbose bool) string {
+ var fmtstr string
+ if vbose /* verbose */ {
+ fmtstr = `%#v`
+ } else /* regular */ {
+ fmtstr = `%+v`
+ }
+ buf := byteutil.Buffer{B: make([]byte, 0, bufsize/2)}
+ appendValuef(&buf, fmtstr, f.V)
+ return buf.String()
+}
+
+// appendValuef appends a quoted value string (formatted by fmt.Appendf) to 'buf'.
+func appendValuef(buf *byteutil.Buffer, format string, args ...interface{}) {
+ // Write format string to a byte buffer
+ fmtbuf := bufPool.Get().(*byteutil.Buffer)
+ fmtbuf.B = fmt.Appendf(fmtbuf.B, format, args...)
+
+ // Append quoted value to dst buffer
+ AppendQuoteValue(buf, fmtbuf.String())
+
+ // Drop overly large capacity buffers
+ if fmtbuf.Cap() > int(^uint16(0)) {
+ return
+ }
+
+ // Replace buffer in pool
+ fmtbuf.Reset()
+ bufPool.Put(fmtbuf)
+}
diff --git a/vendor/codeberg.org/gruf/go-kv/v2/field_format.go b/vendor/codeberg.org/gruf/go-kv/v2/field_format.go
new file mode 100644
index 000000000..8085d4d01
--- /dev/null
+++ b/vendor/codeberg.org/gruf/go-kv/v2/field_format.go
@@ -0,0 +1,46 @@
+//go:build kvformat
+// +build kvformat
+
+package kv
+
+import (
+ "codeberg.org/gruf/go-byteutil"
+ "codeberg.org/gruf/go-kv/v2/format"
+)
+
+var formatter format.Formatter
+
+var argsDefault = format.DefaultArgs()
+
+var argsVerbose = func() format.Args {
+ args := format.DefaultArgs()
+ args.SetWithType()
+ args.SetNoMethod()
+ return args
+}()
+
+// AppendFormat will append formatted format of Field to 'buf'. See .String() for details.
+func (f Field) AppendFormat(buf *byteutil.Buffer, vbose bool) {
+ var args format.Args
+ if vbose {
+ args = argsVerbose
+ } else {
+ args = argsDefault
+ }
+ AppendQuoteString(buf, f.K)
+ buf.WriteByte('=')
+ buf.B = formatter.Append(buf.B, f.V, args)
+}
+
+// Value returns the formatted value string of this Field.
+func (f Field) Value(vbose bool) string {
+ var args format.Args
+ if vbose {
+ args = argsVerbose
+ } else {
+ args = argsDefault
+ }
+ buf := make([]byte, 0, bufsize/2)
+ buf = formatter.Append(buf, f.V, args)
+ return byteutil.B2S(buf)
+}
diff --git a/vendor/codeberg.org/gruf/go-kv/v2/format/abi.go b/vendor/codeberg.org/gruf/go-kv/v2/format/abi.go
new file mode 100644
index 000000000..815660a5e
--- /dev/null
+++ b/vendor/codeberg.org/gruf/go-kv/v2/format/abi.go
@@ -0,0 +1,262 @@
+//go:build go1.24 && !go1.25
+
+package format
+
+import (
+ "reflect"
+ "unsafe"
+)
+
+const (
+ // see: go/src/internal/abi/type.go
+ abi_KindDirectIface uint8 = 1 << 5
+ abi_KindMask uint8 = (1 << 5) - 1
+)
+
+// abi_Type is a copy of the memory layout of abi.Type{}.
+//
+// see: go/src/internal/abi/type.go
+type abi_Type struct {
+ _ uintptr
+ PtrBytes uintptr
+ _ uint32
+ _ uint8
+ _ uint8
+ _ uint8
+ Kind_ uint8
+ _ func(unsafe.Pointer, unsafe.Pointer) bool
+ _ *byte
+ _ int32
+ _ int32
+}
+
+// abi_EmptyInterface is a copy of the memory layout of abi.EmptyInterface{},
+// which is to say also the memory layout of any method-less interface.
+//
+// see: go/src/internal/abi/iface.go
+type abi_EmptyInterface struct {
+ Type *abi_Type
+ Data unsafe.Pointer
+}
+
+// see: go/src/internal/abi/type.go Type.Kind()
+func abi_Type_Kind(t reflect.Type) uint8 {
+ iface := (*reflect_nonEmptyInterface)(unsafe.Pointer(&t))
+ atype := (*abi_Type)(unsafe.Pointer(iface.word))
+ return atype.Kind_ & abi_KindMask
+}
+
+// see: go/src/internal/abi/type.go Type.IfaceIndir()
+func abi_Type_IfaceIndir(t reflect.Type) bool {
+ iface := (*reflect_nonEmptyInterface)(unsafe.Pointer(&t))
+ atype := (*abi_Type)(unsafe.Pointer(iface.word))
+ return atype.Kind_&abi_KindDirectIface == 0
+}
+
+// pack_iface packs a new reflect.nonEmptyInterface{} using shielded itab
+// pointer and data (word) pointer, returning a pointer for caller casting.
+func pack_iface(itab uintptr, word unsafe.Pointer) unsafe.Pointer {
+ return unsafe.Pointer(&reflect_nonEmptyInterface{
+ itab: itab,
+ word: word,
+ })
+}
+
+// get_iface_ITab generates a new value of given type,
+// casts it to the generic param interface type, and
+// returns the .itab portion of the reflect.nonEmptyInterface{}.
+// this is useful for later calls to pack_iface for known type.
+func get_iface_ITab[I any](t reflect.Type) uintptr {
+ s := reflect.New(t).Elem().Interface().(I)
+ i := (*reflect_nonEmptyInterface)(unsafe.Pointer(&s))
+ return i.itab
+}
+
+// unpack_eface returns the .Data portion of an abi.EmptyInterface{}.
+func unpack_eface(a any) unsafe.Pointer {
+ return (*abi_EmptyInterface)(unsafe.Pointer((&a))).Data
+}
+
+// add returns the ptr addition of starting ptr and a delta.
+func add(ptr unsafe.Pointer, delta uintptr) unsafe.Pointer {
+ return unsafe.Pointer(uintptr(ptr) + delta)
+}
+
+// typeof is short-hand for reflect.TypeFor[T]().
+func typeof[T any]() reflect.Type {
+ return reflect.TypeFor[T]()
+}
+
+// see: go/src/reflect/value.go
+type reflect_flag uintptr
+
+const (
+ // see: go/src/reflect/value.go
+ reflect_flagKindWidth = 5 // there are 27 kinds
+ reflect_flagKindMask reflect_flag = 1< SingleTermLine || !IsSafeASCII(v) {
+ // Requires quoting AND escaping
+ s.B = strconv.AppendQuote(s.B, v)
+ } else if ContainsDoubleQuote(v) {
+ // Contains double quotes, needs escaping
+ s.B = append(s.B, '"')
+ s.B = AppendEscape(s.B, v)
+ s.B = append(s.B, '"')
+ } else if s.A.WithType() ||
+ len(v) == 0 || ContainsSpaceOrTab(v) {
+ // Contains space / empty, needs quotes
+ s.B = append(s.B, '"')
+ s.B = append(s.B, v...)
+ s.B = append(s.B, '"')
+ } else {
+ // All else write as-is
+ s.B = append(s.B, v...)
+ }
+ case s.A.AsQuotedText():
+ s.B = strconv.AppendQuote(s.B, v)
+ case s.A.AsQuotedASCII():
+ s.B = strconv.AppendQuoteToASCII(s.B, v)
+ default:
+ s.B = append(s.B, v...)
+ }
+}
+
+func appendInt(s *State, v int64) {
+ args := s.A.Int
+ if args == zeroArgs.Int {
+ args = defaultArgs.Int
+ }
+ if args.Pad > 0 {
+ const zeros = `00000000000000000000`
+ if args.Pad > len(zeros) {
+ panic("cannot pad > " + zeros)
+ }
+ if v == 0 {
+ s.B = append(s.B, zeros[:args.Pad]...)
+ return
+ }
+ abs := abs64(v)
+ chars := int(v / int64(args.Base))
+ if v%int64(args.Base) != 0 {
+ chars++
+ }
+ if abs != v {
+ s.B = append(s.B, '-')
+ v = abs
+ }
+ if n := args.Pad - chars; n > 0 {
+ s.B = append(s.B, zeros[:n]...)
+ }
+ }
+ s.B = strconv.AppendInt(s.B, v, args.Base)
+}
+
+func appendUint(s *State, v uint64) {
+ args := s.A.Int
+ if args == zeroArgs.Int {
+ args = defaultArgs.Int
+ }
+ if args.Pad > 0 {
+ const zeros = `00000000000000000000`
+ if args.Pad > len(zeros) {
+ panic("cannot pad > " + zeros)
+ }
+ if v == 0 {
+ s.B = append(s.B, zeros[:args.Pad]...)
+ return
+ }
+ chars := int(v / uint64(args.Base))
+ if v%uint64(args.Base) != 0 {
+ chars++
+ }
+ if n := args.Pad - chars; n > 0 {
+ s.B = append(s.B, zeros[:n]...)
+ }
+ }
+ s.B = strconv.AppendUint(s.B, v, args.Base)
+}
+
+func appendFloat(s *State, v float64, bits int) {
+ args := s.A.Float
+ if args == zeroArgs.Float {
+ args = defaultArgs.Float
+ }
+ s.B = strconv.AppendFloat(s.B, float64(v), args.Fmt, args.Prec, bits)
+}
+
+func appendComplex(s *State, r, i float64, bits int) {
+ args := s.A.Complex
+ if args == zeroArgs.Complex {
+ args = defaultArgs.Complex
+ }
+ s.B = strconv.AppendFloat(s.B, float64(r), args.Real.Fmt, args.Real.Prec, bits)
+ s.B = append(s.B, '+')
+ s.B = strconv.AppendFloat(s.B, float64(i), args.Imag.Fmt, args.Imag.Prec, bits)
+ s.B = append(s.B, 'i')
+}
+
+func appendPointer(s *State, v unsafe.Pointer) {
+ if v != nil {
+ s.B = append(s.B, "0x"...)
+ s.B = strconv.AppendUint(s.B, uint64(uintptr(v)), 16)
+ } else {
+ appendNil(s)
+ }
+}
+
+func appendNilType(s *State, typestr string) {
+ if s.A.WithType() {
+ s.B = append(s.B, "("+typestr+")()"...)
+ } else {
+ s.B = append(s.B, ""...)
+ }
+}
+
+func appendNil(s *State) {
+ s.B = append(s.B, ""...)
+}
+
+func abs64(i int64) int64 {
+ u := uint64(i >> 63)
+ return (i ^ int64(u)) + int64(u&1)
+}
diff --git a/vendor/codeberg.org/gruf/go-kv/v2/format/formatting.go b/vendor/codeberg.org/gruf/go-kv/v2/format/formatting.go
new file mode 100644
index 000000000..e09edbefc
--- /dev/null
+++ b/vendor/codeberg.org/gruf/go-kv/v2/format/formatting.go
@@ -0,0 +1,161 @@
+package format
+
+import (
+ "strings"
+ "unicode"
+ "unicode/utf8"
+)
+
+const (
+ // SingleTermLine: beyond a certain length of string, all of the
+ // extra checks to handle quoting/not-quoting add a significant
+ // amount of extra processing time. Quoting in this manner only really
+ // effects readability on a single line, so a max string length that
+ // encompasses the maximum number of columns on *most* terminals was
+ // selected. This was chosen using the metric that 1080p is one of the
+ // most common display resolutions, and that a relatively small font size
+ // of 7 requires 223 columns. So 256 should be >= $COLUMNS (fullscreen)
+ // in 99% of usecases (these figures all pulled out of my ass).
+ SingleTermLine = 256
+)
+
+// IsSafeASCII checks whether string is printable (i.e. non-control char) ASCII text.
+func IsSafeASCII(str string) bool {
+ for _, r := range str {
+ if (r < ' ' && r != '\t') ||
+ r >= 0x7f {
+ return false
+ }
+ }
+ return true
+}
+
+// ContainsSpaceOrTab checks if "s" contains space or tabs. EXPECTS ASCII.
+func ContainsSpaceOrTab(s string) bool {
+ if i := strings.IndexByte(s, ' '); i >= 0 {
+ return true // note using indexbyte as it is ASM.
+ } else if i := strings.IndexByte(s, '\t'); i >= 0 {
+ return true
+ }
+ return false
+}
+
+// ContainsDoubleQuote checks if "s" contains a double quote. EXPECTS ASCII.
+func ContainsDoubleQuote(s string) bool {
+ return (strings.IndexByte(s, '"') >= 0)
+}
+
+// AppendEscape will append 's' to 'buf' and escape any double quotes. EXPECTS ASCII.
+func AppendEscape(buf []byte, str string) []byte {
+ for i := range str {
+ switch str[i] {
+ case '\\':
+ // Append delimited '\'
+ buf = append(buf, '\\', '\\')
+
+ case '"':
+ // Append delimited '"'
+ buf = append(buf, '\\', '"')
+ default:
+ // Append char as-is
+ buf = append(buf, str[i])
+ }
+ }
+ return buf
+}
+
+const hex = "0123456789abcdef"
+
+// AppendEscapeByte ...
+func AppendEscapeByte(buf []byte, c byte) []byte {
+ switch c {
+ case '\a':
+ return append(buf, `\a`...)
+ case '\b':
+ return append(buf, `\b`...)
+ case '\f':
+ return append(buf, `\f`...)
+ case '\n':
+ return append(buf, `\n`...)
+ case '\r':
+ return append(buf, `\r`...)
+ case '\t':
+ return append(buf, `\t`...)
+ case '\v':
+ return append(buf, `\v`...)
+ case '\\':
+ return append(buf, `\\`...)
+ default:
+ if c < ' ' {
+ return append(buf, '\\', 'x', hex[c>>4], hex[c&0xF])
+ }
+ return append(buf, c)
+ }
+}
+
+// AppendQuoteByte ...
+func AppendQuoteByte(buf []byte, c byte) []byte {
+ if c == '\'' {
+ return append(buf, `'\''`...)
+ }
+ buf = append(buf, '\'')
+ buf = AppendEscapeByte(buf, c)
+ buf = append(buf, '\'')
+ return buf
+}
+
+// AppendEscapeRune ...
+func AppendEscapeRune(buf []byte, r rune) []byte {
+ if unicode.IsPrint(r) {
+ return utf8.AppendRune(buf, r)
+ }
+ switch r {
+ case '\a':
+ return append(buf, `\a`...)
+ case '\b':
+ return append(buf, `\b`...)
+ case '\f':
+ return append(buf, `\f`...)
+ case '\n':
+ return append(buf, `\n`...)
+ case '\r':
+ return append(buf, `\r`...)
+ case '\t':
+ return append(buf, `\t`...)
+ case '\v':
+ return append(buf, `\v`...)
+ case '\\':
+ return append(buf, `\\`...)
+ default:
+ switch {
+ case r < ' ' || r == 0x7f:
+ buf = append(buf, `\x`...)
+ buf = append(buf, hex[byte(r)>>4])
+ buf = append(buf, hex[byte(r)&0xF])
+ case !utf8.ValidRune(r):
+ r = 0xFFFD
+ fallthrough
+ case r < 0x10000:
+ buf = append(buf, `\u`...)
+ buf = append(buf,
+ hex[r>>uint(12)&0xF],
+ hex[r>>uint(8)&0xF],
+ hex[r>>uint(4)&0xF],
+ hex[r>>uint(0)&0xF],
+ )
+ default:
+ buf = append(buf, `\U`...)
+ buf = append(buf,
+ hex[r>>uint(28)&0xF],
+ hex[r>>uint(24)&0xF],
+ hex[r>>uint(20)&0xF],
+ hex[r>>uint(16)&0xF],
+ hex[r>>uint(12)&0xF],
+ hex[r>>uint(8)&0xF],
+ hex[r>>uint(4)&0xF],
+ hex[r>>uint(0)&0xF],
+ )
+ }
+ }
+ return buf
+}
diff --git a/vendor/codeberg.org/gruf/go-kv/v2/format/map.go b/vendor/codeberg.org/gruf/go-kv/v2/format/map.go
new file mode 100644
index 000000000..038fb74ac
--- /dev/null
+++ b/vendor/codeberg.org/gruf/go-kv/v2/format/map.go
@@ -0,0 +1,130 @@
+package format
+
+import "unsafe"
+
+// iterMapType returns a FormatFunc capable of iterating
+// and formatting the given map type currently in typenode{}.
+// note this will fetch sub-FormatFuncs for key / value types.
+func (fmt *Formatter) iterMapType(t typenode) FormatFunc {
+
+ // Key / value types.
+ key := t.rtype.Key()
+ elem := t.rtype.Elem()
+
+ // Get nested k / v typenodes with appropriate flags.
+ flagsKey := reflect_map_key_flags(key)
+ flagsVal := reflect_map_elem_flags(elem)
+ kt := t.next(t.rtype.Key(), flagsKey)
+ vt := t.next(t.rtype.Elem(), flagsVal)
+
+ // Get key format func.
+ kfn := fmt.loadOrGet(kt)
+ if kfn == nil {
+ panic("unreachable")
+ }
+
+ // Get value format func.
+ vfn := fmt.loadOrGet(vt)
+ if vfn == nil {
+ panic("unreachable")
+ }
+
+ // Final map type.
+ rtype := t.rtype
+ flags := t.flags
+
+ // Map type string with ptrs / refs.
+ typestrPtrs := t.typestr_with_ptrs()
+ typestrRefs := t.typestr_with_refs()
+
+ if !t.needs_typestr() {
+ return func(s *State) {
+ if s.P == nil || *(*unsafe.Pointer)(s.P) == nil {
+ // Append nil.
+ appendNil(s)
+ return
+ }
+
+ // Build reflect value, and then a map iter.
+ v := build_reflect_value(rtype, s.P, flags)
+ i := map_iter(v)
+
+ // Prepend object brace.
+ s.B = append(s.B, '{')
+
+ // Before len.
+ l := len(s.B)
+
+ for i.Next() {
+ // Pass to key fn.
+ s.P = map_key(i)
+ kfn(s)
+
+ // Add key seperator.
+ s.B = append(s.B, '=')
+
+ // Pass to elem fn.
+ s.P = map_elem(i)
+ vfn(s)
+
+ // Add comma pair seperator.
+ s.B = append(s.B, ',', ' ')
+ }
+
+ if len(s.B) != l {
+ // Drop final ", ".
+ s.B = s.B[:len(s.B)-2]
+ }
+
+ // Append object brace.
+ s.B = append(s.B, '}')
+ }
+ }
+
+ return func(s *State) {
+ if s.P == nil || *(*unsafe.Pointer)(s.P) == nil {
+ // Append nil value with type.
+ appendNilType(s, typestrPtrs)
+ return
+ }
+
+ // Build reflect value, and then a map iter.
+ v := build_reflect_value(rtype, s.P, flags)
+ i := map_iter(v)
+
+ // Include type info.
+ if s.A.WithType() {
+ s.B = append(s.B, typestrRefs...)
+ }
+
+ // Prepend object brace.
+ s.B = append(s.B, '{')
+
+ // Before len.
+ l := len(s.B)
+
+ for i.Next() {
+ // Pass to key fn.
+ s.P = map_key(i)
+ kfn(s)
+
+ // Add key seperator.
+ s.B = append(s.B, '=')
+
+ // Pass to elem fn.
+ s.P = map_elem(i)
+ vfn(s)
+
+ // Add comma pair seperator.
+ s.B = append(s.B, ',', ' ')
+ }
+
+ if len(s.B) != l {
+ // Drop final ", ".
+ s.B = s.B[:len(s.B)-2]
+ }
+
+ // Append object brace.
+ s.B = append(s.B, '}')
+ }
+}
diff --git a/vendor/codeberg.org/gruf/go-kv/v2/format/methods.go b/vendor/codeberg.org/gruf/go-kv/v2/format/methods.go
new file mode 100644
index 000000000..7c1795771
--- /dev/null
+++ b/vendor/codeberg.org/gruf/go-kv/v2/format/methods.go
@@ -0,0 +1,153 @@
+package format
+
+import (
+ "reflect"
+ "unsafe"
+)
+
+type Stringer interface{ String() string }
+
+var (
+ // stringer type for implement checks.
+ stringerType = typeof[Stringer]()
+
+ // error type for implement checks.
+ errorType = typeof[error]()
+)
+
+// getMethodType returns a *possible* FormatFunc to handle case
+// of a type that implements any known interface{} types, else nil.
+func getMethodType(t typenode) FormatFunc {
+ switch {
+ case t.rtype.Implements(stringerType):
+ switch t.rtype.Kind() {
+ case reflect.Interface:
+ return getInterfaceStringerType(t)
+ default:
+ return getConcreteStringerType(t)
+ }
+ case t.rtype.Implements(errorType):
+ switch t.rtype.Kind() {
+ case reflect.Interface:
+ return getInterfaceErrorType(t)
+ default:
+ return getConcreteErrorType(t)
+ }
+ default:
+ return nil
+ }
+}
+
+// getInterfaceStringerType returns a FormatFunc to handle case of an interface{}
+// type that implements Stringer{}, i.e. Stringer{} itself and any superset of.
+func getInterfaceStringerType(t typenode) FormatFunc {
+ switch t.indirect() && !t.iface_indir() {
+ case true:
+ return with_typestr_ptrs(t, func(s *State) {
+ s.P = *(*unsafe.Pointer)(s.P)
+ if s.P == nil || (*reflect_nonEmptyInterface)(s.P).word == nil {
+ appendNil(s)
+ return
+ }
+ v := *(*Stringer)(s.P)
+ appendString(s, v.String())
+ })
+ case false:
+ return with_typestr_ptrs(t, func(s *State) {
+ if s.P == nil || (*reflect_nonEmptyInterface)(s.P).word == nil {
+ appendNil(s)
+ return
+ }
+ v := *(*Stringer)(s.P)
+ appendString(s, v.String())
+ })
+ default:
+ panic("unreachable")
+ }
+}
+
+// getConcreteStringerType returns a FormatFunc to handle case of concrete
+// (i.e. non-interface{}) type that has a Stringer{} method receiver.
+func getConcreteStringerType(t typenode) FormatFunc {
+ itab := get_iface_ITab[Stringer](t.rtype)
+ switch t.indirect() && !t.iface_indir() {
+ case true:
+ return with_typestr_ptrs(t, func(s *State) {
+ s.P = *(*unsafe.Pointer)(s.P)
+ if s.P == nil {
+ appendNil(s)
+ return
+ }
+ v := *(*Stringer)(pack_iface(itab, s.P))
+ appendString(s, v.String())
+ })
+ case false:
+ return with_typestr_ptrs(t, func(s *State) {
+ if s.P == nil {
+ appendNil(s)
+ return
+ }
+ v := *(*Stringer)(pack_iface(itab, s.P))
+ appendString(s, v.String())
+ })
+ default:
+ panic("unreachable")
+ }
+}
+
+// getInterfaceErrorType returns a FormatFunc to handle case of an interface{}
+// type that implements error{}, i.e. error{} itself and any superset of.
+func getInterfaceErrorType(t typenode) FormatFunc {
+ switch t.indirect() && !t.iface_indir() {
+ case true:
+ return with_typestr_ptrs(t, func(s *State) {
+ s.P = *(*unsafe.Pointer)(s.P)
+ if s.P == nil || (*reflect_nonEmptyInterface)(s.P).word == nil {
+ appendNil(s)
+ return
+ }
+ v := *(*error)(s.P)
+ appendString(s, v.Error())
+ })
+ case false:
+ return with_typestr_ptrs(t, func(s *State) {
+ if s.P == nil || (*reflect_nonEmptyInterface)(s.P).word == nil {
+ appendNil(s)
+ return
+ }
+ v := *(*error)(s.P)
+ appendString(s, v.Error())
+ })
+ default:
+ panic("unreachable")
+ }
+}
+
+// getConcreteErrorType returns a FormatFunc to handle case of concrete
+// (i.e. non-interface{}) type that has an error{} method receiver.
+func getConcreteErrorType(t typenode) FormatFunc {
+ itab := get_iface_ITab[error](t.rtype)
+ switch t.indirect() && !t.iface_indir() {
+ case true:
+ return with_typestr_ptrs(t, func(s *State) {
+ s.P = *(*unsafe.Pointer)(s.P)
+ if s.P == nil {
+ appendNil(s)
+ return
+ }
+ v := *(*error)(pack_iface(itab, s.P))
+ appendString(s, v.Error())
+ })
+ case false:
+ return with_typestr_ptrs(t, func(s *State) {
+ if s.P == nil {
+ appendNil(s)
+ return
+ }
+ v := *(*error)(pack_iface(itab, s.P))
+ appendString(s, v.Error())
+ })
+ default:
+ panic("unreachable")
+ }
+}
diff --git a/vendor/codeberg.org/gruf/go-kv/v2/format/pointer.go b/vendor/codeberg.org/gruf/go-kv/v2/format/pointer.go
new file mode 100644
index 000000000..1f860aba9
--- /dev/null
+++ b/vendor/codeberg.org/gruf/go-kv/v2/format/pointer.go
@@ -0,0 +1,128 @@
+package format
+
+import (
+ "reflect"
+ "unsafe"
+)
+
+// derefPointerType returns a FormatFunc capable of dereferencing
+// and formatting the given pointer type currently in typenode{}.
+// note this will fetch a sub-FormatFunc for resulting value type.
+func (fmt *Formatter) derefPointerType(t typenode) FormatFunc {
+ var n int
+ rtype := t.rtype
+ flags := t.flags
+
+ // Iteratively dereference pointer types.
+ for rtype.Kind() == reflect.Pointer {
+
+ // If this is actual indirect
+ // memory, increase dereferences.
+ if flags&reflect_flagIndir != 0 {
+ n++
+ }
+
+ // Get next elem type.
+ rtype = rtype.Elem()
+
+ // Get next set of dereferenced elem type flags.
+ flags = reflect_pointer_elem_flags(flags, rtype)
+ }
+
+ // Wrap value as typenode.
+ vt := t.next(rtype, flags)
+
+ // Get value format func.
+ fn := fmt.loadOrGet(vt)
+ if fn == nil {
+ panic("unreachable")
+ }
+
+ if !t.needs_typestr() {
+ if n <= 0 {
+ // No derefs are needed.
+ return func(s *State) {
+ if s.P == nil {
+ // Final check.
+ appendNil(s)
+ return
+ }
+
+ // Format
+ // final
+ // value.
+ fn(s)
+ }
+ }
+
+ return func(s *State) {
+ // Deref n number times.
+ for i := n; i > 0; i-- {
+
+ if s.P == nil {
+ // Nil check.
+ appendNil(s)
+ return
+ }
+
+ // Further deref pointer value.
+ s.P = *(*unsafe.Pointer)(s.P)
+ }
+
+ if s.P == nil {
+ // Final check.
+ appendNil(s)
+ return
+ }
+
+ // Format
+ // final
+ // value.
+ fn(s)
+ }
+ }
+
+ // Final type string with ptrs.
+ typestr := t.typestr_with_ptrs()
+
+ if n <= 0 {
+ // No derefs are needed.
+ return func(s *State) {
+ if s.P == nil {
+ // Final nil value check.
+ appendNilType(s, typestr)
+ return
+ }
+
+ // Format
+ // final
+ // value.
+ fn(s)
+ }
+ }
+
+ return func(s *State) {
+ // Deref n number times.
+ for i := n; i > 0; i-- {
+ if s.P == nil {
+ // Check for nil value.
+ appendNilType(s, typestr)
+ return
+ }
+
+ // Further deref pointer value.
+ s.P = *(*unsafe.Pointer)(s.P)
+ }
+
+ if s.P == nil {
+ // Final nil value check.
+ appendNilType(s, typestr)
+ return
+ }
+
+ // Format
+ // final
+ // value.
+ fn(s)
+ }
+}
diff --git a/vendor/codeberg.org/gruf/go-kv/v2/format/slice.go b/vendor/codeberg.org/gruf/go-kv/v2/format/slice.go
new file mode 100644
index 000000000..f76e85410
--- /dev/null
+++ b/vendor/codeberg.org/gruf/go-kv/v2/format/slice.go
@@ -0,0 +1,158 @@
+package format
+
+// iterSliceType returns a FormatFunc capable of iterating
+// and formatting the given slice type currently in typenode{}.
+// note this will fetch a sub-FormatFunc for the slice element
+// type, and also handle special cases of []byte, []rune slices.
+func (fmt *Formatter) iterSliceType(t typenode) FormatFunc {
+
+ // Get nested element type.
+ elem := t.rtype.Elem()
+ esz := elem.Size()
+
+ // Get nested elem typenode with flags.
+ flags := reflect_slice_elem_flags(elem)
+ et := t.next(elem, flags)
+
+ // Get elem format func.
+ fn := fmt.loadOrGet(et)
+ if fn == nil {
+ panic("unreachable")
+ }
+
+ if !t.needs_typestr() {
+ return func(s *State) {
+ ptr := s.P
+
+ // Get data as unsafe slice header.
+ hdr := (*unsafeheader_Slice)(ptr)
+ if hdr == nil || hdr.Data == nil {
+
+ // Append nil.
+ appendNil(s)
+ return
+ }
+
+ // Prepend array brace.
+ s.B = append(s.B, '[')
+
+ if hdr.Len > 0 {
+ for i := 0; i < hdr.Len; i++ {
+ // Format at array index.
+ offset := esz * uintptr(i)
+ s.P = add(hdr.Data, offset)
+ fn(s)
+
+ // Append separator.
+ s.B = append(s.B, ',')
+ }
+
+ // Drop final comma.
+ s.B = s.B[:len(s.B)-1]
+ }
+
+ // Append array brace.
+ s.B = append(s.B, ']')
+ }
+ }
+
+ // Slice type string with ptrs / refs.
+ typestrPtrs := t.typestr_with_ptrs()
+ typestrRefs := t.typestr_with_refs()
+
+ return func(s *State) {
+ ptr := s.P
+
+ // Get data as unsafe slice header.
+ hdr := (*unsafeheader_Slice)(ptr)
+ if hdr == nil || hdr.Data == nil {
+
+ // Append nil value with type.
+ appendNilType(s, typestrPtrs)
+ return
+ }
+
+ // Open / close braces.
+ var open, close uint8
+ open, close = '[', ']'
+
+ // Include type info.
+ if s.A.WithType() {
+ s.B = append(s.B, typestrRefs...)
+ open, close = '{', '}'
+ }
+
+ // Prepend array brace.
+ s.B = append(s.B, open)
+
+ if hdr.Len > 0 {
+ for i := 0; i < hdr.Len; i++ {
+ // Format at array index.
+ offset := esz * uintptr(i)
+ s.P = add(hdr.Data, offset)
+ fn(s)
+
+ // Append separator.
+ s.B = append(s.B, ',')
+ }
+
+ // Drop final comma.
+ s.B = s.B[:len(s.B)-1]
+ }
+
+ // Append array brace.
+ s.B = append(s.B, close)
+ }
+}
+
+func wrapByteSlice(t typenode, fn FormatFunc) FormatFunc {
+ if !t.needs_typestr() {
+ return func(s *State) {
+ if s.A.AsText() || s.A.AsQuotedText() || s.A.AsQuotedASCII() {
+ appendString(s, *(*string)(s.P))
+ } else {
+ fn(s)
+ }
+ }
+ }
+ typestr := t.typestr_with_ptrs()
+ return func(s *State) {
+ if s.A.AsText() || s.A.AsQuotedText() || s.A.AsQuotedASCII() {
+ if s.A.WithType() {
+ s.B = append(s.B, "("+typestr+")("...)
+ appendString(s, *(*string)(s.P))
+ s.B = append(s.B, ")"...)
+ } else {
+ appendString(s, *(*string)(s.P))
+ }
+ } else {
+ fn(s)
+ }
+ }
+}
+
+func wrapRuneSlice(t typenode, fn FormatFunc) FormatFunc {
+ if !t.needs_typestr() {
+ return func(s *State) {
+ if s.A.AsText() || s.A.AsQuotedText() || s.A.AsQuotedASCII() {
+ appendString(s, string(*(*[]rune)(s.P)))
+ } else {
+ fn(s)
+ }
+ }
+ }
+ typestr := t.typestr_with_ptrs()
+ return func(s *State) {
+ if s.A.AsText() || s.A.AsQuotedText() || s.A.AsQuotedASCII() {
+ if s.A.WithType() {
+ s.B = append(s.B, "("+typestr+")("...)
+ appendString(s, string(*(*[]rune)(s.P)))
+ s.B = append(s.B, ")"...)
+ } else {
+ appendString(s, string(*(*[]rune)(s.P)))
+ }
+ } else {
+ fn(s)
+ }
+ }
+}
diff --git a/vendor/codeberg.org/gruf/go-kv/v2/format/struct.go b/vendor/codeberg.org/gruf/go-kv/v2/format/struct.go
new file mode 100644
index 000000000..cc1c8634d
--- /dev/null
+++ b/vendor/codeberg.org/gruf/go-kv/v2/format/struct.go
@@ -0,0 +1,173 @@
+package format
+
+// field stores the minimum necessary
+// data for iterating and formatting
+// each field in a given struct.
+type field struct {
+ format FormatFunc
+ name string
+ offset uintptr
+}
+
+// iterStructType returns a FormatFunc capable of iterating
+// and formatting the given struct type currently in typenode{}.
+// note this will fetch sub-FormatFuncs for each struct field.
+func (fmt *Formatter) iterStructType(t typenode) FormatFunc {
+ // Number of struct fields.
+ n := t.rtype.NumField()
+
+ // Gather format functions.
+ fields := make([]field, n)
+ for i := 0; i < n; i++ {
+
+ // Get struct field at index.
+ sfield := t.rtype.Field(i)
+ rtype := sfield.Type
+
+ // Get nested field typenode with appropriate flags.
+ flags := reflect_struct_field_flags(t.flags, rtype)
+ ft := t.next(sfield.Type, flags)
+
+ // Get field format func.
+ fn := fmt.loadOrGet(ft)
+ if fn == nil {
+ panic("unreachable")
+ }
+
+ // Set field info.
+ fields[i] = field{
+ format: fn,
+ name: sfield.Name,
+ offset: sfield.Offset,
+ }
+ }
+
+ // Handle no. fields.
+ switch len(fields) {
+ case 0:
+ return emptyStructType(t)
+ case 1:
+ return iterSingleFieldStructType(t, fields[0])
+ default:
+ return iterMultiFieldStructType(t, fields)
+ }
+}
+
+func emptyStructType(t typenode) FormatFunc {
+ if !t.needs_typestr() {
+ return func(s *State) {
+ // Append empty object.
+ s.B = append(s.B, "{}"...)
+ }
+ }
+
+ // Struct type string with refs.
+ typestr := t.typestr_with_refs()
+
+ // Append empty object
+ // with type information.
+ return func(s *State) {
+ if s.A.WithType() {
+ s.B = append(s.B, typestr...)
+ }
+ s.B = append(s.B, "{}"...)
+ }
+}
+
+func iterSingleFieldStructType(t typenode, field field) FormatFunc {
+ if field.format == nil {
+ panic("nil func")
+ }
+
+ if !t.needs_typestr() {
+ return func(s *State) {
+ // Wrap 'fn' with braces + field name.
+ s.B = append(s.B, "{"+field.name+"="...)
+ field.format(s)
+ s.B = append(s.B, "}"...)
+ }
+ }
+
+ // Struct type string with refs.
+ typestr := t.typestr_with_refs()
+
+ return func(s *State) {
+ // Include type info.
+ if s.A.WithType() {
+ s.B = append(s.B, typestr...)
+ }
+
+ // Wrap 'fn' with braces + field name.
+ s.B = append(s.B, "{"+field.name+"="...)
+ field.format(s)
+ s.B = append(s.B, "}"...)
+ }
+}
+
+func iterMultiFieldStructType(t typenode, fields []field) FormatFunc {
+ for _, field := range fields {
+ if field.format == nil {
+ panic("nil func")
+ }
+ }
+
+ if !t.needs_typestr() {
+ return func(s *State) {
+ ptr := s.P
+
+ // Prepend object brace.
+ s.B = append(s.B, '{')
+
+ for i := 0; i < len(fields); i++ {
+ // Get struct field ptr via offset.
+ s.P = add(ptr, fields[i].offset)
+
+ // Append field name and value separator.
+ s.B = append(s.B, fields[i].name+"="...)
+
+ // Format i'th field.
+ fields[i].format(s)
+ s.B = append(s.B, ',', ' ')
+ }
+
+ // Drop final ", ".
+ s.B = s.B[:len(s.B)-2]
+
+ // Append object brace.
+ s.B = append(s.B, '}')
+ }
+ }
+
+ // Struct type string with refs.
+ typestr := t.typestr_with_refs()
+
+ return func(s *State) {
+ ptr := s.P
+
+ // Include type info.
+ if s.A.WithType() {
+ s.B = append(s.B, typestr...)
+ }
+
+ // Prepend object brace.
+ s.B = append(s.B, '{')
+
+ for i := 0; i < len(fields); i++ {
+ // Get struct field ptr via offset.
+ s.P = add(ptr, fields[i].offset)
+
+ // Append field name and value separator.
+ s.B = append(s.B, fields[i].name+"="...)
+
+ // Format i'th field.
+ fields[i].format(s)
+ s.B = append(s.B, ',', ' ')
+ }
+
+ // Drop final ", ".
+ s.B = s.B[:len(s.B)-2]
+
+ // Append object brace.
+ s.B = append(s.B, '}')
+ }
+}
diff --git a/vendor/codeberg.org/gruf/go-kv/v2/format/type.go b/vendor/codeberg.org/gruf/go-kv/v2/format/type.go
new file mode 100644
index 000000000..ec5557b05
--- /dev/null
+++ b/vendor/codeberg.org/gruf/go-kv/v2/format/type.go
@@ -0,0 +1,150 @@
+package format
+
+import (
+ "reflect"
+ "strings"
+)
+
+// typenode ...
+type typenode struct {
+ typeinfo
+ parent *typenode
+}
+
+// typeinfo ...
+type typeinfo struct {
+ rtype reflect.Type
+ flags reflect_flag
+}
+
+// new_typenode returns a new typenode{} with reflect.Type and flags.
+func new_typenode(t reflect.Type, flags reflect_flag) typenode {
+ return typenode{typeinfo: typeinfo{
+ rtype: t,
+ flags: flags,
+ }}
+}
+
+// key returns data (i.e. type value info)
+// to store a FormatFunc under in a cache.
+func (n typenode) key() typeinfo {
+ return n.typeinfo
+}
+
+// indirect returns whether reflect_flagIndir is set for given type flags.
+func (n typenode) indirect() bool {
+ return n.flags&reflect_flagIndir != 0
+}
+
+// iface_indir returns the result of abi.Type{}.IfaceIndir() for underlying type.
+func (n typenode) iface_indir() bool {
+ return abi_Type_IfaceIndir(n.rtype)
+}
+
+// next ...
+func (n typenode) next(t reflect.Type, flags reflect_flag) typenode {
+ child := new_typenode(t, flags)
+ child.parent = &n
+ return child
+}
+
+// visit ...
+func (n typenode) visit() bool {
+ t := n.rtype
+
+ // Check if type is already encountered further up tree.
+ for node := n.parent; node != nil; node = node.parent {
+ if node.rtype == t {
+ return false
+ }
+ }
+
+ return true
+}
+
+// needs_typestr returns whether the type contained in the
+// receiving typenode{} needs type string information prefixed
+// when the TypeMask argument flag bit is set. Certain types
+// don't need this as the parent type already indicates this.
+func (n typenode) needs_typestr() bool {
+ if n.parent == nil {
+ return true
+ }
+ switch p := n.parent.rtype; p.Kind() {
+ case reflect.Pointer:
+ return n.parent.needs_typestr()
+ case reflect.Slice,
+ reflect.Array,
+ reflect.Map:
+ return false
+ default:
+ return true
+ }
+}
+
+// typestr_with_ptrs returns the type string for
+// current typenode{} with asterisks for pointers.
+func (n typenode) typestr_with_ptrs() string {
+ t := n.rtype
+
+ // Check for parent.
+ if n.parent == nil {
+ return t.String()
+ }
+
+ // Get parent type.
+ p := n.parent.rtype
+
+ // If parent is not ptr, then
+ // this was not a deref'd ptr.
+ if p.Kind() != reflect.Pointer {
+ return t.String()
+ }
+
+ // Return un-deref'd
+ // ptr (parent) type.
+ return p.String()
+}
+
+// typestr_with_refs returns the type string for
+// current typenode{} with ampersands for pointers.
+func (n typenode) typestr_with_refs() string {
+ t := n.rtype
+
+ // Check for parent.
+ if n.parent == nil {
+ return t.String()
+ }
+
+ // Get parent type.
+ p := n.parent.rtype
+
+ var d int
+
+ // Count number of dereferences.
+ for p.Kind() == reflect.Pointer {
+ p = p.Elem()
+ d++
+ }
+
+ if d <= 0 {
+ // Prefer just returning our
+ // own string if possible, to
+ // reduce number of strings
+ // we need to allocate.
+ return t.String()
+ }
+
+ // Value type str.
+ str := t.String()
+
+ // Return with type ptrs
+ // symbolized by 'refs'.
+ var buf strings.Builder
+ buf.Grow(len(str) + d)
+ for i := 0; i < d; i++ {
+ buf.WriteByte('&')
+ }
+ buf.WriteString(str)
+ return buf.String()
+}
diff --git a/vendor/codeberg.org/gruf/go-kv/v2/util.go b/vendor/codeberg.org/gruf/go-kv/v2/util.go
new file mode 100644
index 000000000..90fba59de
--- /dev/null
+++ b/vendor/codeberg.org/gruf/go-kv/v2/util.go
@@ -0,0 +1,147 @@
+package kv
+
+import (
+ "strconv"
+ "strings"
+
+ "codeberg.org/gruf/go-byteutil"
+ "codeberg.org/gruf/go-kv/v2/format"
+)
+
+// AppendQuoteString will append (and escape/quote where necessary) a field string.
+func AppendQuoteString(buf *byteutil.Buffer, str string) {
+ switch {
+ case len(str) == 0:
+ // Append empty quotes.
+ buf.B = append(buf.B, `""`...)
+ return
+
+ case len(str) == 1:
+ // Append escaped single byte.
+ buf.B = format.AppendEscapeByte(buf.B, str[0])
+ return
+
+ case len(str) > format.SingleTermLine || !format.IsSafeASCII(str):
+ // Long line or contains non-ascii chars.
+ buf.B = strconv.AppendQuote(buf.B, str)
+ return
+
+ case !isQuoted(str):
+ // Not single/double quoted already.
+
+ if format.ContainsSpaceOrTab(str) {
+ // Quote un-enclosed spaces.
+ buf.B = append(buf.B, '"')
+ buf.B = append(buf.B, str...)
+ buf.B = append(buf.B, '"')
+ return
+ }
+
+ if format.ContainsDoubleQuote(str) {
+ // Contains double quote, double quote
+ // and append escaped existing.
+ buf.B = append(buf.B, '"')
+ buf.B = format.AppendEscape(buf.B, str)
+ buf.B = append(buf.B, '"')
+ return
+ }
+ }
+
+ // Double quoted, enclosed in braces, or
+ // literally anything else: append as-is.
+ buf.B = append(buf.B, str...)
+ return
+}
+
+// AppendQuoteValue will append (and escape/quote where necessary) a formatted value string.
+func AppendQuoteValue(buf *byteutil.Buffer, str string) {
+ switch {
+ case len(str) == 0:
+ // Append empty quotes.
+ buf.B = append(buf.B, `""`...)
+ return
+
+ case len(str) == 1:
+ // Append quoted single byte.
+ buf.B = format.AppendQuoteByte(buf.B, str[0])
+ return
+
+ case len(str) > format.SingleTermLine || !format.IsSafeASCII(str):
+ // Long line or contains non-ascii chars.
+ buf.B = strconv.AppendQuote(buf.B, str)
+ return
+
+ case !isQuoted(str):
+ // Not single/double quoted already.
+
+ // Get space / tab indices (if any).
+ s := strings.IndexByte(str, ' ')
+ t := strings.IndexByte(str, '\t')
+
+ // Find first whitespace.
+ sp0 := smallest(s, t)
+ if sp0 < 0 {
+ break
+ }
+
+ // Check if str is enclosed by braces.
+ // (but without any key-value separator).
+ if (enclosedBy(str, sp0, '{', '}') ||
+ enclosedBy(str, sp0, '[', ']') ||
+ enclosedBy(str, sp0, '(', ')')) &&
+ strings.IndexByte(str, '=') < 0 {
+ break
+ }
+
+ if format.ContainsDoubleQuote(str) {
+ // Contains double quote, double quote
+ // and append escaped existing.
+ buf.B = append(buf.B, '"')
+ buf.B = format.AppendEscape(buf.B, str)
+ buf.B = append(buf.B, '"')
+ return
+ }
+
+ // Quote un-enclosed spaces.
+ buf.B = append(buf.B, '"')
+ buf.B = append(buf.B, str...)
+ buf.B = append(buf.B, '"')
+ return
+ }
+
+ // Double quoted, enclosed in braces, or
+ // literally anything else: append as-is.
+ buf.B = append(buf.B, str...)
+ return
+}
+
+// isQuoted checks if string is single or double quoted.
+func isQuoted(str string) bool {
+ return (str[0] == '"' && str[len(str)-1] == '"') ||
+ (str[0] == '\'' && str[len(str)-1] == '\'')
+}
+
+// smallest attempts to return the smallest positive value of those given.
+func smallest(i1, i2 int) int {
+ if i1 >= 0 && (i2 < 0 || i1 < i2) {
+ return i1
+ }
+ return i2
+}
+
+// enclosedBy will check if given string is enclosed by end, and at least non-whitespace up to start.
+func enclosedBy(str string, sp int, start, end byte) bool {
+ // Check for ending char in string.
+ if str[len(str)-1] != end {
+ return false
+ }
+
+ // Check for starting char in string.
+ i := strings.IndexByte(str, start)
+ if i < 0 {
+ return false
+ }
+
+ // Check before space.
+ return i < sp
+}
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 1dc573de0..8d6d1cbd1 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -255,6 +255,10 @@ codeberg.org/gruf/go-iotools
## explicit; go 1.20
codeberg.org/gruf/go-kv
codeberg.org/gruf/go-kv/format
+# codeberg.org/gruf/go-kv/v2 v2.0.3
+## explicit; go 1.24
+codeberg.org/gruf/go-kv/v2
+codeberg.org/gruf/go-kv/v2/format
# codeberg.org/gruf/go-list v0.0.0-20240425093752-494db03d641f
## explicit; go 1.21.3
codeberg.org/gruf/go-list