[performance] bump codeberg.org/gruf/go-kv to v2 (#4341)

updates our codeberg.org/gruf/go-kv log key-value formatting library to latest version, which comes with some maaaaaaajor speed boosts in the form of:
- very minimal reflect.Value{} usage
- caching prepared formatting functions per type

~~still a work-in-progress until i make a release tag on the go-kv repository, which itself is waiting on published benchmark results in the README and finishing writing some code comments~~

benchmarks so far show this to be ~3x faster than the "fmt" stdlib package on average, when run across a wide variety (106 different types) of test cases, while still creating more visually friendly log output and actually recursing down nested struct ptrs

Reviewed-on: https://codeberg.org/superseriousbusiness/gotosocial/pulls/4341
Co-authored-by: kim <grufwub@gmail.com>
Co-committed-by: kim <grufwub@gmail.com>
This commit is contained in:
kim 2025-07-29 09:23:20 +02:00 committed by tobi
commit e3dfd88893
47 changed files with 2819 additions and 61 deletions

3
go.mod
View file

@ -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

2
go.sum generated
View file

@ -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=

View file

@ -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"

View file

@ -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"
)

View file

@ -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, &gtsmodel.Status{
ID: data[i].ID,

View file

@ -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"

View file

@ -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"
)

View file

@ -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 (

View file

@ -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

View file

@ -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

View file

@ -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 {

View file

@ -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() {

View file

@ -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"
)

View file

@ -21,7 +21,7 @@ import (
"context"
"fmt"
"codeberg.org/gruf/go-kv"
"codeberg.org/gruf/go-kv/v2"
)
type Entry struct {

View file

@ -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 <http://www.gnu.org/licenses/>.
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
}

View file

@ -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 (

View file

@ -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"

View file

@ -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"
)

View file

@ -26,7 +26,7 @@ import (
"net/http"
"strconv"
"codeberg.org/gruf/go-kv"
"codeberg.org/gruf/go-kv/v2"
"github.com/gin-gonic/gin"

View file

@ -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"
)

View file

@ -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

View file

@ -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 (

View file

@ -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

View file

@ -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.

View file

@ -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

View file

@ -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"

View file

@ -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"

View file

@ -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{}{

View file

@ -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)
}

9
vendor/codeberg.org/gruf/go-kv/v2/LICENSE generated vendored Normal file
View file

@ -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.

5
vendor/codeberg.org/gruf/go-kv/v2/README.md generated vendored Normal file
View file

@ -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/`.

103
vendor/codeberg.org/gruf/go-kv/v2/field.go generated vendored Normal file
View file

@ -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()
}

63
vendor/codeberg.org/gruf/go-kv/v2/field_fmt.go generated vendored Normal file
View file

@ -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)
}

46
vendor/codeberg.org/gruf/go-kv/v2/field_format.go generated vendored Normal file
View file

@ -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)
}

262
vendor/codeberg.org/gruf/go-kv/v2/format/abi.go generated vendored Normal file
View file

@ -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<<reflect_flagKindWidth - 1
reflect_flagStickyRO reflect_flag = 1 << 5
reflect_flagEmbedRO reflect_flag = 1 << 6
reflect_flagIndir reflect_flag = 1 << 7
reflect_flagAddr reflect_flag = 1 << 8
reflect_flagMethod reflect_flag = 1 << 9
reflect_flagMethodShift = 10
reflect_flagRO reflect_flag = reflect_flagStickyRO | reflect_flagEmbedRO
// custom flag to indicate key types.
flagKeyType = 1 << 10
)
// reflect_iface_elem_flags returns the reflect_flag expected of an unboxed interface element of type.
//
// see: go/src/reflect/value.go unpackElem()
func reflect_iface_elem_flags(elemType reflect.Type) reflect_flag {
if elemType == nil {
return 0
}
flags := reflect_flag(abi_Type_Kind(elemType))
if abi_Type_IfaceIndir(elemType) {
flags |= reflect_flagIndir
}
return flags
}
// reflect_pointer_elem_flags returns the reflect_flag expected of a dereferenced pointer element of type.
//
// see: go/src/reflect/value.go Value.Elem()
func reflect_pointer_elem_flags(ptrFlags reflect_flag, elemType reflect.Type) reflect_flag {
return ptrFlags | reflect_flagIndir | reflect_flagAddr | reflect_flag(abi_Type_Kind(elemType))
}
// reflect_array_elem_flags returns the reflect_flag expected of an element of type in an array.
//
// see: go/src/reflect/value.go Value.Index()
func reflect_array_elem_flags(arrayFlags reflect_flag, elemType reflect.Type) reflect_flag {
return arrayFlags&(reflect_flagIndir|reflect_flagAddr) | reflect_flag(abi_Type_Kind(elemType))
}
// reflect_slice_elem_flags returns the reflect_flag expected of a slice element of type.
//
// see: go/src/reflect/value.go Value.Index()
func reflect_slice_elem_flags(elemType reflect.Type) reflect_flag {
return reflect_flagAddr | reflect_flagIndir | reflect_flag(abi_Type_Kind(elemType))
}
// reflect_struct_field_flags returns the reflect_flag expected of a struct field of type.
//
// see: go/src/reflect/value.go Value.Field()
func reflect_struct_field_flags(structFlags reflect_flag, fieldType reflect.Type) reflect_flag {
return structFlags&(reflect_flagIndir|reflect_flagAddr) | reflect_flag(abi_Type_Kind(fieldType))
}
// reflect_map_key_flags returns the reflect_flag expected of a map key of type (with our own key type mask set).
//
// see: go/src/reflect/map_swiss.go MapIter.Key()
func reflect_map_key_flags(keyType reflect.Type) reflect_flag {
return flagKeyType | reflect_flag(abi_Type_Kind(keyType))
}
// reflect_map_elem_flags returns the reflect_flag expected of a map element of type.
//
// see: go/src/reflect/map_swiss.go MapIter.Value()
func reflect_map_elem_flags(elemType reflect.Type) reflect_flag {
return reflect_flag(abi_Type_Kind(elemType))
}
// reflect_nonEmptyInterface is a copy of the memory layout of reflect.nonEmptyInterface,
// which is also to say the memory layout of any non-empty (i.e. w/ method) interface.
//
// see: go/src/reflect/value.go
type reflect_nonEmptyInterface struct {
itab uintptr
word unsafe.Pointer
}
// reflect_Value is a copy of the memory layout of reflect.Value{}.
//
// see: go/src/reflect/value.go
type reflect_Value struct {
typ_ unsafe.Pointer
ptr unsafe.Pointer
reflect_flag
}
func init() {
if unsafe.Sizeof(reflect_Value{}) != unsafe.Sizeof(reflect.Value{}) {
panic("reflect_Value{} not in sync with reflect.Value{}")
}
}
// reflect_type_data returns the .word from the reflect.Type{} cast
// as the reflect.nonEmptyInterface{}, which itself will be a pointer
// to the actual abi.Type{} that this reflect.Type{} is wrapping.
func reflect_type_data(t reflect.Type) unsafe.Pointer {
return (*reflect_nonEmptyInterface)(unsafe.Pointer(&t)).word
}
// build_reflect_value manually builds a reflect.Value{} by setting the internal field members.
func build_reflect_value(rtype reflect.Type, data unsafe.Pointer, flags reflect_flag) reflect.Value {
return *(*reflect.Value)(unsafe.Pointer(&reflect_Value{reflect_type_data(rtype), data, flags}))
}
// maps_Iter is a copy of the memory layout of maps.Iter{}.
//
// see: go/src/internal/runtime/maps/table.go
type maps_Iter struct {
key unsafe.Pointer
elem unsafe.Pointer
_ uintptr
_ uintptr
_ uint64
_ uint64
_ uint64
_ uint8
_ int
_ uintptr
_ struct{ _ unsafe.Pointer }
_ uint64
}
// reflect_MapIter is a copy of the memory layout of reflect.MapIter{}.
//
// see: go/src/reflect/map_swiss.go
type reflect_MapIter struct {
m reflect.Value
hiter maps_Iter
}
func init() {
if unsafe.Sizeof(reflect_MapIter{}) != unsafe.Sizeof(reflect.MapIter{}) {
panic("reflect_MapIter{} not in sync with reflect.MapIter{}")
}
}
// map_iter creates a new map iterator from value,
// skipping the initial v.MapRange() type checking.
func map_iter(v reflect.Value) *reflect.MapIter {
var i reflect_MapIter
i.m = v
return (*reflect.MapIter)(unsafe.Pointer(&i))
}
// map_key returns ptr to current map key in iter.
func map_key(i *reflect.MapIter) unsafe.Pointer {
return (*reflect_MapIter)(unsafe.Pointer(i)).hiter.key
}
// map_elem returns ptr to current map element in iter.
func map_elem(i *reflect.MapIter) unsafe.Pointer {
return (*reflect_MapIter)(unsafe.Pointer(i)).hiter.elem
}
// see: go/src/internal/unsafeheader/unsafeheader.go
type unsafeheader_Slice struct {
Data unsafe.Pointer
Len int
Cap int
}
// see: go/src/internal/unsafeheader/unsafeheader.go
type unsafeheader_String struct {
Data unsafe.Pointer
Len int
}

247
vendor/codeberg.org/gruf/go-kv/v2/format/args.go generated vendored Normal file
View file

@ -0,0 +1,247 @@
package format
const (
// TypeMask when set in argument flags
// indicates that type information of
// the passed value, and all nested types,
// should be included in formatted output.
TypeMask = uint64(1) << 0
// LogfmtMask when set in argument flags
// indicates that strings should be escaped
// and quoted only where necessary. i.e. if
// it contains any unsafe ASCII chars or double
// quotes it will be quoted and escaped, if it
// contains any spaces it will be quoted, and
// all else will be printed as-is. This proves
// particularly well readable in key-value types.
LogfmtMask = uint64(1) << 1
// NumberMask when set in argument flags
// indicates that where possible value
// types should be formatted as numbers,
// i.e. byte or rune types.
NumberMask = uint64(1) << 2
// TextMask when set in argument flags
// indicates that where possible value
// types should be formatted as text,
// i.e. []byte or []rune types.
TextMask = uint64(1) << 3
// QuotedTextMask when set in argument flags
// indicates that text should always be quoted.
QuotedTextMask = uint64(1) << 4
// QuotedAsciiMask when set in argument flags
// indicates that text should always be quoted,
// and escaped as ASCII characters where needed.
QuotedAsciiMask = uint64(1) << 5
// NoMethodMask when set in argument flags
// indicates that where a type supports a
// known method, (e.g. Error() or String()),
// this should not be used for formatting
// instead treating as a method-less type.
// e.g. printing the entire struct value of
// a &url.URL{} without calling String().
NoMethodMask = uint64(1) << 6
)
var (
// default set of Args.
defaultArgs = Args{
Flags: LogfmtMask | TextMask,
Int: IntArgs{Base: 10},
Uint: IntArgs{Base: 10},
Float: FloatArgs{Fmt: 'g', Prec: -1},
Complex: ComplexArgs{
Real: FloatArgs{Fmt: 'g', Prec: -1},
Imag: FloatArgs{Fmt: 'g', Prec: -1},
},
}
// zeroArgs used for checking
// zero value Arg{} fields.
zeroArgs Args
)
// DefaultArgs returns default
// set of formatter arguments.
func DefaultArgs() Args {
return defaultArgs
}
// Args contains arguments
// for a call to a FormatFunc.
type Args struct {
// Boolean argument
// flags as bit-field.
Flags uint64
// Integer
// arguments.
// i.e. for:
// - int
// - int8
// - int16
// - int32 (treated as rune char, number with NumberMask)
// - int64
Int IntArgs
// Unsigned
// integer
// arguments.
// i.e. for:
// - uint
// - uint8 (treated as byte char, number with NumberMask)
// - uint16
// - uint32
// - uint64
Uint IntArgs
// Float
// arguments.
// i.e. for:
// - float32
// - float64
Float FloatArgs
// Complex
// arguments.
// i.e. for:
// - complex64
// - complex128
Complex ComplexArgs
}
// IntArgs provides a set of
// arguments for customizing
// integer number serialization.
type IntArgs struct {
Base int
Pad int
}
// FloatArgs provides a set of
// arguments for customizing
// float number serialization.
type FloatArgs struct {
Fmt byte
Prec int
}
// ComplexArgs provides a set of
// arguments for customizing complex
// number serialization, as real and
// imaginary float number parts.
type ComplexArgs struct {
Real FloatArgs
Imag FloatArgs
}
// WithType returns if TypeMask is set.
func (a *Args) WithType() bool {
return a.Flags&TypeMask != 0
}
// Logfmt returns if LogfmtMask is set.
func (a *Args) Logfmt() bool {
return a.Flags&LogfmtMask != 0
}
// AsNumber returns if NumberMask is set.
func (a *Args) AsNumber() bool {
return a.Flags&NumberMask != 0
}
// AsText returns if TextMask is set.
func (a *Args) AsText() bool {
return a.Flags&TextMask != 0
}
// AsQuotedText returns if QuotedTextMask is set.
func (a *Args) AsQuotedText() bool {
return a.Flags&QuotedTextMask != 0
}
// AsQuotedASCII returns if QuotedAsciiMask is set.
func (a *Args) AsQuotedASCII() bool {
return a.Flags&QuotedAsciiMask != 0
}
// NoMethod returns if NoMethodMask is set.
func (a *Args) NoMethod() bool {
return a.Flags&NoMethodMask != 0
}
// SetWithType sets the TypeMask bit.
func (a *Args) SetWithType() {
a.Flags = a.Flags | TypeMask
}
// SetLogfmt sets the LogfmtMask bit.
func (a *Args) SetLogfmt() {
a.Flags = a.Flags | LogfmtMask
}
// SetAsNumber sets the NumberMask bit.
func (a *Args) SetAsNumber() {
a.Flags = a.Flags | NumberMask
}
// SetAsText sets the TextMask bit.
func (a *Args) SetAsText() {
a.Flags = a.Flags | TextMask
}
// SetAsQuotedText sets the QuotedTextMask bit.
func (a *Args) SetAsQuotedText() {
a.Flags = a.Flags | QuotedTextMask
}
// SetAsQuotedASCII sets the QuotedAsciiMask bit.
func (a *Args) SetAsQuotedASCII() {
a.Flags = a.Flags | QuotedAsciiMask
}
// SetNoMethod sets the NoMethodMask bit.
func (a *Args) SetNoMethod() {
a.Flags = a.Flags | NoMethodMask
}
// UnsetWithType unsets the TypeMask bit.
func (a *Args) UnsetWithType() {
a.Flags = a.Flags & ^TypeMask
}
// UnsetLogfmt unsets the LogfmtMask bit.
func (a *Args) UnsetLogfmt() {
a.Flags = a.Flags & ^LogfmtMask
}
// UnsetAsNumber unsets the NumberMask bit.
func (a *Args) UnsetAsNumber() {
a.Flags = a.Flags & ^NumberMask
}
// UnsetAsText unsets the TextMask bit.
func (a *Args) UnsetAsText() {
a.Flags = a.Flags & ^TextMask
}
// UnsetAsQuotedText unsets the QuotedTextMask bit.
func (a *Args) UnsetAsQuotedText() {
a.Flags = a.Flags & ^QuotedTextMask
}
// UnsetAsQuotedASCII unsets the QuotedAsciiMask bit.
func (a *Args) UnsetAsQuotedASCII() {
a.Flags = a.Flags & ^QuotedAsciiMask
}
// UnsetNoMethod unsets the NoMethodMask bit.
func (a *Args) UnsetNoMethod() {
a.Flags = a.Flags & ^NoMethodMask
}

231
vendor/codeberg.org/gruf/go-kv/v2/format/array.go generated vendored Normal file
View file

@ -0,0 +1,231 @@
package format
import "unsafe"
// iterArrayType returns a FormatFunc capable of iterating
// and formatting the given array type currently in typenode{}.
// note this will fetch a sub-FormatFunc for the array element
// type, and also handle special cases of [n]byte, [n]rune arrays.
func (fmt *Formatter) iterArrayType(t typenode) FormatFunc {
// Array element type.
elem := t.rtype.Elem()
// Get nested elem typenode with appropriate flags.
flags := reflect_array_elem_flags(t.flags, elem)
et := t.next(elem, flags)
// Get elem format func.
fn := fmt.loadOrGet(et)
if fn == nil {
panic("unreachable")
}
// Handle possible sizes.
switch t.rtype.Len() {
case 0:
return emptyArrayType(t)
case 1:
return iterSingleArrayType(t, fn)
default:
return iterMultiArrayType(t, fn)
}
}
func emptyArrayType(t typenode) FormatFunc {
if !t.needs_typestr() {
// Simply append empty.
return func(s *State) {
s.B = append(s.B, "[]"...)
}
}
// Array type string with refs.
typestr := t.typestr_with_refs()
// Append empty with type.
return func(s *State) {
if s.A.WithType() {
s.B = append(s.B, typestr...)
s.B = append(s.B, "{}"...)
} else {
s.B = append(s.B, "[]"...)
}
}
}
func iterSingleArrayType(t typenode, fn FormatFunc) FormatFunc {
if !t.needs_typestr() {
return func(s *State) {
// Wrap 'fn' in braces.
s.B = append(s.B, '[')
fn(s)
s.B = append(s.B, ']')
}
}
// Array type string with refs.
typestr := t.typestr_with_refs()
// Wrap in type+braces.
return func(s *State) {
// Open / close braces.
var open, close uint8
open, close = '[', ']'
// Include type info.
if s.A.WithType() {
s.B = append(s.B, typestr...)
open, close = '{', '}'
}
// Wrap 'fn' in braces.
s.B = append(s.B, open)
fn(s)
s.B = append(s.B, close)
}
}
func iterMultiArrayType(t typenode, fn FormatFunc) FormatFunc {
// Array element in-memory size.
esz := t.rtype.Elem().Size()
// Number of elements.
n := t.rtype.Len()
if !t.needs_typestr() {
// Wrap elems in braces.
return func(s *State) {
ptr := s.P
// Prepend array brace.
s.B = append(s.B, '[')
for i := 0; i < n; i++ {
// Format at array index.
offset := esz * uintptr(i)
s.P = add(ptr, offset)
fn(s)
// Append separator.
s.B = append(s.B, ',')
}
// Drop final space.
s.B = s.B[:len(s.B)-1]
// Prepend array brace.
s.B = append(s.B, ']')
}
}
// Array type string with refs.
typestr := t.typestr_with_refs()
// Wrap in type+braces.
return func(s *State) {
ptr := s.P
// Open / close braces.
var open, close uint8
open, close = '[', ']'
// Include type info.
if s.A.WithType() {
s.B = append(s.B, typestr...)
open, close = '{', '}'
}
// Prepend array brace.
s.B = append(s.B, open)
for i := 0; i < n; i++ {
// Format at array index.
offset := esz * uintptr(i)
s.P = add(ptr, offset)
fn(s)
// Append separator.
s.B = append(s.B, ',')
}
// Drop final comma.
s.B = s.B[:len(s.B)-1]
// Prepend array brace.
s.B = append(s.B, close)
}
}
func wrapByteArray(t typenode, fn FormatFunc) FormatFunc {
n := t.rtype.Len()
if !t.needs_typestr() {
return func(s *State) {
if s.A.AsText() || s.A.AsQuotedText() || s.A.AsQuotedASCII() {
var v string
p := (*unsafeheader_String)(unsafe.Pointer(&v))
p.Len = n
p.Data = s.P
appendString(s, v)
} else {
fn(s)
}
}
}
typestr := t.typestr_with_ptrs()
return func(s *State) {
if s.A.AsText() || s.A.AsQuotedText() || s.A.AsQuotedASCII() {
var v string
p := (*unsafeheader_String)(unsafe.Pointer(&v))
p.Len = n
p.Data = s.P
if s.A.WithType() {
s.B = append(s.B, "("+typestr+")("...)
appendString(s, v)
s.B = append(s.B, ")"...)
} else {
appendString(s, v)
}
} else {
fn(s)
}
}
}
func wrapRuneArray(t typenode, fn FormatFunc) FormatFunc {
n := t.rtype.Len()
if !t.needs_typestr() {
return func(s *State) {
if s.A.AsText() || s.A.AsQuotedText() || s.A.AsQuotedASCII() {
var v []rune
p := (*unsafeheader_Slice)(unsafe.Pointer(&v))
p.Cap = n
p.Len = n
p.Data = s.P
appendString(s, string(v))
} else {
fn(s)
}
}
}
typestr := t.typestr_with_ptrs()
return func(s *State) {
if s.A.AsText() || s.A.AsQuotedText() || s.A.AsQuotedASCII() {
var v []rune
p := (*unsafeheader_Slice)(unsafe.Pointer(&v))
p.Cap = n
p.Len = n
p.Data = s.P
if s.A.WithType() {
s.B = append(s.B, "("+typestr+")("...)
appendString(s, string(v))
s.B = append(s.B, ")"...)
} else {
appendString(s, string(v))
}
} else {
fn(s)
}
}
}

614
vendor/codeberg.org/gruf/go-kv/v2/format/format.go generated vendored Normal file
View file

@ -0,0 +1,614 @@
package format
import (
"reflect"
"runtime/debug"
"strconv"
"sync"
"unsafe"
)
// FormatFunc defines a function capable of formatting
// the value contained in State{}.P, based on args in
// State{}.A, storing the result in buffer State{}.B.
type FormatFunc func(*State)
// State contains all necessary
// arguments, buffer and value
// data pointer required for a
// FormatFunc operation, in a
// reusable structure if wanted.
type State struct {
// A contains args
// passed to this
// FormatFunc call.
A Args
// B is the buffer
// that values will
// be formatted into.
B []byte
// P contains a ptr
// to the value type
// being formatted.
P unsafe.Pointer
// stores pointers to the
// recent interface values
// we have visited. to prevent
// possible recursion of
// runtime defined data.
ifaces ptr_ring
}
// ptr_ring size.
const ringsz = 16
// ptr_ring is a ring buffer of pointers,
// purposely stored as uintptrs as all we
// need them for is value comparisons and
// we don't want to hold-up the GC.
type ptr_ring struct {
p [ringsz]uintptr
n uint8
}
func (p *ptr_ring) contains(ptr unsafe.Pointer) bool {
for _, eptr := range p.p {
if uintptr(ptr) == eptr {
return true
}
}
return false
}
func (p *ptr_ring) set(ptr unsafe.Pointer) {
p.p[p.n%ringsz] = uintptr(ptr)
p.n++
}
func (p *ptr_ring) clear() {
p.p = [ringsz]uintptr{}
p.n = 0
}
// Formatter provides access to value formatting
// provided by this library. It encompasses a set
// of configurable default arguments for when none
// are set, and an internal concurrency-safe cache
// of FormatFuncs to passed value type.
type Formatter struct {
// Defaults defines the default
// set of arguments to use when
// none are supplied to calls to
// Append() and AppendState().
Defaults Args
// internal
// format func
// cache map.
fns sync.Map
}
// LoadFor returns a FormatFunc for the given value type.
func (fmt *Formatter) LoadFor(value any) FormatFunc {
rtype := reflect.TypeOf(value)
flags := reflect_iface_elem_flags(rtype)
t := new_typenode(rtype, flags)
return fmt.loadOrStore(t)
}
// Append calls AppendState() with a newly allocated State{}, returning byte buffer.
func (fmt *Formatter) Append(buf []byte, value any, args Args) []byte {
s := new(State)
s.A = args
s.B = buf
fmt.AppendState(s, value)
return s.B
}
// AppendState will format the given value into the given
// State{}'s byte buffer, using currently-set arguments.
func (fmt *Formatter) AppendState(s *State, value any) {
switch {
case s.A != zeroArgs:
break
case fmt.Defaults != zeroArgs:
// use fmt defaults.
s.A = fmt.Defaults
default:
// global defaults.
s.A = defaultArgs
}
s.P = unpack_eface(value)
s.ifaces.clear()
s.ifaces.set(s.P)
fmt.LoadFor(value)(s)
}
func (fmt *Formatter) loadOrGet(t typenode) FormatFunc {
// Look for existing stored
// func under this type key.
v, _ := fmt.fns.Load(t.key())
fn, _ := v.(FormatFunc)
if fn == nil {
// Load format func
// for typecontext.
fn = fmt.get(t)
if fn == nil {
panic("unreachable")
}
}
return fn
}
func (fmt *Formatter) loadOrStore(t typenode) FormatFunc {
// Get cache key.
key := t.key()
// Look for existing stored
// func under this type key.
v, _ := fmt.fns.Load(key)
fn, _ := v.(FormatFunc)
if fn == nil {
// Load format func
// for typecontext.
fn = fmt.get(t)
if fn == nil {
panic("unreachable")
}
// Store in map under type.
fmt.fns.Store(key, fn)
}
return fn
}
var (
// reflectTypeType is the reflected type of the reflect type,
// used in fmt.get() to prevent iter of internal ABI structs.
reflectTypeType = reflect.TypeOf(reflect.TypeOf(0))
// stringable int types.
byteType = typeof[byte]()
runeType = typeof[rune]()
// stringable slice types.
bytesType = typeof[[]byte]()
runesType = typeof[[]rune]()
)
func (fmt *Formatter) get(t typenode) (fn FormatFunc) {
if t.rtype == nil {
// catch nil type.
return appendNil
}
defer func() {
if r := recover(); r != nil {
debug.PrintStack()
panic(r) // keep panicking
} else if fn == nil {
panic("nil func")
}
// Don't allow method functions for map keys,
// to prevent situation of the method receiver
// attempting to modify stored map key itself.
if t.flags&flagKeyType != 0 {
return
}
// Check if type supports known method receiver.
if methodFn := getMethodType(t); methodFn != nil {
// Keep ptr to existing
// non-method format fn.
noMethodFn := fn
// Wrap 'fn' to switch
// between method / none.
fn = func(s *State) {
if s.A.NoMethod() {
noMethodFn(s)
} else {
methodFn(s)
}
}
}
}()
if t.rtype == reflectTypeType {
// DO NOT iterate down internal ABI
// types, some are in non-GC memory.
return getPointerType(t)
}
if !t.visit() {
// On type recursion simply
// format as raw pointer.
return getPointerType(t)
}
// Get func for type kind.
switch t.rtype.Kind() {
case reflect.Interface:
return fmt.getInterfaceType(t)
case reflect.String:
return getStringType(t)
case reflect.Bool:
return getBoolType(t)
case reflect.Int,
reflect.Int8,
reflect.Int16,
reflect.Int32,
reflect.Int64:
return getIntType(t)
case reflect.Uint,
reflect.Uint8,
reflect.Uint16,
reflect.Uint32,
reflect.Uint64:
return getUintType(t)
case reflect.Float32,
reflect.Float64:
return getFloatType(t)
case reflect.Complex64,
reflect.Complex128:
return getComplexType(t)
case reflect.Pointer:
return fmt.derefPointerType(t)
case reflect.Array:
elem := t.rtype.Elem()
switch fn := fmt.iterArrayType(t); {
case elem.AssignableTo(byteType):
return wrapByteArray(t, fn)
case elem.AssignableTo(runeType):
return wrapRuneArray(t, fn)
default:
return fn
}
case reflect.Slice:
switch fn := fmt.iterSliceType(t); {
case t.rtype.AssignableTo(bytesType):
return wrapByteSlice(t, fn)
case t.rtype.AssignableTo(runesType):
return wrapRuneSlice(t, fn)
default:
return fn
}
case reflect.Struct:
return fmt.iterStructType(t)
case reflect.Map:
return fmt.iterMapType(t)
default:
return getPointerType(t)
}
}
func (fmt *Formatter) getInterfaceType(t typenode) FormatFunc {
if t.rtype.NumMethod() == 0 {
return func(s *State) {
eface := *(*any)(s.P)
s.P = unpack_eface(eface)
rtype := reflect.TypeOf(eface)
if rtype == nil {
appendNil(s)
return
}
if s.ifaces.contains(s.P) {
getPointerType(t)(s)
return
}
s.ifaces.set(s.P)
flags := reflect_iface_elem_flags(rtype)
t := new_typenode(rtype, flags)
fmt.loadOrStore(t)(s)
}
} else {
return func(s *State) {
iface := *(*interface{ M() })(s.P)
s.P = unpack_eface(iface)
rtype := reflect.TypeOf(iface)
if rtype == nil {
appendNil(s)
return
}
if s.ifaces.contains(s.P) {
getPointerType(t)(s)
return
}
s.ifaces.set(s.P)
flags := reflect_iface_elem_flags(rtype)
t := new_typenode(rtype, flags)
fmt.loadOrStore(t)(s)
}
}
}
func getStringType(t typenode) FormatFunc {
return with_typestr_ptrs(t, func(s *State) {
appendString(s, *(*string)(s.P))
})
}
func getBoolType(t typenode) FormatFunc {
return with_typestr_ptrs(t, func(s *State) {
s.B = strconv.AppendBool(s.B, *(*bool)(s.P))
})
}
func getIntType(t typenode) FormatFunc {
switch t.rtype.Bits() {
case 8:
return with_typestr_ptrs(t, func(s *State) {
appendInt(s, int64(*(*int8)(s.P)))
})
case 16:
return with_typestr_ptrs(t, func(s *State) {
appendInt(s, int64(*(*int16)(s.P)))
})
case 32:
return with_typestr_ptrs(t, func(s *State) {
switch {
case s.A.AsNumber():
// fallthrough
case s.A.AsQuotedText():
s.B = strconv.AppendQuoteRune(s.B, *(*rune)(s.P))
return
case s.A.AsQuotedASCII():
s.B = strconv.AppendQuoteRuneToASCII(s.B, *(*rune)(s.P))
return
case s.A.AsText():
s.B = AppendEscapeRune(s.B, *(*rune)(s.P))
return
}
appendInt(s, int64(*(*int32)(s.P)))
})
case 64:
return with_typestr_ptrs(t, func(s *State) {
appendInt(s, int64(*(*int64)(s.P)))
})
default:
panic("unreachable")
}
}
func getUintType(t typenode) FormatFunc {
switch t.rtype.Bits() {
case 8:
return with_typestr_ptrs(t, func(s *State) {
switch {
case s.A.AsNumber():
// fallthrough
case s.A.AsQuotedText() || s.A.AsQuotedASCII():
s.B = AppendQuoteByte(s.B, *(*byte)(s.P))
return
case s.A.AsText():
s.B = AppendEscapeByte(s.B, *(*byte)(s.P))
return
}
appendUint(s, uint64(*(*uint8)(s.P)))
})
case 16:
return with_typestr_ptrs(t, func(s *State) {
appendUint(s, uint64(*(*uint16)(s.P)))
})
case 32:
return with_typestr_ptrs(t, func(s *State) {
appendUint(s, uint64(*(*uint32)(s.P)))
})
case 64:
return with_typestr_ptrs(t, func(s *State) {
appendUint(s, uint64(*(*uint64)(s.P)))
})
default:
panic("unreachable")
}
}
func getFloatType(t typenode) FormatFunc {
switch t.rtype.Bits() {
case 32:
return with_typestr_ptrs(t, func(s *State) {
appendFloat(s, float64(*(*float32)(s.P)), 32)
})
case 64:
return with_typestr_ptrs(t, func(s *State) {
appendFloat(s, float64(*(*float64)(s.P)), 64)
})
default:
panic("unreachable")
}
}
func getComplexType(t typenode) FormatFunc {
switch t.rtype.Bits() {
case 64:
return with_typestr_ptrs(t, func(s *State) {
v := *(*complex64)(s.P)
r, i := real(v), imag(v)
appendComplex(s, float64(r), float64(i), 32)
})
case 128:
return with_typestr_ptrs(t, func(s *State) {
v := *(*complex128)(s.P)
r, i := real(v), imag(v)
appendComplex(s, float64(r), float64(i), 64)
})
default:
panic("unreachable")
}
}
func getPointerType(t typenode) FormatFunc {
switch t.indirect() {
case true:
return with_typestr_ptrs(t, func(s *State) {
s.P = *(*unsafe.Pointer)(s.P)
appendPointer(s, s.P)
})
case false:
return with_typestr_ptrs(t, func(s *State) {
appendPointer(s, s.P)
})
default:
panic("unreachable")
}
}
func with_typestr_ptrs(t typenode, fn FormatFunc) FormatFunc {
if fn == nil {
panic("nil func")
}
if !t.needs_typestr() {
return fn
}
typestr := t.typestr_with_ptrs()
return func(s *State) {
if s.A.WithType() {
s.B = append(s.B, "("+typestr+")("...)
fn(s)
s.B = append(s.B, ")"...)
} else {
fn(s)
}
}
}
func appendString(s *State, v string) {
switch {
case s.A.Logfmt() || s.A.WithType():
if len(v) > 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+")(<nil>)"...)
} else {
s.B = append(s.B, "<nil>"...)
}
}
func appendNil(s *State) {
s.B = append(s.B, "<nil>"...)
}
func abs64(i int64) int64 {
u := uint64(i >> 63)
return (i ^ int64(u)) + int64(u&1)
}

161
vendor/codeberg.org/gruf/go-kv/v2/format/formatting.go generated vendored Normal file
View file

@ -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
}

130
vendor/codeberg.org/gruf/go-kv/v2/format/map.go generated vendored Normal file
View file

@ -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, '}')
}
}

153
vendor/codeberg.org/gruf/go-kv/v2/format/methods.go generated vendored Normal file
View file

@ -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")
}
}

128
vendor/codeberg.org/gruf/go-kv/v2/format/pointer.go generated vendored Normal file
View file

@ -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)
}
}

158
vendor/codeberg.org/gruf/go-kv/v2/format/slice.go generated vendored Normal file
View file

@ -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)
}
}
}

173
vendor/codeberg.org/gruf/go-kv/v2/format/struct.go generated vendored Normal file
View file

@ -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, '}')
}
}

150
vendor/codeberg.org/gruf/go-kv/v2/format/type.go generated vendored Normal file
View file

@ -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()
}

147
vendor/codeberg.org/gruf/go-kv/v2/util.go generated vendored Normal file
View file

@ -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
}

4
vendor/modules.txt vendored
View file

@ -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