[bugfix] Fix remote media pruning failing if media already gone (#548)
* fix error check of prune to allow missing files * update go-store library, add test for pruning item with db entry but no file Signed-off-by: kim <grufwub@gmail.com> * remove now-unneccessary error check Signed-off-by: kim <grufwub@gmail.com> Co-authored-by: kim <grufwub@gmail.com>
This commit is contained in:
parent
26b74aefaf
commit
5004e0a9da
15
go.mod
15
go.mod
|
@ -4,10 +4,9 @@ go 1.18
|
|||
|
||||
require (
|
||||
codeberg.org/gruf/go-debug v1.1.2
|
||||
codeberg.org/gruf/go-errors v1.0.5
|
||||
codeberg.org/gruf/go-mutexes v1.1.2
|
||||
codeberg.org/gruf/go-runners v1.2.0
|
||||
codeberg.org/gruf/go-store v1.3.6
|
||||
codeberg.org/gruf/go-runners v1.2.1
|
||||
codeberg.org/gruf/go-store v1.3.7
|
||||
github.com/ReneKroon/ttlcache v1.7.0
|
||||
github.com/buckket/go-blurhash v1.1.0
|
||||
github.com/coreos/go-oidc/v3 v3.1.0
|
||||
|
@ -51,12 +50,14 @@ require (
|
|||
)
|
||||
|
||||
require (
|
||||
codeberg.org/gruf/go-bitutil v1.0.0 // indirect
|
||||
codeberg.org/gruf/go-bytes v1.0.2 // indirect
|
||||
codeberg.org/gruf/go-byteutil v1.0.0 // indirect
|
||||
codeberg.org/gruf/go-errors/v2 v2.0.1 // indirect
|
||||
codeberg.org/gruf/go-fastcopy v1.1.1 // indirect
|
||||
codeberg.org/gruf/go-fastpath v1.0.2 // indirect
|
||||
codeberg.org/gruf/go-format v1.0.3 // indirect
|
||||
codeberg.org/gruf/go-hashenc v1.0.1 // indirect
|
||||
codeberg.org/gruf/go-pools v1.0.2 // indirect
|
||||
codeberg.org/gruf/go-fastpath v1.0.3 // indirect
|
||||
codeberg.org/gruf/go-hashenc v1.0.2 // indirect
|
||||
codeberg.org/gruf/go-pools v1.1.0 // indirect
|
||||
github.com/aymerick/douceur v0.2.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dsoprea/go-exif/v3 v3.0.0-20210625224831-a6301f85c82b // indirect
|
||||
|
|
38
go.sum
38
go.sum
|
@ -35,35 +35,37 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl
|
|||
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||
cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
|
||||
codeberg.org/gruf/go-bitutil v1.0.0 h1:pM3KeDACgQ0ARbdIVEuRyAi3XDyUQdoSR0X24nbCZt8=
|
||||
codeberg.org/gruf/go-bitutil v1.0.0/go.mod h1:sb8IjlDnjVTz8zPK/8lmHesKxY0Yb3iqHWjUM/SkphA=
|
||||
codeberg.org/gruf/go-bytes v1.0.0/go.mod h1:1v/ibfaosfXSZtRdW2rWaVrDXMc9E3bsi/M9Ekx39cg=
|
||||
codeberg.org/gruf/go-bytes v1.0.1/go.mod h1:1v/ibfaosfXSZtRdW2rWaVrDXMc9E3bsi/M9Ekx39cg=
|
||||
codeberg.org/gruf/go-bytes v1.0.2 h1:malqE42Ni+h1nnYWBUAJaDDtEzF4aeN4uPN8DfMNNvo=
|
||||
codeberg.org/gruf/go-bytes v1.0.2/go.mod h1:1v/ibfaosfXSZtRdW2rWaVrDXMc9E3bsi/M9Ekx39cg=
|
||||
codeberg.org/gruf/go-byteutil v1.0.0 h1:xgKFNj/gH1r3yRo7gnyR4qrAKyeWCXs6B19ISX0DUAY=
|
||||
codeberg.org/gruf/go-byteutil v1.0.0/go.mod h1:cWM3tgMCroSzqoBXUXMhvxTxYJp+TbCr6ioISRY5vSU=
|
||||
codeberg.org/gruf/go-cache v1.1.2/go.mod h1:/Dbc+xU72Op3hMn6x2PXF3NE9uIDFeS+sXPF00hN/7o=
|
||||
codeberg.org/gruf/go-debug v1.1.2 h1:7Tqkktg60M/4WtXTTNUFH2T/6irBw4tI4viv7IRLZDE=
|
||||
codeberg.org/gruf/go-debug v1.1.2/go.mod h1:N+vSy9uJBQgpQcJUqjctvqFz7tBHJf+S/PIjLILzpLg=
|
||||
codeberg.org/gruf/go-errors v1.0.5 h1:rxV70oQkfasUdggLHxOX2QAoJOMFM7XWxHQR45Zx/Fg=
|
||||
codeberg.org/gruf/go-errors v1.0.5/go.mod h1:n03EpmvcmfzU3/xJKC0XXtleXXJUNFpT2fgISODvZ1Y=
|
||||
codeberg.org/gruf/go-errors/v2 v2.0.0/go.mod h1:ZRhbdhvgoUA3Yw6e56kd9Ox984RrvbEFC2pOXyHDJP4=
|
||||
codeberg.org/gruf/go-errors/v2 v2.0.1 h1:Y2LNtwoXAk1LUcIPzbowXOi7YTLmzsHKPe2QRbMH1OU=
|
||||
codeberg.org/gruf/go-errors/v2 v2.0.1/go.mod h1:6sI75OmvXE2AtRm4WUyGMEyqEOKTsfe+CA+aBXwbtJY=
|
||||
codeberg.org/gruf/go-fastcopy v1.1.1 h1:HhPCeFdVR5pwiSVDnQEGJ+J2ny9b5QgfiESc0zrWQAY=
|
||||
codeberg.org/gruf/go-fastcopy v1.1.1/go.mod h1:GDDYR0Cnb3U/AIfGM3983V/L+GN+vuwVMvrmVABo21s=
|
||||
codeberg.org/gruf/go-fastpath v1.0.1/go.mod h1:edveE/Kp3Eqi0JJm0lXYdkVrB28cNUkcb/bRGFTPqeI=
|
||||
codeberg.org/gruf/go-fastpath v1.0.2 h1:O3nuYPMXnN89dsgAwVFU5iCGINtPJdITWmbRe2an/iQ=
|
||||
codeberg.org/gruf/go-fastpath v1.0.2/go.mod h1:edveE/Kp3Eqi0JJm0lXYdkVrB28cNUkcb/bRGFTPqeI=
|
||||
codeberg.org/gruf/go-format v1.0.3 h1:WoUGzTwZe6SIhILNvtr0qNIA7BOOCgdBlk5bUrfeiio=
|
||||
codeberg.org/gruf/go-format v1.0.3/go.mod h1:k3TLXp1dqAXdDqxlon0yEM+3FFHdNn0D6BVJTwTy5As=
|
||||
codeberg.org/gruf/go-hashenc v1.0.1 h1:EBvNe2wW8IPMUqT1XihB6/IM6KMJDLMFBxIUvmsy1f8=
|
||||
codeberg.org/gruf/go-hashenc v1.0.1/go.mod h1:IfHhPCVScOiYmJLqdCQT9bYVS1nxNTV4ewMUvFWDPtc=
|
||||
codeberg.org/gruf/go-fastpath v1.0.3 h1:3Iftz9Z2suCEgTLkQMucew+2+4Oe46JPbAM2JEhnjTU=
|
||||
codeberg.org/gruf/go-fastpath v1.0.3/go.mod h1:edveE/Kp3Eqi0JJm0lXYdkVrB28cNUkcb/bRGFTPqeI=
|
||||
codeberg.org/gruf/go-hashenc v1.0.2 h1:U3jH6zMXZiL96czD/qaJd8OR2h7LlBzGv/2WxnMHI/g=
|
||||
codeberg.org/gruf/go-hashenc v1.0.2/go.mod h1:eK+A8clLcEN/m1nftNsRId0kfYDQnETnuIfBGZ8Gvsg=
|
||||
codeberg.org/gruf/go-mutexes v1.1.2 h1:AMC1CFV6kMi+iBjR3yQv8yIagG3lWm68U6sQHYFHEf4=
|
||||
codeberg.org/gruf/go-mutexes v1.1.2/go.mod h1:1j/6/MBeBQUedAtAtysLLnBKogfOZAxdym0E3wlaBD8=
|
||||
codeberg.org/gruf/go-nowish v1.0.0/go.mod h1:70nvICNcqQ9OHpF07N614Dyk7cpL5ToWU1K1ZVCec2s=
|
||||
codeberg.org/gruf/go-nowish v1.1.0/go.mod h1:70nvICNcqQ9OHpF07N614Dyk7cpL5ToWU1K1ZVCec2s=
|
||||
codeberg.org/gruf/go-pools v1.0.2 h1:B0X6yoCL9FVmnvyoizb1SYRwMYPWwEJBjPnBMM5ILos=
|
||||
codeberg.org/gruf/go-pools v1.0.2/go.mod h1:MjUV3H6IASyBeBPCyCr7wjPpSNu8E2N87LG4r4TAyok=
|
||||
codeberg.org/gruf/go-nowish v1.1.2/go.mod h1:70nvICNcqQ9OHpF07N614Dyk7cpL5ToWU1K1ZVCec2s=
|
||||
codeberg.org/gruf/go-pools v1.1.0 h1:LbYP24eQLl/YI1fSU2pafiwhGol1Z1zPjRrMsXpF88s=
|
||||
codeberg.org/gruf/go-pools v1.1.0/go.mod h1:ZMYpt/DjQWYC3zFD3T97QWSFKs62zAUGJ/tzvgB9D68=
|
||||
codeberg.org/gruf/go-runners v1.1.1/go.mod h1:9gTrmMnO3d+50C+hVzcmGBf+zTuswReS278E2EMvnmw=
|
||||
codeberg.org/gruf/go-runners v1.2.0 h1:tkoPrwYMkVg1o/C4PGTR1YbC11XX4r06uLPOYajBsH4=
|
||||
codeberg.org/gruf/go-runners v1.2.0/go.mod h1:9gTrmMnO3d+50C+hVzcmGBf+zTuswReS278E2EMvnmw=
|
||||
codeberg.org/gruf/go-store v1.3.6 h1:OKzdvfUC+nvsWV5FiSKdk+85yvxF2Tb7K5ZtRqlDBDU=
|
||||
codeberg.org/gruf/go-store v1.3.6/go.mod h1:a4vJtZf61UyrsejskX8q+s0lZeNGy7cJLUZt+fH00wo=
|
||||
codeberg.org/gruf/go-runners v1.2.1 h1:eXXofOMkLTkrdqI0qFYm00hP9IW42JZbc+IRKgVyQ28=
|
||||
codeberg.org/gruf/go-runners v1.2.1/go.mod h1:9gTrmMnO3d+50C+hVzcmGBf+zTuswReS278E2EMvnmw=
|
||||
codeberg.org/gruf/go-store v1.3.7 h1:w1nUVXDpdgqXMBRA+8S9MautFhCJXU8H+lQasS5KYXw=
|
||||
codeberg.org/gruf/go-store v1.3.7/go.mod h1:vaWKWMUFgS7b4NcCo/vS+7K0uh9ZATOI9P60gRMKUSw=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
|
@ -546,6 +548,9 @@ github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
|
|||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
|
||||
github.com/zeebo/blake3 v0.2.1/go.mod h1:TSQ0KjMH+pht+bRyvVooJ1rBpvvngSGaPISafq9MxJk=
|
||||
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
|
||||
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
|
@ -718,6 +723,7 @@ golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201014080544-cc95f250f6bc/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201126233918-771906719818/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
|
|
@ -7,7 +7,6 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"codeberg.org/gruf/go-errors"
|
||||
"github.com/gin-contrib/sessions"
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/api/client/auth"
|
||||
|
@ -26,8 +25,7 @@ type authorizeHandlerTestCase struct {
|
|||
}
|
||||
|
||||
func (suite *AuthAuthorizeTestSuite) TestAccountAuthorizeHandler() {
|
||||
|
||||
var tests = []authorizeHandlerTestCase{
|
||||
tests := []authorizeHandlerTestCase{
|
||||
{
|
||||
description: "user has their email unconfirmed",
|
||||
mutateUserAccount: func(user *gtsmodel.User, account *gtsmodel.Account) {
|
||||
|
@ -80,7 +78,7 @@ func (suite *AuthAuthorizeTestSuite) TestAccountAuthorizeHandler() {
|
|||
testSession.Set(sessionUserID, user.ID)
|
||||
testSession.Set(sessionClientID, suite.testApplications["application_1"].ClientID)
|
||||
if err := testSession.Save(); err != nil {
|
||||
panic(errors.WrapMsgf(err, "failed on case: %s", testCase.description))
|
||||
panic(fmt.Errorf("failed on case %s: %w", testCase.description, err))
|
||||
}
|
||||
|
||||
testCase.mutateUserAccount(user, account)
|
||||
|
|
|
@ -69,10 +69,8 @@ func (suite *PruneRemoteTestSuite) TestPruneAndRecache() {
|
|||
|
||||
// media should no longer be stored
|
||||
_, err = suite.storage.Get(testAttachment.File.Path)
|
||||
suite.Error(err)
|
||||
suite.ErrorIs(err, storage.ErrNotFound)
|
||||
_, err = suite.storage.Get(testAttachment.Thumbnail.Path)
|
||||
suite.Error(err)
|
||||
suite.ErrorIs(err, storage.ErrNotFound)
|
||||
|
||||
// now recache the image....
|
||||
|
@ -106,6 +104,23 @@ func (suite *PruneRemoteTestSuite) TestPruneAndRecache() {
|
|||
suite.NoError(err)
|
||||
}
|
||||
|
||||
func (suite *PruneRemoteTestSuite) TestPruneOneNonExistent() {
|
||||
ctx := context.Background()
|
||||
testAttachment := suite.testAttachments["remote_account_1_status_1_attachment_1"]
|
||||
|
||||
// Delete this attachment cached on disk
|
||||
media, err := suite.db.GetAttachmentByID(ctx, testAttachment.ID)
|
||||
suite.NoError(err)
|
||||
suite.True(media.Cached)
|
||||
err = suite.storage.Delete(media.File.Path)
|
||||
suite.NoError(err)
|
||||
|
||||
// Now attempt to prune remote for item with db entry no file
|
||||
totalPruned, err := suite.manager.PruneRemote(ctx, 1)
|
||||
suite.NoError(err)
|
||||
suite.Equal(1, totalPruned)
|
||||
}
|
||||
|
||||
func TestPruneRemoteTestSuite(t *testing.T) {
|
||||
suite.Run(t, &PruneRemoteTestSuite{})
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2021 gruf
|
||||
Copyright (c) 2022 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:
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# go-bitutil
|
||||
|
||||
This library provides helpful methods and types for performing typical bitwise operations on integers, e.g. packing/unpacking, bit flags.
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,94 @@
|
|||
package bitutil
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"codeberg.org/gruf/go-bytes"
|
||||
)
|
||||
|
||||
{{ range $idx, $size := . }}
|
||||
|
||||
// Flags{{ $size.Size }} is a type-casted unsigned integer with helper
|
||||
// methods for easily managing up to {{ $size.Size }} bit flags.
|
||||
type Flags{{ $size.Size }} uint{{ $size.Size }}
|
||||
|
||||
// Get will fetch the flag bit value at index 'bit'.
|
||||
func (f Flags{{ $size.Size }}) Get(bit uint8) bool {
|
||||
mask := Flags{{ $size.Size }}(1) << bit
|
||||
return (f & mask != 0)
|
||||
}
|
||||
|
||||
// Set will set the flag bit value at index 'bit'.
|
||||
func (f Flags{{ $size.Size }}) Set(bit uint8) Flags{{ $size.Size }} {
|
||||
mask := Flags{{ $size.Size }}(1) << bit
|
||||
return f | mask
|
||||
}
|
||||
|
||||
// Unset will unset the flag bit value at index 'bit'.
|
||||
func (f Flags{{ $size.Size }}) Unset(bit uint8) Flags{{ $size.Size }} {
|
||||
mask := Flags{{ $size.Size }}(1) << bit
|
||||
return f & ^mask
|
||||
}
|
||||
|
||||
{{ range $idx := $size.Bits }}
|
||||
|
||||
// Get{{ $idx }} will fetch the flag bit value at index {{ $idx }}.
|
||||
func (f Flags{{ $size.Size }}) Get{{ $idx }}() bool {
|
||||
const mask = Flags{{ $size.Size }}(1) << {{ $idx }}
|
||||
return (f & mask != 0)
|
||||
}
|
||||
|
||||
// Set{{ $idx }} will set the flag bit value at index {{ $idx }}.
|
||||
func (f Flags{{ $size.Size }}) Set{{ $idx }}() Flags{{ $size.Size }} {
|
||||
const mask = Flags{{ $size.Size }}(1) << {{ $idx }}
|
||||
return f | mask
|
||||
}
|
||||
|
||||
// Unset{{ $idx }} will unset the flag bit value at index {{ $idx }}.
|
||||
func (f Flags{{ $size.Size }}) Unset{{ $idx }}() Flags{{ $size.Size }} {
|
||||
const mask = Flags{{ $size.Size }}(1) << {{ $idx }}
|
||||
return f & ^mask
|
||||
}
|
||||
|
||||
{{ end }}
|
||||
|
||||
// String returns a human readable representation of Flags{{ $size.Size }}.
|
||||
func (f Flags{{ $size.Size }}) String() string {
|
||||
var val bool
|
||||
var buf bytes.Buffer
|
||||
|
||||
buf.WriteByte('{')
|
||||
{{ range $idx := .Bits }}
|
||||
val = f.Get{{ $idx }}()
|
||||
buf.WriteString(bool2str(val) + " ")
|
||||
{{ end }}
|
||||
buf.Truncate(1)
|
||||
buf.WriteByte('}')
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// GoString returns a more verbose human readable representation of Flags{{ $size.Size }}.
|
||||
func (f Flags{{ $size.Size }})GoString() string {
|
||||
var val bool
|
||||
var buf bytes.Buffer
|
||||
|
||||
buf.WriteString("bitutil.Flags{{ $size.Size }}{")
|
||||
{{ range $idx := .Bits }}
|
||||
val = f.Get{{ $idx }}()
|
||||
buf.WriteString("{{ $idx }}="+bool2str(val)+" ")
|
||||
{{ end }}
|
||||
buf.Truncate(1)
|
||||
buf.WriteByte('}')
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
{{ end }}
|
||||
|
||||
func bool2str(b bool) string {
|
||||
if b {
|
||||
return "true"
|
||||
}
|
||||
return "false"
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
package bitutil_test
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"codeberg.org/gruf/go-bytes"
|
||||
)
|
||||
|
||||
{{ range $idx, $size := . }}
|
||||
|
||||
func TestFlags{{ $size.Size }}Get(t *testing.T) {
|
||||
var mask, flags bitutil.Flags{{ $size.Size }}
|
||||
|
||||
{{ range $idx := $size.Bits }}
|
||||
|
||||
mask = bitutil.Flags{{ $size.Size }}(1) << {{ $idx }}
|
||||
|
||||
flags = 0
|
||||
|
||||
flags |= mask
|
||||
if !flags.Get({{ $idx }}) {
|
||||
t.Error("failed .Get() set Flags{{ $size.Size }} bit at index {{ $idx }}")
|
||||
}
|
||||
|
||||
flags = ^bitutil.Flags{{ $size.Size }}(0)
|
||||
|
||||
flags &= ^mask
|
||||
if flags.Get({{ $idx }}) {
|
||||
t.Error("failed .Get() unset Flags{{ $size.Size }} bit at index {{ $idx }}")
|
||||
}
|
||||
|
||||
flags = 0
|
||||
|
||||
flags |= mask
|
||||
if !flags.Get{{ $idx }}() {
|
||||
t.Error("failed .Get{{ $idx }}() set Flags{{ $size.Size }} bit at index {{ $idx }}")
|
||||
}
|
||||
|
||||
flags = ^bitutil.Flags{{ $size.Size }}(0)
|
||||
|
||||
flags &= ^mask
|
||||
if flags.Get{{ $idx }}() {
|
||||
t.Error("failed .Get{{ $idx }}() unset Flags{{ $size.Size }} bit at index {{ $idx }}")
|
||||
}
|
||||
|
||||
{{ end }}
|
||||
}
|
||||
|
||||
func TestFlags{{ $size.Size }}Set(t *testing.T) {
|
||||
var mask, flags bitutil.Flags{{ $size.Size }}
|
||||
|
||||
{{ range $idx := $size.Bits }}
|
||||
|
||||
mask = bitutil.Flags{{ $size.Size }}(1) << {{ $idx }}
|
||||
|
||||
flags = 0
|
||||
|
||||
flags = flags.Set({{ $idx }})
|
||||
if flags & mask == 0 {
|
||||
t.Error("failed .Set() Flags{{ $size.Size }} bit at index {{ $idx }}")
|
||||
}
|
||||
|
||||
flags = 0
|
||||
|
||||
flags = flags.Set{{ $idx }}()
|
||||
if flags & mask == 0 {
|
||||
t.Error("failed .Set{{ $idx }}() Flags{{ $size.Size }} bit at index {{ $idx }}")
|
||||
}
|
||||
|
||||
{{ end }}
|
||||
}
|
||||
|
||||
func TestFlags{{ $size.Size }}Unset(t *testing.T) {
|
||||
var mask, flags bitutil.Flags{{ $size.Size }}
|
||||
|
||||
{{ range $idx := $size.Bits }}
|
||||
|
||||
mask = bitutil.Flags{{ $size.Size }}(1) << {{ $idx }}
|
||||
|
||||
flags = ^bitutil.Flags{{ $size.Size }}(0)
|
||||
|
||||
flags = flags.Unset({{ $idx }})
|
||||
if flags & mask != 0 {
|
||||
t.Error("failed .Unset() Flags{{ $size.Size }} bit at index {{ $idx }}")
|
||||
}
|
||||
|
||||
flags = ^bitutil.Flags{{ $size.Size }}(0)
|
||||
|
||||
flags = flags.Unset{{ $idx }}()
|
||||
if flags & mask != 0 {
|
||||
t.Error("failed .Unset{{ $idx }}() Flags{{ $size.Size }} bit at index {{ $idx }}")
|
||||
}
|
||||
|
||||
{{ end }}
|
||||
}
|
||||
|
||||
{{ end }}
|
|
@ -0,0 +1,85 @@
|
|||
package bitutil
|
||||
|
||||
// PackInt8s will pack two signed 8bit integers into an unsigned 16bit integer.
|
||||
func PackInt8s(i1, i2 int8) uint16 {
|
||||
const bits = 8
|
||||
const mask = (1 << bits) - 1
|
||||
return uint16(i1)<<bits | uint16(i2)&mask
|
||||
}
|
||||
|
||||
// UnpackInt8s will unpack two signed 8bit integers from an unsigned 16bit integer.
|
||||
func UnpackInt8s(i uint16) (int8, int8) {
|
||||
const bits = 8
|
||||
const mask = (1 << bits) - 1
|
||||
return int8(i >> bits), int8(i & mask)
|
||||
}
|
||||
|
||||
// PackInt16s will pack two signed 16bit integers into an unsigned 32bit integer.
|
||||
func PackInt16s(i1, i2 int16) uint32 {
|
||||
const bits = 16
|
||||
const mask = (1 << bits) - 1
|
||||
return uint32(i1)<<bits | uint32(i2)&mask
|
||||
}
|
||||
|
||||
// UnpackInt16s will unpack two signed 16bit integers from an unsigned 32bit integer.
|
||||
func UnpackInt16s(i uint32) (int16, int16) {
|
||||
const bits = 16
|
||||
const mask = (1 << bits) - 1
|
||||
return int16(i >> bits), int16(i & mask)
|
||||
}
|
||||
|
||||
// PackInt32s will pack two signed 32bit integers into an unsigned 64bit integer.
|
||||
func PackInt32s(i1, i2 int32) uint64 {
|
||||
const bits = 32
|
||||
const mask = (1 << bits) - 1
|
||||
return uint64(i1)<<bits | uint64(i2)&mask
|
||||
}
|
||||
|
||||
// UnpackInt32s will unpack two signed 32bit integers from an unsigned 64bit integer.
|
||||
func UnpackInt32s(i uint64) (int32, int32) {
|
||||
const bits = 32
|
||||
const mask = (1 << bits) - 1
|
||||
return int32(i >> bits), int32(i & mask)
|
||||
}
|
||||
|
||||
// PackUint8s will pack two unsigned 8bit integers into an unsigned 16bit integer.
|
||||
func PackUint8s(u1, u2 uint8) uint16 {
|
||||
const bits = 8
|
||||
const mask = (1 << bits) - 1
|
||||
return uint16(u1)<<bits | uint16(u2)&mask
|
||||
}
|
||||
|
||||
// UnpackUint8s will unpack two unsigned 8bit integers from an unsigned 16bit integer.
|
||||
func UnpackUint8s(u uint16) (uint8, uint8) {
|
||||
const bits = 8
|
||||
const mask = (1 << bits) - 1
|
||||
return uint8(u >> bits), uint8(u & mask)
|
||||
}
|
||||
|
||||
// PackUint16s will pack two unsigned 16bit integers into an unsigned 32bit integer.
|
||||
func PackUint16s(u1, u2 uint16) uint32 {
|
||||
const bits = 16
|
||||
const mask = (1 << bits) - 1
|
||||
return uint32(u1)<<bits | uint32(u2)&mask
|
||||
}
|
||||
|
||||
// UnpackUint16s will unpack two unsigned 16bit integers from an unsigned 32bit integer.
|
||||
func UnpackUint16s(u uint32) (uint16, uint16) {
|
||||
const bits = 16
|
||||
const mask = (1 << bits) - 1
|
||||
return uint16(u >> bits), uint16(u & mask)
|
||||
}
|
||||
|
||||
// PackUint32s will pack two unsigned 32bit integers into an unsigned 64bit integer.
|
||||
func PackUint32s(u1, u2 uint32) uint64 {
|
||||
const bits = 32
|
||||
const mask = (1 << bits) - 1
|
||||
return uint64(u1)<<bits | uint64(u2)&mask
|
||||
}
|
||||
|
||||
// UnpackUint32s will unpack two unsigned 32bit integers from an unsigned 64bit integer.
|
||||
func UnpackUint32s(u uint64) (uint32, uint32) {
|
||||
const bits = 32
|
||||
const mask = (1 << bits) - 1
|
||||
return uint32(u >> bits), uint32(u & mask)
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
package atomics_test
|
||||
|
||||
import (
|
||||
"atomic"
|
||||
"unsafe"
|
||||
"testing"
|
||||
|
||||
"codeberg.org/gruf/go-atomics"
|
||||
)
|
||||
|
||||
func Test{{ .Name }}StoreLoad(t *testing.T) {
|
||||
for _, test := range {{ .Name }}Tests {
|
||||
val := atomics.New{{ .Name }}()
|
||||
|
||||
val.Store(test.V1)
|
||||
|
||||
if !({{ call .Compare "val.Load()" "test.V1" }}) {
|
||||
t.Fatalf("failed testing .Store and .Load: expect=%v actual=%v", val.Load(), test.V1)
|
||||
}
|
||||
|
||||
val.Store(test.V2)
|
||||
|
||||
if !({{ call .Compare "val.Load()" "test.V2" }}) {
|
||||
t.Fatalf("failed testing .Store and .Load: expect=%v actual=%v", val.Load(), test.V2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Test{{ .Name }}CAS(t *testing.T) {
|
||||
for _, test := range {{ .Name }}Tests {
|
||||
val := atomics.New{{ .Name }}()
|
||||
|
||||
val.Store(test.V1)
|
||||
|
||||
if val.CAS(test.V2, test.V1) {
|
||||
t.Fatalf("failed testing negative .CAS: test=%+v state=%v", test, val.Load())
|
||||
}
|
||||
|
||||
if !val.CAS(test.V1, test.V2) {
|
||||
t.Fatalf("failed testing positive .CAS: test=%+v state=%v", test, val.Load())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Test{{ .Name }}Swap(t *testing.T) {
|
||||
for _, test := range {{ .Name }}Tests {
|
||||
val := atomics.New{{ .Name }}()
|
||||
|
||||
val.Store(test.V1)
|
||||
|
||||
if !({{ call .Compare "val.Swap(test.V2)" "test.V1" }}) {
|
||||
t.Fatal("failed testing .Swap")
|
||||
}
|
||||
|
||||
if !({{ call .Compare "val.Swap(test.V1)" "test.V2" }}) {
|
||||
t.Fatal("failed testing .Swap")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2021 gruf
|
||||
Copyright (c) 2022 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:
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# go-byteutil
|
||||
|
||||
A useful package of byte utilities.
|
|
@ -0,0 +1,129 @@
|
|||
package byteutil
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
var (
|
||||
// ensure we conform to interfaces.
|
||||
_ interface {
|
||||
io.Writer
|
||||
io.ByteWriter
|
||||
WriteRune(rune) (int, error)
|
||||
io.StringWriter
|
||||
io.WriterAt
|
||||
WriteStringAt(string, int64) (int, error)
|
||||
} = (*Buffer)(nil)
|
||||
|
||||
// ErrBeyondBufferLen is returned if .WriteAt() is attempted beyond buffer length.
|
||||
ErrBeyondBufferLen = errors.New("start beyond buffer length")
|
||||
)
|
||||
|
||||
// Buffer is a simple wrapper around a byte slice.
|
||||
type Buffer struct{ B []byte }
|
||||
|
||||
// WriteByte will append given byte to buffer, fulfilling io.ByteWriter.
|
||||
func (buf *Buffer) WriteByte(c byte) error {
|
||||
buf.B = append(buf.B, c)
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteRune will append given rune to buffer.
|
||||
func (buf *Buffer) WriteRune(r rune) (int, error) {
|
||||
// Check for single-byte rune
|
||||
if r < utf8.RuneSelf {
|
||||
buf.B = append(buf.B, byte(r))
|
||||
return 1, nil
|
||||
}
|
||||
|
||||
// Before-len
|
||||
l := len(buf.B)
|
||||
|
||||
// Grow to max size rune
|
||||
buf.Grow(utf8.UTFMax)
|
||||
|
||||
// Write encoded rune to buffer
|
||||
n := utf8.EncodeRune(buf.B[l:len(buf.B)], r)
|
||||
buf.B = buf.B[:l+n]
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// Write will append given byte slice to buffer, fulfilling io.Writer.
|
||||
func (buf *Buffer) Write(b []byte) (int, error) {
|
||||
buf.B = append(buf.B, b...)
|
||||
return len(b), nil
|
||||
}
|
||||
|
||||
// WriteString will append given string to buffer, fulfilling io.StringWriter.
|
||||
func (buf *Buffer) WriteString(s string) (int, error) {
|
||||
buf.B = append(buf.B, s...)
|
||||
return len(s), nil
|
||||
}
|
||||
|
||||
// WriteAt will append given byte slice to buffer at index 'start', fulfilling io.WriterAt.
|
||||
func (buf *Buffer) WriteAt(b []byte, start int64) (int, error) {
|
||||
if start > int64(len(buf.B)) {
|
||||
return 0, ErrBeyondBufferLen
|
||||
}
|
||||
buf.Grow(len(b) - int(int64(len(buf.B))-start))
|
||||
return copy(buf.B[start:], b), nil
|
||||
}
|
||||
|
||||
// WriteStringAt will append given string to buffer at index 'start'.
|
||||
func (buf *Buffer) WriteStringAt(s string, start int64) (int, error) {
|
||||
if start > int64(len(buf.B)) {
|
||||
return 0, ErrBeyondBufferLen
|
||||
}
|
||||
buf.Grow(len(s) - int(int64(len(buf.B))-start))
|
||||
return copy(buf.B[start:], s), nil
|
||||
}
|
||||
|
||||
// Len returns the length of the buffer's underlying byte slice.
|
||||
func (buf *Buffer) Len() int {
|
||||
return len(buf.B)
|
||||
}
|
||||
|
||||
// Cap returns the capacity of the buffer's underlying byte slice.
|
||||
func (buf *Buffer) Cap() int {
|
||||
return cap(buf.B)
|
||||
}
|
||||
|
||||
// Grow will increase the buffers length by 'sz', and the capacity by at least this.
|
||||
func (buf *Buffer) Grow(sz int) {
|
||||
buf.Guarantee(sz)
|
||||
buf.B = buf.B[:len(buf.B)+sz]
|
||||
}
|
||||
|
||||
// Guarantee will guarantee buffer containers at least 'sz' remaining capacity.
|
||||
func (buf *Buffer) Guarantee(sz int) {
|
||||
if sz > cap(buf.B)-len(buf.B) {
|
||||
nb := make([]byte, 2*cap(buf.B)+sz)
|
||||
copy(nb, buf.B)
|
||||
buf.B = nb[:len(buf.B)]
|
||||
}
|
||||
}
|
||||
|
||||
// Truncate will reduce the length of the buffer by 'n'.
|
||||
func (buf *Buffer) Truncate(n int) {
|
||||
if n > len(buf.B) {
|
||||
n = len(buf.B)
|
||||
}
|
||||
buf.B = buf.B[:len(buf.B)-n]
|
||||
}
|
||||
|
||||
// Reset will reset the buffer length to 0 (retains capacity).
|
||||
func (buf *Buffer) Reset() {
|
||||
buf.B = buf.B[:0]
|
||||
}
|
||||
|
||||
// String returns the underlying byte slice as a string. Please note
|
||||
// this value is tied directly to the underlying byte slice, if you
|
||||
// write to the buffer then returned string values will also change.
|
||||
//
|
||||
// To get an immutable string from buffered data, use string(buf.B).
|
||||
func (buf *Buffer) String() string {
|
||||
return B2S(buf.B)
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
package byteutil
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// Copy returns a copy of []byte.
|
||||
func Copy(b []byte) []byte {
|
||||
if b == nil {
|
||||
return nil
|
||||
}
|
||||
p := make([]byte, len(b))
|
||||
copy(p, b)
|
||||
return p
|
||||
}
|
||||
|
||||
// B2S returns a string representation of []byte without allocation.
|
||||
func B2S(b []byte) string {
|
||||
return *(*string)(unsafe.Pointer(&b))
|
||||
}
|
||||
|
||||
// S2B returns a []byte representation of string without allocation (minus slice header).
|
||||
func S2B(s string) []byte {
|
||||
var b []byte
|
||||
|
||||
// Get byte + string headers
|
||||
bh := (*reflect.SliceHeader)(unsafe.Pointer(&b))
|
||||
sh := (*reflect.StringHeader)(unsafe.Pointer(&s))
|
||||
|
||||
// Manually set bytes to string
|
||||
bh.Data = sh.Data
|
||||
bh.Len = sh.Len
|
||||
bh.Cap = sh.Len
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
// ToUpper offers a faster ToUpper implementation using a lookup table.
|
||||
func ToUpper(b []byte) {
|
||||
const toUpperTable = "\x00\x01\x02\x03\x04\x05\x06\a\b\t\n\v\f\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" +
|
||||
" !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`ABCDEFGHIJKLMNOPQRSTUVWXYZ{|}~" +
|
||||
"\u007f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96" +
|
||||
"\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf" +
|
||||
"\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8" +
|
||||
"\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1" +
|
||||
"\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff"
|
||||
for i := 0; i < len(b); i++ {
|
||||
b[i] = toUpperTable[b[i]]
|
||||
}
|
||||
}
|
||||
|
||||
// ToLower offers a faster ToLower implementation using a lookup table.
|
||||
func ToLower(b []byte) {
|
||||
const toLowerTable = "\x00\x01\x02\x03\x04\x05\x06\a\b\t\n\v\f\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" +
|
||||
" !\"#$%&'()*+,-./0123456789:;<=>?@abcdefghijklmnopqrstuvwxyz[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~" +
|
||||
"\u007f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96" +
|
||||
"\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf" +
|
||||
"\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8" +
|
||||
"\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1" +
|
||||
"\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff"
|
||||
for i := 0; i < len(b); i++ {
|
||||
b[i] = toLowerTable[b[i]]
|
||||
}
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
simple but powerful errors library that allows providing context information with errors
|
|
@ -1,107 +0,0 @@
|
|||
package errors
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"codeberg.org/gruf/go-format"
|
||||
)
|
||||
|
||||
// KV is a structure for setting key-value pairs in ErrorData.
|
||||
type KV struct {
|
||||
Key string
|
||||
Value interface{}
|
||||
}
|
||||
|
||||
// ErrorData defines a way to set and access contextual error data.
|
||||
// The default implementation of this is thread-safe.
|
||||
type ErrorData interface {
|
||||
// Value will attempt to fetch value for given key in ErrorData
|
||||
Value(string) (interface{}, bool)
|
||||
|
||||
// Append adds the supplied key-values to ErrorData, similar keys DO overwrite
|
||||
Append(...KV)
|
||||
|
||||
// Implement byte slice representation formatter.
|
||||
format.Formattable
|
||||
|
||||
// Implement string representation formatter.
|
||||
fmt.Stringer
|
||||
}
|
||||
|
||||
// NewData returns a new ErrorData implementation.
|
||||
func NewData() ErrorData {
|
||||
return &errorData{
|
||||
data: make([]KV, 0, 10),
|
||||
}
|
||||
}
|
||||
|
||||
// errorData is our ErrorData implementation, this is essentially
|
||||
// just a thread-safe string-interface map implementation.
|
||||
type errorData struct {
|
||||
data []KV
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func (d *errorData) set(key string, value interface{}) {
|
||||
for i := range d.data {
|
||||
if d.data[i].Key == key {
|
||||
// Found existing, update!
|
||||
d.data[i].Value = value
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Add new KV entry to slice
|
||||
d.data = append(d.data, KV{
|
||||
Key: key,
|
||||
Value: value,
|
||||
})
|
||||
}
|
||||
|
||||
func (d *errorData) Value(key string) (interface{}, bool) {
|
||||
d.mu.Lock()
|
||||
for i := range d.data {
|
||||
if d.data[i].Key == key {
|
||||
v := d.data[i].Value
|
||||
d.mu.Unlock()
|
||||
return v, true
|
||||
}
|
||||
}
|
||||
d.mu.Unlock()
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (d *errorData) Append(kvs ...KV) {
|
||||
d.mu.Lock()
|
||||
for i := range kvs {
|
||||
d.set(kvs[i].Key, kvs[i].Value)
|
||||
}
|
||||
d.mu.Unlock()
|
||||
}
|
||||
|
||||
func (d *errorData) AppendFormat(b []byte) []byte {
|
||||
buf := format.Buffer{B: b}
|
||||
d.mu.Lock()
|
||||
buf.B = append(buf.B, '{')
|
||||
|
||||
// Append data as kv pairs
|
||||
for i := range d.data {
|
||||
key := d.data[i].Key
|
||||
val := d.data[i].Value
|
||||
format.Appendf(&buf, "{:k}={:v} ", key, val)
|
||||
}
|
||||
|
||||
// Drop trailing space
|
||||
if len(d.data) > 0 {
|
||||
buf.Truncate(1)
|
||||
}
|
||||
|
||||
buf.B = append(buf.B, '}')
|
||||
d.mu.Unlock()
|
||||
return buf.B
|
||||
}
|
||||
|
||||
func (d *errorData) String() string {
|
||||
return string(d.AppendFormat(nil))
|
||||
}
|
|
@ -1,199 +0,0 @@
|
|||
package errors
|
||||
|
||||
import "fmt"
|
||||
|
||||
// ErrorContext defines a wrappable error with the ability to hold extra contextual information
|
||||
type ErrorContext interface {
|
||||
// implement base error interface
|
||||
error
|
||||
|
||||
// Is identifies whether the receiver contains / is the target
|
||||
Is(error) bool
|
||||
|
||||
// Unwrap reveals the underlying wrapped error (if any!)
|
||||
Unwrap() error
|
||||
|
||||
// Value attempts to fetch contextual data for given key from this ErrorContext
|
||||
Value(string) (interface{}, bool)
|
||||
|
||||
// Append allows adding contextual data to this ErrorContext
|
||||
Append(...KV) ErrorContext
|
||||
|
||||
// Data returns the contextual data structure associated with this ErrorContext
|
||||
Data() ErrorData
|
||||
}
|
||||
|
||||
// New returns a new ErrorContext created from string
|
||||
func New(msg string) ErrorContext {
|
||||
return stringError(msg)
|
||||
}
|
||||
|
||||
// Newf returns a new ErrorContext created from format string
|
||||
func Newf(s string, a ...interface{}) ErrorContext {
|
||||
return stringError(fmt.Sprintf(s, a...))
|
||||
}
|
||||
|
||||
// Wrap ensures supplied error is an ErrorContext, wrapping if necessary
|
||||
func Wrap(err error) ErrorContext {
|
||||
// Nil error, do nothing
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check if this is already wrapped somewhere in stack
|
||||
if xerr, ok := err.(*errorContext); ok {
|
||||
return xerr
|
||||
} else if As(err, &xerr) {
|
||||
// This is really not an ideal situation,
|
||||
// but we try to make do by salvaging the
|
||||
// contextual error data from earlier in
|
||||
// stack, setting current error to the top
|
||||
// and setting the unwrapped error to inner
|
||||
return &errorContext{
|
||||
data: xerr.data,
|
||||
innr: Unwrap(err),
|
||||
err: err,
|
||||
}
|
||||
}
|
||||
|
||||
// Return new Error type
|
||||
return &errorContext{
|
||||
data: NewData(),
|
||||
innr: nil,
|
||||
err: err,
|
||||
}
|
||||
}
|
||||
|
||||
// WrapMsg wraps supplied error as inner, returning an ErrorContext
|
||||
// with a new outer error made from the supplied message string
|
||||
func WrapMsg(err error, msg string) ErrorContext {
|
||||
// Nil error, do nothing
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check if this is already wrapped
|
||||
var xerr *errorContext
|
||||
if As(err, &xerr) {
|
||||
return &errorContext{
|
||||
data: xerr.data,
|
||||
innr: err,
|
||||
err: New(msg),
|
||||
}
|
||||
}
|
||||
|
||||
// Return new wrapped error
|
||||
return &errorContext{
|
||||
data: NewData(),
|
||||
innr: err,
|
||||
err: stringError(msg),
|
||||
}
|
||||
}
|
||||
|
||||
// WrapMsgf wraps supplied error as inner, returning an ErrorContext with
|
||||
// a new outer error made from the supplied message format string
|
||||
func WrapMsgf(err error, msg string, a ...interface{}) ErrorContext {
|
||||
return WrapMsg(err, fmt.Sprintf(msg, a...))
|
||||
}
|
||||
|
||||
// ErrorData attempts fetch ErrorData from supplied error, returns nil otherwise
|
||||
func Data(err error) ErrorData {
|
||||
x, ok := err.(ErrorContext)
|
||||
if ok {
|
||||
return x.Data()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnwrapAll fully unwraps an error stack to produce a string output.
|
||||
func UnwrapAll(err error) string {
|
||||
if err == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Start error output
|
||||
out := err.Error()
|
||||
err = Unwrap(err)
|
||||
|
||||
// Unwrap and append each
|
||||
for err != nil {
|
||||
out += ": " + err.Error()
|
||||
err = Unwrap(err)
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
// stringError is the simplest ErrorContext implementation
|
||||
type stringError string
|
||||
|
||||
func (e stringError) Error() string {
|
||||
return string(e)
|
||||
}
|
||||
|
||||
func (e stringError) Is(err error) bool {
|
||||
se, ok := err.(stringError)
|
||||
return ok && e == se
|
||||
}
|
||||
|
||||
func (e stringError) Unwrap() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e stringError) Value(key string) (interface{}, bool) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (e stringError) Append(kvs ...KV) ErrorContext {
|
||||
data := NewData()
|
||||
data.Append(kvs...)
|
||||
return &errorContext{
|
||||
data: data,
|
||||
innr: nil,
|
||||
err: e,
|
||||
}
|
||||
}
|
||||
|
||||
func (e stringError) Data() ErrorData {
|
||||
return nil
|
||||
}
|
||||
|
||||
// errorContext is the *actual* ErrorContext implementation
|
||||
type errorContext struct {
|
||||
// data contains any appended context data, there will only ever be one
|
||||
// instance of data within an ErrorContext stack
|
||||
data ErrorData
|
||||
|
||||
// innr is the inner wrapped error in this structure, it is only accessible
|
||||
// via .Unwrap() or via .Is()
|
||||
innr error
|
||||
|
||||
// err is the top-level error in this wrapping structure, we identify
|
||||
// as this error type via .Is() and return its error message
|
||||
err error
|
||||
}
|
||||
|
||||
func (e *errorContext) Error() string {
|
||||
return e.err.Error()
|
||||
}
|
||||
|
||||
func (e *errorContext) Is(err error) bool {
|
||||
return Is(e.err, err) || Is(e.innr, err)
|
||||
}
|
||||
|
||||
func (e *errorContext) Unwrap() error {
|
||||
return e.innr
|
||||
}
|
||||
|
||||
func (e *errorContext) Value(key string) (interface{}, bool) {
|
||||
return e.data.Value(key)
|
||||
}
|
||||
|
||||
func (e *errorContext) Append(kvs ...KV) ErrorContext {
|
||||
e.data.Append(kvs...)
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *errorContext) Data() ErrorData {
|
||||
return e.data
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
package errors
|
||||
|
||||
import "errors"
|
||||
|
||||
// Is wraps "errors".Is()
|
||||
func Is(err, target error) bool {
|
||||
return errors.Is(err, target)
|
||||
}
|
||||
|
||||
// As wraps "errors".As()
|
||||
func As(err error, target interface{}) bool {
|
||||
return errors.As(err, target)
|
||||
}
|
||||
|
||||
// Unwrap wraps "errors".Unwrap()
|
||||
func Unwrap(err error) error {
|
||||
return errors.Unwrap(err)
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2022 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.
|
|
@ -0,0 +1,5 @@
|
|||
# go-errors
|
||||
|
||||
simple but powerful errors library that allows easy wrapping and stacktracing of errors.
|
||||
|
||||
to disable stacktraces set the `notrace` build tag.
|
|
@ -0,0 +1,102 @@
|
|||
package errors
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// Callers is a stacktrace of caller PCs.
|
||||
type Callers []uintptr
|
||||
|
||||
// GetCallers returns a Callers slice of PCs, of at most 'depth'.
|
||||
func GetCallers(skip int, depth int) Callers {
|
||||
rpc := make([]uintptr, depth)
|
||||
n := runtime.Callers(skip+1, rpc)
|
||||
return Callers(rpc[0:n])
|
||||
}
|
||||
|
||||
// Frames fetches runtime frames for a slice of caller PCs.
|
||||
func (f Callers) Frames() []runtime.Frame {
|
||||
// Allocate expected frames slice
|
||||
frames := make([]runtime.Frame, 0, len(f))
|
||||
|
||||
// Get frames iterator for PCs
|
||||
iter := runtime.CallersFrames(f)
|
||||
|
||||
for {
|
||||
// Get next frame in iter
|
||||
frame, ok := iter.Next()
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
|
||||
// Append to frames slice
|
||||
frames = append(frames, frame)
|
||||
}
|
||||
|
||||
return frames
|
||||
}
|
||||
|
||||
// MarshalJSON implements json.Marshaler to provide an easy, simply default.
|
||||
func (f Callers) MarshalJSON() ([]byte, error) {
|
||||
// JSON-able frame type
|
||||
type frame struct {
|
||||
Func string `json:"func"`
|
||||
File string `json:"file"`
|
||||
Line int `json:"line"`
|
||||
}
|
||||
|
||||
// Allocate expected frames slice
|
||||
frames := make([]frame, 0, len(f))
|
||||
|
||||
// Get frames iterator for PCs
|
||||
iter := runtime.CallersFrames(f)
|
||||
|
||||
for {
|
||||
// Get next frame
|
||||
f, ok := iter.Next()
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
|
||||
// Append to frames slice
|
||||
frames = append(frames, frame{
|
||||
Func: funcname(f.Function),
|
||||
File: f.File,
|
||||
Line: f.Line,
|
||||
})
|
||||
}
|
||||
|
||||
// marshal converted frames
|
||||
return json.Marshal(frames)
|
||||
}
|
||||
|
||||
// String will return a simple string representation of receiving Callers slice.
|
||||
func (f Callers) String() string {
|
||||
// Guess-timate to reduce allocs
|
||||
buf := make([]byte, 0, 64*len(f))
|
||||
|
||||
// Convert to frames
|
||||
frames := f.Frames()
|
||||
|
||||
for i := 0; i < len(frames); i++ {
|
||||
frame := frames[i]
|
||||
|
||||
// Append formatted caller info
|
||||
funcname := funcname(frame.Function)
|
||||
buf = append(buf, funcname+"()\n\t"+frame.File+":"...)
|
||||
buf = strconv.AppendInt(buf, int64(frame.Line), 10)
|
||||
buf = append(buf, '\n')
|
||||
}
|
||||
|
||||
return *(*string)(unsafe.Pointer(&buf))
|
||||
}
|
||||
|
||||
// funcname splits a function name with pkg from its path prefix.
|
||||
func funcname(name string) string {
|
||||
i := strings.LastIndex(name, "/")
|
||||
return name[i+1:]
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
//go:build !notrace
|
||||
// +build !notrace
|
||||
|
||||
package errors
|
||||
|
||||
type errormsg struct {
|
||||
msg string
|
||||
wrap error
|
||||
stack Callers
|
||||
}
|
||||
|
||||
func create(msg string, wrap error) *errormsg {
|
||||
return &errormsg{
|
||||
msg: msg,
|
||||
wrap: wrap,
|
||||
stack: GetCallers(2, 10),
|
||||
}
|
||||
}
|
||||
|
||||
func (err *errormsg) Error() string {
|
||||
return err.msg
|
||||
}
|
||||
|
||||
func (err *errormsg) Is(target error) bool {
|
||||
other, ok := target.(*errormsg)
|
||||
return ok && (err.msg == other.msg)
|
||||
}
|
||||
|
||||
func (err *errormsg) Unwrap() error {
|
||||
return err.wrap
|
||||
}
|
||||
|
||||
func (err *errormsg) Stacktrace() Callers {
|
||||
return err.stack
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
//go:build notrace
|
||||
// +build notrace
|
||||
|
||||
package errors
|
||||
|
||||
type errormsg struct {
|
||||
msg string
|
||||
wrap error
|
||||
}
|
||||
|
||||
func create(msg string, wrap error) *errormsg {
|
||||
return &errormsg{
|
||||
msg: msg,
|
||||
wrap: wrap,
|
||||
}
|
||||
}
|
||||
|
||||
func (err *errormsg) Error() string {
|
||||
return err.msg
|
||||
}
|
||||
|
||||
func (err *errormsg) Is(target error) bool {
|
||||
other, ok := target.(*errormsg)
|
||||
return ok && (err.msg == other.msg)
|
||||
}
|
||||
|
||||
func (err *errormsg) Unwrap() error {
|
||||
return err.wrap
|
||||
}
|
||||
|
||||
func (err *errormsg) Stacktrace() Callers {
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
package errors
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// New returns a new error created from message.
|
||||
func New(msg string) error {
|
||||
return create(msg, nil)
|
||||
}
|
||||
|
||||
// Newf returns a new error created from message format and args.
|
||||
func Newf(msgf string, args ...interface{}) error {
|
||||
return create(fmt.Sprintf(msgf, args...), nil)
|
||||
}
|
||||
|
||||
// Wrap will wrap supplied error within a new error created from message.
|
||||
func Wrap(err error, msg string) error {
|
||||
return create(msg, err)
|
||||
}
|
||||
|
||||
// Wrapf will wrap supplied error within a new error created from message format and args.
|
||||
func Wrapf(err error, msgf string, args ...interface{}) error {
|
||||
return create(fmt.Sprintf(msgf, args...), err)
|
||||
}
|
||||
|
||||
// Stacktrace fetches a stored stacktrace of callers from an error, or returns nil.
|
||||
func Stacktrace(err error) Callers {
|
||||
var callers Callers
|
||||
if err, ok := err.(interface { //nolint
|
||||
Stacktrace() Callers
|
||||
}); ok {
|
||||
callers = err.Stacktrace()
|
||||
}
|
||||
return callers
|
||||
}
|
|
@ -5,19 +5,18 @@ import (
|
|||
"unsafe"
|
||||
)
|
||||
|
||||
// OnceError is an error structure that supports safe multi-threaded
|
||||
// usage and setting only once (until reset)
|
||||
type OnceError struct {
|
||||
err unsafe.Pointer
|
||||
}
|
||||
// OnceError is an error structure that supports safe multi
|
||||
// threaded usage and setting only once (until reset).
|
||||
type OnceError struct{ err unsafe.Pointer }
|
||||
|
||||
// NewOnce returns a new OnceError instance
|
||||
// NewOnce returns a new OnceError instance.
|
||||
func NewOnce() OnceError {
|
||||
return OnceError{
|
||||
err: nil,
|
||||
}
|
||||
}
|
||||
|
||||
// Store will safely set the OnceError to value, no-op if nil.
|
||||
func (e *OnceError) Store(err error) {
|
||||
// Nothing to do
|
||||
if err == nil {
|
||||
|
@ -32,14 +31,17 @@ func (e *OnceError) Store(err error) {
|
|||
)
|
||||
}
|
||||
|
||||
// Load will load the currently stored error.
|
||||
func (e *OnceError) Load() error {
|
||||
return *(*error)(atomic.LoadPointer(&e.err))
|
||||
}
|
||||
|
||||
// IsSet returns whether OnceError has been set.
|
||||
func (e *OnceError) IsSet() bool {
|
||||
return (atomic.LoadPointer(&e.err) != nil)
|
||||
}
|
||||
|
||||
// Reset will reset the OnceError value.
|
||||
func (e *OnceError) Reset() {
|
||||
atomic.StorePointer(&e.err, nil)
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
package errors
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
|
||||
"codeberg.org/gruf/go-bitutil"
|
||||
)
|
||||
|
||||
// Is reports whether any error in err's chain matches any of targets
|
||||
// (up to a max of 64 targets).
|
||||
//
|
||||
// The chain consists of err itself followed by the sequence of errors obtained by
|
||||
// repeatedly calling Unwrap.
|
||||
//
|
||||
// An error is considered to match a target if it is equal to that target or if
|
||||
// it implements a method Is(error) bool such that Is(target) returns true.
|
||||
func Is(err error, targets ...error) bool {
|
||||
var flags bitutil.Flags64
|
||||
|
||||
if len(targets) > 64 {
|
||||
panic("too many targets")
|
||||
}
|
||||
|
||||
// Determine if each of targets are comparable
|
||||
for i := 0; i < len(targets); {
|
||||
// Drop nil errors
|
||||
if targets[i] == nil {
|
||||
targets = append(targets[:i], targets[i+1:]...)
|
||||
continue
|
||||
}
|
||||
|
||||
// Check if this error is directly comparable
|
||||
if reflect.TypeOf(targets[i]).Comparable() {
|
||||
flags = flags.Set(uint8(i))
|
||||
}
|
||||
|
||||
i++
|
||||
}
|
||||
|
||||
for err != nil {
|
||||
var errorIs func(error) bool
|
||||
|
||||
// Check if this layer supports .Is interface
|
||||
is, ok := err.(interface{ Is(error) bool })
|
||||
if ok {
|
||||
errorIs = is.Is
|
||||
} else {
|
||||
errorIs = neveris
|
||||
}
|
||||
|
||||
for i := 0; i < len(targets); i++ {
|
||||
// Try directly compare errors
|
||||
if flags.Get(uint8(i)) &&
|
||||
err == targets[i] {
|
||||
return true
|
||||
}
|
||||
|
||||
// Try use .Is() interface
|
||||
if errorIs(targets[i]) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Unwrap to next layer
|
||||
err = errors.Unwrap(err)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// As finds the first error in err's chain that matches target, and if one is found, sets
|
||||
// target to that error value and returns true. Otherwise, it returns false.
|
||||
//
|
||||
// The chain consists of err itself followed by the sequence of errors obtained by
|
||||
// repeatedly calling Unwrap.
|
||||
//
|
||||
// An error matches target if the error's concrete value is assignable to the value
|
||||
// pointed to by target, or if the error has a method As(interface{}) bool such that
|
||||
// As(target) returns true. In the latter case, the As method is responsible for
|
||||
// setting target.
|
||||
//
|
||||
// An error type might provide an As method so it can be treated as if it were a
|
||||
// different error type.
|
||||
//
|
||||
// As panics if target is not a non-nil pointer to either a type that implements
|
||||
// error, or to any interface type.
|
||||
func As(err error, target interface{}) bool {
|
||||
return errors.As(err, target)
|
||||
}
|
||||
|
||||
// Unwrap returns the result of calling the Unwrap method on err, if err's
|
||||
// type contains an Unwrap method returning error. Otherwise, Unwrap returns nil.
|
||||
func Unwrap(err error) error {
|
||||
return errors.Unwrap(err)
|
||||
}
|
||||
|
||||
// neveris fits the .Is(error) bool interface function always returning false.
|
||||
func neveris(error) bool { return false }
|
|
@ -6,20 +6,19 @@ import (
|
|||
|
||||
// allocate these just once
|
||||
var (
|
||||
dot = []byte(".")
|
||||
dotStr = ".'"
|
||||
dot = []byte(dotStr)
|
||||
dotStr = "."
|
||||
)
|
||||
|
||||
type Builder struct {
|
||||
B []byte // B is the underlying byte buffer
|
||||
dd int // pos of last '..' appended to builder
|
||||
|
||||
abs bool // abs stores whether path passed to first .Append() is absolute
|
||||
set bool // set stores whether b.abs has been set i.e. not first call to .Append()
|
||||
}
|
||||
|
||||
// NewBuilder returns a new Builder object using the supplied byte
|
||||
// slice as the underlying buffer
|
||||
// NewBuilder returns a new Builder object using the
|
||||
// supplied byte slice as the underlying buffer
|
||||
func NewBuilder(b []byte) Builder {
|
||||
if b != nil {
|
||||
b = b[:0]
|
||||
|
@ -53,7 +52,7 @@ func (b *Builder) Cap() int {
|
|||
|
||||
// Bytes returns the accumulated path bytes.
|
||||
func (b *Builder) Bytes() []byte {
|
||||
if b.Len() < 1 {
|
||||
if len(b.B) < 1 {
|
||||
return dot
|
||||
}
|
||||
return b.B
|
||||
|
@ -61,7 +60,7 @@ func (b *Builder) Bytes() []byte {
|
|||
|
||||
// String returns the accumulated path string.
|
||||
func (b *Builder) String() string {
|
||||
if b.Len() < 1 {
|
||||
if len(b.B) < 1 {
|
||||
return dotStr
|
||||
}
|
||||
return string(b.B)
|
||||
|
@ -74,7 +73,7 @@ func (b *Builder) String() string {
|
|||
// returned string changing. Consider using .String() if
|
||||
// this is undesired behaviour.
|
||||
func (b *Builder) StringPtr() string {
|
||||
if b.Len() < 1 {
|
||||
if len(b.B) < 1 {
|
||||
return dotStr
|
||||
}
|
||||
return *(*string)(unsafe.Pointer(&b.B))
|
||||
|
@ -89,9 +88,9 @@ func (b *Builder) Absolute() bool {
|
|||
func (b *Builder) SetAbsolute(val bool) {
|
||||
if !b.set {
|
||||
if val {
|
||||
// .Append() has not be called,
|
||||
// add a '/' and set abs
|
||||
b.guarantee(1)
|
||||
// .Append() has not been
|
||||
// called, add a '/' and set abs
|
||||
b.Guarantee(1)
|
||||
b.appendByte('/')
|
||||
b.abs = true
|
||||
}
|
||||
|
@ -107,7 +106,7 @@ func (b *Builder) SetAbsolute(val bool) {
|
|||
|
||||
// If not empty (i.e. not just '/'),
|
||||
// then shift bytes 1 left
|
||||
if b.Len() > 1 {
|
||||
if len(b.B) > 1 {
|
||||
copy(b.B, b.B[1:])
|
||||
}
|
||||
|
||||
|
@ -119,16 +118,16 @@ func (b *Builder) SetAbsolute(val bool) {
|
|||
b.abs = true
|
||||
|
||||
// Guarantee 1 byte available
|
||||
b.guarantee(1)
|
||||
b.Guarantee(1)
|
||||
|
||||
// If empty, just append '/'
|
||||
if b.Len() < 1 {
|
||||
if len(b.B) < 1 {
|
||||
b.appendByte('/')
|
||||
return
|
||||
}
|
||||
|
||||
// Increase length
|
||||
l := b.Len()
|
||||
l := len(b.B)
|
||||
b.B = b.B[:l+1]
|
||||
|
||||
// Shift bytes 1 right
|
||||
|
@ -151,9 +150,9 @@ func (b *Builder) Append(p []byte) {
|
|||
// to accomodate the extra path length
|
||||
func (b *Builder) AppendString(path string) {
|
||||
defer func() {
|
||||
// If buffer is empty, and an absolute path,
|
||||
// ensure it starts with a '/'
|
||||
if b.Len() < 1 && b.abs {
|
||||
// If buffer is empty, and an absolute
|
||||
// path, ensure it starts with a '/'
|
||||
if len(b.B) < 1 && b.abs {
|
||||
b.appendByte('/')
|
||||
}
|
||||
}()
|
||||
|
@ -165,7 +164,7 @@ func (b *Builder) AppendString(path string) {
|
|||
|
||||
// Guarantee at least the total length
|
||||
// of supplied path available in the buffer
|
||||
b.guarantee(len(path))
|
||||
b.Guarantee(len(path))
|
||||
|
||||
// Try store if absolute
|
||||
if !b.set {
|
||||
|
@ -193,7 +192,7 @@ func (b *Builder) AppendString(path string) {
|
|||
// our current state of the buffer. i.e. is
|
||||
// our buffer length longer than the last
|
||||
// '..' we placed?
|
||||
case b.Len() > b.dd:
|
||||
case len(b.B) > b.dd:
|
||||
b.backtrack()
|
||||
// b.cp = b.lp
|
||||
// b.lp = 0
|
||||
|
@ -202,22 +201,22 @@ func (b *Builder) AppendString(path string) {
|
|||
// we can append '..' to the path buffer,
|
||||
// which is ONLY when path is NOT absolute
|
||||
case !b.abs:
|
||||
if b.Len() > 0 {
|
||||
if len(b.B) > 0 {
|
||||
b.appendByte('/')
|
||||
}
|
||||
b.appendByte('.')
|
||||
b.appendByte('.')
|
||||
b.dd = b.Len()
|
||||
b.dd = len(b.B)
|
||||
// b.lp = lp - 2
|
||||
// b.cp = b.dd
|
||||
}
|
||||
|
||||
default:
|
||||
if (b.abs && b.Len() != 1) || (!b.abs && b.Len() > 0) {
|
||||
if (b.abs && len(b.B) != 1) || (!b.abs && len(b.B) > 0) {
|
||||
b.appendByte('/')
|
||||
}
|
||||
// b.lp = b.cp
|
||||
// b.cp = b.Len()
|
||||
// b.cp = len(b.B)
|
||||
i += b.appendSlice(path[i:])
|
||||
}
|
||||
}
|
||||
|
@ -236,24 +235,29 @@ func (b *Builder) Clean(path string) string {
|
|||
// performing this operation and returning the shortest possible combination
|
||||
// of all the supplied paths. The builder object is NOT reset after return
|
||||
func (b *Builder) Join(base string, paths ...string) string {
|
||||
empty := (len(base) < 1)
|
||||
b.Reset()
|
||||
b.AppendString(base)
|
||||
for _, path := range paths {
|
||||
b.AppendString(path)
|
||||
empty = empty && (len(path) < 1)
|
||||
size := len(base)
|
||||
for i := 0; i < len(paths); i++ {
|
||||
b.AppendString(paths[i])
|
||||
size += len(paths[i])
|
||||
}
|
||||
if empty {
|
||||
if size < 1 {
|
||||
return ""
|
||||
} else if len(b.B) < 1 {
|
||||
return dotStr
|
||||
}
|
||||
return b.String()
|
||||
return string(b.B)
|
||||
}
|
||||
|
||||
// Guarantee ensures there is at least the requested size
|
||||
// free bytes available in the buffer, reallocating if
|
||||
// necessary
|
||||
// free bytes available in the buffer, reallocating if necessary
|
||||
func (b *Builder) Guarantee(size int) {
|
||||
b.guarantee(size)
|
||||
if size > cap(b.B)-len(b.B) {
|
||||
nb := make([]byte, 2*cap(b.B)+size)
|
||||
copy(nb, b.B)
|
||||
b.B = nb[:len(b.B)]
|
||||
}
|
||||
}
|
||||
|
||||
// Truncate reduces the length of the buffer by the requested
|
||||
|
@ -261,7 +265,7 @@ func (b *Builder) Guarantee(size int) {
|
|||
// byte (i.e. '/') will never be truncated
|
||||
func (b *Builder) Truncate(size int) {
|
||||
// If absolute and just '/', do nothing
|
||||
if b.abs && b.Len() == 1 {
|
||||
if b.abs && len(b.B) == 1 {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -269,21 +273,10 @@ func (b *Builder) Truncate(size int) {
|
|||
b.truncate(size)
|
||||
}
|
||||
|
||||
// truncate reduces the length of the buffer by the requested size,
|
||||
// no sanity checks are performed
|
||||
// truncate reduces the length of the buffer by the requested
|
||||
// size, no sanity checks are performed
|
||||
func (b *Builder) truncate(size int) {
|
||||
b.B = b.B[:b.Len()-size]
|
||||
}
|
||||
|
||||
// guarantee ensures there is at least the requested size
|
||||
// free bytes available in the buffer, reallocating if necessary.
|
||||
// no sanity checks are performed
|
||||
func (b *Builder) guarantee(size int) {
|
||||
if size > b.Cap()-b.Len() {
|
||||
nb := make([]byte, 2*b.Cap()+size)
|
||||
copy(nb, b.B)
|
||||
b.B = nb[:b.Len()]
|
||||
}
|
||||
b.B = b.B[:len(b.B)-size]
|
||||
}
|
||||
|
||||
// appendByte appends the supplied byte to the end of
|
||||
|
@ -291,8 +284,8 @@ func (b *Builder) guarantee(size int) {
|
|||
// buffer and setting the next byte-at-index, this is safe as guarantee()
|
||||
// will have been called beforehand
|
||||
func (b *Builder) appendByte(c byte) {
|
||||
b.B = b.B[:b.Len()+1]
|
||||
b.B[b.Len()-1] = c
|
||||
b.B = b.B[:len(b.B)+1]
|
||||
b.B[len(b.B)-1] = c
|
||||
}
|
||||
|
||||
// appendSlice appends the supplied string slice to
|
||||
|
@ -304,8 +297,8 @@ func (b *Builder) appendByte(c byte) {
|
|||
func (b *Builder) appendSlice(slice string) int {
|
||||
i := 0
|
||||
for i < len(slice) && slice[i] != '/' {
|
||||
b.B = b.B[:b.Len()+1]
|
||||
b.B[b.Len()-1] = slice[i]
|
||||
b.B = b.B[:len(b.B)+1]
|
||||
b.B[len(b.B)-1] = slice[i]
|
||||
i++
|
||||
}
|
||||
return i
|
||||
|
@ -314,13 +307,13 @@ func (b *Builder) appendSlice(slice string) int {
|
|||
// backtrack reduces the end of the buffer back to the last
|
||||
// separating '/', or end of buffer
|
||||
func (b *Builder) backtrack() {
|
||||
b.B = b.B[:b.Len()-1]
|
||||
b.B = b.B[:len(b.B)-1]
|
||||
|
||||
for b.Len()-1 > b.dd && b.B[b.Len()-1] != '/' {
|
||||
b.B = b.B[:b.Len()-1]
|
||||
for len(b.B)-1 > b.dd && b.B[len(b.B)-1] != '/' {
|
||||
b.B = b.B[:len(b.B)-1]
|
||||
}
|
||||
|
||||
if b.Len() > 0 {
|
||||
b.B = b.B[:b.Len()-1]
|
||||
if len(b.B) > 0 {
|
||||
b.B = b.B[:len(b.B)-1]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
# go-format
|
||||
|
||||
String formatting package using Rust-style formatting directives.
|
||||
|
||||
Output is generally more visually-friendly than `"fmt"`, while performance is neck-and-neck.
|
||||
|
||||
README is WIP.
|
||||
|
||||
## todos
|
||||
|
||||
- improved verbose printing of number types
|
||||
|
||||
- more test cases
|
||||
|
||||
- improved verbose printing of string ptr types
|
||||
|
|
@ -1,81 +0,0 @@
|
|||
package format
|
||||
|
||||
import (
|
||||
"io"
|
||||
"unicode/utf8"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// ensure we conform to io.Writer.
|
||||
var _ io.Writer = (*Buffer)(nil)
|
||||
|
||||
// Buffer is a simple wrapper around a byte slice.
|
||||
type Buffer struct {
|
||||
B []byte
|
||||
}
|
||||
|
||||
// Write will append given byte slice to buffer, fulfilling io.Writer.
|
||||
func (buf *Buffer) Write(b []byte) (int, error) {
|
||||
buf.B = append(buf.B, b...)
|
||||
return len(b), nil
|
||||
}
|
||||
|
||||
// AppendByte appends given byte to the buffer.
|
||||
func (buf *Buffer) AppendByte(b byte) {
|
||||
buf.B = append(buf.B, b)
|
||||
}
|
||||
|
||||
// AppendRune appends given rune to the buffer.
|
||||
func (buf *Buffer) AppendRune(r rune) {
|
||||
if r < utf8.RuneSelf {
|
||||
buf.B = append(buf.B, byte(r))
|
||||
return
|
||||
}
|
||||
|
||||
l := buf.Len()
|
||||
for i := 0; i < utf8.UTFMax; i++ {
|
||||
buf.B = append(buf.B, 0)
|
||||
}
|
||||
n := utf8.EncodeRune(buf.B[l:buf.Len()], r)
|
||||
buf.B = buf.B[:l+n]
|
||||
}
|
||||
|
||||
// Append will append given byte slice to the buffer.
|
||||
func (buf *Buffer) Append(b []byte) {
|
||||
buf.B = append(buf.B, b...)
|
||||
}
|
||||
|
||||
// AppendString appends given string to the buffer.
|
||||
func (buf *Buffer) AppendString(s string) {
|
||||
buf.B = append(buf.B, s...)
|
||||
}
|
||||
|
||||
// Len returns the length of the buffer's underlying byte slice.
|
||||
func (buf *Buffer) Len() int {
|
||||
return len(buf.B)
|
||||
}
|
||||
|
||||
// Cap returns the capacity of the buffer's underlying byte slice.
|
||||
func (buf *Buffer) Cap() int {
|
||||
return cap(buf.B)
|
||||
}
|
||||
|
||||
// Truncate will reduce the length of the buffer by 'n'.
|
||||
func (buf *Buffer) Truncate(n int) {
|
||||
if n > len(buf.B) {
|
||||
n = len(buf.B)
|
||||
}
|
||||
buf.B = buf.B[:buf.Len()-n]
|
||||
}
|
||||
|
||||
// Reset will reset the buffer length to 0 (retains capacity).
|
||||
func (buf *Buffer) Reset() {
|
||||
buf.B = buf.B[:0]
|
||||
}
|
||||
|
||||
// String returns the underlying byte slice as a string. Please note
|
||||
// this value is tied directly to the underlying byte slice, if you
|
||||
// write to the buffer then returned string values will also change.
|
||||
func (buf *Buffer) String() string {
|
||||
return *(*string)(unsafe.Pointer(&buf.B))
|
||||
}
|
|
@ -1,565 +0,0 @@
|
|||
package format
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strconv"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// Formattable defines a type capable of being formatted and appended to a byte buffer.
|
||||
type Formattable interface {
|
||||
AppendFormat([]byte) []byte
|
||||
}
|
||||
|
||||
// format is the object passed among the append___ formatting functions.
|
||||
type format struct {
|
||||
flags uint8 // 'isKey' and 'verbose' flags
|
||||
drefs uint8 // current value deref count
|
||||
curd uint8 // current depth
|
||||
maxd uint8 // maximum depth
|
||||
buf *Buffer // out buffer
|
||||
}
|
||||
|
||||
const (
|
||||
// flag bit constants.
|
||||
isKeyBit = uint8(1) << 0
|
||||
isValBit = uint8(1) << 1
|
||||
vboseBit = uint8(1) << 2
|
||||
panicBit = uint8(1) << 3
|
||||
)
|
||||
|
||||
// AtMaxDepth returns whether format is currently at max depth.
|
||||
func (f format) AtMaxDepth() bool {
|
||||
return f.curd > f.maxd
|
||||
}
|
||||
|
||||
// Derefs returns no. times current value has been dereferenced.
|
||||
func (f format) Derefs() uint8 {
|
||||
return f.drefs
|
||||
}
|
||||
|
||||
// IsKey returns whether the isKey flag is set.
|
||||
func (f format) IsKey() bool {
|
||||
return (f.flags & isKeyBit) != 0
|
||||
}
|
||||
|
||||
// IsValue returns whether the isVal flag is set.
|
||||
func (f format) IsValue() bool {
|
||||
return (f.flags & isValBit) != 0
|
||||
}
|
||||
|
||||
// Verbose returns whether the verbose flag is set.
|
||||
func (f format) Verbose() bool {
|
||||
return (f.flags & vboseBit) != 0
|
||||
}
|
||||
|
||||
// Panic returns whether the panic flag is set.
|
||||
func (f format) Panic() bool {
|
||||
return (f.flags & panicBit) != 0
|
||||
}
|
||||
|
||||
// SetIsKey returns format instance with the isKey bit set to value.
|
||||
func (f format) SetIsKey() format {
|
||||
return format{
|
||||
flags: f.flags & ^isValBit | isKeyBit,
|
||||
curd: f.curd,
|
||||
maxd: f.maxd,
|
||||
buf: f.buf,
|
||||
}
|
||||
}
|
||||
|
||||
// SetIsValue returns format instance with the isVal bit set to value.
|
||||
func (f format) SetIsValue() format {
|
||||
return format{
|
||||
flags: f.flags & ^isKeyBit | isValBit,
|
||||
curd: f.curd,
|
||||
maxd: f.maxd,
|
||||
buf: f.buf,
|
||||
}
|
||||
}
|
||||
|
||||
// SetPanic returns format instance with the panic bit set to value.
|
||||
func (f format) SetPanic() format {
|
||||
return format{
|
||||
flags: f.flags | panicBit /* handle panic as value */ | isValBit & ^isKeyBit,
|
||||
curd: f.curd,
|
||||
maxd: f.maxd,
|
||||
buf: f.buf,
|
||||
}
|
||||
}
|
||||
|
||||
// IncrDepth returns format instance with depth incremented.
|
||||
func (f format) IncrDepth() format {
|
||||
return format{
|
||||
flags: f.flags,
|
||||
curd: f.curd + 1,
|
||||
maxd: f.maxd,
|
||||
buf: f.buf,
|
||||
}
|
||||
}
|
||||
|
||||
// IncrDerefs returns format instance with dereference count incremented.
|
||||
func (f format) IncrDerefs() format {
|
||||
return format{
|
||||
flags: f.flags,
|
||||
drefs: f.drefs + 1,
|
||||
curd: f.curd,
|
||||
maxd: f.maxd,
|
||||
buf: f.buf,
|
||||
}
|
||||
}
|
||||
|
||||
// appendType appends a type using supplied type str.
|
||||
func appendType(fmt format, t string) {
|
||||
for i := uint8(0); i < fmt.Derefs(); i++ {
|
||||
fmt.buf.AppendByte('*')
|
||||
}
|
||||
fmt.buf.AppendString(t)
|
||||
}
|
||||
|
||||
// appendNilType Appends nil to buf, type included if verbose.
|
||||
func appendNilType(fmt format, t string) {
|
||||
if fmt.Verbose() {
|
||||
fmt.buf.AppendByte('(')
|
||||
appendType(fmt, t)
|
||||
fmt.buf.AppendString(`)(nil)`)
|
||||
} else {
|
||||
fmt.buf.AppendString(`nil`)
|
||||
}
|
||||
}
|
||||
|
||||
// appendByte Appends a single byte to buf.
|
||||
func appendByte(fmt format, b byte) {
|
||||
if fmt.IsValue() || fmt.Verbose() {
|
||||
fmt.buf.AppendString(`'` + string(b) + `'`)
|
||||
} else {
|
||||
fmt.buf.AppendByte(b)
|
||||
}
|
||||
}
|
||||
|
||||
// appendBytes Appends a quoted byte slice to buf.
|
||||
func appendBytes(fmt format, b []byte) {
|
||||
if b == nil {
|
||||
// Bytes CAN be nil formatted
|
||||
appendNilType(fmt, `[]byte`)
|
||||
} else {
|
||||
// Append bytes as slice
|
||||
fmt.buf.AppendByte('[')
|
||||
for _, b := range b {
|
||||
fmt.buf.AppendByte(b)
|
||||
fmt.buf.AppendByte(',')
|
||||
}
|
||||
if len(b) > 0 {
|
||||
fmt.buf.Truncate(1)
|
||||
}
|
||||
fmt.buf.AppendByte(']')
|
||||
}
|
||||
}
|
||||
|
||||
// appendString Appends an escaped, double-quoted string to buf.
|
||||
func appendString(fmt format, s string) {
|
||||
switch {
|
||||
// Key in a key-value pair
|
||||
case fmt.IsKey():
|
||||
if !strconv.CanBackquote(s) {
|
||||
// Requires quoting AND escaping
|
||||
fmt.buf.B = strconv.AppendQuote(fmt.buf.B, s)
|
||||
} else if containsSpaceOrTab(s) {
|
||||
// Contains space, needs quotes
|
||||
fmt.buf.AppendString(`"` + s + `"`)
|
||||
} else {
|
||||
// All else write as-is
|
||||
fmt.buf.AppendString(s)
|
||||
}
|
||||
|
||||
// Value in a key-value pair (always escape+quote)
|
||||
case fmt.IsValue():
|
||||
fmt.buf.B = strconv.AppendQuote(fmt.buf.B, s)
|
||||
|
||||
// Verbose but neither key nor value (always quote)
|
||||
case fmt.Verbose():
|
||||
fmt.buf.AppendString(`"` + s + `"`)
|
||||
|
||||
// All else
|
||||
default:
|
||||
fmt.buf.AppendString(s)
|
||||
}
|
||||
}
|
||||
|
||||
// appendBool Appends a formatted bool to buf.
|
||||
func appendBool(fmt format, b bool) {
|
||||
fmt.buf.B = strconv.AppendBool(fmt.buf.B, b)
|
||||
}
|
||||
|
||||
// appendInt Appends a formatted int to buf.
|
||||
func appendInt(fmt format, i int64) {
|
||||
fmt.buf.B = strconv.AppendInt(fmt.buf.B, i, 10)
|
||||
}
|
||||
|
||||
// appendUint Appends a formatted uint to buf.
|
||||
func appendUint(fmt format, u uint64) {
|
||||
fmt.buf.B = strconv.AppendUint(fmt.buf.B, u, 10)
|
||||
}
|
||||
|
||||
// appendFloat Appends a formatted float to buf.
|
||||
func appendFloat(fmt format, f float64) {
|
||||
fmt.buf.B = strconv.AppendFloat(fmt.buf.B, f, 'G', -1, 64)
|
||||
}
|
||||
|
||||
// appendComplex Appends a formatted complex128 to buf.
|
||||
func appendComplex(fmt format, c complex128) {
|
||||
appendFloat(fmt, real(c))
|
||||
fmt.buf.AppendByte('+')
|
||||
appendFloat(fmt, imag(c))
|
||||
fmt.buf.AppendByte('i')
|
||||
}
|
||||
|
||||
// isNil will safely check if 'v' is nil without dealing with weird Go interface nil bullshit.
|
||||
func isNil(i interface{}) bool {
|
||||
e := *(*struct {
|
||||
_ unsafe.Pointer // type
|
||||
v unsafe.Pointer // value
|
||||
})(unsafe.Pointer(&i))
|
||||
return (e.v == nil)
|
||||
}
|
||||
|
||||
// appendIfaceOrReflectValue will attempt to append as interface, falling back to reflection.
|
||||
func appendIfaceOrRValue(fmt format, i interface{}) {
|
||||
if !appendIface(fmt, i) {
|
||||
appendRValue(fmt, reflect.ValueOf(i))
|
||||
}
|
||||
}
|
||||
|
||||
// appendValueNext checks for interface methods before performing appendRValue, checking + incr depth.
|
||||
func appendRValueOrIfaceNext(fmt format, v reflect.Value) {
|
||||
// Check we haven't hit max
|
||||
if fmt.AtMaxDepth() {
|
||||
fmt.buf.AppendString("...")
|
||||
return
|
||||
}
|
||||
|
||||
// Incr the depth
|
||||
fmt = fmt.IncrDepth()
|
||||
|
||||
// Make actual call
|
||||
if !v.CanInterface() || !appendIface(fmt, v.Interface()) {
|
||||
appendRValue(fmt, v)
|
||||
}
|
||||
}
|
||||
|
||||
// appendIface parses and Appends a formatted interface value to buf.
|
||||
func appendIface(fmt format, i interface{}) (ok bool) {
|
||||
ok = true // default
|
||||
catchPanic := func() {
|
||||
if r := recover(); r != nil {
|
||||
// DON'T recurse catchPanic()
|
||||
if fmt.Panic() {
|
||||
panic(r)
|
||||
}
|
||||
|
||||
// Attempt to decode panic into buf
|
||||
fmt.buf.AppendString(`!{PANIC=`)
|
||||
appendIfaceOrRValue(fmt.SetPanic(), r)
|
||||
fmt.buf.AppendByte('}')
|
||||
|
||||
// Ensure return
|
||||
ok = true
|
||||
}
|
||||
}
|
||||
|
||||
switch i := i.(type) {
|
||||
// Nil type
|
||||
case nil:
|
||||
fmt.buf.AppendString(`nil`)
|
||||
|
||||
// Reflect types
|
||||
case reflect.Type:
|
||||
if isNil(i) /* safer nil check */ {
|
||||
appendNilType(fmt, `reflect.Type`)
|
||||
} else {
|
||||
appendType(fmt, `reflect.Type`)
|
||||
fmt.buf.AppendString(`(` + i.String() + `)`)
|
||||
}
|
||||
case reflect.Value:
|
||||
appendType(fmt, `reflect.Value`)
|
||||
fmt.buf.AppendByte('(')
|
||||
fmt.flags |= vboseBit
|
||||
appendRValue(fmt, i)
|
||||
fmt.buf.AppendByte(')')
|
||||
|
||||
// Bytes and string types
|
||||
case byte:
|
||||
appendByte(fmt, i)
|
||||
case []byte:
|
||||
appendBytes(fmt, i)
|
||||
case string:
|
||||
appendString(fmt, i)
|
||||
|
||||
// Int types
|
||||
case int:
|
||||
appendInt(fmt, int64(i))
|
||||
case int8:
|
||||
appendInt(fmt, int64(i))
|
||||
case int16:
|
||||
appendInt(fmt, int64(i))
|
||||
case int32:
|
||||
appendInt(fmt, int64(i))
|
||||
case int64:
|
||||
appendInt(fmt, i)
|
||||
|
||||
// Uint types
|
||||
case uint:
|
||||
appendUint(fmt, uint64(i))
|
||||
// case uint8 :: this is 'byte'
|
||||
case uint16:
|
||||
appendUint(fmt, uint64(i))
|
||||
case uint32:
|
||||
appendUint(fmt, uint64(i))
|
||||
case uint64:
|
||||
appendUint(fmt, i)
|
||||
|
||||
// Float types
|
||||
case float32:
|
||||
appendFloat(fmt, float64(i))
|
||||
case float64:
|
||||
appendFloat(fmt, i)
|
||||
|
||||
// Bool type
|
||||
case bool:
|
||||
appendBool(fmt, i)
|
||||
|
||||
// Complex types
|
||||
case complex64:
|
||||
appendComplex(fmt, complex128(i))
|
||||
case complex128:
|
||||
appendComplex(fmt, i)
|
||||
|
||||
// Method types
|
||||
case error:
|
||||
switch {
|
||||
case fmt.Verbose():
|
||||
ok = false
|
||||
case isNil(i) /* use safer nil check */ :
|
||||
appendNilType(fmt, reflect.TypeOf(i).String())
|
||||
default:
|
||||
defer catchPanic()
|
||||
appendString(fmt, i.Error())
|
||||
}
|
||||
case Formattable:
|
||||
switch {
|
||||
case fmt.Verbose():
|
||||
ok = false
|
||||
case isNil(i) /* use safer nil check */ :
|
||||
appendNilType(fmt, reflect.TypeOf(i).String())
|
||||
default:
|
||||
defer catchPanic()
|
||||
fmt.buf.B = i.AppendFormat(fmt.buf.B)
|
||||
}
|
||||
case interface{ String() string }:
|
||||
switch {
|
||||
case fmt.Verbose():
|
||||
ok = false
|
||||
case isNil(i) /* use safer nil check */ :
|
||||
appendNilType(fmt, reflect.TypeOf(i).String())
|
||||
default:
|
||||
defer catchPanic()
|
||||
appendString(fmt, i.String())
|
||||
}
|
||||
|
||||
// No quick handler
|
||||
default:
|
||||
ok = false
|
||||
}
|
||||
|
||||
return ok
|
||||
}
|
||||
|
||||
// appendReflectValue will safely append a reflected value.
|
||||
func appendRValue(fmt format, v reflect.Value) {
|
||||
switch v.Kind() {
|
||||
// String and byte types
|
||||
case reflect.Uint8:
|
||||
appendByte(fmt, byte(v.Uint()))
|
||||
case reflect.String:
|
||||
appendString(fmt, v.String())
|
||||
|
||||
// Float tpyes
|
||||
case reflect.Float32, reflect.Float64:
|
||||
appendFloat(fmt, v.Float())
|
||||
|
||||
// Int types
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
appendInt(fmt, v.Int())
|
||||
|
||||
// Uint types
|
||||
case reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
appendUint(fmt, v.Uint())
|
||||
|
||||
// Complex types
|
||||
case reflect.Complex64, reflect.Complex128:
|
||||
appendComplex(fmt, v.Complex())
|
||||
|
||||
// Bool type
|
||||
case reflect.Bool:
|
||||
appendBool(fmt, v.Bool())
|
||||
|
||||
// Slice and array types
|
||||
case reflect.Array:
|
||||
appendArrayType(fmt, v)
|
||||
case reflect.Slice:
|
||||
if v.IsNil() {
|
||||
appendNilType(fmt, v.Type().String())
|
||||
} else {
|
||||
appendArrayType(fmt, v)
|
||||
}
|
||||
|
||||
// Map types
|
||||
case reflect.Map:
|
||||
if v.IsNil() {
|
||||
appendNilType(fmt, v.Type().String())
|
||||
} else {
|
||||
appendMapType(fmt, v)
|
||||
}
|
||||
|
||||
// Struct types
|
||||
case reflect.Struct:
|
||||
appendStructType(fmt, v)
|
||||
|
||||
// Deref'able ptr types
|
||||
case reflect.Ptr, reflect.Interface:
|
||||
if v.IsNil() {
|
||||
appendNilType(fmt, v.Type().String())
|
||||
} else {
|
||||
appendRValue(fmt.IncrDerefs(), v.Elem())
|
||||
}
|
||||
|
||||
// 'raw' pointer types
|
||||
case reflect.UnsafePointer:
|
||||
appendType(fmt, `unsafe.Pointer`)
|
||||
fmt.buf.AppendByte('(')
|
||||
if u := v.Pointer(); u != 0 {
|
||||
fmt.buf.AppendString("0x")
|
||||
fmt.buf.B = strconv.AppendUint(fmt.buf.B, uint64(u), 16)
|
||||
} else {
|
||||
fmt.buf.AppendString(`nil`)
|
||||
}
|
||||
fmt.buf.AppendByte(')')
|
||||
case reflect.Uintptr:
|
||||
appendType(fmt, `uintptr`)
|
||||
fmt.buf.AppendByte('(')
|
||||
if u := v.Uint(); u != 0 {
|
||||
fmt.buf.AppendString("0x")
|
||||
fmt.buf.B = strconv.AppendUint(fmt.buf.B, u, 16)
|
||||
} else {
|
||||
fmt.buf.AppendString(`nil`)
|
||||
}
|
||||
fmt.buf.AppendByte(')')
|
||||
|
||||
// Generic types we don't *exactly* handle
|
||||
case reflect.Func, reflect.Chan:
|
||||
if v.IsNil() {
|
||||
appendNilType(fmt, v.Type().String())
|
||||
} else {
|
||||
fmt.buf.AppendString(v.String())
|
||||
}
|
||||
|
||||
// Unhandled kind
|
||||
default:
|
||||
fmt.buf.AppendString(v.String())
|
||||
}
|
||||
}
|
||||
|
||||
// appendArrayType Appends an array of unknown type (parsed by reflection) to buf, unlike appendSliceType does NOT catch nil slice.
|
||||
func appendArrayType(fmt format, v reflect.Value) {
|
||||
// get no. elements
|
||||
n := v.Len()
|
||||
|
||||
fmt.buf.AppendByte('[')
|
||||
|
||||
// Append values
|
||||
for i := 0; i < n; i++ {
|
||||
appendRValueOrIfaceNext(fmt.SetIsValue(), v.Index(i))
|
||||
fmt.buf.AppendByte(',')
|
||||
}
|
||||
|
||||
// Drop last comma
|
||||
if n > 0 {
|
||||
fmt.buf.Truncate(1)
|
||||
}
|
||||
|
||||
fmt.buf.AppendByte(']')
|
||||
}
|
||||
|
||||
// appendMapType Appends a map of unknown types (parsed by reflection) to buf.
|
||||
func appendMapType(fmt format, v reflect.Value) {
|
||||
// Prepend type if verbose
|
||||
if fmt.Verbose() {
|
||||
appendType(fmt, v.Type().String())
|
||||
}
|
||||
|
||||
// Get a map iterator
|
||||
r := v.MapRange()
|
||||
n := v.Len()
|
||||
|
||||
fmt.buf.AppendByte('{')
|
||||
|
||||
// Iterate pairs
|
||||
for r.Next() {
|
||||
appendRValueOrIfaceNext(fmt.SetIsKey(), r.Key())
|
||||
fmt.buf.AppendByte('=')
|
||||
appendRValueOrIfaceNext(fmt.SetIsValue(), r.Value())
|
||||
fmt.buf.AppendByte(' ')
|
||||
}
|
||||
|
||||
// Drop last space
|
||||
if n > 0 {
|
||||
fmt.buf.Truncate(1)
|
||||
}
|
||||
|
||||
fmt.buf.AppendByte('}')
|
||||
}
|
||||
|
||||
// appendStructType Appends a struct (as a set of key-value fields) to buf.
|
||||
func appendStructType(fmt format, v reflect.Value) {
|
||||
// Get value type & no. fields
|
||||
t := v.Type()
|
||||
n := v.NumField()
|
||||
|
||||
// Prepend type if verbose
|
||||
if fmt.Verbose() {
|
||||
appendType(fmt, v.Type().String())
|
||||
}
|
||||
|
||||
fmt.buf.AppendByte('{')
|
||||
|
||||
// Iterate fields
|
||||
for i := 0; i < n; i++ {
|
||||
vfield := v.Field(i)
|
||||
tfield := t.Field(i)
|
||||
|
||||
// Append field name
|
||||
fmt.buf.AppendString(tfield.Name)
|
||||
fmt.buf.AppendByte('=')
|
||||
appendRValueOrIfaceNext(fmt.SetIsValue(), vfield)
|
||||
|
||||
// Iter written count
|
||||
fmt.buf.AppendByte(' ')
|
||||
}
|
||||
|
||||
// Drop last space
|
||||
if n > 0 {
|
||||
fmt.buf.Truncate(1)
|
||||
}
|
||||
|
||||
fmt.buf.AppendByte('}')
|
||||
}
|
||||
|
||||
// containsSpaceOrTab checks if "s" contains space or tabs.
|
||||
func containsSpaceOrTab(s string) bool {
|
||||
for _, r := range s {
|
||||
if r == ' ' || r == '\t' {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
|
@ -1,352 +0,0 @@
|
|||
package format
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Formatter allows configuring value and string formatting.
|
||||
type Formatter struct {
|
||||
// MaxDepth specifies the max depth of fields the formatter will iterate.
|
||||
// Once max depth is reached, value will simply be formatted as "...".
|
||||
// e.g.
|
||||
//
|
||||
// MaxDepth=1
|
||||
// type A struct{
|
||||
// Nested B
|
||||
// }
|
||||
// type B struct{
|
||||
// Nested C
|
||||
// }
|
||||
// type C struct{
|
||||
// Field string
|
||||
// }
|
||||
//
|
||||
// Append(&buf, A{}) => {Nested={Nested={Field=...}}}
|
||||
MaxDepth uint8
|
||||
}
|
||||
|
||||
// Append will append formatted form of supplied values into 'buf'.
|
||||
func (f Formatter) Append(buf *Buffer, v ...interface{}) {
|
||||
for _, v := range v {
|
||||
appendIfaceOrRValue(format{maxd: f.MaxDepth, buf: buf}, v)
|
||||
buf.AppendByte(' ')
|
||||
}
|
||||
if len(v) > 0 {
|
||||
buf.Truncate(1)
|
||||
}
|
||||
}
|
||||
|
||||
// Appendf will append the formatted string with supplied values into 'buf'.
|
||||
// Supported format directives:
|
||||
// - '{}' => format supplied arg, in place
|
||||
// - '{0}' => format arg at index 0 of supplied, in place
|
||||
// - '{:?}' => format supplied arg verbosely, in place
|
||||
// - '{:k}' => format supplied arg as key, in place
|
||||
// - '{:v}' => format supplied arg as value, in place
|
||||
//
|
||||
// To escape either of '{}' simply append an additional brace e.g.
|
||||
// - '{{' => '{'
|
||||
// - '}}' => '}'
|
||||
// - '{{}}' => '{}'
|
||||
// - '{{:?}}' => '{:?}'
|
||||
//
|
||||
// More formatting directives might be included in the future.
|
||||
func (f Formatter) Appendf(buf *Buffer, s string, a ...interface{}) {
|
||||
const (
|
||||
// ground state
|
||||
modeNone = uint8(0)
|
||||
|
||||
// prev reached '{'
|
||||
modeOpen = uint8(1)
|
||||
|
||||
// prev reached '}'
|
||||
modeClose = uint8(2)
|
||||
|
||||
// parsing directive index
|
||||
modeIdx = uint8(3)
|
||||
|
||||
// parsing directive operands
|
||||
modeOp = uint8(4)
|
||||
)
|
||||
|
||||
var (
|
||||
// mode is current parsing mode
|
||||
mode uint8
|
||||
|
||||
// arg is the current arg index
|
||||
arg int
|
||||
|
||||
// carg is current directive-set arg index
|
||||
carg int
|
||||
|
||||
// last is the trailing cursor to see slice windows
|
||||
last int
|
||||
|
||||
// idx is the current index in 's'
|
||||
idx int
|
||||
|
||||
// fmt is the base argument formatter
|
||||
fmt = format{
|
||||
maxd: f.MaxDepth,
|
||||
buf: buf,
|
||||
}
|
||||
|
||||
// NOTE: these functions are defined here as function
|
||||
// locals as it turned out to be better for performance
|
||||
// doing it this way, than encapsulating their logic in
|
||||
// some kind of parsing structure. Maybe if the parser
|
||||
// was pooled along with the buffers it might work out
|
||||
// better, but then it makes more internal functions i.e.
|
||||
// .Append() .Appendf() less accessible outside package.
|
||||
//
|
||||
// Currently, passing '-gcflags "-l=4"' causes a not
|
||||
// insignificant decrease in ns/op, which is likely due
|
||||
// to more aggressive function inlining, which this
|
||||
// function can obviously stand to benefit from :)
|
||||
|
||||
// Str returns current string window slice, and updates
|
||||
// the trailing cursor 'last' to current 'idx'
|
||||
Str = func() string {
|
||||
str := s[last:idx]
|
||||
last = idx
|
||||
return str
|
||||
}
|
||||
|
||||
// MoveUp moves the trailing cursor 'last' just past 'idx'
|
||||
MoveUp = func() {
|
||||
last = idx + 1
|
||||
}
|
||||
|
||||
// MoveUpTo moves the trailing cursor 'last' either up to
|
||||
// closest '}', or current 'idx', whichever is furthest
|
||||
MoveUpTo = func() {
|
||||
if i := strings.IndexByte(s[idx:], '}'); i >= 0 {
|
||||
idx += i
|
||||
}
|
||||
MoveUp()
|
||||
}
|
||||
|
||||
// ParseIndex parses an integer from the current string
|
||||
// window, updating 'last' to 'idx'. The string window
|
||||
// is ASSUMED to contain only valid ASCII numbers. This
|
||||
// only returns false if number exceeds platform int size
|
||||
ParseIndex = func() bool {
|
||||
// Get current window
|
||||
str := Str()
|
||||
if len(str) < 1 {
|
||||
return true
|
||||
}
|
||||
|
||||
// Index HAS to fit within platform int
|
||||
if !can32bitInt(str) && !can64bitInt(str) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Build integer from string
|
||||
carg = 0
|
||||
for _, c := range []byte(str) {
|
||||
carg = carg*10 + int(c-'0')
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// ParseOp parses operands from the current string
|
||||
// window, updating 'last' to 'idx'. The string window
|
||||
// is ASSUMED to contain only valid operand ASCII. This
|
||||
// returns success on parsing of operand logic
|
||||
ParseOp = func() bool {
|
||||
// Get current window
|
||||
str := Str()
|
||||
if len(str) < 1 {
|
||||
return true
|
||||
}
|
||||
|
||||
// (for now) only
|
||||
// accept length = 1
|
||||
if len(str) > 1 {
|
||||
return false
|
||||
}
|
||||
|
||||
switch str[0] {
|
||||
case 'k':
|
||||
fmt.flags |= isKeyBit
|
||||
case 'v':
|
||||
fmt.flags |= isValBit
|
||||
case '?':
|
||||
fmt.flags |= vboseBit
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// AppendArg will take either the directive-set, or
|
||||
// iterated arg index, check within bounds of 'a' and
|
||||
// append the that argument formatted to the buffer.
|
||||
// On failure, it will append an error string
|
||||
AppendArg = func() {
|
||||
// Look for idx
|
||||
if carg < 0 {
|
||||
carg = arg
|
||||
}
|
||||
|
||||
// Incr idx
|
||||
arg++
|
||||
|
||||
if carg < len(a) {
|
||||
// Append formatted argument value
|
||||
appendIfaceOrRValue(fmt, a[carg])
|
||||
} else {
|
||||
// No argument found for index
|
||||
buf.AppendString(`!{MISSING_ARG}`)
|
||||
}
|
||||
}
|
||||
|
||||
// Reset will reset the mode to ground, the flags
|
||||
// to empty and parsed 'carg' to empty
|
||||
Reset = func() {
|
||||
mode = modeNone
|
||||
fmt.flags = 0
|
||||
carg = -1
|
||||
}
|
||||
)
|
||||
|
||||
for idx = 0; idx < len(s); idx++ {
|
||||
// Get next char
|
||||
c := s[idx]
|
||||
|
||||
switch mode {
|
||||
// Ground mode
|
||||
case modeNone:
|
||||
switch c {
|
||||
case '{':
|
||||
// Enter open mode
|
||||
buf.AppendString(Str())
|
||||
mode = modeOpen
|
||||
MoveUp()
|
||||
case '}':
|
||||
// Enter close mode
|
||||
buf.AppendString(Str())
|
||||
mode = modeClose
|
||||
MoveUp()
|
||||
}
|
||||
|
||||
// Encountered open '{'
|
||||
case modeOpen:
|
||||
switch c {
|
||||
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
|
||||
// Starting index
|
||||
mode = modeIdx
|
||||
MoveUp()
|
||||
case '{':
|
||||
// Escaped bracket
|
||||
buf.AppendByte('{')
|
||||
mode = modeNone
|
||||
MoveUp()
|
||||
case '}':
|
||||
// Format arg
|
||||
AppendArg()
|
||||
Reset()
|
||||
MoveUp()
|
||||
case ':':
|
||||
// Starting operands
|
||||
mode = modeOp
|
||||
MoveUp()
|
||||
default:
|
||||
// Bad char, missing a close
|
||||
buf.AppendString(`!{MISSING_CLOSE}`)
|
||||
mode = modeNone
|
||||
MoveUpTo()
|
||||
}
|
||||
|
||||
// Encountered close '}'
|
||||
case modeClose:
|
||||
switch c {
|
||||
case '}':
|
||||
// Escaped close bracket
|
||||
buf.AppendByte('}')
|
||||
mode = modeNone
|
||||
MoveUp()
|
||||
default:
|
||||
// Missing an open bracket
|
||||
buf.AppendString(`!{MISSING_OPEN}`)
|
||||
mode = modeNone
|
||||
MoveUp()
|
||||
}
|
||||
|
||||
// Preparing index
|
||||
case modeIdx:
|
||||
switch c {
|
||||
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
|
||||
case ':':
|
||||
if !ParseIndex() {
|
||||
// Unable to parse an integer
|
||||
buf.AppendString(`!{BAD_INDEX}`)
|
||||
mode = modeNone
|
||||
MoveUpTo()
|
||||
} else {
|
||||
// Starting operands
|
||||
mode = modeOp
|
||||
MoveUp()
|
||||
}
|
||||
case '}':
|
||||
if !ParseIndex() {
|
||||
// Unable to parse an integer
|
||||
buf.AppendString(`!{BAD_INDEX}`)
|
||||
} else {
|
||||
// Format arg
|
||||
AppendArg()
|
||||
}
|
||||
Reset()
|
||||
MoveUp()
|
||||
default:
|
||||
// Not a valid index character
|
||||
buf.AppendString(`!{BAD_INDEX}`)
|
||||
mode = modeNone
|
||||
MoveUpTo()
|
||||
}
|
||||
|
||||
// Preparing operands
|
||||
case modeOp:
|
||||
switch c {
|
||||
case 'k', 'v', '?':
|
||||
// TODO: set flags as received
|
||||
case '}':
|
||||
if !ParseOp() {
|
||||
// Unable to parse operands
|
||||
buf.AppendString(`!{BAD_OPERAND}`)
|
||||
} else {
|
||||
// Format arg
|
||||
AppendArg()
|
||||
}
|
||||
Reset()
|
||||
MoveUp()
|
||||
default:
|
||||
// Not a valid operand char
|
||||
buf.AppendString(`!{BAD_OPERAND}`)
|
||||
Reset()
|
||||
MoveUpTo()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Append any remaining
|
||||
buf.AppendString(s[last:])
|
||||
}
|
||||
|
||||
// formatter is the default formatter instance.
|
||||
var formatter = Formatter{
|
||||
MaxDepth: 10,
|
||||
}
|
||||
|
||||
// Append will append formatted form of supplied values into 'buf' using default formatter.
|
||||
// See Formatter.Append() for more documentation.
|
||||
func Append(buf *Buffer, v ...interface{}) {
|
||||
formatter.Append(buf, v...)
|
||||
}
|
||||
|
||||
// Appendf will append the formatted string with supplied values into 'buf' using default formatter.
|
||||
// See Formatter.Appendf() for more documentation.
|
||||
func Appendf(buf *Buffer, s string, a ...interface{}) {
|
||||
formatter.Appendf(buf, s, a...)
|
||||
}
|
|
@ -1,88 +0,0 @@
|
|||
package format
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// pool is the global printer buffer pool.
|
||||
var pool = sync.Pool{
|
||||
New: func() interface{} {
|
||||
return &Buffer{}
|
||||
},
|
||||
}
|
||||
|
||||
// getBuf fetches a buffer from pool.
|
||||
func getBuf() *Buffer {
|
||||
return pool.Get().(*Buffer)
|
||||
}
|
||||
|
||||
// putBuf places a Buffer back in pool.
|
||||
func putBuf(buf *Buffer) {
|
||||
if buf.Cap() > 64<<10 {
|
||||
return // drop large
|
||||
}
|
||||
buf.Reset()
|
||||
pool.Put(buf)
|
||||
}
|
||||
|
||||
// Sprint will format supplied values, returning this string.
|
||||
func Sprint(v ...interface{}) string {
|
||||
buf := Buffer{}
|
||||
Append(&buf, v...)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// Sprintf will format supplied format string and args, returning this string.
|
||||
// See Formatter.Appendf() for more documentation.
|
||||
func Sprintf(s string, a ...interface{}) string {
|
||||
buf := Buffer{}
|
||||
Appendf(&buf, s, a...)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// Print will format supplied values, print this to os.Stdout.
|
||||
func Print(v ...interface{}) {
|
||||
Fprint(os.Stdout, v...) //nolint
|
||||
}
|
||||
|
||||
// Printf will format supplied format string and args, printing this to os.Stdout.
|
||||
// See Formatter.Appendf() for more documentation.
|
||||
func Printf(s string, a ...interface{}) {
|
||||
Fprintf(os.Stdout, s, a...) //nolint
|
||||
}
|
||||
|
||||
// Println will format supplied values, append a trailing newline and print this to os.Stdout.
|
||||
func Println(v ...interface{}) {
|
||||
Fprintln(os.Stdout, v...) //nolint
|
||||
}
|
||||
|
||||
// Fprint will format supplied values, writing this to an io.Writer.
|
||||
func Fprint(w io.Writer, v ...interface{}) (int, error) {
|
||||
buf := getBuf()
|
||||
Append(buf, v...)
|
||||
n, err := w.Write(buf.B)
|
||||
putBuf(buf)
|
||||
return n, err
|
||||
}
|
||||
|
||||
// Fprintf will format supplied format string and args, writing this to an io.Writer.
|
||||
// See Formatter.Appendf() for more documentation.
|
||||
func Fprintf(w io.Writer, s string, a ...interface{}) (int, error) {
|
||||
buf := getBuf()
|
||||
Appendf(buf, s, a...)
|
||||
n, err := w.Write(buf.B)
|
||||
putBuf(buf)
|
||||
return n, err
|
||||
}
|
||||
|
||||
// Println will format supplied values, append a trailing newline and writer this to an io.Writer.
|
||||
func Fprintln(w io.Writer, v ...interface{}) (int, error) {
|
||||
buf := getBuf()
|
||||
Append(buf, v...)
|
||||
buf.AppendByte('\n')
|
||||
n, err := w.Write(buf.B)
|
||||
putBuf(buf)
|
||||
return n, err
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
package format
|
||||
|
||||
import "strconv"
|
||||
|
||||
// can32bitInt returns whether it's possible for 's' to contain an int on 32bit platforms.
|
||||
func can32bitInt(s string) bool {
|
||||
return strconv.IntSize == 32 && (0 < len(s) && len(s) < 10)
|
||||
}
|
||||
|
||||
// can64bitInt returns whether it's possible for 's' to contain an int on 64bit platforms.
|
||||
func can64bitInt(s string) bool {
|
||||
return strconv.IntSize == 64 && (0 < len(s) && len(s) < 19)
|
||||
}
|
|
@ -6,7 +6,7 @@ import (
|
|||
"encoding/hex"
|
||||
)
|
||||
|
||||
// Encoder defines an interface for encoding binary data
|
||||
// Encoder defines an interface for encoding binary data.
|
||||
type Encoder interface {
|
||||
// Encode encodes the data at src into dst
|
||||
Encode(dst []byte, src []byte)
|
||||
|
@ -15,22 +15,22 @@ type Encoder interface {
|
|||
EncodedLen(int) int
|
||||
}
|
||||
|
||||
// Base32 returns a new base32 Encoder
|
||||
// Base32 returns a new base32 Encoder (StdEncoding, no padding).
|
||||
func Base32() Encoder {
|
||||
return base32.StdEncoding.WithPadding(base64.NoPadding)
|
||||
}
|
||||
|
||||
// Base64 returns a new base64 Encoder
|
||||
// Base64 returns a new base64 Encoder (URLEncoding, no padding).
|
||||
func Base64() Encoder {
|
||||
return base64.URLEncoding.WithPadding(base64.NoPadding)
|
||||
}
|
||||
|
||||
// Hex returns a new hex Encoder
|
||||
// Hex returns a new hex Encoder.
|
||||
func Hex() Encoder {
|
||||
return &hexEncoder{}
|
||||
}
|
||||
|
||||
// hexEncoder simply provides an empty receiver to satisfy Encoder
|
||||
// hexEncoder simply provides an empty receiver to satisfy Encoder.
|
||||
type hexEncoder struct{}
|
||||
|
||||
func (*hexEncoder) Encode(dst []byte, src []byte) {
|
||||
|
|
|
@ -1,136 +0,0 @@
|
|||
package hashenc
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"hash"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Hash defines a pooled hash.Hash implementation
|
||||
type Hash interface {
|
||||
// Hash ensures we implement the base hash.Hash implementation
|
||||
hash.Hash
|
||||
|
||||
// Release resets the Hash and places it back in the pool
|
||||
Release()
|
||||
}
|
||||
|
||||
// poolHash is our Hash implementation, providing a hash.Hash and a pool to return to
|
||||
type poolHash struct {
|
||||
hash.Hash
|
||||
pool *sync.Pool
|
||||
}
|
||||
|
||||
func (h *poolHash) Release() {
|
||||
h.Reset()
|
||||
h.pool.Put(h)
|
||||
}
|
||||
|
||||
// SHA512Pool defines a pool of SHA512 hashes
|
||||
type SHA512Pool interface {
|
||||
// SHA512 returns a Hash implementing the SHA512 hashing algorithm
|
||||
SHA512() Hash
|
||||
}
|
||||
|
||||
// NewSHA512Pool returns a new SHA512Pool implementation
|
||||
func NewSHA512Pool() SHA512Pool {
|
||||
p := &sha512Pool{}
|
||||
p.New = func() interface{} {
|
||||
return &poolHash{
|
||||
Hash: sha512.New(),
|
||||
pool: &p.Pool,
|
||||
}
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
// sha512Pool is our SHA512Pool implementation, simply wrapping sync.Pool
|
||||
type sha512Pool struct {
|
||||
sync.Pool
|
||||
}
|
||||
|
||||
func (p *sha512Pool) SHA512() Hash {
|
||||
return p.Get().(Hash)
|
||||
}
|
||||
|
||||
// SHA256Pool defines a pool of SHA256 hashes
|
||||
type SHA256Pool interface {
|
||||
// SHA256 returns a Hash implementing the SHA256 hashing algorithm
|
||||
SHA256() Hash
|
||||
}
|
||||
|
||||
// NewSHA256Pool returns a new SHA256Pool implementation
|
||||
func NewSHA256Pool() SHA256Pool {
|
||||
p := &sha256Pool{}
|
||||
p.New = func() interface{} {
|
||||
return &poolHash{
|
||||
Hash: sha256.New(),
|
||||
pool: &p.Pool,
|
||||
}
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
// sha256Pool is our SHA256Pool implementation, simply wrapping sync.Pool
|
||||
type sha256Pool struct {
|
||||
sync.Pool
|
||||
}
|
||||
|
||||
func (p *sha256Pool) SHA256() Hash {
|
||||
return p.Get().(Hash)
|
||||
}
|
||||
|
||||
// SHA1Pool defines a pool of SHA1 hashes
|
||||
type SHA1Pool interface {
|
||||
SHA1() Hash
|
||||
}
|
||||
|
||||
// NewSHA1Pool returns a new SHA1Pool implementation
|
||||
func NewSHA1Pool() SHA1Pool {
|
||||
p := &sha1Pool{}
|
||||
p.New = func() interface{} {
|
||||
return &poolHash{
|
||||
Hash: sha1.New(),
|
||||
pool: &p.Pool,
|
||||
}
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
// sha1Pool is our SHA1Pool implementation, simply wrapping sync.Pool
|
||||
type sha1Pool struct {
|
||||
sync.Pool
|
||||
}
|
||||
|
||||
func (p *sha1Pool) SHA1() Hash {
|
||||
return p.Get().(Hash)
|
||||
}
|
||||
|
||||
// MD5Pool defines a pool of MD5 hashes
|
||||
type MD5Pool interface {
|
||||
MD5() Hash
|
||||
}
|
||||
|
||||
// NewMD5Pool returns a new MD5 implementation
|
||||
func NewMD5Pool() MD5Pool {
|
||||
p := &md5Pool{}
|
||||
p.New = func() interface{} {
|
||||
return &poolHash{
|
||||
Hash: md5.New(),
|
||||
pool: &p.Pool,
|
||||
}
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
// md5Pool is our MD5Pool implementation, simply wrapping sync.Pool
|
||||
type md5Pool struct {
|
||||
sync.Pool
|
||||
}
|
||||
|
||||
func (p *md5Pool) MD5() Hash {
|
||||
return p.Get().(Hash)
|
||||
}
|
|
@ -18,7 +18,7 @@ type HashEncoder interface {
|
|||
Size() int
|
||||
}
|
||||
|
||||
// New returns a new HashEncoder instance based on supplied hash.Hash and Encoder supplying functions
|
||||
// New returns a new HashEncoder instance based on supplied hash.Hash and Encoder supplying functions.
|
||||
func New(hash hash.Hash, enc Encoder) HashEncoder {
|
||||
hashSize := hash.Size()
|
||||
return &henc{
|
||||
|
@ -29,7 +29,7 @@ func New(hash hash.Hash, enc Encoder) HashEncoder {
|
|||
}
|
||||
}
|
||||
|
||||
// henc is the HashEncoder implementation
|
||||
// henc is the HashEncoder implementation.
|
||||
type henc struct {
|
||||
hash hash.Hash
|
||||
hbuf []byte
|
||||
|
|
|
@ -6,7 +6,7 @@ import (
|
|||
"sync"
|
||||
)
|
||||
|
||||
// BufioReaderPool is a pooled allocator for bufio.Reader objects
|
||||
// BufioReaderPool is a pooled allocator for bufio.Reader objects.
|
||||
type BufioReaderPool interface {
|
||||
// Get fetches a bufio.Reader from pool and resets to supplied reader
|
||||
Get(io.Reader) *bufio.Reader
|
||||
|
@ -15,32 +15,39 @@ type BufioReaderPool interface {
|
|||
Put(*bufio.Reader)
|
||||
}
|
||||
|
||||
// NewBufioReaderPool returns a newly instantiated bufio.Reader pool
|
||||
// NewBufioReaderPool returns a newly instantiated bufio.Reader pool.
|
||||
func NewBufioReaderPool(size int) BufioReaderPool {
|
||||
return &bufioReaderPool{
|
||||
Pool: sync.Pool{
|
||||
pool: sync.Pool{
|
||||
New: func() interface{} {
|
||||
return bufio.NewReaderSize(nil, size)
|
||||
},
|
||||
},
|
||||
size: size,
|
||||
}
|
||||
}
|
||||
|
||||
// bufioReaderPool is our implementation of BufioReaderPool
|
||||
type bufioReaderPool struct{ sync.Pool }
|
||||
// bufioReaderPool is our implementation of BufioReaderPool.
|
||||
type bufioReaderPool struct {
|
||||
pool sync.Pool
|
||||
size int
|
||||
}
|
||||
|
||||
func (p *bufioReaderPool) Get(r io.Reader) *bufio.Reader {
|
||||
br := p.Pool.Get().(*bufio.Reader)
|
||||
br := p.pool.Get().(*bufio.Reader)
|
||||
br.Reset(r)
|
||||
return br
|
||||
}
|
||||
|
||||
func (p *bufioReaderPool) Put(br *bufio.Reader) {
|
||||
if br.Size() < p.size {
|
||||
return
|
||||
}
|
||||
br.Reset(nil)
|
||||
p.Pool.Put(br)
|
||||
p.pool.Put(br)
|
||||
}
|
||||
|
||||
// BufioWriterPool is a pooled allocator for bufio.Writer objects
|
||||
// BufioWriterPool is a pooled allocator for bufio.Writer objects.
|
||||
type BufioWriterPool interface {
|
||||
// Get fetches a bufio.Writer from pool and resets to supplied writer
|
||||
Get(io.Writer) *bufio.Writer
|
||||
|
@ -49,27 +56,34 @@ type BufioWriterPool interface {
|
|||
Put(*bufio.Writer)
|
||||
}
|
||||
|
||||
// NewBufioWriterPool returns a newly instantiated bufio.Writer pool
|
||||
// NewBufioWriterPool returns a newly instantiated bufio.Writer pool.
|
||||
func NewBufioWriterPool(size int) BufioWriterPool {
|
||||
return &bufioWriterPool{
|
||||
Pool: sync.Pool{
|
||||
pool: sync.Pool{
|
||||
New: func() interface{} {
|
||||
return bufio.NewWriterSize(nil, size)
|
||||
},
|
||||
},
|
||||
size: size,
|
||||
}
|
||||
}
|
||||
|
||||
// bufioWriterPool is our implementation of BufioWriterPool
|
||||
type bufioWriterPool struct{ sync.Pool }
|
||||
// bufioWriterPool is our implementation of BufioWriterPool.
|
||||
type bufioWriterPool struct {
|
||||
pool sync.Pool
|
||||
size int
|
||||
}
|
||||
|
||||
func (p *bufioWriterPool) Get(w io.Writer) *bufio.Writer {
|
||||
bw := p.Pool.Get().(*bufio.Writer)
|
||||
bw := p.pool.Get().(*bufio.Writer)
|
||||
bw.Reset(w)
|
||||
return bw
|
||||
}
|
||||
|
||||
func (p *bufioWriterPool) Put(bw *bufio.Writer) {
|
||||
if bw.Size() < p.size {
|
||||
return
|
||||
}
|
||||
bw.Reset(nil)
|
||||
p.Pool.Put(bw)
|
||||
p.pool.Put(bw)
|
||||
}
|
||||
|
|
|
@ -3,16 +3,16 @@ package pools
|
|||
import (
|
||||
"sync"
|
||||
|
||||
"codeberg.org/gruf/go-bytes"
|
||||
"codeberg.org/gruf/go-byteutil"
|
||||
)
|
||||
|
||||
// BufferPool is a pooled allocator for bytes.Buffer objects
|
||||
type BufferPool interface {
|
||||
// Get fetches a bytes.Buffer from pool
|
||||
Get() *bytes.Buffer
|
||||
Get() *byteutil.Buffer
|
||||
|
||||
// Put places supplied bytes.Buffer in pool
|
||||
Put(*bytes.Buffer)
|
||||
Put(*byteutil.Buffer)
|
||||
}
|
||||
|
||||
// NewBufferPool returns a newly instantiated bytes.Buffer pool
|
||||
|
@ -20,7 +20,7 @@ func NewBufferPool(size int) BufferPool {
|
|||
return &bufferPool{
|
||||
pool: sync.Pool{
|
||||
New: func() interface{} {
|
||||
return &bytes.Buffer{B: make([]byte, 0, size)}
|
||||
return &byteutil.Buffer{B: make([]byte, 0, size)}
|
||||
},
|
||||
},
|
||||
size: size,
|
||||
|
@ -33,11 +33,11 @@ type bufferPool struct {
|
|||
size int
|
||||
}
|
||||
|
||||
func (p *bufferPool) Get() *bytes.Buffer {
|
||||
return p.pool.Get().(*bytes.Buffer)
|
||||
func (p *bufferPool) Get() *byteutil.Buffer {
|
||||
return p.pool.Get().(*byteutil.Buffer)
|
||||
}
|
||||
|
||||
func (p *bufferPool) Put(buf *bytes.Buffer) {
|
||||
func (p *bufferPool) Put(buf *byteutil.Buffer) {
|
||||
if buf.Cap() < p.size {
|
||||
return
|
||||
}
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
package pools
|
||||
|
||||
import (
|
||||
"hash"
|
||||
"sync"
|
||||
|
||||
"codeberg.org/gruf/go-hashenc"
|
||||
)
|
||||
|
||||
// HashEncoderPool is a pooled allocator for hashenc.HashEncoder objects.
|
||||
type HashEncoderPool interface {
|
||||
// Get fetches a hashenc.HashEncoder from pool
|
||||
Get() hashenc.HashEncoder
|
||||
|
||||
// Put places supplied hashenc.HashEncoder back in pool
|
||||
Put(hashenc.HashEncoder)
|
||||
}
|
||||
|
||||
// NewHashEncoderPool returns a newly instantiated hashenc.HashEncoder pool.
|
||||
func NewHashEncoderPool(hash func() hash.Hash, enc func() hashenc.Encoder) HashEncoderPool {
|
||||
return &hencPool{
|
||||
pool: sync.Pool{
|
||||
New: func() interface{} {
|
||||
return hashenc.New(hash(), enc())
|
||||
},
|
||||
},
|
||||
size: hashenc.New(hash(), enc()).Size(),
|
||||
}
|
||||
}
|
||||
|
||||
// hencPool is our implementation of HashEncoderPool.
|
||||
type hencPool struct {
|
||||
pool sync.Pool
|
||||
size int
|
||||
}
|
||||
|
||||
func (p *hencPool) Get() hashenc.HashEncoder {
|
||||
return p.pool.Get().(hashenc.HashEncoder)
|
||||
}
|
||||
|
||||
func (p *hencPool) Put(henc hashenc.HashEncoder) {
|
||||
if henc.Size() < p.size {
|
||||
return
|
||||
}
|
||||
p.pool.Put(henc)
|
||||
}
|
|
@ -0,0 +1,387 @@
|
|||
package pools
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type Pool struct {
|
||||
// New is used to instantiate new items
|
||||
New func() interface{}
|
||||
|
||||
// Evict is called on evicted items during pool .Clean()
|
||||
Evict func(interface{})
|
||||
|
||||
local unsafe.Pointer // ptr to []_ppool
|
||||
localSz int64 // count of all elems in local
|
||||
victim unsafe.Pointer // ptr to []_ppool
|
||||
victimSz int64 // count of all elems in victim
|
||||
mutex sync.Mutex // mutex protects new cleanups, and new allocations of local
|
||||
}
|
||||
|
||||
// Get attempts to fetch an item from the pool, failing that allocates with supplied .New() function
|
||||
func (p *Pool) Get() interface{} {
|
||||
// Get local pool for proc
|
||||
// (also pins proc)
|
||||
pool, pid := p.pin()
|
||||
|
||||
if v := pool.getPrivate(); v != nil {
|
||||
// local _ppool private elem acquired
|
||||
runtime_procUnpin()
|
||||
atomic.AddInt64(&p.localSz, -1)
|
||||
return v
|
||||
}
|
||||
|
||||
if v := pool.get(); v != nil {
|
||||
// local _ppool queue elem acquired
|
||||
runtime_procUnpin()
|
||||
atomic.AddInt64(&p.localSz, -1)
|
||||
return v
|
||||
}
|
||||
|
||||
// Unpin before attempting slow
|
||||
runtime_procUnpin()
|
||||
if v := p.getSlow(pid); v != nil {
|
||||
// note size decrementing
|
||||
// is handled within p.getSlow()
|
||||
// as we don't know if it came
|
||||
// from the local or victim pools
|
||||
return v
|
||||
}
|
||||
|
||||
// Alloc new
|
||||
return p.New()
|
||||
}
|
||||
|
||||
// Put places supplied item in the proc local pool
|
||||
func (p *Pool) Put(v interface{}) {
|
||||
// Don't store nil
|
||||
if v == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Get proc local pool
|
||||
// (also pins proc)
|
||||
pool, _ := p.pin()
|
||||
|
||||
// first try private, then queue
|
||||
if !pool.setPrivate(v) {
|
||||
pool.put(v)
|
||||
}
|
||||
runtime_procUnpin()
|
||||
|
||||
// Increment local pool size
|
||||
atomic.AddInt64(&p.localSz, 1)
|
||||
}
|
||||
|
||||
// Clean will drop the current victim pools, move the current local pools to its
|
||||
// place and reset the local pools ptr in order to be regenerated
|
||||
func (p *Pool) Clean() {
|
||||
p.mutex.Lock()
|
||||
|
||||
// victim becomes local, local becomes nil
|
||||
localPtr := atomic.SwapPointer(&p.local, nil)
|
||||
victimPtr := atomic.SwapPointer(&p.victim, localPtr)
|
||||
localSz := atomic.SwapInt64(&p.localSz, 0)
|
||||
atomic.StoreInt64(&p.victimSz, localSz)
|
||||
|
||||
var victim []ppool
|
||||
if victimPtr != nil {
|
||||
victim = *(*[]ppool)(victimPtr)
|
||||
}
|
||||
|
||||
// drain each of the vict _ppool items
|
||||
for i := 0; i < len(victim); i++ {
|
||||
ppool := &victim[i]
|
||||
ppool.evict(p.Evict)
|
||||
}
|
||||
|
||||
p.mutex.Unlock()
|
||||
}
|
||||
|
||||
// LocalSize returns the total number of elements in all the proc-local pools
|
||||
func (p *Pool) LocalSize() int64 {
|
||||
return atomic.LoadInt64(&p.localSz)
|
||||
}
|
||||
|
||||
// VictimSize returns the total number of elements in all the victim (old proc-local) pools
|
||||
func (p *Pool) VictimSize() int64 {
|
||||
return atomic.LoadInt64(&p.victimSz)
|
||||
}
|
||||
|
||||
// getSlow is the slow path for fetching an element, attempting to steal from other proc's
|
||||
// local pools, and failing that, from the aging-out victim pools. pid is still passed so
|
||||
// not all procs start iterating from the same index
|
||||
func (p *Pool) getSlow(pid int) interface{} {
|
||||
// get local pools
|
||||
local := p.localPools()
|
||||
|
||||
// Try to steal from other proc locals
|
||||
for i := 0; i < len(local); i++ {
|
||||
pool := &local[(pid+i+1)%len(local)]
|
||||
if v := pool.get(); v != nil {
|
||||
atomic.AddInt64(&p.localSz, -1)
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// get victim pools
|
||||
victim := p.victimPools()
|
||||
|
||||
// Attempt to steal from victim pools
|
||||
for i := 0; i < len(victim); i++ {
|
||||
pool := &victim[(pid+i+1)%len(victim)]
|
||||
if v := pool.get(); v != nil {
|
||||
atomic.AddInt64(&p.victimSz, -1)
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// Set victim pools to nil (none found)
|
||||
atomic.StorePointer(&p.victim, nil)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// localPools safely loads slice of local _ppools
|
||||
func (p *Pool) localPools() []ppool {
|
||||
local := atomic.LoadPointer(&p.local)
|
||||
if local == nil {
|
||||
return nil
|
||||
}
|
||||
return *(*[]ppool)(local)
|
||||
}
|
||||
|
||||
// victimPools safely loads slice of victim _ppools
|
||||
func (p *Pool) victimPools() []ppool {
|
||||
victim := atomic.LoadPointer(&p.victim)
|
||||
if victim == nil {
|
||||
return nil
|
||||
}
|
||||
return *(*[]ppool)(victim)
|
||||
}
|
||||
|
||||
// pin will get fetch pin proc to PID, fetch proc-local _ppool and current PID we're pinned to
|
||||
func (p *Pool) pin() (*ppool, int) {
|
||||
for {
|
||||
// get local pools
|
||||
local := p.localPools()
|
||||
|
||||
if len(local) > 0 {
|
||||
// local already initialized
|
||||
|
||||
// pin to current proc
|
||||
pid := runtime_procPin()
|
||||
|
||||
// check for pid local pool
|
||||
if pid < len(local) {
|
||||
return &local[pid], pid
|
||||
}
|
||||
|
||||
// unpin from proc
|
||||
runtime_procUnpin()
|
||||
} else {
|
||||
// local not yet initialized
|
||||
|
||||
// Check functions are set
|
||||
if p.New == nil {
|
||||
panic("new func must not be nil")
|
||||
}
|
||||
if p.Evict == nil {
|
||||
panic("evict func must not be nil")
|
||||
}
|
||||
}
|
||||
|
||||
// allocate local
|
||||
p.allocLocal()
|
||||
}
|
||||
}
|
||||
|
||||
// allocLocal allocates a new local pool slice, with the old length passed to check
|
||||
// if pool was previously nil, or whether a change in GOMAXPROCS occurred
|
||||
func (p *Pool) allocLocal() {
|
||||
// get pool lock
|
||||
p.mutex.Lock()
|
||||
|
||||
// Calculate new size to use
|
||||
size := runtime.GOMAXPROCS(0)
|
||||
|
||||
local := p.localPools()
|
||||
if len(local) != size {
|
||||
// GOMAXPROCS changed, reallocate
|
||||
pools := make([]ppool, size)
|
||||
atomic.StorePointer(&p.local, unsafe.Pointer(&pools))
|
||||
|
||||
// Evict old local elements
|
||||
for i := 0; i < len(local); i++ {
|
||||
pool := &local[i]
|
||||
pool.evict(p.Evict)
|
||||
}
|
||||
}
|
||||
|
||||
// Unlock pool
|
||||
p.mutex.Unlock()
|
||||
}
|
||||
|
||||
// _ppool is a proc local pool
|
||||
type _ppool struct {
|
||||
// root is the root element of the _ppool queue,
|
||||
// and protects concurrent access to the queue
|
||||
root unsafe.Pointer
|
||||
|
||||
// private is a proc private member accessible
|
||||
// only to the pid this _ppool is assigned to,
|
||||
// except during evict (hence the unsafe pointer)
|
||||
private unsafe.Pointer
|
||||
}
|
||||
|
||||
// ppool wraps _ppool with pad.
|
||||
type ppool struct {
|
||||
_ppool
|
||||
|
||||
// Prevents false sharing on widespread platforms with
|
||||
// 128 mod (cache line size) = 0 .
|
||||
pad [128 - unsafe.Sizeof(_ppool{})%128]byte
|
||||
}
|
||||
|
||||
// getPrivate gets the proc private member
|
||||
func (pp *_ppool) getPrivate() interface{} {
|
||||
ptr := atomic.SwapPointer(&pp.private, nil)
|
||||
if ptr == nil {
|
||||
return nil
|
||||
}
|
||||
return *(*interface{})(ptr)
|
||||
}
|
||||
|
||||
// setPrivate sets the proc private member (only if unset)
|
||||
func (pp *_ppool) setPrivate(v interface{}) bool {
|
||||
return atomic.CompareAndSwapPointer(&pp.private, nil, unsafe.Pointer(&v))
|
||||
}
|
||||
|
||||
// get fetches an element from the queue
|
||||
func (pp *_ppool) get() interface{} {
|
||||
for {
|
||||
// Attempt to load root elem
|
||||
root := atomic.LoadPointer(&pp.root)
|
||||
if root == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Attempt to consume root elem
|
||||
if root == inUsePtr ||
|
||||
!atomic.CompareAndSwapPointer(&pp.root, root, inUsePtr) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Root becomes next in chain
|
||||
e := (*elem)(root)
|
||||
v := e.value
|
||||
|
||||
// Place new root back in the chain
|
||||
atomic.StorePointer(&pp.root, unsafe.Pointer(e.next))
|
||||
putElem(e)
|
||||
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// put places an element in the queue
|
||||
func (pp *_ppool) put(v interface{}) {
|
||||
// Prepare next elem
|
||||
e := getElem()
|
||||
e.value = v
|
||||
|
||||
for {
|
||||
// Attempt to load root elem
|
||||
root := atomic.LoadPointer(&pp.root)
|
||||
if root == inUsePtr {
|
||||
continue
|
||||
}
|
||||
|
||||
// Set the next elem value (might be nil)
|
||||
e.next = (*elem)(root)
|
||||
|
||||
// Attempt to store this new value at root
|
||||
if atomic.CompareAndSwapPointer(&pp.root, root, unsafe.Pointer(e)) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// hook evicts all entries from pool, calling hook on each
|
||||
func (pp *_ppool) evict(hook func(interface{})) {
|
||||
if v := pp.getPrivate(); v != nil {
|
||||
hook(v)
|
||||
}
|
||||
for {
|
||||
v := pp.get()
|
||||
if v == nil {
|
||||
break
|
||||
}
|
||||
hook(v)
|
||||
}
|
||||
}
|
||||
|
||||
// inUsePtr is a ptr used to indicate _ppool is in use
|
||||
var inUsePtr = unsafe.Pointer(&elem{
|
||||
next: nil,
|
||||
value: "in_use",
|
||||
})
|
||||
|
||||
// elem defines an element in the _ppool queue
|
||||
type elem struct {
|
||||
next *elem
|
||||
value interface{}
|
||||
}
|
||||
|
||||
// elemPool is a simple pool of unused elements
|
||||
var elemPool = struct {
|
||||
root unsafe.Pointer
|
||||
}{}
|
||||
|
||||
// getElem fetches a new elem from pool, or creates new
|
||||
func getElem() *elem {
|
||||
// Attempt to load root elem
|
||||
root := atomic.LoadPointer(&elemPool.root)
|
||||
if root == nil {
|
||||
return &elem{}
|
||||
}
|
||||
|
||||
// Attempt to consume root elem
|
||||
if root == inUsePtr ||
|
||||
!atomic.CompareAndSwapPointer(&elemPool.root, root, inUsePtr) {
|
||||
return &elem{}
|
||||
}
|
||||
|
||||
// Root becomes next in chain
|
||||
e := (*elem)(root)
|
||||
atomic.StorePointer(&elemPool.root, unsafe.Pointer(e.next))
|
||||
e.next = nil
|
||||
|
||||
return e
|
||||
}
|
||||
|
||||
// putElem will place element in the pool
|
||||
func putElem(e *elem) {
|
||||
e.value = nil
|
||||
|
||||
// Attempt to load root elem
|
||||
root := atomic.LoadPointer(&elemPool.root)
|
||||
if root == inUsePtr {
|
||||
return // drop
|
||||
}
|
||||
|
||||
// Set the next elem value (might be nil)
|
||||
e.next = (*elem)(root)
|
||||
|
||||
// Attempt to store this new value at root
|
||||
atomic.CompareAndSwapPointer(&elemPool.root, root, unsafe.Pointer(e))
|
||||
}
|
||||
|
||||
//go:linkname runtime_procPin sync.runtime_procPin
|
||||
func runtime_procPin() int
|
||||
|
||||
//go:linkname runtime_procUnpin sync.runtime_procUnpin
|
||||
func runtime_procUnpin()
|
|
@ -5,6 +5,13 @@ import (
|
|||
"time"
|
||||
)
|
||||
|
||||
// closedctx is an always closed context.
|
||||
var closedctx = func() context.Context {
|
||||
ctx := make(cancelctx)
|
||||
close(ctx)
|
||||
return ctx
|
||||
}()
|
||||
|
||||
// ContextWithCancel returns a new context.Context impl with cancel.
|
||||
func ContextWithCancel() (context.Context, context.CancelFunc) {
|
||||
ctx := make(cancelctx)
|
||||
|
|
|
@ -2,6 +2,7 @@ package runners
|
|||
|
||||
import (
|
||||
"context"
|
||||
"runtime"
|
||||
"sync"
|
||||
)
|
||||
|
||||
|
@ -22,6 +23,12 @@ type WorkerPool struct {
|
|||
// The number of workers represents how many WorkerFuncs can be executed simultaneously, and the
|
||||
// queue size represents the max number of WorkerFuncs that can be queued at any one time.
|
||||
func NewWorkerPool(workers int, queue int) WorkerPool {
|
||||
if workers < 1 {
|
||||
workers = runtime.GOMAXPROCS(0)
|
||||
}
|
||||
if queue < 1 {
|
||||
queue = workers * 2
|
||||
}
|
||||
return WorkerPool{
|
||||
queue: make(chan WorkerFunc, queue),
|
||||
free: make(chan struct{}, workers),
|
||||
|
@ -59,22 +66,28 @@ func (pool *WorkerPool) Running() bool {
|
|||
|
||||
// execute will take a queued function and pass it to a free worker when available.
|
||||
func (pool *WorkerPool) execute(ctx context.Context, fn WorkerFunc) {
|
||||
var acquired bool
|
||||
|
||||
// Set as running
|
||||
pool.wait.Add(1)
|
||||
|
||||
select {
|
||||
// Pool context cancelled
|
||||
// (we fall through and let
|
||||
// the function execute).
|
||||
case <-ctx.Done():
|
||||
pool.wait.Done()
|
||||
|
||||
// Free worker acquired
|
||||
// Free worker acquired.
|
||||
case pool.free <- struct{}{}:
|
||||
acquired = true
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer func() {
|
||||
// defer in case panic
|
||||
if acquired {
|
||||
<-pool.free
|
||||
}
|
||||
pool.wait.Done()
|
||||
}()
|
||||
|
||||
|
@ -110,8 +123,8 @@ func (pool *WorkerPool) process(ctx context.Context) {
|
|||
}
|
||||
|
||||
// Enqueue will add provided WorkerFunc to the queue to be performed when there is a free worker.
|
||||
// Note that 'fn' will ALWAYS be executed, and the supplied context will specify whether this 'fn'
|
||||
// is being executed during normal pool execution, or if the pool has been stopped with <-ctx.Done().
|
||||
// This will block until the function has been queued. 'fn' will ALWAYS be executed, even on pool
|
||||
// close, which can be determined via context <-ctx.Done(). WorkerFuncs MUST respect the passed context.
|
||||
func (pool *WorkerPool) Enqueue(fn WorkerFunc) {
|
||||
// Check valid fn
|
||||
if fn == nil {
|
||||
|
@ -121,13 +134,14 @@ func (pool *WorkerPool) Enqueue(fn WorkerFunc) {
|
|||
select {
|
||||
// Pool context cancelled
|
||||
case <-pool.svc.Done():
|
||||
fn(closedctx)
|
||||
|
||||
// Placed fn in queue
|
||||
case pool.queue <- fn:
|
||||
}
|
||||
}
|
||||
|
||||
// EnqueueNoBlock performs Enqueue but returns false if queue size is at max. Else, true.
|
||||
// EnqueueNoBlock attempts Enqueue but returns false if not executed.
|
||||
func (pool *WorkerPool) EnqueueNoBlock(fn WorkerFunc) bool {
|
||||
// Check valid fn
|
||||
if fn == nil {
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
package kv
|
||||
|
||||
import (
|
||||
"codeberg.org/gruf/go-errors"
|
||||
"errors"
|
||||
|
||||
"codeberg.org/gruf/go-mutexes"
|
||||
"codeberg.org/gruf/go-store/storage"
|
||||
)
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
package kv
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
|
||||
"codeberg.org/gruf/go-errors"
|
||||
"codeberg.org/gruf/go-mutexes"
|
||||
)
|
||||
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
package storage
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
|
@ -9,8 +11,9 @@ import (
|
|||
"sync"
|
||||
"syscall"
|
||||
|
||||
"codeberg.org/gruf/go-bytes"
|
||||
"codeberg.org/gruf/go-errors"
|
||||
"codeberg.org/gruf/go-byteutil"
|
||||
"codeberg.org/gruf/go-errors/v2"
|
||||
"codeberg.org/gruf/go-fastcopy"
|
||||
"codeberg.org/gruf/go-hashenc"
|
||||
"codeberg.org/gruf/go-pools"
|
||||
"codeberg.org/gruf/go-store/util"
|
||||
|
@ -34,6 +37,9 @@ type BlockConfig struct {
|
|||
// BlockSize is the chunking size to use when splitting and storing blocks of data
|
||||
BlockSize int
|
||||
|
||||
// ReadBufSize is the buffer size to use when reading node files
|
||||
ReadBufSize int
|
||||
|
||||
// WriteBufSize is the buffer size to use when writing file streams (PutStream)
|
||||
WriteBufSize int
|
||||
|
||||
|
@ -87,6 +93,7 @@ type BlockStorage struct {
|
|||
config BlockConfig // cfg is the supplied configuration for this store
|
||||
hashPool sync.Pool // hashPool is this store's hashEncoder pool
|
||||
bufpool pools.BufferPool // bufpool is this store's bytes.Buffer pool
|
||||
cppool fastcopy.CopyPool // cppool is the prepared io copier with buffer pool
|
||||
lock *Lock // lock is the opened lockfile for this storage instance
|
||||
|
||||
// NOTE:
|
||||
|
@ -154,8 +161,8 @@ func OpenBlock(path string, cfg *BlockConfig) (*BlockStorage, error) {
|
|||
bufSz = config.WriteBufSize
|
||||
}
|
||||
|
||||
// Return new BlockStorage
|
||||
return &BlockStorage{
|
||||
// Prepare BlockStorage
|
||||
st := &BlockStorage{
|
||||
path: path,
|
||||
blockPath: pb.Join(path, blockPathPrefix),
|
||||
nodePath: pb.Join(path, nodePathPrefix),
|
||||
|
@ -167,7 +174,12 @@ func OpenBlock(path string, cfg *BlockConfig) (*BlockStorage, error) {
|
|||
},
|
||||
bufpool: pools.NewBufferPool(bufSz),
|
||||
lock: lock,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Set copypool buffer size
|
||||
st.cppool.Buffer(config.ReadBufSize)
|
||||
|
||||
return st, nil
|
||||
}
|
||||
|
||||
// Clean implements storage.Clean()
|
||||
|
@ -297,7 +309,7 @@ func (st *BlockStorage) Clean() error {
|
|||
for key := range nodes {
|
||||
nodeKeys = append(nodeKeys, key)
|
||||
}
|
||||
return errCorruptNodes.Extend("%v", nodeKeys)
|
||||
return fmt.Errorf("store/storage: corrupted nodes: %v", nodeKeys)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -337,7 +349,7 @@ func (st *BlockStorage) ReadStream(key string) (io.ReadCloser, error) {
|
|||
file, err := open(npath, defaultFileROFlags)
|
||||
if err != nil {
|
||||
st.lock.Done()
|
||||
return nil, err
|
||||
return nil, errSwapNotFound(err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
|
@ -347,13 +359,12 @@ func (st *BlockStorage) ReadStream(key string) (io.ReadCloser, error) {
|
|||
|
||||
// Write file contents to node
|
||||
node := node{}
|
||||
_, err = io.CopyBuffer(
|
||||
_, err = st.cppool.Copy(
|
||||
&nodeWriter{
|
||||
node: &node,
|
||||
buf: hbuf,
|
||||
},
|
||||
file,
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
st.lock.Done()
|
||||
|
@ -375,14 +386,14 @@ func (st *BlockStorage) readBlock(key string) ([]byte, error) {
|
|||
// Attempt to open RO file
|
||||
file, err := open(bpath, defaultFileROFlags)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, wrap(errCorruptNode, err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// Wrap the file in a compressor
|
||||
cFile, err := st.config.Compression.Reader(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, wrap(errCorruptNode, err)
|
||||
}
|
||||
defer cFile.Close()
|
||||
|
||||
|
@ -470,10 +481,10 @@ loop:
|
|||
sum := hc.EncodeSum(buf.B)
|
||||
|
||||
// Append to the node's hashes
|
||||
node.hashes = append(node.hashes, sum.String())
|
||||
node.hashes = append(node.hashes, sum)
|
||||
|
||||
// If already on disk, skip
|
||||
has, err := st.statBlock(sum.StringPtr())
|
||||
has, err := st.statBlock(sum)
|
||||
if err != nil {
|
||||
st.bufpool.Put(buf)
|
||||
return err
|
||||
|
@ -497,7 +508,7 @@ loop:
|
|||
}()
|
||||
|
||||
// Write block to store at hash
|
||||
err = st.writeBlock(sum.StringPtr(), buf.B[:n])
|
||||
err = st.writeBlock(sum, buf.B[:n])
|
||||
if err != nil {
|
||||
onceErr.Store(err)
|
||||
return
|
||||
|
@ -564,7 +575,7 @@ func (st *BlockStorage) writeBlock(hash string, value []byte) error {
|
|||
// Attempt to open RW file
|
||||
file, err := open(bpath, defaultFileRWFlags)
|
||||
if err != nil {
|
||||
if err == ErrAlreadyExists {
|
||||
if err == syscall.EEXIST {
|
||||
err = nil /* race issue describe in struct NOTE */
|
||||
}
|
||||
return err
|
||||
|
@ -626,8 +637,12 @@ func (st *BlockStorage) Remove(key string) error {
|
|||
return ErrClosed
|
||||
}
|
||||
|
||||
// Attempt to remove file
|
||||
return os.Remove(kpath)
|
||||
// Remove at path (we know this is file)
|
||||
if err := unlink(kpath); err != nil {
|
||||
return errSwapNotFound(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close implements Storage.Close()
|
||||
|
@ -762,7 +777,7 @@ func (r *nodeReader) Read(b []byte) (int, error) {
|
|||
// which is useful when calculated node file is being read from the store
|
||||
type nodeWriter struct {
|
||||
node *node
|
||||
buf *bytes.Buffer
|
||||
buf *byteutil.Buffer
|
||||
}
|
||||
|
||||
func (w *nodeWriter) Write(b []byte) (int, error) {
|
||||
|
@ -874,7 +889,7 @@ func newHashEncoder() *hashEncoder {
|
|||
}
|
||||
|
||||
// EncodeSum encodes the src data and returns resulting bytes, only valid until next call to EncodeSum()
|
||||
func (henc *hashEncoder) EncodeSum(src []byte) bytes.Bytes {
|
||||
func (henc *hashEncoder) EncodeSum(src []byte) string {
|
||||
henc.henc.EncodeSum(henc.ebuf, src)
|
||||
return bytes.ToBytes(henc.ebuf)
|
||||
return string(henc.ebuf)
|
||||
}
|
||||
|
|
|
@ -318,8 +318,12 @@ func (st *DiskStorage) Remove(key string) error {
|
|||
return ErrClosed
|
||||
}
|
||||
|
||||
// Attempt to remove file
|
||||
return os.Remove(kpath)
|
||||
// Remove at path (we know this is file)
|
||||
if err := unlink(kpath); err != nil {
|
||||
return errSwapNotFound(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close implements Storage.Close()
|
||||
|
|
|
@ -1,52 +1,65 @@
|
|||
package storage
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"errors"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// errorString is our own simple error type
|
||||
type errorString string
|
||||
|
||||
// Error implements error
|
||||
func (e errorString) Error() string {
|
||||
return string(e)
|
||||
}
|
||||
|
||||
// Extend appends extra information to an errorString
|
||||
func (e errorString) Extend(s string, a ...interface{}) errorString {
|
||||
return errorString(string(e) + ": " + fmt.Sprintf(s, a...))
|
||||
}
|
||||
|
||||
var (
|
||||
// ErrClosed is returned on operations on a closed storage
|
||||
ErrClosed = errorString("store/storage: closed")
|
||||
ErrClosed = errors.New("store/storage: closed")
|
||||
|
||||
// ErrNotFound is the error returned when a key cannot be found in storage
|
||||
ErrNotFound = errorString("store/storage: key not found")
|
||||
ErrNotFound = errors.New("store/storage: key not found")
|
||||
|
||||
// ErrAlreadyExist is the error returned when a key already exists in storage
|
||||
ErrAlreadyExists = errorString("store/storage: key already exists")
|
||||
ErrAlreadyExists = errors.New("store/storage: key already exists")
|
||||
|
||||
// ErrInvalidkey is the error returned when an invalid key is passed to storage
|
||||
ErrInvalidKey = errorString("store/storage: invalid key")
|
||||
|
||||
// errPathIsFile is returned when a path for a disk config is actually a file
|
||||
errPathIsFile = errorString("store/storage: path is file")
|
||||
|
||||
// errNoHashesWritten is returned when no blocks are written for given input value
|
||||
errNoHashesWritten = errorString("storage/storage: no hashes written")
|
||||
|
||||
// errInvalidNode is returned when read on an invalid node in the store is attempted
|
||||
errInvalidNode = errorString("store/storage: invalid node")
|
||||
|
||||
// errCorruptNodes is returned when nodes with missing blocks are found during a BlockStorage clean
|
||||
errCorruptNodes = errorString("store/storage: corrupted nodes")
|
||||
ErrInvalidKey = errors.New("store/storage: invalid key")
|
||||
|
||||
// ErrAlreadyLocked is returned on fail opening a storage lockfile
|
||||
ErrAlreadyLocked = errorString("store/storage: storage lock already open")
|
||||
ErrAlreadyLocked = errors.New("store/storage: storage lock already open")
|
||||
|
||||
// errPathIsFile is returned when a path for a disk config is actually a file
|
||||
errPathIsFile = errors.New("store/storage: path is file")
|
||||
|
||||
// errNoHashesWritten is returned when no blocks are written for given input value
|
||||
errNoHashesWritten = errors.New("storage/storage: no hashes written")
|
||||
|
||||
// errInvalidNode is returned when read on an invalid node in the store is attempted
|
||||
errInvalidNode = errors.New("store/storage: invalid node")
|
||||
|
||||
// errCorruptNode is returned when a block fails to be opened / read during read of a node.
|
||||
errCorruptNode = errors.New("store/storage: corrupted node")
|
||||
)
|
||||
|
||||
// wrappedError allows wrapping together an inner with outer error.
|
||||
type wrappedError struct {
|
||||
inner error
|
||||
outer error
|
||||
}
|
||||
|
||||
// wrap will return a new wrapped error from given inner and outer errors.
|
||||
func wrap(outer, inner error) *wrappedError {
|
||||
return &wrappedError{
|
||||
inner: inner,
|
||||
outer: outer,
|
||||
}
|
||||
}
|
||||
|
||||
func (e *wrappedError) Is(target error) bool {
|
||||
return e.outer == target || e.inner == target
|
||||
}
|
||||
|
||||
func (e *wrappedError) Error() string {
|
||||
return e.outer.Error() + ": " + e.inner.Error()
|
||||
}
|
||||
|
||||
func (e *wrappedError) Unwrap() error {
|
||||
return e.inner
|
||||
}
|
||||
|
||||
// errSwapNoop performs no error swaps
|
||||
func errSwapNoop(err error) error {
|
||||
return err
|
||||
|
|
|
@ -9,8 +9,8 @@ import (
|
|||
|
||||
const (
|
||||
// default file permission bits
|
||||
defaultDirPerms = 0755
|
||||
defaultFilePerms = 0644
|
||||
defaultDirPerms = 0o755
|
||||
defaultFilePerms = 0o644
|
||||
|
||||
// default file open flags
|
||||
defaultFileROFlags = syscall.O_RDONLY
|
||||
|
@ -22,7 +22,7 @@ const (
|
|||
// These functions are for opening storage files,
|
||||
// not necessarily for e.g. initial setup (OpenFile)
|
||||
|
||||
// open should not be called directly
|
||||
// open should not be called directly.
|
||||
func open(path string, flags int) (*os.File, error) {
|
||||
var fd int
|
||||
err := util.RetryOnEINTR(func() (err error) {
|
||||
|
@ -35,7 +35,7 @@ func open(path string, flags int) (*os.File, error) {
|
|||
return os.NewFile(uintptr(fd), path), nil
|
||||
}
|
||||
|
||||
// stat checks for a file on disk
|
||||
// stat checks for a file on disk.
|
||||
func stat(path string) (bool, error) {
|
||||
var stat syscall.Stat_t
|
||||
err := util.RetryOnEINTR(func() error {
|
||||
|
@ -49,3 +49,17 @@ func stat(path string) (bool, error) {
|
|||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// unlink removes a file (not dir!) on disk.
|
||||
func unlink(path string) error {
|
||||
return util.RetryOnEINTR(func() error {
|
||||
return syscall.Unlink(path)
|
||||
})
|
||||
}
|
||||
|
||||
// rmdir removes a dir (not file!) on disk.
|
||||
func rmdir(path string) error {
|
||||
return util.RetryOnEINTR(func() error {
|
||||
return syscall.Rmdir(path)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,34 +1,37 @@
|
|||
# codeberg.org/gruf/go-bitutil v1.0.0
|
||||
## explicit; go 1.16
|
||||
codeberg.org/gruf/go-bitutil
|
||||
# codeberg.org/gruf/go-bytes v1.0.2
|
||||
## explicit; go 1.14
|
||||
codeberg.org/gruf/go-bytes
|
||||
# codeberg.org/gruf/go-byteutil v1.0.0
|
||||
## explicit; go 1.16
|
||||
codeberg.org/gruf/go-byteutil
|
||||
# codeberg.org/gruf/go-debug v1.1.2
|
||||
## explicit; go 1.16
|
||||
codeberg.org/gruf/go-debug
|
||||
# codeberg.org/gruf/go-errors v1.0.5
|
||||
## explicit; go 1.15
|
||||
codeberg.org/gruf/go-errors
|
||||
# codeberg.org/gruf/go-errors/v2 v2.0.1
|
||||
## explicit; go 1.16
|
||||
codeberg.org/gruf/go-errors/v2
|
||||
# codeberg.org/gruf/go-fastcopy v1.1.1
|
||||
## explicit; go 1.17
|
||||
codeberg.org/gruf/go-fastcopy
|
||||
# codeberg.org/gruf/go-fastpath v1.0.2
|
||||
# codeberg.org/gruf/go-fastpath v1.0.3
|
||||
## explicit; go 1.14
|
||||
codeberg.org/gruf/go-fastpath
|
||||
# codeberg.org/gruf/go-format v1.0.3
|
||||
## explicit; go 1.17
|
||||
codeberg.org/gruf/go-format
|
||||
# codeberg.org/gruf/go-hashenc v1.0.1
|
||||
# codeberg.org/gruf/go-hashenc v1.0.2
|
||||
## explicit; go 1.16
|
||||
codeberg.org/gruf/go-hashenc
|
||||
# codeberg.org/gruf/go-mutexes v1.1.2
|
||||
## explicit; go 1.14
|
||||
codeberg.org/gruf/go-mutexes
|
||||
# codeberg.org/gruf/go-pools v1.0.2
|
||||
# codeberg.org/gruf/go-pools v1.1.0
|
||||
## explicit; go 1.16
|
||||
codeberg.org/gruf/go-pools
|
||||
# codeberg.org/gruf/go-runners v1.2.0
|
||||
# codeberg.org/gruf/go-runners v1.2.1
|
||||
## explicit; go 1.14
|
||||
codeberg.org/gruf/go-runners
|
||||
# codeberg.org/gruf/go-store v1.3.6
|
||||
# codeberg.org/gruf/go-store v1.3.7
|
||||
## explicit; go 1.14
|
||||
codeberg.org/gruf/go-store/kv
|
||||
codeberg.org/gruf/go-store/storage
|
||||
|
|
Loading…
Reference in New Issue