Bump the go-modules-updates group in /backend with 8 updates
Bumps the go-modules-updates group in /backend with 8 updates: | Package | From | To | | --- | --- | --- | | [github.com/PuerkitoBio/goquery](https://github.com/PuerkitoBio/goquery) | `1.10.1` | `1.10.2` | | [github.com/alecthomas/chroma/v2](https://github.com/alecthomas/chroma) | `2.14.0` | `2.15.0` | | [github.com/go-chi/chi/v5](https://github.com/go-chi/chi) | `5.2.0` | `5.2.1` | | [github.com/go-pkgz/jrpc](https://github.com/go-pkgz/jrpc) | `0.3.0` | `0.3.1` | | [go.etcd.io/bbolt](https://github.com/etcd-io/bbolt) | `1.3.11` | `1.4.0` | | [golang.org/x/crypto](https://github.com/golang/crypto) | `0.31.0` | `0.33.0` | | [golang.org/x/image](https://github.com/golang/image) | `0.23.0` | `0.25.0` | | [golang.org/x/net](https://github.com/golang/net) | `0.33.0` | `0.35.0` | Updates `github.com/PuerkitoBio/goquery` from 1.10.1 to 1.10.2 - [Release notes](https://github.com/PuerkitoBio/goquery/releases) - [Commits](https://github.com/PuerkitoBio/goquery/compare/v1.10.1...v1.10.2) Updates `github.com/alecthomas/chroma/v2` from 2.14.0 to 2.15.0 - [Release notes](https://github.com/alecthomas/chroma/releases) - [Changelog](https://github.com/alecthomas/chroma/blob/master/.goreleaser.yml) - [Commits](https://github.com/alecthomas/chroma/compare/v2.14.0...v2.15.0) Updates `github.com/go-chi/chi/v5` from 5.2.0 to 5.2.1 - [Release notes](https://github.com/go-chi/chi/releases) - [Changelog](https://github.com/go-chi/chi/blob/master/CHANGELOG.md) - [Commits](https://github.com/go-chi/chi/compare/v5.2.0...v5.2.1) Updates `github.com/go-pkgz/jrpc` from 0.3.0 to 0.3.1 - [Release notes](https://github.com/go-pkgz/jrpc/releases) - [Commits](https://github.com/go-pkgz/jrpc/compare/v0.3.0...v0.3.1) Updates `go.etcd.io/bbolt` from 1.3.11 to 1.4.0 - [Release notes](https://github.com/etcd-io/bbolt/releases) - [Commits](https://github.com/etcd-io/bbolt/compare/v1.3.11...v1.4.0) Updates `golang.org/x/crypto` from 0.31.0 to 0.33.0 - [Commits](https://github.com/golang/crypto/compare/v0.31.0...v0.33.0) Updates `golang.org/x/image` from 0.23.0 to 0.25.0 - [Commits](https://github.com/golang/image/compare/v0.23.0...v0.25.0) Updates `golang.org/x/net` from 0.33.0 to 0.35.0 - [Commits](https://github.com/golang/net/compare/v0.33.0...v0.35.0) --- updated-dependencies: - dependency-name: github.com/PuerkitoBio/goquery dependency-type: direct:production update-type: version-update:semver-patch dependency-group: go-modules-updates - dependency-name: github.com/alecthomas/chroma/v2 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: go-modules-updates - dependency-name: github.com/go-chi/chi/v5 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: go-modules-updates - dependency-name: github.com/go-pkgz/jrpc dependency-type: direct:production update-type: version-update:semver-patch dependency-group: go-modules-updates - dependency-name: go.etcd.io/bbolt dependency-type: direct:production update-type: version-update:semver-minor dependency-group: go-modules-updates - dependency-name: golang.org/x/crypto dependency-type: direct:production update-type: version-update:semver-minor dependency-group: go-modules-updates - dependency-name: golang.org/x/image dependency-type: direct:production update-type: version-update:semver-minor dependency-group: go-modules-updates - dependency-name: golang.org/x/net dependency-type: direct:production update-type: version-update:semver-minor dependency-group: go-modules-updates ... Signed-off-by: dependabot[bot] <support@github.com>
This commit is contained in:
committed by
Dmitry V
parent
e6afc58b34
commit
89221ff2bc
@@ -5,7 +5,7 @@ go 1.23.0
|
||||
toolchain go1.24.1
|
||||
|
||||
require (
|
||||
github.com/go-pkgz/jrpc v0.3.0
|
||||
github.com/go-pkgz/jrpc v0.3.1
|
||||
github.com/go-pkgz/lgr v0.11.1
|
||||
github.com/jessevdk/go-flags v1.6.1
|
||||
github.com/stretchr/testify v1.10.0
|
||||
@@ -14,31 +14,32 @@ require (
|
||||
|
||||
require (
|
||||
github.com/Depado/bfchroma/v2 v2.0.0 // indirect
|
||||
github.com/PuerkitoBio/goquery v1.10.1 // indirect
|
||||
github.com/PuerkitoBio/goquery v1.10.2 // indirect
|
||||
github.com/ajg/form v1.5.1 // indirect
|
||||
github.com/alecthomas/chroma/v2 v2.14.0 // indirect
|
||||
github.com/alecthomas/chroma/v2 v2.15.0 // indirect
|
||||
github.com/andybalholm/cascadia v1.3.3 // indirect
|
||||
github.com/aymerick/douceur v0.2.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/didip/tollbooth/v7 v7.0.2 // indirect
|
||||
github.com/didip/tollbooth_chi v0.0.0-20220719025231-d662a7f6928f // indirect
|
||||
github.com/didip/tollbooth/v8 v8.0.1 // indirect
|
||||
github.com/dlclark/regexp2 v1.11.4 // indirect
|
||||
github.com/go-chi/chi/v5 v5.2.0 // indirect
|
||||
github.com/go-chi/chi/v5 v5.2.1 // indirect
|
||||
github.com/go-chi/render v1.0.3 // indirect
|
||||
github.com/go-pkgz/expirable-cache/v3 v3.0.0 // indirect
|
||||
github.com/go-pkgz/rest v1.20.2 // indirect
|
||||
github.com/gorilla/css v1.0.1 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/microcosm-cc/bluemonday v1.0.27 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
||||
github.com/rs/xid v1.6.0 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
go.etcd.io/bbolt v1.3.11 // indirect
|
||||
golang.org/x/crypto v0.35.0 // indirect
|
||||
golang.org/x/image v0.23.0 // indirect
|
||||
golang.org/x/net v0.36.0 // indirect
|
||||
golang.org/x/sys v0.30.0 // indirect
|
||||
go.etcd.io/bbolt v1.4.0 // indirect
|
||||
golang.org/x/crypto v0.36.0 // indirect
|
||||
golang.org/x/image v0.25.0 // indirect
|
||||
golang.org/x/net v0.37.0 // indirect
|
||||
golang.org/x/sys v0.31.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
|
||||
@@ -1,43 +1,36 @@
|
||||
github.com/Depado/bfchroma/v2 v2.0.0 h1:IRpN9BPkNwEpR6w1ectIcNWOuhDSLx+8f1pn83fzxx8=
|
||||
github.com/Depado/bfchroma/v2 v2.0.0/go.mod h1:wFwW/Pw8Tnd0irzgO9Zxtxgzp3aPS8qBWlyadxujxmw=
|
||||
github.com/PuerkitoBio/goquery v1.10.1 h1:Y8JGYUkXWTGRB6Ars3+j3kN0xg1YqqlwvdTV8WTFQcU=
|
||||
github.com/PuerkitoBio/goquery v1.10.1/go.mod h1:IYiHrOMps66ag56LEH7QYDDupKXyo5A8qrjIx3ZtujY=
|
||||
github.com/PuerkitoBio/goquery v1.10.2 h1:7fh2BdHcG6VFZsK7toXBT/Bh1z5Wmy8Q9MV9HqT2AM8=
|
||||
github.com/PuerkitoBio/goquery v1.10.2/go.mod h1:0guWGjcLu9AYC7C1GHnpysHy056u9aEkUHwhdnePMCU=
|
||||
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
|
||||
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
|
||||
github.com/alecthomas/assert/v2 v2.7.0 h1:QtqSACNS3tF7oasA8CU6A6sXZSBDqnm7RfpLl9bZqbE=
|
||||
github.com/alecthomas/assert/v2 v2.7.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
|
||||
github.com/alecthomas/chroma/v2 v2.14.0 h1:R3+wzpnUArGcQz7fCETQBzO5n9IMNi13iIs46aU4V9E=
|
||||
github.com/alecthomas/chroma/v2 v2.14.0/go.mod h1:QolEbTfmUHIMVpBqxeDnNBj2uoeI4EbYP4i6n68SG4I=
|
||||
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
|
||||
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
|
||||
github.com/alecthomas/chroma/v2 v2.15.0 h1:LxXTQHFoYrstG2nnV9y2X5O94sOBzf0CIUpSTbpxvMc=
|
||||
github.com/alecthomas/chroma/v2 v2.15.0/go.mod h1:gUhVLrPDXPtp/f+L1jo9xepo9gL4eLwRuGAunSZMkio=
|
||||
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
|
||||
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
||||
github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM=
|
||||
github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA=
|
||||
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
|
||||
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/didip/tollbooth/v7 v7.0.0/go.mod h1:VZhDSGl5bDSPj4wPsih3PFa4Uh9Ghv8hgacaTm5PRT4=
|
||||
github.com/didip/tollbooth/v7 v7.0.2 h1:WYEfusYI6g64cN0qbZgekDrYfuYBZjUZd5+RlWi69p4=
|
||||
github.com/didip/tollbooth/v7 v7.0.2/go.mod h1:RtRYfEmFGX70+ike5kSndSvLtQ3+F2EAmTI4Un/VXNc=
|
||||
github.com/didip/tollbooth_chi v0.0.0-20220719025231-d662a7f6928f h1:jtKwihcLmUC9BAhoJ9adCUqdSSZcOdH2KL7mPTUm2aw=
|
||||
github.com/didip/tollbooth_chi v0.0.0-20220719025231-d662a7f6928f/go.mod h1:q9C80dnsuVRP2dAskjnXRNWdUJqtGgwG9wNrzt0019s=
|
||||
github.com/didip/tollbooth/v8 v8.0.1 h1:VAAapTo1t4Bn6bbpcHjuovwoa9u3JH++wgjbpWv+rB8=
|
||||
github.com/didip/tollbooth/v8 v8.0.1/go.mod h1:oEd9l+ep373d7DmvKLc0a5gasPOev2mTewi6KPQBGJ4=
|
||||
github.com/dlclark/regexp2 v1.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yAo=
|
||||
github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/go-chi/chi/v5 v5.2.0 h1:Aj1EtB0qR2Rdo2dG4O94RIU35w2lvQSj6BRA4+qwFL0=
|
||||
github.com/go-chi/chi/v5 v5.2.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns=
|
||||
github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8=
|
||||
github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||
github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
|
||||
github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
|
||||
github.com/go-pkgz/expirable-cache v0.1.0/go.mod h1:GTrEl0X+q0mPNqN6dtcQXksACnzCBQ5k/k1SwXJsZKs=
|
||||
github.com/go-pkgz/expirable-cache/v3 v3.0.0 h1:u3/gcu3sabLYiTCevoRKv+WzjIn5oo7P8XtiXBeRDLw=
|
||||
github.com/go-pkgz/expirable-cache/v3 v3.0.0/go.mod h1:2OQiDyEGQalYecLWmXprm3maPXeVb5/6/X7yRPYTzec=
|
||||
github.com/go-pkgz/jrpc v0.3.0 h1:Fls38KqPsHzvp0FWfivr6cGnncC+iFBodHBqvUPY+0U=
|
||||
github.com/go-pkgz/jrpc v0.3.0/go.mod h1:MFtKs75JESiSqVicsQkgN2iDFFuCd3gVT1/vKiwRi00=
|
||||
github.com/go-pkgz/jrpc v0.3.1 h1:jRxnO5ppOookkL9PuOWw/IloevmzqbsKaGImTR3iALQ=
|
||||
github.com/go-pkgz/jrpc v0.3.1/go.mod h1:DJg1RfhwvJpVT3YXtk8LxowB7DN5jUoZ3U4MbHnm76M=
|
||||
github.com/go-pkgz/lgr v0.11.1 h1:hXFhZcznehI6imLhEa379oMOKFz7TQUmisAqb3oLOSM=
|
||||
github.com/go-pkgz/lgr v0.11.1/go.mod h1:tgDF4RXQnBfIgJqjgkv0yOeTQ3F1yewWIZkpUhHnAkU=
|
||||
github.com/go-pkgz/rest v1.15.6/go.mod h1:KUWAqbDteYGS/CiXftomQsKjtEOifXsJ36Ka0skYbmk=
|
||||
github.com/go-pkgz/rest v1.20.2 h1:6wYWo85H7xFU09FadVKKc5LKIfIpCStBXJj9F/P4COc=
|
||||
github.com/go-pkgz/rest v1.20.2/go.mod h1:NC2xNN/y1rIs0PY13FowKoH8rk9RhJNJ0tTbkBg8Yks=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
@@ -54,40 +47,35 @@ github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUq
|
||||
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
|
||||
github.com/jessevdk/go-flags v1.6.1 h1:Cvu5U8UGrLay1rZfv/zP7iLpSHGUZ/Ou68T0iX1bBK4=
|
||||
github.com/jessevdk/go-flags v1.6.1/go.mod h1:Mk8T1hIAWpOiJiHa9rJASDK2UGWji0EuPGBnNLMooyc=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
|
||||
github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
|
||||
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0=
|
||||
go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I=
|
||||
go.etcd.io/bbolt v1.4.0 h1:TU77id3TnN/zKr7CO/uk+fBCwF2jGcMuw2B/FMAzYIk=
|
||||
go.etcd.io/bbolt v1.4.0/go.mod h1:AsD+OCi/qPN1giOX1aiLAha3o1U8rAz65bvN4j0sRuk=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
|
||||
golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
|
||||
golang.org/x/image v0.23.0 h1:HseQ7c2OpPKTPVzNjG5fwJsOTCiiwS4QdsYi5XU6H68=
|
||||
golang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY=
|
||||
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
|
||||
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
|
||||
golang.org/x/image v0.25.0 h1:Y6uW6rH1y5y/LK1J8BPWZtr6yZ7hrsy6hFrXjgsc2fQ=
|
||||
golang.org/x/image v0.25.0/go.mod h1:tCAmOEGthTtkalusGp1g3xa2gke8J6c2N565dTyl9Rs=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
@@ -102,8 +90,8 @@ golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||
golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA=
|
||||
golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I=
|
||||
golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
|
||||
golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -111,8 +99,8 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
|
||||
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
|
||||
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
@@ -124,8 +112,8 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
||||
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
@@ -152,8 +140,7 @@ golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
||||
+12
-11
@@ -5,15 +5,15 @@ toolchain go1.23.3
|
||||
|
||||
require (
|
||||
github.com/Depado/bfchroma/v2 v2.0.0
|
||||
github.com/PuerkitoBio/goquery v1.10.1
|
||||
github.com/alecthomas/chroma/v2 v2.14.0
|
||||
github.com/PuerkitoBio/goquery v1.10.2
|
||||
github.com/alecthomas/chroma/v2 v2.15.0
|
||||
github.com/didip/tollbooth/v7 v7.0.2
|
||||
github.com/didip/tollbooth_chi v0.0.0-20220719025231-d662a7f6928f
|
||||
github.com/go-chi/chi/v5 v5.2.0
|
||||
github.com/go-chi/chi/v5 v5.2.1
|
||||
github.com/go-chi/cors v1.2.1
|
||||
github.com/go-chi/render v1.0.3
|
||||
github.com/go-pkgz/auth/v2 v2.0.0-20241221172452-ba01339372ba
|
||||
github.com/go-pkgz/jrpc v0.3.0
|
||||
github.com/go-pkgz/jrpc v0.3.1
|
||||
github.com/go-pkgz/lcw/v2 v2.0.0
|
||||
github.com/go-pkgz/lgr v0.11.1
|
||||
github.com/go-pkgz/notify v1.2.0
|
||||
@@ -31,11 +31,11 @@ require (
|
||||
github.com/russross/blackfriday/v2 v2.1.0
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
||||
github.com/stretchr/testify v1.10.0
|
||||
go.etcd.io/bbolt v1.3.11
|
||||
go.etcd.io/bbolt v1.4.0
|
||||
go.uber.org/goleak v1.3.0
|
||||
golang.org/x/crypto v0.35.0
|
||||
golang.org/x/image v0.23.0
|
||||
golang.org/x/net v0.36.0
|
||||
golang.org/x/crypto v0.36.0
|
||||
golang.org/x/image v0.25.0
|
||||
golang.org/x/net v0.37.0
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -47,6 +47,7 @@ require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dghubble/oauth1 v0.7.3 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/didip/tollbooth/v8 v8.0.1 // indirect
|
||||
github.com/dlclark/regexp2 v1.11.4 // indirect
|
||||
github.com/go-oauth2/oauth2/v4 v4.5.2 // indirect
|
||||
github.com/go-pkgz/email v0.5.0 // indirect
|
||||
@@ -68,8 +69,8 @@ require (
|
||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
|
||||
go.mongodb.org/mongo-driver v1.17.1 // indirect
|
||||
golang.org/x/oauth2 v0.24.0 // indirect
|
||||
golang.org/x/sync v0.11.0 // indirect
|
||||
golang.org/x/sys v0.30.0 // indirect
|
||||
golang.org/x/text v0.22.0 // indirect
|
||||
golang.org/x/sync v0.12.0 // indirect
|
||||
golang.org/x/sys v0.31.0 // indirect
|
||||
golang.org/x/text v0.23.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
+28
-33
@@ -3,14 +3,14 @@ cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4
|
||||
cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg=
|
||||
github.com/Depado/bfchroma/v2 v2.0.0 h1:IRpN9BPkNwEpR6w1ectIcNWOuhDSLx+8f1pn83fzxx8=
|
||||
github.com/Depado/bfchroma/v2 v2.0.0/go.mod h1:wFwW/Pw8Tnd0irzgO9Zxtxgzp3aPS8qBWlyadxujxmw=
|
||||
github.com/PuerkitoBio/goquery v1.10.1 h1:Y8JGYUkXWTGRB6Ars3+j3kN0xg1YqqlwvdTV8WTFQcU=
|
||||
github.com/PuerkitoBio/goquery v1.10.1/go.mod h1:IYiHrOMps66ag56LEH7QYDDupKXyo5A8qrjIx3ZtujY=
|
||||
github.com/PuerkitoBio/goquery v1.10.2 h1:7fh2BdHcG6VFZsK7toXBT/Bh1z5Wmy8Q9MV9HqT2AM8=
|
||||
github.com/PuerkitoBio/goquery v1.10.2/go.mod h1:0guWGjcLu9AYC7C1GHnpysHy056u9aEkUHwhdnePMCU=
|
||||
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
|
||||
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
|
||||
github.com/alecthomas/assert/v2 v2.7.0 h1:QtqSACNS3tF7oasA8CU6A6sXZSBDqnm7RfpLl9bZqbE=
|
||||
github.com/alecthomas/assert/v2 v2.7.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
|
||||
github.com/alecthomas/chroma/v2 v2.14.0 h1:R3+wzpnUArGcQz7fCETQBzO5n9IMNi13iIs46aU4V9E=
|
||||
github.com/alecthomas/chroma/v2 v2.14.0/go.mod h1:QolEbTfmUHIMVpBqxeDnNBj2uoeI4EbYP4i6n68SG4I=
|
||||
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
|
||||
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
|
||||
github.com/alecthomas/chroma/v2 v2.15.0 h1:LxXTQHFoYrstG2nnV9y2X5O94sOBzf0CIUpSTbpxvMc=
|
||||
github.com/alecthomas/chroma/v2 v2.15.0/go.mod h1:gUhVLrPDXPtp/f+L1jo9xepo9gL4eLwRuGAunSZMkio=
|
||||
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
|
||||
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
||||
github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302 h1:uvdUDbHQHO85qeSydJtItA4T55Pw6BtAejd0APRJOCE=
|
||||
@@ -39,6 +39,8 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cu
|
||||
github.com/didip/tollbooth/v7 v7.0.0/go.mod h1:VZhDSGl5bDSPj4wPsih3PFa4Uh9Ghv8hgacaTm5PRT4=
|
||||
github.com/didip/tollbooth/v7 v7.0.2 h1:WYEfusYI6g64cN0qbZgekDrYfuYBZjUZd5+RlWi69p4=
|
||||
github.com/didip/tollbooth/v7 v7.0.2/go.mod h1:RtRYfEmFGX70+ike5kSndSvLtQ3+F2EAmTI4Un/VXNc=
|
||||
github.com/didip/tollbooth/v8 v8.0.1 h1:VAAapTo1t4Bn6bbpcHjuovwoa9u3JH++wgjbpWv+rB8=
|
||||
github.com/didip/tollbooth/v8 v8.0.1/go.mod h1:oEd9l+ep373d7DmvKLc0a5gasPOev2mTewi6KPQBGJ4=
|
||||
github.com/didip/tollbooth_chi v0.0.0-20220719025231-d662a7f6928f h1:jtKwihcLmUC9BAhoJ9adCUqdSSZcOdH2KL7mPTUm2aw=
|
||||
github.com/didip/tollbooth_chi v0.0.0-20220719025231-d662a7f6928f/go.mod h1:q9C80dnsuVRP2dAskjnXRNWdUJqtGgwG9wNrzt0019s=
|
||||
github.com/dlclark/regexp2 v1.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yAo=
|
||||
@@ -50,12 +52,10 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/gavv/httpexpect v2.0.0+incompatible h1:1X9kcRshkSKEjNJJxX9Y9mQ5BRfbxU5kORdjhlA1yX8=
|
||||
github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc=
|
||||
github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/go-chi/chi/v5 v5.2.0 h1:Aj1EtB0qR2Rdo2dG4O94RIU35w2lvQSj6BRA4+qwFL0=
|
||||
github.com/go-chi/chi/v5 v5.2.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8=
|
||||
github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||
github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
|
||||
github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
|
||||
github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns=
|
||||
github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
|
||||
github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
|
||||
github.com/go-oauth2/oauth2/v4 v4.5.2 h1:CuZhD3lhGuI6aNLyUbRHXsgG2RwGRBOuCBfd4WQKqBQ=
|
||||
@@ -67,8 +67,8 @@ github.com/go-pkgz/email v0.5.0/go.mod h1:BdxglsQnymzhfdbnncEE72a6DrucZHy6I+42LK
|
||||
github.com/go-pkgz/expirable-cache v0.1.0/go.mod h1:GTrEl0X+q0mPNqN6dtcQXksACnzCBQ5k/k1SwXJsZKs=
|
||||
github.com/go-pkgz/expirable-cache/v3 v3.0.0 h1:u3/gcu3sabLYiTCevoRKv+WzjIn5oo7P8XtiXBeRDLw=
|
||||
github.com/go-pkgz/expirable-cache/v3 v3.0.0/go.mod h1:2OQiDyEGQalYecLWmXprm3maPXeVb5/6/X7yRPYTzec=
|
||||
github.com/go-pkgz/jrpc v0.3.0 h1:Fls38KqPsHzvp0FWfivr6cGnncC+iFBodHBqvUPY+0U=
|
||||
github.com/go-pkgz/jrpc v0.3.0/go.mod h1:MFtKs75JESiSqVicsQkgN2iDFFuCd3gVT1/vKiwRi00=
|
||||
github.com/go-pkgz/jrpc v0.3.1 h1:jRxnO5ppOookkL9PuOWw/IloevmzqbsKaGImTR3iALQ=
|
||||
github.com/go-pkgz/jrpc v0.3.1/go.mod h1:DJg1RfhwvJpVT3YXtk8LxowB7DN5jUoZ3U4MbHnm76M=
|
||||
github.com/go-pkgz/lcw/v2 v2.0.0 h1:gTwXpiJBhQeA1rXuqkRuLcV79uATFna8CckH8ZBBrH0=
|
||||
github.com/go-pkgz/lcw/v2 v2.0.0/go.mod h1:yxJHOn+IbQBQHxUqkCtMrbGjIfdYcsBAZcVCBaL1Va8=
|
||||
github.com/go-pkgz/lgr v0.11.1 h1:hXFhZcznehI6imLhEa379oMOKFz7TQUmisAqb3oLOSM=
|
||||
@@ -77,7 +77,6 @@ github.com/go-pkgz/notify v1.2.0 h1:jqbsbWkodCDB9ffyI9fCG1bTSgidWJRS5UWD/twjU44=
|
||||
github.com/go-pkgz/notify v1.2.0/go.mod h1:BTHj9ly7xZpaxg91lBdxCxSmkpVQ9R6q5QviX/upeYo=
|
||||
github.com/go-pkgz/repeater v1.2.0 h1:oJFvjyKdTDd5RCzpzxlzYIZFFj6Zfl17rE1aUfu6UjQ=
|
||||
github.com/go-pkgz/repeater v1.2.0/go.mod h1:vypP6xamA53MFmafnGUucqOmALKk36xgKu2hSG73LHM=
|
||||
github.com/go-pkgz/rest v1.15.6/go.mod h1:KUWAqbDteYGS/CiXftomQsKjtEOifXsJ36Ka0skYbmk=
|
||||
github.com/go-pkgz/rest v1.20.2 h1:6wYWo85H7xFU09FadVKKc5LKIfIpCStBXJj9F/P4COc=
|
||||
github.com/go-pkgz/rest v1.20.2/go.mod h1:NC2xNN/y1rIs0PY13FowKoH8rk9RhJNJ0tTbkBg8Yks=
|
||||
github.com/go-pkgz/syncs v1.3.2 h1:gmioASlJNy3gNosPlgvWOM2QP0Hdjzn2u+/sUShgd8E=
|
||||
@@ -161,7 +160,6 @@ github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108
|
||||
github.com/onsi/ginkgo v1.13.0/go.mod h1:+REjRxOmWfHCjfv9TTWB1jD1Frx4XydAD3zm1lskyM0=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa9E=
|
||||
@@ -184,14 +182,11 @@ github.com/slack-go/slack v0.15.0/go.mod h1:hlGi5oXA+Gt+yWTPP0plCdRKmjsDxecdHxYQ
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/tidwall/btree v0.0.0-20191029221954-400434d76274/go.mod h1:huei1BkDWJ3/sLXmO+bsCNELL+Bp2Kks9OLyQFkzvA8=
|
||||
@@ -249,8 +244,8 @@ github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZ
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M=
|
||||
github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
|
||||
go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0=
|
||||
go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I=
|
||||
go.etcd.io/bbolt v1.4.0 h1:TU77id3TnN/zKr7CO/uk+fBCwF2jGcMuw2B/FMAzYIk=
|
||||
go.etcd.io/bbolt v1.4.0/go.mod h1:AsD+OCi/qPN1giOX1aiLAha3o1U8rAz65bvN4j0sRuk=
|
||||
go.mongodb.org/mongo-driver v1.17.1 h1:Wic5cJIwJgSpBhe3lx3+/RybR5PiYRMpVFgO7cOHyIM=
|
||||
go.mongodb.org/mongo-driver v1.17.1/go.mod h1:wwWm/+BuOddhcq3n68LKRmgk2wXzmF6s0SFOa0GINL4=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
@@ -262,10 +257,10 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
|
||||
golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
|
||||
golang.org/x/image v0.23.0 h1:HseQ7c2OpPKTPVzNjG5fwJsOTCiiwS4QdsYi5XU6H68=
|
||||
golang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY=
|
||||
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
|
||||
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
|
||||
golang.org/x/image v0.25.0 h1:Y6uW6rH1y5y/LK1J8BPWZtr6yZ7hrsy6hFrXjgsc2fQ=
|
||||
golang.org/x/image v0.25.0/go.mod h1:tCAmOEGthTtkalusGp1g3xa2gke8J6c2N565dTyl9Rs=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
@@ -288,8 +283,8 @@ golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||
golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA=
|
||||
golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I=
|
||||
golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
|
||||
golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE=
|
||||
golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
@@ -302,8 +297,8 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
|
||||
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
|
||||
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -326,8 +321,8 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
||||
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
@@ -349,8 +344,8 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
||||
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
||||
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
||||
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
@@ -369,9 +364,9 @@ google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miE
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
|
||||
+4
-3
@@ -24,9 +24,9 @@ Syntax-wise, it is as close as possible to jQuery, with the same function names
|
||||
|
||||
Required Go version:
|
||||
|
||||
* Starting with version `v1.10.0` of goquery, Go 1.23+ is required due to the use of function-based iterators.
|
||||
* For `v1.9.0` of goquery, Go 1.18+ is required due to the use of generics.
|
||||
* For previous goquery versions, a Go version of 1.1+ was required because of the `net/html` dependency.
|
||||
* Starting with version `v1.10.0` of goquery, Go 1.23+ is required due to the use of function-based iterators.
|
||||
* For `v1.9.0` of goquery, Go 1.18+ is required due to the use of generics.
|
||||
* For previous goquery versions, a Go version of 1.1+ was required because of the `net/html` dependency.
|
||||
|
||||
Ongoing goquery development is tested on the latest 2 versions of Go.
|
||||
|
||||
@@ -46,6 +46,7 @@ Ongoing goquery development is tested on the latest 2 versions of Go.
|
||||
|
||||
**Note that goquery's API is now stable, and will not break.**
|
||||
|
||||
* **2025-02-13 (v1.10.2)** : Update `go.mod` dependencies, add go1.24 to the test matrix.
|
||||
* **2024-12-26 (v1.10.1)** : Update `go.mod` dependencies.
|
||||
* **2024-09-06 (v1.10.0)** : Add `EachIter` which provides an iterator that can be used in `for..range` loops on the `*Selection` object. **goquery now requires Go version 1.23+** (thanks [@amikai](https://github.com/amikai)).
|
||||
* **2024-09-06 (v1.9.3)** : Update `go.mod` dependencies.
|
||||
|
||||
+3
@@ -51,6 +51,9 @@ linters:
|
||||
- musttag
|
||||
- depguard
|
||||
- goconst
|
||||
- perfsprint
|
||||
- mnd
|
||||
- predeclared
|
||||
|
||||
linters-settings:
|
||||
govet:
|
||||
|
||||
+7
-7
@@ -39,20 +39,20 @@ translators for Pygments lexers and styles.
|
||||
| D | D, Dart, Dax, Desktop Entry, Diff, Django/Jinja, dns, Docker, DTD, Dylan |
|
||||
| E | EBNF, Elixir, Elm, EmacsLisp, Erlang |
|
||||
| F | Factor, Fennel, Fish, Forth, Fortran, FortranFixed, FSharp |
|
||||
| G | GAS, GDScript, Genshi, Genshi HTML, Genshi Text, Gherkin, GLSL, Gnuplot, Go, Go HTML Template, Go Text Template, GraphQL, Groff, Groovy |
|
||||
| G | GAS, GDScript, Genshi, Genshi HTML, Genshi Text, Gherkin, Gleam, GLSL, Gnuplot, Go, Go HTML Template, Go Text Template, GraphQL, Groff, Groovy |
|
||||
| H | Handlebars, Hare, Haskell, Haxe, HCL, Hexdump, HLB, HLSL, HolyC, HTML, HTTP, Hy |
|
||||
| I | Idris, Igor, INI, Io, ISCdhcpd |
|
||||
| J | J, Java, JavaScript, JSON, Julia, Jungle |
|
||||
| J | J, Java, JavaScript, JSON, Jsonnet, Julia, Jungle |
|
||||
| K | Kotlin |
|
||||
| L | Lighttpd configuration file, LLVM, Lua |
|
||||
| M | Makefile, Mako, markdown, Mason, Materialize SQL dialect, Mathematica, Matlab, mcfunction, Meson, Metal, MiniZinc, MLIR, Modula-2, MonkeyC, MorrowindScript, Myghty, MySQL |
|
||||
| N | NASM, Natural, Newspeak, Nginx configuration file, Nim, Nix |
|
||||
| M | Makefile, Mako, markdown, Mason, Materialize SQL dialect, Mathematica, Matlab, MCFunction, Meson, Metal, MiniZinc, MLIR, Modula-2, MonkeyC, MorrowindScript, Myghty, MySQL |
|
||||
| N | NASM, Natural, Newspeak, Nginx configuration file, Nim, Nix, NSIS |
|
||||
| O | Objective-C, OCaml, Octave, Odin, OnesEnterprise, OpenEdge ABL, OpenSCAD, Org Mode |
|
||||
| P | PacmanConf, Perl, PHP, PHTML, Pig, PkgConfig, PL/pgSQL, plaintext, Plutus Core, Pony, PostgreSQL SQL dialect, PostScript, POVRay, PowerQuery, PowerShell, Prolog, PromQL, Promela, properties, Protocol Buffer, PRQL, PSL, Puppet, Python, Python 2 |
|
||||
| Q | QBasic, QML |
|
||||
| R | R, Racket, Ragel, Raku, react, ReasonML, reg, Rego, reStructuredText, Rexx, RPMSpec, Ruby, Rust |
|
||||
| S | SAS, Sass, Scala, Scheme, Scilab, SCSS, Sed, Sieve, Smali, Smalltalk, Smarty, Snobol, Solidity, SourcePawn, SPARQL, SQL, SquidConf, Standard ML, stas, Stylus, Svelte, Swift, SYSTEMD, systemverilog |
|
||||
| T | TableGen, Tal, TASM, Tcl, Tcsh, Termcap, Terminfo, Terraform, TeX, Thrift, TOML, TradingView, Transact-SQL, Turing, Turtle, Twig, TypeScript, TypoScript, TypoScriptCssData, TypoScriptHtmlData |
|
||||
| S | SAS, Sass, Scala, Scheme, Scilab, SCSS, Sed, Sieve, Smali, Smalltalk, Smarty, SNBT, Snobol, Solidity, SourcePawn, SPARQL, SQL, SquidConf, Standard ML, stas, Stylus, Svelte, Swift, SYSTEMD, systemverilog |
|
||||
| T | TableGen, Tal, TASM, Tcl, Tcsh, Termcap, Terminfo, Terraform, TeX, Thrift, TOML, TradingView, Transact-SQL, Turing, Turtle, Twig, TypeScript, TypoScript, TypoScriptCssData, TypoScriptHtmlData, Typst |
|
||||
| V | V, V shell, Vala, VB.net, verilog, VHDL, VHS, VimL, vue |
|
||||
| W | WDTE, WebGPU Shading Language, Whiley |
|
||||
| X | XML, Xorg |
|
||||
@@ -272,7 +272,7 @@ for that setup the `chroma` executable can be just symlinked to `~/.lessfilter`.
|
||||
If you edit some lexers and want to try it, open a shell in `cmd/chromad` and run:
|
||||
|
||||
```shell
|
||||
go run .
|
||||
go run . --csrf-key=securekey
|
||||
```
|
||||
|
||||
A Link will be printed. Open it in your Browser. Now you can test on the Playground with your local changes.
|
||||
|
||||
+4
-4
@@ -141,7 +141,7 @@ func ParseColour(colour string) Colour {
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return Colour(n + 1)
|
||||
return Colour(n + 1) //nolint:gosec
|
||||
}
|
||||
|
||||
// MustParseColour is like ParseColour except it panics if the colour is invalid.
|
||||
@@ -162,13 +162,13 @@ func (c Colour) String() string { return fmt.Sprintf("#%06x", int(c-1)) }
|
||||
func (c Colour) GoString() string { return fmt.Sprintf("Colour(0x%06x)", int(c-1)) }
|
||||
|
||||
// Red component of colour.
|
||||
func (c Colour) Red() uint8 { return uint8(((c - 1) >> 16) & 0xff) }
|
||||
func (c Colour) Red() uint8 { return uint8(((c - 1) >> 16) & 0xff) } //nolint:gosec
|
||||
|
||||
// Green component of colour.
|
||||
func (c Colour) Green() uint8 { return uint8(((c - 1) >> 8) & 0xff) }
|
||||
func (c Colour) Green() uint8 { return uint8(((c - 1) >> 8) & 0xff) } //nolint:gosec
|
||||
|
||||
// Blue component of colour.
|
||||
func (c Colour) Blue() uint8 { return uint8((c - 1) & 0xff) }
|
||||
func (c Colour) Blue() uint8 { return uint8((c - 1) & 0xff) } //nolint:gosec
|
||||
|
||||
// Colours is an orderable set of colours.
|
||||
type Colours []Colour
|
||||
|
||||
+1
-1
@@ -152,7 +152,7 @@
|
||||
<rule pattern="(?-i)(?:CURRENT|NEW|OLD)\b">
|
||||
<token type="NameBuiltinPseudo"/>
|
||||
</rule>
|
||||
<rule pattern="(?:to_bool|to_number|to_string|to_array|to_list|is_null|is_bool|is_number|is_string|is_array|is_list|is_object|is_document|is_datestring|typename|json_stringify|json_parse|concat|concat_separator|char_length|lower|upper|substring|substring_bytes|left|right|trim|reverse|contains|log|log2|log10|exp|exp2|sin|cos|tan|asin|acos|atan|atan2|radians|degrees|pi|regex_test|regex_replace|like|floor|ceil|round|abs|rand|sqrt|pow|length|count|min|max|average|avg|sum|product|median|variance_population|variance_sample|variance|percentile|bit_and|bit_or|bit_xor|bit_negate|bit_test|bit_popcount|bit_shift_left|bit_shift_right|bit_construct|bit_deconstruct|bit_to_string|bit_from_string|first|last|unique|outersection|interleave|in_range|jaccard|matches|merge|merge_recursive|has|attributes|keys|values|unset|unset_recursive|keep|keep_recursive|near|within|within_rectangle|is_in_polygon|distance|fulltext|stddev_sample|stddev_population|stddev|slice|nth|position|contains_array|translate|zip|call|apply|push|append|pop|shift|unshift|remove_value|remove_values|remove_nth|replace_nth|date_now|date_timestamp|date_iso8601|date_dayofweek|date_year|date_month|date_day|date_hour|date_minute|date_second|date_millisecond|date_dayofyear|date_isoweek|date_isoweekyear|date_leapyear|date_quarter|date_days_in_month|date_trunc|date_round|date_add|date_subtract|date_diff|date_compare|date_format|date_utctolocal|date_localtoutc|date_timezone|date_timezones|fail|passthru|v8|sleep|schema_get|schema_validate|shard_id|version|noopt|noeval|not_null|first_list|first_document|parse_identifier|current_user|current_database|collection_count|pregel_result|collections|document|decode_rev|range|union|union_distinct|minus|intersection|flatten|is_same_collection|check_document|ltrim|rtrim|find_first|find_last|split|substitute|ipv4_to_number|ipv4_from_number|is_ipv4|md5|sha1|sha256|sha512|crc32|fnv64|hash|random_token|to_base64|to_hex|encode_uri_component|soundex|assert|warn|is_key|sorted|sorted_unique|count_distinct|count_unique|levenshtein_distance|levenshtein_match|regex_matches|regex_split|ngram_match|ngram_similarity|ngram_positional_similarity|uuid|tokens|exists|starts_with|phrase|min_match|bm25|tfidf|boost|analyzer|offset_info|value|cosine_similarity|decay_exp|decay_gauss|decay_linear|l1_distance|l2_distance|minhash|minhash_count|minhash_error|minhash_match|geo_point|geo_multipoint|geo_polygon|geo_multipolygon|geo_linestring|geo_multilinestring|geo_contains|geo_intersects|geo_equals|geo_distance|geo_area|geo_in_range)(?=\s*\()">
|
||||
<rule pattern="(?:to_bool|to_number|to_char|to_string|to_array|to_list|is_null|is_bool|is_number|is_string|is_array|is_list|is_object|is_document|is_datestring|typename|json_stringify|json_parse|concat|concat_separator|char_length|lower|upper|substring|substring_bytes|left|right|trim|reverse|repeat|contains|log|log2|log10|exp|exp2|sin|cos|tan|asin|acos|atan|atan2|radians|degrees|pi|regex_test|regex_replace|like|floor|ceil|round|abs|rand|random|sqrt|pow|length|count|min|max|average|avg|sum|product|median|variance_population|variance_sample|variance|percentile|bit_and|bit_or|bit_xor|bit_negate|bit_test|bit_popcount|bit_shift_left|bit_shift_right|bit_construct|bit_deconstruct|bit_to_string|bit_from_string|first|last|unique|outersection|interleave|in_range|jaccard|matches|merge|merge_recursive|has|attributes|keys|values|entries|unset|unset_recursive|keep|keep_recursive|near|within|within_rectangle|is_in_polygon|distance|fulltext|stddev_sample|stddev_population|stddev|slice|nth|position|contains_array|translate|zip|call|apply|push|append|pop|shift|unshift|remove_value|remove_values|remove_nth|replace_nth|date_now|date_timestamp|date_iso8601|date_dayofweek|date_year|date_month|date_day|date_hour|date_minute|date_second|date_millisecond|date_dayofyear|date_isoweek|date_isoweekyear|date_leapyear|date_quarter|date_days_in_month|date_trunc|date_round|date_add|date_subtract|date_diff|date_compare|date_format|date_utctolocal|date_localtoutc|date_timezone|date_timezones|fail|passthru|v8|sleep|schema_get|schema_validate|shard_id|version|noopt|noeval|not_null|first_list|first_document|parse_identifier|parse_collection|parse_key|current_user|current_database|collection_count|pregel_result|collections|document|decode_rev|range|union|union_distinct|minus|intersection|flatten|is_same_collection|check_document|ltrim|rtrim|find_first|find_last|split|substitute|ipv4_to_number|ipv4_from_number|is_ipv4|md5|sha1|sha256|sha512|crc32|fnv64|hash|random_token|to_base64|to_hex|encode_uri_component|soundex|assert|warn|is_key|sorted|sorted_unique|count_distinct|count_unique|levenshtein_distance|levenshtein_match|regex_matches|regex_split|ngram_match|ngram_similarity|ngram_positional_similarity|uuid|tokens|exists|starts_with|phrase|min_match|bm25|tfidf|boost|analyzer|offset_info|value|cosine_similarity|decay_exp|decay_gauss|decay_linear|l1_distance|l2_distance|minhash|minhash_count|minhash_error|minhash_match|geo_point|geo_multipoint|geo_polygon|geo_multipolygon|geo_linestring|geo_multilinestring|geo_contains|geo_intersects|geo_equals|geo_distance|geo_area|geo_in_range)(?=\s*\()">
|
||||
<token type="NameFunction"/>
|
||||
</rule>
|
||||
<rule pattern=""">
|
||||
|
||||
+165
@@ -0,0 +1,165 @@
|
||||
<lexer>
|
||||
<config>
|
||||
<name>ATL</name>
|
||||
<alias>atl</alias>
|
||||
<filename>*.atl</filename>
|
||||
<mime_type>text/x-atl</mime_type>
|
||||
<dot_all>true</dot_all>
|
||||
</config>
|
||||
<rules>
|
||||
<state name="root">
|
||||
<rule pattern="(--.*?)(\n)">
|
||||
<bygroups>
|
||||
<token type="CommentSingle" />
|
||||
<token type="TextWhitespace" />
|
||||
</bygroups>
|
||||
</rule>
|
||||
<rule pattern="(and|distinct|endif|else|for|foreach|if|implies|in|let|not|or|self|super|then|thisModule|xor)\b">
|
||||
<token type="Keyword" />
|
||||
</rule>
|
||||
<rule pattern="(OclUndefined|true|false|#\w+)\b">
|
||||
<token type="KeywordConstant" />
|
||||
</rule>
|
||||
<rule pattern="(module|query|library|create|from|to|uses)\b">
|
||||
<token type="KeywordNamespace" />
|
||||
</rule>
|
||||
<rule pattern="(do)(\s*)({)">
|
||||
<bygroups>
|
||||
<token type="KeywordNamespace" />
|
||||
<token type="TextWhitespace" />
|
||||
<token type="Punctuation" />
|
||||
</bygroups>
|
||||
</rule>
|
||||
<rule pattern="(abstract|endpoint|entrypoint|lazy|unique)(\s+)">
|
||||
<bygroups>
|
||||
<token type="KeywordDeclaration" />
|
||||
<token type="TextWhitespace" />
|
||||
</bygroups>
|
||||
</rule>
|
||||
<rule pattern="(rule)(\s+)">
|
||||
<bygroups>
|
||||
<token type="KeywordNamespace" />
|
||||
<token type="TextWhitespace" />
|
||||
</bygroups>
|
||||
</rule>
|
||||
<rule pattern="(helper)(\s+)">
|
||||
<bygroups>
|
||||
<token type="KeywordNamespace" />
|
||||
<token type="TextWhitespace" />
|
||||
</bygroups>
|
||||
</rule>
|
||||
<rule pattern="(context)(\s+)">
|
||||
<bygroups>
|
||||
<token type="KeywordNamespace" />
|
||||
<token type="TextWhitespace" />
|
||||
</bygroups>
|
||||
</rule>
|
||||
<rule pattern="(def)(\s*)(:)(\s*)">
|
||||
<bygroups>
|
||||
<token type="KeywordNamespace" />
|
||||
<token type="TextWhitespace" />
|
||||
<token type="Punctuation" />
|
||||
<token type="TextWhitespace" />
|
||||
</bygroups>
|
||||
</rule>
|
||||
<rule pattern="(Bag|Boolean|Integer|OrderedSet|Real|Sequence|Set|String|Tuple)">
|
||||
<token type="KeywordType" />
|
||||
</rule>
|
||||
<rule pattern="(\w+)(\s*)(<-|<:=)">
|
||||
<bygroups>
|
||||
<token type="NameNamespace" />
|
||||
<token type="TextWhitespace" />
|
||||
<token type="Punctuation" />
|
||||
</bygroups>
|
||||
</rule>
|
||||
<rule pattern="#"">
|
||||
<token type="KeywordConstant" />
|
||||
<push state="quotedenumliteral" />
|
||||
</rule>
|
||||
<rule pattern=""">
|
||||
<token type="NameNamespace" />
|
||||
<push state="quotedname" />
|
||||
</rule>
|
||||
<rule pattern="[^\S\n]+">
|
||||
<token type="TextWhitespace" />
|
||||
</rule>
|
||||
<rule pattern="'">
|
||||
<token type="LiteralString" />
|
||||
<push state="string" />
|
||||
</rule>
|
||||
<rule
|
||||
pattern="[0-9]*\.[0-9]+">
|
||||
<token type="LiteralNumberFloat" />
|
||||
</rule>
|
||||
<rule pattern="0|[1-9][0-9]*">
|
||||
<token type="LiteralNumberInteger" />
|
||||
</rule>
|
||||
<rule pattern="[*<>+=/-]">
|
||||
<token type="Operator" />
|
||||
</rule>
|
||||
<rule pattern="([{}();:.,!|]|->)">
|
||||
<token type="Punctuation" />
|
||||
</rule>
|
||||
<rule pattern="\n">
|
||||
<token type="TextWhitespace" />
|
||||
</rule>
|
||||
<rule pattern="\w+">
|
||||
<token type="NameNamespace" />
|
||||
</rule>
|
||||
</state>
|
||||
<state name="string">
|
||||
<rule pattern="[^\\']+">
|
||||
<token type="LiteralString" />
|
||||
</rule>
|
||||
<rule pattern="\\\\">
|
||||
<token type="LiteralString" />
|
||||
</rule>
|
||||
<rule pattern="\\'">
|
||||
<token type="LiteralString" />
|
||||
</rule>
|
||||
<rule pattern="\\">
|
||||
<token type="LiteralString" />
|
||||
</rule>
|
||||
<rule pattern="'">
|
||||
<token type="LiteralString" />
|
||||
<pop depth="1" />
|
||||
</rule>
|
||||
</state>
|
||||
<state name="quotedname">
|
||||
<rule pattern="[^\\"]+">
|
||||
<token type="NameNamespace" />
|
||||
</rule>
|
||||
<rule pattern="\\\\">
|
||||
<token type="NameNamespace" />
|
||||
</rule>
|
||||
<rule pattern="\\"">
|
||||
<token type="NameNamespace" />
|
||||
</rule>
|
||||
<rule pattern="\\">
|
||||
<token type="NameNamespace" />
|
||||
</rule>
|
||||
<rule pattern=""">
|
||||
<token type="NameNamespace" />
|
||||
<pop depth="1" />
|
||||
</rule>
|
||||
</state>
|
||||
<state name="quotedenumliteral">
|
||||
<rule pattern="[^\\"]+">
|
||||
<token type="KeywordConstant" />
|
||||
</rule>
|
||||
<rule pattern="\\\\">
|
||||
<token type="KeywordConstant" />
|
||||
</rule>
|
||||
<rule pattern="\\"">
|
||||
<token type="KeywordConstant" />
|
||||
</rule>
|
||||
<rule pattern="\\">
|
||||
<token type="KeywordConstant" />
|
||||
</rule>
|
||||
<rule pattern=""">
|
||||
<token type="KeywordConstant" />
|
||||
<pop depth="1" />
|
||||
</rule>
|
||||
</state>
|
||||
</rules>
|
||||
</lexer>
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
<lexer>
|
||||
<config>
|
||||
<name>Beef</name>
|
||||
<alias>beef</alias>
|
||||
<filename>*.bf</filename>
|
||||
<mime_type>text/x-beef</mime_type>
|
||||
<dot_all>true</dot_all>
|
||||
<ensure_nl>true</ensure_nl>
|
||||
</config>
|
||||
<rules>
|
||||
<state name="root">
|
||||
<rule pattern="^\s*\[.*?\]">
|
||||
<token type="NameAttribute"/>
|
||||
</rule>
|
||||
<rule pattern="[^\S\n]+">
|
||||
<token type="Text"/>
|
||||
</rule>
|
||||
<rule pattern="\\\n">
|
||||
<token type="Text"/>
|
||||
</rule>
|
||||
<rule pattern="///[^\n\r]*">
|
||||
<token type="CommentSpecial"/>
|
||||
</rule>
|
||||
<rule pattern="//[^\n\r]*">
|
||||
<token type="CommentSingle"/>
|
||||
</rule>
|
||||
<rule pattern="/[*].*?[*]/">
|
||||
<token type="CommentMultiline"/>
|
||||
</rule>
|
||||
<rule pattern="\n">
|
||||
<token type="Text"/>
|
||||
</rule>
|
||||
<rule pattern="[~!%^&*()+=|\[\]:;,.<>/?-]">
|
||||
<token type="Punctuation"/>
|
||||
</rule>
|
||||
<rule pattern="[{}]">
|
||||
<token type="Punctuation"/>
|
||||
</rule>
|
||||
<rule pattern="@"(""|[^"])*"">
|
||||
<token type="LiteralString"/>
|
||||
</rule>
|
||||
<rule pattern="\$@?"(""|[^"])*"">
|
||||
<token type="LiteralString"/>
|
||||
</rule>
|
||||
<rule pattern=""(\\\\|\\"|[^"\n])*["\n]">
|
||||
<token type="LiteralString"/>
|
||||
</rule>
|
||||
<rule pattern="'\\.'|'[^\\]'">
|
||||
<token type="LiteralStringChar"/>
|
||||
</rule>
|
||||
<rule pattern="0[xX][0-9a-fA-F]+[Ll]?|\d[_\d]*(\.\d*)?([eE][+-]?\d+)?[flFLdD]?">
|
||||
<token type="LiteralNumber"/>
|
||||
</rule>
|
||||
<rule pattern="#[ \t]*(if|endif|else|elif|define|undef|line|error|warning|region|endregion|pragma|nullable)\b">
|
||||
<token type="CommentPreproc"/>
|
||||
</rule>
|
||||
<rule pattern="\b(extern)(\s+)(alias)\b">
|
||||
<bygroups>
|
||||
<token type="Keyword"/>
|
||||
<token type="Text"/>
|
||||
<token type="Keyword"/>
|
||||
</bygroups>
|
||||
</rule>
|
||||
<rule pattern="(as|await|base|break|by|case|catch|checked|continue|default|delegate|else|event|finally|fixed|for|repeat|goto|if|in|init|is|let|lock|new|scope|on|out|params|readonly|ref|return|sizeof|stackalloc|switch|this|throw|try|typeof|unchecked|virtual|void|while|get|set|new|yield|add|remove|value|alias|ascending|descending|from|group|into|orderby|select|thenby|where|join|equals)\b">
|
||||
<token type="Keyword"/>
|
||||
</rule>
|
||||
<rule pattern="(global)(::)">
|
||||
<bygroups>
|
||||
<token type="Keyword"/>
|
||||
<token type="Punctuation"/>
|
||||
</bygroups>
|
||||
</rule>
|
||||
<rule pattern="(abstract|async|const|enum|explicit|extern|implicit|internal|operator|override|partial|extension|private|protected|public|static|sealed|unsafe|volatile)\b">
|
||||
<token type="KeywordDeclaration"/>
|
||||
</rule>
|
||||
<rule pattern="(bool|byte|char8|char16|char32|decimal|double|float|int|int8|int16|int32|int64|long|object|sbyte|short|string|uint|uint8|uint16|uint32|uint64|uint|let|var)\b\??">
|
||||
<token type="KeywordType"/>
|
||||
</rule>
|
||||
<rule pattern="(true|false|null)\b">
|
||||
<token type="KeywordConstant"/>
|
||||
</rule>
|
||||
<rule pattern="(class|struct|record|interface)(\s+)">
|
||||
<bygroups>
|
||||
<token type="Keyword"/>
|
||||
<token type="Text"/>
|
||||
</bygroups>
|
||||
<push state="class"/>
|
||||
</rule>
|
||||
<rule pattern="(namespace|using)(\s+)">
|
||||
<bygroups>
|
||||
<token type="Keyword"/>
|
||||
<token type="Text"/>
|
||||
</bygroups>
|
||||
<push state="namespace"/>
|
||||
</rule>
|
||||
<rule pattern="@?[_a-zA-Z]\w*">
|
||||
<token type="Name"/>
|
||||
</rule>
|
||||
</state>
|
||||
<state name="class">
|
||||
<rule pattern="@?[_a-zA-Z]\w*">
|
||||
<token type="NameClass"/>
|
||||
<pop depth="1"/>
|
||||
</rule>
|
||||
<rule>
|
||||
<pop depth="1"/>
|
||||
</rule>
|
||||
</state>
|
||||
<state name="namespace">
|
||||
<rule pattern="(?=\()">
|
||||
<token type="Text"/>
|
||||
<pop depth="1"/>
|
||||
</rule>
|
||||
<rule pattern="(@?[_a-zA-Z]\w*|\.)+">
|
||||
<token type="NameNamespace"/>
|
||||
<pop depth="1"/>
|
||||
</rule>
|
||||
</state>
|
||||
</rules>
|
||||
</lexer>
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
<!--
|
||||
Lexer for RFC-4180 compliant CSV subject to the following additions:
|
||||
- UTF-8 encoding is accepted (the RFC requires 7-bit ASCII)
|
||||
- The line terminator character can be LF or CRLF (the RFC allows CRLF only)
|
||||
|
||||
Link to the RFC-4180 specification: https://tools.ietf.org/html/rfc4180
|
||||
|
||||
Additions inspired by:
|
||||
https://github.com/frictionlessdata/datapackage/issues/204#issuecomment-193242077
|
||||
|
||||
Future improvements:
|
||||
- Identify non-quoted numbers as LiteralNumber
|
||||
- Identify y as an error in "x"y. Currently it's identified as another string
|
||||
literal.
|
||||
-->
|
||||
|
||||
<lexer>
|
||||
<config>
|
||||
<name>CSV</name>
|
||||
<alias>csv</alias>
|
||||
<filename>*.csv</filename>
|
||||
<mime_type>text/csv</mime_type>
|
||||
</config>
|
||||
<rules>
|
||||
<state name="root">
|
||||
<rule pattern="\r?\n">
|
||||
<token type="Punctuation" />
|
||||
</rule>
|
||||
<rule pattern=",">
|
||||
<token type="Punctuation" />
|
||||
</rule>
|
||||
<rule pattern=""">
|
||||
<token type="LiteralStringDouble" />
|
||||
<push state="escaped" />
|
||||
</rule>
|
||||
<rule pattern="[^\r\n,]+">
|
||||
<token type="LiteralString" />
|
||||
</rule>
|
||||
</state>
|
||||
<state name="escaped">
|
||||
<rule pattern="""">
|
||||
<token type="LiteralStringEscape"/>
|
||||
</rule>
|
||||
<rule pattern=""">
|
||||
<token type="LiteralStringDouble" />
|
||||
<pop depth="1"/>
|
||||
</rule>
|
||||
<rule pattern="[^"]+">
|
||||
<token type="LiteralStringDouble" />
|
||||
</rule>
|
||||
</state>
|
||||
</rules>
|
||||
</lexer>
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
<lexer>
|
||||
<config>
|
||||
<name>Gleam</name>
|
||||
<alias>gleam></alias>
|
||||
<alias>gleam</alias>
|
||||
<filename>*.gleam</filename>
|
||||
<mime_type>text/x-gleam</mime_type>
|
||||
</config>
|
||||
@@ -114,4 +114,4 @@
|
||||
</rule>
|
||||
</state>
|
||||
</rules>
|
||||
</lexer>
|
||||
</lexer>
|
||||
|
||||
+1
-1
@@ -84,7 +84,7 @@
|
||||
<token type="Punctuation" />
|
||||
</bygroups>
|
||||
</rule>
|
||||
<rule pattern="(str|size|rune|bool|int|uint|uintptr|u8|u16|u32|u64|i8|i16|i32|i64|f32|f64|null|void|nullable|valist|opaque|never)\b">
|
||||
<rule pattern="(str|size|rune|bool|int|uint|uintptr|u8|u16|u32|u64|i8|i16|i32|i64|f32|f64|null|void|done|nullable|valist|opaque|never)\b">
|
||||
<token type="KeywordType"/>
|
||||
</rule>
|
||||
<rule pattern="(true|false)\b">
|
||||
|
||||
+8
-5
@@ -95,19 +95,22 @@
|
||||
<rule pattern="[:!#$%&*+.\\/<=>?@^|~-]+">
|
||||
<token type="Operator"/>
|
||||
</rule>
|
||||
<rule pattern="\d+[eE][+-]?\d+">
|
||||
<rule pattern="\d+_*[eE][+-]?\d+">
|
||||
<token type="LiteralNumberFloat"/>
|
||||
</rule>
|
||||
<rule pattern="\d+\.\d+([eE][+-]?\d+)?">
|
||||
<rule pattern="\d+(_+[\d]+)*\.\d+(_+[\d]+)*([eE][+-]?\d+)?">
|
||||
<token type="LiteralNumberFloat"/>
|
||||
</rule>
|
||||
<rule pattern="0[oO][0-7]+">
|
||||
<rule pattern="0[oO](_*[0-7])+">
|
||||
<token type="LiteralNumberOct"/>
|
||||
</rule>
|
||||
<rule pattern="0[xX][\da-fA-F]+">
|
||||
<rule pattern="0[xX](_*[\da-fA-F])+">
|
||||
<token type="LiteralNumberHex"/>
|
||||
</rule>
|
||||
<rule pattern="\d+">
|
||||
<rule pattern="0[bB](_*[01])+">
|
||||
<token type="LiteralNumberBin"/>
|
||||
</rule>
|
||||
<rule pattern="\d+(_*[\d])*">
|
||||
<token type="LiteralNumberInteger"/>
|
||||
</rule>
|
||||
<rule pattern="'">
|
||||
|
||||
+2
-1
@@ -3,6 +3,7 @@
|
||||
<name>JSON</name>
|
||||
<alias>json</alias>
|
||||
<filename>*.json</filename>
|
||||
<filename>*.jsonc</filename>
|
||||
<filename>*.avsc</filename>
|
||||
<mime_type>application/json</mime_type>
|
||||
<dot_all>true</dot_all>
|
||||
@@ -108,4 +109,4 @@
|
||||
</rule>
|
||||
</state>
|
||||
</rules>
|
||||
</lexer>
|
||||
</lexer>
|
||||
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
<lexer>
|
||||
<config>
|
||||
<name>JSONata</name>
|
||||
<alias>jsonata</alias>
|
||||
<filename>*.jsonata</filename>
|
||||
<dot_all>true</dot_all>
|
||||
</config>
|
||||
<rules>
|
||||
<state name="root">
|
||||
<rule pattern="/\*.*?\*/">
|
||||
<token type="CommentMultiline"/>
|
||||
</rule>
|
||||
<rule pattern="[{}()\[\]:;,\.=]">
|
||||
<token type="Punctuation"/>
|
||||
</rule>
|
||||
<rule pattern="\.\."> // Spread operator
|
||||
<token type="Operator"/>
|
||||
</rule>
|
||||
<rule pattern="\^(?=\()"> // Sort operator
|
||||
<token type="Operator"/>
|
||||
</rule>
|
||||
<rule pattern="\*\*|\*(?=\.)|\*"> // Descendant | Wildcard | Multiplication
|
||||
<token type="Operator"/>
|
||||
</rule>
|
||||
<rule pattern="\/(?!\*)"> // Division
|
||||
<token type="Operator"/>
|
||||
</rule>
|
||||
<rule pattern="[<>!]=?"> // Comparison operators
|
||||
<token type="Operator"/>
|
||||
</rule>
|
||||
<rule pattern="~>">
|
||||
<token type="Operator"/>
|
||||
</rule>
|
||||
<rule pattern="\b(and|or|in)\b">
|
||||
<token type="Operator"/>
|
||||
</rule>
|
||||
<rule pattern="[%@#&?]|\+(?!\d)|\-(?!\d)">
|
||||
<token type="Operator"/>
|
||||
</rule>
|
||||
<rule pattern="\$[a-zA-Z0-9_]*(?![\w\(])">
|
||||
<token type="NameVariable"/>
|
||||
</rule>
|
||||
<rule pattern="\$\w*(?=\()">
|
||||
<token type="NameFunction"/>
|
||||
</rule>
|
||||
<rule pattern="\s+">
|
||||
<token type="Text"/>
|
||||
</rule>
|
||||
<rule pattern="(true|false)\b">
|
||||
<token type="KeywordConstant"/>
|
||||
</rule>
|
||||
<rule pattern="\b(function)\b">
|
||||
<token type="Keyword"/>
|
||||
</rule>
|
||||
<rule pattern="(\+|-)?(0|[1-9]\d*)(\.\d+[eE](\+|-)?\d+|[eE](\+|-)?\d+|\.\d+)">
|
||||
<token type="LiteralNumberFloat"/>
|
||||
</rule>
|
||||
<rule pattern="(\+|-)?(0|[1-9]\d*)">
|
||||
<token type="LiteralNumberInteger"/>
|
||||
</rule>
|
||||
<!-- NOTE: This expression matches all object keys (NameTags), which are essentially strings with double quotes
|
||||
that should only be captured on the left side of a colon (:) within a JSON-like object.
|
||||
Therefore, this expression must preceed the one for all LiteralStringDouble -->
|
||||
<rule pattern=""(\\.|[^\\"\r\n])*"(?=\s*:)">
|
||||
<token type="NameTag"/>
|
||||
</rule>
|
||||
<rule pattern=""(\\\\|\\"|[^"])*"">
|
||||
<token type="LiteralStringDouble"/>
|
||||
</rule>
|
||||
<rule pattern="'(\\|\\'|[^'])*'">
|
||||
<token type="LiteralStringSingle"/>
|
||||
</rule>
|
||||
<rule pattern="`.*`">
|
||||
<token type="LiteralStringBacktick"/>
|
||||
</rule>
|
||||
<!-- NOTE: This expression matches everything remaining, which should be only JSONata names.
|
||||
Therefore, it has been left as last intentionally -->
|
||||
<rule pattern="[a-zA-Z0-9_]*">
|
||||
<token type="Name"/>
|
||||
</rule>
|
||||
</state>
|
||||
</rules>
|
||||
</lexer>
|
||||
+138
@@ -0,0 +1,138 @@
|
||||
|
||||
<lexer>
|
||||
<config>
|
||||
<name>Jsonnet</name>
|
||||
<alias>jsonnet</alias>
|
||||
<filename>*.jsonnet</filename>
|
||||
<filename>*.libsonnet</filename>
|
||||
</config>
|
||||
<rules>
|
||||
<state name="_comments">
|
||||
<rule pattern="(//|#).*\n"><token type="CommentSingle"/></rule>
|
||||
<rule pattern="/\*\*([^/]|/(?!\*))*\*/"><token type="LiteralStringDoc"/></rule>
|
||||
<rule pattern="/\*([^/]|/(?!\*))*\*/"><token type="Comment"/></rule>
|
||||
</state>
|
||||
<state name="root">
|
||||
<rule><include state="_comments"/></rule>
|
||||
<rule pattern="@'.*'"><token type="LiteralString"/></rule>
|
||||
<rule pattern="@".*""><token type="LiteralString"/></rule>
|
||||
<rule pattern="'"><token type="LiteralString"/><push state="singlestring"/></rule>
|
||||
<rule pattern="""><token type="LiteralString"/><push state="doublestring"/></rule>
|
||||
<rule pattern="\|\|\|(.|\n)*\|\|\|"><token type="LiteralString"/></rule>
|
||||
<rule pattern="[+-]?[0-9]+(.[0-9])?"><token type="LiteralNumberFloat"/></rule>
|
||||
<rule pattern="[!$~+\-&|^=<>*/%]"><token type="Operator"/></rule>
|
||||
<rule pattern="\{"><token type="Punctuation"/><push state="object"/></rule>
|
||||
<rule pattern="\["><token type="Punctuation"/><push state="array"/></rule>
|
||||
<rule pattern="local\b"><token type="Keyword"/><push state="local_name"/></rule>
|
||||
<rule pattern="assert\b"><token type="Keyword"/><push state="assert"/></rule>
|
||||
<rule pattern="(assert|else|error|false|for|if|import|importstr|in|null|tailstrict|then|self|super|true)\b"><token type="Keyword"/></rule>
|
||||
<rule pattern="\s+"><token type="TextWhitespace"/></rule>
|
||||
<rule pattern="function(?=\()"><token type="Keyword"/><push state="function_params"/></rule>
|
||||
<rule pattern="std\.[^\W\d]\w*(?=\()"><token type="NameBuiltin"/><push state="function_args"/></rule>
|
||||
<rule pattern="[^\W\d]\w*(?=\()"><token type="NameFunction"/><push state="function_args"/></rule>
|
||||
<rule pattern="[^\W\d]\w*"><token type="NameVariable"/></rule>
|
||||
<rule pattern="[\.()]"><token type="Punctuation"/></rule>
|
||||
</state>
|
||||
<state name="singlestring">
|
||||
<rule pattern="[^'\\]"><token type="LiteralString"/></rule>
|
||||
<rule pattern="\\."><token type="LiteralStringEscape"/></rule>
|
||||
<rule pattern="'"><token type="LiteralString"/><pop depth="1"/></rule>
|
||||
</state>
|
||||
<state name="doublestring">
|
||||
<rule pattern="[^"\\]"><token type="LiteralString"/></rule>
|
||||
<rule pattern="\\."><token type="LiteralStringEscape"/></rule>
|
||||
<rule pattern="""><token type="LiteralString"/><pop depth="1"/></rule>
|
||||
</state>
|
||||
<state name="array">
|
||||
<rule pattern=","><token type="Punctuation"/></rule>
|
||||
<rule pattern="\]"><token type="Punctuation"/><pop depth="1"/></rule>
|
||||
<rule><include state="root"/></rule>
|
||||
</state>
|
||||
<state name="local_name">
|
||||
<rule pattern="[^\W\d]\w*(?=\()"><token type="NameFunction"/><push state="function_params"/></rule>
|
||||
<rule pattern="[^\W\d]\w*"><token type="NameVariable"/></rule>
|
||||
<rule pattern="\s+"><token type="TextWhitespace"/></rule>
|
||||
<rule pattern="(?==)"><token type="TextWhitespace"/><push state="#pop" state="local_value"/></rule>
|
||||
</state>
|
||||
<state name="local_value">
|
||||
<rule pattern="="><token type="Operator"/></rule>
|
||||
<rule pattern=";"><token type="Punctuation"/><pop depth="1"/></rule>
|
||||
<rule><include state="root"/></rule>
|
||||
</state>
|
||||
<state name="assert">
|
||||
<rule pattern=":"><token type="Punctuation"/></rule>
|
||||
<rule pattern=";"><token type="Punctuation"/><pop depth="1"/></rule>
|
||||
<rule><include state="root"/></rule>
|
||||
</state>
|
||||
<state name="function_params">
|
||||
<rule pattern="[^\W\d]\w*"><token type="NameVariable"/></rule>
|
||||
<rule pattern="\("><token type="Punctuation"/></rule>
|
||||
<rule pattern="\)"><token type="Punctuation"/><pop depth="1"/></rule>
|
||||
<rule pattern=","><token type="Punctuation"/></rule>
|
||||
<rule pattern="\s+"><token type="TextWhitespace"/></rule>
|
||||
<rule pattern="="><token type="Operator"/><push state="function_param_default"/></rule>
|
||||
</state>
|
||||
<state name="function_args">
|
||||
<rule pattern="\("><token type="Punctuation"/></rule>
|
||||
<rule pattern="\)"><token type="Punctuation"/><pop depth="1"/></rule>
|
||||
<rule pattern=","><token type="Punctuation"/></rule>
|
||||
<rule pattern="\s+"><token type="TextWhitespace"/></rule>
|
||||
<rule><include state="root"/></rule>
|
||||
</state>
|
||||
<state name="object">
|
||||
<rule pattern="\s+"><token type="TextWhitespace"/></rule>
|
||||
<rule pattern="local\b"><token type="Keyword"/><push state="object_local_name"/></rule>
|
||||
<rule pattern="assert\b"><token type="Keyword"/><push state="object_assert"/></rule>
|
||||
<rule pattern="\["><token type="Operator"/><push state="field_name_expr"/></rule>
|
||||
<rule pattern="(?=[^\W\d]\w*)"><token type="Text"/><push state="field_name"/></rule>
|
||||
<rule pattern="\}"><token type="Punctuation"/><pop depth="1"/></rule>
|
||||
<rule pattern="""><token type="NameVariable"/><push state="double_field_name"/></rule>
|
||||
<rule pattern="'"><token type="NameVariable"/><push state="single_field_name"/></rule>
|
||||
<rule><include state="_comments"/></rule>
|
||||
</state>
|
||||
<state name="field_name">
|
||||
<rule pattern="[^\W\d]\w*(?=\()"><token type="NameFunction"/><push state="field_separator" state="function_params"/></rule>
|
||||
<rule pattern="[^\W\d]\w*"><token type="NameVariable"/><push state="field_separator"/></rule>
|
||||
</state>
|
||||
<state name="double_field_name">
|
||||
<rule pattern="([^"\\]|\\.)*""><token type="NameVariable"/><push state="field_separator"/></rule>
|
||||
</state>
|
||||
<state name="single_field_name">
|
||||
<rule pattern="([^'\\]|\\.)*'"><token type="NameVariable"/><push state="field_separator"/></rule>
|
||||
</state>
|
||||
<state name="field_name_expr">
|
||||
<rule pattern="\]"><token type="Operator"/><push state="field_separator"/></rule>
|
||||
<rule><include state="root"/></rule>
|
||||
</state>
|
||||
<state name="function_param_default">
|
||||
<rule pattern="(?=[,\)])"><token type="TextWhitespace"/><pop depth="1"/></rule>
|
||||
<rule><include state="root"/></rule>
|
||||
</state>
|
||||
<state name="field_separator">
|
||||
<rule pattern="\s+"><token type="TextWhitespace"/></rule>
|
||||
<rule pattern="\+?::?:?"><token type="Punctuation"/><push state="#pop" state="#pop" state="field_value"/></rule>
|
||||
<rule><include state="_comments"/></rule>
|
||||
</state>
|
||||
<state name="field_value">
|
||||
<rule pattern=","><token type="Punctuation"/><pop depth="1"/></rule>
|
||||
<rule pattern="\}"><token type="Punctuation"/><pop depth="2"/></rule>
|
||||
<rule><include state="root"/></rule>
|
||||
</state>
|
||||
<state name="object_assert">
|
||||
<rule pattern=":"><token type="Punctuation"/></rule>
|
||||
<rule pattern=","><token type="Punctuation"/><pop depth="1"/></rule>
|
||||
<rule><include state="root"/></rule>
|
||||
</state>
|
||||
<state name="object_local_name">
|
||||
<rule pattern="[^\W\d]\w*"><token type="NameVariable"/><push state="#pop" state="object_local_value"/></rule>
|
||||
<rule pattern="\s+"><token type="TextWhitespace"/></rule>
|
||||
</state>
|
||||
<state name="object_local_value">
|
||||
<rule pattern="="><token type="Operator"/></rule>
|
||||
<rule pattern=","><token type="Punctuation"/><pop depth="1"/></rule>
|
||||
<rule pattern="\}"><token type="Punctuation"/><pop depth="2"/></rule>
|
||||
<rule><include state="root"/></rule>
|
||||
</state>
|
||||
</rules>
|
||||
</lexer>
|
||||
|
||||
Generated
Vendored
+77
-77
@@ -1,154 +1,154 @@
|
||||
<lexer>
|
||||
<config>
|
||||
<name>Materialize SQL dialect</name>
|
||||
<alias>materialize</alias>
|
||||
<alias>mzsql</alias>
|
||||
<mime_type>text/x-materializesql</mime_type>
|
||||
<case_insensitive>true</case_insensitive>
|
||||
<not_multiline>true</not_multiline>
|
||||
<alias>materialize</alias>
|
||||
<alias>mzsql</alias>
|
||||
</config>
|
||||
<rules>
|
||||
<state name="root">
|
||||
<rule pattern="\s+">
|
||||
<token type="Text"/>
|
||||
<token type="Text" />
|
||||
</rule>
|
||||
<rule pattern="--.*\n?">
|
||||
<token type="CommentSingle"/>
|
||||
<token type="CommentSingle" />
|
||||
</rule>
|
||||
<rule pattern="/\*">
|
||||
<token type="CommentMultiline"/>
|
||||
<push state="multiline-comments"/>
|
||||
<token type="CommentMultiline" />
|
||||
<push state="multiline-comments" />
|
||||
</rule>
|
||||
<rule pattern="(bigint|bigserial|bit|bit\s+varying|bool|boolean|box|bytea|char|character|character\s+varying|cidr|circle|date|decimal|double\s+precision|float4|float8|inet|int|int2|int4|int8|integer|interval|json|jsonb|line|lseg|macaddr|money|numeric|path|pg_lsn|point|polygon|real|serial|serial2|serial4|serial8|smallint|smallserial|text|time|timestamp|timestamptz|timetz|tsquery|tsvector|txid_snapshot|uuid|varbit|varchar|with\s+time\s+zone|without\s+time\s+zone|xml|anyarray|anyelement|anyenum|anynonarray|anyrange|cstring|fdw_handler|internal|language_handler|opaque|record|void)\b">
|
||||
<token type="NameBuiltin"/>
|
||||
<token type="NameBuiltin" />
|
||||
</rule>
|
||||
<rule pattern="(?s)(DO)(\s+)(?:(LANGUAGE)?(\s+)('?)(\w+)?('?)(\s+))?(\$)([^$]*)(\$)(.*?)(\$)(\10)(\$)">
|
||||
<rule pattern="(?s)(DO)(\s+)(?:(LANGUAGE)?(\s+)('?)(\w+)?('?)(\s+))?(\$)([^$]*)(\$)(.*?)(\$)(\10)(\$)">
|
||||
<usingbygroup>
|
||||
<sublexer_name_group>6</sublexer_name_group>
|
||||
<code_group>12</code_group>
|
||||
<emitters>
|
||||
<token type="Keyword"/>
|
||||
<token type="Text"/>
|
||||
<token type="Keyword"/>
|
||||
<token type="Text"/>
|
||||
<token type="LiteralStringSingle"/>
|
||||
<token type="LiteralStringSingle"/>
|
||||
<token type="LiteralStringSingle"/>
|
||||
<token type="Text"/>
|
||||
<token type="LiteralStringHeredoc"/>
|
||||
<token type="LiteralStringHeredoc"/>
|
||||
<token type="LiteralStringHeredoc"/>
|
||||
<token type="LiteralStringHeredoc"/>
|
||||
<token type="LiteralStringHeredoc"/>
|
||||
<token type="LiteralStringHeredoc"/>
|
||||
<token type="LiteralStringHeredoc"/>
|
||||
<token type="Keyword" />
|
||||
<token type="Text" />
|
||||
<token type="Keyword" />
|
||||
<token type="Text" />
|
||||
<token type="LiteralStringSingle" />
|
||||
<token type="LiteralStringSingle" />
|
||||
<token type="LiteralStringSingle" />
|
||||
<token type="Text" />
|
||||
<token type="LiteralStringHeredoc" />
|
||||
<token type="LiteralStringHeredoc" />
|
||||
<token type="LiteralStringHeredoc" />
|
||||
<token type="LiteralStringHeredoc" />
|
||||
<token type="LiteralStringHeredoc" />
|
||||
<token type="LiteralStringHeredoc" />
|
||||
<token type="LiteralStringHeredoc" />
|
||||
</emitters>
|
||||
</usingbygroup>
|
||||
</rule>
|
||||
<rule pattern="(ACCESS|ACKS|ADD|ADDRESSES|AGGREGATE|ALL|ALTER|AND|ANY|ARN|ARRANGEMENT|ARRAY|AS|ASC|ASSERT|AT|AUCTION|AUTHORITY|AVAILABILITY|AVRO|AWS|BEGIN|BETWEEN|BIGINT|BILLED|BODY|BOOLEAN|BOTH|BPCHAR|BROKEN|BROKER|BROKERS|BY|BYTES|CARDINALITY|CASCADE|CASE|CAST|CERTIFICATE|CHAIN|CHAR|CHARACTER|CHARACTERISTICS|CHECK|CLIENT|CLOSE|CLUSTER|CLUSTERS|COALESCE|COLLATE|COLUMN|COLUMNS|COMMENT|COMMIT|COMMITTED|COMPACTION|COMPRESSION|COMPUTE|COMPUTECTL|CONFLUENT|CONNECTION|CONNECTIONS|CONSTRAINT|COPY|COUNT|COUNTER|CREATE|CREATECLUSTER|CREATEDB|CREATEROLE|CROSS|CSV|CURRENT|CURSOR|DATABASE|DATABASES|DATUMS|DAY|DAYS|DEALLOCATE|DEBEZIUM|DEBUG|DEBUGGING|DEC|DECIMAL|DECLARE|DECORRELATED|DEFAULT|DEFAULTS|DELETE|DELIMITED|DELIMITER|DESC|DETAILS|DISCARD|DISK|DISTINCT|DOC|DOT|DOUBLE|DROP|EFFORT|ELEMENT|ELSE|ENABLE|END|ENDPOINT|ENFORCED|ENVELOPE|ERROR|ESCAPE|EXCEPT|EXECUTE|EXISTS|EXPECTED|EXPLAIN|EXPOSE|EXTRACT|FACTOR|FALSE|FETCH|FIELDS|FILTER|FIRST|FLOAT|FOLLOWING|FOR|FOREIGN|FORMAT|FORWARD|FROM|FULL|FULLNAME|FUNCTION|GENERATOR|GRANT|GREATEST|GROUP|GROUPS|HAVING|HEADER|HEADERS|HOLD|HOST|HOUR|HOURS|ID|IDEMPOTENCE|IDLE|IF|IGNORE|ILIKE|IN|INCLUDE|INDEX|INDEXES|INFO|INHERIT|INLINE|INNER|INPUT|INSERT|INSPECT|INT|INTEGER|INTERNAL|INTERSECT|INTERVAL|INTO|INTROSPECTION|IS|ISNULL|ISOLATION|JOIN|JSON|KAFKA|KEY|KEYS|LAST|LATERAL|LATEST|LEADING|LEAST|LEFT|LEVEL|LIKE|LIMIT|LIST|LOAD|LOCAL|LOG|LOGICAL|LOGIN|MANAGED|MAP|MARKETING|MATERIALIZE|MATERIALIZED|MAX|MECHANISMS|MEMBERSHIP|MERGE|MESSAGE|METADATA|MINUTE|MINUTES|MODE|MONTH|MONTHS|MS|MUTUALLY|NAME|NAMES|NATURAL|NEXT|NO|NOCREATECLUSTER|NOCREATEDB|NOCREATEROLE|NOINHERIT|NOLOGIN|NONE|NOSUPERUSER|NOT|NOTICE|NULL|NULLIF|NULLS|OBJECTS|OF|OFFSET|ON|ONLY|OPERATOR|OPTIMIZED|OPTIMIZER|OPTIONS|OR|ORDER|ORDINALITY|OUTER|OVER|OWNED|OWNER|PARTITION|PASSWORD|PHYSICAL|PLAN|PLANS|PORT|POSITION|POSTGRES|PRECEDING|PRECISION|PREFIX|PREPARE|PRIMARY|PRIVATELINK|PRIVILEGES|PROGRESS|PROTOBUF|PROTOCOL|PUBLICATION|QUERY|QUOTE|RAISE|RANGE|RAW|READ|REAL|REASSIGN|RECURSION|RECURSIVE|REFERENCES|REFRESH|REGEX|REGION|REGISTRY|RENAME|REPEATABLE|REPLACE|REPLICA|REPLICAS|REPLICATION|RESET|RESPECT|RESTRICT|RETENTION|RETURN|RETURNING|REVOKE|RIGHT|ROLE|ROLES|ROLLBACK|ROTATE|ROW|ROWS|SASL|SCALE|SCHEMA|SCHEMAS|SCRIPT|SECOND|SECONDS|SECRET|SECRETS|SECURITY|SEED|SELECT|SEQUENCES|SERIALIZABLE|SERVICE|SESSION|SET|SHARD|SHOW|SINK|SINKS|SIZE|SMALLINT|SNAPSHOT|SOME|SOURCE|SOURCES|SSH|SSL|START|STDIN|STDOUT|STORAGE|STORAGECTL|STRATEGY|STRICT|STRING|SUBSCRIBE|SUBSOURCE|SUBSOURCES|SUBSTRING|SUPERUSER|SWAP|SYSTEM|TABLE|TABLES|TAIL|TEMP|TEMPORARY|TEST|TEXT|THEN|TICK|TIES|TIME|TIMELINE|TIMEOUT|TIMESTAMP|TIMESTAMPTZ|TO|TOKEN|TOPIC|TPCH|TRACE|TRAILING|TRANSACTION|TRIM|TRUE|TUNNEL|TYPE|TYPES|UNBOUNDED|UNCOMMITTED|UNION|UNIQUE|UNKNOWN|UP|UPDATE|UPSERT|URL|USAGE|USER|USERNAME|USERS|USING|VALIDATE|VALUE|VALUES|VARCHAR|VARYING|VIEW|VIEWS|WARNING|WEBHOOK|WHEN|WHERE|WINDOW|WIRE|WITH|WITHIN|WITHOUT|WORK|WORKERS|WRITE|YEAR|YEARS|ZONE|ZONES)\b">
|
||||
<token type="Keyword"/>
|
||||
<rule pattern="(ACCESS|ADD|ADDRESSES|AGGREGATE|ALIGNED|ALL|ALTER|ANALYSIS|AND|ANY|ARITY|ARN|ARRANGEMENT|ARRAY|AS|ASC|ASSERT|ASSUME|AT|AUCTION|AUTHORITY|AVAILABILITY|AVRO|AWS|BATCH|BEGIN|BETWEEN|BIGINT|BILLED|BODY|BOOLEAN|BOTH|BPCHAR|BROKEN|BROKER|BROKERS|BY|BYTES|CARDINALITY|CASCADE|CASE|CAST|CERTIFICATE|CHAIN|CHAINS|CHAR|CHARACTER|CHARACTERISTICS|CHECK|CLASS|CLIENT|CLOCK|CLOSE|CLUSTER|CLUSTERS|COALESCE|COLLATE|COLUMN|COLUMNS|COMMENT|COMMIT|COMMITTED|COMPACTION|COMPATIBILITY|COMPRESSION|COMPUTE|COMPUTECTL|CONFIG|CONFLUENT|CONNECTION|CONNECTIONS|CONSTRAINT|CONTINUAL|COPY|COUNT|COUNTER|CREATE|CREATECLUSTER|CREATEDB|CREATEROLE|CREATION|CROSS|CSV|CURRENT|CURSOR|DATABASE|DATABASES|DATUMS|DAY|DAYS|DEALLOCATE|DEBEZIUM|DEBUG|DEBUGGING|DEC|DECIMAL|DECLARE|DECODING|DECORRELATED|DEFAULT|DEFAULTS|DELETE|DELIMITED|DELIMITER|DELTA|DESC|DETAILS|DISCARD|DISK|DISTINCT|DOC|DOT|DOUBLE|DROP|EAGER|ELEMENT|ELSE|ENABLE|END|ENDPOINT|ENFORCED|ENVELOPE|ERROR|ERRORS|ESCAPE|ESTIMATE|EVERY|EXCEPT|EXCLUDE|EXECUTE|EXISTS|EXPECTED|EXPLAIN|EXPOSE|EXPRESSIONS|EXTERNAL|EXTRACT|FACTOR|FALSE|FAST|FEATURES|FETCH|FIELDS|FILE|FILTER|FIRST|FIXPOINT|FLOAT|FOLLOWING|FOR|FOREIGN|FORMAT|FORWARD|FROM|FULL|FULLNAME|FUNCTION|FUSION|GENERATOR|GRANT|GREATEST|GROUP|GROUPS|HAVING|HEADER|HEADERS|HISTORY|HOLD|HOST|HOUR|HOURS|HUMANIZED|HYDRATION|ID|IDENTIFIERS|IDS|IF|IGNORE|ILIKE|IMPLEMENTATIONS|IMPORTED|IN|INCLUDE|INDEX|INDEXES|INFO|INHERIT|INLINE|INNER|INPUT|INSERT|INSIGHTS|INSPECT|INT|INTEGER|INTERNAL|INTERSECT|INTERVAL|INTO|INTROSPECTION|IS|ISNULL|ISOLATION|JOIN|JOINS|JSON|KAFKA|KEY|KEYS|LAST|LATERAL|LATEST|LEADING|LEAST|LEFT|LEGACY|LETREC|LEVEL|LIKE|LIMIT|LINEAR|LIST|LOAD|LOCAL|LOCALLY|LOG|LOGICAL|LOGIN|LOWERING|MANAGED|MANUAL|MAP|MARKETING|MATERIALIZE|MATERIALIZED|MAX|MECHANISMS|MEMBERSHIP|MESSAGE|METADATA|MINUTE|MINUTES|MODE|MONTH|MONTHS|MUTUALLY|MYSQL|NAME|NAMES|NATURAL|NEGATIVE|NEW|NEXT|NO|NOCREATECLUSTER|NOCREATEDB|NOCREATEROLE|NODE|NOINHERIT|NOLOGIN|NON|NONE|NOSUPERUSER|NOT|NOTICE|NOTICES|NULL|NULLIF|NULLS|OBJECTS|OF|OFFSET|ON|ONLY|OPERATOR|OPTIMIZED|OPTIMIZER|OPTIONS|OR|ORDER|ORDINALITY|OUTER|OVER|OWNED|OWNER|PARTITION|PARTITIONS|PASSWORD|PATH|PHYSICAL|PLAN|PLANS|PORT|POSITION|POSTGRES|PRECEDING|PRECISION|PREFIX|PREPARE|PRIMARY|PRIVATELINK|PRIVILEGES|PROGRESS|PROTOBUF|PROTOCOL|PUBLIC|PUBLICATION|PUSHDOWN|QUERY|QUOTE|RAISE|RANGE|RATE|RAW|READ|READY|REAL|REASSIGN|RECURSION|RECURSIVE|REDACTED|REDUCE|REFERENCE|REFERENCES|REFRESH|REGEX|REGION|REGISTRY|RENAME|REOPTIMIZE|REPEATABLE|REPLACE|REPLAN|REPLICA|REPLICAS|REPLICATION|RESET|RESPECT|RESTRICT|RETAIN|RETURN|RETURNING|REVOKE|RIGHT|ROLE|ROLES|ROLLBACK|ROTATE|ROUNDS|ROW|ROWS|SASL|SCALE|SCHEDULE|SCHEMA|SCHEMAS|SECOND|SECONDS|SECRET|SECRETS|SECURITY|SEED|SELECT|SEQUENCES|SERIALIZABLE|SERVICE|SESSION|SET|SHARD|SHOW|SINK|SINKS|SIZE|SMALLINT|SNAPSHOT|SOME|SOURCE|SOURCES|SSH|SSL|START|STDIN|STDOUT|STORAGE|STORAGECTL|STRATEGY|STRICT|STRING|STRONG|SUBSCRIBE|SUBSOURCE|SUBSOURCES|SUBSTRING|SUBTREE|SUPERUSER|SWAP|SYNTAX|SYSTEM|TABLE|TABLES|TAIL|TASK|TEMP|TEMPORARY|TEXT|THEN|TICK|TIES|TIME|TIMELINE|TIMEOUT|TIMESTAMP|TIMESTAMPTZ|TIMING|TO|TOKEN|TOPIC|TPCH|TRACE|TRAILING|TRANSACTION|TRANSACTIONAL|TRIM|TRUE|TUNNEL|TYPE|TYPES|UNBOUNDED|UNCOMMITTED|UNION|UNIQUE|UNKNOWN|UNNEST|UNTIL|UP|UPDATE|UPSERT|URL|USAGE|USER|USERNAME|USERS|USING|VALIDATE|VALUE|VALUES|VARCHAR|VARIADIC|VARYING|VERSION|VIEW|VIEWS|WAIT|WARNING|WEBHOOK|WHEN|WHERE|WINDOW|WIRE|WITH|WITHIN|WITHOUT|WORK|WORKERS|WORKLOAD|WRITE|YEAR|YEARS|YUGABYTE|ZONE|ZONES)\b">
|
||||
<token type="Keyword" />
|
||||
</rule>
|
||||
<rule pattern="[+*/<>=~!@#%^&|`?-]+">
|
||||
<token type="Operator"/>
|
||||
<token type="Operator" />
|
||||
</rule>
|
||||
<rule pattern="::">
|
||||
<token type="Operator"/>
|
||||
<token type="Operator" />
|
||||
</rule>
|
||||
<rule pattern="\$\d+">
|
||||
<token type="NameVariable"/>
|
||||
<token type="NameVariable" />
|
||||
</rule>
|
||||
<rule pattern="([0-9]*\.[0-9]*|[0-9]+)(e[+-]?[0-9]+)?">
|
||||
<token type="LiteralNumberFloat"/>
|
||||
<token type="LiteralNumberFloat" />
|
||||
</rule>
|
||||
<rule pattern="[0-9]+">
|
||||
<token type="LiteralNumberInteger"/>
|
||||
<token type="LiteralNumberInteger" />
|
||||
</rule>
|
||||
<rule pattern="((?:E|U&)?)(')">
|
||||
<rule pattern="((?:E|U&)?)(')">
|
||||
<bygroups>
|
||||
<token type="LiteralStringAffix"/>
|
||||
<token type="LiteralStringSingle"/>
|
||||
<token type="LiteralStringAffix" />
|
||||
<token type="LiteralStringSingle" />
|
||||
</bygroups>
|
||||
<push state="string"/>
|
||||
<push state="string" />
|
||||
</rule>
|
||||
<rule pattern="((?:U&)?)(")">
|
||||
<rule pattern="((?:U&)?)(")">
|
||||
<bygroups>
|
||||
<token type="LiteralStringAffix"/>
|
||||
<token type="LiteralStringName"/>
|
||||
<token type="LiteralStringAffix" />
|
||||
<token type="LiteralStringName" />
|
||||
</bygroups>
|
||||
<push state="quoted-ident"/>
|
||||
<push state="quoted-ident" />
|
||||
</rule>
|
||||
<rule pattern="(?s)(\$)([^$]*)(\$)(.*?)(\$)(\2)(\$)(\s+)(LANGUAGE)?(\s+)('?)(\w+)?('?)">
|
||||
<rule pattern="(?s)(\$)([^$]*)(\$)(.*?)(\$)(\2)(\$)(\s+)(LANGUAGE)?(\s+)('?)(\w+)?('?)">
|
||||
<usingbygroup>
|
||||
<sublexer_name_group>12</sublexer_name_group>
|
||||
<code_group>4</code_group>
|
||||
<emitters>
|
||||
<token type="LiteralStringHeredoc"/>
|
||||
<token type="LiteralStringHeredoc"/>
|
||||
<token type="LiteralStringHeredoc"/>
|
||||
<token type="LiteralStringHeredoc"/>
|
||||
<token type="LiteralStringHeredoc"/>
|
||||
<token type="LiteralStringHeredoc"/>
|
||||
<token type="LiteralStringHeredoc"/>
|
||||
<token type="Text"/>
|
||||
<token type="Keyword"/>
|
||||
<token type="Text"/>
|
||||
<token type="LiteralStringSingle"/>
|
||||
<token type="LiteralStringSingle"/>
|
||||
<token type="LiteralStringSingle"/>
|
||||
<token type="LiteralStringHeredoc" />
|
||||
<token type="LiteralStringHeredoc" />
|
||||
<token type="LiteralStringHeredoc" />
|
||||
<token type="LiteralStringHeredoc" />
|
||||
<token type="LiteralStringHeredoc" />
|
||||
<token type="LiteralStringHeredoc" />
|
||||
<token type="LiteralStringHeredoc" />
|
||||
<token type="Text" />
|
||||
<token type="Keyword" />
|
||||
<token type="Text" />
|
||||
<token type="LiteralStringSingle" />
|
||||
<token type="LiteralStringSingle" />
|
||||
<token type="LiteralStringSingle" />
|
||||
</emitters>
|
||||
</usingbygroup>
|
||||
</rule>
|
||||
<rule pattern="(?s)(\$)([^$]*)(\$)(.*?)(\$)(\2)(\$)">
|
||||
<token type="LiteralStringHeredoc"/>
|
||||
<token type="LiteralStringHeredoc" />
|
||||
</rule>
|
||||
<rule pattern="[a-z_]\w*">
|
||||
<token type="Name"/>
|
||||
<token type="Name" />
|
||||
</rule>
|
||||
<rule pattern=":(['"]?)[a-z]\w*\b\1">
|
||||
<token type="NameVariable"/>
|
||||
<rule pattern=":(['"]?)[a-z]\w*\b\1">
|
||||
<token type="NameVariable" />
|
||||
</rule>
|
||||
<rule pattern="[;:()\[\]{},.]">
|
||||
<token type="Punctuation"/>
|
||||
<token type="Punctuation" />
|
||||
</rule>
|
||||
</state>
|
||||
<state name="multiline-comments">
|
||||
<rule pattern="/\*">
|
||||
<token type="CommentMultiline"/>
|
||||
<push state="multiline-comments"/>
|
||||
<token type="CommentMultiline" />
|
||||
<push state="multiline-comments" />
|
||||
</rule>
|
||||
<rule pattern="\*/">
|
||||
<token type="CommentMultiline"/>
|
||||
<pop depth="1"/>
|
||||
<token type="CommentMultiline" />
|
||||
<pop depth="1" />
|
||||
</rule>
|
||||
<rule pattern="[^/*]+">
|
||||
<token type="CommentMultiline"/>
|
||||
<token type="CommentMultiline" />
|
||||
</rule>
|
||||
<rule pattern="[/*]">
|
||||
<token type="CommentMultiline"/>
|
||||
<token type="CommentMultiline" />
|
||||
</rule>
|
||||
</state>
|
||||
<state name="string">
|
||||
<rule pattern="[^']+">
|
||||
<token type="LiteralStringSingle"/>
|
||||
<rule pattern="[^']+">
|
||||
<token type="LiteralStringSingle" />
|
||||
</rule>
|
||||
<rule pattern="''">
|
||||
<token type="LiteralStringSingle"/>
|
||||
<rule pattern="''">
|
||||
<token type="LiteralStringSingle" />
|
||||
</rule>
|
||||
<rule pattern="'">
|
||||
<token type="LiteralStringSingle"/>
|
||||
<pop depth="1"/>
|
||||
<rule pattern="'">
|
||||
<token type="LiteralStringSingle" />
|
||||
<pop depth="1" />
|
||||
</rule>
|
||||
</state>
|
||||
<state name="quoted-ident">
|
||||
<rule pattern="[^"]+">
|
||||
<token type="LiteralStringName"/>
|
||||
<rule pattern="[^"]+">
|
||||
<token type="LiteralStringName" />
|
||||
</rule>
|
||||
<rule pattern="""">
|
||||
<token type="LiteralStringName"/>
|
||||
<rule pattern="""">
|
||||
<token type="LiteralStringName" />
|
||||
</rule>
|
||||
<rule pattern=""">
|
||||
<token type="LiteralStringName"/>
|
||||
<pop depth="1"/>
|
||||
<rule pattern=""">
|
||||
<token type="LiteralStringName" />
|
||||
<pop depth="1" />
|
||||
</rule>
|
||||
</state>
|
||||
</rules>
|
||||
|
||||
+126
-170
@@ -1,182 +1,138 @@
|
||||
|
||||
<lexer>
|
||||
<config>
|
||||
<name>mcfunction</name>
|
||||
<name>MCFunction</name>
|
||||
<alias>mcfunction</alias>
|
||||
<alias>mcf</alias>
|
||||
<filename>*.mcfunction</filename>
|
||||
<dot_all>true</dot_all>
|
||||
<not_multiline>true</not_multiline>
|
||||
<mime_type>text/mcfunction</mime_type>
|
||||
</config>
|
||||
<rules>
|
||||
<state name="nbtobjectvalue">
|
||||
<rule pattern="("(\\\\|\\"|[^"])*"|[a-zA-Z0-9_]+)">
|
||||
<token type="NameTag"/>
|
||||
<push state="nbtobjectattribute"/>
|
||||
</rule>
|
||||
<rule pattern="\}">
|
||||
<token type="Punctuation"/>
|
||||
<pop depth="1"/>
|
||||
</rule>
|
||||
</state>
|
||||
<state name="nbtarrayvalue">
|
||||
<rule>
|
||||
<include state="nbtvalue"/>
|
||||
</rule>
|
||||
<rule pattern=",">
|
||||
<token type="Punctuation"/>
|
||||
</rule>
|
||||
<rule pattern="\]">
|
||||
<token type="Punctuation"/>
|
||||
<pop depth="1"/>
|
||||
</rule>
|
||||
</state>
|
||||
<state name="nbtvalue">
|
||||
<rule>
|
||||
<include state="simplevalue"/>
|
||||
</rule>
|
||||
<rule pattern="\{">
|
||||
<token type="Punctuation"/>
|
||||
<push state="nbtobjectvalue"/>
|
||||
</rule>
|
||||
<rule pattern="\[">
|
||||
<token type="Punctuation"/>
|
||||
<push state="nbtarrayvalue"/>
|
||||
</rule>
|
||||
</state>
|
||||
<state name="argumentvalue">
|
||||
<rule>
|
||||
<include state="simplevalue"/>
|
||||
</rule>
|
||||
<rule pattern=",">
|
||||
<token type="Punctuation"/>
|
||||
<pop depth="1"/>
|
||||
</rule>
|
||||
<rule pattern="[}\]]">
|
||||
<token type="Punctuation"/>
|
||||
<pop depth="2"/>
|
||||
</rule>
|
||||
</state>
|
||||
<state name="argumentlist">
|
||||
<rule pattern="(nbt)(={)">
|
||||
<bygroups>
|
||||
<token type="NameAttribute"/>
|
||||
<token type="Punctuation"/>
|
||||
</bygroups>
|
||||
<push state="nbtobjectvalue"/>
|
||||
</rule>
|
||||
<rule pattern="([A-Za-z0-9/_!]+)(={)">
|
||||
<bygroups>
|
||||
<token type="NameAttribute"/>
|
||||
<token type="Punctuation"/>
|
||||
</bygroups>
|
||||
<push state="argumentlist"/>
|
||||
</rule>
|
||||
<rule pattern="([A-Za-z0-9/_!]+)(=)">
|
||||
<bygroups>
|
||||
<token type="NameAttribute"/>
|
||||
<token type="Punctuation"/>
|
||||
</bygroups>
|
||||
<push state="argumentvalue"/>
|
||||
</rule>
|
||||
<rule>
|
||||
<include state="simplevalue"/>
|
||||
</rule>
|
||||
<rule pattern=",">
|
||||
<token type="Punctuation"/>
|
||||
</rule>
|
||||
<rule pattern="[}\]]">
|
||||
<token type="Punctuation"/>
|
||||
<pop depth="1"/>
|
||||
</rule>
|
||||
</state>
|
||||
<state name="root">
|
||||
<rule pattern="#.*?\n">
|
||||
<token type="CommentSingle"/>
|
||||
</rule>
|
||||
<rule pattern="/?(geteduclientinfo|clearspawnpoint|defaultgamemode|transferserver|toggledownfall|immutableworld|detectredstone|setidletimeout|playanimation|classroommode|spreadplayers|testforblocks|setmaxplayers|setworldspawn|testforblock|worldbuilder|createagent|worldborder|camerashake|advancement|raytracefog|locatebiome|tickingarea|replaceitem|attributes|spawnpoint|difficulty|experience|scoreboard|whitelist|structure|playsound|stopsound|forceload|spectate|gamerule|function|schedule|wsserver|teleport|position|save-off|particle|setblock|datapack|mobevent|transfer|gamemode|save-all|bossbar|enchant|trigger|collect|execute|weather|teammsg|tpagent|banlist|dropall|publish|tellraw|testfor|save-on|destroy|ability|locate|summon|remove|effect|reload|ban-ip|recipe|pardon|detect|music|clear|clone|event|mixer|debug|title|ride|stop|list|turn|data|team|kick|loot|tell|help|give|flog|fill|move|time|seed|kill|save|item|deop|code|tag|ban|msg|say|tp|me|op|xp|w|place)\b">
|
||||
<token type="KeywordReserved"/>
|
||||
</rule>
|
||||
<rule pattern="(@p|@r|@a|@e|@s|@c|@v)">
|
||||
<token type="KeywordConstant"/>
|
||||
</rule>
|
||||
<rule pattern="\[">
|
||||
<token type="Punctuation"/>
|
||||
<push state="argumentlist"/>
|
||||
</rule>
|
||||
<rule pattern="{">
|
||||
<token type="Punctuation"/>
|
||||
<push state="nbtobjectvalue"/>
|
||||
</rule>
|
||||
<rule pattern="~">
|
||||
<token type="NameBuiltin"/>
|
||||
</rule>
|
||||
<rule pattern="([a-zA-Z_]+:)?[a-zA-Z_]+\b">
|
||||
<token type="Text"/>
|
||||
</rule>
|
||||
<rule pattern="([a-z]+)(\.)([0-9]+)\b">
|
||||
<bygroups>
|
||||
<token type="Text"/>
|
||||
<token type="Punctuation"/>
|
||||
<token type="LiteralNumber"/>
|
||||
</bygroups>
|
||||
</rule>
|
||||
<rule pattern="([<>=]|<=|>=)">
|
||||
<token type="Punctuation"/>
|
||||
</rule>
|
||||
<rule>
|
||||
<include state="simplevalue"/>
|
||||
</rule>
|
||||
<rule pattern="\s+">
|
||||
<token type="TextWhitespace"/>
|
||||
</rule>
|
||||
<rule><include state="names"/></rule>
|
||||
<rule><include state="comments"/></rule>
|
||||
<rule><include state="literals"/></rule>
|
||||
<rule><include state="whitespace"/></rule>
|
||||
<rule><include state="property"/></rule>
|
||||
<rule><include state="operators"/></rule>
|
||||
<rule><include state="selectors"/></rule>
|
||||
</state>
|
||||
<state name="simplevalue">
|
||||
<rule pattern="(true|false)">
|
||||
<token type="KeywordConstant"/>
|
||||
</rule>
|
||||
<rule pattern="[01]b">
|
||||
<token type="LiteralNumber"/>
|
||||
</rule>
|
||||
<rule pattern="-?(0|[1-9]\d*)(\.\d+[eE](\+|-)?\d+|[eE](\+|-)?\d+|\.\d+)">
|
||||
<token type="LiteralNumberFloat"/>
|
||||
</rule>
|
||||
<rule pattern="(-?\d+)(\.\.)(-?\d+)">
|
||||
<bygroups>
|
||||
<token type="LiteralNumberInteger"/>
|
||||
<token type="Punctuation"/>
|
||||
<token type="LiteralNumberInteger"/>
|
||||
</bygroups>
|
||||
</rule>
|
||||
<rule pattern="-?(0|[1-9]\d*)">
|
||||
<token type="LiteralNumberInteger"/>
|
||||
</rule>
|
||||
<rule pattern=""(\\\\|\\"|[^"])*"">
|
||||
<token type="LiteralStringDouble"/>
|
||||
</rule>
|
||||
<rule pattern="'[^']+'">
|
||||
<token type="LiteralStringSingle"/>
|
||||
</rule>
|
||||
<rule pattern="([!#]?)(\w+)">
|
||||
<bygroups>
|
||||
<token type="Punctuation"/>
|
||||
<token type="Text"/>
|
||||
</bygroups>
|
||||
</rule>
|
||||
<state name="names">
|
||||
<rule pattern="^(\s*)([a-z_]+)"><bygroups><token type="TextWhitespace"/><token type="NameBuiltin"/></bygroups></rule>
|
||||
<rule pattern="(?<=run)\s+[a-z_]+"><token type="NameBuiltin"/></rule>
|
||||
<rule pattern="\b[0-9a-fA-F]+(?:-[0-9a-fA-F]+){4}\b"><token type="NameVariable"/></rule>
|
||||
<rule><include state="resource-name"/></rule>
|
||||
<rule pattern="[A-Za-z_][\w.#%$]+"><token type="KeywordConstant"/></rule>
|
||||
<rule pattern="[#%$][\w.#%$]+"><token type="NameVariableMagic"/></rule>
|
||||
</state>
|
||||
<state name="nbtobjectattribute">
|
||||
<rule>
|
||||
<include state="nbtvalue"/>
|
||||
</rule>
|
||||
<rule pattern=":">
|
||||
<token type="Punctuation"/>
|
||||
</rule>
|
||||
<rule pattern=",">
|
||||
<token type="Punctuation"/>
|
||||
<pop depth="1"/>
|
||||
</rule>
|
||||
<rule pattern="\}">
|
||||
<token type="Punctuation"/>
|
||||
<pop depth="2"/>
|
||||
</rule>
|
||||
<state name="resource-name">
|
||||
<rule pattern="#?[a-z_][a-z_.-]*:[a-z0-9_./-]+"><token type="NameFunction"/></rule>
|
||||
<rule pattern="#?[a-z0-9_\.\-]+\/[a-z0-9_\.\-\/]+"><token type="NameFunction"/></rule>
|
||||
</state>
|
||||
<state name="whitespace">
|
||||
<rule pattern="\s+"><token type="TextWhitespace"/></rule>
|
||||
</state>
|
||||
<state name="comments">
|
||||
<rule pattern="^\s*(#[>!])"><token type="CommentMultiline"/><push state="comments.block" state="comments.block.emphasized"/></rule>
|
||||
<rule pattern="#.*$"><token type="CommentSingle"/></rule>
|
||||
</state>
|
||||
<state name="comments.block">
|
||||
<rule pattern="^\s*#[>!]"><token type="CommentMultiline"/><push state="comments.block.emphasized"/></rule>
|
||||
<rule pattern="^\s*#"><token type="CommentMultiline"/><push state="comments.block.normal"/></rule>
|
||||
<rule><pop depth="1"/></rule>
|
||||
</state>
|
||||
<state name="comments.block.normal">
|
||||
<rule><include state="comments.block.special"/></rule>
|
||||
<rule pattern="\S+"><token type="CommentMultiline"/></rule>
|
||||
<rule pattern="\n"><token type="Text"/><pop depth="1"/></rule>
|
||||
<rule><include state="whitespace"/></rule>
|
||||
</state>
|
||||
<state name="comments.block.emphasized">
|
||||
<rule><include state="comments.block.special"/></rule>
|
||||
<rule pattern="\S+"><token type="LiteralStringDoc"/></rule>
|
||||
<rule pattern="\n"><token type="Text"/><pop depth="1"/></rule>
|
||||
<rule><include state="whitespace"/></rule>
|
||||
</state>
|
||||
<state name="comments.block.special">
|
||||
<rule pattern="@\S+"><token type="NameDecorator"/></rule>
|
||||
<rule><include state="resource-name"/></rule>
|
||||
<rule pattern="[#%$][\w.#%$]+"><token type="NameVariableMagic"/></rule>
|
||||
</state>
|
||||
<state name="operators">
|
||||
<rule pattern="[\-~%^?!+*<>\\/|&=.]"><token type="Operator"/></rule>
|
||||
</state>
|
||||
<state name="literals">
|
||||
<rule pattern="\.\."><token type="Literal"/></rule>
|
||||
<rule pattern="(true|false)"><token type="KeywordPseudo"/></rule>
|
||||
<rule pattern="[A-Za-z_]+"><token type="NameVariableClass"/></rule>
|
||||
<rule pattern="[0-7]b"><token type="LiteralNumberByte"/></rule>
|
||||
<rule pattern="[+-]?\d*\.?\d+([eE]?[+-]?\d+)?[df]?\b"><token type="LiteralNumberFloat"/></rule>
|
||||
<rule pattern="[+-]?\d+\b"><token type="LiteralNumberInteger"/></rule>
|
||||
<rule pattern="""><token type="LiteralStringDouble"/><push state="literals.string-double"/></rule>
|
||||
<rule pattern="'"><token type="LiteralStringSingle"/><push state="literals.string-single"/></rule>
|
||||
</state>
|
||||
<state name="literals.string-double">
|
||||
<rule pattern="\\."><token type="LiteralStringEscape"/></rule>
|
||||
<rule pattern="[^\\"\n]+"><token type="LiteralStringDouble"/></rule>
|
||||
<rule pattern="""><token type="LiteralStringDouble"/><pop depth="1"/></rule>
|
||||
</state>
|
||||
<state name="literals.string-single">
|
||||
<rule pattern="\\."><token type="LiteralStringEscape"/></rule>
|
||||
<rule pattern="[^\\'\n]+"><token type="LiteralStringSingle"/></rule>
|
||||
<rule pattern="'"><token type="LiteralStringSingle"/><pop depth="1"/></rule>
|
||||
</state>
|
||||
<state name="selectors">
|
||||
<rule pattern="@[a-z]"><token type="NameVariable"/></rule>
|
||||
</state>
|
||||
<state name="property">
|
||||
<rule pattern="\{"><token type="Punctuation"/><push state="property.curly" state="property.key"/></rule>
|
||||
<rule pattern="\["><token type="Punctuation"/><push state="property.square" state="property.key"/></rule>
|
||||
</state>
|
||||
<state name="property.curly">
|
||||
<rule><include state="whitespace"/></rule>
|
||||
<rule><include state="property"/></rule>
|
||||
<rule pattern="\}"><token type="Punctuation"/><pop depth="1"/></rule>
|
||||
</state>
|
||||
<state name="property.square">
|
||||
<rule><include state="whitespace"/></rule>
|
||||
<rule><include state="property"/></rule>
|
||||
<rule pattern="\]"><token type="Punctuation"/><pop depth="1"/></rule>
|
||||
<rule pattern=","><token type="Punctuation"/></rule>
|
||||
</state>
|
||||
<state name="property.key">
|
||||
<rule><include state="whitespace"/></rule>
|
||||
<rule pattern="#?[a-z_][a-z_\.\-]*\:[a-z0-9_\.\-/]+(?=\s*\=)"><token type="NameAttribute"/><push state="property.delimiter"/></rule>
|
||||
<rule pattern="#?[a-z_][a-z0-9_\.\-/]+"><token type="NameAttribute"/><push state="property.delimiter"/></rule>
|
||||
<rule pattern="[A-Za-z_\-\+]+"><token type="NameAttribute"/><push state="property.delimiter"/></rule>
|
||||
<rule pattern="""><token type="NameAttribute"/><push state="property.delimiter"/></rule>
|
||||
<rule pattern="'"><token type="NameAttribute"/><push state="property.delimiter"/></rule>
|
||||
<rule pattern="-?\d+"><token type="LiteralNumberInteger"/><push state="property.delimiter"/></rule>
|
||||
<rule><pop depth="1"/></rule>
|
||||
</state>
|
||||
<state name="property.key.string-double">
|
||||
<rule pattern="\\."><token type="LiteralStringEscape"/></rule>
|
||||
<rule pattern="[^\\"\n]+"><token type="NameAttribute"/></rule>
|
||||
<rule pattern="""><token type="NameAttribute"/><pop depth="1"/></rule>
|
||||
</state>
|
||||
<state name="property.key.string-single">
|
||||
<rule pattern="\\."><token type="LiteralStringEscape"/></rule>
|
||||
<rule pattern="[^\\'\n]+"><token type="NameAttribute"/></rule>
|
||||
<rule pattern="'"><token type="NameAttribute"/><pop depth="1"/></rule>
|
||||
</state>
|
||||
<state name="property.delimiter">
|
||||
<rule><include state="whitespace"/></rule>
|
||||
<rule pattern="[:=]!?"><token type="Punctuation"/><push state="property.value"/></rule>
|
||||
<rule pattern=","><token type="Punctuation"/></rule>
|
||||
<rule><pop depth="1"/></rule>
|
||||
</state>
|
||||
<state name="property.value">
|
||||
<rule><include state="whitespace"/></rule>
|
||||
<rule pattern="#?[a-z_][a-z_\.\-]*\:[a-z0-9_\.\-/]+"><token type="NameTag"/></rule>
|
||||
<rule pattern="#?[a-z_][a-z0-9_\.\-/]+"><token type="NameTag"/></rule>
|
||||
<rule><include state="literals"/></rule>
|
||||
<rule><include state="property"/></rule>
|
||||
<rule><pop depth="1"/></rule>
|
||||
</state>
|
||||
</rules>
|
||||
</lexer>
|
||||
|
||||
|
||||
+1
-1
@@ -106,7 +106,7 @@
|
||||
</bygroups>
|
||||
<push state="interpol"/>
|
||||
</rule>
|
||||
<rule pattern="(&&|>=|<=|\+\+|->|!=|\|\||//|==|@|!|\+|\?|<|\.|>|\*)">
|
||||
<rule pattern="(&&|>=|<=|\+\+|->|!=|=|\|\||//|==|@|!|\+|\?|<|\.|>|\*)">
|
||||
<token type="Operator"/>
|
||||
</rule>
|
||||
<rule pattern="[;:]">
|
||||
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
<lexer>
|
||||
<config>
|
||||
<name>NSIS</name>
|
||||
<alias>nsis</alias>
|
||||
<alias>nsi</alias>
|
||||
<alias>nsh</alias>
|
||||
<filename>*.nsi</filename>
|
||||
<filename>*.nsh</filename>
|
||||
<mime_type>text/x-nsis</mime_type>
|
||||
<case_insensitive>true</case_insensitive>
|
||||
<not_multiline>true</not_multiline>
|
||||
</config>
|
||||
<rules>
|
||||
<state name="root">
|
||||
<rule pattern="([;#].*)(\n)"><bygroups><token type="Comment"/><token type="TextWhitespace"/></bygroups></rule>
|
||||
<rule pattern="'.*?'"><token type="LiteralStringSingle"/></rule>
|
||||
<rule pattern="""><token type="LiteralStringDouble"/><push state="str_double"/></rule>
|
||||
<rule pattern="`"><token type="LiteralStringBacktick"/><push state="str_backtick"/></rule>
|
||||
<rule><include state="macro"/></rule>
|
||||
<rule><include state="interpol"/></rule>
|
||||
<rule><include state="basic"/></rule>
|
||||
<rule pattern="\$\{[a-z_|][\w|]*\}"><token type="KeywordPseudo"/></rule>
|
||||
<rule pattern="/[a-z_]\w*"><token type="NameAttribute"/></rule>
|
||||
<rule pattern="\s+"><token type="TextWhitespace"/></rule>
|
||||
<rule pattern="[\w.]+"><token type="Text"/></rule>
|
||||
</state>
|
||||
<state name="basic">
|
||||
<rule pattern="(\n)(Function)(\s+)([._a-z][.\w]*)\b"><bygroups><token type="TextWhitespace"/><token type="Keyword"/><token type="TextWhitespace"/><token type="NameFunction"/></bygroups></rule>
|
||||
<rule pattern="\b([_a-z]\w*)(::)([a-z][a-z0-9]*)\b"><bygroups><token type="KeywordNamespace"/><token type="Punctuation"/><token type="NameFunction"/></bygroups></rule>
|
||||
<rule pattern="\b([_a-z]\w*)(:)"><bygroups><token type="NameLabel"/><token type="Punctuation"/></bygroups></rule>
|
||||
<rule pattern="(\b[ULS]|\B)([!<>=]?=|\<\>?|\>)\B"><token type="Operator"/></rule>
|
||||
<rule pattern="[|+-]"><token type="Operator"/></rule>
|
||||
<rule pattern="\\"><token type="Punctuation"/></rule>
|
||||
<rule pattern="\b(Abort|Add(?:BrandingImage|Size)|Allow(?:RootDirInstall|SkipFiles)|AutoCloseWindow|BG(?:Font|Gradient)|BrandingText|BringToFront|Call(?:InstDLL)?|(?:Sub)?Caption|ChangeUI|CheckBitmap|ClearErrors|CompletedText|ComponentText|CopyFiles|CRCCheck|Create(?:Directory|Font|Shortcut)|Delete(?:INI(?:Sec|Str)|Reg(?:Key|Value))?|DetailPrint|DetailsButtonText|Dir(?:Show|Text|Var|Verify)|(?:Disabled|Enabled)Bitmap|EnableWindow|EnumReg(?:Key|Value)|Exch|Exec(?:Shell|Wait)?|ExpandEnvStrings|File(?:BufSize|Close|ErrorText|Open|Read(?:Byte)?|Seek|Write(?:Byte)?)?|Find(?:Close|First|Next|Window)|FlushINI|Function(?:End)?|Get(?:CurInstType|CurrentAddress|DlgItem|DLLVersion(?:Local)?|ErrorLevel|FileTime(?:Local)?|FullPathName|FunctionAddress|InstDirError|LabelAddress|TempFileName)|Goto|HideWindow|Icon|If(?:Abort|Errors|FileExists|RebootFlag|Silent)|InitPluginsDir|Install(?:ButtonText|Colors|Dir(?:RegKey)?)|Inst(?:ProgressFlags|Type(?:[GS]etText)?)|Int(?:CmpU?|Fmt|Op)|IsWindow|LangString(?:UP)?|License(?:BkColor|Data|ForceSelection|LangString|Text)|LoadLanguageFile|LockWindow|Log(?:Set|Text)|MessageBox|MiscButtonText|Name|Nop|OutFile|(?:Uninst)?Page(?:Ex(?:End)?)?|PluginDir|Pop|Push|Quit|Read(?:(?:Env|INI|Reg)Str|RegDWORD)|Reboot|(?:Un)?RegDLL|Rename|RequestExecutionLevel|ReserveFile|Return|RMDir|SearchPath|Section(?:Divider|End|(?:(?:Get|Set)(?:Flags|InstTypes|Size|Text))|Group(?:End)?|In)?|SendMessage|Set(?:AutoClose|BrandingImage|Compress(?:ionLevel|or(?:DictSize)?)?|CtlColors|CurInstType|DatablockOptimize|DateSave|Details(?:Print|View)|Error(?:s|Level)|FileAttributes|Font|OutPath|Overwrite|PluginUnload|RebootFlag|ShellVarContext|Silent|StaticBkColor)|Show(?:(?:I|Uni)nstDetails|Window)|Silent(?:Un)?Install|Sleep|SpaceTexts|Str(?:CmpS?|Cpy|Len)|SubSection(?:End)?|Uninstall(?:ButtonText|(?:Sub)?Caption|EXEName|Icon|Text)|UninstPage|Var|VI(?:AddVersionKey|ProductVersion)|WindowIcon|Write(?:INIStr|Reg(:?Bin|DWORD|(?:Expand)?Str)|Uninstaller)|XPStyle)\b"><token type="Keyword"/></rule>
|
||||
<rule pattern="\b(CUR|END|(?:FILE_ATTRIBUTE_)?(?:ARCHIVE|HIDDEN|NORMAL|OFFLINE|READONLY|SYSTEM|TEMPORARY)|HK(CC|CR|CU|DD|LM|PD|U)|HKEY_(?:CLASSES_ROOT|CURRENT_(?:CONFIG|USER)|DYN_DATA|LOCAL_MACHINE|PERFORMANCE_DATA|USERS)|ID(?:ABORT|CANCEL|IGNORE|NO|OK|RETRY|YES)|MB_(?:ABORTRETRYIGNORE|DEFBUTTON[1-4]|ICON(?:EXCLAMATION|INFORMATION|QUESTION|STOP)|OK(?:CANCEL)?|RETRYCANCEL|RIGHT|SETFOREGROUND|TOPMOST|USERICON|YESNO(?:CANCEL)?)|SET|SHCTX|SW_(?:HIDE|SHOW(?:MAXIMIZED|MINIMIZED|NORMAL))|admin|all|auto|both|bottom|bzip2|checkbox|colored|current|false|force|hide|highest|if(?:diff|newer)|lastused|leave|left|listonly|lzma|nevershow|none|normal|off|on|pop|push|radiobuttons|right|show|silent|silentlog|smooth|textonly|top|true|try|user|zlib)\b"><token type="NameConstant"/></rule>
|
||||
</state>
|
||||
<state name="macro">
|
||||
<rule pattern="\!(addincludedir(?:dir)?|addplugindir|appendfile|cd|define|delfilefile|echo(?:message)?|else|endif|error|execute|if(?:macro)?n?(?:def)?|include|insertmacro|macro(?:end)?|packhdr|search(?:parse|replace)|system|tempfilesymbol|undef|verbose|warning)\b"><token type="CommentPreproc"/></rule>
|
||||
</state>
|
||||
<state name="interpol">
|
||||
<rule pattern="\$(R?[0-9])"><token type="NameBuiltinPseudo"/></rule>
|
||||
<rule pattern="\$(ADMINTOOLS|APPDATA|CDBURN_AREA|COOKIES|COMMONFILES(?:32|64)|DESKTOP|DOCUMENTS|EXE(?:DIR|FILE|PATH)|FAVORITES|FONTS|HISTORY|HWNDPARENT|INTERNET_CACHE|LOCALAPPDATA|MUSIC|NETHOOD|PICTURES|PLUGINSDIR|PRINTHOOD|PROFILE|PROGRAMFILES(?:32|64)|QUICKLAUNCH|RECENT|RESOURCES(?:_LOCALIZED)?|SENDTO|SM(?:PROGRAMS|STARTUP)|STARTMENU|SYSDIR|TEMP(?:LATES)?|VIDEOS|WINDIR|\{NSISDIR\})"><token type="NameBuiltin"/></rule>
|
||||
<rule pattern="\$(CMDLINE|INSTDIR|OUTDIR|LANGUAGE)"><token type="NameVariableGlobal"/></rule>
|
||||
<rule pattern="\$[a-z_]\w*"><token type="NameVariable"/></rule>
|
||||
</state>
|
||||
<state name="str_double">
|
||||
<rule pattern="""><token type="LiteralStringDouble"/><pop depth="1"/></rule>
|
||||
<rule pattern="\$(\\[nrt"]|\$)"><token type="LiteralStringEscape"/></rule>
|
||||
<rule><include state="interpol"/></rule>
|
||||
<rule pattern="[^"]+"><token type="LiteralStringDouble"/></rule>
|
||||
</state>
|
||||
<state name="str_backtick">
|
||||
<rule pattern="`"><token type="LiteralStringDouble"/><pop depth="1"/></rule>
|
||||
<rule pattern="\$(\\[nrt"]|\$)"><token type="LiteralStringEscape"/></rule>
|
||||
<rule><include state="interpol"/></rule>
|
||||
<rule pattern="[^`]+"><token type="LiteralStringDouble"/></rule>
|
||||
</state>
|
||||
</rules>
|
||||
</lexer>
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
|
||||
<lexer>
|
||||
<config>
|
||||
<name>SNBT</name>
|
||||
<alias>snbt</alias>
|
||||
<filename>*.snbt</filename>
|
||||
<mime_type>text/snbt</mime_type>
|
||||
</config>
|
||||
<rules>
|
||||
<state name="root">
|
||||
<rule pattern="\{"><token type="Punctuation"/><push state="compound"/></rule>
|
||||
<rule pattern="[^\{]+"><token type="Text"/></rule>
|
||||
</state>
|
||||
<state name="whitespace">
|
||||
<rule pattern="\s+"><token type="TextWhitespace"/></rule>
|
||||
</state>
|
||||
<state name="operators">
|
||||
<rule pattern="[,:;]"><token type="Punctuation"/></rule>
|
||||
</state>
|
||||
<state name="literals">
|
||||
<rule pattern="(true|false)"><token type="KeywordConstant"/></rule>
|
||||
<rule pattern="-?\d+[eE]-?\d+"><token type="LiteralNumberFloat"/></rule>
|
||||
<rule pattern="-?\d*\.\d+[fFdD]?"><token type="LiteralNumberFloat"/></rule>
|
||||
<rule pattern="-?\d+[bBsSlLfFdD]?"><token type="LiteralNumberInteger"/></rule>
|
||||
<rule pattern="""><token type="LiteralStringDouble"/><push state="literals.string_double"/></rule>
|
||||
<rule pattern="'"><token type="LiteralStringSingle"/><push state="literals.string_single"/></rule>
|
||||
</state>
|
||||
<state name="literals.string_double">
|
||||
<rule pattern="\\."><token type="LiteralStringEscape"/></rule>
|
||||
<rule pattern="[^\\"\n]+"><token type="LiteralStringDouble"/></rule>
|
||||
<rule pattern="""><token type="LiteralStringDouble"/><pop depth="1"/></rule>
|
||||
</state>
|
||||
<state name="literals.string_single">
|
||||
<rule pattern="\\."><token type="LiteralStringEscape"/></rule>
|
||||
<rule pattern="[^\\'\n]+"><token type="LiteralStringSingle"/></rule>
|
||||
<rule pattern="'"><token type="LiteralStringSingle"/><pop depth="1"/></rule>
|
||||
</state>
|
||||
<state name="compound">
|
||||
<rule pattern="[A-Z_a-z]+"><token type="NameAttribute"/></rule>
|
||||
<rule><include state="operators"/></rule>
|
||||
<rule><include state="whitespace"/></rule>
|
||||
<rule><include state="literals"/></rule>
|
||||
<rule pattern="\{"><token type="Punctuation"/><push/></rule>
|
||||
<rule pattern="\["><token type="Punctuation"/><push state="list"/></rule>
|
||||
<rule pattern="\}"><token type="Punctuation"/><pop depth="1"/></rule>
|
||||
</state>
|
||||
<state name="list">
|
||||
<rule pattern="[A-Z_a-z]+"><token type="NameAttribute"/></rule>
|
||||
<rule><include state="literals"/></rule>
|
||||
<rule><include state="operators"/></rule>
|
||||
<rule><include state="whitespace"/></rule>
|
||||
<rule pattern="\["><token type="Punctuation"/><push/></rule>
|
||||
<rule pattern="\{"><token type="Punctuation"/><push state="compound"/></rule>
|
||||
<rule pattern="\]"><token type="Punctuation"/><pop depth="1"/></rule>
|
||||
</state>
|
||||
</rules>
|
||||
</lexer>
|
||||
|
||||
+18
-2
@@ -51,6 +51,22 @@
|
||||
</rule>
|
||||
</state>
|
||||
<state name="tag">
|
||||
<rule>
|
||||
<include state="jsx"/>
|
||||
</rule>
|
||||
<rule pattern=",">
|
||||
<token type="Punctuation"/>
|
||||
</rule>
|
||||
<rule pattern=""(\\\\|\\"|[^"])*"">
|
||||
<token type="LiteralStringDouble"/>
|
||||
</rule>
|
||||
<rule pattern="'(\\\\|\\'|[^'])*'">
|
||||
<token type="LiteralStringSingle"/>
|
||||
</rule>
|
||||
<rule pattern="`">
|
||||
<token type="LiteralStringBacktick"/>
|
||||
<push state="interp"/>
|
||||
</rule>
|
||||
<rule>
|
||||
<include state="commentsandwhitespace"/>
|
||||
</rule>
|
||||
@@ -171,7 +187,7 @@
|
||||
</rule>
|
||||
<rule pattern="(?=/)">
|
||||
<token type="Text"/>
|
||||
<push state="#pop" state="badregex"/>
|
||||
<push state="badregex"/>
|
||||
</rule>
|
||||
<rule>
|
||||
<pop depth="1"/>
|
||||
@@ -276,4 +292,4 @@
|
||||
</rule>
|
||||
</state>
|
||||
</rules>
|
||||
</lexer>
|
||||
</lexer>
|
||||
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
|
||||
<lexer>
|
||||
<config>
|
||||
<name>Typst</name>
|
||||
<alias>typst</alias>
|
||||
<filename>*.typ</filename>
|
||||
<mime_type>text/x-typst</mime_type>
|
||||
</config>
|
||||
<rules>
|
||||
<state name="root">
|
||||
<rule><include state="markup"/></rule>
|
||||
</state>
|
||||
<state name="into_code">
|
||||
<rule pattern="(\#let|\#set|\#show)\b"><token type="KeywordDeclaration"/><push state="inline_code"/></rule>
|
||||
<rule pattern="(\#import|\#include)\b"><token type="KeywordNamespace"/><push state="inline_code"/></rule>
|
||||
<rule pattern="(\#if|\#for|\#while|\#export)\b"><token type="KeywordReserved"/><push state="inline_code"/></rule>
|
||||
<rule pattern="#\{"><token type="Punctuation"/><push state="code"/></rule>
|
||||
<rule pattern="#\("><token type="Punctuation"/><push state="code"/></rule>
|
||||
<rule pattern="(#[a-zA-Z_][a-zA-Z0-9_-]*)(\[)"><bygroups><token type="NameFunction"/><token type="Punctuation"/></bygroups><push state="markup"/></rule>
|
||||
<rule pattern="(#[a-zA-Z_][a-zA-Z0-9_-]*)(\()"><bygroups><token type="NameFunction"/><token type="Punctuation"/></bygroups><push state="code"/></rule>
|
||||
<rule pattern="(\#true|\#false|\#none|\#auto)\b"><token type="KeywordConstant"/></rule>
|
||||
<rule pattern="#[a-zA-Z_][a-zA-Z0-9_]*"><token type="NameVariable"/></rule>
|
||||
<rule pattern="#0x[0-9a-fA-F]+"><token type="LiteralNumberHex"/></rule>
|
||||
<rule pattern="#0b[01]+"><token type="LiteralNumberBin"/></rule>
|
||||
<rule pattern="#0o[0-7]+"><token type="LiteralNumberOct"/></rule>
|
||||
<rule pattern="#[0-9]+[\.e][0-9]+"><token type="LiteralNumberFloat"/></rule>
|
||||
<rule pattern="#[0-9]+"><token type="LiteralNumberInteger"/></rule>
|
||||
</state>
|
||||
<state name="markup">
|
||||
<rule><include state="comment"/></rule>
|
||||
<rule pattern="^\s*=+.*$"><token type="GenericHeading"/></rule>
|
||||
<rule pattern="[*][^*]*[*]"><token type="GenericStrong"/></rule>
|
||||
<rule pattern="_[^_]*_"><token type="GenericEmph"/></rule>
|
||||
<rule pattern="\$"><token type="Punctuation"/><push state="math"/></rule>
|
||||
<rule pattern="`[^`]*`"><token type="LiteralStringBacktick"/></rule>
|
||||
<rule pattern="^(\s*)(-)(\s+)"><bygroups><token type="TextWhitespace"/><token type="Punctuation"/><token type="TextWhitespace"/></bygroups></rule>
|
||||
<rule pattern="^(\s*)(\+)(\s+)"><bygroups><token type="TextWhitespace"/><token type="Punctuation"/><token type="TextWhitespace"/></bygroups></rule>
|
||||
<rule pattern="^(\s*)([0-9]+\.)"><bygroups><token type="TextWhitespace"/><token type="Punctuation"/></bygroups></rule>
|
||||
<rule pattern="^(\s*)(/)(\s+)([^:]+)(:)"><bygroups><token type="TextWhitespace"/><token type="Punctuation"/><token type="TextWhitespace"/><token type="NameVariable"/><token type="Punctuation"/></bygroups></rule>
|
||||
<rule pattern="<[a-zA-Z_][a-zA-Z0-9_-]*>"><token type="NameLabel"/></rule>
|
||||
<rule pattern="@[a-zA-Z_][a-zA-Z0-9_-]*"><token type="NameLabel"/></rule>
|
||||
<rule pattern="\\#"><token type="Text"/></rule>
|
||||
<rule><include state="into_code"/></rule>
|
||||
<rule pattern="```(?:.|\n)*?```"><token type="LiteralStringBacktick"/></rule>
|
||||
<rule pattern="https?://[0-9a-zA-Z~/%#&=\',;.+?]*"><token type="GenericEmph"/></rule>
|
||||
<rule pattern="(\-\-\-|\\|\~|\-\-|\.\.\.)\B"><token type="Punctuation"/></rule>
|
||||
<rule pattern="\\\["><token type="Punctuation"/></rule>
|
||||
<rule pattern="\\\]"><token type="Punctuation"/></rule>
|
||||
<rule pattern="\["><token type="Punctuation"/><push/></rule>
|
||||
<rule pattern="\]"><token type="Punctuation"/><pop depth="1"/></rule>
|
||||
<rule pattern="[ \t]+\n?|\n"><token type="TextWhitespace"/></rule>
|
||||
<rule pattern="((?![*_$`<@\\#\] ]|https?://).)+"><token type="Text"/></rule>
|
||||
</state>
|
||||
<state name="math">
|
||||
<rule><include state="comment"/></rule>
|
||||
<rule pattern="(\\_|\\\^|\\\&)"><token type="Text"/></rule>
|
||||
<rule pattern="(_|\^|\&|;)"><token type="Punctuation"/></rule>
|
||||
<rule pattern="(\+|/|=|\[\||\|\]|\|\||\*|:=|::=|\.\.\.|'|\-|=:|!=|>>|>=|>>>|<<|<=|<<<|\->|\|\->|=>|\|=>|==>|\-\->|\~\~>|\~>|>\->|\->>|<\-|<==|<\-\-|<\~\~|<\~|<\-<|<<\-|<\->|<=>|<==>|<\-\->|>|<|\~|:|\|)"><token type="Operator"/></rule>
|
||||
<rule pattern="\\"><token type="Punctuation"/></rule>
|
||||
<rule pattern="\\\$"><token type="Punctuation"/></rule>
|
||||
<rule pattern="\$"><token type="Punctuation"/><pop depth="1"/></rule>
|
||||
<rule><include state="into_code"/></rule>
|
||||
<rule pattern="([a-zA-Z][a-zA-Z0-9-]*)(\s*)(\()"><bygroups><token type="NameFunction"/><token type="TextWhitespace"/><token type="Punctuation"/></bygroups></rule>
|
||||
<rule pattern="([a-zA-Z][a-zA-Z0-9-]*)(:)"><bygroups><token type="NameVariable"/><token type="Punctuation"/></bygroups></rule>
|
||||
<rule pattern="([a-zA-Z][a-zA-Z0-9-]*)"><token type="NameVariable"/></rule>
|
||||
<rule pattern="[0-9]+(\.[0-9]+)?"><token type="LiteralNumber"/></rule>
|
||||
<rule pattern="\.{1,3}|\(|\)|,|\{|\}"><token type="Punctuation"/></rule>
|
||||
<rule pattern=""[^"]*""><token type="LiteralStringDouble"/></rule>
|
||||
<rule pattern="[ \t\n]+"><token type="TextWhitespace"/></rule>
|
||||
</state>
|
||||
<state name="comment">
|
||||
<rule pattern="//.*$"><token type="CommentSingle"/></rule>
|
||||
<rule pattern="/[*](.|\n)*?[*]/"><token type="CommentMultiline"/></rule>
|
||||
</state>
|
||||
<state name="code">
|
||||
<rule><include state="comment"/></rule>
|
||||
<rule pattern="\["><token type="Punctuation"/><push state="markup"/></rule>
|
||||
<rule pattern="\(|\{"><token type="Punctuation"/><push state="code"/></rule>
|
||||
<rule pattern="\)|\}"><token type="Punctuation"/><pop depth="1"/></rule>
|
||||
<rule pattern=""[^"]*""><token type="LiteralStringDouble"/></rule>
|
||||
<rule pattern=",|\.{1,2}"><token type="Punctuation"/></rule>
|
||||
<rule pattern="="><token type="Operator"/></rule>
|
||||
<rule pattern="(and|or|not)\b"><token type="OperatorWord"/></rule>
|
||||
<rule pattern="=>|<=|==|!=|>|<|-=|\+=|\*=|/=|\+|-|\\|\*"><token type="Operator"/></rule>
|
||||
<rule pattern="([a-zA-Z_][a-zA-Z0-9_-]*)(:)"><bygroups><token type="NameVariable"/><token type="Punctuation"/></bygroups></rule>
|
||||
<rule pattern="([a-zA-Z_][a-zA-Z0-9_-]*)(\()"><bygroups><token type="NameFunction"/><token type="Punctuation"/></bygroups><push state="code"/></rule>
|
||||
<rule pattern="(as|break|export|continue|else|for|if|in|return|while)\b"><token type="KeywordReserved"/></rule>
|
||||
<rule pattern="(import|include)\b"><token type="KeywordNamespace"/></rule>
|
||||
<rule pattern="(auto|none|true|false)\b"><token type="KeywordConstant"/></rule>
|
||||
<rule pattern="([0-9.]+)(mm|pt|cm|in|em|fr|%)"><bygroups><token type="LiteralNumber"/><token type="KeywordReserved"/></bygroups></rule>
|
||||
<rule pattern="0x[0-9a-fA-F]+"><token type="LiteralNumberHex"/></rule>
|
||||
<rule pattern="0b[01]+"><token type="LiteralNumberBin"/></rule>
|
||||
<rule pattern="0o[0-7]+"><token type="LiteralNumberOct"/></rule>
|
||||
<rule pattern="[0-9]+[\.e][0-9]+"><token type="LiteralNumberFloat"/></rule>
|
||||
<rule pattern="[0-9]+"><token type="LiteralNumberInteger"/></rule>
|
||||
<rule pattern="(let|set|show)\b"><token type="KeywordDeclaration"/></rule>
|
||||
<rule pattern="([a-zA-Z_][a-zA-Z0-9_-]*)"><token type="NameVariable"/></rule>
|
||||
<rule pattern="[ \t\n]+"><token type="TextWhitespace"/></rule>
|
||||
<rule pattern=":"><token type="Punctuation"/></rule>
|
||||
</state>
|
||||
<state name="inline_code">
|
||||
<rule pattern=";\b"><token type="Punctuation"/><pop depth="1"/></rule>
|
||||
<rule pattern="\n"><token type="TextWhitespace"/><pop depth="1"/></rule>
|
||||
<rule><include state="code"/></rule>
|
||||
</state>
|
||||
</rules>
|
||||
</lexer>
|
||||
|
||||
+283
@@ -0,0 +1,283 @@
|
||||
<lexer>
|
||||
<config>
|
||||
<name>WebVTT</name>
|
||||
<alias>vtt</alias>
|
||||
<filename>*.vtt</filename>
|
||||
<mime_type>text/vtt</mime_type>
|
||||
</config>
|
||||
<!--
|
||||
The WebVTT spec refers to a WebVTT line terminator as either CRLF, CR or LF.
|
||||
(https://www.w3.org/TR/webvtt1/#webvtt-line-terminator) However, with this
|
||||
definition it is unclear whether CRLF is one line terminator (CRLF) or two
|
||||
line terminators (CR and LF).
|
||||
|
||||
To work around this ambiguity, only CRLF and LF are considered as line terminators.
|
||||
To my knowledge only classic Mac OS uses CR as line terminators, so the lexer should
|
||||
still work for most files.
|
||||
-->
|
||||
<rules>
|
||||
<!-- https://www.w3.org/TR/webvtt1/#webvtt-file-body -->
|
||||
<state name="root">
|
||||
<rule pattern="(\AWEBVTT)((?:[ \t][^\r\n]*)?(?:\r?\n){2,})">
|
||||
<bygroups>
|
||||
<token type="Keyword" />
|
||||
<token type="Text" />
|
||||
</bygroups>
|
||||
</rule>
|
||||
<rule pattern="(^REGION)([ \t]*$)">
|
||||
<bygroups>
|
||||
<token type="Keyword" />
|
||||
<token type="Text" />
|
||||
</bygroups>
|
||||
<push state="region-settings-list" />
|
||||
</rule>
|
||||
<rule
|
||||
pattern="(^STYLE)([ \t]*$)((?:(?!-->)[\s\S])*?)((?:\r?\n){2})">
|
||||
<bygroups>
|
||||
<token type="Keyword" />
|
||||
<token type="Text" />
|
||||
<using lexer="CSS" />
|
||||
<token type="Text" />
|
||||
</bygroups>
|
||||
</rule>
|
||||
<rule>
|
||||
<include state="comment" />
|
||||
</rule>
|
||||
<rule
|
||||
pattern="(?=((?![^\r\n]*-->)[^\r\n]*\r?\n)?(\d{2}:)?(?:[0-5][0-9]):(?:[0-5][0-9])\.\d{3}[ \t]+-->[ \t]+(\d{2}:)?(?:[0-5][0-9]):(?:[0-5][0-9])\.\d{3})"
|
||||
>
|
||||
<push state="cues" />
|
||||
</rule>
|
||||
</state>
|
||||
|
||||
<!-- https://www.w3.org/TR/webvtt1/#webvtt-region-settings-list -->
|
||||
<state name="region-settings-list">
|
||||
<rule pattern="(?: |\t|\r?\n(?!\r?\n))+">
|
||||
<token type="Text" />
|
||||
</rule>
|
||||
<rule pattern="(?:\r?\n){2}">
|
||||
<token type="Text" />
|
||||
<pop depth="1" />
|
||||
</rule>
|
||||
<rule pattern="(id)(:)(?!-->)(\S+)">
|
||||
<bygroups>
|
||||
<token type="Keyword" />
|
||||
<token type="Punctuation" />
|
||||
<token type="Literal" />
|
||||
</bygroups>
|
||||
</rule>
|
||||
<rule pattern="(width)(:)((?:[1-9]?\d|100)(?:\.\d+)?)(%)">
|
||||
<bygroups>
|
||||
<token type="Keyword" />
|
||||
<token type="Punctuation" />
|
||||
<token type="Literal" />
|
||||
<token type="KeywordType" />
|
||||
</bygroups>
|
||||
</rule>
|
||||
<rule pattern="(lines)(:)(\d+)">
|
||||
<bygroups>
|
||||
<token type="Keyword" />
|
||||
<token type="Punctuation" />
|
||||
<token type="Literal" />
|
||||
</bygroups>
|
||||
</rule>
|
||||
<rule
|
||||
pattern="(regionanchor|viewportanchor)(:)((?:[1-9]?\d|100)(?:\.\d+)?)(%)(,)((?:[1-9]?\d|100)(?:\.\d+)?)(%)">
|
||||
<bygroups>
|
||||
<token type="Keyword" />
|
||||
<token type="Punctuation" />
|
||||
<token type="Literal" />
|
||||
<token type="KeywordType" />
|
||||
<token type="Punctuation" />
|
||||
<token type="Literal" />
|
||||
<token type="KeywordType" />
|
||||
</bygroups>
|
||||
</rule>
|
||||
<rule pattern="(scroll)(:)(up)">
|
||||
<bygroups>
|
||||
<token type="Keyword" />
|
||||
<token type="Punctuation" />
|
||||
<token type="KeywordConstant" />
|
||||
</bygroups>
|
||||
</rule>
|
||||
</state>
|
||||
|
||||
<!-- https://www.w3.org/TR/webvtt1/#webvtt-comment-block -->
|
||||
<state name="comment">
|
||||
<rule
|
||||
pattern="^NOTE( |\t|\r?\n)((?!-->)[\s\S])*?(?:(\r?\n){2}|\Z)">
|
||||
<token type="Comment" />
|
||||
</rule>
|
||||
</state>
|
||||
|
||||
<!--
|
||||
"Zero or more WebVTT cue blocks and WebVTT comment blocks separated from each other by one or more
|
||||
WebVTT line terminators." (https://www.w3.org/TR/webvtt1/#file-structure)
|
||||
-->
|
||||
<state name="cues">
|
||||
<rule
|
||||
pattern="(?:((?!-->)[^\r\n]+)?(\r?\n))?((?:\d{2}:)?(?:[0-5][0-9]):(?:[0-5][0-9])\.\d{3})([ \t]+)(-->)([ \t]+)((?:\d{2}:)?(?:[0-5][0-9]):(?:[0-5][0-9])\.\d{3})([ \t]*)">
|
||||
<bygroups>
|
||||
<token type="Name" />
|
||||
<token type="Text" />
|
||||
<token type="LiteralDate" />
|
||||
<token type="Text" />
|
||||
<token type="Operator" />
|
||||
<token type="Text" />
|
||||
<token type="LiteralDate" />
|
||||
<token type="Text" />
|
||||
</bygroups>
|
||||
<push state="cue-settings-list" />
|
||||
</rule>
|
||||
<rule>
|
||||
<include state="comment" />
|
||||
</rule>
|
||||
</state>
|
||||
|
||||
<!-- https://www.w3.org/TR/webvtt1/#webvtt-cue-settings-list -->
|
||||
<state name="cue-settings-list">
|
||||
<rule pattern="[ \t]+">
|
||||
<token type="Text" />
|
||||
</rule>
|
||||
<rule pattern="(vertical)(:)?(rl|lr)?">
|
||||
<bygroups>
|
||||
<token type="Keyword" />
|
||||
<token type="Punctuation" />
|
||||
<token type="KeywordConstant" />
|
||||
</bygroups>
|
||||
</rule>
|
||||
<rule
|
||||
pattern="(line)(:)?(?:(?:((?:[1-9]?\d|100)(?:\.\d+)?)(%)|(-?\d+))(?:(,)(start|center|end))?)?">
|
||||
<bygroups>
|
||||
<token type="Keyword" />
|
||||
<token type="Punctuation" />
|
||||
<token type="Literal" />
|
||||
<token type="KeywordType" />
|
||||
<token type="Literal" />
|
||||
<token type="Punctuation" />
|
||||
<token type="KeywordConstant" />
|
||||
</bygroups>
|
||||
</rule>
|
||||
<rule
|
||||
pattern="(position)(:)?(?:(?:((?:[1-9]?\d|100)(?:\.\d+)?)(%)|(-?\d+))(?:(,)(line-left|center|line-right))?)?">
|
||||
<bygroups>
|
||||
<token type="Keyword" />
|
||||
<token type="Punctuation" />
|
||||
<token type="Literal" />
|
||||
<token type="KeywordType" />
|
||||
<token type="Literal" />
|
||||
<token type="Punctuation" />
|
||||
<token type="KeywordConstant" />
|
||||
</bygroups>
|
||||
</rule>
|
||||
<rule pattern="(size)(:)?(?:((?:[1-9]?\d|100)(?:\.\d+)?)(%))?">
|
||||
<bygroups>
|
||||
<token type="Keyword" />
|
||||
<token type="Punctuation" />
|
||||
<token type="Literal" />
|
||||
<token type="KeywordType" />
|
||||
</bygroups>
|
||||
</rule>
|
||||
<rule pattern="(align)(:)?(start|center|end|left|right)?">
|
||||
<bygroups>
|
||||
<token type="Keyword" />
|
||||
<token type="Punctuation" />
|
||||
<token type="KeywordConstant" />
|
||||
</bygroups>
|
||||
</rule>
|
||||
<rule pattern="(region)(:)?((?![^\r\n]*-->(?=[ \t]+?))[^ \t\r\n]+)?">
|
||||
<bygroups>
|
||||
<token type="Keyword" />
|
||||
<token type="Punctuation" />
|
||||
<token type="Literal" />
|
||||
</bygroups>
|
||||
</rule>
|
||||
<rule
|
||||
pattern="(?=\r?\n)">
|
||||
<push state="cue-payload" />
|
||||
</rule>
|
||||
</state>
|
||||
|
||||
<!-- https://www.w3.org/TR/webvtt1/#cue-payload -->
|
||||
<state name="cue-payload">
|
||||
<rule pattern="(\r?\n){2,}">
|
||||
<token type="Text" />
|
||||
<pop depth="2" />
|
||||
</rule>
|
||||
<rule pattern="[^<&]+?">
|
||||
<token type="Text" />
|
||||
</rule>
|
||||
<rule pattern="&(#\d+|#x[0-9A-Fa-f]+|[a-zA-Z0-9]+);">
|
||||
<token type="Text" />
|
||||
</rule>
|
||||
<rule pattern="(?=<)">
|
||||
<token type="Text" />
|
||||
<push state="cue-span-tag" />
|
||||
</rule>
|
||||
</state>
|
||||
<state name="cue-span-tag">
|
||||
<rule
|
||||
pattern="<(?=c|i|b|u|ruby|rt|v|lang|(?:\d{2}:)?(?:[0-5][0-9]):(?:[0-5][0-9])\.\d{3})">
|
||||
<token type="Punctuation" />
|
||||
<push state="cue-span-start-tag-name" />
|
||||
</rule>
|
||||
<rule pattern="(</)(c|i|b|u|ruby|rt|v|lang)">
|
||||
<bygroups>
|
||||
<token type="Punctuation" />
|
||||
<token type="NameTag" />
|
||||
</bygroups>
|
||||
</rule>
|
||||
<rule pattern=">">
|
||||
<token type="Punctuation" />
|
||||
<pop depth="1" />
|
||||
</rule>
|
||||
</state>
|
||||
<state name="cue-span-start-tag-name">
|
||||
<rule pattern="(c|i|b|u|ruby|rt)|((?:\d{2}:)?(?:[0-5][0-9]):(?:[0-5][0-9])\.\d{3})">
|
||||
<bygroups>
|
||||
<token type="NameTag" />
|
||||
<token type="LiteralDate" />
|
||||
</bygroups>
|
||||
<push state="cue-span-classes-without-annotations" />
|
||||
</rule>
|
||||
<rule pattern="v|lang">
|
||||
<token type="NameTag" />
|
||||
<push state="cue-span-classes-with-annotations" />
|
||||
</rule>
|
||||
</state>
|
||||
<state name="cue-span-classes-without-annotations">
|
||||
<rule>
|
||||
<include state="cue-span-classes" />
|
||||
</rule>
|
||||
<rule pattern="(?=>)">
|
||||
<pop depth="2" />
|
||||
</rule>
|
||||
</state>
|
||||
<state name="cue-span-classes-with-annotations">
|
||||
<rule>
|
||||
<include state="cue-span-classes" />
|
||||
</rule>
|
||||
<rule pattern="(?=[ \t])">
|
||||
<push state="cue-span-start-tag-annotations" />
|
||||
</rule>
|
||||
</state>
|
||||
<state name="cue-span-classes">
|
||||
<rule pattern="(\.)([^ \t\n\r&<>\.]+)">
|
||||
<bygroups>
|
||||
<token type="Punctuation" />
|
||||
<token type="NameTag" />
|
||||
</bygroups>
|
||||
</rule>
|
||||
</state>
|
||||
<state name="cue-span-start-tag-annotations">
|
||||
<rule
|
||||
pattern="[ \t](?:[^\n\r&>]|&(?:#\d+|#x[0-9A-Fa-f]+|[a-zA-Z0-9]+);)+">
|
||||
<token type="Text" />
|
||||
</rule>
|
||||
<rule pattern="(?=>)">
|
||||
<token type="Text" />
|
||||
<pop depth="3" />
|
||||
</rule>
|
||||
</state>
|
||||
</rules>
|
||||
</lexer>
|
||||
+1
-1
@@ -53,7 +53,7 @@
|
||||
<bygroups>
|
||||
<token type="Punctuation"/>
|
||||
<token type="LiteralStringDoc"/>
|
||||
<token type="TextWhitespace"/>
|
||||
<token type="Ignore"/>
|
||||
</bygroups>
|
||||
</rule>
|
||||
<rule pattern="(false|False|FALSE|true|True|TRUE|null|Off|off|yes|Yes|YES|OFF|On|ON|no|No|on|NO|n|N|Y|y)\b">
|
||||
|
||||
+2
-3
@@ -13,7 +13,6 @@ var Go = Register(MustNewLexer(
|
||||
Aliases: []string{"go", "golang"},
|
||||
Filenames: []string{"*.go"},
|
||||
MimeTypes: []string{"text/x-gosrc"},
|
||||
EnsureNL: true,
|
||||
},
|
||||
goRules,
|
||||
).SetAnalyser(func(text string) float32 {
|
||||
@@ -32,14 +31,14 @@ func goRules() Rules {
|
||||
{`\n`, Text, nil},
|
||||
{`\s+`, Text, nil},
|
||||
{`\\\n`, Text, nil},
|
||||
{`//(.*?)\n`, CommentSingle, nil},
|
||||
{`//[^\n\r]*`, CommentSingle, nil},
|
||||
{`/(\\\n)?[*](.|\n)*?[*](\\\n)?/`, CommentMultiline, nil},
|
||||
{`(import|package)\b`, KeywordNamespace, nil},
|
||||
{`(var|func|struct|map|chan|type|interface|const)\b`, KeywordDeclaration, nil},
|
||||
{Words(``, `\b`, `break`, `default`, `select`, `case`, `defer`, `go`, `else`, `goto`, `switch`, `fallthrough`, `if`, `range`, `continue`, `for`, `return`), Keyword, nil},
|
||||
{`(true|false|iota|nil)\b`, KeywordConstant, nil},
|
||||
{Words(``, `\b(\()`, `uint`, `uint8`, `uint16`, `uint32`, `uint64`, `int`, `int8`, `int16`, `int32`, `int64`, `float`, `float32`, `float64`, `complex64`, `complex128`, `byte`, `rune`, `string`, `bool`, `error`, `uintptr`, `print`, `println`, `panic`, `recover`, `close`, `complex`, `real`, `imag`, `len`, `cap`, `append`, `copy`, `delete`, `new`, `make`, `clear`, `min`, `max`), ByGroups(NameBuiltin, Punctuation), nil},
|
||||
{Words(``, `\b`, `uint`, `uint8`, `uint16`, `uint32`, `uint64`, `int`, `int8`, `int16`, `int32`, `int64`, `float`, `float32`, `float64`, `complex64`, `complex128`, `byte`, `rune`, `string`, `bool`, `error`, `uintptr`), KeywordType, nil},
|
||||
{Words(``, `\b`, `uint`, `uint8`, `uint16`, `uint32`, `uint64`, `int`, `int8`, `int16`, `int32`, `int64`, `float`, `float32`, `float64`, `complex64`, `complex128`, `byte`, `rune`, `string`, `bool`, `error`, `uintptr`, `any`), KeywordType, nil},
|
||||
{`\d+i`, LiteralNumber, nil},
|
||||
{`\d+\.\d*([Ee][-+]\d+)?i`, LiteralNumber, nil},
|
||||
{`\.\d+([Ee][-+]\d+)?i`, LiteralNumber, nil},
|
||||
|
||||
-1
@@ -40,7 +40,6 @@ func markdownRules() Rules {
|
||||
{"`[^`]+`", LiteralStringBacktick, nil},
|
||||
{`[@#][\w/:]+`, NameEntity, nil},
|
||||
{`(!?\[)([^]]+)(\])(\()([^)]+)(\))`, ByGroups(Text, NameTag, Text, Text, NameAttribute, Text), nil},
|
||||
{`[^\\\s]+`, Other, nil},
|
||||
{`.|\n`, Other, nil},
|
||||
},
|
||||
}
|
||||
|
||||
+6
@@ -194,6 +194,9 @@ func (l *LexerState) Iterator() Token { // nolint: gocognit
|
||||
for len(l.iteratorStack) > 0 {
|
||||
n := len(l.iteratorStack) - 1
|
||||
t := l.iteratorStack[n]()
|
||||
if t.Type == Ignore {
|
||||
continue
|
||||
}
|
||||
if t == EOF {
|
||||
l.iteratorStack = l.iteratorStack[:n]
|
||||
continue
|
||||
@@ -243,6 +246,9 @@ func (l *LexerState) Iterator() Token { // nolint: gocognit
|
||||
for len(l.iteratorStack) > 0 {
|
||||
n := len(l.iteratorStack) - 1
|
||||
t := l.iteratorStack[n]()
|
||||
if t.Type == Ignore {
|
||||
continue
|
||||
}
|
||||
if t == EOF {
|
||||
l.iteratorStack = l.iteratorStack[:n]
|
||||
continue
|
||||
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
<style name="evergarden">
|
||||
<entry type="Background" style="noinherit #D6CBB4 bg:#252B2E"/>
|
||||
<entry type="Keyword" style="noinherit #E67E80"/>
|
||||
<entry type="KeywordType" style="noinherit #DBBC7F"/>
|
||||
<entry type="Name" style="#D6CBB4"/>
|
||||
<entry type="NameAttribute" style="bold #D699B6"/>
|
||||
<entry type="NameBuiltin" style="#D699B6"/>
|
||||
<entry type="NameConstant" style="noinherit #D699B6"/>
|
||||
<entry type="NameEntity" style="noinherit #DBBC7F"/>
|
||||
<entry type="NameException" style="noinherit #E67E80"/>
|
||||
<entry type="NameFunction" style="#B2C98F"/>
|
||||
<entry type="NameLabel" style="noinherit #E67E80"/>
|
||||
<entry type="NameTag" style="noinherit #7a8478"/>
|
||||
<entry type="NameVariable" style="noinherit #D6CBB4"/>
|
||||
<entry type="LiteralString" style="noinherit #B2C98F"/>
|
||||
<entry type="LiteralStringSymbol" style="#E69875"/>
|
||||
<entry type="LiteralNumber" style="noinherit #D699B6"/>
|
||||
<entry type="LiteralNumberFloat" style="noinherit #D699B6"/>
|
||||
<entry type="Operator" style="#7a8478"/>
|
||||
<entry type="Comment" style="italic #859289"/>
|
||||
<entry type="CommentPreproc" style="noinherit #E67E80"/>
|
||||
<entry type="Generic" style="#D6CBB4"/>
|
||||
<entry type="GenericDeleted" style="noinherit #252B2E bg:#E67E80"/>
|
||||
<entry type="GenericEmph" style="#6E8585"/>
|
||||
<entry type="GenericError" style="bold bg:#E67E80"/>
|
||||
<entry type="GenericHeading" style="bold #D699B6"/>
|
||||
<entry type="GenericInserted" style="noinherit #252B2E bg:#B2C98F"/>
|
||||
<entry type="GenericOutput" style="noinherit #6E8585"/>
|
||||
<entry type="GenericPrompt" style="#D6CBB4"/>
|
||||
<entry type="GenericStrong" style="#D6CBB4"/>
|
||||
<entry type="GenericSubheading" style="bold #B2C98F"/>
|
||||
<entry type="GenericTraceback" style="bold bg:#E67E80"/>
|
||||
</style>
|
||||
+34
-39
@@ -1,44 +1,39 @@
|
||||
<style name="github">
|
||||
<entry type="Error" style="#a61717 bg:#e3d2d2"/>
|
||||
<entry type="Error" style="#f6f8fa bg:#82071e"/>
|
||||
<entry type="Background" style="bg:#ffffff"/>
|
||||
<entry type="Keyword" style="bold #000000"/>
|
||||
<entry type="KeywordType" style="bold #445588"/>
|
||||
<entry type="NameAttribute" style="#008080"/>
|
||||
<entry type="NameBuiltin" style="#0086b3"/>
|
||||
<entry type="NameBuiltinPseudo" style="#999999"/>
|
||||
<entry type="NameClass" style="bold #445588"/>
|
||||
<entry type="NameConstant" style="#008080"/>
|
||||
<entry type="NameDecorator" style="bold #3c5d5d"/>
|
||||
<entry type="NameEntity" style="#800080"/>
|
||||
<entry type="NameException" style="bold #990000"/>
|
||||
<entry type="NameFunction" style="bold #990000"/>
|
||||
<entry type="Keyword" style="#cf222e"/>
|
||||
<entry type="KeywordType" style="#cf222e"/>
|
||||
<entry type="NameAttribute" style="#1f2328"/>
|
||||
<entry type="NameBuiltin" style="#6639ba"/>
|
||||
<entry type="NameBuiltinPseudo" style="#6a737d"/>
|
||||
<entry type="NameClass" style="#1f2328"/>
|
||||
<entry type="NameConstant" style="#0550ae"/>
|
||||
<entry type="NameDecorator" style="#0550ae"/>
|
||||
<entry type="NameEntity" style="#6639ba"/>
|
||||
<entry type="NameFunction" style="#6639ba"/>
|
||||
<entry type="NameLabel" style="bold #990000"/>
|
||||
<entry type="NameNamespace" style="#555555"/>
|
||||
<entry type="NameTag" style="#000080"/>
|
||||
<entry type="NameVariable" style="#008080"/>
|
||||
<entry type="NameVariableClass" style="#008080"/>
|
||||
<entry type="NameVariableGlobal" style="#008080"/>
|
||||
<entry type="NameVariableInstance" style="#008080"/>
|
||||
<entry type="LiteralString" style="#dd1144"/>
|
||||
<entry type="LiteralStringRegex" style="#009926"/>
|
||||
<entry type="LiteralStringSymbol" style="#990073"/>
|
||||
<entry type="LiteralNumber" style="#009999"/>
|
||||
<entry type="Operator" style="bold #000000"/>
|
||||
<entry type="Comment" style="italic #999988"/>
|
||||
<entry type="CommentMultiline" style="italic #999988"/>
|
||||
<entry type="CommentSingle" style="italic #999988"/>
|
||||
<entry type="CommentSpecial" style="bold italic #999999"/>
|
||||
<entry type="CommentPreproc" style="bold #999999"/>
|
||||
<entry type="GenericDeleted" style="#000000 bg:#ffdddd"/>
|
||||
<entry type="GenericEmph" style="italic #000000"/>
|
||||
<entry type="GenericError" style="#aa0000"/>
|
||||
<entry type="GenericHeading" style="#999999"/>
|
||||
<entry type="GenericInserted" style="#000000 bg:#ddffdd"/>
|
||||
<entry type="GenericOutput" style="#888888"/>
|
||||
<entry type="GenericPrompt" style="#555555"/>
|
||||
<entry type="GenericStrong" style="bold"/>
|
||||
<entry type="GenericSubheading" style="#aaaaaa"/>
|
||||
<entry type="GenericTraceback" style="#aa0000"/>
|
||||
<entry type="NameNamespace" style="#24292e"/>
|
||||
<entry type="NameOther" style="#1f2328"/>
|
||||
<entry type="NameTag" style="#0550ae"/>
|
||||
<entry type="NameVariable" style="#953800"/>
|
||||
<entry type="NameVariableClass" style="#953800"/>
|
||||
<entry type="NameVariableGlobal" style="#953800"/>
|
||||
<entry type="NameVariableInstance" style="#953800"/>
|
||||
<entry type="LiteralString" style="#0a3069"/>
|
||||
<entry type="LiteralStringRegex" style="#0a3069"/>
|
||||
<entry type="LiteralStringSymbol" style="#032f62"/>
|
||||
<entry type="LiteralNumber" style="#0550ae"/>
|
||||
<entry type="Operator" style="#0550ae"/>
|
||||
<entry type="Comment" style="#57606a"/>
|
||||
<entry type="CommentMultiline" style="#57606a"/>
|
||||
<entry type="CommentSingle" style="#57606a"/>
|
||||
<entry type="CommentSpecial" style="#57606a"/>
|
||||
<entry type="CommentPreproc" style="#57606a"/>
|
||||
<entry type="GenericDeleted" style="#82071e bg:#ffebe9"/>
|
||||
<entry type="GenericEmph" style="#1f2328"/>
|
||||
<entry type="GenericInserted" style="#116329 bg:#dafbe1"/>
|
||||
<entry type="GenericOutput" style="#1f2328"/>
|
||||
<entry type="GenericUnderline" style="underline"/>
|
||||
<entry type="TextWhitespace" style="#bbbbbb"/>
|
||||
<entry type="Punctuation" style="#1f2328"/>
|
||||
<entry type="TextWhitespace" style="#ffffff"/>
|
||||
</style>
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
<style name="nordic">
|
||||
<entry type="Error" style="#C5727A"/>
|
||||
<entry type="Background" style="#BBC3D4 bg:#242933"/>
|
||||
<entry type="Keyword" style="bold #D08770"/>
|
||||
<entry type="KeywordPseudo" style="nobold #D08770"/>
|
||||
<entry type="KeywordType" style="nobold #D08770"/>
|
||||
<entry type="Name" style="#BBC3D4"/>
|
||||
<entry type="NameAttribute" style="#8FBCBB"/>
|
||||
<entry type="NameBuiltin" style="#5E81AC"/>
|
||||
<entry type="NameClass" style="#8FBCBB"/>
|
||||
<entry type="NameConstant" style="#8FBCBB"/>
|
||||
<entry type="NameDecorator" style="#D08770"/>
|
||||
<entry type="NameEntity" style="#D08770"/>
|
||||
<entry type="NameException" style="#C5727A"/>
|
||||
<entry type="NameFunction" style="#88C0D0"/>
|
||||
<entry type="NameLabel" style="#8FBCBB"/>
|
||||
<entry type="NameNamespace" style="#8FBCBB"/>
|
||||
<entry type="NameOther" style="#BBC3D4"/>
|
||||
<entry type="NameTag" style="#5E81AC"/>
|
||||
<entry type="NameVariable" style="#BBC3D4"/>
|
||||
<entry type="NameProperty" style="#8FBCBB"/>
|
||||
<entry type="LiteralString" style="#A3BE8C"/>
|
||||
<entry type="LiteralStringDoc" style="#4C566A"/>
|
||||
<entry type="LiteralStringEscape" style="#EBCB8B"/>
|
||||
<entry type="LiteralStringInterpol" style="#A3BE8C"/>
|
||||
<entry type="LiteralStringOther" style="#A3BE8C"/>
|
||||
<entry type="LiteralStringRegex" style="#EBCB8B"/>
|
||||
<entry type="LiteralStringSymbol" style="#A3BE8C"/>
|
||||
<entry type="LiteralNumber" style="#B48EAD"/>
|
||||
<entry type="Operator" style="#5E81AC"/>
|
||||
<entry type="OperatorWord" style="bold #5E81AC"/>
|
||||
<entry type="Punctuation" style="#ECEFF4"/>
|
||||
<entry type="Comment" style="italic #4C566A"/>
|
||||
<entry type="CommentPreproc" style="#5E81AC"/>
|
||||
<entry type="GenericDeleted" style="#C5727A"/>
|
||||
<entry type="GenericEmph" style="italic"/>
|
||||
<entry type="GenericError" style="#C5727A"/>
|
||||
<entry type="GenericHeading" style="bold #88C0D0"/>
|
||||
<entry type="GenericInserted" style="#A3BE8C"/>
|
||||
<entry type="GenericOutput" style="#BBC3D4"/>
|
||||
<entry type="GenericPrompt" style="bold #1E222A"/>
|
||||
<entry type="GenericStrong" style="bold"/>
|
||||
<entry type="GenericSubheading" style="bold #88C0D0"/>
|
||||
<entry type="GenericTraceback" style="#C5727A"/>
|
||||
<entry type="TextWhitespace" style="#BBC3D4"/>
|
||||
</style>
|
||||
+409
-399
@@ -7,109 +7,111 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
const _TokenTypeName = "NoneOtherErrorCodeLineLineLinkLineTableTDLineTableLineHighlightLineNumbersTableLineNumbersLinePreWrapperBackgroundEOFTypeKeywordKeywordConstantKeywordDeclarationKeywordNamespaceKeywordPseudoKeywordReservedKeywordTypeNameNameAttributeNameBuiltinNameBuiltinPseudoNameClassNameConstantNameDecoratorNameEntityNameExceptionNameFunctionNameFunctionMagicNameKeywordNameLabelNameNamespaceNameOperatorNameOtherNamePseudoNamePropertyNameTagNameVariableNameVariableAnonymousNameVariableClassNameVariableGlobalNameVariableInstanceNameVariableMagicLiteralLiteralDateLiteralOtherLiteralStringLiteralStringAffixLiteralStringAtomLiteralStringBacktickLiteralStringBooleanLiteralStringCharLiteralStringDelimiterLiteralStringDocLiteralStringDoubleLiteralStringEscapeLiteralStringHeredocLiteralStringInterpolLiteralStringNameLiteralStringOtherLiteralStringRegexLiteralStringSingleLiteralStringSymbolLiteralNumberLiteralNumberBinLiteralNumberFloatLiteralNumberHexLiteralNumberIntegerLiteralNumberIntegerLongLiteralNumberOctOperatorOperatorWordPunctuationCommentCommentHashbangCommentMultilineCommentSingleCommentSpecialCommentPreprocCommentPreprocFileGenericGenericDeletedGenericEmphGenericErrorGenericHeadingGenericInsertedGenericOutputGenericPromptGenericStrongGenericSubheadingGenericTracebackGenericUnderlineTextTextWhitespaceTextSymbolTextPunctuation"
|
||||
const _TokenTypeLowerName = "noneothererrorcodelinelinelinklinetabletdlinetablelinehighlightlinenumberstablelinenumberslineprewrapperbackgroundeoftypekeywordkeywordconstantkeyworddeclarationkeywordnamespacekeywordpseudokeywordreservedkeywordtypenamenameattributenamebuiltinnamebuiltinpseudonameclassnameconstantnamedecoratornameentitynameexceptionnamefunctionnamefunctionmagicnamekeywordnamelabelnamenamespacenameoperatornameothernamepseudonamepropertynametagnamevariablenamevariableanonymousnamevariableclassnamevariableglobalnamevariableinstancenamevariablemagicliteralliteraldateliteralotherliteralstringliteralstringaffixliteralstringatomliteralstringbacktickliteralstringbooleanliteralstringcharliteralstringdelimiterliteralstringdocliteralstringdoubleliteralstringescapeliteralstringheredocliteralstringinterpolliteralstringnameliteralstringotherliteralstringregexliteralstringsingleliteralstringsymbolliteralnumberliteralnumberbinliteralnumberfloatliteralnumberhexliteralnumberintegerliteralnumberintegerlongliteralnumberoctoperatoroperatorwordpunctuationcommentcommenthashbangcommentmultilinecommentsinglecommentspecialcommentpreproccommentpreprocfilegenericgenericdeletedgenericemphgenericerrorgenericheadinggenericinsertedgenericoutputgenericpromptgenericstronggenericsubheadinggenerictracebackgenericunderlinetexttextwhitespacetextsymboltextpunctuation"
|
||||
const _TokenTypeName = "IgnoreNoneOtherErrorCodeLineLineLinkLineTableTDLineTableLineHighlightLineNumbersTableLineNumbersLinePreWrapperBackgroundEOFTypeKeywordKeywordConstantKeywordDeclarationKeywordNamespaceKeywordPseudoKeywordReservedKeywordTypeNameNameAttributeNameBuiltinNameBuiltinPseudoNameClassNameConstantNameDecoratorNameEntityNameExceptionNameFunctionNameFunctionMagicNameKeywordNameLabelNameNamespaceNameOperatorNameOtherNamePseudoNamePropertyNameTagNameVariableNameVariableAnonymousNameVariableClassNameVariableGlobalNameVariableInstanceNameVariableMagicLiteralLiteralDateLiteralOtherLiteralStringLiteralStringAffixLiteralStringAtomLiteralStringBacktickLiteralStringBooleanLiteralStringCharLiteralStringDelimiterLiteralStringDocLiteralStringDoubleLiteralStringEscapeLiteralStringHeredocLiteralStringInterpolLiteralStringNameLiteralStringOtherLiteralStringRegexLiteralStringSingleLiteralStringSymbolLiteralNumberLiteralNumberBinLiteralNumberFloatLiteralNumberHexLiteralNumberIntegerLiteralNumberIntegerLongLiteralNumberOctLiteralNumberByteOperatorOperatorWordPunctuationCommentCommentHashbangCommentMultilineCommentSingleCommentSpecialCommentPreprocCommentPreprocFileGenericGenericDeletedGenericEmphGenericErrorGenericHeadingGenericInsertedGenericOutputGenericPromptGenericStrongGenericSubheadingGenericTracebackGenericUnderlineTextTextWhitespaceTextSymbolTextPunctuation"
|
||||
const _TokenTypeLowerName = "ignorenoneothererrorcodelinelinelinklinetabletdlinetablelinehighlightlinenumberstablelinenumberslineprewrapperbackgroundeoftypekeywordkeywordconstantkeyworddeclarationkeywordnamespacekeywordpseudokeywordreservedkeywordtypenamenameattributenamebuiltinnamebuiltinpseudonameclassnameconstantnamedecoratornameentitynameexceptionnamefunctionnamefunctionmagicnamekeywordnamelabelnamenamespacenameoperatornameothernamepseudonamepropertynametagnamevariablenamevariableanonymousnamevariableclassnamevariableglobalnamevariableinstancenamevariablemagicliteralliteraldateliteralotherliteralstringliteralstringaffixliteralstringatomliteralstringbacktickliteralstringbooleanliteralstringcharliteralstringdelimiterliteralstringdocliteralstringdoubleliteralstringescapeliteralstringheredocliteralstringinterpolliteralstringnameliteralstringotherliteralstringregexliteralstringsingleliteralstringsymbolliteralnumberliteralnumberbinliteralnumberfloatliteralnumberhexliteralnumberintegerliteralnumberintegerlongliteralnumberoctliteralnumberbyteoperatoroperatorwordpunctuationcommentcommenthashbangcommentmultilinecommentsinglecommentspecialcommentpreproccommentpreprocfilegenericgenericdeletedgenericemphgenericerrorgenericheadinggenericinsertedgenericoutputgenericpromptgenericstronggenericsubheadinggenerictracebackgenericunderlinetexttextwhitespacetextsymboltextpunctuation"
|
||||
|
||||
var _TokenTypeMap = map[TokenType]string{
|
||||
-13: _TokenTypeName[0:4],
|
||||
-12: _TokenTypeName[4:9],
|
||||
-11: _TokenTypeName[9:14],
|
||||
-10: _TokenTypeName[14:22],
|
||||
-9: _TokenTypeName[22:30],
|
||||
-8: _TokenTypeName[30:41],
|
||||
-7: _TokenTypeName[41:50],
|
||||
-6: _TokenTypeName[50:63],
|
||||
-5: _TokenTypeName[63:79],
|
||||
-4: _TokenTypeName[79:90],
|
||||
-3: _TokenTypeName[90:94],
|
||||
-2: _TokenTypeName[94:104],
|
||||
-1: _TokenTypeName[104:114],
|
||||
0: _TokenTypeName[114:121],
|
||||
1000: _TokenTypeName[121:128],
|
||||
1001: _TokenTypeName[128:143],
|
||||
1002: _TokenTypeName[143:161],
|
||||
1003: _TokenTypeName[161:177],
|
||||
1004: _TokenTypeName[177:190],
|
||||
1005: _TokenTypeName[190:205],
|
||||
1006: _TokenTypeName[205:216],
|
||||
2000: _TokenTypeName[216:220],
|
||||
2001: _TokenTypeName[220:233],
|
||||
2002: _TokenTypeName[233:244],
|
||||
2003: _TokenTypeName[244:261],
|
||||
2004: _TokenTypeName[261:270],
|
||||
2005: _TokenTypeName[270:282],
|
||||
2006: _TokenTypeName[282:295],
|
||||
2007: _TokenTypeName[295:305],
|
||||
2008: _TokenTypeName[305:318],
|
||||
2009: _TokenTypeName[318:330],
|
||||
2010: _TokenTypeName[330:347],
|
||||
2011: _TokenTypeName[347:358],
|
||||
2012: _TokenTypeName[358:367],
|
||||
2013: _TokenTypeName[367:380],
|
||||
2014: _TokenTypeName[380:392],
|
||||
2015: _TokenTypeName[392:401],
|
||||
2016: _TokenTypeName[401:411],
|
||||
2017: _TokenTypeName[411:423],
|
||||
2018: _TokenTypeName[423:430],
|
||||
2019: _TokenTypeName[430:442],
|
||||
2020: _TokenTypeName[442:463],
|
||||
2021: _TokenTypeName[463:480],
|
||||
2022: _TokenTypeName[480:498],
|
||||
2023: _TokenTypeName[498:518],
|
||||
2024: _TokenTypeName[518:535],
|
||||
3000: _TokenTypeName[535:542],
|
||||
3001: _TokenTypeName[542:553],
|
||||
3002: _TokenTypeName[553:565],
|
||||
3100: _TokenTypeName[565:578],
|
||||
3101: _TokenTypeName[578:596],
|
||||
3102: _TokenTypeName[596:613],
|
||||
3103: _TokenTypeName[613:634],
|
||||
3104: _TokenTypeName[634:654],
|
||||
3105: _TokenTypeName[654:671],
|
||||
3106: _TokenTypeName[671:693],
|
||||
3107: _TokenTypeName[693:709],
|
||||
3108: _TokenTypeName[709:728],
|
||||
3109: _TokenTypeName[728:747],
|
||||
3110: _TokenTypeName[747:767],
|
||||
3111: _TokenTypeName[767:788],
|
||||
3112: _TokenTypeName[788:805],
|
||||
3113: _TokenTypeName[805:823],
|
||||
3114: _TokenTypeName[823:841],
|
||||
3115: _TokenTypeName[841:860],
|
||||
3116: _TokenTypeName[860:879],
|
||||
3200: _TokenTypeName[879:892],
|
||||
3201: _TokenTypeName[892:908],
|
||||
3202: _TokenTypeName[908:926],
|
||||
3203: _TokenTypeName[926:942],
|
||||
3204: _TokenTypeName[942:962],
|
||||
3205: _TokenTypeName[962:986],
|
||||
3206: _TokenTypeName[986:1002],
|
||||
4000: _TokenTypeName[1002:1010],
|
||||
4001: _TokenTypeName[1010:1022],
|
||||
5000: _TokenTypeName[1022:1033],
|
||||
6000: _TokenTypeName[1033:1040],
|
||||
6001: _TokenTypeName[1040:1055],
|
||||
6002: _TokenTypeName[1055:1071],
|
||||
6003: _TokenTypeName[1071:1084],
|
||||
6004: _TokenTypeName[1084:1098],
|
||||
6100: _TokenTypeName[1098:1112],
|
||||
6101: _TokenTypeName[1112:1130],
|
||||
7000: _TokenTypeName[1130:1137],
|
||||
7001: _TokenTypeName[1137:1151],
|
||||
7002: _TokenTypeName[1151:1162],
|
||||
7003: _TokenTypeName[1162:1174],
|
||||
7004: _TokenTypeName[1174:1188],
|
||||
7005: _TokenTypeName[1188:1203],
|
||||
7006: _TokenTypeName[1203:1216],
|
||||
7007: _TokenTypeName[1216:1229],
|
||||
7008: _TokenTypeName[1229:1242],
|
||||
7009: _TokenTypeName[1242:1259],
|
||||
7010: _TokenTypeName[1259:1275],
|
||||
7011: _TokenTypeName[1275:1291],
|
||||
8000: _TokenTypeName[1291:1295],
|
||||
8001: _TokenTypeName[1295:1309],
|
||||
8002: _TokenTypeName[1309:1319],
|
||||
8003: _TokenTypeName[1319:1334],
|
||||
-14: _TokenTypeName[0:6],
|
||||
-13: _TokenTypeName[6:10],
|
||||
-12: _TokenTypeName[10:15],
|
||||
-11: _TokenTypeName[15:20],
|
||||
-10: _TokenTypeName[20:28],
|
||||
-9: _TokenTypeName[28:36],
|
||||
-8: _TokenTypeName[36:47],
|
||||
-7: _TokenTypeName[47:56],
|
||||
-6: _TokenTypeName[56:69],
|
||||
-5: _TokenTypeName[69:85],
|
||||
-4: _TokenTypeName[85:96],
|
||||
-3: _TokenTypeName[96:100],
|
||||
-2: _TokenTypeName[100:110],
|
||||
-1: _TokenTypeName[110:120],
|
||||
0: _TokenTypeName[120:127],
|
||||
1000: _TokenTypeName[127:134],
|
||||
1001: _TokenTypeName[134:149],
|
||||
1002: _TokenTypeName[149:167],
|
||||
1003: _TokenTypeName[167:183],
|
||||
1004: _TokenTypeName[183:196],
|
||||
1005: _TokenTypeName[196:211],
|
||||
1006: _TokenTypeName[211:222],
|
||||
2000: _TokenTypeName[222:226],
|
||||
2001: _TokenTypeName[226:239],
|
||||
2002: _TokenTypeName[239:250],
|
||||
2003: _TokenTypeName[250:267],
|
||||
2004: _TokenTypeName[267:276],
|
||||
2005: _TokenTypeName[276:288],
|
||||
2006: _TokenTypeName[288:301],
|
||||
2007: _TokenTypeName[301:311],
|
||||
2008: _TokenTypeName[311:324],
|
||||
2009: _TokenTypeName[324:336],
|
||||
2010: _TokenTypeName[336:353],
|
||||
2011: _TokenTypeName[353:364],
|
||||
2012: _TokenTypeName[364:373],
|
||||
2013: _TokenTypeName[373:386],
|
||||
2014: _TokenTypeName[386:398],
|
||||
2015: _TokenTypeName[398:407],
|
||||
2016: _TokenTypeName[407:417],
|
||||
2017: _TokenTypeName[417:429],
|
||||
2018: _TokenTypeName[429:436],
|
||||
2019: _TokenTypeName[436:448],
|
||||
2020: _TokenTypeName[448:469],
|
||||
2021: _TokenTypeName[469:486],
|
||||
2022: _TokenTypeName[486:504],
|
||||
2023: _TokenTypeName[504:524],
|
||||
2024: _TokenTypeName[524:541],
|
||||
3000: _TokenTypeName[541:548],
|
||||
3001: _TokenTypeName[548:559],
|
||||
3002: _TokenTypeName[559:571],
|
||||
3100: _TokenTypeName[571:584],
|
||||
3101: _TokenTypeName[584:602],
|
||||
3102: _TokenTypeName[602:619],
|
||||
3103: _TokenTypeName[619:640],
|
||||
3104: _TokenTypeName[640:660],
|
||||
3105: _TokenTypeName[660:677],
|
||||
3106: _TokenTypeName[677:699],
|
||||
3107: _TokenTypeName[699:715],
|
||||
3108: _TokenTypeName[715:734],
|
||||
3109: _TokenTypeName[734:753],
|
||||
3110: _TokenTypeName[753:773],
|
||||
3111: _TokenTypeName[773:794],
|
||||
3112: _TokenTypeName[794:811],
|
||||
3113: _TokenTypeName[811:829],
|
||||
3114: _TokenTypeName[829:847],
|
||||
3115: _TokenTypeName[847:866],
|
||||
3116: _TokenTypeName[866:885],
|
||||
3200: _TokenTypeName[885:898],
|
||||
3201: _TokenTypeName[898:914],
|
||||
3202: _TokenTypeName[914:932],
|
||||
3203: _TokenTypeName[932:948],
|
||||
3204: _TokenTypeName[948:968],
|
||||
3205: _TokenTypeName[968:992],
|
||||
3206: _TokenTypeName[992:1008],
|
||||
3207: _TokenTypeName[1008:1025],
|
||||
4000: _TokenTypeName[1025:1033],
|
||||
4001: _TokenTypeName[1033:1045],
|
||||
5000: _TokenTypeName[1045:1056],
|
||||
6000: _TokenTypeName[1056:1063],
|
||||
6001: _TokenTypeName[1063:1078],
|
||||
6002: _TokenTypeName[1078:1094],
|
||||
6003: _TokenTypeName[1094:1107],
|
||||
6004: _TokenTypeName[1107:1121],
|
||||
6100: _TokenTypeName[1121:1135],
|
||||
6101: _TokenTypeName[1135:1153],
|
||||
7000: _TokenTypeName[1153:1160],
|
||||
7001: _TokenTypeName[1160:1174],
|
||||
7002: _TokenTypeName[1174:1185],
|
||||
7003: _TokenTypeName[1185:1197],
|
||||
7004: _TokenTypeName[1197:1211],
|
||||
7005: _TokenTypeName[1211:1226],
|
||||
7006: _TokenTypeName[1226:1239],
|
||||
7007: _TokenTypeName[1239:1252],
|
||||
7008: _TokenTypeName[1252:1265],
|
||||
7009: _TokenTypeName[1265:1282],
|
||||
7010: _TokenTypeName[1282:1298],
|
||||
7011: _TokenTypeName[1298:1314],
|
||||
8000: _TokenTypeName[1314:1318],
|
||||
8001: _TokenTypeName[1318:1332],
|
||||
8002: _TokenTypeName[1332:1342],
|
||||
8003: _TokenTypeName[1342:1357],
|
||||
}
|
||||
|
||||
func (i TokenType) String() string {
|
||||
@@ -123,6 +125,7 @@ func (i TokenType) String() string {
|
||||
// Re-run the stringer command to generate them again.
|
||||
func _TokenTypeNoOp() {
|
||||
var x [1]struct{}
|
||||
_ = x[Ignore-(-14)]
|
||||
_ = x[None-(-13)]
|
||||
_ = x[Other-(-12)]
|
||||
_ = x[Error-(-11)]
|
||||
@@ -196,6 +199,7 @@ func _TokenTypeNoOp() {
|
||||
_ = x[LiteralNumberInteger-(3204)]
|
||||
_ = x[LiteralNumberIntegerLong-(3205)]
|
||||
_ = x[LiteralNumberOct-(3206)]
|
||||
_ = x[LiteralNumberByte-(3207)]
|
||||
_ = x[Operator-(4000)]
|
||||
_ = x[OperatorWord-(4001)]
|
||||
_ = x[Punctuation-(5000)]
|
||||
@@ -224,309 +228,315 @@ func _TokenTypeNoOp() {
|
||||
_ = x[TextPunctuation-(8003)]
|
||||
}
|
||||
|
||||
var _TokenTypeValues = []TokenType{None, Other, Error, CodeLine, LineLink, LineTableTD, LineTable, LineHighlight, LineNumbersTable, LineNumbers, Line, PreWrapper, Background, EOFType, Keyword, KeywordConstant, KeywordDeclaration, KeywordNamespace, KeywordPseudo, KeywordReserved, KeywordType, Name, NameAttribute, NameBuiltin, NameBuiltinPseudo, NameClass, NameConstant, NameDecorator, NameEntity, NameException, NameFunction, NameFunctionMagic, NameKeyword, NameLabel, NameNamespace, NameOperator, NameOther, NamePseudo, NameProperty, NameTag, NameVariable, NameVariableAnonymous, NameVariableClass, NameVariableGlobal, NameVariableInstance, NameVariableMagic, Literal, LiteralDate, LiteralOther, LiteralString, LiteralStringAffix, LiteralStringAtom, LiteralStringBacktick, LiteralStringBoolean, LiteralStringChar, LiteralStringDelimiter, LiteralStringDoc, LiteralStringDouble, LiteralStringEscape, LiteralStringHeredoc, LiteralStringInterpol, LiteralStringName, LiteralStringOther, LiteralStringRegex, LiteralStringSingle, LiteralStringSymbol, LiteralNumber, LiteralNumberBin, LiteralNumberFloat, LiteralNumberHex, LiteralNumberInteger, LiteralNumberIntegerLong, LiteralNumberOct, Operator, OperatorWord, Punctuation, Comment, CommentHashbang, CommentMultiline, CommentSingle, CommentSpecial, CommentPreproc, CommentPreprocFile, Generic, GenericDeleted, GenericEmph, GenericError, GenericHeading, GenericInserted, GenericOutput, GenericPrompt, GenericStrong, GenericSubheading, GenericTraceback, GenericUnderline, Text, TextWhitespace, TextSymbol, TextPunctuation}
|
||||
var _TokenTypeValues = []TokenType{Ignore, None, Other, Error, CodeLine, LineLink, LineTableTD, LineTable, LineHighlight, LineNumbersTable, LineNumbers, Line, PreWrapper, Background, EOFType, Keyword, KeywordConstant, KeywordDeclaration, KeywordNamespace, KeywordPseudo, KeywordReserved, KeywordType, Name, NameAttribute, NameBuiltin, NameBuiltinPseudo, NameClass, NameConstant, NameDecorator, NameEntity, NameException, NameFunction, NameFunctionMagic, NameKeyword, NameLabel, NameNamespace, NameOperator, NameOther, NamePseudo, NameProperty, NameTag, NameVariable, NameVariableAnonymous, NameVariableClass, NameVariableGlobal, NameVariableInstance, NameVariableMagic, Literal, LiteralDate, LiteralOther, LiteralString, LiteralStringAffix, LiteralStringAtom, LiteralStringBacktick, LiteralStringBoolean, LiteralStringChar, LiteralStringDelimiter, LiteralStringDoc, LiteralStringDouble, LiteralStringEscape, LiteralStringHeredoc, LiteralStringInterpol, LiteralStringName, LiteralStringOther, LiteralStringRegex, LiteralStringSingle, LiteralStringSymbol, LiteralNumber, LiteralNumberBin, LiteralNumberFloat, LiteralNumberHex, LiteralNumberInteger, LiteralNumberIntegerLong, LiteralNumberOct, LiteralNumberByte, Operator, OperatorWord, Punctuation, Comment, CommentHashbang, CommentMultiline, CommentSingle, CommentSpecial, CommentPreproc, CommentPreprocFile, Generic, GenericDeleted, GenericEmph, GenericError, GenericHeading, GenericInserted, GenericOutput, GenericPrompt, GenericStrong, GenericSubheading, GenericTraceback, GenericUnderline, Text, TextWhitespace, TextSymbol, TextPunctuation}
|
||||
|
||||
var _TokenTypeNameToValueMap = map[string]TokenType{
|
||||
_TokenTypeName[0:4]: None,
|
||||
_TokenTypeLowerName[0:4]: None,
|
||||
_TokenTypeName[4:9]: Other,
|
||||
_TokenTypeLowerName[4:9]: Other,
|
||||
_TokenTypeName[9:14]: Error,
|
||||
_TokenTypeLowerName[9:14]: Error,
|
||||
_TokenTypeName[14:22]: CodeLine,
|
||||
_TokenTypeLowerName[14:22]: CodeLine,
|
||||
_TokenTypeName[22:30]: LineLink,
|
||||
_TokenTypeLowerName[22:30]: LineLink,
|
||||
_TokenTypeName[30:41]: LineTableTD,
|
||||
_TokenTypeLowerName[30:41]: LineTableTD,
|
||||
_TokenTypeName[41:50]: LineTable,
|
||||
_TokenTypeLowerName[41:50]: LineTable,
|
||||
_TokenTypeName[50:63]: LineHighlight,
|
||||
_TokenTypeLowerName[50:63]: LineHighlight,
|
||||
_TokenTypeName[63:79]: LineNumbersTable,
|
||||
_TokenTypeLowerName[63:79]: LineNumbersTable,
|
||||
_TokenTypeName[79:90]: LineNumbers,
|
||||
_TokenTypeLowerName[79:90]: LineNumbers,
|
||||
_TokenTypeName[90:94]: Line,
|
||||
_TokenTypeLowerName[90:94]: Line,
|
||||
_TokenTypeName[94:104]: PreWrapper,
|
||||
_TokenTypeLowerName[94:104]: PreWrapper,
|
||||
_TokenTypeName[104:114]: Background,
|
||||
_TokenTypeLowerName[104:114]: Background,
|
||||
_TokenTypeName[114:121]: EOFType,
|
||||
_TokenTypeLowerName[114:121]: EOFType,
|
||||
_TokenTypeName[121:128]: Keyword,
|
||||
_TokenTypeLowerName[121:128]: Keyword,
|
||||
_TokenTypeName[128:143]: KeywordConstant,
|
||||
_TokenTypeLowerName[128:143]: KeywordConstant,
|
||||
_TokenTypeName[143:161]: KeywordDeclaration,
|
||||
_TokenTypeLowerName[143:161]: KeywordDeclaration,
|
||||
_TokenTypeName[161:177]: KeywordNamespace,
|
||||
_TokenTypeLowerName[161:177]: KeywordNamespace,
|
||||
_TokenTypeName[177:190]: KeywordPseudo,
|
||||
_TokenTypeLowerName[177:190]: KeywordPseudo,
|
||||
_TokenTypeName[190:205]: KeywordReserved,
|
||||
_TokenTypeLowerName[190:205]: KeywordReserved,
|
||||
_TokenTypeName[205:216]: KeywordType,
|
||||
_TokenTypeLowerName[205:216]: KeywordType,
|
||||
_TokenTypeName[216:220]: Name,
|
||||
_TokenTypeLowerName[216:220]: Name,
|
||||
_TokenTypeName[220:233]: NameAttribute,
|
||||
_TokenTypeLowerName[220:233]: NameAttribute,
|
||||
_TokenTypeName[233:244]: NameBuiltin,
|
||||
_TokenTypeLowerName[233:244]: NameBuiltin,
|
||||
_TokenTypeName[244:261]: NameBuiltinPseudo,
|
||||
_TokenTypeLowerName[244:261]: NameBuiltinPseudo,
|
||||
_TokenTypeName[261:270]: NameClass,
|
||||
_TokenTypeLowerName[261:270]: NameClass,
|
||||
_TokenTypeName[270:282]: NameConstant,
|
||||
_TokenTypeLowerName[270:282]: NameConstant,
|
||||
_TokenTypeName[282:295]: NameDecorator,
|
||||
_TokenTypeLowerName[282:295]: NameDecorator,
|
||||
_TokenTypeName[295:305]: NameEntity,
|
||||
_TokenTypeLowerName[295:305]: NameEntity,
|
||||
_TokenTypeName[305:318]: NameException,
|
||||
_TokenTypeLowerName[305:318]: NameException,
|
||||
_TokenTypeName[318:330]: NameFunction,
|
||||
_TokenTypeLowerName[318:330]: NameFunction,
|
||||
_TokenTypeName[330:347]: NameFunctionMagic,
|
||||
_TokenTypeLowerName[330:347]: NameFunctionMagic,
|
||||
_TokenTypeName[347:358]: NameKeyword,
|
||||
_TokenTypeLowerName[347:358]: NameKeyword,
|
||||
_TokenTypeName[358:367]: NameLabel,
|
||||
_TokenTypeLowerName[358:367]: NameLabel,
|
||||
_TokenTypeName[367:380]: NameNamespace,
|
||||
_TokenTypeLowerName[367:380]: NameNamespace,
|
||||
_TokenTypeName[380:392]: NameOperator,
|
||||
_TokenTypeLowerName[380:392]: NameOperator,
|
||||
_TokenTypeName[392:401]: NameOther,
|
||||
_TokenTypeLowerName[392:401]: NameOther,
|
||||
_TokenTypeName[401:411]: NamePseudo,
|
||||
_TokenTypeLowerName[401:411]: NamePseudo,
|
||||
_TokenTypeName[411:423]: NameProperty,
|
||||
_TokenTypeLowerName[411:423]: NameProperty,
|
||||
_TokenTypeName[423:430]: NameTag,
|
||||
_TokenTypeLowerName[423:430]: NameTag,
|
||||
_TokenTypeName[430:442]: NameVariable,
|
||||
_TokenTypeLowerName[430:442]: NameVariable,
|
||||
_TokenTypeName[442:463]: NameVariableAnonymous,
|
||||
_TokenTypeLowerName[442:463]: NameVariableAnonymous,
|
||||
_TokenTypeName[463:480]: NameVariableClass,
|
||||
_TokenTypeLowerName[463:480]: NameVariableClass,
|
||||
_TokenTypeName[480:498]: NameVariableGlobal,
|
||||
_TokenTypeLowerName[480:498]: NameVariableGlobal,
|
||||
_TokenTypeName[498:518]: NameVariableInstance,
|
||||
_TokenTypeLowerName[498:518]: NameVariableInstance,
|
||||
_TokenTypeName[518:535]: NameVariableMagic,
|
||||
_TokenTypeLowerName[518:535]: NameVariableMagic,
|
||||
_TokenTypeName[535:542]: Literal,
|
||||
_TokenTypeLowerName[535:542]: Literal,
|
||||
_TokenTypeName[542:553]: LiteralDate,
|
||||
_TokenTypeLowerName[542:553]: LiteralDate,
|
||||
_TokenTypeName[553:565]: LiteralOther,
|
||||
_TokenTypeLowerName[553:565]: LiteralOther,
|
||||
_TokenTypeName[565:578]: LiteralString,
|
||||
_TokenTypeLowerName[565:578]: LiteralString,
|
||||
_TokenTypeName[578:596]: LiteralStringAffix,
|
||||
_TokenTypeLowerName[578:596]: LiteralStringAffix,
|
||||
_TokenTypeName[596:613]: LiteralStringAtom,
|
||||
_TokenTypeLowerName[596:613]: LiteralStringAtom,
|
||||
_TokenTypeName[613:634]: LiteralStringBacktick,
|
||||
_TokenTypeLowerName[613:634]: LiteralStringBacktick,
|
||||
_TokenTypeName[634:654]: LiteralStringBoolean,
|
||||
_TokenTypeLowerName[634:654]: LiteralStringBoolean,
|
||||
_TokenTypeName[654:671]: LiteralStringChar,
|
||||
_TokenTypeLowerName[654:671]: LiteralStringChar,
|
||||
_TokenTypeName[671:693]: LiteralStringDelimiter,
|
||||
_TokenTypeLowerName[671:693]: LiteralStringDelimiter,
|
||||
_TokenTypeName[693:709]: LiteralStringDoc,
|
||||
_TokenTypeLowerName[693:709]: LiteralStringDoc,
|
||||
_TokenTypeName[709:728]: LiteralStringDouble,
|
||||
_TokenTypeLowerName[709:728]: LiteralStringDouble,
|
||||
_TokenTypeName[728:747]: LiteralStringEscape,
|
||||
_TokenTypeLowerName[728:747]: LiteralStringEscape,
|
||||
_TokenTypeName[747:767]: LiteralStringHeredoc,
|
||||
_TokenTypeLowerName[747:767]: LiteralStringHeredoc,
|
||||
_TokenTypeName[767:788]: LiteralStringInterpol,
|
||||
_TokenTypeLowerName[767:788]: LiteralStringInterpol,
|
||||
_TokenTypeName[788:805]: LiteralStringName,
|
||||
_TokenTypeLowerName[788:805]: LiteralStringName,
|
||||
_TokenTypeName[805:823]: LiteralStringOther,
|
||||
_TokenTypeLowerName[805:823]: LiteralStringOther,
|
||||
_TokenTypeName[823:841]: LiteralStringRegex,
|
||||
_TokenTypeLowerName[823:841]: LiteralStringRegex,
|
||||
_TokenTypeName[841:860]: LiteralStringSingle,
|
||||
_TokenTypeLowerName[841:860]: LiteralStringSingle,
|
||||
_TokenTypeName[860:879]: LiteralStringSymbol,
|
||||
_TokenTypeLowerName[860:879]: LiteralStringSymbol,
|
||||
_TokenTypeName[879:892]: LiteralNumber,
|
||||
_TokenTypeLowerName[879:892]: LiteralNumber,
|
||||
_TokenTypeName[892:908]: LiteralNumberBin,
|
||||
_TokenTypeLowerName[892:908]: LiteralNumberBin,
|
||||
_TokenTypeName[908:926]: LiteralNumberFloat,
|
||||
_TokenTypeLowerName[908:926]: LiteralNumberFloat,
|
||||
_TokenTypeName[926:942]: LiteralNumberHex,
|
||||
_TokenTypeLowerName[926:942]: LiteralNumberHex,
|
||||
_TokenTypeName[942:962]: LiteralNumberInteger,
|
||||
_TokenTypeLowerName[942:962]: LiteralNumberInteger,
|
||||
_TokenTypeName[962:986]: LiteralNumberIntegerLong,
|
||||
_TokenTypeLowerName[962:986]: LiteralNumberIntegerLong,
|
||||
_TokenTypeName[986:1002]: LiteralNumberOct,
|
||||
_TokenTypeLowerName[986:1002]: LiteralNumberOct,
|
||||
_TokenTypeName[1002:1010]: Operator,
|
||||
_TokenTypeLowerName[1002:1010]: Operator,
|
||||
_TokenTypeName[1010:1022]: OperatorWord,
|
||||
_TokenTypeLowerName[1010:1022]: OperatorWord,
|
||||
_TokenTypeName[1022:1033]: Punctuation,
|
||||
_TokenTypeLowerName[1022:1033]: Punctuation,
|
||||
_TokenTypeName[1033:1040]: Comment,
|
||||
_TokenTypeLowerName[1033:1040]: Comment,
|
||||
_TokenTypeName[1040:1055]: CommentHashbang,
|
||||
_TokenTypeLowerName[1040:1055]: CommentHashbang,
|
||||
_TokenTypeName[1055:1071]: CommentMultiline,
|
||||
_TokenTypeLowerName[1055:1071]: CommentMultiline,
|
||||
_TokenTypeName[1071:1084]: CommentSingle,
|
||||
_TokenTypeLowerName[1071:1084]: CommentSingle,
|
||||
_TokenTypeName[1084:1098]: CommentSpecial,
|
||||
_TokenTypeLowerName[1084:1098]: CommentSpecial,
|
||||
_TokenTypeName[1098:1112]: CommentPreproc,
|
||||
_TokenTypeLowerName[1098:1112]: CommentPreproc,
|
||||
_TokenTypeName[1112:1130]: CommentPreprocFile,
|
||||
_TokenTypeLowerName[1112:1130]: CommentPreprocFile,
|
||||
_TokenTypeName[1130:1137]: Generic,
|
||||
_TokenTypeLowerName[1130:1137]: Generic,
|
||||
_TokenTypeName[1137:1151]: GenericDeleted,
|
||||
_TokenTypeLowerName[1137:1151]: GenericDeleted,
|
||||
_TokenTypeName[1151:1162]: GenericEmph,
|
||||
_TokenTypeLowerName[1151:1162]: GenericEmph,
|
||||
_TokenTypeName[1162:1174]: GenericError,
|
||||
_TokenTypeLowerName[1162:1174]: GenericError,
|
||||
_TokenTypeName[1174:1188]: GenericHeading,
|
||||
_TokenTypeLowerName[1174:1188]: GenericHeading,
|
||||
_TokenTypeName[1188:1203]: GenericInserted,
|
||||
_TokenTypeLowerName[1188:1203]: GenericInserted,
|
||||
_TokenTypeName[1203:1216]: GenericOutput,
|
||||
_TokenTypeLowerName[1203:1216]: GenericOutput,
|
||||
_TokenTypeName[1216:1229]: GenericPrompt,
|
||||
_TokenTypeLowerName[1216:1229]: GenericPrompt,
|
||||
_TokenTypeName[1229:1242]: GenericStrong,
|
||||
_TokenTypeLowerName[1229:1242]: GenericStrong,
|
||||
_TokenTypeName[1242:1259]: GenericSubheading,
|
||||
_TokenTypeLowerName[1242:1259]: GenericSubheading,
|
||||
_TokenTypeName[1259:1275]: GenericTraceback,
|
||||
_TokenTypeLowerName[1259:1275]: GenericTraceback,
|
||||
_TokenTypeName[1275:1291]: GenericUnderline,
|
||||
_TokenTypeLowerName[1275:1291]: GenericUnderline,
|
||||
_TokenTypeName[1291:1295]: Text,
|
||||
_TokenTypeLowerName[1291:1295]: Text,
|
||||
_TokenTypeName[1295:1309]: TextWhitespace,
|
||||
_TokenTypeLowerName[1295:1309]: TextWhitespace,
|
||||
_TokenTypeName[1309:1319]: TextSymbol,
|
||||
_TokenTypeLowerName[1309:1319]: TextSymbol,
|
||||
_TokenTypeName[1319:1334]: TextPunctuation,
|
||||
_TokenTypeLowerName[1319:1334]: TextPunctuation,
|
||||
_TokenTypeName[0:6]: Ignore,
|
||||
_TokenTypeLowerName[0:6]: Ignore,
|
||||
_TokenTypeName[6:10]: None,
|
||||
_TokenTypeLowerName[6:10]: None,
|
||||
_TokenTypeName[10:15]: Other,
|
||||
_TokenTypeLowerName[10:15]: Other,
|
||||
_TokenTypeName[15:20]: Error,
|
||||
_TokenTypeLowerName[15:20]: Error,
|
||||
_TokenTypeName[20:28]: CodeLine,
|
||||
_TokenTypeLowerName[20:28]: CodeLine,
|
||||
_TokenTypeName[28:36]: LineLink,
|
||||
_TokenTypeLowerName[28:36]: LineLink,
|
||||
_TokenTypeName[36:47]: LineTableTD,
|
||||
_TokenTypeLowerName[36:47]: LineTableTD,
|
||||
_TokenTypeName[47:56]: LineTable,
|
||||
_TokenTypeLowerName[47:56]: LineTable,
|
||||
_TokenTypeName[56:69]: LineHighlight,
|
||||
_TokenTypeLowerName[56:69]: LineHighlight,
|
||||
_TokenTypeName[69:85]: LineNumbersTable,
|
||||
_TokenTypeLowerName[69:85]: LineNumbersTable,
|
||||
_TokenTypeName[85:96]: LineNumbers,
|
||||
_TokenTypeLowerName[85:96]: LineNumbers,
|
||||
_TokenTypeName[96:100]: Line,
|
||||
_TokenTypeLowerName[96:100]: Line,
|
||||
_TokenTypeName[100:110]: PreWrapper,
|
||||
_TokenTypeLowerName[100:110]: PreWrapper,
|
||||
_TokenTypeName[110:120]: Background,
|
||||
_TokenTypeLowerName[110:120]: Background,
|
||||
_TokenTypeName[120:127]: EOFType,
|
||||
_TokenTypeLowerName[120:127]: EOFType,
|
||||
_TokenTypeName[127:134]: Keyword,
|
||||
_TokenTypeLowerName[127:134]: Keyword,
|
||||
_TokenTypeName[134:149]: KeywordConstant,
|
||||
_TokenTypeLowerName[134:149]: KeywordConstant,
|
||||
_TokenTypeName[149:167]: KeywordDeclaration,
|
||||
_TokenTypeLowerName[149:167]: KeywordDeclaration,
|
||||
_TokenTypeName[167:183]: KeywordNamespace,
|
||||
_TokenTypeLowerName[167:183]: KeywordNamespace,
|
||||
_TokenTypeName[183:196]: KeywordPseudo,
|
||||
_TokenTypeLowerName[183:196]: KeywordPseudo,
|
||||
_TokenTypeName[196:211]: KeywordReserved,
|
||||
_TokenTypeLowerName[196:211]: KeywordReserved,
|
||||
_TokenTypeName[211:222]: KeywordType,
|
||||
_TokenTypeLowerName[211:222]: KeywordType,
|
||||
_TokenTypeName[222:226]: Name,
|
||||
_TokenTypeLowerName[222:226]: Name,
|
||||
_TokenTypeName[226:239]: NameAttribute,
|
||||
_TokenTypeLowerName[226:239]: NameAttribute,
|
||||
_TokenTypeName[239:250]: NameBuiltin,
|
||||
_TokenTypeLowerName[239:250]: NameBuiltin,
|
||||
_TokenTypeName[250:267]: NameBuiltinPseudo,
|
||||
_TokenTypeLowerName[250:267]: NameBuiltinPseudo,
|
||||
_TokenTypeName[267:276]: NameClass,
|
||||
_TokenTypeLowerName[267:276]: NameClass,
|
||||
_TokenTypeName[276:288]: NameConstant,
|
||||
_TokenTypeLowerName[276:288]: NameConstant,
|
||||
_TokenTypeName[288:301]: NameDecorator,
|
||||
_TokenTypeLowerName[288:301]: NameDecorator,
|
||||
_TokenTypeName[301:311]: NameEntity,
|
||||
_TokenTypeLowerName[301:311]: NameEntity,
|
||||
_TokenTypeName[311:324]: NameException,
|
||||
_TokenTypeLowerName[311:324]: NameException,
|
||||
_TokenTypeName[324:336]: NameFunction,
|
||||
_TokenTypeLowerName[324:336]: NameFunction,
|
||||
_TokenTypeName[336:353]: NameFunctionMagic,
|
||||
_TokenTypeLowerName[336:353]: NameFunctionMagic,
|
||||
_TokenTypeName[353:364]: NameKeyword,
|
||||
_TokenTypeLowerName[353:364]: NameKeyword,
|
||||
_TokenTypeName[364:373]: NameLabel,
|
||||
_TokenTypeLowerName[364:373]: NameLabel,
|
||||
_TokenTypeName[373:386]: NameNamespace,
|
||||
_TokenTypeLowerName[373:386]: NameNamespace,
|
||||
_TokenTypeName[386:398]: NameOperator,
|
||||
_TokenTypeLowerName[386:398]: NameOperator,
|
||||
_TokenTypeName[398:407]: NameOther,
|
||||
_TokenTypeLowerName[398:407]: NameOther,
|
||||
_TokenTypeName[407:417]: NamePseudo,
|
||||
_TokenTypeLowerName[407:417]: NamePseudo,
|
||||
_TokenTypeName[417:429]: NameProperty,
|
||||
_TokenTypeLowerName[417:429]: NameProperty,
|
||||
_TokenTypeName[429:436]: NameTag,
|
||||
_TokenTypeLowerName[429:436]: NameTag,
|
||||
_TokenTypeName[436:448]: NameVariable,
|
||||
_TokenTypeLowerName[436:448]: NameVariable,
|
||||
_TokenTypeName[448:469]: NameVariableAnonymous,
|
||||
_TokenTypeLowerName[448:469]: NameVariableAnonymous,
|
||||
_TokenTypeName[469:486]: NameVariableClass,
|
||||
_TokenTypeLowerName[469:486]: NameVariableClass,
|
||||
_TokenTypeName[486:504]: NameVariableGlobal,
|
||||
_TokenTypeLowerName[486:504]: NameVariableGlobal,
|
||||
_TokenTypeName[504:524]: NameVariableInstance,
|
||||
_TokenTypeLowerName[504:524]: NameVariableInstance,
|
||||
_TokenTypeName[524:541]: NameVariableMagic,
|
||||
_TokenTypeLowerName[524:541]: NameVariableMagic,
|
||||
_TokenTypeName[541:548]: Literal,
|
||||
_TokenTypeLowerName[541:548]: Literal,
|
||||
_TokenTypeName[548:559]: LiteralDate,
|
||||
_TokenTypeLowerName[548:559]: LiteralDate,
|
||||
_TokenTypeName[559:571]: LiteralOther,
|
||||
_TokenTypeLowerName[559:571]: LiteralOther,
|
||||
_TokenTypeName[571:584]: LiteralString,
|
||||
_TokenTypeLowerName[571:584]: LiteralString,
|
||||
_TokenTypeName[584:602]: LiteralStringAffix,
|
||||
_TokenTypeLowerName[584:602]: LiteralStringAffix,
|
||||
_TokenTypeName[602:619]: LiteralStringAtom,
|
||||
_TokenTypeLowerName[602:619]: LiteralStringAtom,
|
||||
_TokenTypeName[619:640]: LiteralStringBacktick,
|
||||
_TokenTypeLowerName[619:640]: LiteralStringBacktick,
|
||||
_TokenTypeName[640:660]: LiteralStringBoolean,
|
||||
_TokenTypeLowerName[640:660]: LiteralStringBoolean,
|
||||
_TokenTypeName[660:677]: LiteralStringChar,
|
||||
_TokenTypeLowerName[660:677]: LiteralStringChar,
|
||||
_TokenTypeName[677:699]: LiteralStringDelimiter,
|
||||
_TokenTypeLowerName[677:699]: LiteralStringDelimiter,
|
||||
_TokenTypeName[699:715]: LiteralStringDoc,
|
||||
_TokenTypeLowerName[699:715]: LiteralStringDoc,
|
||||
_TokenTypeName[715:734]: LiteralStringDouble,
|
||||
_TokenTypeLowerName[715:734]: LiteralStringDouble,
|
||||
_TokenTypeName[734:753]: LiteralStringEscape,
|
||||
_TokenTypeLowerName[734:753]: LiteralStringEscape,
|
||||
_TokenTypeName[753:773]: LiteralStringHeredoc,
|
||||
_TokenTypeLowerName[753:773]: LiteralStringHeredoc,
|
||||
_TokenTypeName[773:794]: LiteralStringInterpol,
|
||||
_TokenTypeLowerName[773:794]: LiteralStringInterpol,
|
||||
_TokenTypeName[794:811]: LiteralStringName,
|
||||
_TokenTypeLowerName[794:811]: LiteralStringName,
|
||||
_TokenTypeName[811:829]: LiteralStringOther,
|
||||
_TokenTypeLowerName[811:829]: LiteralStringOther,
|
||||
_TokenTypeName[829:847]: LiteralStringRegex,
|
||||
_TokenTypeLowerName[829:847]: LiteralStringRegex,
|
||||
_TokenTypeName[847:866]: LiteralStringSingle,
|
||||
_TokenTypeLowerName[847:866]: LiteralStringSingle,
|
||||
_TokenTypeName[866:885]: LiteralStringSymbol,
|
||||
_TokenTypeLowerName[866:885]: LiteralStringSymbol,
|
||||
_TokenTypeName[885:898]: LiteralNumber,
|
||||
_TokenTypeLowerName[885:898]: LiteralNumber,
|
||||
_TokenTypeName[898:914]: LiteralNumberBin,
|
||||
_TokenTypeLowerName[898:914]: LiteralNumberBin,
|
||||
_TokenTypeName[914:932]: LiteralNumberFloat,
|
||||
_TokenTypeLowerName[914:932]: LiteralNumberFloat,
|
||||
_TokenTypeName[932:948]: LiteralNumberHex,
|
||||
_TokenTypeLowerName[932:948]: LiteralNumberHex,
|
||||
_TokenTypeName[948:968]: LiteralNumberInteger,
|
||||
_TokenTypeLowerName[948:968]: LiteralNumberInteger,
|
||||
_TokenTypeName[968:992]: LiteralNumberIntegerLong,
|
||||
_TokenTypeLowerName[968:992]: LiteralNumberIntegerLong,
|
||||
_TokenTypeName[992:1008]: LiteralNumberOct,
|
||||
_TokenTypeLowerName[992:1008]: LiteralNumberOct,
|
||||
_TokenTypeName[1008:1025]: LiteralNumberByte,
|
||||
_TokenTypeLowerName[1008:1025]: LiteralNumberByte,
|
||||
_TokenTypeName[1025:1033]: Operator,
|
||||
_TokenTypeLowerName[1025:1033]: Operator,
|
||||
_TokenTypeName[1033:1045]: OperatorWord,
|
||||
_TokenTypeLowerName[1033:1045]: OperatorWord,
|
||||
_TokenTypeName[1045:1056]: Punctuation,
|
||||
_TokenTypeLowerName[1045:1056]: Punctuation,
|
||||
_TokenTypeName[1056:1063]: Comment,
|
||||
_TokenTypeLowerName[1056:1063]: Comment,
|
||||
_TokenTypeName[1063:1078]: CommentHashbang,
|
||||
_TokenTypeLowerName[1063:1078]: CommentHashbang,
|
||||
_TokenTypeName[1078:1094]: CommentMultiline,
|
||||
_TokenTypeLowerName[1078:1094]: CommentMultiline,
|
||||
_TokenTypeName[1094:1107]: CommentSingle,
|
||||
_TokenTypeLowerName[1094:1107]: CommentSingle,
|
||||
_TokenTypeName[1107:1121]: CommentSpecial,
|
||||
_TokenTypeLowerName[1107:1121]: CommentSpecial,
|
||||
_TokenTypeName[1121:1135]: CommentPreproc,
|
||||
_TokenTypeLowerName[1121:1135]: CommentPreproc,
|
||||
_TokenTypeName[1135:1153]: CommentPreprocFile,
|
||||
_TokenTypeLowerName[1135:1153]: CommentPreprocFile,
|
||||
_TokenTypeName[1153:1160]: Generic,
|
||||
_TokenTypeLowerName[1153:1160]: Generic,
|
||||
_TokenTypeName[1160:1174]: GenericDeleted,
|
||||
_TokenTypeLowerName[1160:1174]: GenericDeleted,
|
||||
_TokenTypeName[1174:1185]: GenericEmph,
|
||||
_TokenTypeLowerName[1174:1185]: GenericEmph,
|
||||
_TokenTypeName[1185:1197]: GenericError,
|
||||
_TokenTypeLowerName[1185:1197]: GenericError,
|
||||
_TokenTypeName[1197:1211]: GenericHeading,
|
||||
_TokenTypeLowerName[1197:1211]: GenericHeading,
|
||||
_TokenTypeName[1211:1226]: GenericInserted,
|
||||
_TokenTypeLowerName[1211:1226]: GenericInserted,
|
||||
_TokenTypeName[1226:1239]: GenericOutput,
|
||||
_TokenTypeLowerName[1226:1239]: GenericOutput,
|
||||
_TokenTypeName[1239:1252]: GenericPrompt,
|
||||
_TokenTypeLowerName[1239:1252]: GenericPrompt,
|
||||
_TokenTypeName[1252:1265]: GenericStrong,
|
||||
_TokenTypeLowerName[1252:1265]: GenericStrong,
|
||||
_TokenTypeName[1265:1282]: GenericSubheading,
|
||||
_TokenTypeLowerName[1265:1282]: GenericSubheading,
|
||||
_TokenTypeName[1282:1298]: GenericTraceback,
|
||||
_TokenTypeLowerName[1282:1298]: GenericTraceback,
|
||||
_TokenTypeName[1298:1314]: GenericUnderline,
|
||||
_TokenTypeLowerName[1298:1314]: GenericUnderline,
|
||||
_TokenTypeName[1314:1318]: Text,
|
||||
_TokenTypeLowerName[1314:1318]: Text,
|
||||
_TokenTypeName[1318:1332]: TextWhitespace,
|
||||
_TokenTypeLowerName[1318:1332]: TextWhitespace,
|
||||
_TokenTypeName[1332:1342]: TextSymbol,
|
||||
_TokenTypeLowerName[1332:1342]: TextSymbol,
|
||||
_TokenTypeName[1342:1357]: TextPunctuation,
|
||||
_TokenTypeLowerName[1342:1357]: TextPunctuation,
|
||||
}
|
||||
|
||||
var _TokenTypeNames = []string{
|
||||
_TokenTypeName[0:4],
|
||||
_TokenTypeName[4:9],
|
||||
_TokenTypeName[9:14],
|
||||
_TokenTypeName[14:22],
|
||||
_TokenTypeName[22:30],
|
||||
_TokenTypeName[30:41],
|
||||
_TokenTypeName[41:50],
|
||||
_TokenTypeName[50:63],
|
||||
_TokenTypeName[63:79],
|
||||
_TokenTypeName[79:90],
|
||||
_TokenTypeName[90:94],
|
||||
_TokenTypeName[94:104],
|
||||
_TokenTypeName[104:114],
|
||||
_TokenTypeName[114:121],
|
||||
_TokenTypeName[121:128],
|
||||
_TokenTypeName[128:143],
|
||||
_TokenTypeName[143:161],
|
||||
_TokenTypeName[161:177],
|
||||
_TokenTypeName[177:190],
|
||||
_TokenTypeName[190:205],
|
||||
_TokenTypeName[205:216],
|
||||
_TokenTypeName[216:220],
|
||||
_TokenTypeName[220:233],
|
||||
_TokenTypeName[233:244],
|
||||
_TokenTypeName[244:261],
|
||||
_TokenTypeName[261:270],
|
||||
_TokenTypeName[270:282],
|
||||
_TokenTypeName[282:295],
|
||||
_TokenTypeName[295:305],
|
||||
_TokenTypeName[305:318],
|
||||
_TokenTypeName[318:330],
|
||||
_TokenTypeName[330:347],
|
||||
_TokenTypeName[347:358],
|
||||
_TokenTypeName[358:367],
|
||||
_TokenTypeName[367:380],
|
||||
_TokenTypeName[380:392],
|
||||
_TokenTypeName[392:401],
|
||||
_TokenTypeName[401:411],
|
||||
_TokenTypeName[411:423],
|
||||
_TokenTypeName[423:430],
|
||||
_TokenTypeName[430:442],
|
||||
_TokenTypeName[442:463],
|
||||
_TokenTypeName[463:480],
|
||||
_TokenTypeName[480:498],
|
||||
_TokenTypeName[498:518],
|
||||
_TokenTypeName[518:535],
|
||||
_TokenTypeName[535:542],
|
||||
_TokenTypeName[542:553],
|
||||
_TokenTypeName[553:565],
|
||||
_TokenTypeName[565:578],
|
||||
_TokenTypeName[578:596],
|
||||
_TokenTypeName[596:613],
|
||||
_TokenTypeName[613:634],
|
||||
_TokenTypeName[634:654],
|
||||
_TokenTypeName[654:671],
|
||||
_TokenTypeName[671:693],
|
||||
_TokenTypeName[693:709],
|
||||
_TokenTypeName[709:728],
|
||||
_TokenTypeName[728:747],
|
||||
_TokenTypeName[747:767],
|
||||
_TokenTypeName[767:788],
|
||||
_TokenTypeName[788:805],
|
||||
_TokenTypeName[805:823],
|
||||
_TokenTypeName[823:841],
|
||||
_TokenTypeName[841:860],
|
||||
_TokenTypeName[860:879],
|
||||
_TokenTypeName[879:892],
|
||||
_TokenTypeName[892:908],
|
||||
_TokenTypeName[908:926],
|
||||
_TokenTypeName[926:942],
|
||||
_TokenTypeName[942:962],
|
||||
_TokenTypeName[962:986],
|
||||
_TokenTypeName[986:1002],
|
||||
_TokenTypeName[1002:1010],
|
||||
_TokenTypeName[1010:1022],
|
||||
_TokenTypeName[1022:1033],
|
||||
_TokenTypeName[1033:1040],
|
||||
_TokenTypeName[1040:1055],
|
||||
_TokenTypeName[1055:1071],
|
||||
_TokenTypeName[1071:1084],
|
||||
_TokenTypeName[1084:1098],
|
||||
_TokenTypeName[1098:1112],
|
||||
_TokenTypeName[1112:1130],
|
||||
_TokenTypeName[1130:1137],
|
||||
_TokenTypeName[1137:1151],
|
||||
_TokenTypeName[1151:1162],
|
||||
_TokenTypeName[1162:1174],
|
||||
_TokenTypeName[1174:1188],
|
||||
_TokenTypeName[1188:1203],
|
||||
_TokenTypeName[1203:1216],
|
||||
_TokenTypeName[1216:1229],
|
||||
_TokenTypeName[1229:1242],
|
||||
_TokenTypeName[1242:1259],
|
||||
_TokenTypeName[1259:1275],
|
||||
_TokenTypeName[1275:1291],
|
||||
_TokenTypeName[1291:1295],
|
||||
_TokenTypeName[1295:1309],
|
||||
_TokenTypeName[1309:1319],
|
||||
_TokenTypeName[1319:1334],
|
||||
_TokenTypeName[0:6],
|
||||
_TokenTypeName[6:10],
|
||||
_TokenTypeName[10:15],
|
||||
_TokenTypeName[15:20],
|
||||
_TokenTypeName[20:28],
|
||||
_TokenTypeName[28:36],
|
||||
_TokenTypeName[36:47],
|
||||
_TokenTypeName[47:56],
|
||||
_TokenTypeName[56:69],
|
||||
_TokenTypeName[69:85],
|
||||
_TokenTypeName[85:96],
|
||||
_TokenTypeName[96:100],
|
||||
_TokenTypeName[100:110],
|
||||
_TokenTypeName[110:120],
|
||||
_TokenTypeName[120:127],
|
||||
_TokenTypeName[127:134],
|
||||
_TokenTypeName[134:149],
|
||||
_TokenTypeName[149:167],
|
||||
_TokenTypeName[167:183],
|
||||
_TokenTypeName[183:196],
|
||||
_TokenTypeName[196:211],
|
||||
_TokenTypeName[211:222],
|
||||
_TokenTypeName[222:226],
|
||||
_TokenTypeName[226:239],
|
||||
_TokenTypeName[239:250],
|
||||
_TokenTypeName[250:267],
|
||||
_TokenTypeName[267:276],
|
||||
_TokenTypeName[276:288],
|
||||
_TokenTypeName[288:301],
|
||||
_TokenTypeName[301:311],
|
||||
_TokenTypeName[311:324],
|
||||
_TokenTypeName[324:336],
|
||||
_TokenTypeName[336:353],
|
||||
_TokenTypeName[353:364],
|
||||
_TokenTypeName[364:373],
|
||||
_TokenTypeName[373:386],
|
||||
_TokenTypeName[386:398],
|
||||
_TokenTypeName[398:407],
|
||||
_TokenTypeName[407:417],
|
||||
_TokenTypeName[417:429],
|
||||
_TokenTypeName[429:436],
|
||||
_TokenTypeName[436:448],
|
||||
_TokenTypeName[448:469],
|
||||
_TokenTypeName[469:486],
|
||||
_TokenTypeName[486:504],
|
||||
_TokenTypeName[504:524],
|
||||
_TokenTypeName[524:541],
|
||||
_TokenTypeName[541:548],
|
||||
_TokenTypeName[548:559],
|
||||
_TokenTypeName[559:571],
|
||||
_TokenTypeName[571:584],
|
||||
_TokenTypeName[584:602],
|
||||
_TokenTypeName[602:619],
|
||||
_TokenTypeName[619:640],
|
||||
_TokenTypeName[640:660],
|
||||
_TokenTypeName[660:677],
|
||||
_TokenTypeName[677:699],
|
||||
_TokenTypeName[699:715],
|
||||
_TokenTypeName[715:734],
|
||||
_TokenTypeName[734:753],
|
||||
_TokenTypeName[753:773],
|
||||
_TokenTypeName[773:794],
|
||||
_TokenTypeName[794:811],
|
||||
_TokenTypeName[811:829],
|
||||
_TokenTypeName[829:847],
|
||||
_TokenTypeName[847:866],
|
||||
_TokenTypeName[866:885],
|
||||
_TokenTypeName[885:898],
|
||||
_TokenTypeName[898:914],
|
||||
_TokenTypeName[914:932],
|
||||
_TokenTypeName[932:948],
|
||||
_TokenTypeName[948:968],
|
||||
_TokenTypeName[968:992],
|
||||
_TokenTypeName[992:1008],
|
||||
_TokenTypeName[1008:1025],
|
||||
_TokenTypeName[1025:1033],
|
||||
_TokenTypeName[1033:1045],
|
||||
_TokenTypeName[1045:1056],
|
||||
_TokenTypeName[1056:1063],
|
||||
_TokenTypeName[1063:1078],
|
||||
_TokenTypeName[1078:1094],
|
||||
_TokenTypeName[1094:1107],
|
||||
_TokenTypeName[1107:1121],
|
||||
_TokenTypeName[1121:1135],
|
||||
_TokenTypeName[1135:1153],
|
||||
_TokenTypeName[1153:1160],
|
||||
_TokenTypeName[1160:1174],
|
||||
_TokenTypeName[1174:1185],
|
||||
_TokenTypeName[1185:1197],
|
||||
_TokenTypeName[1197:1211],
|
||||
_TokenTypeName[1211:1226],
|
||||
_TokenTypeName[1226:1239],
|
||||
_TokenTypeName[1239:1252],
|
||||
_TokenTypeName[1252:1265],
|
||||
_TokenTypeName[1265:1282],
|
||||
_TokenTypeName[1282:1298],
|
||||
_TokenTypeName[1298:1314],
|
||||
_TokenTypeName[1314:1318],
|
||||
_TokenTypeName[1318:1332],
|
||||
_TokenTypeName[1332:1342],
|
||||
_TokenTypeName[1342:1357],
|
||||
}
|
||||
|
||||
// TokenTypeString retrieves an enum value from the enum constants string name.
|
||||
|
||||
+3
@@ -41,6 +41,8 @@ const (
|
||||
Other
|
||||
// No highlighting.
|
||||
None
|
||||
// Don't emit this token to the output.
|
||||
Ignore
|
||||
// Used as an EOF marker / nil token
|
||||
EOFType TokenType = 0
|
||||
)
|
||||
@@ -122,6 +124,7 @@ const (
|
||||
LiteralNumberInteger
|
||||
LiteralNumberIntegerLong
|
||||
LiteralNumberOct
|
||||
LiteralNumberByte
|
||||
)
|
||||
|
||||
// Operators.
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
/debug
|
||||
/.vscode
|
||||
/.idea
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
linters:
|
||||
enable:
|
||||
- revive
|
||||
- govet
|
||||
- unconvert
|
||||
- megacheck
|
||||
- gas
|
||||
- gocyclo
|
||||
- dupl
|
||||
- misspell
|
||||
- unparam
|
||||
- unused
|
||||
- typecheck
|
||||
- ineffassign
|
||||
- stylecheck
|
||||
- gochecknoinits
|
||||
- exportloopref
|
||||
- gocritic
|
||||
- nakedret
|
||||
- gosimple
|
||||
- prealloc
|
||||
fast: false
|
||||
disable-all: true
|
||||
|
||||
issues:
|
||||
exclude-rules:
|
||||
- path: _test\.go
|
||||
linters:
|
||||
- dupl
|
||||
- text: "Errors unhandled"
|
||||
linters:
|
||||
- gosec
|
||||
exclude-use-default: false
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Didip Kerabat
|
||||
|
||||
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.
|
||||
+217
@@ -0,0 +1,217 @@
|
||||
[](http://godoc.org/github.com/didip/tollbooth)
|
||||
[](https://raw.githubusercontent.com/didip/tollbooth/master/LICENSE)
|
||||
|
||||
## Tollbooth
|
||||
|
||||
This is a generic middleware to rate-limit HTTP requests.
|
||||
|
||||
**NOTE 1:** This library is considered finished.
|
||||
|
||||
**NOTE 2:** Major version changes are backward-incompatible. `v2.0.0` streamlines the ugliness of the old API.
|
||||
|
||||
## Versions
|
||||
|
||||
**v1.0.0:** This version maintains the old API but all the thirdparty modules are moved to their own repo.
|
||||
|
||||
**v2.x.x:** Brand-new API for the sake of code cleanup, thread safety, & auto-expiring data structures.
|
||||
|
||||
**v3.x.x:** Apparently we have been using golang.org/x/time/rate incorrectly. See issue #48. It always limits X number per 1 second. The time duration is not changeable, so it does not make sense to pass TTL to tollbooth.
|
||||
|
||||
**v4.x.x:** Float64 for max requests per second
|
||||
|
||||
**v5.x.x:** go.mod and go.sum
|
||||
|
||||
**v6.x.x:** Replaced `go-cache` with `github.com/go-pkgz/expirable-cache` because `go-cache` leaks goroutines.
|
||||
|
||||
**v7.x.x:** Replaced `time/rate` with `embedded time/rate` so that we can support more rate limit headers.
|
||||
|
||||
**v8.x.x:** Address `RemoteIP` vulnerability concern by replacing `SetIPLookups` with `SetIPLookup`, an explicit way to pick the IP address.
|
||||
|
||||
|
||||
## Five Minute Tutorial
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/didip/tollbooth/v8"
|
||||
"github.com/didip/tollbooth/v8/limiter"
|
||||
)
|
||||
|
||||
func HelloHandler(w http.ResponseWriter, req *http.Request) {
|
||||
w.Write([]byte("Hello, World!"))
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Create a request limiter per handler.
|
||||
lmt := tollbooth.NewLimiter(1, nil)
|
||||
|
||||
// New in version >= 8, you must explicitly define how to pick the IP address.
|
||||
lmt.SetIPLookup(limiter.IPLookup{
|
||||
Name: "X-Real-IP",
|
||||
IndexFromRight: 0,
|
||||
})
|
||||
|
||||
http.Handle("/", tollbooth.LimitFuncHandler(lmt, HelloHandler))
|
||||
http.ListenAndServe(":12345", nil)
|
||||
}
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
1. Rate-limit by request's remote IP, path, methods, custom headers, & basic auth usernames.
|
||||
```go
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/didip/tollbooth/v8"
|
||||
"github.com/didip/tollbooth/v8/limiter"
|
||||
)
|
||||
|
||||
lmt := tollbooth.NewLimiter(1, nil)
|
||||
|
||||
// or create a limiter with expirable token buckets
|
||||
// This setting means:
|
||||
// create a 1 request/second limiter and
|
||||
// every token bucket in it will expire 1 hour after it was initially set.
|
||||
lmt = tollbooth.NewLimiter(1, &limiter.ExpirableOptions{DefaultExpirationTTL: time.Hour})
|
||||
|
||||
// New in version >= 8, you must explicitly define how to pick the IP address.
|
||||
// If IP address cannot be found, rate limiter will not be activated.
|
||||
lmt.SetIPLookup(limiter.IPLookup{
|
||||
// The name of lookup method.
|
||||
// Possible options are: RemoteAddr, X-Forwarded-For, X-Real-IP, CF-Connecting-IP
|
||||
// All other headers are considered unknown and will be ignored.
|
||||
Name: "X-Real-IP",
|
||||
|
||||
// The index position to pick the ip address from a comma separated list.
|
||||
// The index goes from right to left.
|
||||
//
|
||||
// When there are multiple of the same headers,
|
||||
// we will concat them together in the order of first to last seen.
|
||||
// And then we pick the IP using this index position.
|
||||
IndexFromRight: 0,
|
||||
})
|
||||
|
||||
// In version >= 8, lmt.SetIPLookups and lmt.GetIPLookups are removed.
|
||||
|
||||
// Limit only GET and POST requests.
|
||||
lmt.SetMethods([]string{"GET", "POST"})
|
||||
|
||||
// Limit based on basic auth usernames.
|
||||
// You add them on-load, or later as you handle requests.
|
||||
lmt.SetBasicAuthUsers([]string{"bob", "jane", "didip", "vip"})
|
||||
// You can remove them later as well.
|
||||
lmt.RemoveBasicAuthUsers([]string{"vip"})
|
||||
|
||||
// Limit request headers containing certain values.
|
||||
// You add them on-load, or later as you handle requests.
|
||||
lmt.SetHeader("X-Access-Token", []string{"abc123", "xyz098"})
|
||||
// You can remove all entries at once.
|
||||
lmt.RemoveHeader("X-Access-Token")
|
||||
// Or remove specific ones.
|
||||
lmt.RemoveHeaderEntries("X-Access-Token", []string{"limitless-token"})
|
||||
|
||||
// By the way, the setters are chainable. Example:
|
||||
lmt.SetMethods([]string{"GET", "POST"}).
|
||||
SetBasicAuthUsers([]string{"sansa"}).
|
||||
SetBasicAuthUsers([]string{"tyrion"})
|
||||
```
|
||||
|
||||
2. Compose your own middleware by using `LimitByKeys()`.
|
||||
|
||||
3. Header entries and basic auth users can expire over time (to conserve memory).
|
||||
|
||||
```go
|
||||
import "time"
|
||||
|
||||
lmt := tollbooth.NewLimiter(1, nil)
|
||||
|
||||
// Set a custom expiration TTL for token bucket.
|
||||
lmt.SetTokenBucketExpirationTTL(time.Hour)
|
||||
|
||||
// Set a custom expiration TTL for basic auth users.
|
||||
lmt.SetBasicAuthExpirationTTL(time.Hour)
|
||||
|
||||
// Set a custom expiration TTL for header entries.
|
||||
lmt.SetHeaderEntryExpirationTTL(time.Hour)
|
||||
```
|
||||
|
||||
4. Upon rejection, the following HTTP response headers are available to users:
|
||||
|
||||
* `X-Rate-Limit-Limit` The maximum request limit.
|
||||
|
||||
* `X-Rate-Limit-Duration` The rate-limiter duration.
|
||||
|
||||
* `X-Rate-Limit-Request-Forwarded-For` The rejected request `X-Forwarded-For`.
|
||||
|
||||
* `X-Rate-Limit-Request-Remote-Addr` The rejected request `RemoteAddr`.
|
||||
|
||||
Upon both success and rejection [RateLimit](https://datatracker.ietf.org/doc/html/draft-ietf-httpapi-ratelimit-headers) headers are sent:
|
||||
|
||||
* `RateLimit-Limit` The maximum request limit within the time window (1s).
|
||||
|
||||
* `RateLimit-Reset` The rate-limiter time window duration in seconds (always 1s).
|
||||
|
||||
* `RateLimit-Remaining` The remaining tokens.
|
||||
|
||||
5. Customize your own message or function when limit is reached.
|
||||
|
||||
```go
|
||||
lmt := tollbooth.NewLimiter(1, nil)
|
||||
|
||||
// New in version >= 8, you must explicitly define how to pick the IP address.
|
||||
lmt.SetIPLookup(limiter.IPLookup{
|
||||
Name: "X-Forwarded-For",
|
||||
IndexFromRight: 0,
|
||||
})
|
||||
|
||||
// Set a custom message.
|
||||
lmt.SetMessage("You have reached maximum request limit.")
|
||||
|
||||
// Set a custom content-type.
|
||||
lmt.SetMessageContentType("text/plain; charset=utf-8")
|
||||
|
||||
// Set a custom function for rejection.
|
||||
lmt.SetOnLimitReached(func(w http.ResponseWriter, r *http.Request) { fmt.Println("A request was rejected") })
|
||||
```
|
||||
|
||||
6. Tollbooth does not require external storage since it uses an algorithm called [Token Bucket](http://en.wikipedia.org/wiki/Token_bucket) [(Go library: golang.org/x/time/rate)](https://godoc.org/golang.org/x/time/rate).
|
||||
|
||||
## Other Web Frameworks
|
||||
|
||||
Sometimes, other frameworks require a little bit of shim to use Tollbooth. These shims below are contributed by the community, so I make no promises on how well they work. The one I am familiar with are: Chi, Gin, and Negroni.
|
||||
|
||||
* [Chi](https://github.com/didip/tollbooth_chi)
|
||||
|
||||
* [Echo](https://github.com/didip/tollbooth_echo)
|
||||
|
||||
* [FastHTTP](https://github.com/didip/tollbooth_fasthttp)
|
||||
|
||||
* [Gin](https://github.com/didip/tollbooth_gin)
|
||||
|
||||
* [GoRestful](https://github.com/didip/tollbooth_gorestful)
|
||||
|
||||
* [HTTPRouter](https://github.com/didip/tollbooth_httprouter)
|
||||
|
||||
* [Iris](https://github.com/didip/tollbooth_iris)
|
||||
|
||||
* [Negroni](https://github.com/didip/tollbooth_negroni)
|
||||
|
||||
## My other Go libraries
|
||||
|
||||
* [ErrStack](https://github.com/didip/errstack): A small library to combine errors and also display filename and line number.
|
||||
|
||||
* [Stopwatch](https://github.com/didip/stopwatch): A small library to measure latency of things. Useful if you want to report latency data to Graphite.
|
||||
|
||||
* [LaborUnion](https://github.com/didip/laborunion): A dynamic worker pool library.
|
||||
|
||||
* [Gomet](https://github.com/didip/gomet): Simple HTTP client & server long poll library for Go. Useful for receiving live updates without needing Websocket.
|
||||
|
||||
## Contributions
|
||||
|
||||
Before sending a PR with code changes, please make sure altered code is covered with tests which are passing, and that golangci-lint shows no errors.
|
||||
|
||||
To check the linter output, [install it](https://golangci-lint.run/usage/install/#local-installation) and then run `golangci-lint run` in the root directory of the repository.
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
// Package errors provide data structure for errors.
|
||||
package errors
|
||||
|
||||
import "fmt"
|
||||
|
||||
// HTTPError is an error struct that returns both message and status code.
|
||||
type HTTPError struct {
|
||||
Message string
|
||||
StatusCode int
|
||||
}
|
||||
|
||||
// Error returns error message.
|
||||
func (httperror *HTTPError) Error() string {
|
||||
return fmt.Sprintf("%v: %v", httperror.StatusCode, httperror.Message)
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
# This source code refers to The Go Authors for copyright purposes.
|
||||
# The master list of authors is in the main Go distribution,
|
||||
# visible at http://tip.golang.org/AUTHORS.
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
# This source code was written by the Go contributors.
|
||||
# The master list of contributors is in the main Go distribution,
|
||||
# visible at http://tip.golang.org/CONTRIBUTORS.
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
Additional IP Rights Grant (Patents)
|
||||
|
||||
"This implementation" means the copyrightable works distributed by
|
||||
Google as part of the Go project.
|
||||
|
||||
Google hereby grants to You a perpetual, worldwide, non-exclusive,
|
||||
no-charge, royalty-free, irrevocable (except as stated in this section)
|
||||
patent license to make, have made, use, offer to sell, sell, import,
|
||||
transfer and otherwise run, modify and propagate the contents of this
|
||||
implementation of Go, where such license applies only to those patent
|
||||
claims, both currently owned or controlled by Google and acquired in
|
||||
the future, licensable by Google that are necessarily infringed by this
|
||||
implementation of Go. This grant does not include claims that would be
|
||||
infringed only as a consequence of further modification of this
|
||||
implementation. If you or your agent or exclusive licensee institute or
|
||||
order or agree to the institution of patent litigation against any
|
||||
entity (including a cross-claim or counterclaim in a lawsuit) alleging
|
||||
that this implementation of Go or any code incorporated within this
|
||||
implementation of Go constitutes direct or contributory patent
|
||||
infringement, or inducement of patent infringement, then any patent
|
||||
rights granted to you under this License for this implementation of Go
|
||||
shall terminate as of the date such litigation is filed.
|
||||
+396
@@ -0,0 +1,396 @@
|
||||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package rate provides a rate limiter.
|
||||
package rate
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Limit defines the maximum frequency of some events.
|
||||
// Limit is represented as number of events per second.
|
||||
// A zero Limit allows no events.
|
||||
type Limit float64
|
||||
|
||||
// Inf is the infinite rate limit; it allows all events (even if burst is zero).
|
||||
const Inf = Limit(math.MaxFloat64)
|
||||
|
||||
// Every converts a minimum time interval between events to a Limit.
|
||||
func Every(interval time.Duration) Limit {
|
||||
if interval <= 0 {
|
||||
return Inf
|
||||
}
|
||||
return 1 / Limit(interval.Seconds())
|
||||
}
|
||||
|
||||
// A Limiter controls how frequently events are allowed to happen.
|
||||
// It implements a "token bucket" of size b, initially full and refilled
|
||||
// at rate r tokens per second.
|
||||
// Informally, in any large enough time interval, the Limiter limits the
|
||||
// rate to r tokens per second, with a maximum burst size of b events.
|
||||
// As a special case, if r == Inf (the infinite rate), b is ignored.
|
||||
// See https://en.wikipedia.org/wiki/Token_bucket for more about token buckets.
|
||||
//
|
||||
// The zero value is a valid Limiter, but it will reject all events.
|
||||
// Use NewLimiter to create non-zero Limiters.
|
||||
//
|
||||
// Limiter has three main methods, Allow, Reserve, and Wait.
|
||||
// Most callers should use Wait.
|
||||
//
|
||||
// Each of the three methods consumes a single token.
|
||||
// They differ in their behavior when no token is available.
|
||||
// If no token is available, Allow returns false.
|
||||
// If no token is available, Reserve returns a reservation for a future token
|
||||
// and the amount of time the caller must wait before using it.
|
||||
// If no token is available, Wait blocks until one can be obtained
|
||||
// or its associated context.Context is canceled.
|
||||
//
|
||||
// The methods AllowN, ReserveN, and WaitN consume n tokens.
|
||||
type Limiter struct {
|
||||
mu sync.Mutex
|
||||
limit Limit
|
||||
burst int
|
||||
tokens float64
|
||||
// last is the last time the limiter's tokens field was updated
|
||||
last time.Time
|
||||
// lastEvent is the latest time of a rate-limited event (past or future)
|
||||
lastEvent time.Time
|
||||
}
|
||||
|
||||
// Limit returns the maximum overall event rate.
|
||||
func (lim *Limiter) Limit() Limit {
|
||||
lim.mu.Lock()
|
||||
defer lim.mu.Unlock()
|
||||
return lim.limit
|
||||
}
|
||||
|
||||
// Burst returns the maximum burst size. Burst is the maximum number of tokens
|
||||
// that can be consumed in a single call to Allow, Reserve, or Wait, so higher
|
||||
// Burst values allow more events to happen at once.
|
||||
// A zero Burst allows no events, unless limit == Inf.
|
||||
func (lim *Limiter) Burst() int {
|
||||
lim.mu.Lock()
|
||||
defer lim.mu.Unlock()
|
||||
return lim.burst
|
||||
}
|
||||
|
||||
// NewLimiter returns a new Limiter that allows events up to rate r and permits
|
||||
// bursts of at most b tokens.
|
||||
func NewLimiter(r Limit, b int) *Limiter {
|
||||
return &Limiter{
|
||||
limit: r,
|
||||
burst: b,
|
||||
}
|
||||
}
|
||||
|
||||
// Allow is shorthand for AllowN(time.Now(), 1).
|
||||
func (lim *Limiter) Allow() bool {
|
||||
return lim.AllowN(time.Now(), 1)
|
||||
}
|
||||
|
||||
// TokensAt returns the number of tokens available for the given time.
|
||||
func (lim *Limiter) TokensAt(t time.Time) float64 {
|
||||
lim.mu.Lock()
|
||||
_, _, tokens := lim.advance(t) // does not mutate lim
|
||||
lim.mu.Unlock()
|
||||
return tokens
|
||||
}
|
||||
|
||||
// AllowN reports whether n events may happen at time now.
|
||||
// Use this method if you intend to drop / skip events that exceed the rate limit.
|
||||
// Otherwise use Reserve or Wait.
|
||||
func (lim *Limiter) AllowN(now time.Time, n int) bool {
|
||||
return lim.reserveN(now, n, 0).ok
|
||||
}
|
||||
|
||||
// A Reservation holds information about events that are permitted by a Limiter to happen after a delay.
|
||||
// A Reservation may be canceled, which may enable the Limiter to permit additional events.
|
||||
type Reservation struct {
|
||||
ok bool
|
||||
lim *Limiter
|
||||
tokens int
|
||||
timeToAct time.Time
|
||||
// This is the Limit at reservation time, it can change later.
|
||||
limit Limit
|
||||
}
|
||||
|
||||
// OK returns whether the limiter can provide the requested number of tokens
|
||||
// within the maximum wait time. If OK is false, Delay returns InfDuration, and
|
||||
// Cancel does nothing.
|
||||
func (r *Reservation) OK() bool {
|
||||
return r.ok
|
||||
}
|
||||
|
||||
// Delay is shorthand for DelayFrom(time.Now()).
|
||||
func (r *Reservation) Delay() time.Duration {
|
||||
return r.DelayFrom(time.Now())
|
||||
}
|
||||
|
||||
// InfDuration is the duration returned by Delay when a Reservation is not OK.
|
||||
const InfDuration = time.Duration(1<<63 - 1)
|
||||
|
||||
// DelayFrom returns the duration for which the reservation holder must wait
|
||||
// before taking the reserved action. Zero duration means act immediately.
|
||||
// InfDuration means the limiter cannot grant the tokens requested in this
|
||||
// Reservation within the maximum wait time.
|
||||
func (r *Reservation) DelayFrom(now time.Time) time.Duration {
|
||||
if !r.ok {
|
||||
return InfDuration
|
||||
}
|
||||
delay := r.timeToAct.Sub(now)
|
||||
if delay < 0 {
|
||||
return 0
|
||||
}
|
||||
return delay
|
||||
}
|
||||
|
||||
// Cancel is shorthand for CancelAt(time.Now()).
|
||||
func (r *Reservation) Cancel() {
|
||||
r.CancelAt(time.Now())
|
||||
}
|
||||
|
||||
// CancelAt indicates that the reservation holder will not perform the reserved action
|
||||
// and reverses the effects of this Reservation on the rate limit as much as possible,
|
||||
// considering that other reservations may have already been made.
|
||||
func (r *Reservation) CancelAt(now time.Time) {
|
||||
if !r.ok {
|
||||
return
|
||||
}
|
||||
|
||||
r.lim.mu.Lock()
|
||||
defer r.lim.mu.Unlock()
|
||||
|
||||
if r.lim.limit == Inf || r.tokens == 0 || r.timeToAct.Before(now) {
|
||||
return
|
||||
}
|
||||
|
||||
// calculate tokens to restore
|
||||
// The duration between lim.lastEvent and r.timeToAct tells us how many tokens were reserved
|
||||
// after r was obtained. These tokens should not be restored.
|
||||
restoreTokens := float64(r.tokens) - r.limit.tokensFromDuration(r.lim.lastEvent.Sub(r.timeToAct))
|
||||
if restoreTokens <= 0 {
|
||||
return
|
||||
}
|
||||
// advance time to now
|
||||
now, _, tokens := r.lim.advance(now)
|
||||
// calculate new number of tokens
|
||||
tokens += restoreTokens
|
||||
if burst := float64(r.lim.burst); tokens > burst {
|
||||
tokens = burst
|
||||
}
|
||||
// update state
|
||||
r.lim.last = now
|
||||
r.lim.tokens = tokens
|
||||
if r.timeToAct == r.lim.lastEvent {
|
||||
prevEvent := r.timeToAct.Add(r.limit.durationFromTokens(float64(-r.tokens)))
|
||||
if !prevEvent.Before(now) {
|
||||
r.lim.lastEvent = prevEvent
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reserve is shorthand for ReserveN(time.Now(), 1).
|
||||
func (lim *Limiter) Reserve() *Reservation {
|
||||
return lim.ReserveN(time.Now(), 1)
|
||||
}
|
||||
|
||||
// ReserveN returns a Reservation that indicates how long the caller must wait before n events happen.
|
||||
// The Limiter takes this Reservation into account when allowing future events.
|
||||
// The returned Reservation’s OK() method returns false if n exceeds the Limiter's burst size.
|
||||
// Usage example:
|
||||
// r := lim.ReserveN(time.Now(), 1)
|
||||
// if !r.OK() {
|
||||
// // Not allowed to act! Did you remember to set lim.burst to be > 0 ?
|
||||
// return
|
||||
// }
|
||||
// time.Sleep(r.Delay())
|
||||
// Act()
|
||||
// Use this method if you wish to wait and slow down in accordance with the rate limit without dropping events.
|
||||
// If you need to respect a deadline or cancel the delay, use Wait instead.
|
||||
// To drop or skip events exceeding rate limit, use Allow instead.
|
||||
func (lim *Limiter) ReserveN(now time.Time, n int) *Reservation {
|
||||
r := lim.reserveN(now, n, InfDuration)
|
||||
return &r
|
||||
}
|
||||
|
||||
// Wait is shorthand for WaitN(ctx, 1).
|
||||
func (lim *Limiter) Wait(ctx context.Context) (err error) {
|
||||
return lim.WaitN(ctx, 1)
|
||||
}
|
||||
|
||||
// WaitN blocks until lim permits n events to happen.
|
||||
// It returns an error if n exceeds the Limiter's burst size, the Context is
|
||||
// canceled, or the expected wait time exceeds the Context's Deadline.
|
||||
// The burst limit is ignored if the rate limit is Inf.
|
||||
func (lim *Limiter) WaitN(ctx context.Context, n int) (err error) {
|
||||
lim.mu.Lock()
|
||||
burst := lim.burst
|
||||
limit := lim.limit
|
||||
lim.mu.Unlock()
|
||||
|
||||
if n > burst && limit != Inf {
|
||||
return fmt.Errorf("rate: Wait(n=%d) exceeds limiter's burst %d", n, burst)
|
||||
}
|
||||
// Check if ctx is already cancelled
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
default:
|
||||
}
|
||||
// Determine wait limit
|
||||
now := time.Now()
|
||||
waitLimit := InfDuration
|
||||
if deadline, ok := ctx.Deadline(); ok {
|
||||
waitLimit = deadline.Sub(now)
|
||||
}
|
||||
// Reserve
|
||||
r := lim.reserveN(now, n, waitLimit)
|
||||
if !r.ok {
|
||||
return fmt.Errorf("rate: Wait(n=%d) would exceed context deadline", n)
|
||||
}
|
||||
// Wait if necessary
|
||||
delay := r.DelayFrom(now)
|
||||
if delay == 0 {
|
||||
return nil
|
||||
}
|
||||
t := time.NewTimer(delay)
|
||||
defer t.Stop()
|
||||
select {
|
||||
case <-t.C:
|
||||
// We can proceed.
|
||||
return nil
|
||||
case <-ctx.Done():
|
||||
// Context was canceled before we could proceed. Cancel the
|
||||
// reservation, which may permit other events to proceed sooner.
|
||||
r.Cancel()
|
||||
return ctx.Err()
|
||||
}
|
||||
}
|
||||
|
||||
// SetLimit is shorthand for SetLimitAt(time.Now(), newLimit).
|
||||
func (lim *Limiter) SetLimit(newLimit Limit) {
|
||||
lim.SetLimitAt(time.Now(), newLimit)
|
||||
}
|
||||
|
||||
// SetLimitAt sets a new Limit for the limiter. The new Limit, and Burst, may be violated
|
||||
// or underutilized by those which reserved (using Reserve or Wait) but did not yet act
|
||||
// before SetLimitAt was called.
|
||||
func (lim *Limiter) SetLimitAt(now time.Time, newLimit Limit) {
|
||||
lim.mu.Lock()
|
||||
defer lim.mu.Unlock()
|
||||
|
||||
now, _, tokens := lim.advance(now)
|
||||
|
||||
lim.last = now
|
||||
lim.tokens = tokens
|
||||
lim.limit = newLimit
|
||||
}
|
||||
|
||||
// SetBurst is shorthand for SetBurstAt(time.Now(), newBurst).
|
||||
func (lim *Limiter) SetBurst(newBurst int) {
|
||||
lim.SetBurstAt(time.Now(), newBurst)
|
||||
}
|
||||
|
||||
// SetBurstAt sets a new burst size for the limiter.
|
||||
func (lim *Limiter) SetBurstAt(now time.Time, newBurst int) {
|
||||
lim.mu.Lock()
|
||||
defer lim.mu.Unlock()
|
||||
|
||||
now, _, tokens := lim.advance(now)
|
||||
|
||||
lim.last = now
|
||||
lim.tokens = tokens
|
||||
lim.burst = newBurst
|
||||
}
|
||||
|
||||
// reserveN is a helper method for AllowN, ReserveN, and WaitN.
|
||||
// maxFutureReserve specifies the maximum reservation wait duration allowed.
|
||||
// reserveN returns Reservation, not *Reservation, to avoid allocation in AllowN and WaitN.
|
||||
func (lim *Limiter) reserveN(now time.Time, n int, maxFutureReserve time.Duration) Reservation {
|
||||
lim.mu.Lock()
|
||||
|
||||
if lim.limit == Inf {
|
||||
lim.mu.Unlock()
|
||||
return Reservation{
|
||||
ok: true,
|
||||
lim: lim,
|
||||
tokens: n,
|
||||
timeToAct: now,
|
||||
}
|
||||
}
|
||||
|
||||
now, last, tokens := lim.advance(now)
|
||||
|
||||
// Calculate the remaining number of tokens resulting from the request.
|
||||
tokens -= float64(n)
|
||||
|
||||
// Calculate the wait duration
|
||||
var waitDuration time.Duration
|
||||
if tokens < 0 {
|
||||
waitDuration = lim.limit.durationFromTokens(-tokens)
|
||||
}
|
||||
|
||||
// Decide result
|
||||
ok := n <= lim.burst && waitDuration <= maxFutureReserve
|
||||
|
||||
// Prepare reservation
|
||||
r := Reservation{
|
||||
ok: ok,
|
||||
lim: lim,
|
||||
limit: lim.limit,
|
||||
}
|
||||
if ok {
|
||||
r.tokens = n
|
||||
r.timeToAct = now.Add(waitDuration)
|
||||
}
|
||||
|
||||
// Update state
|
||||
if ok {
|
||||
lim.last = now
|
||||
lim.tokens = tokens
|
||||
lim.lastEvent = r.timeToAct
|
||||
} else {
|
||||
lim.last = last
|
||||
}
|
||||
|
||||
lim.mu.Unlock()
|
||||
return r
|
||||
}
|
||||
|
||||
// advance calculates and returns an updated state for lim resulting from the passage of time.
|
||||
// lim is not changed.
|
||||
// advance requires that lim.mu is held.
|
||||
func (lim *Limiter) advance(now time.Time) (newNow time.Time, newLast time.Time, newTokens float64) {
|
||||
last := lim.last
|
||||
if now.Before(last) {
|
||||
last = now
|
||||
}
|
||||
|
||||
// Calculate the new number of tokens, due to time that passed.
|
||||
elapsed := now.Sub(last)
|
||||
delta := lim.limit.tokensFromDuration(elapsed)
|
||||
tokens := lim.tokens + delta
|
||||
if burst := float64(lim.burst); tokens > burst {
|
||||
tokens = burst
|
||||
}
|
||||
return now, last, tokens
|
||||
}
|
||||
|
||||
// durationFromTokens is a unit conversion function from the number of tokens to the duration
|
||||
// of time it takes to accumulate them at a rate of limit tokens per second.
|
||||
func (limit Limit) durationFromTokens(tokens float64) time.Duration {
|
||||
seconds := tokens / float64(limit)
|
||||
return time.Duration(float64(time.Second) * seconds)
|
||||
}
|
||||
|
||||
// tokensFromDuration is a unit conversion function from a time duration to the number of tokens
|
||||
// which could be accumulated during that duration at a rate of limit tokens per second.
|
||||
func (limit Limit) tokensFromDuration(d time.Duration) float64 {
|
||||
return d.Seconds() * float64(limit)
|
||||
}
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
// Package libstring provides various string related functions.
|
||||
package libstring
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/didip/tollbooth/v8/limiter"
|
||||
)
|
||||
|
||||
// StringInSlice finds needle in a slice of strings.
|
||||
func StringInSlice(sliceString []string, needle string) bool {
|
||||
for _, b := range sliceString {
|
||||
if b == needle {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// RemoteIPFromIPLookup picks an ip address explicitly from limiter.IPLookup criteria.
|
||||
// This function is intended to replace RemoteIP function.
|
||||
func RemoteIPFromIPLookup(ipLookup limiter.IPLookup, r *http.Request) string {
|
||||
switch ipLookup.Name {
|
||||
case "RemoteAddr":
|
||||
// 1. Cover the basic use cases for both ipv4 and ipv6
|
||||
ip, _, err := net.SplitHostPort(r.RemoteAddr)
|
||||
if err != nil {
|
||||
// 2. Upon error, just return the remote addr.
|
||||
return r.RemoteAddr
|
||||
}
|
||||
return ip
|
||||
|
||||
case "X-Forwarded-For", "X-Real-IP", "CF-Connecting-IP":
|
||||
ipAddrListCommaSeparated := r.Header.Values(ipLookup.Name)
|
||||
|
||||
ipAddrCommaSeparated := strings.Join(ipAddrListCommaSeparated, ",")
|
||||
|
||||
ips := strings.Split(ipAddrCommaSeparated, ",")
|
||||
for i, p := range ips {
|
||||
ips[i] = strings.TrimSpace(p)
|
||||
}
|
||||
|
||||
ipIndex := len(ips) - 1 - ipLookup.IndexFromRight
|
||||
if ipIndex < 0 {
|
||||
ipIndex = 0
|
||||
}
|
||||
|
||||
return ips[ipIndex]
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// CanonicalizeIP returns a form of ip suitable for comparison to other IPs.
|
||||
// For IPv4 addresses, this is simply the whole string.
|
||||
// For IPv6 addresses, this is the /64 prefix.
|
||||
func CanonicalizeIP(ip string) string {
|
||||
isIPv6 := false
|
||||
// This is how net.ParseIP decides if an address is IPv6
|
||||
// https://cs.opensource.google/go/go/+/refs/tags/go1.17.7:src/net/ip.go;l=704
|
||||
for i := 0; !isIPv6 && i < len(ip); i++ {
|
||||
switch ip[i] {
|
||||
case '.':
|
||||
// IPv4
|
||||
return ip
|
||||
case ':':
|
||||
// IPv6
|
||||
isIPv6 = true
|
||||
}
|
||||
}
|
||||
if !isIPv6 {
|
||||
// Not an IP address at all
|
||||
return ip
|
||||
}
|
||||
|
||||
// By default, the string representation of a net.IPNet (masked IP address) is just
|
||||
// "full_address/mask_bits". But using that will result in different addresses with
|
||||
// the same /64 prefix comparing differently. So we need to zero out the last 64 bits
|
||||
// so that all IPs in the same prefix will be the same.
|
||||
//
|
||||
// Note: When 1.18 is the minimum Go version, this can be written more cleanly like:
|
||||
// netip.PrefixFrom(netip.MustParseAddr(ipv6), 64).Masked().Addr().String()
|
||||
// (With appropriate error checking.)
|
||||
|
||||
ipv6 := net.ParseIP(ip)
|
||||
if ipv6 == nil {
|
||||
return ip
|
||||
}
|
||||
|
||||
const bytesToZero = (128 - 64) / 8
|
||||
for i := len(ipv6) - bytesToZero; i < len(ipv6); i++ {
|
||||
ipv6[i] = 0
|
||||
}
|
||||
|
||||
// Note that this doesn't have the "/64" suffix customary with a CIDR representation,
|
||||
// but those three bytes add nothing for us.
|
||||
return ipv6.String()
|
||||
}
|
||||
+624
@@ -0,0 +1,624 @@
|
||||
// Package limiter provides data structure to configure rate-limiter.
|
||||
package limiter
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
cache "github.com/go-pkgz/expirable-cache/v3"
|
||||
|
||||
"github.com/didip/tollbooth/v8/internal/time/rate"
|
||||
)
|
||||
|
||||
// New is a constructor for Limiter.
|
||||
func New(generalExpirableOptions *ExpirableOptions) *Limiter {
|
||||
lmt := &Limiter{}
|
||||
|
||||
lmt.SetMessageContentType("text/plain; charset=utf-8").
|
||||
SetMessage("You have reached maximum request limit.").
|
||||
SetStatusCode(429).
|
||||
SetOnLimitReached(nil).
|
||||
SetForwardedForIndexFromBehind(0).
|
||||
SetHeaders(make(map[string][]string)).
|
||||
SetContextValues(make(map[string][]string)).
|
||||
SetIgnoreURL(false)
|
||||
|
||||
if generalExpirableOptions != nil {
|
||||
lmt.generalExpirableOptions = generalExpirableOptions
|
||||
} else {
|
||||
lmt.generalExpirableOptions = &ExpirableOptions{}
|
||||
}
|
||||
|
||||
// Default for DefaultExpirationTTL is 10 years.
|
||||
if lmt.generalExpirableOptions.DefaultExpirationTTL <= 0 {
|
||||
lmt.generalExpirableOptions.DefaultExpirationTTL = 87600 * time.Hour
|
||||
}
|
||||
|
||||
lmt.tokenBuckets = cache.NewCache[string, *rate.Limiter]().WithTTL(lmt.generalExpirableOptions.DefaultExpirationTTL)
|
||||
|
||||
lmt.basicAuthUsers = cache.NewCache[string, bool]().WithTTL(lmt.generalExpirableOptions.DefaultExpirationTTL)
|
||||
|
||||
return lmt
|
||||
}
|
||||
|
||||
// IPLookup is a config struct to define how users want to pick the remote IP address.
|
||||
type IPLookup struct {
|
||||
// The name of lookup method.
|
||||
// Possible options are: RemoteAddr, X-Forwarded-For, X-Real-IP, CF-Connecting-IP
|
||||
// All other headers are considered unknown and will be ignored.
|
||||
Name string
|
||||
|
||||
// The index position to pick the ip address from a comma separated list.
|
||||
// The index goes from right to left.
|
||||
IndexFromRight int
|
||||
}
|
||||
|
||||
// Limiter is a config struct to limit a particular request handler.
|
||||
type Limiter struct {
|
||||
// Maximum number of requests to limit per second.
|
||||
max float64
|
||||
|
||||
// Limiter burst size
|
||||
burst int
|
||||
|
||||
// HTTP message when limit is reached.
|
||||
message string
|
||||
|
||||
// Content-Type for Message
|
||||
messageContentType string
|
||||
|
||||
// HTTP status code when limit is reached.
|
||||
statusCode int
|
||||
|
||||
// A function to call when a request is rejected.
|
||||
onLimitReached func(w http.ResponseWriter, r *http.Request)
|
||||
|
||||
// An option to write back what you want upon reaching a limit.
|
||||
overrideDefaultResponseWriter bool
|
||||
|
||||
// Explicitly define how to look up IP address.
|
||||
// This is intended to replace ipLookups
|
||||
explicitIPLookup IPLookup
|
||||
|
||||
forwardedForIndex int
|
||||
|
||||
// List of HTTP Methods to limit (GET, POST, PUT, etc.).
|
||||
// Empty means limit all methods.
|
||||
methods []string
|
||||
|
||||
// Able to configure token bucket expirations.
|
||||
generalExpirableOptions *ExpirableOptions
|
||||
|
||||
// List of basic auth usernames to limit.
|
||||
basicAuthUsers cache.Cache[string, bool]
|
||||
|
||||
// Map of HTTP headers to limit.
|
||||
// Empty means skip headers checking.
|
||||
headers map[string]cache.Cache[string, bool]
|
||||
|
||||
// Map of Context values to limit.
|
||||
contextValues map[string]cache.Cache[string, bool]
|
||||
|
||||
// Map of limiters with TTL
|
||||
tokenBuckets cache.Cache[string, *rate.Limiter]
|
||||
|
||||
// Ignore URL on the rate limiter keys
|
||||
ignoreURL bool
|
||||
|
||||
tokenBucketExpirationTTL time.Duration
|
||||
basicAuthExpirationTTL time.Duration
|
||||
headerEntryExpirationTTL time.Duration
|
||||
contextEntryExpirationTTL time.Duration
|
||||
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
// SetTokenBucketExpirationTTL is thread-safe way of setting custom token bucket expiration TTL.
|
||||
func (l *Limiter) SetTokenBucketExpirationTTL(ttl time.Duration) *Limiter {
|
||||
l.Lock()
|
||||
l.tokenBucketExpirationTTL = ttl
|
||||
l.Unlock()
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
// GetTokenBucketExpirationTTL is thread-safe way of getting custom token bucket expiration TTL.
|
||||
func (l *Limiter) GetTokenBucketExpirationTTL() time.Duration {
|
||||
l.RLock()
|
||||
defer l.RUnlock()
|
||||
return l.tokenBucketExpirationTTL
|
||||
}
|
||||
|
||||
// SetBasicAuthExpirationTTL is thread-safe way of setting custom basic auth expiration TTL.
|
||||
func (l *Limiter) SetBasicAuthExpirationTTL(ttl time.Duration) *Limiter {
|
||||
l.Lock()
|
||||
l.basicAuthExpirationTTL = ttl
|
||||
l.Unlock()
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
// GetBasicAuthExpirationTTL is thread-safe way of getting custom basic auth expiration TTL.
|
||||
func (l *Limiter) GetBasicAuthExpirationTTL() time.Duration {
|
||||
l.RLock()
|
||||
defer l.RUnlock()
|
||||
return l.basicAuthExpirationTTL
|
||||
}
|
||||
|
||||
// SetHeaderEntryExpirationTTL is thread-safe way of setting custom basic auth expiration TTL.
|
||||
func (l *Limiter) SetHeaderEntryExpirationTTL(ttl time.Duration) *Limiter {
|
||||
l.Lock()
|
||||
l.headerEntryExpirationTTL = ttl
|
||||
l.Unlock()
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
// GetHeaderEntryExpirationTTL is thread-safe way of getting custom basic auth expiration TTL.
|
||||
func (l *Limiter) GetHeaderEntryExpirationTTL() time.Duration {
|
||||
l.RLock()
|
||||
defer l.RUnlock()
|
||||
return l.headerEntryExpirationTTL
|
||||
}
|
||||
|
||||
// SetContextValueEntryExpirationTTL is thread-safe way of setting custom Context value expiration TTL.
|
||||
func (l *Limiter) SetContextValueEntryExpirationTTL(ttl time.Duration) *Limiter {
|
||||
l.Lock()
|
||||
l.contextEntryExpirationTTL = ttl
|
||||
l.Unlock()
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
// GetContextValueEntryExpirationTTL is thread-safe way of getting custom Context value expiration TTL.
|
||||
func (l *Limiter) GetContextValueEntryExpirationTTL() time.Duration {
|
||||
l.RLock()
|
||||
defer l.RUnlock()
|
||||
return l.contextEntryExpirationTTL
|
||||
}
|
||||
|
||||
// SetMax is thread-safe way of setting maximum number of requests to limit per second.
|
||||
func (l *Limiter) SetMax(max float64) *Limiter {
|
||||
l.Lock()
|
||||
l.max = max
|
||||
l.Unlock()
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
// GetMax is thread-safe way of getting maximum number of requests to limit per second.
|
||||
func (l *Limiter) GetMax() float64 {
|
||||
l.RLock()
|
||||
defer l.RUnlock()
|
||||
return l.max
|
||||
}
|
||||
|
||||
// SetBurst is thread-safe way of setting maximum burst size.
|
||||
func (l *Limiter) SetBurst(burst int) *Limiter {
|
||||
l.Lock()
|
||||
l.burst = burst
|
||||
l.Unlock()
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
// GetBurst is thread-safe way of setting maximum burst size.
|
||||
func (l *Limiter) GetBurst() int {
|
||||
l.RLock()
|
||||
defer l.RUnlock()
|
||||
|
||||
return l.burst
|
||||
}
|
||||
|
||||
// SetMessage is thread-safe way of setting HTTP message when limit is reached.
|
||||
func (l *Limiter) SetMessage(msg string) *Limiter {
|
||||
l.Lock()
|
||||
l.message = msg
|
||||
l.Unlock()
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
// GetMessage is thread-safe way of getting HTTP message when limit is reached.
|
||||
func (l *Limiter) GetMessage() string {
|
||||
l.RLock()
|
||||
defer l.RUnlock()
|
||||
return l.message
|
||||
}
|
||||
|
||||
// SetMessageContentType is thread-safe way of setting HTTP message Content-Type when limit is reached.
|
||||
func (l *Limiter) SetMessageContentType(contentType string) *Limiter {
|
||||
l.Lock()
|
||||
l.messageContentType = contentType
|
||||
l.Unlock()
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
// GetMessageContentType is thread-safe way of getting HTTP message Content-Type when limit is reached.
|
||||
func (l *Limiter) GetMessageContentType() string {
|
||||
l.RLock()
|
||||
defer l.RUnlock()
|
||||
return l.messageContentType
|
||||
}
|
||||
|
||||
// SetStatusCode is thread-safe way of setting HTTP status code when limit is reached.
|
||||
func (l *Limiter) SetStatusCode(statusCode int) *Limiter {
|
||||
l.Lock()
|
||||
l.statusCode = statusCode
|
||||
l.Unlock()
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
// GetStatusCode is thread-safe way of getting HTTP status code when limit is reached.
|
||||
func (l *Limiter) GetStatusCode() int {
|
||||
l.RLock()
|
||||
defer l.RUnlock()
|
||||
return l.statusCode
|
||||
}
|
||||
|
||||
// SetOnLimitReached is thread-safe way of setting after-rejection function when limit is reached.
|
||||
func (l *Limiter) SetOnLimitReached(fn func(w http.ResponseWriter, r *http.Request)) *Limiter {
|
||||
l.Lock()
|
||||
l.onLimitReached = fn
|
||||
l.Unlock()
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
// ExecOnLimitReached is thread-safe way of executing after-rejection function when limit is reached.
|
||||
func (l *Limiter) ExecOnLimitReached(w http.ResponseWriter, r *http.Request) {
|
||||
l.RLock()
|
||||
fn := l.onLimitReached
|
||||
l.RUnlock()
|
||||
|
||||
if fn != nil {
|
||||
fn(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
// SetOverrideDefaultResponseWriter is a thread-safe way of setting the response writer override variable.
|
||||
func (l *Limiter) SetOverrideDefaultResponseWriter(override bool) *Limiter {
|
||||
l.Lock()
|
||||
l.overrideDefaultResponseWriter = override
|
||||
l.Unlock()
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
// GetOverrideDefaultResponseWriter is a thread-safe way of getting the response writer override variable.
|
||||
func (l *Limiter) GetOverrideDefaultResponseWriter() bool {
|
||||
l.RLock()
|
||||
defer l.RUnlock()
|
||||
return l.overrideDefaultResponseWriter
|
||||
}
|
||||
|
||||
// SetIPLookup is thread-safe way of setting an explicit way to look up IP address.
|
||||
// This method is intended to replace SetIPLookups (version 6 or older).
|
||||
func (l *Limiter) SetIPLookup(lookup IPLookup) *Limiter {
|
||||
l.Lock()
|
||||
l.explicitIPLookup = lookup
|
||||
l.Unlock()
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
// GetIPLookup is thread-safe way of getting an explicit way to look up IP address.
|
||||
// This method is intended to replace the old GetIPLookups (version 6 or older).
|
||||
func (l *Limiter) GetIPLookup() IPLookup {
|
||||
l.RLock()
|
||||
defer l.RUnlock()
|
||||
return l.explicitIPLookup
|
||||
}
|
||||
|
||||
// SetIgnoreURL is thread-safe way of setting whenever ignore the URL on rate limit keys
|
||||
func (l *Limiter) SetIgnoreURL(enabled bool) *Limiter {
|
||||
l.Lock()
|
||||
l.ignoreURL = enabled
|
||||
l.Unlock()
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
// GetIgnoreURL returns whether the URL is ignored in the rate limit key set
|
||||
func (l *Limiter) GetIgnoreURL() bool {
|
||||
l.RLock()
|
||||
defer l.RUnlock()
|
||||
return l.ignoreURL
|
||||
}
|
||||
|
||||
// SetForwardedForIndexFromBehind is thread-safe way of setting which X-Forwarded-For index to choose.
|
||||
func (l *Limiter) SetForwardedForIndexFromBehind(forwardedForIndex int) *Limiter {
|
||||
l.Lock()
|
||||
l.forwardedForIndex = forwardedForIndex
|
||||
l.Unlock()
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
// GetForwardedForIndexFromBehind is thread-safe way of getting which X-Forwarded-For index to choose.
|
||||
func (l *Limiter) GetForwardedForIndexFromBehind() int {
|
||||
l.RLock()
|
||||
defer l.RUnlock()
|
||||
return l.forwardedForIndex
|
||||
}
|
||||
|
||||
// SetMethods is thread-safe way of setting list of HTTP Methods to limit (GET, POST, PUT, etc.).
|
||||
func (l *Limiter) SetMethods(methods []string) *Limiter {
|
||||
l.Lock()
|
||||
l.methods = methods
|
||||
l.Unlock()
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
// GetMethods is thread-safe way of getting list of HTTP Methods to limit (GET, POST, PUT, etc.).
|
||||
func (l *Limiter) GetMethods() []string {
|
||||
l.RLock()
|
||||
defer l.RUnlock()
|
||||
return l.methods
|
||||
}
|
||||
|
||||
// SetBasicAuthUsers is thread-safe way of setting list of basic auth usernames to limit.
|
||||
func (l *Limiter) SetBasicAuthUsers(basicAuthUsers []string) *Limiter {
|
||||
ttl := l.GetBasicAuthExpirationTTL()
|
||||
if ttl <= 0 {
|
||||
ttl = l.generalExpirableOptions.DefaultExpirationTTL
|
||||
}
|
||||
|
||||
for _, basicAuthUser := range basicAuthUsers {
|
||||
l.basicAuthUsers.Set(basicAuthUser, true, ttl)
|
||||
}
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
// GetBasicAuthUsers is thread-safe way of getting list of basic auth usernames to limit.
|
||||
func (l *Limiter) GetBasicAuthUsers() []string {
|
||||
return l.basicAuthUsers.Keys()
|
||||
}
|
||||
|
||||
// RemoveBasicAuthUsers is thread-safe way of removing basic auth usernames from existing list.
|
||||
func (l *Limiter) RemoveBasicAuthUsers(basicAuthUsers []string) *Limiter {
|
||||
for _, toBeRemoved := range basicAuthUsers {
|
||||
l.basicAuthUsers.Invalidate(toBeRemoved)
|
||||
}
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
// DeleteExpiredTokenBuckets is thread-safe way of deleting expired token buckets
|
||||
func (l *Limiter) DeleteExpiredTokenBuckets() {
|
||||
l.tokenBuckets.DeleteExpired()
|
||||
}
|
||||
|
||||
// SetHeaders is thread-safe way of setting map of HTTP headers to limit.
|
||||
func (l *Limiter) SetHeaders(headers map[string][]string) *Limiter {
|
||||
if l.headers == nil {
|
||||
l.headers = make(map[string]cache.Cache[string, bool])
|
||||
}
|
||||
|
||||
for header, entries := range headers {
|
||||
l.SetHeader(header, entries)
|
||||
}
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
// GetHeaders is thread-safe way of getting map of HTTP headers to limit.
|
||||
func (l *Limiter) GetHeaders() map[string][]string {
|
||||
results := make(map[string][]string)
|
||||
|
||||
l.RLock()
|
||||
defer l.RUnlock()
|
||||
|
||||
for header, entriesAsGoCache := range l.headers {
|
||||
results[header] = entriesAsGoCache.Keys()
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
// SetHeader is thread-safe way of setting entries of 1 HTTP header.
|
||||
func (l *Limiter) SetHeader(header string, entries []string) *Limiter {
|
||||
l.RLock()
|
||||
existing, found := l.headers[header]
|
||||
l.RUnlock()
|
||||
|
||||
ttl := l.GetHeaderEntryExpirationTTL()
|
||||
if ttl <= 0 {
|
||||
ttl = l.generalExpirableOptions.DefaultExpirationTTL
|
||||
}
|
||||
|
||||
if !found {
|
||||
existing = cache.NewCache[string, bool]().WithTTL(ttl)
|
||||
}
|
||||
|
||||
for _, entry := range entries {
|
||||
existing.Set(entry, true, ttl)
|
||||
}
|
||||
|
||||
l.Lock()
|
||||
l.headers[header] = existing
|
||||
l.Unlock()
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
// GetHeader is thread-safe way of getting entries of 1 HTTP header.
|
||||
func (l *Limiter) GetHeader(header string) []string {
|
||||
l.RLock()
|
||||
entriesAsGoCache := l.headers[header]
|
||||
l.RUnlock()
|
||||
|
||||
return entriesAsGoCache.Keys()
|
||||
}
|
||||
|
||||
// RemoveHeader is thread-safe way of removing entries of 1 HTTP header.
|
||||
func (l *Limiter) RemoveHeader(header string) *Limiter {
|
||||
ttl := l.GetHeaderEntryExpirationTTL()
|
||||
if ttl <= 0 {
|
||||
ttl = l.generalExpirableOptions.DefaultExpirationTTL
|
||||
}
|
||||
|
||||
l.Lock()
|
||||
l.headers[header] = cache.NewCache[string, bool]().WithTTL(ttl)
|
||||
l.Unlock()
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
// RemoveHeaderEntries is thread-safe way of removing new entries to 1 HTTP header rule.
|
||||
func (l *Limiter) RemoveHeaderEntries(header string, entriesForRemoval []string) *Limiter {
|
||||
l.RLock()
|
||||
entries, found := l.headers[header]
|
||||
l.RUnlock()
|
||||
|
||||
if !found {
|
||||
return l
|
||||
}
|
||||
|
||||
for _, toBeRemoved := range entriesForRemoval {
|
||||
entries.Invalidate(toBeRemoved)
|
||||
}
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
// SetContextValues is thread-safe way of setting map of HTTP headers to limit.
|
||||
func (l *Limiter) SetContextValues(contextValues map[string][]string) *Limiter {
|
||||
if l.contextValues == nil {
|
||||
l.contextValues = make(map[string]cache.Cache[string, bool])
|
||||
}
|
||||
|
||||
for contextValue, entries := range contextValues {
|
||||
l.SetContextValue(contextValue, entries)
|
||||
}
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
// GetContextValues is thread-safe way of getting a map of Context values to limit.
|
||||
func (l *Limiter) GetContextValues() map[string][]string {
|
||||
results := make(map[string][]string)
|
||||
|
||||
l.RLock()
|
||||
defer l.RUnlock()
|
||||
|
||||
for contextValue, entriesAsGoCache := range l.contextValues {
|
||||
results[contextValue] = entriesAsGoCache.Keys()
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
// SetContextValue is thread-safe way of setting entries of 1 Context value.
|
||||
func (l *Limiter) SetContextValue(contextValue string, entries []string) *Limiter {
|
||||
l.RLock()
|
||||
existing, found := l.contextValues[contextValue]
|
||||
l.RUnlock()
|
||||
|
||||
ttl := l.GetContextValueEntryExpirationTTL()
|
||||
if ttl <= 0 {
|
||||
ttl = l.generalExpirableOptions.DefaultExpirationTTL
|
||||
}
|
||||
|
||||
if !found {
|
||||
existing = cache.NewCache[string, bool]().WithTTL(ttl)
|
||||
}
|
||||
|
||||
for _, entry := range entries {
|
||||
existing.Set(entry, true, ttl)
|
||||
}
|
||||
|
||||
l.Lock()
|
||||
l.contextValues[contextValue] = existing
|
||||
l.Unlock()
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
// GetContextValue is thread-safe way of getting 1 Context value entry.
|
||||
func (l *Limiter) GetContextValue(contextValue string) []string {
|
||||
l.RLock()
|
||||
entriesAsGoCache := l.contextValues[contextValue]
|
||||
l.RUnlock()
|
||||
|
||||
return entriesAsGoCache.Keys()
|
||||
}
|
||||
|
||||
// RemoveContextValue is thread-safe way of removing entries of 1 Context value.
|
||||
func (l *Limiter) RemoveContextValue(contextValue string) *Limiter {
|
||||
ttl := l.GetContextValueEntryExpirationTTL()
|
||||
if ttl <= 0 {
|
||||
ttl = l.generalExpirableOptions.DefaultExpirationTTL
|
||||
}
|
||||
|
||||
l.Lock()
|
||||
l.contextValues[contextValue] = cache.NewCache[string, bool]().WithTTL(ttl)
|
||||
l.Unlock()
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
// RemoveContextValuesEntries is thread-safe way of removing entries to a ContextValue.
|
||||
func (l *Limiter) RemoveContextValuesEntries(contextValue string, entriesForRemoval []string) *Limiter {
|
||||
l.RLock()
|
||||
entries, found := l.contextValues[contextValue]
|
||||
l.RUnlock()
|
||||
|
||||
if !found {
|
||||
return l
|
||||
}
|
||||
|
||||
for _, toBeRemoved := range entriesForRemoval {
|
||||
entries.Invalidate(toBeRemoved)
|
||||
}
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
func (l *Limiter) limitReachedWithTokenBucketTTL(key string, tokenBucketTTL time.Duration) bool {
|
||||
lmtMax := l.GetMax()
|
||||
lmtBurst := l.GetBurst()
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
|
||||
if _, found := l.tokenBuckets.Get(key); !found {
|
||||
l.tokenBuckets.Set(
|
||||
key,
|
||||
rate.NewLimiter(rate.Limit(lmtMax), lmtBurst),
|
||||
tokenBucketTTL,
|
||||
)
|
||||
}
|
||||
|
||||
expiringMap, found := l.tokenBuckets.Get(key)
|
||||
if !found {
|
||||
return false
|
||||
}
|
||||
|
||||
return !expiringMap.Allow()
|
||||
}
|
||||
|
||||
// LimitReached returns a bool indicating if the Bucket identified by key ran out of tokens.
|
||||
func (l *Limiter) LimitReached(key string) bool {
|
||||
ttl := l.GetTokenBucketExpirationTTL()
|
||||
|
||||
if ttl <= 0 {
|
||||
ttl = l.generalExpirableOptions.DefaultExpirationTTL
|
||||
}
|
||||
|
||||
return l.limitReachedWithTokenBucketTTL(key, ttl)
|
||||
}
|
||||
|
||||
// Tokens returns current amount of tokens left in the Bucket identified by key.
|
||||
func (l *Limiter) Tokens(key string) int {
|
||||
expiringMap, found := l.tokenBuckets.Get(key)
|
||||
if !found {
|
||||
return 0
|
||||
}
|
||||
|
||||
return int(expiringMap.TokensAt(time.Now()))
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
package limiter
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// ExpirableOptions are options used for new limiter creation
|
||||
type ExpirableOptions struct {
|
||||
DefaultExpirationTTL time.Duration
|
||||
|
||||
// How frequently expire job triggers
|
||||
// Deprecated: not used anymore
|
||||
ExpireJobInterval time.Duration
|
||||
}
|
||||
+375
@@ -0,0 +1,375 @@
|
||||
// Package tollbooth provides rate-limiting logic to HTTP request handler.
|
||||
package tollbooth
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/didip/tollbooth/v8/errors"
|
||||
"github.com/didip/tollbooth/v8/libstring"
|
||||
"github.com/didip/tollbooth/v8/limiter"
|
||||
)
|
||||
|
||||
// setResponseHeaders configures X-Rate-Limit-Limit and X-Rate-Limit-Duration
|
||||
func setResponseHeaders(lmt *limiter.Limiter, w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Add("X-Rate-Limit-Limit", fmt.Sprintf("%.2f", lmt.GetMax()))
|
||||
w.Header().Add("X-Rate-Limit-Duration", "1")
|
||||
|
||||
xForwardedFor := r.Header.Get("X-Forwarded-For")
|
||||
if strings.TrimSpace(xForwardedFor) != "" {
|
||||
w.Header().Add("X-Rate-Limit-Request-Forwarded-For", xForwardedFor)
|
||||
}
|
||||
|
||||
w.Header().Add("X-Rate-Limit-Request-Remote-Addr", r.RemoteAddr)
|
||||
}
|
||||
|
||||
// setRateLimitResponseHeaders configures RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset
|
||||
// as seen at https://datatracker.ietf.org/doc/html/draft-ietf-httpapi-ratelimit-headers
|
||||
func setRateLimitResponseHeaders(lmt *limiter.Limiter, w http.ResponseWriter, tokensLeft int) {
|
||||
w.Header().Add("RateLimit-Limit", fmt.Sprintf("%d", int(math.Round(lmt.GetMax()))))
|
||||
w.Header().Add("RateLimit-Reset", "1")
|
||||
w.Header().Add("RateLimit-Remaining", fmt.Sprintf("%d", tokensLeft))
|
||||
}
|
||||
|
||||
// NewLimiter is a convenience function to limiter.New.
|
||||
func NewLimiter(max float64, tbOptions *limiter.ExpirableOptions) *limiter.Limiter {
|
||||
return limiter.New(tbOptions).
|
||||
SetMax(max).
|
||||
SetBurst(int(math.Max(1, max)))
|
||||
}
|
||||
|
||||
// LimitByKeys keeps track number of request made by keys separated by pipe.
|
||||
// It returns HTTPError when limit is exceeded.
|
||||
func LimitByKeys(lmt *limiter.Limiter, keys []string) *errors.HTTPError {
|
||||
err, _ := LimitByKeysAndReturn(lmt, keys)
|
||||
return err
|
||||
}
|
||||
|
||||
// LimitByKeysAndReturn keeps track number of request made by keys separated by pipe.
|
||||
// It returns HTTPError when limit is exceeded, and also returns the current limit value.
|
||||
func LimitByKeysAndReturn(lmt *limiter.Limiter, keys []string) (*errors.HTTPError, int) {
|
||||
if lmt.LimitReached(strings.Join(keys, "|")) {
|
||||
return &errors.HTTPError{Message: lmt.GetMessage(), StatusCode: lmt.GetStatusCode()}, 0
|
||||
}
|
||||
|
||||
return nil, lmt.Tokens(strings.Join(keys, "|"))
|
||||
}
|
||||
|
||||
// ShouldSkipLimiter is a series of filter that decides if request should be limited or not.
|
||||
func ShouldSkipLimiter(lmt *limiter.Limiter, r *http.Request) bool {
|
||||
// ---------------------------------
|
||||
// Filter by remote ip
|
||||
// If we are unable to find remoteIP, skip limiter
|
||||
remoteIP := libstring.RemoteIPFromIPLookup(lmt.GetIPLookup(), r)
|
||||
remoteIP = libstring.CanonicalizeIP(remoteIP)
|
||||
if remoteIP == "" {
|
||||
return true
|
||||
}
|
||||
|
||||
// ---------------------------------
|
||||
// Filter by request method
|
||||
lmtMethods := lmt.GetMethods()
|
||||
lmtMethodsIsSet := len(lmtMethods) > 0
|
||||
|
||||
if lmtMethodsIsSet {
|
||||
// If request does not contain all of the methods in limiter,
|
||||
// skip limiter
|
||||
requestMethodDefinedInLimiter := libstring.StringInSlice(lmtMethods, r.Method)
|
||||
|
||||
if !requestMethodDefinedInLimiter {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------
|
||||
// Filter by request headers
|
||||
lmtHeaders := lmt.GetHeaders()
|
||||
lmtHeadersIsSet := len(lmtHeaders) > 0
|
||||
|
||||
if lmtHeadersIsSet {
|
||||
// If request does not contain all of the headers in limiter,
|
||||
// skip limiter
|
||||
requestHeadersDefinedInLimiter := false
|
||||
|
||||
for headerKey := range lmtHeaders {
|
||||
reqHeaderValue := r.Header.Get(headerKey)
|
||||
if reqHeaderValue != "" {
|
||||
requestHeadersDefinedInLimiter = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !requestHeadersDefinedInLimiter {
|
||||
return true
|
||||
}
|
||||
|
||||
// ------------------------------
|
||||
// If request contains the header key but not the values,
|
||||
// skip limiter
|
||||
requestHeadersDefinedInLimiter = false
|
||||
|
||||
for headerKey, headerValues := range lmtHeaders {
|
||||
if len(headerValues) == 0 {
|
||||
requestHeadersDefinedInLimiter = true
|
||||
continue
|
||||
}
|
||||
for _, headerValue := range headerValues {
|
||||
if r.Header.Get(headerKey) == headerValue {
|
||||
requestHeadersDefinedInLimiter = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !requestHeadersDefinedInLimiter {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------
|
||||
// Filter by context values
|
||||
lmtContextValues := lmt.GetContextValues()
|
||||
lmtContextValuesIsSet := len(lmtContextValues) > 0
|
||||
|
||||
if lmtContextValuesIsSet {
|
||||
// If request does not contain all of the contexts in limiter,
|
||||
// skip limiter
|
||||
requestContextValuesDefinedInLimiter := false
|
||||
|
||||
for contextKey := range lmtContextValues {
|
||||
reqContextValue := fmt.Sprintf("%v", r.Context().Value(contextKey))
|
||||
if reqContextValue != "" {
|
||||
requestContextValuesDefinedInLimiter = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !requestContextValuesDefinedInLimiter {
|
||||
return true
|
||||
}
|
||||
|
||||
// ------------------------------
|
||||
// If request contains the context key but not the values,
|
||||
// skip limiter
|
||||
requestContextValuesDefinedInLimiter = false
|
||||
|
||||
for contextKey, contextValues := range lmtContextValues {
|
||||
for _, contextValue := range contextValues {
|
||||
if r.Header.Get(contextKey) == contextValue {
|
||||
requestContextValuesDefinedInLimiter = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !requestContextValuesDefinedInLimiter {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------
|
||||
// Filter by basic auth usernames
|
||||
lmtBasicAuthUsers := lmt.GetBasicAuthUsers()
|
||||
lmtBasicAuthUsersIsSet := len(lmtBasicAuthUsers) > 0
|
||||
|
||||
if lmtBasicAuthUsersIsSet {
|
||||
// If request does not contain all of the basic auth users in limiter,
|
||||
// skip limiter
|
||||
requestAuthUsernameDefinedInLimiter := false
|
||||
|
||||
username, _, ok := r.BasicAuth()
|
||||
if ok && libstring.StringInSlice(lmtBasicAuthUsers, username) {
|
||||
requestAuthUsernameDefinedInLimiter = true
|
||||
}
|
||||
|
||||
if !requestAuthUsernameDefinedInLimiter {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// BuildKeys generates a slice of keys to rate-limit by given limiter and request structs.
|
||||
func BuildKeys(lmt *limiter.Limiter, r *http.Request) [][]string {
|
||||
remoteIP := libstring.RemoteIPFromIPLookup(lmt.GetIPLookup(), r)
|
||||
remoteIP = libstring.CanonicalizeIP(remoteIP)
|
||||
path := r.URL.Path
|
||||
sliceKeys := make([][]string, 0)
|
||||
|
||||
lmtMethods := lmt.GetMethods()
|
||||
lmtHeaders := lmt.GetHeaders()
|
||||
lmtContextValues := lmt.GetContextValues()
|
||||
lmtBasicAuthUsers := lmt.GetBasicAuthUsers()
|
||||
lmtIgnoreURL := lmt.GetIgnoreURL()
|
||||
|
||||
lmtHeadersIsSet := len(lmtHeaders) > 0
|
||||
lmtContextValuesIsSet := len(lmtContextValues) > 0
|
||||
lmtBasicAuthUsersIsSet := len(lmtBasicAuthUsers) > 0
|
||||
|
||||
usernameToLimit := ""
|
||||
if lmtBasicAuthUsersIsSet {
|
||||
username, _, ok := r.BasicAuth()
|
||||
if ok && libstring.StringInSlice(lmtBasicAuthUsers, username) {
|
||||
usernameToLimit = username
|
||||
}
|
||||
}
|
||||
|
||||
headerValuesToLimit := [][]string{}
|
||||
if lmtHeadersIsSet {
|
||||
for headerKey, headerValues := range lmtHeaders {
|
||||
reqHeaderValue := r.Header.Get(headerKey)
|
||||
if reqHeaderValue == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if len(headerValues) == 0 {
|
||||
// If header values are empty, rate-limit all request containing headerKey.
|
||||
headerValuesToLimit = append(headerValuesToLimit, []string{headerKey, reqHeaderValue})
|
||||
|
||||
} else {
|
||||
// If header values are not empty, rate-limit all request with headerKey and headerValues.
|
||||
for _, headerValue := range headerValues {
|
||||
if r.Header.Get(headerKey) == headerValue {
|
||||
headerValuesToLimit = append(headerValuesToLimit, []string{headerKey, headerValue})
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
contextValuesToLimit := [][]string{}
|
||||
if lmtContextValuesIsSet {
|
||||
for contextKey, contextValues := range lmtContextValues {
|
||||
reqContextValue := fmt.Sprintf("%v", r.Context().Value(contextKey))
|
||||
if reqContextValue == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if len(contextValues) == 0 {
|
||||
// If context values are empty, rate-limit all request containing contextKey.
|
||||
contextValuesToLimit = append(contextValuesToLimit, []string{contextKey, reqContextValue})
|
||||
|
||||
} else {
|
||||
// If context values are not empty, rate-limit all request with contextKey and contextValues.
|
||||
for _, contextValue := range contextValues {
|
||||
if reqContextValue == contextValue {
|
||||
contextValuesToLimit = append(contextValuesToLimit, []string{contextKey, contextValue})
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sliceKey := []string{remoteIP}
|
||||
if !lmtIgnoreURL {
|
||||
sliceKey = append(sliceKey, path)
|
||||
}
|
||||
|
||||
sliceKey = append(sliceKey, lmtMethods...)
|
||||
|
||||
for _, header := range headerValuesToLimit {
|
||||
sliceKey = append(sliceKey, header[0], header[1])
|
||||
}
|
||||
|
||||
for _, contextValue := range contextValuesToLimit {
|
||||
sliceKey = append(sliceKey, contextValue[0], contextValue[1])
|
||||
}
|
||||
|
||||
sliceKey = append(sliceKey, usernameToLimit)
|
||||
|
||||
sliceKeys = append(sliceKeys, sliceKey)
|
||||
|
||||
return sliceKeys
|
||||
}
|
||||
|
||||
// LimitByRequest builds keys based on http.Request struct,
|
||||
// loops through all the keys, and check if any one of them returns HTTPError.
|
||||
func LimitByRequest(lmt *limiter.Limiter, w http.ResponseWriter, r *http.Request) *errors.HTTPError {
|
||||
setResponseHeaders(lmt, w, r)
|
||||
|
||||
shouldSkip := ShouldSkipLimiter(lmt, r)
|
||||
if shouldSkip {
|
||||
return nil
|
||||
}
|
||||
|
||||
sliceKeys := BuildKeys(lmt, r)
|
||||
|
||||
// Get the lowest value over all keys to return in headers.
|
||||
// Start with high arbitrary number so that any limit returned would be lower and would
|
||||
// overwrite the value we start with.
|
||||
var tokensLeft = math.MaxInt32
|
||||
|
||||
// Loop sliceKeys and check if one of them has error.
|
||||
for _, keys := range sliceKeys {
|
||||
httpError, keysLimit := LimitByKeysAndReturn(lmt, keys)
|
||||
if tokensLeft > keysLimit {
|
||||
tokensLeft = keysLimit
|
||||
}
|
||||
if httpError != nil {
|
||||
setRateLimitResponseHeaders(lmt, w, tokensLeft)
|
||||
return httpError
|
||||
}
|
||||
}
|
||||
|
||||
setRateLimitResponseHeaders(lmt, w, tokensLeft)
|
||||
return nil
|
||||
}
|
||||
|
||||
// LimitHandler is a middleware that performs rate-limiting given http.Handler struct.
|
||||
func LimitHandler(lmt *limiter.Limiter, next http.Handler) http.Handler {
|
||||
middle := func(w http.ResponseWriter, r *http.Request) {
|
||||
httpError := LimitByRequest(lmt, w, r)
|
||||
if httpError != nil {
|
||||
lmt.ExecOnLimitReached(w, r)
|
||||
if lmt.GetOverrideDefaultResponseWriter() {
|
||||
return
|
||||
}
|
||||
w.Header().Add("Content-Type", lmt.GetMessageContentType())
|
||||
w.WriteHeader(httpError.StatusCode)
|
||||
w.Write([]byte(httpError.Message))
|
||||
return
|
||||
}
|
||||
|
||||
// There's no rate-limit error, serve the next handler.
|
||||
next.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
return http.HandlerFunc(middle)
|
||||
}
|
||||
|
||||
// LimitFuncHandler is a middleware that performs rate-limiting given request handler function.
|
||||
func LimitFuncHandler(lmt *limiter.Limiter, nextFunc func(http.ResponseWriter, *http.Request)) http.Handler {
|
||||
return LimitHandler(lmt, http.HandlerFunc(nextFunc))
|
||||
}
|
||||
|
||||
// HTTPMiddleware wraps http.Handler with tollbooth limiter
|
||||
func HTTPMiddleware(lmt *limiter.Limiter) func(http.Handler) http.Handler {
|
||||
// // set IP lookup only if not set
|
||||
if lmt.GetIPLookup().Name == "" {
|
||||
lmt.SetIPLookup(limiter.IPLookup{Name: "RemoteAddr"})
|
||||
}
|
||||
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
select {
|
||||
case <-r.Context().Done():
|
||||
http.Error(w, "Context was canceled", http.StatusServiceUnavailable)
|
||||
return
|
||||
default:
|
||||
if httpError := LimitByRequest(lmt, w, r); httpError != nil {
|
||||
lmt.ExecOnLimitReached(w, r)
|
||||
w.Header().Add("Content-Type", lmt.GetMessageContentType())
|
||||
w.WriteHeader(httpError.StatusCode)
|
||||
w.Write([]byte(httpError.Message)) //nolint:gosec // not much we can do here with failed write
|
||||
return
|
||||
}
|
||||
next.ServeHTTP(w, r)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
+55
-18
@@ -1,8 +1,8 @@
|
||||
linters-settings:
|
||||
govet:
|
||||
check-shadowing: true
|
||||
shadow: true
|
||||
golint:
|
||||
min-confidence: 0
|
||||
min-confidence: 0.6
|
||||
gocyclo:
|
||||
min-complexity: 15
|
||||
maligned:
|
||||
@@ -23,44 +23,81 @@ linters-settings:
|
||||
- experimental
|
||||
disabled-checks:
|
||||
- wrapperFunc
|
||||
- hugeParam
|
||||
- rangeValCopy
|
||||
|
||||
linters:
|
||||
disable-all: true
|
||||
enable:
|
||||
- megacheck
|
||||
- revive
|
||||
- govet
|
||||
- unconvert
|
||||
- megacheck
|
||||
- structcheck
|
||||
- gas
|
||||
- gocyclo
|
||||
- dupl
|
||||
- misspell
|
||||
- gosec
|
||||
- unparam
|
||||
- varcheck
|
||||
- deadcode
|
||||
- unused
|
||||
- typecheck
|
||||
- ineffassign
|
||||
- varcheck
|
||||
- stylecheck
|
||||
- gochecknoinits
|
||||
- exportloopref
|
||||
- gocritic
|
||||
- nakedret
|
||||
- gosimple
|
||||
- prealloc
|
||||
|
||||
fast: false
|
||||
disable-all: true
|
||||
|
||||
|
||||
run:
|
||||
output:
|
||||
format: tab
|
||||
skip-dirs:
|
||||
- vendor
|
||||
concurrency: 4
|
||||
|
||||
issues:
|
||||
exclude-dirs:
|
||||
- vendor
|
||||
exclude-rules:
|
||||
- text: "should have a package comment, unless it's in another file for this package"
|
||||
linters:
|
||||
- golint
|
||||
- text: "exitAfterDefer:"
|
||||
linters:
|
||||
- gocritic
|
||||
- text: "whyNoLint: include an explanation for nolint directive"
|
||||
linters:
|
||||
- gocritic
|
||||
- text: "go.mongodb.org/mongo-driver/bson/primitive.E"
|
||||
linters:
|
||||
- govet
|
||||
- text: "weak cryptographic primitive"
|
||||
linters:
|
||||
- gosec
|
||||
- text: "integer overflow conversion"
|
||||
linters:
|
||||
- gosec
|
||||
- text: "should have a package comment"
|
||||
linters:
|
||||
- revive
|
||||
- text: "at least one file in a package should have a package comment"
|
||||
linters:
|
||||
- stylecheck
|
||||
- text: "commentedOutCode: may want to remove commented-out code"
|
||||
linters:
|
||||
- gocritic
|
||||
- text: "unnamedResult: consider giving a name to these results"
|
||||
linters:
|
||||
- gocritic
|
||||
- text: "var-naming: don't use an underscore in package name"
|
||||
linters:
|
||||
- revive
|
||||
- text: "should not use underscores in package names"
|
||||
linters:
|
||||
- stylecheck
|
||||
- text: "struct literal uses unkeyed fields"
|
||||
linters:
|
||||
- govet
|
||||
- linters:
|
||||
- unparam
|
||||
- unused
|
||||
- revive
|
||||
path: _test\.go$
|
||||
text: "unused-parameter"
|
||||
exclude-use-default: false
|
||||
|
||||
|
||||
+6
-4
@@ -8,8 +8,8 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/didip/tollbooth/v7"
|
||||
"github.com/didip/tollbooth_chi"
|
||||
"github.com/didip/tollbooth/v8"
|
||||
"github.com/didip/tollbooth/v8/limiter"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/chi/v5/middleware"
|
||||
"github.com/go-chi/render"
|
||||
@@ -116,7 +116,9 @@ func (s *Server) Run(port int) error {
|
||||
router.Use(logInfoWithBody)
|
||||
|
||||
if s.limits.clientLimit > 0 {
|
||||
router.Use(tollbooth_chi.LimitHandler(tollbooth.NewLimiter(s.limits.clientLimit, nil)))
|
||||
lmt := tollbooth.NewLimiter(s.limits.clientLimit, nil)
|
||||
lmt.SetIPLookup(limiter.IPLookup{Name: "X-Real-IP"})
|
||||
router.Use(tollbooth.HTTPMiddleware(lmt))
|
||||
}
|
||||
|
||||
router.Use(middleware.NoCache)
|
||||
@@ -243,4 +245,4 @@ type LoggerFunc func(format string, args ...interface{})
|
||||
func (f LoggerFunc) Logf(format string, args ...interface{}) { f(format, args...) }
|
||||
|
||||
// NoOpLogger logger does nothing
|
||||
var NoOpLogger = LoggerFunc(func(format string, args ...interface{}) {})
|
||||
var NoOpLogger = LoggerFunc(func(format string, args ...interface{}) {}) //nolint
|
||||
|
||||
+2
@@ -6,5 +6,7 @@ cover.out
|
||||
cover-*.out
|
||||
/.idea
|
||||
*.iml
|
||||
/bbolt
|
||||
/cmd/bbolt/bbolt
|
||||
.DS_Store
|
||||
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
1.22.6
|
||||
1.23.6
|
||||
|
||||
+46
-14
@@ -1,6 +1,7 @@
|
||||
BRANCH=`git rev-parse --abbrev-ref HEAD`
|
||||
COMMIT=`git rev-parse --short HEAD`
|
||||
GOLDFLAGS="-X main.branch $(BRANCH) -X main.commit $(COMMIT)"
|
||||
GOFILES = $(shell find . -name \*.go)
|
||||
|
||||
TESTFLAGS_RACE=-race=false
|
||||
ifdef ENABLE_RACE
|
||||
@@ -13,9 +14,26 @@ ifdef CPU
|
||||
endif
|
||||
TESTFLAGS = $(TESTFLAGS_RACE) $(TESTFLAGS_CPU) $(EXTRA_TESTFLAGS)
|
||||
|
||||
TESTFLAGS_TIMEOUT=30m
|
||||
ifdef TIMEOUT
|
||||
TESTFLAGS_TIMEOUT=$(TIMEOUT)
|
||||
endif
|
||||
|
||||
TESTFLAGS_ENABLE_STRICT_MODE=false
|
||||
ifdef ENABLE_STRICT_MODE
|
||||
TESTFLAGS_ENABLE_STRICT_MODE=$(ENABLE_STRICT_MODE)
|
||||
endif
|
||||
|
||||
.EXPORT_ALL_VARIABLES:
|
||||
TEST_ENABLE_STRICT_MODE=${TESTFLAGS_ENABLE_STRICT_MODE}
|
||||
|
||||
.PHONY: fmt
|
||||
fmt:
|
||||
!(gofmt -l -s -d $(shell find . -name \*.go) | grep '[a-z]')
|
||||
@echo "Verifying gofmt, failures can be fixed with ./scripts/fix.sh"
|
||||
@!(gofmt -l -s -d ${GOFILES} | grep '[a-z]')
|
||||
|
||||
@echo "Verifying goimports, failures can be fixed with ./scripts/fix.sh"
|
||||
@!(go run golang.org/x/tools/cmd/goimports@latest -l -d ${GOFILES} | grep '[a-z]')
|
||||
|
||||
.PHONY: lint
|
||||
lint:
|
||||
@@ -24,21 +42,23 @@ lint:
|
||||
.PHONY: test
|
||||
test:
|
||||
@echo "hashmap freelist test"
|
||||
TEST_FREELIST_TYPE=hashmap go test -v ${TESTFLAGS} -timeout 30m
|
||||
TEST_FREELIST_TYPE=hashmap go test -v ${TESTFLAGS} ./cmd/bbolt
|
||||
BBOLT_VERIFY=all TEST_FREELIST_TYPE=hashmap go test -v ${TESTFLAGS} -timeout ${TESTFLAGS_TIMEOUT}
|
||||
BBOLT_VERIFY=all TEST_FREELIST_TYPE=hashmap go test -v ${TESTFLAGS} ./internal/...
|
||||
BBOLT_VERIFY=all TEST_FREELIST_TYPE=hashmap go test -v ${TESTFLAGS} ./cmd/bbolt
|
||||
|
||||
@echo "array freelist test"
|
||||
TEST_FREELIST_TYPE=array go test -v ${TESTFLAGS} -timeout 30m
|
||||
TEST_FREELIST_TYPE=array go test -v ${TESTFLAGS} ./cmd/bbolt
|
||||
BBOLT_VERIFY=all TEST_FREELIST_TYPE=array go test -v ${TESTFLAGS} -timeout ${TESTFLAGS_TIMEOUT}
|
||||
BBOLT_VERIFY=all TEST_FREELIST_TYPE=array go test -v ${TESTFLAGS} ./internal/...
|
||||
BBOLT_VERIFY=all TEST_FREELIST_TYPE=array go test -v ${TESTFLAGS} ./cmd/bbolt
|
||||
|
||||
.PHONY: coverage
|
||||
coverage:
|
||||
@echo "hashmap freelist test"
|
||||
TEST_FREELIST_TYPE=hashmap go test -v -timeout 30m \
|
||||
TEST_FREELIST_TYPE=hashmap go test -v -timeout ${TESTFLAGS_TIMEOUT} \
|
||||
-coverprofile cover-freelist-hashmap.out -covermode atomic
|
||||
|
||||
@echo "array freelist test"
|
||||
TEST_FREELIST_TYPE=array go test -v -timeout 30m \
|
||||
TEST_FREELIST_TYPE=array go test -v -timeout ${TESTFLAGS_TIMEOUT} \
|
||||
-coverprofile cover-freelist-array.out -covermode atomic
|
||||
|
||||
BOLT_CMD=bbolt
|
||||
@@ -55,7 +75,7 @@ gofail-enable: install-gofail
|
||||
gofail enable .
|
||||
|
||||
.PHONY: gofail-disable
|
||||
gofail-disable:
|
||||
gofail-disable: install-gofail
|
||||
gofail disable .
|
||||
|
||||
.PHONY: install-gofail
|
||||
@@ -65,12 +85,24 @@ install-gofail:
|
||||
.PHONY: test-failpoint
|
||||
test-failpoint:
|
||||
@echo "[failpoint] hashmap freelist test"
|
||||
TEST_FREELIST_TYPE=hashmap go test -v ${TESTFLAGS} -timeout 30m ./tests/failpoint
|
||||
BBOLT_VERIFY=all TEST_FREELIST_TYPE=hashmap go test -v ${TESTFLAGS} -timeout 30m ./tests/failpoint
|
||||
|
||||
@echo "[failpoint] array freelist test"
|
||||
TEST_FREELIST_TYPE=array go test -v ${TESTFLAGS} -timeout 30m ./tests/failpoint
|
||||
BBOLT_VERIFY=all TEST_FREELIST_TYPE=array go test -v ${TESTFLAGS} -timeout 30m ./tests/failpoint
|
||||
|
||||
.PHONY: test-robustness # Running robustness tests requires root permission
|
||||
test-robustness:
|
||||
go test -v ${TESTFLAGS} ./tests/dmflakey -test.root
|
||||
go test -v ${TESTFLAGS} ./tests/robustness -test.root
|
||||
.PHONY: test-robustness # Running robustness tests requires root permission for now
|
||||
# TODO: Remove sudo once we fully migrate to the prow infrastructure
|
||||
test-robustness: gofail-enable build
|
||||
sudo env PATH=$$PATH go test -v ${TESTFLAGS} ./tests/dmflakey -test.root
|
||||
sudo env PATH=$(PWD)/bin:$$PATH go test -v ${TESTFLAGS} ${ROBUSTNESS_TESTFLAGS} ./tests/robustness -test.root
|
||||
|
||||
.PHONY: test-benchmark-compare
|
||||
# Runs benchmark tests on the current git ref and the given REF, and compares
|
||||
# the two.
|
||||
test-benchmark-compare: install-benchstat
|
||||
@git fetch
|
||||
./scripts/compare_benchmarks.sh $(REF)
|
||||
|
||||
.PHONY: install-benchstat
|
||||
install-benchstat:
|
||||
go install golang.org/x/perf/cmd/benchstat@latest
|
||||
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
# See the OWNERS docs at https://go.k8s.io/owners
|
||||
|
||||
approvers:
|
||||
- ahrtr # Benjamin Wang <benjamin.ahrtr@gmail.com> <benjamin.wang@broadcom.com>
|
||||
- serathius # Marek Siarkowicz <siarkowicz@google.com> <marek.siarkowicz@gmail.com>
|
||||
- ptabor # Piotr Tabor <piotr.tabor@gmail.com>
|
||||
- spzala # Sahdev Zala <spzala@us.ibm.com>
|
||||
reviewers:
|
||||
- fuweid # Wei Fu <fuweid89@gmail.com>
|
||||
- tjungblu # Thomas Jungblut <tjungblu@redhat.com>
|
||||
+59
-9
@@ -1,10 +1,8 @@
|
||||
bbolt
|
||||
=====
|
||||
|
||||
[](https://goreportcard.com/report/github.com/etcd-io/bbolt)
|
||||
[](https://codecov.io/gh/etcd-io/bbolt)
|
||||
[](https://travis-ci.com/etcd-io/bbolt)
|
||||
[](https://godoc.org/github.com/etcd-io/bbolt)
|
||||
[](https://goreportcard.com/report/go.etcd.io/bbolt)
|
||||
[](https://pkg.go.dev/go.etcd.io/bbolt)
|
||||
[](https://github.com/etcd-io/bbolt/releases)
|
||||
[](https://github.com/etcd-io/bbolt/blob/master/LICENSE)
|
||||
|
||||
@@ -71,13 +69,14 @@ New minor versions may add additional features to the API.
|
||||
- [LMDB](#lmdb)
|
||||
- [Caveats & Limitations](#caveats--limitations)
|
||||
- [Reading the Source](#reading-the-source)
|
||||
- [Known Issues](#known-issues)
|
||||
- [Other Projects Using Bolt](#other-projects-using-bolt)
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Installing
|
||||
|
||||
To start using Bolt, install Go and run `go get`:
|
||||
To start using `bbolt`, install Go and run `go get`:
|
||||
```sh
|
||||
$ go get go.etcd.io/bbolt@latest
|
||||
```
|
||||
@@ -103,7 +102,7 @@ To use bbolt as an embedded key-value store, import as:
|
||||
```go
|
||||
import bolt "go.etcd.io/bbolt"
|
||||
|
||||
db, err := bolt.Open(path, 0666, nil)
|
||||
db, err := bolt.Open(path, 0600, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -298,6 +297,17 @@ db.Update(func(tx *bolt.Tx) error {
|
||||
})
|
||||
```
|
||||
|
||||
You can retrieve an existing bucket using the `Tx.Bucket()` function:
|
||||
```go
|
||||
db.Update(func(tx *bolt.Tx) error {
|
||||
b := tx.Bucket([]byte("MyBucket"))
|
||||
if b == nil {
|
||||
return errors.New("bucket does not exist")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
```
|
||||
|
||||
You can also create a bucket only if it doesn't exist by using the
|
||||
`Tx.CreateBucketIfNotExists()` function. It's a common pattern to call this
|
||||
function for all your top-level buckets after you open your database so you can
|
||||
@@ -305,6 +315,17 @@ guarantee that they exist for future transactions.
|
||||
|
||||
To delete a bucket, simply call the `Tx.DeleteBucket()` function.
|
||||
|
||||
You can also iterate over all existing top-level buckets with `Tx.ForEach()`:
|
||||
|
||||
```go
|
||||
db.View(func(tx *bolt.Tx) error {
|
||||
tx.ForEach(func(name []byte, b *bolt.Bucket) error {
|
||||
fmt.Println(string(name))
|
||||
return nil
|
||||
})
|
||||
return nil
|
||||
})
|
||||
```
|
||||
|
||||
### Using key/value pairs
|
||||
|
||||
@@ -336,7 +357,17 @@ exists then it will return its byte slice value. If it doesn't exist then it
|
||||
will return `nil`. It's important to note that you can have a zero-length value
|
||||
set to a key which is different than the key not existing.
|
||||
|
||||
Use the `Bucket.Delete()` function to delete a key from the bucket.
|
||||
Use the `Bucket.Delete()` function to delete a key from the bucket:
|
||||
|
||||
```go
|
||||
db.Update(func (tx *bolt.Tx) error {
|
||||
b := tx.Bucket([]byte("MyBucket"))
|
||||
err := b.Delete([]byte("answer"))
|
||||
return err
|
||||
})
|
||||
```
|
||||
|
||||
This will delete the key `answers` from the bucket `MyBucket`.
|
||||
|
||||
Please note that values returned from `Get()` are only valid while the
|
||||
transaction is open. If you need to use a value outside of the transaction
|
||||
@@ -654,7 +685,7 @@ uses a shared lock to allow multiple processes to read from the database but
|
||||
it will block any processes from opening the database in read-write mode.
|
||||
|
||||
```go
|
||||
db, err := bolt.Open("my.db", 0666, &bolt.Options{ReadOnly: true})
|
||||
db, err := bolt.Open("my.db", 0600, &bolt.Options{ReadOnly: true})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
@@ -890,7 +921,7 @@ The best places to start are the main entry points into Bolt:
|
||||
|
||||
- `Bucket.Put()` - Writes a key/value pair into a bucket. After validating the
|
||||
arguments, a cursor is used to traverse the B+tree to the page and position
|
||||
where they key & value will be written. Once the position is found, the bucket
|
||||
where the key & value will be written. Once the position is found, the bucket
|
||||
materializes the underlying page and the page's parent pages into memory as
|
||||
"nodes". These nodes are where mutations occur during read-write transactions.
|
||||
These changes get flushed to disk during commit.
|
||||
@@ -919,6 +950,21 @@ The best places to start are the main entry points into Bolt:
|
||||
If you have additional notes that could be helpful for others, please submit
|
||||
them via pull request.
|
||||
|
||||
## Known Issues
|
||||
|
||||
- bbolt might run into data corruption issue on Linux when the feature
|
||||
[ext4: fast commit](https://lwn.net/Articles/842385/), which was introduced in
|
||||
linux kernel version v5.10, is enabled. The fixes to the issue were included in
|
||||
linux kernel version v5.17, please refer to links below,
|
||||
|
||||
* [ext4: fast commit may miss tracking unwritten range during ftruncate](https://lore.kernel.org/linux-ext4/20211223032337.5198-3-yinxin.x@bytedance.com/)
|
||||
* [ext4: fast commit may not fallback for ineligible commit](https://lore.kernel.org/lkml/202201091544.W5HHEXAp-lkp@intel.com/T/#ma0768815e4b5f671e9e451d578256ef9a76fe30e)
|
||||
* [ext4 updates for 5.17](https://lore.kernel.org/lkml/YdyxjTFaLWif6BCM@mit.edu/)
|
||||
|
||||
Please also refer to the discussion in https://github.com/etcd-io/bbolt/issues/562.
|
||||
|
||||
- Writing a value with a length of 0 will always result in reading back an empty `[]byte{}` value.
|
||||
Please refer to [issues/726#issuecomment-2061694802](https://github.com/etcd-io/bbolt/issues/726#issuecomment-2061694802).
|
||||
|
||||
## Other Projects Using Bolt
|
||||
|
||||
@@ -934,13 +980,16 @@ Below is a list of public, open source projects that use Bolt:
|
||||
* [BoltDbWeb](https://github.com/evnix/boltdbweb) - A web based GUI for BoltDB files.
|
||||
* [BoltDB Viewer](https://github.com/zc310/rich_boltdb) - A BoltDB Viewer Can run on Windows、Linux、Android system.
|
||||
* [bleve](http://www.blevesearch.com/) - A pure Go search engine similar to ElasticSearch that uses Bolt as the default storage backend.
|
||||
* [bstore](https://github.com/mjl-/bstore) - Database library storing Go values, with referential/unique/nonzero constraints, indices, automatic schema management with struct tags, and a query API.
|
||||
* [btcwallet](https://github.com/btcsuite/btcwallet) - A bitcoin wallet.
|
||||
* [buckets](https://github.com/joyrexus/buckets) - a bolt wrapper streamlining
|
||||
simple tx and key scans.
|
||||
* [Buildkit](https://github.com/moby/buildkit) - concurrent, cache-efficient, and Dockerfile-agnostic builder toolkit
|
||||
* [cayley](https://github.com/google/cayley) - Cayley is an open-source graph database using Bolt as optional backend.
|
||||
* [ChainStore](https://github.com/pressly/chainstore) - Simple key-value interface to a variety of storage engines organized as a chain of operations.
|
||||
* [🌰 Chestnut](https://github.com/jrapoport/chestnut) - Chestnut is encrypted storage for Go.
|
||||
* [Consul](https://github.com/hashicorp/consul) - Consul is service discovery and configuration made easy. Distributed, highly available, and datacenter-aware.
|
||||
* [Containerd](https://github.com/containerd/containerd) - An open and reliable container runtime
|
||||
* [DVID](https://github.com/janelia-flyem/dvid) - Added Bolt as optional storage engine and testing it against Basho-tuned leveldb.
|
||||
* [dcrwallet](https://github.com/decred/dcrwallet) - A wallet for the Decred cryptocurrency.
|
||||
* [drive](https://github.com/odeke-em/drive) - drive is an unofficial Google Drive command line client for \*NIX operating systems.
|
||||
@@ -964,6 +1013,7 @@ Below is a list of public, open source projects that use Bolt:
|
||||
* [MetricBase](https://github.com/msiebuhr/MetricBase) - Single-binary version of Graphite.
|
||||
* [MuLiFS](https://github.com/dankomiocevic/mulifs) - Music Library Filesystem creates a filesystem to organise your music files.
|
||||
* [NATS](https://github.com/nats-io/nats-streaming-server) - NATS Streaming uses bbolt for message and metadata storage.
|
||||
* [Portainer](https://github.com/portainer/portainer) - A lightweight service delivery platform for containerized applications that can be used to manage Docker, Swarm, Kubernetes and ACI environments.
|
||||
* [Prometheus Annotation Server](https://github.com/oliver006/prom_annotation_server) - Annotation server for PromDash & Prometheus service monitoring system.
|
||||
* [Rain](https://github.com/cenkalti/rain) - BitTorrent client and library.
|
||||
* [reef-pi](https://github.com/reef-pi/reef-pi) - reef-pi is an award winning, modular, DIY reef tank controller using easy to learn electronics based on a Raspberry Pi.
|
||||
|
||||
Generated
Vendored
-1
@@ -1,5 +1,4 @@
|
||||
//go:build aix
|
||||
// +build aix
|
||||
|
||||
package bbolt
|
||||
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
package bbolt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"syscall"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// flock acquires an advisory lock on a file descriptor.
|
||||
func flock(db *DB, exclusive bool, timeout time.Duration) error {
|
||||
var t time.Time
|
||||
if timeout != 0 {
|
||||
t = time.Now()
|
||||
}
|
||||
fd := db.file.Fd()
|
||||
var lockType int16
|
||||
if exclusive {
|
||||
lockType = syscall.F_WRLCK
|
||||
} else {
|
||||
lockType = syscall.F_RDLCK
|
||||
}
|
||||
for {
|
||||
// Attempt to obtain an exclusive lock.
|
||||
lock := syscall.Flock_t{Type: lockType}
|
||||
err := syscall.FcntlFlock(fd, syscall.F_SETLK, &lock)
|
||||
if err == nil {
|
||||
return nil
|
||||
} else if err != syscall.EAGAIN {
|
||||
return err
|
||||
}
|
||||
|
||||
// If we timed out then return an error.
|
||||
if timeout != 0 && time.Since(t) > timeout-flockRetryTimeout {
|
||||
return ErrTimeout
|
||||
}
|
||||
|
||||
// Wait for a bit and try again.
|
||||
time.Sleep(flockRetryTimeout)
|
||||
}
|
||||
}
|
||||
|
||||
// funlock releases an advisory lock on a file descriptor.
|
||||
func funlock(db *DB) error {
|
||||
var lock syscall.Flock_t
|
||||
lock.Start = 0
|
||||
lock.Len = 0
|
||||
lock.Type = syscall.F_UNLCK
|
||||
lock.Whence = 0
|
||||
return syscall.FcntlFlock(uintptr(db.file.Fd()), syscall.F_SETLK, &lock)
|
||||
}
|
||||
|
||||
// mmap memory maps a DB's data file.
|
||||
func mmap(db *DB, sz int) error {
|
||||
// Map the data file to memory.
|
||||
b, err := unix.Mmap(int(db.file.Fd()), 0, sz, syscall.PROT_READ, syscall.MAP_SHARED|db.MmapFlags)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Advise the kernel that the mmap is accessed randomly.
|
||||
err = unix.Madvise(b, syscall.MADV_RANDOM)
|
||||
if err != nil && err != syscall.ENOSYS {
|
||||
// Ignore not implemented error in kernel because it still works.
|
||||
return fmt.Errorf("madvise: %s", err)
|
||||
}
|
||||
|
||||
// Save the original byte slice and convert to a byte array pointer.
|
||||
db.dataref = b
|
||||
db.data = (*[maxMapSize]byte)(unsafe.Pointer(&b[0]))
|
||||
db.datasz = sz
|
||||
return nil
|
||||
}
|
||||
|
||||
// munmap unmaps a DB's data file from memory.
|
||||
func munmap(db *DB) error {
|
||||
// Ignore the unmap if we have no mapped data.
|
||||
if db.dataref == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Unmap using the original byte slice.
|
||||
err := unix.Munmap(db.dataref)
|
||||
db.dataref = nil
|
||||
db.data = nil
|
||||
db.datasz = 0
|
||||
return err
|
||||
}
|
||||
-1
@@ -1,5 +1,4 @@
|
||||
//go:build arm64
|
||||
// +build arm64
|
||||
|
||||
package bbolt
|
||||
|
||||
|
||||
-1
@@ -1,5 +1,4 @@
|
||||
//go:build loong64
|
||||
// +build loong64
|
||||
|
||||
package bbolt
|
||||
|
||||
|
||||
-1
@@ -1,5 +1,4 @@
|
||||
//go:build mips64 || mips64le
|
||||
// +build mips64 mips64le
|
||||
|
||||
package bbolt
|
||||
|
||||
|
||||
-1
@@ -1,5 +1,4 @@
|
||||
//go:build mips || mipsle
|
||||
// +build mips mipsle
|
||||
|
||||
package bbolt
|
||||
|
||||
|
||||
-1
@@ -1,5 +1,4 @@
|
||||
//go:build ppc
|
||||
// +build ppc
|
||||
|
||||
package bbolt
|
||||
|
||||
|
||||
-1
@@ -1,5 +1,4 @@
|
||||
//go:build ppc64
|
||||
// +build ppc64
|
||||
|
||||
package bbolt
|
||||
|
||||
|
||||
-1
@@ -1,5 +1,4 @@
|
||||
//go:build ppc64le
|
||||
// +build ppc64le
|
||||
|
||||
package bbolt
|
||||
|
||||
|
||||
-1
@@ -1,5 +1,4 @@
|
||||
//go:build riscv64
|
||||
// +build riscv64
|
||||
|
||||
package bbolt
|
||||
|
||||
|
||||
-1
@@ -1,5 +1,4 @@
|
||||
//go:build s390x
|
||||
// +build s390x
|
||||
|
||||
package bbolt
|
||||
|
||||
|
||||
Generated
Vendored
+4
-3
@@ -1,5 +1,4 @@
|
||||
//go:build !windows && !plan9 && !solaris && !aix
|
||||
// +build !windows,!plan9,!solaris,!aix
|
||||
//go:build !windows && !plan9 && !solaris && !aix && !android
|
||||
|
||||
package bbolt
|
||||
|
||||
@@ -10,6 +9,8 @@ import (
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"go.etcd.io/bbolt/errors"
|
||||
)
|
||||
|
||||
// flock acquires an advisory lock on a file descriptor.
|
||||
@@ -36,7 +37,7 @@ func flock(db *DB, exclusive bool, timeout time.Duration) error {
|
||||
|
||||
// If we timed out then return an error.
|
||||
if timeout != 0 && time.Since(t) > timeout-flockRetryTimeout {
|
||||
return ErrTimeout
|
||||
return errors.ErrTimeout
|
||||
}
|
||||
|
||||
// Wait for a bit and try again.
|
||||
|
||||
+5
-3
@@ -8,6 +8,8 @@ import (
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
|
||||
"go.etcd.io/bbolt/errors"
|
||||
)
|
||||
|
||||
// fdatasync flushes written data to a file descriptor.
|
||||
@@ -42,7 +44,7 @@ func flock(db *DB, exclusive bool, timeout time.Duration) error {
|
||||
|
||||
// If we timed oumercit then return an error.
|
||||
if timeout != 0 && time.Since(t) > timeout-flockRetryTimeout {
|
||||
return ErrTimeout
|
||||
return errors.ErrTimeout
|
||||
}
|
||||
|
||||
// Wait for a bit and try again.
|
||||
@@ -70,7 +72,7 @@ func mmap(db *DB, sz int) error {
|
||||
return fmt.Errorf("truncate: %s", err)
|
||||
}
|
||||
sizehi = uint32(sz >> 32)
|
||||
sizelo = uint32(sz) & 0xffffffff
|
||||
sizelo = uint32(sz)
|
||||
}
|
||||
|
||||
// Open a file mapping handle.
|
||||
@@ -93,7 +95,7 @@ func mmap(db *DB, sz int) error {
|
||||
}
|
||||
|
||||
// Convert to a byte array.
|
||||
db.data = ((*[maxMapSize]byte)(unsafe.Pointer(addr)))
|
||||
db.data = (*[maxMapSize]byte)(unsafe.Pointer(addr))
|
||||
db.datasz = sz
|
||||
|
||||
return nil
|
||||
|
||||
-1
@@ -1,5 +1,4 @@
|
||||
//go:build !windows && !plan9 && !linux && !openbsd
|
||||
// +build !windows,!plan9,!linux,!openbsd
|
||||
|
||||
package bbolt
|
||||
|
||||
|
||||
+349
-143
@@ -4,6 +4,9 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"unsafe"
|
||||
|
||||
"go.etcd.io/bbolt/errors"
|
||||
"go.etcd.io/bbolt/internal/common"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -14,8 +17,6 @@ const (
|
||||
MaxValueSize = (1 << 31) - 2
|
||||
)
|
||||
|
||||
const bucketHeaderSize = int(unsafe.Sizeof(bucket{}))
|
||||
|
||||
const (
|
||||
minFillPercent = 0.1
|
||||
maxFillPercent = 1.0
|
||||
@@ -27,12 +28,12 @@ const DefaultFillPercent = 0.5
|
||||
|
||||
// Bucket represents a collection of key/value pairs inside the database.
|
||||
type Bucket struct {
|
||||
*bucket
|
||||
tx *Tx // the associated transaction
|
||||
buckets map[string]*Bucket // subbucket cache
|
||||
page *page // inline page reference
|
||||
rootNode *node // materialized node for the root page.
|
||||
nodes map[pgid]*node // node cache
|
||||
*common.InBucket
|
||||
tx *Tx // the associated transaction
|
||||
buckets map[string]*Bucket // subbucket cache
|
||||
page *common.Page // inline page reference
|
||||
rootNode *node // materialized node for the root page.
|
||||
nodes map[common.Pgid]*node // node cache
|
||||
|
||||
// Sets the threshold for filling nodes when they split. By default,
|
||||
// the bucket will fill to 50% but it can be useful to increase this
|
||||
@@ -42,21 +43,12 @@ type Bucket struct {
|
||||
FillPercent float64
|
||||
}
|
||||
|
||||
// bucket represents the on-file representation of a bucket.
|
||||
// This is stored as the "value" of a bucket key. If the bucket is small enough,
|
||||
// then its root page can be stored inline in the "value", after the bucket
|
||||
// header. In the case of inline buckets, the "root" will be 0.
|
||||
type bucket struct {
|
||||
root pgid // page id of the bucket's root-level page
|
||||
sequence uint64 // monotonically incrementing, used by NextSequence()
|
||||
}
|
||||
|
||||
// newBucket returns a new bucket associated with a transaction.
|
||||
func newBucket(tx *Tx) Bucket {
|
||||
var b = Bucket{tx: tx, FillPercent: DefaultFillPercent}
|
||||
if tx.writable {
|
||||
b.buckets = make(map[string]*Bucket)
|
||||
b.nodes = make(map[pgid]*node)
|
||||
b.nodes = make(map[common.Pgid]*node)
|
||||
}
|
||||
return b
|
||||
}
|
||||
@@ -67,8 +59,8 @@ func (b *Bucket) Tx() *Tx {
|
||||
}
|
||||
|
||||
// Root returns the root of the bucket.
|
||||
func (b *Bucket) Root() pgid {
|
||||
return b.root
|
||||
func (b *Bucket) Root() common.Pgid {
|
||||
return b.RootPage()
|
||||
}
|
||||
|
||||
// Writable returns whether the bucket is writable.
|
||||
@@ -105,7 +97,7 @@ func (b *Bucket) Bucket(name []byte) *Bucket {
|
||||
k, v, flags := c.seek(name)
|
||||
|
||||
// Return nil if the key doesn't exist or it is not a bucket.
|
||||
if !bytes.Equal(name, k) || (flags&bucketLeafFlag) == 0 {
|
||||
if !bytes.Equal(name, k) || (flags&common.BucketLeafFlag) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -125,8 +117,8 @@ func (b *Bucket) openBucket(value []byte) *Bucket {
|
||||
|
||||
// Unaligned access requires a copy to be made.
|
||||
const unalignedMask = unsafe.Alignof(struct {
|
||||
bucket
|
||||
page
|
||||
common.InBucket
|
||||
common.Page
|
||||
}{}) - 1
|
||||
unaligned := uintptr(unsafe.Pointer(&value[0]))&unalignedMask != 0
|
||||
if unaligned {
|
||||
@@ -136,15 +128,15 @@ func (b *Bucket) openBucket(value []byte) *Bucket {
|
||||
// If this is a writable transaction then we need to copy the bucket entry.
|
||||
// Read-only transactions can point directly at the mmap entry.
|
||||
if b.tx.writable && !unaligned {
|
||||
child.bucket = &bucket{}
|
||||
*child.bucket = *(*bucket)(unsafe.Pointer(&value[0]))
|
||||
child.InBucket = &common.InBucket{}
|
||||
*child.InBucket = *(*common.InBucket)(unsafe.Pointer(&value[0]))
|
||||
} else {
|
||||
child.bucket = (*bucket)(unsafe.Pointer(&value[0]))
|
||||
child.InBucket = (*common.InBucket)(unsafe.Pointer(&value[0]))
|
||||
}
|
||||
|
||||
// Save a reference to the inline page if the bucket is inline.
|
||||
if child.root == 0 {
|
||||
child.page = (*page)(unsafe.Pointer(&value[bucketHeaderSize]))
|
||||
if child.RootPage() == 0 {
|
||||
child.page = (*common.Page)(unsafe.Pointer(&value[common.BucketHeaderSize]))
|
||||
}
|
||||
|
||||
return &child
|
||||
@@ -153,13 +145,23 @@ func (b *Bucket) openBucket(value []byte) *Bucket {
|
||||
// CreateBucket creates a new bucket at the given key and returns the new bucket.
|
||||
// Returns an error if the key already exists, if the bucket name is blank, or if the bucket name is too long.
|
||||
// The bucket instance is only valid for the lifetime of the transaction.
|
||||
func (b *Bucket) CreateBucket(key []byte) (*Bucket, error) {
|
||||
func (b *Bucket) CreateBucket(key []byte) (rb *Bucket, err error) {
|
||||
if lg := b.tx.db.Logger(); lg != discardLogger {
|
||||
lg.Debugf("Creating bucket %q", key)
|
||||
defer func() {
|
||||
if err != nil {
|
||||
lg.Errorf("Creating bucket %q failed: %v", key, err)
|
||||
} else {
|
||||
lg.Debugf("Creating bucket %q successfully", key)
|
||||
}
|
||||
}()
|
||||
}
|
||||
if b.tx.db == nil {
|
||||
return nil, ErrTxClosed
|
||||
return nil, errors.ErrTxClosed
|
||||
} else if !b.tx.writable {
|
||||
return nil, ErrTxNotWritable
|
||||
return nil, errors.ErrTxNotWritable
|
||||
} else if len(key) == 0 {
|
||||
return nil, ErrBucketNameRequired
|
||||
return nil, errors.ErrBucketNameRequired
|
||||
}
|
||||
|
||||
// Insert into node.
|
||||
@@ -173,21 +175,21 @@ func (b *Bucket) CreateBucket(key []byte) (*Bucket, error) {
|
||||
|
||||
// Return an error if there is an existing key.
|
||||
if bytes.Equal(newKey, k) {
|
||||
if (flags & bucketLeafFlag) != 0 {
|
||||
return nil, ErrBucketExists
|
||||
if (flags & common.BucketLeafFlag) != 0 {
|
||||
return nil, errors.ErrBucketExists
|
||||
}
|
||||
return nil, ErrIncompatibleValue
|
||||
return nil, errors.ErrIncompatibleValue
|
||||
}
|
||||
|
||||
// Create empty, inline bucket.
|
||||
var bucket = Bucket{
|
||||
bucket: &bucket{},
|
||||
InBucket: &common.InBucket{},
|
||||
rootNode: &node{isLeaf: true},
|
||||
FillPercent: DefaultFillPercent,
|
||||
}
|
||||
var value = bucket.write()
|
||||
|
||||
c.node().put(newKey, newKey, value, 0, bucketLeafFlag)
|
||||
c.node().put(newKey, newKey, value, 0, common.BucketLeafFlag)
|
||||
|
||||
// Since subbuckets are not allowed on inline buckets, we need to
|
||||
// dereference the inline page, if it exists. This will cause the bucket
|
||||
@@ -200,39 +202,108 @@ func (b *Bucket) CreateBucket(key []byte) (*Bucket, error) {
|
||||
// CreateBucketIfNotExists creates a new bucket if it doesn't already exist and returns a reference to it.
|
||||
// Returns an error if the bucket name is blank, or if the bucket name is too long.
|
||||
// The bucket instance is only valid for the lifetime of the transaction.
|
||||
func (b *Bucket) CreateBucketIfNotExists(key []byte) (*Bucket, error) {
|
||||
child, err := b.CreateBucket(key)
|
||||
if err == ErrBucketExists {
|
||||
return b.Bucket(key), nil
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
func (b *Bucket) CreateBucketIfNotExists(key []byte) (rb *Bucket, err error) {
|
||||
if lg := b.tx.db.Logger(); lg != discardLogger {
|
||||
lg.Debugf("Creating bucket if not exist %q", key)
|
||||
defer func() {
|
||||
if err != nil {
|
||||
lg.Errorf("Creating bucket if not exist %q failed: %v", key, err)
|
||||
} else {
|
||||
lg.Debugf("Creating bucket if not exist %q successfully", key)
|
||||
}
|
||||
}()
|
||||
}
|
||||
return child, nil
|
||||
}
|
||||
|
||||
// DeleteBucket deletes a bucket at the given key.
|
||||
// Returns an error if the bucket does not exist, or if the key represents a non-bucket value.
|
||||
func (b *Bucket) DeleteBucket(key []byte) error {
|
||||
if b.tx.db == nil {
|
||||
return ErrTxClosed
|
||||
} else if !b.Writable() {
|
||||
return ErrTxNotWritable
|
||||
return nil, errors.ErrTxClosed
|
||||
} else if !b.tx.writable {
|
||||
return nil, errors.ErrTxNotWritable
|
||||
} else if len(key) == 0 {
|
||||
return nil, errors.ErrBucketNameRequired
|
||||
}
|
||||
|
||||
// Insert into node.
|
||||
// Tip: Use a new variable `newKey` instead of reusing the existing `key` to prevent
|
||||
// it from being marked as leaking, and accordingly cannot be allocated on stack.
|
||||
newKey := cloneBytes(key)
|
||||
|
||||
if b.buckets != nil {
|
||||
if child := b.buckets[string(newKey)]; child != nil {
|
||||
return child, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Move cursor to correct position.
|
||||
c := b.Cursor()
|
||||
k, _, flags := c.seek(key)
|
||||
k, v, flags := c.seek(newKey)
|
||||
|
||||
// Return an error if there is an existing non-bucket key.
|
||||
if bytes.Equal(newKey, k) {
|
||||
if (flags & common.BucketLeafFlag) != 0 {
|
||||
var child = b.openBucket(v)
|
||||
if b.buckets != nil {
|
||||
b.buckets[string(newKey)] = child
|
||||
}
|
||||
|
||||
return child, nil
|
||||
}
|
||||
return nil, errors.ErrIncompatibleValue
|
||||
}
|
||||
|
||||
// Create empty, inline bucket.
|
||||
var bucket = Bucket{
|
||||
InBucket: &common.InBucket{},
|
||||
rootNode: &node{isLeaf: true},
|
||||
FillPercent: DefaultFillPercent,
|
||||
}
|
||||
var value = bucket.write()
|
||||
|
||||
c.node().put(newKey, newKey, value, 0, common.BucketLeafFlag)
|
||||
|
||||
// Since subbuckets are not allowed on inline buckets, we need to
|
||||
// dereference the inline page, if it exists. This will cause the bucket
|
||||
// to be treated as a regular, non-inline bucket for the rest of the tx.
|
||||
b.page = nil
|
||||
|
||||
return b.Bucket(newKey), nil
|
||||
}
|
||||
|
||||
// DeleteBucket deletes a bucket at the given key.
|
||||
// Returns an error if the bucket does not exist, or if the key represents a non-bucket value.
|
||||
func (b *Bucket) DeleteBucket(key []byte) (err error) {
|
||||
if lg := b.tx.db.Logger(); lg != discardLogger {
|
||||
lg.Debugf("Deleting bucket %q", key)
|
||||
defer func() {
|
||||
if err != nil {
|
||||
lg.Errorf("Deleting bucket %q failed: %v", key, err)
|
||||
} else {
|
||||
lg.Debugf("Deleting bucket %q successfully", key)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
if b.tx.db == nil {
|
||||
return errors.ErrTxClosed
|
||||
} else if !b.Writable() {
|
||||
return errors.ErrTxNotWritable
|
||||
}
|
||||
|
||||
newKey := cloneBytes(key)
|
||||
|
||||
// Move cursor to correct position.
|
||||
c := b.Cursor()
|
||||
k, _, flags := c.seek(newKey)
|
||||
|
||||
// Return an error if bucket doesn't exist or is not a bucket.
|
||||
if !bytes.Equal(key, k) {
|
||||
return ErrBucketNotFound
|
||||
} else if (flags & bucketLeafFlag) == 0 {
|
||||
return ErrIncompatibleValue
|
||||
if !bytes.Equal(newKey, k) {
|
||||
return errors.ErrBucketNotFound
|
||||
} else if (flags & common.BucketLeafFlag) == 0 {
|
||||
return errors.ErrIncompatibleValue
|
||||
}
|
||||
|
||||
// Recursively delete all child buckets.
|
||||
child := b.Bucket(key)
|
||||
err := child.ForEachBucket(func(k []byte) error {
|
||||
child := b.Bucket(newKey)
|
||||
err = child.ForEachBucket(func(k []byte) error {
|
||||
if err := child.DeleteBucket(k); err != nil {
|
||||
return fmt.Errorf("delete bucket: %s", err)
|
||||
}
|
||||
@@ -243,7 +314,7 @@ func (b *Bucket) DeleteBucket(key []byte) error {
|
||||
}
|
||||
|
||||
// Remove cached copy.
|
||||
delete(b.buckets, string(key))
|
||||
delete(b.buckets, string(newKey))
|
||||
|
||||
// Release all bucket pages to freelist.
|
||||
child.nodes = nil
|
||||
@@ -251,19 +322,119 @@ func (b *Bucket) DeleteBucket(key []byte) error {
|
||||
child.free()
|
||||
|
||||
// Delete the node if we have a matching key.
|
||||
c.node().del(key)
|
||||
c.node().del(newKey)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MoveBucket moves a sub-bucket from the source bucket to the destination bucket.
|
||||
// Returns an error if
|
||||
// 1. the sub-bucket cannot be found in the source bucket;
|
||||
// 2. or the key already exists in the destination bucket;
|
||||
// 3. or the key represents a non-bucket value;
|
||||
// 4. the source and destination buckets are the same.
|
||||
func (b *Bucket) MoveBucket(key []byte, dstBucket *Bucket) (err error) {
|
||||
lg := b.tx.db.Logger()
|
||||
if lg != discardLogger {
|
||||
lg.Debugf("Moving bucket %q", key)
|
||||
defer func() {
|
||||
if err != nil {
|
||||
lg.Errorf("Moving bucket %q failed: %v", key, err)
|
||||
} else {
|
||||
lg.Debugf("Moving bucket %q successfully", key)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
if b.tx.db == nil || dstBucket.tx.db == nil {
|
||||
return errors.ErrTxClosed
|
||||
} else if !b.Writable() || !dstBucket.Writable() {
|
||||
return errors.ErrTxNotWritable
|
||||
}
|
||||
|
||||
if b.tx.db.Path() != dstBucket.tx.db.Path() || b.tx != dstBucket.tx {
|
||||
lg.Errorf("The source and target buckets are not in the same db file, source bucket in %s and target bucket in %s", b.tx.db.Path(), dstBucket.tx.db.Path())
|
||||
return errors.ErrDifferentDB
|
||||
}
|
||||
|
||||
newKey := cloneBytes(key)
|
||||
|
||||
// Move cursor to correct position.
|
||||
c := b.Cursor()
|
||||
k, v, flags := c.seek(newKey)
|
||||
|
||||
// Return an error if bucket doesn't exist or is not a bucket.
|
||||
if !bytes.Equal(newKey, k) {
|
||||
return errors.ErrBucketNotFound
|
||||
} else if (flags & common.BucketLeafFlag) == 0 {
|
||||
lg.Errorf("An incompatible key %s exists in the source bucket", newKey)
|
||||
return errors.ErrIncompatibleValue
|
||||
}
|
||||
|
||||
// Do nothing (return true directly) if the source bucket and the
|
||||
// destination bucket are actually the same bucket.
|
||||
if b == dstBucket || (b.RootPage() == dstBucket.RootPage() && b.RootPage() != 0) {
|
||||
lg.Errorf("The source bucket (%s) and the target bucket (%s) are the same bucket", b, dstBucket)
|
||||
return errors.ErrSameBuckets
|
||||
}
|
||||
|
||||
// check whether the key already exists in the destination bucket
|
||||
curDst := dstBucket.Cursor()
|
||||
k, _, flags = curDst.seek(newKey)
|
||||
|
||||
// Return an error if there is an existing key in the destination bucket.
|
||||
if bytes.Equal(newKey, k) {
|
||||
if (flags & common.BucketLeafFlag) != 0 {
|
||||
return errors.ErrBucketExists
|
||||
}
|
||||
lg.Errorf("An incompatible key %s exists in the target bucket", newKey)
|
||||
return errors.ErrIncompatibleValue
|
||||
}
|
||||
|
||||
// remove the sub-bucket from the source bucket
|
||||
delete(b.buckets, string(newKey))
|
||||
c.node().del(newKey)
|
||||
|
||||
// add te sub-bucket to the destination bucket
|
||||
newValue := cloneBytes(v)
|
||||
curDst.node().put(newKey, newKey, newValue, 0, common.BucketLeafFlag)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Inspect returns the structure of the bucket.
|
||||
func (b *Bucket) Inspect() BucketStructure {
|
||||
return b.recursivelyInspect([]byte("root"))
|
||||
}
|
||||
|
||||
func (b *Bucket) recursivelyInspect(name []byte) BucketStructure {
|
||||
bs := BucketStructure{Name: string(name)}
|
||||
|
||||
keyN := 0
|
||||
c := b.Cursor()
|
||||
for k, _, flags := c.first(); k != nil; k, _, flags = c.next() {
|
||||
if flags&common.BucketLeafFlag != 0 {
|
||||
childBucket := b.Bucket(k)
|
||||
childBS := childBucket.recursivelyInspect(k)
|
||||
bs.Children = append(bs.Children, childBS)
|
||||
} else {
|
||||
keyN++
|
||||
}
|
||||
}
|
||||
bs.KeyN = keyN
|
||||
|
||||
return bs
|
||||
}
|
||||
|
||||
// Get retrieves the value for a key in the bucket.
|
||||
// Returns a nil value if the key does not exist or if the key is a nested bucket.
|
||||
// The returned value is only valid for the life of the transaction.
|
||||
// The returned memory is owned by bbolt and must never be modified; writing to this memory might corrupt the database.
|
||||
func (b *Bucket) Get(key []byte) []byte {
|
||||
k, v, flags := b.Cursor().seek(key)
|
||||
|
||||
// Return nil if this is a bucket.
|
||||
if (flags & bucketLeafFlag) != 0 {
|
||||
if (flags & common.BucketLeafFlag) != 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -278,17 +449,27 @@ func (b *Bucket) Get(key []byte) []byte {
|
||||
// If the key exist then its previous value will be overwritten.
|
||||
// Supplied value must remain valid for the life of the transaction.
|
||||
// Returns an error if the bucket was created from a read-only transaction, if the key is blank, if the key is too large, or if the value is too large.
|
||||
func (b *Bucket) Put(key []byte, value []byte) error {
|
||||
func (b *Bucket) Put(key []byte, value []byte) (err error) {
|
||||
if lg := b.tx.db.Logger(); lg != discardLogger {
|
||||
lg.Debugf("Putting key %q", key)
|
||||
defer func() {
|
||||
if err != nil {
|
||||
lg.Errorf("Putting key %q failed: %v", key, err)
|
||||
} else {
|
||||
lg.Debugf("Putting key %q successfully", key)
|
||||
}
|
||||
}()
|
||||
}
|
||||
if b.tx.db == nil {
|
||||
return ErrTxClosed
|
||||
return errors.ErrTxClosed
|
||||
} else if !b.Writable() {
|
||||
return ErrTxNotWritable
|
||||
return errors.ErrTxNotWritable
|
||||
} else if len(key) == 0 {
|
||||
return ErrKeyRequired
|
||||
return errors.ErrKeyRequired
|
||||
} else if len(key) > MaxKeySize {
|
||||
return ErrKeyTooLarge
|
||||
return errors.ErrKeyTooLarge
|
||||
} else if int64(len(value)) > MaxValueSize {
|
||||
return ErrValueTooLarge
|
||||
return errors.ErrValueTooLarge
|
||||
}
|
||||
|
||||
// Insert into node.
|
||||
@@ -301,8 +482,8 @@ func (b *Bucket) Put(key []byte, value []byte) error {
|
||||
k, _, flags := c.seek(newKey)
|
||||
|
||||
// Return an error if there is an existing key with a bucket value.
|
||||
if bytes.Equal(newKey, k) && (flags&bucketLeafFlag) != 0 {
|
||||
return ErrIncompatibleValue
|
||||
if bytes.Equal(newKey, k) && (flags&common.BucketLeafFlag) != 0 {
|
||||
return errors.ErrIncompatibleValue
|
||||
}
|
||||
|
||||
// gofail: var beforeBucketPut struct{}
|
||||
@@ -315,11 +496,22 @@ func (b *Bucket) Put(key []byte, value []byte) error {
|
||||
// Delete removes a key from the bucket.
|
||||
// If the key does not exist then nothing is done and a nil error is returned.
|
||||
// Returns an error if the bucket was created from a read-only transaction.
|
||||
func (b *Bucket) Delete(key []byte) error {
|
||||
func (b *Bucket) Delete(key []byte) (err error) {
|
||||
if lg := b.tx.db.Logger(); lg != discardLogger {
|
||||
lg.Debugf("Deleting key %q", key)
|
||||
defer func() {
|
||||
if err != nil {
|
||||
lg.Errorf("Deleting key %q failed: %v", key, err)
|
||||
} else {
|
||||
lg.Debugf("Deleting key %q successfully", key)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
if b.tx.db == nil {
|
||||
return ErrTxClosed
|
||||
return errors.ErrTxClosed
|
||||
} else if !b.Writable() {
|
||||
return ErrTxNotWritable
|
||||
return errors.ErrTxNotWritable
|
||||
}
|
||||
|
||||
// Move cursor to correct position.
|
||||
@@ -332,8 +524,8 @@ func (b *Bucket) Delete(key []byte) error {
|
||||
}
|
||||
|
||||
// Return an error if there is already existing bucket value.
|
||||
if (flags & bucketLeafFlag) != 0 {
|
||||
return ErrIncompatibleValue
|
||||
if (flags & common.BucketLeafFlag) != 0 {
|
||||
return errors.ErrIncompatibleValue
|
||||
}
|
||||
|
||||
// Delete the node if we have a matching key.
|
||||
@@ -343,44 +535,46 @@ func (b *Bucket) Delete(key []byte) error {
|
||||
}
|
||||
|
||||
// Sequence returns the current integer for the bucket without incrementing it.
|
||||
func (b *Bucket) Sequence() uint64 { return b.bucket.sequence }
|
||||
func (b *Bucket) Sequence() uint64 {
|
||||
return b.InSequence()
|
||||
}
|
||||
|
||||
// SetSequence updates the sequence number for the bucket.
|
||||
func (b *Bucket) SetSequence(v uint64) error {
|
||||
if b.tx.db == nil {
|
||||
return ErrTxClosed
|
||||
return errors.ErrTxClosed
|
||||
} else if !b.Writable() {
|
||||
return ErrTxNotWritable
|
||||
return errors.ErrTxNotWritable
|
||||
}
|
||||
|
||||
// Materialize the root node if it hasn't been already so that the
|
||||
// bucket will be saved during commit.
|
||||
if b.rootNode == nil {
|
||||
_ = b.node(b.root, nil)
|
||||
_ = b.node(b.RootPage(), nil)
|
||||
}
|
||||
|
||||
// Set the sequence.
|
||||
b.bucket.sequence = v
|
||||
b.SetInSequence(v)
|
||||
return nil
|
||||
}
|
||||
|
||||
// NextSequence returns an autoincrementing integer for the bucket.
|
||||
func (b *Bucket) NextSequence() (uint64, error) {
|
||||
if b.tx.db == nil {
|
||||
return 0, ErrTxClosed
|
||||
return 0, errors.ErrTxClosed
|
||||
} else if !b.Writable() {
|
||||
return 0, ErrTxNotWritable
|
||||
return 0, errors.ErrTxNotWritable
|
||||
}
|
||||
|
||||
// Materialize the root node if it hasn't been already so that the
|
||||
// bucket will be saved during commit.
|
||||
if b.rootNode == nil {
|
||||
_ = b.node(b.root, nil)
|
||||
_ = b.node(b.RootPage(), nil)
|
||||
}
|
||||
|
||||
// Increment and return the sequence.
|
||||
b.bucket.sequence++
|
||||
return b.bucket.sequence, nil
|
||||
b.IncSequence()
|
||||
return b.Sequence(), nil
|
||||
}
|
||||
|
||||
// ForEach executes a function for each key/value pair in a bucket.
|
||||
@@ -390,7 +584,7 @@ func (b *Bucket) NextSequence() (uint64, error) {
|
||||
// the bucket; this will result in undefined behavior.
|
||||
func (b *Bucket) ForEach(fn func(k, v []byte) error) error {
|
||||
if b.tx.db == nil {
|
||||
return ErrTxClosed
|
||||
return errors.ErrTxClosed
|
||||
}
|
||||
c := b.Cursor()
|
||||
for k, v := c.First(); k != nil; k, v = c.Next() {
|
||||
@@ -403,11 +597,11 @@ func (b *Bucket) ForEach(fn func(k, v []byte) error) error {
|
||||
|
||||
func (b *Bucket) ForEachBucket(fn func(k []byte) error) error {
|
||||
if b.tx.db == nil {
|
||||
return ErrTxClosed
|
||||
return errors.ErrTxClosed
|
||||
}
|
||||
c := b.Cursor()
|
||||
for k, _, flags := c.first(); k != nil; k, _, flags = c.next() {
|
||||
if flags&bucketLeafFlag != 0 {
|
||||
if flags&common.BucketLeafFlag != 0 {
|
||||
if err := fn(k); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -421,64 +615,64 @@ func (b *Bucket) Stats() BucketStats {
|
||||
var s, subStats BucketStats
|
||||
pageSize := b.tx.db.pageSize
|
||||
s.BucketN += 1
|
||||
if b.root == 0 {
|
||||
if b.RootPage() == 0 {
|
||||
s.InlineBucketN += 1
|
||||
}
|
||||
b.forEachPage(func(p *page, depth int, pgstack []pgid) {
|
||||
if (p.flags & leafPageFlag) != 0 {
|
||||
s.KeyN += int(p.count)
|
||||
b.forEachPage(func(p *common.Page, depth int, pgstack []common.Pgid) {
|
||||
if p.IsLeafPage() {
|
||||
s.KeyN += int(p.Count())
|
||||
|
||||
// used totals the used bytes for the page
|
||||
used := pageHeaderSize
|
||||
used := common.PageHeaderSize
|
||||
|
||||
if p.count != 0 {
|
||||
if p.Count() != 0 {
|
||||
// If page has any elements, add all element headers.
|
||||
used += leafPageElementSize * uintptr(p.count-1)
|
||||
used += common.LeafPageElementSize * uintptr(p.Count()-1)
|
||||
|
||||
// Add all element key, value sizes.
|
||||
// The computation takes advantage of the fact that the position
|
||||
// of the last element's key/value equals to the total of the sizes
|
||||
// of all previous elements' keys and values.
|
||||
// It also includes the last element's header.
|
||||
lastElement := p.leafPageElement(p.count - 1)
|
||||
used += uintptr(lastElement.pos + lastElement.ksize + lastElement.vsize)
|
||||
lastElement := p.LeafPageElement(p.Count() - 1)
|
||||
used += uintptr(lastElement.Pos() + lastElement.Ksize() + lastElement.Vsize())
|
||||
}
|
||||
|
||||
if b.root == 0 {
|
||||
if b.RootPage() == 0 {
|
||||
// For inlined bucket just update the inline stats
|
||||
s.InlineBucketInuse += int(used)
|
||||
} else {
|
||||
// For non-inlined bucket update all the leaf stats
|
||||
s.LeafPageN++
|
||||
s.LeafInuse += int(used)
|
||||
s.LeafOverflowN += int(p.overflow)
|
||||
s.LeafOverflowN += int(p.Overflow())
|
||||
|
||||
// Collect stats from sub-buckets.
|
||||
// Do that by iterating over all element headers
|
||||
// looking for the ones with the bucketLeafFlag.
|
||||
for i := uint16(0); i < p.count; i++ {
|
||||
e := p.leafPageElement(i)
|
||||
if (e.flags & bucketLeafFlag) != 0 {
|
||||
for i := uint16(0); i < p.Count(); i++ {
|
||||
e := p.LeafPageElement(i)
|
||||
if (e.Flags() & common.BucketLeafFlag) != 0 {
|
||||
// For any bucket element, open the element value
|
||||
// and recursively call Stats on the contained bucket.
|
||||
subStats.Add(b.openBucket(e.value()).Stats())
|
||||
subStats.Add(b.openBucket(e.Value()).Stats())
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (p.flags & branchPageFlag) != 0 {
|
||||
} else if p.IsBranchPage() {
|
||||
s.BranchPageN++
|
||||
lastElement := p.branchPageElement(p.count - 1)
|
||||
lastElement := p.BranchPageElement(p.Count() - 1)
|
||||
|
||||
// used totals the used bytes for the page
|
||||
// Add header and all element headers.
|
||||
used := pageHeaderSize + (branchPageElementSize * uintptr(p.count-1))
|
||||
used := common.PageHeaderSize + (common.BranchPageElementSize * uintptr(p.Count()-1))
|
||||
|
||||
// Add size of all keys and values.
|
||||
// Again, use the fact that last element's position equals to
|
||||
// the total of key, value sizes of all previous elements.
|
||||
used += uintptr(lastElement.pos + lastElement.ksize)
|
||||
used += uintptr(lastElement.Pos() + lastElement.Ksize())
|
||||
s.BranchInuse += int(used)
|
||||
s.BranchOverflowN += int(p.overflow)
|
||||
s.BranchOverflowN += int(p.Overflow())
|
||||
}
|
||||
|
||||
// Keep track of maximum page depth.
|
||||
@@ -499,29 +693,29 @@ func (b *Bucket) Stats() BucketStats {
|
||||
}
|
||||
|
||||
// forEachPage iterates over every page in a bucket, including inline pages.
|
||||
func (b *Bucket) forEachPage(fn func(*page, int, []pgid)) {
|
||||
func (b *Bucket) forEachPage(fn func(*common.Page, int, []common.Pgid)) {
|
||||
// If we have an inline page then just use that.
|
||||
if b.page != nil {
|
||||
fn(b.page, 0, []pgid{b.root})
|
||||
fn(b.page, 0, []common.Pgid{b.RootPage()})
|
||||
return
|
||||
}
|
||||
|
||||
// Otherwise traverse the page hierarchy.
|
||||
b.tx.forEachPage(b.root, fn)
|
||||
b.tx.forEachPage(b.RootPage(), fn)
|
||||
}
|
||||
|
||||
// forEachPageNode iterates over every page (or node) in a bucket.
|
||||
// This also includes inline pages.
|
||||
func (b *Bucket) forEachPageNode(fn func(*page, *node, int)) {
|
||||
func (b *Bucket) forEachPageNode(fn func(*common.Page, *node, int)) {
|
||||
// If we have an inline page or root node then just use that.
|
||||
if b.page != nil {
|
||||
fn(b.page, nil, 0)
|
||||
return
|
||||
}
|
||||
b._forEachPageNode(b.root, 0, fn)
|
||||
b._forEachPageNode(b.RootPage(), 0, fn)
|
||||
}
|
||||
|
||||
func (b *Bucket) _forEachPageNode(pgId pgid, depth int, fn func(*page, *node, int)) {
|
||||
func (b *Bucket) _forEachPageNode(pgId common.Pgid, depth int, fn func(*common.Page, *node, int)) {
|
||||
var p, n = b.pageNode(pgId)
|
||||
|
||||
// Execute function.
|
||||
@@ -529,16 +723,16 @@ func (b *Bucket) _forEachPageNode(pgId pgid, depth int, fn func(*page, *node, in
|
||||
|
||||
// Recursively loop over children.
|
||||
if p != nil {
|
||||
if (p.flags & branchPageFlag) != 0 {
|
||||
for i := 0; i < int(p.count); i++ {
|
||||
elem := p.branchPageElement(uint16(i))
|
||||
b._forEachPageNode(elem.pgid, depth+1, fn)
|
||||
if p.IsBranchPage() {
|
||||
for i := 0; i < int(p.Count()); i++ {
|
||||
elem := p.BranchPageElement(uint16(i))
|
||||
b._forEachPageNode(elem.Pgid(), depth+1, fn)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if !n.isLeaf {
|
||||
for _, inode := range n.inodes {
|
||||
b._forEachPageNode(inode.pgid, depth+1, fn)
|
||||
b._forEachPageNode(inode.Pgid(), depth+1, fn)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -561,9 +755,9 @@ func (b *Bucket) spill() error {
|
||||
}
|
||||
|
||||
// Update the child bucket header in this bucket.
|
||||
value = make([]byte, unsafe.Sizeof(bucket{}))
|
||||
var bucket = (*bucket)(unsafe.Pointer(&value[0]))
|
||||
*bucket = *child.bucket
|
||||
value = make([]byte, unsafe.Sizeof(common.InBucket{}))
|
||||
var bucket = (*common.InBucket)(unsafe.Pointer(&value[0]))
|
||||
*bucket = *child.InBucket
|
||||
}
|
||||
|
||||
// Skip writing the bucket if there are no materialized nodes.
|
||||
@@ -577,10 +771,10 @@ func (b *Bucket) spill() error {
|
||||
if !bytes.Equal([]byte(name), k) {
|
||||
panic(fmt.Sprintf("misplaced bucket header: %x -> %x", []byte(name), k))
|
||||
}
|
||||
if flags&bucketLeafFlag == 0 {
|
||||
if flags&common.BucketLeafFlag == 0 {
|
||||
panic(fmt.Sprintf("unexpected bucket header flag: %x", flags))
|
||||
}
|
||||
c.node().put([]byte(name), []byte(name), value, 0, bucketLeafFlag)
|
||||
c.node().put([]byte(name), []byte(name), value, 0, common.BucketLeafFlag)
|
||||
}
|
||||
|
||||
// Ignore if there's not a materialized root node.
|
||||
@@ -595,16 +789,16 @@ func (b *Bucket) spill() error {
|
||||
b.rootNode = b.rootNode.root()
|
||||
|
||||
// Update the root node for this bucket.
|
||||
if b.rootNode.pgid >= b.tx.meta.pgid {
|
||||
panic(fmt.Sprintf("pgid (%d) above high water mark (%d)", b.rootNode.pgid, b.tx.meta.pgid))
|
||||
if b.rootNode.pgid >= b.tx.meta.Pgid() {
|
||||
panic(fmt.Sprintf("pgid (%d) above high water mark (%d)", b.rootNode.pgid, b.tx.meta.Pgid()))
|
||||
}
|
||||
b.root = b.rootNode.pgid
|
||||
b.SetRootPage(b.rootNode.pgid)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// inlineable returns true if a bucket is small enough to be written inline
|
||||
// and if it contains no subbuckets. Otherwise returns false.
|
||||
// and if it contains no subbuckets. Otherwise, returns false.
|
||||
func (b *Bucket) inlineable() bool {
|
||||
var n = b.rootNode
|
||||
|
||||
@@ -615,11 +809,11 @@ func (b *Bucket) inlineable() bool {
|
||||
|
||||
// Bucket is not inlineable if it contains subbuckets or if it goes beyond
|
||||
// our threshold for inline bucket size.
|
||||
var size = pageHeaderSize
|
||||
var size = common.PageHeaderSize
|
||||
for _, inode := range n.inodes {
|
||||
size += leafPageElementSize + uintptr(len(inode.key)) + uintptr(len(inode.value))
|
||||
size += common.LeafPageElementSize + uintptr(len(inode.Key())) + uintptr(len(inode.Value()))
|
||||
|
||||
if inode.flags&bucketLeafFlag != 0 {
|
||||
if inode.Flags()&common.BucketLeafFlag != 0 {
|
||||
return false
|
||||
} else if size > b.maxInlineBucketSize() {
|
||||
return false
|
||||
@@ -638,14 +832,14 @@ func (b *Bucket) maxInlineBucketSize() uintptr {
|
||||
func (b *Bucket) write() []byte {
|
||||
// Allocate the appropriate size.
|
||||
var n = b.rootNode
|
||||
var value = make([]byte, bucketHeaderSize+n.size())
|
||||
var value = make([]byte, common.BucketHeaderSize+n.size())
|
||||
|
||||
// Write a bucket header.
|
||||
var bucket = (*bucket)(unsafe.Pointer(&value[0]))
|
||||
*bucket = *b.bucket
|
||||
var bucket = (*common.InBucket)(unsafe.Pointer(&value[0]))
|
||||
*bucket = *b.InBucket
|
||||
|
||||
// Convert byte slice to a fake page and write the root node.
|
||||
var p = (*page)(unsafe.Pointer(&value[bucketHeaderSize]))
|
||||
var p = (*common.Page)(unsafe.Pointer(&value[common.BucketHeaderSize]))
|
||||
n.write(p)
|
||||
|
||||
return value
|
||||
@@ -662,8 +856,8 @@ func (b *Bucket) rebalance() {
|
||||
}
|
||||
|
||||
// node creates a node from a page and associates it with a given parent.
|
||||
func (b *Bucket) node(pgId pgid, parent *node) *node {
|
||||
_assert(b.nodes != nil, "nodes map expected")
|
||||
func (b *Bucket) node(pgId common.Pgid, parent *node) *node {
|
||||
common.Assert(b.nodes != nil, "nodes map expected")
|
||||
|
||||
// Retrieve node if it's already been created.
|
||||
if n := b.nodes[pgId]; n != nil {
|
||||
@@ -682,6 +876,12 @@ func (b *Bucket) node(pgId pgid, parent *node) *node {
|
||||
var p = b.page
|
||||
if p == nil {
|
||||
p = b.tx.page(pgId)
|
||||
} else {
|
||||
// if p isn't nil, then it's an inline bucket.
|
||||
// The pgId must be 0 in this case.
|
||||
common.Verify(func() {
|
||||
common.Assert(pgId == 0, "The page ID (%d) isn't 0 for an inline bucket", pgId)
|
||||
})
|
||||
}
|
||||
|
||||
// Read the page into the node and cache it.
|
||||
@@ -696,19 +896,19 @@ func (b *Bucket) node(pgId pgid, parent *node) *node {
|
||||
|
||||
// free recursively frees all pages in the bucket.
|
||||
func (b *Bucket) free() {
|
||||
if b.root == 0 {
|
||||
if b.RootPage() == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
var tx = b.tx
|
||||
b.forEachPageNode(func(p *page, n *node, _ int) {
|
||||
b.forEachPageNode(func(p *common.Page, n *node, _ int) {
|
||||
if p != nil {
|
||||
tx.db.freelist.free(tx.meta.txid, p)
|
||||
tx.db.freelist.Free(tx.meta.Txid(), p)
|
||||
} else {
|
||||
n.free()
|
||||
}
|
||||
})
|
||||
b.root = 0
|
||||
b.SetRootPage(0)
|
||||
}
|
||||
|
||||
// dereference removes all references to the old mmap.
|
||||
@@ -723,11 +923,11 @@ func (b *Bucket) dereference() {
|
||||
}
|
||||
|
||||
// pageNode returns the in-memory node, if it exists.
|
||||
// Otherwise returns the underlying page.
|
||||
func (b *Bucket) pageNode(id pgid) (*page, *node) {
|
||||
// Otherwise, returns the underlying page.
|
||||
func (b *Bucket) pageNode(id common.Pgid) (*common.Page, *node) {
|
||||
// Inline buckets have a fake page embedded in their value so treat them
|
||||
// differently. We'll return the rootNode (if available) or the fake page.
|
||||
if b.root == 0 {
|
||||
if b.RootPage() == 0 {
|
||||
if id != 0 {
|
||||
panic(fmt.Sprintf("inline bucket non-zero page access(2): %d != 0", id))
|
||||
}
|
||||
@@ -797,3 +997,9 @@ func cloneBytes(v []byte) []byte {
|
||||
copy(clone, v)
|
||||
return clone
|
||||
}
|
||||
|
||||
type BucketStructure struct {
|
||||
Name string `json:"name"` // name of the bucket
|
||||
KeyN int `json:"keyN"` // number of key/value pairs
|
||||
Children []BucketStructure `json:"buckets,omitempty"` // child buckets
|
||||
}
|
||||
|
||||
+51
-48
@@ -4,6 +4,9 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"go.etcd.io/bbolt/errors"
|
||||
"go.etcd.io/bbolt/internal/common"
|
||||
)
|
||||
|
||||
// Cursor represents an iterator that can traverse over all key/value pairs in a bucket
|
||||
@@ -30,9 +33,9 @@ func (c *Cursor) Bucket() *Bucket {
|
||||
// If the bucket is empty then a nil key and value are returned.
|
||||
// The returned key and value are only valid for the life of the transaction.
|
||||
func (c *Cursor) First() (key []byte, value []byte) {
|
||||
_assert(c.bucket.tx.db != nil, "tx closed")
|
||||
common.Assert(c.bucket.tx.db != nil, "tx closed")
|
||||
k, v, flags := c.first()
|
||||
if (flags & uint32(bucketLeafFlag)) != 0 {
|
||||
if (flags & uint32(common.BucketLeafFlag)) != 0 {
|
||||
return k, nil
|
||||
}
|
||||
return k, v
|
||||
@@ -40,7 +43,7 @@ func (c *Cursor) First() (key []byte, value []byte) {
|
||||
|
||||
func (c *Cursor) first() (key []byte, value []byte, flags uint32) {
|
||||
c.stack = c.stack[:0]
|
||||
p, n := c.bucket.pageNode(c.bucket.root)
|
||||
p, n := c.bucket.pageNode(c.bucket.RootPage())
|
||||
c.stack = append(c.stack, elemRef{page: p, node: n, index: 0})
|
||||
c.goToFirstElementOnTheStack()
|
||||
|
||||
@@ -51,7 +54,7 @@ func (c *Cursor) first() (key []byte, value []byte, flags uint32) {
|
||||
}
|
||||
|
||||
k, v, flags := c.keyValue()
|
||||
if (flags & uint32(bucketLeafFlag)) != 0 {
|
||||
if (flags & uint32(common.BucketLeafFlag)) != 0 {
|
||||
return k, nil, flags
|
||||
}
|
||||
return k, v, flags
|
||||
@@ -61,9 +64,9 @@ func (c *Cursor) first() (key []byte, value []byte, flags uint32) {
|
||||
// If the bucket is empty then a nil key and value are returned.
|
||||
// The returned key and value are only valid for the life of the transaction.
|
||||
func (c *Cursor) Last() (key []byte, value []byte) {
|
||||
_assert(c.bucket.tx.db != nil, "tx closed")
|
||||
common.Assert(c.bucket.tx.db != nil, "tx closed")
|
||||
c.stack = c.stack[:0]
|
||||
p, n := c.bucket.pageNode(c.bucket.root)
|
||||
p, n := c.bucket.pageNode(c.bucket.RootPage())
|
||||
ref := elemRef{page: p, node: n}
|
||||
ref.index = ref.count() - 1
|
||||
c.stack = append(c.stack, ref)
|
||||
@@ -80,7 +83,7 @@ func (c *Cursor) Last() (key []byte, value []byte) {
|
||||
}
|
||||
|
||||
k, v, flags := c.keyValue()
|
||||
if (flags & uint32(bucketLeafFlag)) != 0 {
|
||||
if (flags & uint32(common.BucketLeafFlag)) != 0 {
|
||||
return k, nil
|
||||
}
|
||||
return k, v
|
||||
@@ -90,9 +93,9 @@ func (c *Cursor) Last() (key []byte, value []byte) {
|
||||
// If the cursor is at the end of the bucket then a nil key and value are returned.
|
||||
// The returned key and value are only valid for the life of the transaction.
|
||||
func (c *Cursor) Next() (key []byte, value []byte) {
|
||||
_assert(c.bucket.tx.db != nil, "tx closed")
|
||||
common.Assert(c.bucket.tx.db != nil, "tx closed")
|
||||
k, v, flags := c.next()
|
||||
if (flags & uint32(bucketLeafFlag)) != 0 {
|
||||
if (flags & uint32(common.BucketLeafFlag)) != 0 {
|
||||
return k, nil
|
||||
}
|
||||
return k, v
|
||||
@@ -102,9 +105,9 @@ func (c *Cursor) Next() (key []byte, value []byte) {
|
||||
// If the cursor is at the beginning of the bucket then a nil key and value are returned.
|
||||
// The returned key and value are only valid for the life of the transaction.
|
||||
func (c *Cursor) Prev() (key []byte, value []byte) {
|
||||
_assert(c.bucket.tx.db != nil, "tx closed")
|
||||
common.Assert(c.bucket.tx.db != nil, "tx closed")
|
||||
k, v, flags := c.prev()
|
||||
if (flags & uint32(bucketLeafFlag)) != 0 {
|
||||
if (flags & uint32(common.BucketLeafFlag)) != 0 {
|
||||
return k, nil
|
||||
}
|
||||
return k, v
|
||||
@@ -115,7 +118,7 @@ func (c *Cursor) Prev() (key []byte, value []byte) {
|
||||
// follow, a nil key is returned.
|
||||
// The returned key and value are only valid for the life of the transaction.
|
||||
func (c *Cursor) Seek(seek []byte) (key []byte, value []byte) {
|
||||
_assert(c.bucket.tx.db != nil, "tx closed")
|
||||
common.Assert(c.bucket.tx.db != nil, "tx closed")
|
||||
|
||||
k, v, flags := c.seek(seek)
|
||||
|
||||
@@ -126,7 +129,7 @@ func (c *Cursor) Seek(seek []byte) (key []byte, value []byte) {
|
||||
|
||||
if k == nil {
|
||||
return nil, nil
|
||||
} else if (flags & uint32(bucketLeafFlag)) != 0 {
|
||||
} else if (flags & uint32(common.BucketLeafFlag)) != 0 {
|
||||
return k, nil
|
||||
}
|
||||
return k, v
|
||||
@@ -136,15 +139,15 @@ func (c *Cursor) Seek(seek []byte) (key []byte, value []byte) {
|
||||
// Delete fails if current key/value is a bucket or if the transaction is not writable.
|
||||
func (c *Cursor) Delete() error {
|
||||
if c.bucket.tx.db == nil {
|
||||
return ErrTxClosed
|
||||
return errors.ErrTxClosed
|
||||
} else if !c.bucket.Writable() {
|
||||
return ErrTxNotWritable
|
||||
return errors.ErrTxNotWritable
|
||||
}
|
||||
|
||||
key, _, flags := c.keyValue()
|
||||
// Return an error if current value is a bucket.
|
||||
if (flags & bucketLeafFlag) != 0 {
|
||||
return ErrIncompatibleValue
|
||||
if (flags & common.BucketLeafFlag) != 0 {
|
||||
return errors.ErrIncompatibleValue
|
||||
}
|
||||
c.node().del(key)
|
||||
|
||||
@@ -156,7 +159,7 @@ func (c *Cursor) Delete() error {
|
||||
func (c *Cursor) seek(seek []byte) (key []byte, value []byte, flags uint32) {
|
||||
// Start from root page/node and traverse to correct page.
|
||||
c.stack = c.stack[:0]
|
||||
c.search(seek, c.bucket.root)
|
||||
c.search(seek, c.bucket.RootPage())
|
||||
|
||||
// If this is a bucket then return a nil value.
|
||||
return c.keyValue()
|
||||
@@ -172,11 +175,11 @@ func (c *Cursor) goToFirstElementOnTheStack() {
|
||||
}
|
||||
|
||||
// Keep adding pages pointing to the first element to the stack.
|
||||
var pgId pgid
|
||||
var pgId common.Pgid
|
||||
if ref.node != nil {
|
||||
pgId = ref.node.inodes[ref.index].pgid
|
||||
pgId = ref.node.inodes[ref.index].Pgid()
|
||||
} else {
|
||||
pgId = ref.page.branchPageElement(uint16(ref.index)).pgid
|
||||
pgId = ref.page.BranchPageElement(uint16(ref.index)).Pgid()
|
||||
}
|
||||
p, n := c.bucket.pageNode(pgId)
|
||||
c.stack = append(c.stack, elemRef{page: p, node: n, index: 0})
|
||||
@@ -193,11 +196,11 @@ func (c *Cursor) last() {
|
||||
}
|
||||
|
||||
// Keep adding pages pointing to the last element in the stack.
|
||||
var pgId pgid
|
||||
var pgId common.Pgid
|
||||
if ref.node != nil {
|
||||
pgId = ref.node.inodes[ref.index].pgid
|
||||
pgId = ref.node.inodes[ref.index].Pgid()
|
||||
} else {
|
||||
pgId = ref.page.branchPageElement(uint16(ref.index)).pgid
|
||||
pgId = ref.page.BranchPageElement(uint16(ref.index)).Pgid()
|
||||
}
|
||||
p, n := c.bucket.pageNode(pgId)
|
||||
|
||||
@@ -277,10 +280,10 @@ func (c *Cursor) prev() (key []byte, value []byte, flags uint32) {
|
||||
}
|
||||
|
||||
// search recursively performs a binary search against a given page/node until it finds a given key.
|
||||
func (c *Cursor) search(key []byte, pgId pgid) {
|
||||
func (c *Cursor) search(key []byte, pgId common.Pgid) {
|
||||
p, n := c.bucket.pageNode(pgId)
|
||||
if p != nil && (p.flags&(branchPageFlag|leafPageFlag)) == 0 {
|
||||
panic(fmt.Sprintf("invalid page type: %d: %x", p.id, p.flags))
|
||||
if p != nil && !p.IsBranchPage() && !p.IsLeafPage() {
|
||||
panic(fmt.Sprintf("invalid page type: %d: %x", p.Id(), p.Flags()))
|
||||
}
|
||||
e := elemRef{page: p, node: n}
|
||||
c.stack = append(c.stack, e)
|
||||
@@ -303,7 +306,7 @@ func (c *Cursor) searchNode(key []byte, n *node) {
|
||||
index := sort.Search(len(n.inodes), func(i int) bool {
|
||||
// TODO(benbjohnson): Optimize this range search. It's a bit hacky right now.
|
||||
// sort.Search() finds the lowest index where f() != -1 but we need the highest index.
|
||||
ret := bytes.Compare(n.inodes[i].key, key)
|
||||
ret := bytes.Compare(n.inodes[i].Key(), key)
|
||||
if ret == 0 {
|
||||
exact = true
|
||||
}
|
||||
@@ -315,18 +318,18 @@ func (c *Cursor) searchNode(key []byte, n *node) {
|
||||
c.stack[len(c.stack)-1].index = index
|
||||
|
||||
// Recursively search to the next page.
|
||||
c.search(key, n.inodes[index].pgid)
|
||||
c.search(key, n.inodes[index].Pgid())
|
||||
}
|
||||
|
||||
func (c *Cursor) searchPage(key []byte, p *page) {
|
||||
func (c *Cursor) searchPage(key []byte, p *common.Page) {
|
||||
// Binary search for the correct range.
|
||||
inodes := p.branchPageElements()
|
||||
inodes := p.BranchPageElements()
|
||||
|
||||
var exact bool
|
||||
index := sort.Search(int(p.count), func(i int) bool {
|
||||
index := sort.Search(int(p.Count()), func(i int) bool {
|
||||
// TODO(benbjohnson): Optimize this range search. It's a bit hacky right now.
|
||||
// sort.Search() finds the lowest index where f() != -1 but we need the highest index.
|
||||
ret := bytes.Compare(inodes[i].key(), key)
|
||||
ret := bytes.Compare(inodes[i].Key(), key)
|
||||
if ret == 0 {
|
||||
exact = true
|
||||
}
|
||||
@@ -338,7 +341,7 @@ func (c *Cursor) searchPage(key []byte, p *page) {
|
||||
c.stack[len(c.stack)-1].index = index
|
||||
|
||||
// Recursively search to the next page.
|
||||
c.search(key, inodes[index].pgid)
|
||||
c.search(key, inodes[index].Pgid())
|
||||
}
|
||||
|
||||
// nsearch searches the leaf node on the top of the stack for a key.
|
||||
@@ -349,16 +352,16 @@ func (c *Cursor) nsearch(key []byte) {
|
||||
// If we have a node then search its inodes.
|
||||
if n != nil {
|
||||
index := sort.Search(len(n.inodes), func(i int) bool {
|
||||
return bytes.Compare(n.inodes[i].key, key) != -1
|
||||
return bytes.Compare(n.inodes[i].Key(), key) != -1
|
||||
})
|
||||
e.index = index
|
||||
return
|
||||
}
|
||||
|
||||
// If we have a page then search its leaf elements.
|
||||
inodes := p.leafPageElements()
|
||||
index := sort.Search(int(p.count), func(i int) bool {
|
||||
return bytes.Compare(inodes[i].key(), key) != -1
|
||||
inodes := p.LeafPageElements()
|
||||
index := sort.Search(int(p.Count()), func(i int) bool {
|
||||
return bytes.Compare(inodes[i].Key(), key) != -1
|
||||
})
|
||||
e.index = index
|
||||
}
|
||||
@@ -375,17 +378,17 @@ func (c *Cursor) keyValue() ([]byte, []byte, uint32) {
|
||||
// Retrieve value from node.
|
||||
if ref.node != nil {
|
||||
inode := &ref.node.inodes[ref.index]
|
||||
return inode.key, inode.value, inode.flags
|
||||
return inode.Key(), inode.Value(), inode.Flags()
|
||||
}
|
||||
|
||||
// Or retrieve value from page.
|
||||
elem := ref.page.leafPageElement(uint16(ref.index))
|
||||
return elem.key(), elem.value(), elem.flags
|
||||
elem := ref.page.LeafPageElement(uint16(ref.index))
|
||||
return elem.Key(), elem.Value(), elem.Flags()
|
||||
}
|
||||
|
||||
// node returns the node that the cursor is currently positioned on.
|
||||
func (c *Cursor) node() *node {
|
||||
_assert(len(c.stack) > 0, "accessing a node with a zero-length cursor stack")
|
||||
common.Assert(len(c.stack) > 0, "accessing a node with a zero-length cursor stack")
|
||||
|
||||
// If the top of the stack is a leaf node then just return it.
|
||||
if ref := &c.stack[len(c.stack)-1]; ref.node != nil && ref.isLeaf() {
|
||||
@@ -395,19 +398,19 @@ func (c *Cursor) node() *node {
|
||||
// Start from root and traverse down the hierarchy.
|
||||
var n = c.stack[0].node
|
||||
if n == nil {
|
||||
n = c.bucket.node(c.stack[0].page.id, nil)
|
||||
n = c.bucket.node(c.stack[0].page.Id(), nil)
|
||||
}
|
||||
for _, ref := range c.stack[:len(c.stack)-1] {
|
||||
_assert(!n.isLeaf, "expected branch node")
|
||||
common.Assert(!n.isLeaf, "expected branch node")
|
||||
n = n.childAt(ref.index)
|
||||
}
|
||||
_assert(n.isLeaf, "expected leaf node")
|
||||
common.Assert(n.isLeaf, "expected leaf node")
|
||||
return n
|
||||
}
|
||||
|
||||
// elemRef represents a reference to an element on a given page/node.
|
||||
type elemRef struct {
|
||||
page *page
|
||||
page *common.Page
|
||||
node *node
|
||||
index int
|
||||
}
|
||||
@@ -417,7 +420,7 @@ func (r *elemRef) isLeaf() bool {
|
||||
if r.node != nil {
|
||||
return r.node.isLeaf
|
||||
}
|
||||
return (r.page.flags & leafPageFlag) != 0
|
||||
return r.page.IsLeafPage()
|
||||
}
|
||||
|
||||
// count returns the number of inodes or page elements.
|
||||
@@ -425,5 +428,5 @@ func (r *elemRef) count() int {
|
||||
if r.node != nil {
|
||||
return len(r.node.inodes)
|
||||
}
|
||||
return int(r.page.count)
|
||||
return int(r.page.Count())
|
||||
}
|
||||
|
||||
+227
-226
@@ -3,49 +3,28 @@ package bbolt
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"io"
|
||||
"os"
|
||||
"runtime"
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
berrors "go.etcd.io/bbolt/errors"
|
||||
"go.etcd.io/bbolt/internal/common"
|
||||
fl "go.etcd.io/bbolt/internal/freelist"
|
||||
)
|
||||
|
||||
// The largest step that can be taken when remapping the mmap.
|
||||
const maxMmapStep = 1 << 30 // 1GB
|
||||
|
||||
// The data file format version.
|
||||
const version = 2
|
||||
|
||||
// Represents a marker value to indicate that a file is a Bolt DB.
|
||||
const magic uint32 = 0xED0CDAED
|
||||
|
||||
const pgidNoFreelist pgid = 0xffffffffffffffff
|
||||
|
||||
// IgnoreNoSync specifies whether the NoSync field of a DB is ignored when
|
||||
// syncing changes to a file. This is required as some operating systems,
|
||||
// such as OpenBSD, do not have a unified buffer cache (UBC) and writes
|
||||
// must be synchronized using the msync(2) syscall.
|
||||
const IgnoreNoSync = runtime.GOOS == "openbsd"
|
||||
|
||||
// Default values if not set in a DB instance.
|
||||
const (
|
||||
DefaultMaxBatchSize int = 1000
|
||||
DefaultMaxBatchDelay = 10 * time.Millisecond
|
||||
DefaultAllocSize = 16 * 1024 * 1024
|
||||
)
|
||||
|
||||
// default page size for db is set to the OS page size.
|
||||
var defaultPageSize = os.Getpagesize()
|
||||
|
||||
// The time elapsed between consecutive file locking attempts.
|
||||
const flockRetryTimeout = 50 * time.Millisecond
|
||||
|
||||
// FreelistType is the type of the freelist backend
|
||||
type FreelistType string
|
||||
|
||||
// TODO(ahrtr): eventually we should (step by step)
|
||||
// 1. default to `FreelistMapType`;
|
||||
// 2. remove the `FreelistArrayType`, do not export `FreelistMapType`
|
||||
// and remove field `FreelistType' from both `DB` and `Options`;
|
||||
const (
|
||||
// FreelistArrayType indicates backend freelist type is array
|
||||
FreelistArrayType = FreelistType("array")
|
||||
@@ -137,6 +116,8 @@ type DB struct {
|
||||
// Supported only on Unix via mlock/munlock syscalls.
|
||||
Mlock bool
|
||||
|
||||
logger Logger
|
||||
|
||||
path string
|
||||
openFile func(string, int, os.FileMode) (*os.File, error)
|
||||
file *os.File
|
||||
@@ -146,15 +127,14 @@ type DB struct {
|
||||
dataref []byte // mmap'ed readonly, write throws SEGV
|
||||
data *[maxMapSize]byte
|
||||
datasz int
|
||||
filesz int // current on disk file size
|
||||
meta0 *meta
|
||||
meta1 *meta
|
||||
meta0 *common.Meta
|
||||
meta1 *common.Meta
|
||||
pageSize int
|
||||
opened bool
|
||||
rwtx *Tx
|
||||
txs []*Tx
|
||||
|
||||
freelist *freelist
|
||||
freelist fl.Interface
|
||||
freelistLoad sync.Once
|
||||
|
||||
pagePool sync.Pool
|
||||
@@ -191,13 +171,15 @@ func (db *DB) String() string {
|
||||
return fmt.Sprintf("DB<%q>", db.path)
|
||||
}
|
||||
|
||||
// Open creates and opens a database at the given path.
|
||||
// If the file does not exist then it will be created automatically.
|
||||
// Open creates and opens a database at the given path with a given file mode.
|
||||
// If the file does not exist then it will be created automatically with a given file mode.
|
||||
// Passing in nil options will cause Bolt to open the database with the default options.
|
||||
func Open(path string, mode os.FileMode, options *Options) (*DB, error) {
|
||||
db := &DB{
|
||||
// Note: For read/write transactions, ensure the owner has write permission on the created/opened database file, e.g. 0600
|
||||
func Open(path string, mode os.FileMode, options *Options) (db *DB, err error) {
|
||||
db = &DB{
|
||||
opened: true,
|
||||
}
|
||||
|
||||
// Set default options if no options are provided.
|
||||
if options == nil {
|
||||
options = DefaultOptions
|
||||
@@ -211,9 +193,27 @@ func Open(path string, mode os.FileMode, options *Options) (*DB, error) {
|
||||
db.Mlock = options.Mlock
|
||||
|
||||
// Set default values for later DB operations.
|
||||
db.MaxBatchSize = DefaultMaxBatchSize
|
||||
db.MaxBatchDelay = DefaultMaxBatchDelay
|
||||
db.AllocSize = DefaultAllocSize
|
||||
db.MaxBatchSize = common.DefaultMaxBatchSize
|
||||
db.MaxBatchDelay = common.DefaultMaxBatchDelay
|
||||
db.AllocSize = common.DefaultAllocSize
|
||||
|
||||
if options.Logger == nil {
|
||||
db.logger = getDiscardLogger()
|
||||
} else {
|
||||
db.logger = options.Logger
|
||||
}
|
||||
|
||||
lg := db.Logger()
|
||||
if lg != discardLogger {
|
||||
lg.Infof("Opening db file (%s) with mode %s and with options: %s", path, mode, options)
|
||||
defer func() {
|
||||
if err != nil {
|
||||
lg.Errorf("Opening bbolt db (%s) failed: %v", path, err)
|
||||
} else {
|
||||
lg.Infof("Opening bbolt db (%s) successfully", path)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
flag := os.O_RDWR
|
||||
if options.ReadOnly {
|
||||
@@ -222,6 +222,7 @@ func Open(path string, mode os.FileMode, options *Options) (*DB, error) {
|
||||
} else {
|
||||
// always load free pages in write mode
|
||||
db.PreLoadFreelist = true
|
||||
flag |= os.O_CREATE
|
||||
}
|
||||
|
||||
db.openFile = options.OpenFile
|
||||
@@ -230,9 +231,9 @@ func Open(path string, mode os.FileMode, options *Options) (*DB, error) {
|
||||
}
|
||||
|
||||
// Open data file and separate sync handler for metadata writes.
|
||||
var err error
|
||||
if db.file, err = db.openFile(path, flag|os.O_CREATE, mode); err != nil {
|
||||
if db.file, err = db.openFile(path, flag, mode); err != nil {
|
||||
_ = db.close()
|
||||
lg.Errorf("failed to open db file (%s): %v", path, err)
|
||||
return nil, err
|
||||
}
|
||||
db.path = db.file.Name()
|
||||
@@ -244,8 +245,9 @@ func Open(path string, mode os.FileMode, options *Options) (*DB, error) {
|
||||
// if !options.ReadOnly.
|
||||
// The database file is locked using the shared lock (more than one process may
|
||||
// hold a lock at the same time) otherwise (options.ReadOnly is set).
|
||||
if err := flock(db, !db.readOnly, options.Timeout); err != nil {
|
||||
if err = flock(db, !db.readOnly, options.Timeout); err != nil {
|
||||
_ = db.close()
|
||||
lg.Errorf("failed to lock db file (%s), readonly: %t, error: %v", path, db.readOnly, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -254,27 +256,28 @@ func Open(path string, mode os.FileMode, options *Options) (*DB, error) {
|
||||
|
||||
if db.pageSize = options.PageSize; db.pageSize == 0 {
|
||||
// Set the default page size to the OS page size.
|
||||
db.pageSize = defaultPageSize
|
||||
db.pageSize = common.DefaultPageSize
|
||||
}
|
||||
|
||||
// Initialize the database if it doesn't exist.
|
||||
if info, err := db.file.Stat(); err != nil {
|
||||
if info, statErr := db.file.Stat(); statErr != nil {
|
||||
_ = db.close()
|
||||
return nil, err
|
||||
lg.Errorf("failed to get db file's stats (%s): %v", path, err)
|
||||
return nil, statErr
|
||||
} else if info.Size() == 0 {
|
||||
// Initialize new files with meta pages.
|
||||
if err := db.init(); err != nil {
|
||||
if err = db.init(); err != nil {
|
||||
// clean up file descriptor on initialization fail
|
||||
_ = db.close()
|
||||
lg.Errorf("failed to initialize db file (%s): %v", path, err)
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
// try to get the page size from the metadata pages
|
||||
if pgSize, err := db.getPageSize(); err == nil {
|
||||
db.pageSize = pgSize
|
||||
} else {
|
||||
if db.pageSize, err = db.getPageSize(); err != nil {
|
||||
_ = db.close()
|
||||
return nil, ErrInvalid
|
||||
lg.Errorf("failed to get page size from db file (%s): %v", path, err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -286,8 +289,9 @@ func Open(path string, mode os.FileMode, options *Options) (*DB, error) {
|
||||
}
|
||||
|
||||
// Memory map the data file.
|
||||
if err := db.mmap(options.InitialMmapSize); err != nil {
|
||||
if err = db.mmap(options.InitialMmapSize); err != nil {
|
||||
_ = db.close()
|
||||
lg.Errorf("failed to map db file (%s): %v", path, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -302,13 +306,14 @@ func Open(path string, mode os.FileMode, options *Options) (*DB, error) {
|
||||
// Flush freelist when transitioning from no sync to sync so
|
||||
// NoFreelistSync unaware boltdb can open the db later.
|
||||
if !db.NoFreelistSync && !db.hasSyncedFreelist() {
|
||||
tx, err := db.Begin(true)
|
||||
tx, txErr := db.Begin(true)
|
||||
if tx != nil {
|
||||
err = tx.Commit()
|
||||
txErr = tx.Commit()
|
||||
}
|
||||
if err != nil {
|
||||
if txErr != nil {
|
||||
lg.Errorf("starting readwrite transaction failed: %v", txErr)
|
||||
_ = db.close()
|
||||
return nil, err
|
||||
return nil, txErr
|
||||
}
|
||||
}
|
||||
|
||||
@@ -352,7 +357,7 @@ func (db *DB) getPageSize() (int, error) {
|
||||
return db.pageSize, nil
|
||||
}
|
||||
|
||||
return 0, ErrInvalid
|
||||
return 0, berrors.ErrInvalid
|
||||
}
|
||||
|
||||
// getPageSizeFromFirstMeta reads the pageSize from the first meta page
|
||||
@@ -361,11 +366,11 @@ func (db *DB) getPageSizeFromFirstMeta() (int, bool, error) {
|
||||
var metaCanRead bool
|
||||
if bw, err := db.file.ReadAt(buf[:], 0); err == nil && bw == len(buf) {
|
||||
metaCanRead = true
|
||||
if m := db.pageInBuffer(buf[:], 0).meta(); m.validate() == nil {
|
||||
return int(m.pageSize), metaCanRead, nil
|
||||
if m := db.pageInBuffer(buf[:], 0).Meta(); m.Validate() == nil {
|
||||
return int(m.PageSize()), metaCanRead, nil
|
||||
}
|
||||
}
|
||||
return 0, metaCanRead, ErrInvalid
|
||||
return 0, metaCanRead, berrors.ErrInvalid
|
||||
}
|
||||
|
||||
// getPageSizeFromSecondMeta reads the pageSize from the second meta page
|
||||
@@ -397,13 +402,13 @@ func (db *DB) getPageSizeFromSecondMeta() (int, bool, error) {
|
||||
bw, err := db.file.ReadAt(buf[:], pos)
|
||||
if (err == nil && bw == len(buf)) || (err == io.EOF && int64(bw) == (fileSize-pos)) {
|
||||
metaCanRead = true
|
||||
if m := db.pageInBuffer(buf[:], 0).meta(); m.validate() == nil {
|
||||
return int(m.pageSize), metaCanRead, nil
|
||||
if m := db.pageInBuffer(buf[:], 0).Meta(); m.Validate() == nil {
|
||||
return int(m.PageSize()), metaCanRead, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0, metaCanRead, ErrInvalid
|
||||
return 0, metaCanRead, berrors.ErrInvalid
|
||||
}
|
||||
|
||||
// loadFreelist reads the freelist if it is synced, or reconstructs it
|
||||
@@ -414,17 +419,29 @@ func (db *DB) loadFreelist() {
|
||||
db.freelist = newFreelist(db.FreelistType)
|
||||
if !db.hasSyncedFreelist() {
|
||||
// Reconstruct free list by scanning the DB.
|
||||
db.freelist.readIDs(db.freepages())
|
||||
db.freelist.Init(db.freepages())
|
||||
} else {
|
||||
// Read free list from freelist page.
|
||||
db.freelist.read(db.page(db.meta().freelist))
|
||||
db.freelist.Read(db.page(db.meta().Freelist()))
|
||||
}
|
||||
db.stats.FreePageN = db.freelist.free_count()
|
||||
db.stats.FreePageN = db.freelist.FreeCount()
|
||||
})
|
||||
}
|
||||
|
||||
func (db *DB) hasSyncedFreelist() bool {
|
||||
return db.meta().freelist != pgidNoFreelist
|
||||
return db.meta().Freelist() != common.PgidNoFreelist
|
||||
}
|
||||
|
||||
func (db *DB) fileSize() (int, error) {
|
||||
info, err := db.file.Stat()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("file stat error: %w", err)
|
||||
}
|
||||
sz := int(info.Size())
|
||||
if sz < db.pageSize*2 {
|
||||
return 0, fmt.Errorf("file size too small %d", sz)
|
||||
}
|
||||
return sz, nil
|
||||
}
|
||||
|
||||
// mmap opens the underlying memory-mapped file and initializes the meta references.
|
||||
@@ -433,21 +450,22 @@ func (db *DB) mmap(minsz int) (err error) {
|
||||
db.mmaplock.Lock()
|
||||
defer db.mmaplock.Unlock()
|
||||
|
||||
info, err := db.file.Stat()
|
||||
if err != nil {
|
||||
return fmt.Errorf("mmap stat error: %s", err)
|
||||
} else if int(info.Size()) < db.pageSize*2 {
|
||||
return fmt.Errorf("file size too small")
|
||||
}
|
||||
lg := db.Logger()
|
||||
|
||||
// Ensure the size is at least the minimum size.
|
||||
fileSize := int(info.Size())
|
||||
var fileSize int
|
||||
fileSize, err = db.fileSize()
|
||||
if err != nil {
|
||||
lg.Errorf("getting file size failed: %w", err)
|
||||
return err
|
||||
}
|
||||
var size = fileSize
|
||||
if size < minsz {
|
||||
size = minsz
|
||||
}
|
||||
size, err = db.mmapSize(size)
|
||||
if err != nil {
|
||||
lg.Errorf("getting map size failed: %w", err)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -472,6 +490,7 @@ func (db *DB) mmap(minsz int) (err error) {
|
||||
// gofail: var mapError string
|
||||
// return errors.New(mapError)
|
||||
if err = mmap(db, size); err != nil {
|
||||
lg.Errorf("[GOOS: %s, GOARCH: %s] mmap failed, size: %d, error: %v", runtime.GOOS, runtime.GOARCH, size, err)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -493,15 +512,16 @@ func (db *DB) mmap(minsz int) (err error) {
|
||||
}
|
||||
|
||||
// Save references to the meta pages.
|
||||
db.meta0 = db.page(0).meta()
|
||||
db.meta1 = db.page(1).meta()
|
||||
db.meta0 = db.page(0).Meta()
|
||||
db.meta1 = db.page(1).Meta()
|
||||
|
||||
// Validate the meta pages. We only return an error if both meta pages fail
|
||||
// validation, since meta0 failing validation means that it wasn't saved
|
||||
// properly -- but we can recover using meta1. And vice-versa.
|
||||
err0 := db.meta0.validate()
|
||||
err1 := db.meta1.validate()
|
||||
err0 := db.meta0.Validate()
|
||||
err1 := db.meta1.Validate()
|
||||
if err0 != nil && err1 != nil {
|
||||
lg.Errorf("both meta pages are invalid, meta0: %v, meta1: %v", err0, err1)
|
||||
return err0
|
||||
}
|
||||
|
||||
@@ -524,6 +544,7 @@ func (db *DB) munmap() error {
|
||||
// gofail: var unmapError string
|
||||
// return errors.New(unmapError)
|
||||
if err := munmap(db); err != nil {
|
||||
db.Logger().Errorf("[GOOS: %s, GOARCH: %s] munmap failed, db.datasz: %d, error: %v", runtime.GOOS, runtime.GOARCH, db.datasz, err)
|
||||
return fmt.Errorf("unmap error: %v", err.Error())
|
||||
}
|
||||
|
||||
@@ -543,13 +564,13 @@ func (db *DB) mmapSize(size int) (int, error) {
|
||||
|
||||
// Verify the requested size is not above the maximum allowed.
|
||||
if size > maxMapSize {
|
||||
return 0, fmt.Errorf("mmap too large")
|
||||
return 0, errors.New("mmap too large")
|
||||
}
|
||||
|
||||
// If larger than 1GB then grow by 1GB at a time.
|
||||
sz := int64(size)
|
||||
if remainder := sz % int64(maxMmapStep); remainder > 0 {
|
||||
sz += int64(maxMmapStep) - remainder
|
||||
if remainder := sz % int64(common.MaxMmapStep); remainder > 0 {
|
||||
sz += int64(common.MaxMmapStep) - remainder
|
||||
}
|
||||
|
||||
// Ensure that the mmap size is a multiple of the page size.
|
||||
@@ -571,6 +592,7 @@ func (db *DB) munlock(fileSize int) error {
|
||||
// gofail: var munlockError string
|
||||
// return errors.New(munlockError)
|
||||
if err := munlock(db, fileSize); err != nil {
|
||||
db.Logger().Errorf("[GOOS: %s, GOARCH: %s] munlock failed, fileSize: %d, db.datasz: %d, error: %v", runtime.GOOS, runtime.GOARCH, fileSize, db.datasz, err)
|
||||
return fmt.Errorf("munlock error: %v", err.Error())
|
||||
}
|
||||
return nil
|
||||
@@ -580,6 +602,7 @@ func (db *DB) mlock(fileSize int) error {
|
||||
// gofail: var mlockError string
|
||||
// return errors.New(mlockError)
|
||||
if err := mlock(db, fileSize); err != nil {
|
||||
db.Logger().Errorf("[GOOS: %s, GOARCH: %s] mlock failed, fileSize: %d, db.datasz: %d, error: %v", runtime.GOOS, runtime.GOARCH, fileSize, db.datasz, err)
|
||||
return fmt.Errorf("mlock error: %v", err.Error())
|
||||
}
|
||||
return nil
|
||||
@@ -600,42 +623,43 @@ func (db *DB) init() error {
|
||||
// Create two meta pages on a buffer.
|
||||
buf := make([]byte, db.pageSize*4)
|
||||
for i := 0; i < 2; i++ {
|
||||
p := db.pageInBuffer(buf, pgid(i))
|
||||
p.id = pgid(i)
|
||||
p.flags = metaPageFlag
|
||||
p := db.pageInBuffer(buf, common.Pgid(i))
|
||||
p.SetId(common.Pgid(i))
|
||||
p.SetFlags(common.MetaPageFlag)
|
||||
|
||||
// Initialize the meta page.
|
||||
m := p.meta()
|
||||
m.magic = magic
|
||||
m.version = version
|
||||
m.pageSize = uint32(db.pageSize)
|
||||
m.freelist = 2
|
||||
m.root = bucket{root: 3}
|
||||
m.pgid = 4
|
||||
m.txid = txid(i)
|
||||
m.checksum = m.sum64()
|
||||
m := p.Meta()
|
||||
m.SetMagic(common.Magic)
|
||||
m.SetVersion(common.Version)
|
||||
m.SetPageSize(uint32(db.pageSize))
|
||||
m.SetFreelist(2)
|
||||
m.SetRootBucket(common.NewInBucket(3, 0))
|
||||
m.SetPgid(4)
|
||||
m.SetTxid(common.Txid(i))
|
||||
m.SetChecksum(m.Sum64())
|
||||
}
|
||||
|
||||
// Write an empty freelist at page 3.
|
||||
p := db.pageInBuffer(buf, pgid(2))
|
||||
p.id = pgid(2)
|
||||
p.flags = freelistPageFlag
|
||||
p.count = 0
|
||||
p := db.pageInBuffer(buf, common.Pgid(2))
|
||||
p.SetId(2)
|
||||
p.SetFlags(common.FreelistPageFlag)
|
||||
p.SetCount(0)
|
||||
|
||||
// Write an empty leaf page at page 4.
|
||||
p = db.pageInBuffer(buf, pgid(3))
|
||||
p.id = pgid(3)
|
||||
p.flags = leafPageFlag
|
||||
p.count = 0
|
||||
p = db.pageInBuffer(buf, common.Pgid(3))
|
||||
p.SetId(3)
|
||||
p.SetFlags(common.LeafPageFlag)
|
||||
p.SetCount(0)
|
||||
|
||||
// Write the buffer to our data file.
|
||||
if _, err := db.ops.writeAt(buf, 0); err != nil {
|
||||
db.Logger().Errorf("writeAt failed: %w", err)
|
||||
return err
|
||||
}
|
||||
if err := fdatasync(db); err != nil {
|
||||
db.Logger().Errorf("[GOOS: %s, GOARCH: %s] fdatasync failed: %w", runtime.GOOS, runtime.GOARCH, err)
|
||||
return err
|
||||
}
|
||||
db.filesz = len(buf)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -716,13 +740,31 @@ func (db *DB) close() error {
|
||||
//
|
||||
// IMPORTANT: You must close read-only transactions after you are finished or
|
||||
// else the database will not reclaim old pages.
|
||||
func (db *DB) Begin(writable bool) (*Tx, error) {
|
||||
func (db *DB) Begin(writable bool) (t *Tx, err error) {
|
||||
if lg := db.Logger(); lg != discardLogger {
|
||||
lg.Debugf("Starting a new transaction [writable: %t]", writable)
|
||||
defer func() {
|
||||
if err != nil {
|
||||
lg.Errorf("Starting a new transaction [writable: %t] failed: %v", writable, err)
|
||||
} else {
|
||||
lg.Debugf("Starting a new transaction [writable: %t] successfully", writable)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
if writable {
|
||||
return db.beginRWTx()
|
||||
}
|
||||
return db.beginTx()
|
||||
}
|
||||
|
||||
func (db *DB) Logger() Logger {
|
||||
if db == nil || db.logger == nil {
|
||||
return getDiscardLogger()
|
||||
}
|
||||
return db.logger
|
||||
}
|
||||
|
||||
func (db *DB) beginTx() (*Tx, error) {
|
||||
// Lock the meta pages while we initialize the transaction. We obtain
|
||||
// the meta lock before the mmap lock because that's the order that the
|
||||
@@ -738,14 +780,14 @@ func (db *DB) beginTx() (*Tx, error) {
|
||||
if !db.opened {
|
||||
db.mmaplock.RUnlock()
|
||||
db.metalock.Unlock()
|
||||
return nil, ErrDatabaseNotOpen
|
||||
return nil, berrors.ErrDatabaseNotOpen
|
||||
}
|
||||
|
||||
// Exit if the database is not correctly mapped.
|
||||
if db.data == nil {
|
||||
db.mmaplock.RUnlock()
|
||||
db.metalock.Unlock()
|
||||
return nil, ErrInvalidMapping
|
||||
return nil, berrors.ErrInvalidMapping
|
||||
}
|
||||
|
||||
// Create a transaction associated with the database.
|
||||
@@ -755,6 +797,9 @@ func (db *DB) beginTx() (*Tx, error) {
|
||||
// Keep track of transaction until it closes.
|
||||
db.txs = append(db.txs, t)
|
||||
n := len(db.txs)
|
||||
if db.freelist != nil {
|
||||
db.freelist.AddReadonlyTXID(t.meta.Txid())
|
||||
}
|
||||
|
||||
// Unlock the meta pages.
|
||||
db.metalock.Unlock()
|
||||
@@ -771,7 +816,7 @@ func (db *DB) beginTx() (*Tx, error) {
|
||||
func (db *DB) beginRWTx() (*Tx, error) {
|
||||
// If the database was opened with Options.ReadOnly, return an error.
|
||||
if db.readOnly {
|
||||
return nil, ErrDatabaseReadOnly
|
||||
return nil, berrors.ErrDatabaseReadOnly
|
||||
}
|
||||
|
||||
// Obtain writer lock. This is released by the transaction when it closes.
|
||||
@@ -786,49 +831,23 @@ func (db *DB) beginRWTx() (*Tx, error) {
|
||||
// Exit if the database is not open yet.
|
||||
if !db.opened {
|
||||
db.rwlock.Unlock()
|
||||
return nil, ErrDatabaseNotOpen
|
||||
return nil, berrors.ErrDatabaseNotOpen
|
||||
}
|
||||
|
||||
// Exit if the database is not correctly mapped.
|
||||
if db.data == nil {
|
||||
db.rwlock.Unlock()
|
||||
return nil, ErrInvalidMapping
|
||||
return nil, berrors.ErrInvalidMapping
|
||||
}
|
||||
|
||||
// Create a transaction associated with the database.
|
||||
t := &Tx{writable: true}
|
||||
t.init(db)
|
||||
db.rwtx = t
|
||||
db.freePages()
|
||||
db.freelist.ReleasePendingPages()
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// freePages releases any pages associated with closed read-only transactions.
|
||||
func (db *DB) freePages() {
|
||||
// Free all pending pages prior to earliest open transaction.
|
||||
sort.Sort(txsById(db.txs))
|
||||
minid := txid(0xFFFFFFFFFFFFFFFF)
|
||||
if len(db.txs) > 0 {
|
||||
minid = db.txs[0].meta.txid
|
||||
}
|
||||
if minid > 0 {
|
||||
db.freelist.release(minid - 1)
|
||||
}
|
||||
// Release unused txid extents.
|
||||
for _, t := range db.txs {
|
||||
db.freelist.releaseRange(minid, t.meta.txid-1)
|
||||
minid = t.meta.txid + 1
|
||||
}
|
||||
db.freelist.releaseRange(minid, txid(0xFFFFFFFFFFFFFFFF))
|
||||
// Any page both allocated and freed in an extent is safe to release.
|
||||
}
|
||||
|
||||
type txsById []*Tx
|
||||
|
||||
func (t txsById) Len() int { return len(t) }
|
||||
func (t txsById) Swap(i, j int) { t[i], t[j] = t[j], t[i] }
|
||||
func (t txsById) Less(i, j int) bool { return t[i].meta.txid < t[j].meta.txid }
|
||||
|
||||
// removeTx removes a transaction from the database.
|
||||
func (db *DB) removeTx(tx *Tx) {
|
||||
// Release the read lock on the mmap.
|
||||
@@ -848,6 +867,9 @@ func (db *DB) removeTx(tx *Tx) {
|
||||
}
|
||||
}
|
||||
n := len(db.txs)
|
||||
if db.freelist != nil {
|
||||
db.freelist.RemoveReadonlyTXID(tx.meta.Txid())
|
||||
}
|
||||
|
||||
// Unlock the meta pages.
|
||||
db.metalock.Unlock()
|
||||
@@ -1056,7 +1078,20 @@ func safelyCall(fn func(*Tx) error, tx *Tx) (err error) {
|
||||
//
|
||||
// This is not necessary under normal operation, however, if you use NoSync
|
||||
// then it allows you to force the database file to sync against the disk.
|
||||
func (db *DB) Sync() error { return fdatasync(db) }
|
||||
func (db *DB) Sync() (err error) {
|
||||
if lg := db.Logger(); lg != discardLogger {
|
||||
lg.Debug("Syncing bbolt db (%s)", db.path)
|
||||
defer func() {
|
||||
if err != nil {
|
||||
lg.Errorf("[GOOS: %s, GOARCH: %s] syncing bbolt db (%s) failed: %v", runtime.GOOS, runtime.GOARCH, db.path, err)
|
||||
} else {
|
||||
lg.Debugf("Syncing bbolt db (%s) successfully", db.path)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
return fdatasync(db)
|
||||
}
|
||||
|
||||
// Stats retrieves ongoing performance stats for the database.
|
||||
// This is only updated when a transaction closes.
|
||||
@@ -1069,37 +1104,37 @@ func (db *DB) Stats() Stats {
|
||||
// This is for internal access to the raw data bytes from the C cursor, use
|
||||
// carefully, or not at all.
|
||||
func (db *DB) Info() *Info {
|
||||
_assert(db.data != nil, "database file isn't correctly mapped")
|
||||
common.Assert(db.data != nil, "database file isn't correctly mapped")
|
||||
return &Info{uintptr(unsafe.Pointer(&db.data[0])), db.pageSize}
|
||||
}
|
||||
|
||||
// page retrieves a page reference from the mmap based on the current page size.
|
||||
func (db *DB) page(id pgid) *page {
|
||||
pos := id * pgid(db.pageSize)
|
||||
return (*page)(unsafe.Pointer(&db.data[pos]))
|
||||
func (db *DB) page(id common.Pgid) *common.Page {
|
||||
pos := id * common.Pgid(db.pageSize)
|
||||
return (*common.Page)(unsafe.Pointer(&db.data[pos]))
|
||||
}
|
||||
|
||||
// pageInBuffer retrieves a page reference from a given byte array based on the current page size.
|
||||
func (db *DB) pageInBuffer(b []byte, id pgid) *page {
|
||||
return (*page)(unsafe.Pointer(&b[id*pgid(db.pageSize)]))
|
||||
func (db *DB) pageInBuffer(b []byte, id common.Pgid) *common.Page {
|
||||
return (*common.Page)(unsafe.Pointer(&b[id*common.Pgid(db.pageSize)]))
|
||||
}
|
||||
|
||||
// meta retrieves the current meta page reference.
|
||||
func (db *DB) meta() *meta {
|
||||
func (db *DB) meta() *common.Meta {
|
||||
// We have to return the meta with the highest txid which doesn't fail
|
||||
// validation. Otherwise, we can cause errors when in fact the database is
|
||||
// in a consistent state. metaA is the one with the higher txid.
|
||||
metaA := db.meta0
|
||||
metaB := db.meta1
|
||||
if db.meta1.txid > db.meta0.txid {
|
||||
if db.meta1.Txid() > db.meta0.Txid() {
|
||||
metaA = db.meta1
|
||||
metaB = db.meta0
|
||||
}
|
||||
|
||||
// Use higher meta page if valid. Otherwise, fallback to previous, if valid.
|
||||
if err := metaA.validate(); err == nil {
|
||||
if err := metaA.Validate(); err == nil {
|
||||
return metaA
|
||||
} else if err := metaB.validate(); err == nil {
|
||||
} else if err := metaB.Validate(); err == nil {
|
||||
return metaB
|
||||
}
|
||||
|
||||
@@ -1109,7 +1144,7 @@ func (db *DB) meta() *meta {
|
||||
}
|
||||
|
||||
// allocate returns a contiguous block of memory starting at a given page.
|
||||
func (db *DB) allocate(txid txid, count int) (*page, error) {
|
||||
func (db *DB) allocate(txid common.Txid, count int) (*common.Page, error) {
|
||||
// Allocate a temporary buffer for the page.
|
||||
var buf []byte
|
||||
if count == 1 {
|
||||
@@ -1117,17 +1152,18 @@ func (db *DB) allocate(txid txid, count int) (*page, error) {
|
||||
} else {
|
||||
buf = make([]byte, count*db.pageSize)
|
||||
}
|
||||
p := (*page)(unsafe.Pointer(&buf[0]))
|
||||
p.overflow = uint32(count - 1)
|
||||
p := (*common.Page)(unsafe.Pointer(&buf[0]))
|
||||
p.SetOverflow(uint32(count - 1))
|
||||
|
||||
// Use pages from the freelist if they are available.
|
||||
if p.id = db.freelist.allocate(txid, count); p.id != 0 {
|
||||
p.SetId(db.freelist.Allocate(txid, count))
|
||||
if p.Id() != 0 {
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// Resize mmap() if we're at the end.
|
||||
p.id = db.rwtx.meta.pgid
|
||||
var minsz = int((p.id+pgid(count))+1) * db.pageSize
|
||||
p.SetId(db.rwtx.meta.Pgid())
|
||||
var minsz = int((p.Id()+common.Pgid(count))+1) * db.pageSize
|
||||
if minsz >= db.datasz {
|
||||
if err := db.mmap(minsz); err != nil {
|
||||
return nil, fmt.Errorf("mmap allocate error: %s", err)
|
||||
@@ -1135,7 +1171,8 @@ func (db *DB) allocate(txid txid, count int) (*page, error) {
|
||||
}
|
||||
|
||||
// Move the page id high water mark.
|
||||
db.rwtx.meta.pgid += pgid(count)
|
||||
curPgid := db.rwtx.meta.Pgid()
|
||||
db.rwtx.meta.SetPgid(curPgid + common.Pgid(count))
|
||||
|
||||
return p, nil
|
||||
}
|
||||
@@ -1143,7 +1180,13 @@ func (db *DB) allocate(txid txid, count int) (*page, error) {
|
||||
// grow grows the size of the database to the given sz.
|
||||
func (db *DB) grow(sz int) error {
|
||||
// Ignore if the new size is less than available file size.
|
||||
if sz <= db.filesz {
|
||||
lg := db.Logger()
|
||||
fileSize, err := db.fileSize()
|
||||
if err != nil {
|
||||
lg.Errorf("getting file size failed: %w", err)
|
||||
return err
|
||||
}
|
||||
if sz <= fileSize {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1162,21 +1205,22 @@ func (db *DB) grow(sz int) error {
|
||||
// gofail: var resizeFileError string
|
||||
// return errors.New(resizeFileError)
|
||||
if err := db.file.Truncate(int64(sz)); err != nil {
|
||||
lg.Errorf("[GOOS: %s, GOARCH: %s] truncating file failed, size: %d, db.datasz: %d, error: %v", runtime.GOOS, runtime.GOARCH, sz, db.datasz, err)
|
||||
return fmt.Errorf("file resize error: %s", err)
|
||||
}
|
||||
}
|
||||
if err := db.file.Sync(); err != nil {
|
||||
lg.Errorf("[GOOS: %s, GOARCH: %s] syncing file failed, db.datasz: %d, error: %v", runtime.GOOS, runtime.GOARCH, db.datasz, err)
|
||||
return fmt.Errorf("file sync error: %s", err)
|
||||
}
|
||||
if db.Mlock {
|
||||
// unlock old file and lock new one
|
||||
if err := db.mrelock(db.filesz, sz); err != nil {
|
||||
if err := db.mrelock(fileSize, sz); err != nil {
|
||||
return fmt.Errorf("mlock/munlock error: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
db.filesz = sz
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1184,7 +1228,7 @@ func (db *DB) IsReadOnly() bool {
|
||||
return db.readOnly
|
||||
}
|
||||
|
||||
func (db *DB) freepages() []pgid {
|
||||
func (db *DB) freepages() []common.Pgid {
|
||||
tx, err := db.beginTx()
|
||||
defer func() {
|
||||
err = tx.Rollback()
|
||||
@@ -1196,21 +1240,21 @@ func (db *DB) freepages() []pgid {
|
||||
panic("freepages: failed to open read only tx")
|
||||
}
|
||||
|
||||
reachable := make(map[pgid]*page)
|
||||
nofreed := make(map[pgid]bool)
|
||||
reachable := make(map[common.Pgid]*common.Page)
|
||||
nofreed := make(map[common.Pgid]bool)
|
||||
ech := make(chan error)
|
||||
go func() {
|
||||
for e := range ech {
|
||||
panic(fmt.Sprintf("freepages: failed to get all reachable pages (%v)", e))
|
||||
}
|
||||
}()
|
||||
tx.checkBucket(&tx.root, reachable, nofreed, HexKVStringer(), ech)
|
||||
tx.recursivelyCheckBucket(&tx.root, reachable, nofreed, HexKVStringer(), ech)
|
||||
close(ech)
|
||||
|
||||
// TODO: If check bucket reported any corruptions (ech) we shouldn't proceed to freeing the pages.
|
||||
|
||||
var fids []pgid
|
||||
for i := pgid(2); i < db.meta().pgid; i++ {
|
||||
var fids []common.Pgid
|
||||
for i := common.Pgid(2); i < db.meta().Pgid(); i++ {
|
||||
if _, ok := reachable[i]; !ok {
|
||||
fids = append(fids, i)
|
||||
}
|
||||
@@ -1218,11 +1262,17 @@ func (db *DB) freepages() []pgid {
|
||||
return fids
|
||||
}
|
||||
|
||||
func newFreelist(freelistType FreelistType) fl.Interface {
|
||||
if freelistType == FreelistMapType {
|
||||
return fl.NewHashMapFreelist()
|
||||
}
|
||||
return fl.NewArrayFreelist()
|
||||
}
|
||||
|
||||
// Options represents the options that can be set when opening a database.
|
||||
type Options struct {
|
||||
// Timeout is the amount of time to wait to obtain a file lock.
|
||||
// When set to zero it will wait indefinitely. This option is only
|
||||
// available on Darwin and Linux.
|
||||
// When set to zero it will wait indefinitely.
|
||||
Timeout time.Duration
|
||||
|
||||
// Sets the DB.NoGrowSync flag before memory mapping the file.
|
||||
@@ -1277,6 +1327,19 @@ type Options struct {
|
||||
// It prevents potential page faults, however
|
||||
// used memory can't be reclaimed. (UNIX only)
|
||||
Mlock bool
|
||||
|
||||
// Logger is the logger used for bbolt.
|
||||
Logger Logger
|
||||
}
|
||||
|
||||
func (o *Options) String() string {
|
||||
if o == nil {
|
||||
return "{}"
|
||||
}
|
||||
|
||||
return fmt.Sprintf("{Timeout: %s, NoGrowSync: %t, NoFreelistSync: %t, PreLoadFreelist: %t, FreelistType: %s, ReadOnly: %t, MmapFlags: %x, InitialMmapSize: %d, PageSize: %d, NoSync: %t, OpenFile: %p, Mlock: %t, Logger: %p}",
|
||||
o.Timeout, o.NoGrowSync, o.NoFreelistSync, o.PreLoadFreelist, o.FreelistType, o.ReadOnly, o.MmapFlags, o.InitialMmapSize, o.PageSize, o.NoSync, o.OpenFile, o.Mlock, o.Logger)
|
||||
|
||||
}
|
||||
|
||||
// DefaultOptions represent the options used if nil options are passed into Open().
|
||||
@@ -1327,65 +1390,3 @@ type Info struct {
|
||||
Data uintptr
|
||||
PageSize int
|
||||
}
|
||||
|
||||
type meta struct {
|
||||
magic uint32
|
||||
version uint32
|
||||
pageSize uint32
|
||||
flags uint32
|
||||
root bucket
|
||||
freelist pgid
|
||||
pgid pgid
|
||||
txid txid
|
||||
checksum uint64
|
||||
}
|
||||
|
||||
// validate checks the marker bytes and version of the meta page to ensure it matches this binary.
|
||||
func (m *meta) validate() error {
|
||||
if m.magic != magic {
|
||||
return ErrInvalid
|
||||
} else if m.version != version {
|
||||
return ErrVersionMismatch
|
||||
} else if m.checksum != m.sum64() {
|
||||
return ErrChecksum
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// copy copies one meta object to another.
|
||||
func (m *meta) copy(dest *meta) {
|
||||
*dest = *m
|
||||
}
|
||||
|
||||
// write writes the meta onto a page.
|
||||
func (m *meta) write(p *page) {
|
||||
if m.root.root >= m.pgid {
|
||||
panic(fmt.Sprintf("root bucket pgid (%d) above high water mark (%d)", m.root.root, m.pgid))
|
||||
} else if m.freelist >= m.pgid && m.freelist != pgidNoFreelist {
|
||||
// TODO: reject pgidNoFreeList if !NoFreelistSync
|
||||
panic(fmt.Sprintf("freelist pgid (%d) above high water mark (%d)", m.freelist, m.pgid))
|
||||
}
|
||||
|
||||
// Page id is either going to be 0 or 1 which we can determine by the transaction ID.
|
||||
p.id = pgid(m.txid % 2)
|
||||
p.flags |= metaPageFlag
|
||||
|
||||
// Calculate the checksum.
|
||||
m.checksum = m.sum64()
|
||||
|
||||
m.copy(p.meta())
|
||||
}
|
||||
|
||||
// generates the checksum for the meta.
|
||||
func (m *meta) sum64() uint64 {
|
||||
var h = fnv.New64a()
|
||||
_, _ = h.Write((*[unsafe.Offsetof(meta{}.checksum)]byte)(unsafe.Pointer(m))[:])
|
||||
return h.Sum64()
|
||||
}
|
||||
|
||||
// _assert will panic with a given formatted message if the given condition is false.
|
||||
func _assert(condition bool, msg string, v ...interface{}) {
|
||||
if !condition {
|
||||
panic(fmt.Sprintf("assertion failed: "+msg, v...))
|
||||
}
|
||||
}
|
||||
|
||||
+53
-23
@@ -1,78 +1,108 @@
|
||||
package bbolt
|
||||
|
||||
import "errors"
|
||||
import "go.etcd.io/bbolt/errors"
|
||||
|
||||
// These errors can be returned when opening or calling methods on a DB.
|
||||
var (
|
||||
// ErrDatabaseNotOpen is returned when a DB instance is accessed before it
|
||||
// is opened or after it is closed.
|
||||
ErrDatabaseNotOpen = errors.New("database not open")
|
||||
|
||||
// ErrDatabaseOpen is returned when opening a database that is
|
||||
// already open.
|
||||
ErrDatabaseOpen = errors.New("database already open")
|
||||
//
|
||||
// Deprecated: Use the error variables defined in the bbolt/errors package.
|
||||
ErrDatabaseNotOpen = errors.ErrDatabaseNotOpen
|
||||
|
||||
// ErrInvalid is returned when both meta pages on a database are invalid.
|
||||
// This typically occurs when a file is not a bolt database.
|
||||
ErrInvalid = errors.New("invalid database")
|
||||
//
|
||||
// Deprecated: Use the error variables defined in the bbolt/errors package.
|
||||
ErrInvalid = errors.ErrInvalid
|
||||
|
||||
// ErrInvalidMapping is returned when the database file fails to get mapped.
|
||||
ErrInvalidMapping = errors.New("database isn't correctly mapped")
|
||||
//
|
||||
// Deprecated: Use the error variables defined in the bbolt/errors package.
|
||||
ErrInvalidMapping = errors.ErrInvalidMapping
|
||||
|
||||
// ErrVersionMismatch is returned when the data file was created with a
|
||||
// different version of Bolt.
|
||||
ErrVersionMismatch = errors.New("version mismatch")
|
||||
//
|
||||
// Deprecated: Use the error variables defined in the bbolt/errors package.
|
||||
ErrVersionMismatch = errors.ErrVersionMismatch
|
||||
|
||||
// ErrChecksum is returned when either meta page checksum does not match.
|
||||
ErrChecksum = errors.New("checksum error")
|
||||
// ErrChecksum is returned when a checksum mismatch occurs on either of the two meta pages.
|
||||
//
|
||||
// Deprecated: Use the error variables defined in the bbolt/errors package.
|
||||
ErrChecksum = errors.ErrChecksum
|
||||
|
||||
// ErrTimeout is returned when a database cannot obtain an exclusive lock
|
||||
// on the data file after the timeout passed to Open().
|
||||
ErrTimeout = errors.New("timeout")
|
||||
//
|
||||
// Deprecated: Use the error variables defined in the bbolt/errors package.
|
||||
ErrTimeout = errors.ErrTimeout
|
||||
)
|
||||
|
||||
// These errors can occur when beginning or committing a Tx.
|
||||
var (
|
||||
// ErrTxNotWritable is returned when performing a write operation on a
|
||||
// read-only transaction.
|
||||
ErrTxNotWritable = errors.New("tx not writable")
|
||||
//
|
||||
// Deprecated: Use the error variables defined in the bbolt/errors package.
|
||||
ErrTxNotWritable = errors.ErrTxNotWritable
|
||||
|
||||
// ErrTxClosed is returned when committing or rolling back a transaction
|
||||
// that has already been committed or rolled back.
|
||||
ErrTxClosed = errors.New("tx closed")
|
||||
//
|
||||
// Deprecated: Use the error variables defined in the bbolt/errors package.
|
||||
ErrTxClosed = errors.ErrTxClosed
|
||||
|
||||
// ErrDatabaseReadOnly is returned when a mutating transaction is started on a
|
||||
// read-only database.
|
||||
ErrDatabaseReadOnly = errors.New("database is in read-only mode")
|
||||
//
|
||||
// Deprecated: Use the error variables defined in the bbolt/errors package.
|
||||
ErrDatabaseReadOnly = errors.ErrDatabaseReadOnly
|
||||
|
||||
// ErrFreePagesNotLoaded is returned when a readonly transaction without
|
||||
// preloading the free pages is trying to access the free pages.
|
||||
ErrFreePagesNotLoaded = errors.New("free pages are not pre-loaded")
|
||||
//
|
||||
// Deprecated: Use the error variables defined in the bbolt/errors package.
|
||||
ErrFreePagesNotLoaded = errors.ErrFreePagesNotLoaded
|
||||
)
|
||||
|
||||
// These errors can occur when putting or deleting a value or a bucket.
|
||||
var (
|
||||
// ErrBucketNotFound is returned when trying to access a bucket that has
|
||||
// not been created yet.
|
||||
ErrBucketNotFound = errors.New("bucket not found")
|
||||
//
|
||||
// Deprecated: Use the error variables defined in the bbolt/errors package.
|
||||
ErrBucketNotFound = errors.ErrBucketNotFound
|
||||
|
||||
// ErrBucketExists is returned when creating a bucket that already exists.
|
||||
ErrBucketExists = errors.New("bucket already exists")
|
||||
//
|
||||
// Deprecated: Use the error variables defined in the bbolt/errors package.
|
||||
ErrBucketExists = errors.ErrBucketExists
|
||||
|
||||
// ErrBucketNameRequired is returned when creating a bucket with a blank name.
|
||||
ErrBucketNameRequired = errors.New("bucket name required")
|
||||
//
|
||||
// Deprecated: Use the error variables defined in the bbolt/errors package.
|
||||
ErrBucketNameRequired = errors.ErrBucketNameRequired
|
||||
|
||||
// ErrKeyRequired is returned when inserting a zero-length key.
|
||||
ErrKeyRequired = errors.New("key required")
|
||||
//
|
||||
// Deprecated: Use the error variables defined in the bbolt/errors package.
|
||||
ErrKeyRequired = errors.ErrKeyRequired
|
||||
|
||||
// ErrKeyTooLarge is returned when inserting a key that is larger than MaxKeySize.
|
||||
ErrKeyTooLarge = errors.New("key too large")
|
||||
//
|
||||
// Deprecated: Use the error variables defined in the bbolt/errors package.
|
||||
ErrKeyTooLarge = errors.ErrKeyTooLarge
|
||||
|
||||
// ErrValueTooLarge is returned when inserting a value that is larger than MaxValueSize.
|
||||
ErrValueTooLarge = errors.New("value too large")
|
||||
//
|
||||
// Deprecated: Use the error variables defined in the bbolt/errors package.
|
||||
ErrValueTooLarge = errors.ErrValueTooLarge
|
||||
|
||||
// ErrIncompatibleValue is returned when trying create or delete a bucket
|
||||
// on an existing non-bucket key or when trying to create or delete a
|
||||
// non-bucket key on an existing bucket key.
|
||||
ErrIncompatibleValue = errors.New("incompatible value")
|
||||
//
|
||||
// Deprecated: Use the error variables defined in the bbolt/errors package.
|
||||
ErrIncompatibleValue = errors.ErrIncompatibleValue
|
||||
)
|
||||
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
// Package errors defines the error variables that may be returned
|
||||
// during bbolt operations.
|
||||
package errors
|
||||
|
||||
import "errors"
|
||||
|
||||
// These errors can be returned when opening or calling methods on a DB.
|
||||
var (
|
||||
// ErrDatabaseNotOpen is returned when a DB instance is accessed before it
|
||||
// is opened or after it is closed.
|
||||
ErrDatabaseNotOpen = errors.New("database not open")
|
||||
|
||||
// ErrInvalid is returned when both meta pages on a database are invalid.
|
||||
// This typically occurs when a file is not a bolt database.
|
||||
ErrInvalid = errors.New("invalid database")
|
||||
|
||||
// ErrInvalidMapping is returned when the database file fails to get mapped.
|
||||
ErrInvalidMapping = errors.New("database isn't correctly mapped")
|
||||
|
||||
// ErrVersionMismatch is returned when the data file was created with a
|
||||
// different version of Bolt.
|
||||
ErrVersionMismatch = errors.New("version mismatch")
|
||||
|
||||
// ErrChecksum is returned when a checksum mismatch occurs on either of the two meta pages.
|
||||
ErrChecksum = errors.New("checksum error")
|
||||
|
||||
// ErrTimeout is returned when a database cannot obtain an exclusive lock
|
||||
// on the data file after the timeout passed to Open().
|
||||
ErrTimeout = errors.New("timeout")
|
||||
)
|
||||
|
||||
// These errors can occur when beginning or committing a Tx.
|
||||
var (
|
||||
// ErrTxNotWritable is returned when performing a write operation on a
|
||||
// read-only transaction.
|
||||
ErrTxNotWritable = errors.New("tx not writable")
|
||||
|
||||
// ErrTxClosed is returned when committing or rolling back a transaction
|
||||
// that has already been committed or rolled back.
|
||||
ErrTxClosed = errors.New("tx closed")
|
||||
|
||||
// ErrDatabaseReadOnly is returned when a mutating transaction is started on a
|
||||
// read-only database.
|
||||
ErrDatabaseReadOnly = errors.New("database is in read-only mode")
|
||||
|
||||
// ErrFreePagesNotLoaded is returned when a readonly transaction without
|
||||
// preloading the free pages is trying to access the free pages.
|
||||
ErrFreePagesNotLoaded = errors.New("free pages are not pre-loaded")
|
||||
)
|
||||
|
||||
// These errors can occur when putting or deleting a value or a bucket.
|
||||
var (
|
||||
// ErrBucketNotFound is returned when trying to access a bucket that has
|
||||
// not been created yet.
|
||||
ErrBucketNotFound = errors.New("bucket not found")
|
||||
|
||||
// ErrBucketExists is returned when creating a bucket that already exists.
|
||||
ErrBucketExists = errors.New("bucket already exists")
|
||||
|
||||
// ErrBucketNameRequired is returned when creating a bucket with a blank name.
|
||||
ErrBucketNameRequired = errors.New("bucket name required")
|
||||
|
||||
// ErrKeyRequired is returned when inserting a zero-length key.
|
||||
ErrKeyRequired = errors.New("key required")
|
||||
|
||||
// ErrKeyTooLarge is returned when inserting a key that is larger than MaxKeySize.
|
||||
ErrKeyTooLarge = errors.New("key too large")
|
||||
|
||||
// ErrValueTooLarge is returned when inserting a value that is larger than MaxValueSize.
|
||||
ErrValueTooLarge = errors.New("value too large")
|
||||
|
||||
// ErrIncompatibleValue is returned when trying to create or delete a bucket
|
||||
// on an existing non-bucket key or when trying to create or delete a
|
||||
// non-bucket key on an existing bucket key.
|
||||
ErrIncompatibleValue = errors.New("incompatible value")
|
||||
|
||||
// ErrSameBuckets is returned when trying to move a sub-bucket between
|
||||
// source and target buckets, while source and target buckets are the same.
|
||||
ErrSameBuckets = errors.New("the source and target are the same bucket")
|
||||
|
||||
// ErrDifferentDB is returned when trying to move a sub-bucket between
|
||||
// source and target buckets, while source and target buckets are in different database files.
|
||||
ErrDifferentDB = errors.New("the source and target buckets are in different database files")
|
||||
)
|
||||
-410
@@ -1,410 +0,0 @@
|
||||
package bbolt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// txPending holds a list of pgids and corresponding allocation txns
|
||||
// that are pending to be freed.
|
||||
type txPending struct {
|
||||
ids []pgid
|
||||
alloctx []txid // txids allocating the ids
|
||||
lastReleaseBegin txid // beginning txid of last matching releaseRange
|
||||
}
|
||||
|
||||
// pidSet holds the set of starting pgids which have the same span size
|
||||
type pidSet map[pgid]struct{}
|
||||
|
||||
// freelist represents a list of all pages that are available for allocation.
|
||||
// It also tracks pages that have been freed but are still in use by open transactions.
|
||||
type freelist struct {
|
||||
freelistType FreelistType // freelist type
|
||||
ids []pgid // all free and available free page ids.
|
||||
allocs map[pgid]txid // mapping of txid that allocated a pgid.
|
||||
pending map[txid]*txPending // mapping of soon-to-be free page ids by tx.
|
||||
cache map[pgid]struct{} // fast lookup of all free and pending page ids.
|
||||
freemaps map[uint64]pidSet // key is the size of continuous pages(span), value is a set which contains the starting pgids of same size
|
||||
forwardMap map[pgid]uint64 // key is start pgid, value is its span size
|
||||
backwardMap map[pgid]uint64 // key is end pgid, value is its span size
|
||||
allocate func(txid txid, n int) pgid // the freelist allocate func
|
||||
free_count func() int // the function which gives you free page number
|
||||
mergeSpans func(ids pgids) // the mergeSpan func
|
||||
getFreePageIDs func() []pgid // get free pgids func
|
||||
readIDs func(pgids []pgid) // readIDs func reads list of pages and init the freelist
|
||||
}
|
||||
|
||||
// newFreelist returns an empty, initialized freelist.
|
||||
func newFreelist(freelistType FreelistType) *freelist {
|
||||
f := &freelist{
|
||||
freelistType: freelistType,
|
||||
allocs: make(map[pgid]txid),
|
||||
pending: make(map[txid]*txPending),
|
||||
cache: make(map[pgid]struct{}),
|
||||
freemaps: make(map[uint64]pidSet),
|
||||
forwardMap: make(map[pgid]uint64),
|
||||
backwardMap: make(map[pgid]uint64),
|
||||
}
|
||||
|
||||
if freelistType == FreelistMapType {
|
||||
f.allocate = f.hashmapAllocate
|
||||
f.free_count = f.hashmapFreeCount
|
||||
f.mergeSpans = f.hashmapMergeSpans
|
||||
f.getFreePageIDs = f.hashmapGetFreePageIDs
|
||||
f.readIDs = f.hashmapReadIDs
|
||||
} else {
|
||||
f.allocate = f.arrayAllocate
|
||||
f.free_count = f.arrayFreeCount
|
||||
f.mergeSpans = f.arrayMergeSpans
|
||||
f.getFreePageIDs = f.arrayGetFreePageIDs
|
||||
f.readIDs = f.arrayReadIDs
|
||||
}
|
||||
|
||||
return f
|
||||
}
|
||||
|
||||
// size returns the size of the page after serialization.
|
||||
func (f *freelist) size() int {
|
||||
n := f.count()
|
||||
if n >= 0xFFFF {
|
||||
// The first element will be used to store the count. See freelist.write.
|
||||
n++
|
||||
}
|
||||
return int(pageHeaderSize) + (int(unsafe.Sizeof(pgid(0))) * n)
|
||||
}
|
||||
|
||||
// count returns count of pages on the freelist
|
||||
func (f *freelist) count() int {
|
||||
return f.free_count() + f.pending_count()
|
||||
}
|
||||
|
||||
// arrayFreeCount returns count of free pages(array version)
|
||||
func (f *freelist) arrayFreeCount() int {
|
||||
return len(f.ids)
|
||||
}
|
||||
|
||||
// pending_count returns count of pending pages
|
||||
func (f *freelist) pending_count() int {
|
||||
var count int
|
||||
for _, txp := range f.pending {
|
||||
count += len(txp.ids)
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
// copyall copies a list of all free ids and all pending ids in one sorted list.
|
||||
// f.count returns the minimum length required for dst.
|
||||
func (f *freelist) copyall(dst []pgid) {
|
||||
m := make(pgids, 0, f.pending_count())
|
||||
for _, txp := range f.pending {
|
||||
m = append(m, txp.ids...)
|
||||
}
|
||||
sort.Sort(m)
|
||||
mergepgids(dst, f.getFreePageIDs(), m)
|
||||
}
|
||||
|
||||
// arrayAllocate returns the starting page id of a contiguous list of pages of a given size.
|
||||
// If a contiguous block cannot be found then 0 is returned.
|
||||
func (f *freelist) arrayAllocate(txid txid, n int) pgid {
|
||||
if len(f.ids) == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
var initial, previd pgid
|
||||
for i, id := range f.ids {
|
||||
if id <= 1 {
|
||||
panic(fmt.Sprintf("invalid page allocation: %d", id))
|
||||
}
|
||||
|
||||
// Reset initial page if this is not contiguous.
|
||||
if previd == 0 || id-previd != 1 {
|
||||
initial = id
|
||||
}
|
||||
|
||||
// If we found a contiguous block then remove it and return it.
|
||||
if (id-initial)+1 == pgid(n) {
|
||||
// If we're allocating off the beginning then take the fast path
|
||||
// and just adjust the existing slice. This will use extra memory
|
||||
// temporarily but the append() in free() will realloc the slice
|
||||
// as is necessary.
|
||||
if (i + 1) == n {
|
||||
f.ids = f.ids[i+1:]
|
||||
} else {
|
||||
copy(f.ids[i-n+1:], f.ids[i+1:])
|
||||
f.ids = f.ids[:len(f.ids)-n]
|
||||
}
|
||||
|
||||
// Remove from the free cache.
|
||||
for i := pgid(0); i < pgid(n); i++ {
|
||||
delete(f.cache, initial+i)
|
||||
}
|
||||
f.allocs[initial] = txid
|
||||
return initial
|
||||
}
|
||||
|
||||
previd = id
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// free releases a page and its overflow for a given transaction id.
|
||||
// If the page is already free then a panic will occur.
|
||||
func (f *freelist) free(txid txid, p *page) {
|
||||
if p.id <= 1 {
|
||||
panic(fmt.Sprintf("cannot free page 0 or 1: %d", p.id))
|
||||
}
|
||||
|
||||
// Free page and all its overflow pages.
|
||||
txp := f.pending[txid]
|
||||
if txp == nil {
|
||||
txp = &txPending{}
|
||||
f.pending[txid] = txp
|
||||
}
|
||||
allocTxid, ok := f.allocs[p.id]
|
||||
if ok {
|
||||
delete(f.allocs, p.id)
|
||||
} else if (p.flags & freelistPageFlag) != 0 {
|
||||
// Freelist is always allocated by prior tx.
|
||||
allocTxid = txid - 1
|
||||
}
|
||||
|
||||
for id := p.id; id <= p.id+pgid(p.overflow); id++ {
|
||||
// Verify that page is not already free.
|
||||
if _, ok := f.cache[id]; ok {
|
||||
panic(fmt.Sprintf("page %d already freed", id))
|
||||
}
|
||||
// Add to the freelist and cache.
|
||||
txp.ids = append(txp.ids, id)
|
||||
txp.alloctx = append(txp.alloctx, allocTxid)
|
||||
f.cache[id] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
// release moves all page ids for a transaction id (or older) to the freelist.
|
||||
func (f *freelist) release(txid txid) {
|
||||
m := make(pgids, 0)
|
||||
for tid, txp := range f.pending {
|
||||
if tid <= txid {
|
||||
// Move transaction's pending pages to the available freelist.
|
||||
// Don't remove from the cache since the page is still free.
|
||||
m = append(m, txp.ids...)
|
||||
delete(f.pending, tid)
|
||||
}
|
||||
}
|
||||
f.mergeSpans(m)
|
||||
}
|
||||
|
||||
// releaseRange moves pending pages allocated within an extent [begin,end] to the free list.
|
||||
func (f *freelist) releaseRange(begin, end txid) {
|
||||
if begin > end {
|
||||
return
|
||||
}
|
||||
var m pgids
|
||||
for tid, txp := range f.pending {
|
||||
if tid < begin || tid > end {
|
||||
continue
|
||||
}
|
||||
// Don't recompute freed pages if ranges haven't updated.
|
||||
if txp.lastReleaseBegin == begin {
|
||||
continue
|
||||
}
|
||||
for i := 0; i < len(txp.ids); i++ {
|
||||
if atx := txp.alloctx[i]; atx < begin || atx > end {
|
||||
continue
|
||||
}
|
||||
m = append(m, txp.ids[i])
|
||||
txp.ids[i] = txp.ids[len(txp.ids)-1]
|
||||
txp.ids = txp.ids[:len(txp.ids)-1]
|
||||
txp.alloctx[i] = txp.alloctx[len(txp.alloctx)-1]
|
||||
txp.alloctx = txp.alloctx[:len(txp.alloctx)-1]
|
||||
i--
|
||||
}
|
||||
txp.lastReleaseBegin = begin
|
||||
if len(txp.ids) == 0 {
|
||||
delete(f.pending, tid)
|
||||
}
|
||||
}
|
||||
f.mergeSpans(m)
|
||||
}
|
||||
|
||||
// rollback removes the pages from a given pending tx.
|
||||
func (f *freelist) rollback(txid txid) {
|
||||
// Remove page ids from cache.
|
||||
txp := f.pending[txid]
|
||||
if txp == nil {
|
||||
return
|
||||
}
|
||||
var m pgids
|
||||
for i, pgid := range txp.ids {
|
||||
delete(f.cache, pgid)
|
||||
tx := txp.alloctx[i]
|
||||
if tx == 0 {
|
||||
continue
|
||||
}
|
||||
if tx != txid {
|
||||
// Pending free aborted; restore page back to alloc list.
|
||||
f.allocs[pgid] = tx
|
||||
} else {
|
||||
// Freed page was allocated by this txn; OK to throw away.
|
||||
m = append(m, pgid)
|
||||
}
|
||||
}
|
||||
// Remove pages from pending list and mark as free if allocated by txid.
|
||||
delete(f.pending, txid)
|
||||
|
||||
// Remove pgids which are allocated by this txid
|
||||
for pgid, tid := range f.allocs {
|
||||
if tid == txid {
|
||||
delete(f.allocs, pgid)
|
||||
}
|
||||
}
|
||||
|
||||
f.mergeSpans(m)
|
||||
}
|
||||
|
||||
// freed returns whether a given page is in the free list.
|
||||
func (f *freelist) freed(pgId pgid) bool {
|
||||
_, ok := f.cache[pgId]
|
||||
return ok
|
||||
}
|
||||
|
||||
// read initializes the freelist from a freelist page.
|
||||
func (f *freelist) read(p *page) {
|
||||
if (p.flags & freelistPageFlag) == 0 {
|
||||
panic(fmt.Sprintf("invalid freelist page: %d, page type is %s", p.id, p.typ()))
|
||||
}
|
||||
// If the page.count is at the max uint16 value (64k) then it's considered
|
||||
// an overflow and the size of the freelist is stored as the first element.
|
||||
var idx, count = 0, int(p.count)
|
||||
if count == 0xFFFF {
|
||||
idx = 1
|
||||
c := *(*pgid)(unsafeAdd(unsafe.Pointer(p), unsafe.Sizeof(*p)))
|
||||
count = int(c)
|
||||
if count < 0 {
|
||||
panic(fmt.Sprintf("leading element count %d overflows int", c))
|
||||
}
|
||||
}
|
||||
|
||||
// Copy the list of page ids from the freelist.
|
||||
if count == 0 {
|
||||
f.ids = nil
|
||||
} else {
|
||||
data := unsafeIndex(unsafe.Pointer(p), unsafe.Sizeof(*p), unsafe.Sizeof(pgid(0)), idx)
|
||||
ids := unsafe.Slice((*pgid)(data), count)
|
||||
|
||||
// copy the ids, so we don't modify on the freelist page directly
|
||||
idsCopy := make([]pgid, count)
|
||||
copy(idsCopy, ids)
|
||||
// Make sure they're sorted.
|
||||
sort.Sort(pgids(idsCopy))
|
||||
|
||||
f.readIDs(idsCopy)
|
||||
}
|
||||
}
|
||||
|
||||
// arrayReadIDs initializes the freelist from a given list of ids.
|
||||
func (f *freelist) arrayReadIDs(ids []pgid) {
|
||||
f.ids = ids
|
||||
f.reindex()
|
||||
}
|
||||
|
||||
func (f *freelist) arrayGetFreePageIDs() []pgid {
|
||||
return f.ids
|
||||
}
|
||||
|
||||
// write writes the page ids onto a freelist page. All free and pending ids are
|
||||
// saved to disk since in the event of a program crash, all pending ids will
|
||||
// become free.
|
||||
func (f *freelist) write(p *page) error {
|
||||
// Combine the old free pgids and pgids waiting on an open transaction.
|
||||
|
||||
// Update the header flag.
|
||||
p.flags |= freelistPageFlag
|
||||
|
||||
// The page.count can only hold up to 64k elements so if we overflow that
|
||||
// number then we handle it by putting the size in the first element.
|
||||
l := f.count()
|
||||
if l == 0 {
|
||||
p.count = uint16(l)
|
||||
} else if l < 0xFFFF {
|
||||
p.count = uint16(l)
|
||||
data := unsafeAdd(unsafe.Pointer(p), unsafe.Sizeof(*p))
|
||||
ids := unsafe.Slice((*pgid)(data), l)
|
||||
f.copyall(ids)
|
||||
} else {
|
||||
p.count = 0xFFFF
|
||||
data := unsafeAdd(unsafe.Pointer(p), unsafe.Sizeof(*p))
|
||||
ids := unsafe.Slice((*pgid)(data), l+1)
|
||||
ids[0] = pgid(l)
|
||||
f.copyall(ids[1:])
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// reload reads the freelist from a page and filters out pending items.
|
||||
func (f *freelist) reload(p *page) {
|
||||
f.read(p)
|
||||
|
||||
// Build a cache of only pending pages.
|
||||
pcache := make(map[pgid]bool)
|
||||
for _, txp := range f.pending {
|
||||
for _, pendingID := range txp.ids {
|
||||
pcache[pendingID] = true
|
||||
}
|
||||
}
|
||||
|
||||
// Check each page in the freelist and build a new available freelist
|
||||
// with any pages not in the pending lists.
|
||||
var a []pgid
|
||||
for _, id := range f.getFreePageIDs() {
|
||||
if !pcache[id] {
|
||||
a = append(a, id)
|
||||
}
|
||||
}
|
||||
|
||||
f.readIDs(a)
|
||||
}
|
||||
|
||||
// noSyncReload reads the freelist from pgids and filters out pending items.
|
||||
func (f *freelist) noSyncReload(pgids []pgid) {
|
||||
// Build a cache of only pending pages.
|
||||
pcache := make(map[pgid]bool)
|
||||
for _, txp := range f.pending {
|
||||
for _, pendingID := range txp.ids {
|
||||
pcache[pendingID] = true
|
||||
}
|
||||
}
|
||||
|
||||
// Check each page in the freelist and build a new available freelist
|
||||
// with any pages not in the pending lists.
|
||||
var a []pgid
|
||||
for _, id := range pgids {
|
||||
if !pcache[id] {
|
||||
a = append(a, id)
|
||||
}
|
||||
}
|
||||
|
||||
f.readIDs(a)
|
||||
}
|
||||
|
||||
// reindex rebuilds the free cache based on available and pending free lists.
|
||||
func (f *freelist) reindex() {
|
||||
ids := f.getFreePageIDs()
|
||||
f.cache = make(map[pgid]struct{}, len(ids))
|
||||
for _, id := range ids {
|
||||
f.cache[id] = struct{}{}
|
||||
}
|
||||
for _, txp := range f.pending {
|
||||
for _, pendingID := range txp.ids {
|
||||
f.cache[pendingID] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// arrayMergeSpans try to merge list of pages(represented by pgids) with existing spans but using array
|
||||
func (f *freelist) arrayMergeSpans(ids pgids) {
|
||||
sort.Sort(ids)
|
||||
f.ids = pgids(f.ids).merge(ids)
|
||||
}
|
||||
-178
@@ -1,178 +0,0 @@
|
||||
package bbolt
|
||||
|
||||
import "sort"
|
||||
|
||||
// hashmapFreeCount returns count of free pages(hashmap version)
|
||||
func (f *freelist) hashmapFreeCount() int {
|
||||
// use the forwardMap to get the total count
|
||||
count := 0
|
||||
for _, size := range f.forwardMap {
|
||||
count += int(size)
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
// hashmapAllocate serves the same purpose as arrayAllocate, but use hashmap as backend
|
||||
func (f *freelist) hashmapAllocate(txid txid, n int) pgid {
|
||||
if n == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// if we have a exact size match just return short path
|
||||
if bm, ok := f.freemaps[uint64(n)]; ok {
|
||||
for pid := range bm {
|
||||
// remove the span
|
||||
f.delSpan(pid, uint64(n))
|
||||
|
||||
f.allocs[pid] = txid
|
||||
|
||||
for i := pgid(0); i < pgid(n); i++ {
|
||||
delete(f.cache, pid+i)
|
||||
}
|
||||
return pid
|
||||
}
|
||||
}
|
||||
|
||||
// lookup the map to find larger span
|
||||
for size, bm := range f.freemaps {
|
||||
if size < uint64(n) {
|
||||
continue
|
||||
}
|
||||
|
||||
for pid := range bm {
|
||||
// remove the initial
|
||||
f.delSpan(pid, size)
|
||||
|
||||
f.allocs[pid] = txid
|
||||
|
||||
remain := size - uint64(n)
|
||||
|
||||
// add remain span
|
||||
f.addSpan(pid+pgid(n), remain)
|
||||
|
||||
for i := pgid(0); i < pgid(n); i++ {
|
||||
delete(f.cache, pid+i)
|
||||
}
|
||||
return pid
|
||||
}
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
// hashmapReadIDs reads pgids as input an initial the freelist(hashmap version)
|
||||
func (f *freelist) hashmapReadIDs(pgids []pgid) {
|
||||
f.init(pgids)
|
||||
|
||||
// Rebuild the page cache.
|
||||
f.reindex()
|
||||
}
|
||||
|
||||
// hashmapGetFreePageIDs returns the sorted free page ids
|
||||
func (f *freelist) hashmapGetFreePageIDs() []pgid {
|
||||
count := f.free_count()
|
||||
if count == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
m := make([]pgid, 0, count)
|
||||
for start, size := range f.forwardMap {
|
||||
for i := 0; i < int(size); i++ {
|
||||
m = append(m, start+pgid(i))
|
||||
}
|
||||
}
|
||||
sort.Sort(pgids(m))
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
// hashmapMergeSpans try to merge list of pages(represented by pgids) with existing spans
|
||||
func (f *freelist) hashmapMergeSpans(ids pgids) {
|
||||
for _, id := range ids {
|
||||
// try to see if we can merge and update
|
||||
f.mergeWithExistingSpan(id)
|
||||
}
|
||||
}
|
||||
|
||||
// mergeWithExistingSpan merges pid to the existing free spans, try to merge it backward and forward
|
||||
func (f *freelist) mergeWithExistingSpan(pid pgid) {
|
||||
prev := pid - 1
|
||||
next := pid + 1
|
||||
|
||||
preSize, mergeWithPrev := f.backwardMap[prev]
|
||||
nextSize, mergeWithNext := f.forwardMap[next]
|
||||
newStart := pid
|
||||
newSize := uint64(1)
|
||||
|
||||
if mergeWithPrev {
|
||||
//merge with previous span
|
||||
start := prev + 1 - pgid(preSize)
|
||||
f.delSpan(start, preSize)
|
||||
|
||||
newStart -= pgid(preSize)
|
||||
newSize += preSize
|
||||
}
|
||||
|
||||
if mergeWithNext {
|
||||
// merge with next span
|
||||
f.delSpan(next, nextSize)
|
||||
newSize += nextSize
|
||||
}
|
||||
|
||||
f.addSpan(newStart, newSize)
|
||||
}
|
||||
|
||||
func (f *freelist) addSpan(start pgid, size uint64) {
|
||||
f.backwardMap[start-1+pgid(size)] = size
|
||||
f.forwardMap[start] = size
|
||||
if _, ok := f.freemaps[size]; !ok {
|
||||
f.freemaps[size] = make(map[pgid]struct{})
|
||||
}
|
||||
|
||||
f.freemaps[size][start] = struct{}{}
|
||||
}
|
||||
|
||||
func (f *freelist) delSpan(start pgid, size uint64) {
|
||||
delete(f.forwardMap, start)
|
||||
delete(f.backwardMap, start+pgid(size-1))
|
||||
delete(f.freemaps[size], start)
|
||||
if len(f.freemaps[size]) == 0 {
|
||||
delete(f.freemaps, size)
|
||||
}
|
||||
}
|
||||
|
||||
// initial from pgids using when use hashmap version
|
||||
// pgids must be sorted
|
||||
func (f *freelist) init(pgids []pgid) {
|
||||
if len(pgids) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
size := uint64(1)
|
||||
start := pgids[0]
|
||||
|
||||
if !sort.SliceIsSorted([]pgid(pgids), func(i, j int) bool { return pgids[i] < pgids[j] }) {
|
||||
panic("pgids not sorted")
|
||||
}
|
||||
|
||||
f.freemaps = make(map[uint64]pidSet)
|
||||
f.forwardMap = make(map[pgid]uint64)
|
||||
f.backwardMap = make(map[pgid]uint64)
|
||||
|
||||
for i := 1; i < len(pgids); i++ {
|
||||
// continuous page
|
||||
if pgids[i] == pgids[i-1]+1 {
|
||||
size++
|
||||
} else {
|
||||
f.addSpan(start, size)
|
||||
|
||||
size = 1
|
||||
start = pgids[i]
|
||||
}
|
||||
}
|
||||
|
||||
// init the tail
|
||||
if size != 0 && start != 0 {
|
||||
f.addSpan(start, size)
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const BucketHeaderSize = int(unsafe.Sizeof(InBucket{}))
|
||||
|
||||
// InBucket represents the on-file representation of a bucket.
|
||||
// This is stored as the "value" of a bucket key. If the bucket is small enough,
|
||||
// then its root page can be stored inline in the "value", after the bucket
|
||||
// header. In the case of inline buckets, the "root" will be 0.
|
||||
type InBucket struct {
|
||||
root Pgid // page id of the bucket's root-level page
|
||||
sequence uint64 // monotonically incrementing, used by NextSequence()
|
||||
}
|
||||
|
||||
func NewInBucket(root Pgid, seq uint64) InBucket {
|
||||
return InBucket{
|
||||
root: root,
|
||||
sequence: seq,
|
||||
}
|
||||
}
|
||||
|
||||
func (b *InBucket) RootPage() Pgid {
|
||||
return b.root
|
||||
}
|
||||
|
||||
func (b *InBucket) SetRootPage(id Pgid) {
|
||||
b.root = id
|
||||
}
|
||||
|
||||
// InSequence returns the sequence. The reason why not naming it `Sequence`
|
||||
// is to avoid duplicated name as `(*Bucket) Sequence()`
|
||||
func (b *InBucket) InSequence() uint64 {
|
||||
return b.sequence
|
||||
}
|
||||
|
||||
func (b *InBucket) SetInSequence(v uint64) {
|
||||
b.sequence = v
|
||||
}
|
||||
|
||||
func (b *InBucket) IncSequence() {
|
||||
b.sequence++
|
||||
}
|
||||
|
||||
func (b *InBucket) InlinePage(v []byte) *Page {
|
||||
return (*Page)(unsafe.Pointer(&v[BucketHeaderSize]))
|
||||
}
|
||||
|
||||
func (b *InBucket) String() string {
|
||||
return fmt.Sprintf("<pgid=%d,seq=%d>", b.root, b.sequence)
|
||||
}
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
package common
|
||||
|
||||
import "unsafe"
|
||||
|
||||
// Inode represents an internal node inside of a node.
|
||||
// It can be used to point to elements in a page or point
|
||||
// to an element which hasn't been added to a page yet.
|
||||
type Inode struct {
|
||||
flags uint32
|
||||
pgid Pgid
|
||||
key []byte
|
||||
value []byte
|
||||
}
|
||||
|
||||
type Inodes []Inode
|
||||
|
||||
func (in *Inode) Flags() uint32 {
|
||||
return in.flags
|
||||
}
|
||||
|
||||
func (in *Inode) SetFlags(flags uint32) {
|
||||
in.flags = flags
|
||||
}
|
||||
|
||||
func (in *Inode) Pgid() Pgid {
|
||||
return in.pgid
|
||||
}
|
||||
|
||||
func (in *Inode) SetPgid(id Pgid) {
|
||||
in.pgid = id
|
||||
}
|
||||
|
||||
func (in *Inode) Key() []byte {
|
||||
return in.key
|
||||
}
|
||||
|
||||
func (in *Inode) SetKey(key []byte) {
|
||||
in.key = key
|
||||
}
|
||||
|
||||
func (in *Inode) Value() []byte {
|
||||
return in.value
|
||||
}
|
||||
|
||||
func (in *Inode) SetValue(value []byte) {
|
||||
in.value = value
|
||||
}
|
||||
|
||||
func ReadInodeFromPage(p *Page) Inodes {
|
||||
inodes := make(Inodes, int(p.Count()))
|
||||
isLeaf := p.IsLeafPage()
|
||||
for i := 0; i < int(p.Count()); i++ {
|
||||
inode := &inodes[i]
|
||||
if isLeaf {
|
||||
elem := p.LeafPageElement(uint16(i))
|
||||
inode.SetFlags(elem.Flags())
|
||||
inode.SetKey(elem.Key())
|
||||
inode.SetValue(elem.Value())
|
||||
} else {
|
||||
elem := p.BranchPageElement(uint16(i))
|
||||
inode.SetPgid(elem.Pgid())
|
||||
inode.SetKey(elem.Key())
|
||||
}
|
||||
Assert(len(inode.Key()) > 0, "read: zero-length inode key")
|
||||
}
|
||||
|
||||
return inodes
|
||||
}
|
||||
|
||||
func WriteInodeToPage(inodes Inodes, p *Page) uint32 {
|
||||
// Loop over each item and write it to the page.
|
||||
// off tracks the offset into p of the start of the next data.
|
||||
off := unsafe.Sizeof(*p) + p.PageElementSize()*uintptr(len(inodes))
|
||||
isLeaf := p.IsLeafPage()
|
||||
for i, item := range inodes {
|
||||
Assert(len(item.Key()) > 0, "write: zero-length inode key")
|
||||
|
||||
// Create a slice to write into of needed size and advance
|
||||
// byte pointer for next iteration.
|
||||
sz := len(item.Key()) + len(item.Value())
|
||||
b := UnsafeByteSlice(unsafe.Pointer(p), off, 0, sz)
|
||||
off += uintptr(sz)
|
||||
|
||||
// Write the page element.
|
||||
if isLeaf {
|
||||
elem := p.LeafPageElement(uint16(i))
|
||||
elem.SetPos(uint32(uintptr(unsafe.Pointer(&b[0])) - uintptr(unsafe.Pointer(elem))))
|
||||
elem.SetFlags(item.Flags())
|
||||
elem.SetKsize(uint32(len(item.Key())))
|
||||
elem.SetVsize(uint32(len(item.Value())))
|
||||
} else {
|
||||
elem := p.BranchPageElement(uint16(i))
|
||||
elem.SetPos(uint32(uintptr(unsafe.Pointer(&b[0])) - uintptr(unsafe.Pointer(elem))))
|
||||
elem.SetKsize(uint32(len(item.Key())))
|
||||
elem.SetPgid(item.Pgid())
|
||||
Assert(elem.Pgid() != p.Id(), "write: circular dependency occurred")
|
||||
}
|
||||
|
||||
// Write data for the element to the end of the page.
|
||||
l := copy(b, item.Key())
|
||||
copy(b[l:], item.Value())
|
||||
}
|
||||
|
||||
return uint32(off)
|
||||
}
|
||||
|
||||
func UsedSpaceInPage(inodes Inodes, p *Page) uint32 {
|
||||
off := unsafe.Sizeof(*p) + p.PageElementSize()*uintptr(len(inodes))
|
||||
for _, item := range inodes {
|
||||
sz := len(item.Key()) + len(item.Value())
|
||||
off += uintptr(sz)
|
||||
}
|
||||
|
||||
return uint32(off)
|
||||
}
|
||||
+161
@@ -0,0 +1,161 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"io"
|
||||
"unsafe"
|
||||
|
||||
"go.etcd.io/bbolt/errors"
|
||||
)
|
||||
|
||||
type Meta struct {
|
||||
magic uint32
|
||||
version uint32
|
||||
pageSize uint32
|
||||
flags uint32
|
||||
root InBucket
|
||||
freelist Pgid
|
||||
pgid Pgid
|
||||
txid Txid
|
||||
checksum uint64
|
||||
}
|
||||
|
||||
// Validate checks the marker bytes and version of the meta page to ensure it matches this binary.
|
||||
func (m *Meta) Validate() error {
|
||||
if m.magic != Magic {
|
||||
return errors.ErrInvalid
|
||||
} else if m.version != Version {
|
||||
return errors.ErrVersionMismatch
|
||||
} else if m.checksum != m.Sum64() {
|
||||
return errors.ErrChecksum
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Copy copies one meta object to another.
|
||||
func (m *Meta) Copy(dest *Meta) {
|
||||
*dest = *m
|
||||
}
|
||||
|
||||
// Write writes the meta onto a page.
|
||||
func (m *Meta) Write(p *Page) {
|
||||
if m.root.root >= m.pgid {
|
||||
panic(fmt.Sprintf("root bucket pgid (%d) above high water mark (%d)", m.root.root, m.pgid))
|
||||
} else if m.freelist >= m.pgid && m.freelist != PgidNoFreelist {
|
||||
// TODO: reject pgidNoFreeList if !NoFreelistSync
|
||||
panic(fmt.Sprintf("freelist pgid (%d) above high water mark (%d)", m.freelist, m.pgid))
|
||||
}
|
||||
|
||||
// Page id is either going to be 0 or 1 which we can determine by the transaction ID.
|
||||
p.id = Pgid(m.txid % 2)
|
||||
p.SetFlags(MetaPageFlag)
|
||||
|
||||
// Calculate the checksum.
|
||||
m.checksum = m.Sum64()
|
||||
|
||||
m.Copy(p.Meta())
|
||||
}
|
||||
|
||||
// Sum64 generates the checksum for the meta.
|
||||
func (m *Meta) Sum64() uint64 {
|
||||
var h = fnv.New64a()
|
||||
_, _ = h.Write((*[unsafe.Offsetof(Meta{}.checksum)]byte)(unsafe.Pointer(m))[:])
|
||||
return h.Sum64()
|
||||
}
|
||||
|
||||
func (m *Meta) Magic() uint32 {
|
||||
return m.magic
|
||||
}
|
||||
|
||||
func (m *Meta) SetMagic(v uint32) {
|
||||
m.magic = v
|
||||
}
|
||||
|
||||
func (m *Meta) Version() uint32 {
|
||||
return m.version
|
||||
}
|
||||
|
||||
func (m *Meta) SetVersion(v uint32) {
|
||||
m.version = v
|
||||
}
|
||||
|
||||
func (m *Meta) PageSize() uint32 {
|
||||
return m.pageSize
|
||||
}
|
||||
|
||||
func (m *Meta) SetPageSize(v uint32) {
|
||||
m.pageSize = v
|
||||
}
|
||||
|
||||
func (m *Meta) Flags() uint32 {
|
||||
return m.flags
|
||||
}
|
||||
|
||||
func (m *Meta) SetFlags(v uint32) {
|
||||
m.flags = v
|
||||
}
|
||||
|
||||
func (m *Meta) SetRootBucket(b InBucket) {
|
||||
m.root = b
|
||||
}
|
||||
|
||||
func (m *Meta) RootBucket() *InBucket {
|
||||
return &m.root
|
||||
}
|
||||
|
||||
func (m *Meta) Freelist() Pgid {
|
||||
return m.freelist
|
||||
}
|
||||
|
||||
func (m *Meta) SetFreelist(v Pgid) {
|
||||
m.freelist = v
|
||||
}
|
||||
|
||||
func (m *Meta) IsFreelistPersisted() bool {
|
||||
return m.freelist != PgidNoFreelist
|
||||
}
|
||||
|
||||
func (m *Meta) Pgid() Pgid {
|
||||
return m.pgid
|
||||
}
|
||||
|
||||
func (m *Meta) SetPgid(id Pgid) {
|
||||
m.pgid = id
|
||||
}
|
||||
|
||||
func (m *Meta) Txid() Txid {
|
||||
return m.txid
|
||||
}
|
||||
|
||||
func (m *Meta) SetTxid(id Txid) {
|
||||
m.txid = id
|
||||
}
|
||||
|
||||
func (m *Meta) IncTxid() {
|
||||
m.txid += 1
|
||||
}
|
||||
|
||||
func (m *Meta) DecTxid() {
|
||||
m.txid -= 1
|
||||
}
|
||||
|
||||
func (m *Meta) Checksum() uint64 {
|
||||
return m.checksum
|
||||
}
|
||||
|
||||
func (m *Meta) SetChecksum(v uint64) {
|
||||
m.checksum = v
|
||||
}
|
||||
|
||||
func (m *Meta) Print(w io.Writer) {
|
||||
fmt.Fprintf(w, "Version: %d\n", m.version)
|
||||
fmt.Fprintf(w, "Page Size: %d bytes\n", m.pageSize)
|
||||
fmt.Fprintf(w, "Flags: %08x\n", m.flags)
|
||||
fmt.Fprintf(w, "Root: <pgid=%d>\n", m.root.root)
|
||||
fmt.Fprintf(w, "Freelist: <pgid=%d>\n", m.freelist)
|
||||
fmt.Fprintf(w, "HWM: <pgid=%d>\n", m.pgid)
|
||||
fmt.Fprintf(w, "Txn ID: %d\n", m.txid)
|
||||
fmt.Fprintf(w, "Checksum: %016x\n", m.checksum)
|
||||
fmt.Fprintf(w, "\n")
|
||||
}
|
||||
+391
@@ -0,0 +1,391 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const PageHeaderSize = unsafe.Sizeof(Page{})
|
||||
|
||||
const MinKeysPerPage = 2
|
||||
|
||||
const BranchPageElementSize = unsafe.Sizeof(branchPageElement{})
|
||||
const LeafPageElementSize = unsafe.Sizeof(leafPageElement{})
|
||||
const pgidSize = unsafe.Sizeof(Pgid(0))
|
||||
|
||||
const (
|
||||
BranchPageFlag = 0x01
|
||||
LeafPageFlag = 0x02
|
||||
MetaPageFlag = 0x04
|
||||
FreelistPageFlag = 0x10
|
||||
)
|
||||
|
||||
const (
|
||||
BucketLeafFlag = 0x01
|
||||
)
|
||||
|
||||
type Pgid uint64
|
||||
|
||||
type Page struct {
|
||||
id Pgid
|
||||
flags uint16
|
||||
count uint16
|
||||
overflow uint32
|
||||
}
|
||||
|
||||
func NewPage(id Pgid, flags, count uint16, overflow uint32) *Page {
|
||||
return &Page{
|
||||
id: id,
|
||||
flags: flags,
|
||||
count: count,
|
||||
overflow: overflow,
|
||||
}
|
||||
}
|
||||
|
||||
// Typ returns a human-readable page type string used for debugging.
|
||||
func (p *Page) Typ() string {
|
||||
if p.IsBranchPage() {
|
||||
return "branch"
|
||||
} else if p.IsLeafPage() {
|
||||
return "leaf"
|
||||
} else if p.IsMetaPage() {
|
||||
return "meta"
|
||||
} else if p.IsFreelistPage() {
|
||||
return "freelist"
|
||||
}
|
||||
return fmt.Sprintf("unknown<%02x>", p.flags)
|
||||
}
|
||||
|
||||
func (p *Page) IsBranchPage() bool {
|
||||
return p.flags == BranchPageFlag
|
||||
}
|
||||
|
||||
func (p *Page) IsLeafPage() bool {
|
||||
return p.flags == LeafPageFlag
|
||||
}
|
||||
|
||||
func (p *Page) IsMetaPage() bool {
|
||||
return p.flags == MetaPageFlag
|
||||
}
|
||||
|
||||
func (p *Page) IsFreelistPage() bool {
|
||||
return p.flags == FreelistPageFlag
|
||||
}
|
||||
|
||||
// Meta returns a pointer to the metadata section of the page.
|
||||
func (p *Page) Meta() *Meta {
|
||||
return (*Meta)(UnsafeAdd(unsafe.Pointer(p), unsafe.Sizeof(*p)))
|
||||
}
|
||||
|
||||
func (p *Page) FastCheck(id Pgid) {
|
||||
Assert(p.id == id, "Page expected to be: %v, but self identifies as %v", id, p.id)
|
||||
// Only one flag of page-type can be set.
|
||||
Assert(p.IsBranchPage() ||
|
||||
p.IsLeafPage() ||
|
||||
p.IsMetaPage() ||
|
||||
p.IsFreelistPage(),
|
||||
"page %v: has unexpected type/flags: %x", p.id, p.flags)
|
||||
}
|
||||
|
||||
// LeafPageElement retrieves the leaf node by index
|
||||
func (p *Page) LeafPageElement(index uint16) *leafPageElement {
|
||||
return (*leafPageElement)(UnsafeIndex(unsafe.Pointer(p), unsafe.Sizeof(*p),
|
||||
LeafPageElementSize, int(index)))
|
||||
}
|
||||
|
||||
// LeafPageElements retrieves a list of leaf nodes.
|
||||
func (p *Page) LeafPageElements() []leafPageElement {
|
||||
if p.count == 0 {
|
||||
return nil
|
||||
}
|
||||
data := UnsafeAdd(unsafe.Pointer(p), unsafe.Sizeof(*p))
|
||||
elems := unsafe.Slice((*leafPageElement)(data), int(p.count))
|
||||
return elems
|
||||
}
|
||||
|
||||
// BranchPageElement retrieves the branch node by index
|
||||
func (p *Page) BranchPageElement(index uint16) *branchPageElement {
|
||||
return (*branchPageElement)(UnsafeIndex(unsafe.Pointer(p), unsafe.Sizeof(*p),
|
||||
unsafe.Sizeof(branchPageElement{}), int(index)))
|
||||
}
|
||||
|
||||
// BranchPageElements retrieves a list of branch nodes.
|
||||
func (p *Page) BranchPageElements() []branchPageElement {
|
||||
if p.count == 0 {
|
||||
return nil
|
||||
}
|
||||
data := UnsafeAdd(unsafe.Pointer(p), unsafe.Sizeof(*p))
|
||||
elems := unsafe.Slice((*branchPageElement)(data), int(p.count))
|
||||
return elems
|
||||
}
|
||||
|
||||
func (p *Page) FreelistPageCount() (int, int) {
|
||||
Assert(p.IsFreelistPage(), fmt.Sprintf("can't get freelist page count from a non-freelist page: %2x", p.flags))
|
||||
|
||||
// If the page.count is at the max uint16 value (64k) then it's considered
|
||||
// an overflow and the size of the freelist is stored as the first element.
|
||||
var idx, count = 0, int(p.count)
|
||||
if count == 0xFFFF {
|
||||
idx = 1
|
||||
c := *(*Pgid)(UnsafeAdd(unsafe.Pointer(p), unsafe.Sizeof(*p)))
|
||||
count = int(c)
|
||||
if count < 0 {
|
||||
panic(fmt.Sprintf("leading element count %d overflows int", c))
|
||||
}
|
||||
}
|
||||
|
||||
return idx, count
|
||||
}
|
||||
|
||||
func (p *Page) FreelistPageIds() []Pgid {
|
||||
Assert(p.IsFreelistPage(), fmt.Sprintf("can't get freelist page IDs from a non-freelist page: %2x", p.flags))
|
||||
|
||||
idx, count := p.FreelistPageCount()
|
||||
|
||||
if count == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
data := UnsafeIndex(unsafe.Pointer(p), unsafe.Sizeof(*p), pgidSize, idx)
|
||||
ids := unsafe.Slice((*Pgid)(data), count)
|
||||
|
||||
return ids
|
||||
}
|
||||
|
||||
// dump writes n bytes of the page to STDERR as hex output.
|
||||
func (p *Page) hexdump(n int) {
|
||||
buf := UnsafeByteSlice(unsafe.Pointer(p), 0, 0, n)
|
||||
fmt.Fprintf(os.Stderr, "%x\n", buf)
|
||||
}
|
||||
|
||||
func (p *Page) PageElementSize() uintptr {
|
||||
if p.IsLeafPage() {
|
||||
return LeafPageElementSize
|
||||
}
|
||||
return BranchPageElementSize
|
||||
}
|
||||
|
||||
func (p *Page) Id() Pgid {
|
||||
return p.id
|
||||
}
|
||||
|
||||
func (p *Page) SetId(target Pgid) {
|
||||
p.id = target
|
||||
}
|
||||
|
||||
func (p *Page) Flags() uint16 {
|
||||
return p.flags
|
||||
}
|
||||
|
||||
func (p *Page) SetFlags(v uint16) {
|
||||
p.flags = v
|
||||
}
|
||||
|
||||
func (p *Page) Count() uint16 {
|
||||
return p.count
|
||||
}
|
||||
|
||||
func (p *Page) SetCount(target uint16) {
|
||||
p.count = target
|
||||
}
|
||||
|
||||
func (p *Page) Overflow() uint32 {
|
||||
return p.overflow
|
||||
}
|
||||
|
||||
func (p *Page) SetOverflow(target uint32) {
|
||||
p.overflow = target
|
||||
}
|
||||
|
||||
func (p *Page) String() string {
|
||||
return fmt.Sprintf("ID: %d, Type: %s, count: %d, overflow: %d", p.id, p.Typ(), p.count, p.overflow)
|
||||
}
|
||||
|
||||
type Pages []*Page
|
||||
|
||||
func (s Pages) Len() int { return len(s) }
|
||||
func (s Pages) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||
func (s Pages) Less(i, j int) bool { return s[i].id < s[j].id }
|
||||
|
||||
// branchPageElement represents a node on a branch page.
|
||||
type branchPageElement struct {
|
||||
pos uint32
|
||||
ksize uint32
|
||||
pgid Pgid
|
||||
}
|
||||
|
||||
func (n *branchPageElement) Pos() uint32 {
|
||||
return n.pos
|
||||
}
|
||||
|
||||
func (n *branchPageElement) SetPos(v uint32) {
|
||||
n.pos = v
|
||||
}
|
||||
|
||||
func (n *branchPageElement) Ksize() uint32 {
|
||||
return n.ksize
|
||||
}
|
||||
|
||||
func (n *branchPageElement) SetKsize(v uint32) {
|
||||
n.ksize = v
|
||||
}
|
||||
|
||||
func (n *branchPageElement) Pgid() Pgid {
|
||||
return n.pgid
|
||||
}
|
||||
|
||||
func (n *branchPageElement) SetPgid(v Pgid) {
|
||||
n.pgid = v
|
||||
}
|
||||
|
||||
// Key returns a byte slice of the node key.
|
||||
func (n *branchPageElement) Key() []byte {
|
||||
return UnsafeByteSlice(unsafe.Pointer(n), 0, int(n.pos), int(n.pos)+int(n.ksize))
|
||||
}
|
||||
|
||||
// leafPageElement represents a node on a leaf page.
|
||||
type leafPageElement struct {
|
||||
flags uint32
|
||||
pos uint32
|
||||
ksize uint32
|
||||
vsize uint32
|
||||
}
|
||||
|
||||
func NewLeafPageElement(flags, pos, ksize, vsize uint32) *leafPageElement {
|
||||
return &leafPageElement{
|
||||
flags: flags,
|
||||
pos: pos,
|
||||
ksize: ksize,
|
||||
vsize: vsize,
|
||||
}
|
||||
}
|
||||
|
||||
func (n *leafPageElement) Flags() uint32 {
|
||||
return n.flags
|
||||
}
|
||||
|
||||
func (n *leafPageElement) SetFlags(v uint32) {
|
||||
n.flags = v
|
||||
}
|
||||
|
||||
func (n *leafPageElement) Pos() uint32 {
|
||||
return n.pos
|
||||
}
|
||||
|
||||
func (n *leafPageElement) SetPos(v uint32) {
|
||||
n.pos = v
|
||||
}
|
||||
|
||||
func (n *leafPageElement) Ksize() uint32 {
|
||||
return n.ksize
|
||||
}
|
||||
|
||||
func (n *leafPageElement) SetKsize(v uint32) {
|
||||
n.ksize = v
|
||||
}
|
||||
|
||||
func (n *leafPageElement) Vsize() uint32 {
|
||||
return n.vsize
|
||||
}
|
||||
|
||||
func (n *leafPageElement) SetVsize(v uint32) {
|
||||
n.vsize = v
|
||||
}
|
||||
|
||||
// Key returns a byte slice of the node key.
|
||||
func (n *leafPageElement) Key() []byte {
|
||||
i := int(n.pos)
|
||||
j := i + int(n.ksize)
|
||||
return UnsafeByteSlice(unsafe.Pointer(n), 0, i, j)
|
||||
}
|
||||
|
||||
// Value returns a byte slice of the node value.
|
||||
func (n *leafPageElement) Value() []byte {
|
||||
i := int(n.pos) + int(n.ksize)
|
||||
j := i + int(n.vsize)
|
||||
return UnsafeByteSlice(unsafe.Pointer(n), 0, i, j)
|
||||
}
|
||||
|
||||
func (n *leafPageElement) IsBucketEntry() bool {
|
||||
return n.flags&uint32(BucketLeafFlag) != 0
|
||||
}
|
||||
|
||||
func (n *leafPageElement) Bucket() *InBucket {
|
||||
if n.IsBucketEntry() {
|
||||
return LoadBucket(n.Value())
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// PageInfo represents human readable information about a page.
|
||||
type PageInfo struct {
|
||||
ID int
|
||||
Type string
|
||||
Count int
|
||||
OverflowCount int
|
||||
}
|
||||
|
||||
type Pgids []Pgid
|
||||
|
||||
func (s Pgids) Len() int { return len(s) }
|
||||
func (s Pgids) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||
func (s Pgids) Less(i, j int) bool { return s[i] < s[j] }
|
||||
|
||||
// Merge returns the sorted union of a and b.
|
||||
func (a Pgids) Merge(b Pgids) Pgids {
|
||||
// Return the opposite slice if one is nil.
|
||||
if len(a) == 0 {
|
||||
return b
|
||||
}
|
||||
if len(b) == 0 {
|
||||
return a
|
||||
}
|
||||
merged := make(Pgids, len(a)+len(b))
|
||||
Mergepgids(merged, a, b)
|
||||
return merged
|
||||
}
|
||||
|
||||
// Mergepgids copies the sorted union of a and b into dst.
|
||||
// If dst is too small, it panics.
|
||||
func Mergepgids(dst, a, b Pgids) {
|
||||
if len(dst) < len(a)+len(b) {
|
||||
panic(fmt.Errorf("mergepgids bad len %d < %d + %d", len(dst), len(a), len(b)))
|
||||
}
|
||||
// Copy in the opposite slice if one is nil.
|
||||
if len(a) == 0 {
|
||||
copy(dst, b)
|
||||
return
|
||||
}
|
||||
if len(b) == 0 {
|
||||
copy(dst, a)
|
||||
return
|
||||
}
|
||||
|
||||
// Merged will hold all elements from both lists.
|
||||
merged := dst[:0]
|
||||
|
||||
// Assign lead to the slice with a lower starting value, follow to the higher value.
|
||||
lead, follow := a, b
|
||||
if b[0] < a[0] {
|
||||
lead, follow = b, a
|
||||
}
|
||||
|
||||
// Continue while there are elements in the lead.
|
||||
for len(lead) > 0 {
|
||||
// Merge largest prefix of lead that is ahead of follow[0].
|
||||
n := sort.Search(len(lead), func(i int) bool { return lead[i] > follow[0] })
|
||||
merged = append(merged, lead[:n]...)
|
||||
if n >= len(lead) {
|
||||
break
|
||||
}
|
||||
|
||||
// Swap lead and follow.
|
||||
lead, follow = follow, lead[n:]
|
||||
}
|
||||
|
||||
// Append what's left in follow.
|
||||
_ = append(merged, follow...)
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"os"
|
||||
"runtime"
|
||||
"time"
|
||||
)
|
||||
|
||||
// MaxMmapStep is the largest step that can be taken when remapping the mmap.
|
||||
const MaxMmapStep = 1 << 30 // 1GB
|
||||
|
||||
// Version represents the data file format version.
|
||||
const Version uint32 = 2
|
||||
|
||||
// Magic represents a marker value to indicate that a file is a Bolt DB.
|
||||
const Magic uint32 = 0xED0CDAED
|
||||
|
||||
const PgidNoFreelist Pgid = 0xffffffffffffffff
|
||||
|
||||
// DO NOT EDIT. Copied from the "bolt" package.
|
||||
const pageMaxAllocSize = 0xFFFFFFF
|
||||
|
||||
// IgnoreNoSync specifies whether the NoSync field of a DB is ignored when
|
||||
// syncing changes to a file. This is required as some operating systems,
|
||||
// such as OpenBSD, do not have a unified buffer cache (UBC) and writes
|
||||
// must be synchronized using the msync(2) syscall.
|
||||
const IgnoreNoSync = runtime.GOOS == "openbsd"
|
||||
|
||||
// Default values if not set in a DB instance.
|
||||
const (
|
||||
DefaultMaxBatchSize int = 1000
|
||||
DefaultMaxBatchDelay = 10 * time.Millisecond
|
||||
DefaultAllocSize = 16 * 1024 * 1024
|
||||
)
|
||||
|
||||
// DefaultPageSize is the default page size for db which is set to the OS page size.
|
||||
var DefaultPageSize = os.Getpagesize()
|
||||
|
||||
// Txid represents the internal transaction identifier.
|
||||
type Txid uint64
|
||||
Generated
Vendored
+5
-5
@@ -1,18 +1,18 @@
|
||||
package bbolt
|
||||
package common
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func unsafeAdd(base unsafe.Pointer, offset uintptr) unsafe.Pointer {
|
||||
func UnsafeAdd(base unsafe.Pointer, offset uintptr) unsafe.Pointer {
|
||||
return unsafe.Pointer(uintptr(base) + offset)
|
||||
}
|
||||
|
||||
func unsafeIndex(base unsafe.Pointer, offset uintptr, elemsz uintptr, n int) unsafe.Pointer {
|
||||
func UnsafeIndex(base unsafe.Pointer, offset uintptr, elemsz uintptr, n int) unsafe.Pointer {
|
||||
return unsafe.Pointer(uintptr(base) + offset + uintptr(n)*elemsz)
|
||||
}
|
||||
|
||||
func unsafeByteSlice(base unsafe.Pointer, offset uintptr, i, j int) []byte {
|
||||
func UnsafeByteSlice(base unsafe.Pointer, offset uintptr, i, j int) []byte {
|
||||
// See: https://github.com/golang/go/wiki/cgo#turning-c-arrays-into-go-slices
|
||||
//
|
||||
// This memory is not allocated from C, but it is unmanaged by Go's
|
||||
@@ -23,5 +23,5 @@ func unsafeByteSlice(base unsafe.Pointer, offset uintptr, i, j int) []byte {
|
||||
// index 0. However, the wiki never says that the address must be to
|
||||
// the beginning of a C allocation (or even that malloc was used at
|
||||
// all), so this is believed to be correct.
|
||||
return (*[maxAllocSize]byte)(unsafeAdd(base, offset))[i:j:j]
|
||||
return (*[pageMaxAllocSize]byte)(UnsafeAdd(base, offset))[i:j:j]
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func LoadBucket(buf []byte) *InBucket {
|
||||
return (*InBucket)(unsafe.Pointer(&buf[0]))
|
||||
}
|
||||
|
||||
func LoadPage(buf []byte) *Page {
|
||||
return (*Page)(unsafe.Pointer(&buf[0]))
|
||||
}
|
||||
|
||||
func LoadPageMeta(buf []byte) *Meta {
|
||||
return (*Meta)(unsafe.Pointer(&buf[PageHeaderSize]))
|
||||
}
|
||||
|
||||
func CopyFile(srcPath, dstPath string) error {
|
||||
// Ensure source file exists.
|
||||
_, err := os.Stat(srcPath)
|
||||
if os.IsNotExist(err) {
|
||||
return fmt.Errorf("source file %q not found", srcPath)
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Ensure output file not exist.
|
||||
_, err = os.Stat(dstPath)
|
||||
if err == nil {
|
||||
return fmt.Errorf("output file %q already exists", dstPath)
|
||||
} else if !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
srcDB, err := os.Open(srcPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open source file %q: %w", srcPath, err)
|
||||
}
|
||||
defer srcDB.Close()
|
||||
dstDB, err := os.Create(dstPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create output file %q: %w", dstPath, err)
|
||||
}
|
||||
defer dstDB.Close()
|
||||
written, err := io.Copy(dstDB, srcDB)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to copy database file from %q to %q: %w", srcPath, dstPath, err)
|
||||
}
|
||||
|
||||
srcFi, err := srcDB.Stat()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get source file info %q: %w", srcPath, err)
|
||||
}
|
||||
initialSize := srcFi.Size()
|
||||
if initialSize != written {
|
||||
return fmt.Errorf("the byte copied (%q: %d) isn't equal to the initial db size (%q: %d)", dstPath, written, srcPath, initialSize)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
// Copied from https://github.com/etcd-io/etcd/blob/main/client/pkg/verify/verify.go
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const ENV_VERIFY = "BBOLT_VERIFY"
|
||||
|
||||
type VerificationType string
|
||||
|
||||
const (
|
||||
ENV_VERIFY_VALUE_ALL VerificationType = "all"
|
||||
ENV_VERIFY_VALUE_ASSERT VerificationType = "assert"
|
||||
)
|
||||
|
||||
func getEnvVerify() string {
|
||||
return strings.ToLower(os.Getenv(ENV_VERIFY))
|
||||
}
|
||||
|
||||
func IsVerificationEnabled(verification VerificationType) bool {
|
||||
env := getEnvVerify()
|
||||
return env == string(ENV_VERIFY_VALUE_ALL) || env == strings.ToLower(string(verification))
|
||||
}
|
||||
|
||||
// EnableVerifications sets `ENV_VERIFY` and returns a function that
|
||||
// can be used to bring the original settings.
|
||||
func EnableVerifications(verification VerificationType) func() {
|
||||
previousEnv := getEnvVerify()
|
||||
os.Setenv(ENV_VERIFY, string(verification))
|
||||
return func() {
|
||||
os.Setenv(ENV_VERIFY, previousEnv)
|
||||
}
|
||||
}
|
||||
|
||||
// EnableAllVerifications enables verification and returns a function
|
||||
// that can be used to bring the original settings.
|
||||
func EnableAllVerifications() func() {
|
||||
return EnableVerifications(ENV_VERIFY_VALUE_ALL)
|
||||
}
|
||||
|
||||
// DisableVerifications unsets `ENV_VERIFY` and returns a function that
|
||||
// can be used to bring the original settings.
|
||||
func DisableVerifications() func() {
|
||||
previousEnv := getEnvVerify()
|
||||
os.Unsetenv(ENV_VERIFY)
|
||||
return func() {
|
||||
os.Setenv(ENV_VERIFY, previousEnv)
|
||||
}
|
||||
}
|
||||
|
||||
// Verify performs verification if the assertions are enabled.
|
||||
// In the default setup running in tests and skipped in the production code.
|
||||
func Verify(f func()) {
|
||||
if IsVerificationEnabled(ENV_VERIFY_VALUE_ASSERT) {
|
||||
f()
|
||||
}
|
||||
}
|
||||
|
||||
// Assert will panic with a given formatted message if the given condition is false.
|
||||
func Assert(condition bool, msg string, v ...any) {
|
||||
if !condition {
|
||||
panic(fmt.Sprintf("assertion failed: "+msg, v...))
|
||||
}
|
||||
}
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
package freelist
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"go.etcd.io/bbolt/internal/common"
|
||||
)
|
||||
|
||||
type array struct {
|
||||
*shared
|
||||
|
||||
ids []common.Pgid // all free and available free page ids.
|
||||
}
|
||||
|
||||
func (f *array) Init(ids common.Pgids) {
|
||||
f.ids = ids
|
||||
f.reindex()
|
||||
}
|
||||
|
||||
func (f *array) Allocate(txid common.Txid, n int) common.Pgid {
|
||||
if len(f.ids) == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
var initial, previd common.Pgid
|
||||
for i, id := range f.ids {
|
||||
if id <= 1 {
|
||||
panic(fmt.Sprintf("invalid page allocation: %d", id))
|
||||
}
|
||||
|
||||
// Reset initial page if this is not contiguous.
|
||||
if previd == 0 || id-previd != 1 {
|
||||
initial = id
|
||||
}
|
||||
|
||||
// If we found a contiguous block then remove it and return it.
|
||||
if (id-initial)+1 == common.Pgid(n) {
|
||||
// If we're allocating off the beginning then take the fast path
|
||||
// and just adjust the existing slice. This will use extra memory
|
||||
// temporarily but the append() in free() will realloc the slice
|
||||
// as is necessary.
|
||||
if (i + 1) == n {
|
||||
f.ids = f.ids[i+1:]
|
||||
} else {
|
||||
copy(f.ids[i-n+1:], f.ids[i+1:])
|
||||
f.ids = f.ids[:len(f.ids)-n]
|
||||
}
|
||||
|
||||
// Remove from the free cache.
|
||||
for i := common.Pgid(0); i < common.Pgid(n); i++ {
|
||||
delete(f.cache, initial+i)
|
||||
}
|
||||
f.allocs[initial] = txid
|
||||
return initial
|
||||
}
|
||||
|
||||
previd = id
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (f *array) FreeCount() int {
|
||||
return len(f.ids)
|
||||
}
|
||||
|
||||
func (f *array) freePageIds() common.Pgids {
|
||||
return f.ids
|
||||
}
|
||||
|
||||
func (f *array) mergeSpans(ids common.Pgids) {
|
||||
sort.Sort(ids)
|
||||
common.Verify(func() {
|
||||
idsIdx := make(map[common.Pgid]struct{})
|
||||
for _, id := range f.ids {
|
||||
// The existing f.ids shouldn't have duplicated free ID.
|
||||
if _, ok := idsIdx[id]; ok {
|
||||
panic(fmt.Sprintf("detected duplicated free page ID: %d in existing f.ids: %v", id, f.ids))
|
||||
}
|
||||
idsIdx[id] = struct{}{}
|
||||
}
|
||||
|
||||
prev := common.Pgid(0)
|
||||
for _, id := range ids {
|
||||
// The ids shouldn't have duplicated free ID. Note page 0 and 1
|
||||
// are reserved for meta pages, so they can never be free page IDs.
|
||||
if prev == id {
|
||||
panic(fmt.Sprintf("detected duplicated free ID: %d in ids: %v", id, ids))
|
||||
}
|
||||
prev = id
|
||||
|
||||
// The ids shouldn't have any overlap with the existing f.ids.
|
||||
if _, ok := idsIdx[id]; ok {
|
||||
panic(fmt.Sprintf("detected overlapped free page ID: %d between ids: %v and existing f.ids: %v", id, ids, f.ids))
|
||||
}
|
||||
}
|
||||
})
|
||||
f.ids = common.Pgids(f.ids).Merge(ids)
|
||||
}
|
||||
|
||||
func NewArrayFreelist() Interface {
|
||||
a := &array{
|
||||
shared: newShared(),
|
||||
ids: []common.Pgid{},
|
||||
}
|
||||
a.Interface = a
|
||||
return a
|
||||
}
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
package freelist
|
||||
|
||||
import (
|
||||
"go.etcd.io/bbolt/internal/common"
|
||||
)
|
||||
|
||||
type ReadWriter interface {
|
||||
// Read calls Init with the page ids stored in the given page.
|
||||
Read(page *common.Page)
|
||||
|
||||
// Write writes the freelist into the given page.
|
||||
Write(page *common.Page)
|
||||
|
||||
// EstimatedWritePageSize returns the size in bytes of the freelist after serialization in Write.
|
||||
// This should never underestimate the size.
|
||||
EstimatedWritePageSize() int
|
||||
}
|
||||
|
||||
type Interface interface {
|
||||
ReadWriter
|
||||
|
||||
// Init initializes this freelist with the given list of pages.
|
||||
Init(ids common.Pgids)
|
||||
|
||||
// Allocate tries to allocate the given number of contiguous pages
|
||||
// from the free list pages. It returns the starting page ID if
|
||||
// available; otherwise, it returns 0.
|
||||
Allocate(txid common.Txid, numPages int) common.Pgid
|
||||
|
||||
// Count returns the number of free and pending pages.
|
||||
Count() int
|
||||
|
||||
// FreeCount returns the number of free pages.
|
||||
FreeCount() int
|
||||
|
||||
// PendingCount returns the number of pending pages.
|
||||
PendingCount() int
|
||||
|
||||
// AddReadonlyTXID adds a given read-only transaction id for pending page tracking.
|
||||
AddReadonlyTXID(txid common.Txid)
|
||||
|
||||
// RemoveReadonlyTXID removes a given read-only transaction id for pending page tracking.
|
||||
RemoveReadonlyTXID(txid common.Txid)
|
||||
|
||||
// ReleasePendingPages releases any pages associated with closed read-only transactions.
|
||||
ReleasePendingPages()
|
||||
|
||||
// Free releases a page and its overflow for a given transaction id.
|
||||
// If the page is already free or is one of the meta pages, then a panic will occur.
|
||||
Free(txId common.Txid, p *common.Page)
|
||||
|
||||
// Freed returns whether a given page is in the free list.
|
||||
Freed(pgId common.Pgid) bool
|
||||
|
||||
// Rollback removes the pages from a given pending tx.
|
||||
Rollback(txId common.Txid)
|
||||
|
||||
// Copyall copies a list of all free ids and all pending ids in one sorted list.
|
||||
// f.count returns the minimum length required for dst.
|
||||
Copyall(dst []common.Pgid)
|
||||
|
||||
// Reload reads the freelist from a page and filters out pending items.
|
||||
Reload(p *common.Page)
|
||||
|
||||
// NoSyncReload reads the freelist from Pgids and filters out pending items.
|
||||
NoSyncReload(pgIds common.Pgids)
|
||||
|
||||
// freePageIds returns the IDs of all free pages. Returns an empty slice if no free pages are available.
|
||||
freePageIds() common.Pgids
|
||||
|
||||
// pendingPageIds returns all pending pages by transaction id.
|
||||
pendingPageIds() map[common.Txid]*txPending
|
||||
|
||||
// release moves all page ids for a transaction id (or older) to the freelist.
|
||||
release(txId common.Txid)
|
||||
|
||||
// releaseRange moves pending pages allocated within an extent [begin,end] to the free list.
|
||||
releaseRange(begin, end common.Txid)
|
||||
|
||||
// mergeSpans is merging the given pages into the freelist
|
||||
mergeSpans(ids common.Pgids)
|
||||
}
|
||||
+292
@@ -0,0 +1,292 @@
|
||||
package freelist
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
|
||||
"go.etcd.io/bbolt/internal/common"
|
||||
)
|
||||
|
||||
// pidSet holds the set of starting pgids which have the same span size
|
||||
type pidSet map[common.Pgid]struct{}
|
||||
|
||||
type hashMap struct {
|
||||
*shared
|
||||
|
||||
freePagesCount uint64 // count of free pages(hashmap version)
|
||||
freemaps map[uint64]pidSet // key is the size of continuous pages(span), value is a set which contains the starting pgids of same size
|
||||
forwardMap map[common.Pgid]uint64 // key is start pgid, value is its span size
|
||||
backwardMap map[common.Pgid]uint64 // key is end pgid, value is its span size
|
||||
}
|
||||
|
||||
func (f *hashMap) Init(pgids common.Pgids) {
|
||||
// reset the counter when freelist init
|
||||
f.freePagesCount = 0
|
||||
f.freemaps = make(map[uint64]pidSet)
|
||||
f.forwardMap = make(map[common.Pgid]uint64)
|
||||
f.backwardMap = make(map[common.Pgid]uint64)
|
||||
|
||||
if len(pgids) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if !sort.SliceIsSorted([]common.Pgid(pgids), func(i, j int) bool { return pgids[i] < pgids[j] }) {
|
||||
panic("pgids not sorted")
|
||||
}
|
||||
|
||||
size := uint64(1)
|
||||
start := pgids[0]
|
||||
|
||||
for i := 1; i < len(pgids); i++ {
|
||||
// continuous page
|
||||
if pgids[i] == pgids[i-1]+1 {
|
||||
size++
|
||||
} else {
|
||||
f.addSpan(start, size)
|
||||
|
||||
size = 1
|
||||
start = pgids[i]
|
||||
}
|
||||
}
|
||||
|
||||
// init the tail
|
||||
if size != 0 && start != 0 {
|
||||
f.addSpan(start, size)
|
||||
}
|
||||
|
||||
f.reindex()
|
||||
}
|
||||
|
||||
func (f *hashMap) Allocate(txid common.Txid, n int) common.Pgid {
|
||||
if n == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// if we have a exact size match just return short path
|
||||
if bm, ok := f.freemaps[uint64(n)]; ok {
|
||||
for pid := range bm {
|
||||
// remove the span
|
||||
f.delSpan(pid, uint64(n))
|
||||
|
||||
f.allocs[pid] = txid
|
||||
|
||||
for i := common.Pgid(0); i < common.Pgid(n); i++ {
|
||||
delete(f.cache, pid+i)
|
||||
}
|
||||
return pid
|
||||
}
|
||||
}
|
||||
|
||||
// lookup the map to find larger span
|
||||
for size, bm := range f.freemaps {
|
||||
if size < uint64(n) {
|
||||
continue
|
||||
}
|
||||
|
||||
for pid := range bm {
|
||||
// remove the initial
|
||||
f.delSpan(pid, size)
|
||||
|
||||
f.allocs[pid] = txid
|
||||
|
||||
remain := size - uint64(n)
|
||||
|
||||
// add remain span
|
||||
f.addSpan(pid+common.Pgid(n), remain)
|
||||
|
||||
for i := common.Pgid(0); i < common.Pgid(n); i++ {
|
||||
delete(f.cache, pid+i)
|
||||
}
|
||||
return pid
|
||||
}
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
func (f *hashMap) FreeCount() int {
|
||||
common.Verify(func() {
|
||||
expectedFreePageCount := f.hashmapFreeCountSlow()
|
||||
common.Assert(int(f.freePagesCount) == expectedFreePageCount,
|
||||
"freePagesCount (%d) is out of sync with free pages map (%d)", f.freePagesCount, expectedFreePageCount)
|
||||
})
|
||||
return int(f.freePagesCount)
|
||||
}
|
||||
|
||||
func (f *hashMap) freePageIds() common.Pgids {
|
||||
count := f.FreeCount()
|
||||
if count == 0 {
|
||||
return common.Pgids{}
|
||||
}
|
||||
|
||||
m := make([]common.Pgid, 0, count)
|
||||
|
||||
startPageIds := make([]common.Pgid, 0, len(f.forwardMap))
|
||||
for k := range f.forwardMap {
|
||||
startPageIds = append(startPageIds, k)
|
||||
}
|
||||
sort.Sort(common.Pgids(startPageIds))
|
||||
|
||||
for _, start := range startPageIds {
|
||||
if size, ok := f.forwardMap[start]; ok {
|
||||
for i := 0; i < int(size); i++ {
|
||||
m = append(m, start+common.Pgid(i))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
func (f *hashMap) hashmapFreeCountSlow() int {
|
||||
count := 0
|
||||
for _, size := range f.forwardMap {
|
||||
count += int(size)
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
func (f *hashMap) addSpan(start common.Pgid, size uint64) {
|
||||
f.backwardMap[start-1+common.Pgid(size)] = size
|
||||
f.forwardMap[start] = size
|
||||
if _, ok := f.freemaps[size]; !ok {
|
||||
f.freemaps[size] = make(map[common.Pgid]struct{})
|
||||
}
|
||||
|
||||
f.freemaps[size][start] = struct{}{}
|
||||
f.freePagesCount += size
|
||||
}
|
||||
|
||||
func (f *hashMap) delSpan(start common.Pgid, size uint64) {
|
||||
delete(f.forwardMap, start)
|
||||
delete(f.backwardMap, start+common.Pgid(size-1))
|
||||
delete(f.freemaps[size], start)
|
||||
if len(f.freemaps[size]) == 0 {
|
||||
delete(f.freemaps, size)
|
||||
}
|
||||
f.freePagesCount -= size
|
||||
}
|
||||
|
||||
func (f *hashMap) mergeSpans(ids common.Pgids) {
|
||||
common.Verify(func() {
|
||||
ids1Freemap := f.idsFromFreemaps()
|
||||
ids2Forward := f.idsFromForwardMap()
|
||||
ids3Backward := f.idsFromBackwardMap()
|
||||
|
||||
if !reflect.DeepEqual(ids1Freemap, ids2Forward) {
|
||||
panic(fmt.Sprintf("Detected mismatch, f.freemaps: %v, f.forwardMap: %v", f.freemaps, f.forwardMap))
|
||||
}
|
||||
if !reflect.DeepEqual(ids1Freemap, ids3Backward) {
|
||||
panic(fmt.Sprintf("Detected mismatch, f.freemaps: %v, f.backwardMap: %v", f.freemaps, f.backwardMap))
|
||||
}
|
||||
|
||||
sort.Sort(ids)
|
||||
prev := common.Pgid(0)
|
||||
for _, id := range ids {
|
||||
// The ids shouldn't have duplicated free ID.
|
||||
if prev == id {
|
||||
panic(fmt.Sprintf("detected duplicated free ID: %d in ids: %v", id, ids))
|
||||
}
|
||||
prev = id
|
||||
|
||||
// The ids shouldn't have any overlap with the existing f.freemaps.
|
||||
if _, ok := ids1Freemap[id]; ok {
|
||||
panic(fmt.Sprintf("detected overlapped free page ID: %d between ids: %v and existing f.freemaps: %v", id, ids, f.freemaps))
|
||||
}
|
||||
}
|
||||
})
|
||||
for _, id := range ids {
|
||||
// try to see if we can merge and update
|
||||
f.mergeWithExistingSpan(id)
|
||||
}
|
||||
}
|
||||
|
||||
// mergeWithExistingSpan merges pid to the existing free spans, try to merge it backward and forward
|
||||
func (f *hashMap) mergeWithExistingSpan(pid common.Pgid) {
|
||||
prev := pid - 1
|
||||
next := pid + 1
|
||||
|
||||
preSize, mergeWithPrev := f.backwardMap[prev]
|
||||
nextSize, mergeWithNext := f.forwardMap[next]
|
||||
newStart := pid
|
||||
newSize := uint64(1)
|
||||
|
||||
if mergeWithPrev {
|
||||
//merge with previous span
|
||||
start := prev + 1 - common.Pgid(preSize)
|
||||
f.delSpan(start, preSize)
|
||||
|
||||
newStart -= common.Pgid(preSize)
|
||||
newSize += preSize
|
||||
}
|
||||
|
||||
if mergeWithNext {
|
||||
// merge with next span
|
||||
f.delSpan(next, nextSize)
|
||||
newSize += nextSize
|
||||
}
|
||||
|
||||
f.addSpan(newStart, newSize)
|
||||
}
|
||||
|
||||
// idsFromFreemaps get all free page IDs from f.freemaps.
|
||||
// used by test only.
|
||||
func (f *hashMap) idsFromFreemaps() map[common.Pgid]struct{} {
|
||||
ids := make(map[common.Pgid]struct{})
|
||||
for size, idSet := range f.freemaps {
|
||||
for start := range idSet {
|
||||
for i := 0; i < int(size); i++ {
|
||||
id := start + common.Pgid(i)
|
||||
if _, ok := ids[id]; ok {
|
||||
panic(fmt.Sprintf("detected duplicated free page ID: %d in f.freemaps: %v", id, f.freemaps))
|
||||
}
|
||||
ids[id] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ids
|
||||
}
|
||||
|
||||
// idsFromForwardMap get all free page IDs from f.forwardMap.
|
||||
// used by test only.
|
||||
func (f *hashMap) idsFromForwardMap() map[common.Pgid]struct{} {
|
||||
ids := make(map[common.Pgid]struct{})
|
||||
for start, size := range f.forwardMap {
|
||||
for i := 0; i < int(size); i++ {
|
||||
id := start + common.Pgid(i)
|
||||
if _, ok := ids[id]; ok {
|
||||
panic(fmt.Sprintf("detected duplicated free page ID: %d in f.forwardMap: %v", id, f.forwardMap))
|
||||
}
|
||||
ids[id] = struct{}{}
|
||||
}
|
||||
}
|
||||
return ids
|
||||
}
|
||||
|
||||
// idsFromBackwardMap get all free page IDs from f.backwardMap.
|
||||
// used by test only.
|
||||
func (f *hashMap) idsFromBackwardMap() map[common.Pgid]struct{} {
|
||||
ids := make(map[common.Pgid]struct{})
|
||||
for end, size := range f.backwardMap {
|
||||
for i := 0; i < int(size); i++ {
|
||||
id := end - common.Pgid(i)
|
||||
if _, ok := ids[id]; ok {
|
||||
panic(fmt.Sprintf("detected duplicated free page ID: %d in f.backwardMap: %v", id, f.backwardMap))
|
||||
}
|
||||
ids[id] = struct{}{}
|
||||
}
|
||||
}
|
||||
return ids
|
||||
}
|
||||
|
||||
func NewHashMapFreelist() Interface {
|
||||
hm := &hashMap{
|
||||
shared: newShared(),
|
||||
freemaps: make(map[uint64]pidSet),
|
||||
forwardMap: make(map[common.Pgid]uint64),
|
||||
backwardMap: make(map[common.Pgid]uint64),
|
||||
}
|
||||
hm.Interface = hm
|
||||
return hm
|
||||
}
|
||||
+310
@@ -0,0 +1,310 @@
|
||||
package freelist
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"sort"
|
||||
"unsafe"
|
||||
|
||||
"go.etcd.io/bbolt/internal/common"
|
||||
)
|
||||
|
||||
type txPending struct {
|
||||
ids []common.Pgid
|
||||
alloctx []common.Txid // txids allocating the ids
|
||||
lastReleaseBegin common.Txid // beginning txid of last matching releaseRange
|
||||
}
|
||||
|
||||
type shared struct {
|
||||
Interface
|
||||
|
||||
readonlyTXIDs []common.Txid // all readonly transaction IDs.
|
||||
allocs map[common.Pgid]common.Txid // mapping of Txid that allocated a pgid.
|
||||
cache map[common.Pgid]struct{} // fast lookup of all free and pending page ids.
|
||||
pending map[common.Txid]*txPending // mapping of soon-to-be free page ids by tx.
|
||||
}
|
||||
|
||||
func newShared() *shared {
|
||||
return &shared{
|
||||
pending: make(map[common.Txid]*txPending),
|
||||
allocs: make(map[common.Pgid]common.Txid),
|
||||
cache: make(map[common.Pgid]struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
func (t *shared) pendingPageIds() map[common.Txid]*txPending {
|
||||
return t.pending
|
||||
}
|
||||
|
||||
func (t *shared) PendingCount() int {
|
||||
var count int
|
||||
for _, txp := range t.pending {
|
||||
count += len(txp.ids)
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
func (t *shared) Count() int {
|
||||
return t.FreeCount() + t.PendingCount()
|
||||
}
|
||||
|
||||
func (t *shared) Freed(pgId common.Pgid) bool {
|
||||
_, ok := t.cache[pgId]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (t *shared) Free(txid common.Txid, p *common.Page) {
|
||||
if p.Id() <= 1 {
|
||||
panic(fmt.Sprintf("cannot free page 0 or 1: %d", p.Id()))
|
||||
}
|
||||
|
||||
// Free page and all its overflow pages.
|
||||
txp := t.pending[txid]
|
||||
if txp == nil {
|
||||
txp = &txPending{}
|
||||
t.pending[txid] = txp
|
||||
}
|
||||
allocTxid, ok := t.allocs[p.Id()]
|
||||
common.Verify(func() {
|
||||
if allocTxid == txid {
|
||||
panic(fmt.Sprintf("free: freed page (%d) was allocated by the same transaction (%d)", p.Id(), txid))
|
||||
}
|
||||
})
|
||||
if ok {
|
||||
delete(t.allocs, p.Id())
|
||||
}
|
||||
|
||||
for id := p.Id(); id <= p.Id()+common.Pgid(p.Overflow()); id++ {
|
||||
// Verify that page is not already free.
|
||||
if _, ok := t.cache[id]; ok {
|
||||
panic(fmt.Sprintf("page %d already freed", id))
|
||||
}
|
||||
// Add to the freelist and cache.
|
||||
txp.ids = append(txp.ids, id)
|
||||
txp.alloctx = append(txp.alloctx, allocTxid)
|
||||
t.cache[id] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *shared) Rollback(txid common.Txid) {
|
||||
// Remove page ids from cache.
|
||||
txp := t.pending[txid]
|
||||
if txp == nil {
|
||||
return
|
||||
}
|
||||
for i, pgid := range txp.ids {
|
||||
delete(t.cache, pgid)
|
||||
tx := txp.alloctx[i]
|
||||
if tx == 0 {
|
||||
continue
|
||||
}
|
||||
if tx != txid {
|
||||
// Pending free aborted; restore page back to alloc list.
|
||||
t.allocs[pgid] = tx
|
||||
} else {
|
||||
// A writing TXN should never free a page which was allocated by itself.
|
||||
panic(fmt.Sprintf("rollback: freed page (%d) was allocated by the same transaction (%d)", pgid, txid))
|
||||
}
|
||||
}
|
||||
// Remove pages from pending list and mark as free if allocated by txid.
|
||||
delete(t.pending, txid)
|
||||
|
||||
// Remove pgids which are allocated by this txid
|
||||
for pgid, tid := range t.allocs {
|
||||
if tid == txid {
|
||||
delete(t.allocs, pgid)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *shared) AddReadonlyTXID(tid common.Txid) {
|
||||
t.readonlyTXIDs = append(t.readonlyTXIDs, tid)
|
||||
}
|
||||
|
||||
func (t *shared) RemoveReadonlyTXID(tid common.Txid) {
|
||||
for i := range t.readonlyTXIDs {
|
||||
if t.readonlyTXIDs[i] == tid {
|
||||
last := len(t.readonlyTXIDs) - 1
|
||||
t.readonlyTXIDs[i] = t.readonlyTXIDs[last]
|
||||
t.readonlyTXIDs = t.readonlyTXIDs[:last]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type txIDx []common.Txid
|
||||
|
||||
func (t txIDx) Len() int { return len(t) }
|
||||
func (t txIDx) Swap(i, j int) { t[i], t[j] = t[j], t[i] }
|
||||
func (t txIDx) Less(i, j int) bool { return t[i] < t[j] }
|
||||
|
||||
func (t *shared) ReleasePendingPages() {
|
||||
// Free all pending pages prior to the earliest open transaction.
|
||||
sort.Sort(txIDx(t.readonlyTXIDs))
|
||||
minid := common.Txid(math.MaxUint64)
|
||||
if len(t.readonlyTXIDs) > 0 {
|
||||
minid = t.readonlyTXIDs[0]
|
||||
}
|
||||
if minid > 0 {
|
||||
t.release(minid - 1)
|
||||
}
|
||||
// Release unused txid extents.
|
||||
for _, tid := range t.readonlyTXIDs {
|
||||
t.releaseRange(minid, tid-1)
|
||||
minid = tid + 1
|
||||
}
|
||||
t.releaseRange(minid, common.Txid(math.MaxUint64))
|
||||
// Any page both allocated and freed in an extent is safe to release.
|
||||
}
|
||||
|
||||
func (t *shared) release(txid common.Txid) {
|
||||
m := make(common.Pgids, 0)
|
||||
for tid, txp := range t.pending {
|
||||
if tid <= txid {
|
||||
// Move transaction's pending pages to the available freelist.
|
||||
// Don't remove from the cache since the page is still free.
|
||||
m = append(m, txp.ids...)
|
||||
delete(t.pending, tid)
|
||||
}
|
||||
}
|
||||
t.mergeSpans(m)
|
||||
}
|
||||
|
||||
func (t *shared) releaseRange(begin, end common.Txid) {
|
||||
if begin > end {
|
||||
return
|
||||
}
|
||||
m := common.Pgids{}
|
||||
for tid, txp := range t.pending {
|
||||
if tid < begin || tid > end {
|
||||
continue
|
||||
}
|
||||
// Don't recompute freed pages if ranges haven't updated.
|
||||
if txp.lastReleaseBegin == begin {
|
||||
continue
|
||||
}
|
||||
for i := 0; i < len(txp.ids); i++ {
|
||||
if atx := txp.alloctx[i]; atx < begin || atx > end {
|
||||
continue
|
||||
}
|
||||
m = append(m, txp.ids[i])
|
||||
txp.ids[i] = txp.ids[len(txp.ids)-1]
|
||||
txp.ids = txp.ids[:len(txp.ids)-1]
|
||||
txp.alloctx[i] = txp.alloctx[len(txp.alloctx)-1]
|
||||
txp.alloctx = txp.alloctx[:len(txp.alloctx)-1]
|
||||
i--
|
||||
}
|
||||
txp.lastReleaseBegin = begin
|
||||
if len(txp.ids) == 0 {
|
||||
delete(t.pending, tid)
|
||||
}
|
||||
}
|
||||
t.mergeSpans(m)
|
||||
}
|
||||
|
||||
// Copyall copies a list of all free ids and all pending ids in one sorted list.
|
||||
// f.count returns the minimum length required for dst.
|
||||
func (t *shared) Copyall(dst []common.Pgid) {
|
||||
m := make(common.Pgids, 0, t.PendingCount())
|
||||
for _, txp := range t.pendingPageIds() {
|
||||
m = append(m, txp.ids...)
|
||||
}
|
||||
sort.Sort(m)
|
||||
common.Mergepgids(dst, t.freePageIds(), m)
|
||||
}
|
||||
|
||||
func (t *shared) Reload(p *common.Page) {
|
||||
t.Read(p)
|
||||
t.NoSyncReload(t.freePageIds())
|
||||
}
|
||||
|
||||
func (t *shared) NoSyncReload(pgIds common.Pgids) {
|
||||
// Build a cache of only pending pages.
|
||||
pcache := make(map[common.Pgid]bool)
|
||||
for _, txp := range t.pending {
|
||||
for _, pendingID := range txp.ids {
|
||||
pcache[pendingID] = true
|
||||
}
|
||||
}
|
||||
|
||||
// Check each page in the freelist and build a new available freelist
|
||||
// with any pages not in the pending lists.
|
||||
a := []common.Pgid{}
|
||||
for _, id := range pgIds {
|
||||
if !pcache[id] {
|
||||
a = append(a, id)
|
||||
}
|
||||
}
|
||||
|
||||
t.Init(a)
|
||||
}
|
||||
|
||||
// reindex rebuilds the free cache based on available and pending free lists.
|
||||
func (t *shared) reindex() {
|
||||
free := t.freePageIds()
|
||||
pending := t.pendingPageIds()
|
||||
t.cache = make(map[common.Pgid]struct{}, len(free))
|
||||
for _, id := range free {
|
||||
t.cache[id] = struct{}{}
|
||||
}
|
||||
for _, txp := range pending {
|
||||
for _, pendingID := range txp.ids {
|
||||
t.cache[pendingID] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *shared) Read(p *common.Page) {
|
||||
if !p.IsFreelistPage() {
|
||||
panic(fmt.Sprintf("invalid freelist page: %d, page type is %s", p.Id(), p.Typ()))
|
||||
}
|
||||
|
||||
ids := p.FreelistPageIds()
|
||||
|
||||
// Copy the list of page ids from the freelist.
|
||||
if len(ids) == 0 {
|
||||
t.Init([]common.Pgid{})
|
||||
} else {
|
||||
// copy the ids, so we don't modify on the freelist page directly
|
||||
idsCopy := make([]common.Pgid, len(ids))
|
||||
copy(idsCopy, ids)
|
||||
// Make sure they're sorted.
|
||||
sort.Sort(common.Pgids(idsCopy))
|
||||
|
||||
t.Init(idsCopy)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *shared) EstimatedWritePageSize() int {
|
||||
n := t.Count()
|
||||
if n >= 0xFFFF {
|
||||
// The first element will be used to store the count. See freelist.write.
|
||||
n++
|
||||
}
|
||||
return int(common.PageHeaderSize) + (int(unsafe.Sizeof(common.Pgid(0))) * n)
|
||||
}
|
||||
|
||||
func (t *shared) Write(p *common.Page) {
|
||||
// Combine the old free pgids and pgids waiting on an open transaction.
|
||||
|
||||
// Update the header flag.
|
||||
p.SetFlags(common.FreelistPageFlag)
|
||||
|
||||
// The page.count can only hold up to 64k elements so if we overflow that
|
||||
// number then we handle it by putting the size in the first element.
|
||||
l := t.Count()
|
||||
if l == 0 {
|
||||
p.SetCount(uint16(l))
|
||||
} else if l < 0xFFFF {
|
||||
p.SetCount(uint16(l))
|
||||
data := common.UnsafeAdd(unsafe.Pointer(p), unsafe.Sizeof(*p))
|
||||
ids := unsafe.Slice((*common.Pgid)(data), l)
|
||||
t.Copyall(ids)
|
||||
} else {
|
||||
p.SetCount(0xFFFF)
|
||||
data := common.UnsafeAdd(unsafe.Pointer(p), unsafe.Sizeof(*p))
|
||||
ids := unsafe.Slice((*common.Pgid)(data), l+1)
|
||||
ids[0] = common.Pgid(l)
|
||||
t.Copyall(ids[1:])
|
||||
}
|
||||
}
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
package bbolt
|
||||
|
||||
// See https://github.com/etcd-io/raft/blob/main/logger.go
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
type Logger interface {
|
||||
Debug(v ...interface{})
|
||||
Debugf(format string, v ...interface{})
|
||||
|
||||
Error(v ...interface{})
|
||||
Errorf(format string, v ...interface{})
|
||||
|
||||
Info(v ...interface{})
|
||||
Infof(format string, v ...interface{})
|
||||
|
||||
Warning(v ...interface{})
|
||||
Warningf(format string, v ...interface{})
|
||||
|
||||
Fatal(v ...interface{})
|
||||
Fatalf(format string, v ...interface{})
|
||||
|
||||
Panic(v ...interface{})
|
||||
Panicf(format string, v ...interface{})
|
||||
}
|
||||
|
||||
func getDiscardLogger() Logger {
|
||||
return discardLogger
|
||||
}
|
||||
|
||||
var (
|
||||
discardLogger = &DefaultLogger{Logger: log.New(io.Discard, "", 0)}
|
||||
)
|
||||
|
||||
const (
|
||||
calldepth = 2
|
||||
)
|
||||
|
||||
// DefaultLogger is a default implementation of the Logger interface.
|
||||
type DefaultLogger struct {
|
||||
*log.Logger
|
||||
debug bool
|
||||
}
|
||||
|
||||
func (l *DefaultLogger) EnableTimestamps() {
|
||||
l.SetFlags(l.Flags() | log.Ldate | log.Ltime)
|
||||
}
|
||||
|
||||
func (l *DefaultLogger) EnableDebug() {
|
||||
l.debug = true
|
||||
}
|
||||
|
||||
func (l *DefaultLogger) Debug(v ...interface{}) {
|
||||
if l.debug {
|
||||
_ = l.Output(calldepth, header("DEBUG", fmt.Sprint(v...)))
|
||||
}
|
||||
}
|
||||
|
||||
func (l *DefaultLogger) Debugf(format string, v ...interface{}) {
|
||||
if l.debug {
|
||||
_ = l.Output(calldepth, header("DEBUG", fmt.Sprintf(format, v...)))
|
||||
}
|
||||
}
|
||||
|
||||
func (l *DefaultLogger) Info(v ...interface{}) {
|
||||
_ = l.Output(calldepth, header("INFO", fmt.Sprint(v...)))
|
||||
}
|
||||
|
||||
func (l *DefaultLogger) Infof(format string, v ...interface{}) {
|
||||
_ = l.Output(calldepth, header("INFO", fmt.Sprintf(format, v...)))
|
||||
}
|
||||
|
||||
func (l *DefaultLogger) Error(v ...interface{}) {
|
||||
_ = l.Output(calldepth, header("ERROR", fmt.Sprint(v...)))
|
||||
}
|
||||
|
||||
func (l *DefaultLogger) Errorf(format string, v ...interface{}) {
|
||||
_ = l.Output(calldepth, header("ERROR", fmt.Sprintf(format, v...)))
|
||||
}
|
||||
|
||||
func (l *DefaultLogger) Warning(v ...interface{}) {
|
||||
_ = l.Output(calldepth, header("WARN", fmt.Sprint(v...)))
|
||||
}
|
||||
|
||||
func (l *DefaultLogger) Warningf(format string, v ...interface{}) {
|
||||
_ = l.Output(calldepth, header("WARN", fmt.Sprintf(format, v...)))
|
||||
}
|
||||
|
||||
func (l *DefaultLogger) Fatal(v ...interface{}) {
|
||||
_ = l.Output(calldepth, header("FATAL", fmt.Sprint(v...)))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func (l *DefaultLogger) Fatalf(format string, v ...interface{}) {
|
||||
_ = l.Output(calldepth, header("FATAL", fmt.Sprintf(format, v...)))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func (l *DefaultLogger) Panic(v ...interface{}) {
|
||||
l.Logger.Panic(v...)
|
||||
}
|
||||
|
||||
func (l *DefaultLogger) Panicf(format string, v ...interface{}) {
|
||||
l.Logger.Panicf(format, v...)
|
||||
}
|
||||
|
||||
func header(lvl, msg string) string {
|
||||
return fmt.Sprintf("%s: %s", lvl, msg)
|
||||
}
|
||||
-1
@@ -1,5 +1,4 @@
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package bbolt
|
||||
|
||||
|
||||
+90
-162
@@ -4,7 +4,8 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"sort"
|
||||
"unsafe"
|
||||
|
||||
"go.etcd.io/bbolt/internal/common"
|
||||
)
|
||||
|
||||
// node represents an in-memory, deserialized page.
|
||||
@@ -14,10 +15,10 @@ type node struct {
|
||||
unbalanced bool
|
||||
spilled bool
|
||||
key []byte
|
||||
pgid pgid
|
||||
pgid common.Pgid
|
||||
parent *node
|
||||
children nodes
|
||||
inodes inodes
|
||||
inodes common.Inodes
|
||||
}
|
||||
|
||||
// root returns the top-level node this node is attached to.
|
||||
@@ -38,10 +39,10 @@ func (n *node) minKeys() int {
|
||||
|
||||
// size returns the size of the node after serialization.
|
||||
func (n *node) size() int {
|
||||
sz, elsz := pageHeaderSize, n.pageElementSize()
|
||||
sz, elsz := common.PageHeaderSize, n.pageElementSize()
|
||||
for i := 0; i < len(n.inodes); i++ {
|
||||
item := &n.inodes[i]
|
||||
sz += elsz + uintptr(len(item.key)) + uintptr(len(item.value))
|
||||
sz += elsz + uintptr(len(item.Key())) + uintptr(len(item.Value()))
|
||||
}
|
||||
return int(sz)
|
||||
}
|
||||
@@ -50,10 +51,10 @@ func (n *node) size() int {
|
||||
// This is an optimization to avoid calculating a large node when we only need
|
||||
// to know if it fits inside a certain page size.
|
||||
func (n *node) sizeLessThan(v uintptr) bool {
|
||||
sz, elsz := pageHeaderSize, n.pageElementSize()
|
||||
sz, elsz := common.PageHeaderSize, n.pageElementSize()
|
||||
for i := 0; i < len(n.inodes); i++ {
|
||||
item := &n.inodes[i]
|
||||
sz += elsz + uintptr(len(item.key)) + uintptr(len(item.value))
|
||||
sz += elsz + uintptr(len(item.Key())) + uintptr(len(item.Value()))
|
||||
if sz >= v {
|
||||
return false
|
||||
}
|
||||
@@ -64,9 +65,9 @@ func (n *node) sizeLessThan(v uintptr) bool {
|
||||
// pageElementSize returns the size of each page element based on the type of node.
|
||||
func (n *node) pageElementSize() uintptr {
|
||||
if n.isLeaf {
|
||||
return leafPageElementSize
|
||||
return common.LeafPageElementSize
|
||||
}
|
||||
return branchPageElementSize
|
||||
return common.BranchPageElementSize
|
||||
}
|
||||
|
||||
// childAt returns the child node at a given index.
|
||||
@@ -74,12 +75,12 @@ func (n *node) childAt(index int) *node {
|
||||
if n.isLeaf {
|
||||
panic(fmt.Sprintf("invalid childAt(%d) on a leaf node", index))
|
||||
}
|
||||
return n.bucket.node(n.inodes[index].pgid, n)
|
||||
return n.bucket.node(n.inodes[index].Pgid(), n)
|
||||
}
|
||||
|
||||
// childIndex returns the index of a given child node.
|
||||
func (n *node) childIndex(child *node) int {
|
||||
index := sort.Search(len(n.inodes), func(i int) bool { return bytes.Compare(n.inodes[i].key, child.key) != -1 })
|
||||
index := sort.Search(len(n.inodes), func(i int) bool { return bytes.Compare(n.inodes[i].Key(), child.key) != -1 })
|
||||
return index
|
||||
}
|
||||
|
||||
@@ -113,9 +114,9 @@ func (n *node) prevSibling() *node {
|
||||
}
|
||||
|
||||
// put inserts a key/value.
|
||||
func (n *node) put(oldKey, newKey, value []byte, pgId pgid, flags uint32) {
|
||||
if pgId >= n.bucket.tx.meta.pgid {
|
||||
panic(fmt.Sprintf("pgId (%d) above high water mark (%d)", pgId, n.bucket.tx.meta.pgid))
|
||||
func (n *node) put(oldKey, newKey, value []byte, pgId common.Pgid, flags uint32) {
|
||||
if pgId >= n.bucket.tx.meta.Pgid() {
|
||||
panic(fmt.Sprintf("pgId (%d) above high water mark (%d)", pgId, n.bucket.tx.meta.Pgid()))
|
||||
} else if len(oldKey) <= 0 {
|
||||
panic("put: zero-length old key")
|
||||
} else if len(newKey) <= 0 {
|
||||
@@ -123,30 +124,30 @@ func (n *node) put(oldKey, newKey, value []byte, pgId pgid, flags uint32) {
|
||||
}
|
||||
|
||||
// Find insertion index.
|
||||
index := sort.Search(len(n.inodes), func(i int) bool { return bytes.Compare(n.inodes[i].key, oldKey) != -1 })
|
||||
index := sort.Search(len(n.inodes), func(i int) bool { return bytes.Compare(n.inodes[i].Key(), oldKey) != -1 })
|
||||
|
||||
// Add capacity and shift nodes if we don't have an exact match and need to insert.
|
||||
exact := (len(n.inodes) > 0 && index < len(n.inodes) && bytes.Equal(n.inodes[index].key, oldKey))
|
||||
exact := len(n.inodes) > 0 && index < len(n.inodes) && bytes.Equal(n.inodes[index].Key(), oldKey)
|
||||
if !exact {
|
||||
n.inodes = append(n.inodes, inode{})
|
||||
n.inodes = append(n.inodes, common.Inode{})
|
||||
copy(n.inodes[index+1:], n.inodes[index:])
|
||||
}
|
||||
|
||||
inode := &n.inodes[index]
|
||||
inode.flags = flags
|
||||
inode.key = newKey
|
||||
inode.value = value
|
||||
inode.pgid = pgId
|
||||
_assert(len(inode.key) > 0, "put: zero-length inode key")
|
||||
inode.SetFlags(flags)
|
||||
inode.SetKey(newKey)
|
||||
inode.SetValue(value)
|
||||
inode.SetPgid(pgId)
|
||||
common.Assert(len(inode.Key()) > 0, "put: zero-length inode key")
|
||||
}
|
||||
|
||||
// del removes a key from the node.
|
||||
func (n *node) del(key []byte) {
|
||||
// Find index of key.
|
||||
index := sort.Search(len(n.inodes), func(i int) bool { return bytes.Compare(n.inodes[i].key, key) != -1 })
|
||||
index := sort.Search(len(n.inodes), func(i int) bool { return bytes.Compare(n.inodes[i].Key(), key) != -1 })
|
||||
|
||||
// Exit if the key isn't found.
|
||||
if index >= len(n.inodes) || !bytes.Equal(n.inodes[index].key, key) {
|
||||
if index >= len(n.inodes) || !bytes.Equal(n.inodes[index].Key(), key) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -158,30 +159,15 @@ func (n *node) del(key []byte) {
|
||||
}
|
||||
|
||||
// read initializes the node from a page.
|
||||
func (n *node) read(p *page) {
|
||||
n.pgid = p.id
|
||||
n.isLeaf = ((p.flags & leafPageFlag) != 0)
|
||||
n.inodes = make(inodes, int(p.count))
|
||||
func (n *node) read(p *common.Page) {
|
||||
n.pgid = p.Id()
|
||||
n.isLeaf = p.IsLeafPage()
|
||||
n.inodes = common.ReadInodeFromPage(p)
|
||||
|
||||
for i := 0; i < int(p.count); i++ {
|
||||
inode := &n.inodes[i]
|
||||
if n.isLeaf {
|
||||
elem := p.leafPageElement(uint16(i))
|
||||
inode.flags = elem.flags
|
||||
inode.key = elem.key()
|
||||
inode.value = elem.value()
|
||||
} else {
|
||||
elem := p.branchPageElement(uint16(i))
|
||||
inode.pgid = elem.pgid
|
||||
inode.key = elem.key()
|
||||
}
|
||||
_assert(len(inode.key) > 0, "read: zero-length inode key")
|
||||
}
|
||||
|
||||
// Save first key so we can find the node in the parent when we spill.
|
||||
// Save first key, so we can find the node in the parent when we spill.
|
||||
if len(n.inodes) > 0 {
|
||||
n.key = n.inodes[0].key
|
||||
_assert(len(n.key) > 0, "read: zero-length node key")
|
||||
n.key = n.inodes[0].Key()
|
||||
common.Assert(len(n.key) > 0, "read: zero-length node key")
|
||||
} else {
|
||||
n.key = nil
|
||||
}
|
||||
@@ -190,57 +176,27 @@ func (n *node) read(p *page) {
|
||||
// write writes the items onto one or more pages.
|
||||
// The page should have p.id (might be 0 for meta or bucket-inline page) and p.overflow set
|
||||
// and the rest should be zeroed.
|
||||
func (n *node) write(p *page) {
|
||||
_assert(p.count == 0 && p.flags == 0, "node cannot be written into a not empty page")
|
||||
func (n *node) write(p *common.Page) {
|
||||
common.Assert(p.Count() == 0 && p.Flags() == 0, "node cannot be written into a not empty page")
|
||||
|
||||
// Initialize page.
|
||||
if n.isLeaf {
|
||||
p.flags = leafPageFlag
|
||||
p.SetFlags(common.LeafPageFlag)
|
||||
} else {
|
||||
p.flags = branchPageFlag
|
||||
p.SetFlags(common.BranchPageFlag)
|
||||
}
|
||||
|
||||
if len(n.inodes) >= 0xFFFF {
|
||||
panic(fmt.Sprintf("inode overflow: %d (pgid=%d)", len(n.inodes), p.id))
|
||||
panic(fmt.Sprintf("inode overflow: %d (pgid=%d)", len(n.inodes), p.Id()))
|
||||
}
|
||||
p.count = uint16(len(n.inodes))
|
||||
p.SetCount(uint16(len(n.inodes)))
|
||||
|
||||
// Stop here if there are no items to write.
|
||||
if p.count == 0 {
|
||||
if p.Count() == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// Loop over each item and write it to the page.
|
||||
// off tracks the offset into p of the start of the next data.
|
||||
off := unsafe.Sizeof(*p) + n.pageElementSize()*uintptr(len(n.inodes))
|
||||
for i, item := range n.inodes {
|
||||
_assert(len(item.key) > 0, "write: zero-length inode key")
|
||||
|
||||
// Create a slice to write into of needed size and advance
|
||||
// byte pointer for next iteration.
|
||||
sz := len(item.key) + len(item.value)
|
||||
b := unsafeByteSlice(unsafe.Pointer(p), off, 0, sz)
|
||||
off += uintptr(sz)
|
||||
|
||||
// Write the page element.
|
||||
if n.isLeaf {
|
||||
elem := p.leafPageElement(uint16(i))
|
||||
elem.pos = uint32(uintptr(unsafe.Pointer(&b[0])) - uintptr(unsafe.Pointer(elem)))
|
||||
elem.flags = item.flags
|
||||
elem.ksize = uint32(len(item.key))
|
||||
elem.vsize = uint32(len(item.value))
|
||||
} else {
|
||||
elem := p.branchPageElement(uint16(i))
|
||||
elem.pos = uint32(uintptr(unsafe.Pointer(&b[0])) - uintptr(unsafe.Pointer(elem)))
|
||||
elem.ksize = uint32(len(item.key))
|
||||
elem.pgid = item.pgid
|
||||
_assert(elem.pgid != p.id, "write: circular dependency occurred")
|
||||
}
|
||||
|
||||
// Write data for the element to the end of the page.
|
||||
l := copy(b, item.key)
|
||||
copy(b[l:], item.value)
|
||||
}
|
||||
common.WriteInodeToPage(n.inodes, p)
|
||||
|
||||
// DEBUG ONLY: n.dump()
|
||||
}
|
||||
@@ -273,7 +229,7 @@ func (n *node) split(pageSize uintptr) []*node {
|
||||
func (n *node) splitTwo(pageSize uintptr) (*node, *node) {
|
||||
// Ignore the split if the page doesn't have at least enough nodes for
|
||||
// two pages or if the nodes can fit in a single page.
|
||||
if len(n.inodes) <= (minKeysPerPage*2) || n.sizeLessThan(pageSize) {
|
||||
if len(n.inodes) <= (common.MinKeysPerPage*2) || n.sizeLessThan(pageSize) {
|
||||
return n, nil
|
||||
}
|
||||
|
||||
@@ -313,17 +269,17 @@ func (n *node) splitTwo(pageSize uintptr) (*node, *node) {
|
||||
// It returns the index as well as the size of the first page.
|
||||
// This is only be called from split().
|
||||
func (n *node) splitIndex(threshold int) (index, sz uintptr) {
|
||||
sz = pageHeaderSize
|
||||
sz = common.PageHeaderSize
|
||||
|
||||
// Loop until we only have the minimum number of keys required for the second page.
|
||||
for i := 0; i < len(n.inodes)-minKeysPerPage; i++ {
|
||||
for i := 0; i < len(n.inodes)-common.MinKeysPerPage; i++ {
|
||||
index = uintptr(i)
|
||||
inode := n.inodes[i]
|
||||
elsize := n.pageElementSize() + uintptr(len(inode.key)) + uintptr(len(inode.value))
|
||||
elsize := n.pageElementSize() + uintptr(len(inode.Key())) + uintptr(len(inode.Value()))
|
||||
|
||||
// If we have at least the minimum number of keys and adding another
|
||||
// node would put us over the threshold then exit and return.
|
||||
if index >= minKeysPerPage && sz+elsize > uintptr(threshold) {
|
||||
if index >= common.MinKeysPerPage && sz+elsize > uintptr(threshold) {
|
||||
break
|
||||
}
|
||||
|
||||
@@ -360,7 +316,7 @@ func (n *node) spill() error {
|
||||
for _, node := range nodes {
|
||||
// Add node's page to the freelist if it's not new.
|
||||
if node.pgid > 0 {
|
||||
tx.db.freelist.free(tx.meta.txid, tx.page(node.pgid))
|
||||
tx.db.freelist.Free(tx.meta.Txid(), tx.page(node.pgid))
|
||||
node.pgid = 0
|
||||
}
|
||||
|
||||
@@ -371,10 +327,10 @@ func (n *node) spill() error {
|
||||
}
|
||||
|
||||
// Write the node.
|
||||
if p.id >= tx.meta.pgid {
|
||||
panic(fmt.Sprintf("pgid (%d) above high water mark (%d)", p.id, tx.meta.pgid))
|
||||
if p.Id() >= tx.meta.Pgid() {
|
||||
panic(fmt.Sprintf("pgid (%d) above high water mark (%d)", p.Id(), tx.meta.Pgid()))
|
||||
}
|
||||
node.pgid = p.id
|
||||
node.pgid = p.Id()
|
||||
node.write(p)
|
||||
node.spilled = true
|
||||
|
||||
@@ -382,12 +338,12 @@ func (n *node) spill() error {
|
||||
if node.parent != nil {
|
||||
var key = node.key
|
||||
if key == nil {
|
||||
key = node.inodes[0].key
|
||||
key = node.inodes[0].Key()
|
||||
}
|
||||
|
||||
node.parent.put(key, node.inodes[0].key, nil, node.pgid, 0)
|
||||
node.key = node.inodes[0].key
|
||||
_assert(len(node.key) > 0, "spill: zero-length node key")
|
||||
node.parent.put(key, node.inodes[0].Key(), nil, node.pgid, 0)
|
||||
node.key = node.inodes[0].Key()
|
||||
common.Assert(len(node.key) > 0, "spill: zero-length node key")
|
||||
}
|
||||
|
||||
// Update the statistics.
|
||||
@@ -415,8 +371,8 @@ func (n *node) rebalance() {
|
||||
// Update statistics.
|
||||
n.bucket.tx.stats.IncRebalance(1)
|
||||
|
||||
// Ignore if node is above threshold (25%) and has enough keys.
|
||||
var threshold = n.bucket.tx.db.pageSize / 4
|
||||
// Ignore if node is above threshold (25% when FillPercent is set to DefaultFillPercent) and has enough keys.
|
||||
var threshold = int(float64(n.bucket.tx.db.pageSize)*n.bucket.FillPercent) / 2
|
||||
if n.size() > threshold && len(n.inodes) > n.minKeys() {
|
||||
return
|
||||
}
|
||||
@@ -426,14 +382,14 @@ func (n *node) rebalance() {
|
||||
// If root node is a branch and only has one node then collapse it.
|
||||
if !n.isLeaf && len(n.inodes) == 1 {
|
||||
// Move root's child up.
|
||||
child := n.bucket.node(n.inodes[0].pgid, n)
|
||||
child := n.bucket.node(n.inodes[0].Pgid(), n)
|
||||
n.isLeaf = child.isLeaf
|
||||
n.inodes = child.inodes[:]
|
||||
n.children = child.children
|
||||
|
||||
// Reparent all child nodes being moved.
|
||||
for _, inode := range n.inodes {
|
||||
if child, ok := n.bucket.nodes[inode.pgid]; ok {
|
||||
if child, ok := n.bucket.nodes[inode.Pgid()]; ok {
|
||||
child.parent = n
|
||||
}
|
||||
}
|
||||
@@ -457,53 +413,37 @@ func (n *node) rebalance() {
|
||||
return
|
||||
}
|
||||
|
||||
_assert(n.parent.numChildren() > 1, "parent must have at least 2 children")
|
||||
common.Assert(n.parent.numChildren() > 1, "parent must have at least 2 children")
|
||||
|
||||
// Destination node is right sibling if idx == 0, otherwise left sibling.
|
||||
var target *node
|
||||
var useNextSibling = (n.parent.childIndex(n) == 0)
|
||||
// Merge with right sibling if idx == 0, otherwise left sibling.
|
||||
var leftNode, rightNode *node
|
||||
var useNextSibling = n.parent.childIndex(n) == 0
|
||||
if useNextSibling {
|
||||
target = n.nextSibling()
|
||||
leftNode = n
|
||||
rightNode = n.nextSibling()
|
||||
} else {
|
||||
target = n.prevSibling()
|
||||
leftNode = n.prevSibling()
|
||||
rightNode = n
|
||||
}
|
||||
|
||||
// If both this node and the target node are too small then merge them.
|
||||
if useNextSibling {
|
||||
// Reparent all child nodes being moved.
|
||||
for _, inode := range target.inodes {
|
||||
if child, ok := n.bucket.nodes[inode.pgid]; ok {
|
||||
child.parent.removeChild(child)
|
||||
child.parent = n
|
||||
child.parent.children = append(child.parent.children, child)
|
||||
}
|
||||
// If both nodes are too small then merge them.
|
||||
// Reparent all child nodes being moved.
|
||||
for _, inode := range rightNode.inodes {
|
||||
if child, ok := n.bucket.nodes[inode.Pgid()]; ok {
|
||||
child.parent.removeChild(child)
|
||||
child.parent = leftNode
|
||||
child.parent.children = append(child.parent.children, child)
|
||||
}
|
||||
|
||||
// Copy over inodes from target and remove target.
|
||||
n.inodes = append(n.inodes, target.inodes...)
|
||||
n.parent.del(target.key)
|
||||
n.parent.removeChild(target)
|
||||
delete(n.bucket.nodes, target.pgid)
|
||||
target.free()
|
||||
} else {
|
||||
// Reparent all child nodes being moved.
|
||||
for _, inode := range n.inodes {
|
||||
if child, ok := n.bucket.nodes[inode.pgid]; ok {
|
||||
child.parent.removeChild(child)
|
||||
child.parent = target
|
||||
child.parent.children = append(child.parent.children, child)
|
||||
}
|
||||
}
|
||||
|
||||
// Copy over inodes to target and remove node.
|
||||
target.inodes = append(target.inodes, n.inodes...)
|
||||
n.parent.del(n.key)
|
||||
n.parent.removeChild(n)
|
||||
delete(n.bucket.nodes, n.pgid)
|
||||
n.free()
|
||||
}
|
||||
|
||||
// Either this node or the target node was deleted from the parent so rebalance it.
|
||||
// Copy over inodes from right node to left node and remove right node.
|
||||
leftNode.inodes = append(leftNode.inodes, rightNode.inodes...)
|
||||
n.parent.del(rightNode.key)
|
||||
n.parent.removeChild(rightNode)
|
||||
delete(n.bucket.nodes, rightNode.pgid)
|
||||
rightNode.free()
|
||||
|
||||
// Either this node or the sibling node was deleted from the parent so rebalance it.
|
||||
n.parent.rebalance()
|
||||
}
|
||||
|
||||
@@ -525,20 +465,20 @@ func (n *node) dereference() {
|
||||
key := make([]byte, len(n.key))
|
||||
copy(key, n.key)
|
||||
n.key = key
|
||||
_assert(n.pgid == 0 || len(n.key) > 0, "dereference: zero-length node key on existing node")
|
||||
common.Assert(n.pgid == 0 || len(n.key) > 0, "dereference: zero-length node key on existing node")
|
||||
}
|
||||
|
||||
for i := range n.inodes {
|
||||
inode := &n.inodes[i]
|
||||
|
||||
key := make([]byte, len(inode.key))
|
||||
copy(key, inode.key)
|
||||
inode.key = key
|
||||
_assert(len(inode.key) > 0, "dereference: zero-length inode key")
|
||||
key := make([]byte, len(inode.Key()))
|
||||
copy(key, inode.Key())
|
||||
inode.SetKey(key)
|
||||
common.Assert(len(inode.Key()) > 0, "dereference: zero-length inode key")
|
||||
|
||||
value := make([]byte, len(inode.value))
|
||||
copy(value, inode.value)
|
||||
inode.value = value
|
||||
value := make([]byte, len(inode.Value()))
|
||||
copy(value, inode.Value())
|
||||
inode.SetValue(value)
|
||||
}
|
||||
|
||||
// Recursively dereference children.
|
||||
@@ -553,7 +493,7 @@ func (n *node) dereference() {
|
||||
// free adds the node's underlying page to the freelist.
|
||||
func (n *node) free() {
|
||||
if n.pgid != 0 {
|
||||
n.bucket.tx.db.freelist.free(n.bucket.tx.meta.txid, n.bucket.tx.page(n.pgid))
|
||||
n.bucket.tx.db.freelist.Free(n.bucket.tx.meta.Txid(), n.bucket.tx.page(n.pgid))
|
||||
n.pgid = 0
|
||||
}
|
||||
}
|
||||
@@ -594,17 +534,5 @@ type nodes []*node
|
||||
func (s nodes) Len() int { return len(s) }
|
||||
func (s nodes) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||
func (s nodes) Less(i, j int) bool {
|
||||
return bytes.Compare(s[i].inodes[0].key, s[j].inodes[0].key) == -1
|
||||
return bytes.Compare(s[i].inodes[0].Key(), s[j].inodes[0].Key()) == -1
|
||||
}
|
||||
|
||||
// inode represents an internal node inside of a node.
|
||||
// It can be used to point to elements in a page or point
|
||||
// to an element which hasn't been added to a page yet.
|
||||
type inode struct {
|
||||
flags uint32
|
||||
pgid pgid
|
||||
key []byte
|
||||
value []byte
|
||||
}
|
||||
|
||||
type inodes []inode
|
||||
|
||||
-212
@@ -1,212 +0,0 @@
|
||||
package bbolt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const pageHeaderSize = unsafe.Sizeof(page{})
|
||||
|
||||
const minKeysPerPage = 2
|
||||
|
||||
const branchPageElementSize = unsafe.Sizeof(branchPageElement{})
|
||||
const leafPageElementSize = unsafe.Sizeof(leafPageElement{})
|
||||
|
||||
const (
|
||||
branchPageFlag = 0x01
|
||||
leafPageFlag = 0x02
|
||||
metaPageFlag = 0x04
|
||||
freelistPageFlag = 0x10
|
||||
)
|
||||
|
||||
const (
|
||||
bucketLeafFlag = 0x01
|
||||
)
|
||||
|
||||
type pgid uint64
|
||||
|
||||
type page struct {
|
||||
id pgid
|
||||
flags uint16
|
||||
count uint16
|
||||
overflow uint32
|
||||
}
|
||||
|
||||
// typ returns a human readable page type string used for debugging.
|
||||
func (p *page) typ() string {
|
||||
if (p.flags & branchPageFlag) != 0 {
|
||||
return "branch"
|
||||
} else if (p.flags & leafPageFlag) != 0 {
|
||||
return "leaf"
|
||||
} else if (p.flags & metaPageFlag) != 0 {
|
||||
return "meta"
|
||||
} else if (p.flags & freelistPageFlag) != 0 {
|
||||
return "freelist"
|
||||
}
|
||||
return fmt.Sprintf("unknown<%02x>", p.flags)
|
||||
}
|
||||
|
||||
// meta returns a pointer to the metadata section of the page.
|
||||
func (p *page) meta() *meta {
|
||||
return (*meta)(unsafeAdd(unsafe.Pointer(p), unsafe.Sizeof(*p)))
|
||||
}
|
||||
|
||||
func (p *page) fastCheck(id pgid) {
|
||||
_assert(p.id == id, "Page expected to be: %v, but self identifies as %v", id, p.id)
|
||||
// Only one flag of page-type can be set.
|
||||
_assert(p.flags == branchPageFlag ||
|
||||
p.flags == leafPageFlag ||
|
||||
p.flags == metaPageFlag ||
|
||||
p.flags == freelistPageFlag,
|
||||
"page %v: has unexpected type/flags: %x", p.id, p.flags)
|
||||
}
|
||||
|
||||
// leafPageElement retrieves the leaf node by index
|
||||
func (p *page) leafPageElement(index uint16) *leafPageElement {
|
||||
return (*leafPageElement)(unsafeIndex(unsafe.Pointer(p), unsafe.Sizeof(*p),
|
||||
leafPageElementSize, int(index)))
|
||||
}
|
||||
|
||||
// leafPageElements retrieves a list of leaf nodes.
|
||||
func (p *page) leafPageElements() []leafPageElement {
|
||||
if p.count == 0 {
|
||||
return nil
|
||||
}
|
||||
data := unsafeAdd(unsafe.Pointer(p), unsafe.Sizeof(*p))
|
||||
elems := unsafe.Slice((*leafPageElement)(data), int(p.count))
|
||||
return elems
|
||||
}
|
||||
|
||||
// branchPageElement retrieves the branch node by index
|
||||
func (p *page) branchPageElement(index uint16) *branchPageElement {
|
||||
return (*branchPageElement)(unsafeIndex(unsafe.Pointer(p), unsafe.Sizeof(*p),
|
||||
unsafe.Sizeof(branchPageElement{}), int(index)))
|
||||
}
|
||||
|
||||
// branchPageElements retrieves a list of branch nodes.
|
||||
func (p *page) branchPageElements() []branchPageElement {
|
||||
if p.count == 0 {
|
||||
return nil
|
||||
}
|
||||
data := unsafeAdd(unsafe.Pointer(p), unsafe.Sizeof(*p))
|
||||
elems := unsafe.Slice((*branchPageElement)(data), int(p.count))
|
||||
return elems
|
||||
}
|
||||
|
||||
// dump writes n bytes of the page to STDERR as hex output.
|
||||
func (p *page) hexdump(n int) {
|
||||
buf := unsafeByteSlice(unsafe.Pointer(p), 0, 0, n)
|
||||
fmt.Fprintf(os.Stderr, "%x\n", buf)
|
||||
}
|
||||
|
||||
type pages []*page
|
||||
|
||||
func (s pages) Len() int { return len(s) }
|
||||
func (s pages) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||
func (s pages) Less(i, j int) bool { return s[i].id < s[j].id }
|
||||
|
||||
// branchPageElement represents a node on a branch page.
|
||||
type branchPageElement struct {
|
||||
pos uint32
|
||||
ksize uint32
|
||||
pgid pgid
|
||||
}
|
||||
|
||||
// key returns a byte slice of the node key.
|
||||
func (n *branchPageElement) key() []byte {
|
||||
return unsafeByteSlice(unsafe.Pointer(n), 0, int(n.pos), int(n.pos)+int(n.ksize))
|
||||
}
|
||||
|
||||
// leafPageElement represents a node on a leaf page.
|
||||
type leafPageElement struct {
|
||||
flags uint32
|
||||
pos uint32
|
||||
ksize uint32
|
||||
vsize uint32
|
||||
}
|
||||
|
||||
// key returns a byte slice of the node key.
|
||||
func (n *leafPageElement) key() []byte {
|
||||
i := int(n.pos)
|
||||
j := i + int(n.ksize)
|
||||
return unsafeByteSlice(unsafe.Pointer(n), 0, i, j)
|
||||
}
|
||||
|
||||
// value returns a byte slice of the node value.
|
||||
func (n *leafPageElement) value() []byte {
|
||||
i := int(n.pos) + int(n.ksize)
|
||||
j := i + int(n.vsize)
|
||||
return unsafeByteSlice(unsafe.Pointer(n), 0, i, j)
|
||||
}
|
||||
|
||||
// PageInfo represents human readable information about a page.
|
||||
type PageInfo struct {
|
||||
ID int
|
||||
Type string
|
||||
Count int
|
||||
OverflowCount int
|
||||
}
|
||||
|
||||
type pgids []pgid
|
||||
|
||||
func (s pgids) Len() int { return len(s) }
|
||||
func (s pgids) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||
func (s pgids) Less(i, j int) bool { return s[i] < s[j] }
|
||||
|
||||
// merge returns the sorted union of a and b.
|
||||
func (a pgids) merge(b pgids) pgids {
|
||||
// Return the opposite slice if one is nil.
|
||||
if len(a) == 0 {
|
||||
return b
|
||||
}
|
||||
if len(b) == 0 {
|
||||
return a
|
||||
}
|
||||
merged := make(pgids, len(a)+len(b))
|
||||
mergepgids(merged, a, b)
|
||||
return merged
|
||||
}
|
||||
|
||||
// mergepgids copies the sorted union of a and b into dst.
|
||||
// If dst is too small, it panics.
|
||||
func mergepgids(dst, a, b pgids) {
|
||||
if len(dst) < len(a)+len(b) {
|
||||
panic(fmt.Errorf("mergepgids bad len %d < %d + %d", len(dst), len(a), len(b)))
|
||||
}
|
||||
// Copy in the opposite slice if one is nil.
|
||||
if len(a) == 0 {
|
||||
copy(dst, b)
|
||||
return
|
||||
}
|
||||
if len(b) == 0 {
|
||||
copy(dst, a)
|
||||
return
|
||||
}
|
||||
|
||||
// Merged will hold all elements from both lists.
|
||||
merged := dst[:0]
|
||||
|
||||
// Assign lead to the slice with a lower starting value, follow to the higher value.
|
||||
lead, follow := a, b
|
||||
if b[0] < a[0] {
|
||||
lead, follow = b, a
|
||||
}
|
||||
|
||||
// Continue while there are elements in the lead.
|
||||
for len(lead) > 0 {
|
||||
// Merge largest prefix of lead that is ahead of follow[0].
|
||||
n := sort.Search(len(lead), func(i int) bool { return lead[i] > follow[0] })
|
||||
merged = append(merged, lead[:n]...)
|
||||
if n >= len(lead) {
|
||||
break
|
||||
}
|
||||
|
||||
// Swap lead and follow.
|
||||
lead, follow = follow, lead[n:]
|
||||
}
|
||||
|
||||
// Append what's left in follow.
|
||||
_ = append(merged, follow...)
|
||||
}
|
||||
+144
-90
@@ -5,15 +5,16 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// txid represents the internal transaction identifier.
|
||||
type txid uint64
|
||||
berrors "go.etcd.io/bbolt/errors"
|
||||
"go.etcd.io/bbolt/internal/common"
|
||||
)
|
||||
|
||||
// Tx represents a read-only or read/write transaction on the database.
|
||||
// Read-only transactions can be used for retrieving values for keys and creating cursors.
|
||||
@@ -27,9 +28,9 @@ type Tx struct {
|
||||
writable bool
|
||||
managed bool
|
||||
db *DB
|
||||
meta *meta
|
||||
meta *common.Meta
|
||||
root Bucket
|
||||
pages map[pgid]*page
|
||||
pages map[common.Pgid]*common.Page
|
||||
stats TxStats
|
||||
commitHandlers []func()
|
||||
|
||||
@@ -48,24 +49,27 @@ func (tx *Tx) init(db *DB) {
|
||||
tx.pages = nil
|
||||
|
||||
// Copy the meta page since it can be changed by the writer.
|
||||
tx.meta = &meta{}
|
||||
db.meta().copy(tx.meta)
|
||||
tx.meta = &common.Meta{}
|
||||
db.meta().Copy(tx.meta)
|
||||
|
||||
// Copy over the root bucket.
|
||||
tx.root = newBucket(tx)
|
||||
tx.root.bucket = &bucket{}
|
||||
*tx.root.bucket = tx.meta.root
|
||||
tx.root.InBucket = &common.InBucket{}
|
||||
*tx.root.InBucket = *(tx.meta.RootBucket())
|
||||
|
||||
// Increment the transaction id and add a page cache for writable transactions.
|
||||
if tx.writable {
|
||||
tx.pages = make(map[pgid]*page)
|
||||
tx.meta.txid += txid(1)
|
||||
tx.pages = make(map[common.Pgid]*common.Page)
|
||||
tx.meta.IncTxid()
|
||||
}
|
||||
}
|
||||
|
||||
// ID returns the transaction id.
|
||||
func (tx *Tx) ID() int {
|
||||
return int(tx.meta.txid)
|
||||
if tx == nil || tx.meta == nil {
|
||||
return -1
|
||||
}
|
||||
return int(tx.meta.Txid())
|
||||
}
|
||||
|
||||
// DB returns a reference to the database that created the transaction.
|
||||
@@ -75,7 +79,7 @@ func (tx *Tx) DB() *DB {
|
||||
|
||||
// Size returns current database size in bytes as seen by this transaction.
|
||||
func (tx *Tx) Size() int64 {
|
||||
return int64(tx.meta.pgid) * int64(tx.db.pageSize)
|
||||
return int64(tx.meta.Pgid()) * int64(tx.db.pageSize)
|
||||
}
|
||||
|
||||
// Writable returns whether the transaction can perform write operations.
|
||||
@@ -96,6 +100,11 @@ func (tx *Tx) Stats() TxStats {
|
||||
return tx.stats
|
||||
}
|
||||
|
||||
// Inspect returns the structure of the database.
|
||||
func (tx *Tx) Inspect() BucketStructure {
|
||||
return tx.root.Inspect()
|
||||
}
|
||||
|
||||
// Bucket retrieves a bucket by name.
|
||||
// Returns nil if the bucket does not exist.
|
||||
// The bucket instance is only valid for the lifetime of the transaction.
|
||||
@@ -123,6 +132,24 @@ func (tx *Tx) DeleteBucket(name []byte) error {
|
||||
return tx.root.DeleteBucket(name)
|
||||
}
|
||||
|
||||
// MoveBucket moves a sub-bucket from the source bucket to the destination bucket.
|
||||
// Returns an error if
|
||||
// 1. the sub-bucket cannot be found in the source bucket;
|
||||
// 2. or the key already exists in the destination bucket;
|
||||
// 3. the key represents a non-bucket value.
|
||||
//
|
||||
// If src is nil, it means moving a top level bucket into the target bucket.
|
||||
// If dst is nil, it means converting the child bucket into a top level bucket.
|
||||
func (tx *Tx) MoveBucket(child []byte, src *Bucket, dst *Bucket) error {
|
||||
if src == nil {
|
||||
src = &tx.root
|
||||
}
|
||||
if dst == nil {
|
||||
dst = &tx.root
|
||||
}
|
||||
return src.MoveBucket(child, dst)
|
||||
}
|
||||
|
||||
// ForEach executes a function for each bucket in the root.
|
||||
// If the provided function returns an error then the iteration is stopped and
|
||||
// the error is returned to the caller.
|
||||
@@ -137,15 +164,28 @@ func (tx *Tx) OnCommit(fn func()) {
|
||||
tx.commitHandlers = append(tx.commitHandlers, fn)
|
||||
}
|
||||
|
||||
// Commit writes all changes to disk and updates the meta page.
|
||||
// Commit writes all changes to disk, updates the meta page and closes the transaction.
|
||||
// Returns an error if a disk write error occurs, or if Commit is
|
||||
// called on a read-only transaction.
|
||||
func (tx *Tx) Commit() error {
|
||||
_assert(!tx.managed, "managed tx commit not allowed")
|
||||
func (tx *Tx) Commit() (err error) {
|
||||
txId := tx.ID()
|
||||
lg := tx.db.Logger()
|
||||
if lg != discardLogger {
|
||||
lg.Debugf("Committing transaction %d", txId)
|
||||
defer func() {
|
||||
if err != nil {
|
||||
lg.Errorf("Committing transaction failed: %v", err)
|
||||
} else {
|
||||
lg.Debugf("Committing transaction %d successfully", txId)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
common.Assert(!tx.managed, "managed tx commit not allowed")
|
||||
if tx.db == nil {
|
||||
return ErrTxClosed
|
||||
return berrors.ErrTxClosed
|
||||
} else if !tx.writable {
|
||||
return ErrTxNotWritable
|
||||
return berrors.ErrTxNotWritable
|
||||
}
|
||||
|
||||
// TODO(benbjohnson): Use vectorized I/O to write out dirty pages.
|
||||
@@ -157,40 +197,43 @@ func (tx *Tx) Commit() error {
|
||||
tx.stats.IncRebalanceTime(time.Since(startTime))
|
||||
}
|
||||
|
||||
opgid := tx.meta.pgid
|
||||
opgid := tx.meta.Pgid()
|
||||
|
||||
// spill data onto dirty pages.
|
||||
startTime = time.Now()
|
||||
if err := tx.root.spill(); err != nil {
|
||||
if err = tx.root.spill(); err != nil {
|
||||
lg.Errorf("spilling data onto dirty pages failed: %v", err)
|
||||
tx.rollback()
|
||||
return err
|
||||
}
|
||||
tx.stats.IncSpillTime(time.Since(startTime))
|
||||
|
||||
// Free the old root bucket.
|
||||
tx.meta.root.root = tx.root.root
|
||||
tx.meta.RootBucket().SetRootPage(tx.root.RootPage())
|
||||
|
||||
// Free the old freelist because commit writes out a fresh freelist.
|
||||
if tx.meta.freelist != pgidNoFreelist {
|
||||
tx.db.freelist.free(tx.meta.txid, tx.db.page(tx.meta.freelist))
|
||||
if tx.meta.Freelist() != common.PgidNoFreelist {
|
||||
tx.db.freelist.Free(tx.meta.Txid(), tx.db.page(tx.meta.Freelist()))
|
||||
}
|
||||
|
||||
if !tx.db.NoFreelistSync {
|
||||
err := tx.commitFreelist()
|
||||
err = tx.commitFreelist()
|
||||
if err != nil {
|
||||
lg.Errorf("committing freelist failed: %v", err)
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
tx.meta.freelist = pgidNoFreelist
|
||||
tx.meta.SetFreelist(common.PgidNoFreelist)
|
||||
}
|
||||
|
||||
// If the high water mark has moved up then attempt to grow the database.
|
||||
if tx.meta.pgid > opgid {
|
||||
if tx.meta.Pgid() > opgid {
|
||||
_ = errors.New("")
|
||||
// gofail: var lackOfDiskSpace string
|
||||
// tx.rollback()
|
||||
// return errors.New(lackOfDiskSpace)
|
||||
if err := tx.db.grow(int(tx.meta.pgid+1) * tx.db.pageSize); err != nil {
|
||||
if err = tx.db.grow(int(tx.meta.Pgid()+1) * tx.db.pageSize); err != nil {
|
||||
lg.Errorf("growing db size failed, pgid: %d, pagesize: %d, error: %v", tx.meta.Pgid(), tx.db.pageSize, err)
|
||||
tx.rollback()
|
||||
return err
|
||||
}
|
||||
@@ -198,7 +241,8 @@ func (tx *Tx) Commit() error {
|
||||
|
||||
// Write dirty pages to disk.
|
||||
startTime = time.Now()
|
||||
if err := tx.write(); err != nil {
|
||||
if err = tx.write(); err != nil {
|
||||
lg.Errorf("writing data failed: %v", err)
|
||||
tx.rollback()
|
||||
return err
|
||||
}
|
||||
@@ -208,11 +252,11 @@ func (tx *Tx) Commit() error {
|
||||
ch := tx.Check()
|
||||
var errs []string
|
||||
for {
|
||||
err, ok := <-ch
|
||||
chkErr, ok := <-ch
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
errs = append(errs, err.Error())
|
||||
errs = append(errs, chkErr.Error())
|
||||
}
|
||||
if len(errs) > 0 {
|
||||
panic("check fail: " + strings.Join(errs, "\n"))
|
||||
@@ -220,7 +264,8 @@ func (tx *Tx) Commit() error {
|
||||
}
|
||||
|
||||
// Write meta to disk.
|
||||
if err := tx.writeMeta(); err != nil {
|
||||
if err = tx.writeMeta(); err != nil {
|
||||
lg.Errorf("writeMeta failed: %v", err)
|
||||
tx.rollback()
|
||||
return err
|
||||
}
|
||||
@@ -240,16 +285,14 @@ func (tx *Tx) Commit() error {
|
||||
func (tx *Tx) commitFreelist() error {
|
||||
// Allocate new pages for the new free list. This will overestimate
|
||||
// the size of the freelist but not underestimate the size (which would be bad).
|
||||
p, err := tx.allocate((tx.db.freelist.size() / tx.db.pageSize) + 1)
|
||||
p, err := tx.allocate((tx.db.freelist.EstimatedWritePageSize() / tx.db.pageSize) + 1)
|
||||
if err != nil {
|
||||
tx.rollback()
|
||||
return err
|
||||
}
|
||||
if err := tx.db.freelist.write(p); err != nil {
|
||||
tx.rollback()
|
||||
return err
|
||||
}
|
||||
tx.meta.freelist = p.id
|
||||
|
||||
tx.db.freelist.Write(p)
|
||||
tx.meta.SetFreelist(p.Id())
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -257,9 +300,9 @@ func (tx *Tx) commitFreelist() error {
|
||||
// Rollback closes the transaction and ignores all previous updates. Read-only
|
||||
// transactions must be rolled back and not committed.
|
||||
func (tx *Tx) Rollback() error {
|
||||
_assert(!tx.managed, "managed tx rollback not allowed")
|
||||
common.Assert(!tx.managed, "managed tx rollback not allowed")
|
||||
if tx.db == nil {
|
||||
return ErrTxClosed
|
||||
return berrors.ErrTxClosed
|
||||
}
|
||||
tx.nonPhysicalRollback()
|
||||
return nil
|
||||
@@ -271,7 +314,7 @@ func (tx *Tx) nonPhysicalRollback() {
|
||||
return
|
||||
}
|
||||
if tx.writable {
|
||||
tx.db.freelist.rollback(tx.meta.txid)
|
||||
tx.db.freelist.Rollback(tx.meta.Txid())
|
||||
}
|
||||
tx.close()
|
||||
}
|
||||
@@ -282,17 +325,17 @@ func (tx *Tx) rollback() {
|
||||
return
|
||||
}
|
||||
if tx.writable {
|
||||
tx.db.freelist.rollback(tx.meta.txid)
|
||||
tx.db.freelist.Rollback(tx.meta.Txid())
|
||||
// When mmap fails, the `data`, `dataref` and `datasz` may be reset to
|
||||
// zero values, and there is no way to reload free page IDs in this case.
|
||||
if tx.db.data != nil {
|
||||
if !tx.db.hasSyncedFreelist() {
|
||||
// Reconstruct free page list by scanning the DB to get the whole free page list.
|
||||
// Note: scaning the whole db is heavy if your db size is large in NoSyncFreeList mode.
|
||||
tx.db.freelist.noSyncReload(tx.db.freepages())
|
||||
// Note: scanning the whole db is heavy if your db size is large in NoSyncFreeList mode.
|
||||
tx.db.freelist.NoSyncReload(tx.db.freepages())
|
||||
} else {
|
||||
// Read free page list from freelist page.
|
||||
tx.db.freelist.reload(tx.db.page(tx.db.meta().freelist))
|
||||
tx.db.freelist.Reload(tx.db.page(tx.db.meta().Freelist()))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -305,9 +348,9 @@ func (tx *Tx) close() {
|
||||
}
|
||||
if tx.writable {
|
||||
// Grab freelist stats.
|
||||
var freelistFreeN = tx.db.freelist.free_count()
|
||||
var freelistPendingN = tx.db.freelist.pending_count()
|
||||
var freelistAlloc = tx.db.freelist.size()
|
||||
var freelistFreeN = tx.db.freelist.FreeCount()
|
||||
var freelistPendingN = tx.db.freelist.PendingCount()
|
||||
var freelistAlloc = tx.db.freelist.EstimatedWritePageSize()
|
||||
|
||||
// Remove transaction ref & writer lock.
|
||||
tx.db.rwtx = nil
|
||||
@@ -335,7 +378,7 @@ func (tx *Tx) close() {
|
||||
// Copy writes the entire database to a writer.
|
||||
// This function exists for backwards compatibility.
|
||||
//
|
||||
// Deprecated; Use WriteTo() instead.
|
||||
// Deprecated: Use WriteTo() instead.
|
||||
func (tx *Tx) Copy(w io.Writer) error {
|
||||
_, err := tx.WriteTo(w)
|
||||
return err
|
||||
@@ -357,13 +400,13 @@ func (tx *Tx) WriteTo(w io.Writer) (n int64, err error) {
|
||||
|
||||
// Generate a meta page. We use the same page data for both meta pages.
|
||||
buf := make([]byte, tx.db.pageSize)
|
||||
page := (*page)(unsafe.Pointer(&buf[0]))
|
||||
page.flags = metaPageFlag
|
||||
*page.meta() = *tx.meta
|
||||
page := (*common.Page)(unsafe.Pointer(&buf[0]))
|
||||
page.SetFlags(common.MetaPageFlag)
|
||||
*page.Meta() = *tx.meta
|
||||
|
||||
// Write meta 0.
|
||||
page.id = 0
|
||||
page.meta().checksum = page.meta().sum64()
|
||||
page.SetId(0)
|
||||
page.Meta().SetChecksum(page.Meta().Sum64())
|
||||
nn, err := w.Write(buf)
|
||||
n += int64(nn)
|
||||
if err != nil {
|
||||
@@ -371,9 +414,9 @@ func (tx *Tx) WriteTo(w io.Writer) (n int64, err error) {
|
||||
}
|
||||
|
||||
// Write meta 1 with a lower transaction id.
|
||||
page.id = 1
|
||||
page.meta().txid -= 1
|
||||
page.meta().checksum = page.meta().sum64()
|
||||
page.SetId(1)
|
||||
page.Meta().DecTxid()
|
||||
page.Meta().SetChecksum(page.Meta().Sum64())
|
||||
nn, err = w.Write(buf)
|
||||
n += int64(nn)
|
||||
if err != nil {
|
||||
@@ -413,14 +456,16 @@ func (tx *Tx) CopyFile(path string, mode os.FileMode) error {
|
||||
}
|
||||
|
||||
// allocate returns a contiguous block of memory starting at a given page.
|
||||
func (tx *Tx) allocate(count int) (*page, error) {
|
||||
p, err := tx.db.allocate(tx.meta.txid, count)
|
||||
func (tx *Tx) allocate(count int) (*common.Page, error) {
|
||||
lg := tx.db.Logger()
|
||||
p, err := tx.db.allocate(tx.meta.Txid(), count)
|
||||
if err != nil {
|
||||
lg.Errorf("allocating failed, txid: %d, count: %d, error: %v", tx.meta.Txid(), count, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Save to our page cache.
|
||||
tx.pages[p.id] = p
|
||||
tx.pages[p.Id()] = p
|
||||
|
||||
// Update statistics.
|
||||
tx.stats.IncPageCount(int64(count))
|
||||
@@ -432,18 +477,19 @@ func (tx *Tx) allocate(count int) (*page, error) {
|
||||
// write writes any dirty pages to disk.
|
||||
func (tx *Tx) write() error {
|
||||
// Sort pages by id.
|
||||
pages := make(pages, 0, len(tx.pages))
|
||||
lg := tx.db.Logger()
|
||||
pages := make(common.Pages, 0, len(tx.pages))
|
||||
for _, p := range tx.pages {
|
||||
pages = append(pages, p)
|
||||
}
|
||||
// Clear out page cache early.
|
||||
tx.pages = make(map[pgid]*page)
|
||||
tx.pages = make(map[common.Pgid]*common.Page)
|
||||
sort.Sort(pages)
|
||||
|
||||
// Write pages to disk in order.
|
||||
for _, p := range pages {
|
||||
rem := (uint64(p.overflow) + 1) * uint64(tx.db.pageSize)
|
||||
offset := int64(p.id) * int64(tx.db.pageSize)
|
||||
rem := (uint64(p.Overflow()) + 1) * uint64(tx.db.pageSize)
|
||||
offset := int64(p.Id()) * int64(tx.db.pageSize)
|
||||
var written uintptr
|
||||
|
||||
// Write out page in "max allocation" sized chunks.
|
||||
@@ -452,9 +498,10 @@ func (tx *Tx) write() error {
|
||||
if sz > maxAllocSize-1 {
|
||||
sz = maxAllocSize - 1
|
||||
}
|
||||
buf := unsafeByteSlice(unsafe.Pointer(p), written, 0, int(sz))
|
||||
buf := common.UnsafeByteSlice(unsafe.Pointer(p), written, 0, int(sz))
|
||||
|
||||
if _, err := tx.db.ops.writeAt(buf, offset); err != nil {
|
||||
lg.Errorf("writeAt failed, offset: %d: %w", offset, err)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -474,9 +521,10 @@ func (tx *Tx) write() error {
|
||||
}
|
||||
|
||||
// Ignore file sync if flag is set on DB.
|
||||
if !tx.db.NoSync || IgnoreNoSync {
|
||||
if !tx.db.NoSync || common.IgnoreNoSync {
|
||||
// gofail: var beforeSyncDataPages struct{}
|
||||
if err := fdatasync(tx.db); err != nil {
|
||||
lg.Errorf("[GOOS: %s, GOARCH: %s] fdatasync failed: %w", runtime.GOOS, runtime.GOARCH, err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -485,11 +533,11 @@ func (tx *Tx) write() error {
|
||||
for _, p := range pages {
|
||||
// Ignore page sizes over 1 page.
|
||||
// These are allocated using make() instead of the page pool.
|
||||
if int(p.overflow) != 0 {
|
||||
if int(p.Overflow()) != 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
buf := unsafeByteSlice(unsafe.Pointer(p), 0, 0, tx.db.pageSize)
|
||||
buf := common.UnsafeByteSlice(unsafe.Pointer(p), 0, 0, tx.db.pageSize)
|
||||
|
||||
// See https://go.googlesource.com/go/+/f03c9202c43e0abb130669852082117ca50aa9b1
|
||||
for i := range buf {
|
||||
@@ -503,18 +551,24 @@ func (tx *Tx) write() error {
|
||||
|
||||
// writeMeta writes the meta to the disk.
|
||||
func (tx *Tx) writeMeta() error {
|
||||
// gofail: var beforeWriteMetaError string
|
||||
// return errors.New(beforeWriteMetaError)
|
||||
|
||||
// Create a temporary buffer for the meta page.
|
||||
lg := tx.db.Logger()
|
||||
buf := make([]byte, tx.db.pageSize)
|
||||
p := tx.db.pageInBuffer(buf, 0)
|
||||
tx.meta.write(p)
|
||||
tx.meta.Write(p)
|
||||
|
||||
// Write the meta page to file.
|
||||
if _, err := tx.db.ops.writeAt(buf, int64(p.id)*int64(tx.db.pageSize)); err != nil {
|
||||
if _, err := tx.db.ops.writeAt(buf, int64(p.Id())*int64(tx.db.pageSize)); err != nil {
|
||||
lg.Errorf("writeAt failed, pgid: %d, pageSize: %d, error: %v", p.Id(), tx.db.pageSize, err)
|
||||
return err
|
||||
}
|
||||
if !tx.db.NoSync || IgnoreNoSync {
|
||||
if !tx.db.NoSync || common.IgnoreNoSync {
|
||||
// gofail: var beforeSyncMetaPage struct{}
|
||||
if err := fdatasync(tx.db); err != nil {
|
||||
lg.Errorf("[GOOS: %s, GOARCH: %s] fdatasync failed: %w", runtime.GOOS, runtime.GOARCH, err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -527,69 +581,69 @@ func (tx *Tx) writeMeta() error {
|
||||
|
||||
// page returns a reference to the page with a given id.
|
||||
// If page has been written to then a temporary buffered page is returned.
|
||||
func (tx *Tx) page(id pgid) *page {
|
||||
func (tx *Tx) page(id common.Pgid) *common.Page {
|
||||
// Check the dirty pages first.
|
||||
if tx.pages != nil {
|
||||
if p, ok := tx.pages[id]; ok {
|
||||
p.fastCheck(id)
|
||||
p.FastCheck(id)
|
||||
return p
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise return directly from the mmap.
|
||||
p := tx.db.page(id)
|
||||
p.fastCheck(id)
|
||||
p.FastCheck(id)
|
||||
return p
|
||||
}
|
||||
|
||||
// forEachPage iterates over every page within a given page and executes a function.
|
||||
func (tx *Tx) forEachPage(pgidnum pgid, fn func(*page, int, []pgid)) {
|
||||
stack := make([]pgid, 10)
|
||||
func (tx *Tx) forEachPage(pgidnum common.Pgid, fn func(*common.Page, int, []common.Pgid)) {
|
||||
stack := make([]common.Pgid, 10)
|
||||
stack[0] = pgidnum
|
||||
tx.forEachPageInternal(stack[:1], fn)
|
||||
}
|
||||
|
||||
func (tx *Tx) forEachPageInternal(pgidstack []pgid, fn func(*page, int, []pgid)) {
|
||||
func (tx *Tx) forEachPageInternal(pgidstack []common.Pgid, fn func(*common.Page, int, []common.Pgid)) {
|
||||
p := tx.page(pgidstack[len(pgidstack)-1])
|
||||
|
||||
// Execute function.
|
||||
fn(p, len(pgidstack)-1, pgidstack)
|
||||
|
||||
// Recursively loop over children.
|
||||
if (p.flags & branchPageFlag) != 0 {
|
||||
for i := 0; i < int(p.count); i++ {
|
||||
elem := p.branchPageElement(uint16(i))
|
||||
tx.forEachPageInternal(append(pgidstack, elem.pgid), fn)
|
||||
if p.IsBranchPage() {
|
||||
for i := 0; i < int(p.Count()); i++ {
|
||||
elem := p.BranchPageElement(uint16(i))
|
||||
tx.forEachPageInternal(append(pgidstack, elem.Pgid()), fn)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Page returns page information for a given page number.
|
||||
// This is only safe for concurrent use when used by a writable transaction.
|
||||
func (tx *Tx) Page(id int) (*PageInfo, error) {
|
||||
func (tx *Tx) Page(id int) (*common.PageInfo, error) {
|
||||
if tx.db == nil {
|
||||
return nil, ErrTxClosed
|
||||
} else if pgid(id) >= tx.meta.pgid {
|
||||
return nil, berrors.ErrTxClosed
|
||||
} else if common.Pgid(id) >= tx.meta.Pgid() {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if tx.db.freelist == nil {
|
||||
return nil, ErrFreePagesNotLoaded
|
||||
return nil, berrors.ErrFreePagesNotLoaded
|
||||
}
|
||||
|
||||
// Build the page info.
|
||||
p := tx.db.page(pgid(id))
|
||||
info := &PageInfo{
|
||||
p := tx.db.page(common.Pgid(id))
|
||||
info := &common.PageInfo{
|
||||
ID: id,
|
||||
Count: int(p.count),
|
||||
OverflowCount: int(p.overflow),
|
||||
Count: int(p.Count()),
|
||||
OverflowCount: int(p.Overflow()),
|
||||
}
|
||||
|
||||
// Determine the type (or if it's free).
|
||||
if tx.db.freelist.freed(pgid(id)) {
|
||||
if tx.db.freelist.Freed(common.Pgid(id)) {
|
||||
info.Type = "free"
|
||||
} else {
|
||||
info.Type = p.typ()
|
||||
info.Type = p.Typ()
|
||||
}
|
||||
|
||||
return info, nil
|
||||
|
||||
+138
-74
@@ -3,6 +3,8 @@ package bbolt
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
|
||||
"go.etcd.io/bbolt/internal/common"
|
||||
)
|
||||
|
||||
// Check performs several consistency checks on the database for this transaction.
|
||||
@@ -13,13 +15,10 @@ import (
|
||||
// because of caching. This overhead can be removed if running on a read-only
|
||||
// transaction, however, it is not safe to execute other writer transactions at
|
||||
// the same time.
|
||||
func (tx *Tx) Check() <-chan error {
|
||||
return tx.CheckWithOptions()
|
||||
}
|
||||
|
||||
// CheckWithOptions allows users to provide a customized `KVStringer` implementation,
|
||||
//
|
||||
// It also allows users to provide a customized `KVStringer` implementation,
|
||||
// so that bolt can generate human-readable diagnostic messages.
|
||||
func (tx *Tx) CheckWithOptions(options ...CheckOption) <-chan error {
|
||||
func (tx *Tx) Check(options ...CheckOption) <-chan error {
|
||||
chkConfig := checkConfig{
|
||||
kvStringer: HexKVStringer(),
|
||||
}
|
||||
@@ -28,18 +27,22 @@ func (tx *Tx) CheckWithOptions(options ...CheckOption) <-chan error {
|
||||
}
|
||||
|
||||
ch := make(chan error)
|
||||
go tx.check(chkConfig.kvStringer, ch)
|
||||
go func() {
|
||||
// Close the channel to signal completion.
|
||||
defer close(ch)
|
||||
tx.check(chkConfig, ch)
|
||||
}()
|
||||
return ch
|
||||
}
|
||||
|
||||
func (tx *Tx) check(kvStringer KVStringer, ch chan error) {
|
||||
func (tx *Tx) check(cfg checkConfig, ch chan error) {
|
||||
// Force loading free list if opened in ReadOnly mode.
|
||||
tx.db.loadFreelist()
|
||||
|
||||
// Check if any pages are double freed.
|
||||
freed := make(map[pgid]bool)
|
||||
all := make([]pgid, tx.db.freelist.count())
|
||||
tx.db.freelist.copyall(all)
|
||||
freed := make(map[common.Pgid]bool)
|
||||
all := make([]common.Pgid, tx.db.freelist.Count())
|
||||
tx.db.freelist.Copyall(all)
|
||||
for _, id := range all {
|
||||
if freed[id] {
|
||||
ch <- fmt.Errorf("page %d: already freed", id)
|
||||
@@ -48,118 +51,171 @@ func (tx *Tx) check(kvStringer KVStringer, ch chan error) {
|
||||
}
|
||||
|
||||
// Track every reachable page.
|
||||
reachable := make(map[pgid]*page)
|
||||
reachable := make(map[common.Pgid]*common.Page)
|
||||
reachable[0] = tx.page(0) // meta0
|
||||
reachable[1] = tx.page(1) // meta1
|
||||
if tx.meta.freelist != pgidNoFreelist {
|
||||
for i := uint32(0); i <= tx.page(tx.meta.freelist).overflow; i++ {
|
||||
reachable[tx.meta.freelist+pgid(i)] = tx.page(tx.meta.freelist)
|
||||
if tx.meta.Freelist() != common.PgidNoFreelist {
|
||||
for i := uint32(0); i <= tx.page(tx.meta.Freelist()).Overflow(); i++ {
|
||||
reachable[tx.meta.Freelist()+common.Pgid(i)] = tx.page(tx.meta.Freelist())
|
||||
}
|
||||
}
|
||||
|
||||
// Recursively check buckets.
|
||||
tx.checkBucket(&tx.root, reachable, freed, kvStringer, ch)
|
||||
if cfg.pageId == 0 {
|
||||
// Check the whole db file, starting from the root bucket and
|
||||
// recursively check all child buckets.
|
||||
tx.recursivelyCheckBucket(&tx.root, reachable, freed, cfg.kvStringer, ch)
|
||||
|
||||
// Ensure all pages below high water mark are either reachable or freed.
|
||||
for i := pgid(0); i < tx.meta.pgid; i++ {
|
||||
_, isReachable := reachable[i]
|
||||
if !isReachable && !freed[i] {
|
||||
ch <- fmt.Errorf("page %d: unreachable unfreed", int(i))
|
||||
// Ensure all pages below high water mark are either reachable or freed.
|
||||
for i := common.Pgid(0); i < tx.meta.Pgid(); i++ {
|
||||
_, isReachable := reachable[i]
|
||||
if !isReachable && !freed[i] {
|
||||
ch <- fmt.Errorf("page %d: unreachable unfreed", int(i))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Check the db file starting from a specified pageId.
|
||||
if cfg.pageId < 2 || cfg.pageId >= uint64(tx.meta.Pgid()) {
|
||||
ch <- fmt.Errorf("page ID (%d) out of range [%d, %d)", cfg.pageId, 2, tx.meta.Pgid())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Close the channel to signal completion.
|
||||
close(ch)
|
||||
tx.recursivelyCheckPage(common.Pgid(cfg.pageId), reachable, freed, cfg.kvStringer, ch)
|
||||
}
|
||||
}
|
||||
|
||||
func (tx *Tx) checkBucket(b *Bucket, reachable map[pgid]*page, freed map[pgid]bool,
|
||||
func (tx *Tx) recursivelyCheckPage(pageId common.Pgid, reachable map[common.Pgid]*common.Page, freed map[common.Pgid]bool,
|
||||
kvStringer KVStringer, ch chan error) {
|
||||
tx.checkInvariantProperties(pageId, reachable, freed, kvStringer, ch)
|
||||
tx.recursivelyCheckBucketInPage(pageId, reachable, freed, kvStringer, ch)
|
||||
}
|
||||
|
||||
func (tx *Tx) recursivelyCheckBucketInPage(pageId common.Pgid, reachable map[common.Pgid]*common.Page, freed map[common.Pgid]bool,
|
||||
kvStringer KVStringer, ch chan error) {
|
||||
p := tx.page(pageId)
|
||||
|
||||
switch {
|
||||
case p.IsBranchPage():
|
||||
for i := range p.BranchPageElements() {
|
||||
elem := p.BranchPageElement(uint16(i))
|
||||
tx.recursivelyCheckBucketInPage(elem.Pgid(), reachable, freed, kvStringer, ch)
|
||||
}
|
||||
case p.IsLeafPage():
|
||||
for i := range p.LeafPageElements() {
|
||||
elem := p.LeafPageElement(uint16(i))
|
||||
if elem.IsBucketEntry() {
|
||||
inBkt := common.NewInBucket(pageId, 0)
|
||||
tmpBucket := Bucket{
|
||||
InBucket: &inBkt,
|
||||
rootNode: &node{isLeaf: p.IsLeafPage()},
|
||||
FillPercent: DefaultFillPercent,
|
||||
tx: tx,
|
||||
}
|
||||
if child := tmpBucket.Bucket(elem.Key()); child != nil {
|
||||
tx.recursivelyCheckBucket(child, reachable, freed, kvStringer, ch)
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
ch <- fmt.Errorf("unexpected page type (flags: %x) for pgId:%d", p.Flags(), pageId)
|
||||
}
|
||||
}
|
||||
|
||||
func (tx *Tx) recursivelyCheckBucket(b *Bucket, reachable map[common.Pgid]*common.Page, freed map[common.Pgid]bool,
|
||||
kvStringer KVStringer, ch chan error) {
|
||||
// Ignore inline buckets.
|
||||
if b.root == 0 {
|
||||
if b.RootPage() == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// Check every page used by this bucket.
|
||||
b.tx.forEachPage(b.root, func(p *page, _ int, stack []pgid) {
|
||||
if p.id > tx.meta.pgid {
|
||||
ch <- fmt.Errorf("page %d: out of bounds: %d (stack: %v)", int(p.id), int(b.tx.meta.pgid), stack)
|
||||
}
|
||||
|
||||
// Ensure each page is only referenced once.
|
||||
for i := pgid(0); i <= pgid(p.overflow); i++ {
|
||||
var id = p.id + i
|
||||
if _, ok := reachable[id]; ok {
|
||||
ch <- fmt.Errorf("page %d: multiple references (stack: %v)", int(id), stack)
|
||||
}
|
||||
reachable[id] = p
|
||||
}
|
||||
|
||||
// We should only encounter un-freed leaf and branch pages.
|
||||
if freed[p.id] {
|
||||
ch <- fmt.Errorf("page %d: reachable freed", int(p.id))
|
||||
} else if (p.flags&branchPageFlag) == 0 && (p.flags&leafPageFlag) == 0 {
|
||||
ch <- fmt.Errorf("page %d: invalid type: %s (stack: %v)", int(p.id), p.typ(), stack)
|
||||
}
|
||||
})
|
||||
|
||||
tx.recursivelyCheckPages(b.root, kvStringer.KeyToString, ch)
|
||||
tx.checkInvariantProperties(b.RootPage(), reachable, freed, kvStringer, ch)
|
||||
|
||||
// Check each bucket within this bucket.
|
||||
_ = b.ForEachBucket(func(k []byte) error {
|
||||
if child := b.Bucket(k); child != nil {
|
||||
tx.checkBucket(child, reachable, freed, kvStringer, ch)
|
||||
tx.recursivelyCheckBucket(child, reachable, freed, kvStringer, ch)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// recursivelyCheckPages confirms database consistency with respect to b-tree
|
||||
func (tx *Tx) checkInvariantProperties(pageId common.Pgid, reachable map[common.Pgid]*common.Page, freed map[common.Pgid]bool,
|
||||
kvStringer KVStringer, ch chan error) {
|
||||
tx.forEachPage(pageId, func(p *common.Page, _ int, stack []common.Pgid) {
|
||||
verifyPageReachable(p, tx.meta.Pgid(), stack, reachable, freed, ch)
|
||||
})
|
||||
|
||||
tx.recursivelyCheckPageKeyOrder(pageId, kvStringer.KeyToString, ch)
|
||||
}
|
||||
|
||||
func verifyPageReachable(p *common.Page, hwm common.Pgid, stack []common.Pgid, reachable map[common.Pgid]*common.Page, freed map[common.Pgid]bool, ch chan error) {
|
||||
if p.Id() > hwm {
|
||||
ch <- fmt.Errorf("page %d: out of bounds: %d (stack: %v)", int(p.Id()), int(hwm), stack)
|
||||
}
|
||||
|
||||
// Ensure each page is only referenced once.
|
||||
for i := common.Pgid(0); i <= common.Pgid(p.Overflow()); i++ {
|
||||
var id = p.Id() + i
|
||||
if _, ok := reachable[id]; ok {
|
||||
ch <- fmt.Errorf("page %d: multiple references (stack: %v)", int(id), stack)
|
||||
}
|
||||
reachable[id] = p
|
||||
}
|
||||
|
||||
// We should only encounter un-freed leaf and branch pages.
|
||||
if freed[p.Id()] {
|
||||
ch <- fmt.Errorf("page %d: reachable freed", int(p.Id()))
|
||||
} else if !p.IsBranchPage() && !p.IsLeafPage() {
|
||||
ch <- fmt.Errorf("page %d: invalid type: %s (stack: %v)", int(p.Id()), p.Typ(), stack)
|
||||
}
|
||||
}
|
||||
|
||||
// recursivelyCheckPageKeyOrder verifies database consistency with respect to b-tree
|
||||
// key order constraints:
|
||||
// - keys on pages must be sorted
|
||||
// - keys on children pages are between 2 consecutive keys on the parent's branch page).
|
||||
func (tx *Tx) recursivelyCheckPages(pgId pgid, keyToString func([]byte) string, ch chan error) {
|
||||
tx.recursivelyCheckPagesInternal(pgId, nil, nil, nil, keyToString, ch)
|
||||
func (tx *Tx) recursivelyCheckPageKeyOrder(pgId common.Pgid, keyToString func([]byte) string, ch chan error) {
|
||||
tx.recursivelyCheckPageKeyOrderInternal(pgId, nil, nil, nil, keyToString, ch)
|
||||
}
|
||||
|
||||
// recursivelyCheckPagesInternal verifies that all keys in the subtree rooted at `pgid` are:
|
||||
// recursivelyCheckPageKeyOrderInternal verifies that all keys in the subtree rooted at `pgid` are:
|
||||
// - >=`minKeyClosed` (can be nil)
|
||||
// - <`maxKeyOpen` (can be nil)
|
||||
// - Are in right ordering relationship to their parents.
|
||||
// `pagesStack` is expected to contain IDs of pages from the tree root to `pgid` for the clean debugging message.
|
||||
func (tx *Tx) recursivelyCheckPagesInternal(
|
||||
pgId pgid, minKeyClosed, maxKeyOpen []byte, pagesStack []pgid,
|
||||
func (tx *Tx) recursivelyCheckPageKeyOrderInternal(
|
||||
pgId common.Pgid, minKeyClosed, maxKeyOpen []byte, pagesStack []common.Pgid,
|
||||
keyToString func([]byte) string, ch chan error) (maxKeyInSubtree []byte) {
|
||||
|
||||
p := tx.page(pgId)
|
||||
pagesStack = append(pagesStack, pgId)
|
||||
switch {
|
||||
case p.flags&branchPageFlag != 0:
|
||||
case p.IsBranchPage():
|
||||
// For branch page we navigate ranges of all subpages.
|
||||
runningMin := minKeyClosed
|
||||
for i := range p.branchPageElements() {
|
||||
elem := p.branchPageElement(uint16(i))
|
||||
verifyKeyOrder(elem.pgid, "branch", i, elem.key(), runningMin, maxKeyOpen, ch, keyToString, pagesStack)
|
||||
for i := range p.BranchPageElements() {
|
||||
elem := p.BranchPageElement(uint16(i))
|
||||
verifyKeyOrder(elem.Pgid(), "branch", i, elem.Key(), runningMin, maxKeyOpen, ch, keyToString, pagesStack)
|
||||
|
||||
maxKey := maxKeyOpen
|
||||
if i < len(p.branchPageElements())-1 {
|
||||
maxKey = p.branchPageElement(uint16(i + 1)).key()
|
||||
if i < len(p.BranchPageElements())-1 {
|
||||
maxKey = p.BranchPageElement(uint16(i + 1)).Key()
|
||||
}
|
||||
maxKeyInSubtree = tx.recursivelyCheckPagesInternal(elem.pgid, elem.key(), maxKey, pagesStack, keyToString, ch)
|
||||
maxKeyInSubtree = tx.recursivelyCheckPageKeyOrderInternal(elem.Pgid(), elem.Key(), maxKey, pagesStack, keyToString, ch)
|
||||
runningMin = maxKeyInSubtree
|
||||
}
|
||||
return maxKeyInSubtree
|
||||
case p.flags&leafPageFlag != 0:
|
||||
case p.IsLeafPage():
|
||||
runningMin := minKeyClosed
|
||||
for i := range p.leafPageElements() {
|
||||
elem := p.leafPageElement(uint16(i))
|
||||
verifyKeyOrder(pgId, "leaf", i, elem.key(), runningMin, maxKeyOpen, ch, keyToString, pagesStack)
|
||||
runningMin = elem.key()
|
||||
for i := range p.LeafPageElements() {
|
||||
elem := p.LeafPageElement(uint16(i))
|
||||
verifyKeyOrder(pgId, "leaf", i, elem.Key(), runningMin, maxKeyOpen, ch, keyToString, pagesStack)
|
||||
runningMin = elem.Key()
|
||||
}
|
||||
if p.count > 0 {
|
||||
return p.leafPageElement(p.count - 1).key()
|
||||
if p.Count() > 0 {
|
||||
return p.LeafPageElement(p.Count() - 1).Key()
|
||||
}
|
||||
default:
|
||||
ch <- fmt.Errorf("unexpected page type for pgId:%d", pgId)
|
||||
ch <- fmt.Errorf("unexpected page type (flags: %x) for pgId:%d", p.Flags(), pgId)
|
||||
}
|
||||
return maxKeyInSubtree
|
||||
}
|
||||
@@ -168,7 +224,7 @@ func (tx *Tx) recursivelyCheckPagesInternal(
|
||||
* verifyKeyOrder checks whether an entry with given #index on pgId (pageType: "branch|leaf") that has given "key",
|
||||
* is within range determined by (previousKey..maxKeyOpen) and reports found violations to the channel (ch).
|
||||
*/
|
||||
func verifyKeyOrder(pgId pgid, pageType string, index int, key []byte, previousKey []byte, maxKeyOpen []byte, ch chan error, keyToString func([]byte) string, pagesStack []pgid) {
|
||||
func verifyKeyOrder(pgId common.Pgid, pageType string, index int, key []byte, previousKey []byte, maxKeyOpen []byte, ch chan error, keyToString func([]byte) string, pagesStack []common.Pgid) {
|
||||
if index == 0 && previousKey != nil && compareKeys(previousKey, key) > 0 {
|
||||
ch <- fmt.Errorf("the first key[%d]=(hex)%s on %s page(%d) needs to be >= the key in the ancestor (%s). Stack: %v",
|
||||
index, keyToString(key), pageType, pgId, keyToString(previousKey), pagesStack)
|
||||
@@ -194,6 +250,7 @@ func verifyKeyOrder(pgId pgid, pageType string, index int, key []byte, previousK
|
||||
|
||||
type checkConfig struct {
|
||||
kvStringer KVStringer
|
||||
pageId uint64
|
||||
}
|
||||
|
||||
type CheckOption func(options *checkConfig)
|
||||
@@ -204,6 +261,13 @@ func WithKVStringer(kvStringer KVStringer) CheckOption {
|
||||
}
|
||||
}
|
||||
|
||||
// WithPageId sets a page ID from which the check command starts to check
|
||||
func WithPageId(pageId uint64) CheckOption {
|
||||
return func(c *checkConfig) {
|
||||
c.pageId = pageId
|
||||
}
|
||||
}
|
||||
|
||||
// KVStringer allows to prepare human-readable diagnostic messages.
|
||||
type KVStringer interface {
|
||||
KeyToString([]byte) string
|
||||
|
||||
+1
-1
@@ -46,7 +46,7 @@ func (g *Group) done() {
|
||||
// returns a non-nil error or the first time Wait returns, whichever occurs
|
||||
// first.
|
||||
func WithContext(ctx context.Context) (*Group, context.Context) {
|
||||
ctx, cancel := withCancelCause(ctx)
|
||||
ctx, cancel := context.WithCancelCause(ctx)
|
||||
return &Group{cancel: cancel}, ctx
|
||||
}
|
||||
|
||||
|
||||
-13
@@ -1,13 +0,0 @@
|
||||
// Copyright 2023 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.20
|
||||
|
||||
package errgroup
|
||||
|
||||
import "context"
|
||||
|
||||
func withCancelCause(parent context.Context) (context.Context, func(error)) {
|
||||
return context.WithCancelCause(parent)
|
||||
}
|
||||
-14
@@ -1,14 +0,0 @@
|
||||
// Copyright 2023 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !go1.20
|
||||
|
||||
package errgroup
|
||||
|
||||
import "context"
|
||||
|
||||
func withCancelCause(parent context.Context) (context.Context, func(error)) {
|
||||
ctx, cancel := context.WithCancel(parent)
|
||||
return ctx, func(error) { cancel() }
|
||||
}
|
||||
Vendored
+28
-18
@@ -4,13 +4,13 @@ cloud.google.com/go/compute/metadata
|
||||
# github.com/Depado/bfchroma/v2 v2.0.0
|
||||
## explicit; go 1.18
|
||||
github.com/Depado/bfchroma/v2
|
||||
# github.com/PuerkitoBio/goquery v1.10.1
|
||||
# github.com/PuerkitoBio/goquery v1.10.2
|
||||
## explicit; go 1.23
|
||||
github.com/PuerkitoBio/goquery
|
||||
# github.com/ajg/form v1.5.1
|
||||
## explicit
|
||||
github.com/ajg/form
|
||||
# github.com/alecthomas/chroma/v2 v2.14.0
|
||||
# github.com/alecthomas/chroma/v2 v2.15.0
|
||||
## explicit; go 1.19
|
||||
github.com/alecthomas/chroma/v2
|
||||
github.com/alecthomas/chroma/v2/formatters/html
|
||||
@@ -43,6 +43,13 @@ github.com/didip/tollbooth/v7/errors
|
||||
github.com/didip/tollbooth/v7/internal/time/rate
|
||||
github.com/didip/tollbooth/v7/libstring
|
||||
github.com/didip/tollbooth/v7/limiter
|
||||
# github.com/didip/tollbooth/v8 v8.0.1
|
||||
## explicit; go 1.19
|
||||
github.com/didip/tollbooth/v8
|
||||
github.com/didip/tollbooth/v8/errors
|
||||
github.com/didip/tollbooth/v8/internal/time/rate
|
||||
github.com/didip/tollbooth/v8/libstring
|
||||
github.com/didip/tollbooth/v8/limiter
|
||||
# github.com/didip/tollbooth_chi v0.0.0-20220719025231-d662a7f6928f
|
||||
## explicit; go 1.14
|
||||
github.com/didip/tollbooth_chi
|
||||
@@ -50,8 +57,8 @@ github.com/didip/tollbooth_chi
|
||||
## explicit; go 1.13
|
||||
github.com/dlclark/regexp2
|
||||
github.com/dlclark/regexp2/syntax
|
||||
# github.com/go-chi/chi/v5 v5.2.0
|
||||
## explicit; go 1.14
|
||||
# github.com/go-chi/chi/v5 v5.2.1
|
||||
## explicit; go 1.20
|
||||
github.com/go-chi/chi/v5
|
||||
github.com/go-chi/chi/v5/middleware
|
||||
# github.com/go-chi/cors v1.2.1
|
||||
@@ -80,8 +87,8 @@ github.com/go-pkgz/email
|
||||
# github.com/go-pkgz/expirable-cache/v3 v3.0.0
|
||||
## explicit; go 1.20
|
||||
github.com/go-pkgz/expirable-cache/v3
|
||||
# github.com/go-pkgz/jrpc v0.3.0
|
||||
## explicit; go 1.16
|
||||
# github.com/go-pkgz/jrpc v0.3.1
|
||||
## explicit; go 1.23
|
||||
github.com/go-pkgz/jrpc
|
||||
# github.com/go-pkgz/lcw/v2 v2.0.0
|
||||
## explicit; go 1.21
|
||||
@@ -208,9 +215,12 @@ github.com/xdg-go/stringprep
|
||||
# github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78
|
||||
## explicit; go 1.17
|
||||
github.com/youmark/pkcs8
|
||||
# go.etcd.io/bbolt v1.3.11
|
||||
## explicit; go 1.22
|
||||
# go.etcd.io/bbolt v1.4.0
|
||||
## explicit; go 1.23
|
||||
go.etcd.io/bbolt
|
||||
go.etcd.io/bbolt/errors
|
||||
go.etcd.io/bbolt/internal/common
|
||||
go.etcd.io/bbolt/internal/freelist
|
||||
# go.mongodb.org/mongo-driver v1.17.1
|
||||
## explicit; go 1.18
|
||||
go.mongodb.org/mongo-driver/bson
|
||||
@@ -265,7 +275,7 @@ go.mongodb.org/mongo-driver/x/mongo/driver/wiremessage
|
||||
## explicit; go 1.20
|
||||
go.uber.org/goleak
|
||||
go.uber.org/goleak/internal/stack
|
||||
# golang.org/x/crypto v0.35.0
|
||||
# golang.org/x/crypto v0.36.0
|
||||
## explicit; go 1.23.0
|
||||
golang.org/x/crypto/acme
|
||||
golang.org/x/crypto/acme/autocert
|
||||
@@ -276,11 +286,11 @@ golang.org/x/crypto/blowfish
|
||||
golang.org/x/crypto/ocsp
|
||||
golang.org/x/crypto/pbkdf2
|
||||
golang.org/x/crypto/scrypt
|
||||
# golang.org/x/image v0.23.0
|
||||
## explicit; go 1.18
|
||||
# golang.org/x/image v0.25.0
|
||||
## explicit; go 1.23.0
|
||||
golang.org/x/image/draw
|
||||
golang.org/x/image/math/f64
|
||||
# golang.org/x/net v0.36.0
|
||||
# golang.org/x/net v0.37.0
|
||||
## explicit; go 1.23.0
|
||||
golang.org/x/net/html
|
||||
golang.org/x/net/html/atom
|
||||
@@ -302,18 +312,18 @@ golang.org/x/oauth2/jws
|
||||
golang.org/x/oauth2/jwt
|
||||
golang.org/x/oauth2/microsoft
|
||||
golang.org/x/oauth2/yandex
|
||||
# golang.org/x/sync v0.11.0
|
||||
## explicit; go 1.18
|
||||
# golang.org/x/sync v0.12.0
|
||||
## explicit; go 1.23.0
|
||||
golang.org/x/sync/errgroup
|
||||
golang.org/x/sync/singleflight
|
||||
# golang.org/x/sys v0.30.0
|
||||
## explicit; go 1.18
|
||||
# golang.org/x/sys v0.31.0
|
||||
## explicit; go 1.23.0
|
||||
golang.org/x/sys/cpu
|
||||
golang.org/x/sys/unix
|
||||
golang.org/x/sys/windows
|
||||
golang.org/x/sys/windows/registry
|
||||
# golang.org/x/text v0.22.0
|
||||
## explicit; go 1.18
|
||||
# golang.org/x/text v0.23.0
|
||||
## explicit; go 1.23.0
|
||||
golang.org/x/text/secure/bidirule
|
||||
golang.org/x/text/transform
|
||||
golang.org/x/text/unicode/bidi
|
||||
|
||||
Reference in New Issue
Block a user