Bump sigs.k8s.io/cluster-api to v1.7.1
Signed-off-by: zhzhuang-zju <m17799853869@163.com>
This commit is contained in:
parent
3fc8ef5a76
commit
ac6a8d4b8b
39
go.mod
39
go.mod
|
@ -6,7 +6,7 @@ require (
|
|||
github.com/adhocore/gronx v1.6.3
|
||||
github.com/distribution/reference v0.5.0
|
||||
github.com/emirpasic/gods v1.18.1
|
||||
github.com/evanphx/json-patch/v5 v5.8.0
|
||||
github.com/evanphx/json-patch/v5 v5.9.0
|
||||
github.com/go-co-op/gocron v1.30.1
|
||||
github.com/gogo/protobuf v1.3.2
|
||||
github.com/google/go-cmp v0.6.0
|
||||
|
@ -14,8 +14,8 @@ require (
|
|||
github.com/kr/pretty v0.3.1
|
||||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f
|
||||
github.com/olekukonko/tablewriter v0.0.5
|
||||
github.com/onsi/ginkgo/v2 v2.14.0
|
||||
github.com/onsi/gomega v1.30.0
|
||||
github.com/onsi/ginkgo/v2 v2.17.1
|
||||
github.com/onsi/gomega v1.32.0
|
||||
github.com/opensearch-project/opensearch-go v1.1.0
|
||||
github.com/prometheus/client_golang v1.18.0
|
||||
github.com/spf13/cobra v1.8.0
|
||||
|
@ -30,7 +30,7 @@ require (
|
|||
golang.org/x/term v0.18.0
|
||||
golang.org/x/text v0.14.0
|
||||
golang.org/x/time v0.5.0
|
||||
golang.org/x/tools v0.16.1
|
||||
golang.org/x/tools v0.17.0
|
||||
gomodules.xyz/jsonpatch/v2 v2.4.0
|
||||
google.golang.org/grpc v1.60.1
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
|
@ -52,7 +52,7 @@ require (
|
|||
k8s.io/metrics v0.29.4
|
||||
k8s.io/utils v0.0.0-20231127182322-b307cd553661
|
||||
layeh.com/gopher-json v0.0.0-20201124131017-552bb3c4c3bf
|
||||
sigs.k8s.io/cluster-api v1.5.0
|
||||
sigs.k8s.io/cluster-api v1.7.1
|
||||
sigs.k8s.io/controller-runtime v0.17.5
|
||||
sigs.k8s.io/custom-metrics-apiserver v1.29.0
|
||||
sigs.k8s.io/kind v0.22.0
|
||||
|
@ -71,7 +71,6 @@ require (
|
|||
github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df // indirect
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/blang/semver v3.5.1+incompatible // indirect
|
||||
github.com/blang/semver/v4 v4.0.0 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
|
@ -118,7 +117,7 @@ require (
|
|||
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mattn/go-isatty v0.0.17 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.14 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
|
||||
|
@ -131,10 +130,10 @@ require (
|
|||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/pelletier/go-toml v1.9.4 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/prometheus/client_model v0.5.0 // indirect
|
||||
github.com/prometheus/common v0.46.0 // indirect
|
||||
github.com/prometheus/procfs v0.12.0 // indirect
|
||||
|
@ -143,19 +142,21 @@ require (
|
|||
github.com/rogpeppe/go-internal v1.11.0 // indirect
|
||||
github.com/rs/zerolog v1.26.1 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/spf13/afero v1.9.5 // indirect
|
||||
github.com/spf13/cast v1.5.1 // indirect
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/spf13/viper v1.16.0 // indirect
|
||||
github.com/sagikazarmark/locafero v0.4.0 // indirect
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
github.com/spf13/afero v1.11.0 // indirect
|
||||
github.com/spf13/cast v1.6.0 // indirect
|
||||
github.com/spf13/viper v1.18.2 // indirect
|
||||
github.com/stoewer/go-strcase v1.3.0 // indirect
|
||||
github.com/stretchr/objx v0.5.2 // indirect
|
||||
github.com/subosito/gotenv v1.4.2 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.0 // indirect
|
||||
github.com/xlab/treeprint v1.2.0 // indirect
|
||||
go.etcd.io/etcd/api/v3 v3.5.11 // indirect
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.11 // indirect
|
||||
go.etcd.io/etcd/client/v3 v3.5.11 // indirect
|
||||
go.etcd.io/etcd/api/v3 v3.5.13 // indirect
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.13 // indirect
|
||||
go.etcd.io/etcd/client/v3 v3.5.13 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 // indirect
|
||||
go.opentelemetry.io/otel v1.21.0 // indirect
|
||||
|
@ -172,8 +173,8 @@ require (
|
|||
golang.org/x/crypto v0.21.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20231226003508-02704c960a9b // indirect
|
||||
golang.org/x/mod v0.14.0 // indirect
|
||||
golang.org/x/oauth2 v0.16.0 // indirect
|
||||
golang.org/x/sync v0.5.0 // indirect
|
||||
golang.org/x/oauth2 v0.18.0 // indirect
|
||||
golang.org/x/sync v0.6.0 // indirect
|
||||
golang.org/x/sys v0.18.0 // indirect
|
||||
google.golang.org/appengine v1.6.8 // indirect
|
||||
google.golang.org/genproto v0.0.0-20231212172506-995d672761c0 // indirect
|
||||
|
|
85
go.sum
85
go.sum
|
@ -113,8 +113,6 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
|||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||
github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
|
||||
github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ=
|
||||
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
|
||||
github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
|
||||
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
|
||||
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
|
||||
|
@ -215,8 +213,8 @@ github.com/evanphx/json-patch v5.7.0+incompatible h1:vgGkfT/9f8zE6tvSCe74nfpAVDQ
|
|||
github.com/evanphx/json-patch v5.7.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||
github.com/evanphx/json-patch/v5 v5.0.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4=
|
||||
github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4=
|
||||
github.com/evanphx/json-patch/v5 v5.8.0 h1:lRj6N9Nci7MvzrXuX6HFzU8XjmhPiXPlsKEy1u0KQro=
|
||||
github.com/evanphx/json-patch/v5 v5.8.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ=
|
||||
github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg=
|
||||
github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ=
|
||||
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d h1:105gxyaGwCFad8crR9dcMQWvV9Hvulu6hwUh4tWPJnM=
|
||||
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4=
|
||||
github.com/fatih/camelcase v1.0.0 h1:hxNvNX/xYBp0ovncs8WyWZrOrpBNub/JfaMvbURyft8=
|
||||
|
@ -226,8 +224,8 @@ github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL
|
|||
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY=
|
||||
github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
|
||||
|
@ -553,8 +551,8 @@ github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcME
|
|||
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
|
||||
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
|
||||
|
@ -604,15 +602,15 @@ github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W
|
|||
github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||
github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
|
||||
github.com/onsi/ginkgo/v2 v2.14.0 h1:vSmGj2Z5YPb9JwCWT6z6ihcUvDhuXLc3sJiqd3jMKAY=
|
||||
github.com/onsi/ginkgo/v2 v2.14.0/go.mod h1:JkUdW7JkN0V6rFvsHcJ478egV3XH9NxpD27Hal/PhZw=
|
||||
github.com/onsi/ginkgo/v2 v2.17.1 h1:V++EzdbhI4ZV4ev0UTIj0PzhzOcReJFyJaLjtSF55M8=
|
||||
github.com/onsi/ginkgo/v2 v2.17.1/go.mod h1:llBI3WDLL9Z6taip6f33H76YcWtJv+7R3HigUjbIBOs=
|
||||
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
|
||||
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.8.1/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8=
|
||||
github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
|
||||
github.com/onsi/gomega v1.32.0 h1:JRYU78fJ1LPxlckP6Txi/EYqJvjtMrDC04/MM5XRHPk=
|
||||
github.com/onsi/gomega v1.32.0/go.mod h1:a4x4gW6Pz2yK1MAmvluYme5lvYTn61afQ2ETw/8n4Lg=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opensearch-project/opensearch-go v1.1.0 h1:eG5sh3843bbU1itPRjA9QXbxcg8LaZ+DjEzQH9aLN3M=
|
||||
|
@ -624,8 +622,8 @@ github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/9
|
|||
github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE=
|
||||
github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM=
|
||||
github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||
github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
|
||||
github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
|
||||
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
|
||||
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI=
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
|
@ -635,8 +633,9 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
|||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
|
||||
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
|
||||
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/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
||||
github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
|
||||
github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=
|
||||
|
@ -689,6 +688,10 @@ github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD
|
|||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig=
|
||||
github.com/sagikazarmark/crypt v0.4.0/go.mod h1:ALv2SRj7GxYV4HO9elxH9nS6M9gW+xDNxqmyJ6RfDFM=
|
||||
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
|
||||
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
|
||||
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
|
||||
|
@ -701,18 +704,20 @@ github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs
|
|||
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||
github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js=
|
||||
github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0=
|
||||
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
|
||||
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
|
||||
github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4=
|
||||
github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
|
||||
github.com/spf13/afero v1.8.0/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo=
|
||||
github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM=
|
||||
github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ=
|
||||
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
|
||||
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
|
||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA=
|
||||
github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48=
|
||||
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
|
||||
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
||||
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
|
||||
github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
|
||||
|
@ -721,7 +726,6 @@ github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB
|
|||
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
|
||||
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
|
||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
|
||||
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
|
||||
github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
|
@ -732,8 +736,8 @@ github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DM
|
|||
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
|
||||
github.com/spf13/viper v1.10.0/go.mod h1:SoyBPwAtKDzypXNDFKN5kzH7ppppbGZtls1UpIy5AsM=
|
||||
github.com/spf13/viper v1.10.1/go.mod h1:IGlFPqhNAPKRxohIzWpI5QEy4kuI7tcl5WvR+8qy1rU=
|
||||
github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc=
|
||||
github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg=
|
||||
github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ=
|
||||
github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk=
|
||||
github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs=
|
||||
github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
|
@ -753,12 +757,12 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
|||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||
github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8=
|
||||
github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
|
||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/gjson v1.17.1 h1:wlYEnwqAHgzmhNUFfw7Xalt2JzQvsMx2Se4PcoFCT/U=
|
||||
github.com/tidwall/gjson v1.17.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
|
@ -802,16 +806,16 @@ go.etcd.io/bbolt v1.3.8/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw=
|
|||
go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738 h1:VcrIfasaLFkyjk6KNlXQSzO+B0fZcnECiDrKJsfxka0=
|
||||
go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
|
||||
go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
|
||||
go.etcd.io/etcd/api/v3 v3.5.11 h1:B54KwXbWDHyD3XYAwprxNzTe7vlhR69LuBgZnMVvS7E=
|
||||
go.etcd.io/etcd/api/v3 v3.5.11/go.mod h1:Ot+o0SWSyT6uHhA56al1oCED0JImsRiU9Dc26+C2a+4=
|
||||
go.etcd.io/etcd/api/v3 v3.5.13 h1:8WXU2/NBge6AUF1K1gOexB6e07NgsN1hXK0rSTtgSp4=
|
||||
go.etcd.io/etcd/api/v3 v3.5.13/go.mod h1:gBqlqkcMMZMVTMm4NDZloEVJzxQOQIls8splbqBDa0c=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.1/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.11 h1:bT2xVspdiCj2910T0V+/KHcVKjkUrCZVtk8J2JF2z1A=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.11/go.mod h1:seTzl2d9APP8R5Y2hFL3NVlD6qC/dOT+3kvrqPyTas4=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.13 h1:RVZSAnWWWiI5IrYAXjQorajncORbS0zI48LQlE2kQWg=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.13/go.mod h1:XxHT4u1qU12E2+po+UVPrEeL94Um6zL58ppuJWXSAB8=
|
||||
go.etcd.io/etcd/client/v2 v2.305.1/go.mod h1:pMEacxZW7o8pg4CrFE7pquyCJJzZvkvdD2RibOCCCGs=
|
||||
go.etcd.io/etcd/client/v2 v2.305.10 h1:MrmRktzv/XF8CvtQt+P6wLUlURaNpSDJHFZhe//2QE4=
|
||||
go.etcd.io/etcd/client/v2 v2.305.10/go.mod h1:m3CKZi69HzilhVqtPDcjhSGp+kA1OmbNn0qamH80xjA=
|
||||
go.etcd.io/etcd/client/v3 v3.5.11 h1:ajWtgoNSZJ1gmS8k+icvPtqsqEav+iUorF7b0qozgUU=
|
||||
go.etcd.io/etcd/client/v3 v3.5.11/go.mod h1:a6xQUEqFJ8vztO1agJh/KQKOMfFI8og52ZconzcDJwE=
|
||||
go.etcd.io/etcd/client/v3 v3.5.13 h1:o0fHTNJLeO0MyVbc7I3fsCf6nrOqn5d+diSarKnB2js=
|
||||
go.etcd.io/etcd/client/v3 v3.5.13/go.mod h1:cqiAeY8b5DEEcpxvgWKsbLIWNM/8Wy2xJSDMtioMcoI=
|
||||
go.etcd.io/etcd/pkg/v3 v3.5.10 h1:WPR8K0e9kWl1gAhB5A7gEa5ZBTNkT9NdNWrR8Qpo1CM=
|
||||
go.etcd.io/etcd/pkg/v3 v3.5.10/go.mod h1:TKTuCKKcF1zxmfKWDkfz5qqYaE3JncKKZPFf8c1nFUs=
|
||||
go.etcd.io/etcd/raft/v3 v3.5.10 h1:cgNAYe7xrsrn/5kXMSaH8kM/Ky8mAdMqGOxyYwpP0LA=
|
||||
|
@ -888,7 +892,6 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
|
|||
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||
golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
|
||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
|
@ -1009,8 +1012,8 @@ golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ
|
|||
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ=
|
||||
golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o=
|
||||
golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI=
|
||||
golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
|
@ -1023,8 +1026,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
|
|||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/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.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
|
||||
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
|
@ -1113,7 +1116,7 @@ golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
|
||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
|
@ -1206,8 +1209,8 @@ golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
|||
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
|
||||
golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA=
|
||||
golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0=
|
||||
golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc=
|
||||
golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
@ -1495,8 +1498,8 @@ rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
|||
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.7/go.mod h1:PHgbrJT7lCHcxMU+mDHEm+nx46H4zuuHZkDP6icnhu0=
|
||||
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.29.0 h1:/U5vjBbQn3RChhv7P11uhYvCSm5G2GaIi5AIGBS6r4c=
|
||||
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.29.0/go.mod h1:z7+wmGM2dfIiLRfrC6jb5kV2Mq/sK1ZP303cxzkV5Y4=
|
||||
sigs.k8s.io/cluster-api v1.5.0 h1:pwXvzScbAwnrB7EWHTApzW+VQfrj2OSrWAQDC9+bcbU=
|
||||
sigs.k8s.io/cluster-api v1.5.0/go.mod h1:ZSEP01t8oT6104gB4ljsOwwp5uJcI8SWy8IFp2HUvrc=
|
||||
sigs.k8s.io/cluster-api v1.7.1 h1:JkMAbAMzBM+WBHxXLTJXTiCisv1PAaHRzld/3qrmLYY=
|
||||
sigs.k8s.io/cluster-api v1.7.1/go.mod h1:V9ZhKLvQtsDODwjXOKgbitjyCmC71yMBwDcMyNNIov0=
|
||||
sigs.k8s.io/controller-runtime v0.6.1/go.mod h1:XRYBPdbf5XJu9kpS84VJiZ7h/u1hF3gEORz0efEja7A=
|
||||
sigs.k8s.io/controller-runtime v0.17.5 h1:1FI9Lm7NiOOmBsgTV36/s2XrEFXnO2C4sbg/Zme72Rw=
|
||||
sigs.k8s.io/controller-runtime v0.17.5/go.mod h1:N0jpP5Lo7lMTF9aL56Z/B2oWBJjey6StQM0jRbKQXtY=
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
language: go
|
||||
matrix:
|
||||
include:
|
||||
- go: 1.4.3
|
||||
- go: 1.5.4
|
||||
- go: 1.6.3
|
||||
- go: 1.7
|
||||
- go: tip
|
||||
allow_failures:
|
||||
- go: tip
|
||||
install:
|
||||
- go get golang.org/x/tools/cmd/cover
|
||||
- go get github.com/mattn/goveralls
|
||||
script:
|
||||
- echo "Test and track coverage" ; $HOME/gopath/bin/goveralls -package "." -service=travis-ci
|
||||
-repotoken $COVERALLS_TOKEN
|
||||
- echo "Build examples" ; cd examples && go build
|
||||
- echo "Check if gofmt'd" ; diff -u <(echo -n) <(gofmt -d -s .)
|
||||
env:
|
||||
global:
|
||||
secure: HroGEAUQpVq9zX1b1VIkraLiywhGbzvNnTZq2TMxgK7JHP8xqNplAeF1izrR2i4QLL9nsY+9WtYss4QuPvEtZcVHUobw6XnL6radF7jS1LgfYZ9Y7oF+zogZ2I5QUMRLGA7rcxQ05s7mKq3XZQfeqaNts4bms/eZRefWuaFZbkw=
|
|
@ -1,194 +0,0 @@
|
|||
semver for golang [](https://travis-ci.org/blang/semver) [](https://godoc.org/github.com/blang/semver) [](https://coveralls.io/r/blang/semver?branch=master)
|
||||
======
|
||||
|
||||
semver is a [Semantic Versioning](http://semver.org/) library written in golang. It fully covers spec version `2.0.0`.
|
||||
|
||||
Usage
|
||||
-----
|
||||
```bash
|
||||
$ go get github.com/blang/semver
|
||||
```
|
||||
Note: Always vendor your dependencies or fix on a specific version tag.
|
||||
|
||||
```go
|
||||
import github.com/blang/semver
|
||||
v1, err := semver.Make("1.0.0-beta")
|
||||
v2, err := semver.Make("2.0.0-beta")
|
||||
v1.Compare(v2)
|
||||
```
|
||||
|
||||
Also check the [GoDocs](http://godoc.org/github.com/blang/semver).
|
||||
|
||||
Why should I use this lib?
|
||||
-----
|
||||
|
||||
- Fully spec compatible
|
||||
- No reflection
|
||||
- No regex
|
||||
- Fully tested (Coverage >99%)
|
||||
- Readable parsing/validation errors
|
||||
- Fast (See [Benchmarks](#benchmarks))
|
||||
- Only Stdlib
|
||||
- Uses values instead of pointers
|
||||
- Many features, see below
|
||||
|
||||
|
||||
Features
|
||||
-----
|
||||
|
||||
- Parsing and validation at all levels
|
||||
- Comparator-like comparisons
|
||||
- Compare Helper Methods
|
||||
- InPlace manipulation
|
||||
- Ranges `>=1.0.0 <2.0.0 || >=3.0.0 !3.0.1-beta.1`
|
||||
- Wildcards `>=1.x`, `<=2.5.x`
|
||||
- Sortable (implements sort.Interface)
|
||||
- database/sql compatible (sql.Scanner/Valuer)
|
||||
- encoding/json compatible (json.Marshaler/Unmarshaler)
|
||||
|
||||
Ranges
|
||||
------
|
||||
|
||||
A `Range` is a set of conditions which specify which versions satisfy the range.
|
||||
|
||||
A condition is composed of an operator and a version. The supported operators are:
|
||||
|
||||
- `<1.0.0` Less than `1.0.0`
|
||||
- `<=1.0.0` Less than or equal to `1.0.0`
|
||||
- `>1.0.0` Greater than `1.0.0`
|
||||
- `>=1.0.0` Greater than or equal to `1.0.0`
|
||||
- `1.0.0`, `=1.0.0`, `==1.0.0` Equal to `1.0.0`
|
||||
- `!1.0.0`, `!=1.0.0` Not equal to `1.0.0`. Excludes version `1.0.0`.
|
||||
|
||||
Note that spaces between the operator and the version will be gracefully tolerated.
|
||||
|
||||
A `Range` can link multiple `Ranges` separated by space:
|
||||
|
||||
Ranges can be linked by logical AND:
|
||||
|
||||
- `>1.0.0 <2.0.0` would match between both ranges, so `1.1.1` and `1.8.7` but not `1.0.0` or `2.0.0`
|
||||
- `>1.0.0 <3.0.0 !2.0.3-beta.2` would match every version between `1.0.0` and `3.0.0` except `2.0.3-beta.2`
|
||||
|
||||
Ranges can also be linked by logical OR:
|
||||
|
||||
- `<2.0.0 || >=3.0.0` would match `1.x.x` and `3.x.x` but not `2.x.x`
|
||||
|
||||
AND has a higher precedence than OR. It's not possible to use brackets.
|
||||
|
||||
Ranges can be combined by both AND and OR
|
||||
|
||||
- `>1.0.0 <2.0.0 || >3.0.0 !4.2.1` would match `1.2.3`, `1.9.9`, `3.1.1`, but not `4.2.1`, `2.1.1`
|
||||
|
||||
Range usage:
|
||||
|
||||
```
|
||||
v, err := semver.Parse("1.2.3")
|
||||
range, err := semver.ParseRange(">1.0.0 <2.0.0 || >=3.0.0")
|
||||
if range(v) {
|
||||
//valid
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Example
|
||||
-----
|
||||
|
||||
Have a look at full examples in [examples/main.go](examples/main.go)
|
||||
|
||||
```go
|
||||
import github.com/blang/semver
|
||||
|
||||
v, err := semver.Make("0.0.1-alpha.preview+123.github")
|
||||
fmt.Printf("Major: %d\n", v.Major)
|
||||
fmt.Printf("Minor: %d\n", v.Minor)
|
||||
fmt.Printf("Patch: %d\n", v.Patch)
|
||||
fmt.Printf("Pre: %s\n", v.Pre)
|
||||
fmt.Printf("Build: %s\n", v.Build)
|
||||
|
||||
// Prerelease versions array
|
||||
if len(v.Pre) > 0 {
|
||||
fmt.Println("Prerelease versions:")
|
||||
for i, pre := range v.Pre {
|
||||
fmt.Printf("%d: %q\n", i, pre)
|
||||
}
|
||||
}
|
||||
|
||||
// Build meta data array
|
||||
if len(v.Build) > 0 {
|
||||
fmt.Println("Build meta data:")
|
||||
for i, build := range v.Build {
|
||||
fmt.Printf("%d: %q\n", i, build)
|
||||
}
|
||||
}
|
||||
|
||||
v001, err := semver.Make("0.0.1")
|
||||
// Compare using helpers: v.GT(v2), v.LT, v.GTE, v.LTE
|
||||
v001.GT(v) == true
|
||||
v.LT(v001) == true
|
||||
v.GTE(v) == true
|
||||
v.LTE(v) == true
|
||||
|
||||
// Or use v.Compare(v2) for comparisons (-1, 0, 1):
|
||||
v001.Compare(v) == 1
|
||||
v.Compare(v001) == -1
|
||||
v.Compare(v) == 0
|
||||
|
||||
// Manipulate Version in place:
|
||||
v.Pre[0], err = semver.NewPRVersion("beta")
|
||||
if err != nil {
|
||||
fmt.Printf("Error parsing pre release version: %q", err)
|
||||
}
|
||||
|
||||
fmt.Println("\nValidate versions:")
|
||||
v.Build[0] = "?"
|
||||
|
||||
err = v.Validate()
|
||||
if err != nil {
|
||||
fmt.Printf("Validation failed: %s\n", err)
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Benchmarks
|
||||
-----
|
||||
|
||||
BenchmarkParseSimple-4 5000000 390 ns/op 48 B/op 1 allocs/op
|
||||
BenchmarkParseComplex-4 1000000 1813 ns/op 256 B/op 7 allocs/op
|
||||
BenchmarkParseAverage-4 1000000 1171 ns/op 163 B/op 4 allocs/op
|
||||
BenchmarkStringSimple-4 20000000 119 ns/op 16 B/op 1 allocs/op
|
||||
BenchmarkStringLarger-4 10000000 206 ns/op 32 B/op 2 allocs/op
|
||||
BenchmarkStringComplex-4 5000000 324 ns/op 80 B/op 3 allocs/op
|
||||
BenchmarkStringAverage-4 5000000 273 ns/op 53 B/op 2 allocs/op
|
||||
BenchmarkValidateSimple-4 200000000 9.33 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkValidateComplex-4 3000000 469 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkValidateAverage-4 5000000 256 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkCompareSimple-4 100000000 11.8 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkCompareComplex-4 50000000 30.8 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkCompareAverage-4 30000000 41.5 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkSort-4 3000000 419 ns/op 256 B/op 2 allocs/op
|
||||
BenchmarkRangeParseSimple-4 2000000 850 ns/op 192 B/op 5 allocs/op
|
||||
BenchmarkRangeParseAverage-4 1000000 1677 ns/op 400 B/op 10 allocs/op
|
||||
BenchmarkRangeParseComplex-4 300000 5214 ns/op 1440 B/op 30 allocs/op
|
||||
BenchmarkRangeMatchSimple-4 50000000 25.6 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkRangeMatchAverage-4 30000000 56.4 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkRangeMatchComplex-4 10000000 153 ns/op 0 B/op 0 allocs/op
|
||||
|
||||
See benchmark cases at [semver_test.go](semver_test.go)
|
||||
|
||||
|
||||
Motivation
|
||||
-----
|
||||
|
||||
I simply couldn't find any lib supporting the full spec. Others were just wrong or used reflection and regex which i don't like.
|
||||
|
||||
|
||||
Contribution
|
||||
-----
|
||||
|
||||
Feel free to make a pull request. For bigger changes create a issue first to discuss about it.
|
||||
|
||||
|
||||
License
|
||||
-----
|
||||
|
||||
See [LICENSE](LICENSE) file.
|
|
@ -1,23 +0,0 @@
|
|||
package semver
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
// MarshalJSON implements the encoding/json.Marshaler interface.
|
||||
func (v Version) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(v.String())
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements the encoding/json.Unmarshaler interface.
|
||||
func (v *Version) UnmarshalJSON(data []byte) (err error) {
|
||||
var versionString string
|
||||
|
||||
if err = json.Unmarshal(data, &versionString); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
*v, err = Parse(versionString)
|
||||
|
||||
return
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
{
|
||||
"author": "blang",
|
||||
"bugs": {
|
||||
"URL": "https://github.com/blang/semver/issues",
|
||||
"url": "https://github.com/blang/semver/issues"
|
||||
},
|
||||
"gx": {
|
||||
"dvcsimport": "github.com/blang/semver"
|
||||
},
|
||||
"gxVersion": "0.10.0",
|
||||
"language": "go",
|
||||
"license": "MIT",
|
||||
"name": "semver",
|
||||
"releaseCmd": "git commit -a -m \"gx publish $VERSION\"",
|
||||
"version": "3.5.1"
|
||||
}
|
||||
|
|
@ -1,416 +0,0 @@
|
|||
package semver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
type wildcardType int
|
||||
|
||||
const (
|
||||
noneWildcard wildcardType = iota
|
||||
majorWildcard wildcardType = 1
|
||||
minorWildcard wildcardType = 2
|
||||
patchWildcard wildcardType = 3
|
||||
)
|
||||
|
||||
func wildcardTypefromInt(i int) wildcardType {
|
||||
switch i {
|
||||
case 1:
|
||||
return majorWildcard
|
||||
case 2:
|
||||
return minorWildcard
|
||||
case 3:
|
||||
return patchWildcard
|
||||
default:
|
||||
return noneWildcard
|
||||
}
|
||||
}
|
||||
|
||||
type comparator func(Version, Version) bool
|
||||
|
||||
var (
|
||||
compEQ comparator = func(v1 Version, v2 Version) bool {
|
||||
return v1.Compare(v2) == 0
|
||||
}
|
||||
compNE = func(v1 Version, v2 Version) bool {
|
||||
return v1.Compare(v2) != 0
|
||||
}
|
||||
compGT = func(v1 Version, v2 Version) bool {
|
||||
return v1.Compare(v2) == 1
|
||||
}
|
||||
compGE = func(v1 Version, v2 Version) bool {
|
||||
return v1.Compare(v2) >= 0
|
||||
}
|
||||
compLT = func(v1 Version, v2 Version) bool {
|
||||
return v1.Compare(v2) == -1
|
||||
}
|
||||
compLE = func(v1 Version, v2 Version) bool {
|
||||
return v1.Compare(v2) <= 0
|
||||
}
|
||||
)
|
||||
|
||||
type versionRange struct {
|
||||
v Version
|
||||
c comparator
|
||||
}
|
||||
|
||||
// rangeFunc creates a Range from the given versionRange.
|
||||
func (vr *versionRange) rangeFunc() Range {
|
||||
return Range(func(v Version) bool {
|
||||
return vr.c(v, vr.v)
|
||||
})
|
||||
}
|
||||
|
||||
// Range represents a range of versions.
|
||||
// A Range can be used to check if a Version satisfies it:
|
||||
//
|
||||
// range, err := semver.ParseRange(">1.0.0 <2.0.0")
|
||||
// range(semver.MustParse("1.1.1") // returns true
|
||||
type Range func(Version) bool
|
||||
|
||||
// OR combines the existing Range with another Range using logical OR.
|
||||
func (rf Range) OR(f Range) Range {
|
||||
return Range(func(v Version) bool {
|
||||
return rf(v) || f(v)
|
||||
})
|
||||
}
|
||||
|
||||
// AND combines the existing Range with another Range using logical AND.
|
||||
func (rf Range) AND(f Range) Range {
|
||||
return Range(func(v Version) bool {
|
||||
return rf(v) && f(v)
|
||||
})
|
||||
}
|
||||
|
||||
// ParseRange parses a range and returns a Range.
|
||||
// If the range could not be parsed an error is returned.
|
||||
//
|
||||
// Valid ranges are:
|
||||
// - "<1.0.0"
|
||||
// - "<=1.0.0"
|
||||
// - ">1.0.0"
|
||||
// - ">=1.0.0"
|
||||
// - "1.0.0", "=1.0.0", "==1.0.0"
|
||||
// - "!1.0.0", "!=1.0.0"
|
||||
//
|
||||
// A Range can consist of multiple ranges separated by space:
|
||||
// Ranges can be linked by logical AND:
|
||||
// - ">1.0.0 <2.0.0" would match between both ranges, so "1.1.1" and "1.8.7" but not "1.0.0" or "2.0.0"
|
||||
// - ">1.0.0 <3.0.0 !2.0.3-beta.2" would match every version between 1.0.0 and 3.0.0 except 2.0.3-beta.2
|
||||
//
|
||||
// Ranges can also be linked by logical OR:
|
||||
// - "<2.0.0 || >=3.0.0" would match "1.x.x" and "3.x.x" but not "2.x.x"
|
||||
//
|
||||
// AND has a higher precedence than OR. It's not possible to use brackets.
|
||||
//
|
||||
// Ranges can be combined by both AND and OR
|
||||
//
|
||||
// - `>1.0.0 <2.0.0 || >3.0.0 !4.2.1` would match `1.2.3`, `1.9.9`, `3.1.1`, but not `4.2.1`, `2.1.1`
|
||||
func ParseRange(s string) (Range, error) {
|
||||
parts := splitAndTrim(s)
|
||||
orParts, err := splitORParts(parts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
expandedParts, err := expandWildcardVersion(orParts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var orFn Range
|
||||
for _, p := range expandedParts {
|
||||
var andFn Range
|
||||
for _, ap := range p {
|
||||
opStr, vStr, err := splitComparatorVersion(ap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vr, err := buildVersionRange(opStr, vStr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Could not parse Range %q: %s", ap, err)
|
||||
}
|
||||
rf := vr.rangeFunc()
|
||||
|
||||
// Set function
|
||||
if andFn == nil {
|
||||
andFn = rf
|
||||
} else { // Combine with existing function
|
||||
andFn = andFn.AND(rf)
|
||||
}
|
||||
}
|
||||
if orFn == nil {
|
||||
orFn = andFn
|
||||
} else {
|
||||
orFn = orFn.OR(andFn)
|
||||
}
|
||||
|
||||
}
|
||||
return orFn, nil
|
||||
}
|
||||
|
||||
// splitORParts splits the already cleaned parts by '||'.
|
||||
// Checks for invalid positions of the operator and returns an
|
||||
// error if found.
|
||||
func splitORParts(parts []string) ([][]string, error) {
|
||||
var ORparts [][]string
|
||||
last := 0
|
||||
for i, p := range parts {
|
||||
if p == "||" {
|
||||
if i == 0 {
|
||||
return nil, fmt.Errorf("First element in range is '||'")
|
||||
}
|
||||
ORparts = append(ORparts, parts[last:i])
|
||||
last = i + 1
|
||||
}
|
||||
}
|
||||
if last == len(parts) {
|
||||
return nil, fmt.Errorf("Last element in range is '||'")
|
||||
}
|
||||
ORparts = append(ORparts, parts[last:])
|
||||
return ORparts, nil
|
||||
}
|
||||
|
||||
// buildVersionRange takes a slice of 2: operator and version
|
||||
// and builds a versionRange, otherwise an error.
|
||||
func buildVersionRange(opStr, vStr string) (*versionRange, error) {
|
||||
c := parseComparator(opStr)
|
||||
if c == nil {
|
||||
return nil, fmt.Errorf("Could not parse comparator %q in %q", opStr, strings.Join([]string{opStr, vStr}, ""))
|
||||
}
|
||||
v, err := Parse(vStr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Could not parse version %q in %q: %s", vStr, strings.Join([]string{opStr, vStr}, ""), err)
|
||||
}
|
||||
|
||||
return &versionRange{
|
||||
v: v,
|
||||
c: c,
|
||||
}, nil
|
||||
|
||||
}
|
||||
|
||||
// inArray checks if a byte is contained in an array of bytes
|
||||
func inArray(s byte, list []byte) bool {
|
||||
for _, el := range list {
|
||||
if el == s {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// splitAndTrim splits a range string by spaces and cleans whitespaces
|
||||
func splitAndTrim(s string) (result []string) {
|
||||
last := 0
|
||||
var lastChar byte
|
||||
excludeFromSplit := []byte{'>', '<', '='}
|
||||
for i := 0; i < len(s); i++ {
|
||||
if s[i] == ' ' && !inArray(lastChar, excludeFromSplit) {
|
||||
if last < i-1 {
|
||||
result = append(result, s[last:i])
|
||||
}
|
||||
last = i + 1
|
||||
} else if s[i] != ' ' {
|
||||
lastChar = s[i]
|
||||
}
|
||||
}
|
||||
if last < len(s)-1 {
|
||||
result = append(result, s[last:])
|
||||
}
|
||||
|
||||
for i, v := range result {
|
||||
result[i] = strings.Replace(v, " ", "", -1)
|
||||
}
|
||||
|
||||
// parts := strings.Split(s, " ")
|
||||
// for _, x := range parts {
|
||||
// if s := strings.TrimSpace(x); len(s) != 0 {
|
||||
// result = append(result, s)
|
||||
// }
|
||||
// }
|
||||
return
|
||||
}
|
||||
|
||||
// splitComparatorVersion splits the comparator from the version.
|
||||
// Input must be free of leading or trailing spaces.
|
||||
func splitComparatorVersion(s string) (string, string, error) {
|
||||
i := strings.IndexFunc(s, unicode.IsDigit)
|
||||
if i == -1 {
|
||||
return "", "", fmt.Errorf("Could not get version from string: %q", s)
|
||||
}
|
||||
return strings.TrimSpace(s[0:i]), s[i:], nil
|
||||
}
|
||||
|
||||
// getWildcardType will return the type of wildcard that the
|
||||
// passed version contains
|
||||
func getWildcardType(vStr string) wildcardType {
|
||||
parts := strings.Split(vStr, ".")
|
||||
nparts := len(parts)
|
||||
wildcard := parts[nparts-1]
|
||||
|
||||
possibleWildcardType := wildcardTypefromInt(nparts)
|
||||
if wildcard == "x" {
|
||||
return possibleWildcardType
|
||||
}
|
||||
|
||||
return noneWildcard
|
||||
}
|
||||
|
||||
// createVersionFromWildcard will convert a wildcard version
|
||||
// into a regular version, replacing 'x's with '0's, handling
|
||||
// special cases like '1.x.x' and '1.x'
|
||||
func createVersionFromWildcard(vStr string) string {
|
||||
// handle 1.x.x
|
||||
vStr2 := strings.Replace(vStr, ".x.x", ".x", 1)
|
||||
vStr2 = strings.Replace(vStr2, ".x", ".0", 1)
|
||||
parts := strings.Split(vStr2, ".")
|
||||
|
||||
// handle 1.x
|
||||
if len(parts) == 2 {
|
||||
return vStr2 + ".0"
|
||||
}
|
||||
|
||||
return vStr2
|
||||
}
|
||||
|
||||
// incrementMajorVersion will increment the major version
|
||||
// of the passed version
|
||||
func incrementMajorVersion(vStr string) (string, error) {
|
||||
parts := strings.Split(vStr, ".")
|
||||
i, err := strconv.Atoi(parts[0])
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
parts[0] = strconv.Itoa(i + 1)
|
||||
|
||||
return strings.Join(parts, "."), nil
|
||||
}
|
||||
|
||||
// incrementMajorVersion will increment the minor version
|
||||
// of the passed version
|
||||
func incrementMinorVersion(vStr string) (string, error) {
|
||||
parts := strings.Split(vStr, ".")
|
||||
i, err := strconv.Atoi(parts[1])
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
parts[1] = strconv.Itoa(i + 1)
|
||||
|
||||
return strings.Join(parts, "."), nil
|
||||
}
|
||||
|
||||
// expandWildcardVersion will expand wildcards inside versions
|
||||
// following these rules:
|
||||
//
|
||||
// * when dealing with patch wildcards:
|
||||
// >= 1.2.x will become >= 1.2.0
|
||||
// <= 1.2.x will become < 1.3.0
|
||||
// > 1.2.x will become >= 1.3.0
|
||||
// < 1.2.x will become < 1.2.0
|
||||
// != 1.2.x will become < 1.2.0 >= 1.3.0
|
||||
//
|
||||
// * when dealing with minor wildcards:
|
||||
// >= 1.x will become >= 1.0.0
|
||||
// <= 1.x will become < 2.0.0
|
||||
// > 1.x will become >= 2.0.0
|
||||
// < 1.0 will become < 1.0.0
|
||||
// != 1.x will become < 1.0.0 >= 2.0.0
|
||||
//
|
||||
// * when dealing with wildcards without
|
||||
// version operator:
|
||||
// 1.2.x will become >= 1.2.0 < 1.3.0
|
||||
// 1.x will become >= 1.0.0 < 2.0.0
|
||||
func expandWildcardVersion(parts [][]string) ([][]string, error) {
|
||||
var expandedParts [][]string
|
||||
for _, p := range parts {
|
||||
var newParts []string
|
||||
for _, ap := range p {
|
||||
if strings.Index(ap, "x") != -1 {
|
||||
opStr, vStr, err := splitComparatorVersion(ap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
versionWildcardType := getWildcardType(vStr)
|
||||
flatVersion := createVersionFromWildcard(vStr)
|
||||
|
||||
var resultOperator string
|
||||
var shouldIncrementVersion bool
|
||||
switch opStr {
|
||||
case ">":
|
||||
resultOperator = ">="
|
||||
shouldIncrementVersion = true
|
||||
case ">=":
|
||||
resultOperator = ">="
|
||||
case "<":
|
||||
resultOperator = "<"
|
||||
case "<=":
|
||||
resultOperator = "<"
|
||||
shouldIncrementVersion = true
|
||||
case "", "=", "==":
|
||||
newParts = append(newParts, ">="+flatVersion)
|
||||
resultOperator = "<"
|
||||
shouldIncrementVersion = true
|
||||
case "!=", "!":
|
||||
newParts = append(newParts, "<"+flatVersion)
|
||||
resultOperator = ">="
|
||||
shouldIncrementVersion = true
|
||||
}
|
||||
|
||||
var resultVersion string
|
||||
if shouldIncrementVersion {
|
||||
switch versionWildcardType {
|
||||
case patchWildcard:
|
||||
resultVersion, _ = incrementMinorVersion(flatVersion)
|
||||
case minorWildcard:
|
||||
resultVersion, _ = incrementMajorVersion(flatVersion)
|
||||
}
|
||||
} else {
|
||||
resultVersion = flatVersion
|
||||
}
|
||||
|
||||
ap = resultOperator + resultVersion
|
||||
}
|
||||
newParts = append(newParts, ap)
|
||||
}
|
||||
expandedParts = append(expandedParts, newParts)
|
||||
}
|
||||
|
||||
return expandedParts, nil
|
||||
}
|
||||
|
||||
func parseComparator(s string) comparator {
|
||||
switch s {
|
||||
case "==":
|
||||
fallthrough
|
||||
case "":
|
||||
fallthrough
|
||||
case "=":
|
||||
return compEQ
|
||||
case ">":
|
||||
return compGT
|
||||
case ">=":
|
||||
return compGE
|
||||
case "<":
|
||||
return compLT
|
||||
case "<=":
|
||||
return compLE
|
||||
case "!":
|
||||
fallthrough
|
||||
case "!=":
|
||||
return compNE
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MustParseRange is like ParseRange but panics if the range cannot be parsed.
|
||||
func MustParseRange(s string) Range {
|
||||
r, err := ParseRange(s)
|
||||
if err != nil {
|
||||
panic(`semver: ParseRange(` + s + `): ` + err.Error())
|
||||
}
|
||||
return r
|
||||
}
|
|
@ -1,418 +0,0 @@
|
|||
package semver
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
numbers string = "0123456789"
|
||||
alphas = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-"
|
||||
alphanum = alphas + numbers
|
||||
)
|
||||
|
||||
// SpecVersion is the latest fully supported spec version of semver
|
||||
var SpecVersion = Version{
|
||||
Major: 2,
|
||||
Minor: 0,
|
||||
Patch: 0,
|
||||
}
|
||||
|
||||
// Version represents a semver compatible version
|
||||
type Version struct {
|
||||
Major uint64
|
||||
Minor uint64
|
||||
Patch uint64
|
||||
Pre []PRVersion
|
||||
Build []string //No Precendence
|
||||
}
|
||||
|
||||
// Version to string
|
||||
func (v Version) String() string {
|
||||
b := make([]byte, 0, 5)
|
||||
b = strconv.AppendUint(b, v.Major, 10)
|
||||
b = append(b, '.')
|
||||
b = strconv.AppendUint(b, v.Minor, 10)
|
||||
b = append(b, '.')
|
||||
b = strconv.AppendUint(b, v.Patch, 10)
|
||||
|
||||
if len(v.Pre) > 0 {
|
||||
b = append(b, '-')
|
||||
b = append(b, v.Pre[0].String()...)
|
||||
|
||||
for _, pre := range v.Pre[1:] {
|
||||
b = append(b, '.')
|
||||
b = append(b, pre.String()...)
|
||||
}
|
||||
}
|
||||
|
||||
if len(v.Build) > 0 {
|
||||
b = append(b, '+')
|
||||
b = append(b, v.Build[0]...)
|
||||
|
||||
for _, build := range v.Build[1:] {
|
||||
b = append(b, '.')
|
||||
b = append(b, build...)
|
||||
}
|
||||
}
|
||||
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// Equals checks if v is equal to o.
|
||||
func (v Version) Equals(o Version) bool {
|
||||
return (v.Compare(o) == 0)
|
||||
}
|
||||
|
||||
// EQ checks if v is equal to o.
|
||||
func (v Version) EQ(o Version) bool {
|
||||
return (v.Compare(o) == 0)
|
||||
}
|
||||
|
||||
// NE checks if v is not equal to o.
|
||||
func (v Version) NE(o Version) bool {
|
||||
return (v.Compare(o) != 0)
|
||||
}
|
||||
|
||||
// GT checks if v is greater than o.
|
||||
func (v Version) GT(o Version) bool {
|
||||
return (v.Compare(o) == 1)
|
||||
}
|
||||
|
||||
// GTE checks if v is greater than or equal to o.
|
||||
func (v Version) GTE(o Version) bool {
|
||||
return (v.Compare(o) >= 0)
|
||||
}
|
||||
|
||||
// GE checks if v is greater than or equal to o.
|
||||
func (v Version) GE(o Version) bool {
|
||||
return (v.Compare(o) >= 0)
|
||||
}
|
||||
|
||||
// LT checks if v is less than o.
|
||||
func (v Version) LT(o Version) bool {
|
||||
return (v.Compare(o) == -1)
|
||||
}
|
||||
|
||||
// LTE checks if v is less than or equal to o.
|
||||
func (v Version) LTE(o Version) bool {
|
||||
return (v.Compare(o) <= 0)
|
||||
}
|
||||
|
||||
// LE checks if v is less than or equal to o.
|
||||
func (v Version) LE(o Version) bool {
|
||||
return (v.Compare(o) <= 0)
|
||||
}
|
||||
|
||||
// Compare compares Versions v to o:
|
||||
// -1 == v is less than o
|
||||
// 0 == v is equal to o
|
||||
// 1 == v is greater than o
|
||||
func (v Version) Compare(o Version) int {
|
||||
if v.Major != o.Major {
|
||||
if v.Major > o.Major {
|
||||
return 1
|
||||
}
|
||||
return -1
|
||||
}
|
||||
if v.Minor != o.Minor {
|
||||
if v.Minor > o.Minor {
|
||||
return 1
|
||||
}
|
||||
return -1
|
||||
}
|
||||
if v.Patch != o.Patch {
|
||||
if v.Patch > o.Patch {
|
||||
return 1
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// Quick comparison if a version has no prerelease versions
|
||||
if len(v.Pre) == 0 && len(o.Pre) == 0 {
|
||||
return 0
|
||||
} else if len(v.Pre) == 0 && len(o.Pre) > 0 {
|
||||
return 1
|
||||
} else if len(v.Pre) > 0 && len(o.Pre) == 0 {
|
||||
return -1
|
||||
}
|
||||
|
||||
i := 0
|
||||
for ; i < len(v.Pre) && i < len(o.Pre); i++ {
|
||||
if comp := v.Pre[i].Compare(o.Pre[i]); comp == 0 {
|
||||
continue
|
||||
} else if comp == 1 {
|
||||
return 1
|
||||
} else {
|
||||
return -1
|
||||
}
|
||||
}
|
||||
|
||||
// If all pr versions are the equal but one has further prversion, this one greater
|
||||
if i == len(v.Pre) && i == len(o.Pre) {
|
||||
return 0
|
||||
} else if i == len(v.Pre) && i < len(o.Pre) {
|
||||
return -1
|
||||
} else {
|
||||
return 1
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Validate validates v and returns error in case
|
||||
func (v Version) Validate() error {
|
||||
// Major, Minor, Patch already validated using uint64
|
||||
|
||||
for _, pre := range v.Pre {
|
||||
if !pre.IsNum { //Numeric prerelease versions already uint64
|
||||
if len(pre.VersionStr) == 0 {
|
||||
return fmt.Errorf("Prerelease can not be empty %q", pre.VersionStr)
|
||||
}
|
||||
if !containsOnly(pre.VersionStr, alphanum) {
|
||||
return fmt.Errorf("Invalid character(s) found in prerelease %q", pre.VersionStr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, build := range v.Build {
|
||||
if len(build) == 0 {
|
||||
return fmt.Errorf("Build meta data can not be empty %q", build)
|
||||
}
|
||||
if !containsOnly(build, alphanum) {
|
||||
return fmt.Errorf("Invalid character(s) found in build meta data %q", build)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// New is an alias for Parse and returns a pointer, parses version string and returns a validated Version or error
|
||||
func New(s string) (vp *Version, err error) {
|
||||
v, err := Parse(s)
|
||||
vp = &v
|
||||
return
|
||||
}
|
||||
|
||||
// Make is an alias for Parse, parses version string and returns a validated Version or error
|
||||
func Make(s string) (Version, error) {
|
||||
return Parse(s)
|
||||
}
|
||||
|
||||
// ParseTolerant allows for certain version specifications that do not strictly adhere to semver
|
||||
// specs to be parsed by this library. It does so by normalizing versions before passing them to
|
||||
// Parse(). It currently trims spaces, removes a "v" prefix, and adds a 0 patch number to versions
|
||||
// with only major and minor components specified
|
||||
func ParseTolerant(s string) (Version, error) {
|
||||
s = strings.TrimSpace(s)
|
||||
s = strings.TrimPrefix(s, "v")
|
||||
|
||||
// Split into major.minor.(patch+pr+meta)
|
||||
parts := strings.SplitN(s, ".", 3)
|
||||
if len(parts) < 3 {
|
||||
if strings.ContainsAny(parts[len(parts)-1], "+-") {
|
||||
return Version{}, errors.New("Short version cannot contain PreRelease/Build meta data")
|
||||
}
|
||||
for len(parts) < 3 {
|
||||
parts = append(parts, "0")
|
||||
}
|
||||
s = strings.Join(parts, ".")
|
||||
}
|
||||
|
||||
return Parse(s)
|
||||
}
|
||||
|
||||
// Parse parses version string and returns a validated Version or error
|
||||
func Parse(s string) (Version, error) {
|
||||
if len(s) == 0 {
|
||||
return Version{}, errors.New("Version string empty")
|
||||
}
|
||||
|
||||
// Split into major.minor.(patch+pr+meta)
|
||||
parts := strings.SplitN(s, ".", 3)
|
||||
if len(parts) != 3 {
|
||||
return Version{}, errors.New("No Major.Minor.Patch elements found")
|
||||
}
|
||||
|
||||
// Major
|
||||
if !containsOnly(parts[0], numbers) {
|
||||
return Version{}, fmt.Errorf("Invalid character(s) found in major number %q", parts[0])
|
||||
}
|
||||
if hasLeadingZeroes(parts[0]) {
|
||||
return Version{}, fmt.Errorf("Major number must not contain leading zeroes %q", parts[0])
|
||||
}
|
||||
major, err := strconv.ParseUint(parts[0], 10, 64)
|
||||
if err != nil {
|
||||
return Version{}, err
|
||||
}
|
||||
|
||||
// Minor
|
||||
if !containsOnly(parts[1], numbers) {
|
||||
return Version{}, fmt.Errorf("Invalid character(s) found in minor number %q", parts[1])
|
||||
}
|
||||
if hasLeadingZeroes(parts[1]) {
|
||||
return Version{}, fmt.Errorf("Minor number must not contain leading zeroes %q", parts[1])
|
||||
}
|
||||
minor, err := strconv.ParseUint(parts[1], 10, 64)
|
||||
if err != nil {
|
||||
return Version{}, err
|
||||
}
|
||||
|
||||
v := Version{}
|
||||
v.Major = major
|
||||
v.Minor = minor
|
||||
|
||||
var build, prerelease []string
|
||||
patchStr := parts[2]
|
||||
|
||||
if buildIndex := strings.IndexRune(patchStr, '+'); buildIndex != -1 {
|
||||
build = strings.Split(patchStr[buildIndex+1:], ".")
|
||||
patchStr = patchStr[:buildIndex]
|
||||
}
|
||||
|
||||
if preIndex := strings.IndexRune(patchStr, '-'); preIndex != -1 {
|
||||
prerelease = strings.Split(patchStr[preIndex+1:], ".")
|
||||
patchStr = patchStr[:preIndex]
|
||||
}
|
||||
|
||||
if !containsOnly(patchStr, numbers) {
|
||||
return Version{}, fmt.Errorf("Invalid character(s) found in patch number %q", patchStr)
|
||||
}
|
||||
if hasLeadingZeroes(patchStr) {
|
||||
return Version{}, fmt.Errorf("Patch number must not contain leading zeroes %q", patchStr)
|
||||
}
|
||||
patch, err := strconv.ParseUint(patchStr, 10, 64)
|
||||
if err != nil {
|
||||
return Version{}, err
|
||||
}
|
||||
|
||||
v.Patch = patch
|
||||
|
||||
// Prerelease
|
||||
for _, prstr := range prerelease {
|
||||
parsedPR, err := NewPRVersion(prstr)
|
||||
if err != nil {
|
||||
return Version{}, err
|
||||
}
|
||||
v.Pre = append(v.Pre, parsedPR)
|
||||
}
|
||||
|
||||
// Build meta data
|
||||
for _, str := range build {
|
||||
if len(str) == 0 {
|
||||
return Version{}, errors.New("Build meta data is empty")
|
||||
}
|
||||
if !containsOnly(str, alphanum) {
|
||||
return Version{}, fmt.Errorf("Invalid character(s) found in build meta data %q", str)
|
||||
}
|
||||
v.Build = append(v.Build, str)
|
||||
}
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// MustParse is like Parse but panics if the version cannot be parsed.
|
||||
func MustParse(s string) Version {
|
||||
v, err := Parse(s)
|
||||
if err != nil {
|
||||
panic(`semver: Parse(` + s + `): ` + err.Error())
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// PRVersion represents a PreRelease Version
|
||||
type PRVersion struct {
|
||||
VersionStr string
|
||||
VersionNum uint64
|
||||
IsNum bool
|
||||
}
|
||||
|
||||
// NewPRVersion creates a new valid prerelease version
|
||||
func NewPRVersion(s string) (PRVersion, error) {
|
||||
if len(s) == 0 {
|
||||
return PRVersion{}, errors.New("Prerelease is empty")
|
||||
}
|
||||
v := PRVersion{}
|
||||
if containsOnly(s, numbers) {
|
||||
if hasLeadingZeroes(s) {
|
||||
return PRVersion{}, fmt.Errorf("Numeric PreRelease version must not contain leading zeroes %q", s)
|
||||
}
|
||||
num, err := strconv.ParseUint(s, 10, 64)
|
||||
|
||||
// Might never be hit, but just in case
|
||||
if err != nil {
|
||||
return PRVersion{}, err
|
||||
}
|
||||
v.VersionNum = num
|
||||
v.IsNum = true
|
||||
} else if containsOnly(s, alphanum) {
|
||||
v.VersionStr = s
|
||||
v.IsNum = false
|
||||
} else {
|
||||
return PRVersion{}, fmt.Errorf("Invalid character(s) found in prerelease %q", s)
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// IsNumeric checks if prerelease-version is numeric
|
||||
func (v PRVersion) IsNumeric() bool {
|
||||
return v.IsNum
|
||||
}
|
||||
|
||||
// Compare compares two PreRelease Versions v and o:
|
||||
// -1 == v is less than o
|
||||
// 0 == v is equal to o
|
||||
// 1 == v is greater than o
|
||||
func (v PRVersion) Compare(o PRVersion) int {
|
||||
if v.IsNum && !o.IsNum {
|
||||
return -1
|
||||
} else if !v.IsNum && o.IsNum {
|
||||
return 1
|
||||
} else if v.IsNum && o.IsNum {
|
||||
if v.VersionNum == o.VersionNum {
|
||||
return 0
|
||||
} else if v.VersionNum > o.VersionNum {
|
||||
return 1
|
||||
} else {
|
||||
return -1
|
||||
}
|
||||
} else { // both are Alphas
|
||||
if v.VersionStr == o.VersionStr {
|
||||
return 0
|
||||
} else if v.VersionStr > o.VersionStr {
|
||||
return 1
|
||||
} else {
|
||||
return -1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PreRelease version to string
|
||||
func (v PRVersion) String() string {
|
||||
if v.IsNum {
|
||||
return strconv.FormatUint(v.VersionNum, 10)
|
||||
}
|
||||
return v.VersionStr
|
||||
}
|
||||
|
||||
func containsOnly(s string, set string) bool {
|
||||
return strings.IndexFunc(s, func(r rune) bool {
|
||||
return !strings.ContainsRune(set, r)
|
||||
}) == -1
|
||||
}
|
||||
|
||||
func hasLeadingZeroes(s string) bool {
|
||||
return len(s) > 1 && s[0] == '0'
|
||||
}
|
||||
|
||||
// NewBuildVersion creates a new valid build version
|
||||
func NewBuildVersion(s string) (string, error) {
|
||||
if len(s) == 0 {
|
||||
return "", errors.New("Buildversion is empty")
|
||||
}
|
||||
if !containsOnly(s, alphanum) {
|
||||
return "", fmt.Errorf("Invalid character(s) found in build meta data %q", s)
|
||||
}
|
||||
return s, nil
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
package semver
|
||||
|
||||
import (
|
||||
"sort"
|
||||
)
|
||||
|
||||
// Versions represents multiple versions.
|
||||
type Versions []Version
|
||||
|
||||
// Len returns length of version collection
|
||||
func (s Versions) Len() int {
|
||||
return len(s)
|
||||
}
|
||||
|
||||
// Swap swaps two versions inside the collection by its indices
|
||||
func (s Versions) Swap(i, j int) {
|
||||
s[i], s[j] = s[j], s[i]
|
||||
}
|
||||
|
||||
// Less checks if version at index i is less than version at index j
|
||||
func (s Versions) Less(i, j int) bool {
|
||||
return s[i].LT(s[j])
|
||||
}
|
||||
|
||||
// Sort sorts a slice of versions
|
||||
func Sort(versions []Version) {
|
||||
sort.Sort(Versions(versions))
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
package semver
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Scan implements the database/sql.Scanner interface.
|
||||
func (v *Version) Scan(src interface{}) (err error) {
|
||||
var str string
|
||||
switch src := src.(type) {
|
||||
case string:
|
||||
str = src
|
||||
case []byte:
|
||||
str = string(src)
|
||||
default:
|
||||
return fmt.Errorf("Version.Scan: cannot convert %T to string.", src)
|
||||
}
|
||||
|
||||
if t, err := Parse(str); err == nil {
|
||||
*v = t
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Value implements the database/sql/driver.Valuer interface.
|
||||
func (v Version) Value() (driver.Value, error) {
|
||||
return v.String(), nil
|
||||
}
|
|
@ -167,6 +167,19 @@ func Marshal(v any) ([]byte, error) {
|
|||
return buf, nil
|
||||
}
|
||||
|
||||
func MarshalEscaped(v any, escape bool) ([]byte, error) {
|
||||
e := newEncodeState()
|
||||
defer encodeStatePool.Put(e)
|
||||
|
||||
err := e.marshal(v, encOpts{escapeHTML: escape})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buf := append([]byte(nil), e.Bytes()...)
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
// MarshalIndent is like Marshal but applies Indent to format the output.
|
||||
// Each JSON element in the output will begin on a new line beginning with prefix
|
||||
// followed by one or more copies of indent according to the indentation nesting.
|
||||
|
|
|
@ -6,7 +6,7 @@ package json
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"encoding/json"
|
||||
"io"
|
||||
)
|
||||
|
||||
|
@ -259,27 +259,7 @@ func (enc *Encoder) SetEscapeHTML(on bool) {
|
|||
// RawMessage is a raw encoded JSON value.
|
||||
// It implements Marshaler and Unmarshaler and can
|
||||
// be used to delay JSON decoding or precompute a JSON encoding.
|
||||
type RawMessage []byte
|
||||
|
||||
// MarshalJSON returns m as the JSON encoding of m.
|
||||
func (m RawMessage) MarshalJSON() ([]byte, error) {
|
||||
if m == nil {
|
||||
return []byte("null"), nil
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON sets *m to a copy of data.
|
||||
func (m *RawMessage) UnmarshalJSON(data []byte) error {
|
||||
if m == nil {
|
||||
return errors.New("json.RawMessage: UnmarshalJSON on nil pointer")
|
||||
}
|
||||
*m = append((*m)[0:0], data...)
|
||||
return nil
|
||||
}
|
||||
|
||||
var _ Marshaler = (*RawMessage)(nil)
|
||||
var _ Unmarshaler = (*RawMessage)(nil)
|
||||
type RawMessage = json.RawMessage
|
||||
|
||||
// A Token holds a value of one of these types:
|
||||
//
|
||||
|
|
|
@ -10,26 +10,26 @@ import (
|
|||
"github.com/evanphx/json-patch/v5/internal/json"
|
||||
)
|
||||
|
||||
func merge(cur, patch *lazyNode, mergeMerge bool) *lazyNode {
|
||||
curDoc, err := cur.intoDoc()
|
||||
func merge(cur, patch *lazyNode, mergeMerge bool, options *ApplyOptions) *lazyNode {
|
||||
curDoc, err := cur.intoDoc(options)
|
||||
|
||||
if err != nil {
|
||||
pruneNulls(patch)
|
||||
pruneNulls(patch, options)
|
||||
return patch
|
||||
}
|
||||
|
||||
patchDoc, err := patch.intoDoc()
|
||||
patchDoc, err := patch.intoDoc(options)
|
||||
|
||||
if err != nil {
|
||||
return patch
|
||||
}
|
||||
|
||||
mergeDocs(curDoc, patchDoc, mergeMerge)
|
||||
mergeDocs(curDoc, patchDoc, mergeMerge, options)
|
||||
|
||||
return cur
|
||||
}
|
||||
|
||||
func mergeDocs(doc, patch *partialDoc, mergeMerge bool) {
|
||||
func mergeDocs(doc, patch *partialDoc, mergeMerge bool, options *ApplyOptions) {
|
||||
for k, v := range patch.obj {
|
||||
if v == nil {
|
||||
if mergeMerge {
|
||||
|
@ -45,55 +45,55 @@ func mergeDocs(doc, patch *partialDoc, mergeMerge bool) {
|
|||
}
|
||||
doc.obj[k] = nil
|
||||
} else {
|
||||
_ = doc.remove(k, &ApplyOptions{})
|
||||
_ = doc.remove(k, options)
|
||||
}
|
||||
} else {
|
||||
cur, ok := doc.obj[k]
|
||||
|
||||
if !ok || cur == nil {
|
||||
if !mergeMerge {
|
||||
pruneNulls(v)
|
||||
pruneNulls(v, options)
|
||||
}
|
||||
_ = doc.set(k, v, &ApplyOptions{})
|
||||
_ = doc.set(k, v, options)
|
||||
} else {
|
||||
_ = doc.set(k, merge(cur, v, mergeMerge), &ApplyOptions{})
|
||||
_ = doc.set(k, merge(cur, v, mergeMerge, options), options)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func pruneNulls(n *lazyNode) {
|
||||
sub, err := n.intoDoc()
|
||||
func pruneNulls(n *lazyNode, options *ApplyOptions) {
|
||||
sub, err := n.intoDoc(options)
|
||||
|
||||
if err == nil {
|
||||
pruneDocNulls(sub)
|
||||
pruneDocNulls(sub, options)
|
||||
} else {
|
||||
ary, err := n.intoAry()
|
||||
|
||||
if err == nil {
|
||||
pruneAryNulls(ary)
|
||||
pruneAryNulls(ary, options)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func pruneDocNulls(doc *partialDoc) *partialDoc {
|
||||
func pruneDocNulls(doc *partialDoc, options *ApplyOptions) *partialDoc {
|
||||
for k, v := range doc.obj {
|
||||
if v == nil {
|
||||
_ = doc.remove(k, &ApplyOptions{})
|
||||
} else {
|
||||
pruneNulls(v)
|
||||
pruneNulls(v, options)
|
||||
}
|
||||
}
|
||||
|
||||
return doc
|
||||
}
|
||||
|
||||
func pruneAryNulls(ary *partialArray) *partialArray {
|
||||
func pruneAryNulls(ary *partialArray, options *ApplyOptions) *partialArray {
|
||||
newAry := []*lazyNode{}
|
||||
|
||||
for _, v := range ary.nodes {
|
||||
if v != nil {
|
||||
pruneNulls(v)
|
||||
pruneNulls(v, options)
|
||||
}
|
||||
newAry = append(newAry, v)
|
||||
}
|
||||
|
@ -128,11 +128,17 @@ func doMergePatch(docData, patchData []byte, mergeMerge bool) ([]byte, error) {
|
|||
return nil, errBadJSONPatch
|
||||
}
|
||||
|
||||
doc := &partialDoc{}
|
||||
options := NewApplyOptions()
|
||||
|
||||
doc := &partialDoc{
|
||||
opts: options,
|
||||
}
|
||||
|
||||
docErr := doc.UnmarshalJSON(docData)
|
||||
|
||||
patch := &partialDoc{}
|
||||
patch := &partialDoc{
|
||||
opts: options,
|
||||
}
|
||||
|
||||
patchErr := patch.UnmarshalJSON(patchData)
|
||||
|
||||
|
@ -158,7 +164,7 @@ func doMergePatch(docData, patchData []byte, mergeMerge bool) ([]byte, error) {
|
|||
if mergeMerge {
|
||||
doc = patch
|
||||
} else {
|
||||
doc = pruneDocNulls(patch)
|
||||
doc = pruneDocNulls(patch, options)
|
||||
}
|
||||
} else {
|
||||
patchAry := &partialArray{}
|
||||
|
@ -172,7 +178,7 @@ func doMergePatch(docData, patchData []byte, mergeMerge bool) ([]byte, error) {
|
|||
return nil, errBadJSONPatch
|
||||
}
|
||||
|
||||
pruneAryNulls(patchAry)
|
||||
pruneAryNulls(patchAry, options)
|
||||
|
||||
out, patchErr := json.Marshal(patchAry.nodes)
|
||||
|
||||
|
@ -183,7 +189,7 @@ func doMergePatch(docData, patchData []byte, mergeMerge bool) ([]byte, error) {
|
|||
return out, nil
|
||||
}
|
||||
} else {
|
||||
mergeDocs(doc, patch, mergeMerge)
|
||||
mergeDocs(doc, patch, mergeMerge, options)
|
||||
}
|
||||
|
||||
return json.Marshal(doc)
|
||||
|
|
|
@ -38,6 +38,8 @@ var (
|
|||
ErrInvalid = errors.New("invalid state detected")
|
||||
ErrInvalidIndex = errors.New("invalid index referenced")
|
||||
|
||||
ErrExpectedObject = errors.New("invalid value, expected object")
|
||||
|
||||
rawJSONArray = []byte("[]")
|
||||
rawJSONObject = []byte("{}")
|
||||
rawJSONNull = []byte("null")
|
||||
|
@ -60,6 +62,8 @@ type partialDoc struct {
|
|||
self *lazyNode
|
||||
keys []string
|
||||
obj map[string]*lazyNode
|
||||
|
||||
opts *ApplyOptions
|
||||
}
|
||||
|
||||
type partialArray struct {
|
||||
|
@ -90,6 +94,8 @@ type ApplyOptions struct {
|
|||
// EnsurePathExistsOnAdd instructs json-patch to recursively create the missing parts of path on "add" operation.
|
||||
// Default to false.
|
||||
EnsurePathExistsOnAdd bool
|
||||
|
||||
EscapeHTML bool
|
||||
}
|
||||
|
||||
// NewApplyOptions creates a default set of options for calls to ApplyWithOptions.
|
||||
|
@ -99,6 +105,7 @@ func NewApplyOptions() *ApplyOptions {
|
|||
AccumulatedCopySizeLimit: AccumulatedCopySizeLimit,
|
||||
AllowMissingPathOnRemove: false,
|
||||
EnsurePathExistsOnAdd: false,
|
||||
EscapeHTML: true,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -134,16 +141,28 @@ func (n *lazyNode) UnmarshalJSON(data []byte) error {
|
|||
}
|
||||
|
||||
func (n *partialDoc) TrustMarshalJSON(buf *bytes.Buffer) error {
|
||||
if n.obj == nil {
|
||||
return ErrExpectedObject
|
||||
}
|
||||
|
||||
if err := buf.WriteByte('{'); err != nil {
|
||||
return err
|
||||
}
|
||||
escaped := true
|
||||
|
||||
// n.opts should always be set, but in case we missed a case,
|
||||
// guard.
|
||||
if n.opts != nil {
|
||||
escaped = n.opts.EscapeHTML
|
||||
}
|
||||
|
||||
for i, k := range n.keys {
|
||||
if i > 0 {
|
||||
if err := buf.WriteByte(','); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
key, err := json.Marshal(k)
|
||||
key, err := json.MarshalEscaped(k, escaped)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -153,7 +172,7 @@ func (n *partialDoc) TrustMarshalJSON(buf *bytes.Buffer) error {
|
|||
if err := buf.WriteByte(':'); err != nil {
|
||||
return err
|
||||
}
|
||||
value, err := json.Marshal(n.obj[k])
|
||||
value, err := json.MarshalEscaped(n.obj[k], escaped)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -194,11 +213,11 @@ func (n *partialArray) RedirectMarshalJSON() (interface{}, error) {
|
|||
return n.nodes, nil
|
||||
}
|
||||
|
||||
func deepCopy(src *lazyNode) (*lazyNode, int, error) {
|
||||
func deepCopy(src *lazyNode, options *ApplyOptions) (*lazyNode, int, error) {
|
||||
if src == nil {
|
||||
return nil, 0, nil
|
||||
}
|
||||
a, err := json.Marshal(src)
|
||||
a, err := json.MarshalEscaped(src, options.EscapeHTML)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
@ -216,7 +235,7 @@ func (n *lazyNode) nextByte() byte {
|
|||
return s[0]
|
||||
}
|
||||
|
||||
func (n *lazyNode) intoDoc() (*partialDoc, error) {
|
||||
func (n *lazyNode) intoDoc(options *ApplyOptions) (*partialDoc, error) {
|
||||
if n.which == eDoc {
|
||||
return n.doc, nil
|
||||
}
|
||||
|
@ -235,6 +254,7 @@ func (n *lazyNode) intoDoc() (*partialDoc, error) {
|
|||
return nil, ErrInvalid
|
||||
}
|
||||
|
||||
n.doc.opts = options
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -545,7 +565,7 @@ func findObject(pd *container, path string, options *ApplyOptions) (container, s
|
|||
return nil, ""
|
||||
}
|
||||
} else {
|
||||
doc, err = next.intoDoc()
|
||||
doc, err = next.intoDoc(options)
|
||||
|
||||
if err != nil {
|
||||
return nil, ""
|
||||
|
@ -557,6 +577,10 @@ func findObject(pd *container, path string, options *ApplyOptions) (container, s
|
|||
}
|
||||
|
||||
func (d *partialDoc) set(key string, val *lazyNode, options *ApplyOptions) error {
|
||||
if d.obj == nil {
|
||||
return ErrExpectedObject
|
||||
}
|
||||
|
||||
found := false
|
||||
for _, k := range d.keys {
|
||||
if k == key {
|
||||
|
@ -579,6 +603,11 @@ func (d *partialDoc) get(key string, options *ApplyOptions) (*lazyNode, error) {
|
|||
if key == "" {
|
||||
return d.self, nil
|
||||
}
|
||||
|
||||
if d.obj == nil {
|
||||
return nil, ErrExpectedObject
|
||||
}
|
||||
|
||||
v, ok := d.obj[key]
|
||||
if !ok {
|
||||
return v, errors.Wrapf(ErrMissing, "unable to get nonexistent key: %s", key)
|
||||
|
@ -587,6 +616,10 @@ func (d *partialDoc) get(key string, options *ApplyOptions) (*lazyNode, error) {
|
|||
}
|
||||
|
||||
func (d *partialDoc) remove(key string, options *ApplyOptions) error {
|
||||
if d.obj == nil {
|
||||
return ErrExpectedObject
|
||||
}
|
||||
|
||||
_, ok := d.obj[key]
|
||||
if !ok {
|
||||
if options.AllowMissingPathOnRemove {
|
||||
|
@ -750,6 +783,7 @@ func (p Patch) add(doc *container, op Operation, options *ApplyOptions) error {
|
|||
} else {
|
||||
pd = &partialDoc{
|
||||
self: val,
|
||||
opts: options,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -855,7 +889,7 @@ func ensurePathExists(pd *container, path string, options *ApplyOptions) error {
|
|||
newNode := newLazyNode(newRawMessage(rawJSONObject))
|
||||
|
||||
doc.add(part, newNode, options)
|
||||
doc, err = newNode.intoDoc()
|
||||
doc, err = newNode.intoDoc(options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -868,7 +902,7 @@ func ensurePathExists(pd *container, path string, options *ApplyOptions) error {
|
|||
return err
|
||||
}
|
||||
} else {
|
||||
doc, err = target.intoDoc()
|
||||
doc, err = target.intoDoc(options)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -954,6 +988,8 @@ func (p Patch) replace(doc *container, op Operation, options *ApplyOptions) erro
|
|||
if !val.tryAry() {
|
||||
return errors.Wrapf(err, "replace operation value must be object or array")
|
||||
}
|
||||
} else {
|
||||
val.doc.opts = options
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1115,7 +1151,7 @@ func (p Patch) copy(doc *container, op Operation, accumulatedCopySize *int64, op
|
|||
return errors.Wrapf(ErrMissing, "copy operation does not apply: doc is missing destination path: %s", path)
|
||||
}
|
||||
|
||||
valCopy, sz, err := deepCopy(val)
|
||||
valCopy, sz, err := deepCopy(val, options)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error while performing deep copy")
|
||||
}
|
||||
|
@ -1202,6 +1238,7 @@ func (p Patch) ApplyIndentWithOptions(doc []byte, indent string, options *ApplyO
|
|||
} else {
|
||||
pd = &partialDoc{
|
||||
self: self,
|
||||
opts: options,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1238,11 +1275,18 @@ func (p Patch) ApplyIndentWithOptions(doc []byte, indent string, options *ApplyO
|
|||
}
|
||||
}
|
||||
|
||||
if indent != "" {
|
||||
return json.MarshalIndent(pd, "", indent)
|
||||
data, err := json.MarshalEscaped(pd, options.EscapeHTML)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return json.Marshal(pd)
|
||||
if indent == "" {
|
||||
return data, nil
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
json.Indent(&buf, data, "", indent)
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// From http://tools.ietf.org/html/rfc6901#section-4 :
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
//go:build (darwin || freebsd || openbsd || netbsd || dragonfly || hurd) && !appengine
|
||||
//go:build (darwin || freebsd || openbsd || netbsd || dragonfly || hurd) && !appengine && !tinygo
|
||||
// +build darwin freebsd openbsd netbsd dragonfly hurd
|
||||
// +build !appengine
|
||||
// +build !tinygo
|
||||
|
||||
package isatty
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
//go:build appengine || js || nacl || wasm
|
||||
// +build appengine js nacl wasm
|
||||
//go:build (appengine || js || nacl || tinygo || wasm) && !windows
|
||||
// +build appengine js nacl tinygo wasm
|
||||
// +build !windows
|
||||
|
||||
package isatty
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
//go:build (linux || aix || zos) && !appengine
|
||||
//go:build (linux || aix || zos) && !appengine && !tinygo
|
||||
// +build linux aix zos
|
||||
// +build !appengine
|
||||
// +build !tinygo
|
||||
|
||||
package isatty
|
||||
|
||||
|
|
|
@ -1,3 +1,56 @@
|
|||
## 2.17.1
|
||||
|
||||
### Fixes
|
||||
- If the user sets --seed=0, make sure all parallel nodes get the same seed [af0330d]
|
||||
|
||||
## 2.17.0
|
||||
|
||||
### Features
|
||||
|
||||
- add `--github-output` for nicer output in github actions [e8a2056]
|
||||
|
||||
### Maintenance
|
||||
|
||||
- fix typo in core_dsl.go [977bc6f]
|
||||
- Fix typo in docs [e297e7b]
|
||||
|
||||
## 2.16.0
|
||||
|
||||
### Features
|
||||
- add SpecContext to reporting nodes
|
||||
|
||||
### Fixes
|
||||
- merge coverages instead of combining them (#1329) (#1340) [23f0cc5]
|
||||
- core_dsl: disable Getwd() with environment variable (#1357) [cd418b7]
|
||||
|
||||
### Maintenance
|
||||
- docs/index.md: Typo [2cebe8d]
|
||||
- fix docs [06de431]
|
||||
- chore: test with Go 1.22 (#1352) [898cba9]
|
||||
- Bump golang.org/x/tools from 0.16.1 to 0.17.0 (#1336) [17ae120]
|
||||
- Bump golang.org/x/sys from 0.15.0 to 0.16.0 (#1327) [5a179ed]
|
||||
- Bump github.com/go-logr/logr from 1.3.0 to 1.4.1 (#1321) [a1e6b69]
|
||||
- Bump github-pages and jekyll-feed in /docs (#1351) [d52951d]
|
||||
- Fix docs for handling failures in goroutines (#1339) [4471b2e]
|
||||
|
||||
## 2.15.0
|
||||
|
||||
### Features
|
||||
|
||||
- JUnit reports now interpret Label(owner:X) and set owner to X. [8f3bd70]
|
||||
- include cancellation reason when cancelling spec context [96e915c]
|
||||
|
||||
### Fixes
|
||||
|
||||
- emit output of failed go tool cover invocation so users can try to debug things for themselves [c245d09]
|
||||
- fix outline when using nodot in ginkgo v2 [dca77c8]
|
||||
- Document areas where GinkgoT() behaves differently from testing.T [dbaf18f]
|
||||
- bugfix(docs): use Unsetenv instead of Clearenv (#1337) [6f67a14]
|
||||
|
||||
### Maintenance
|
||||
|
||||
- Bump to go 1.20 [4fcd0b3]
|
||||
|
||||
## 2.14.0
|
||||
|
||||
### Features
|
||||
|
|
|
@ -292,7 +292,7 @@ func RunSpecs(t GinkgoTestingT, description string, args ...interface{}) bool {
|
|||
|
||||
err = global.Suite.BuildTree()
|
||||
exitIfErr(err)
|
||||
suitePath, err := os.Getwd()
|
||||
suitePath, err := getwd()
|
||||
exitIfErr(err)
|
||||
suitePath, err = filepath.Abs(suitePath)
|
||||
exitIfErr(err)
|
||||
|
@ -345,6 +345,15 @@ func extractSuiteConfiguration(args []interface{}) Labels {
|
|||
return suiteLabels
|
||||
}
|
||||
|
||||
func getwd() (string, error) {
|
||||
if !strings.EqualFold(os.Getenv("GINKGO_PRESERVE_CACHE"), "true") {
|
||||
// Getwd calls os.Getenv("PWD"), which breaks test caching if the cache
|
||||
// is shared between two different directories with the same test code.
|
||||
return os.Getwd()
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
/*
|
||||
PreviewSpecs walks the testing tree and produces a report without actually invoking the specs.
|
||||
See http://onsi.github.io/ginkgo/#previewing-specs for more information.
|
||||
|
@ -369,7 +378,7 @@ func PreviewSpecs(description string, args ...any) Report {
|
|||
|
||||
err = global.Suite.BuildTree()
|
||||
exitIfErr(err)
|
||||
suitePath, err := os.Getwd()
|
||||
suitePath, err := getwd()
|
||||
exitIfErr(err)
|
||||
suitePath, err = filepath.Abs(suitePath)
|
||||
exitIfErr(err)
|
||||
|
@ -783,8 +792,8 @@ DeferCleanup can be passed:
|
|||
For example:
|
||||
|
||||
BeforeEach(func() {
|
||||
DeferCleanup(os.SetEnv, "FOO", os.GetEnv("FOO"))
|
||||
os.SetEnv("FOO", "BAR")
|
||||
DeferCleanup(os.Setenv, "FOO", os.GetEnv("FOO"))
|
||||
os.Setenv("FOO", "BAR")
|
||||
})
|
||||
|
||||
will register a cleanup handler that will set the environment variable "FOO" to its current value (obtained by os.GetEnv("FOO")) after the spec runs and then sets the environment variable "FOO" to "BAR" for the current spec.
|
||||
|
|
|
@ -0,0 +1,129 @@
|
|||
// Copyright (c) 2015, Wade Simmons
|
||||
// All rights reserved.
|
||||
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
|
||||
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||
// list of conditions and the following disclaimer.
|
||||
// 2. 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.
|
||||
|
||||
// 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.
|
||||
|
||||
// Package gocovmerge takes the results from multiple `go test -coverprofile`
|
||||
// runs and merges them into one profile
|
||||
|
||||
// this file was originally taken from the gocovmerge project
|
||||
// see also: https://go.shabbyrobe.org/gocovmerge
|
||||
package internal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
|
||||
"golang.org/x/tools/cover"
|
||||
)
|
||||
|
||||
func AddCoverProfile(profiles []*cover.Profile, p *cover.Profile) []*cover.Profile {
|
||||
i := sort.Search(len(profiles), func(i int) bool { return profiles[i].FileName >= p.FileName })
|
||||
if i < len(profiles) && profiles[i].FileName == p.FileName {
|
||||
MergeCoverProfiles(profiles[i], p)
|
||||
} else {
|
||||
profiles = append(profiles, nil)
|
||||
copy(profiles[i+1:], profiles[i:])
|
||||
profiles[i] = p
|
||||
}
|
||||
return profiles
|
||||
}
|
||||
|
||||
func DumpCoverProfiles(profiles []*cover.Profile, out io.Writer) error {
|
||||
if len(profiles) == 0 {
|
||||
return nil
|
||||
}
|
||||
if _, err := fmt.Fprintf(out, "mode: %s\n", profiles[0].Mode); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, p := range profiles {
|
||||
for _, b := range p.Blocks {
|
||||
if _, err := fmt.Fprintf(out, "%s:%d.%d,%d.%d %d %d\n", p.FileName, b.StartLine, b.StartCol, b.EndLine, b.EndCol, b.NumStmt, b.Count); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func MergeCoverProfiles(into *cover.Profile, merge *cover.Profile) error {
|
||||
if into.Mode != merge.Mode {
|
||||
return fmt.Errorf("cannot merge profiles with different modes")
|
||||
}
|
||||
// Since the blocks are sorted, we can keep track of where the last block
|
||||
// was inserted and only look at the blocks after that as targets for merge
|
||||
startIndex := 0
|
||||
for _, b := range merge.Blocks {
|
||||
var err error
|
||||
startIndex, err = mergeProfileBlock(into, b, startIndex)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func mergeProfileBlock(p *cover.Profile, pb cover.ProfileBlock, startIndex int) (int, error) {
|
||||
sortFunc := func(i int) bool {
|
||||
pi := p.Blocks[i+startIndex]
|
||||
return pi.StartLine >= pb.StartLine && (pi.StartLine != pb.StartLine || pi.StartCol >= pb.StartCol)
|
||||
}
|
||||
|
||||
i := 0
|
||||
if sortFunc(i) != true {
|
||||
i = sort.Search(len(p.Blocks)-startIndex, sortFunc)
|
||||
}
|
||||
|
||||
i += startIndex
|
||||
if i < len(p.Blocks) && p.Blocks[i].StartLine == pb.StartLine && p.Blocks[i].StartCol == pb.StartCol {
|
||||
if p.Blocks[i].EndLine != pb.EndLine || p.Blocks[i].EndCol != pb.EndCol {
|
||||
return i, fmt.Errorf("gocovmerge: overlapping merge %v %v %v", p.FileName, p.Blocks[i], pb)
|
||||
}
|
||||
switch p.Mode {
|
||||
case "set":
|
||||
p.Blocks[i].Count |= pb.Count
|
||||
case "count", "atomic":
|
||||
p.Blocks[i].Count += pb.Count
|
||||
default:
|
||||
return i, fmt.Errorf("gocovmerge: unsupported covermode '%s'", p.Mode)
|
||||
}
|
||||
|
||||
} else {
|
||||
if i > 0 {
|
||||
pa := p.Blocks[i-1]
|
||||
if pa.EndLine >= pb.EndLine && (pa.EndLine != pb.EndLine || pa.EndCol > pb.EndCol) {
|
||||
return i, fmt.Errorf("gocovmerge: overlap before %v %v %v", p.FileName, pa, pb)
|
||||
}
|
||||
}
|
||||
if i < len(p.Blocks)-1 {
|
||||
pa := p.Blocks[i+1]
|
||||
if pa.StartLine <= pb.StartLine && (pa.StartLine != pb.StartLine || pa.StartCol < pb.StartCol) {
|
||||
return i, fmt.Errorf("gocovmerge: overlap after %v %v %v", p.FileName, pa, pb)
|
||||
}
|
||||
}
|
||||
p.Blocks = append(p.Blocks, cover.ProfileBlock{})
|
||||
copy(p.Blocks[i+1:], p.Blocks[i:])
|
||||
p.Blocks[i] = pb
|
||||
}
|
||||
|
||||
return i + 1, nil
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
package internal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
@ -12,6 +11,7 @@ import (
|
|||
"github.com/google/pprof/profile"
|
||||
"github.com/onsi/ginkgo/v2/reporters"
|
||||
"github.com/onsi/ginkgo/v2/types"
|
||||
"golang.org/x/tools/cover"
|
||||
)
|
||||
|
||||
func AbsPathForGeneratedAsset(assetName string, suite TestSuite, cliConfig types.CLIConfig, process int) string {
|
||||
|
@ -144,38 +144,26 @@ func FinalizeProfilesAndReportsForSuites(suites TestSuites, cliConfig types.CLIC
|
|||
return messages, nil
|
||||
}
|
||||
|
||||
//loads each profile, combines them, deletes them, stores them in destination
|
||||
// loads each profile, merges them, deletes them, stores them in destination
|
||||
func MergeAndCleanupCoverProfiles(profiles []string, destination string) error {
|
||||
combined := &bytes.Buffer{}
|
||||
modeRegex := regexp.MustCompile(`^mode: .*\n`)
|
||||
for i, profile := range profiles {
|
||||
contents, err := os.ReadFile(profile)
|
||||
var merged []*cover.Profile
|
||||
for _, file := range profiles {
|
||||
parsedProfiles, err := cover.ParseProfiles(file)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to read coverage file %s:\n%s", profile, err.Error())
|
||||
return err
|
||||
}
|
||||
os.Remove(profile)
|
||||
|
||||
// remove the cover mode line from every file
|
||||
// except the first one
|
||||
if i > 0 {
|
||||
contents = modeRegex.ReplaceAll(contents, []byte{})
|
||||
}
|
||||
|
||||
_, err = combined.Write(contents)
|
||||
|
||||
// Add a newline to the end of every file if missing.
|
||||
if err == nil && len(contents) > 0 && contents[len(contents)-1] != '\n' {
|
||||
_, err = combined.Write([]byte("\n"))
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to append to coverprofile:\n%s", err.Error())
|
||||
os.Remove(file)
|
||||
for _, p := range parsedProfiles {
|
||||
merged = AddCoverProfile(merged, p)
|
||||
}
|
||||
}
|
||||
|
||||
err := os.WriteFile(destination, combined.Bytes(), 0666)
|
||||
dst, err := os.OpenFile(destination, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to create combined cover profile:\n%s", err.Error())
|
||||
return err
|
||||
}
|
||||
err = DumpCoverProfiles(merged, dst)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -184,7 +172,7 @@ func GetCoverageFromCoverProfile(profile string) (float64, error) {
|
|||
cmd := exec.Command("go", "tool", "cover", "-func", profile)
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("Could not process Coverprofile %s: %s", profile, err.Error())
|
||||
return 0, fmt.Errorf("Could not process Coverprofile %s: %s - %s", profile, err.Error(), string(output))
|
||||
}
|
||||
re := regexp.MustCompile(`total:\s*\(statements\)\s*(\d*\.\d*)\%`)
|
||||
matches := re.FindStringSubmatch(string(output))
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
package outline
|
||||
|
||||
import (
|
||||
"github.com/onsi/ginkgo/v2/types"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"strconv"
|
||||
|
||||
"github.com/onsi/ginkgo/v2/types"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
|
@ -28,14 +28,7 @@ func packageNameForImport(f *ast.File, path string) *string {
|
|||
}
|
||||
name := spec.Name.String()
|
||||
if name == "<nil>" {
|
||||
// If the package name is not explicitly specified,
|
||||
// make an educated guess. This is not guaranteed to be correct.
|
||||
lastSlash := strings.LastIndex(path, "/")
|
||||
if lastSlash == -1 {
|
||||
name = path
|
||||
} else {
|
||||
name = path[lastSlash+1:]
|
||||
}
|
||||
name = "ginkgo"
|
||||
}
|
||||
if name == "." {
|
||||
name = ""
|
||||
|
|
|
@ -15,6 +15,11 @@ GinkgoT() is analogous to *testing.T and implements the majority of *testing.T's
|
|||
GinkgoT() takes an optional offset argument that can be used to get the
|
||||
correct line number associated with the failure - though you do not need to use this if you call GinkgoHelper() or GinkgoT().Helper() appropriately
|
||||
|
||||
GinkgoT() attempts to mimic the behavior of `testing.T` with the exception of the following:
|
||||
|
||||
- Error/Errorf: failures in Ginkgo always immediately stop execution and there is no mechanism to log a failure without aborting the test. As such Error/Errorf are equivalent to Fatal/Fatalf.
|
||||
- Parallel() is a no-op as Ginkgo's multi-process parallelism model is substantially different from go test's in-process model.
|
||||
|
||||
You can learn more here: https://onsi.github.io/ginkgo/#using-third-party-libraries
|
||||
*/
|
||||
func GinkgoT(optionalOffset ...int) FullGinkgoTInterface {
|
||||
|
|
|
@ -5,9 +5,8 @@ import (
|
|||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/onsi/ginkgo/v2/types"
|
||||
)
|
||||
|
@ -16,8 +15,8 @@ var _global_node_id_counter = uint(0)
|
|||
var _global_id_mutex = &sync.Mutex{}
|
||||
|
||||
func UniqueNodeID() uint {
|
||||
//There's a reace in the internal integration tests if we don't make
|
||||
//accessing _global_node_id_counter safe across goroutines.
|
||||
// There's a reace in the internal integration tests if we don't make
|
||||
// accessing _global_node_id_counter safe across goroutines.
|
||||
_global_id_mutex.Lock()
|
||||
defer _global_id_mutex.Unlock()
|
||||
_global_node_id_counter += 1
|
||||
|
@ -44,8 +43,8 @@ type Node struct {
|
|||
SynchronizedAfterSuiteProc1Body func(SpecContext)
|
||||
SynchronizedAfterSuiteProc1BodyHasContext bool
|
||||
|
||||
ReportEachBody func(types.SpecReport)
|
||||
ReportSuiteBody func(types.Report)
|
||||
ReportEachBody func(SpecContext, types.SpecReport)
|
||||
ReportSuiteBody func(SpecContext, types.Report)
|
||||
|
||||
MarkedFocus bool
|
||||
MarkedPending bool
|
||||
|
@ -209,7 +208,7 @@ func NewNode(deprecationTracker *types.DeprecationTracker, nodeType types.NodeTy
|
|||
args = unrollInterfaceSlice(args)
|
||||
|
||||
remainingArgs := []interface{}{}
|
||||
//First get the CodeLocation up-to-date
|
||||
// First get the CodeLocation up-to-date
|
||||
for _, arg := range args {
|
||||
switch v := arg.(type) {
|
||||
case Offset:
|
||||
|
@ -225,11 +224,11 @@ func NewNode(deprecationTracker *types.DeprecationTracker, nodeType types.NodeTy
|
|||
trackedFunctionError := false
|
||||
args = remainingArgs
|
||||
remainingArgs = []interface{}{}
|
||||
//now process the rest of the args
|
||||
// now process the rest of the args
|
||||
for _, arg := range args {
|
||||
switch t := reflect.TypeOf(arg); {
|
||||
case t == reflect.TypeOf(float64(0)):
|
||||
break //ignore deprecated timeouts
|
||||
break // ignore deprecated timeouts
|
||||
case t == reflect.TypeOf(Focus):
|
||||
node.MarkedFocus = bool(arg.(focusType))
|
||||
if !nodeType.Is(types.NodeTypesForContainerAndIt) {
|
||||
|
@ -325,7 +324,12 @@ func NewNode(deprecationTracker *types.DeprecationTracker, nodeType types.NodeTy
|
|||
node.Body = func(SpecContext) { body() }
|
||||
} else if nodeType.Is(types.NodeTypeReportBeforeEach | types.NodeTypeReportAfterEach) {
|
||||
if node.ReportEachBody == nil {
|
||||
node.ReportEachBody = arg.(func(types.SpecReport))
|
||||
if fn, ok := arg.(func(types.SpecReport)); ok {
|
||||
node.ReportEachBody = func(_ SpecContext, r types.SpecReport) { fn(r) }
|
||||
} else {
|
||||
node.ReportEachBody = arg.(func(SpecContext, types.SpecReport))
|
||||
node.HasContext = true
|
||||
}
|
||||
} else {
|
||||
appendError(types.GinkgoErrors.MultipleBodyFunctions(node.CodeLocation, nodeType))
|
||||
trackedFunctionError = true
|
||||
|
@ -333,7 +337,12 @@ func NewNode(deprecationTracker *types.DeprecationTracker, nodeType types.NodeTy
|
|||
}
|
||||
} else if nodeType.Is(types.NodeTypeReportBeforeSuite | types.NodeTypeReportAfterSuite) {
|
||||
if node.ReportSuiteBody == nil {
|
||||
node.ReportSuiteBody = arg.(func(types.Report))
|
||||
if fn, ok := arg.(func(types.Report)); ok {
|
||||
node.ReportSuiteBody = func(_ SpecContext, r types.Report) { fn(r) }
|
||||
} else {
|
||||
node.ReportSuiteBody = arg.(func(SpecContext, types.Report))
|
||||
node.HasContext = true
|
||||
}
|
||||
} else {
|
||||
appendError(types.GinkgoErrors.MultipleBodyFunctions(node.CodeLocation, nodeType))
|
||||
trackedFunctionError = true
|
||||
|
@ -395,7 +404,7 @@ func NewNode(deprecationTracker *types.DeprecationTracker, nodeType types.NodeTy
|
|||
}
|
||||
}
|
||||
|
||||
//validations
|
||||
// validations
|
||||
if node.MarkedPending && node.MarkedFocus {
|
||||
appendError(types.GinkgoErrors.InvalidDeclarationOfFocusedAndPending(node.CodeLocation, nodeType))
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ type specContext struct {
|
|||
context.Context
|
||||
*ProgressReporterManager
|
||||
|
||||
cancel context.CancelFunc
|
||||
cancel context.CancelCauseFunc
|
||||
|
||||
suite *Suite
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ Note that while SpecContext is used to enforce deadlines by Ginkgo it is not con
|
|||
This is because Ginkgo needs finer control over when the context is canceled. Specifically, Ginkgo needs to generate a ProgressReport before it cancels the context to ensure progress is captured where the spec is currently running. The only way to avoid a race here is to manually control the cancellation.
|
||||
*/
|
||||
func NewSpecContext(suite *Suite) *specContext {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
ctx, cancel := context.WithCancelCause(context.Background())
|
||||
sc := &specContext{
|
||||
cancel: cancel,
|
||||
suite: suite,
|
||||
|
|
|
@ -594,8 +594,8 @@ func (suite *Suite) reportEach(spec Spec, nodeType types.NodeType) {
|
|||
suite.writer.Truncate()
|
||||
suite.outputInterceptor.StartInterceptingOutput()
|
||||
report := suite.currentSpecReport
|
||||
nodes[i].Body = func(SpecContext) {
|
||||
nodes[i].ReportEachBody(report)
|
||||
nodes[i].Body = func(ctx SpecContext) {
|
||||
nodes[i].ReportEachBody(ctx, report)
|
||||
}
|
||||
state, failure := suite.runNode(nodes[i], time.Time{}, spec.Nodes.BestTextFor(nodes[i]))
|
||||
|
||||
|
@ -762,7 +762,7 @@ func (suite *Suite) runReportSuiteNode(node Node, report types.Report) {
|
|||
report = report.Add(aggregatedReport)
|
||||
}
|
||||
|
||||
node.Body = func(SpecContext) { node.ReportSuiteBody(report) }
|
||||
node.Body = func(ctx SpecContext) { node.ReportSuiteBody(ctx, report) }
|
||||
suite.currentSpecReport.State, suite.currentSpecReport.Failure = suite.runNode(node, time.Time{}, "")
|
||||
|
||||
suite.currentSpecReport.EndTime = time.Now()
|
||||
|
@ -840,7 +840,7 @@ func (suite *Suite) runNode(node Node, specDeadline time.Time, text string) (typ
|
|||
timeoutInPlay = "node"
|
||||
}
|
||||
if (!deadline.IsZero() && deadline.Before(now)) || interruptStatus.Interrupted() {
|
||||
//we're out of time already. let's wait for a NodeTimeout if we have it, or GracePeriod if we don't
|
||||
// we're out of time already. let's wait for a NodeTimeout if we have it, or GracePeriod if we don't
|
||||
if node.NodeTimeout > 0 {
|
||||
deadline = now.Add(node.NodeTimeout)
|
||||
timeoutInPlay = "node"
|
||||
|
@ -858,7 +858,7 @@ func (suite *Suite) runNode(node Node, specDeadline time.Time, text string) (typ
|
|||
}
|
||||
|
||||
sc := NewSpecContext(suite)
|
||||
defer sc.cancel()
|
||||
defer sc.cancel(fmt.Errorf("spec has finished"))
|
||||
|
||||
suite.selectiveLock.Lock()
|
||||
suite.currentSpecContext = sc
|
||||
|
@ -918,9 +918,9 @@ func (suite *Suite) runNode(node Node, specDeadline time.Time, text string) (typ
|
|||
if outcomeFromRun != types.SpecStatePassed {
|
||||
additionalFailure := types.AdditionalFailure{
|
||||
State: outcomeFromRun,
|
||||
Failure: failure, //we make a copy - this will include all the configuration set up above...
|
||||
Failure: failure, // we make a copy - this will include all the configuration set up above...
|
||||
}
|
||||
//...and then we update the failure with the details from failureFromRun
|
||||
// ...and then we update the failure with the details from failureFromRun
|
||||
additionalFailure.Failure.Location, additionalFailure.Failure.ForwardedPanic, additionalFailure.Failure.TimelineLocation = failureFromRun.Location, failureFromRun.ForwardedPanic, failureFromRun.TimelineLocation
|
||||
additionalFailure.Failure.ProgressReport = types.ProgressReport{}
|
||||
if outcome == types.SpecStateTimedout {
|
||||
|
@ -958,8 +958,8 @@ func (suite *Suite) runNode(node Node, specDeadline time.Time, text string) (typ
|
|||
|
||||
// tell the spec to stop. it's important we generate the progress report first to make sure we capture where
|
||||
// the spec is actually stuck
|
||||
sc.cancel()
|
||||
//and now we wait for the grace period
|
||||
sc.cancel(fmt.Errorf("%s timeout occurred", timeoutInPlay))
|
||||
// and now we wait for the grace period
|
||||
gracePeriodChannel = time.After(gracePeriod)
|
||||
case <-interruptStatus.Channel:
|
||||
interruptStatus = suite.interruptHandler.Status()
|
||||
|
@ -985,7 +985,7 @@ func (suite *Suite) runNode(node Node, specDeadline time.Time, text string) (typ
|
|||
}
|
||||
|
||||
progressReport = progressReport.WithoutOtherGoroutines()
|
||||
sc.cancel()
|
||||
sc.cancel(fmt.Errorf(interruptStatus.Message()))
|
||||
|
||||
if interruptStatus.Level == interrupt_handler.InterruptLevelBailOut {
|
||||
if interruptStatus.ShouldIncludeProgressReport() {
|
||||
|
|
|
@ -182,6 +182,22 @@ func (r *DefaultReporter) WillRun(report types.SpecReport) {
|
|||
r.emitBlock(r.f(r.codeLocationBlock(report, "{{/}}", v.Is(types.VerbosityLevelVeryVerbose), false)))
|
||||
}
|
||||
|
||||
func (r *DefaultReporter) wrapTextBlock(sectionName string, fn func()) {
|
||||
r.emitBlock("\n")
|
||||
if r.conf.GithubOutput {
|
||||
r.emitBlock(r.fi(1, "::group::%s", sectionName))
|
||||
} else {
|
||||
r.emitBlock(r.fi(1, "{{gray}}%s >>{{/}}", sectionName))
|
||||
}
|
||||
fn()
|
||||
if r.conf.GithubOutput {
|
||||
r.emitBlock(r.fi(1, "::endgroup::"))
|
||||
} else {
|
||||
r.emitBlock(r.fi(1, "{{gray}}<< %s{{/}}", sectionName))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (r *DefaultReporter) DidRun(report types.SpecReport) {
|
||||
v := r.conf.Verbosity()
|
||||
inParallel := report.RunningInParallel
|
||||
|
@ -283,26 +299,23 @@ func (r *DefaultReporter) DidRun(report types.SpecReport) {
|
|||
|
||||
//Emit Stdout/Stderr Output
|
||||
if showSeparateStdSection {
|
||||
r.emitBlock("\n")
|
||||
r.emitBlock(r.fi(1, "{{gray}}Captured StdOut/StdErr Output >>{{/}}"))
|
||||
r.emitBlock(r.fi(1, "%s", report.CapturedStdOutErr))
|
||||
r.emitBlock(r.fi(1, "{{gray}}<< Captured StdOut/StdErr Output{{/}}"))
|
||||
r.wrapTextBlock("Captured StdOut/StdErr Output", func() {
|
||||
r.emitBlock(r.fi(1, "%s", report.CapturedStdOutErr))
|
||||
})
|
||||
}
|
||||
|
||||
if showSeparateVisibilityAlwaysReportsSection {
|
||||
r.emitBlock("\n")
|
||||
r.emitBlock(r.fi(1, "{{gray}}Report Entries >>{{/}}"))
|
||||
for _, entry := range report.ReportEntries.WithVisibility(types.ReportEntryVisibilityAlways) {
|
||||
r.emitReportEntry(1, entry)
|
||||
}
|
||||
r.emitBlock(r.fi(1, "{{gray}}<< Report Entries{{/}}"))
|
||||
r.wrapTextBlock("Report Entries", func() {
|
||||
for _, entry := range report.ReportEntries.WithVisibility(types.ReportEntryVisibilityAlways) {
|
||||
r.emitReportEntry(1, entry)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if showTimeline {
|
||||
r.emitBlock("\n")
|
||||
r.emitBlock(r.fi(1, "{{gray}}Timeline >>{{/}}"))
|
||||
r.emitTimeline(1, report, timeline)
|
||||
r.emitBlock(r.fi(1, "{{gray}}<< Timeline{{/}}"))
|
||||
r.wrapTextBlock("Timeline", func() {
|
||||
r.emitTimeline(1, report, timeline)
|
||||
})
|
||||
}
|
||||
|
||||
// Emit Failure Message
|
||||
|
@ -405,7 +418,11 @@ func (r *DefaultReporter) emitShortFailure(indent uint, state types.SpecState, f
|
|||
func (r *DefaultReporter) emitFailure(indent uint, state types.SpecState, failure types.Failure, includeAdditionalFailure bool) {
|
||||
highlightColor := r.highlightColorForState(state)
|
||||
r.emitBlock(r.fi(indent, highlightColor+"[%s] %s{{/}}", r.humanReadableState(state), failure.Message))
|
||||
r.emitBlock(r.fi(indent, highlightColor+"In {{bold}}[%s]{{/}}"+highlightColor+" at: {{bold}}%s{{/}} {{gray}}@ %s{{/}}\n", failure.FailureNodeType, failure.Location, failure.TimelineLocation.Time.Format(types.GINKGO_TIME_FORMAT)))
|
||||
if r.conf.GithubOutput {
|
||||
r.emitBlock(r.fi(indent, "::error file=%s,line=%d::%s %s", failure.Location.FileName, failure.Location.LineNumber, failure.FailureNodeType, failure.TimelineLocation.Time.Format(types.GINKGO_TIME_FORMAT)))
|
||||
} else {
|
||||
r.emitBlock(r.fi(indent, highlightColor+"In {{bold}}[%s]{{/}}"+highlightColor+" at: {{bold}}%s{{/}} {{gray}}@ %s{{/}}\n", failure.FailureNodeType, failure.Location, failure.TimelineLocation.Time.Format(types.GINKGO_TIME_FORMAT)))
|
||||
}
|
||||
if failure.ForwardedPanic != "" {
|
||||
r.emitBlock("\n")
|
||||
r.emitBlock(r.fi(indent, highlightColor+"%s{{/}}", failure.ForwardedPanic))
|
||||
|
|
|
@ -15,6 +15,7 @@ import (
|
|||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/onsi/ginkgo/v2/config"
|
||||
|
@ -104,6 +105,8 @@ type JUnitProperty struct {
|
|||
Value string `xml:"value,attr"`
|
||||
}
|
||||
|
||||
var ownerRE = regexp.MustCompile(`(?i)^owner:(.*)$`)
|
||||
|
||||
type JUnitTestCase struct {
|
||||
// Name maps onto the full text of the spec - equivalent to "[SpecReport.LeafNodeType] SpecReport.FullText()"
|
||||
Name string `xml:"name,attr"`
|
||||
|
@ -113,6 +116,8 @@ type JUnitTestCase struct {
|
|||
Status string `xml:"status,attr"`
|
||||
// Time is the time in seconds to execute the spec - maps onto SpecReport.RunTime
|
||||
Time float64 `xml:"time,attr"`
|
||||
// Owner is the owner the spec - is set if a label matching Label("owner:X") is provided. The last matching label is used as the owner, thereby allowing specs to override owners specified in container nodes.
|
||||
Owner string `xml:"owner,attr,omitempty"`
|
||||
//Skipped is populated with a message if the test was skipped or pending
|
||||
Skipped *JUnitSkipped `xml:"skipped,omitempty"`
|
||||
//Error is populated if the test panicked or was interrupted
|
||||
|
@ -195,6 +200,12 @@ func GenerateJUnitReportWithConfig(report types.Report, dst string, config Junit
|
|||
if len(labels) > 0 && !config.OmitSpecLabels {
|
||||
name = name + " [" + strings.Join(labels, ", ") + "]"
|
||||
}
|
||||
owner := ""
|
||||
for _, label := range labels {
|
||||
if matches := ownerRE.FindStringSubmatch(label); len(matches) == 2 {
|
||||
owner = matches[1]
|
||||
}
|
||||
}
|
||||
name = strings.TrimSpace(name)
|
||||
|
||||
test := JUnitTestCase{
|
||||
|
@ -202,6 +213,7 @@ func GenerateJUnitReportWithConfig(report types.Report, dst string, config Junit
|
|||
Classname: report.SuiteDescription,
|
||||
Status: spec.State.String(),
|
||||
Time: spec.RunTime.Seconds(),
|
||||
Owner: owner,
|
||||
}
|
||||
if !spec.State.Is(config.OmitTimelinesForSpecState) {
|
||||
test.SystemErr = systemErrForUnstructuredReporters(spec)
|
||||
|
|
|
@ -74,12 +74,21 @@ func AddReportEntry(name string, args ...interface{}) {
|
|||
|
||||
/*
|
||||
ReportBeforeEach nodes are run for each spec, even if the spec is skipped or pending. ReportBeforeEach nodes take a function that
|
||||
receives a SpecReport. They are called before the spec starts.
|
||||
receives a SpecReport or both SpecContext and Report for interruptible behavior. They are called before the spec starts.
|
||||
|
||||
Example:
|
||||
|
||||
ReportBeforeEach(func(report SpecReport) { // process report })
|
||||
ReportBeforeEach(func(ctx SpecContext, report SpecReport) {
|
||||
// process report
|
||||
}), NodeTimeout(1 * time.Minute))
|
||||
|
||||
You cannot nest any other Ginkgo nodes within a ReportBeforeEach node's closure.
|
||||
You can learn more about ReportBeforeEach here: https://onsi.github.io/ginkgo/#generating-reports-programmatically
|
||||
|
||||
You can learn about interruptible nodes here: https://onsi.github.io/ginkgo/#spec-timeouts-and-interruptible-nodes
|
||||
*/
|
||||
func ReportBeforeEach(body func(SpecReport), args ...interface{}) bool {
|
||||
func ReportBeforeEach(body any, args ...any) bool {
|
||||
combinedArgs := []interface{}{body}
|
||||
combinedArgs = append(combinedArgs, args...)
|
||||
|
||||
|
@ -87,13 +96,23 @@ func ReportBeforeEach(body func(SpecReport), args ...interface{}) bool {
|
|||
}
|
||||
|
||||
/*
|
||||
ReportAfterEach nodes are run for each spec, even if the spec is skipped or pending. ReportAfterEach nodes take a function that
|
||||
receives a SpecReport. They are called after the spec has completed and receive the final report for the spec.
|
||||
ReportAfterEach nodes are run for each spec, even if the spec is skipped or pending.
|
||||
ReportAfterEach nodes take a function that receives a SpecReport or both SpecContext and Report for interruptible behavior.
|
||||
They are called after the spec has completed and receive the final report for the spec.
|
||||
|
||||
Example:
|
||||
|
||||
ReportAfterEach(func(report SpecReport) { // process report })
|
||||
ReportAfterEach(func(ctx SpecContext, report SpecReport) {
|
||||
// process report
|
||||
}), NodeTimeout(1 * time.Minute))
|
||||
|
||||
You cannot nest any other Ginkgo nodes within a ReportAfterEach node's closure.
|
||||
You can learn more about ReportAfterEach here: https://onsi.github.io/ginkgo/#generating-reports-programmatically
|
||||
|
||||
You can learn about interruptible nodes here: https://onsi.github.io/ginkgo/#spec-timeouts-and-interruptible-nodes
|
||||
*/
|
||||
func ReportAfterEach(body func(SpecReport), args ...interface{}) bool {
|
||||
func ReportAfterEach(body any, args ...any) bool {
|
||||
combinedArgs := []interface{}{body}
|
||||
combinedArgs = append(combinedArgs, args...)
|
||||
|
||||
|
@ -101,7 +120,15 @@ func ReportAfterEach(body func(SpecReport), args ...interface{}) bool {
|
|||
}
|
||||
|
||||
/*
|
||||
ReportBeforeSuite nodes are run at the beginning of the suite. ReportBeforeSuite nodes take a function that receives a suite Report.
|
||||
ReportBeforeSuite nodes are run at the beginning of the suite. ReportBeforeSuite nodes take a function
|
||||
that can either receive Report or both SpecContext and Report for interruptible behavior.
|
||||
|
||||
Example Usage:
|
||||
|
||||
ReportBeforeSuite(func(r Report) { // process report })
|
||||
ReportBeforeSuite(func(ctx SpecContext, r Report) {
|
||||
// process report
|
||||
}, NodeTimeout(1 * time.Minute))
|
||||
|
||||
They are called at the beginning of the suite, before any specs have run and any BeforeSuite or SynchronizedBeforeSuite nodes, and are passed in the initial report for the suite.
|
||||
ReportBeforeSuite nodes must be created at the top-level (i.e. not nested in a Context/Describe/When node)
|
||||
|
@ -112,18 +139,28 @@ You cannot nest any other Ginkgo nodes within a ReportAfterSuite node's closure.
|
|||
You can learn more about ReportAfterSuite here: https://onsi.github.io/ginkgo/#generating-reports-programmatically
|
||||
|
||||
You can learn more about Ginkgo's reporting infrastructure, including generating reports with the CLI here: https://onsi.github.io/ginkgo/#generating-machine-readable-reports
|
||||
|
||||
You can learn about interruptible nodes here: https://onsi.github.io/ginkgo/#spec-timeouts-and-interruptible-nodes
|
||||
*/
|
||||
func ReportBeforeSuite(body func(Report), args ...interface{}) bool {
|
||||
func ReportBeforeSuite(body any, args ...any) bool {
|
||||
combinedArgs := []interface{}{body}
|
||||
combinedArgs = append(combinedArgs, args...)
|
||||
return pushNode(internal.NewNode(deprecationTracker, types.NodeTypeReportBeforeSuite, "", combinedArgs...))
|
||||
}
|
||||
|
||||
/*
|
||||
ReportAfterSuite nodes are run at the end of the suite. ReportAfterSuite nodes take a function that receives a suite Report.
|
||||
ReportAfterSuite nodes are run at the end of the suite. ReportAfterSuite nodes execute at the suite's conclusion,
|
||||
and accept a function that can either receive Report or both SpecContext and Report for interruptible behavior.
|
||||
|
||||
Example Usage:
|
||||
|
||||
ReportAfterSuite("Non-interruptible ReportAfterSuite", func(r Report) { // process report })
|
||||
ReportAfterSuite("Interruptible ReportAfterSuite", func(ctx SpecContext, r Report) {
|
||||
// process report
|
||||
}, NodeTimeout(1 * time.Minute))
|
||||
|
||||
They are called at the end of the suite, after all specs have run and any AfterSuite or SynchronizedAfterSuite nodes, and are passed in the final report for the suite.
|
||||
ReportAftersuite nodes must be created at the top-level (i.e. not nested in a Context/Describe/When node)
|
||||
ReportAfterSuite nodes must be created at the top-level (i.e. not nested in a Context/Describe/When node)
|
||||
|
||||
When running in parallel, Ginkgo ensures that only one of the parallel nodes runs the ReportAfterSuite and that it is passed a report that is aggregated across
|
||||
all parallel nodes
|
||||
|
@ -134,8 +171,10 @@ You cannot nest any other Ginkgo nodes within a ReportAfterSuite node's closure.
|
|||
You can learn more about ReportAfterSuite here: https://onsi.github.io/ginkgo/#generating-reports-programmatically
|
||||
|
||||
You can learn more about Ginkgo's reporting infrastructure, including generating reports with the CLI here: https://onsi.github.io/ginkgo/#generating-machine-readable-reports
|
||||
|
||||
You can learn about interruptible nodes here: https://onsi.github.io/ginkgo/#spec-timeouts-and-interruptible-nodes
|
||||
*/
|
||||
func ReportAfterSuite(text string, body func(Report), args ...interface{}) bool {
|
||||
func ReportAfterSuite(text string, body any, args ...interface{}) bool {
|
||||
combinedArgs := []interface{}{body}
|
||||
combinedArgs = append(combinedArgs, args...)
|
||||
return pushNode(internal.NewNode(deprecationTracker, types.NodeTypeReportAfterSuite, text, combinedArgs...))
|
||||
|
|
|
@ -89,6 +89,7 @@ type ReporterConfig struct {
|
|||
VeryVerbose bool
|
||||
FullTrace bool
|
||||
ShowNodeEvents bool
|
||||
GithubOutput bool
|
||||
|
||||
JSONReport string
|
||||
JUnitReport string
|
||||
|
@ -264,7 +265,7 @@ var FlagSections = GinkgoFlagSections{
|
|||
// SuiteConfigFlags provides flags for the Ginkgo test process, and CLI
|
||||
var SuiteConfigFlags = GinkgoFlags{
|
||||
{KeyPath: "S.RandomSeed", Name: "seed", SectionKey: "order", UsageDefaultValue: "randomly generated by Ginkgo",
|
||||
Usage: "The seed used to randomize the spec suite."},
|
||||
Usage: "The seed used to randomize the spec suite.", AlwaysExport: true},
|
||||
{KeyPath: "S.RandomizeAllSpecs", Name: "randomize-all", SectionKey: "order", DeprecatedName: "randomizeAllSpecs", DeprecatedDocLink: "changed-command-line-flags",
|
||||
Usage: "If set, ginkgo will randomize all specs together. By default, ginkgo only randomizes the top level Describe, Context and When containers."},
|
||||
|
||||
|
@ -331,6 +332,8 @@ var ReporterConfigFlags = GinkgoFlags{
|
|||
Usage: "If set, default reporter prints out the full stack trace when a failure occurs"},
|
||||
{KeyPath: "R.ShowNodeEvents", Name: "show-node-events", SectionKey: "output",
|
||||
Usage: "If set, default reporter prints node > Enter and < Exit events when specs fail"},
|
||||
{KeyPath: "R.GithubOutput", Name: "github-output", SectionKey: "output",
|
||||
Usage: "If set, default reporter prints easier to manage output in Github Actions."},
|
||||
|
||||
{KeyPath: "R.JSONReport", Name: "json-report", UsageArgument: "filename.json", SectionKey: "output",
|
||||
Usage: "If set, Ginkgo will generate a JSON-formatted test report at the specified location."},
|
||||
|
|
|
@ -24,7 +24,8 @@ type GinkgoFlag struct {
|
|||
DeprecatedDocLink string
|
||||
DeprecatedVersion string
|
||||
|
||||
ExportAs string
|
||||
ExportAs string
|
||||
AlwaysExport bool
|
||||
}
|
||||
|
||||
type GinkgoFlags []GinkgoFlag
|
||||
|
@ -431,7 +432,7 @@ func (ssv stringSliceVar) Set(s string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
//given a set of GinkgoFlags and bindings, generate flag arguments suitable to be passed to an application with that set of flags configured.
|
||||
// given a set of GinkgoFlags and bindings, generate flag arguments suitable to be passed to an application with that set of flags configured.
|
||||
func GenerateFlagArgs(flags GinkgoFlags, bindings interface{}) ([]string, error) {
|
||||
result := []string{}
|
||||
for _, flag := range flags {
|
||||
|
@ -451,19 +452,19 @@ func GenerateFlagArgs(flags GinkgoFlags, bindings interface{}) ([]string, error)
|
|||
iface := value.Interface()
|
||||
switch value.Type() {
|
||||
case reflect.TypeOf(string("")):
|
||||
if iface.(string) != "" {
|
||||
if iface.(string) != "" || flag.AlwaysExport {
|
||||
result = append(result, fmt.Sprintf("--%s=%s", name, iface))
|
||||
}
|
||||
case reflect.TypeOf(int64(0)):
|
||||
if iface.(int64) != 0 {
|
||||
if iface.(int64) != 0 || flag.AlwaysExport {
|
||||
result = append(result, fmt.Sprintf("--%s=%d", name, iface))
|
||||
}
|
||||
case reflect.TypeOf(float64(0)):
|
||||
if iface.(float64) != 0 {
|
||||
if iface.(float64) != 0 || flag.AlwaysExport {
|
||||
result = append(result, fmt.Sprintf("--%s=%f", name, iface))
|
||||
}
|
||||
case reflect.TypeOf(int(0)):
|
||||
if iface.(int) != 0 {
|
||||
if iface.(int) != 0 || flag.AlwaysExport {
|
||||
result = append(result, fmt.Sprintf("--%s=%d", name, iface))
|
||||
}
|
||||
case reflect.TypeOf(bool(true)):
|
||||
|
@ -471,7 +472,7 @@ func GenerateFlagArgs(flags GinkgoFlags, bindings interface{}) ([]string, error)
|
|||
result = append(result, fmt.Sprintf("--%s", name))
|
||||
}
|
||||
case reflect.TypeOf(time.Duration(0)):
|
||||
if iface.(time.Duration) != time.Duration(0) {
|
||||
if iface.(time.Duration) != time.Duration(0) || flag.AlwaysExport {
|
||||
result = append(result, fmt.Sprintf("--%s=%s", name, iface))
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
package types
|
||||
|
||||
const VERSION = "2.14.0"
|
||||
const VERSION = "2.17.1"
|
||||
|
|
|
@ -1,3 +1,41 @@
|
|||
## 1.32.0
|
||||
|
||||
### Maintenance
|
||||
- Migrate github.com/golang/protobuf to google.golang.org/protobuf [436a197]
|
||||
|
||||
This release drops the deprecated github.com/golang/protobuf and adopts google.golang.org/protobuf. Care was taken to ensure the release is backwards compatible (thanks @jbduncan !). Please open an issue if you run into one.
|
||||
|
||||
- chore: test with Go 1.22 (#733) [32ef35e]
|
||||
- Bump golang.org/x/net from 0.19.0 to 0.20.0 (#717) [a0d0387]
|
||||
- Bump github-pages and jekyll-feed in /docs (#732) [b71e477]
|
||||
- docs: fix typo and broken anchor link to gstruct [f460154]
|
||||
- docs: fix HaveEach matcher signature [a2862e4]
|
||||
|
||||
## 1.31.1
|
||||
|
||||
### Fixes
|
||||
- Inverted arguments order of FailureMessage of BeComparableToMatcher [e0dd999]
|
||||
- Update test in case keeping msg is desired [ad1a367]
|
||||
|
||||
### Maintenance
|
||||
- Show how to import the format sub package [24e958d]
|
||||
- tidy up go.sum [26661b8]
|
||||
- bump dependencies [bde8f7a]
|
||||
|
||||
## 1.31.0
|
||||
|
||||
### Features
|
||||
- Async assertions include context cancellation cause if present [121c37f]
|
||||
|
||||
### Maintenance
|
||||
- Bump minimum go version [dee1e3c]
|
||||
- docs: fix typo in example usage "occured" -> "occurred" [49005fe]
|
||||
- Bump actions/setup-go from 4 to 5 (#714) [f1c8757]
|
||||
- Bump github/codeql-action from 2 to 3 (#715) [9836e76]
|
||||
- Bump github.com/onsi/ginkgo/v2 from 2.13.0 to 2.13.2 (#713) [54726f0]
|
||||
- Bump golang.org/x/net from 0.17.0 to 0.19.0 (#711) [df97ecc]
|
||||
- docs: fix `HaveExactElement` typo (#712) [a672c86]
|
||||
|
||||
## 1.30.0
|
||||
|
||||
### Features
|
||||
|
|
|
@ -22,7 +22,7 @@ import (
|
|||
"github.com/onsi/gomega/types"
|
||||
)
|
||||
|
||||
const GOMEGA_VERSION = "1.30.0"
|
||||
const GOMEGA_VERSION = "1.32.0"
|
||||
|
||||
const nilGomegaPanic = `You are trying to make an assertion, but haven't registered Gomega's fail handler.
|
||||
If you're using Ginkgo then you probably forgot to put your assertion in an It().
|
||||
|
|
|
@ -553,7 +553,12 @@ func (assertion *AsyncAssertion) match(matcher types.GomegaMatcher, desiredMatch
|
|||
lock.Unlock()
|
||||
}
|
||||
case <-contextDone:
|
||||
fail("Context was cancelled")
|
||||
err := context.Cause(assertion.ctx)
|
||||
if err != nil && err != context.Canceled {
|
||||
fail(fmt.Sprintf("Context was cancelled (cause: %s)", err))
|
||||
} else {
|
||||
fail("Context was cancelled")
|
||||
}
|
||||
return false
|
||||
case <-timeout:
|
||||
if assertion.asyncType == AsyncAssertionTypeEventually {
|
||||
|
|
|
@ -394,7 +394,7 @@ func ConsistOf(elements ...interface{}) types.GomegaMatcher {
|
|||
}
|
||||
}
|
||||
|
||||
// HaveExactElemets succeeds if actual contains elements that precisely match the elemets passed into the matcher. The ordering of the elements does matter.
|
||||
// HaveExactElements succeeds if actual contains elements that precisely match the elemets passed into the matcher. The ordering of the elements does matter.
|
||||
// By default HaveExactElements() uses Equal() to match the elements, however custom matchers can be passed in instead. Here are some examples:
|
||||
//
|
||||
// Expect([]string{"Foo", "FooBar"}).Should(HaveExactElements("Foo", "FooBar"))
|
||||
|
|
|
@ -41,9 +41,9 @@ func (matcher *BeComparableToMatcher) Match(actual interface{}) (success bool, m
|
|||
}
|
||||
|
||||
func (matcher *BeComparableToMatcher) FailureMessage(actual interface{}) (message string) {
|
||||
return cmp.Diff(matcher.Expected, actual, matcher.Options)
|
||||
return fmt.Sprint("Expected object to be comparable, diff: ", cmp.Diff(actual, matcher.Expected, matcher.Options...))
|
||||
}
|
||||
|
||||
func (matcher *BeComparableToMatcher) NegatedFailureMessage(actual interface{}) (message string) {
|
||||
return format.Message(actual, "not to equal", matcher.Expected)
|
||||
return format.Message(actual, "not to be comparable to", matcher.Expected)
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ builds:
|
|||
- linux_amd64
|
||||
- linux_arm64
|
||||
- linux_arm
|
||||
- linux_riscv64
|
||||
- windows_amd64
|
||||
- windows_arm64
|
||||
- windows_arm
|
||||
|
@ -37,6 +38,7 @@ builds:
|
|||
- linux_amd64
|
||||
- linux_arm64
|
||||
- linux_arm
|
||||
- linux_riscv64
|
||||
- windows_amd64
|
||||
- windows_arm64
|
||||
- windows_arm
|
||||
|
@ -55,6 +57,7 @@ builds:
|
|||
targets:
|
||||
- linux_amd64
|
||||
- linux_arm64
|
||||
- linux_riscv64
|
||||
- linux_arm
|
||||
- windows_amd64
|
||||
- windows_arm64
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013 - 2022 Thomas Pelletier, Eric Anderton
|
||||
go-toml v2
|
||||
Copyright (c) 2021 - 2023 Thomas Pelletier
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
|
@ -45,16 +45,15 @@ to check for typos. [See example in the documentation][strict].
|
|||
|
||||
### Contextualized errors
|
||||
|
||||
When most decoding errors occur, go-toml returns [`DecodeError`][decode-err]),
|
||||
When most decoding errors occur, go-toml returns [`DecodeError`][decode-err],
|
||||
which contains a human readable contextualized version of the error. For
|
||||
example:
|
||||
|
||||
```
|
||||
2| key1 = "value1"
|
||||
3| key2 = "missing2"
|
||||
| ~~~~ missing field
|
||||
4| key3 = "missing3"
|
||||
5| key4 = "value4"
|
||||
1| [server]
|
||||
2| path = 100
|
||||
| ~~~ cannot decode TOML integer into struct field toml_test.Server.Path of type string
|
||||
3| port = 50
|
||||
```
|
||||
|
||||
[decode-err]: https://pkg.go.dev/github.com/pelletier/go-toml/v2#DecodeError
|
||||
|
@ -73,6 +72,26 @@ representation.
|
|||
[tlt]: https://pkg.go.dev/github.com/pelletier/go-toml/v2#LocalTime
|
||||
[tldt]: https://pkg.go.dev/github.com/pelletier/go-toml/v2#LocalDateTime
|
||||
|
||||
### Commented config
|
||||
|
||||
Since TOML is often used for configuration files, go-toml can emit documents
|
||||
annotated with [comments and commented-out values][comments-example]. For
|
||||
example, it can generate the following file:
|
||||
|
||||
```toml
|
||||
# Host IP to connect to.
|
||||
host = '127.0.0.1'
|
||||
# Port of the remote server.
|
||||
port = 4242
|
||||
|
||||
# Encryption parameters (optional)
|
||||
# [TLS]
|
||||
# cipher = 'AEAD-AES128-GCM-SHA256'
|
||||
# version = 'TLS 1.3'
|
||||
```
|
||||
|
||||
[comments-example]: https://pkg.go.dev/github.com/pelletier/go-toml/v2#example-Marshal-Commented
|
||||
|
||||
## Getting started
|
||||
|
||||
Given the following struct, let's see how to read it and write it as TOML:
|
||||
|
@ -497,27 +516,20 @@ is not necessary anymore.
|
|||
|
||||
V1 used to provide multiple struct tags: `comment`, `commented`, `multiline`,
|
||||
`toml`, and `omitempty`. To behave more like the standard library, v2 has merged
|
||||
`toml`, `multiline`, and `omitempty`. For example:
|
||||
`toml`, `multiline`, `commented`, and `omitempty`. For example:
|
||||
|
||||
```go
|
||||
type doc struct {
|
||||
// v1
|
||||
F string `toml:"field" multiline:"true" omitempty:"true"`
|
||||
F string `toml:"field" multiline:"true" omitempty:"true" commented:"true"`
|
||||
// v2
|
||||
F string `toml:"field,multiline,omitempty"`
|
||||
F string `toml:"field,multiline,omitempty,commented"`
|
||||
}
|
||||
```
|
||||
|
||||
Has a result, the `Encoder.SetTag*` methods have been removed, as there is just
|
||||
one tag now.
|
||||
|
||||
|
||||
#### `commented` tag has been removed
|
||||
|
||||
There is no replacement for the `commented` tag. This feature would be better
|
||||
suited in a proper document model for go-toml v2, which has been [cut from
|
||||
scope][nodoc] at the moment.
|
||||
|
||||
#### `Encoder.ArraysWithOneElementPerLine` has been renamed
|
||||
|
||||
The new name is `Encoder.SetArraysMultiline`. The behavior should be the same.
|
||||
|
|
|
@ -79,6 +79,7 @@ cover() {
|
|||
go test -covermode=atomic -coverpkg=./... -coverprofile=coverage.out.tmp ./...
|
||||
cat coverage.out.tmp | grep -v fuzz | grep -v testsuite | grep -v tomltestgen | grep -v gotoml-test-decoder > coverage.out
|
||||
go tool cover -func=coverage.out
|
||||
echo "Coverage profile for ${branch}: ${dir}/coverage.out" >&2
|
||||
popd
|
||||
|
||||
if [ "${branch}" != "HEAD" ]; then
|
||||
|
|
|
@ -318,7 +318,7 @@ func parseFloat(b []byte) (float64, error) {
|
|||
if cleaned[0] == '+' || cleaned[0] == '-' {
|
||||
start = 1
|
||||
}
|
||||
if cleaned[start] == '0' && isDigit(cleaned[start+1]) {
|
||||
if cleaned[start] == '0' && len(cleaned) > start+1 && isDigit(cleaned[start+1]) {
|
||||
return 0, unstable.NewParserError(b, "float integer part cannot have leading zeroes")
|
||||
}
|
||||
|
||||
|
|
|
@ -148,6 +148,9 @@ func (enc *Encoder) SetIndentTables(indent bool) *Encoder {
|
|||
//
|
||||
// The "omitempty" option prevents empty values or groups from being emitted.
|
||||
//
|
||||
// The "commented" option prefixes the value and all its children with a comment
|
||||
// symbol.
|
||||
//
|
||||
// In addition to the "toml" tag struct tag, a "comment" tag can be used to emit
|
||||
// a TOML comment before the value being annotated. Comments are ignored inside
|
||||
// inline tables. For array tables, the comment is only present before the first
|
||||
|
@ -180,6 +183,7 @@ func (enc *Encoder) Encode(v interface{}) error {
|
|||
type valueOptions struct {
|
||||
multiline bool
|
||||
omitempty bool
|
||||
commented bool
|
||||
comment string
|
||||
}
|
||||
|
||||
|
@ -205,6 +209,9 @@ type encoderCtx struct {
|
|||
// Indentation level
|
||||
indent int
|
||||
|
||||
// Prefix the current value with a comment.
|
||||
commented bool
|
||||
|
||||
// Options coming from struct tags
|
||||
options valueOptions
|
||||
}
|
||||
|
@ -273,7 +280,7 @@ func (enc *Encoder) encode(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, e
|
|||
return enc.encodeMap(b, ctx, v)
|
||||
case reflect.Struct:
|
||||
return enc.encodeStruct(b, ctx, v)
|
||||
case reflect.Slice:
|
||||
case reflect.Slice, reflect.Array:
|
||||
return enc.encodeSlice(b, ctx, v)
|
||||
case reflect.Interface:
|
||||
if v.IsNil() {
|
||||
|
@ -357,6 +364,7 @@ func (enc *Encoder) encodeKv(b []byte, ctx encoderCtx, options valueOptions, v r
|
|||
|
||||
if !ctx.inline {
|
||||
b = enc.encodeComment(ctx.indent, options.comment, b)
|
||||
b = enc.commented(ctx.commented, b)
|
||||
b = enc.indent(ctx.indent, b)
|
||||
}
|
||||
|
||||
|
@ -378,6 +386,13 @@ func (enc *Encoder) encodeKv(b []byte, ctx encoderCtx, options valueOptions, v r
|
|||
return b, nil
|
||||
}
|
||||
|
||||
func (enc *Encoder) commented(commented bool, b []byte) []byte {
|
||||
if commented {
|
||||
return append(b, "# "...)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func isEmptyValue(v reflect.Value) bool {
|
||||
switch v.Kind() {
|
||||
case reflect.Struct:
|
||||
|
@ -526,6 +541,8 @@ func (enc *Encoder) encodeTableHeader(ctx encoderCtx, b []byte) ([]byte, error)
|
|||
|
||||
b = enc.encodeComment(ctx.indent, ctx.options.comment, b)
|
||||
|
||||
b = enc.commented(ctx.commented, b)
|
||||
|
||||
b = enc.indent(ctx.indent, b)
|
||||
|
||||
b = append(b, '[')
|
||||
|
@ -704,6 +721,7 @@ func walkStruct(ctx encoderCtx, t *table, v reflect.Value) {
|
|||
options := valueOptions{
|
||||
multiline: opts.multiline,
|
||||
omitempty: opts.omitempty,
|
||||
commented: opts.commented,
|
||||
comment: fieldType.Tag.Get("comment"),
|
||||
}
|
||||
|
||||
|
@ -763,6 +781,7 @@ type tagOptions struct {
|
|||
multiline bool
|
||||
inline bool
|
||||
omitempty bool
|
||||
commented bool
|
||||
}
|
||||
|
||||
func parseTag(tag string) (string, tagOptions) {
|
||||
|
@ -790,6 +809,8 @@ func parseTag(tag string) (string, tagOptions) {
|
|||
opts.inline = true
|
||||
case "omitempty":
|
||||
opts.omitempty = true
|
||||
case "commented":
|
||||
opts.commented = true
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -825,8 +846,10 @@ func (enc *Encoder) encodeTable(b []byte, ctx encoderCtx, t table) ([]byte, erro
|
|||
hasNonEmptyKV = true
|
||||
|
||||
ctx.setKey(kv.Key)
|
||||
ctx2 := ctx
|
||||
ctx2.commented = kv.Options.commented || ctx2.commented
|
||||
|
||||
b, err = enc.encodeKv(b, ctx, kv.Options, kv.Value)
|
||||
b, err = enc.encodeKv(b, ctx2, kv.Options, kv.Value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -851,8 +874,10 @@ func (enc *Encoder) encodeTable(b []byte, ctx encoderCtx, t table) ([]byte, erro
|
|||
ctx.setKey(table.Key)
|
||||
|
||||
ctx.options = table.Options
|
||||
ctx2 := ctx
|
||||
ctx2.commented = ctx2.commented || ctx.options.commented
|
||||
|
||||
b, err = enc.encode(b, ctx, table.Value)
|
||||
b, err = enc.encode(b, ctx2, table.Value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -930,7 +955,7 @@ func willConvertToTableOrArrayTable(ctx encoderCtx, v reflect.Value) bool {
|
|||
return willConvertToTableOrArrayTable(ctx, v.Elem())
|
||||
}
|
||||
|
||||
if t.Kind() == reflect.Slice {
|
||||
if t.Kind() == reflect.Slice || t.Kind() == reflect.Array {
|
||||
if v.Len() == 0 {
|
||||
// An empty slice should be a kv = [].
|
||||
return false
|
||||
|
@ -970,6 +995,9 @@ func (enc *Encoder) encodeSliceAsArrayTable(b []byte, ctx encoderCtx, v reflect.
|
|||
ctx.shiftKey()
|
||||
|
||||
scratch := make([]byte, 0, 64)
|
||||
|
||||
scratch = enc.commented(ctx.commented, scratch)
|
||||
|
||||
scratch = append(scratch, "[["...)
|
||||
|
||||
for i, k := range ctx.parentKey {
|
||||
|
@ -985,6 +1013,10 @@ func (enc *Encoder) encodeSliceAsArrayTable(b []byte, ctx encoderCtx, v reflect.
|
|||
|
||||
b = enc.encodeComment(ctx.indent, ctx.options.comment, b)
|
||||
|
||||
if enc.indentTables {
|
||||
ctx.indent++
|
||||
}
|
||||
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
if i != 0 {
|
||||
b = append(b, "\n"...)
|
||||
|
|
|
@ -149,12 +149,16 @@ type errorContext struct {
|
|||
}
|
||||
|
||||
func (d *decoder) typeMismatchError(toml string, target reflect.Type) error {
|
||||
return fmt.Errorf("toml: %s", d.typeMismatchString(toml, target))
|
||||
}
|
||||
|
||||
func (d *decoder) typeMismatchString(toml string, target reflect.Type) string {
|
||||
if d.errorContext != nil && d.errorContext.Struct != nil {
|
||||
ctx := d.errorContext
|
||||
f := ctx.Struct.FieldByIndex(ctx.Field)
|
||||
return fmt.Errorf("toml: cannot decode TOML %s into struct field %s.%s of type %s", toml, ctx.Struct, f.Name, f.Type)
|
||||
return fmt.Sprintf("cannot decode TOML %s into struct field %s.%s of type %s", toml, ctx.Struct, f.Name, f.Type)
|
||||
}
|
||||
return fmt.Errorf("toml: cannot decode TOML %s into a Go value of type %s", toml, target)
|
||||
return fmt.Sprintf("cannot decode TOML %s into a Go value of type %s", toml, target)
|
||||
}
|
||||
|
||||
func (d *decoder) expr() *unstable.Node {
|
||||
|
@ -963,7 +967,7 @@ func (d *decoder) unmarshalInteger(value *unstable.Node, v reflect.Value) error
|
|||
case reflect.Interface:
|
||||
r = reflect.ValueOf(i)
|
||||
default:
|
||||
return d.typeMismatchError("integer", v.Type())
|
||||
return unstable.NewParserError(d.p.Raw(value.Raw), d.typeMismatchString("integer", v.Type()))
|
||||
}
|
||||
|
||||
if !r.Type().AssignableTo(v.Type()) {
|
||||
|
@ -982,7 +986,7 @@ func (d *decoder) unmarshalString(value *unstable.Node, v reflect.Value) error {
|
|||
case reflect.Interface:
|
||||
v.Set(reflect.ValueOf(string(value.Data)))
|
||||
default:
|
||||
return unstable.NewParserError(d.p.Raw(value.Raw), "cannot store TOML string into a Go %s", v.Kind())
|
||||
return unstable.NewParserError(d.p.Raw(value.Raw), d.typeMismatchString("string", v.Type()))
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -1170,10 +1174,10 @@ func initAndDereferencePointer(v reflect.Value) reflect.Value {
|
|||
|
||||
// Same as reflect.Value.FieldByIndex, but creates pointers if needed.
|
||||
func fieldByIndex(v reflect.Value, path []int) reflect.Value {
|
||||
for i, x := range path {
|
||||
for _, x := range path {
|
||||
v = v.Field(x)
|
||||
|
||||
if i < len(path)-1 && v.Kind() == reflect.Ptr {
|
||||
if v.Kind() == reflect.Ptr {
|
||||
if v.IsNil() {
|
||||
v.Set(reflect.New(v.Type().Elem()))
|
||||
}
|
||||
|
|
|
@ -1013,6 +1013,7 @@ func (p *Parser) parseIntOrFloatOrDateTime(b []byte) (reference, []byte, error)
|
|||
return p.builder.Push(Node{
|
||||
Kind: Float,
|
||||
Data: b[:3],
|
||||
Raw: p.Range(b[:3]),
|
||||
}), b[3:], nil
|
||||
case 'n':
|
||||
if !scanFollowsNan(b) {
|
||||
|
@ -1022,6 +1023,7 @@ func (p *Parser) parseIntOrFloatOrDateTime(b []byte) (reference, []byte, error)
|
|||
return p.builder.Push(Node{
|
||||
Kind: Float,
|
||||
Data: b[:3],
|
||||
Raw: p.Range(b[:3]),
|
||||
}), b[3:], nil
|
||||
case '+', '-':
|
||||
return p.scanIntOrFloat(b)
|
||||
|
@ -1146,6 +1148,7 @@ func (p *Parser) scanIntOrFloat(b []byte) (reference, []byte, error) {
|
|||
return p.builder.Push(Node{
|
||||
Kind: Integer,
|
||||
Data: b[:i],
|
||||
Raw: p.Range(b[:i]),
|
||||
}), b[i:], nil
|
||||
}
|
||||
|
||||
|
@ -1169,6 +1172,7 @@ func (p *Parser) scanIntOrFloat(b []byte) (reference, []byte, error) {
|
|||
return p.builder.Push(Node{
|
||||
Kind: Float,
|
||||
Data: b[:i+3],
|
||||
Raw: p.Range(b[:i+3]),
|
||||
}), b[i+3:], nil
|
||||
}
|
||||
|
||||
|
@ -1180,6 +1184,7 @@ func (p *Parser) scanIntOrFloat(b []byte) (reference, []byte, error) {
|
|||
return p.builder.Push(Node{
|
||||
Kind: Float,
|
||||
Data: b[:i+3],
|
||||
Raw: p.Range(b[:i+3]),
|
||||
}), b[i+3:], nil
|
||||
}
|
||||
|
||||
|
@ -1202,6 +1207,7 @@ func (p *Parser) scanIntOrFloat(b []byte) (reference, []byte, error) {
|
|||
return p.builder.Push(Node{
|
||||
Kind: kind,
|
||||
Data: b[:i],
|
||||
Raw: p.Range(b[:i]),
|
||||
}), b[i:], nil
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_size = 4
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[{Makefile,*.mk}]
|
||||
indent_style = tab
|
||||
|
||||
[*.nix]
|
||||
indent_size = 2
|
||||
|
||||
[*.go]
|
||||
indent_style = tab
|
||||
|
||||
[{*.yml,*.yaml}]
|
||||
indent_size = 2
|
|
@ -0,0 +1,4 @@
|
|||
if ! has nix_direnv_version || ! nix_direnv_version 2.3.0; then
|
||||
source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/2.3.0/direnvrc" "sha256-Dmd+j63L84wuzgyjITIfSxSD57Tx7v51DMxVZOsiUD8="
|
||||
fi
|
||||
use flake . --impure
|
|
@ -0,0 +1,8 @@
|
|||
/.devenv/
|
||||
/.direnv/
|
||||
/.task/
|
||||
/bin/
|
||||
/build/
|
||||
/tmp/
|
||||
/var/
|
||||
/vendor/
|
|
@ -0,0 +1,27 @@
|
|||
run:
|
||||
timeout: 10m
|
||||
|
||||
linters-settings:
|
||||
gci:
|
||||
sections:
|
||||
- standard
|
||||
- default
|
||||
- prefix(github.com/sagikazarmark/locafero)
|
||||
goimports:
|
||||
local-prefixes: github.com/sagikazarmark/locafero
|
||||
misspell:
|
||||
locale: US
|
||||
nolintlint:
|
||||
allow-leading-space: false # require machine-readable nolint directives (with no leading space)
|
||||
allow-unused: false # report any unused nolint directives
|
||||
require-specific: false # don't require nolint directives to be specific about which linter is being skipped
|
||||
revive:
|
||||
confidence: 0
|
||||
|
||||
linters:
|
||||
enable:
|
||||
- gci
|
||||
- goimports
|
||||
- misspell
|
||||
- nolintlint
|
||||
- revive
|
|
@ -1,16 +1,14 @@
|
|||
The MIT License
|
||||
|
||||
Copyright (c) 2014 Benedikt Lang <github at benediktlang.de>
|
||||
Copyright (c) 2023 Márk Sági-Kazár <mark.sagikazar@gmail.com>
|
||||
|
||||
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:
|
||||
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 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,
|
||||
|
@ -19,4 +17,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
# Finder library for [Afero](https://github.com/spf13/afero)
|
||||
|
||||
[](https://github.com/sagikazarmark/locafero/actions/workflows/ci.yaml)
|
||||
[](https://pkg.go.dev/mod/github.com/sagikazarmark/locafero)
|
||||

|
||||
[](https://builtwithnix.org)
|
||||
|
||||
**Finder library for [Afero](https://github.com/spf13/afero) ported from [go-finder](https://github.com/sagikazarmark/go-finder).**
|
||||
|
||||
> [!WARNING]
|
||||
> This is an experimental library under development.
|
||||
>
|
||||
> **Backwards compatibility is not guaranteed, expect breaking changes.**
|
||||
|
||||
## Installation
|
||||
|
||||
```shell
|
||||
go get github.com/sagikazarmark/locafero
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Check out the [package example](https://pkg.go.dev/github.com/sagikazarmark/locafero#example-package) on go.dev.
|
||||
|
||||
## Development
|
||||
|
||||
**For an optimal developer experience, it is recommended to install [Nix](https://nixos.org/download.html) and [direnv](https://direnv.net/docs/installation.html).**
|
||||
|
||||
Run the test suite:
|
||||
|
||||
```shell
|
||||
just test
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
The project is licensed under the [MIT License](LICENSE).
|
|
@ -0,0 +1,28 @@
|
|||
package locafero
|
||||
|
||||
import "io/fs"
|
||||
|
||||
// FileType represents the kind of entries [Finder] can return.
|
||||
type FileType int
|
||||
|
||||
const (
|
||||
FileTypeAll FileType = iota
|
||||
FileTypeFile
|
||||
FileTypeDir
|
||||
)
|
||||
|
||||
func (ft FileType) matchFileInfo(info fs.FileInfo) bool {
|
||||
switch ft {
|
||||
case FileTypeAll:
|
||||
return true
|
||||
|
||||
case FileTypeFile:
|
||||
return !info.IsDir()
|
||||
|
||||
case FileTypeDir:
|
||||
return info.IsDir()
|
||||
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
|
@ -0,0 +1,165 @@
|
|||
// Package finder looks for files and directories in an {fs.Fs} filesystem.
|
||||
package locafero
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/fs"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/sourcegraph/conc/iter"
|
||||
"github.com/spf13/afero"
|
||||
)
|
||||
|
||||
// Finder looks for files and directories in an [afero.Fs] filesystem.
|
||||
type Finder struct {
|
||||
// Paths represents a list of locations that the [Finder] will search in.
|
||||
//
|
||||
// They are essentially the root directories or starting points for the search.
|
||||
//
|
||||
// Examples:
|
||||
// - home/user
|
||||
// - etc
|
||||
Paths []string
|
||||
|
||||
// Names are specific entries that the [Finder] will look for within the given Paths.
|
||||
//
|
||||
// It provides the capability to search for entries with depth,
|
||||
// meaning it can target deeper locations within the directory structure.
|
||||
//
|
||||
// It also supports glob syntax (as defined by [filepat.Match]), offering greater flexibility in search patterns.
|
||||
//
|
||||
// Examples:
|
||||
// - config.yaml
|
||||
// - home/*/config.yaml
|
||||
// - home/*/config.*
|
||||
Names []string
|
||||
|
||||
// Type restricts the kind of entries returned by the [Finder].
|
||||
//
|
||||
// This parameter helps in differentiating and filtering out files from directories or vice versa.
|
||||
Type FileType
|
||||
}
|
||||
|
||||
// Find looks for files and directories in an [afero.Fs] filesystem.
|
||||
func (f Finder) Find(fsys afero.Fs) ([]string, error) {
|
||||
// Arbitrary go routine limit (TODO: make this a parameter)
|
||||
// pool := pool.NewWithResults[[]string]().WithMaxGoroutines(5).WithErrors().WithFirstError()
|
||||
|
||||
type searchItem struct {
|
||||
path string
|
||||
name string
|
||||
}
|
||||
|
||||
var searchItems []searchItem
|
||||
|
||||
for _, searchPath := range f.Paths {
|
||||
searchPath := searchPath
|
||||
|
||||
for _, searchName := range f.Names {
|
||||
searchName := searchName
|
||||
|
||||
searchItems = append(searchItems, searchItem{searchPath, searchName})
|
||||
|
||||
// pool.Go(func() ([]string, error) {
|
||||
// // If the name contains any glob character, perform a glob match
|
||||
// if strings.ContainsAny(searchName, "*?[]\\^") {
|
||||
// return globWalkSearch(fsys, searchPath, searchName, f.Type)
|
||||
// }
|
||||
//
|
||||
// return statSearch(fsys, searchPath, searchName, f.Type)
|
||||
// })
|
||||
}
|
||||
}
|
||||
|
||||
// allResults, err := pool.Wait()
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
allResults, err := iter.MapErr(searchItems, func(item *searchItem) ([]string, error) {
|
||||
// If the name contains any glob character, perform a glob match
|
||||
if strings.ContainsAny(item.name, "*?[]\\^") {
|
||||
return globWalkSearch(fsys, item.path, item.name, f.Type)
|
||||
}
|
||||
|
||||
return statSearch(fsys, item.path, item.name, f.Type)
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var results []string
|
||||
|
||||
for _, r := range allResults {
|
||||
results = append(results, r...)
|
||||
}
|
||||
|
||||
// Sort results in alphabetical order for now
|
||||
// sort.Strings(results)
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func globWalkSearch(fsys afero.Fs, searchPath string, searchName string, searchType FileType) ([]string, error) {
|
||||
var results []string
|
||||
|
||||
err := afero.Walk(fsys, searchPath, func(p string, fileInfo fs.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Skip the root path
|
||||
if p == searchPath {
|
||||
return nil
|
||||
}
|
||||
|
||||
var result error
|
||||
|
||||
// Stop reading subdirectories
|
||||
// TODO: add depth detection here
|
||||
if fileInfo.IsDir() && filepath.Dir(p) == searchPath {
|
||||
result = fs.SkipDir
|
||||
}
|
||||
|
||||
// Skip unmatching type
|
||||
if !searchType.matchFileInfo(fileInfo) {
|
||||
return result
|
||||
}
|
||||
|
||||
match, err := filepath.Match(searchName, fileInfo.Name())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if match {
|
||||
results = append(results, p)
|
||||
}
|
||||
|
||||
return result
|
||||
})
|
||||
if err != nil {
|
||||
return results, err
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func statSearch(fsys afero.Fs, searchPath string, searchName string, searchType FileType) ([]string, error) {
|
||||
filePath := filepath.Join(searchPath, searchName)
|
||||
|
||||
fileInfo, err := fsys.Stat(filePath)
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Skip unmatching type
|
||||
if !searchType.matchFileInfo(fileInfo) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return []string{filePath}, nil
|
||||
}
|
|
@ -0,0 +1,273 @@
|
|||
{
|
||||
"nodes": {
|
||||
"devenv": {
|
||||
"inputs": {
|
||||
"flake-compat": "flake-compat",
|
||||
"nix": "nix",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"pre-commit-hooks": "pre-commit-hooks"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1694097209,
|
||||
"narHash": "sha256-gQmBjjxeSyySjbh0yQVBKApo2KWIFqqbRUvG+Fa+QpM=",
|
||||
"owner": "cachix",
|
||||
"repo": "devenv",
|
||||
"rev": "7a8e6a91510efe89d8dcb8e43233f93e86f6b189",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "cachix",
|
||||
"repo": "devenv",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-compat": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1673956053,
|
||||
"narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=",
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-parts": {
|
||||
"inputs": {
|
||||
"nixpkgs-lib": "nixpkgs-lib"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1693611461,
|
||||
"narHash": "sha256-aPODl8vAgGQ0ZYFIRisxYG5MOGSkIczvu2Cd8Gb9+1Y=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"rev": "7f53fdb7bdc5bb237da7fefef12d099e4fd611ca",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1685518550,
|
||||
"narHash": "sha256-o2d0KcvaXzTrPRIo0kOLV0/QXHhDQ5DTi+OxcjO8xqY=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "a1720a10a6cfe8234c0e93907ffe81be440f4cef",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"gitignore": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"devenv",
|
||||
"pre-commit-hooks",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1660459072,
|
||||
"narHash": "sha256-8DFJjXG8zqoONA1vXtgeKXy68KdJL5UaXR8NtVMUbx8=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "gitignore.nix",
|
||||
"rev": "a20de23b925fd8264fd7fad6454652e142fd7f73",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "hercules-ci",
|
||||
"repo": "gitignore.nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"lowdown-src": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1633514407,
|
||||
"narHash": "sha256-Dw32tiMjdK9t3ETl5fzGrutQTzh2rufgZV4A/BbxuD4=",
|
||||
"owner": "kristapsdz",
|
||||
"repo": "lowdown",
|
||||
"rev": "d2c2b44ff6c27b936ec27358a2653caaef8f73b8",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "kristapsdz",
|
||||
"repo": "lowdown",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nix": {
|
||||
"inputs": {
|
||||
"lowdown-src": "lowdown-src",
|
||||
"nixpkgs": [
|
||||
"devenv",
|
||||
"nixpkgs"
|
||||
],
|
||||
"nixpkgs-regression": "nixpkgs-regression"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1676545802,
|
||||
"narHash": "sha256-EK4rZ+Hd5hsvXnzSzk2ikhStJnD63odF7SzsQ8CuSPU=",
|
||||
"owner": "domenkozar",
|
||||
"repo": "nix",
|
||||
"rev": "7c91803598ffbcfe4a55c44ac6d49b2cf07a527f",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "domenkozar",
|
||||
"ref": "relaxed-flakes",
|
||||
"repo": "nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1678875422,
|
||||
"narHash": "sha256-T3o6NcQPwXjxJMn2shz86Chch4ljXgZn746c2caGxd8=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "126f49a01de5b7e35a43fd43f891ecf6d3a51459",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixpkgs-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs-lib": {
|
||||
"locked": {
|
||||
"dir": "lib",
|
||||
"lastModified": 1693471703,
|
||||
"narHash": "sha256-0l03ZBL8P1P6z8MaSDS/MvuU8E75rVxe5eE1N6gxeTo=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "3e52e76b70d5508f3cec70b882a29199f4d1ee85",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"dir": "lib",
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs-regression": {
|
||||
"locked": {
|
||||
"lastModified": 1643052045,
|
||||
"narHash": "sha256-uGJ0VXIhWKGXxkeNnq4TvV3CIOkUJ3PAoLZ3HMzNVMw=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs-stable": {
|
||||
"locked": {
|
||||
"lastModified": 1685801374,
|
||||
"narHash": "sha256-otaSUoFEMM+LjBI1XL/xGB5ao6IwnZOXc47qhIgJe8U=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "c37ca420157f4abc31e26f436c1145f8951ff373",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-23.05",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1694343207,
|
||||
"narHash": "sha256-jWi7OwFxU5Owi4k2JmiL1sa/OuBCQtpaAesuj5LXC8w=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "78058d810644f5ed276804ce7ea9e82d92bee293",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixpkgs-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"pre-commit-hooks": {
|
||||
"inputs": {
|
||||
"flake-compat": [
|
||||
"devenv",
|
||||
"flake-compat"
|
||||
],
|
||||
"flake-utils": "flake-utils",
|
||||
"gitignore": "gitignore",
|
||||
"nixpkgs": [
|
||||
"devenv",
|
||||
"nixpkgs"
|
||||
],
|
||||
"nixpkgs-stable": "nixpkgs-stable"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1688056373,
|
||||
"narHash": "sha256-2+SDlNRTKsgo3LBRiMUcoEUb6sDViRNQhzJquZ4koOI=",
|
||||
"owner": "cachix",
|
||||
"repo": "pre-commit-hooks.nix",
|
||||
"rev": "5843cf069272d92b60c3ed9e55b7a8989c01d4c7",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "cachix",
|
||||
"repo": "pre-commit-hooks.nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"devenv": "devenv",
|
||||
"flake-parts": "flake-parts",
|
||||
"nixpkgs": "nixpkgs_2"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
{
|
||||
description = "Finder library for Afero";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
||||
flake-parts.url = "github:hercules-ci/flake-parts";
|
||||
devenv.url = "github:cachix/devenv";
|
||||
};
|
||||
|
||||
outputs = inputs@{ flake-parts, ... }:
|
||||
flake-parts.lib.mkFlake { inherit inputs; } {
|
||||
imports = [
|
||||
inputs.devenv.flakeModule
|
||||
];
|
||||
|
||||
systems = [ "x86_64-linux" "aarch64-darwin" ];
|
||||
|
||||
perSystem = { config, self', inputs', pkgs, system, ... }: rec {
|
||||
devenv.shells = {
|
||||
default = {
|
||||
languages = {
|
||||
go.enable = true;
|
||||
};
|
||||
|
||||
packages = with pkgs; [
|
||||
just
|
||||
|
||||
golangci-lint
|
||||
];
|
||||
|
||||
# https://github.com/cachix/devenv/issues/528#issuecomment-1556108767
|
||||
containers = pkgs.lib.mkForce { };
|
||||
};
|
||||
|
||||
ci = devenv.shells.default;
|
||||
|
||||
ci_1_20 = {
|
||||
imports = [ devenv.shells.ci ];
|
||||
|
||||
languages = {
|
||||
go.package = pkgs.go_1_20;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package locafero
|
||||
|
||||
import "fmt"
|
||||
|
||||
// NameWithExtensions creates a list of names from a base name and a list of extensions.
|
||||
//
|
||||
// TODO: find a better name for this function.
|
||||
func NameWithExtensions(baseName string, extensions ...string) []string {
|
||||
var names []string
|
||||
|
||||
if baseName == "" {
|
||||
return names
|
||||
}
|
||||
|
||||
for _, ext := range extensions {
|
||||
if ext == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
names = append(names, fmt.Sprintf("%s.%s", baseName, ext))
|
||||
}
|
||||
|
||||
return names
|
||||
}
|
||||
|
||||
// NameWithOptionalExtensions creates a list of names from a base name and a list of extensions,
|
||||
// plus it adds the base name (without any extensions) to the end of the list.
|
||||
//
|
||||
// TODO: find a better name for this function.
|
||||
func NameWithOptionalExtensions(baseName string, extensions ...string) []string {
|
||||
var names []string
|
||||
|
||||
if baseName == "" {
|
||||
return names
|
||||
}
|
||||
|
||||
names = NameWithExtensions(baseName, extensions...)
|
||||
names = append(names, baseName)
|
||||
|
||||
return names
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
default:
|
||||
just --list
|
||||
|
||||
test:
|
||||
go test -race -v ./...
|
||||
|
||||
lint:
|
||||
golangci-lint run
|
||||
|
||||
fmt:
|
||||
golangci-lint run --fix
|
|
@ -0,0 +1,18 @@
|
|||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_size = 4
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.nix]
|
||||
indent_size = 2
|
||||
|
||||
[{Makefile,*.mk}]
|
||||
indent_style = tab
|
||||
|
||||
[Taskfile.yaml]
|
||||
indent_size = 2
|
|
@ -0,0 +1,4 @@
|
|||
if ! has nix_direnv_version || ! nix_direnv_version 2.3.0; then
|
||||
source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/2.3.0/direnvrc" "sha256-Dmd+j63L84wuzgyjITIfSxSD57Tx7v51DMxVZOsiUD8="
|
||||
fi
|
||||
use flake . --impure
|
|
@ -0,0 +1,4 @@
|
|||
/.devenv/
|
||||
/.direnv/
|
||||
/.task/
|
||||
/build/
|
|
@ -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.
|
|
@ -0,0 +1,81 @@
|
|||
# [slog](https://pkg.go.dev/log/slog) shim
|
||||
|
||||
[](https://github.com/sagikazarmark/slog-shim/actions/workflows/ci.yaml)
|
||||
[](https://pkg.go.dev/mod/github.com/sagikazarmark/slog-shim)
|
||||

|
||||
[](https://builtwithnix.org)
|
||||
|
||||
Go 1.21 introduced a [new structured logging package](https://golang.org/doc/go1.21#slog), `log/slog`, to the standard library.
|
||||
Although it's been eagerly anticipated by many, widespread adoption isn't expected to occur immediately,
|
||||
especially since updating to Go 1.21 is a decision that most libraries won't make overnight.
|
||||
|
||||
Before this package was added to the standard library, there was an _experimental_ version available at [golang.org/x/exp/slog](https://pkg.go.dev/golang.org/x/exp/slog).
|
||||
While it's generally advised against using experimental packages in production,
|
||||
this one served as a sort of backport package for the last few years,
|
||||
incorporating new features before they were added to the standard library (like `slices`, `maps` or `errors`).
|
||||
|
||||
This package serves as a bridge, helping libraries integrate slog in a backward-compatible way without having to immediately update their Go version requirement to 1.21. On Go 1.21 (and above), it acts as a drop-in replacement for `log/slog`, while below 1.21 it falls back to `golang.org/x/exp/slog`.
|
||||
|
||||
**How does it achieve backwards compatibility?**
|
||||
|
||||
Although there's no consensus on whether dropping support for older Go versions is considered backward compatible, a majority seems to believe it is.
|
||||
(I don't have scientific proof for this, but it's based on conversations with various individuals across different channels.)
|
||||
|
||||
This package adheres to that interpretation of backward compatibility. On Go 1.21, the shim uses type aliases to offer the same API as `slog/log`.
|
||||
Once a library upgrades its version requirement to Go 1.21, it should be able to discard this shim and use `log/slog` directly.
|
||||
|
||||
For older Go versions, the library might become unstable after removing the shim.
|
||||
However, since those older versions are no longer supported, the promise of backward compatibility remains intact.
|
||||
|
||||
## Installation
|
||||
|
||||
```shell
|
||||
go get github.com/sagikazarmark/slog-shim
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Import this package into your library and use it in your public API:
|
||||
|
||||
```go
|
||||
package mylib
|
||||
|
||||
import slog "github.com/sagikazarmark/slog-shim"
|
||||
|
||||
func New(logger *slog.Logger) MyLib {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
When using the library, clients can either use `log/slog` (when on Go 1.21) or `golang.org/x/exp/slog` (below Go 1.21):
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import "log/slog"
|
||||
|
||||
// OR
|
||||
|
||||
import "golang.org/x/exp/slog"
|
||||
|
||||
mylib.New(slog.Default())
|
||||
```
|
||||
|
||||
**Make sure consumers are aware that your API behaves differently on different Go versions.**
|
||||
|
||||
Once you bump your Go version requirement to Go 1.21, you can drop the shim entirely from your code:
|
||||
|
||||
```diff
|
||||
package mylib
|
||||
|
||||
- import slog "github.com/sagikazarmark/slog-shim"
|
||||
+ import "log/slog"
|
||||
|
||||
func New(logger *slog.Logger) MyLib {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
The project is licensed under a [BSD-style license](LICENSE).
|
|
@ -0,0 +1,74 @@
|
|||
// Copyright 2022 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.21
|
||||
|
||||
package slog
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"time"
|
||||
)
|
||||
|
||||
// An Attr is a key-value pair.
|
||||
type Attr = slog.Attr
|
||||
|
||||
// String returns an Attr for a string value.
|
||||
func String(key, value string) Attr {
|
||||
return slog.String(key, value)
|
||||
}
|
||||
|
||||
// Int64 returns an Attr for an int64.
|
||||
func Int64(key string, value int64) Attr {
|
||||
return slog.Int64(key, value)
|
||||
}
|
||||
|
||||
// Int converts an int to an int64 and returns
|
||||
// an Attr with that value.
|
||||
func Int(key string, value int) Attr {
|
||||
return slog.Int(key, value)
|
||||
}
|
||||
|
||||
// Uint64 returns an Attr for a uint64.
|
||||
func Uint64(key string, v uint64) Attr {
|
||||
return slog.Uint64(key, v)
|
||||
}
|
||||
|
||||
// Float64 returns an Attr for a floating-point number.
|
||||
func Float64(key string, v float64) Attr {
|
||||
return slog.Float64(key, v)
|
||||
}
|
||||
|
||||
// Bool returns an Attr for a bool.
|
||||
func Bool(key string, v bool) Attr {
|
||||
return slog.Bool(key, v)
|
||||
}
|
||||
|
||||
// Time returns an Attr for a time.Time.
|
||||
// It discards the monotonic portion.
|
||||
func Time(key string, v time.Time) Attr {
|
||||
return slog.Time(key, v)
|
||||
}
|
||||
|
||||
// Duration returns an Attr for a time.Duration.
|
||||
func Duration(key string, v time.Duration) Attr {
|
||||
return slog.Duration(key, v)
|
||||
}
|
||||
|
||||
// Group returns an Attr for a Group Value.
|
||||
// The first argument is the key; the remaining arguments
|
||||
// are converted to Attrs as in [Logger.Log].
|
||||
//
|
||||
// Use Group to collect several key-value pairs under a single
|
||||
// key on a log line, or as the result of LogValue
|
||||
// in order to log a single value as multiple Attrs.
|
||||
func Group(key string, args ...any) Attr {
|
||||
return slog.Group(key, args...)
|
||||
}
|
||||
|
||||
// Any returns an Attr for the supplied value.
|
||||
// See [Value.AnyValue] for how values are treated.
|
||||
func Any(key string, value any) Attr {
|
||||
return slog.Any(key, value)
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
// Copyright 2022 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.21
|
||||
|
||||
package slog
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"golang.org/x/exp/slog"
|
||||
)
|
||||
|
||||
// An Attr is a key-value pair.
|
||||
type Attr = slog.Attr
|
||||
|
||||
// String returns an Attr for a string value.
|
||||
func String(key, value string) Attr {
|
||||
return slog.String(key, value)
|
||||
}
|
||||
|
||||
// Int64 returns an Attr for an int64.
|
||||
func Int64(key string, value int64) Attr {
|
||||
return slog.Int64(key, value)
|
||||
}
|
||||
|
||||
// Int converts an int to an int64 and returns
|
||||
// an Attr with that value.
|
||||
func Int(key string, value int) Attr {
|
||||
return slog.Int(key, value)
|
||||
}
|
||||
|
||||
// Uint64 returns an Attr for a uint64.
|
||||
func Uint64(key string, v uint64) Attr {
|
||||
return slog.Uint64(key, v)
|
||||
}
|
||||
|
||||
// Float64 returns an Attr for a floating-point number.
|
||||
func Float64(key string, v float64) Attr {
|
||||
return slog.Float64(key, v)
|
||||
}
|
||||
|
||||
// Bool returns an Attr for a bool.
|
||||
func Bool(key string, v bool) Attr {
|
||||
return slog.Bool(key, v)
|
||||
}
|
||||
|
||||
// Time returns an Attr for a time.Time.
|
||||
// It discards the monotonic portion.
|
||||
func Time(key string, v time.Time) Attr {
|
||||
return slog.Time(key, v)
|
||||
}
|
||||
|
||||
// Duration returns an Attr for a time.Duration.
|
||||
func Duration(key string, v time.Duration) Attr {
|
||||
return slog.Duration(key, v)
|
||||
}
|
||||
|
||||
// Group returns an Attr for a Group Value.
|
||||
// The first argument is the key; the remaining arguments
|
||||
// are converted to Attrs as in [Logger.Log].
|
||||
//
|
||||
// Use Group to collect several key-value pairs under a single
|
||||
// key on a log line, or as the result of LogValue
|
||||
// in order to log a single value as multiple Attrs.
|
||||
func Group(key string, args ...any) Attr {
|
||||
return slog.Group(key, args...)
|
||||
}
|
||||
|
||||
// Any returns an Attr for the supplied value.
|
||||
// See [Value.AnyValue] for how values are treated.
|
||||
func Any(key string, value any) Attr {
|
||||
return slog.Any(key, value)
|
||||
}
|
|
@ -0,0 +1,273 @@
|
|||
{
|
||||
"nodes": {
|
||||
"devenv": {
|
||||
"inputs": {
|
||||
"flake-compat": "flake-compat",
|
||||
"nix": "nix",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"pre-commit-hooks": "pre-commit-hooks"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1694097209,
|
||||
"narHash": "sha256-gQmBjjxeSyySjbh0yQVBKApo2KWIFqqbRUvG+Fa+QpM=",
|
||||
"owner": "cachix",
|
||||
"repo": "devenv",
|
||||
"rev": "7a8e6a91510efe89d8dcb8e43233f93e86f6b189",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "cachix",
|
||||
"repo": "devenv",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-compat": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1673956053,
|
||||
"narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=",
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-parts": {
|
||||
"inputs": {
|
||||
"nixpkgs-lib": "nixpkgs-lib"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1693611461,
|
||||
"narHash": "sha256-aPODl8vAgGQ0ZYFIRisxYG5MOGSkIczvu2Cd8Gb9+1Y=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"rev": "7f53fdb7bdc5bb237da7fefef12d099e4fd611ca",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1685518550,
|
||||
"narHash": "sha256-o2d0KcvaXzTrPRIo0kOLV0/QXHhDQ5DTi+OxcjO8xqY=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "a1720a10a6cfe8234c0e93907ffe81be440f4cef",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"gitignore": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"devenv",
|
||||
"pre-commit-hooks",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1660459072,
|
||||
"narHash": "sha256-8DFJjXG8zqoONA1vXtgeKXy68KdJL5UaXR8NtVMUbx8=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "gitignore.nix",
|
||||
"rev": "a20de23b925fd8264fd7fad6454652e142fd7f73",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "hercules-ci",
|
||||
"repo": "gitignore.nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"lowdown-src": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1633514407,
|
||||
"narHash": "sha256-Dw32tiMjdK9t3ETl5fzGrutQTzh2rufgZV4A/BbxuD4=",
|
||||
"owner": "kristapsdz",
|
||||
"repo": "lowdown",
|
||||
"rev": "d2c2b44ff6c27b936ec27358a2653caaef8f73b8",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "kristapsdz",
|
||||
"repo": "lowdown",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nix": {
|
||||
"inputs": {
|
||||
"lowdown-src": "lowdown-src",
|
||||
"nixpkgs": [
|
||||
"devenv",
|
||||
"nixpkgs"
|
||||
],
|
||||
"nixpkgs-regression": "nixpkgs-regression"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1676545802,
|
||||
"narHash": "sha256-EK4rZ+Hd5hsvXnzSzk2ikhStJnD63odF7SzsQ8CuSPU=",
|
||||
"owner": "domenkozar",
|
||||
"repo": "nix",
|
||||
"rev": "7c91803598ffbcfe4a55c44ac6d49b2cf07a527f",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "domenkozar",
|
||||
"ref": "relaxed-flakes",
|
||||
"repo": "nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1678875422,
|
||||
"narHash": "sha256-T3o6NcQPwXjxJMn2shz86Chch4ljXgZn746c2caGxd8=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "126f49a01de5b7e35a43fd43f891ecf6d3a51459",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixpkgs-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs-lib": {
|
||||
"locked": {
|
||||
"dir": "lib",
|
||||
"lastModified": 1693471703,
|
||||
"narHash": "sha256-0l03ZBL8P1P6z8MaSDS/MvuU8E75rVxe5eE1N6gxeTo=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "3e52e76b70d5508f3cec70b882a29199f4d1ee85",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"dir": "lib",
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs-regression": {
|
||||
"locked": {
|
||||
"lastModified": 1643052045,
|
||||
"narHash": "sha256-uGJ0VXIhWKGXxkeNnq4TvV3CIOkUJ3PAoLZ3HMzNVMw=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs-stable": {
|
||||
"locked": {
|
||||
"lastModified": 1685801374,
|
||||
"narHash": "sha256-otaSUoFEMM+LjBI1XL/xGB5ao6IwnZOXc47qhIgJe8U=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "c37ca420157f4abc31e26f436c1145f8951ff373",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-23.05",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1694345580,
|
||||
"narHash": "sha256-BbG0NUxQTz1dN/Y87yPWZc/0Kp/coJ0vM3+7sNa5kUM=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "f002de6834fdde9c864f33c1ec51da7df19cd832",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "master",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"pre-commit-hooks": {
|
||||
"inputs": {
|
||||
"flake-compat": [
|
||||
"devenv",
|
||||
"flake-compat"
|
||||
],
|
||||
"flake-utils": "flake-utils",
|
||||
"gitignore": "gitignore",
|
||||
"nixpkgs": [
|
||||
"devenv",
|
||||
"nixpkgs"
|
||||
],
|
||||
"nixpkgs-stable": "nixpkgs-stable"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1688056373,
|
||||
"narHash": "sha256-2+SDlNRTKsgo3LBRiMUcoEUb6sDViRNQhzJquZ4koOI=",
|
||||
"owner": "cachix",
|
||||
"repo": "pre-commit-hooks.nix",
|
||||
"rev": "5843cf069272d92b60c3ed9e55b7a8989c01d4c7",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "cachix",
|
||||
"repo": "pre-commit-hooks.nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"devenv": "devenv",
|
||||
"flake-parts": "flake-parts",
|
||||
"nixpkgs": "nixpkgs_2"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
{
|
||||
inputs = {
|
||||
# nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/master";
|
||||
flake-parts.url = "github:hercules-ci/flake-parts";
|
||||
devenv.url = "github:cachix/devenv";
|
||||
};
|
||||
|
||||
outputs = inputs@{ flake-parts, ... }:
|
||||
flake-parts.lib.mkFlake { inherit inputs; } {
|
||||
imports = [
|
||||
inputs.devenv.flakeModule
|
||||
];
|
||||
|
||||
systems = [ "x86_64-linux" "x86_64-darwin" "aarch64-darwin" ];
|
||||
|
||||
perSystem = { config, self', inputs', pkgs, system, ... }: rec {
|
||||
devenv.shells = {
|
||||
default = {
|
||||
languages = {
|
||||
go.enable = true;
|
||||
go.package = pkgs.lib.mkDefault pkgs.go_1_21;
|
||||
};
|
||||
|
||||
# https://github.com/cachix/devenv/issues/528#issuecomment-1556108767
|
||||
containers = pkgs.lib.mkForce { };
|
||||
};
|
||||
|
||||
ci = devenv.shells.default;
|
||||
|
||||
ci_1_19 = {
|
||||
imports = [ devenv.shells.ci ];
|
||||
|
||||
languages = {
|
||||
go.package = pkgs.go_1_19;
|
||||
};
|
||||
};
|
||||
|
||||
ci_1_20 = {
|
||||
imports = [ devenv.shells.ci ];
|
||||
|
||||
languages = {
|
||||
go.package = pkgs.go_1_20;
|
||||
};
|
||||
};
|
||||
|
||||
ci_1_21 = {
|
||||
imports = [ devenv.shells.ci ];
|
||||
|
||||
languages = {
|
||||
go.package = pkgs.go_1_21;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
// Copyright 2022 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.21
|
||||
|
||||
package slog
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
)
|
||||
|
||||
// A Handler handles log records produced by a Logger..
|
||||
//
|
||||
// A typical handler may print log records to standard error,
|
||||
// or write them to a file or database, or perhaps augment them
|
||||
// with additional attributes and pass them on to another handler.
|
||||
//
|
||||
// Any of the Handler's methods may be called concurrently with itself
|
||||
// or with other methods. It is the responsibility of the Handler to
|
||||
// manage this concurrency.
|
||||
//
|
||||
// Users of the slog package should not invoke Handler methods directly.
|
||||
// They should use the methods of [Logger] instead.
|
||||
type Handler = slog.Handler
|
||||
|
||||
// HandlerOptions are options for a TextHandler or JSONHandler.
|
||||
// A zero HandlerOptions consists entirely of default values.
|
||||
type HandlerOptions = slog.HandlerOptions
|
||||
|
||||
// Keys for "built-in" attributes.
|
||||
const (
|
||||
// TimeKey is the key used by the built-in handlers for the time
|
||||
// when the log method is called. The associated Value is a [time.Time].
|
||||
TimeKey = slog.TimeKey
|
||||
// LevelKey is the key used by the built-in handlers for the level
|
||||
// of the log call. The associated value is a [Level].
|
||||
LevelKey = slog.LevelKey
|
||||
// MessageKey is the key used by the built-in handlers for the
|
||||
// message of the log call. The associated value is a string.
|
||||
MessageKey = slog.MessageKey
|
||||
// SourceKey is the key used by the built-in handlers for the source file
|
||||
// and line of the log call. The associated value is a string.
|
||||
SourceKey = slog.SourceKey
|
||||
)
|
|
@ -0,0 +1,45 @@
|
|||
// Copyright 2022 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.21
|
||||
|
||||
package slog
|
||||
|
||||
import (
|
||||
"golang.org/x/exp/slog"
|
||||
)
|
||||
|
||||
// A Handler handles log records produced by a Logger..
|
||||
//
|
||||
// A typical handler may print log records to standard error,
|
||||
// or write them to a file or database, or perhaps augment them
|
||||
// with additional attributes and pass them on to another handler.
|
||||
//
|
||||
// Any of the Handler's methods may be called concurrently with itself
|
||||
// or with other methods. It is the responsibility of the Handler to
|
||||
// manage this concurrency.
|
||||
//
|
||||
// Users of the slog package should not invoke Handler methods directly.
|
||||
// They should use the methods of [Logger] instead.
|
||||
type Handler = slog.Handler
|
||||
|
||||
// HandlerOptions are options for a TextHandler or JSONHandler.
|
||||
// A zero HandlerOptions consists entirely of default values.
|
||||
type HandlerOptions = slog.HandlerOptions
|
||||
|
||||
// Keys for "built-in" attributes.
|
||||
const (
|
||||
// TimeKey is the key used by the built-in handlers for the time
|
||||
// when the log method is called. The associated Value is a [time.Time].
|
||||
TimeKey = slog.TimeKey
|
||||
// LevelKey is the key used by the built-in handlers for the level
|
||||
// of the log call. The associated value is a [Level].
|
||||
LevelKey = slog.LevelKey
|
||||
// MessageKey is the key used by the built-in handlers for the
|
||||
// message of the log call. The associated value is a string.
|
||||
MessageKey = slog.MessageKey
|
||||
// SourceKey is the key used by the built-in handlers for the source file
|
||||
// and line of the log call. The associated value is a string.
|
||||
SourceKey = slog.SourceKey
|
||||
)
|
|
@ -0,0 +1,23 @@
|
|||
// Copyright 2022 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.21
|
||||
|
||||
package slog
|
||||
|
||||
import (
|
||||
"io"
|
||||
"log/slog"
|
||||
)
|
||||
|
||||
// JSONHandler is a Handler that writes Records to an io.Writer as
|
||||
// line-delimited JSON objects.
|
||||
type JSONHandler = slog.JSONHandler
|
||||
|
||||
// NewJSONHandler creates a JSONHandler that writes to w,
|
||||
// using the given options.
|
||||
// If opts is nil, the default options are used.
|
||||
func NewJSONHandler(w io.Writer, opts *HandlerOptions) *JSONHandler {
|
||||
return slog.NewJSONHandler(w, opts)
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
// Copyright 2022 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.21
|
||||
|
||||
package slog
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"golang.org/x/exp/slog"
|
||||
)
|
||||
|
||||
// JSONHandler is a Handler that writes Records to an io.Writer as
|
||||
// line-delimited JSON objects.
|
||||
type JSONHandler = slog.JSONHandler
|
||||
|
||||
// NewJSONHandler creates a JSONHandler that writes to w,
|
||||
// using the given options.
|
||||
// If opts is nil, the default options are used.
|
||||
func NewJSONHandler(w io.Writer, opts *HandlerOptions) *JSONHandler {
|
||||
return slog.NewJSONHandler(w, opts)
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
// Copyright 2022 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.21
|
||||
|
||||
package slog
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
)
|
||||
|
||||
// A Level is the importance or severity of a log event.
|
||||
// The higher the level, the more important or severe the event.
|
||||
type Level = slog.Level
|
||||
|
||||
// Level numbers are inherently arbitrary,
|
||||
// but we picked them to satisfy three constraints.
|
||||
// Any system can map them to another numbering scheme if it wishes.
|
||||
//
|
||||
// First, we wanted the default level to be Info, Since Levels are ints, Info is
|
||||
// the default value for int, zero.
|
||||
//
|
||||
// Second, we wanted to make it easy to use levels to specify logger verbosity.
|
||||
// Since a larger level means a more severe event, a logger that accepts events
|
||||
// with smaller (or more negative) level means a more verbose logger. Logger
|
||||
// verbosity is thus the negation of event severity, and the default verbosity
|
||||
// of 0 accepts all events at least as severe as INFO.
|
||||
//
|
||||
// Third, we wanted some room between levels to accommodate schemes with named
|
||||
// levels between ours. For example, Google Cloud Logging defines a Notice level
|
||||
// between Info and Warn. Since there are only a few of these intermediate
|
||||
// levels, the gap between the numbers need not be large. Our gap of 4 matches
|
||||
// OpenTelemetry's mapping. Subtracting 9 from an OpenTelemetry level in the
|
||||
// DEBUG, INFO, WARN and ERROR ranges converts it to the corresponding slog
|
||||
// Level range. OpenTelemetry also has the names TRACE and FATAL, which slog
|
||||
// does not. But those OpenTelemetry levels can still be represented as slog
|
||||
// Levels by using the appropriate integers.
|
||||
//
|
||||
// Names for common levels.
|
||||
const (
|
||||
LevelDebug Level = slog.LevelDebug
|
||||
LevelInfo Level = slog.LevelInfo
|
||||
LevelWarn Level = slog.LevelWarn
|
||||
LevelError Level = slog.LevelError
|
||||
)
|
||||
|
||||
// A LevelVar is a Level variable, to allow a Handler level to change
|
||||
// dynamically.
|
||||
// It implements Leveler as well as a Set method,
|
||||
// and it is safe for use by multiple goroutines.
|
||||
// The zero LevelVar corresponds to LevelInfo.
|
||||
type LevelVar = slog.LevelVar
|
||||
|
||||
// A Leveler provides a Level value.
|
||||
//
|
||||
// As Level itself implements Leveler, clients typically supply
|
||||
// a Level value wherever a Leveler is needed, such as in HandlerOptions.
|
||||
// Clients who need to vary the level dynamically can provide a more complex
|
||||
// Leveler implementation such as *LevelVar.
|
||||
type Leveler = slog.Leveler
|
|
@ -0,0 +1,61 @@
|
|||
// Copyright 2022 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.21
|
||||
|
||||
package slog
|
||||
|
||||
import (
|
||||
"golang.org/x/exp/slog"
|
||||
)
|
||||
|
||||
// A Level is the importance or severity of a log event.
|
||||
// The higher the level, the more important or severe the event.
|
||||
type Level = slog.Level
|
||||
|
||||
// Level numbers are inherently arbitrary,
|
||||
// but we picked them to satisfy three constraints.
|
||||
// Any system can map them to another numbering scheme if it wishes.
|
||||
//
|
||||
// First, we wanted the default level to be Info, Since Levels are ints, Info is
|
||||
// the default value for int, zero.
|
||||
//
|
||||
// Second, we wanted to make it easy to use levels to specify logger verbosity.
|
||||
// Since a larger level means a more severe event, a logger that accepts events
|
||||
// with smaller (or more negative) level means a more verbose logger. Logger
|
||||
// verbosity is thus the negation of event severity, and the default verbosity
|
||||
// of 0 accepts all events at least as severe as INFO.
|
||||
//
|
||||
// Third, we wanted some room between levels to accommodate schemes with named
|
||||
// levels between ours. For example, Google Cloud Logging defines a Notice level
|
||||
// between Info and Warn. Since there are only a few of these intermediate
|
||||
// levels, the gap between the numbers need not be large. Our gap of 4 matches
|
||||
// OpenTelemetry's mapping. Subtracting 9 from an OpenTelemetry level in the
|
||||
// DEBUG, INFO, WARN and ERROR ranges converts it to the corresponding slog
|
||||
// Level range. OpenTelemetry also has the names TRACE and FATAL, which slog
|
||||
// does not. But those OpenTelemetry levels can still be represented as slog
|
||||
// Levels by using the appropriate integers.
|
||||
//
|
||||
// Names for common levels.
|
||||
const (
|
||||
LevelDebug Level = slog.LevelDebug
|
||||
LevelInfo Level = slog.LevelInfo
|
||||
LevelWarn Level = slog.LevelWarn
|
||||
LevelError Level = slog.LevelError
|
||||
)
|
||||
|
||||
// A LevelVar is a Level variable, to allow a Handler level to change
|
||||
// dynamically.
|
||||
// It implements Leveler as well as a Set method,
|
||||
// and it is safe for use by multiple goroutines.
|
||||
// The zero LevelVar corresponds to LevelInfo.
|
||||
type LevelVar = slog.LevelVar
|
||||
|
||||
// A Leveler provides a Level value.
|
||||
//
|
||||
// As Level itself implements Leveler, clients typically supply
|
||||
// a Level value wherever a Leveler is needed, such as in HandlerOptions.
|
||||
// Clients who need to vary the level dynamically can provide a more complex
|
||||
// Leveler implementation such as *LevelVar.
|
||||
type Leveler = slog.Leveler
|
|
@ -0,0 +1,98 @@
|
|||
// Copyright 2022 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.21
|
||||
|
||||
package slog
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"log/slog"
|
||||
)
|
||||
|
||||
// Default returns the default Logger.
|
||||
func Default() *Logger { return slog.Default() }
|
||||
|
||||
// SetDefault makes l the default Logger.
|
||||
// After this call, output from the log package's default Logger
|
||||
// (as with [log.Print], etc.) will be logged at LevelInfo using l's Handler.
|
||||
func SetDefault(l *Logger) {
|
||||
slog.SetDefault(l)
|
||||
}
|
||||
|
||||
// A Logger records structured information about each call to its
|
||||
// Log, Debug, Info, Warn, and Error methods.
|
||||
// For each call, it creates a Record and passes it to a Handler.
|
||||
//
|
||||
// To create a new Logger, call [New] or a Logger method
|
||||
// that begins "With".
|
||||
type Logger = slog.Logger
|
||||
|
||||
// New creates a new Logger with the given non-nil Handler.
|
||||
func New(h Handler) *Logger {
|
||||
return slog.New(h)
|
||||
}
|
||||
|
||||
// With calls Logger.With on the default logger.
|
||||
func With(args ...any) *Logger {
|
||||
return slog.With(args...)
|
||||
}
|
||||
|
||||
// NewLogLogger returns a new log.Logger such that each call to its Output method
|
||||
// dispatches a Record to the specified handler. The logger acts as a bridge from
|
||||
// the older log API to newer structured logging handlers.
|
||||
func NewLogLogger(h Handler, level Level) *log.Logger {
|
||||
return slog.NewLogLogger(h, level)
|
||||
}
|
||||
|
||||
// Debug calls Logger.Debug on the default logger.
|
||||
func Debug(msg string, args ...any) {
|
||||
slog.Debug(msg, args...)
|
||||
}
|
||||
|
||||
// DebugContext calls Logger.DebugContext on the default logger.
|
||||
func DebugContext(ctx context.Context, msg string, args ...any) {
|
||||
slog.DebugContext(ctx, msg, args...)
|
||||
}
|
||||
|
||||
// Info calls Logger.Info on the default logger.
|
||||
func Info(msg string, args ...any) {
|
||||
slog.Info(msg, args...)
|
||||
}
|
||||
|
||||
// InfoContext calls Logger.InfoContext on the default logger.
|
||||
func InfoContext(ctx context.Context, msg string, args ...any) {
|
||||
slog.InfoContext(ctx, msg, args...)
|
||||
}
|
||||
|
||||
// Warn calls Logger.Warn on the default logger.
|
||||
func Warn(msg string, args ...any) {
|
||||
slog.Warn(msg, args...)
|
||||
}
|
||||
|
||||
// WarnContext calls Logger.WarnContext on the default logger.
|
||||
func WarnContext(ctx context.Context, msg string, args ...any) {
|
||||
slog.WarnContext(ctx, msg, args...)
|
||||
}
|
||||
|
||||
// Error calls Logger.Error on the default logger.
|
||||
func Error(msg string, args ...any) {
|
||||
slog.Error(msg, args...)
|
||||
}
|
||||
|
||||
// ErrorContext calls Logger.ErrorContext on the default logger.
|
||||
func ErrorContext(ctx context.Context, msg string, args ...any) {
|
||||
slog.ErrorContext(ctx, msg, args...)
|
||||
}
|
||||
|
||||
// Log calls Logger.Log on the default logger.
|
||||
func Log(ctx context.Context, level Level, msg string, args ...any) {
|
||||
slog.Log(ctx, level, msg, args...)
|
||||
}
|
||||
|
||||
// LogAttrs calls Logger.LogAttrs on the default logger.
|
||||
func LogAttrs(ctx context.Context, level Level, msg string, attrs ...Attr) {
|
||||
slog.LogAttrs(ctx, level, msg, attrs...)
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
// Copyright 2022 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.21
|
||||
|
||||
package slog
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
|
||||
"golang.org/x/exp/slog"
|
||||
)
|
||||
|
||||
// Default returns the default Logger.
|
||||
func Default() *Logger { return slog.Default() }
|
||||
|
||||
// SetDefault makes l the default Logger.
|
||||
// After this call, output from the log package's default Logger
|
||||
// (as with [log.Print], etc.) will be logged at LevelInfo using l's Handler.
|
||||
func SetDefault(l *Logger) {
|
||||
slog.SetDefault(l)
|
||||
}
|
||||
|
||||
// A Logger records structured information about each call to its
|
||||
// Log, Debug, Info, Warn, and Error methods.
|
||||
// For each call, it creates a Record and passes it to a Handler.
|
||||
//
|
||||
// To create a new Logger, call [New] or a Logger method
|
||||
// that begins "With".
|
||||
type Logger = slog.Logger
|
||||
|
||||
// New creates a new Logger with the given non-nil Handler.
|
||||
func New(h Handler) *Logger {
|
||||
return slog.New(h)
|
||||
}
|
||||
|
||||
// With calls Logger.With on the default logger.
|
||||
func With(args ...any) *Logger {
|
||||
return slog.With(args...)
|
||||
}
|
||||
|
||||
// NewLogLogger returns a new log.Logger such that each call to its Output method
|
||||
// dispatches a Record to the specified handler. The logger acts as a bridge from
|
||||
// the older log API to newer structured logging handlers.
|
||||
func NewLogLogger(h Handler, level Level) *log.Logger {
|
||||
return slog.NewLogLogger(h, level)
|
||||
}
|
||||
|
||||
// Debug calls Logger.Debug on the default logger.
|
||||
func Debug(msg string, args ...any) {
|
||||
slog.Debug(msg, args...)
|
||||
}
|
||||
|
||||
// DebugContext calls Logger.DebugContext on the default logger.
|
||||
func DebugContext(ctx context.Context, msg string, args ...any) {
|
||||
slog.DebugContext(ctx, msg, args...)
|
||||
}
|
||||
|
||||
// Info calls Logger.Info on the default logger.
|
||||
func Info(msg string, args ...any) {
|
||||
slog.Info(msg, args...)
|
||||
}
|
||||
|
||||
// InfoContext calls Logger.InfoContext on the default logger.
|
||||
func InfoContext(ctx context.Context, msg string, args ...any) {
|
||||
slog.InfoContext(ctx, msg, args...)
|
||||
}
|
||||
|
||||
// Warn calls Logger.Warn on the default logger.
|
||||
func Warn(msg string, args ...any) {
|
||||
slog.Warn(msg, args...)
|
||||
}
|
||||
|
||||
// WarnContext calls Logger.WarnContext on the default logger.
|
||||
func WarnContext(ctx context.Context, msg string, args ...any) {
|
||||
slog.WarnContext(ctx, msg, args...)
|
||||
}
|
||||
|
||||
// Error calls Logger.Error on the default logger.
|
||||
func Error(msg string, args ...any) {
|
||||
slog.Error(msg, args...)
|
||||
}
|
||||
|
||||
// ErrorContext calls Logger.ErrorContext on the default logger.
|
||||
func ErrorContext(ctx context.Context, msg string, args ...any) {
|
||||
slog.ErrorContext(ctx, msg, args...)
|
||||
}
|
||||
|
||||
// Log calls Logger.Log on the default logger.
|
||||
func Log(ctx context.Context, level Level, msg string, args ...any) {
|
||||
slog.Log(ctx, level, msg, args...)
|
||||
}
|
||||
|
||||
// LogAttrs calls Logger.LogAttrs on the default logger.
|
||||
func LogAttrs(ctx context.Context, level Level, msg string, attrs ...Attr) {
|
||||
slog.LogAttrs(ctx, level, msg, attrs...)
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
// Copyright 2022 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.21
|
||||
|
||||
package slog
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"time"
|
||||
)
|
||||
|
||||
// A Record holds information about a log event.
|
||||
// Copies of a Record share state.
|
||||
// Do not modify a Record after handing out a copy to it.
|
||||
// Call [NewRecord] to create a new Record.
|
||||
// Use [Record.Clone] to create a copy with no shared state.
|
||||
type Record = slog.Record
|
||||
|
||||
// NewRecord creates a Record from the given arguments.
|
||||
// Use [Record.AddAttrs] to add attributes to the Record.
|
||||
//
|
||||
// NewRecord is intended for logging APIs that want to support a [Handler] as
|
||||
// a backend.
|
||||
func NewRecord(t time.Time, level Level, msg string, pc uintptr) Record {
|
||||
return slog.NewRecord(t, level, msg, pc)
|
||||
}
|
||||
|
||||
// Source describes the location of a line of source code.
|
||||
type Source = slog.Source
|
|
@ -0,0 +1,32 @@
|
|||
// Copyright 2022 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.21
|
||||
|
||||
package slog
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"golang.org/x/exp/slog"
|
||||
)
|
||||
|
||||
// A Record holds information about a log event.
|
||||
// Copies of a Record share state.
|
||||
// Do not modify a Record after handing out a copy to it.
|
||||
// Call [NewRecord] to create a new Record.
|
||||
// Use [Record.Clone] to create a copy with no shared state.
|
||||
type Record = slog.Record
|
||||
|
||||
// NewRecord creates a Record from the given arguments.
|
||||
// Use [Record.AddAttrs] to add attributes to the Record.
|
||||
//
|
||||
// NewRecord is intended for logging APIs that want to support a [Handler] as
|
||||
// a backend.
|
||||
func NewRecord(t time.Time, level Level, msg string, pc uintptr) Record {
|
||||
return slog.NewRecord(t, level, msg, pc)
|
||||
}
|
||||
|
||||
// Source describes the location of a line of source code.
|
||||
type Source = slog.Source
|
|
@ -0,0 +1,23 @@
|
|||
// Copyright 2022 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.21
|
||||
|
||||
package slog
|
||||
|
||||
import (
|
||||
"io"
|
||||
"log/slog"
|
||||
)
|
||||
|
||||
// TextHandler is a Handler that writes Records to an io.Writer as a
|
||||
// sequence of key=value pairs separated by spaces and followed by a newline.
|
||||
type TextHandler = slog.TextHandler
|
||||
|
||||
// NewTextHandler creates a TextHandler that writes to w,
|
||||
// using the given options.
|
||||
// If opts is nil, the default options are used.
|
||||
func NewTextHandler(w io.Writer, opts *HandlerOptions) *TextHandler {
|
||||
return slog.NewTextHandler(w, opts)
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
// Copyright 2022 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.21
|
||||
|
||||
package slog
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"golang.org/x/exp/slog"
|
||||
)
|
||||
|
||||
// TextHandler is a Handler that writes Records to an io.Writer as a
|
||||
// sequence of key=value pairs separated by spaces and followed by a newline.
|
||||
type TextHandler = slog.TextHandler
|
||||
|
||||
// NewTextHandler creates a TextHandler that writes to w,
|
||||
// using the given options.
|
||||
// If opts is nil, the default options are used.
|
||||
func NewTextHandler(w io.Writer, opts *HandlerOptions) *TextHandler {
|
||||
return slog.NewTextHandler(w, opts)
|
||||
}
|
|
@ -0,0 +1,109 @@
|
|||
// Copyright 2022 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.21
|
||||
|
||||
package slog
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"time"
|
||||
)
|
||||
|
||||
// A Value can represent any Go value, but unlike type any,
|
||||
// it can represent most small values without an allocation.
|
||||
// The zero Value corresponds to nil.
|
||||
type Value = slog.Value
|
||||
|
||||
// Kind is the kind of a Value.
|
||||
type Kind = slog.Kind
|
||||
|
||||
// The following list is sorted alphabetically, but it's also important that
|
||||
// KindAny is 0 so that a zero Value represents nil.
|
||||
const (
|
||||
KindAny = slog.KindAny
|
||||
KindBool = slog.KindBool
|
||||
KindDuration = slog.KindDuration
|
||||
KindFloat64 = slog.KindFloat64
|
||||
KindInt64 = slog.KindInt64
|
||||
KindString = slog.KindString
|
||||
KindTime = slog.KindTime
|
||||
KindUint64 = slog.KindUint64
|
||||
KindGroup = slog.KindGroup
|
||||
KindLogValuer = slog.KindLogValuer
|
||||
)
|
||||
|
||||
//////////////// Constructors
|
||||
|
||||
// StringValue returns a new Value for a string.
|
||||
func StringValue(value string) Value {
|
||||
return slog.StringValue(value)
|
||||
}
|
||||
|
||||
// IntValue returns a Value for an int.
|
||||
func IntValue(v int) Value {
|
||||
return slog.IntValue(v)
|
||||
}
|
||||
|
||||
// Int64Value returns a Value for an int64.
|
||||
func Int64Value(v int64) Value {
|
||||
return slog.Int64Value(v)
|
||||
}
|
||||
|
||||
// Uint64Value returns a Value for a uint64.
|
||||
func Uint64Value(v uint64) Value {
|
||||
return slog.Uint64Value(v)
|
||||
}
|
||||
|
||||
// Float64Value returns a Value for a floating-point number.
|
||||
func Float64Value(v float64) Value {
|
||||
return slog.Float64Value(v)
|
||||
}
|
||||
|
||||
// BoolValue returns a Value for a bool.
|
||||
func BoolValue(v bool) Value {
|
||||
return slog.BoolValue(v)
|
||||
}
|
||||
|
||||
// TimeValue returns a Value for a time.Time.
|
||||
// It discards the monotonic portion.
|
||||
func TimeValue(v time.Time) Value {
|
||||
return slog.TimeValue(v)
|
||||
}
|
||||
|
||||
// DurationValue returns a Value for a time.Duration.
|
||||
func DurationValue(v time.Duration) Value {
|
||||
return slog.DurationValue(v)
|
||||
}
|
||||
|
||||
// GroupValue returns a new Value for a list of Attrs.
|
||||
// The caller must not subsequently mutate the argument slice.
|
||||
func GroupValue(as ...Attr) Value {
|
||||
return slog.GroupValue(as...)
|
||||
}
|
||||
|
||||
// AnyValue returns a Value for the supplied value.
|
||||
//
|
||||
// If the supplied value is of type Value, it is returned
|
||||
// unmodified.
|
||||
//
|
||||
// Given a value of one of Go's predeclared string, bool, or
|
||||
// (non-complex) numeric types, AnyValue returns a Value of kind
|
||||
// String, Bool, Uint64, Int64, or Float64. The width of the
|
||||
// original numeric type is not preserved.
|
||||
//
|
||||
// Given a time.Time or time.Duration value, AnyValue returns a Value of kind
|
||||
// KindTime or KindDuration. The monotonic time is not preserved.
|
||||
//
|
||||
// For nil, or values of all other types, including named types whose
|
||||
// underlying type is numeric, AnyValue returns a value of kind KindAny.
|
||||
func AnyValue(v any) Value {
|
||||
return slog.AnyValue(v)
|
||||
}
|
||||
|
||||
// A LogValuer is any Go value that can convert itself into a Value for logging.
|
||||
//
|
||||
// This mechanism may be used to defer expensive operations until they are
|
||||
// needed, or to expand a single value into a sequence of components.
|
||||
type LogValuer = slog.LogValuer
|
|
@ -0,0 +1,110 @@
|
|||
// Copyright 2022 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.21
|
||||
|
||||
package slog
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"golang.org/x/exp/slog"
|
||||
)
|
||||
|
||||
// A Value can represent any Go value, but unlike type any,
|
||||
// it can represent most small values without an allocation.
|
||||
// The zero Value corresponds to nil.
|
||||
type Value = slog.Value
|
||||
|
||||
// Kind is the kind of a Value.
|
||||
type Kind = slog.Kind
|
||||
|
||||
// The following list is sorted alphabetically, but it's also important that
|
||||
// KindAny is 0 so that a zero Value represents nil.
|
||||
const (
|
||||
KindAny = slog.KindAny
|
||||
KindBool = slog.KindBool
|
||||
KindDuration = slog.KindDuration
|
||||
KindFloat64 = slog.KindFloat64
|
||||
KindInt64 = slog.KindInt64
|
||||
KindString = slog.KindString
|
||||
KindTime = slog.KindTime
|
||||
KindUint64 = slog.KindUint64
|
||||
KindGroup = slog.KindGroup
|
||||
KindLogValuer = slog.KindLogValuer
|
||||
)
|
||||
|
||||
//////////////// Constructors
|
||||
|
||||
// StringValue returns a new Value for a string.
|
||||
func StringValue(value string) Value {
|
||||
return slog.StringValue(value)
|
||||
}
|
||||
|
||||
// IntValue returns a Value for an int.
|
||||
func IntValue(v int) Value {
|
||||
return slog.IntValue(v)
|
||||
}
|
||||
|
||||
// Int64Value returns a Value for an int64.
|
||||
func Int64Value(v int64) Value {
|
||||
return slog.Int64Value(v)
|
||||
}
|
||||
|
||||
// Uint64Value returns a Value for a uint64.
|
||||
func Uint64Value(v uint64) Value {
|
||||
return slog.Uint64Value(v)
|
||||
}
|
||||
|
||||
// Float64Value returns a Value for a floating-point number.
|
||||
func Float64Value(v float64) Value {
|
||||
return slog.Float64Value(v)
|
||||
}
|
||||
|
||||
// BoolValue returns a Value for a bool.
|
||||
func BoolValue(v bool) Value {
|
||||
return slog.BoolValue(v)
|
||||
}
|
||||
|
||||
// TimeValue returns a Value for a time.Time.
|
||||
// It discards the monotonic portion.
|
||||
func TimeValue(v time.Time) Value {
|
||||
return slog.TimeValue(v)
|
||||
}
|
||||
|
||||
// DurationValue returns a Value for a time.Duration.
|
||||
func DurationValue(v time.Duration) Value {
|
||||
return slog.DurationValue(v)
|
||||
}
|
||||
|
||||
// GroupValue returns a new Value for a list of Attrs.
|
||||
// The caller must not subsequently mutate the argument slice.
|
||||
func GroupValue(as ...Attr) Value {
|
||||
return slog.GroupValue(as...)
|
||||
}
|
||||
|
||||
// AnyValue returns a Value for the supplied value.
|
||||
//
|
||||
// If the supplied value is of type Value, it is returned
|
||||
// unmodified.
|
||||
//
|
||||
// Given a value of one of Go's predeclared string, bool, or
|
||||
// (non-complex) numeric types, AnyValue returns a Value of kind
|
||||
// String, Bool, Uint64, Int64, or Float64. The width of the
|
||||
// original numeric type is not preserved.
|
||||
//
|
||||
// Given a time.Time or time.Duration value, AnyValue returns a Value of kind
|
||||
// KindTime or KindDuration. The monotonic time is not preserved.
|
||||
//
|
||||
// For nil, or values of all other types, including named types whose
|
||||
// underlying type is numeric, AnyValue returns a value of kind KindAny.
|
||||
func AnyValue(v any) Value {
|
||||
return slog.AnyValue(v)
|
||||
}
|
||||
|
||||
// A LogValuer is any Go value that can convert itself into a Value for logging.
|
||||
//
|
||||
// This mechanism may be used to defer expensive operations until they are
|
||||
// needed, or to expand a single value into a sequence of components.
|
||||
type LogValuer = slog.LogValuer
|
|
@ -0,0 +1,11 @@
|
|||
linters:
|
||||
disable-all: true
|
||||
enable:
|
||||
- errcheck
|
||||
- godot
|
||||
- gosimple
|
||||
- govet
|
||||
- ineffassign
|
||||
- staticcheck
|
||||
- typecheck
|
||||
- unused
|
|
@ -1,6 +1,6 @@
|
|||
The MIT License (MIT)
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2014 Steve Francia
|
||||
Copyright (c) 2023 Sourcegraph
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -18,4 +18,4 @@ 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.
|
||||
SOFTWARE.
|
|
@ -0,0 +1,464 @@
|
|||

|
||||
|
||||
# `conc`: better structured concurrency for go
|
||||
|
||||
[](https://pkg.go.dev/github.com/sourcegraph/conc)
|
||||
[](https://sourcegraph.com/github.com/sourcegraph/conc)
|
||||
[](https://goreportcard.com/report/github.com/sourcegraph/conc)
|
||||
[](https://codecov.io/gh/sourcegraph/conc)
|
||||
[](https://discord.gg/bvXQXmtRjN)
|
||||
|
||||
`conc` is your toolbelt for structured concurrency in go, making common tasks
|
||||
easier and safer.
|
||||
|
||||
```sh
|
||||
go get github.com/sourcegraph/conc
|
||||
```
|
||||
|
||||
# At a glance
|
||||
|
||||
- Use [`conc.WaitGroup`](https://pkg.go.dev/github.com/sourcegraph/conc#WaitGroup) if you just want a safer version of `sync.WaitGroup`
|
||||
- Use [`pool.Pool`](https://pkg.go.dev/github.com/sourcegraph/conc/pool#Pool) if you want a concurrency-limited task runner
|
||||
- Use [`pool.ResultPool`](https://pkg.go.dev/github.com/sourcegraph/conc/pool#ResultPool) if you want a concurrent task runner that collects task results
|
||||
- Use [`pool.(Result)?ErrorPool`](https://pkg.go.dev/github.com/sourcegraph/conc/pool#ErrorPool) if your tasks are fallible
|
||||
- Use [`pool.(Result)?ContextPool`](https://pkg.go.dev/github.com/sourcegraph/conc/pool#ContextPool) if your tasks should be canceled on failure
|
||||
- Use [`stream.Stream`](https://pkg.go.dev/github.com/sourcegraph/conc/stream#Stream) if you want to process an ordered stream of tasks in parallel with serial callbacks
|
||||
- Use [`iter.Map`](https://pkg.go.dev/github.com/sourcegraph/conc/iter#Map) if you want to concurrently map a slice
|
||||
- Use [`iter.ForEach`](https://pkg.go.dev/github.com/sourcegraph/conc/iter#ForEach) if you want to concurrently iterate over a slice
|
||||
- Use [`panics.Catcher`](https://pkg.go.dev/github.com/sourcegraph/conc/panics#Catcher) if you want to catch panics in your own goroutines
|
||||
|
||||
All pools are created with
|
||||
[`pool.New()`](https://pkg.go.dev/github.com/sourcegraph/conc/pool#New)
|
||||
or
|
||||
[`pool.NewWithResults[T]()`](https://pkg.go.dev/github.com/sourcegraph/conc/pool#NewWithResults),
|
||||
then configured with methods:
|
||||
|
||||
- [`p.WithMaxGoroutines()`](https://pkg.go.dev/github.com/sourcegraph/conc/pool#Pool.MaxGoroutines) configures the maximum number of goroutines in the pool
|
||||
- [`p.WithErrors()`](https://pkg.go.dev/github.com/sourcegraph/conc/pool#Pool.WithErrors) configures the pool to run tasks that return errors
|
||||
- [`p.WithContext(ctx)`](https://pkg.go.dev/github.com/sourcegraph/conc/pool#Pool.WithContext) configures the pool to run tasks that should be canceled on first error
|
||||
- [`p.WithFirstError()`](https://pkg.go.dev/github.com/sourcegraph/conc/pool#ErrorPool.WithFirstError) configures error pools to only keep the first returned error rather than an aggregated error
|
||||
- [`p.WithCollectErrored()`](https://pkg.go.dev/github.com/sourcegraph/conc/pool#ResultContextPool.WithCollectErrored) configures result pools to collect results even when the task errored
|
||||
|
||||
# Goals
|
||||
|
||||
The main goals of the package are:
|
||||
1) Make it harder to leak goroutines
|
||||
2) Handle panics gracefully
|
||||
3) Make concurrent code easier to read
|
||||
|
||||
## Goal #1: Make it harder to leak goroutines
|
||||
|
||||
A common pain point when working with goroutines is cleaning them up. It's
|
||||
really easy to fire off a `go` statement and fail to properly wait for it to
|
||||
complete.
|
||||
|
||||
`conc` takes the opinionated stance that all concurrency should be scoped.
|
||||
That is, goroutines should have an owner and that owner should always
|
||||
ensure that its owned goroutines exit properly.
|
||||
|
||||
In `conc`, the owner of a goroutine is always a `conc.WaitGroup`. Goroutines
|
||||
are spawned in a `WaitGroup` with `(*WaitGroup).Go()`, and
|
||||
`(*WaitGroup).Wait()` should always be called before the `WaitGroup` goes out
|
||||
of scope.
|
||||
|
||||
In some cases, you might want a spawned goroutine to outlast the scope of the
|
||||
caller. In that case, you could pass a `WaitGroup` into the spawning function.
|
||||
|
||||
```go
|
||||
func main() {
|
||||
var wg conc.WaitGroup
|
||||
defer wg.Wait()
|
||||
|
||||
startTheThing(&wg)
|
||||
}
|
||||
|
||||
func startTheThing(wg *conc.WaitGroup) {
|
||||
wg.Go(func() { ... })
|
||||
}
|
||||
```
|
||||
|
||||
For some more discussion on why scoped concurrency is nice, check out [this
|
||||
blog
|
||||
post](https://vorpus.org/blog/notes-on-structured-concurrency-or-go-statement-considered-harmful/).
|
||||
|
||||
## Goal #2: Handle panics gracefully
|
||||
|
||||
A frequent problem with goroutines in long-running applications is handling
|
||||
panics. A goroutine spawned without a panic handler will crash the whole process
|
||||
on panic. This is usually undesirable.
|
||||
|
||||
However, if you do add a panic handler to a goroutine, what do you do with the
|
||||
panic once you catch it? Some options:
|
||||
1) Ignore it
|
||||
2) Log it
|
||||
3) Turn it into an error and return that to the goroutine spawner
|
||||
4) Propagate the panic to the goroutine spawner
|
||||
|
||||
Ignoring panics is a bad idea since panics usually mean there is actually
|
||||
something wrong and someone should fix it.
|
||||
|
||||
Just logging panics isn't great either because then there is no indication to the spawner
|
||||
that something bad happened, and it might just continue on as normal even though your
|
||||
program is in a really bad state.
|
||||
|
||||
Both (3) and (4) are reasonable options, but both require the goroutine to have
|
||||
an owner that can actually receive the message that something went wrong. This
|
||||
is generally not true with a goroutine spawned with `go`, but in the `conc`
|
||||
package, all goroutines have an owner that must collect the spawned goroutine.
|
||||
In the conc package, any call to `Wait()` will panic if any of the spawned goroutines
|
||||
panicked. Additionally, it decorates the panic value with a stacktrace from the child
|
||||
goroutine so that you don't lose information about what caused the panic.
|
||||
|
||||
Doing this all correctly every time you spawn something with `go` is not
|
||||
trivial and it requires a lot of boilerplate that makes the important parts of
|
||||
the code more difficult to read, so `conc` does this for you.
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th><code>stdlib</code></th>
|
||||
<th><code>conc</code></th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
```go
|
||||
type caughtPanicError struct {
|
||||
val any
|
||||
stack []byte
|
||||
}
|
||||
|
||||
func (e *caughtPanicError) Error() string {
|
||||
return fmt.Sprintf(
|
||||
"panic: %q\n%s",
|
||||
e.val,
|
||||
string(e.stack)
|
||||
)
|
||||
}
|
||||
|
||||
func main() {
|
||||
done := make(chan error)
|
||||
go func() {
|
||||
defer func() {
|
||||
if v := recover(); v != nil {
|
||||
done <- &caughtPanicError{
|
||||
val: v,
|
||||
stack: debug.Stack()
|
||||
}
|
||||
} else {
|
||||
done <- nil
|
||||
}
|
||||
}()
|
||||
doSomethingThatMightPanic()
|
||||
}()
|
||||
err := <-done
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
```
|
||||
</td>
|
||||
<td>
|
||||
|
||||
```go
|
||||
func main() {
|
||||
var wg conc.WaitGroup
|
||||
wg.Go(doSomethingThatMightPanic)
|
||||
// panics with a nice stacktrace
|
||||
wg.Wait()
|
||||
}
|
||||
```
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
## Goal #3: Make concurrent code easier to read
|
||||
|
||||
Doing concurrency correctly is difficult. Doing it in a way that doesn't
|
||||
obfuscate what the code is actually doing is more difficult. The `conc` package
|
||||
attempts to make common operations easier by abstracting as much boilerplate
|
||||
complexity as possible.
|
||||
|
||||
Want to run a set of concurrent tasks with a bounded set of goroutines? Use
|
||||
`pool.New()`. Want to process an ordered stream of results concurrently, but
|
||||
still maintain order? Try `stream.New()`. What about a concurrent map over
|
||||
a slice? Take a peek at `iter.Map()`.
|
||||
|
||||
Browse some examples below for some comparisons with doing these by hand.
|
||||
|
||||
# Examples
|
||||
|
||||
Each of these examples forgoes propagating panics for simplicity. To see
|
||||
what kind of complexity that would add, check out the "Goal #2" header above.
|
||||
|
||||
Spawn a set of goroutines and waiting for them to finish:
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th><code>stdlib</code></th>
|
||||
<th><code>conc</code></th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
```go
|
||||
func main() {
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < 10; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
// crashes on panic!
|
||||
doSomething()
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
```
|
||||
</td>
|
||||
<td>
|
||||
|
||||
```go
|
||||
func main() {
|
||||
var wg conc.WaitGroup
|
||||
for i := 0; i < 10; i++ {
|
||||
wg.Go(doSomething)
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
```
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
Process each element of a stream in a static pool of goroutines:
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th><code>stdlib</code></th>
|
||||
<th><code>conc</code></th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
```go
|
||||
func process(stream chan int) {
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < 10; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for elem := range stream {
|
||||
handle(elem)
|
||||
}
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
```
|
||||
</td>
|
||||
<td>
|
||||
|
||||
```go
|
||||
func process(stream chan int) {
|
||||
p := pool.New().WithMaxGoroutines(10)
|
||||
for elem := range stream {
|
||||
elem := elem
|
||||
p.Go(func() {
|
||||
handle(elem)
|
||||
})
|
||||
}
|
||||
p.Wait()
|
||||
}
|
||||
```
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
Process each element of a slice in a static pool of goroutines:
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th><code>stdlib</code></th>
|
||||
<th><code>conc</code></th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
```go
|
||||
func process(values []int) {
|
||||
feeder := make(chan int, 8)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < 10; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for elem := range feeder {
|
||||
handle(elem)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
for _, value := range values {
|
||||
feeder <- value
|
||||
}
|
||||
close(feeder)
|
||||
wg.Wait()
|
||||
}
|
||||
```
|
||||
</td>
|
||||
<td>
|
||||
|
||||
```go
|
||||
func process(values []int) {
|
||||
iter.ForEach(values, handle)
|
||||
}
|
||||
```
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
Concurrently map a slice:
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th><code>stdlib</code></th>
|
||||
<th><code>conc</code></th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
```go
|
||||
func concMap(
|
||||
input []int,
|
||||
f func(int) int,
|
||||
) []int {
|
||||
res := make([]int, len(input))
|
||||
var idx atomic.Int64
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < 10; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
for {
|
||||
i := int(idx.Add(1) - 1)
|
||||
if i >= len(input) {
|
||||
return
|
||||
}
|
||||
|
||||
res[i] = f(input[i])
|
||||
}
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
return res
|
||||
}
|
||||
```
|
||||
</td>
|
||||
<td>
|
||||
|
||||
```go
|
||||
func concMap(
|
||||
input []int,
|
||||
f func(*int) int,
|
||||
) []int {
|
||||
return iter.Map(input, f)
|
||||
}
|
||||
```
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
Process an ordered stream concurrently:
|
||||
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th><code>stdlib</code></th>
|
||||
<th><code>conc</code></th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
```go
|
||||
func mapStream(
|
||||
in chan int,
|
||||
out chan int,
|
||||
f func(int) int,
|
||||
) {
|
||||
tasks := make(chan func())
|
||||
taskResults := make(chan chan int)
|
||||
|
||||
// Worker goroutines
|
||||
var workerWg sync.WaitGroup
|
||||
for i := 0; i < 10; i++ {
|
||||
workerWg.Add(1)
|
||||
go func() {
|
||||
defer workerWg.Done()
|
||||
for task := range tasks {
|
||||
task()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Ordered reader goroutines
|
||||
var readerWg sync.WaitGroup
|
||||
readerWg.Add(1)
|
||||
go func() {
|
||||
defer readerWg.Done()
|
||||
for result := range taskResults {
|
||||
item := <-result
|
||||
out <- item
|
||||
}
|
||||
}()
|
||||
|
||||
// Feed the workers with tasks
|
||||
for elem := range in {
|
||||
resultCh := make(chan int, 1)
|
||||
taskResults <- resultCh
|
||||
tasks <- func() {
|
||||
resultCh <- f(elem)
|
||||
}
|
||||
}
|
||||
|
||||
// We've exhausted input.
|
||||
// Wait for everything to finish
|
||||
close(tasks)
|
||||
workerWg.Wait()
|
||||
close(taskResults)
|
||||
readerWg.Wait()
|
||||
}
|
||||
```
|
||||
</td>
|
||||
<td>
|
||||
|
||||
```go
|
||||
func mapStream(
|
||||
in chan int,
|
||||
out chan int,
|
||||
f func(int) int,
|
||||
) {
|
||||
s := stream.New().WithMaxGoroutines(10)
|
||||
for elem := range in {
|
||||
elem := elem
|
||||
s.Go(func() stream.Callback {
|
||||
res := f(elem)
|
||||
return func() { out <- res }
|
||||
})
|
||||
}
|
||||
s.Wait()
|
||||
}
|
||||
```
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
# Status
|
||||
|
||||
This package is currently pre-1.0. There are likely to be minor breaking
|
||||
changes before a 1.0 release as we stabilize the APIs and tweak defaults.
|
||||
Please open an issue if you have questions, concerns, or requests that you'd
|
||||
like addressed before the 1.0 release. Currently, a 1.0 is targeted for
|
||||
March 2023.
|
10
vendor/github.com/sourcegraph/conc/internal/multierror/multierror_go119.go
generated
vendored
Normal file
10
vendor/github.com/sourcegraph/conc/internal/multierror/multierror_go119.go
generated
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
//go:build !go1.20
|
||||
// +build !go1.20
|
||||
|
||||
package multierror
|
||||
|
||||
import "go.uber.org/multierr"
|
||||
|
||||
var (
|
||||
Join = multierr.Combine
|
||||
)
|
10
vendor/github.com/sourcegraph/conc/internal/multierror/multierror_go120.go
generated
vendored
Normal file
10
vendor/github.com/sourcegraph/conc/internal/multierror/multierror_go120.go
generated
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
//go:build go1.20
|
||||
// +build go1.20
|
||||
|
||||
package multierror
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
Join = errors.Join
|
||||
)
|
|
@ -0,0 +1,85 @@
|
|||
package iter
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/sourcegraph/conc"
|
||||
)
|
||||
|
||||
// defaultMaxGoroutines returns the default maximum number of
|
||||
// goroutines to use within this package.
|
||||
func defaultMaxGoroutines() int { return runtime.GOMAXPROCS(0) }
|
||||
|
||||
// Iterator can be used to configure the behaviour of ForEach
|
||||
// and ForEachIdx. The zero value is safe to use with reasonable
|
||||
// defaults.
|
||||
//
|
||||
// Iterator is also safe for reuse and concurrent use.
|
||||
type Iterator[T any] struct {
|
||||
// MaxGoroutines controls the maximum number of goroutines
|
||||
// to use on this Iterator's methods.
|
||||
//
|
||||
// If unset, MaxGoroutines defaults to runtime.GOMAXPROCS(0).
|
||||
MaxGoroutines int
|
||||
}
|
||||
|
||||
// ForEach executes f in parallel over each element in input.
|
||||
//
|
||||
// It is safe to mutate the input parameter, which makes it
|
||||
// possible to map in place.
|
||||
//
|
||||
// ForEach always uses at most runtime.GOMAXPROCS goroutines.
|
||||
// It takes roughly 2µs to start up the goroutines and adds
|
||||
// an overhead of roughly 50ns per element of input. For
|
||||
// a configurable goroutine limit, use a custom Iterator.
|
||||
func ForEach[T any](input []T, f func(*T)) { Iterator[T]{}.ForEach(input, f) }
|
||||
|
||||
// ForEach executes f in parallel over each element in input,
|
||||
// using up to the Iterator's configured maximum number of
|
||||
// goroutines.
|
||||
//
|
||||
// It is safe to mutate the input parameter, which makes it
|
||||
// possible to map in place.
|
||||
//
|
||||
// It takes roughly 2µs to start up the goroutines and adds
|
||||
// an overhead of roughly 50ns per element of input.
|
||||
func (iter Iterator[T]) ForEach(input []T, f func(*T)) {
|
||||
iter.ForEachIdx(input, func(_ int, t *T) {
|
||||
f(t)
|
||||
})
|
||||
}
|
||||
|
||||
// ForEachIdx is the same as ForEach except it also provides the
|
||||
// index of the element to the callback.
|
||||
func ForEachIdx[T any](input []T, f func(int, *T)) { Iterator[T]{}.ForEachIdx(input, f) }
|
||||
|
||||
// ForEachIdx is the same as ForEach except it also provides the
|
||||
// index of the element to the callback.
|
||||
func (iter Iterator[T]) ForEachIdx(input []T, f func(int, *T)) {
|
||||
if iter.MaxGoroutines == 0 {
|
||||
// iter is a value receiver and is hence safe to mutate
|
||||
iter.MaxGoroutines = defaultMaxGoroutines()
|
||||
}
|
||||
|
||||
numInput := len(input)
|
||||
if iter.MaxGoroutines > numInput {
|
||||
// No more concurrent tasks than the number of input items.
|
||||
iter.MaxGoroutines = numInput
|
||||
}
|
||||
|
||||
var idx atomic.Int64
|
||||
// Create the task outside the loop to avoid extra closure allocations.
|
||||
task := func() {
|
||||
i := int(idx.Add(1) - 1)
|
||||
for ; i < numInput; i = int(idx.Add(1) - 1) {
|
||||
f(i, &input[i])
|
||||
}
|
||||
}
|
||||
|
||||
var wg conc.WaitGroup
|
||||
for i := 0; i < iter.MaxGoroutines; i++ {
|
||||
wg.Go(task)
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
package iter
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/sourcegraph/conc/internal/multierror"
|
||||
)
|
||||
|
||||
// Mapper is an Iterator with a result type R. It can be used to configure
|
||||
// the behaviour of Map and MapErr. The zero value is safe to use with
|
||||
// reasonable defaults.
|
||||
//
|
||||
// Mapper is also safe for reuse and concurrent use.
|
||||
type Mapper[T, R any] Iterator[T]
|
||||
|
||||
// Map applies f to each element of input, returning the mapped result.
|
||||
//
|
||||
// Map always uses at most runtime.GOMAXPROCS goroutines. For a configurable
|
||||
// goroutine limit, use a custom Mapper.
|
||||
func Map[T, R any](input []T, f func(*T) R) []R {
|
||||
return Mapper[T, R]{}.Map(input, f)
|
||||
}
|
||||
|
||||
// Map applies f to each element of input, returning the mapped result.
|
||||
//
|
||||
// Map uses up to the configured Mapper's maximum number of goroutines.
|
||||
func (m Mapper[T, R]) Map(input []T, f func(*T) R) []R {
|
||||
res := make([]R, len(input))
|
||||
Iterator[T](m).ForEachIdx(input, func(i int, t *T) {
|
||||
res[i] = f(t)
|
||||
})
|
||||
return res
|
||||
}
|
||||
|
||||
// MapErr applies f to each element of the input, returning the mapped result
|
||||
// and a combined error of all returned errors.
|
||||
//
|
||||
// Map always uses at most runtime.GOMAXPROCS goroutines. For a configurable
|
||||
// goroutine limit, use a custom Mapper.
|
||||
func MapErr[T, R any](input []T, f func(*T) (R, error)) ([]R, error) {
|
||||
return Mapper[T, R]{}.MapErr(input, f)
|
||||
}
|
||||
|
||||
// MapErr applies f to each element of the input, returning the mapped result
|
||||
// and a combined error of all returned errors.
|
||||
//
|
||||
// Map uses up to the configured Mapper's maximum number of goroutines.
|
||||
func (m Mapper[T, R]) MapErr(input []T, f func(*T) (R, error)) ([]R, error) {
|
||||
var (
|
||||
res = make([]R, len(input))
|
||||
errMux sync.Mutex
|
||||
errs error
|
||||
)
|
||||
Iterator[T](m).ForEachIdx(input, func(i int, t *T) {
|
||||
var err error
|
||||
res[i], err = f(t)
|
||||
if err != nil {
|
||||
errMux.Lock()
|
||||
// TODO: use stdlib errors once multierrors land in go 1.20
|
||||
errs = multierror.Join(errs, err)
|
||||
errMux.Unlock()
|
||||
}
|
||||
})
|
||||
return res, errs
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
package panics
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// Catcher is used to catch panics. You can execute a function with Try,
|
||||
// which will catch any spawned panic. Try can be called any number of times,
|
||||
// from any number of goroutines. Once all calls to Try have completed, you can
|
||||
// get the value of the first panic (if any) with Recovered(), or you can just
|
||||
// propagate the panic (re-panic) with Repanic().
|
||||
type Catcher struct {
|
||||
recovered atomic.Pointer[Recovered]
|
||||
}
|
||||
|
||||
// Try executes f, catching any panic it might spawn. It is safe
|
||||
// to call from multiple goroutines simultaneously.
|
||||
func (p *Catcher) Try(f func()) {
|
||||
defer p.tryRecover()
|
||||
f()
|
||||
}
|
||||
|
||||
func (p *Catcher) tryRecover() {
|
||||
if val := recover(); val != nil {
|
||||
rp := NewRecovered(1, val)
|
||||
p.recovered.CompareAndSwap(nil, &rp)
|
||||
}
|
||||
}
|
||||
|
||||
// Repanic panics if any calls to Try caught a panic. It will panic with the
|
||||
// value of the first panic caught, wrapped in a panics.Recovered with caller
|
||||
// information.
|
||||
func (p *Catcher) Repanic() {
|
||||
if val := p.Recovered(); val != nil {
|
||||
panic(val)
|
||||
}
|
||||
}
|
||||
|
||||
// Recovered returns the value of the first panic caught by Try, or nil if
|
||||
// no calls to Try panicked.
|
||||
func (p *Catcher) Recovered() *Recovered {
|
||||
return p.recovered.Load()
|
||||
}
|
||||
|
||||
// NewRecovered creates a panics.Recovered from a panic value and a collected
|
||||
// stacktrace. The skip parameter allows the caller to skip stack frames when
|
||||
// collecting the stacktrace. Calling with a skip of 0 means include the call to
|
||||
// NewRecovered in the stacktrace.
|
||||
func NewRecovered(skip int, value any) Recovered {
|
||||
// 64 frames should be plenty
|
||||
var callers [64]uintptr
|
||||
n := runtime.Callers(skip+1, callers[:])
|
||||
return Recovered{
|
||||
Value: value,
|
||||
Callers: callers[:n],
|
||||
Stack: debug.Stack(),
|
||||
}
|
||||
}
|
||||
|
||||
// Recovered is a panic that was caught with recover().
|
||||
type Recovered struct {
|
||||
// The original value of the panic.
|
||||
Value any
|
||||
// The caller list as returned by runtime.Callers when the panic was
|
||||
// recovered. Can be used to produce a more detailed stack information with
|
||||
// runtime.CallersFrames.
|
||||
Callers []uintptr
|
||||
// The formatted stacktrace from the goroutine where the panic was recovered.
|
||||
// Easier to use than Callers.
|
||||
Stack []byte
|
||||
}
|
||||
|
||||
// String renders a human-readable formatting of the panic.
|
||||
func (p *Recovered) String() string {
|
||||
return fmt.Sprintf("panic: %v\nstacktrace:\n%s\n", p.Value, p.Stack)
|
||||
}
|
||||
|
||||
// AsError casts the panic into an error implementation. The implementation
|
||||
// is unwrappable with the cause of the panic, if the panic was provided one.
|
||||
func (p *Recovered) AsError() error {
|
||||
if p == nil {
|
||||
return nil
|
||||
}
|
||||
return &ErrRecovered{*p}
|
||||
}
|
||||
|
||||
// ErrRecovered wraps a panics.Recovered in an error implementation.
|
||||
type ErrRecovered struct{ Recovered }
|
||||
|
||||
var _ error = (*ErrRecovered)(nil)
|
||||
|
||||
func (p *ErrRecovered) Error() string { return p.String() }
|
||||
|
||||
func (p *ErrRecovered) Unwrap() error {
|
||||
if err, ok := p.Value.(error); ok {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package panics
|
||||
|
||||
// Try executes f, catching and returning any panic it might spawn.
|
||||
//
|
||||
// The recovered panic can be propagated with panic(), or handled as a normal error with
|
||||
// (*panics.Recovered).AsError().
|
||||
func Try(f func()) *Recovered {
|
||||
var c Catcher
|
||||
c.Try(f)
|
||||
return c.Recovered()
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
package conc
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/sourcegraph/conc/panics"
|
||||
)
|
||||
|
||||
// NewWaitGroup creates a new WaitGroup.
|
||||
func NewWaitGroup() *WaitGroup {
|
||||
return &WaitGroup{}
|
||||
}
|
||||
|
||||
// WaitGroup is the primary building block for scoped concurrency.
|
||||
// Goroutines can be spawned in the WaitGroup with the Go method,
|
||||
// and calling Wait() will ensure that each of those goroutines exits
|
||||
// before continuing. Any panics in a child goroutine will be caught
|
||||
// and propagated to the caller of Wait().
|
||||
//
|
||||
// The zero value of WaitGroup is usable, just like sync.WaitGroup.
|
||||
// Also like sync.WaitGroup, it must not be copied after first use.
|
||||
type WaitGroup struct {
|
||||
wg sync.WaitGroup
|
||||
pc panics.Catcher
|
||||
}
|
||||
|
||||
// Go spawns a new goroutine in the WaitGroup.
|
||||
func (h *WaitGroup) Go(f func()) {
|
||||
h.wg.Add(1)
|
||||
go func() {
|
||||
defer h.wg.Done()
|
||||
h.pc.Try(f)
|
||||
}()
|
||||
}
|
||||
|
||||
// Wait will block until all goroutines spawned with Go exit and will
|
||||
// propagate any panics spawned in a child goroutine.
|
||||
func (h *WaitGroup) Wait() {
|
||||
h.wg.Wait()
|
||||
|
||||
// Propagate a panic if we caught one from a child goroutine.
|
||||
h.pc.Repanic()
|
||||
}
|
||||
|
||||
// WaitAndRecover will block until all goroutines spawned with Go exit and
|
||||
// will return a *panics.Recovered if one of the child goroutines panics.
|
||||
func (h *WaitGroup) WaitAndRecover() *panics.Recovered {
|
||||
h.wg.Wait()
|
||||
|
||||
// Return a recovered panic if we caught one from a child goroutine.
|
||||
return h.pc.Recovered()
|
||||
}
|
|
@ -11,8 +11,8 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build aix || darwin || openbsd || freebsd || netbsd || dragonfly
|
||||
// +build aix darwin openbsd freebsd netbsd dragonfly
|
||||
//go:build aix || darwin || openbsd || freebsd || netbsd || dragonfly || zos
|
||||
// +build aix darwin openbsd freebsd netbsd dragonfly zos
|
||||
|
||||
package afero
|
||||
|
||||
|
|
|
@ -10,8 +10,8 @@
|
|||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//go:build !darwin && !openbsd && !freebsd && !dragonfly && !netbsd && !aix
|
||||
// +build !darwin,!openbsd,!freebsd,!dragonfly,!netbsd,!aix
|
||||
//go:build !darwin && !openbsd && !freebsd && !dragonfly && !netbsd && !aix && !zos
|
||||
// +build !darwin,!openbsd,!freebsd,!dragonfly,!netbsd,!aix,!zos
|
||||
|
||||
package afero
|
||||
|
||||
|
|
|
@ -16,9 +16,12 @@ package afero
|
|||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
@ -88,6 +91,24 @@ func (m *MemMapFs) findParent(f *mem.FileData) *mem.FileData {
|
|||
return pfile
|
||||
}
|
||||
|
||||
func (m *MemMapFs) findDescendants(name string) []*mem.FileData {
|
||||
fData := m.getData()
|
||||
descendants := make([]*mem.FileData, 0, len(fData))
|
||||
for p, dFile := range fData {
|
||||
if strings.HasPrefix(p, name+FilePathSeparator) {
|
||||
descendants = append(descendants, dFile)
|
||||
}
|
||||
}
|
||||
|
||||
sort.Slice(descendants, func(i, j int) bool {
|
||||
cur := len(strings.Split(descendants[i].Name(), FilePathSeparator))
|
||||
next := len(strings.Split(descendants[j].Name(), FilePathSeparator))
|
||||
return cur < next
|
||||
})
|
||||
|
||||
return descendants
|
||||
}
|
||||
|
||||
func (m *MemMapFs) registerWithParent(f *mem.FileData, perm os.FileMode) {
|
||||
if f == nil {
|
||||
return
|
||||
|
@ -309,29 +330,51 @@ func (m *MemMapFs) Rename(oldname, newname string) error {
|
|||
if _, ok := m.getData()[oldname]; ok {
|
||||
m.mu.RUnlock()
|
||||
m.mu.Lock()
|
||||
m.unRegisterWithParent(oldname)
|
||||
err := m.unRegisterWithParent(oldname)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fileData := m.getData()[oldname]
|
||||
delete(m.getData(), oldname)
|
||||
mem.ChangeFileName(fileData, newname)
|
||||
m.getData()[newname] = fileData
|
||||
|
||||
err = m.renameDescendants(oldname, newname)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
delete(m.getData(), oldname)
|
||||
|
||||
m.registerWithParent(fileData, 0)
|
||||
m.mu.Unlock()
|
||||
m.mu.RLock()
|
||||
} else {
|
||||
return &os.PathError{Op: "rename", Path: oldname, Err: ErrFileNotFound}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
for p, fileData := range m.getData() {
|
||||
if strings.HasPrefix(p, oldname+FilePathSeparator) {
|
||||
m.mu.RUnlock()
|
||||
m.mu.Lock()
|
||||
delete(m.getData(), p)
|
||||
p := strings.Replace(p, oldname, newname, 1)
|
||||
m.getData()[p] = fileData
|
||||
m.mu.Unlock()
|
||||
m.mu.RLock()
|
||||
func (m *MemMapFs) renameDescendants(oldname, newname string) error {
|
||||
descendants := m.findDescendants(oldname)
|
||||
removes := make([]string, 0, len(descendants))
|
||||
for _, desc := range descendants {
|
||||
descNewName := strings.Replace(desc.Name(), oldname, newname, 1)
|
||||
err := m.unRegisterWithParent(desc.Name())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
removes = append(removes, desc.Name())
|
||||
mem.ChangeFileName(desc, descNewName)
|
||||
m.getData()[descNewName] = desc
|
||||
|
||||
m.registerWithParent(desc, 0)
|
||||
}
|
||||
for _, r := range removes {
|
||||
delete(m.getData(), r)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
# cast
|
||||
|
||||
[](https://github.com/spf13/cast/actions/workflows/ci.yml)
|
||||
[](https://github.com/spf13/cast/actions/workflows/ci.yaml)
|
||||
[](https://pkg.go.dev/mod/github.com/spf13/cast)
|
||||

|
||||
[](https://goreportcard.com/report/github.com/spf13/cast)
|
||||
[](https://goreportcard.com/report/github.com/spf13/cast)
|
||||
|
||||
Easy and safe casting from one type to another in Go
|
||||
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.bench
|
||||
go.sum
|
|
@ -1,148 +0,0 @@
|
|||
jWalterWeatherman
|
||||
=================
|
||||
|
||||
Seamless printing to the terminal (stdout) and logging to a io.Writer
|
||||
(file) that’s as easy to use as fmt.Println.
|
||||
|
||||

|
||||
Graphic by [JonnyEtc](http://jonnyetc.deviantart.com/art/And-That-s-Why-You-Always-Leave-a-Note-315311422)
|
||||
|
||||
JWW is primarily a wrapper around the excellent standard log library. It
|
||||
provides a few advantages over using the standard log library alone.
|
||||
|
||||
1. Ready to go out of the box.
|
||||
2. One library for both printing to the terminal and logging (to files).
|
||||
3. Really easy to log to either a temp file or a file you specify.
|
||||
|
||||
|
||||
I really wanted a very straightforward library that could seamlessly do
|
||||
the following things.
|
||||
|
||||
1. Replace all the println, printf, etc statements thoughout my code with
|
||||
something more useful
|
||||
2. Allow the user to easily control what levels are printed to stdout
|
||||
3. Allow the user to easily control what levels are logged
|
||||
4. Provide an easy mechanism (like fmt.Println) to print info to the user
|
||||
which can be easily logged as well
|
||||
5. Due to 2 & 3 provide easy verbose mode for output and logs
|
||||
6. Not have any unnecessary initialization cruft. Just use it.
|
||||
|
||||
# Usage
|
||||
|
||||
## Step 1. Use it
|
||||
Put calls throughout your source based on type of feedback.
|
||||
No initialization or setup needs to happen. Just start calling things.
|
||||
|
||||
Available Loggers are:
|
||||
|
||||
* TRACE
|
||||
* DEBUG
|
||||
* INFO
|
||||
* WARN
|
||||
* ERROR
|
||||
* CRITICAL
|
||||
* FATAL
|
||||
|
||||
These each are loggers based on the log standard library and follow the
|
||||
standard usage. Eg.
|
||||
|
||||
```go
|
||||
import (
|
||||
jww "github.com/spf13/jwalterweatherman"
|
||||
)
|
||||
|
||||
...
|
||||
|
||||
if err != nil {
|
||||
|
||||
// This is a pretty serious error and the user should know about
|
||||
// it. It will be printed to the terminal as well as logged under the
|
||||
// default thresholds.
|
||||
|
||||
jww.ERROR.Println(err)
|
||||
}
|
||||
|
||||
if err2 != nil {
|
||||
// This error isn’t going to materially change the behavior of the
|
||||
// application, but it’s something that may not be what the user
|
||||
// expects. Under the default thresholds, Warn will be logged, but
|
||||
// not printed to the terminal.
|
||||
|
||||
jww.WARN.Println(err2)
|
||||
}
|
||||
|
||||
// Information that’s relevant to what’s happening, but not very
|
||||
// important for the user. Under the default thresholds this will be
|
||||
// discarded.
|
||||
|
||||
jww.INFO.Printf("information %q", response)
|
||||
|
||||
```
|
||||
|
||||
NOTE: You can also use the library in a non-global setting by creating an instance of a Notebook:
|
||||
|
||||
```go
|
||||
notepad = jww.NewNotepad(jww.LevelInfo, jww.LevelTrace, os.Stdout, ioutil.Discard, "", log.Ldate|log.Ltime)
|
||||
notepad.WARN.Println("Some warning"")
|
||||
```
|
||||
|
||||
_Why 7 levels?_
|
||||
|
||||
Maybe you think that 7 levels are too much for any application... and you
|
||||
are probably correct. Just because there are seven levels doesn’t mean
|
||||
that you should be using all 7 levels. Pick the right set for your needs.
|
||||
Remember they only have to mean something to your project.
|
||||
|
||||
## Step 2. Optionally configure JWW
|
||||
|
||||
Under the default thresholds :
|
||||
|
||||
* Debug, Trace & Info goto /dev/null
|
||||
* Warn and above is logged (when a log file/io.Writer is provided)
|
||||
* Error and above is printed to the terminal (stdout)
|
||||
|
||||
### Changing the thresholds
|
||||
|
||||
The threshold can be changed at any time, but will only affect calls that
|
||||
execute after the change was made.
|
||||
|
||||
This is very useful if your application has a verbose mode. Of course you
|
||||
can decide what verbose means to you or even have multiple levels of
|
||||
verbosity.
|
||||
|
||||
|
||||
```go
|
||||
import (
|
||||
jww "github.com/spf13/jwalterweatherman"
|
||||
)
|
||||
|
||||
if Verbose {
|
||||
jww.SetLogThreshold(jww.LevelTrace)
|
||||
jww.SetStdoutThreshold(jww.LevelInfo)
|
||||
}
|
||||
```
|
||||
|
||||
Note that JWW's own internal output uses log levels as well, so set the log
|
||||
level before making any other calls if you want to see what it's up to.
|
||||
|
||||
|
||||
### Setting a log file
|
||||
|
||||
JWW can log to any `io.Writer`:
|
||||
|
||||
|
||||
```go
|
||||
|
||||
jww.SetLogOutput(customWriter)
|
||||
|
||||
```
|
||||
|
||||
|
||||
# More information
|
||||
|
||||
This is an early release. I’ve been using it for a while and this is the
|
||||
third interface I’ve tried. I like this one pretty well, but no guarantees
|
||||
that it won’t change a bit.
|
||||
|
||||
I wrote this for use in [hugo](https://gohugo.io). If you are looking
|
||||
for a static website engine that’s super fast please checkout Hugo.
|
|
@ -1,111 +0,0 @@
|
|||
// Copyright © 2016 Steve Francia <spf@spf13.com>.
|
||||
//
|
||||
// Use of this source code is governed by an MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package jwalterweatherman
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
var (
|
||||
TRACE *log.Logger
|
||||
DEBUG *log.Logger
|
||||
INFO *log.Logger
|
||||
WARN *log.Logger
|
||||
ERROR *log.Logger
|
||||
CRITICAL *log.Logger
|
||||
FATAL *log.Logger
|
||||
|
||||
LOG *log.Logger
|
||||
FEEDBACK *Feedback
|
||||
|
||||
defaultNotepad *Notepad
|
||||
)
|
||||
|
||||
func reloadDefaultNotepad() {
|
||||
TRACE = defaultNotepad.TRACE
|
||||
DEBUG = defaultNotepad.DEBUG
|
||||
INFO = defaultNotepad.INFO
|
||||
WARN = defaultNotepad.WARN
|
||||
ERROR = defaultNotepad.ERROR
|
||||
CRITICAL = defaultNotepad.CRITICAL
|
||||
FATAL = defaultNotepad.FATAL
|
||||
|
||||
LOG = defaultNotepad.LOG
|
||||
FEEDBACK = defaultNotepad.FEEDBACK
|
||||
}
|
||||
|
||||
func init() {
|
||||
defaultNotepad = NewNotepad(LevelError, LevelWarn, os.Stdout, ioutil.Discard, "", log.Ldate|log.Ltime)
|
||||
reloadDefaultNotepad()
|
||||
}
|
||||
|
||||
// SetLogThreshold set the log threshold for the default notepad. Trace by default.
|
||||
func SetLogThreshold(threshold Threshold) {
|
||||
defaultNotepad.SetLogThreshold(threshold)
|
||||
reloadDefaultNotepad()
|
||||
}
|
||||
|
||||
// SetLogOutput set the log output for the default notepad. Discarded by default.
|
||||
func SetLogOutput(handle io.Writer) {
|
||||
defaultNotepad.SetLogOutput(handle)
|
||||
reloadDefaultNotepad()
|
||||
}
|
||||
|
||||
// SetStdoutThreshold set the standard output threshold for the default notepad.
|
||||
// Info by default.
|
||||
func SetStdoutThreshold(threshold Threshold) {
|
||||
defaultNotepad.SetStdoutThreshold(threshold)
|
||||
reloadDefaultNotepad()
|
||||
}
|
||||
|
||||
// SetStdoutOutput set the stdout output for the default notepad. Default is stdout.
|
||||
func SetStdoutOutput(handle io.Writer) {
|
||||
defaultNotepad.outHandle = handle
|
||||
defaultNotepad.init()
|
||||
reloadDefaultNotepad()
|
||||
}
|
||||
|
||||
// SetPrefix set the prefix for the default logger. Empty by default.
|
||||
func SetPrefix(prefix string) {
|
||||
defaultNotepad.SetPrefix(prefix)
|
||||
reloadDefaultNotepad()
|
||||
}
|
||||
|
||||
// SetFlags set the flags for the default logger. "log.Ldate | log.Ltime" by default.
|
||||
func SetFlags(flags int) {
|
||||
defaultNotepad.SetFlags(flags)
|
||||
reloadDefaultNotepad()
|
||||
}
|
||||
|
||||
// SetLogListeners configures the default logger with one or more log listeners.
|
||||
func SetLogListeners(l ...LogListener) {
|
||||
defaultNotepad.logListeners = l
|
||||
defaultNotepad.init()
|
||||
reloadDefaultNotepad()
|
||||
}
|
||||
|
||||
// Level returns the current global log threshold.
|
||||
func LogThreshold() Threshold {
|
||||
return defaultNotepad.logThreshold
|
||||
}
|
||||
|
||||
// Level returns the current global output threshold.
|
||||
func StdoutThreshold() Threshold {
|
||||
return defaultNotepad.stdoutThreshold
|
||||
}
|
||||
|
||||
// GetStdoutThreshold returns the defined Treshold for the log logger.
|
||||
func GetLogThreshold() Threshold {
|
||||
return defaultNotepad.GetLogThreshold()
|
||||
}
|
||||
|
||||
// GetStdoutThreshold returns the Treshold for the stdout logger.
|
||||
func GetStdoutThreshold() Threshold {
|
||||
return defaultNotepad.GetStdoutThreshold()
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
// Copyright © 2016 Steve Francia <spf@spf13.com>.
|
||||
//
|
||||
// Use of this source code is governed by an MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package jwalterweatherman
|
||||
|
||||
import (
|
||||
"io"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// Counter is an io.Writer that increments a counter on Write.
|
||||
type Counter struct {
|
||||
count uint64
|
||||
}
|
||||
|
||||
func (c *Counter) incr() {
|
||||
atomic.AddUint64(&c.count, 1)
|
||||
}
|
||||
|
||||
// Reset resets the counter.
|
||||
func (c *Counter) Reset() {
|
||||
atomic.StoreUint64(&c.count, 0)
|
||||
}
|
||||
|
||||
// Count returns the current count.
|
||||
func (c *Counter) Count() uint64 {
|
||||
return atomic.LoadUint64(&c.count)
|
||||
}
|
||||
|
||||
func (c *Counter) Write(p []byte) (n int, err error) {
|
||||
c.incr()
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
// LogCounter creates a LogListener that counts log statements >= the given threshold.
|
||||
func LogCounter(counter *Counter, t1 Threshold) LogListener {
|
||||
return func(t2 Threshold) io.Writer {
|
||||
if t2 < t1 {
|
||||
// Not interested in this threshold.
|
||||
return nil
|
||||
}
|
||||
return counter
|
||||
}
|
||||
}
|
|
@ -1,225 +0,0 @@
|
|||
// Copyright © 2016 Steve Francia <spf@spf13.com>.
|
||||
//
|
||||
// Use of this source code is governed by an MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package jwalterweatherman
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
)
|
||||
|
||||
type Threshold int
|
||||
|
||||
func (t Threshold) String() string {
|
||||
return prefixes[t]
|
||||
}
|
||||
|
||||
const (
|
||||
LevelTrace Threshold = iota
|
||||
LevelDebug
|
||||
LevelInfo
|
||||
LevelWarn
|
||||
LevelError
|
||||
LevelCritical
|
||||
LevelFatal
|
||||
)
|
||||
|
||||
var prefixes map[Threshold]string = map[Threshold]string{
|
||||
LevelTrace: "TRACE",
|
||||
LevelDebug: "DEBUG",
|
||||
LevelInfo: "INFO",
|
||||
LevelWarn: "WARN",
|
||||
LevelError: "ERROR",
|
||||
LevelCritical: "CRITICAL",
|
||||
LevelFatal: "FATAL",
|
||||
}
|
||||
|
||||
// Notepad is where you leave a note!
|
||||
type Notepad struct {
|
||||
TRACE *log.Logger
|
||||
DEBUG *log.Logger
|
||||
INFO *log.Logger
|
||||
WARN *log.Logger
|
||||
ERROR *log.Logger
|
||||
CRITICAL *log.Logger
|
||||
FATAL *log.Logger
|
||||
|
||||
LOG *log.Logger
|
||||
FEEDBACK *Feedback
|
||||
|
||||
loggers [7]**log.Logger
|
||||
logHandle io.Writer
|
||||
outHandle io.Writer
|
||||
logThreshold Threshold
|
||||
stdoutThreshold Threshold
|
||||
prefix string
|
||||
flags int
|
||||
|
||||
logListeners []LogListener
|
||||
}
|
||||
|
||||
// A LogListener can ble supplied to a Notepad to listen on log writes for a given
|
||||
// threshold. This can be used to capture log events in unit tests and similar.
|
||||
// Note that this function will be invoked once for each log threshold. If
|
||||
// the given threshold is not of interest to you, return nil.
|
||||
// Note that these listeners will receive log events for a given threshold, even
|
||||
// if the current configuration says not to log it. That way you can count ERRORs even
|
||||
// if you don't print them to the console.
|
||||
type LogListener func(t Threshold) io.Writer
|
||||
|
||||
// NewNotepad creates a new Notepad.
|
||||
func NewNotepad(
|
||||
outThreshold Threshold,
|
||||
logThreshold Threshold,
|
||||
outHandle, logHandle io.Writer,
|
||||
prefix string, flags int,
|
||||
logListeners ...LogListener,
|
||||
) *Notepad {
|
||||
|
||||
n := &Notepad{logListeners: logListeners}
|
||||
|
||||
n.loggers = [7]**log.Logger{&n.TRACE, &n.DEBUG, &n.INFO, &n.WARN, &n.ERROR, &n.CRITICAL, &n.FATAL}
|
||||
n.outHandle = outHandle
|
||||
n.logHandle = logHandle
|
||||
n.stdoutThreshold = outThreshold
|
||||
n.logThreshold = logThreshold
|
||||
|
||||
if len(prefix) != 0 {
|
||||
n.prefix = "[" + prefix + "] "
|
||||
} else {
|
||||
n.prefix = ""
|
||||
}
|
||||
|
||||
n.flags = flags
|
||||
|
||||
n.LOG = log.New(n.logHandle,
|
||||
"LOG: ",
|
||||
n.flags)
|
||||
n.FEEDBACK = &Feedback{out: log.New(outHandle, "", 0), log: n.LOG}
|
||||
|
||||
n.init()
|
||||
return n
|
||||
}
|
||||
|
||||
// init creates the loggers for each level depending on the notepad thresholds.
|
||||
func (n *Notepad) init() {
|
||||
logAndOut := io.MultiWriter(n.outHandle, n.logHandle)
|
||||
|
||||
for t, logger := range n.loggers {
|
||||
threshold := Threshold(t)
|
||||
prefix := n.prefix + threshold.String() + " "
|
||||
|
||||
switch {
|
||||
case threshold >= n.logThreshold && threshold >= n.stdoutThreshold:
|
||||
*logger = log.New(n.createLogWriters(threshold, logAndOut), prefix, n.flags)
|
||||
|
||||
case threshold >= n.logThreshold:
|
||||
*logger = log.New(n.createLogWriters(threshold, n.logHandle), prefix, n.flags)
|
||||
|
||||
case threshold >= n.stdoutThreshold:
|
||||
*logger = log.New(n.createLogWriters(threshold, n.outHandle), prefix, n.flags)
|
||||
|
||||
default:
|
||||
*logger = log.New(n.createLogWriters(threshold, ioutil.Discard), prefix, n.flags)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (n *Notepad) createLogWriters(t Threshold, handle io.Writer) io.Writer {
|
||||
if len(n.logListeners) == 0 {
|
||||
return handle
|
||||
}
|
||||
writers := []io.Writer{handle}
|
||||
for _, l := range n.logListeners {
|
||||
w := l(t)
|
||||
if w != nil {
|
||||
writers = append(writers, w)
|
||||
}
|
||||
}
|
||||
|
||||
if len(writers) == 1 {
|
||||
return handle
|
||||
}
|
||||
|
||||
return io.MultiWriter(writers...)
|
||||
}
|
||||
|
||||
// SetLogThreshold changes the threshold above which messages are written to the
|
||||
// log file.
|
||||
func (n *Notepad) SetLogThreshold(threshold Threshold) {
|
||||
n.logThreshold = threshold
|
||||
n.init()
|
||||
}
|
||||
|
||||
// SetLogOutput changes the file where log messages are written.
|
||||
func (n *Notepad) SetLogOutput(handle io.Writer) {
|
||||
n.logHandle = handle
|
||||
n.init()
|
||||
}
|
||||
|
||||
// GetStdoutThreshold returns the defined Treshold for the log logger.
|
||||
func (n *Notepad) GetLogThreshold() Threshold {
|
||||
return n.logThreshold
|
||||
}
|
||||
|
||||
// SetStdoutThreshold changes the threshold above which messages are written to the
|
||||
// standard output.
|
||||
func (n *Notepad) SetStdoutThreshold(threshold Threshold) {
|
||||
n.stdoutThreshold = threshold
|
||||
n.init()
|
||||
}
|
||||
|
||||
// GetStdoutThreshold returns the Treshold for the stdout logger.
|
||||
func (n *Notepad) GetStdoutThreshold() Threshold {
|
||||
return n.stdoutThreshold
|
||||
}
|
||||
|
||||
// SetPrefix changes the prefix used by the notepad. Prefixes are displayed between
|
||||
// brackets at the beginning of the line. An empty prefix won't be displayed at all.
|
||||
func (n *Notepad) SetPrefix(prefix string) {
|
||||
if len(prefix) != 0 {
|
||||
n.prefix = "[" + prefix + "] "
|
||||
} else {
|
||||
n.prefix = ""
|
||||
}
|
||||
n.init()
|
||||
}
|
||||
|
||||
// SetFlags choose which flags the logger will display (after prefix and message
|
||||
// level). See the package log for more informations on this.
|
||||
func (n *Notepad) SetFlags(flags int) {
|
||||
n.flags = flags
|
||||
n.init()
|
||||
}
|
||||
|
||||
// Feedback writes plainly to the outHandle while
|
||||
// logging with the standard extra information (date, file, etc).
|
||||
type Feedback struct {
|
||||
out *log.Logger
|
||||
log *log.Logger
|
||||
}
|
||||
|
||||
func (fb *Feedback) Println(v ...interface{}) {
|
||||
fb.output(fmt.Sprintln(v...))
|
||||
}
|
||||
|
||||
func (fb *Feedback) Printf(format string, v ...interface{}) {
|
||||
fb.output(fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
||||
func (fb *Feedback) Print(v ...interface{}) {
|
||||
fb.output(fmt.Sprint(v...))
|
||||
}
|
||||
|
||||
func (fb *Feedback) output(s string) {
|
||||
if fb.out != nil {
|
||||
fb.out.Output(2, s)
|
||||
}
|
||||
if fb.log != nil {
|
||||
fb.log.Output(2, s)
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue