chore: make deps
This commit is contained in:
64
go.mod
64
go.mod
@ -1,8 +1,6 @@
|
|||||||
module coopcloud.tech/abra
|
module coopcloud.tech/abra
|
||||||
|
|
||||||
go 1.24.0
|
go 1.24.2
|
||||||
|
|
||||||
toolchain go1.24.1
|
|
||||||
|
|
||||||
require (
|
require (
|
||||||
coopcloud.tech/tagcmp v0.0.0-20250818180036-0ec1b205b5ca
|
coopcloud.tech/tagcmp v0.0.0-20250818180036-0ec1b205b5ca
|
||||||
@ -12,17 +10,17 @@ require (
|
|||||||
github.com/charmbracelet/lipgloss v1.1.0
|
github.com/charmbracelet/lipgloss v1.1.0
|
||||||
github.com/charmbracelet/log v0.4.2
|
github.com/charmbracelet/log v0.4.2
|
||||||
github.com/distribution/reference v0.6.0
|
github.com/distribution/reference v0.6.0
|
||||||
github.com/docker/cli v28.4.0+incompatible
|
github.com/docker/cli v29.0.0+incompatible
|
||||||
github.com/docker/docker v28.4.0+incompatible
|
github.com/docker/docker v28.5.2+incompatible
|
||||||
github.com/docker/go-units v0.5.0
|
github.com/docker/go-units v0.5.0
|
||||||
github.com/go-git/go-git/v5 v5.16.2
|
github.com/go-git/go-git/v5 v5.16.3
|
||||||
github.com/google/go-cmp v0.7.0
|
github.com/google/go-cmp v0.7.0
|
||||||
github.com/leonelquinteros/gotext v1.7.2
|
github.com/leonelquinteros/gotext v1.7.2
|
||||||
github.com/moby/sys/signal v0.7.1
|
github.com/moby/sys/signal v0.7.1
|
||||||
github.com/moby/term v0.5.2
|
github.com/moby/term v0.5.2
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/schollz/progressbar/v3 v3.18.0
|
github.com/schollz/progressbar/v3 v3.18.0
|
||||||
golang.org/x/term v0.35.0
|
golang.org/x/term v0.36.0
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
gotest.tools/v3 v3.5.2
|
gotest.tools/v3 v3.5.2
|
||||||
)
|
)
|
||||||
@ -38,19 +36,21 @@ require (
|
|||||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||||
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
|
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
github.com/charmbracelet/colorprofile v0.3.2 // indirect
|
github.com/charmbracelet/colorprofile v0.3.3 // indirect
|
||||||
github.com/charmbracelet/x/ansi v0.10.2 // indirect
|
github.com/charmbracelet/x/ansi v0.11.0 // indirect
|
||||||
github.com/charmbracelet/x/cellbuf v0.0.13 // indirect
|
github.com/charmbracelet/x/cellbuf v0.0.14 // indirect
|
||||||
github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 // indirect
|
github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 // indirect
|
||||||
github.com/charmbracelet/x/term v0.2.1 // indirect
|
github.com/charmbracelet/x/term v0.2.2 // indirect
|
||||||
github.com/clipperhouse/uax29/v2 v2.2.0 // indirect
|
github.com/clipperhouse/displaywidth v0.5.0 // indirect
|
||||||
|
github.com/clipperhouse/stringish v0.1.1 // indirect
|
||||||
|
github.com/clipperhouse/uax29/v2 v2.3.0 // indirect
|
||||||
github.com/cloudflare/circl v1.6.1 // indirect
|
github.com/cloudflare/circl v1.6.1 // indirect
|
||||||
github.com/containerd/errdefs v1.0.0 // indirect
|
github.com/containerd/errdefs v1.0.0 // indirect
|
||||||
github.com/containerd/errdefs/pkg v0.3.0 // indirect
|
github.com/containerd/errdefs/pkg v0.3.0 // indirect
|
||||||
github.com/containerd/log v0.1.0 // indirect
|
github.com/containerd/log v0.1.0 // indirect
|
||||||
github.com/containerd/platforms v0.2.1 // indirect
|
github.com/containerd/platforms v0.2.1 // indirect
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
|
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
|
||||||
github.com/cyphar/filepath-securejoin v0.5.0 // indirect
|
github.com/cyphar/filepath-securejoin v0.6.0 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/docker/distribution v2.8.3+incompatible // indirect
|
github.com/docker/distribution v2.8.3+incompatible // indirect
|
||||||
github.com/docker/go-connections v0.6.0 // indirect
|
github.com/docker/go-connections v0.6.0 // indirect
|
||||||
@ -62,19 +62,19 @@ require (
|
|||||||
github.com/ghodss/yaml v1.0.0 // indirect
|
github.com/ghodss/yaml v1.0.0 // indirect
|
||||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
||||||
github.com/go-git/go-billy/v5 v5.6.2 // indirect
|
github.com/go-git/go-billy/v5 v5.6.2 // indirect
|
||||||
github.com/go-logfmt/logfmt v0.6.0 // indirect
|
github.com/go-logfmt/logfmt v0.6.1 // indirect
|
||||||
github.com/go-logr/logr v1.4.3 // indirect
|
github.com/go-logr/logr v1.4.3 // indirect
|
||||||
github.com/go-logr/stdr v1.2.2 // indirect
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
||||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 // indirect
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||||
github.com/kevinburke/ssh_config v1.4.0 // indirect
|
github.com/kevinburke/ssh_config v1.4.0 // indirect
|
||||||
github.com/klauspost/compress v1.18.0 // indirect
|
github.com/klauspost/compress v1.18.1 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||||
github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
|
github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||||
@ -86,6 +86,8 @@ require (
|
|||||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
|
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
|
||||||
github.com/moby/docker-image-spec v1.3.1 // indirect
|
github.com/moby/docker-image-spec v1.3.1 // indirect
|
||||||
github.com/moby/go-archive v0.1.0 // indirect
|
github.com/moby/go-archive v0.1.0 // indirect
|
||||||
|
github.com/moby/moby/api v1.52.0 // indirect
|
||||||
|
github.com/moby/moby/client v0.1.0 // indirect
|
||||||
github.com/moby/sys/atomicwriter v0.1.0 // indirect
|
github.com/moby/sys/atomicwriter v0.1.0 // indirect
|
||||||
github.com/moby/sys/user v0.4.0 // indirect
|
github.com/moby/sys/user v0.4.0 // indirect
|
||||||
github.com/moby/sys/userns v0.1.0 // indirect
|
github.com/moby/sys/userns v0.1.0 // indirect
|
||||||
@ -100,12 +102,12 @@ require (
|
|||||||
github.com/pjbgf/sha1cd v0.5.0 // indirect
|
github.com/pjbgf/sha1cd v0.5.0 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/prometheus/client_model v0.6.2 // indirect
|
github.com/prometheus/client_model v0.6.2 // indirect
|
||||||
github.com/prometheus/common v0.66.1 // indirect
|
github.com/prometheus/common v0.67.2 // indirect
|
||||||
github.com/prometheus/procfs v0.17.0 // indirect
|
github.com/prometheus/procfs v0.19.2 // indirect
|
||||||
github.com/rivo/uniseg v0.4.7 // indirect
|
github.com/rivo/uniseg v0.4.7 // indirect
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||||
github.com/skeema/knownhosts v1.3.1 // indirect
|
github.com/skeema/knownhosts v1.3.2 // indirect
|
||||||
github.com/spf13/pflag v1.0.10 // indirect
|
github.com/spf13/pflag v1.0.10 // indirect
|
||||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||||
@ -122,17 +124,17 @@ require (
|
|||||||
go.opentelemetry.io/otel/sdk v1.38.0 // indirect
|
go.opentelemetry.io/otel/sdk v1.38.0 // indirect
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.38.0 // indirect
|
go.opentelemetry.io/otel/sdk/metric v1.38.0 // indirect
|
||||||
go.opentelemetry.io/otel/trace v1.38.0 // indirect
|
go.opentelemetry.io/otel/trace v1.38.0 // indirect
|
||||||
go.opentelemetry.io/proto/otlp v1.8.0 // indirect
|
go.opentelemetry.io/proto/otlp v1.9.0 // indirect
|
||||||
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
||||||
golang.org/x/crypto v0.42.0 // indirect
|
golang.org/x/crypto v0.43.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20250911091902-df9299821621 // indirect
|
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect
|
||||||
golang.org/x/net v0.44.0 // indirect
|
golang.org/x/net v0.46.0 // indirect
|
||||||
golang.org/x/text v0.29.0 // indirect
|
golang.org/x/text v0.30.0 // indirect
|
||||||
golang.org/x/time v0.13.0 // indirect
|
golang.org/x/time v0.14.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20250929231259-57b25ae835d4 // indirect
|
google.golang.org/genproto/googleapis/api v0.0.0-20251110190251-83f479183930 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250929231259-57b25ae835d4 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251110190251-83f479183930 // indirect
|
||||||
google.golang.org/grpc v1.75.1 // indirect
|
google.golang.org/grpc v1.76.0 // indirect
|
||||||
google.golang.org/protobuf v1.36.9 // indirect
|
google.golang.org/protobuf v1.36.10 // indirect
|
||||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
)
|
)
|
||||||
@ -141,7 +143,7 @@ require (
|
|||||||
github.com/containers/image v3.0.2+incompatible
|
github.com/containers/image v3.0.2+incompatible
|
||||||
github.com/containers/storage v1.38.2 // indirect
|
github.com/containers/storage v1.38.2 // indirect
|
||||||
github.com/decentral1se/passgen v1.0.1
|
github.com/decentral1se/passgen v1.0.1
|
||||||
github.com/docker/docker-credential-helpers v0.9.3 // indirect
|
github.com/docker/docker-credential-helpers v0.9.4 // indirect
|
||||||
github.com/fvbommel/sortorder v1.1.0 // indirect
|
github.com/fvbommel/sortorder v1.1.0 // indirect
|
||||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||||
github.com/gorilla/mux v1.8.1 // indirect
|
github.com/gorilla/mux v1.8.1 // indirect
|
||||||
@ -155,5 +157,5 @@ require (
|
|||||||
github.com/stretchr/testify v1.11.1
|
github.com/stretchr/testify v1.11.1
|
||||||
github.com/theupdateframework/notary v0.7.0 // indirect
|
github.com/theupdateframework/notary v0.7.0 // indirect
|
||||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
||||||
golang.org/x/sys v0.36.0
|
golang.org/x/sys v0.38.0
|
||||||
)
|
)
|
||||||
|
|||||||
69
go.sum
69
go.sum
@ -129,6 +129,7 @@ github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyY
|
|||||||
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
|
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
|
||||||
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
|
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
|
||||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||||
|
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
||||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||||
@ -137,18 +138,26 @@ github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlv
|
|||||||
github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4=
|
github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4=
|
||||||
github.com/charmbracelet/colorprofile v0.3.2 h1:9J27WdztfJQVAQKX2WOlSSRB+5gaKqqITmrvb1uTIiI=
|
github.com/charmbracelet/colorprofile v0.3.2 h1:9J27WdztfJQVAQKX2WOlSSRB+5gaKqqITmrvb1uTIiI=
|
||||||
github.com/charmbracelet/colorprofile v0.3.2/go.mod h1:mTD5XzNeWHj8oqHb+S1bssQb7vIHbepiebQ2kPKVKbI=
|
github.com/charmbracelet/colorprofile v0.3.2/go.mod h1:mTD5XzNeWHj8oqHb+S1bssQb7vIHbepiebQ2kPKVKbI=
|
||||||
|
github.com/charmbracelet/colorprofile v0.3.3 h1:DjJzJtLP6/NZ8p7Cgjno0CKGr7wwRJGxWUwh2IyhfAI=
|
||||||
|
github.com/charmbracelet/colorprofile v0.3.3/go.mod h1:nB1FugsAbzq284eJcjfah2nhdSLppN2NqvfotkfRYP4=
|
||||||
github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
|
github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
|
||||||
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
|
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
|
||||||
github.com/charmbracelet/log v0.4.2 h1:hYt8Qj6a8yLnvR+h7MwsJv/XvmBJXiueUcI3cIxsyig=
|
github.com/charmbracelet/log v0.4.2 h1:hYt8Qj6a8yLnvR+h7MwsJv/XvmBJXiueUcI3cIxsyig=
|
||||||
github.com/charmbracelet/log v0.4.2/go.mod h1:qifHGX/tc7eluv2R6pWIpyHDDrrb/AG71Pf2ysQu5nw=
|
github.com/charmbracelet/log v0.4.2/go.mod h1:qifHGX/tc7eluv2R6pWIpyHDDrrb/AG71Pf2ysQu5nw=
|
||||||
github.com/charmbracelet/x/ansi v0.10.2 h1:ith2ArZS0CJG30cIUfID1LXN7ZFXRCww6RUvAPA+Pzw=
|
github.com/charmbracelet/x/ansi v0.10.2 h1:ith2ArZS0CJG30cIUfID1LXN7ZFXRCww6RUvAPA+Pzw=
|
||||||
github.com/charmbracelet/x/ansi v0.10.2/go.mod h1:HbLdJjQH4UH4AqA2HpRWuWNluRE6zxJH/yteYEYCFa8=
|
github.com/charmbracelet/x/ansi v0.10.2/go.mod h1:HbLdJjQH4UH4AqA2HpRWuWNluRE6zxJH/yteYEYCFa8=
|
||||||
|
github.com/charmbracelet/x/ansi v0.11.0 h1:uuIVK7GIplwX6UBIz8S2TF8nkr7xRlygSsBRjSJqIvA=
|
||||||
|
github.com/charmbracelet/x/ansi v0.11.0/go.mod h1:uQt8bOrq/xgXjlGcFMc8U2WYbnxyjrKhnvTQluvfCaE=
|
||||||
github.com/charmbracelet/x/cellbuf v0.0.13 h1:/KBBKHuVRbq1lYx5BzEHBAFBP8VcQzJejZ/IA3iR28k=
|
github.com/charmbracelet/x/cellbuf v0.0.13 h1:/KBBKHuVRbq1lYx5BzEHBAFBP8VcQzJejZ/IA3iR28k=
|
||||||
github.com/charmbracelet/x/cellbuf v0.0.13/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
|
github.com/charmbracelet/x/cellbuf v0.0.13/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
|
||||||
|
github.com/charmbracelet/x/cellbuf v0.0.14 h1:iUEMryGyFTelKW3THW4+FfPgi4fkmKnnaLOXuc+/Kj4=
|
||||||
|
github.com/charmbracelet/x/cellbuf v0.0.14/go.mod h1:P447lJl49ywBbil/KjCk2HexGh4tEY9LH0/1QrZZ9rA=
|
||||||
github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 h1:payRxjMjKgx2PaCWLZ4p3ro9y97+TVLZNaRZgJwSVDQ=
|
github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 h1:payRxjMjKgx2PaCWLZ4p3ro9y97+TVLZNaRZgJwSVDQ=
|
||||||
github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=
|
github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=
|
||||||
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
|
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
|
||||||
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
|
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
|
||||||
|
github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk=
|
||||||
|
github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI=
|
||||||
github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw=
|
github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw=
|
||||||
github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M=
|
github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M=
|
||||||
github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E=
|
github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E=
|
||||||
@ -164,8 +173,14 @@ github.com/cilium/ebpf v0.4.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJ
|
|||||||
github.com/cilium/ebpf v0.6.2/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs=
|
github.com/cilium/ebpf v0.6.2/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs=
|
||||||
github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA=
|
github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA=
|
||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
|
github.com/clipperhouse/displaywidth v0.5.0 h1:AIG5vQaSL2EKqzt0M9JMnvNxOCRTKUc4vUnLWGgP89I=
|
||||||
|
github.com/clipperhouse/displaywidth v0.5.0/go.mod h1:R+kHuzaYWFkTm7xoMmK1lFydbci4X2CicfbGstSGg0o=
|
||||||
|
github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs=
|
||||||
|
github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA=
|
||||||
github.com/clipperhouse/uax29/v2 v2.2.0 h1:ChwIKnQN3kcZteTXMgb1wztSgaU+ZemkgWdohwgs8tY=
|
github.com/clipperhouse/uax29/v2 v2.2.0 h1:ChwIKnQN3kcZteTXMgb1wztSgaU+ZemkgWdohwgs8tY=
|
||||||
github.com/clipperhouse/uax29/v2 v2.2.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
|
github.com/clipperhouse/uax29/v2 v2.2.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
|
||||||
|
github.com/clipperhouse/uax29/v2 v2.3.0 h1:SNdx9DVUqMoBuBoW3iLOj4FQv3dN5mDtuqwuhIGpJy4=
|
||||||
|
github.com/clipperhouse/uax29/v2 v2.3.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g=
|
||||||
github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004/go.mod h1:yMWuSON2oQp+43nFtAV/uvKQIFpSPerB57DCt9t8sSA=
|
github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004/go.mod h1:yMWuSON2oQp+43nFtAV/uvKQIFpSPerB57DCt9t8sSA=
|
||||||
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
|
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
|
||||||
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
||||||
@ -300,6 +315,8 @@ github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2
|
|||||||
github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
|
github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
|
||||||
github.com/cyphar/filepath-securejoin v0.5.0 h1:hIAhkRBMQ8nIeuVwcAoymp7MY4oherZdAxD+m0u9zaw=
|
github.com/cyphar/filepath-securejoin v0.5.0 h1:hIAhkRBMQ8nIeuVwcAoymp7MY4oherZdAxD+m0u9zaw=
|
||||||
github.com/cyphar/filepath-securejoin v0.5.0/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
|
github.com/cyphar/filepath-securejoin v0.5.0/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
|
||||||
|
github.com/cyphar/filepath-securejoin v0.6.0 h1:BtGB77njd6SVO6VztOHfPxKitJvd/VPT+OFBFMOi1Is=
|
||||||
|
github.com/cyphar/filepath-securejoin v0.6.0/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc=
|
||||||
github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ=
|
github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ=
|
||||||
github.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s=
|
github.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s=
|
||||||
github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5/go.mod h1:Eo87+Kg/IX2hfWJfwxMzLyuSZyxSoAug2nGa1G2QAi8=
|
github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5/go.mod h1:Eo87+Kg/IX2hfWJfwxMzLyuSZyxSoAug2nGa1G2QAi8=
|
||||||
@ -320,6 +337,8 @@ github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyG
|
|||||||
github.com/docker/cli v0.0.0-20191017083524-a8ff7f821017/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
github.com/docker/cli v0.0.0-20191017083524-a8ff7f821017/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||||
github.com/docker/cli v28.4.0+incompatible h1:RBcf3Kjw2pMtwui5V0DIMdyeab8glEw5QY0UUU4C9kY=
|
github.com/docker/cli v28.4.0+incompatible h1:RBcf3Kjw2pMtwui5V0DIMdyeab8glEw5QY0UUU4C9kY=
|
||||||
github.com/docker/cli v28.4.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
github.com/docker/cli v28.4.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||||
|
github.com/docker/cli v29.0.0+incompatible h1:KgsN2RUFMNM8wChxryicn4p46BdQWpXOA1XLGBGPGAw=
|
||||||
|
github.com/docker/cli v29.0.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||||
github.com/docker/distribution v0.0.0-20190905152932-14b96e55d84c/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY=
|
github.com/docker/distribution v0.0.0-20190905152932-14b96e55d84c/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY=
|
||||||
github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||||
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||||
@ -328,9 +347,13 @@ github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4Kfc
|
|||||||
github.com/docker/docker v1.4.2-0.20190924003213-a8608b5b67c7/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
github.com/docker/docker v1.4.2-0.20190924003213-a8608b5b67c7/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||||
github.com/docker/docker v28.4.0+incompatible h1:KVC7bz5zJY/4AZe/78BIvCnPsLaC9T/zh72xnlrTTOk=
|
github.com/docker/docker v28.4.0+incompatible h1:KVC7bz5zJY/4AZe/78BIvCnPsLaC9T/zh72xnlrTTOk=
|
||||||
github.com/docker/docker v28.4.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
github.com/docker/docker v28.4.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||||
|
github.com/docker/docker v28.5.2+incompatible h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM=
|
||||||
|
github.com/docker/docker v28.5.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||||
github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y=
|
github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y=
|
||||||
github.com/docker/docker-credential-helpers v0.9.3 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqIjCBS4wrsOh9yRqcz8=
|
github.com/docker/docker-credential-helpers v0.9.3 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqIjCBS4wrsOh9yRqcz8=
|
||||||
github.com/docker/docker-credential-helpers v0.9.3/go.mod h1:x+4Gbw9aGmChi3qTLZj8Dfn0TD20M/fuWy0E5+WDeCo=
|
github.com/docker/docker-credential-helpers v0.9.3/go.mod h1:x+4Gbw9aGmChi3qTLZj8Dfn0TD20M/fuWy0E5+WDeCo=
|
||||||
|
github.com/docker/docker-credential-helpers v0.9.4 h1:76ItO69/AP/V4yT9V4uuuItG0B1N8hvt0T0c0NN/DzI=
|
||||||
|
github.com/docker/docker-credential-helpers v0.9.4/go.mod h1:v1S+hepowrQXITkEfw6o4+BMbGot02wiKpzWhGUZK6c=
|
||||||
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c h1:lzqkGL9b3znc+ZUgi7FlLnqjQhcXxkNM/quxIjBVMD0=
|
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c h1:lzqkGL9b3znc+ZUgi7FlLnqjQhcXxkNM/quxIjBVMD0=
|
||||||
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c/go.mod h1:CADgU4DSXK5QUlFslkQu2yW2TKzFZcXq/leZfM0UH5Q=
|
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c/go.mod h1:CADgU4DSXK5QUlFslkQu2yW2TKzFZcXq/leZfM0UH5Q=
|
||||||
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
||||||
@ -395,6 +418,8 @@ github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMj
|
|||||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
|
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
|
||||||
github.com/go-git/go-git/v5 v5.16.2 h1:fT6ZIOjE5iEnkzKyxTHK1W4HGAsPhqEqiSAssSO77hM=
|
github.com/go-git/go-git/v5 v5.16.2 h1:fT6ZIOjE5iEnkzKyxTHK1W4HGAsPhqEqiSAssSO77hM=
|
||||||
github.com/go-git/go-git/v5 v5.16.2/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8=
|
github.com/go-git/go-git/v5 v5.16.2/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8=
|
||||||
|
github.com/go-git/go-git/v5 v5.16.3 h1:Z8BtvxZ09bYm/yYNgPKCzgWtaRqDTgIKRgIRHBfU6Z8=
|
||||||
|
github.com/go-git/go-git/v5 v5.16.3/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8=
|
||||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
@ -405,6 +430,8 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9
|
|||||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||||
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
|
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
|
||||||
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
|
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
|
||||||
|
github.com/go-logfmt/logfmt v0.6.1 h1:4hvbpePJKnIzH1B+8OR/JPbTx37NktoI9LE2QZBBkvE=
|
||||||
|
github.com/go-logfmt/logfmt v0.6.1/go.mod h1:EV2pOAQoZaT1ZXZbqDl5hrymndi4SY9ED9/z6CO0XAk=
|
||||||
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
|
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
|
||||||
github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
|
github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
|
||||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||||
@ -529,9 +556,12 @@ github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de
|
|||||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||||
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||||
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=
|
||||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU=
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU=
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs=
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs=
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg=
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63DSVPFklWpGX4OWAc9bFuaH4=
|
||||||
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4=
|
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4=
|
||||||
github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||||
@ -591,6 +621,8 @@ github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdY
|
|||||||
github.com/klauspost/compress v1.14.2/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
github.com/klauspost/compress v1.14.2/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||||
|
github.com/klauspost/compress v1.18.1 h1:bcSGx7UbpBqMChDtsF28Lw6v/G94LPrrbMbdC3JH2co=
|
||||||
|
github.com/klauspost/compress v1.18.1/go.mod h1:ZQFFVG+MdnR0P+l6wpXgIL4NTtwiKIdBnrBd8Nrxr+0=
|
||||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||||
github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
||||||
@ -662,6 +694,10 @@ github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6U
|
|||||||
github.com/moby/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ=
|
github.com/moby/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ=
|
||||||
github.com/moby/go-archive v0.1.0/go.mod h1:G9B+YoujNohJmrIYFBpSd54GTUB4lt9S+xVQvsJyFuo=
|
github.com/moby/go-archive v0.1.0/go.mod h1:G9B+YoujNohJmrIYFBpSd54GTUB4lt9S+xVQvsJyFuo=
|
||||||
github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc=
|
github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc=
|
||||||
|
github.com/moby/moby/api v1.52.0 h1:00BtlJY4MXkkt84WhUZPRqt5TvPbgig2FZvTbe3igYg=
|
||||||
|
github.com/moby/moby/api v1.52.0/go.mod h1:8mb+ReTlisw4pS6BRzCMts5M49W5M7bKt1cJy/YbAqc=
|
||||||
|
github.com/moby/moby/client v0.1.0 h1:nt+hn6O9cyJQqq5UWnFGqsZRTS/JirUqzPjEl0Bdc/8=
|
||||||
|
github.com/moby/moby/client v0.1.0/go.mod h1:O+/tw5d4a1Ha/ZA/tPxIZJapJRUS6LNZ1wiVRxYHyUE=
|
||||||
github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk=
|
github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk=
|
||||||
github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
|
github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
|
||||||
github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw=
|
github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw=
|
||||||
@ -795,6 +831,8 @@ github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+
|
|||||||
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
|
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
|
||||||
github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs=
|
github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs=
|
||||||
github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
|
github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
|
||||||
|
github.com/prometheus/common v0.67.2 h1:PcBAckGFTIHt2+L3I33uNRTlKTplNzFctXcWhPyAEN8=
|
||||||
|
github.com/prometheus/common v0.67.2/go.mod h1:63W3KZb1JOKgcjlIr64WW/LvFGAqKPj0atm+knVGEko=
|
||||||
github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||||
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||||
@ -808,6 +846,8 @@ github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O
|
|||||||
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||||
github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0=
|
github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0=
|
||||||
github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw=
|
github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw=
|
||||||
|
github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws=
|
||||||
|
github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw=
|
||||||
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
||||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||||
@ -816,6 +856,8 @@ github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6L
|
|||||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||||
|
github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww=
|
||||||
|
github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY=
|
||||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
@ -841,6 +883,8 @@ github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ
|
|||||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||||
github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8=
|
github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8=
|
||||||
github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY=
|
github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY=
|
||||||
|
github.com/skeema/knownhosts v1.3.2 h1:EDL9mgf4NzwMXCTfaxSD/o/a5fxDw/xL9nkU28JjdBg=
|
||||||
|
github.com/skeema/knownhosts v1.3.2/go.mod h1:bEg3iQAuw+jyiw+484wwFJoKSLwcfd7fqRy+N0QTiow=
|
||||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||||
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||||
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||||
@ -964,6 +1008,8 @@ go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42s
|
|||||||
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
||||||
go.opentelemetry.io/proto/otlp v1.8.0 h1:fRAZQDcAFHySxpJ1TwlA1cJ4tvcrw7nXl9xWWC8N5CE=
|
go.opentelemetry.io/proto/otlp v1.8.0 h1:fRAZQDcAFHySxpJ1TwlA1cJ4tvcrw7nXl9xWWC8N5CE=
|
||||||
go.opentelemetry.io/proto/otlp v1.8.0/go.mod h1:tIeYOeNBU4cvmPqpaji1P+KbB4Oloai8wN4rWzRrFF0=
|
go.opentelemetry.io/proto/otlp v1.8.0/go.mod h1:tIeYOeNBU4cvmPqpaji1P+KbB4Oloai8wN4rWzRrFF0=
|
||||||
|
go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A=
|
||||||
|
go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4=
|
||||||
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||||
@ -992,6 +1038,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
|
|||||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
|
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
|
||||||
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
|
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
|
||||||
|
golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
|
||||||
|
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||||
@ -1004,6 +1052,8 @@ golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EH
|
|||||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||||
golang.org/x/exp v0.0.0-20250911091902-df9299821621 h1:2id6c1/gto0kaHYyrixvknJ8tUK/Qs5IsmBtrc+FtgU=
|
golang.org/x/exp v0.0.0-20250911091902-df9299821621 h1:2id6c1/gto0kaHYyrixvknJ8tUK/Qs5IsmBtrc+FtgU=
|
||||||
golang.org/x/exp v0.0.0-20250911091902-df9299821621/go.mod h1:TwQYMMnGpvZyc+JpB/UAuTNIsVJifOlSkrZkhcvpVUk=
|
golang.org/x/exp v0.0.0-20250911091902-df9299821621/go.mod h1:TwQYMMnGpvZyc+JpB/UAuTNIsVJifOlSkrZkhcvpVUk=
|
||||||
|
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2wtOnGAHZWCHUM4KGzY=
|
||||||
|
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70=
|
||||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
@ -1069,6 +1119,8 @@ golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qx
|
|||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I=
|
golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I=
|
||||||
golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
|
golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
|
||||||
|
golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
|
||||||
|
golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
@ -1166,11 +1218,15 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
|
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
|
||||||
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
|
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||||
|
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ=
|
golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ=
|
||||||
golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA=
|
golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA=
|
||||||
|
golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q=
|
||||||
|
golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss=
|
||||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
@ -1182,6 +1238,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
|||||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
|
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
|
||||||
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
|
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
|
||||||
|
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
|
||||||
|
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
|
||||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
@ -1190,6 +1248,8 @@ golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxb
|
|||||||
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.13.0 h1:eUlYslOIt32DgYD6utsuUeHs4d7AsEYLuIAdg7FlYgI=
|
golang.org/x/time v0.13.0 h1:eUlYslOIt32DgYD6utsuUeHs4d7AsEYLuIAdg7FlYgI=
|
||||||
golang.org/x/time v0.13.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
golang.org/x/time v0.13.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||||
|
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||||
|
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
@ -1287,8 +1347,12 @@ google.golang.org/genproto v0.0.0-20200527145253-8367513e4ece/go.mod h1:jDfRM7Fc
|
|||||||
google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20250929231259-57b25ae835d4 h1:8XJ4pajGwOlasW+L13MnEGA8W4115jJySQtVfS2/IBU=
|
google.golang.org/genproto/googleapis/api v0.0.0-20250929231259-57b25ae835d4 h1:8XJ4pajGwOlasW+L13MnEGA8W4115jJySQtVfS2/IBU=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20250929231259-57b25ae835d4/go.mod h1:NnuHhy+bxcg30o7FnVAZbXsPHUDQ9qKWAQKCD7VxFtk=
|
google.golang.org/genproto/googleapis/api v0.0.0-20250929231259-57b25ae835d4/go.mod h1:NnuHhy+bxcg30o7FnVAZbXsPHUDQ9qKWAQKCD7VxFtk=
|
||||||
|
google.golang.org/genproto/googleapis/api v0.0.0-20251110190251-83f479183930 h1:8BWFtrvJRbplrKV5VHlIm4YM726eeBPPAL2QDNWhRrU=
|
||||||
|
google.golang.org/genproto/googleapis/api v0.0.0-20251110190251-83f479183930/go.mod h1:G5IanEx8/PgI9w6CFcYQf7jMtHQhZruvfM1i3qOqk5U=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250929231259-57b25ae835d4 h1:i8QOKZfYg6AbGVZzUAY3LrNWCKF8O6zFisU9Wl9RER4=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250929231259-57b25ae835d4 h1:i8QOKZfYg6AbGVZzUAY3LrNWCKF8O6zFisU9Wl9RER4=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250929231259-57b25ae835d4/go.mod h1:HSkG/KdJWusxU1F6CNrwNDjBMgisKxGnc5dAZfT0mjQ=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250929231259-57b25ae835d4/go.mod h1:HSkG/KdJWusxU1F6CNrwNDjBMgisKxGnc5dAZfT0mjQ=
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251110190251-83f479183930 h1:tK4fkUnnRhig9TsTp4otV1FxwBFYgbKUq1RY0V6KZ4U=
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251110190251-83f479183930/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||||
google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||||
google.golang.org/grpc v1.0.5/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
google.golang.org/grpc v1.0.5/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
@ -1310,6 +1374,8 @@ google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAG
|
|||||||
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
|
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
|
||||||
google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI=
|
google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI=
|
||||||
google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
|
google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
|
||||||
|
google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A=
|
||||||
|
google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c=
|
||||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||||
@ -1325,6 +1391,8 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ
|
|||||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
|
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
|
||||||
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
||||||
|
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
|
||||||
|
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||||
gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
|
gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
|
||||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||||
gopkg.in/cenkalti/backoff.v2 v2.2.1/go.mod h1:S0QdOvT2AlerfSBkp0O+dk+bbIMaNbEmVk876gPCthU=
|
gopkg.in/cenkalti/backoff.v2 v2.2.1/go.mod h1:S0QdOvT2AlerfSBkp0O+dk+bbIMaNbEmVk876gPCthU=
|
||||||
@ -1362,6 +1430,7 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
|||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
|
||||||
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
|
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
|
||||||
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
|
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
|
||||||
gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8=
|
gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8=
|
||||||
|
|||||||
@ -19,7 +19,7 @@ import (
|
|||||||
"coopcloud.tech/abra/pkg/ui"
|
"coopcloud.tech/abra/pkg/ui"
|
||||||
"coopcloud.tech/abra/pkg/upstream/convert"
|
"coopcloud.tech/abra/pkg/upstream/convert"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/docker/cli/cli/command/stack/formatter"
|
"github.com/docker/cli/cli/command/formatter"
|
||||||
composetypes "github.com/docker/cli/cli/compose/types"
|
composetypes "github.com/docker/cli/cli/compose/types"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/docker/docker/api/types/container"
|
||||||
|
|||||||
33
vendor/github.com/charmbracelet/colorprofile/env.go
generated
vendored
33
vendor/github.com/charmbracelet/colorprofile/env.go
generated
vendored
@ -153,29 +153,24 @@ func envColorProfile(env environ) (p Profile) {
|
|||||||
p = ANSI
|
p = ANSI
|
||||||
}
|
}
|
||||||
|
|
||||||
parts := strings.Split(term, "-")
|
switch {
|
||||||
switch parts[0] {
|
case strings.Contains(term, "alacritty"),
|
||||||
case "alacritty",
|
strings.Contains(term, "contour"),
|
||||||
"contour",
|
strings.Contains(term, "foot"),
|
||||||
"foot",
|
strings.Contains(term, "ghostty"),
|
||||||
"ghostty",
|
strings.Contains(term, "kitty"),
|
||||||
"kitty",
|
strings.Contains(term, "rio"),
|
||||||
"rio",
|
strings.Contains(term, "st"),
|
||||||
"st",
|
strings.Contains(term, "wezterm"):
|
||||||
"wezterm":
|
|
||||||
return TrueColor
|
return TrueColor
|
||||||
case "xterm":
|
case strings.HasPrefix(term, "tmux"), strings.HasPrefix(term, "screen"):
|
||||||
if len(parts) > 1 {
|
|
||||||
switch parts[1] {
|
|
||||||
case "ghostty", "kitty":
|
|
||||||
// These terminals can be defined as xterm-TERMNAME
|
|
||||||
return TrueColor
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case "tmux", "screen":
|
|
||||||
if p < ANSI256 {
|
if p < ANSI256 {
|
||||||
p = ANSI256
|
p = ANSI256
|
||||||
}
|
}
|
||||||
|
case strings.HasPrefix(term, "xterm"):
|
||||||
|
if p < ANSI {
|
||||||
|
p = ANSI
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if isCloudShell, _ := strconv.ParseBool(env.get("GOOGLE_CLOUD_SHELL")); isCloudShell {
|
if isCloudShell, _ := strconv.ParseBool(env.get("GOOGLE_CLOUD_SHELL")); isCloudShell {
|
||||||
|
|||||||
465
vendor/github.com/charmbracelet/x/ansi/mode.go
generated
vendored
465
vendor/github.com/charmbracelet/x/ansi/mode.go
generated
vendored
@ -108,7 +108,7 @@ func DECRST(modes ...Mode) string {
|
|||||||
|
|
||||||
func setMode(reset bool, modes ...Mode) (s string) {
|
func setMode(reset bool, modes ...Mode) (s string) {
|
||||||
if len(modes) == 0 {
|
if len(modes) == 0 {
|
||||||
return //nolint:nakedret
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := "h"
|
cmd := "h"
|
||||||
@ -142,7 +142,7 @@ func setMode(reset bool, modes ...Mode) (s string) {
|
|||||||
if len(dec) > 0 {
|
if len(dec) > 0 {
|
||||||
s += seq + "?" + strings.Join(dec, ";") + cmd
|
s += seq + "?" + strings.Join(dec, ";") + cmd
|
||||||
}
|
}
|
||||||
return //nolint:nakedret
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
// RequestMode (DECRQM) returns a sequence to request a mode from the terminal.
|
// RequestMode (DECRQM) returns a sequence to request a mode from the terminal.
|
||||||
@ -228,12 +228,12 @@ func (m DECMode) Mode() int {
|
|||||||
//
|
//
|
||||||
// See: https://vt100.net/docs/vt510-rm/KAM.html
|
// See: https://vt100.net/docs/vt510-rm/KAM.html
|
||||||
const (
|
const (
|
||||||
KeyboardActionMode = ANSIMode(2)
|
ModeKeyboardAction = ANSIMode(2)
|
||||||
KAM = KeyboardActionMode
|
KAM = ModeKeyboardAction
|
||||||
|
|
||||||
SetKeyboardActionMode = "\x1b[2h"
|
SetModeKeyboardAction = "\x1b[2h"
|
||||||
ResetKeyboardActionMode = "\x1b[2l"
|
ResetModeKeyboardAction = "\x1b[2l"
|
||||||
RequestKeyboardActionMode = "\x1b[2$p"
|
RequestModeKeyboardAction = "\x1b[2$p"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Insert/Replace Mode (IRM) is a mode that determines whether characters are
|
// Insert/Replace Mode (IRM) is a mode that determines whether characters are
|
||||||
@ -245,12 +245,12 @@ const (
|
|||||||
//
|
//
|
||||||
// See: https://vt100.net/docs/vt510-rm/IRM.html
|
// See: https://vt100.net/docs/vt510-rm/IRM.html
|
||||||
const (
|
const (
|
||||||
InsertReplaceMode = ANSIMode(4)
|
ModeInsertReplace = ANSIMode(4)
|
||||||
IRM = InsertReplaceMode
|
IRM = ModeInsertReplace
|
||||||
|
|
||||||
SetInsertReplaceMode = "\x1b[4h"
|
SetModeInsertReplace = "\x1b[4h"
|
||||||
ResetInsertReplaceMode = "\x1b[4l"
|
ResetModeInsertReplace = "\x1b[4l"
|
||||||
RequestInsertReplaceMode = "\x1b[4$p"
|
RequestModeInsertReplace = "\x1b[4$p"
|
||||||
)
|
)
|
||||||
|
|
||||||
// BiDirectional Support Mode (BDSM) is a mode that determines whether the
|
// BiDirectional Support Mode (BDSM) is a mode that determines whether the
|
||||||
@ -260,12 +260,12 @@ const (
|
|||||||
//
|
//
|
||||||
// See ECMA-48 7.2.1.
|
// See ECMA-48 7.2.1.
|
||||||
const (
|
const (
|
||||||
BiDirectionalSupportMode = ANSIMode(8)
|
ModeBiDirectionalSupport = ANSIMode(8)
|
||||||
BDSM = BiDirectionalSupportMode
|
BDSM = ModeBiDirectionalSupport
|
||||||
|
|
||||||
SetBiDirectionalSupportMode = "\x1b[8h"
|
SetModeBiDirectionalSupport = "\x1b[8h"
|
||||||
ResetBiDirectionalSupportMode = "\x1b[8l"
|
ResetModeBiDirectionalSupport = "\x1b[8l"
|
||||||
RequestBiDirectionalSupportMode = "\x1b[8$p"
|
RequestModeBiDirectionalSupport = "\x1b[8$p"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Send Receive Mode (SRM) or Local Echo Mode is a mode that determines whether
|
// Send Receive Mode (SRM) or Local Echo Mode is a mode that determines whether
|
||||||
@ -274,17 +274,17 @@ const (
|
|||||||
//
|
//
|
||||||
// See: https://vt100.net/docs/vt510-rm/SRM.html
|
// See: https://vt100.net/docs/vt510-rm/SRM.html
|
||||||
const (
|
const (
|
||||||
SendReceiveMode = ANSIMode(12)
|
ModeSendReceive = ANSIMode(12)
|
||||||
LocalEchoMode = SendReceiveMode
|
ModeLocalEcho = ModeSendReceive
|
||||||
SRM = SendReceiveMode
|
SRM = ModeSendReceive
|
||||||
|
|
||||||
SetSendReceiveMode = "\x1b[12h"
|
SetModeSendReceive = "\x1b[12h"
|
||||||
ResetSendReceiveMode = "\x1b[12l"
|
ResetModeSendReceive = "\x1b[12l"
|
||||||
RequestSendReceiveMode = "\x1b[12$p"
|
RequestModeSendReceive = "\x1b[12$p"
|
||||||
|
|
||||||
SetLocalEchoMode = "\x1b[12h"
|
SetModeLocalEcho = "\x1b[12h"
|
||||||
ResetLocalEchoMode = "\x1b[12l"
|
ResetModeLocalEcho = "\x1b[12l"
|
||||||
RequestLocalEchoMode = "\x1b[12$p"
|
RequestModeLocalEcho = "\x1b[12$p"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Line Feed/New Line Mode (LNM) is a mode that determines whether the terminal
|
// Line Feed/New Line Mode (LNM) is a mode that determines whether the terminal
|
||||||
@ -299,12 +299,12 @@ const (
|
|||||||
//
|
//
|
||||||
// See: https://vt100.net/docs/vt510-rm/LNM.html
|
// See: https://vt100.net/docs/vt510-rm/LNM.html
|
||||||
const (
|
const (
|
||||||
LineFeedNewLineMode = ANSIMode(20)
|
ModeLineFeedNewLine = ANSIMode(20)
|
||||||
LNM = LineFeedNewLineMode
|
LNM = ModeLineFeedNewLine
|
||||||
|
|
||||||
SetLineFeedNewLineMode = "\x1b[20h"
|
SetModeLineFeedNewLine = "\x1b[20h"
|
||||||
ResetLineFeedNewLineMode = "\x1b[20l"
|
ResetModeLineFeedNewLine = "\x1b[20l"
|
||||||
RequestLineFeedNewLineMode = "\x1b[20$p"
|
RequestModeLineFeedNewLine = "\x1b[20$p"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Cursor Keys Mode (DECCKM) is a mode that determines whether the cursor keys
|
// Cursor Keys Mode (DECCKM) is a mode that determines whether the cursor keys
|
||||||
@ -312,18 +312,12 @@ const (
|
|||||||
//
|
//
|
||||||
// See: https://vt100.net/docs/vt510-rm/DECCKM.html
|
// See: https://vt100.net/docs/vt510-rm/DECCKM.html
|
||||||
const (
|
const (
|
||||||
CursorKeysMode = DECMode(1)
|
ModeCursorKeys = DECMode(1)
|
||||||
DECCKM = CursorKeysMode
|
DECCKM = ModeCursorKeys
|
||||||
|
|
||||||
SetCursorKeysMode = "\x1b[?1h"
|
SetModeCursorKeys = "\x1b[?1h"
|
||||||
ResetCursorKeysMode = "\x1b[?1l"
|
ResetModeCursorKeys = "\x1b[?1l"
|
||||||
RequestCursorKeysMode = "\x1b[?1$p"
|
RequestModeCursorKeys = "\x1b[?1$p"
|
||||||
)
|
|
||||||
|
|
||||||
// Deprecated: use [SetCursorKeysMode] and [ResetCursorKeysMode] instead.
|
|
||||||
const (
|
|
||||||
EnableCursorKeys = "\x1b[?1h" //nolint:revive // grouped constants
|
|
||||||
DisableCursorKeys = "\x1b[?1l"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Origin Mode (DECOM) is a mode that determines whether the cursor moves to the
|
// Origin Mode (DECOM) is a mode that determines whether the cursor moves to the
|
||||||
@ -331,12 +325,12 @@ const (
|
|||||||
//
|
//
|
||||||
// See: https://vt100.net/docs/vt510-rm/DECOM.html
|
// See: https://vt100.net/docs/vt510-rm/DECOM.html
|
||||||
const (
|
const (
|
||||||
OriginMode = DECMode(6)
|
ModeOrigin = DECMode(6)
|
||||||
DECOM = OriginMode
|
DECOM = ModeOrigin
|
||||||
|
|
||||||
SetOriginMode = "\x1b[?6h"
|
SetModeOrigin = "\x1b[?6h"
|
||||||
ResetOriginMode = "\x1b[?6l"
|
ResetModeOrigin = "\x1b[?6l"
|
||||||
RequestOriginMode = "\x1b[?6$p"
|
RequestModeOrigin = "\x1b[?6$p"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Auto Wrap Mode (DECAWM) is a mode that determines whether the cursor wraps
|
// Auto Wrap Mode (DECAWM) is a mode that determines whether the cursor wraps
|
||||||
@ -344,12 +338,12 @@ const (
|
|||||||
//
|
//
|
||||||
// See: https://vt100.net/docs/vt510-rm/DECAWM.html
|
// See: https://vt100.net/docs/vt510-rm/DECAWM.html
|
||||||
const (
|
const (
|
||||||
AutoWrapMode = DECMode(7)
|
ModeAutoWrap = DECMode(7)
|
||||||
DECAWM = AutoWrapMode
|
DECAWM = ModeAutoWrap
|
||||||
|
|
||||||
SetAutoWrapMode = "\x1b[?7h"
|
SetModeAutoWrap = "\x1b[?7h"
|
||||||
ResetAutoWrapMode = "\x1b[?7l"
|
ResetModeAutoWrap = "\x1b[?7l"
|
||||||
RequestAutoWrapMode = "\x1b[?7$p"
|
RequestModeAutoWrap = "\x1b[?7$p"
|
||||||
)
|
)
|
||||||
|
|
||||||
// X10 Mouse Mode is a mode that determines whether the mouse reports on button
|
// X10 Mouse Mode is a mode that determines whether the mouse reports on button
|
||||||
@ -364,39 +358,29 @@ const (
|
|||||||
//
|
//
|
||||||
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
|
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
|
||||||
const (
|
const (
|
||||||
X10MouseMode = DECMode(9)
|
ModeMouseX10 = DECMode(9)
|
||||||
|
|
||||||
SetX10MouseMode = "\x1b[?9h"
|
SetModeMouseX10 = "\x1b[?9h"
|
||||||
ResetX10MouseMode = "\x1b[?9l"
|
ResetModeMouseX10 = "\x1b[?9l"
|
||||||
RequestX10MouseMode = "\x1b[?9$p"
|
RequestModeMouseX10 = "\x1b[?9$p"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Text Cursor Enable Mode (DECTCEM) is a mode that shows/hides the cursor.
|
// Text Cursor Enable Mode (DECTCEM) is a mode that shows/hides the cursor.
|
||||||
//
|
//
|
||||||
// See: https://vt100.net/docs/vt510-rm/DECTCEM.html
|
// See: https://vt100.net/docs/vt510-rm/DECTCEM.html
|
||||||
const (
|
const (
|
||||||
TextCursorEnableMode = DECMode(25)
|
ModeTextCursorEnable = DECMode(25)
|
||||||
DECTCEM = TextCursorEnableMode
|
DECTCEM = ModeTextCursorEnable
|
||||||
|
|
||||||
SetTextCursorEnableMode = "\x1b[?25h"
|
SetModeTextCursorEnable = "\x1b[?25h"
|
||||||
ResetTextCursorEnableMode = "\x1b[?25l"
|
ResetModeTextCursorEnable = "\x1b[?25l"
|
||||||
RequestTextCursorEnableMode = "\x1b[?25$p"
|
RequestModeTextCursorEnable = "\x1b[?25$p"
|
||||||
)
|
)
|
||||||
|
|
||||||
// These are aliases for [SetTextCursorEnableMode] and [ResetTextCursorEnableMode].
|
// These are aliases for [SetModeTextCursorEnable] and [ResetModeTextCursorEnable].
|
||||||
const (
|
const (
|
||||||
ShowCursor = SetTextCursorEnableMode
|
ShowCursor = SetModeTextCursorEnable
|
||||||
HideCursor = ResetTextCursorEnableMode
|
HideCursor = ResetModeTextCursorEnable
|
||||||
)
|
|
||||||
|
|
||||||
// Text Cursor Enable Mode (DECTCEM) is a mode that shows/hides the cursor.
|
|
||||||
//
|
|
||||||
// See: https://vt100.net/docs/vt510-rm/DECTCEM.html
|
|
||||||
//
|
|
||||||
// Deprecated: use [SetTextCursorEnableMode] and [ResetTextCursorEnableMode] instead.
|
|
||||||
const (
|
|
||||||
CursorEnableMode = DECMode(25)
|
|
||||||
RequestCursorVisibility = "\x1b[?25$p"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Numeric Keypad Mode (DECNKM) is a mode that determines whether the keypad
|
// Numeric Keypad Mode (DECNKM) is a mode that determines whether the keypad
|
||||||
@ -406,12 +390,12 @@ const (
|
|||||||
//
|
//
|
||||||
// See: https://vt100.net/docs/vt510-rm/DECNKM.html
|
// See: https://vt100.net/docs/vt510-rm/DECNKM.html
|
||||||
const (
|
const (
|
||||||
NumericKeypadMode = DECMode(66)
|
ModeNumericKeypad = DECMode(66)
|
||||||
DECNKM = NumericKeypadMode
|
DECNKM = ModeNumericKeypad
|
||||||
|
|
||||||
SetNumericKeypadMode = "\x1b[?66h"
|
SetModeNumericKeypad = "\x1b[?66h"
|
||||||
ResetNumericKeypadMode = "\x1b[?66l"
|
ResetModeNumericKeypad = "\x1b[?66l"
|
||||||
RequestNumericKeypadMode = "\x1b[?66$p"
|
RequestModeNumericKeypad = "\x1b[?66$p"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Backarrow Key Mode (DECBKM) is a mode that determines whether the backspace
|
// Backarrow Key Mode (DECBKM) is a mode that determines whether the backspace
|
||||||
@ -419,12 +403,12 @@ const (
|
|||||||
//
|
//
|
||||||
// See: https://vt100.net/docs/vt510-rm/DECBKM.html
|
// See: https://vt100.net/docs/vt510-rm/DECBKM.html
|
||||||
const (
|
const (
|
||||||
BackarrowKeyMode = DECMode(67)
|
ModeBackarrowKey = DECMode(67)
|
||||||
DECBKM = BackarrowKeyMode
|
DECBKM = ModeBackarrowKey
|
||||||
|
|
||||||
SetBackarrowKeyMode = "\x1b[?67h"
|
SetModeBackarrowKey = "\x1b[?67h"
|
||||||
ResetBackarrowKeyMode = "\x1b[?67l"
|
ResetModeBackarrowKey = "\x1b[?67l"
|
||||||
RequestBackarrowKeyMode = "\x1b[?67$p"
|
RequestModeBackarrowKey = "\x1b[?67$p"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Left Right Margin Mode (DECLRMM) is a mode that determines whether the left
|
// Left Right Margin Mode (DECLRMM) is a mode that determines whether the left
|
||||||
@ -432,47 +416,33 @@ const (
|
|||||||
//
|
//
|
||||||
// See: https://vt100.net/docs/vt510-rm/DECLRMM.html
|
// See: https://vt100.net/docs/vt510-rm/DECLRMM.html
|
||||||
const (
|
const (
|
||||||
LeftRightMarginMode = DECMode(69)
|
ModeLeftRightMargin = DECMode(69)
|
||||||
DECLRMM = LeftRightMarginMode
|
DECLRMM = ModeLeftRightMargin
|
||||||
|
|
||||||
SetLeftRightMarginMode = "\x1b[?69h"
|
SetModeLeftRightMargin = "\x1b[?69h"
|
||||||
ResetLeftRightMarginMode = "\x1b[?69l"
|
ResetModeLeftRightMargin = "\x1b[?69l"
|
||||||
RequestLeftRightMarginMode = "\x1b[?69$p"
|
RequestModeLeftRightMargin = "\x1b[?69$p"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Normal Mouse Mode is a mode that determines whether the mouse reports on
|
// Normal Mouse Mode is a mode that determines whether the mouse reports on
|
||||||
// button presses and releases. It will also report modifier keys, wheel
|
// button presses and releases. It will also report modifier keys, wheel
|
||||||
// events, and extra buttons.
|
// events, and extra buttons.
|
||||||
//
|
//
|
||||||
// It uses the same encoding as [X10MouseMode] with a few differences:
|
// It uses the same encoding as [ModeMouseX10] with a few differences:
|
||||||
//
|
//
|
||||||
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
|
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
|
||||||
const (
|
const (
|
||||||
NormalMouseMode = DECMode(1000)
|
ModeMouseNormal = DECMode(1000)
|
||||||
|
|
||||||
SetNormalMouseMode = "\x1b[?1000h"
|
SetModeMouseNormal = "\x1b[?1000h"
|
||||||
ResetNormalMouseMode = "\x1b[?1000l"
|
ResetModeMouseNormal = "\x1b[?1000l"
|
||||||
RequestNormalMouseMode = "\x1b[?1000$p"
|
RequestModeMouseNormal = "\x1b[?1000$p"
|
||||||
)
|
|
||||||
|
|
||||||
// VT Mouse Tracking is a mode that determines whether the mouse reports on
|
|
||||||
// button press and release.
|
|
||||||
//
|
|
||||||
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
|
|
||||||
//
|
|
||||||
// Deprecated: use [NormalMouseMode] instead.
|
|
||||||
const (
|
|
||||||
MouseMode = DECMode(1000)
|
|
||||||
|
|
||||||
EnableMouse = "\x1b[?1000h"
|
|
||||||
DisableMouse = "\x1b[?1000l"
|
|
||||||
RequestMouse = "\x1b[?1000$p"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Highlight Mouse Tracking is a mode that determines whether the mouse reports
|
// Highlight Mouse Tracking is a mode that determines whether the mouse reports
|
||||||
// on button presses, releases, and highlighted cells.
|
// on button presses, releases, and highlighted cells.
|
||||||
//
|
//
|
||||||
// It uses the same encoding as [NormalMouseMode] with a few differences:
|
// It uses the same encoding as [ModeMouseNormal] with a few differences:
|
||||||
//
|
//
|
||||||
// On highlight events, the terminal responds with the following encoding:
|
// On highlight events, the terminal responds with the following encoding:
|
||||||
//
|
//
|
||||||
@ -481,11 +451,11 @@ const (
|
|||||||
//
|
//
|
||||||
// Where the parameters are startx, starty, endx, endy, mousex, and mousey.
|
// Where the parameters are startx, starty, endx, endy, mousex, and mousey.
|
||||||
const (
|
const (
|
||||||
HighlightMouseMode = DECMode(1001)
|
ModeMouseHighlight = DECMode(1001)
|
||||||
|
|
||||||
SetHighlightMouseMode = "\x1b[?1001h"
|
SetModeMouseHighlight = "\x1b[?1001h"
|
||||||
ResetHighlightMouseMode = "\x1b[?1001l"
|
ResetModeMouseHighlight = "\x1b[?1001l"
|
||||||
RequestHighlightMouseMode = "\x1b[?1001$p"
|
RequestModeMouseHighlight = "\x1b[?1001$p"
|
||||||
)
|
)
|
||||||
|
|
||||||
// VT Hilite Mouse Tracking is a mode that determines whether the mouse reports on
|
// VT Hilite Mouse Tracking is a mode that determines whether the mouse reports on
|
||||||
@ -493,65 +463,29 @@ const (
|
|||||||
//
|
//
|
||||||
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
|
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
|
||||||
//
|
//
|
||||||
// Deprecated: use [HighlightMouseMode] instead.
|
|
||||||
const (
|
|
||||||
MouseHiliteMode = DECMode(1001)
|
|
||||||
|
|
||||||
EnableMouseHilite = "\x1b[?1001h"
|
// Button Event Mouse Tracking is essentially the same as [ModeMouseNormal],
|
||||||
DisableMouseHilite = "\x1b[?1001l"
|
|
||||||
RequestMouseHilite = "\x1b[?1001$p"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Button Event Mouse Tracking is essentially the same as [NormalMouseMode],
|
|
||||||
// but it also reports button-motion events when a button is pressed.
|
// but it also reports button-motion events when a button is pressed.
|
||||||
//
|
//
|
||||||
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
|
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
|
||||||
const (
|
const (
|
||||||
ButtonEventMouseMode = DECMode(1002)
|
ModeMouseButtonEvent = DECMode(1002)
|
||||||
|
|
||||||
SetButtonEventMouseMode = "\x1b[?1002h"
|
SetModeMouseButtonEvent = "\x1b[?1002h"
|
||||||
ResetButtonEventMouseMode = "\x1b[?1002l"
|
ResetModeMouseButtonEvent = "\x1b[?1002l"
|
||||||
RequestButtonEventMouseMode = "\x1b[?1002$p"
|
RequestModeMouseButtonEvent = "\x1b[?1002$p"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Cell Motion Mouse Tracking is a mode that determines whether the mouse
|
// Any Event Mouse Tracking is the same as [ModeMouseButtonEvent], except that
|
||||||
// reports on button press, release, and motion events.
|
|
||||||
//
|
|
||||||
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
|
|
||||||
//
|
|
||||||
// Deprecated: use [ButtonEventMouseMode] instead.
|
|
||||||
const (
|
|
||||||
MouseCellMotionMode = DECMode(1002)
|
|
||||||
|
|
||||||
EnableMouseCellMotion = "\x1b[?1002h"
|
|
||||||
DisableMouseCellMotion = "\x1b[?1002l"
|
|
||||||
RequestMouseCellMotion = "\x1b[?1002$p"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Any Event Mouse Tracking is the same as [ButtonEventMouseMode], except that
|
|
||||||
// all motion events are reported even if no mouse buttons are pressed.
|
// all motion events are reported even if no mouse buttons are pressed.
|
||||||
//
|
//
|
||||||
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
|
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
|
||||||
const (
|
const (
|
||||||
AnyEventMouseMode = DECMode(1003)
|
ModeMouseAnyEvent = DECMode(1003)
|
||||||
|
|
||||||
SetAnyEventMouseMode = "\x1b[?1003h"
|
SetModeMouseAnyEvent = "\x1b[?1003h"
|
||||||
ResetAnyEventMouseMode = "\x1b[?1003l"
|
ResetModeMouseAnyEvent = "\x1b[?1003l"
|
||||||
RequestAnyEventMouseMode = "\x1b[?1003$p"
|
RequestModeMouseAnyEvent = "\x1b[?1003$p"
|
||||||
)
|
|
||||||
|
|
||||||
// All Mouse Tracking is a mode that determines whether the mouse reports on
|
|
||||||
// button press, release, motion, and highlight events.
|
|
||||||
//
|
|
||||||
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
|
|
||||||
//
|
|
||||||
// Deprecated: use [AnyEventMouseMode] instead.
|
|
||||||
const (
|
|
||||||
MouseAllMotionMode = DECMode(1003)
|
|
||||||
|
|
||||||
EnableMouseAllMotion = "\x1b[?1003h"
|
|
||||||
DisableMouseAllMotion = "\x1b[?1003l"
|
|
||||||
RequestMouseAllMotion = "\x1b[?1003$p"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Focus Event Mode is a mode that determines whether the terminal reports focus
|
// Focus Event Mode is a mode that determines whether the terminal reports focus
|
||||||
@ -564,22 +498,11 @@ const (
|
|||||||
//
|
//
|
||||||
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Focus-Tracking
|
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Focus-Tracking
|
||||||
const (
|
const (
|
||||||
FocusEventMode = DECMode(1004)
|
ModeFocusEvent = DECMode(1004)
|
||||||
|
|
||||||
SetFocusEventMode = "\x1b[?1004h"
|
SetModeFocusEvent = "\x1b[?1004h"
|
||||||
ResetFocusEventMode = "\x1b[?1004l"
|
ResetModeFocusEvent = "\x1b[?1004l"
|
||||||
RequestFocusEventMode = "\x1b[?1004$p"
|
RequestModeFocusEvent = "\x1b[?1004$p"
|
||||||
)
|
|
||||||
|
|
||||||
// Deprecated: use [SetFocusEventMode], [ResetFocusEventMode], and
|
|
||||||
// [RequestFocusEventMode] instead.
|
|
||||||
// Focus reporting mode constants.
|
|
||||||
const (
|
|
||||||
ReportFocusMode = DECMode(1004) //nolint:revive // grouped constants
|
|
||||||
|
|
||||||
EnableReportFocus = "\x1b[?1004h"
|
|
||||||
DisableReportFocus = "\x1b[?1004l"
|
|
||||||
RequestReportFocus = "\x1b[?1004$p"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// SGR Extended Mouse Mode is a mode that changes the mouse tracking encoding
|
// SGR Extended Mouse Mode is a mode that changes the mouse tracking encoding
|
||||||
@ -589,24 +512,15 @@ const (
|
|||||||
//
|
//
|
||||||
// CSI < Cb ; Cx ; Cy M
|
// CSI < Cb ; Cx ; Cy M
|
||||||
//
|
//
|
||||||
// Where Cb is the same as [NormalMouseMode], and Cx and Cy are the x and y.
|
// Where Cb is the same as [ModeMouseNormal], and Cx and Cy are the x and y.
|
||||||
//
|
//
|
||||||
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
|
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
|
||||||
const (
|
const (
|
||||||
SgrExtMouseMode = DECMode(1006)
|
ModeMouseExtSgr = DECMode(1006)
|
||||||
|
|
||||||
SetSgrExtMouseMode = "\x1b[?1006h"
|
SetModeMouseExtSgr = "\x1b[?1006h"
|
||||||
ResetSgrExtMouseMode = "\x1b[?1006l"
|
ResetModeMouseExtSgr = "\x1b[?1006l"
|
||||||
RequestSgrExtMouseMode = "\x1b[?1006$p"
|
RequestModeMouseExtSgr = "\x1b[?1006$p"
|
||||||
)
|
|
||||||
|
|
||||||
// Deprecated: use [SgrExtMouseMode] [SetSgrExtMouseMode],
|
|
||||||
// [ResetSgrExtMouseMode], and [RequestSgrExtMouseMode] instead.
|
|
||||||
const (
|
|
||||||
MouseSgrExtMode = DECMode(1006) //nolint:revive // grouped constants
|
|
||||||
EnableMouseSgrExt = "\x1b[?1006h"
|
|
||||||
DisableMouseSgrExt = "\x1b[?1006l"
|
|
||||||
RequestMouseSgrExt = "\x1b[?1006$p"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// UTF-8 Extended Mouse Mode is a mode that changes the mouse tracking encoding
|
// UTF-8 Extended Mouse Mode is a mode that changes the mouse tracking encoding
|
||||||
@ -614,11 +528,11 @@ const (
|
|||||||
//
|
//
|
||||||
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
|
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
|
||||||
const (
|
const (
|
||||||
Utf8ExtMouseMode = DECMode(1005)
|
ModeMouseExtUtf8 = DECMode(1005)
|
||||||
|
|
||||||
SetUtf8ExtMouseMode = "\x1b[?1005h"
|
SetModeMouseExtUtf8 = "\x1b[?1005h"
|
||||||
ResetUtf8ExtMouseMode = "\x1b[?1005l"
|
ResetModeMouseExtUtf8 = "\x1b[?1005l"
|
||||||
RequestUtf8ExtMouseMode = "\x1b[?1005$p"
|
RequestModeMouseExtUtf8 = "\x1b[?1005$p"
|
||||||
)
|
)
|
||||||
|
|
||||||
// URXVT Extended Mouse Mode is a mode that changes the mouse tracking encoding
|
// URXVT Extended Mouse Mode is a mode that changes the mouse tracking encoding
|
||||||
@ -626,25 +540,25 @@ const (
|
|||||||
//
|
//
|
||||||
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
|
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
|
||||||
const (
|
const (
|
||||||
UrxvtExtMouseMode = DECMode(1015)
|
ModeMouseExtUrxvt = DECMode(1015)
|
||||||
|
|
||||||
SetUrxvtExtMouseMode = "\x1b[?1015h"
|
SetModeMouseExtUrxvt = "\x1b[?1015h"
|
||||||
ResetUrxvtExtMouseMode = "\x1b[?1015l"
|
ResetModeMouseExtUrxvt = "\x1b[?1015l"
|
||||||
RequestUrxvtExtMouseMode = "\x1b[?1015$p"
|
RequestModeMouseExtUrxvt = "\x1b[?1015$p"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SGR Pixel Extended Mouse Mode is a mode that changes the mouse tracking
|
// SGR Pixel Extended Mouse Mode is a mode that changes the mouse tracking
|
||||||
// encoding to use SGR parameters with pixel coordinates.
|
// encoding to use SGR parameters with pixel coordinates.
|
||||||
//
|
//
|
||||||
// This is similar to [SgrExtMouseMode], but also reports pixel coordinates.
|
// This is similar to [ModeMouseExtSgr], but also reports pixel coordinates.
|
||||||
//
|
//
|
||||||
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
|
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
|
||||||
const (
|
const (
|
||||||
SgrPixelExtMouseMode = DECMode(1016)
|
ModeMouseExtSgrPixel = DECMode(1016)
|
||||||
|
|
||||||
SetSgrPixelExtMouseMode = "\x1b[?1016h"
|
SetModeMouseExtSgrPixel = "\x1b[?1016h"
|
||||||
ResetSgrPixelExtMouseMode = "\x1b[?1016l"
|
ResetModeMouseExtSgrPixel = "\x1b[?1016l"
|
||||||
RequestSgrPixelExtMouseMode = "\x1b[?1016$p"
|
RequestModeMouseExtSgrPixel = "\x1b[?1016$p"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Alternate Screen Mode is a mode that determines whether the alternate screen
|
// Alternate Screen Mode is a mode that determines whether the alternate screen
|
||||||
@ -653,11 +567,11 @@ const (
|
|||||||
//
|
//
|
||||||
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-The-Alternate-Screen-Buffer
|
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-The-Alternate-Screen-Buffer
|
||||||
const (
|
const (
|
||||||
AltScreenMode = DECMode(1047)
|
ModeAltScreen = DECMode(1047)
|
||||||
|
|
||||||
SetAltScreenMode = "\x1b[?1047h"
|
SetModeAltScreen = "\x1b[?1047h"
|
||||||
ResetAltScreenMode = "\x1b[?1047l"
|
ResetModeAltScreen = "\x1b[?1047l"
|
||||||
RequestAltScreenMode = "\x1b[?1047$p"
|
RequestModeAltScreen = "\x1b[?1047$p"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Save Cursor Mode is a mode that saves the cursor position.
|
// Save Cursor Mode is a mode that saves the cursor position.
|
||||||
@ -665,42 +579,24 @@ const (
|
|||||||
//
|
//
|
||||||
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-The-Alternate-Screen-Buffer
|
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-The-Alternate-Screen-Buffer
|
||||||
const (
|
const (
|
||||||
SaveCursorMode = DECMode(1048)
|
ModeSaveCursor = DECMode(1048)
|
||||||
|
|
||||||
SetSaveCursorMode = "\x1b[?1048h"
|
SetModeSaveCursor = "\x1b[?1048h"
|
||||||
ResetSaveCursorMode = "\x1b[?1048l"
|
ResetModeSaveCursor = "\x1b[?1048l"
|
||||||
RequestSaveCursorMode = "\x1b[?1048$p"
|
RequestModeSaveCursor = "\x1b[?1048$p"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Alternate Screen Save Cursor Mode is a mode that saves the cursor position as in
|
// Alternate Screen Save Cursor Mode is a mode that saves the cursor position as in
|
||||||
// [SaveCursorMode], switches to the alternate screen buffer as in [AltScreenMode],
|
// [ModeSaveCursor], switches to the alternate screen buffer as in [ModeAltScreen],
|
||||||
// and clears the screen on switch.
|
// and clears the screen on switch.
|
||||||
//
|
//
|
||||||
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-The-Alternate-Screen-Buffer
|
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-The-Alternate-Screen-Buffer
|
||||||
const (
|
const (
|
||||||
AltScreenSaveCursorMode = DECMode(1049)
|
ModeAltScreenSaveCursor = DECMode(1049)
|
||||||
|
|
||||||
SetAltScreenSaveCursorMode = "\x1b[?1049h"
|
SetModeAltScreenSaveCursor = "\x1b[?1049h"
|
||||||
ResetAltScreenSaveCursorMode = "\x1b[?1049l"
|
ResetModeAltScreenSaveCursor = "\x1b[?1049l"
|
||||||
RequestAltScreenSaveCursorMode = "\x1b[?1049$p"
|
RequestModeAltScreenSaveCursor = "\x1b[?1049$p"
|
||||||
)
|
|
||||||
|
|
||||||
// Alternate Screen Buffer is a mode that determines whether the alternate screen
|
|
||||||
// buffer is active.
|
|
||||||
//
|
|
||||||
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-The-Alternate-Screen-Buffer
|
|
||||||
//
|
|
||||||
// Deprecated: use [AltScreenSaveCursorMode] instead.
|
|
||||||
const (
|
|
||||||
AltScreenBufferMode = DECMode(1049)
|
|
||||||
|
|
||||||
SetAltScreenBufferMode = "\x1b[?1049h"
|
|
||||||
ResetAltScreenBufferMode = "\x1b[?1049l"
|
|
||||||
RequestAltScreenBufferMode = "\x1b[?1049$p"
|
|
||||||
|
|
||||||
EnableAltScreenBuffer = "\x1b[?1049h"
|
|
||||||
DisableAltScreenBuffer = "\x1b[?1049l"
|
|
||||||
RequestAltScreenBuffer = "\x1b[?1049$p"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Bracketed Paste Mode is a mode that determines whether pasted text is
|
// Bracketed Paste Mode is a mode that determines whether pasted text is
|
||||||
@ -709,19 +605,11 @@ const (
|
|||||||
// See: https://cirw.in/blog/bracketed-paste
|
// See: https://cirw.in/blog/bracketed-paste
|
||||||
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Bracketed-Paste-Mode
|
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Bracketed-Paste-Mode
|
||||||
const (
|
const (
|
||||||
BracketedPasteMode = DECMode(2004)
|
ModeBracketedPaste = DECMode(2004)
|
||||||
|
|
||||||
SetBracketedPasteMode = "\x1b[?2004h"
|
SetModeBracketedPaste = "\x1b[?2004h"
|
||||||
ResetBracketedPasteMode = "\x1b[?2004l"
|
ResetModeBracketedPaste = "\x1b[?2004l"
|
||||||
RequestBracketedPasteMode = "\x1b[?2004$p"
|
RequestModeBracketedPaste = "\x1b[?2004$p"
|
||||||
)
|
|
||||||
|
|
||||||
// Deprecated: use [SetBracketedPasteMode], [ResetBracketedPasteMode], and
|
|
||||||
// [RequestBracketedPasteMode] instead.
|
|
||||||
const (
|
|
||||||
EnableBracketedPaste = "\x1b[?2004h" //nolint:revive // grouped constants
|
|
||||||
DisableBracketedPaste = "\x1b[?2004l"
|
|
||||||
RequestBracketedPaste = "\x1b[?2004$p"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Synchronized Output Mode is a mode that determines whether output is
|
// Synchronized Output Mode is a mode that determines whether output is
|
||||||
@ -729,23 +617,11 @@ const (
|
|||||||
//
|
//
|
||||||
// See: https://gist.github.com/christianparpart/d8a62cc1ab659194337d73e399004036
|
// See: https://gist.github.com/christianparpart/d8a62cc1ab659194337d73e399004036
|
||||||
const (
|
const (
|
||||||
SynchronizedOutputMode = DECMode(2026)
|
ModeSynchronizedOutput = DECMode(2026)
|
||||||
|
|
||||||
SetSynchronizedOutputMode = "\x1b[?2026h"
|
SetModeSynchronizedOutput = "\x1b[?2026h"
|
||||||
ResetSynchronizedOutputMode = "\x1b[?2026l"
|
ResetModeSynchronizedOutput = "\x1b[?2026l"
|
||||||
RequestSynchronizedOutputMode = "\x1b[?2026$p"
|
RequestModeSynchronizedOutput = "\x1b[?2026$p"
|
||||||
)
|
|
||||||
|
|
||||||
// Synchronized Output Mode. See [SynchronizedOutputMode].
|
|
||||||
//
|
|
||||||
// Deprecated: use [SynchronizedOutputMode], [SetSynchronizedOutputMode], and
|
|
||||||
// [ResetSynchronizedOutputMode], and [RequestSynchronizedOutputMode] instead.
|
|
||||||
const (
|
|
||||||
SyncdOutputMode = DECMode(2026)
|
|
||||||
|
|
||||||
EnableSyncdOutput = "\x1b[?2026h"
|
|
||||||
DisableSyncdOutput = "\x1b[?2026l"
|
|
||||||
RequestSyncdOutput = "\x1b[?2026$p"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Unicode Core Mode is a mode that determines whether the terminal should use
|
// Unicode Core Mode is a mode that determines whether the terminal should use
|
||||||
@ -754,41 +630,16 @@ const (
|
|||||||
//
|
//
|
||||||
// See: https://github.com/contour-terminal/terminal-unicode-core
|
// See: https://github.com/contour-terminal/terminal-unicode-core
|
||||||
const (
|
const (
|
||||||
UnicodeCoreMode = DECMode(2027)
|
ModeUnicodeCore = DECMode(2027)
|
||||||
|
|
||||||
SetUnicodeCoreMode = "\x1b[?2027h"
|
SetModeUnicodeCore = "\x1b[?2027h"
|
||||||
ResetUnicodeCoreMode = "\x1b[?2027l"
|
ResetModeUnicodeCore = "\x1b[?2027l"
|
||||||
RequestUnicodeCoreMode = "\x1b[?2027$p"
|
RequestModeUnicodeCore = "\x1b[?2027$p"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Grapheme Clustering Mode is a mode that determines whether the terminal
|
|
||||||
// should look for grapheme clusters instead of single runes in the rendered
|
|
||||||
// text. This makes the terminal properly render combining characters such as
|
|
||||||
// emojis.
|
|
||||||
//
|
//
|
||||||
// See: https://github.com/contour-terminal/terminal-unicode-core
|
|
||||||
//
|
|
||||||
// Deprecated: use [GraphemeClusteringMode], [SetUnicodeCoreMode],
|
|
||||||
// [ResetUnicodeCoreMode], and [RequestUnicodeCoreMode] instead.
|
|
||||||
const (
|
|
||||||
GraphemeClusteringMode = DECMode(2027)
|
|
||||||
|
|
||||||
SetGraphemeClusteringMode = "\x1b[?2027h"
|
// ModeLightDark is a mode that enables reporting the operating system's color
|
||||||
ResetGraphemeClusteringMode = "\x1b[?2027l"
|
|
||||||
RequestGraphemeClusteringMode = "\x1b[?2027$p"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Grapheme Clustering Mode. See [GraphemeClusteringMode].
|
|
||||||
//
|
|
||||||
// Deprecated: use [SetUnicodeCoreMode], [ResetUnicodeCoreMode], and
|
|
||||||
// [RequestUnicodeCoreMode] instead.
|
|
||||||
const (
|
|
||||||
EnableGraphemeClustering = "\x1b[?2027h"
|
|
||||||
DisableGraphemeClustering = "\x1b[?2027l"
|
|
||||||
RequestGraphemeClustering = "\x1b[?2027$p"
|
|
||||||
)
|
|
||||||
|
|
||||||
// LightDarkMode is a mode that enables reporting the operating system's color
|
|
||||||
// scheme (light or dark) preference. It reports the color scheme as a [DSR]
|
// scheme (light or dark) preference. It reports the color scheme as a [DSR]
|
||||||
// and [LightDarkReport] escape sequences encoded as follows:
|
// and [LightDarkReport] escape sequences encoded as follows:
|
||||||
//
|
//
|
||||||
@ -802,14 +653,14 @@ const (
|
|||||||
//
|
//
|
||||||
// See: https://contour-terminal.org/vt-extensions/color-palette-update-notifications/
|
// See: https://contour-terminal.org/vt-extensions/color-palette-update-notifications/
|
||||||
const (
|
const (
|
||||||
LightDarkMode = DECMode(2031)
|
ModeLightDark = DECMode(2031)
|
||||||
|
|
||||||
SetLightDarkMode = "\x1b[?2031h"
|
SetModeLightDark = "\x1b[?2031h"
|
||||||
ResetLightDarkMode = "\x1b[?2031l"
|
ResetModeLightDark = "\x1b[?2031l"
|
||||||
RequestLightDarkMode = "\x1b[?2031$p"
|
RequestModeLightDark = "\x1b[?2031$p"
|
||||||
)
|
)
|
||||||
|
|
||||||
// InBandResizeMode is a mode that reports terminal resize events as escape
|
// ModeInBandResize is a mode that reports terminal resize events as escape
|
||||||
// sequences. This is useful for systems that do not support [SIGWINCH] like
|
// sequences. This is useful for systems that do not support [SIGWINCH] like
|
||||||
// Windows.
|
// Windows.
|
||||||
//
|
//
|
||||||
@ -819,11 +670,11 @@ const (
|
|||||||
//
|
//
|
||||||
// See: https://gist.github.com/rockorager/e695fb2924d36b2bcf1fff4a3704bd83
|
// See: https://gist.github.com/rockorager/e695fb2924d36b2bcf1fff4a3704bd83
|
||||||
const (
|
const (
|
||||||
InBandResizeMode = DECMode(2048)
|
ModeInBandResize = DECMode(2048)
|
||||||
|
|
||||||
SetInBandResizeMode = "\x1b[?2048h"
|
SetModeInBandResize = "\x1b[?2048h"
|
||||||
ResetInBandResizeMode = "\x1b[?2048l"
|
ResetModeInBandResize = "\x1b[?2048l"
|
||||||
RequestInBandResizeMode = "\x1b[?2048$p"
|
RequestModeInBandResize = "\x1b[?2048$p"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Win32Input is a mode that determines whether input is processed by the
|
// Win32Input is a mode that determines whether input is processed by the
|
||||||
@ -831,17 +682,9 @@ const (
|
|||||||
//
|
//
|
||||||
// See: https://github.com/microsoft/terminal/blob/main/doc/specs/%234999%20-%20Improved%20keyboard%20handling%20in%20Conpty.md
|
// See: https://github.com/microsoft/terminal/blob/main/doc/specs/%234999%20-%20Improved%20keyboard%20handling%20in%20Conpty.md
|
||||||
const (
|
const (
|
||||||
Win32InputMode = DECMode(9001)
|
ModeWin32Input = DECMode(9001)
|
||||||
|
|
||||||
SetWin32InputMode = "\x1b[?9001h"
|
SetModeWin32Input = "\x1b[?9001h"
|
||||||
ResetWin32InputMode = "\x1b[?9001l"
|
ResetModeWin32Input = "\x1b[?9001l"
|
||||||
RequestWin32InputMode = "\x1b[?9001$p"
|
RequestModeWin32Input = "\x1b[?9001$p"
|
||||||
)
|
|
||||||
|
|
||||||
// Deprecated: use [SetWin32InputMode], [ResetWin32InputMode], and
|
|
||||||
// [RequestWin32InputMode] instead.
|
|
||||||
const (
|
|
||||||
EnableWin32Input = "\x1b[?9001h" //nolint:revive // grouped constants
|
|
||||||
DisableWin32Input = "\x1b[?9001l"
|
|
||||||
RequestWin32Input = "\x1b[?9001$p"
|
|
||||||
)
|
)
|
||||||
|
|||||||
495
vendor/github.com/charmbracelet/x/ansi/mode_deprecated.go
generated
vendored
Normal file
495
vendor/github.com/charmbracelet/x/ansi/mode_deprecated.go
generated
vendored
Normal file
@ -0,0 +1,495 @@
|
|||||||
|
package ansi
|
||||||
|
|
||||||
|
// Keyboard Action Mode (KAM) controls locking of the keyboard.
|
||||||
|
//
|
||||||
|
// Deprecated: use [ModeKeyboardAction] instead.
|
||||||
|
const (
|
||||||
|
KeyboardActionMode = ANSIMode(2)
|
||||||
|
|
||||||
|
SetKeyboardActionMode = "\x1b[2h"
|
||||||
|
ResetKeyboardActionMode = "\x1b[2l"
|
||||||
|
RequestKeyboardActionMode = "\x1b[2$p"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Insert/Replace Mode (IRM) determines whether characters are inserted or replaced.
|
||||||
|
//
|
||||||
|
// Deprecated: use [ModeInsertReplace] instead.
|
||||||
|
const (
|
||||||
|
InsertReplaceMode = ANSIMode(4)
|
||||||
|
|
||||||
|
SetInsertReplaceMode = "\x1b[4h"
|
||||||
|
ResetInsertReplaceMode = "\x1b[4l"
|
||||||
|
RequestInsertReplaceMode = "\x1b[4$p"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BiDirectional Support Mode (BDSM) determines whether the terminal supports bidirectional text.
|
||||||
|
//
|
||||||
|
// Deprecated: use [ModeBiDirectionalSupport] instead.
|
||||||
|
const (
|
||||||
|
BiDirectionalSupportMode = ANSIMode(8)
|
||||||
|
|
||||||
|
SetBiDirectionalSupportMode = "\x1b[8h"
|
||||||
|
ResetBiDirectionalSupportMode = "\x1b[8l"
|
||||||
|
RequestBiDirectionalSupportMode = "\x1b[8$p"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Send Receive Mode (SRM) or Local Echo Mode determines whether the terminal echoes characters.
|
||||||
|
//
|
||||||
|
// Deprecated: use [ModeSendReceive] instead.
|
||||||
|
const (
|
||||||
|
SendReceiveMode = ANSIMode(12)
|
||||||
|
LocalEchoMode = SendReceiveMode
|
||||||
|
|
||||||
|
SetSendReceiveMode = "\x1b[12h"
|
||||||
|
ResetSendReceiveMode = "\x1b[12l"
|
||||||
|
RequestSendReceiveMode = "\x1b[12$p"
|
||||||
|
|
||||||
|
SetLocalEchoMode = "\x1b[12h"
|
||||||
|
ResetLocalEchoMode = "\x1b[12l"
|
||||||
|
RequestLocalEchoMode = "\x1b[12$p"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Line Feed/New Line Mode (LNM) determines whether the terminal interprets line feed as new line.
|
||||||
|
//
|
||||||
|
// Deprecated: use [ModeLineFeedNewLine] instead.
|
||||||
|
const (
|
||||||
|
LineFeedNewLineMode = ANSIMode(20)
|
||||||
|
|
||||||
|
SetLineFeedNewLineMode = "\x1b[20h"
|
||||||
|
ResetLineFeedNewLineMode = "\x1b[20l"
|
||||||
|
RequestLineFeedNewLineMode = "\x1b[20$p"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Cursor Keys Mode (DECCKM) determines whether cursor keys send ANSI or application sequences.
|
||||||
|
//
|
||||||
|
// Deprecated: use [ModeCursorKeys] instead.
|
||||||
|
const (
|
||||||
|
CursorKeysMode = DECMode(1)
|
||||||
|
|
||||||
|
SetCursorKeysMode = "\x1b[?1h"
|
||||||
|
ResetCursorKeysMode = "\x1b[?1l"
|
||||||
|
RequestCursorKeysMode = "\x1b[?1$p"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Cursor Keys mode.
|
||||||
|
//
|
||||||
|
// Deprecated: use [SetModeCursorKeys] and [ResetModeCursorKeys] instead.
|
||||||
|
const (
|
||||||
|
EnableCursorKeys = "\x1b[?1h"
|
||||||
|
DisableCursorKeys = "\x1b[?1l"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Origin Mode (DECOM) determines whether the cursor moves to home or margin position.
|
||||||
|
//
|
||||||
|
// Deprecated: use [ModeOrigin] instead.
|
||||||
|
const (
|
||||||
|
OriginMode = DECMode(6)
|
||||||
|
|
||||||
|
SetOriginMode = "\x1b[?6h"
|
||||||
|
ResetOriginMode = "\x1b[?6l"
|
||||||
|
RequestOriginMode = "\x1b[?6$p"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Auto Wrap Mode (DECAWM) determines whether the cursor wraps to the next line.
|
||||||
|
//
|
||||||
|
// Deprecated: use [ModeAutoWrap] instead.
|
||||||
|
const (
|
||||||
|
AutoWrapMode = DECMode(7)
|
||||||
|
|
||||||
|
SetAutoWrapMode = "\x1b[?7h"
|
||||||
|
ResetAutoWrapMode = "\x1b[?7l"
|
||||||
|
RequestAutoWrapMode = "\x1b[?7$p"
|
||||||
|
)
|
||||||
|
|
||||||
|
// X10 Mouse Mode determines whether the mouse reports on button presses.
|
||||||
|
//
|
||||||
|
// Deprecated: use [ModeMouseX10] instead.
|
||||||
|
const (
|
||||||
|
X10MouseMode = DECMode(9)
|
||||||
|
|
||||||
|
SetX10MouseMode = "\x1b[?9h"
|
||||||
|
ResetX10MouseMode = "\x1b[?9l"
|
||||||
|
RequestX10MouseMode = "\x1b[?9$p"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Text Cursor Enable Mode (DECTCEM) shows/hides the cursor.
|
||||||
|
//
|
||||||
|
// Deprecated: use [ModeTextCursorEnable] instead.
|
||||||
|
const (
|
||||||
|
TextCursorEnableMode = DECMode(25)
|
||||||
|
|
||||||
|
SetTextCursorEnableMode = "\x1b[?25h"
|
||||||
|
ResetTextCursorEnableMode = "\x1b[?25l"
|
||||||
|
RequestTextCursorEnableMode = "\x1b[?25$p"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Text Cursor Enable mode.
|
||||||
|
//
|
||||||
|
// Deprecated: use [SetModeTextCursorEnable] and [ResetModeTextCursorEnable] instead.
|
||||||
|
const (
|
||||||
|
CursorEnableMode = DECMode(25)
|
||||||
|
RequestCursorVisibility = "\x1b[?25$p"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Numeric Keypad Mode (DECNKM) determines whether the keypad sends application or numeric sequences.
|
||||||
|
//
|
||||||
|
// Deprecated: use [ModeNumericKeypad] instead.
|
||||||
|
const (
|
||||||
|
NumericKeypadMode = DECMode(66)
|
||||||
|
|
||||||
|
SetNumericKeypadMode = "\x1b[?66h"
|
||||||
|
ResetNumericKeypadMode = "\x1b[?66l"
|
||||||
|
RequestNumericKeypadMode = "\x1b[?66$p"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Backarrow Key Mode (DECBKM) determines whether the backspace key sends backspace or delete.
|
||||||
|
//
|
||||||
|
// Deprecated: use [ModeBackarrowKey] instead.
|
||||||
|
const (
|
||||||
|
BackarrowKeyMode = DECMode(67)
|
||||||
|
|
||||||
|
SetBackarrowKeyMode = "\x1b[?67h"
|
||||||
|
ResetBackarrowKeyMode = "\x1b[?67l"
|
||||||
|
RequestBackarrowKeyMode = "\x1b[?67$p"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Left Right Margin Mode (DECLRMM) determines whether left and right margins can be set.
|
||||||
|
//
|
||||||
|
// Deprecated: use [ModeLeftRightMargin] instead.
|
||||||
|
const (
|
||||||
|
LeftRightMarginMode = DECMode(69)
|
||||||
|
|
||||||
|
SetLeftRightMarginMode = "\x1b[?69h"
|
||||||
|
ResetLeftRightMarginMode = "\x1b[?69l"
|
||||||
|
RequestLeftRightMarginMode = "\x1b[?69$p"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Normal Mouse Mode determines whether the mouse reports on button presses and releases.
|
||||||
|
//
|
||||||
|
// Deprecated: use [ModeMouseNormal] instead.
|
||||||
|
const (
|
||||||
|
NormalMouseMode = DECMode(1000)
|
||||||
|
|
||||||
|
SetNormalMouseMode = "\x1b[?1000h"
|
||||||
|
ResetNormalMouseMode = "\x1b[?1000l"
|
||||||
|
RequestNormalMouseMode = "\x1b[?1000$p"
|
||||||
|
)
|
||||||
|
|
||||||
|
// VT Mouse Tracking mode.
|
||||||
|
//
|
||||||
|
// Deprecated: use [ModeMouseNormal] instead.
|
||||||
|
const (
|
||||||
|
MouseMode = DECMode(1000)
|
||||||
|
|
||||||
|
EnableMouse = "\x1b[?1000h"
|
||||||
|
DisableMouse = "\x1b[?1000l"
|
||||||
|
RequestMouse = "\x1b[?1000$p"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Highlight Mouse Tracking determines whether the mouse reports on button presses and highlighted cells.
|
||||||
|
//
|
||||||
|
// Deprecated: use [ModeMouseHighlight] instead.
|
||||||
|
const (
|
||||||
|
HighlightMouseMode = DECMode(1001)
|
||||||
|
|
||||||
|
SetHighlightMouseMode = "\x1b[?1001h"
|
||||||
|
ResetHighlightMouseMode = "\x1b[?1001l"
|
||||||
|
RequestHighlightMouseMode = "\x1b[?1001$p"
|
||||||
|
)
|
||||||
|
|
||||||
|
// VT Hilite Mouse Tracking mode.
|
||||||
|
//
|
||||||
|
// Deprecated: use [ModeMouseHighlight] instead.
|
||||||
|
const (
|
||||||
|
MouseHiliteMode = DECMode(1001)
|
||||||
|
|
||||||
|
EnableMouseHilite = "\x1b[?1001h"
|
||||||
|
DisableMouseHilite = "\x1b[?1001l"
|
||||||
|
RequestMouseHilite = "\x1b[?1001$p"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Button Event Mouse Tracking reports button-motion events when a button is pressed.
|
||||||
|
//
|
||||||
|
// Deprecated: use [ModeMouseButtonEvent] instead.
|
||||||
|
const (
|
||||||
|
ButtonEventMouseMode = DECMode(1002)
|
||||||
|
|
||||||
|
SetButtonEventMouseMode = "\x1b[?1002h"
|
||||||
|
ResetButtonEventMouseMode = "\x1b[?1002l"
|
||||||
|
RequestButtonEventMouseMode = "\x1b[?1002$p"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Cell Motion Mouse Tracking mode.
|
||||||
|
//
|
||||||
|
// Deprecated: use [ModeMouseButtonEvent] instead.
|
||||||
|
const (
|
||||||
|
MouseCellMotionMode = DECMode(1002)
|
||||||
|
|
||||||
|
EnableMouseCellMotion = "\x1b[?1002h"
|
||||||
|
DisableMouseCellMotion = "\x1b[?1002l"
|
||||||
|
RequestMouseCellMotion = "\x1b[?1002$p"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Any Event Mouse Tracking reports all motion events.
|
||||||
|
//
|
||||||
|
// Deprecated: use [ModeMouseAnyEvent] instead.
|
||||||
|
const (
|
||||||
|
AnyEventMouseMode = DECMode(1003)
|
||||||
|
|
||||||
|
SetAnyEventMouseMode = "\x1b[?1003h"
|
||||||
|
ResetAnyEventMouseMode = "\x1b[?1003l"
|
||||||
|
RequestAnyEventMouseMode = "\x1b[?1003$p"
|
||||||
|
)
|
||||||
|
|
||||||
|
// All Mouse Tracking mode.
|
||||||
|
//
|
||||||
|
// Deprecated: use [ModeMouseAnyEvent] instead.
|
||||||
|
const (
|
||||||
|
MouseAllMotionMode = DECMode(1003)
|
||||||
|
|
||||||
|
EnableMouseAllMotion = "\x1b[?1003h"
|
||||||
|
DisableMouseAllMotion = "\x1b[?1003l"
|
||||||
|
RequestMouseAllMotion = "\x1b[?1003$p"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Focus Event Mode determines whether the terminal reports focus and blur events.
|
||||||
|
//
|
||||||
|
// Deprecated: use [ModeFocusEvent] instead.
|
||||||
|
const (
|
||||||
|
FocusEventMode = DECMode(1004)
|
||||||
|
|
||||||
|
SetFocusEventMode = "\x1b[?1004h"
|
||||||
|
ResetFocusEventMode = "\x1b[?1004l"
|
||||||
|
RequestFocusEventMode = "\x1b[?1004$p"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Focus reporting mode.
|
||||||
|
//
|
||||||
|
// Deprecated: use [SetModeFocusEvent], [ResetModeFocusEvent], and
|
||||||
|
// [RequestModeFocusEvent] instead.
|
||||||
|
const (
|
||||||
|
ReportFocusMode = DECMode(1004)
|
||||||
|
|
||||||
|
EnableReportFocus = "\x1b[?1004h"
|
||||||
|
DisableReportFocus = "\x1b[?1004l"
|
||||||
|
RequestReportFocus = "\x1b[?1004$p"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UTF-8 Extended Mouse Mode changes the mouse tracking encoding to use UTF-8 parameters.
|
||||||
|
//
|
||||||
|
// Deprecated: use [ModeMouseExtUtf8] instead.
|
||||||
|
const (
|
||||||
|
Utf8ExtMouseMode = DECMode(1005)
|
||||||
|
|
||||||
|
SetUtf8ExtMouseMode = "\x1b[?1005h"
|
||||||
|
ResetUtf8ExtMouseMode = "\x1b[?1005l"
|
||||||
|
RequestUtf8ExtMouseMode = "\x1b[?1005$p"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SGR Extended Mouse Mode changes the mouse tracking encoding to use SGR parameters.
|
||||||
|
//
|
||||||
|
// Deprecated: use [ModeMouseExtSgr] instead.
|
||||||
|
const (
|
||||||
|
SgrExtMouseMode = DECMode(1006)
|
||||||
|
|
||||||
|
SetSgrExtMouseMode = "\x1b[?1006h"
|
||||||
|
ResetSgrExtMouseMode = "\x1b[?1006l"
|
||||||
|
RequestSgrExtMouseMode = "\x1b[?1006$p"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Mouse SGR Extended mode.
|
||||||
|
//
|
||||||
|
// Deprecated: use [ModeMouseExtSgr], [SetModeMouseExtSgr],
|
||||||
|
// [ResetModeMouseExtSgr], and [RequestModeMouseExtSgr] instead.
|
||||||
|
const (
|
||||||
|
MouseSgrExtMode = DECMode(1006)
|
||||||
|
EnableMouseSgrExt = "\x1b[?1006h"
|
||||||
|
DisableMouseSgrExt = "\x1b[?1006l"
|
||||||
|
RequestMouseSgrExt = "\x1b[?1006$p"
|
||||||
|
)
|
||||||
|
|
||||||
|
// URXVT Extended Mouse Mode changes the mouse tracking encoding to use an alternate encoding.
|
||||||
|
//
|
||||||
|
// Deprecated: use [ModeMouseUrxvtExt] instead.
|
||||||
|
const (
|
||||||
|
UrxvtExtMouseMode = DECMode(1015)
|
||||||
|
|
||||||
|
SetUrxvtExtMouseMode = "\x1b[?1015h"
|
||||||
|
ResetUrxvtExtMouseMode = "\x1b[?1015l"
|
||||||
|
RequestUrxvtExtMouseMode = "\x1b[?1015$p"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SGR Pixel Extended Mouse Mode changes the mouse tracking encoding to use SGR parameters with pixel coordinates.
|
||||||
|
//
|
||||||
|
// Deprecated: use [ModeMouseExtSgrPixel] instead.
|
||||||
|
const (
|
||||||
|
SgrPixelExtMouseMode = DECMode(1016)
|
||||||
|
|
||||||
|
SetSgrPixelExtMouseMode = "\x1b[?1016h"
|
||||||
|
ResetSgrPixelExtMouseMode = "\x1b[?1016l"
|
||||||
|
RequestSgrPixelExtMouseMode = "\x1b[?1016$p"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Alternate Screen Mode determines whether the alternate screen buffer is active.
|
||||||
|
//
|
||||||
|
// Deprecated: use [ModeAltScreen] instead.
|
||||||
|
const (
|
||||||
|
AltScreenMode = DECMode(1047)
|
||||||
|
|
||||||
|
SetAltScreenMode = "\x1b[?1047h"
|
||||||
|
ResetAltScreenMode = "\x1b[?1047l"
|
||||||
|
RequestAltScreenMode = "\x1b[?1047$p"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Save Cursor Mode saves the cursor position.
|
||||||
|
//
|
||||||
|
// Deprecated: use [ModeSaveCursor] instead.
|
||||||
|
const (
|
||||||
|
SaveCursorMode = DECMode(1048)
|
||||||
|
|
||||||
|
SetSaveCursorMode = "\x1b[?1048h"
|
||||||
|
ResetSaveCursorMode = "\x1b[?1048l"
|
||||||
|
RequestSaveCursorMode = "\x1b[?1048$p"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Alternate Screen Save Cursor Mode saves the cursor position and switches to alternate screen.
|
||||||
|
//
|
||||||
|
// Deprecated: use [ModeAltScreenSaveCursor] instead.
|
||||||
|
const (
|
||||||
|
AltScreenSaveCursorMode = DECMode(1049)
|
||||||
|
|
||||||
|
SetAltScreenSaveCursorMode = "\x1b[?1049h"
|
||||||
|
ResetAltScreenSaveCursorMode = "\x1b[?1049l"
|
||||||
|
RequestAltScreenSaveCursorMode = "\x1b[?1049$p"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Alternate Screen Buffer mode.
|
||||||
|
//
|
||||||
|
// Deprecated: use [ModeAltScreenSaveCursor] instead.
|
||||||
|
const (
|
||||||
|
AltScreenBufferMode = DECMode(1049)
|
||||||
|
|
||||||
|
SetAltScreenBufferMode = "\x1b[?1049h"
|
||||||
|
ResetAltScreenBufferMode = "\x1b[?1049l"
|
||||||
|
RequestAltScreenBufferMode = "\x1b[?1049$p"
|
||||||
|
|
||||||
|
EnableAltScreenBuffer = "\x1b[?1049h"
|
||||||
|
DisableAltScreenBuffer = "\x1b[?1049l"
|
||||||
|
RequestAltScreenBuffer = "\x1b[?1049$p"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Bracketed Paste Mode determines whether pasted text is bracketed with escape sequences.
|
||||||
|
//
|
||||||
|
// Deprecated: use [ModeBracketedPaste] instead.
|
||||||
|
const (
|
||||||
|
BracketedPasteMode = DECMode(2004)
|
||||||
|
|
||||||
|
SetBracketedPasteMode = "\x1b[?2004h"
|
||||||
|
ResetBracketedPasteMode = "\x1b[?2004l"
|
||||||
|
RequestBracketedPasteMode = "\x1b[?2004$p"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Deprecated: use [SetModeBracketedPaste], [ResetModeBracketedPaste], and
|
||||||
|
// [RequestModeBracketedPaste] instead.
|
||||||
|
const (
|
||||||
|
EnableBracketedPaste = "\x1b[?2004h" //nolint:revive
|
||||||
|
DisableBracketedPaste = "\x1b[?2004l"
|
||||||
|
RequestBracketedPaste = "\x1b[?2004$p"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Synchronized Output Mode determines whether output is synchronized with the terminal.
|
||||||
|
//
|
||||||
|
// Deprecated: use [ModeSynchronizedOutput] instead.
|
||||||
|
const (
|
||||||
|
SynchronizedOutputMode = DECMode(2026)
|
||||||
|
|
||||||
|
SetSynchronizedOutputMode = "\x1b[?2026h"
|
||||||
|
ResetSynchronizedOutputMode = "\x1b[?2026l"
|
||||||
|
RequestSynchronizedOutputMode = "\x1b[?2026$p"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Synchronized output mode.
|
||||||
|
//
|
||||||
|
// Deprecated: use [ModeSynchronizedOutput], [SetModeSynchronizedOutput],
|
||||||
|
// [ResetModeSynchronizedOutput], and [RequestModeSynchronizedOutput] instead.
|
||||||
|
const (
|
||||||
|
SyncdOutputMode = DECMode(2026)
|
||||||
|
|
||||||
|
EnableSyncdOutput = "\x1b[?2026h"
|
||||||
|
DisableSyncdOutput = "\x1b[?2026l"
|
||||||
|
RequestSyncdOutput = "\x1b[?2026$p"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Unicode Core Mode determines whether the terminal uses Unicode grapheme clustering.
|
||||||
|
//
|
||||||
|
// Deprecated: use [ModeUnicodeCore] instead.
|
||||||
|
const (
|
||||||
|
UnicodeCoreMode = DECMode(2027)
|
||||||
|
|
||||||
|
SetUnicodeCoreMode = "\x1b[?2027h"
|
||||||
|
ResetUnicodeCoreMode = "\x1b[?2027l"
|
||||||
|
RequestUnicodeCoreMode = "\x1b[?2027$p"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Grapheme Clustering Mode determines whether the terminal looks for grapheme clusters.
|
||||||
|
//
|
||||||
|
// Deprecated: use [ModeUnicodeCore], [SetModeUnicodeCore],
|
||||||
|
// [ResetModeUnicodeCore], and [RequestModeUnicodeCore] instead.
|
||||||
|
const (
|
||||||
|
GraphemeClusteringMode = DECMode(2027)
|
||||||
|
|
||||||
|
SetGraphemeClusteringMode = "\x1b[?2027h"
|
||||||
|
ResetGraphemeClusteringMode = "\x1b[?2027l"
|
||||||
|
RequestGraphemeClusteringMode = "\x1b[?2027$p"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Unicode Core mode.
|
||||||
|
//
|
||||||
|
// Deprecated: use [SetModeUnicodeCore], [ResetModeUnicodeCore], and
|
||||||
|
// [RequestModeUnicodeCore] instead.
|
||||||
|
const (
|
||||||
|
EnableGraphemeClustering = "\x1b[?2027h"
|
||||||
|
DisableGraphemeClustering = "\x1b[?2027l"
|
||||||
|
RequestGraphemeClustering = "\x1b[?2027$p"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Light Dark Mode enables reporting the operating system's color scheme preference.
|
||||||
|
//
|
||||||
|
// Deprecated: use [ModeLightDark] instead.
|
||||||
|
const (
|
||||||
|
LightDarkMode = DECMode(2031)
|
||||||
|
|
||||||
|
SetLightDarkMode = "\x1b[?2031h"
|
||||||
|
ResetLightDarkMode = "\x1b[?2031l"
|
||||||
|
RequestLightDarkMode = "\x1b[?2031$p"
|
||||||
|
)
|
||||||
|
|
||||||
|
// In Band Resize Mode reports terminal resize events as escape sequences.
|
||||||
|
//
|
||||||
|
// Deprecated: use [ModeInBandResize] instead.
|
||||||
|
const (
|
||||||
|
InBandResizeMode = DECMode(2048)
|
||||||
|
|
||||||
|
SetInBandResizeMode = "\x1b[?2048h"
|
||||||
|
ResetInBandResizeMode = "\x1b[?2048l"
|
||||||
|
RequestInBandResizeMode = "\x1b[?2048$p"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Win32Input determines whether input is processed by the Win32 console and Conpty.
|
||||||
|
//
|
||||||
|
// Deprecated: use [ModeWin32Input] instead.
|
||||||
|
const (
|
||||||
|
Win32InputMode = DECMode(9001)
|
||||||
|
|
||||||
|
SetWin32InputMode = "\x1b[?9001h"
|
||||||
|
ResetWin32InputMode = "\x1b[?9001l"
|
||||||
|
RequestWin32InputMode = "\x1b[?9001$p"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Deprecated: use [SetModeWin32Input], [ResetModeWin32Input], and
|
||||||
|
// [RequestModeWin32Input] instead.
|
||||||
|
const (
|
||||||
|
EnableWin32Input = "\x1b[?9001h" //nolint:revive
|
||||||
|
DisableWin32Input = "\x1b[?9001l"
|
||||||
|
RequestWin32Input = "\x1b[?9001$p"
|
||||||
|
)
|
||||||
2
vendor/github.com/charmbracelet/x/ansi/mouse.go
generated
vendored
2
vendor/github.com/charmbracelet/x/ansi/mouse.go
generated
vendored
@ -134,7 +134,7 @@ func EncodeMouseButton(b MouseButton, motion, shift, alt, ctrl bool) (m byte) {
|
|||||||
m |= bitMotion
|
m |= bitMotion
|
||||||
}
|
}
|
||||||
|
|
||||||
return //nolint:nakedret
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
// x10Offset is the offset for X10 mouse events.
|
// x10Offset is the offset for X10 mouse events.
|
||||||
|
|||||||
19
vendor/github.com/charmbracelet/x/ansi/notification.go
generated
vendored
19
vendor/github.com/charmbracelet/x/ansi/notification.go
generated
vendored
@ -1,5 +1,10 @@
|
|||||||
package ansi
|
package ansi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
// Notify sends a desktop notification using iTerm's OSC 9.
|
// Notify sends a desktop notification using iTerm's OSC 9.
|
||||||
//
|
//
|
||||||
// OSC 9 ; Mc ST
|
// OSC 9 ; Mc ST
|
||||||
@ -11,3 +16,17 @@ package ansi
|
|||||||
func Notify(s string) string {
|
func Notify(s string) string {
|
||||||
return "\x1b]9;" + s + "\x07"
|
return "\x1b]9;" + s + "\x07"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DesktopNotification sends a desktop notification based on the extensible OSC
|
||||||
|
// 99 escape code.
|
||||||
|
//
|
||||||
|
// OSC 99 ; <metadata> ; <payload> ST
|
||||||
|
// OSC 99 ; <metadata> ; <payload> BEL
|
||||||
|
//
|
||||||
|
// Where <metadata> is a colon-separated list of key-value pairs, and
|
||||||
|
// <payload> is the notification body.
|
||||||
|
//
|
||||||
|
// See: https://sw.kovidgoyal.net/kitty/desktop-notifications/
|
||||||
|
func DesktopNotification(payload string, metadata ...string) string {
|
||||||
|
return fmt.Sprintf("\x1b]99;%s;%s\x07", strings.Join(metadata, ":"), payload)
|
||||||
|
}
|
||||||
|
|||||||
33
vendor/github.com/charmbracelet/x/ansi/parser_decode.go
generated
vendored
33
vendor/github.com/charmbracelet/x/ansi/parser_decode.go
generated
vendored
@ -4,8 +4,9 @@ import (
|
|||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
"github.com/charmbracelet/x/ansi/parser"
|
"github.com/charmbracelet/x/ansi/parser"
|
||||||
|
"github.com/clipperhouse/displaywidth"
|
||||||
|
"github.com/clipperhouse/uax29/v2/graphemes"
|
||||||
"github.com/mattn/go-runewidth"
|
"github.com/mattn/go-runewidth"
|
||||||
"github.com/rivo/uniseg"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// State represents the state of the ANSI escape sequence parser used by
|
// State represents the state of the ANSI escape sequence parser used by
|
||||||
@ -176,10 +177,7 @@ func decodeSequence[T string | []byte](m Method, b T, state State, p *Parser) (s
|
|||||||
}
|
}
|
||||||
|
|
||||||
if utf8.RuneStart(c) {
|
if utf8.RuneStart(c) {
|
||||||
seq, _, width, _ = FirstGraphemeCluster(b, -1)
|
seq, width = FirstGraphemeCluster(b, m)
|
||||||
if m == WcWidth {
|
|
||||||
width = runewidth.StringWidth(string(seq))
|
|
||||||
}
|
|
||||||
i += len(seq)
|
i += len(seq)
|
||||||
return b[:i], width, i, NormalState
|
return b[:i], width, i, NormalState
|
||||||
}
|
}
|
||||||
@ -434,17 +432,22 @@ func HasEscPrefix[T string | []byte](b T) bool {
|
|||||||
return len(b) > 0 && b[0] == ESC
|
return len(b) > 0 && b[0] == ESC
|
||||||
}
|
}
|
||||||
|
|
||||||
// FirstGraphemeCluster returns the first grapheme cluster in the given string or byte slice.
|
// FirstGraphemeCluster returns the first grapheme cluster in the given string
|
||||||
// This is a syntactic sugar function that wraps
|
// or byte slice, and its monospace display width.
|
||||||
// uniseg.FirstGraphemeClusterInString and uniseg.FirstGraphemeCluster.
|
func FirstGraphemeCluster[T string | []byte](b T, m Method) (T, int) {
|
||||||
func FirstGraphemeCluster[T string | []byte](b T, state int) (T, T, int, int) {
|
|
||||||
switch b := any(b).(type) {
|
switch b := any(b).(type) {
|
||||||
case string:
|
case string:
|
||||||
cluster, rest, width, newState := uniseg.FirstGraphemeClusterInString(b, state)
|
cluster := graphemes.FromString(b).First()
|
||||||
return T(cluster), T(rest), width, newState
|
if m == WcWidth {
|
||||||
|
return T(cluster), runewidth.StringWidth(cluster)
|
||||||
|
}
|
||||||
|
return T(cluster), displaywidth.String(cluster)
|
||||||
case []byte:
|
case []byte:
|
||||||
cluster, rest, width, newState := uniseg.FirstGraphemeCluster(b, state)
|
cluster := graphemes.FromBytes(b).First()
|
||||||
return T(cluster), T(rest), width, newState
|
if m == WcWidth {
|
||||||
|
return T(cluster), runewidth.StringWidth(string(cluster))
|
||||||
|
}
|
||||||
|
return T(cluster), displaywidth.Bytes(cluster)
|
||||||
}
|
}
|
||||||
panic("unreachable")
|
panic("unreachable")
|
||||||
}
|
}
|
||||||
@ -490,7 +493,7 @@ func Command(prefix, inter, final byte) (c int) {
|
|||||||
c = int(final)
|
c = int(final)
|
||||||
c |= int(prefix) << parser.PrefixShift
|
c |= int(prefix) << parser.PrefixShift
|
||||||
c |= int(inter) << parser.IntermedShift
|
c |= int(inter) << parser.IntermedShift
|
||||||
return
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
// Param represents a sequence parameter. Sequence parameters with
|
// Param represents a sequence parameter. Sequence parameters with
|
||||||
@ -520,5 +523,5 @@ func Parameter(p int, hasMore bool) (s int) {
|
|||||||
if hasMore {
|
if hasMore {
|
||||||
s |= parser.HasMoreFlag
|
s |= parser.HasMoreFlag
|
||||||
}
|
}
|
||||||
return
|
return s
|
||||||
}
|
}
|
||||||
|
|||||||
110
vendor/github.com/charmbracelet/x/ansi/sgr.go
generated
vendored
110
vendor/github.com/charmbracelet/x/ansi/sgr.go
generated
vendored
@ -21,59 +21,59 @@ func SGR(ps ...Attr) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var attrStrings = map[int]string{
|
var attrStrings = map[int]string{
|
||||||
ResetAttr: resetAttr,
|
AttrReset: attrReset,
|
||||||
BoldAttr: boldAttr,
|
AttrBold: attrBold,
|
||||||
FaintAttr: faintAttr,
|
AttrFaint: attrFaint,
|
||||||
ItalicAttr: italicAttr,
|
AttrItalic: attrItalic,
|
||||||
UnderlineAttr: underlineAttr,
|
AttrUnderline: attrUnderline,
|
||||||
SlowBlinkAttr: slowBlinkAttr,
|
AttrBlink: attrBlink,
|
||||||
RapidBlinkAttr: rapidBlinkAttr,
|
AttrRapidBlink: attrRapidBlink,
|
||||||
ReverseAttr: reverseAttr,
|
AttrReverse: attrReverse,
|
||||||
ConcealAttr: concealAttr,
|
AttrConceal: attrConceal,
|
||||||
StrikethroughAttr: strikethroughAttr,
|
AttrStrikethrough: attrStrikethrough,
|
||||||
NormalIntensityAttr: normalIntensityAttr,
|
AttrNormalIntensity: attrNormalIntensity,
|
||||||
NoItalicAttr: noItalicAttr,
|
AttrNoItalic: attrNoItalic,
|
||||||
NoUnderlineAttr: noUnderlineAttr,
|
AttrNoUnderline: attrNoUnderline,
|
||||||
NoBlinkAttr: noBlinkAttr,
|
AttrNoBlink: attrNoBlink,
|
||||||
NoReverseAttr: noReverseAttr,
|
AttrNoReverse: attrNoReverse,
|
||||||
NoConcealAttr: noConcealAttr,
|
AttrNoConceal: attrNoConceal,
|
||||||
NoStrikethroughAttr: noStrikethroughAttr,
|
AttrNoStrikethrough: attrNoStrikethrough,
|
||||||
BlackForegroundColorAttr: blackForegroundColorAttr,
|
AttrBlackForegroundColor: attrBlackForegroundColor,
|
||||||
RedForegroundColorAttr: redForegroundColorAttr,
|
AttrRedForegroundColor: attrRedForegroundColor,
|
||||||
GreenForegroundColorAttr: greenForegroundColorAttr,
|
AttrGreenForegroundColor: attrGreenForegroundColor,
|
||||||
YellowForegroundColorAttr: yellowForegroundColorAttr,
|
AttrYellowForegroundColor: attrYellowForegroundColor,
|
||||||
BlueForegroundColorAttr: blueForegroundColorAttr,
|
AttrBlueForegroundColor: attrBlueForegroundColor,
|
||||||
MagentaForegroundColorAttr: magentaForegroundColorAttr,
|
AttrMagentaForegroundColor: attrMagentaForegroundColor,
|
||||||
CyanForegroundColorAttr: cyanForegroundColorAttr,
|
AttrCyanForegroundColor: attrCyanForegroundColor,
|
||||||
WhiteForegroundColorAttr: whiteForegroundColorAttr,
|
AttrWhiteForegroundColor: attrWhiteForegroundColor,
|
||||||
ExtendedForegroundColorAttr: extendedForegroundColorAttr,
|
AttrExtendedForegroundColor: attrExtendedForegroundColor,
|
||||||
DefaultForegroundColorAttr: defaultForegroundColorAttr,
|
AttrDefaultForegroundColor: attrDefaultForegroundColor,
|
||||||
BlackBackgroundColorAttr: blackBackgroundColorAttr,
|
AttrBlackBackgroundColor: attrBlackBackgroundColor,
|
||||||
RedBackgroundColorAttr: redBackgroundColorAttr,
|
AttrRedBackgroundColor: attrRedBackgroundColor,
|
||||||
GreenBackgroundColorAttr: greenBackgroundColorAttr,
|
AttrGreenBackgroundColor: attrGreenBackgroundColor,
|
||||||
YellowBackgroundColorAttr: yellowBackgroundColorAttr,
|
AttrYellowBackgroundColor: attrYellowBackgroundColor,
|
||||||
BlueBackgroundColorAttr: blueBackgroundColorAttr,
|
AttrBlueBackgroundColor: attrBlueBackgroundColor,
|
||||||
MagentaBackgroundColorAttr: magentaBackgroundColorAttr,
|
AttrMagentaBackgroundColor: attrMagentaBackgroundColor,
|
||||||
CyanBackgroundColorAttr: cyanBackgroundColorAttr,
|
AttrCyanBackgroundColor: attrCyanBackgroundColor,
|
||||||
WhiteBackgroundColorAttr: whiteBackgroundColorAttr,
|
AttrWhiteBackgroundColor: attrWhiteBackgroundColor,
|
||||||
ExtendedBackgroundColorAttr: extendedBackgroundColorAttr,
|
AttrExtendedBackgroundColor: attrExtendedBackgroundColor,
|
||||||
DefaultBackgroundColorAttr: defaultBackgroundColorAttr,
|
AttrDefaultBackgroundColor: attrDefaultBackgroundColor,
|
||||||
ExtendedUnderlineColorAttr: extendedUnderlineColorAttr,
|
AttrExtendedUnderlineColor: attrExtendedUnderlineColor,
|
||||||
DefaultUnderlineColorAttr: defaultUnderlineColorAttr,
|
AttrDefaultUnderlineColor: attrDefaultUnderlineColor,
|
||||||
BrightBlackForegroundColorAttr: brightBlackForegroundColorAttr,
|
AttrBrightBlackForegroundColor: attrBrightBlackForegroundColor,
|
||||||
BrightRedForegroundColorAttr: brightRedForegroundColorAttr,
|
AttrBrightRedForegroundColor: attrBrightRedForegroundColor,
|
||||||
BrightGreenForegroundColorAttr: brightGreenForegroundColorAttr,
|
AttrBrightGreenForegroundColor: attrBrightGreenForegroundColor,
|
||||||
BrightYellowForegroundColorAttr: brightYellowForegroundColorAttr,
|
AttrBrightYellowForegroundColor: attrBrightYellowForegroundColor,
|
||||||
BrightBlueForegroundColorAttr: brightBlueForegroundColorAttr,
|
AttrBrightBlueForegroundColor: attrBrightBlueForegroundColor,
|
||||||
BrightMagentaForegroundColorAttr: brightMagentaForegroundColorAttr,
|
AttrBrightMagentaForegroundColor: attrBrightMagentaForegroundColor,
|
||||||
BrightCyanForegroundColorAttr: brightCyanForegroundColorAttr,
|
AttrBrightCyanForegroundColor: attrBrightCyanForegroundColor,
|
||||||
BrightWhiteForegroundColorAttr: brightWhiteForegroundColorAttr,
|
AttrBrightWhiteForegroundColor: attrBrightWhiteForegroundColor,
|
||||||
BrightBlackBackgroundColorAttr: brightBlackBackgroundColorAttr,
|
AttrBrightBlackBackgroundColor: attrBrightBlackBackgroundColor,
|
||||||
BrightRedBackgroundColorAttr: brightRedBackgroundColorAttr,
|
AttrBrightRedBackgroundColor: attrBrightRedBackgroundColor,
|
||||||
BrightGreenBackgroundColorAttr: brightGreenBackgroundColorAttr,
|
AttrBrightGreenBackgroundColor: attrBrightGreenBackgroundColor,
|
||||||
BrightYellowBackgroundColorAttr: brightYellowBackgroundColorAttr,
|
AttrBrightYellowBackgroundColor: attrBrightYellowBackgroundColor,
|
||||||
BrightBlueBackgroundColorAttr: brightBlueBackgroundColorAttr,
|
AttrBrightBlueBackgroundColor: attrBrightBlueBackgroundColor,
|
||||||
BrightMagentaBackgroundColorAttr: brightMagentaBackgroundColorAttr,
|
AttrBrightMagentaBackgroundColor: attrBrightMagentaBackgroundColor,
|
||||||
BrightCyanBackgroundColorAttr: brightCyanBackgroundColorAttr,
|
AttrBrightCyanBackgroundColor: attrBrightCyanBackgroundColor,
|
||||||
BrightWhiteBackgroundColorAttr: brightWhiteBackgroundColorAttr,
|
AttrBrightWhiteBackgroundColor: attrBrightWhiteBackgroundColor,
|
||||||
}
|
}
|
||||||
|
|||||||
603
vendor/github.com/charmbracelet/x/ansi/style.go
generated
vendored
603
vendor/github.com/charmbracelet/x/ansi/style.go
generated
vendored
@ -17,7 +17,9 @@ type Attr = int
|
|||||||
// Style represents an ANSI SGR (Select Graphic Rendition) style.
|
// Style represents an ANSI SGR (Select Graphic Rendition) style.
|
||||||
type Style []string
|
type Style []string
|
||||||
|
|
||||||
// NewStyle returns a new style with the given attributes.
|
// NewStyle returns a new style with the given attributes. Attributes are SGR
|
||||||
|
// (Select Graphic Rendition) codes that control text formatting like bold,
|
||||||
|
// italic, colors, etc.
|
||||||
func NewStyle(attrs ...Attr) Style {
|
func NewStyle(attrs ...Attr) Style {
|
||||||
if len(attrs) == 0 {
|
if len(attrs) == 0 {
|
||||||
return Style{}
|
return Style{}
|
||||||
@ -46,7 +48,8 @@ func (s Style) String() string {
|
|||||||
return "\x1b[" + strings.Join(s, ";") + "m"
|
return "\x1b[" + strings.Join(s, ";") + "m"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Styled returns a styled string with the given style applied.
|
// Styled returns a styled string with the given style applied. The style is
|
||||||
|
// applied at the beginning and reset at the end of the string.
|
||||||
func (s Style) Styled(str string) string {
|
func (s Style) Styled(str string) string {
|
||||||
if len(s) == 0 {
|
if len(s) == 0 {
|
||||||
return str
|
return str
|
||||||
@ -54,161 +57,211 @@ func (s Style) Styled(str string) string {
|
|||||||
return s.String() + str + ResetStyle
|
return s.String() + str + ResetStyle
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset appends the reset style attribute to the style.
|
// Reset appends the reset style attribute to the style. This resets all
|
||||||
|
// formatting attributes to their defaults.
|
||||||
func (s Style) Reset() Style {
|
func (s Style) Reset() Style {
|
||||||
return append(s, resetAttr)
|
return append(s, attrReset)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bold appends the bold style attribute to the style.
|
// Bold appends the bold or normal intensity style attribute to the style.
|
||||||
|
// You can use [Style.Normal] to reset to normal intensity.
|
||||||
func (s Style) Bold() Style {
|
func (s Style) Bold() Style {
|
||||||
return append(s, boldAttr)
|
return append(s, attrBold)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Faint appends the faint style attribute to the style.
|
// Faint appends the faint or normal intensity style attribute to the style.
|
||||||
|
// You can use [Style.Normal] to reset to normal intensity.
|
||||||
func (s Style) Faint() Style {
|
func (s Style) Faint() Style {
|
||||||
return append(s, faintAttr)
|
return append(s, attrFaint)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Italic appends the italic style attribute to the style.
|
// Italic appends the italic or no italic style attribute to the style.
|
||||||
func (s Style) Italic() Style {
|
// When v is true, text is rendered in italic. When false, italic is disabled.
|
||||||
return append(s, italicAttr)
|
func (s Style) Italic(v bool) Style {
|
||||||
|
if v {
|
||||||
|
return append(s, attrItalic)
|
||||||
|
}
|
||||||
|
return append(s, attrNoItalic)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Underline appends the underline style attribute to the style.
|
// Underline appends the underline or no underline style attribute to the style.
|
||||||
func (s Style) Underline() Style {
|
// When v is true, text is underlined. When false, underline is disabled.
|
||||||
return append(s, underlineAttr)
|
func (s Style) Underline(v bool) Style {
|
||||||
|
if v {
|
||||||
|
return append(s, attrUnderline)
|
||||||
|
}
|
||||||
|
return append(s, attrNoUnderline)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnderlineStyle appends the underline style attribute to the style.
|
// UnderlineStyle appends the underline style attribute to the style.
|
||||||
|
// Supports various underline styles including single, double, curly, dotted,
|
||||||
|
// and dashed.
|
||||||
func (s Style) UnderlineStyle(u UnderlineStyle) Style {
|
func (s Style) UnderlineStyle(u UnderlineStyle) Style {
|
||||||
switch u {
|
switch u {
|
||||||
case NoUnderlineStyle:
|
case UnderlineStyleNone:
|
||||||
return s.NoUnderline()
|
return s.Underline(false)
|
||||||
case SingleUnderlineStyle:
|
case UnderlineStyleSingle:
|
||||||
return s.Underline()
|
return s.Underline(true)
|
||||||
case DoubleUnderlineStyle:
|
case UnderlineStyleDouble:
|
||||||
return append(s, doubleUnderlineStyle)
|
return append(s, underlineStyleDouble)
|
||||||
case CurlyUnderlineStyle:
|
case UnderlineStyleCurly:
|
||||||
return append(s, curlyUnderlineStyle)
|
return append(s, underlineStyleCurly)
|
||||||
case DottedUnderlineStyle:
|
case UnderlineStyleDotted:
|
||||||
return append(s, dottedUnderlineStyle)
|
return append(s, underlineStyleDotted)
|
||||||
case DashedUnderlineStyle:
|
case UnderlineStyleDashed:
|
||||||
return append(s, dashedUnderlineStyle)
|
return append(s, underlineStyleDashed)
|
||||||
}
|
}
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
// DoubleUnderline appends the double underline style attribute to the style.
|
// Blink appends the slow blink or no blink style attribute to the style.
|
||||||
// This is a convenience method for UnderlineStyle(DoubleUnderlineStyle).
|
// When v is true, text blinks slowly (less than 150 per minute). When false,
|
||||||
func (s Style) DoubleUnderline() Style {
|
// blinking is disabled.
|
||||||
return s.UnderlineStyle(DoubleUnderlineStyle)
|
func (s Style) Blink(v bool) Style {
|
||||||
|
if v {
|
||||||
|
return append(s, attrBlink)
|
||||||
|
}
|
||||||
|
return append(s, attrNoBlink)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CurlyUnderline appends the curly underline style attribute to the style.
|
// RapidBlink appends the rapid blink or no blink style attribute to the style.
|
||||||
// This is a convenience method for UnderlineStyle(CurlyUnderlineStyle).
|
// When v is true, text blinks rapidly (150+ per minute). When false, blinking
|
||||||
func (s Style) CurlyUnderline() Style {
|
// is disabled.
|
||||||
return s.UnderlineStyle(CurlyUnderlineStyle)
|
//
|
||||||
|
// Note that this is not widely supported in terminal emulators.
|
||||||
|
func (s Style) RapidBlink(v bool) Style {
|
||||||
|
if v {
|
||||||
|
return append(s, attrRapidBlink)
|
||||||
|
}
|
||||||
|
return append(s, attrNoBlink)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DottedUnderline appends the dotted underline style attribute to the style.
|
// Reverse appends the reverse or no reverse style attribute to the style.
|
||||||
// This is a convenience method for UnderlineStyle(DottedUnderlineStyle).
|
// When v is true, foreground and background colors are swapped. When false,
|
||||||
func (s Style) DottedUnderline() Style {
|
// reverse video is disabled.
|
||||||
return s.UnderlineStyle(DottedUnderlineStyle)
|
func (s Style) Reverse(v bool) Style {
|
||||||
|
if v {
|
||||||
|
return append(s, attrReverse)
|
||||||
|
}
|
||||||
|
return append(s, attrNoReverse)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DashedUnderline appends the dashed underline style attribute to the style.
|
// Conceal appends the conceal or no conceal style attribute to the style.
|
||||||
// This is a convenience method for UnderlineStyle(DashedUnderlineStyle).
|
// When v is true, text is hidden/concealed. When false, concealment is
|
||||||
func (s Style) DashedUnderline() Style {
|
// disabled.
|
||||||
return s.UnderlineStyle(DashedUnderlineStyle)
|
func (s Style) Conceal(v bool) Style {
|
||||||
|
if v {
|
||||||
|
return append(s, attrConceal)
|
||||||
|
}
|
||||||
|
return append(s, attrNoConceal)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SlowBlink appends the slow blink style attribute to the style.
|
// Strikethrough appends the strikethrough or no strikethrough style attribute
|
||||||
func (s Style) SlowBlink() Style {
|
// to the style. When v is true, text is rendered with a horizontal line through
|
||||||
return append(s, slowBlinkAttr)
|
// it. When false, strikethrough is disabled.
|
||||||
|
func (s Style) Strikethrough(v bool) Style {
|
||||||
|
if v {
|
||||||
|
return append(s, attrStrikethrough)
|
||||||
|
}
|
||||||
|
return append(s, attrNoStrikethrough)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RapidBlink appends the rapid blink style attribute to the style.
|
// Normal appends the normal intensity style attribute to the style. This
|
||||||
func (s Style) RapidBlink() Style {
|
// resets [Style.Bold] and [Style.Faint] attributes.
|
||||||
return append(s, rapidBlinkAttr)
|
func (s Style) Normal() Style {
|
||||||
}
|
return append(s, attrNormalIntensity)
|
||||||
|
|
||||||
// Reverse appends the reverse style attribute to the style.
|
|
||||||
func (s Style) Reverse() Style {
|
|
||||||
return append(s, reverseAttr)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Conceal appends the conceal style attribute to the style.
|
|
||||||
func (s Style) Conceal() Style {
|
|
||||||
return append(s, concealAttr)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Strikethrough appends the strikethrough style attribute to the style.
|
|
||||||
func (s Style) Strikethrough() Style {
|
|
||||||
return append(s, strikethroughAttr)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NormalIntensity appends the normal intensity style attribute to the style.
|
|
||||||
func (s Style) NormalIntensity() Style {
|
|
||||||
return append(s, normalIntensityAttr)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NoItalic appends the no italic style attribute to the style.
|
// NoItalic appends the no italic style attribute to the style.
|
||||||
|
//
|
||||||
|
// Deprecated: use [Style.Italic](false) instead.
|
||||||
func (s Style) NoItalic() Style {
|
func (s Style) NoItalic() Style {
|
||||||
return append(s, noItalicAttr)
|
return append(s, attrNoItalic)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NoUnderline appends the no underline style attribute to the style.
|
// NoUnderline appends the no underline style attribute to the style.
|
||||||
|
//
|
||||||
|
// Deprecated: use [Style.Underline](false) instead.
|
||||||
func (s Style) NoUnderline() Style {
|
func (s Style) NoUnderline() Style {
|
||||||
return append(s, noUnderlineAttr)
|
return append(s, attrNoUnderline)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NoBlink appends the no blink style attribute to the style.
|
// NoBlink appends the no blink style attribute to the style.
|
||||||
|
//
|
||||||
|
// Deprecated: use [Style.Blink](false) or [Style.RapidBlink](false) instead.
|
||||||
func (s Style) NoBlink() Style {
|
func (s Style) NoBlink() Style {
|
||||||
return append(s, noBlinkAttr)
|
return append(s, attrNoBlink)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NoReverse appends the no reverse style attribute to the style.
|
// NoReverse appends the no reverse style attribute to the style.
|
||||||
|
//
|
||||||
|
// Deprecated: use [Style.Reverse](false) instead.
|
||||||
func (s Style) NoReverse() Style {
|
func (s Style) NoReverse() Style {
|
||||||
return append(s, noReverseAttr)
|
return append(s, attrNoReverse)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NoConceal appends the no conceal style attribute to the style.
|
// NoConceal appends the no conceal style attribute to the style.
|
||||||
|
//
|
||||||
|
// Deprecated: use [Style.Conceal](false) instead.
|
||||||
func (s Style) NoConceal() Style {
|
func (s Style) NoConceal() Style {
|
||||||
return append(s, noConcealAttr)
|
return append(s, attrNoConceal)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NoStrikethrough appends the no strikethrough style attribute to the style.
|
// NoStrikethrough appends the no strikethrough style attribute to the style.
|
||||||
|
//
|
||||||
|
// Deprecated: use [Style.Strikethrough](false) instead.
|
||||||
func (s Style) NoStrikethrough() Style {
|
func (s Style) NoStrikethrough() Style {
|
||||||
return append(s, noStrikethroughAttr)
|
return append(s, attrNoStrikethrough)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultForegroundColor appends the default foreground color style attribute to the style.
|
// DefaultForegroundColor appends the default foreground color style attribute to the style.
|
||||||
|
//
|
||||||
|
// Deprecated: use [Style.ForegroundColor](nil) instead.
|
||||||
func (s Style) DefaultForegroundColor() Style {
|
func (s Style) DefaultForegroundColor() Style {
|
||||||
return append(s, defaultForegroundColorAttr)
|
return append(s, attrDefaultForegroundColor)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultBackgroundColor appends the default background color style attribute to the style.
|
// DefaultBackgroundColor appends the default background color style attribute to the style.
|
||||||
|
//
|
||||||
|
// Deprecated: use [Style.BackgroundColor](nil) instead.
|
||||||
func (s Style) DefaultBackgroundColor() Style {
|
func (s Style) DefaultBackgroundColor() Style {
|
||||||
return append(s, defaultBackgroundColorAttr)
|
return append(s, attrDefaultBackgroundColor)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultUnderlineColor appends the default underline color style attribute to the style.
|
// DefaultUnderlineColor appends the default underline color style attribute to the style.
|
||||||
|
//
|
||||||
|
// Deprecated: use [Style.UnderlineColor](nil) instead.
|
||||||
func (s Style) DefaultUnderlineColor() Style {
|
func (s Style) DefaultUnderlineColor() Style {
|
||||||
return append(s, defaultUnderlineColorAttr)
|
return append(s, attrDefaultUnderlineColor)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ForegroundColor appends the foreground color style attribute to the style.
|
// ForegroundColor appends the foreground color style attribute to the style.
|
||||||
|
// If c is nil, the default foreground color is used. Supports [BasicColor],
|
||||||
|
// [IndexedColor] (256-color), and [color.Color] (24-bit RGB).
|
||||||
func (s Style) ForegroundColor(c Color) Style {
|
func (s Style) ForegroundColor(c Color) Style {
|
||||||
|
if c == nil {
|
||||||
|
return append(s, attrDefaultForegroundColor)
|
||||||
|
}
|
||||||
return append(s, foregroundColorString(c))
|
return append(s, foregroundColorString(c))
|
||||||
}
|
}
|
||||||
|
|
||||||
// BackgroundColor appends the background color style attribute to the style.
|
// BackgroundColor appends the background color style attribute to the style.
|
||||||
|
// If c is nil, the default background color is used. Supports [BasicColor],
|
||||||
|
// [IndexedColor] (256-color), and [color.Color] (24-bit RGB).
|
||||||
func (s Style) BackgroundColor(c Color) Style {
|
func (s Style) BackgroundColor(c Color) Style {
|
||||||
|
if c == nil {
|
||||||
|
return append(s, attrDefaultBackgroundColor)
|
||||||
|
}
|
||||||
return append(s, backgroundColorString(c))
|
return append(s, backgroundColorString(c))
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnderlineColor appends the underline color style attribute to the style.
|
// UnderlineColor appends the underline color style attribute to the style.
|
||||||
|
// If c is nil, the default underline color is used. Supports [BasicColor],
|
||||||
|
// [IndexedColor] (256-color), and [color.Color] (24-bit RGB).
|
||||||
func (s Style) UnderlineColor(c Color) Style {
|
func (s Style) UnderlineColor(c Color) Style {
|
||||||
|
if c == nil {
|
||||||
|
return append(s, attrDefaultUnderlineColor)
|
||||||
|
}
|
||||||
return append(s, underlineColorString(c))
|
return append(s, underlineColorString(c))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -217,146 +270,216 @@ func (s Style) UnderlineColor(c Color) Style {
|
|||||||
type UnderlineStyle = byte
|
type UnderlineStyle = byte
|
||||||
|
|
||||||
const (
|
const (
|
||||||
doubleUnderlineStyle = "4:2"
|
underlineStyleDouble = "4:2"
|
||||||
curlyUnderlineStyle = "4:3"
|
underlineStyleCurly = "4:3"
|
||||||
dottedUnderlineStyle = "4:4"
|
underlineStyleDotted = "4:4"
|
||||||
dashedUnderlineStyle = "4:5"
|
underlineStyleDashed = "4:5"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Underline styles constants.
|
||||||
|
const (
|
||||||
|
UnderlineStyleNone UnderlineStyle = iota
|
||||||
|
UnderlineStyleSingle
|
||||||
|
UnderlineStyleDouble
|
||||||
|
UnderlineStyleCurly
|
||||||
|
UnderlineStyleDotted
|
||||||
|
UnderlineStyleDashed
|
||||||
|
)
|
||||||
|
|
||||||
|
// Underline styles constants.
|
||||||
|
//
|
||||||
|
// Deprecated: use [UnderlineStyleNone], [UnderlineStyleSingle], etc. instead.
|
||||||
const (
|
const (
|
||||||
// NoUnderlineStyle is the default underline style.
|
|
||||||
NoUnderlineStyle UnderlineStyle = iota
|
NoUnderlineStyle UnderlineStyle = iota
|
||||||
// SingleUnderlineStyle is a single underline style.
|
|
||||||
SingleUnderlineStyle
|
SingleUnderlineStyle
|
||||||
// DoubleUnderlineStyle is a double underline style.
|
|
||||||
DoubleUnderlineStyle
|
DoubleUnderlineStyle
|
||||||
// CurlyUnderlineStyle is a curly underline style.
|
|
||||||
CurlyUnderlineStyle
|
CurlyUnderlineStyle
|
||||||
// DottedUnderlineStyle is a dotted underline style.
|
|
||||||
DottedUnderlineStyle
|
DottedUnderlineStyle
|
||||||
// DashedUnderlineStyle is a dashed underline style.
|
|
||||||
DashedUnderlineStyle
|
DashedUnderlineStyle
|
||||||
)
|
)
|
||||||
|
|
||||||
// SGR (Select Graphic Rendition) style attributes.
|
// SGR (Select Graphic Rendition) style attributes.
|
||||||
// See: https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters
|
// See: https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters
|
||||||
const (
|
const (
|
||||||
ResetAttr Attr = 0
|
AttrReset Attr = 0
|
||||||
BoldAttr Attr = 1
|
AttrBold Attr = 1
|
||||||
FaintAttr Attr = 2
|
AttrFaint Attr = 2
|
||||||
ItalicAttr Attr = 3
|
AttrItalic Attr = 3
|
||||||
UnderlineAttr Attr = 4
|
AttrUnderline Attr = 4
|
||||||
SlowBlinkAttr Attr = 5
|
AttrBlink Attr = 5
|
||||||
RapidBlinkAttr Attr = 6
|
AttrRapidBlink Attr = 6
|
||||||
ReverseAttr Attr = 7
|
AttrReverse Attr = 7
|
||||||
ConcealAttr Attr = 8
|
AttrConceal Attr = 8
|
||||||
StrikethroughAttr Attr = 9
|
AttrStrikethrough Attr = 9
|
||||||
NormalIntensityAttr Attr = 22
|
AttrNormalIntensity Attr = 22
|
||||||
NoItalicAttr Attr = 23
|
AttrNoItalic Attr = 23
|
||||||
NoUnderlineAttr Attr = 24
|
AttrNoUnderline Attr = 24
|
||||||
NoBlinkAttr Attr = 25
|
AttrNoBlink Attr = 25
|
||||||
NoReverseAttr Attr = 27
|
AttrNoReverse Attr = 27
|
||||||
NoConcealAttr Attr = 28
|
AttrNoConceal Attr = 28
|
||||||
NoStrikethroughAttr Attr = 29
|
AttrNoStrikethrough Attr = 29
|
||||||
BlackForegroundColorAttr Attr = 30
|
AttrBlackForegroundColor Attr = 30
|
||||||
RedForegroundColorAttr Attr = 31
|
AttrRedForegroundColor Attr = 31
|
||||||
GreenForegroundColorAttr Attr = 32
|
AttrGreenForegroundColor Attr = 32
|
||||||
YellowForegroundColorAttr Attr = 33
|
AttrYellowForegroundColor Attr = 33
|
||||||
BlueForegroundColorAttr Attr = 34
|
AttrBlueForegroundColor Attr = 34
|
||||||
MagentaForegroundColorAttr Attr = 35
|
AttrMagentaForegroundColor Attr = 35
|
||||||
CyanForegroundColorAttr Attr = 36
|
AttrCyanForegroundColor Attr = 36
|
||||||
WhiteForegroundColorAttr Attr = 37
|
AttrWhiteForegroundColor Attr = 37
|
||||||
ExtendedForegroundColorAttr Attr = 38
|
AttrExtendedForegroundColor Attr = 38
|
||||||
DefaultForegroundColorAttr Attr = 39
|
AttrDefaultForegroundColor Attr = 39
|
||||||
BlackBackgroundColorAttr Attr = 40
|
AttrBlackBackgroundColor Attr = 40
|
||||||
RedBackgroundColorAttr Attr = 41
|
AttrRedBackgroundColor Attr = 41
|
||||||
GreenBackgroundColorAttr Attr = 42
|
AttrGreenBackgroundColor Attr = 42
|
||||||
YellowBackgroundColorAttr Attr = 43
|
AttrYellowBackgroundColor Attr = 43
|
||||||
BlueBackgroundColorAttr Attr = 44
|
AttrBlueBackgroundColor Attr = 44
|
||||||
MagentaBackgroundColorAttr Attr = 45
|
AttrMagentaBackgroundColor Attr = 45
|
||||||
CyanBackgroundColorAttr Attr = 46
|
AttrCyanBackgroundColor Attr = 46
|
||||||
WhiteBackgroundColorAttr Attr = 47
|
AttrWhiteBackgroundColor Attr = 47
|
||||||
ExtendedBackgroundColorAttr Attr = 48
|
AttrExtendedBackgroundColor Attr = 48
|
||||||
DefaultBackgroundColorAttr Attr = 49
|
AttrDefaultBackgroundColor Attr = 49
|
||||||
ExtendedUnderlineColorAttr Attr = 58
|
AttrExtendedUnderlineColor Attr = 58
|
||||||
DefaultUnderlineColorAttr Attr = 59
|
AttrDefaultUnderlineColor Attr = 59
|
||||||
BrightBlackForegroundColorAttr Attr = 90
|
AttrBrightBlackForegroundColor Attr = 90
|
||||||
BrightRedForegroundColorAttr Attr = 91
|
AttrBrightRedForegroundColor Attr = 91
|
||||||
BrightGreenForegroundColorAttr Attr = 92
|
AttrBrightGreenForegroundColor Attr = 92
|
||||||
BrightYellowForegroundColorAttr Attr = 93
|
AttrBrightYellowForegroundColor Attr = 93
|
||||||
BrightBlueForegroundColorAttr Attr = 94
|
AttrBrightBlueForegroundColor Attr = 94
|
||||||
BrightMagentaForegroundColorAttr Attr = 95
|
AttrBrightMagentaForegroundColor Attr = 95
|
||||||
BrightCyanForegroundColorAttr Attr = 96
|
AttrBrightCyanForegroundColor Attr = 96
|
||||||
BrightWhiteForegroundColorAttr Attr = 97
|
AttrBrightWhiteForegroundColor Attr = 97
|
||||||
BrightBlackBackgroundColorAttr Attr = 100
|
AttrBrightBlackBackgroundColor Attr = 100
|
||||||
BrightRedBackgroundColorAttr Attr = 101
|
AttrBrightRedBackgroundColor Attr = 101
|
||||||
BrightGreenBackgroundColorAttr Attr = 102
|
AttrBrightGreenBackgroundColor Attr = 102
|
||||||
BrightYellowBackgroundColorAttr Attr = 103
|
AttrBrightYellowBackgroundColor Attr = 103
|
||||||
BrightBlueBackgroundColorAttr Attr = 104
|
AttrBrightBlueBackgroundColor Attr = 104
|
||||||
BrightMagentaBackgroundColorAttr Attr = 105
|
AttrBrightMagentaBackgroundColor Attr = 105
|
||||||
BrightCyanBackgroundColorAttr Attr = 106
|
AttrBrightCyanBackgroundColor Attr = 106
|
||||||
BrightWhiteBackgroundColorAttr Attr = 107
|
AttrBrightWhiteBackgroundColor Attr = 107
|
||||||
|
|
||||||
RGBColorIntroducerAttr Attr = 2
|
AttrRGBColorIntroducer Attr = 2
|
||||||
ExtendedColorIntroducerAttr Attr = 5
|
AttrExtendedColorIntroducer Attr = 5
|
||||||
|
)
|
||||||
|
|
||||||
|
// SGR (Select Graphic Rendition) style attributes.
|
||||||
|
//
|
||||||
|
// Deprecated: use Attr* constants instead.
|
||||||
|
const (
|
||||||
|
ResetAttr = AttrReset
|
||||||
|
BoldAttr = AttrBold
|
||||||
|
FaintAttr = AttrFaint
|
||||||
|
ItalicAttr = AttrItalic
|
||||||
|
UnderlineAttr = AttrUnderline
|
||||||
|
SlowBlinkAttr = AttrBlink
|
||||||
|
RapidBlinkAttr = AttrRapidBlink
|
||||||
|
ReverseAttr = AttrReverse
|
||||||
|
ConcealAttr = AttrConceal
|
||||||
|
StrikethroughAttr = AttrStrikethrough
|
||||||
|
NormalIntensityAttr = AttrNormalIntensity
|
||||||
|
NoItalicAttr = AttrNoItalic
|
||||||
|
NoUnderlineAttr = AttrNoUnderline
|
||||||
|
NoBlinkAttr = AttrNoBlink
|
||||||
|
NoReverseAttr = AttrNoReverse
|
||||||
|
NoConcealAttr = AttrNoConceal
|
||||||
|
NoStrikethroughAttr = AttrNoStrikethrough
|
||||||
|
BlackForegroundColorAttr = AttrBlackForegroundColor
|
||||||
|
RedForegroundColorAttr = AttrRedForegroundColor
|
||||||
|
GreenForegroundColorAttr = AttrGreenForegroundColor
|
||||||
|
YellowForegroundColorAttr = AttrYellowForegroundColor
|
||||||
|
BlueForegroundColorAttr = AttrBlueForegroundColor
|
||||||
|
MagentaForegroundColorAttr = AttrMagentaForegroundColor
|
||||||
|
CyanForegroundColorAttr = AttrCyanForegroundColor
|
||||||
|
WhiteForegroundColorAttr = AttrWhiteForegroundColor
|
||||||
|
ExtendedForegroundColorAttr = AttrExtendedForegroundColor
|
||||||
|
DefaultForegroundColorAttr = AttrDefaultForegroundColor
|
||||||
|
BlackBackgroundColorAttr = AttrBlackBackgroundColor
|
||||||
|
RedBackgroundColorAttr = AttrRedBackgroundColor
|
||||||
|
GreenBackgroundColorAttr = AttrGreenBackgroundColor
|
||||||
|
YellowBackgroundColorAttr = AttrYellowBackgroundColor
|
||||||
|
BlueBackgroundColorAttr = AttrBlueBackgroundColor
|
||||||
|
MagentaBackgroundColorAttr = AttrMagentaBackgroundColor
|
||||||
|
CyanBackgroundColorAttr = AttrCyanBackgroundColor
|
||||||
|
WhiteBackgroundColorAttr = AttrWhiteBackgroundColor
|
||||||
|
ExtendedBackgroundColorAttr = AttrExtendedBackgroundColor
|
||||||
|
DefaultBackgroundColorAttr = AttrDefaultBackgroundColor
|
||||||
|
ExtendedUnderlineColorAttr = AttrExtendedUnderlineColor
|
||||||
|
DefaultUnderlineColorAttr = AttrDefaultUnderlineColor
|
||||||
|
BrightBlackForegroundColorAttr = AttrBrightBlackForegroundColor
|
||||||
|
BrightRedForegroundColorAttr = AttrBrightRedForegroundColor
|
||||||
|
BrightGreenForegroundColorAttr = AttrBrightGreenForegroundColor
|
||||||
|
BrightYellowForegroundColorAttr = AttrBrightYellowForegroundColor
|
||||||
|
BrightBlueForegroundColorAttr = AttrBrightBlueForegroundColor
|
||||||
|
BrightMagentaForegroundColorAttr = AttrBrightMagentaForegroundColor
|
||||||
|
BrightCyanForegroundColorAttr = AttrBrightCyanForegroundColor
|
||||||
|
BrightWhiteForegroundColorAttr = AttrBrightWhiteForegroundColor
|
||||||
|
BrightBlackBackgroundColorAttr = AttrBrightBlackBackgroundColor
|
||||||
|
BrightRedBackgroundColorAttr = AttrBrightRedBackgroundColor
|
||||||
|
BrightGreenBackgroundColorAttr = AttrBrightGreenBackgroundColor
|
||||||
|
BrightYellowBackgroundColorAttr = AttrBrightYellowBackgroundColor
|
||||||
|
BrightBlueBackgroundColorAttr = AttrBrightBlueBackgroundColor
|
||||||
|
BrightMagentaBackgroundColorAttr = AttrBrightMagentaBackgroundColor
|
||||||
|
BrightCyanBackgroundColorAttr = AttrBrightCyanBackgroundColor
|
||||||
|
BrightWhiteBackgroundColorAttr = AttrBrightWhiteBackgroundColor
|
||||||
|
RGBColorIntroducerAttr = AttrRGBColorIntroducer
|
||||||
|
ExtendedColorIntroducerAttr = AttrExtendedColorIntroducer
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
resetAttr = "0"
|
attrReset = "0"
|
||||||
boldAttr = "1"
|
attrBold = "1"
|
||||||
faintAttr = "2"
|
attrFaint = "2"
|
||||||
italicAttr = "3"
|
attrItalic = "3"
|
||||||
underlineAttr = "4"
|
attrUnderline = "4"
|
||||||
slowBlinkAttr = "5"
|
attrBlink = "5"
|
||||||
rapidBlinkAttr = "6"
|
attrRapidBlink = "6"
|
||||||
reverseAttr = "7"
|
attrReverse = "7"
|
||||||
concealAttr = "8"
|
attrConceal = "8"
|
||||||
strikethroughAttr = "9"
|
attrStrikethrough = "9"
|
||||||
normalIntensityAttr = "22"
|
attrNormalIntensity = "22"
|
||||||
noItalicAttr = "23"
|
attrNoItalic = "23"
|
||||||
noUnderlineAttr = "24"
|
attrNoUnderline = "24"
|
||||||
noBlinkAttr = "25"
|
attrNoBlink = "25"
|
||||||
noReverseAttr = "27"
|
attrNoReverse = "27"
|
||||||
noConcealAttr = "28"
|
attrNoConceal = "28"
|
||||||
noStrikethroughAttr = "29"
|
attrNoStrikethrough = "29"
|
||||||
blackForegroundColorAttr = "30"
|
attrBlackForegroundColor = "30"
|
||||||
redForegroundColorAttr = "31"
|
attrRedForegroundColor = "31"
|
||||||
greenForegroundColorAttr = "32"
|
attrGreenForegroundColor = "32"
|
||||||
yellowForegroundColorAttr = "33"
|
attrYellowForegroundColor = "33"
|
||||||
blueForegroundColorAttr = "34"
|
attrBlueForegroundColor = "34"
|
||||||
magentaForegroundColorAttr = "35"
|
attrMagentaForegroundColor = "35"
|
||||||
cyanForegroundColorAttr = "36"
|
attrCyanForegroundColor = "36"
|
||||||
whiteForegroundColorAttr = "37"
|
attrWhiteForegroundColor = "37"
|
||||||
extendedForegroundColorAttr = "38"
|
attrExtendedForegroundColor = "38"
|
||||||
defaultForegroundColorAttr = "39"
|
attrDefaultForegroundColor = "39"
|
||||||
blackBackgroundColorAttr = "40"
|
attrBlackBackgroundColor = "40"
|
||||||
redBackgroundColorAttr = "41"
|
attrRedBackgroundColor = "41"
|
||||||
greenBackgroundColorAttr = "42"
|
attrGreenBackgroundColor = "42"
|
||||||
yellowBackgroundColorAttr = "43"
|
attrYellowBackgroundColor = "43"
|
||||||
blueBackgroundColorAttr = "44"
|
attrBlueBackgroundColor = "44"
|
||||||
magentaBackgroundColorAttr = "45"
|
attrMagentaBackgroundColor = "45"
|
||||||
cyanBackgroundColorAttr = "46"
|
attrCyanBackgroundColor = "46"
|
||||||
whiteBackgroundColorAttr = "47"
|
attrWhiteBackgroundColor = "47"
|
||||||
extendedBackgroundColorAttr = "48"
|
attrExtendedBackgroundColor = "48"
|
||||||
defaultBackgroundColorAttr = "49"
|
attrDefaultBackgroundColor = "49"
|
||||||
extendedUnderlineColorAttr = "58"
|
attrExtendedUnderlineColor = "58"
|
||||||
defaultUnderlineColorAttr = "59"
|
attrDefaultUnderlineColor = "59"
|
||||||
brightBlackForegroundColorAttr = "90"
|
attrBrightBlackForegroundColor = "90"
|
||||||
brightRedForegroundColorAttr = "91"
|
attrBrightRedForegroundColor = "91"
|
||||||
brightGreenForegroundColorAttr = "92"
|
attrBrightGreenForegroundColor = "92"
|
||||||
brightYellowForegroundColorAttr = "93"
|
attrBrightYellowForegroundColor = "93"
|
||||||
brightBlueForegroundColorAttr = "94"
|
attrBrightBlueForegroundColor = "94"
|
||||||
brightMagentaForegroundColorAttr = "95"
|
attrBrightMagentaForegroundColor = "95"
|
||||||
brightCyanForegroundColorAttr = "96"
|
attrBrightCyanForegroundColor = "96"
|
||||||
brightWhiteForegroundColorAttr = "97"
|
attrBrightWhiteForegroundColor = "97"
|
||||||
brightBlackBackgroundColorAttr = "100"
|
attrBrightBlackBackgroundColor = "100"
|
||||||
brightRedBackgroundColorAttr = "101"
|
attrBrightRedBackgroundColor = "101"
|
||||||
brightGreenBackgroundColorAttr = "102"
|
attrBrightGreenBackgroundColor = "102"
|
||||||
brightYellowBackgroundColorAttr = "103"
|
attrBrightYellowBackgroundColor = "103"
|
||||||
brightBlueBackgroundColorAttr = "104"
|
attrBrightBlueBackgroundColor = "104"
|
||||||
brightMagentaBackgroundColorAttr = "105"
|
attrBrightMagentaBackgroundColor = "105"
|
||||||
brightCyanBackgroundColorAttr = "106"
|
attrBrightCyanBackgroundColor = "106"
|
||||||
brightWhiteBackgroundColorAttr = "107"
|
attrBrightWhiteBackgroundColor = "107"
|
||||||
)
|
)
|
||||||
|
|
||||||
// foregroundColorString returns the style SGR attribute for the given
|
// foregroundColorString returns the style SGR attribute for the given
|
||||||
@ -369,37 +492,37 @@ func foregroundColorString(c Color) string {
|
|||||||
// "3<n>" or "9<n>" where n is the color number from 0 to 7
|
// "3<n>" or "9<n>" where n is the color number from 0 to 7
|
||||||
switch c {
|
switch c {
|
||||||
case Black:
|
case Black:
|
||||||
return blackForegroundColorAttr
|
return attrBlackForegroundColor
|
||||||
case Red:
|
case Red:
|
||||||
return redForegroundColorAttr
|
return attrRedForegroundColor
|
||||||
case Green:
|
case Green:
|
||||||
return greenForegroundColorAttr
|
return attrGreenForegroundColor
|
||||||
case Yellow:
|
case Yellow:
|
||||||
return yellowForegroundColorAttr
|
return attrYellowForegroundColor
|
||||||
case Blue:
|
case Blue:
|
||||||
return blueForegroundColorAttr
|
return attrBlueForegroundColor
|
||||||
case Magenta:
|
case Magenta:
|
||||||
return magentaForegroundColorAttr
|
return attrMagentaForegroundColor
|
||||||
case Cyan:
|
case Cyan:
|
||||||
return cyanForegroundColorAttr
|
return attrCyanForegroundColor
|
||||||
case White:
|
case White:
|
||||||
return whiteForegroundColorAttr
|
return attrWhiteForegroundColor
|
||||||
case BrightBlack:
|
case BrightBlack:
|
||||||
return brightBlackForegroundColorAttr
|
return attrBrightBlackForegroundColor
|
||||||
case BrightRed:
|
case BrightRed:
|
||||||
return brightRedForegroundColorAttr
|
return attrBrightRedForegroundColor
|
||||||
case BrightGreen:
|
case BrightGreen:
|
||||||
return brightGreenForegroundColorAttr
|
return attrBrightGreenForegroundColor
|
||||||
case BrightYellow:
|
case BrightYellow:
|
||||||
return brightYellowForegroundColorAttr
|
return attrBrightYellowForegroundColor
|
||||||
case BrightBlue:
|
case BrightBlue:
|
||||||
return brightBlueForegroundColorAttr
|
return attrBrightBlueForegroundColor
|
||||||
case BrightMagenta:
|
case BrightMagenta:
|
||||||
return brightMagentaForegroundColorAttr
|
return attrBrightMagentaForegroundColor
|
||||||
case BrightCyan:
|
case BrightCyan:
|
||||||
return brightCyanForegroundColorAttr
|
return attrBrightCyanForegroundColor
|
||||||
case BrightWhite:
|
case BrightWhite:
|
||||||
return brightWhiteForegroundColorAttr
|
return attrBrightWhiteForegroundColor
|
||||||
}
|
}
|
||||||
case ExtendedColor:
|
case ExtendedColor:
|
||||||
// 256-color ANSI foreground
|
// 256-color ANSI foreground
|
||||||
@ -414,7 +537,7 @@ func foregroundColorString(c Color) string {
|
|||||||
strconv.FormatUint(uint64(shift(g)), 10) + ";" +
|
strconv.FormatUint(uint64(shift(g)), 10) + ";" +
|
||||||
strconv.FormatUint(uint64(shift(b)), 10)
|
strconv.FormatUint(uint64(shift(b)), 10)
|
||||||
}
|
}
|
||||||
return defaultForegroundColorAttr
|
return attrDefaultForegroundColor
|
||||||
}
|
}
|
||||||
|
|
||||||
// backgroundColorString returns the style SGR attribute for the given
|
// backgroundColorString returns the style SGR attribute for the given
|
||||||
@ -427,37 +550,37 @@ func backgroundColorString(c Color) string {
|
|||||||
// "4<n>" or "10<n>" where n is the color number from 0 to 7
|
// "4<n>" or "10<n>" where n is the color number from 0 to 7
|
||||||
switch c {
|
switch c {
|
||||||
case Black:
|
case Black:
|
||||||
return blackBackgroundColorAttr
|
return attrBlackBackgroundColor
|
||||||
case Red:
|
case Red:
|
||||||
return redBackgroundColorAttr
|
return attrRedBackgroundColor
|
||||||
case Green:
|
case Green:
|
||||||
return greenBackgroundColorAttr
|
return attrGreenBackgroundColor
|
||||||
case Yellow:
|
case Yellow:
|
||||||
return yellowBackgroundColorAttr
|
return attrYellowBackgroundColor
|
||||||
case Blue:
|
case Blue:
|
||||||
return blueBackgroundColorAttr
|
return attrBlueBackgroundColor
|
||||||
case Magenta:
|
case Magenta:
|
||||||
return magentaBackgroundColorAttr
|
return attrMagentaBackgroundColor
|
||||||
case Cyan:
|
case Cyan:
|
||||||
return cyanBackgroundColorAttr
|
return attrCyanBackgroundColor
|
||||||
case White:
|
case White:
|
||||||
return whiteBackgroundColorAttr
|
return attrWhiteBackgroundColor
|
||||||
case BrightBlack:
|
case BrightBlack:
|
||||||
return brightBlackBackgroundColorAttr
|
return attrBrightBlackBackgroundColor
|
||||||
case BrightRed:
|
case BrightRed:
|
||||||
return brightRedBackgroundColorAttr
|
return attrBrightRedBackgroundColor
|
||||||
case BrightGreen:
|
case BrightGreen:
|
||||||
return brightGreenBackgroundColorAttr
|
return attrBrightGreenBackgroundColor
|
||||||
case BrightYellow:
|
case BrightYellow:
|
||||||
return brightYellowBackgroundColorAttr
|
return attrBrightYellowBackgroundColor
|
||||||
case BrightBlue:
|
case BrightBlue:
|
||||||
return brightBlueBackgroundColorAttr
|
return attrBrightBlueBackgroundColor
|
||||||
case BrightMagenta:
|
case BrightMagenta:
|
||||||
return brightMagentaBackgroundColorAttr
|
return attrBrightMagentaBackgroundColor
|
||||||
case BrightCyan:
|
case BrightCyan:
|
||||||
return brightCyanBackgroundColorAttr
|
return attrBrightCyanBackgroundColor
|
||||||
case BrightWhite:
|
case BrightWhite:
|
||||||
return brightWhiteBackgroundColorAttr
|
return attrBrightWhiteBackgroundColor
|
||||||
}
|
}
|
||||||
case ExtendedColor:
|
case ExtendedColor:
|
||||||
// 256-color ANSI foreground
|
// 256-color ANSI foreground
|
||||||
@ -472,7 +595,7 @@ func backgroundColorString(c Color) string {
|
|||||||
strconv.FormatUint(uint64(shift(g)), 10) + ";" +
|
strconv.FormatUint(uint64(shift(g)), 10) + ";" +
|
||||||
strconv.FormatUint(uint64(shift(b)), 10)
|
strconv.FormatUint(uint64(shift(b)), 10)
|
||||||
}
|
}
|
||||||
return defaultBackgroundColorAttr
|
return attrDefaultBackgroundColor
|
||||||
}
|
}
|
||||||
|
|
||||||
// underlineColorString returns the style SGR attribute for the given underline
|
// underlineColorString returns the style SGR attribute for the given underline
|
||||||
@ -498,7 +621,7 @@ func underlineColorString(c Color) string {
|
|||||||
strconv.FormatUint(uint64(shift(g)), 10) + ";" +
|
strconv.FormatUint(uint64(shift(g)), 10) + ";" +
|
||||||
strconv.FormatUint(uint64(shift(b)), 10)
|
strconv.FormatUint(uint64(shift(b)), 10)
|
||||||
}
|
}
|
||||||
return defaultUnderlineColorAttr
|
return attrDefaultUnderlineColor
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadStyleColor decodes a color from a slice of parameters. It returns the
|
// ReadStyleColor decodes a color from a slice of parameters. It returns the
|
||||||
@ -526,7 +649,7 @@ func underlineColorString(c Color) string {
|
|||||||
// 2. Support ignoring and omitting the color space id (second parameter) with respect to RGB colors
|
// 2. Support ignoring and omitting the color space id (second parameter) with respect to RGB colors
|
||||||
// 3. Support ignoring and omitting the 6th parameter with respect to RGB and CMY colors
|
// 3. Support ignoring and omitting the 6th parameter with respect to RGB and CMY colors
|
||||||
// 4. Support reading RGBA colors
|
// 4. Support reading RGBA colors
|
||||||
func ReadStyleColor(params Params, co *color.Color) (n int) {
|
func ReadStyleColor(params Params, co *color.Color) int {
|
||||||
if len(params) < 2 { // Need at least SGR type and color type
|
if len(params) < 2 { // Need at least SGR type and color type
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
@ -535,7 +658,7 @@ func ReadStyleColor(params Params, co *color.Color) (n int) {
|
|||||||
s := params[0]
|
s := params[0]
|
||||||
p := params[1]
|
p := params[1]
|
||||||
colorType := p.Param(0)
|
colorType := p.Param(0)
|
||||||
n = 2
|
n := 2
|
||||||
|
|
||||||
paramsfn := func() (p1, p2, p3, p4 int) {
|
paramsfn := func() (p1, p2, p3, p4 int) {
|
||||||
// Where should we start reading the color?
|
// Where should we start reading the color?
|
||||||
@ -594,7 +717,7 @@ func ReadStyleColor(params Params, co *color.Color) (n int) {
|
|||||||
B: uint8(b), //nolint:gosec
|
B: uint8(b), //nolint:gosec
|
||||||
A: 0xff,
|
A: 0xff,
|
||||||
}
|
}
|
||||||
return //nolint:nakedret
|
return n
|
||||||
|
|
||||||
case 3: // CMY direct color
|
case 3: // CMY direct color
|
||||||
if len(params) < 5 {
|
if len(params) < 5 {
|
||||||
@ -612,7 +735,7 @@ func ReadStyleColor(params Params, co *color.Color) (n int) {
|
|||||||
Y: uint8(y), //nolint:gosec
|
Y: uint8(y), //nolint:gosec
|
||||||
K: 0,
|
K: 0,
|
||||||
}
|
}
|
||||||
return //nolint:nakedret
|
return n
|
||||||
|
|
||||||
case 4: // CMYK direct color
|
case 4: // CMYK direct color
|
||||||
if len(params) < 6 {
|
if len(params) < 6 {
|
||||||
@ -630,7 +753,7 @@ func ReadStyleColor(params Params, co *color.Color) (n int) {
|
|||||||
Y: uint8(y), //nolint:gosec
|
Y: uint8(y), //nolint:gosec
|
||||||
K: uint8(k), //nolint:gosec
|
K: uint8(k), //nolint:gosec
|
||||||
}
|
}
|
||||||
return //nolint:nakedret
|
return n
|
||||||
|
|
||||||
case 5: // indexed color
|
case 5: // indexed color
|
||||||
if len(params) < 3 {
|
if len(params) < 3 {
|
||||||
@ -665,7 +788,7 @@ func ReadStyleColor(params Params, co *color.Color) (n int) {
|
|||||||
B: uint8(b), //nolint:gosec
|
B: uint8(b), //nolint:gosec
|
||||||
A: uint8(a), //nolint:gosec
|
A: uint8(a), //nolint:gosec
|
||||||
}
|
}
|
||||||
return //nolint:nakedret
|
return n
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return 0
|
return 0
|
||||||
|
|||||||
57
vendor/github.com/charmbracelet/x/ansi/truncate.go
generated
vendored
57
vendor/github.com/charmbracelet/x/ansi/truncate.go
generated
vendored
@ -1,11 +1,11 @@
|
|||||||
package ansi
|
package ansi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"strings"
|
||||||
|
|
||||||
"github.com/charmbracelet/x/ansi/parser"
|
"github.com/charmbracelet/x/ansi/parser"
|
||||||
"github.com/mattn/go-runewidth"
|
"github.com/clipperhouse/displaywidth"
|
||||||
"github.com/rivo/uniseg"
|
"github.com/clipperhouse/uax29/v2/graphemes"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Cut the string, without adding any prefix or tail strings. This function is
|
// Cut the string, without adding any prefix or tail strings. This function is
|
||||||
@ -74,12 +74,11 @@ func truncate(m Method, s string, length int, tail string) string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
var cluster []byte
|
var cluster string
|
||||||
var buf bytes.Buffer
|
var buf strings.Builder
|
||||||
curWidth := 0
|
curWidth := 0
|
||||||
ignoring := false
|
ignoring := false
|
||||||
pstate := parser.GroundState // initial state
|
pstate := parser.GroundState // initial state
|
||||||
b := []byte(s)
|
|
||||||
i := 0
|
i := 0
|
||||||
|
|
||||||
// Here we iterate over the bytes of the string and collect printable
|
// Here we iterate over the bytes of the string and collect printable
|
||||||
@ -88,16 +87,12 @@ func truncate(m Method, s string, length int, tail string) string {
|
|||||||
//
|
//
|
||||||
// Once we reach the given length, we start ignoring characters and only
|
// Once we reach the given length, we start ignoring characters and only
|
||||||
// collect ANSI escape codes until we reach the end of string.
|
// collect ANSI escape codes until we reach the end of string.
|
||||||
for i < len(b) {
|
for i < len(s) {
|
||||||
state, action := parser.Table.Transition(pstate, b[i])
|
state, action := parser.Table.Transition(pstate, s[i])
|
||||||
if state == parser.Utf8State {
|
if state == parser.Utf8State {
|
||||||
// This action happens when we transition to the Utf8State.
|
// This action happens when we transition to the Utf8State.
|
||||||
var width int
|
var width int
|
||||||
cluster, _, width, _ = uniseg.FirstGraphemeCluster(b[i:], -1)
|
cluster, width = FirstGraphemeCluster(s[i:], m)
|
||||||
if m == WcWidth {
|
|
||||||
width = runewidth.StringWidth(string(cluster))
|
|
||||||
}
|
|
||||||
|
|
||||||
// increment the index by the length of the cluster
|
// increment the index by the length of the cluster
|
||||||
i += len(cluster)
|
i += len(cluster)
|
||||||
curWidth += width
|
curWidth += width
|
||||||
@ -118,7 +113,7 @@ func truncate(m Method, s string, length int, tail string) string {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
buf.Write(cluster)
|
buf.WriteString(cluster)
|
||||||
|
|
||||||
// Done collecting, now we're back in the ground state.
|
// Done collecting, now we're back in the ground state.
|
||||||
pstate = parser.GroundState
|
pstate = parser.GroundState
|
||||||
@ -152,7 +147,7 @@ func truncate(m Method, s string, length int, tail string) string {
|
|||||||
}
|
}
|
||||||
fallthrough
|
fallthrough
|
||||||
default:
|
default:
|
||||||
buf.WriteByte(b[i])
|
buf.WriteByte(s[i])
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -193,27 +188,23 @@ func truncateLeft(m Method, s string, n int, prefix string) string {
|
|||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
var cluster []byte
|
var cluster string
|
||||||
var buf bytes.Buffer
|
var buf strings.Builder
|
||||||
curWidth := 0
|
curWidth := 0
|
||||||
ignoring := true
|
ignoring := true
|
||||||
pstate := parser.GroundState
|
pstate := parser.GroundState
|
||||||
b := []byte(s)
|
|
||||||
i := 0
|
i := 0
|
||||||
|
|
||||||
for i < len(b) {
|
for i < len(s) {
|
||||||
if !ignoring {
|
if !ignoring {
|
||||||
buf.Write(b[i:])
|
buf.WriteString(s[i:])
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
state, action := parser.Table.Transition(pstate, b[i])
|
state, action := parser.Table.Transition(pstate, s[i])
|
||||||
if state == parser.Utf8State {
|
if state == parser.Utf8State {
|
||||||
var width int
|
var width int
|
||||||
cluster, _, width, _ = uniseg.FirstGraphemeCluster(b[i:], -1)
|
cluster, width = FirstGraphemeCluster(s[i:], m)
|
||||||
if m == WcWidth {
|
|
||||||
width = runewidth.StringWidth(string(cluster))
|
|
||||||
}
|
|
||||||
|
|
||||||
i += len(cluster)
|
i += len(cluster)
|
||||||
curWidth += width
|
curWidth += width
|
||||||
@ -224,7 +215,7 @@ func truncateLeft(m Method, s string, n int, prefix string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if curWidth > n {
|
if curWidth > n {
|
||||||
buf.Write(cluster)
|
buf.WriteString(cluster)
|
||||||
}
|
}
|
||||||
|
|
||||||
if ignoring {
|
if ignoring {
|
||||||
@ -259,7 +250,7 @@ func truncateLeft(m Method, s string, n int, prefix string) string {
|
|||||||
}
|
}
|
||||||
fallthrough
|
fallthrough
|
||||||
default:
|
default:
|
||||||
buf.WriteByte(b[i])
|
buf.WriteByte(s[i])
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -278,22 +269,22 @@ func truncateLeft(m Method, s string, n int, prefix string) string {
|
|||||||
// You can use this with [Truncate], [TruncateLeft], and [Cut].
|
// You can use this with [Truncate], [TruncateLeft], and [Cut].
|
||||||
func ByteToGraphemeRange(str string, byteStart, byteStop int) (charStart, charStop int) {
|
func ByteToGraphemeRange(str string, byteStart, byteStop int) (charStart, charStop int) {
|
||||||
bytePos, charPos := 0, 0
|
bytePos, charPos := 0, 0
|
||||||
gr := uniseg.NewGraphemes(str)
|
gr := graphemes.FromString(str)
|
||||||
for byteStart > bytePos {
|
for byteStart > bytePos {
|
||||||
if !gr.Next() {
|
if !gr.Next() {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
bytePos += len(gr.Str())
|
bytePos += len(gr.Value())
|
||||||
charPos += max(1, gr.Width())
|
charPos += max(1, displaywidth.String(gr.Value()))
|
||||||
}
|
}
|
||||||
charStart = charPos
|
charStart = charPos
|
||||||
for byteStop > bytePos {
|
for byteStop > bytePos {
|
||||||
if !gr.Next() {
|
if !gr.Next() {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
bytePos += len(gr.Str())
|
bytePos += len(gr.Value())
|
||||||
charPos += max(1, gr.Width())
|
charPos += max(1, displaywidth.String(gr.Value()))
|
||||||
}
|
}
|
||||||
charStop = charPos
|
charStop = charPos
|
||||||
return
|
return charStart, charStop
|
||||||
}
|
}
|
||||||
|
|||||||
17
vendor/github.com/charmbracelet/x/ansi/urxvt.go
generated
vendored
Normal file
17
vendor/github.com/charmbracelet/x/ansi/urxvt.go
generated
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package ansi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// URxvtExt returns an escape sequence for calling a URxvt perl extension with
|
||||||
|
// the given name and parameters.
|
||||||
|
//
|
||||||
|
// OSC 777 ; extension_name ; param1 ; param2 ; ... ST
|
||||||
|
// OSC 777 ; extension_name ; param1 ; param2 ; ... BEL
|
||||||
|
//
|
||||||
|
// See: https://man.archlinux.org/man/extra/rxvt-unicode/urxvt.7.en#XTerm_Operating_System_Commands
|
||||||
|
func URxvtExt(extension string, params ...string) string {
|
||||||
|
return fmt.Sprintf("\x1b]777;%s;%s\x07", extension, strings.Join(params, ";"))
|
||||||
|
}
|
||||||
14
vendor/github.com/charmbracelet/x/ansi/width.go
generated
vendored
14
vendor/github.com/charmbracelet/x/ansi/width.go
generated
vendored
@ -4,8 +4,6 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
|
|
||||||
"github.com/charmbracelet/x/ansi/parser"
|
"github.com/charmbracelet/x/ansi/parser"
|
||||||
"github.com/mattn/go-runewidth"
|
|
||||||
"github.com/rivo/uniseg"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Strip removes ANSI escape codes from a string.
|
// Strip removes ANSI escape codes from a string.
|
||||||
@ -83,20 +81,16 @@ func stringWidth(m Method, s string) int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
pstate = parser.GroundState // initial state
|
pstate = parser.GroundState // initial state
|
||||||
cluster string
|
width int
|
||||||
width int
|
|
||||||
)
|
)
|
||||||
|
|
||||||
for i := 0; i < len(s); i++ {
|
for i := 0; i < len(s); i++ {
|
||||||
state, action := parser.Table.Transition(pstate, s[i])
|
state, action := parser.Table.Transition(pstate, s[i])
|
||||||
if state == parser.Utf8State {
|
if state == parser.Utf8State {
|
||||||
var w int
|
cluster, w := FirstGraphemeCluster(s[i:], m)
|
||||||
cluster, _, w, _ = uniseg.FirstGraphemeClusterInString(s[i:], -1)
|
|
||||||
if m == WcWidth {
|
|
||||||
w = runewidth.StringWidth(cluster)
|
|
||||||
}
|
|
||||||
width += w
|
width += w
|
||||||
|
|
||||||
i += len(cluster) - 1
|
i += len(cluster) - 1
|
||||||
pstate = parser.GroundState
|
pstate = parser.GroundState
|
||||||
continue
|
continue
|
||||||
|
|||||||
50
vendor/github.com/charmbracelet/x/ansi/wrap.go
generated
vendored
50
vendor/github.com/charmbracelet/x/ansi/wrap.go
generated
vendored
@ -2,12 +2,11 @@ package ansi
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"strings"
|
||||||
"unicode"
|
"unicode"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
"github.com/charmbracelet/x/ansi/parser"
|
"github.com/charmbracelet/x/ansi/parser"
|
||||||
"github.com/mattn/go-runewidth"
|
|
||||||
"github.com/rivo/uniseg"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// nbsp is a non-breaking space.
|
// nbsp is a non-breaking space.
|
||||||
@ -55,12 +54,9 @@ func hardwrap(m Method, s string, limit int, preserveSpace bool) string {
|
|||||||
i := 0
|
i := 0
|
||||||
for i < len(b) {
|
for i < len(b) {
|
||||||
state, action := parser.Table.Transition(pstate, b[i])
|
state, action := parser.Table.Transition(pstate, b[i])
|
||||||
if state == parser.Utf8State { //nolint:nestif
|
if state == parser.Utf8State {
|
||||||
var width int
|
var width int
|
||||||
cluster, _, width, _ = uniseg.FirstGraphemeCluster(b[i:], -1)
|
cluster, width = FirstGraphemeCluster(b[i:], m)
|
||||||
if m == WcWidth {
|
|
||||||
width = runewidth.StringWidth(string(cluster))
|
|
||||||
}
|
|
||||||
i += len(cluster)
|
i += len(cluster)
|
||||||
|
|
||||||
if curWidth+width > limit {
|
if curWidth+width > limit {
|
||||||
@ -192,10 +188,7 @@ func wordwrap(m Method, s string, limit int, breakpoints string) string {
|
|||||||
state, action := parser.Table.Transition(pstate, b[i])
|
state, action := parser.Table.Transition(pstate, b[i])
|
||||||
if state == parser.Utf8State { //nolint:nestif
|
if state == parser.Utf8State { //nolint:nestif
|
||||||
var width int
|
var width int
|
||||||
cluster, _, width, _ = uniseg.FirstGraphemeCluster(b[i:], -1)
|
cluster, width = FirstGraphemeCluster(b[i:], m)
|
||||||
if m == WcWidth {
|
|
||||||
width = runewidth.StringWidth(string(cluster))
|
|
||||||
}
|
|
||||||
i += len(cluster)
|
i += len(cluster)
|
||||||
|
|
||||||
r, _ := utf8.DecodeRune(cluster)
|
r, _ := utf8.DecodeRune(cluster)
|
||||||
@ -303,7 +296,7 @@ func wrap(m Method, s string, limit int, breakpoints string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
cluster []byte
|
cluster string
|
||||||
buf bytes.Buffer
|
buf bytes.Buffer
|
||||||
word bytes.Buffer
|
word bytes.Buffer
|
||||||
space bytes.Buffer
|
space bytes.Buffer
|
||||||
@ -311,10 +304,12 @@ func wrap(m Method, s string, limit int, breakpoints string) string {
|
|||||||
curWidth int // written width of the line
|
curWidth int // written width of the line
|
||||||
wordLen int // word buffer len without ANSI escape codes
|
wordLen int // word buffer len without ANSI escape codes
|
||||||
pstate = parser.GroundState // initial state
|
pstate = parser.GroundState // initial state
|
||||||
b = []byte(s)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
addSpace := func() {
|
addSpace := func() {
|
||||||
|
if spaceWidth == 0 && space.Len() == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
curWidth += spaceWidth
|
curWidth += spaceWidth
|
||||||
buf.Write(space.Bytes())
|
buf.Write(space.Bytes())
|
||||||
space.Reset()
|
space.Reset()
|
||||||
@ -341,30 +336,27 @@ func wrap(m Method, s string, limit int, breakpoints string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
i := 0
|
i := 0
|
||||||
for i < len(b) {
|
for i < len(s) {
|
||||||
state, action := parser.Table.Transition(pstate, b[i])
|
state, action := parser.Table.Transition(pstate, s[i])
|
||||||
if state == parser.Utf8State { //nolint:nestif
|
if state == parser.Utf8State { //nolint:nestif
|
||||||
var width int
|
var width int
|
||||||
cluster, _, width, _ = uniseg.FirstGraphemeCluster(b[i:], -1)
|
cluster, width = FirstGraphemeCluster(s[i:], m)
|
||||||
if m == WcWidth {
|
|
||||||
width = runewidth.StringWidth(string(cluster))
|
|
||||||
}
|
|
||||||
i += len(cluster)
|
i += len(cluster)
|
||||||
|
|
||||||
r, _ := utf8.DecodeRune(cluster)
|
r, _ := utf8.DecodeRuneInString(cluster)
|
||||||
switch {
|
switch {
|
||||||
case r != utf8.RuneError && unicode.IsSpace(r) && r != nbsp: // nbsp is a non-breaking space
|
case r != utf8.RuneError && unicode.IsSpace(r) && r != nbsp: // nbsp is a non-breaking space
|
||||||
addWord()
|
addWord()
|
||||||
space.WriteRune(r)
|
space.WriteRune(r)
|
||||||
spaceWidth += width
|
spaceWidth += width
|
||||||
case bytes.ContainsAny(cluster, breakpoints):
|
case strings.ContainsAny(cluster, breakpoints):
|
||||||
addSpace()
|
addSpace()
|
||||||
if curWidth+wordLen+width > limit {
|
if curWidth+wordLen+width > limit {
|
||||||
word.Write(cluster)
|
word.WriteString(cluster)
|
||||||
wordLen += width
|
wordLen += width
|
||||||
} else {
|
} else {
|
||||||
addWord()
|
addWord()
|
||||||
buf.Write(cluster)
|
buf.WriteString(cluster)
|
||||||
curWidth += width
|
curWidth += width
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
@ -373,12 +365,17 @@ func wrap(m Method, s string, limit int, breakpoints string) string {
|
|||||||
addWord()
|
addWord()
|
||||||
}
|
}
|
||||||
|
|
||||||
word.Write(cluster)
|
word.WriteString(cluster)
|
||||||
wordLen += width
|
wordLen += width
|
||||||
|
|
||||||
if curWidth+wordLen+spaceWidth > limit {
|
if curWidth+wordLen+spaceWidth > limit {
|
||||||
addNewline()
|
addNewline()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if wordLen == limit {
|
||||||
|
// Hardwrap the word if it's too long
|
||||||
|
addWord()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pstate = parser.GroundState
|
pstate = parser.GroundState
|
||||||
@ -387,7 +384,7 @@ func wrap(m Method, s string, limit int, breakpoints string) string {
|
|||||||
|
|
||||||
switch action {
|
switch action {
|
||||||
case parser.PrintAction, parser.ExecuteAction:
|
case parser.PrintAction, parser.ExecuteAction:
|
||||||
switch r := rune(b[i]); {
|
switch r := rune(s[i]); {
|
||||||
case r == '\n':
|
case r == '\n':
|
||||||
if wordLen == 0 {
|
if wordLen == 0 {
|
||||||
if curWidth+spaceWidth > limit {
|
if curWidth+spaceWidth > limit {
|
||||||
@ -424,6 +421,7 @@ func wrap(m Method, s string, limit int, breakpoints string) string {
|
|||||||
if curWidth == limit {
|
if curWidth == limit {
|
||||||
addNewline()
|
addNewline()
|
||||||
}
|
}
|
||||||
|
|
||||||
word.WriteRune(r)
|
word.WriteRune(r)
|
||||||
wordLen++
|
wordLen++
|
||||||
|
|
||||||
@ -438,7 +436,7 @@ func wrap(m Method, s string, limit int, breakpoints string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
word.WriteByte(b[i])
|
word.WriteByte(s[i])
|
||||||
}
|
}
|
||||||
|
|
||||||
// We manage the UTF8 state separately manually above.
|
// We manage the UTF8 state separately manually above.
|
||||||
|
|||||||
15
vendor/github.com/charmbracelet/x/cellbuf/buffer.go
generated
vendored
15
vendor/github.com/charmbracelet/x/cellbuf/buffer.go
generated
vendored
@ -1,3 +1,4 @@
|
|||||||
|
// Package cellbuf provides terminal cell buffer functionality.
|
||||||
package cellbuf
|
package cellbuf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -24,7 +25,7 @@ func NewCell(r rune, comb ...rune) (c *Cell) {
|
|||||||
}
|
}
|
||||||
c.Comb = comb
|
c.Comb = comb
|
||||||
c.Width = runewidth.StringWidth(string(append([]rune{r}, comb...)))
|
c.Width = runewidth.StringWidth(string(append([]rune{r}, comb...)))
|
||||||
return
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCellString returns a new cell with the given string content. This is a
|
// NewCellString returns a new cell with the given string content. This is a
|
||||||
@ -46,7 +47,7 @@ func NewCellString(s string) (c *Cell) {
|
|||||||
c.Comb = append(c.Comb, r)
|
c.Comb = append(c.Comb, r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewGraphemeCell returns a new cell. This is a convenience function that
|
// NewGraphemeCell returns a new cell. This is a convenience function that
|
||||||
@ -71,7 +72,7 @@ func newGraphemeCell(s string, w int) (c *Cell) {
|
|||||||
c.Comb = append(c.Comb, r)
|
c.Comb = append(c.Comb, r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
// Line represents a line in the terminal.
|
// Line represents a line in the terminal.
|
||||||
@ -104,7 +105,7 @@ func (l Line) String() (s string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
s = strings.TrimRight(s, " ")
|
s = strings.TrimRight(s, " ")
|
||||||
return
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
// At returns the cell at the given x position.
|
// At returns the cell at the given x position.
|
||||||
@ -150,7 +151,7 @@ func (l Line) set(x int, c *Cell, clone bool) bool {
|
|||||||
for j := 1; j < maxCellWidth && x-j >= 0; j++ {
|
for j := 1; j < maxCellWidth && x-j >= 0; j++ {
|
||||||
wide := l.At(x - j)
|
wide := l.At(x - j)
|
||||||
if wide != nil && wide.Width > 1 && j < wide.Width {
|
if wide != nil && wide.Width > 1 && j < wide.Width {
|
||||||
for k := 0; k < wide.Width; k++ {
|
for k := range wide.Width {
|
||||||
l[x-j+k] = wide.Clone().Blank()
|
l[x-j+k] = wide.Clone().Blank()
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
@ -206,7 +207,7 @@ func (b *Buffer) String() (s string) {
|
|||||||
s += "\r\n"
|
s += "\r\n"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
// Line returns a pointer to the line at the given y position.
|
// Line returns a pointer to the line at the given y position.
|
||||||
@ -296,7 +297,7 @@ func (b *Buffer) FillRect(c *Cell, rect Rectangle) {
|
|||||||
}
|
}
|
||||||
for y := rect.Min.Y; y < rect.Max.Y; y++ {
|
for y := rect.Min.Y; y < rect.Max.Y; y++ {
|
||||||
for x := rect.Min.X; x < rect.Max.X; x += cellWidth {
|
for x := rect.Min.X; x < rect.Max.X; x += cellWidth {
|
||||||
b.setCell(x, y, c, false) //nolint:errcheck
|
b.setCell(x, y, c, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
83
vendor/github.com/charmbracelet/x/cellbuf/cell.go
generated
vendored
83
vendor/github.com/charmbracelet/x/cellbuf/cell.go
generated
vendored
@ -96,7 +96,7 @@ func (c *Cell) Clear() bool {
|
|||||||
func (c *Cell) Clone() (n *Cell) {
|
func (c *Cell) Clone() (n *Cell) {
|
||||||
n = new(Cell)
|
n = new(Cell)
|
||||||
*n = *c
|
*n = *c
|
||||||
return
|
return n
|
||||||
}
|
}
|
||||||
|
|
||||||
// Blank makes the cell a blank cell by setting the rune to a space, comb to
|
// Blank makes the cell a blank cell by setting the rune to a space, comb to
|
||||||
@ -164,12 +164,12 @@ type UnderlineStyle = ansi.UnderlineStyle
|
|||||||
|
|
||||||
// These are the available underline styles.
|
// These are the available underline styles.
|
||||||
const (
|
const (
|
||||||
NoUnderline = ansi.NoUnderlineStyle
|
NoUnderline = ansi.UnderlineStyleNone
|
||||||
SingleUnderline = ansi.SingleUnderlineStyle
|
SingleUnderline = ansi.UnderlineStyleSingle
|
||||||
DoubleUnderline = ansi.DoubleUnderlineStyle
|
DoubleUnderline = ansi.UnderlineStyleDouble
|
||||||
CurlyUnderline = ansi.CurlyUnderlineStyle
|
CurlyUnderline = ansi.UnderlineStyleCurly
|
||||||
DottedUnderline = ansi.DottedUnderlineStyle
|
DottedUnderline = ansi.UnderlineStyleDotted
|
||||||
DashedUnderline = ansi.DashedUnderlineStyle
|
DashedUnderline = ansi.UnderlineStyleDashed
|
||||||
)
|
)
|
||||||
|
|
||||||
// Style represents the Style of a cell.
|
// Style represents the Style of a cell.
|
||||||
@ -189,7 +189,7 @@ func (s Style) Sequence() string {
|
|||||||
|
|
||||||
var b ansi.Style
|
var b ansi.Style
|
||||||
|
|
||||||
if s.Attrs != 0 {
|
if s.Attrs != 0 { //nolint:nestif
|
||||||
if s.Attrs&BoldAttr != 0 {
|
if s.Attrs&BoldAttr != 0 {
|
||||||
b = b.Bold()
|
b = b.Bold()
|
||||||
}
|
}
|
||||||
@ -197,36 +197,31 @@ func (s Style) Sequence() string {
|
|||||||
b = b.Faint()
|
b = b.Faint()
|
||||||
}
|
}
|
||||||
if s.Attrs&ItalicAttr != 0 {
|
if s.Attrs&ItalicAttr != 0 {
|
||||||
b = b.Italic()
|
b = b.Italic(true)
|
||||||
}
|
}
|
||||||
if s.Attrs&SlowBlinkAttr != 0 {
|
if s.Attrs&SlowBlinkAttr != 0 {
|
||||||
b = b.SlowBlink()
|
b = b.Blink(true)
|
||||||
}
|
}
|
||||||
if s.Attrs&RapidBlinkAttr != 0 {
|
if s.Attrs&RapidBlinkAttr != 0 {
|
||||||
b = b.RapidBlink()
|
b = b.RapidBlink(true)
|
||||||
}
|
}
|
||||||
if s.Attrs&ReverseAttr != 0 {
|
if s.Attrs&ReverseAttr != 0 {
|
||||||
b = b.Reverse()
|
b = b.Reverse(true)
|
||||||
}
|
}
|
||||||
if s.Attrs&ConcealAttr != 0 {
|
if s.Attrs&ConcealAttr != 0 {
|
||||||
b = b.Conceal()
|
b = b.Conceal(true)
|
||||||
}
|
}
|
||||||
if s.Attrs&StrikethroughAttr != 0 {
|
if s.Attrs&StrikethroughAttr != 0 {
|
||||||
b = b.Strikethrough()
|
b = b.Strikethrough(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if s.UlStyle != NoUnderline {
|
if s.UlStyle != NoUnderline {
|
||||||
switch s.UlStyle {
|
switch u := s.UlStyle; u {
|
||||||
case SingleUnderline:
|
case NoUnderline:
|
||||||
b = b.Underline()
|
b = b.Underline(false)
|
||||||
case DoubleUnderline:
|
default:
|
||||||
b = b.DoubleUnderline()
|
b = b.Underline(true)
|
||||||
case CurlyUnderline:
|
b = b.UnderlineStyle(u)
|
||||||
b = b.CurlyUnderline()
|
|
||||||
case DottedUnderline:
|
|
||||||
b = b.DottedUnderline()
|
|
||||||
case DashedUnderline:
|
|
||||||
b = b.DashedUnderline()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if s.Fg != nil {
|
if s.Fg != nil {
|
||||||
@ -268,64 +263,48 @@ func (s Style) DiffSequence(o Style) string {
|
|||||||
isNormal bool
|
isNormal bool
|
||||||
)
|
)
|
||||||
|
|
||||||
if s.Attrs != o.Attrs {
|
if s.Attrs != o.Attrs { //nolint:nestif
|
||||||
if s.Attrs&BoldAttr != o.Attrs&BoldAttr {
|
if s.Attrs&BoldAttr != o.Attrs&BoldAttr {
|
||||||
if s.Attrs&BoldAttr != 0 {
|
if s.Attrs&BoldAttr != 0 {
|
||||||
b = b.Bold()
|
b = b.Bold()
|
||||||
} else if !isNormal {
|
} else if !isNormal {
|
||||||
isNormal = true
|
isNormal = true
|
||||||
b = b.NormalIntensity()
|
b = b.Normal()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if s.Attrs&FaintAttr != o.Attrs&FaintAttr {
|
if s.Attrs&FaintAttr != o.Attrs&FaintAttr {
|
||||||
if s.Attrs&FaintAttr != 0 {
|
if s.Attrs&FaintAttr != 0 {
|
||||||
b = b.Faint()
|
b = b.Faint()
|
||||||
} else if !isNormal {
|
} else if !isNormal {
|
||||||
b = b.NormalIntensity()
|
b = b.Normal()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if s.Attrs&ItalicAttr != o.Attrs&ItalicAttr {
|
if s.Attrs&ItalicAttr != o.Attrs&ItalicAttr {
|
||||||
if s.Attrs&ItalicAttr != 0 {
|
b = b.Italic(s.Attrs&ItalicAttr != 0)
|
||||||
b = b.Italic()
|
|
||||||
} else {
|
|
||||||
b = b.NoItalic()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if s.Attrs&SlowBlinkAttr != o.Attrs&SlowBlinkAttr {
|
if s.Attrs&SlowBlinkAttr != o.Attrs&SlowBlinkAttr {
|
||||||
if s.Attrs&SlowBlinkAttr != 0 {
|
if s.Attrs&SlowBlinkAttr != 0 {
|
||||||
b = b.SlowBlink()
|
b = b.Blink(true)
|
||||||
} else if !noBlink {
|
} else if !noBlink {
|
||||||
noBlink = true
|
noBlink = true
|
||||||
b = b.NoBlink()
|
b = b.Blink(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if s.Attrs&RapidBlinkAttr != o.Attrs&RapidBlinkAttr {
|
if s.Attrs&RapidBlinkAttr != o.Attrs&RapidBlinkAttr {
|
||||||
if s.Attrs&RapidBlinkAttr != 0 {
|
if s.Attrs&RapidBlinkAttr != 0 {
|
||||||
b = b.RapidBlink()
|
b = b.RapidBlink(true)
|
||||||
} else if !noBlink {
|
} else if !noBlink {
|
||||||
b = b.NoBlink()
|
b = b.Blink(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if s.Attrs&ReverseAttr != o.Attrs&ReverseAttr {
|
if s.Attrs&ReverseAttr != o.Attrs&ReverseAttr {
|
||||||
if s.Attrs&ReverseAttr != 0 {
|
b = b.Reverse(s.Attrs&ReverseAttr != 0)
|
||||||
b = b.Reverse()
|
|
||||||
} else {
|
|
||||||
b = b.NoReverse()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if s.Attrs&ConcealAttr != o.Attrs&ConcealAttr {
|
if s.Attrs&ConcealAttr != o.Attrs&ConcealAttr {
|
||||||
if s.Attrs&ConcealAttr != 0 {
|
b = b.Conceal(s.Attrs&ConcealAttr != 0)
|
||||||
b = b.Conceal()
|
|
||||||
} else {
|
|
||||||
b = b.NoConceal()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if s.Attrs&StrikethroughAttr != o.Attrs&StrikethroughAttr {
|
if s.Attrs&StrikethroughAttr != o.Attrs&StrikethroughAttr {
|
||||||
if s.Attrs&StrikethroughAttr != 0 {
|
b = b.Strikethrough(s.Attrs&StrikethroughAttr != 0)
|
||||||
b = b.Strikethrough()
|
|
||||||
} else {
|
|
||||||
b = b.NoStrikethrough()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
2
vendor/github.com/charmbracelet/x/cellbuf/geom.go
generated
vendored
2
vendor/github.com/charmbracelet/x/cellbuf/geom.go
generated
vendored
@ -12,7 +12,7 @@ func Pos(x, y int) Position {
|
|||||||
return image.Pt(x, y)
|
return image.Pt(x, y)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rectange represents a rectangle.
|
// Rectangle represents a rectangle.
|
||||||
type Rectangle = image.Rectangle
|
type Rectangle = image.Rectangle
|
||||||
|
|
||||||
// Rect is a shorthand for Rectangle.
|
// Rect is a shorthand for Rectangle.
|
||||||
|
|||||||
19
vendor/github.com/charmbracelet/x/cellbuf/hardscroll.go
generated
vendored
19
vendor/github.com/charmbracelet/x/cellbuf/hardscroll.go
generated
vendored
@ -75,7 +75,7 @@ func (s *Screen) scrolln(n, top, bot, maxY int) (v bool) { //nolint:unparam
|
|||||||
)
|
)
|
||||||
|
|
||||||
blank := s.clearBlank()
|
blank := s.clearBlank()
|
||||||
if n > 0 {
|
if n > 0 { //nolint:nestif
|
||||||
// Scroll up (forward)
|
// Scroll up (forward)
|
||||||
v = s.scrollUp(n, top, bot, 0, maxY, blank)
|
v = s.scrollUp(n, top, bot, 0, maxY, blank)
|
||||||
if !v {
|
if !v {
|
||||||
@ -99,7 +99,7 @@ func (s *Screen) scrolln(n, top, bot, maxY int) (v bool) { //nolint:unparam
|
|||||||
s.move(0, bot-n+1)
|
s.move(0, bot-n+1)
|
||||||
s.clearToBottom(nil)
|
s.clearToBottom(nil)
|
||||||
} else {
|
} else {
|
||||||
for i := 0; i < n; i++ {
|
for i := range n {
|
||||||
s.move(0, bot-i)
|
s.move(0, bot-i)
|
||||||
s.clearToEnd(nil, false)
|
s.clearToEnd(nil, false)
|
||||||
}
|
}
|
||||||
@ -124,7 +124,7 @@ func (s *Screen) scrolln(n, top, bot, maxY int) (v bool) { //nolint:unparam
|
|||||||
// Clear newly shifted-in lines.
|
// Clear newly shifted-in lines.
|
||||||
if v &&
|
if v &&
|
||||||
(nonDestScrollRegion || (memoryBelow && top == 0)) {
|
(nonDestScrollRegion || (memoryBelow && top == 0)) {
|
||||||
for i := 0; i < -n; i++ {
|
for i := range -n {
|
||||||
s.move(0, top+i)
|
s.move(0, top+i)
|
||||||
s.clearToEnd(nil, false)
|
s.clearToEnd(nil, false)
|
||||||
}
|
}
|
||||||
@ -133,7 +133,7 @@ func (s *Screen) scrolln(n, top, bot, maxY int) (v bool) { //nolint:unparam
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !v {
|
if !v {
|
||||||
return
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
s.scrollBuffer(s.curbuf, n, top, bot, blank)
|
s.scrollBuffer(s.curbuf, n, top, bot, blank)
|
||||||
@ -193,7 +193,7 @@ func (s *Screen) touchLine(width, height, y, n int, changed bool) {
|
|||||||
|
|
||||||
// scrollUp scrolls the screen up by n lines.
|
// scrollUp scrolls the screen up by n lines.
|
||||||
func (s *Screen) scrollUp(n, top, bot, minY, maxY int, blank *Cell) bool {
|
func (s *Screen) scrollUp(n, top, bot, minY, maxY int, blank *Cell) bool {
|
||||||
if n == 1 && top == minY && bot == maxY {
|
if n == 1 && top == minY && bot == maxY { //nolint:nestif
|
||||||
s.move(0, bot)
|
s.move(0, bot)
|
||||||
s.updatePen(blank)
|
s.updatePen(blank)
|
||||||
s.buf.WriteByte('\n')
|
s.buf.WriteByte('\n')
|
||||||
@ -202,13 +202,14 @@ func (s *Screen) scrollUp(n, top, bot, minY, maxY int, blank *Cell) bool {
|
|||||||
s.updatePen(blank)
|
s.updatePen(blank)
|
||||||
s.buf.WriteString(ansi.DeleteLine(1))
|
s.buf.WriteString(ansi.DeleteLine(1))
|
||||||
} else if top == minY && bot == maxY {
|
} else if top == minY && bot == maxY {
|
||||||
if s.xtermLike {
|
supportsSU := s.caps.Contains(capSU)
|
||||||
|
if supportsSU {
|
||||||
s.move(0, bot)
|
s.move(0, bot)
|
||||||
} else {
|
} else {
|
||||||
s.move(0, top)
|
s.move(0, top)
|
||||||
}
|
}
|
||||||
s.updatePen(blank)
|
s.updatePen(blank)
|
||||||
if s.xtermLike {
|
if supportsSU {
|
||||||
s.buf.WriteString(ansi.ScrollUp(n))
|
s.buf.WriteString(ansi.ScrollUp(n))
|
||||||
} else {
|
} else {
|
||||||
s.buf.WriteString(strings.Repeat("\n", n))
|
s.buf.WriteString(strings.Repeat("\n", n))
|
||||||
@ -225,7 +226,7 @@ func (s *Screen) scrollUp(n, top, bot, minY, maxY int, blank *Cell) bool {
|
|||||||
|
|
||||||
// scrollDown scrolls the screen down by n lines.
|
// scrollDown scrolls the screen down by n lines.
|
||||||
func (s *Screen) scrollDown(n, top, bot, minY, maxY int, blank *Cell) bool {
|
func (s *Screen) scrollDown(n, top, bot, minY, maxY int, blank *Cell) bool {
|
||||||
if n == 1 && top == minY && bot == maxY {
|
if n == 1 && top == minY && bot == maxY { //nolint:nestif
|
||||||
s.move(0, top)
|
s.move(0, top)
|
||||||
s.updatePen(blank)
|
s.updatePen(blank)
|
||||||
s.buf.WriteString(ansi.ReverseIndex)
|
s.buf.WriteString(ansi.ReverseIndex)
|
||||||
@ -236,7 +237,7 @@ func (s *Screen) scrollDown(n, top, bot, minY, maxY int, blank *Cell) bool {
|
|||||||
} else if top == minY && bot == maxY {
|
} else if top == minY && bot == maxY {
|
||||||
s.move(0, top)
|
s.move(0, top)
|
||||||
s.updatePen(blank)
|
s.updatePen(blank)
|
||||||
if s.xtermLike {
|
if s.caps.Contains(capSD) {
|
||||||
s.buf.WriteString(ansi.ScrollDown(n))
|
s.buf.WriteString(ansi.ScrollDown(n))
|
||||||
} else {
|
} else {
|
||||||
s.buf.WriteString(strings.Repeat(ansi.ReverseIndex, n))
|
s.buf.WriteString(strings.Repeat(ansi.ReverseIndex, n))
|
||||||
|
|||||||
16
vendor/github.com/charmbracelet/x/cellbuf/hashmap.go
generated
vendored
16
vendor/github.com/charmbracelet/x/cellbuf/hashmap.go
generated
vendored
@ -15,7 +15,7 @@ func hash(l Line) (h uint64) {
|
|||||||
}
|
}
|
||||||
h += (h << 5) + uint64(r)
|
h += (h << 5) + uint64(r)
|
||||||
}
|
}
|
||||||
return
|
return h
|
||||||
}
|
}
|
||||||
|
|
||||||
// hashmap represents a single [Line] hash.
|
// hashmap represents a single [Line] hash.
|
||||||
@ -33,7 +33,7 @@ func (s *Screen) updateHashmap() {
|
|||||||
height := s.newbuf.Height()
|
height := s.newbuf.Height()
|
||||||
if len(s.oldhash) >= height && len(s.newhash) >= height {
|
if len(s.oldhash) >= height && len(s.newhash) >= height {
|
||||||
// rehash changed lines
|
// rehash changed lines
|
||||||
for i := 0; i < height; i++ {
|
for i := range height {
|
||||||
_, ok := s.touch[i]
|
_, ok := s.touch[i]
|
||||||
if ok {
|
if ok {
|
||||||
s.oldhash[i] = hash(s.curbuf.Line(i))
|
s.oldhash[i] = hash(s.curbuf.Line(i))
|
||||||
@ -48,14 +48,14 @@ func (s *Screen) updateHashmap() {
|
|||||||
if len(s.newhash) != height {
|
if len(s.newhash) != height {
|
||||||
s.newhash = make([]uint64, height)
|
s.newhash = make([]uint64, height)
|
||||||
}
|
}
|
||||||
for i := 0; i < height; i++ {
|
for i := range height {
|
||||||
s.oldhash[i] = hash(s.curbuf.Line(i))
|
s.oldhash[i] = hash(s.curbuf.Line(i))
|
||||||
s.newhash[i] = hash(s.newbuf.Line(i))
|
s.newhash[i] = hash(s.newbuf.Line(i))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
s.hashtab = make([]hashmap, height*2)
|
s.hashtab = make([]hashmap, height*2)
|
||||||
for i := 0; i < height; i++ {
|
for i := range height {
|
||||||
hashval := s.oldhash[i]
|
hashval := s.oldhash[i]
|
||||||
|
|
||||||
// Find matching hash or empty slot
|
// Find matching hash or empty slot
|
||||||
@ -71,7 +71,7 @@ func (s *Screen) updateHashmap() {
|
|||||||
s.hashtab[idx].oldcount++
|
s.hashtab[idx].oldcount++
|
||||||
s.hashtab[idx].oldindex = i
|
s.hashtab[idx].oldindex = i
|
||||||
}
|
}
|
||||||
for i := 0; i < height; i++ {
|
for i := range height {
|
||||||
hashval := s.newhash[i]
|
hashval := s.newhash[i]
|
||||||
|
|
||||||
// Find matching hash or empty slot
|
// Find matching hash or empty slot
|
||||||
@ -130,7 +130,7 @@ func (s *Screen) updateHashmap() {
|
|||||||
s.growHunks()
|
s.growHunks()
|
||||||
}
|
}
|
||||||
|
|
||||||
// scrollOldhash
|
// scrollOldhash.
|
||||||
func (s *Screen) scrollOldhash(n, top, bot int) {
|
func (s *Screen) scrollOldhash(n, top, bot int) {
|
||||||
if len(s.oldhash) == 0 {
|
if len(s.oldhash) == 0 {
|
||||||
return
|
return
|
||||||
@ -287,7 +287,7 @@ func (s *Screen) updateCost(from, to Line) (cost int) {
|
|||||||
cost++
|
cost++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return cost
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Screen) updateCostBlank(to Line) (cost int) {
|
func (s *Screen) updateCostBlank(to Line) (cost int) {
|
||||||
@ -297,5 +297,5 @@ func (s *Screen) updateCostBlank(to Line) (cost int) {
|
|||||||
cost++
|
cost++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return cost
|
||||||
}
|
}
|
||||||
|
|||||||
2
vendor/github.com/charmbracelet/x/cellbuf/link.go
generated
vendored
2
vendor/github.com/charmbracelet/x/cellbuf/link.go
generated
vendored
@ -4,7 +4,7 @@ import (
|
|||||||
"github.com/charmbracelet/colorprofile"
|
"github.com/charmbracelet/colorprofile"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Convert converts a hyperlink to respect the given color profile.
|
// ConvertLink converts a hyperlink to respect the given color profile.
|
||||||
func ConvertLink(h Link, p colorprofile.Profile) Link {
|
func ConvertLink(h Link, p colorprofile.Profile) Link {
|
||||||
if p == colorprofile.NoTTY {
|
if p == colorprofile.NoTTY {
|
||||||
return Link{}
|
return Link{}
|
||||||
|
|||||||
92
vendor/github.com/charmbracelet/x/cellbuf/pen.go
generated
vendored
Normal file
92
vendor/github.com/charmbracelet/x/cellbuf/pen.go
generated
vendored
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
package cellbuf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/charmbracelet/x/ansi"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PenWriter is a writer that writes to a buffer and keeps track of the current
|
||||||
|
// pen style and link state for the purpose of wrapping with newlines.
|
||||||
|
type PenWriter struct {
|
||||||
|
w io.Writer
|
||||||
|
p *ansi.Parser
|
||||||
|
style Style
|
||||||
|
link Link
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPenWriter returns a new PenWriter.
|
||||||
|
func NewPenWriter(w io.Writer) *PenWriter {
|
||||||
|
pw := &PenWriter{w: w}
|
||||||
|
pw.p = ansi.GetParser()
|
||||||
|
handleCsi := func(cmd ansi.Cmd, params ansi.Params) {
|
||||||
|
if cmd == 'm' {
|
||||||
|
ReadStyle(params, &pw.style)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
handleOsc := func(cmd int, data []byte) {
|
||||||
|
if cmd == 8 {
|
||||||
|
ReadLink(data, &pw.link)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pw.p.SetHandler(ansi.Handler{
|
||||||
|
HandleCsi: handleCsi,
|
||||||
|
HandleOsc: handleOsc,
|
||||||
|
})
|
||||||
|
return pw
|
||||||
|
}
|
||||||
|
|
||||||
|
// Style returns the current pen style.
|
||||||
|
func (w *PenWriter) Style() Style {
|
||||||
|
return w.style
|
||||||
|
}
|
||||||
|
|
||||||
|
// Link returns the current pen link.
|
||||||
|
func (w *PenWriter) Link() Link {
|
||||||
|
return w.link
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write writes to the buffer.
|
||||||
|
func (w *PenWriter) Write(p []byte) (int, error) {
|
||||||
|
for i := range p {
|
||||||
|
b := p[i]
|
||||||
|
w.p.Advance(b)
|
||||||
|
if b == '\n' {
|
||||||
|
if !w.style.Empty() {
|
||||||
|
_, _ = w.w.Write([]byte(ansi.ResetStyle))
|
||||||
|
}
|
||||||
|
if !w.link.Empty() {
|
||||||
|
_, _ = w.w.Write([]byte(ansi.ResetHyperlink()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _ = w.w.Write([]byte{b})
|
||||||
|
if b == '\n' {
|
||||||
|
if !w.link.Empty() {
|
||||||
|
_, _ = w.w.Write([]byte(ansi.SetHyperlink(w.link.URL, w.link.Params)))
|
||||||
|
}
|
||||||
|
if !w.style.Empty() {
|
||||||
|
_, _ = w.w.Write([]byte(w.style.Sequence()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return len(p), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes the writer, resets the style and link if necessary, and releases
|
||||||
|
// its parser. Calling it is performance critical, but forgetting it does not
|
||||||
|
// cause safety issues or leaks.
|
||||||
|
func (w *PenWriter) Close() error {
|
||||||
|
if !w.style.Empty() {
|
||||||
|
_, _ = w.w.Write([]byte(ansi.ResetStyle))
|
||||||
|
}
|
||||||
|
if !w.link.Empty() {
|
||||||
|
_, _ = w.w.Write([]byte(ansi.ResetHyperlink()))
|
||||||
|
}
|
||||||
|
if w.p != nil {
|
||||||
|
ansi.PutParser(w.p)
|
||||||
|
w.p = nil
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
221
vendor/github.com/charmbracelet/x/cellbuf/screen.go
generated
vendored
221
vendor/github.com/charmbracelet/x/cellbuf/screen.go
generated
vendored
@ -39,9 +39,9 @@ func relativeCursorMove(s *Screen, fx, fy, tx, ty int, overwrite, useTabs, useBa
|
|||||||
var seq strings.Builder
|
var seq strings.Builder
|
||||||
|
|
||||||
width, height := s.newbuf.Width(), s.newbuf.Height()
|
width, height := s.newbuf.Width(), s.newbuf.Height()
|
||||||
if ty != fy {
|
if ty != fy { //nolint:nestif
|
||||||
var yseq string
|
var yseq string
|
||||||
if s.xtermLike && !s.opts.RelativeCursor {
|
if s.caps.Contains(capVPA) && !s.opts.RelativeCursor {
|
||||||
yseq = ansi.VerticalPositionAbsolute(ty + 1)
|
yseq = ansi.VerticalPositionAbsolute(ty + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,9 +54,13 @@ func relativeCursorMove(s *Screen, fx, fy, tx, ty int, overwrite, useTabs, useBa
|
|||||||
}
|
}
|
||||||
shouldScroll := !s.opts.AltScreen && fy+n >= s.scrollHeight
|
shouldScroll := !s.opts.AltScreen && fy+n >= s.scrollHeight
|
||||||
if lf := strings.Repeat("\n", n); shouldScroll || (fy+n < height && len(lf) < len(yseq)) {
|
if lf := strings.Repeat("\n", n); shouldScroll || (fy+n < height && len(lf) < len(yseq)) {
|
||||||
|
//nolint:godox
|
||||||
// TODO: Ensure we're not unintentionally scrolling the screen down.
|
// TODO: Ensure we're not unintentionally scrolling the screen down.
|
||||||
yseq = lf
|
yseq = lf
|
||||||
s.scrollHeight = max(s.scrollHeight, fy+n)
|
s.scrollHeight = max(s.scrollHeight, fy+n)
|
||||||
|
if s.opts.MapNL {
|
||||||
|
fx = 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if ty < fy {
|
} else if ty < fy {
|
||||||
n := fy - ty
|
n := fy - ty
|
||||||
@ -64,6 +68,7 @@ func relativeCursorMove(s *Screen, fx, fy, tx, ty int, overwrite, useTabs, useBa
|
|||||||
yseq = cuu
|
yseq = cuu
|
||||||
}
|
}
|
||||||
if n == 1 && fy-1 > 0 {
|
if n == 1 && fy-1 > 0 {
|
||||||
|
//nolint:godox
|
||||||
// TODO: Ensure we're not unintentionally scrolling the screen up.
|
// TODO: Ensure we're not unintentionally scrolling the screen up.
|
||||||
yseq = ansi.ReverseIndex
|
yseq = ansi.ReverseIndex
|
||||||
}
|
}
|
||||||
@ -72,9 +77,9 @@ func relativeCursorMove(s *Screen, fx, fy, tx, ty int, overwrite, useTabs, useBa
|
|||||||
seq.WriteString(yseq)
|
seq.WriteString(yseq)
|
||||||
}
|
}
|
||||||
|
|
||||||
if tx != fx {
|
if tx != fx { //nolint:nestif
|
||||||
var xseq string
|
var xseq string
|
||||||
if s.xtermLike && !s.opts.RelativeCursor {
|
if s.caps.Contains(capHPA) && !s.opts.RelativeCursor {
|
||||||
xseq = ansi.HorizontalPositionAbsolute(tx + 1)
|
xseq = ansi.HorizontalPositionAbsolute(tx + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,7 +98,8 @@ func relativeCursorMove(s *Screen, fx, fy, tx, ty int, overwrite, useTabs, useBa
|
|||||||
if tabs > 0 {
|
if tabs > 0 {
|
||||||
cht := ansi.CursorHorizontalForwardTab(tabs)
|
cht := ansi.CursorHorizontalForwardTab(tabs)
|
||||||
tab := strings.Repeat("\t", tabs)
|
tab := strings.Repeat("\t", tabs)
|
||||||
if false && s.xtermLike && len(cht) < len(tab) {
|
if false && s.caps.Contains(capCHT) && len(cht) < len(tab) {
|
||||||
|
//nolint:godox
|
||||||
// TODO: The linux console and some terminals such as
|
// TODO: The linux console and some terminals such as
|
||||||
// Alacritty don't support [ansi.CHT]. Enable this when
|
// Alacritty don't support [ansi.CHT]. Enable this when
|
||||||
// we have a way to detect this, or after 5 years when
|
// we have a way to detect this, or after 5 years when
|
||||||
@ -144,7 +150,7 @@ func relativeCursorMove(s *Screen, fx, fy, tx, ty int, overwrite, useTabs, useBa
|
|||||||
}
|
}
|
||||||
} else if tx < fx {
|
} else if tx < fx {
|
||||||
n := fx - tx
|
n := fx - tx
|
||||||
if useTabs && s.xtermLike {
|
if useTabs && s.caps.Contains(capCBT) {
|
||||||
// VT100 does not support backward tabs [ansi.CBT].
|
// VT100 does not support backward tabs [ansi.CBT].
|
||||||
|
|
||||||
col := fx
|
col := fx
|
||||||
@ -190,7 +196,7 @@ func moveCursor(s *Screen, x, y int, overwrite bool) (seq string) {
|
|||||||
// Method #0: Use [ansi.CUP] if the distance is long.
|
// Method #0: Use [ansi.CUP] if the distance is long.
|
||||||
seq = ansi.CursorPosition(x+1, y+1)
|
seq = ansi.CursorPosition(x+1, y+1)
|
||||||
if fx == -1 || fy == -1 || notLocal(s.newbuf.Width(), fx, fy, x, y) {
|
if fx == -1 || fy == -1 || notLocal(s.newbuf.Width(), fx, fy, x, y) {
|
||||||
return
|
return seq
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -234,7 +240,7 @@ func moveCursor(s *Screen, x, y int, overwrite bool) (seq string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return seq
|
||||||
}
|
}
|
||||||
|
|
||||||
// moveCursor moves the cursor to the specified position.
|
// moveCursor moves the cursor to the specified position.
|
||||||
@ -242,10 +248,10 @@ func (s *Screen) moveCursor(x, y int, overwrite bool) {
|
|||||||
if !s.opts.AltScreen && s.cur.X == -1 && s.cur.Y == -1 {
|
if !s.opts.AltScreen && s.cur.X == -1 && s.cur.Y == -1 {
|
||||||
// First cursor movement in inline mode, move the cursor to the first
|
// First cursor movement in inline mode, move the cursor to the first
|
||||||
// column before moving to the target position.
|
// column before moving to the target position.
|
||||||
s.buf.WriteByte('\r') //nolint:errcheck
|
s.buf.WriteByte('\r')
|
||||||
s.cur.X, s.cur.Y = 0, 0
|
s.cur.X, s.cur.Y = 0, 0
|
||||||
}
|
}
|
||||||
s.buf.WriteString(moveCursor(s, x, y, overwrite)) //nolint:errcheck
|
s.buf.WriteString(moveCursor(s, x, y, overwrite))
|
||||||
s.cur.X, s.cur.Y = x, y
|
s.cur.X, s.cur.Y = x, y
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -274,10 +280,11 @@ func (s *Screen) move(x, y int) {
|
|||||||
// Reset wrap around (phantom cursor) state
|
// Reset wrap around (phantom cursor) state
|
||||||
if s.atPhantom {
|
if s.atPhantom {
|
||||||
s.cur.X = 0
|
s.cur.X = 0
|
||||||
s.buf.WriteByte('\r') //nolint:errcheck
|
s.buf.WriteByte('\r')
|
||||||
s.atPhantom = false // reset phantom cell state
|
s.atPhantom = false // reset phantom cell state
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//nolint:godox
|
||||||
// TODO: Investigate if we need to handle this case and/or if we need the
|
// TODO: Investigate if we need to handle this case and/or if we need the
|
||||||
// following code.
|
// following code.
|
||||||
//
|
//
|
||||||
@ -291,7 +298,7 @@ func (s *Screen) move(x, y int) {
|
|||||||
//
|
//
|
||||||
// if l > 0 {
|
// if l > 0 {
|
||||||
// s.cur.X = 0
|
// s.cur.X = 0
|
||||||
// s.buf.WriteString("\r" + strings.Repeat("\n", l)) //nolint:errcheck
|
// s.buf.WriteString("\r" + strings.Repeat("\n", l))
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
@ -339,6 +346,10 @@ type ScreenOptions struct {
|
|||||||
HardTabs bool
|
HardTabs bool
|
||||||
// Backspace is whether to use backspace characters to move the cursor.
|
// Backspace is whether to use backspace characters to move the cursor.
|
||||||
Backspace bool
|
Backspace bool
|
||||||
|
// MapNL whether we have ONLCR mapping enabled. When we set the terminal to
|
||||||
|
// raw mode, the ONLCR mode gets disabled. ONLCR maps any newline/linefeed
|
||||||
|
// (`\n`) character to carriage return + line feed (`\r\n`).
|
||||||
|
MapNL bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// lineData represents the metadata for a line.
|
// lineData represents the metadata for a line.
|
||||||
@ -365,13 +376,13 @@ type Screen struct {
|
|||||||
opts ScreenOptions
|
opts ScreenOptions
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
method ansi.Method
|
method ansi.Method
|
||||||
scrollHeight int // keeps track of how many lines we've scrolled down (inline mode)
|
scrollHeight int // keeps track of how many lines we've scrolled down (inline mode)
|
||||||
altScreenMode bool // whether alternate screen mode is enabled
|
altScreenMode bool // whether alternate screen mode is enabled
|
||||||
cursorHidden bool // whether text cursor mode is enabled
|
cursorHidden bool // whether text cursor mode is enabled
|
||||||
clear bool // whether to force clear the screen
|
clear bool // whether to force clear the screen
|
||||||
xtermLike bool // whether to use xterm-like optimizations, otherwise, it uses vt100 only
|
caps capabilities // terminal control sequence capabilities
|
||||||
queuedText bool // whether we have queued non-zero width text queued up
|
queuedText bool // whether we have queued non-zero width text queued up
|
||||||
atPhantom bool // whether the cursor is out of bounds and at a phantom cell
|
atPhantom bool // whether the cursor is out of bounds and at a phantom cell
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetMethod sets the method used to calculate the width of cells.
|
// SetMethod sets the method used to calculate the width of cells.
|
||||||
@ -491,36 +502,77 @@ func (s *Screen) FillRect(cell *Cell, r Rectangle) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// isXtermLike returns whether the terminal is xterm-like. This means that the
|
// capabilities represents a mask of supported ANSI escape sequences.
|
||||||
|
type capabilities uint
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Vertical Position Absolute [ansi.VPA].
|
||||||
|
capVPA capabilities = 1 << iota
|
||||||
|
// Horizontal Position Absolute [ansi.HPA].
|
||||||
|
capHPA
|
||||||
|
// Cursor Horizontal Tab [ansi.CHT].
|
||||||
|
capCHT
|
||||||
|
// Cursor Backward Tab [ansi.CBT].
|
||||||
|
capCBT
|
||||||
|
// Repeat Previous Character [ansi.REP].
|
||||||
|
capREP
|
||||||
|
// Erase Character [ansi.ECH].
|
||||||
|
capECH
|
||||||
|
// Insert Character [ansi.ICH].
|
||||||
|
capICH
|
||||||
|
// Scroll Down [ansi.SD].
|
||||||
|
capSD
|
||||||
|
// Scroll Up [ansi.SU].
|
||||||
|
capSU
|
||||||
|
|
||||||
|
noCaps capabilities = 0
|
||||||
|
allCaps = capVPA | capHPA | capCHT | capCBT | capREP | capECH | capICH |
|
||||||
|
capSD | capSU
|
||||||
|
)
|
||||||
|
|
||||||
|
// Contains returns whether the capabilities contains the given capability.
|
||||||
|
func (v capabilities) Contains(c capabilities) bool {
|
||||||
|
return v&c == c
|
||||||
|
}
|
||||||
|
|
||||||
|
// xtermCaps returns whether the terminal is xterm-like. This means that the
|
||||||
// terminal supports ECMA-48 and ANSI X3.64 escape sequences.
|
// terminal supports ECMA-48 and ANSI X3.64 escape sequences.
|
||||||
// TODO: Should this be a lookup table into each $TERM terminfo database? Like
|
// xtermCaps returns a list of control sequence capabilities for the given
|
||||||
// we could keep a map of ANSI escape sequence to terminfo capability name and
|
// terminal type. This only supports a subset of sequences that can
|
||||||
// check if the database supports the escape sequence. Instead of keeping a
|
// be different among terminals.
|
||||||
// list of terminal names here.
|
// NOTE: A hybrid approach would be to support Terminfo databases for a full
|
||||||
func isXtermLike(termtype string) (v bool) {
|
// set of capabilities.
|
||||||
|
func xtermCaps(termtype string) (v capabilities) {
|
||||||
parts := strings.Split(termtype, "-")
|
parts := strings.Split(termtype, "-")
|
||||||
if len(parts) == 0 {
|
if len(parts) == 0 {
|
||||||
return
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
switch parts[0] {
|
switch parts[0] {
|
||||||
case
|
case
|
||||||
"alacritty",
|
|
||||||
"contour",
|
"contour",
|
||||||
"foot",
|
"foot",
|
||||||
"ghostty",
|
"ghostty",
|
||||||
"kitty",
|
"kitty",
|
||||||
"linux",
|
|
||||||
"rio",
|
"rio",
|
||||||
"screen",
|
|
||||||
"st",
|
"st",
|
||||||
"tmux",
|
"tmux",
|
||||||
"wezterm",
|
"wezterm",
|
||||||
"xterm":
|
"xterm":
|
||||||
v = true
|
v = allCaps
|
||||||
|
case "alacritty":
|
||||||
|
v = allCaps
|
||||||
|
v &^= capCHT // NOTE: alacritty added support for [ansi.CHT] in 2024-12-28 #62d5b13.
|
||||||
|
case "screen":
|
||||||
|
// See https://www.gnu.org/software/screen/manual/screen.html#Control-Sequences-1
|
||||||
|
v = allCaps
|
||||||
|
v &^= capREP
|
||||||
|
case "linux":
|
||||||
|
// See https://man7.org/linux/man-pages/man4/console_codes.4.html
|
||||||
|
v = capVPA | capHPA | capECH | capICH
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewScreen creates a new Screen.
|
// NewScreen creates a new Screen.
|
||||||
@ -548,14 +600,14 @@ func NewScreen(w io.Writer, width, height int, opts *ScreenOptions) (s *Screen)
|
|||||||
}
|
}
|
||||||
|
|
||||||
s.buf = new(bytes.Buffer)
|
s.buf = new(bytes.Buffer)
|
||||||
s.xtermLike = isXtermLike(s.opts.Term)
|
s.caps = xtermCaps(s.opts.Term)
|
||||||
s.curbuf = NewBuffer(width, height)
|
s.curbuf = NewBuffer(width, height)
|
||||||
s.newbuf = NewBuffer(width, height)
|
s.newbuf = NewBuffer(width, height)
|
||||||
s.cur = Cursor{Position: Pos(-1, -1)} // start at -1 to force a move
|
s.cur = Cursor{Position: Pos(-1, -1)} // start at -1 to force a move
|
||||||
s.saved = s.cur
|
s.saved = s.cur
|
||||||
s.reset()
|
s.reset()
|
||||||
|
|
||||||
return
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
// Width returns the width of the screen.
|
// Width returns the width of the screen.
|
||||||
@ -595,7 +647,7 @@ func (s *Screen) putCell(cell *Cell) {
|
|||||||
|
|
||||||
// wrapCursor wraps the cursor to the next line.
|
// wrapCursor wraps the cursor to the next line.
|
||||||
//
|
//
|
||||||
//nolint:unused
|
|
||||||
func (s *Screen) wrapCursor() {
|
func (s *Screen) wrapCursor() {
|
||||||
const autoRightMargin = true
|
const autoRightMargin = true
|
||||||
if autoRightMargin {
|
if autoRightMargin {
|
||||||
@ -628,9 +680,9 @@ func (s *Screen) putAttrCell(cell *Cell) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
s.updatePen(cell)
|
s.updatePen(cell)
|
||||||
s.buf.WriteRune(cell.Rune) //nolint:errcheck
|
s.buf.WriteRune(cell.Rune)
|
||||||
for _, c := range cell.Comb {
|
for _, c := range cell.Comb {
|
||||||
s.buf.WriteRune(c) //nolint:errcheck
|
s.buf.WriteRune(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
s.cur.X += cell.Width
|
s.cur.X += cell.Width
|
||||||
@ -649,12 +701,12 @@ func (s *Screen) putCellLR(cell *Cell) {
|
|||||||
// Optimize for the lower right corner cell.
|
// Optimize for the lower right corner cell.
|
||||||
curX := s.cur.X
|
curX := s.cur.X
|
||||||
if cell == nil || !cell.Empty() {
|
if cell == nil || !cell.Empty() {
|
||||||
s.buf.WriteString(ansi.ResetAutoWrapMode) //nolint:errcheck
|
s.buf.WriteString(ansi.ResetModeAutoWrap)
|
||||||
s.putAttrCell(cell)
|
s.putAttrCell(cell)
|
||||||
// Writing to lower-right corner cell should not wrap.
|
// Writing to lower-right corner cell should not wrap.
|
||||||
s.atPhantom = false
|
s.atPhantom = false
|
||||||
s.cur.X = curX
|
s.cur.X = curX
|
||||||
s.buf.WriteString(ansi.SetAutoWrapMode) //nolint:errcheck
|
s.buf.WriteString(ansi.SetModeAutoWrap)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -675,11 +727,11 @@ func (s *Screen) updatePen(cell *Cell) {
|
|||||||
if cell.Style.Empty() && len(seq) > len(ansi.ResetStyle) {
|
if cell.Style.Empty() && len(seq) > len(ansi.ResetStyle) {
|
||||||
seq = ansi.ResetStyle
|
seq = ansi.ResetStyle
|
||||||
}
|
}
|
||||||
s.buf.WriteString(seq) //nolint:errcheck
|
s.buf.WriteString(seq)
|
||||||
s.cur.Style = cell.Style
|
s.cur.Style = cell.Style
|
||||||
}
|
}
|
||||||
if !cell.Link.Equal(&s.cur.Link) {
|
if !cell.Link.Equal(&s.cur.Link) {
|
||||||
s.buf.WriteString(ansi.SetHyperlink(cell.Link.URL, cell.Link.Params)) //nolint:errcheck
|
s.buf.WriteString(ansi.SetHyperlink(cell.Link.URL, cell.Link.Params))
|
||||||
s.cur.Link = cell.Link
|
s.cur.Link = cell.Link
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -712,9 +764,9 @@ func (s *Screen) emitRange(line Line, n int) (eoi bool) {
|
|||||||
ech := ansi.EraseCharacter(count)
|
ech := ansi.EraseCharacter(count)
|
||||||
cup := ansi.CursorPosition(s.cur.X+count, s.cur.Y)
|
cup := ansi.CursorPosition(s.cur.X+count, s.cur.Y)
|
||||||
rep := ansi.RepeatPreviousCharacter(count)
|
rep := ansi.RepeatPreviousCharacter(count)
|
||||||
if s.xtermLike && count > len(ech)+len(cup) && cell0 != nil && cell0.Clear() {
|
if s.caps.Contains(capECH) && count > len(ech)+len(cup) && cell0 != nil && cell0.Clear() { //nolint:nestif
|
||||||
s.updatePen(cell0)
|
s.updatePen(cell0)
|
||||||
s.buf.WriteString(ech) //nolint:errcheck
|
s.buf.WriteString(ech)
|
||||||
|
|
||||||
// If this is the last cell, we don't need to move the cursor.
|
// If this is the last cell, we don't need to move the cursor.
|
||||||
if count < n {
|
if count < n {
|
||||||
@ -722,7 +774,7 @@ func (s *Screen) emitRange(line Line, n int) (eoi bool) {
|
|||||||
} else {
|
} else {
|
||||||
return true // cursor in the middle
|
return true // cursor in the middle
|
||||||
}
|
}
|
||||||
} else if s.xtermLike && count > len(rep) &&
|
} else if s.caps.Contains(capREP) && count > len(rep) &&
|
||||||
(cell0 == nil || (len(cell0.Comb) == 0 && cell0.Rune < 256)) {
|
(cell0 == nil || (len(cell0.Comb) == 0 && cell0.Rune < 256)) {
|
||||||
// We only support ASCII characters. Most terminals will handle
|
// We only support ASCII characters. Most terminals will handle
|
||||||
// non-ASCII characters correctly, but some might not, ahem xterm.
|
// non-ASCII characters correctly, but some might not, ahem xterm.
|
||||||
@ -740,13 +792,13 @@ func (s *Screen) emitRange(line Line, n int) (eoi bool) {
|
|||||||
s.putCell(cell0)
|
s.putCell(cell0)
|
||||||
repCount-- // cell0 is a single width cell ASCII character
|
repCount-- // cell0 is a single width cell ASCII character
|
||||||
|
|
||||||
s.buf.WriteString(ansi.RepeatPreviousCharacter(repCount)) //nolint:errcheck
|
s.buf.WriteString(ansi.RepeatPreviousCharacter(repCount))
|
||||||
s.cur.X += repCount
|
s.cur.X += repCount
|
||||||
if wrapPossible {
|
if wrapPossible {
|
||||||
s.putCell(cell0)
|
s.putCell(cell0)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for i := 0; i < count; i++ {
|
for i := range count {
|
||||||
s.putCell(line.At(i))
|
s.putCell(line.At(i))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -755,7 +807,7 @@ func (s *Screen) emitRange(line Line, n int) (eoi bool) {
|
|||||||
n -= count
|
n -= count
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return eoi
|
||||||
}
|
}
|
||||||
|
|
||||||
// putRange puts a range of cells from the old line to the new line.
|
// putRange puts a range of cells from the old line to the new line.
|
||||||
@ -765,7 +817,7 @@ func (s *Screen) putRange(oldLine, newLine Line, y, start, end int) (eoi bool) {
|
|||||||
inline := min(len(ansi.CursorPosition(start+1, y+1)),
|
inline := min(len(ansi.CursorPosition(start+1, y+1)),
|
||||||
min(len(ansi.HorizontalPositionAbsolute(start+1)),
|
min(len(ansi.HorizontalPositionAbsolute(start+1)),
|
||||||
len(ansi.CursorForward(start+1))))
|
len(ansi.CursorForward(start+1))))
|
||||||
if (end - start + 1) > inline {
|
if (end - start + 1) > inline { //nolint:nestif
|
||||||
var j, same int
|
var j, same int
|
||||||
for j, same = start, 0; j <= end; j++ {
|
for j, same = start, 0; j <= end; j++ {
|
||||||
oldCell, newCell := oldLine.At(j), newLine.At(j)
|
oldCell, newCell := oldLine.At(j), newLine.At(j)
|
||||||
@ -817,9 +869,9 @@ func (s *Screen) clearToEnd(blank *Cell, force bool) { //nolint:unparam
|
|||||||
s.updatePen(blank)
|
s.updatePen(blank)
|
||||||
count := s.newbuf.Width() - s.cur.X
|
count := s.newbuf.Width() - s.cur.X
|
||||||
if s.el0Cost() <= count {
|
if s.el0Cost() <= count {
|
||||||
s.buf.WriteString(ansi.EraseLineRight) //nolint:errcheck
|
s.buf.WriteString(ansi.EraseLineRight)
|
||||||
} else {
|
} else {
|
||||||
for i := 0; i < count; i++ {
|
for range count {
|
||||||
s.putCell(blank)
|
s.putCell(blank)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -839,12 +891,13 @@ func (s *Screen) clearBlank() *Cell {
|
|||||||
// insertCells inserts the count cells pointed by the given line at the current
|
// insertCells inserts the count cells pointed by the given line at the current
|
||||||
// cursor position.
|
// cursor position.
|
||||||
func (s *Screen) insertCells(line Line, count int) {
|
func (s *Screen) insertCells(line Line, count int) {
|
||||||
if s.xtermLike {
|
supportsICH := s.caps.Contains(capICH)
|
||||||
|
if supportsICH {
|
||||||
// Use [ansi.ICH] as an optimization.
|
// Use [ansi.ICH] as an optimization.
|
||||||
s.buf.WriteString(ansi.InsertCharacter(count)) //nolint:errcheck
|
s.buf.WriteString(ansi.InsertCharacter(count))
|
||||||
} else {
|
} else {
|
||||||
// Otherwise, use [ansi.IRM] mode.
|
// Otherwise, use [ansi.IRM] mode.
|
||||||
s.buf.WriteString(ansi.SetInsertReplaceMode) //nolint:errcheck
|
s.buf.WriteString(ansi.SetModeInsertReplace)
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := 0; count > 0; i++ {
|
for i := 0; count > 0; i++ {
|
||||||
@ -852,8 +905,8 @@ func (s *Screen) insertCells(line Line, count int) {
|
|||||||
count--
|
count--
|
||||||
}
|
}
|
||||||
|
|
||||||
if !s.xtermLike {
|
if !supportsICH {
|
||||||
s.buf.WriteString(ansi.ResetInsertReplaceMode) //nolint:errcheck
|
s.buf.WriteString(ansi.ResetModeInsertReplace)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -862,7 +915,7 @@ func (s *Screen) insertCells(line Line, count int) {
|
|||||||
// [ansi.EL] 0 i.e. [ansi.EraseLineRight] to clear
|
// [ansi.EL] 0 i.e. [ansi.EraseLineRight] to clear
|
||||||
// trailing spaces.
|
// trailing spaces.
|
||||||
func (s *Screen) el0Cost() int {
|
func (s *Screen) el0Cost() int {
|
||||||
if s.xtermLike {
|
if s.caps != noCaps {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
return len(ansi.EraseLineRight)
|
return len(ansi.EraseLineRight)
|
||||||
@ -878,7 +931,7 @@ func (s *Screen) transformLine(y int) {
|
|||||||
|
|
||||||
// Find the first changed cell in the line
|
// Find the first changed cell in the line
|
||||||
var lineChanged bool
|
var lineChanged bool
|
||||||
for i := 0; i < s.newbuf.Width(); i++ {
|
for i := range s.newbuf.Width() {
|
||||||
if !cellEqual(newLine.At(i), oldLine.At(i)) {
|
if !cellEqual(newLine.At(i), oldLine.At(i)) {
|
||||||
lineChanged = true
|
lineChanged = true
|
||||||
break
|
break
|
||||||
@ -886,7 +939,7 @@ func (s *Screen) transformLine(y int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const ceolStandoutGlitch = false
|
const ceolStandoutGlitch = false
|
||||||
if ceolStandoutGlitch && lineChanged {
|
if ceolStandoutGlitch && lineChanged { //nolint:nestif
|
||||||
s.move(0, y)
|
s.move(0, y)
|
||||||
s.clearToEnd(nil, false)
|
s.clearToEnd(nil, false)
|
||||||
s.putRange(oldLine, newLine, y, 0, s.newbuf.Width()-1)
|
s.putRange(oldLine, newLine, y, 0, s.newbuf.Width()-1)
|
||||||
@ -897,12 +950,12 @@ func (s *Screen) transformLine(y int) {
|
|||||||
// [ansi.EraseLineLeft].
|
// [ansi.EraseLineLeft].
|
||||||
if blank == nil || blank.Clear() {
|
if blank == nil || blank.Clear() {
|
||||||
var oFirstCell, nFirstCell int
|
var oFirstCell, nFirstCell int
|
||||||
for oFirstCell = 0; oFirstCell < s.curbuf.Width(); oFirstCell++ {
|
for oFirstCell = range s.curbuf.Width() {
|
||||||
if !cellEqual(oldLine.At(oFirstCell), blank) {
|
if !cellEqual(oldLine.At(oFirstCell), blank) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for nFirstCell = 0; nFirstCell < s.newbuf.Width(); nFirstCell++ {
|
for nFirstCell = range s.newbuf.Width() {
|
||||||
if !cellEqual(newLine.At(nFirstCell), blank) {
|
if !cellEqual(newLine.At(nFirstCell), blank) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -925,11 +978,11 @@ func (s *Screen) transformLine(y int) {
|
|||||||
if nFirstCell >= s.newbuf.Width() {
|
if nFirstCell >= s.newbuf.Width() {
|
||||||
s.move(0, y)
|
s.move(0, y)
|
||||||
s.updatePen(blank)
|
s.updatePen(blank)
|
||||||
s.buf.WriteString(ansi.EraseLineRight) //nolint:errcheck
|
s.buf.WriteString(ansi.EraseLineRight)
|
||||||
} else {
|
} else {
|
||||||
s.move(nFirstCell-1, y)
|
s.move(nFirstCell-1, y)
|
||||||
s.updatePen(blank)
|
s.updatePen(blank)
|
||||||
s.buf.WriteString(ansi.EraseLineLeft) //nolint:errcheck
|
s.buf.WriteString(ansi.EraseLineLeft)
|
||||||
}
|
}
|
||||||
|
|
||||||
for firstCell < nFirstCell {
|
for firstCell < nFirstCell {
|
||||||
@ -1045,7 +1098,7 @@ func (s *Screen) transformLine(y int) {
|
|||||||
|
|
||||||
s.move(n+1, y)
|
s.move(n+1, y)
|
||||||
ichCost := 3 + nLastCell - oLastCell
|
ichCost := 3 + nLastCell - oLastCell
|
||||||
if s.xtermLike && (nLastCell < nLastNonBlank || ichCost > (m-n)) {
|
if s.caps.Contains(capICH) && (nLastCell < nLastNonBlank || ichCost > (m-n)) {
|
||||||
s.putRange(oldLine, newLine, y, n+1, m)
|
s.putRange(oldLine, newLine, y, n+1, m)
|
||||||
} else {
|
} else {
|
||||||
s.insertCells(newLine[n+1:], nLastCell-oLastCell)
|
s.insertCells(newLine[n+1:], nLastCell-oLastCell)
|
||||||
@ -1079,7 +1132,7 @@ func (s *Screen) transformLine(y int) {
|
|||||||
func (s *Screen) deleteCells(count int) {
|
func (s *Screen) deleteCells(count int) {
|
||||||
// [ansi.DCH] will shift in cells from the right margin so we need to
|
// [ansi.DCH] will shift in cells from the right margin so we need to
|
||||||
// ensure that they are the right style.
|
// ensure that they are the right style.
|
||||||
s.buf.WriteString(ansi.DeleteCharacter(count)) //nolint:errcheck
|
s.buf.WriteString(ansi.DeleteCharacter(count))
|
||||||
}
|
}
|
||||||
|
|
||||||
// clearToBottom clears the screen from the current cursor position to the end
|
// clearToBottom clears the screen from the current cursor position to the end
|
||||||
@ -1091,7 +1144,7 @@ func (s *Screen) clearToBottom(blank *Cell) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
s.updatePen(blank)
|
s.updatePen(blank)
|
||||||
s.buf.WriteString(ansi.EraseScreenBelow) //nolint:errcheck
|
s.buf.WriteString(ansi.EraseScreenBelow)
|
||||||
// Clear the rest of the current line
|
// Clear the rest of the current line
|
||||||
s.curbuf.ClearRect(Rect(col, row, s.curbuf.Width()-col, 1))
|
s.curbuf.ClearRect(Rect(col, row, s.curbuf.Width()-col, 1))
|
||||||
// Clear everything below the current line
|
// Clear everything below the current line
|
||||||
@ -1104,7 +1157,7 @@ func (s *Screen) clearToBottom(blank *Cell) {
|
|||||||
// It returns the top line.
|
// It returns the top line.
|
||||||
func (s *Screen) clearBottom(total int) (top int) {
|
func (s *Screen) clearBottom(total int) (top int) {
|
||||||
if total <= 0 {
|
if total <= 0 {
|
||||||
return
|
return top
|
||||||
}
|
}
|
||||||
|
|
||||||
top = total
|
top = total
|
||||||
@ -1112,7 +1165,7 @@ func (s *Screen) clearBottom(total int) (top int) {
|
|||||||
blank := s.clearBlank()
|
blank := s.clearBlank()
|
||||||
canClearWithBlank := blank == nil || blank.Clear()
|
canClearWithBlank := blank == nil || blank.Clear()
|
||||||
|
|
||||||
if canClearWithBlank {
|
if canClearWithBlank { //nolint:nestif
|
||||||
var row int
|
var row int
|
||||||
for row = total - 1; row >= 0; row-- {
|
for row = total - 1; row >= 0; row-- {
|
||||||
oldLine := s.curbuf.Line(row)
|
oldLine := s.curbuf.Line(row)
|
||||||
@ -1147,14 +1200,14 @@ func (s *Screen) clearBottom(total int) (top int) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return top
|
||||||
}
|
}
|
||||||
|
|
||||||
// clearScreen clears the screen and put cursor at home.
|
// clearScreen clears the screen and put cursor at home.
|
||||||
func (s *Screen) clearScreen(blank *Cell) {
|
func (s *Screen) clearScreen(blank *Cell) {
|
||||||
s.updatePen(blank)
|
s.updatePen(blank)
|
||||||
s.buf.WriteString(ansi.CursorHomePosition) //nolint:errcheck
|
s.buf.WriteString(ansi.CursorHomePosition)
|
||||||
s.buf.WriteString(ansi.EraseEntireScreen) //nolint:errcheck
|
s.buf.WriteString(ansi.EraseEntireScreen)
|
||||||
s.cur.X, s.cur.Y = 0, 0
|
s.cur.X, s.cur.Y = 0, 0
|
||||||
s.curbuf.Fill(blank)
|
s.curbuf.Fill(blank)
|
||||||
}
|
}
|
||||||
@ -1179,7 +1232,7 @@ func (s *Screen) clearUpdate() {
|
|||||||
s.clearBelow(blank, 0)
|
s.clearBelow(blank, 0)
|
||||||
}
|
}
|
||||||
nonEmpty = s.clearBottom(nonEmpty)
|
nonEmpty = s.clearBottom(nonEmpty)
|
||||||
for i := 0; i < nonEmpty; i++ {
|
for i := range nonEmpty {
|
||||||
s.transformLine(i)
|
s.transformLine(i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1194,13 +1247,13 @@ func (s *Screen) Flush() (err error) {
|
|||||||
func (s *Screen) flush() (err error) {
|
func (s *Screen) flush() (err error) {
|
||||||
// Write the buffer
|
// Write the buffer
|
||||||
if s.buf.Len() > 0 {
|
if s.buf.Len() > 0 {
|
||||||
_, err = s.w.Write(s.buf.Bytes()) //nolint:errcheck
|
_, err = s.w.Write(s.buf.Bytes())
|
||||||
if err == nil {
|
if err == nil {
|
||||||
s.buf.Reset()
|
s.buf.Reset()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return err //nolint:wrapcheck
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render renders changes of the screen to the internal buffer. Call
|
// Render renders changes of the screen to the internal buffer. Call
|
||||||
@ -1221,6 +1274,7 @@ func (s *Screen) render() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//nolint:godox
|
||||||
// TODO: Investigate whether this is necessary. Theoretically, terminals
|
// TODO: Investigate whether this is necessary. Theoretically, terminals
|
||||||
// can add/remove tab stops and we should be able to handle that. We could
|
// can add/remove tab stops and we should be able to handle that. We could
|
||||||
// use [ansi.DECTABSR] to read the tab stops, but that's not implemented in
|
// use [ansi.DECTABSR] to read the tab stops, but that's not implemented in
|
||||||
@ -1235,9 +1289,9 @@ func (s *Screen) render() {
|
|||||||
// Do we need alt-screen mode?
|
// Do we need alt-screen mode?
|
||||||
if s.opts.AltScreen != s.altScreenMode {
|
if s.opts.AltScreen != s.altScreenMode {
|
||||||
if s.opts.AltScreen {
|
if s.opts.AltScreen {
|
||||||
s.buf.WriteString(ansi.SetAltScreenSaveCursorMode)
|
s.buf.WriteString(ansi.SetModeAltScreenSaveCursor)
|
||||||
} else {
|
} else {
|
||||||
s.buf.WriteString(ansi.ResetAltScreenSaveCursorMode)
|
s.buf.WriteString(ansi.ResetModeAltScreenSaveCursor)
|
||||||
}
|
}
|
||||||
s.altScreenMode = s.opts.AltScreen
|
s.altScreenMode = s.opts.AltScreen
|
||||||
}
|
}
|
||||||
@ -1252,7 +1306,9 @@ func (s *Screen) render() {
|
|||||||
|
|
||||||
// Do we have queued strings to write above the screen?
|
// Do we have queued strings to write above the screen?
|
||||||
if len(s.queueAbove) > 0 {
|
if len(s.queueAbove) > 0 {
|
||||||
|
//nolint:godox
|
||||||
// TODO: Use scrolling region if available.
|
// TODO: Use scrolling region if available.
|
||||||
|
//nolint:godox
|
||||||
// TODO: Use [Screen.Write] [io.Writer] interface.
|
// TODO: Use [Screen.Write] [io.Writer] interface.
|
||||||
|
|
||||||
// We need to scroll the screen up by the number of lines in the queue.
|
// We need to scroll the screen up by the number of lines in the queue.
|
||||||
@ -1290,12 +1346,13 @@ func (s *Screen) render() {
|
|||||||
s.clearBelow(nil, s.newbuf.Height()-1)
|
s.clearBelow(nil, s.newbuf.Height()-1)
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.clear {
|
if s.clear { //nolint:nestif
|
||||||
s.clearUpdate()
|
s.clearUpdate()
|
||||||
s.clear = false
|
s.clear = false
|
||||||
} else if len(s.touch) > 0 {
|
} else if len(s.touch) > 0 {
|
||||||
if s.opts.AltScreen {
|
if s.opts.AltScreen {
|
||||||
// Optimize scrolling for the alternate screen buffer.
|
// Optimize scrolling for the alternate screen buffer.
|
||||||
|
//nolint:godox
|
||||||
// TODO: Should we optimize for inline mode as well? If so, we need
|
// TODO: Should we optimize for inline mode as well? If so, we need
|
||||||
// to know the actual cursor position to use [ansi.DECSTBM].
|
// to know the actual cursor position to use [ansi.DECSTBM].
|
||||||
s.scrollOptimize()
|
s.scrollOptimize()
|
||||||
@ -1311,7 +1368,7 @@ func (s *Screen) render() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
nonEmpty = s.clearBottom(nonEmpty)
|
nonEmpty = s.clearBottom(nonEmpty)
|
||||||
for i = 0; i < nonEmpty; i++ {
|
for i = range nonEmpty {
|
||||||
_, ok := s.touch[i]
|
_, ok := s.touch[i]
|
||||||
if ok {
|
if ok {
|
||||||
s.transformLine(i)
|
s.transformLine(i)
|
||||||
@ -1359,7 +1416,7 @@ func (s *Screen) Close() (err error) {
|
|||||||
s.move(0, s.newbuf.Height()-1)
|
s.move(0, s.newbuf.Height()-1)
|
||||||
|
|
||||||
if s.altScreenMode {
|
if s.altScreenMode {
|
||||||
s.buf.WriteString(ansi.ResetAltScreenSaveCursorMode)
|
s.buf.WriteString(ansi.ResetModeAltScreenSaveCursor)
|
||||||
s.altScreenMode = false
|
s.altScreenMode = false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1371,11 +1428,11 @@ func (s *Screen) Close() (err error) {
|
|||||||
// Write the buffer
|
// Write the buffer
|
||||||
err = s.flush()
|
err = s.flush()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
s.reset()
|
s.reset()
|
||||||
return
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// reset resets the screen to its initial state.
|
// reset resets the screen to its initial state.
|
||||||
@ -1420,9 +1477,9 @@ func (s *Screen) Resize(width, height int) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if height > oldh {
|
if height > oldh {
|
||||||
s.ClearRect(Rect(0, max(oldh-1, 0), width, height-oldh))
|
s.ClearRect(Rect(0, max(oldh, 0), width, height-oldh))
|
||||||
} else if height < oldh {
|
} else if height < oldh {
|
||||||
s.ClearRect(Rect(0, max(height-1, 0), width, oldh-height))
|
s.ClearRect(Rect(0, max(height, 0), width, oldh-height))
|
||||||
}
|
}
|
||||||
|
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
|
|||||||
4
vendor/github.com/charmbracelet/x/cellbuf/style.go
generated
vendored
4
vendor/github.com/charmbracelet/x/cellbuf/style.go
generated
vendored
@ -4,9 +4,9 @@ import (
|
|||||||
"github.com/charmbracelet/colorprofile"
|
"github.com/charmbracelet/colorprofile"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Convert converts a style to respect the given color profile.
|
// ConvertStyle converts a style to respect the given color profile.
|
||||||
func ConvertStyle(s Style, p colorprofile.Profile) Style {
|
func ConvertStyle(s Style, p colorprofile.Profile) Style {
|
||||||
switch p {
|
switch p { //nolint:exhaustive
|
||||||
case colorprofile.TrueColor:
|
case colorprofile.TrueColor:
|
||||||
return s
|
return s
|
||||||
case colorprofile.Ascii:
|
case colorprofile.Ascii:
|
||||||
|
|||||||
14
vendor/github.com/charmbracelet/x/cellbuf/utils.go
generated
vendored
14
vendor/github.com/charmbracelet/x/cellbuf/utils.go
generated
vendored
@ -9,20 +9,6 @@ func Height(s string) int {
|
|||||||
return strings.Count(s, "\n") + 1
|
return strings.Count(s, "\n") + 1
|
||||||
}
|
}
|
||||||
|
|
||||||
func min(a, b int) int { //nolint:predeclared
|
|
||||||
if a > b {
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
return a
|
|
||||||
}
|
|
||||||
|
|
||||||
func max(a, b int) int { //nolint:predeclared
|
|
||||||
if a > b {
|
|
||||||
return a
|
|
||||||
}
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
func clamp(v, low, high int) int {
|
func clamp(v, low, high int) int {
|
||||||
if high < low {
|
if high < low {
|
||||||
low, high = high, low
|
low, high = high, low
|
||||||
|
|||||||
20
vendor/github.com/charmbracelet/x/cellbuf/wrap.go
generated
vendored
20
vendor/github.com/charmbracelet/x/cellbuf/wrap.go
generated
vendored
@ -2,6 +2,7 @@ package cellbuf
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"slices"
|
||||||
"unicode"
|
"unicode"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
@ -20,6 +21,16 @@ const nbsp = '\u00a0'
|
|||||||
//
|
//
|
||||||
// Note: breakpoints must be a string of 1-cell wide rune characters.
|
// Note: breakpoints must be a string of 1-cell wide rune characters.
|
||||||
func Wrap(s string, limit int, breakpoints string) string {
|
func Wrap(s string, limit int, breakpoints string) string {
|
||||||
|
//nolint:godox
|
||||||
|
// TODO: Use [PenWriter] once we get
|
||||||
|
// https://github.com/charmbracelet/lipgloss/pull/489 out the door and
|
||||||
|
// released.
|
||||||
|
// The problem is that [ansi.Wrap] doesn't keep track of style and link
|
||||||
|
// state, so combining both breaks styled space cells. To fix this, we use
|
||||||
|
// non-breaking space cells for padding and styled blank cells. And since
|
||||||
|
// both wrapping methods respect non-breaking spaces, we can use them to
|
||||||
|
// preserve styled spaces in the output.
|
||||||
|
|
||||||
if len(s) == 0 {
|
if len(s) == 0 {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
@ -90,7 +101,7 @@ func Wrap(s string, limit int, breakpoints string) string {
|
|||||||
seq, width, n, newState := ansi.DecodeSequence(s, state, p)
|
seq, width, n, newState := ansi.DecodeSequence(s, state, p)
|
||||||
switch width {
|
switch width {
|
||||||
case 0:
|
case 0:
|
||||||
if ansi.Equal(seq, "\t") {
|
if ansi.Equal(seq, "\t") { //nolint:nestif
|
||||||
addWord()
|
addWord()
|
||||||
space.WriteString(seq)
|
space.WriteString(seq)
|
||||||
break
|
break
|
||||||
@ -176,10 +187,5 @@ func Wrap(s string, limit int, breakpoints string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func runeContainsAny[T string | []rune](r rune, s T) bool {
|
func runeContainsAny[T string | []rune](r rune, s T) bool {
|
||||||
for _, c := range []rune(s) {
|
return slices.Contains([]rune(s), r)
|
||||||
if c == r {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|||||||
29
vendor/github.com/charmbracelet/x/cellbuf/writer.go
generated
vendored
29
vendor/github.com/charmbracelet/x/cellbuf/writer.go
generated
vendored
@ -25,7 +25,7 @@ type CellBuffer interface {
|
|||||||
func FillRect(s CellBuffer, c *Cell, rect Rectangle) {
|
func FillRect(s CellBuffer, c *Cell, rect Rectangle) {
|
||||||
for y := rect.Min.Y; y < rect.Max.Y; y++ {
|
for y := rect.Min.Y; y < rect.Max.Y; y++ {
|
||||||
for x := rect.Min.X; x < rect.Max.X; x++ {
|
for x := rect.Min.X; x < rect.Max.X; x++ {
|
||||||
s.SetCell(x, y, c) //nolint:errcheck
|
s.SetCell(x, y, c)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -68,7 +68,7 @@ func SetContent(s CellBuffer, str string) {
|
|||||||
func Render(d CellBuffer) string {
|
func Render(d CellBuffer) string {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
height := d.Bounds().Dy()
|
height := d.Bounds().Dy()
|
||||||
for y := 0; y < height; y++ {
|
for y := range height {
|
||||||
_, line := RenderLine(d, y)
|
_, line := RenderLine(d, y)
|
||||||
buf.WriteString(line)
|
buf.WriteString(line)
|
||||||
if y < height-1 {
|
if y < height-1 {
|
||||||
@ -98,32 +98,32 @@ func RenderLine(d CellBuffer, n int) (w int, line string) {
|
|||||||
pendingLine = ""
|
pendingLine = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
for x := 0; x < d.Bounds().Dx(); x++ {
|
for x := range d.Bounds().Dx() {
|
||||||
if cell := d.Cell(x, n); cell != nil && cell.Width > 0 {
|
if cell := d.Cell(x, n); cell != nil && cell.Width > 0 { //nolint:nestif
|
||||||
// Convert the cell's style and link to the given color profile.
|
// Convert the cell's style and link to the given color profile.
|
||||||
cellStyle := cell.Style
|
cellStyle := cell.Style
|
||||||
cellLink := cell.Link
|
cellLink := cell.Link
|
||||||
if cellStyle.Empty() && !pen.Empty() {
|
if cellStyle.Empty() && !pen.Empty() {
|
||||||
writePending()
|
writePending()
|
||||||
buf.WriteString(ansi.ResetStyle) //nolint:errcheck
|
buf.WriteString(ansi.ResetStyle)
|
||||||
pen.Reset()
|
pen.Reset()
|
||||||
}
|
}
|
||||||
if !cellStyle.Equal(&pen) {
|
if !cellStyle.Equal(&pen) {
|
||||||
writePending()
|
writePending()
|
||||||
seq := cellStyle.DiffSequence(pen)
|
seq := cellStyle.DiffSequence(pen)
|
||||||
buf.WriteString(seq) // nolint:errcheck
|
buf.WriteString(seq)
|
||||||
pen = cellStyle
|
pen = cellStyle
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write the URL escape sequence
|
// Write the URL escape sequence
|
||||||
if cellLink != link && link.URL != "" {
|
if cellLink != link && link.URL != "" {
|
||||||
writePending()
|
writePending()
|
||||||
buf.WriteString(ansi.ResetHyperlink()) //nolint:errcheck
|
buf.WriteString(ansi.ResetHyperlink())
|
||||||
link.Reset()
|
link.Reset()
|
||||||
}
|
}
|
||||||
if cellLink != link {
|
if cellLink != link {
|
||||||
writePending()
|
writePending()
|
||||||
buf.WriteString(ansi.SetHyperlink(cellLink.URL, cellLink.Params)) //nolint:errcheck
|
buf.WriteString(ansi.SetHyperlink(cellLink.URL, cellLink.Params))
|
||||||
link = cellLink
|
link = cellLink
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,10 +140,10 @@ func RenderLine(d CellBuffer, n int) (w int, line string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if link.URL != "" {
|
if link.URL != "" {
|
||||||
buf.WriteString(ansi.ResetHyperlink()) //nolint:errcheck
|
buf.WriteString(ansi.ResetHyperlink())
|
||||||
}
|
}
|
||||||
if !pen.Empty() {
|
if !pen.Empty() {
|
||||||
buf.WriteString(ansi.ResetStyle) //nolint:errcheck
|
buf.WriteString(ansi.ResetStyle)
|
||||||
}
|
}
|
||||||
return w, strings.TrimRight(buf.String(), " ") // Trim trailing spaces
|
return w, strings.TrimRight(buf.String(), " ") // Trim trailing spaces
|
||||||
}
|
}
|
||||||
@ -201,7 +201,7 @@ func (s *ScreenWriter) SetContentRect(str string, rect Rectangle) {
|
|||||||
// string to the width of the screen if it exceeds the width of the screen.
|
// string to the width of the screen if it exceeds the width of the screen.
|
||||||
// This will recognize ANSI [ansi.SGR] style and [ansi.SetHyperlink] escape
|
// This will recognize ANSI [ansi.SGR] style and [ansi.SetHyperlink] escape
|
||||||
// sequences.
|
// sequences.
|
||||||
func (s *ScreenWriter) Print(str string, v ...interface{}) {
|
func (s *ScreenWriter) Print(str string, v ...any) {
|
||||||
if len(v) > 0 {
|
if len(v) > 0 {
|
||||||
str = fmt.Sprintf(str, v...)
|
str = fmt.Sprintf(str, v...)
|
||||||
}
|
}
|
||||||
@ -214,7 +214,7 @@ func (s *ScreenWriter) Print(str string, v ...interface{}) {
|
|||||||
// the width of the screen if it exceeds the width of the screen.
|
// the width of the screen if it exceeds the width of the screen.
|
||||||
// This will recognize ANSI [ansi.SGR] style and [ansi.SetHyperlink] escape
|
// This will recognize ANSI [ansi.SGR] style and [ansi.SetHyperlink] escape
|
||||||
// sequences.
|
// sequences.
|
||||||
func (s *ScreenWriter) PrintAt(x, y int, str string, v ...interface{}) {
|
func (s *ScreenWriter) PrintAt(x, y int, str string, v ...any) {
|
||||||
if len(v) > 0 {
|
if len(v) > 0 {
|
||||||
str = fmt.Sprintf(str, v...)
|
str = fmt.Sprintf(str, v...)
|
||||||
}
|
}
|
||||||
@ -299,7 +299,7 @@ func printString[T []byte | string](
|
|||||||
// Print the cell to the screen
|
// Print the cell to the screen
|
||||||
cell.Style = style
|
cell.Style = style
|
||||||
cell.Link = link
|
cell.Link = link
|
||||||
s.SetCell(x, y, &cell) //nolint:errcheck
|
s.SetCell(x, y, &cell)
|
||||||
x += width
|
x += width
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -309,6 +309,7 @@ func printString[T []byte | string](
|
|||||||
cell.Reset()
|
cell.Reset()
|
||||||
default:
|
default:
|
||||||
// Valid sequences always have a non-zero Cmd.
|
// Valid sequences always have a non-zero Cmd.
|
||||||
|
//nolint:godox
|
||||||
// TODO: Handle cursor movement and other sequences
|
// TODO: Handle cursor movement and other sequences
|
||||||
switch {
|
switch {
|
||||||
case ansi.HasCsiPrefix(seq) && p.Command() == 'm':
|
case ansi.HasCsiPrefix(seq) && p.Command() == 'm':
|
||||||
@ -333,7 +334,7 @@ func printString[T []byte | string](
|
|||||||
|
|
||||||
// Make sure to set the last cell if it's not empty.
|
// Make sure to set the last cell if it's not empty.
|
||||||
if !cell.Empty() {
|
if !cell.Empty() {
|
||||||
s.SetCell(x, y, &cell) //nolint:errcheck
|
s.SetCell(x, y, &cell)
|
||||||
cell.Reset()
|
cell.Reset()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
2
vendor/github.com/charmbracelet/x/term/term.go
generated
vendored
2
vendor/github.com/charmbracelet/x/term/term.go
generated
vendored
@ -1,3 +1,5 @@
|
|||||||
|
// Package term provides a platform-independent interfaces for interacting with
|
||||||
|
// Terminal and TTY devices.
|
||||||
package term
|
package term
|
||||||
|
|
||||||
// State contains platform-specific state of a terminal.
|
// State contains platform-specific state of a terminal.
|
||||||
|
|||||||
118
vendor/github.com/charmbracelet/x/term/term_plan9.go
generated
vendored
Normal file
118
vendor/github.com/charmbracelet/x/term/term_plan9.go
generated
vendored
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
package term
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type state struct {
|
||||||
|
termName string
|
||||||
|
raw bool
|
||||||
|
ctl *os.File
|
||||||
|
}
|
||||||
|
|
||||||
|
// termName returns the name of the terminal or os.ErrNotExist if there is no terminal.
|
||||||
|
func termName(fd uintptr) (string, error) {
|
||||||
|
ctl, err := os.ReadFile(filepath.Join("/fd", fmt.Sprintf("%dctl", fd)))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
f := strings.Fields(string(ctl))
|
||||||
|
if len(f) == 0 {
|
||||||
|
return "", os.ErrNotExist
|
||||||
|
}
|
||||||
|
return f[len(f)-1], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isTerminal(fd uintptr) bool {
|
||||||
|
ctl, err := os.ReadFile(filepath.Join("/fd", fmt.Sprintf("%dctl", fd)))
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if strings.Contains(string(ctl), "/dev/cons") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeRaw(fd uintptr) (*State, error) {
|
||||||
|
t, err := termName(fd)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ctl, err := os.OpenFile(t, os.O_RDWR, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if _, err := ctl.Write([]byte("rawon")); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &State{state: state{termName: t, raw: true, ctl: ctl}}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getState(fd uintptr) (*State, error) {
|
||||||
|
t, err := termName(fd)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ctl, err := os.OpenFile(t, os.O_RDWR, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &State{state: state{termName: t, raw: false, ctl: ctl}}, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func restore(_ uintptr, state *State) error {
|
||||||
|
if _, err := state.ctl.Write([]byte("rawoff")); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getSize returns the size. This will only work if you are running
|
||||||
|
// under a window manager in Plan 9. Else, the only option
|
||||||
|
// is to return a reasonable default.
|
||||||
|
func getSize(fd uintptr) (int, int, error) {
|
||||||
|
w, h := 80, 40
|
||||||
|
b, err := os.ReadFile("/dev/wctl")
|
||||||
|
if err != nil {
|
||||||
|
return w, h, err
|
||||||
|
}
|
||||||
|
f := strings.Fields(string(b))
|
||||||
|
if len(f) != 4 {
|
||||||
|
return w, h, fmt.Errorf("%q only has %d of 4 needed fields:%w", f, len(f), os.ErrInvalid)
|
||||||
|
}
|
||||||
|
// The contents of wctl, as defined in the driver, are
|
||||||
|
// 4 12-char fields: upper left x, y; and lower-right x, y
|
||||||
|
var ulx, uly, lrx, lry int
|
||||||
|
if n, err := fmt.Sscanf(string(b[:48]), "%d%d%d%d", &ulx, &uly, &lrx, &lry); n != 4 || err != nil {
|
||||||
|
return w, h, fmt.Errorf("scanning %q:%d of 4 items scanned:%w", string(b[:48]), n, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
w, h = lrx-lrx, lry-uly
|
||||||
|
return w, h, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setState(_ uintptr, state *State) error {
|
||||||
|
raw := "rawoff"
|
||||||
|
if state.raw {
|
||||||
|
raw = "rawon"
|
||||||
|
}
|
||||||
|
if _, err := state.ctl.Write([]byte(raw)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func readPassword(fd uintptr) ([]byte, error) {
|
||||||
|
f := os.NewFile(fd, "cons")
|
||||||
|
var b [128]byte
|
||||||
|
n, err := f.Read(b[:])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return b[:n], nil
|
||||||
|
}
|
||||||
20
vendor/github.com/charmbracelet/x/term/term_unix.go
generated
vendored
20
vendor/github.com/charmbracelet/x/term/term_unix.go
generated
vendored
@ -19,7 +19,7 @@ func isTerminal(fd uintptr) bool {
|
|||||||
func makeRaw(fd uintptr) (*State, error) {
|
func makeRaw(fd uintptr) (*State, error) {
|
||||||
termios, err := unix.IoctlGetTermios(int(fd), ioctlReadTermios)
|
termios, err := unix.IoctlGetTermios(int(fd), ioctlReadTermios)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err //nolint:wrapcheck
|
||||||
}
|
}
|
||||||
|
|
||||||
oldState := State{state{Termios: *termios}}
|
oldState := State{state{Termios: *termios}}
|
||||||
@ -34,7 +34,7 @@ func makeRaw(fd uintptr) (*State, error) {
|
|||||||
termios.Cc[unix.VMIN] = 1
|
termios.Cc[unix.VMIN] = 1
|
||||||
termios.Cc[unix.VTIME] = 0
|
termios.Cc[unix.VTIME] = 0
|
||||||
if err := unix.IoctlSetTermios(int(fd), ioctlWriteTermios, termios); err != nil {
|
if err := unix.IoctlSetTermios(int(fd), ioctlWriteTermios, termios); err != nil {
|
||||||
return nil, err
|
return nil, err //nolint:wrapcheck
|
||||||
}
|
}
|
||||||
|
|
||||||
return &oldState, nil
|
return &oldState, nil
|
||||||
@ -45,26 +45,26 @@ func setState(fd uintptr, state *State) error {
|
|||||||
if state != nil {
|
if state != nil {
|
||||||
termios = &state.Termios
|
termios = &state.Termios
|
||||||
}
|
}
|
||||||
return unix.IoctlSetTermios(int(fd), ioctlWriteTermios, termios)
|
return unix.IoctlSetTermios(int(fd), ioctlWriteTermios, termios) //nolint:wrapcheck
|
||||||
}
|
}
|
||||||
|
|
||||||
func getState(fd uintptr) (*State, error) {
|
func getState(fd uintptr) (*State, error) {
|
||||||
termios, err := unix.IoctlGetTermios(int(fd), ioctlReadTermios)
|
termios, err := unix.IoctlGetTermios(int(fd), ioctlReadTermios)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err //nolint:wrapcheck
|
||||||
}
|
}
|
||||||
|
|
||||||
return &State{state{Termios: *termios}}, nil
|
return &State{state{Termios: *termios}}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func restore(fd uintptr, state *State) error {
|
func restore(fd uintptr, state *State) error {
|
||||||
return unix.IoctlSetTermios(int(fd), ioctlWriteTermios, &state.Termios)
|
return unix.IoctlSetTermios(int(fd), ioctlWriteTermios, &state.Termios) //nolint:wrapcheck
|
||||||
}
|
}
|
||||||
|
|
||||||
func getSize(fd uintptr) (width, height int, err error) {
|
func getSize(fd uintptr) (width, height int, err error) {
|
||||||
ws, err := unix.IoctlGetWinsize(int(fd), unix.TIOCGWINSZ)
|
ws, err := unix.IoctlGetWinsize(int(fd), unix.TIOCGWINSZ)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, 0, err
|
return 0, 0, err //nolint:wrapcheck
|
||||||
}
|
}
|
||||||
return int(ws.Col), int(ws.Row), nil
|
return int(ws.Col), int(ws.Row), nil
|
||||||
}
|
}
|
||||||
@ -73,13 +73,13 @@ func getSize(fd uintptr) (width, height int, err error) {
|
|||||||
type passwordReader int
|
type passwordReader int
|
||||||
|
|
||||||
func (r passwordReader) Read(buf []byte) (int, error) {
|
func (r passwordReader) Read(buf []byte) (int, error) {
|
||||||
return unix.Read(int(r), buf)
|
return unix.Read(int(r), buf) //nolint:wrapcheck
|
||||||
}
|
}
|
||||||
|
|
||||||
func readPassword(fd uintptr) ([]byte, error) {
|
func readPassword(fd uintptr) ([]byte, error) {
|
||||||
termios, err := unix.IoctlGetTermios(int(fd), ioctlReadTermios)
|
termios, err := unix.IoctlGetTermios(int(fd), ioctlReadTermios)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err //nolint:wrapcheck
|
||||||
}
|
}
|
||||||
|
|
||||||
newState := *termios
|
newState := *termios
|
||||||
@ -87,10 +87,10 @@ func readPassword(fd uintptr) ([]byte, error) {
|
|||||||
newState.Lflag |= unix.ICANON | unix.ISIG
|
newState.Lflag |= unix.ICANON | unix.ISIG
|
||||||
newState.Iflag |= unix.ICRNL
|
newState.Iflag |= unix.ICRNL
|
||||||
if err := unix.IoctlSetTermios(int(fd), ioctlWriteTermios, &newState); err != nil {
|
if err := unix.IoctlSetTermios(int(fd), ioctlWriteTermios, &newState); err != nil {
|
||||||
return nil, err
|
return nil, err //nolint:wrapcheck
|
||||||
}
|
}
|
||||||
|
|
||||||
defer unix.IoctlSetTermios(int(fd), ioctlWriteTermios, termios)
|
defer unix.IoctlSetTermios(int(fd), ioctlWriteTermios, termios) //nolint:errcheck
|
||||||
|
|
||||||
return readPasswordLine(passwordReader(fd))
|
return readPasswordLine(passwordReader(fd))
|
||||||
}
|
}
|
||||||
|
|||||||
2
vendor/github.com/charmbracelet/x/term/term_windows.go
generated
vendored
2
vendor/github.com/charmbracelet/x/term/term_windows.go
generated
vendored
@ -24,7 +24,7 @@ func makeRaw(fd uintptr) (*State, error) {
|
|||||||
if err := windows.GetConsoleMode(windows.Handle(fd), &st); err != nil {
|
if err := windows.GetConsoleMode(windows.Handle(fd), &st); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
raw := st &^ (windows.ENABLE_ECHO_INPUT | windows.ENABLE_PROCESSED_INPUT | windows.ENABLE_LINE_INPUT | windows.ENABLE_PROCESSED_OUTPUT)
|
raw := st &^ (windows.ENABLE_ECHO_INPUT | windows.ENABLE_PROCESSED_INPUT | windows.ENABLE_LINE_INPUT)
|
||||||
raw |= windows.ENABLE_VIRTUAL_TERMINAL_INPUT
|
raw |= windows.ENABLE_VIRTUAL_TERMINAL_INPUT
|
||||||
if err := windows.SetConsoleMode(windows.Handle(fd), raw); err != nil {
|
if err := windows.SetConsoleMode(windows.Handle(fd), raw); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
2
vendor/github.com/charmbracelet/x/term/util.go
generated
vendored
2
vendor/github.com/charmbracelet/x/term/util.go
generated
vendored
@ -41,7 +41,7 @@ func readPasswordLine(reader io.Reader) ([]byte, error) {
|
|||||||
if err == io.EOF && len(ret) > 0 {
|
if err == io.EOF && len(ret) > 0 {
|
||||||
return ret, nil
|
return ret, nil
|
||||||
}
|
}
|
||||||
return ret, err
|
return ret, err //nolint:wrapcheck
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
1
vendor/github.com/clipperhouse/displaywidth/.gitignore
generated
vendored
Normal file
1
vendor/github.com/clipperhouse/displaywidth/.gitignore
generated
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
.DS_Store
|
||||||
37
vendor/github.com/clipperhouse/displaywidth/AGENTS.md
generated
vendored
Normal file
37
vendor/github.com/clipperhouse/displaywidth/AGENTS.md
generated
vendored
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
The goals and overview of this package can be found in the README.md file,
|
||||||
|
start by reading that.
|
||||||
|
|
||||||
|
The goal of this package is to determine the display (column) width of a
|
||||||
|
string, UTF-8 bytes, or runes, as would happen in a monospace font, especially
|
||||||
|
in a terminal.
|
||||||
|
|
||||||
|
When troubleshooting, write Go unit tests instead of executing debug scripts.
|
||||||
|
The tests can return whatever logs or output you need. If those tests are
|
||||||
|
only for temporary troubleshooting, clean up the tests after the debugging is
|
||||||
|
done.
|
||||||
|
|
||||||
|
(Separate executable debugging scripts are messy, tend to have conflicting
|
||||||
|
dependencies and are hard to cleanup.)
|
||||||
|
|
||||||
|
If you make changes to the trie generation in internal/gen, it can be invoked
|
||||||
|
by running `go generate` from the top package directory.
|
||||||
|
|
||||||
|
## Pull Requests and branches
|
||||||
|
|
||||||
|
For PRs (pull requests), you can use the gh CLI tool to retrieve details,
|
||||||
|
or post comments. Then, compare the current branch with main. Reviewing a PR
|
||||||
|
and reviewing a branch are about the same, but the PR may add context.
|
||||||
|
|
||||||
|
Look for bugs. Think like GitHub Copilot or Cursor BugBot.
|
||||||
|
|
||||||
|
Offer to post a brief summary of the review to the PR, via the gh CLI tool.
|
||||||
|
|
||||||
|
## Comparisons to go-runewidth
|
||||||
|
|
||||||
|
We originally attempted to make this package compatible with go-runewidth.
|
||||||
|
However, we found that there were too many differences in the handling of
|
||||||
|
certain characters and properties.
|
||||||
|
|
||||||
|
We believe, preliminarily, that our choices are more correct and complete,
|
||||||
|
by using more complete categories such as Unicode Cf (format) for zero-width
|
||||||
|
and Mn (Nonspacing_Mark) for combining marks.
|
||||||
49
vendor/github.com/clipperhouse/displaywidth/CHANGELOG.md
generated
vendored
Normal file
49
vendor/github.com/clipperhouse/displaywidth/CHANGELOG.md
generated
vendored
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
## [0.5.0]
|
||||||
|
|
||||||
|
[Compare](https://github.com/clipperhouse/displaywidth/compare/v0.4.1...v0.5.0)
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Unicode 16 support
|
||||||
|
- Improved emoji presentation handling per Unicode TR51
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Corrected VS15 (U+FE0E) handling: now preserves base character width (no-op) per Unicode TR51
|
||||||
|
- Performance optimizations: reduced property lookups
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- VS15 variation selector now correctly preserves base character width instead of forcing width 1
|
||||||
|
|
||||||
|
## [0.4.1]
|
||||||
|
|
||||||
|
[Compare](https://github.com/clipperhouse/displaywidth/compare/v0.4.0...v0.4.1)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Updated uax29 dependency
|
||||||
|
- Improved flag handling
|
||||||
|
|
||||||
|
## [0.4.0]
|
||||||
|
|
||||||
|
[Compare](https://github.com/clipperhouse/displaywidth/compare/v0.3.1...v0.4.0)
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Support for variation selectors (VS15, VS16) and regional indicator pairs (flags)
|
||||||
|
|
||||||
|
## [0.3.1]
|
||||||
|
|
||||||
|
[Compare](https://github.com/clipperhouse/displaywidth/compare/v0.3.0...v0.3.1)
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Fuzz testing support
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Updated stringish dependency
|
||||||
|
|
||||||
|
## [0.3.0]
|
||||||
|
|
||||||
|
[Compare](https://github.com/clipperhouse/displaywidth/compare/v0.2.0...v0.3.0)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Dropped compatibility with go-runewidth
|
||||||
|
- Trie implementation cleanup
|
||||||
21
vendor/github.com/clipperhouse/displaywidth/LICENSE
generated
vendored
Normal file
21
vendor/github.com/clipperhouse/displaywidth/LICENSE
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2025 Matt Sherman
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
123
vendor/github.com/clipperhouse/displaywidth/README.md
generated
vendored
Normal file
123
vendor/github.com/clipperhouse/displaywidth/README.md
generated
vendored
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
# displaywidth
|
||||||
|
|
||||||
|
A high-performance Go package for measuring the monospace display width of strings, UTF-8 bytes, and runes.
|
||||||
|
|
||||||
|
[](https://pkg.go.dev/github.com/clipperhouse/displaywidth)
|
||||||
|
[](https://github.com/clipperhouse/displaywidth/actions/workflows/gotest.yml)
|
||||||
|
[](https://github.com/clipperhouse/displaywidth/actions/workflows/gofuzz.yml)
|
||||||
|
|
||||||
|
## Install
|
||||||
|
```bash
|
||||||
|
go get github.com/clipperhouse/displaywidth
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/clipperhouse/displaywidth"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
width := displaywidth.String("Hello, 世界!")
|
||||||
|
fmt.Println(width)
|
||||||
|
|
||||||
|
width = displaywidth.Bytes([]byte("🌍"))
|
||||||
|
fmt.Println(width)
|
||||||
|
|
||||||
|
width = displaywidth.Rune('🌍')
|
||||||
|
fmt.Println(width)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
For most purposes, you should use the `String` or `Bytes` methods.
|
||||||
|
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
You can specify East Asian Width settings. When false (default),
|
||||||
|
[East Asian Ambiguous characters](https://www.unicode.org/reports/tr11/#Ambiguous)
|
||||||
|
are treated as width 1. When true, East Asian Ambiguous characters are treated
|
||||||
|
as width 2.
|
||||||
|
|
||||||
|
```go
|
||||||
|
myOptions := displaywidth.Options{
|
||||||
|
EastAsianWidth: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
width := myOptions.String("Hello, 世界!")
|
||||||
|
fmt.Println(width)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Technical details
|
||||||
|
|
||||||
|
This package implements the Unicode East Asian Width standard
|
||||||
|
([UAX #11](https://www.unicode.org/reports/tr11/)), and handles
|
||||||
|
[version selectors](https://en.wikipedia.org/wiki/Variation_Selectors_(Unicode_block)),
|
||||||
|
and [regional indicator pairs](https://en.wikipedia.org/wiki/Regional_indicator_symbol)
|
||||||
|
(flags). We implement [Unicode TR51](https://unicode.org/reports/tr51/).
|
||||||
|
|
||||||
|
`clipperhouse/displaywidth`, `mattn/go-runewidth`, and `rivo/uniseg` will
|
||||||
|
give the same outputs for most real-world text. See extensive details in the
|
||||||
|
[compatibility analysis](comparison/COMPATIBILITY_ANALYSIS.md).
|
||||||
|
|
||||||
|
If you wish to investigate the core logic, see the `lookupProperties` and `width`
|
||||||
|
functions in [width.go](width.go#L135). The essential trie generation logic is in
|
||||||
|
`buildPropertyBitmap` in [unicode.go](internal/gen/unicode.go#L317).
|
||||||
|
|
||||||
|
I (@clipperhouse) am keeping an eye on [emerging standards and test suites](https://www.jeffquast.com/post/state-of-terminal-emulation-2025/).
|
||||||
|
|
||||||
|
## Prior Art
|
||||||
|
|
||||||
|
[mattn/go-runewidth](https://github.com/mattn/go-runewidth)
|
||||||
|
|
||||||
|
[rivo/uniseg](https://github.com/rivo/uniseg)
|
||||||
|
|
||||||
|
[x/text/width](https://pkg.go.dev/golang.org/x/text/width)
|
||||||
|
|
||||||
|
[x/text/internal/triegen](https://pkg.go.dev/golang.org/x/text/internal/triegen)
|
||||||
|
|
||||||
|
## Benchmarks
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd comparison
|
||||||
|
go test -bench=. -benchmem
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
goos: darwin
|
||||||
|
goarch: arm64
|
||||||
|
pkg: github.com/clipperhouse/displaywidth/comparison
|
||||||
|
cpu: Apple M2
|
||||||
|
|
||||||
|
BenchmarkString_Mixed/clipperhouse/displaywidth-8 10929 ns/op 154.36 MB/s 0 B/op 0 allocs/op
|
||||||
|
BenchmarkString_Mixed/mattn/go-runewidth-8 14540 ns/op 116.02 MB/s 0 B/op 0 allocs/op
|
||||||
|
BenchmarkString_Mixed/rivo/uniseg-8 19751 ns/op 85.41 MB/s 0 B/op 0 allocs/op
|
||||||
|
|
||||||
|
BenchmarkString_EastAsian/clipperhouse/displaywidth-8 10885 ns/op 154.98 MB/s 0 B/op 0 allocs/op
|
||||||
|
BenchmarkString_EastAsian/mattn/go-runewidth-8 23969 ns/op 70.38 MB/s 0 B/op 0 allocs/op
|
||||||
|
BenchmarkString_EastAsian/rivo/uniseg-8 19852 ns/op 84.98 MB/s 0 B/op 0 allocs/op
|
||||||
|
|
||||||
|
BenchmarkString_ASCII/clipperhouse/displaywidth-8 1103 ns/op 116.01 MB/s 0 B/op 0 allocs/op
|
||||||
|
BenchmarkString_ASCII/mattn/go-runewidth-8 1166 ns/op 109.79 MB/s 0 B/op 0 allocs/op
|
||||||
|
BenchmarkString_ASCII/rivo/uniseg-8 1584 ns/op 80.83 MB/s 0 B/op 0 allocs/op
|
||||||
|
|
||||||
|
BenchmarkString_Emoji/clipperhouse/displaywidth-8 3108 ns/op 232.93 MB/s 0 B/op 0 allocs/op
|
||||||
|
BenchmarkString_Emoji/mattn/go-runewidth-8 4802 ns/op 150.76 MB/s 0 B/op 0 allocs/op
|
||||||
|
BenchmarkString_Emoji/rivo/uniseg-8 6607 ns/op 109.58 MB/s 0 B/op 0 allocs/op
|
||||||
|
|
||||||
|
BenchmarkRune_Mixed/clipperhouse/displaywidth-8 3456 ns/op 488.20 MB/s 0 B/op 0 allocs/op
|
||||||
|
BenchmarkRune_Mixed/mattn/go-runewidth-8 5400 ns/op 312.39 MB/s 0 B/op 0 allocs/op
|
||||||
|
|
||||||
|
BenchmarkRune_EastAsian/clipperhouse/displaywidth-8 3475 ns/op 485.41 MB/s 0 B/op 0 allocs/op
|
||||||
|
BenchmarkRune_EastAsian/mattn/go-runewidth-8 15701 ns/op 107.44 MB/s 0 B/op 0 allocs/op
|
||||||
|
|
||||||
|
BenchmarkRune_ASCII/clipperhouse/displaywidth-8 257.0 ns/op 498.13 MB/s 0 B/op 0 allocs/op
|
||||||
|
BenchmarkRune_ASCII/mattn/go-runewidth-8 266.4 ns/op 480.50 MB/s 0 B/op 0 allocs/op
|
||||||
|
|
||||||
|
BenchmarkRune_Emoji/clipperhouse/displaywidth-8 1384 ns/op 523.02 MB/s 0 B/op 0 allocs/op
|
||||||
|
BenchmarkRune_Emoji/mattn/go-runewidth-8 2273 ns/op 318.45 MB/s 0 B/op 0 allocs/op
|
||||||
|
```
|
||||||
3
vendor/github.com/clipperhouse/displaywidth/gen.go
generated
vendored
Normal file
3
vendor/github.com/clipperhouse/displaywidth/gen.go
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
package displaywidth
|
||||||
|
|
||||||
|
//go:generate go run -C internal/gen .
|
||||||
1716
vendor/github.com/clipperhouse/displaywidth/trie.go
generated
vendored
Normal file
1716
vendor/github.com/clipperhouse/displaywidth/trie.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
210
vendor/github.com/clipperhouse/displaywidth/width.go
generated
vendored
Normal file
210
vendor/github.com/clipperhouse/displaywidth/width.go
generated
vendored
Normal file
@ -0,0 +1,210 @@
|
|||||||
|
package displaywidth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
|
"github.com/clipperhouse/stringish"
|
||||||
|
"github.com/clipperhouse/uax29/v2/graphemes"
|
||||||
|
)
|
||||||
|
|
||||||
|
// String calculates the display width of a string,
|
||||||
|
// by iterating over grapheme clusters in the string
|
||||||
|
// and summing their widths.
|
||||||
|
func String(s string) int {
|
||||||
|
return DefaultOptions.String(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bytes calculates the display width of a []byte,
|
||||||
|
// by iterating over grapheme clusters in the byte slice
|
||||||
|
// and summing their widths.
|
||||||
|
func Bytes(s []byte) int {
|
||||||
|
return DefaultOptions.Bytes(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rune calculates the display width of a rune. You
|
||||||
|
// should almost certainly use [String] or [Bytes] for
|
||||||
|
// most purposes.
|
||||||
|
//
|
||||||
|
// The smallest unit of display width is a grapheme
|
||||||
|
// cluster, not a rune. Iterating over runes to measure
|
||||||
|
// width is incorrect in most cases.
|
||||||
|
func Rune(r rune) int {
|
||||||
|
return DefaultOptions.Rune(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Options allows you to specify the treatment of ambiguous East Asian
|
||||||
|
// characters. When EastAsianWidth is false (default), ambiguous East Asian
|
||||||
|
// characters are treated as width 1. When EastAsianWidth is true, ambiguous
|
||||||
|
// East Asian characters are treated as width 2.
|
||||||
|
type Options struct {
|
||||||
|
EastAsianWidth bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultOptions is the default options for the display width
|
||||||
|
// calculation, which is EastAsianWidth: false.
|
||||||
|
var DefaultOptions = Options{EastAsianWidth: false}
|
||||||
|
|
||||||
|
// String calculates the display width of a string,
|
||||||
|
// for the given options, by iterating over grapheme clusters
|
||||||
|
// and summing their widths.
|
||||||
|
func (options Options) String(s string) int {
|
||||||
|
if len(s) == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
total := 0
|
||||||
|
g := graphemes.FromString(s)
|
||||||
|
for g.Next() {
|
||||||
|
props := lookupProperties(g.Value())
|
||||||
|
total += props.width(options)
|
||||||
|
}
|
||||||
|
return total
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bytes calculates the display width of a []byte,
|
||||||
|
// for the given options, by iterating over grapheme
|
||||||
|
// clusters in the byte slice and summing their widths.
|
||||||
|
func (options Options) Bytes(s []byte) int {
|
||||||
|
if len(s) == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
total := 0
|
||||||
|
g := graphemes.FromBytes(s)
|
||||||
|
for g.Next() {
|
||||||
|
props := lookupProperties(g.Value())
|
||||||
|
total += props.width(options)
|
||||||
|
}
|
||||||
|
return total
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rune calculates the display width of a rune,
|
||||||
|
// for the given options.
|
||||||
|
//
|
||||||
|
// The smallest unit of display width is a grapheme
|
||||||
|
// cluster, not a rune. Iterating over runes to measure
|
||||||
|
// width is incorrect in most cases.
|
||||||
|
func (options Options) Rune(r rune) int {
|
||||||
|
// Fast path for ASCII
|
||||||
|
if r < utf8.RuneSelf {
|
||||||
|
if isASCIIControl(byte(r)) {
|
||||||
|
// Control (0x00-0x1F) and DEL (0x7F)
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
// ASCII printable (0x20-0x7E)
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Surrogates (U+D800-U+DFFF) are invalid UTF-8 and have zero width
|
||||||
|
// Other packages might turn them into the replacement character (U+FFFD)
|
||||||
|
// in which case, we won't see it.
|
||||||
|
if r >= 0xD800 && r <= 0xDFFF {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stack-allocated to avoid heap allocation
|
||||||
|
var buf [4]byte // UTF-8 is at most 4 bytes
|
||||||
|
n := utf8.EncodeRune(buf[:], r)
|
||||||
|
// Skip the grapheme iterator and directly lookup properties
|
||||||
|
props := lookupProperties(buf[:n])
|
||||||
|
return props.width(options)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isASCIIControl(b byte) bool {
|
||||||
|
return b < 0x20 || b == 0x7F
|
||||||
|
}
|
||||||
|
|
||||||
|
// isRIPrefix checks if the slice matches the Regional Indicator prefix
|
||||||
|
// (F0 9F 87). It assumes len(s) >= 3.
|
||||||
|
func isRIPrefix[T stringish.Interface](s T) bool {
|
||||||
|
return s[0] == 0xF0 && s[1] == 0x9F && s[2] == 0x87
|
||||||
|
}
|
||||||
|
|
||||||
|
// isVS16 checks if the slice matches VS16 (U+FE0F) UTF-8 encoding
|
||||||
|
// (EF B8 8F). It assumes len(s) >= 3.
|
||||||
|
func isVS16[T stringish.Interface](s T) bool {
|
||||||
|
return s[0] == 0xEF && s[1] == 0xB8 && s[2] == 0x8F
|
||||||
|
}
|
||||||
|
|
||||||
|
// lookupProperties returns the properties for the first character in a string
|
||||||
|
func lookupProperties[T stringish.Interface](s T) property {
|
||||||
|
l := len(s)
|
||||||
|
|
||||||
|
if l == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
b := s[0]
|
||||||
|
if isASCIIControl(b) {
|
||||||
|
return _Zero_Width
|
||||||
|
}
|
||||||
|
|
||||||
|
if b < utf8.RuneSelf {
|
||||||
|
// Check for variation selector after ASCII (e.g., keycap sequences like 1️⃣)
|
||||||
|
if l >= 4 {
|
||||||
|
// Subslice may help eliminate bounds checks
|
||||||
|
vs := s[1:4]
|
||||||
|
if isVS16(vs) {
|
||||||
|
// VS16 requests emoji presentation (width 2)
|
||||||
|
return _Emoji
|
||||||
|
}
|
||||||
|
// VS15 (0x8E) requests text presentation but does not affect width,
|
||||||
|
// in my reading of Unicode TR51. Falls through to _Default.
|
||||||
|
}
|
||||||
|
return _Default
|
||||||
|
}
|
||||||
|
|
||||||
|
// Regional indicator pair (flag)
|
||||||
|
if l >= 8 {
|
||||||
|
// Subslice may help eliminate bounds checks
|
||||||
|
ri := s[:8]
|
||||||
|
if isRIPrefix(ri[0:3]) {
|
||||||
|
b3 := ri[3]
|
||||||
|
if b3 >= 0xA6 && b3 <= 0xBF && isRIPrefix(ri[4:7]) {
|
||||||
|
b7 := ri[7]
|
||||||
|
if b7 >= 0xA6 && b7 <= 0xBF {
|
||||||
|
return _Emoji
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
props, size := lookup(s)
|
||||||
|
p := property(props)
|
||||||
|
|
||||||
|
// Variation Selectors
|
||||||
|
if size > 0 && l >= size+3 {
|
||||||
|
// Subslice may help eliminate bounds checks
|
||||||
|
vs := s[size : size+3]
|
||||||
|
if isVS16(vs) {
|
||||||
|
// VS16 requests emoji presentation (width 2)
|
||||||
|
return _Emoji
|
||||||
|
}
|
||||||
|
// VS15 (0x8E) requests text presentation but does not affect width,
|
||||||
|
// in my reading of Unicode TR51. Falls through to return the base
|
||||||
|
// character's property (p).
|
||||||
|
}
|
||||||
|
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
const _Default property = 0
|
||||||
|
|
||||||
|
// a jump table of sorts, instead of a switch
|
||||||
|
var widthTable = [5]int{
|
||||||
|
_Default: 1,
|
||||||
|
_Zero_Width: 0,
|
||||||
|
_East_Asian_Wide: 2,
|
||||||
|
_East_Asian_Ambiguous: 1,
|
||||||
|
_Emoji: 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
// width determines the display width of a character based on its properties
|
||||||
|
// and configuration options
|
||||||
|
func (p property) width(options Options) int {
|
||||||
|
if options.EastAsianWidth && p == _East_Asian_Ambiguous {
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
|
||||||
|
return widthTable[p]
|
||||||
|
}
|
||||||
2
vendor/github.com/clipperhouse/stringish/.gitignore
generated
vendored
Normal file
2
vendor/github.com/clipperhouse/stringish/.gitignore
generated
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
.DS_Store
|
||||||
|
*.test
|
||||||
21
vendor/github.com/clipperhouse/stringish/LICENSE
generated
vendored
Normal file
21
vendor/github.com/clipperhouse/stringish/LICENSE
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2025 Matt Sherman
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
64
vendor/github.com/clipperhouse/stringish/README.md
generated
vendored
Normal file
64
vendor/github.com/clipperhouse/stringish/README.md
generated
vendored
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
# stringish
|
||||||
|
|
||||||
|
A small Go module that provides a generic type constraint for “string-like”
|
||||||
|
data, and a utf8 package that works with both strings and byte slices
|
||||||
|
without conversions.
|
||||||
|
|
||||||
|
```go
|
||||||
|
type Interface interface {
|
||||||
|
~[]byte | ~string
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
[](https://pkg.go.dev/github.com/clipperhouse/stringish/utf8)
|
||||||
|
[](https://github.com/clipperhouse/stringish/actions/workflows/gotest.yml)
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
```
|
||||||
|
go get github.com/clipperhouse/stringish
|
||||||
|
```
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
```go
|
||||||
|
import (
|
||||||
|
"github.com/clipperhouse/stringish"
|
||||||
|
"github.com/clipperhouse/stringish/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
s := "Hello, 世界"
|
||||||
|
r, size := utf8.DecodeRune(s) // not DecodeRuneInString 🎉
|
||||||
|
|
||||||
|
b := []byte("Hello, 世界")
|
||||||
|
r, size = utf8.DecodeRune(b) // same API!
|
||||||
|
|
||||||
|
func MyFoo[T stringish.Interface](s T) T {
|
||||||
|
// pass a string or a []byte
|
||||||
|
// iterate, slice, transform, whatever
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Motivation
|
||||||
|
|
||||||
|
Sometimes we want APIs to accept `string` or `[]byte` without having to convert
|
||||||
|
between those types. That conversion usually allocates!
|
||||||
|
|
||||||
|
By implementing with `stringish.Interface`, we can have a single API, and
|
||||||
|
single implementation for both types: one `Foo` instead of `Foo` and
|
||||||
|
`FooString`.
|
||||||
|
|
||||||
|
We have converted the
|
||||||
|
[`unicode/utf8` package](https://github.com/clipperhouse/stringish/blob/main/utf8/utf8.go)
|
||||||
|
as an example -- note the absence of`*InString` funcs. We might look at `x/text`
|
||||||
|
next.
|
||||||
|
|
||||||
|
## Used by
|
||||||
|
|
||||||
|
- clipperhouse/uax29: [stringish trie](https://github.com/clipperhouse/uax29/blob/master/graphemes/trie.go#L27), [stringish iterator](https://github.com/clipperhouse/uax29/blob/master/internal/iterators/iterator.go#L9), [stringish SplitFunc](https://github.com/clipperhouse/uax29/blob/master/graphemes/splitfunc.go#L21)
|
||||||
|
|
||||||
|
- [clipperhouse/displaywidth](https://github.com/clipperhouse/displaywidth)
|
||||||
|
|
||||||
|
## Prior discussion
|
||||||
|
|
||||||
|
- [Consideration of similar by the Go team](https://github.com/golang/go/issues/48643)
|
||||||
5
vendor/github.com/clipperhouse/stringish/interface.go
generated
vendored
Normal file
5
vendor/github.com/clipperhouse/stringish/interface.go
generated
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
package stringish
|
||||||
|
|
||||||
|
type Interface interface {
|
||||||
|
~[]byte | ~string
|
||||||
|
}
|
||||||
24
vendor/github.com/clipperhouse/uax29/v2/graphemes/README.md
generated
vendored
24
vendor/github.com/clipperhouse/uax29/v2/graphemes/README.md
generated
vendored
@ -1,5 +1,9 @@
|
|||||||
An implementation of grapheme cluster boundaries from [Unicode text segmentation](https://unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries) (UAX 29), for Unicode version 15.0.0.
|
An implementation of grapheme cluster boundaries from [Unicode text segmentation](https://unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries) (UAX 29), for Unicode version 15.0.0.
|
||||||
|
|
||||||
|
[](https://pkg.go.dev/github.com/clipperhouse/uax29/v2/graphemes)
|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
## Quick start
|
## Quick start
|
||||||
|
|
||||||
```
|
```
|
||||||
@ -18,15 +22,14 @@ for tokens.Next() { // Next() returns true until end of data
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
[](https://pkg.go.dev/github.com/clipperhouse/uax29/v2/graphemes)
|
|
||||||
|
|
||||||
_A grapheme is a “single visible character”, which might be a simple as a single letter, or a complex emoji that consists of several Unicode code points._
|
_A grapheme is a “single visible character”, which might be a simple as a single letter, or a complex emoji that consists of several Unicode code points._
|
||||||
|
|
||||||
## Conformance
|
## Conformance
|
||||||
|
|
||||||
We use the Unicode [test suite](https://unicode.org/reports/tr41/tr41-26.html#Tests29). Status:
|
We use the Unicode [test suite](https://unicode.org/reports/tr41/tr41-26.html#Tests29).
|
||||||
|
|
||||||

|

|
||||||
|

|
||||||
|
|
||||||
## APIs
|
## APIs
|
||||||
|
|
||||||
@ -71,9 +74,18 @@ for tokens.Next() { // Next() returns true until end of data
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Performance
|
### Benchmarks
|
||||||
|
|
||||||
On a Mac M2 laptop, we see around 200MB/s, or around 100 million graphemes per second. You should see ~constant memory, and no allocations.
|
On a Mac M2 laptop, we see around 200MB/s, or around 100 million graphemes per second, and no allocations.
|
||||||
|
|
||||||
|
```
|
||||||
|
goos: darwin
|
||||||
|
goarch: arm64
|
||||||
|
pkg: github.com/clipperhouse/uax29/graphemes/comparative
|
||||||
|
cpu: Apple M2
|
||||||
|
BenchmarkGraphemes/clipperhouse/uax29-8 173805 ns/op 201.16 MB/s 0 B/op 0 allocs/op
|
||||||
|
BenchmarkGraphemes/rivo/uniseg-8 2045128 ns/op 17.10 MB/s 0 B/op 0 allocs/op
|
||||||
|
```
|
||||||
|
|
||||||
### Invalid inputs
|
### Invalid inputs
|
||||||
|
|
||||||
|
|||||||
7
vendor/github.com/clipperhouse/uax29/v2/graphemes/iterator.go
generated
vendored
7
vendor/github.com/clipperhouse/uax29/v2/graphemes/iterator.go
generated
vendored
@ -1,8 +1,11 @@
|
|||||||
package graphemes
|
package graphemes
|
||||||
|
|
||||||
import "github.com/clipperhouse/uax29/v2/internal/iterators"
|
import (
|
||||||
|
"github.com/clipperhouse/stringish"
|
||||||
|
"github.com/clipperhouse/uax29/v2/internal/iterators"
|
||||||
|
)
|
||||||
|
|
||||||
type Iterator[T iterators.Stringish] struct {
|
type Iterator[T stringish.Interface] struct {
|
||||||
*iterators.Iterator[T]
|
*iterators.Iterator[T]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
4
vendor/github.com/clipperhouse/uax29/v2/graphemes/splitfunc.go
generated
vendored
4
vendor/github.com/clipperhouse/uax29/v2/graphemes/splitfunc.go
generated
vendored
@ -3,7 +3,7 @@ package graphemes
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
|
||||||
"github.com/clipperhouse/uax29/v2/internal/iterators"
|
"github.com/clipperhouse/stringish"
|
||||||
)
|
)
|
||||||
|
|
||||||
// is determines if lookup intersects propert(ies)
|
// is determines if lookup intersects propert(ies)
|
||||||
@ -18,7 +18,7 @@ const _Ignore = _Extend
|
|||||||
// See https://unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries.
|
// See https://unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries.
|
||||||
var SplitFunc bufio.SplitFunc = splitFunc[[]byte]
|
var SplitFunc bufio.SplitFunc = splitFunc[[]byte]
|
||||||
|
|
||||||
func splitFunc[T iterators.Stringish](data T, atEOF bool) (advance int, token T, err error) {
|
func splitFunc[T stringish.Interface](data T, atEOF bool) (advance int, token T, err error) {
|
||||||
var empty T
|
var empty T
|
||||||
if len(data) == 0 {
|
if len(data) == 0 {
|
||||||
return 0, empty, nil
|
return 0, empty, nil
|
||||||
|
|||||||
6
vendor/github.com/clipperhouse/uax29/v2/graphemes/trie.go
generated
vendored
6
vendor/github.com/clipperhouse/uax29/v2/graphemes/trie.go
generated
vendored
@ -1,10 +1,10 @@
|
|||||||
package graphemes
|
package graphemes
|
||||||
|
|
||||||
|
import "github.com/clipperhouse/stringish"
|
||||||
|
|
||||||
// generated by github.com/clipperhouse/uax29/v2
|
// generated by github.com/clipperhouse/uax29/v2
|
||||||
// from https://www.unicode.org/Public/15.0.0/ucd/auxiliary/GraphemeBreakProperty.txt
|
// from https://www.unicode.org/Public/15.0.0/ucd/auxiliary/GraphemeBreakProperty.txt
|
||||||
|
|
||||||
import "github.com/clipperhouse/uax29/v2/internal/iterators"
|
|
||||||
|
|
||||||
type property uint16
|
type property uint16
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -27,7 +27,7 @@ const (
|
|||||||
// lookup returns the trie value for the first UTF-8 encoding in s and
|
// lookup returns the trie value for the first UTF-8 encoding in s and
|
||||||
// the width in bytes of this encoding. The size will be 0 if s does not
|
// the width in bytes of this encoding. The size will be 0 if s does not
|
||||||
// hold enough bytes to complete the encoding. len(s) must be greater than 0.
|
// hold enough bytes to complete the encoding. len(s) must be greater than 0.
|
||||||
func lookup[T iterators.Stringish](s T) (v property, sz int) {
|
func lookup[T stringish.Interface](s T) (v property, sz int) {
|
||||||
c0 := s[0]
|
c0 := s[0]
|
||||||
switch {
|
switch {
|
||||||
case c0 < 0x80: // is ASCII
|
case c0 < 0x80: // is ASCII
|
||||||
|
|||||||
27
vendor/github.com/clipperhouse/uax29/v2/internal/iterators/iterator.go
generated
vendored
27
vendor/github.com/clipperhouse/uax29/v2/internal/iterators/iterator.go
generated
vendored
@ -1,14 +1,12 @@
|
|||||||
package iterators
|
package iterators
|
||||||
|
|
||||||
type Stringish interface {
|
import "github.com/clipperhouse/stringish"
|
||||||
[]byte | string
|
|
||||||
}
|
|
||||||
|
|
||||||
type SplitFunc[T Stringish] func(T, bool) (int, T, error)
|
type SplitFunc[T stringish.Interface] func(T, bool) (int, T, error)
|
||||||
|
|
||||||
// Iterator is a generic iterator for words that are either []byte or string.
|
// Iterator is a generic iterator for words that are either []byte or string.
|
||||||
// Iterate while Next() is true, and access the word via Value().
|
// Iterate while Next() is true, and access the word via Value().
|
||||||
type Iterator[T Stringish] struct {
|
type Iterator[T stringish.Interface] struct {
|
||||||
split SplitFunc[T]
|
split SplitFunc[T]
|
||||||
data T
|
data T
|
||||||
start int
|
start int
|
||||||
@ -16,7 +14,7 @@ type Iterator[T Stringish] struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new Iterator for the given data and SplitFunc.
|
// New creates a new Iterator for the given data and SplitFunc.
|
||||||
func New[T Stringish](split SplitFunc[T], data T) *Iterator[T] {
|
func New[T stringish.Interface](split SplitFunc[T], data T) *Iterator[T] {
|
||||||
return &Iterator[T]{
|
return &Iterator[T]{
|
||||||
split: split,
|
split: split,
|
||||||
data: data,
|
data: data,
|
||||||
@ -83,3 +81,20 @@ func (iter *Iterator[T]) Reset() {
|
|||||||
iter.start = 0
|
iter.start = 0
|
||||||
iter.pos = 0
|
iter.pos = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (iter *Iterator[T]) First() T {
|
||||||
|
if len(iter.data) == 0 {
|
||||||
|
return iter.data
|
||||||
|
}
|
||||||
|
advance, _, err := iter.split(iter.data, true)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if advance <= 0 {
|
||||||
|
panic("SplitFunc returned a zero or negative advance")
|
||||||
|
}
|
||||||
|
if advance > len(iter.data) {
|
||||||
|
panic("SplitFunc advanced beyond the end of the data")
|
||||||
|
}
|
||||||
|
return iter.data[:advance]
|
||||||
|
}
|
||||||
|
|||||||
4
vendor/github.com/cyphar/filepath-securejoin/.golangci.yml
generated
vendored
4
vendor/github.com/cyphar/filepath-securejoin/.golangci.yml
generated
vendored
@ -9,6 +9,10 @@
|
|||||||
|
|
||||||
version: "2"
|
version: "2"
|
||||||
|
|
||||||
|
run:
|
||||||
|
build-tags:
|
||||||
|
- libpathrs
|
||||||
|
|
||||||
linters:
|
linters:
|
||||||
enable:
|
enable:
|
||||||
- asasalint
|
- asasalint
|
||||||
|
|||||||
90
vendor/github.com/cyphar/filepath-securejoin/CHANGELOG.md
generated
vendored
90
vendor/github.com/cyphar/filepath-securejoin/CHANGELOG.md
generated
vendored
@ -6,6 +6,92 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
|||||||
|
|
||||||
## [Unreleased] ##
|
## [Unreleased] ##
|
||||||
|
|
||||||
|
## [0.6.0] - 2025-11-03 ##
|
||||||
|
|
||||||
|
> By the Power of Greyskull!
|
||||||
|
|
||||||
|
While quite small code-wise, this release marks a very key point in the
|
||||||
|
development of filepath-securejoin.
|
||||||
|
|
||||||
|
filepath-securejoin was originally intended (back in 2017) to simply be a
|
||||||
|
single-purpose library that would take some common code used in container
|
||||||
|
runtimes (specifically, Docker's `FollowSymlinksInScope`) and make it more
|
||||||
|
general-purpose (with the eventual goals of it ending up in the Go stdlib).
|
||||||
|
|
||||||
|
Of course, I quickly discovered that this problem was actually far more
|
||||||
|
complicated to solve when dealing with racing attackers, which lead to me
|
||||||
|
developing `openat2(2)` and [libpathrs][]. I had originally planned for
|
||||||
|
libpathrs to completely replace filepath-securejoin "once it was ready" but in
|
||||||
|
the interim we needed to fix several race attacks in runc as part of security
|
||||||
|
advisories. Obviously we couldn't require the usage of a pre-0.1 Rust library
|
||||||
|
in runc so it was necessary to port bits of libpathrs into filepath-securejoin.
|
||||||
|
(Ironically the first prototypes of libpathrs were originally written in Go and
|
||||||
|
then rewritten to Rust, so the code in filepath-securejoin is actually Go code
|
||||||
|
that was rewritten to Rust then re-rewritten to Go.)
|
||||||
|
|
||||||
|
It then became clear that pure-Go libraries will likely not be willing to
|
||||||
|
require CGo for all of their builds, so it was necessary to accept that
|
||||||
|
filepath-securejoin will need to stay. As such, in v0.5.0 we provided more
|
||||||
|
pure-Go implementations of features from libpathrs but moved them into
|
||||||
|
`pathrs-lite` subpackage to clarify what purpose these helpers serve.
|
||||||
|
|
||||||
|
This release finally closes the loop and makes it so that pathrs-lite can
|
||||||
|
transparently use libpathrs (via a `libpathrs` build-tag). This means that
|
||||||
|
upstream libraries can use the pure Go version if they prefer, but downstreams
|
||||||
|
(either downstream library users or even downstream distributions) are able to
|
||||||
|
migrate to libpathrs for all usages of pathrs-lite in an entire Go binary.
|
||||||
|
|
||||||
|
I should make it clear that I do not plan to port the rest of libpathrs to Go,
|
||||||
|
as I do not wish to maintain two copies of the same codebase. pathrs-lite
|
||||||
|
already provides the core essentials necessary to operate on paths safely for
|
||||||
|
most modern systems. Users who want additional hardening or more ergonomic APIs
|
||||||
|
are free to use [`cyphar.com/go-pathrs`][go-pathrs] (libpathrs's Go bindings).
|
||||||
|
|
||||||
|
[libpathrs]: https://github.com/cyphar/libpathrs
|
||||||
|
[go-pathrs]: https://cyphar.com/go-pathrs
|
||||||
|
|
||||||
|
### Breaking ###
|
||||||
|
- The deprecated `MkdirAll`, `MkdirAllHandle`, `OpenInRoot`, `OpenatInRoot` and
|
||||||
|
`Reopen` wrappers have been removed. Please switch to using `pathrs-lite`
|
||||||
|
directly.
|
||||||
|
|
||||||
|
### Added ###
|
||||||
|
- `pathrs-lite` now has support for using [libpathrs][libpathrs] as a backend.
|
||||||
|
This is opt-in and can be enabled at build time with the `libpathrs` build
|
||||||
|
tag. The intention is to allow for downstream libraries and other projects to
|
||||||
|
make use of the pure-Go `github.com/cyphar/filepath-securejoin/pathrs-lite`
|
||||||
|
package and distributors can then opt-in to using `libpathrs` for the entire
|
||||||
|
binary if they wish.
|
||||||
|
|
||||||
|
## [0.5.1] - 2025-10-31 ##
|
||||||
|
|
||||||
|
> Spooky scary skeletons send shivers down your spine!
|
||||||
|
|
||||||
|
### Changed ###
|
||||||
|
- `openat2` can return `-EAGAIN` if it detects a possible attack in certain
|
||||||
|
scenarios (namely if there was a rename or mount while walking a path with a
|
||||||
|
`..` component). While this is necessary to avoid a denial-of-service in the
|
||||||
|
kernel, it does require retry loops in userspace.
|
||||||
|
|
||||||
|
In previous versions, `pathrs-lite` would retry `openat2` 32 times before
|
||||||
|
returning an error, but we've received user reports that this limit can be
|
||||||
|
hit on systems with very heavy load. In some synthetic benchmarks (testing
|
||||||
|
the worst-case of an attacker doing renames in a tight loop on every core of
|
||||||
|
a 16-core machine) we managed to get a ~3% failure rate in runc. We have
|
||||||
|
improved this situation in two ways:
|
||||||
|
|
||||||
|
* We have now increased this limit to 128, which should be good enough for
|
||||||
|
most use-cases without becoming a denial-of-service vector (the number of
|
||||||
|
syscalls called by the `O_PATH` resolver in a typical case is within the
|
||||||
|
same ballpark). The same benchmarks show a failure rate of ~0.12% which
|
||||||
|
(while not zero) is probably sufficient for most users.
|
||||||
|
|
||||||
|
* In addition, we now return a `unix.EAGAIN` error that is bubbled up and can
|
||||||
|
be detected by callers. This means that callers with stricter requirements
|
||||||
|
to avoid spurious errors can choose to do their own infinite `EAGAIN` retry
|
||||||
|
loop (though we would strongly recommend users use time-based deadlines in
|
||||||
|
such retry loops to avoid potentially unbounded denials-of-service).
|
||||||
|
|
||||||
## [0.5.0] - 2025-09-26 ##
|
## [0.5.0] - 2025-09-26 ##
|
||||||
|
|
||||||
> Let the past die. Kill it if you have to.
|
> Let the past die. Kill it if you have to.
|
||||||
@ -354,7 +440,9 @@ This is our first release of `github.com/cyphar/filepath-securejoin`,
|
|||||||
containing a full implementation with a coverage of 93.5% (the only missing
|
containing a full implementation with a coverage of 93.5% (the only missing
|
||||||
cases are the error cases, which are hard to mocktest at the moment).
|
cases are the error cases, which are hard to mocktest at the moment).
|
||||||
|
|
||||||
[Unreleased]: https://github.com/cyphar/filepath-securejoin/compare/v0.5.0...HEAD
|
[Unreleased]: https://github.com/cyphar/filepath-securejoin/compare/v0.6.0...HEAD
|
||||||
|
[0.6.0]: https://github.com/cyphar/filepath-securejoin/compare/v0.5.1...v0.6.0
|
||||||
|
[0.5.1]: https://github.com/cyphar/filepath-securejoin/compare/v0.5.0...v0.5.1
|
||||||
[0.5.0]: https://github.com/cyphar/filepath-securejoin/compare/v0.4.1...v0.5.0
|
[0.5.0]: https://github.com/cyphar/filepath-securejoin/compare/v0.4.1...v0.5.0
|
||||||
[0.4.1]: https://github.com/cyphar/filepath-securejoin/compare/v0.4.0...v0.4.1
|
[0.4.1]: https://github.com/cyphar/filepath-securejoin/compare/v0.4.0...v0.4.1
|
||||||
[0.4.0]: https://github.com/cyphar/filepath-securejoin/compare/v0.3.6...v0.4.0
|
[0.4.0]: https://github.com/cyphar/filepath-securejoin/compare/v0.3.6...v0.4.0
|
||||||
|
|||||||
2
vendor/github.com/cyphar/filepath-securejoin/VERSION
generated
vendored
2
vendor/github.com/cyphar/filepath-securejoin/VERSION
generated
vendored
@ -1 +1 @@
|
|||||||
0.5.0
|
0.6.0
|
||||||
|
|||||||
48
vendor/github.com/cyphar/filepath-securejoin/deprecated_linux.go
generated
vendored
48
vendor/github.com/cyphar/filepath-securejoin/deprecated_linux.go
generated
vendored
@ -1,48 +0,0 @@
|
|||||||
// SPDX-License-Identifier: MPL-2.0
|
|
||||||
|
|
||||||
//go:build linux
|
|
||||||
|
|
||||||
// Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com>
|
|
||||||
// Copyright (C) 2024-2025 SUSE LLC
|
|
||||||
//
|
|
||||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
|
|
||||||
package securejoin
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/cyphar/filepath-securejoin/pathrs-lite"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// MkdirAll is a wrapper around [pathrs.MkdirAll].
|
|
||||||
//
|
|
||||||
// Deprecated: You should use [pathrs.MkdirAll] directly instead. This
|
|
||||||
// wrapper will be removed in filepath-securejoin v0.6.
|
|
||||||
MkdirAll = pathrs.MkdirAll
|
|
||||||
|
|
||||||
// MkdirAllHandle is a wrapper around [pathrs.MkdirAllHandle].
|
|
||||||
//
|
|
||||||
// Deprecated: You should use [pathrs.MkdirAllHandle] directly instead.
|
|
||||||
// This wrapper will be removed in filepath-securejoin v0.6.
|
|
||||||
MkdirAllHandle = pathrs.MkdirAllHandle
|
|
||||||
|
|
||||||
// OpenInRoot is a wrapper around [pathrs.OpenInRoot].
|
|
||||||
//
|
|
||||||
// Deprecated: You should use [pathrs.OpenInRoot] directly instead. This
|
|
||||||
// wrapper will be removed in filepath-securejoin v0.6.
|
|
||||||
OpenInRoot = pathrs.OpenInRoot
|
|
||||||
|
|
||||||
// OpenatInRoot is a wrapper around [pathrs.OpenatInRoot].
|
|
||||||
//
|
|
||||||
// Deprecated: You should use [pathrs.OpenatInRoot] directly instead. This
|
|
||||||
// wrapper will be removed in filepath-securejoin v0.6.
|
|
||||||
OpenatInRoot = pathrs.OpenatInRoot
|
|
||||||
|
|
||||||
// Reopen is a wrapper around [pathrs.Reopen].
|
|
||||||
//
|
|
||||||
// Deprecated: You should use [pathrs.Reopen] directly instead. This
|
|
||||||
// wrapper will be removed in filepath-securejoin v0.6.
|
|
||||||
Reopen = pathrs.Reopen
|
|
||||||
)
|
|
||||||
33
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/README.md
generated
vendored
33
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/README.md
generated
vendored
@ -1,33 +0,0 @@
|
|||||||
## `pathrs-lite` ##
|
|
||||||
|
|
||||||
`github.com/cyphar/filepath-securejoin/pathrs-lite` provides a minimal **pure
|
|
||||||
Go** implementation of the core bits of [libpathrs][]. This is not intended to
|
|
||||||
be a complete replacement for libpathrs, instead it is mainly intended to be
|
|
||||||
useful as a transition tool for existing Go projects.
|
|
||||||
|
|
||||||
The long-term plan for `pathrs-lite` is to provide a build tag that will cause
|
|
||||||
all `pathrs-lite` operations to call into libpathrs directly, thus removing
|
|
||||||
code duplication for projects that wish to make use of libpathrs (and providing
|
|
||||||
the ability for software packagers to opt-in to libpathrs support without
|
|
||||||
needing to patch upstream).
|
|
||||||
|
|
||||||
[libpathrs]: https://github.com/cyphar/libpathrs
|
|
||||||
|
|
||||||
### License ###
|
|
||||||
|
|
||||||
Most of this subpackage is licensed under the Mozilla Public License (version
|
|
||||||
2.0). For more information, see the top-level [COPYING.md][] and
|
|
||||||
[LICENSE.MPL-2.0][] files, as well as the individual license headers for each
|
|
||||||
file.
|
|
||||||
|
|
||||||
```
|
|
||||||
Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com>
|
|
||||||
Copyright (C) 2024-2025 SUSE LLC
|
|
||||||
|
|
||||||
This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
```
|
|
||||||
|
|
||||||
[COPYING.md]: ../COPYING.md
|
|
||||||
[LICENSE.MPL-2.0]: ../LICENSE.MPL-2.0
|
|
||||||
14
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/doc.go
generated
vendored
14
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/doc.go
generated
vendored
@ -1,14 +0,0 @@
|
|||||||
// SPDX-License-Identifier: MPL-2.0
|
|
||||||
|
|
||||||
//go:build linux
|
|
||||||
|
|
||||||
// Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com>
|
|
||||||
// Copyright (C) 2024-2025 SUSE LLC
|
|
||||||
//
|
|
||||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
|
|
||||||
// Package pathrs (pathrs-lite) is a less complete pure Go implementation of
|
|
||||||
// some of the APIs provided by [libpathrs].
|
|
||||||
package pathrs
|
|
||||||
30
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/assert/assert.go
generated
vendored
30
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/assert/assert.go
generated
vendored
@ -1,30 +0,0 @@
|
|||||||
// SPDX-License-Identifier: MPL-2.0
|
|
||||||
|
|
||||||
// Copyright (C) 2025 Aleksa Sarai <cyphar@cyphar.com>
|
|
||||||
// Copyright (C) 2025 SUSE LLC
|
|
||||||
//
|
|
||||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
|
|
||||||
// Package assert provides some basic assertion helpers for Go.
|
|
||||||
package assert
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Assert panics if the predicate is false with the provided argument.
|
|
||||||
func Assert(predicate bool, msg any) {
|
|
||||||
if !predicate {
|
|
||||||
panic(msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assertf panics if the predicate is false and formats the message using the
|
|
||||||
// same formatting as [fmt.Printf].
|
|
||||||
//
|
|
||||||
// [fmt.Printf]: https://pkg.go.dev/fmt#Printf
|
|
||||||
func Assertf(predicate bool, fmtMsg string, args ...any) {
|
|
||||||
Assert(predicate, fmt.Sprintf(fmtMsg, args...))
|
|
||||||
}
|
|
||||||
30
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/errors.go
generated
vendored
30
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/errors.go
generated
vendored
@ -1,30 +0,0 @@
|
|||||||
// SPDX-License-Identifier: MPL-2.0
|
|
||||||
|
|
||||||
// Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com>
|
|
||||||
// Copyright (C) 2024-2025 SUSE LLC
|
|
||||||
//
|
|
||||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
|
|
||||||
// Package internal contains unexported common code for filepath-securejoin.
|
|
||||||
package internal
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// ErrPossibleAttack indicates that some attack was detected.
|
|
||||||
ErrPossibleAttack = errors.New("possible attack detected")
|
|
||||||
|
|
||||||
// ErrPossibleBreakout indicates that during an operation we ended up in a
|
|
||||||
// state that could be a breakout but we detected it.
|
|
||||||
ErrPossibleBreakout = errors.New("possible breakout detected")
|
|
||||||
|
|
||||||
// ErrInvalidDirectory indicates an unlinked directory.
|
|
||||||
ErrInvalidDirectory = errors.New("wandered into deleted directory")
|
|
||||||
|
|
||||||
// ErrDeletedInode indicates an unlinked file (non-directory).
|
|
||||||
ErrDeletedInode = errors.New("cannot verify path of deleted inode")
|
|
||||||
)
|
|
||||||
148
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/at_linux.go
generated
vendored
148
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/at_linux.go
generated
vendored
@ -1,148 +0,0 @@
|
|||||||
// SPDX-License-Identifier: MPL-2.0
|
|
||||||
|
|
||||||
//go:build linux
|
|
||||||
|
|
||||||
// Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com>
|
|
||||||
// Copyright (C) 2024-2025 SUSE LLC
|
|
||||||
//
|
|
||||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
|
|
||||||
package fd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
|
|
||||||
"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat"
|
|
||||||
)
|
|
||||||
|
|
||||||
// prepareAtWith returns -EBADF (an invalid fd) if dir is nil, otherwise using
|
|
||||||
// the dir.Fd(). We use -EBADF because in filepath-securejoin we generally
|
|
||||||
// don't want to allow relative-to-cwd paths. The returned path is an
|
|
||||||
// *informational* string that describes a reasonable pathname for the given
|
|
||||||
// *at(2) arguments. You must not use the full path for any actual filesystem
|
|
||||||
// operations.
|
|
||||||
func prepareAt(dir Fd, path string) (dirFd int, unsafeUnmaskedPath string) {
|
|
||||||
dirFd, dirPath := -int(unix.EBADF), "."
|
|
||||||
if dir != nil {
|
|
||||||
dirFd, dirPath = int(dir.Fd()), dir.Name()
|
|
||||||
}
|
|
||||||
if !filepath.IsAbs(path) {
|
|
||||||
// only prepend the dirfd path for relative paths
|
|
||||||
path = dirPath + "/" + path
|
|
||||||
}
|
|
||||||
// NOTE: If path is "." or "", the returned path won't be filepath.Clean,
|
|
||||||
// but that's okay since this path is either used for errors (in which case
|
|
||||||
// a trailing "/" or "/." is important information) or will be
|
|
||||||
// filepath.Clean'd later (in the case of fd.Openat).
|
|
||||||
return dirFd, path
|
|
||||||
}
|
|
||||||
|
|
||||||
// Openat is an [Fd]-based wrapper around unix.Openat.
|
|
||||||
func Openat(dir Fd, path string, flags int, mode int) (*os.File, error) { //nolint:unparam // wrapper func
|
|
||||||
dirFd, fullPath := prepareAt(dir, path)
|
|
||||||
// Make sure we always set O_CLOEXEC.
|
|
||||||
flags |= unix.O_CLOEXEC
|
|
||||||
fd, err := unix.Openat(dirFd, path, flags, uint32(mode))
|
|
||||||
if err != nil {
|
|
||||||
return nil, &os.PathError{Op: "openat", Path: fullPath, Err: err}
|
|
||||||
}
|
|
||||||
runtime.KeepAlive(dir)
|
|
||||||
// openat is only used with lexically-safe paths so we can use
|
|
||||||
// filepath.Clean here, and also the path itself is not going to be used
|
|
||||||
// for actual path operations.
|
|
||||||
fullPath = filepath.Clean(fullPath)
|
|
||||||
return os.NewFile(uintptr(fd), fullPath), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fstatat is an [Fd]-based wrapper around unix.Fstatat.
|
|
||||||
func Fstatat(dir Fd, path string, flags int) (unix.Stat_t, error) {
|
|
||||||
dirFd, fullPath := prepareAt(dir, path)
|
|
||||||
var stat unix.Stat_t
|
|
||||||
if err := unix.Fstatat(dirFd, path, &stat, flags); err != nil {
|
|
||||||
return stat, &os.PathError{Op: "fstatat", Path: fullPath, Err: err}
|
|
||||||
}
|
|
||||||
runtime.KeepAlive(dir)
|
|
||||||
return stat, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Faccessat is an [Fd]-based wrapper around unix.Faccessat.
|
|
||||||
func Faccessat(dir Fd, path string, mode uint32, flags int) error {
|
|
||||||
dirFd, fullPath := prepareAt(dir, path)
|
|
||||||
err := unix.Faccessat(dirFd, path, mode, flags)
|
|
||||||
if err != nil {
|
|
||||||
err = &os.PathError{Op: "faccessat", Path: fullPath, Err: err}
|
|
||||||
}
|
|
||||||
runtime.KeepAlive(dir)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Readlinkat is an [Fd]-based wrapper around unix.Readlinkat.
|
|
||||||
func Readlinkat(dir Fd, path string) (string, error) {
|
|
||||||
dirFd, fullPath := prepareAt(dir, path)
|
|
||||||
size := 4096
|
|
||||||
for {
|
|
||||||
linkBuf := make([]byte, size)
|
|
||||||
n, err := unix.Readlinkat(dirFd, path, linkBuf)
|
|
||||||
if err != nil {
|
|
||||||
return "", &os.PathError{Op: "readlinkat", Path: fullPath, Err: err}
|
|
||||||
}
|
|
||||||
runtime.KeepAlive(dir)
|
|
||||||
if n != size {
|
|
||||||
return string(linkBuf[:n]), nil
|
|
||||||
}
|
|
||||||
// Possible truncation, resize the buffer.
|
|
||||||
size *= 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
// STATX_MNT_ID_UNIQUE is provided in golang.org/x/sys@v0.20.0, but in order to
|
|
||||||
// avoid bumping the requirement for a single constant we can just define it
|
|
||||||
// ourselves.
|
|
||||||
_STATX_MNT_ID_UNIQUE = 0x4000 //nolint:revive // unix.* name
|
|
||||||
|
|
||||||
// We don't care which mount ID we get. The kernel will give us the unique
|
|
||||||
// one if it is supported. If the kernel doesn't support
|
|
||||||
// STATX_MNT_ID_UNIQUE, the bit is ignored and the returned request mask
|
|
||||||
// will only contain STATX_MNT_ID (if supported).
|
|
||||||
wantStatxMntMask = _STATX_MNT_ID_UNIQUE | unix.STATX_MNT_ID
|
|
||||||
)
|
|
||||||
|
|
||||||
var hasStatxMountID = gocompat.SyncOnceValue(func() bool {
|
|
||||||
var stx unix.Statx_t
|
|
||||||
err := unix.Statx(-int(unix.EBADF), "/", 0, wantStatxMntMask, &stx)
|
|
||||||
return err == nil && stx.Mask&wantStatxMntMask != 0
|
|
||||||
})
|
|
||||||
|
|
||||||
// GetMountID gets the mount identifier associated with the fd and path
|
|
||||||
// combination. It is effectively a wrapper around fetching
|
|
||||||
// STATX_MNT_ID{,_UNIQUE} with unix.Statx, but with a fallback to 0 if the
|
|
||||||
// kernel doesn't support the feature.
|
|
||||||
func GetMountID(dir Fd, path string) (uint64, error) {
|
|
||||||
// If we don't have statx(STATX_MNT_ID*) support, we can't do anything.
|
|
||||||
if !hasStatxMountID() {
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
dirFd, fullPath := prepareAt(dir, path)
|
|
||||||
|
|
||||||
var stx unix.Statx_t
|
|
||||||
err := unix.Statx(dirFd, path, unix.AT_EMPTY_PATH|unix.AT_SYMLINK_NOFOLLOW, wantStatxMntMask, &stx)
|
|
||||||
if stx.Mask&wantStatxMntMask == 0 {
|
|
||||||
// It's not a kernel limitation, for some reason we couldn't get a
|
|
||||||
// mount ID. Assume it's some kind of attack.
|
|
||||||
err = fmt.Errorf("could not get mount id: %w", err)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return 0, &os.PathError{Op: "statx(STATX_MNT_ID_...)", Path: fullPath, Err: err}
|
|
||||||
}
|
|
||||||
runtime.KeepAlive(dir)
|
|
||||||
return stx.Mnt_id, nil
|
|
||||||
}
|
|
||||||
55
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/fd.go
generated
vendored
55
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/fd.go
generated
vendored
@ -1,55 +0,0 @@
|
|||||||
// SPDX-License-Identifier: MPL-2.0
|
|
||||||
|
|
||||||
// Copyright (C) 2025 Aleksa Sarai <cyphar@cyphar.com>
|
|
||||||
// Copyright (C) 2025 SUSE LLC
|
|
||||||
//
|
|
||||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
|
|
||||||
// Package fd provides a drop-in interface-based replacement of [*os.File] that
|
|
||||||
// allows for things like noop-Close wrappers to be used.
|
|
||||||
//
|
|
||||||
// [*os.File]: https://pkg.go.dev/os#File
|
|
||||||
package fd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Fd is an interface that mirrors most of the API of [*os.File], allowing you
|
|
||||||
// to create wrappers that can be used in place of [*os.File].
|
|
||||||
//
|
|
||||||
// [*os.File]: https://pkg.go.dev/os#File
|
|
||||||
type Fd interface {
|
|
||||||
io.Closer
|
|
||||||
Name() string
|
|
||||||
Fd() uintptr
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compile-time interface checks.
|
|
||||||
var (
|
|
||||||
_ Fd = (*os.File)(nil)
|
|
||||||
_ Fd = noClose{}
|
|
||||||
)
|
|
||||||
|
|
||||||
type noClose struct{ inner Fd }
|
|
||||||
|
|
||||||
func (f noClose) Name() string { return f.inner.Name() }
|
|
||||||
func (f noClose) Fd() uintptr { return f.inner.Fd() }
|
|
||||||
|
|
||||||
func (f noClose) Close() error { return nil }
|
|
||||||
|
|
||||||
// NopCloser returns an [*os.File]-like object where the [Close] method is now
|
|
||||||
// a no-op.
|
|
||||||
//
|
|
||||||
// Note that for [*os.File] and similar objects, the Go garbage collector will
|
|
||||||
// still call [Close] on the underlying file unless you use
|
|
||||||
// [runtime.SetFinalizer] to disable this behaviour. This is up to the caller
|
|
||||||
// to do (if necessary).
|
|
||||||
//
|
|
||||||
// [*os.File]: https://pkg.go.dev/os#File
|
|
||||||
// [Close]: https://pkg.go.dev/io#Closer
|
|
||||||
// [runtime.SetFinalizer]: https://pkg.go.dev/runtime#SetFinalizer
|
|
||||||
func NopCloser(f Fd) Fd { return noClose{inner: f} }
|
|
||||||
78
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/fd_linux.go
generated
vendored
78
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/fd_linux.go
generated
vendored
@ -1,78 +0,0 @@
|
|||||||
// SPDX-License-Identifier: MPL-2.0
|
|
||||||
|
|
||||||
//go:build linux
|
|
||||||
|
|
||||||
// Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com>
|
|
||||||
// Copyright (C) 2024-2025 SUSE LLC
|
|
||||||
//
|
|
||||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
|
|
||||||
package fd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"runtime"
|
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
|
|
||||||
"github.com/cyphar/filepath-securejoin/pathrs-lite/internal"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DupWithName creates a new file descriptor referencing the same underlying
|
|
||||||
// file, but with the provided name instead of fd.Name().
|
|
||||||
func DupWithName(fd Fd, name string) (*os.File, error) {
|
|
||||||
fd2, err := unix.FcntlInt(fd.Fd(), unix.F_DUPFD_CLOEXEC, 0)
|
|
||||||
if err != nil {
|
|
||||||
return nil, os.NewSyscallError("fcntl(F_DUPFD_CLOEXEC)", err)
|
|
||||||
}
|
|
||||||
runtime.KeepAlive(fd)
|
|
||||||
return os.NewFile(uintptr(fd2), name), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dup creates a new file description referencing the same underlying file.
|
|
||||||
func Dup(fd Fd) (*os.File, error) {
|
|
||||||
return DupWithName(fd, fd.Name())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fstat is an [Fd]-based wrapper around unix.Fstat.
|
|
||||||
func Fstat(fd Fd) (unix.Stat_t, error) {
|
|
||||||
var stat unix.Stat_t
|
|
||||||
if err := unix.Fstat(int(fd.Fd()), &stat); err != nil {
|
|
||||||
return stat, &os.PathError{Op: "fstat", Path: fd.Name(), Err: err}
|
|
||||||
}
|
|
||||||
runtime.KeepAlive(fd)
|
|
||||||
return stat, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fstatfs is an [Fd]-based wrapper around unix.Fstatfs.
|
|
||||||
func Fstatfs(fd Fd) (unix.Statfs_t, error) {
|
|
||||||
var statfs unix.Statfs_t
|
|
||||||
if err := unix.Fstatfs(int(fd.Fd()), &statfs); err != nil {
|
|
||||||
return statfs, &os.PathError{Op: "fstatfs", Path: fd.Name(), Err: err}
|
|
||||||
}
|
|
||||||
runtime.KeepAlive(fd)
|
|
||||||
return statfs, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsDeadInode detects whether the file has been unlinked from a filesystem and
|
|
||||||
// is thus a "dead inode" from the kernel's perspective.
|
|
||||||
func IsDeadInode(file Fd) error {
|
|
||||||
// If the nlink of a file drops to 0, there is an attacker deleting
|
|
||||||
// directories during our walk, which could result in weird /proc values.
|
|
||||||
// It's better to error out in this case.
|
|
||||||
stat, err := Fstat(file)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("check for dead inode: %w", err)
|
|
||||||
}
|
|
||||||
if stat.Nlink == 0 {
|
|
||||||
err := internal.ErrDeletedInode
|
|
||||||
if stat.Mode&unix.S_IFMT == unix.S_IFDIR {
|
|
||||||
err = internal.ErrInvalidDirectory
|
|
||||||
}
|
|
||||||
return fmt.Errorf("%w %q", err, file.Name())
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
54
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/mount_linux.go
generated
vendored
54
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/mount_linux.go
generated
vendored
@ -1,54 +0,0 @@
|
|||||||
// SPDX-License-Identifier: MPL-2.0
|
|
||||||
|
|
||||||
//go:build linux
|
|
||||||
|
|
||||||
// Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com>
|
|
||||||
// Copyright (C) 2024-2025 SUSE LLC
|
|
||||||
//
|
|
||||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
|
|
||||||
package fd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"runtime"
|
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Fsopen is an [Fd]-based wrapper around unix.Fsopen.
|
|
||||||
func Fsopen(fsName string, flags int) (*os.File, error) {
|
|
||||||
// Make sure we always set O_CLOEXEC.
|
|
||||||
flags |= unix.FSOPEN_CLOEXEC
|
|
||||||
fd, err := unix.Fsopen(fsName, flags)
|
|
||||||
if err != nil {
|
|
||||||
return nil, os.NewSyscallError("fsopen "+fsName, err)
|
|
||||||
}
|
|
||||||
return os.NewFile(uintptr(fd), "fscontext:"+fsName), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fsmount is an [Fd]-based wrapper around unix.Fsmount.
|
|
||||||
func Fsmount(ctx Fd, flags, mountAttrs int) (*os.File, error) {
|
|
||||||
// Make sure we always set O_CLOEXEC.
|
|
||||||
flags |= unix.FSMOUNT_CLOEXEC
|
|
||||||
fd, err := unix.Fsmount(int(ctx.Fd()), flags, mountAttrs)
|
|
||||||
if err != nil {
|
|
||||||
return nil, os.NewSyscallError("fsmount "+ctx.Name(), err)
|
|
||||||
}
|
|
||||||
return os.NewFile(uintptr(fd), "fsmount:"+ctx.Name()), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// OpenTree is an [Fd]-based wrapper around unix.OpenTree.
|
|
||||||
func OpenTree(dir Fd, path string, flags uint) (*os.File, error) {
|
|
||||||
dirFd, fullPath := prepareAt(dir, path)
|
|
||||||
// Make sure we always set O_CLOEXEC.
|
|
||||||
flags |= unix.OPEN_TREE_CLOEXEC
|
|
||||||
fd, err := unix.OpenTree(dirFd, path, flags)
|
|
||||||
if err != nil {
|
|
||||||
return nil, &os.PathError{Op: "open_tree", Path: fullPath, Err: err}
|
|
||||||
}
|
|
||||||
runtime.KeepAlive(dir)
|
|
||||||
return os.NewFile(uintptr(fd), fullPath), nil
|
|
||||||
}
|
|
||||||
62
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/openat2_linux.go
generated
vendored
62
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/openat2_linux.go
generated
vendored
@ -1,62 +0,0 @@
|
|||||||
// SPDX-License-Identifier: MPL-2.0
|
|
||||||
|
|
||||||
//go:build linux
|
|
||||||
|
|
||||||
// Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com>
|
|
||||||
// Copyright (C) 2024-2025 SUSE LLC
|
|
||||||
//
|
|
||||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
|
|
||||||
package fd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"os"
|
|
||||||
"runtime"
|
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
|
|
||||||
"github.com/cyphar/filepath-securejoin/pathrs-lite/internal"
|
|
||||||
)
|
|
||||||
|
|
||||||
func scopedLookupShouldRetry(how *unix.OpenHow, err error) bool {
|
|
||||||
// RESOLVE_IN_ROOT (and RESOLVE_BENEATH) can return -EAGAIN if we resolve
|
|
||||||
// ".." while a mount or rename occurs anywhere on the system. This could
|
|
||||||
// happen spuriously, or as the result of an attacker trying to mess with
|
|
||||||
// us during lookup.
|
|
||||||
//
|
|
||||||
// In addition, scoped lookups have a "safety check" at the end of
|
|
||||||
// complete_walk which will return -EXDEV if the final path is not in the
|
|
||||||
// root.
|
|
||||||
return how.Resolve&(unix.RESOLVE_IN_ROOT|unix.RESOLVE_BENEATH) != 0 &&
|
|
||||||
(errors.Is(err, unix.EAGAIN) || errors.Is(err, unix.EXDEV))
|
|
||||||
}
|
|
||||||
|
|
||||||
const scopedLookupMaxRetries = 32
|
|
||||||
|
|
||||||
// Openat2 is an [Fd]-based wrapper around unix.Openat2, but with some retry
|
|
||||||
// logic in case of EAGAIN errors.
|
|
||||||
func Openat2(dir Fd, path string, how *unix.OpenHow) (*os.File, error) {
|
|
||||||
dirFd, fullPath := prepareAt(dir, path)
|
|
||||||
// Make sure we always set O_CLOEXEC.
|
|
||||||
how.Flags |= unix.O_CLOEXEC
|
|
||||||
var tries int
|
|
||||||
for tries < scopedLookupMaxRetries {
|
|
||||||
fd, err := unix.Openat2(dirFd, path, how)
|
|
||||||
if err != nil {
|
|
||||||
if scopedLookupShouldRetry(how, err) {
|
|
||||||
// We retry a couple of times to avoid the spurious errors, and
|
|
||||||
// if we are being attacked then returning -EAGAIN is the best
|
|
||||||
// we can do.
|
|
||||||
tries++
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return nil, &os.PathError{Op: "openat2", Path: fullPath, Err: err}
|
|
||||||
}
|
|
||||||
runtime.KeepAlive(dir)
|
|
||||||
return os.NewFile(uintptr(fd), fullPath), nil
|
|
||||||
}
|
|
||||||
return nil, &os.PathError{Op: "openat2", Path: fullPath, Err: internal.ErrPossibleAttack}
|
|
||||||
}
|
|
||||||
10
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/README.md
generated
vendored
10
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/README.md
generated
vendored
@ -1,10 +0,0 @@
|
|||||||
## gocompat ##
|
|
||||||
|
|
||||||
This directory contains backports of stdlib functions from later Go versions so
|
|
||||||
the filepath-securejoin can continue to be used by projects that are stuck with
|
|
||||||
Go 1.18 support. Note that often filepath-securejoin is added in security
|
|
||||||
patches for old releases, so avoiding the need to bump Go compiler requirements
|
|
||||||
is a huge plus to downstreams.
|
|
||||||
|
|
||||||
The source code is licensed under the same license as the Go stdlib. See the
|
|
||||||
source files for the precise license information.
|
|
||||||
13
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/doc.go
generated
vendored
13
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/doc.go
generated
vendored
@ -1,13 +0,0 @@
|
|||||||
// SPDX-License-Identifier: BSD-3-Clause
|
|
||||||
//go:build linux && go1.20
|
|
||||||
|
|
||||||
// Copyright (C) 2025 SUSE LLC. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// Package gocompat includes compatibility shims (backported from future Go
|
|
||||||
// stdlib versions) to permit filepath-securejoin to be used with older Go
|
|
||||||
// versions (often filepath-securejoin is added in security patches for old
|
|
||||||
// releases, so avoiding the need to bump Go compiler requirements is a huge
|
|
||||||
// plus to downstreams).
|
|
||||||
package gocompat
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
// SPDX-License-Identifier: BSD-3-Clause
|
|
||||||
//go:build linux && go1.20
|
|
||||||
|
|
||||||
// Copyright (C) 2024 SUSE LLC. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package gocompat
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
// WrapBaseError is a helper that is equivalent to fmt.Errorf("%w: %w"), except
|
|
||||||
// that on pre-1.20 Go versions only errors.Is() works properly (errors.Unwrap)
|
|
||||||
// is only guaranteed to give you baseErr.
|
|
||||||
func WrapBaseError(baseErr, extraErr error) error {
|
|
||||||
return fmt.Errorf("%w: %w", extraErr, baseErr)
|
|
||||||
}
|
|
||||||
@ -1,40 +0,0 @@
|
|||||||
// SPDX-License-Identifier: BSD-3-Clause
|
|
||||||
|
|
||||||
//go:build linux && !go1.20
|
|
||||||
|
|
||||||
// Copyright (C) 2024 SUSE LLC. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package gocompat
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
type wrappedError struct {
|
|
||||||
inner error
|
|
||||||
isError error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (err wrappedError) Is(target error) bool {
|
|
||||||
return err.isError == target
|
|
||||||
}
|
|
||||||
|
|
||||||
func (err wrappedError) Unwrap() error {
|
|
||||||
return err.inner
|
|
||||||
}
|
|
||||||
|
|
||||||
func (err wrappedError) Error() string {
|
|
||||||
return fmt.Sprintf("%v: %v", err.isError, err.inner)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WrapBaseError is a helper that is equivalent to fmt.Errorf("%w: %w"), except
|
|
||||||
// that on pre-1.20 Go versions only errors.Is() works properly (errors.Unwrap)
|
|
||||||
// is only guaranteed to give you baseErr.
|
|
||||||
func WrapBaseError(baseErr, extraErr error) error {
|
|
||||||
return wrappedError{
|
|
||||||
inner: baseErr,
|
|
||||||
isError: extraErr,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,53 +0,0 @@
|
|||||||
// SPDX-License-Identifier: BSD-3-Clause
|
|
||||||
|
|
||||||
//go:build linux && go1.21
|
|
||||||
|
|
||||||
// Copyright (C) 2024-2025 SUSE LLC. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package gocompat
|
|
||||||
|
|
||||||
import (
|
|
||||||
"cmp"
|
|
||||||
"slices"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SlicesDeleteFunc is equivalent to Go 1.21's slices.DeleteFunc.
|
|
||||||
func SlicesDeleteFunc[S ~[]E, E any](slice S, delFn func(E) bool) S {
|
|
||||||
return slices.DeleteFunc(slice, delFn)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SlicesContains is equivalent to Go 1.21's slices.Contains.
|
|
||||||
func SlicesContains[S ~[]E, E comparable](slice S, val E) bool {
|
|
||||||
return slices.Contains(slice, val)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SlicesClone is equivalent to Go 1.21's slices.Clone.
|
|
||||||
func SlicesClone[S ~[]E, E any](slice S) S {
|
|
||||||
return slices.Clone(slice)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SyncOnceValue is equivalent to Go 1.21's sync.OnceValue.
|
|
||||||
func SyncOnceValue[T any](f func() T) func() T {
|
|
||||||
return sync.OnceValue(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SyncOnceValues is equivalent to Go 1.21's sync.OnceValues.
|
|
||||||
func SyncOnceValues[T1, T2 any](f func() (T1, T2)) func() (T1, T2) {
|
|
||||||
return sync.OnceValues(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CmpOrdered is equivalent to Go 1.21's cmp.Ordered generic type definition.
|
|
||||||
type CmpOrdered = cmp.Ordered
|
|
||||||
|
|
||||||
// CmpCompare is equivalent to Go 1.21's cmp.Compare.
|
|
||||||
func CmpCompare[T CmpOrdered](x, y T) int {
|
|
||||||
return cmp.Compare(x, y)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Max2 is equivalent to Go 1.21's max builtin (but only for two parameters).
|
|
||||||
func Max2[T CmpOrdered](x, y T) T {
|
|
||||||
return max(x, y)
|
|
||||||
}
|
|
||||||
@ -1,187 +0,0 @@
|
|||||||
// SPDX-License-Identifier: BSD-3-Clause
|
|
||||||
|
|
||||||
//go:build linux && !go1.21
|
|
||||||
|
|
||||||
// Copyright (C) 2021, 2022 The Go Authors. All rights reserved.
|
|
||||||
// Copyright (C) 2024-2025 SUSE LLC. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE.BSD file.
|
|
||||||
|
|
||||||
package gocompat
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
// These are very minimal implementations of functions that appear in Go 1.21's
|
|
||||||
// stdlib, included so that we can build on older Go versions. Most are
|
|
||||||
// borrowed directly from the stdlib, and a few are modified to be "obviously
|
|
||||||
// correct" without needing to copy too many other helpers.
|
|
||||||
|
|
||||||
// clearSlice is equivalent to Go 1.21's builtin clear.
|
|
||||||
// Copied from the Go 1.24 stdlib implementation.
|
|
||||||
func clearSlice[S ~[]E, E any](slice S) {
|
|
||||||
var zero E
|
|
||||||
for i := range slice {
|
|
||||||
slice[i] = zero
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// slicesIndexFunc is equivalent to Go 1.21's slices.IndexFunc.
|
|
||||||
// Copied from the Go 1.24 stdlib implementation.
|
|
||||||
func slicesIndexFunc[S ~[]E, E any](s S, f func(E) bool) int {
|
|
||||||
for i := range s {
|
|
||||||
if f(s[i]) {
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
|
|
||||||
// SlicesDeleteFunc is equivalent to Go 1.21's slices.DeleteFunc.
|
|
||||||
// Copied from the Go 1.24 stdlib implementation.
|
|
||||||
func SlicesDeleteFunc[S ~[]E, E any](s S, del func(E) bool) S {
|
|
||||||
i := slicesIndexFunc(s, del)
|
|
||||||
if i == -1 {
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
// Don't start copying elements until we find one to delete.
|
|
||||||
for j := i + 1; j < len(s); j++ {
|
|
||||||
if v := s[j]; !del(v) {
|
|
||||||
s[i] = v
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
clearSlice(s[i:]) // zero/nil out the obsolete elements, for GC
|
|
||||||
return s[:i]
|
|
||||||
}
|
|
||||||
|
|
||||||
// SlicesContains is equivalent to Go 1.21's slices.Contains.
|
|
||||||
// Similar to the stdlib slices.Contains, except that we don't have
|
|
||||||
// slices.Index so we need to use slices.IndexFunc for this non-Func helper.
|
|
||||||
func SlicesContains[S ~[]E, E comparable](s S, v E) bool {
|
|
||||||
return slicesIndexFunc(s, func(e E) bool { return e == v }) >= 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// SlicesClone is equivalent to Go 1.21's slices.Clone.
|
|
||||||
// Copied from the Go 1.24 stdlib implementation.
|
|
||||||
func SlicesClone[S ~[]E, E any](s S) S {
|
|
||||||
// Preserve nil in case it matters.
|
|
||||||
if s == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return append(S([]E{}), s...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SyncOnceValue is equivalent to Go 1.21's sync.OnceValue.
|
|
||||||
// Copied from the Go 1.25 stdlib implementation.
|
|
||||||
func SyncOnceValue[T any](f func() T) func() T {
|
|
||||||
// Use a struct so that there's a single heap allocation.
|
|
||||||
d := struct {
|
|
||||||
f func() T
|
|
||||||
once sync.Once
|
|
||||||
valid bool
|
|
||||||
p any
|
|
||||||
result T
|
|
||||||
}{
|
|
||||||
f: f,
|
|
||||||
}
|
|
||||||
return func() T {
|
|
||||||
d.once.Do(func() {
|
|
||||||
defer func() {
|
|
||||||
d.f = nil
|
|
||||||
d.p = recover()
|
|
||||||
if !d.valid {
|
|
||||||
panic(d.p)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
d.result = d.f()
|
|
||||||
d.valid = true
|
|
||||||
})
|
|
||||||
if !d.valid {
|
|
||||||
panic(d.p)
|
|
||||||
}
|
|
||||||
return d.result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SyncOnceValues is equivalent to Go 1.21's sync.OnceValues.
|
|
||||||
// Copied from the Go 1.25 stdlib implementation.
|
|
||||||
func SyncOnceValues[T1, T2 any](f func() (T1, T2)) func() (T1, T2) {
|
|
||||||
// Use a struct so that there's a single heap allocation.
|
|
||||||
d := struct {
|
|
||||||
f func() (T1, T2)
|
|
||||||
once sync.Once
|
|
||||||
valid bool
|
|
||||||
p any
|
|
||||||
r1 T1
|
|
||||||
r2 T2
|
|
||||||
}{
|
|
||||||
f: f,
|
|
||||||
}
|
|
||||||
return func() (T1, T2) {
|
|
||||||
d.once.Do(func() {
|
|
||||||
defer func() {
|
|
||||||
d.f = nil
|
|
||||||
d.p = recover()
|
|
||||||
if !d.valid {
|
|
||||||
panic(d.p)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
d.r1, d.r2 = d.f()
|
|
||||||
d.valid = true
|
|
||||||
})
|
|
||||||
if !d.valid {
|
|
||||||
panic(d.p)
|
|
||||||
}
|
|
||||||
return d.r1, d.r2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// CmpOrdered is equivalent to Go 1.21's cmp.Ordered generic type definition.
|
|
||||||
// Copied from the Go 1.25 stdlib implementation.
|
|
||||||
type CmpOrdered interface {
|
|
||||||
~int | ~int8 | ~int16 | ~int32 | ~int64 |
|
|
||||||
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr |
|
|
||||||
~float32 | ~float64 |
|
|
||||||
~string
|
|
||||||
}
|
|
||||||
|
|
||||||
// isNaN reports whether x is a NaN without requiring the math package.
|
|
||||||
// This will always return false if T is not floating-point.
|
|
||||||
// Copied from the Go 1.25 stdlib implementation.
|
|
||||||
func isNaN[T CmpOrdered](x T) bool {
|
|
||||||
return x != x
|
|
||||||
}
|
|
||||||
|
|
||||||
// CmpCompare is equivalent to Go 1.21's cmp.Compare.
|
|
||||||
// Copied from the Go 1.25 stdlib implementation.
|
|
||||||
func CmpCompare[T CmpOrdered](x, y T) int {
|
|
||||||
xNaN := isNaN(x)
|
|
||||||
yNaN := isNaN(y)
|
|
||||||
if xNaN {
|
|
||||||
if yNaN {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
if yNaN {
|
|
||||||
return +1
|
|
||||||
}
|
|
||||||
if x < y {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
if x > y {
|
|
||||||
return +1
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Max2 is equivalent to Go 1.21's max builtin for two parameters.
|
|
||||||
func Max2[T CmpOrdered](x, y T) T {
|
|
||||||
m := x
|
|
||||||
if y > m {
|
|
||||||
m = y
|
|
||||||
}
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
@ -1,123 +0,0 @@
|
|||||||
// SPDX-License-Identifier: BSD-3-Clause
|
|
||||||
|
|
||||||
// Copyright (C) 2022 The Go Authors. All rights reserved.
|
|
||||||
// Copyright (C) 2025 SUSE LLC. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE.BSD file.
|
|
||||||
|
|
||||||
// The parsing logic is very loosely based on the Go stdlib's
|
|
||||||
// src/internal/syscall/unix/kernel_version_linux.go but with an API that looks
|
|
||||||
// a bit like runc's libcontainer/system/kernelversion.
|
|
||||||
//
|
|
||||||
// TODO(cyphar): This API has been copied around to a lot of different projects
|
|
||||||
// (Docker, containerd, runc, and now filepath-securejoin) -- maybe we should
|
|
||||||
// put it in a separate project?
|
|
||||||
|
|
||||||
// Package kernelversion provides a simple mechanism for checking whether the
|
|
||||||
// running kernel is at least as new as some baseline kernel version. This is
|
|
||||||
// often useful when checking for features that would be too complicated to
|
|
||||||
// test support for (or in cases where we know that some kernel features in
|
|
||||||
// backport-heavy kernels are broken and need to be avoided).
|
|
||||||
package kernelversion
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
|
|
||||||
"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat"
|
|
||||||
)
|
|
||||||
|
|
||||||
// KernelVersion is a numeric representation of the key numerical elements of a
|
|
||||||
// kernel version (for instance, "4.1.2-default-1" would be represented as
|
|
||||||
// KernelVersion{4, 1, 2}).
|
|
||||||
type KernelVersion []uint64
|
|
||||||
|
|
||||||
func (kver KernelVersion) String() string {
|
|
||||||
var str strings.Builder
|
|
||||||
for idx, elem := range kver {
|
|
||||||
if idx != 0 {
|
|
||||||
_, _ = str.WriteRune('.')
|
|
||||||
}
|
|
||||||
_, _ = str.WriteString(strconv.FormatUint(elem, 10))
|
|
||||||
}
|
|
||||||
return str.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
var errInvalidKernelVersion = errors.New("invalid kernel version")
|
|
||||||
|
|
||||||
// parseKernelVersion parses a string and creates a KernelVersion based on it.
|
|
||||||
func parseKernelVersion(kverStr string) (KernelVersion, error) {
|
|
||||||
kver := make(KernelVersion, 1, 3)
|
|
||||||
for idx, ch := range kverStr {
|
|
||||||
if '0' <= ch && ch <= '9' {
|
|
||||||
v := &kver[len(kver)-1]
|
|
||||||
*v = (*v * 10) + uint64(ch-'0')
|
|
||||||
} else {
|
|
||||||
if idx == 0 || kverStr[idx-1] < '0' || '9' < kverStr[idx-1] {
|
|
||||||
// "." must be preceded by a digit while in version section
|
|
||||||
return nil, fmt.Errorf("%w %q: kernel version has dot(s) followed by non-digit in version section", errInvalidKernelVersion, kverStr)
|
|
||||||
}
|
|
||||||
if ch != '.' {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
kver = append(kver, 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(kver) < 2 {
|
|
||||||
return nil, fmt.Errorf("%w %q: kernel versions must contain at least two components", errInvalidKernelVersion, kverStr)
|
|
||||||
}
|
|
||||||
return kver, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// getKernelVersion gets the current kernel version.
|
|
||||||
var getKernelVersion = gocompat.SyncOnceValues(func() (KernelVersion, error) {
|
|
||||||
var uts unix.Utsname
|
|
||||||
if err := unix.Uname(&uts); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// Remove the \x00 from the release.
|
|
||||||
release := uts.Release[:]
|
|
||||||
return parseKernelVersion(string(release[:bytes.IndexByte(release, 0)]))
|
|
||||||
})
|
|
||||||
|
|
||||||
// GreaterEqualThan returns true if the the host kernel version is greater than
|
|
||||||
// or equal to the provided [KernelVersion]. When doing this comparison, any
|
|
||||||
// non-numerical suffixes of the host kernel version are ignored.
|
|
||||||
//
|
|
||||||
// If the number of components provided is not equal to the number of numerical
|
|
||||||
// components of the host kernel version, any missing components are treated as
|
|
||||||
// 0. This means that GreaterEqualThan(KernelVersion{4}) will be treated the
|
|
||||||
// same as GreaterEqualThan(KernelVersion{4, 0, 0, ..., 0, 0}), and that if the
|
|
||||||
// host kernel version is "4" then GreaterEqualThan(KernelVersion{4, 1}) will
|
|
||||||
// return false (because the host version will be treated as "4.0").
|
|
||||||
func GreaterEqualThan(wantKver KernelVersion) (bool, error) {
|
|
||||||
hostKver, err := getKernelVersion()
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pad out the kernel version lengths to match one another.
|
|
||||||
cmpLen := gocompat.Max2(len(hostKver), len(wantKver))
|
|
||||||
hostKver = append(hostKver, make(KernelVersion, cmpLen-len(hostKver))...)
|
|
||||||
wantKver = append(wantKver, make(KernelVersion, cmpLen-len(wantKver))...)
|
|
||||||
|
|
||||||
for i := 0; i < cmpLen; i++ {
|
|
||||||
switch gocompat.CmpCompare(hostKver[i], wantKver[i]) {
|
|
||||||
case -1:
|
|
||||||
// host < want
|
|
||||||
return false, nil
|
|
||||||
case +1:
|
|
||||||
// host > want
|
|
||||||
return true, nil
|
|
||||||
case 0:
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// equal version values
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
12
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux/doc.go
generated
vendored
12
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux/doc.go
generated
vendored
@ -1,12 +0,0 @@
|
|||||||
// SPDX-License-Identifier: MPL-2.0
|
|
||||||
|
|
||||||
// Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com>
|
|
||||||
// Copyright (C) 2024-2025 SUSE LLC
|
|
||||||
//
|
|
||||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
|
|
||||||
// Package linux returns information about what features are supported on the
|
|
||||||
// running kernel.
|
|
||||||
package linux
|
|
||||||
47
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux/mount_linux.go
generated
vendored
47
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux/mount_linux.go
generated
vendored
@ -1,47 +0,0 @@
|
|||||||
// SPDX-License-Identifier: MPL-2.0
|
|
||||||
|
|
||||||
//go:build linux
|
|
||||||
|
|
||||||
// Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com>
|
|
||||||
// Copyright (C) 2024-2025 SUSE LLC
|
|
||||||
//
|
|
||||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
|
|
||||||
package linux
|
|
||||||
|
|
||||||
import (
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
|
|
||||||
"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat"
|
|
||||||
"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/kernelversion"
|
|
||||||
)
|
|
||||||
|
|
||||||
// HasNewMountAPI returns whether the new fsopen(2) mount API is supported on
|
|
||||||
// the running kernel.
|
|
||||||
var HasNewMountAPI = gocompat.SyncOnceValue(func() bool {
|
|
||||||
// All of the pieces of the new mount API we use (fsopen, fsconfig,
|
|
||||||
// fsmount, open_tree) were added together in Linux 5.2[1,2], so we can
|
|
||||||
// just check for one of the syscalls and the others should also be
|
|
||||||
// available.
|
|
||||||
//
|
|
||||||
// Just try to use open_tree(2) to open a file without OPEN_TREE_CLONE.
|
|
||||||
// This is equivalent to openat(2), but tells us if open_tree is
|
|
||||||
// available (and thus all of the other basic new mount API syscalls).
|
|
||||||
// open_tree(2) is most light-weight syscall to test here.
|
|
||||||
//
|
|
||||||
// [1]: merge commit 400913252d09
|
|
||||||
// [2]: <https://lore.kernel.org/lkml/153754740781.17872.7869536526927736855.stgit@warthog.procyon.org.uk/>
|
|
||||||
fd, err := unix.OpenTree(-int(unix.EBADF), "/", unix.OPEN_TREE_CLOEXEC)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
_ = unix.Close(fd)
|
|
||||||
|
|
||||||
// RHEL 8 has a backport of fsopen(2) that appears to have some very
|
|
||||||
// difficult to debug performance pathology. As such, it seems prudent to
|
|
||||||
// simply reject pre-5.2 kernels.
|
|
||||||
isNotBackport, _ := kernelversion.GreaterEqualThan(kernelversion.KernelVersion{5, 2})
|
|
||||||
return isNotBackport
|
|
||||||
})
|
|
||||||
31
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux/openat2_linux.go
generated
vendored
31
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux/openat2_linux.go
generated
vendored
@ -1,31 +0,0 @@
|
|||||||
// SPDX-License-Identifier: MPL-2.0
|
|
||||||
|
|
||||||
//go:build linux
|
|
||||||
|
|
||||||
// Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com>
|
|
||||||
// Copyright (C) 2024-2025 SUSE LLC
|
|
||||||
//
|
|
||||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
|
|
||||||
package linux
|
|
||||||
|
|
||||||
import (
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
|
|
||||||
"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat"
|
|
||||||
)
|
|
||||||
|
|
||||||
// HasOpenat2 returns whether openat2(2) is supported on the running kernel.
|
|
||||||
var HasOpenat2 = gocompat.SyncOnceValue(func() bool {
|
|
||||||
fd, err := unix.Openat2(unix.AT_FDCWD, ".", &unix.OpenHow{
|
|
||||||
Flags: unix.O_PATH | unix.O_CLOEXEC,
|
|
||||||
Resolve: unix.RESOLVE_NO_SYMLINKS | unix.RESOLVE_IN_ROOT,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
_ = unix.Close(fd)
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
544
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/procfs/procfs_linux.go
generated
vendored
544
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/procfs/procfs_linux.go
generated
vendored
@ -1,544 +0,0 @@
|
|||||||
// SPDX-License-Identifier: MPL-2.0
|
|
||||||
|
|
||||||
//go:build linux
|
|
||||||
|
|
||||||
// Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com>
|
|
||||||
// Copyright (C) 2024-2025 SUSE LLC
|
|
||||||
//
|
|
||||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
|
|
||||||
// Package procfs provides a safe API for operating on /proc on Linux. Note
|
|
||||||
// that this is the *internal* procfs API, mainy needed due to Go's
|
|
||||||
// restrictions on cyclic dependencies and its incredibly minimal visibility
|
|
||||||
// system without making a separate internal/ package.
|
|
||||||
package procfs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"runtime"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
|
|
||||||
"github.com/cyphar/filepath-securejoin/pathrs-lite/internal"
|
|
||||||
"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/assert"
|
|
||||||
"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd"
|
|
||||||
"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat"
|
|
||||||
"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux"
|
|
||||||
)
|
|
||||||
|
|
||||||
// The kernel guarantees that the root inode of a procfs mount has an
|
|
||||||
// f_type of PROC_SUPER_MAGIC and st_ino of PROC_ROOT_INO.
|
|
||||||
const (
|
|
||||||
procSuperMagic = 0x9fa0 // PROC_SUPER_MAGIC
|
|
||||||
procRootIno = 1 // PROC_ROOT_INO
|
|
||||||
)
|
|
||||||
|
|
||||||
// verifyProcHandle checks that the handle is from a procfs filesystem.
|
|
||||||
// Contrast this to [verifyProcRoot], which also verifies that the handle is
|
|
||||||
// the root of a procfs mount.
|
|
||||||
func verifyProcHandle(procHandle fd.Fd) error {
|
|
||||||
if statfs, err := fd.Fstatfs(procHandle); err != nil {
|
|
||||||
return err
|
|
||||||
} else if statfs.Type != procSuperMagic {
|
|
||||||
return fmt.Errorf("%w: incorrect procfs root filesystem type 0x%x", errUnsafeProcfs, statfs.Type)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// verifyProcRoot verifies that the handle is the root of a procfs filesystem.
|
|
||||||
// Contrast this to [verifyProcHandle], which only verifies if the handle is
|
|
||||||
// some file on procfs (regardless of what file it is).
|
|
||||||
func verifyProcRoot(procRoot fd.Fd) error {
|
|
||||||
if err := verifyProcHandle(procRoot); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if stat, err := fd.Fstat(procRoot); err != nil {
|
|
||||||
return err
|
|
||||||
} else if stat.Ino != procRootIno {
|
|
||||||
return fmt.Errorf("%w: incorrect procfs root inode number %d", errUnsafeProcfs, stat.Ino)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type procfsFeatures struct {
|
|
||||||
// hasSubsetPid was added in Linux 5.8, along with hidepid=ptraceable (and
|
|
||||||
// string-based hidepid= values). Before this patchset, it was not really
|
|
||||||
// safe to try to modify procfs superblock flags because the superblock was
|
|
||||||
// shared -- so if this feature is not available, **you should not set any
|
|
||||||
// superblock flags**.
|
|
||||||
//
|
|
||||||
// 6814ef2d992a ("proc: add option to mount only a pids subset")
|
|
||||||
// fa10fed30f25 ("proc: allow to mount many instances of proc in one pid namespace")
|
|
||||||
// 24a71ce5c47f ("proc: instantiate only pids that we can ptrace on 'hidepid=4' mount option")
|
|
||||||
// 1c6c4d112e81 ("proc: use human-readable values for hidepid")
|
|
||||||
// 9ff7258575d5 ("Merge branch 'proc-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/ebiederm/user-namespace")
|
|
||||||
hasSubsetPid bool
|
|
||||||
}
|
|
||||||
|
|
||||||
var getProcfsFeatures = gocompat.SyncOnceValue(func() procfsFeatures {
|
|
||||||
if !linux.HasNewMountAPI() {
|
|
||||||
return procfsFeatures{}
|
|
||||||
}
|
|
||||||
procfsCtx, err := fd.Fsopen("proc", unix.FSOPEN_CLOEXEC)
|
|
||||||
if err != nil {
|
|
||||||
return procfsFeatures{}
|
|
||||||
}
|
|
||||||
defer procfsCtx.Close() //nolint:errcheck // close failures aren't critical here
|
|
||||||
|
|
||||||
return procfsFeatures{
|
|
||||||
hasSubsetPid: unix.FsconfigSetString(int(procfsCtx.Fd()), "subset", "pid") == nil,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
func newPrivateProcMount(subset bool) (_ *Handle, Err error) {
|
|
||||||
procfsCtx, err := fd.Fsopen("proc", unix.FSOPEN_CLOEXEC)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer procfsCtx.Close() //nolint:errcheck // close failures aren't critical here
|
|
||||||
|
|
||||||
if subset && getProcfsFeatures().hasSubsetPid {
|
|
||||||
// Try to configure hidepid=ptraceable,subset=pid if possible, but
|
|
||||||
// ignore errors.
|
|
||||||
_ = unix.FsconfigSetString(int(procfsCtx.Fd()), "hidepid", "ptraceable")
|
|
||||||
_ = unix.FsconfigSetString(int(procfsCtx.Fd()), "subset", "pid")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get an actual handle.
|
|
||||||
if err := unix.FsconfigCreate(int(procfsCtx.Fd())); err != nil {
|
|
||||||
return nil, os.NewSyscallError("fsconfig create procfs", err)
|
|
||||||
}
|
|
||||||
// TODO: Output any information from the fscontext log to debug logs.
|
|
||||||
procRoot, err := fd.Fsmount(procfsCtx, unix.FSMOUNT_CLOEXEC, unix.MS_NODEV|unix.MS_NOEXEC|unix.MS_NOSUID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
if Err != nil {
|
|
||||||
_ = procRoot.Close()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
return newHandle(procRoot)
|
|
||||||
}
|
|
||||||
|
|
||||||
func clonePrivateProcMount() (_ *Handle, Err error) {
|
|
||||||
// Try to make a clone without using AT_RECURSIVE if we can. If this works,
|
|
||||||
// we can be sure there are no over-mounts and so if the root is valid then
|
|
||||||
// we're golden. Otherwise, we have to deal with over-mounts.
|
|
||||||
procRoot, err := fd.OpenTree(nil, "/proc", unix.OPEN_TREE_CLONE)
|
|
||||||
if err != nil || hookForcePrivateProcRootOpenTreeAtRecursive(procRoot) {
|
|
||||||
procRoot, err = fd.OpenTree(nil, "/proc", unix.OPEN_TREE_CLONE|unix.AT_RECURSIVE)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("creating a detached procfs clone: %w", err)
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
if Err != nil {
|
|
||||||
_ = procRoot.Close()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
return newHandle(procRoot)
|
|
||||||
}
|
|
||||||
|
|
||||||
func privateProcRoot(subset bool) (*Handle, error) {
|
|
||||||
if !linux.HasNewMountAPI() || hookForceGetProcRootUnsafe() {
|
|
||||||
return nil, fmt.Errorf("new mount api: %w", unix.ENOTSUP)
|
|
||||||
}
|
|
||||||
// Try to create a new procfs mount from scratch if we can. This ensures we
|
|
||||||
// can get a procfs mount even if /proc is fake (for whatever reason).
|
|
||||||
procRoot, err := newPrivateProcMount(subset)
|
|
||||||
if err != nil || hookForcePrivateProcRootOpenTree(procRoot) {
|
|
||||||
// Try to clone /proc then...
|
|
||||||
procRoot, err = clonePrivateProcMount()
|
|
||||||
}
|
|
||||||
return procRoot, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func unsafeHostProcRoot() (_ *Handle, Err error) {
|
|
||||||
procRoot, err := os.OpenFile("/proc", unix.O_PATH|unix.O_NOFOLLOW|unix.O_DIRECTORY|unix.O_CLOEXEC, 0)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
if Err != nil {
|
|
||||||
_ = procRoot.Close()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
return newHandle(procRoot)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle is a wrapper around an *os.File handle to "/proc", which can be used
|
|
||||||
// to do further procfs-related operations in a safe way.
|
|
||||||
type Handle struct {
|
|
||||||
Inner fd.Fd
|
|
||||||
// Does this handle have subset=pid set?
|
|
||||||
isSubset bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func newHandle(procRoot fd.Fd) (*Handle, error) {
|
|
||||||
if err := verifyProcRoot(procRoot); err != nil {
|
|
||||||
// This is only used in methods that
|
|
||||||
_ = procRoot.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
proc := &Handle{Inner: procRoot}
|
|
||||||
// With subset=pid we can be sure that /proc/uptime will not exist.
|
|
||||||
if err := fd.Faccessat(proc.Inner, "uptime", unix.F_OK, unix.AT_SYMLINK_NOFOLLOW); err != nil {
|
|
||||||
proc.isSubset = errors.Is(err, os.ErrNotExist)
|
|
||||||
}
|
|
||||||
return proc, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close closes the underlying file for the Handle.
|
|
||||||
func (proc *Handle) Close() error { return proc.Inner.Close() }
|
|
||||||
|
|
||||||
var getCachedProcRoot = gocompat.SyncOnceValue(func() *Handle {
|
|
||||||
procRoot, err := getProcRoot(true)
|
|
||||||
if err != nil {
|
|
||||||
return nil // just don't cache if we see an error
|
|
||||||
}
|
|
||||||
if !procRoot.isSubset {
|
|
||||||
return nil // we only cache verified subset=pid handles
|
|
||||||
}
|
|
||||||
|
|
||||||
// Disarm (*Handle).Close() to stop someone from accidentally closing
|
|
||||||
// the global handle.
|
|
||||||
procRoot.Inner = fd.NopCloser(procRoot.Inner)
|
|
||||||
return procRoot
|
|
||||||
})
|
|
||||||
|
|
||||||
// OpenProcRoot tries to open a "safer" handle to "/proc".
|
|
||||||
func OpenProcRoot() (*Handle, error) {
|
|
||||||
if proc := getCachedProcRoot(); proc != nil {
|
|
||||||
return proc, nil
|
|
||||||
}
|
|
||||||
return getProcRoot(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
// OpenUnsafeProcRoot opens a handle to "/proc" without any overmounts or
|
|
||||||
// masked paths (but also without "subset=pid").
|
|
||||||
func OpenUnsafeProcRoot() (*Handle, error) { return getProcRoot(false) }
|
|
||||||
|
|
||||||
func getProcRoot(subset bool) (*Handle, error) {
|
|
||||||
proc, err := privateProcRoot(subset)
|
|
||||||
if err != nil {
|
|
||||||
// Fall back to using a /proc handle if making a private mount failed.
|
|
||||||
// If we have openat2, at least we can avoid some kinds of over-mount
|
|
||||||
// attacks, but without openat2 there's not much we can do.
|
|
||||||
proc, err = unsafeHostProcRoot()
|
|
||||||
}
|
|
||||||
return proc, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var hasProcThreadSelf = gocompat.SyncOnceValue(func() bool {
|
|
||||||
return unix.Access("/proc/thread-self/", unix.F_OK) == nil
|
|
||||||
})
|
|
||||||
|
|
||||||
var errUnsafeProcfs = errors.New("unsafe procfs detected")
|
|
||||||
|
|
||||||
// lookup is a very minimal wrapper around [procfsLookupInRoot] which is
|
|
||||||
// intended to be called from the external API.
|
|
||||||
func (proc *Handle) lookup(subpath string) (*os.File, error) {
|
|
||||||
handle, err := procfsLookupInRoot(proc.Inner, subpath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return handle, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// procfsBase is an enum indicating the prefix of a subpath in operations
|
|
||||||
// involving [Handle]s.
|
|
||||||
type procfsBase string
|
|
||||||
|
|
||||||
const (
|
|
||||||
// ProcRoot refers to the root of the procfs (i.e., "/proc/<subpath>").
|
|
||||||
ProcRoot procfsBase = "/proc"
|
|
||||||
// ProcSelf refers to the current process' subdirectory (i.e.,
|
|
||||||
// "/proc/self/<subpath>").
|
|
||||||
ProcSelf procfsBase = "/proc/self"
|
|
||||||
// ProcThreadSelf refers to the current thread's subdirectory (i.e.,
|
|
||||||
// "/proc/thread-self/<subpath>"). In multi-threaded programs (i.e., all Go
|
|
||||||
// programs) where one thread has a different CLONE_FS, it is possible for
|
|
||||||
// "/proc/self" to point the wrong thread and so "/proc/thread-self" may be
|
|
||||||
// necessary. Note that on pre-3.17 kernels, "/proc/thread-self" doesn't
|
|
||||||
// exist and so a fallback will be used in that case.
|
|
||||||
ProcThreadSelf procfsBase = "/proc/thread-self"
|
|
||||||
// TODO: Switch to an interface setup so we can have a more type-safe
|
|
||||||
// version of ProcPid and remove the need to worry about invalid string
|
|
||||||
// values.
|
|
||||||
)
|
|
||||||
|
|
||||||
// prefix returns a prefix that can be used with the given [Handle].
|
|
||||||
func (base procfsBase) prefix(proc *Handle) (string, error) {
|
|
||||||
switch base {
|
|
||||||
case ProcRoot:
|
|
||||||
return ".", nil
|
|
||||||
case ProcSelf:
|
|
||||||
return "self", nil
|
|
||||||
case ProcThreadSelf:
|
|
||||||
threadSelf := "thread-self"
|
|
||||||
if !hasProcThreadSelf() || hookForceProcSelfTask() {
|
|
||||||
// Pre-3.17 kernels don't have /proc/thread-self, so do it
|
|
||||||
// manually.
|
|
||||||
threadSelf = "self/task/" + strconv.Itoa(unix.Gettid())
|
|
||||||
if err := fd.Faccessat(proc.Inner, threadSelf, unix.F_OK, unix.AT_SYMLINK_NOFOLLOW); err != nil || hookForceProcSelf() {
|
|
||||||
// In this case, we running in a pid namespace that doesn't
|
|
||||||
// match the /proc mount we have. This can happen inside runc.
|
|
||||||
//
|
|
||||||
// Unfortunately, there is no nice way to get the correct TID
|
|
||||||
// to use here because of the age of the kernel, so we have to
|
|
||||||
// just use /proc/self and hope that it works.
|
|
||||||
threadSelf = "self"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return threadSelf, nil
|
|
||||||
}
|
|
||||||
return "", fmt.Errorf("invalid procfs base %q", base)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ProcThreadSelfCloser is a callback that needs to be called when you are done
|
|
||||||
// operating on an [os.File] fetched using [ProcThreadSelf].
|
|
||||||
//
|
|
||||||
// [os.File]: https://pkg.go.dev/os#File
|
|
||||||
type ProcThreadSelfCloser func()
|
|
||||||
|
|
||||||
// open is the core lookup operation for [Handle]. It returns a handle to
|
|
||||||
// "/proc/<base>/<subpath>". If the returned [ProcThreadSelfCloser] is non-nil,
|
|
||||||
// you should call it after you are done interacting with the returned handle.
|
|
||||||
//
|
|
||||||
// In general you should use prefer to use the other helpers, as they remove
|
|
||||||
// the need to interact with [procfsBase] and do not return a nil
|
|
||||||
// [ProcThreadSelfCloser] for [procfsBase] values other than [ProcThreadSelf]
|
|
||||||
// where it is necessary.
|
|
||||||
func (proc *Handle) open(base procfsBase, subpath string) (_ *os.File, closer ProcThreadSelfCloser, Err error) {
|
|
||||||
prefix, err := base.prefix(proc)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
subpath = prefix + "/" + subpath
|
|
||||||
|
|
||||||
switch base {
|
|
||||||
case ProcRoot:
|
|
||||||
file, err := proc.lookup(subpath)
|
|
||||||
if errors.Is(err, os.ErrNotExist) {
|
|
||||||
// The Handle handle in use might be a subset=pid one, which will
|
|
||||||
// result in spurious errors. In this case, just open a temporary
|
|
||||||
// unmasked procfs handle for this operation.
|
|
||||||
proc, err2 := OpenUnsafeProcRoot() // !subset=pid
|
|
||||||
if err2 != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
defer proc.Close() //nolint:errcheck // close failures aren't critical here
|
|
||||||
|
|
||||||
file, err = proc.lookup(subpath)
|
|
||||||
}
|
|
||||||
return file, nil, err
|
|
||||||
|
|
||||||
case ProcSelf:
|
|
||||||
file, err := proc.lookup(subpath)
|
|
||||||
return file, nil, err
|
|
||||||
|
|
||||||
case ProcThreadSelf:
|
|
||||||
// We need to lock our thread until the caller is done with the handle
|
|
||||||
// because between getting the handle and using it we could get
|
|
||||||
// interrupted by the Go runtime and hit the case where the underlying
|
|
||||||
// thread is swapped out and the original thread is killed, resulting
|
|
||||||
// in pull-your-hair-out-hard-to-debug issues in the caller.
|
|
||||||
runtime.LockOSThread()
|
|
||||||
defer func() {
|
|
||||||
if Err != nil {
|
|
||||||
runtime.UnlockOSThread()
|
|
||||||
closer = nil
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
file, err := proc.lookup(subpath)
|
|
||||||
return file, runtime.UnlockOSThread, err
|
|
||||||
}
|
|
||||||
// should never be reached
|
|
||||||
return nil, nil, fmt.Errorf("[internal error] invalid procfs base %q", base)
|
|
||||||
}
|
|
||||||
|
|
||||||
// OpenThreadSelf returns a handle to "/proc/thread-self/<subpath>" (or an
|
|
||||||
// equivalent handle on older kernels where "/proc/thread-self" doesn't exist).
|
|
||||||
// Once finished with the handle, you must call the returned closer function
|
|
||||||
// (runtime.UnlockOSThread). You must not pass the returned *os.File to other
|
|
||||||
// Go threads or use the handle after calling the closer.
|
|
||||||
func (proc *Handle) OpenThreadSelf(subpath string) (_ *os.File, _ ProcThreadSelfCloser, Err error) {
|
|
||||||
return proc.open(ProcThreadSelf, subpath)
|
|
||||||
}
|
|
||||||
|
|
||||||
// OpenSelf returns a handle to /proc/self/<subpath>.
|
|
||||||
func (proc *Handle) OpenSelf(subpath string) (*os.File, error) {
|
|
||||||
file, closer, err := proc.open(ProcSelf, subpath)
|
|
||||||
assert.Assert(closer == nil, "closer for ProcSelf must be nil")
|
|
||||||
return file, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// OpenRoot returns a handle to /proc/<subpath>.
|
|
||||||
func (proc *Handle) OpenRoot(subpath string) (*os.File, error) {
|
|
||||||
file, closer, err := proc.open(ProcRoot, subpath)
|
|
||||||
assert.Assert(closer == nil, "closer for ProcRoot must be nil")
|
|
||||||
return file, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// OpenPid returns a handle to /proc/$pid/<subpath> (pid can be a pid or tid).
|
|
||||||
// This is mainly intended for usage when operating on other processes.
|
|
||||||
func (proc *Handle) OpenPid(pid int, subpath string) (*os.File, error) {
|
|
||||||
return proc.OpenRoot(strconv.Itoa(pid) + "/" + subpath)
|
|
||||||
}
|
|
||||||
|
|
||||||
// checkSubpathOvermount checks if the dirfd and path combination is on the
|
|
||||||
// same mount as the given root.
|
|
||||||
func checkSubpathOvermount(root, dir fd.Fd, path string) error {
|
|
||||||
// Get the mntID of our procfs handle.
|
|
||||||
expectedMountID, err := fd.GetMountID(root, "")
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("get root mount id: %w", err)
|
|
||||||
}
|
|
||||||
// Get the mntID of the target magic-link.
|
|
||||||
gotMountID, err := fd.GetMountID(dir, path)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("get subpath mount id: %w", err)
|
|
||||||
}
|
|
||||||
// As long as the directory mount is alive, even with wrapping mount IDs,
|
|
||||||
// we would expect to see a different mount ID here. (Of course, if we're
|
|
||||||
// using unsafeHostProcRoot() then an attaker could change this after we
|
|
||||||
// did this check.)
|
|
||||||
if expectedMountID != gotMountID {
|
|
||||||
return fmt.Errorf("%w: subpath %s/%s has an overmount obscuring the real path (mount ids do not match %d != %d)",
|
|
||||||
errUnsafeProcfs, dir.Name(), path, expectedMountID, gotMountID)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Readlink performs a readlink operation on "/proc/<base>/<subpath>" in a way
|
|
||||||
// that should be free from race attacks. This is most commonly used to get the
|
|
||||||
// real path of a file by looking at "/proc/self/fd/$n", with the same safety
|
|
||||||
// protections as [Open] (as well as some additional checks against
|
|
||||||
// overmounts).
|
|
||||||
func (proc *Handle) Readlink(base procfsBase, subpath string) (string, error) {
|
|
||||||
link, closer, err := proc.open(base, subpath)
|
|
||||||
if closer != nil {
|
|
||||||
defer closer()
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("get safe %s/%s handle: %w", base, subpath, err)
|
|
||||||
}
|
|
||||||
defer link.Close() //nolint:errcheck // close failures aren't critical here
|
|
||||||
|
|
||||||
// Try to detect if there is a mount on top of the magic-link. This should
|
|
||||||
// be safe in general (a mount on top of the path afterwards would not
|
|
||||||
// affect the handle itself) and will definitely be safe if we are using
|
|
||||||
// privateProcRoot() (at least since Linux 5.12[1], when anonymous mount
|
|
||||||
// namespaces were completely isolated from external mounts including mount
|
|
||||||
// propagation events).
|
|
||||||
//
|
|
||||||
// [1]: Linux commit ee2e3f50629f ("mount: fix mounting of detached mounts
|
|
||||||
// onto targets that reside on shared mounts").
|
|
||||||
if err := checkSubpathOvermount(proc.Inner, link, ""); err != nil {
|
|
||||||
return "", fmt.Errorf("check safety of %s/%s magiclink: %w", base, subpath, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// readlinkat implies AT_EMPTY_PATH since Linux 2.6.39. See Linux commit
|
|
||||||
// 65cfc6722361 ("readlinkat(), fchownat() and fstatat() with empty
|
|
||||||
// relative pathnames").
|
|
||||||
return fd.Readlinkat(link, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
// ProcSelfFdReadlink gets the real path of the given file by looking at
|
|
||||||
// readlink(/proc/thread-self/fd/$n).
|
|
||||||
//
|
|
||||||
// This is just a wrapper around [Handle.Readlink].
|
|
||||||
func ProcSelfFdReadlink(fd fd.Fd) (string, error) {
|
|
||||||
procRoot, err := OpenProcRoot() // subset=pid
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
defer procRoot.Close() //nolint:errcheck // close failures aren't critical here
|
|
||||||
|
|
||||||
fdPath := "fd/" + strconv.Itoa(int(fd.Fd()))
|
|
||||||
return procRoot.Readlink(ProcThreadSelf, fdPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CheckProcSelfFdPath returns whether the given file handle matches the
|
|
||||||
// expected path. (This is inherently racy.)
|
|
||||||
func CheckProcSelfFdPath(path string, file fd.Fd) error {
|
|
||||||
if err := fd.IsDeadInode(file); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
actualPath, err := ProcSelfFdReadlink(file)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("get path of handle: %w", err)
|
|
||||||
}
|
|
||||||
if actualPath != path {
|
|
||||||
return fmt.Errorf("%w: handle path %q doesn't match expected path %q", internal.ErrPossibleBreakout, actualPath, path)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReopenFd takes an existing file descriptor and "re-opens" it through
|
|
||||||
// /proc/thread-self/fd/<fd>. This allows for O_PATH file descriptors to be
|
|
||||||
// upgraded to regular file descriptors, as well as changing the open mode of a
|
|
||||||
// regular file descriptor. Some filesystems have unique handling of open(2)
|
|
||||||
// which make this incredibly useful (such as /dev/ptmx).
|
|
||||||
func ReopenFd(handle fd.Fd, flags int) (*os.File, error) {
|
|
||||||
procRoot, err := OpenProcRoot() // subset=pid
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer procRoot.Close() //nolint:errcheck // close failures aren't critical here
|
|
||||||
|
|
||||||
// We can't operate on /proc/thread-self/fd/$n directly when doing a
|
|
||||||
// re-open, so we need to open /proc/thread-self/fd and then open a single
|
|
||||||
// final component.
|
|
||||||
procFdDir, closer, err := procRoot.OpenThreadSelf("fd/")
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("get safe /proc/thread-self/fd handle: %w", err)
|
|
||||||
}
|
|
||||||
defer procFdDir.Close() //nolint:errcheck // close failures aren't critical here
|
|
||||||
defer closer()
|
|
||||||
|
|
||||||
// Try to detect if there is a mount on top of the magic-link we are about
|
|
||||||
// to open. If we are using unsafeHostProcRoot(), this could change after
|
|
||||||
// we check it (and there's nothing we can do about that) but for
|
|
||||||
// privateProcRoot() this should be guaranteed to be safe (at least since
|
|
||||||
// Linux 5.12[1], when anonymous mount namespaces were completely isolated
|
|
||||||
// from external mounts including mount propagation events).
|
|
||||||
//
|
|
||||||
// [1]: Linux commit ee2e3f50629f ("mount: fix mounting of detached mounts
|
|
||||||
// onto targets that reside on shared mounts").
|
|
||||||
fdStr := strconv.Itoa(int(handle.Fd()))
|
|
||||||
if err := checkSubpathOvermount(procRoot.Inner, procFdDir, fdStr); err != nil {
|
|
||||||
return nil, fmt.Errorf("check safety of /proc/thread-self/fd/%s magiclink: %w", fdStr, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
flags |= unix.O_CLOEXEC
|
|
||||||
// Rather than just wrapping fd.Openat, open-code it so we can copy
|
|
||||||
// handle.Name().
|
|
||||||
reopenFd, err := unix.Openat(int(procFdDir.Fd()), fdStr, flags, 0)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("reopen fd %d: %w", handle.Fd(), err)
|
|
||||||
}
|
|
||||||
return os.NewFile(uintptr(reopenFd), handle.Name()), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test hooks used in the procfs tests to verify that the fallback logic works.
|
|
||||||
// See testing_mocks_linux_test.go and procfs_linux_test.go for more details.
|
|
||||||
var (
|
|
||||||
hookForcePrivateProcRootOpenTree = hookDummyFile
|
|
||||||
hookForcePrivateProcRootOpenTreeAtRecursive = hookDummyFile
|
|
||||||
hookForceGetProcRootUnsafe = hookDummy
|
|
||||||
|
|
||||||
hookForceProcSelfTask = hookDummy
|
|
||||||
hookForceProcSelf = hookDummy
|
|
||||||
)
|
|
||||||
|
|
||||||
func hookDummy() bool { return false }
|
|
||||||
func hookDummyFile(_ io.Closer) bool { return false }
|
|
||||||
@ -1,222 +0,0 @@
|
|||||||
// SPDX-License-Identifier: MPL-2.0
|
|
||||||
|
|
||||||
//go:build linux
|
|
||||||
|
|
||||||
// Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com>
|
|
||||||
// Copyright (C) 2024-2025 SUSE LLC
|
|
||||||
//
|
|
||||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
|
|
||||||
// This code is adapted to be a minimal version of the libpathrs proc resolver
|
|
||||||
// <https://github.com/opensuse/libpathrs/blob/v0.1.3/src/resolvers/procfs.rs>.
|
|
||||||
// As we only need O_PATH|O_NOFOLLOW support, this is not too much to port.
|
|
||||||
|
|
||||||
package procfs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
|
|
||||||
"github.com/cyphar/filepath-securejoin/internal/consts"
|
|
||||||
"github.com/cyphar/filepath-securejoin/pathrs-lite/internal"
|
|
||||||
"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd"
|
|
||||||
"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat"
|
|
||||||
"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux"
|
|
||||||
)
|
|
||||||
|
|
||||||
// procfsLookupInRoot is a stripped down version of completeLookupInRoot,
|
|
||||||
// entirely designed to support the very small set of features necessary to
|
|
||||||
// make procfs handling work. Unlike completeLookupInRoot, we always have
|
|
||||||
// O_PATH|O_NOFOLLOW behaviour for trailing symlinks.
|
|
||||||
//
|
|
||||||
// The main restrictions are:
|
|
||||||
//
|
|
||||||
// - ".." is not supported (as it requires either os.Root-style replays,
|
|
||||||
// which is more bug-prone; or procfs verification, which is not possible
|
|
||||||
// due to re-entrancy issues).
|
|
||||||
// - Absolute symlinks for the same reason (and all absolute symlinks in
|
|
||||||
// procfs are magic-links, which we want to skip anyway).
|
|
||||||
// - If statx is supported (checkSymlinkOvermount), any mount-point crossings
|
|
||||||
// (which is the main attack of concern against /proc).
|
|
||||||
// - Partial lookups are not supported, so the symlink stack is not needed.
|
|
||||||
// - Trailing slash special handling is not necessary in most cases (if we
|
|
||||||
// operating on procfs, it's usually with programmer-controlled strings
|
|
||||||
// that will then be re-opened), so we skip it since whatever re-opens it
|
|
||||||
// can deal with it. It's a creature comfort anyway.
|
|
||||||
//
|
|
||||||
// If the system supports openat2(), this is implemented using equivalent flags
|
|
||||||
// (RESOLVE_BENEATH | RESOLVE_NO_XDEV | RESOLVE_NO_MAGICLINKS).
|
|
||||||
func procfsLookupInRoot(procRoot fd.Fd, unsafePath string) (Handle *os.File, _ error) {
|
|
||||||
unsafePath = filepath.ToSlash(unsafePath) // noop
|
|
||||||
|
|
||||||
// Make sure that an empty unsafe path still returns something sane, even
|
|
||||||
// with openat2 (which doesn't have AT_EMPTY_PATH semantics yet).
|
|
||||||
if unsafePath == "" {
|
|
||||||
unsafePath = "."
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is already checked by getProcRoot, but make sure here since the
|
|
||||||
// core security of this lookup is based on this assumption.
|
|
||||||
if err := verifyProcRoot(procRoot); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if linux.HasOpenat2() {
|
|
||||||
// We prefer being able to use RESOLVE_NO_XDEV if we can, to be
|
|
||||||
// absolutely sure we are operating on a clean /proc handle that
|
|
||||||
// doesn't have any cheeky overmounts that could trick us (including
|
|
||||||
// symlink mounts on top of /proc/thread-self). RESOLVE_BENEATH isn't
|
|
||||||
// strictly needed, but just use it since we have it.
|
|
||||||
//
|
|
||||||
// NOTE: /proc/self is technically a magic-link (the contents of the
|
|
||||||
// symlink are generated dynamically), but it doesn't use
|
|
||||||
// nd_jump_link() so RESOLVE_NO_MAGICLINKS allows it.
|
|
||||||
//
|
|
||||||
// TODO: It would be nice to have RESOLVE_NO_DOTDOT, purely for
|
|
||||||
// self-consistency with the backup O_PATH resolver.
|
|
||||||
handle, err := fd.Openat2(procRoot, unsafePath, &unix.OpenHow{
|
|
||||||
Flags: unix.O_PATH | unix.O_NOFOLLOW | unix.O_CLOEXEC,
|
|
||||||
Resolve: unix.RESOLVE_BENEATH | unix.RESOLVE_NO_XDEV | unix.RESOLVE_NO_MAGICLINKS,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
// TODO: Once we bump the minimum Go version to 1.20, we can use
|
|
||||||
// multiple %w verbs for this wrapping. For now we need to use a
|
|
||||||
// compatibility shim for older Go versions.
|
|
||||||
// err = fmt.Errorf("%w: %w", errUnsafeProcfs, err)
|
|
||||||
return nil, gocompat.WrapBaseError(err, errUnsafeProcfs)
|
|
||||||
}
|
|
||||||
return handle, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// To mirror openat2(RESOLVE_BENEATH), we need to return an error if the
|
|
||||||
// path is absolute.
|
|
||||||
if path.IsAbs(unsafePath) {
|
|
||||||
return nil, fmt.Errorf("%w: cannot resolve absolute paths in procfs resolver", internal.ErrPossibleBreakout)
|
|
||||||
}
|
|
||||||
|
|
||||||
currentDir, err := fd.Dup(procRoot)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("clone root fd: %w", err)
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
// If a handle is not returned, close the internal handle.
|
|
||||||
if Handle == nil {
|
|
||||||
_ = currentDir.Close()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
var (
|
|
||||||
linksWalked int
|
|
||||||
currentPath string
|
|
||||||
remainingPath = unsafePath
|
|
||||||
)
|
|
||||||
for remainingPath != "" {
|
|
||||||
// Get the next path component.
|
|
||||||
var part string
|
|
||||||
if i := strings.IndexByte(remainingPath, '/'); i == -1 {
|
|
||||||
part, remainingPath = remainingPath, ""
|
|
||||||
} else {
|
|
||||||
part, remainingPath = remainingPath[:i], remainingPath[i+1:]
|
|
||||||
}
|
|
||||||
if part == "" {
|
|
||||||
// no-op component, but treat it the same as "."
|
|
||||||
part = "."
|
|
||||||
}
|
|
||||||
if part == ".." {
|
|
||||||
// not permitted
|
|
||||||
return nil, fmt.Errorf("%w: cannot walk into '..' in procfs resolver", internal.ErrPossibleBreakout)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply the component lexically to the path we are building.
|
|
||||||
// currentPath does not contain any symlinks, and we are lexically
|
|
||||||
// dealing with a single component, so it's okay to do a filepath.Clean
|
|
||||||
// here. (Not to mention that ".." isn't allowed.)
|
|
||||||
nextPath := path.Join("/", currentPath, part)
|
|
||||||
// If we logically hit the root, just clone the root rather than
|
|
||||||
// opening the part and doing all of the other checks.
|
|
||||||
if nextPath == "/" {
|
|
||||||
// Jump to root.
|
|
||||||
rootClone, err := fd.Dup(procRoot)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("clone root fd: %w", err)
|
|
||||||
}
|
|
||||||
_ = currentDir.Close()
|
|
||||||
currentDir = rootClone
|
|
||||||
currentPath = nextPath
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to open the next component.
|
|
||||||
nextDir, err := fd.Openat(currentDir, part, unix.O_PATH|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure we are still on procfs and haven't crossed mounts.
|
|
||||||
if err := verifyProcHandle(nextDir); err != nil {
|
|
||||||
_ = nextDir.Close()
|
|
||||||
return nil, fmt.Errorf("check %q component is on procfs: %w", part, err)
|
|
||||||
}
|
|
||||||
if err := checkSubpathOvermount(procRoot, nextDir, ""); err != nil {
|
|
||||||
_ = nextDir.Close()
|
|
||||||
return nil, fmt.Errorf("check %q component is not overmounted: %w", part, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// We are emulating O_PATH|O_NOFOLLOW, so we only need to traverse into
|
|
||||||
// trailing symlinks if we are not the final component. Otherwise we
|
|
||||||
// can just return the currentDir.
|
|
||||||
if remainingPath != "" {
|
|
||||||
st, err := nextDir.Stat()
|
|
||||||
if err != nil {
|
|
||||||
_ = nextDir.Close()
|
|
||||||
return nil, fmt.Errorf("stat component %q: %w", part, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if st.Mode()&os.ModeType == os.ModeSymlink {
|
|
||||||
// readlinkat implies AT_EMPTY_PATH since Linux 2.6.39. See
|
|
||||||
// Linux commit 65cfc6722361 ("readlinkat(), fchownat() and
|
|
||||||
// fstatat() with empty relative pathnames").
|
|
||||||
linkDest, err := fd.Readlinkat(nextDir, "")
|
|
||||||
// We don't need the handle anymore.
|
|
||||||
_ = nextDir.Close()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
linksWalked++
|
|
||||||
if linksWalked > consts.MaxSymlinkLimit {
|
|
||||||
return nil, &os.PathError{Op: "securejoin.procfsLookupInRoot", Path: "/proc/" + unsafePath, Err: unix.ELOOP}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update our logical remaining path.
|
|
||||||
remainingPath = linkDest + "/" + remainingPath
|
|
||||||
// Absolute symlinks are probably magiclinks, we reject them.
|
|
||||||
if path.IsAbs(linkDest) {
|
|
||||||
return nil, fmt.Errorf("%w: cannot jump to / in procfs resolver -- possible magiclink", internal.ErrPossibleBreakout)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Walk into the next component.
|
|
||||||
_ = currentDir.Close()
|
|
||||||
currentDir = nextDir
|
|
||||||
currentPath = nextPath
|
|
||||||
}
|
|
||||||
|
|
||||||
// One final sanity-check.
|
|
||||||
if err := verifyProcHandle(currentDir); err != nil {
|
|
||||||
return nil, fmt.Errorf("check final handle is on procfs: %w", err)
|
|
||||||
}
|
|
||||||
if err := checkSubpathOvermount(procRoot, currentDir, ""); err != nil {
|
|
||||||
return nil, fmt.Errorf("check final handle is not overmounted: %w", err)
|
|
||||||
}
|
|
||||||
return currentDir, nil
|
|
||||||
}
|
|
||||||
399
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/lookup_linux.go
generated
vendored
399
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/lookup_linux.go
generated
vendored
@ -1,399 +0,0 @@
|
|||||||
// SPDX-License-Identifier: MPL-2.0
|
|
||||||
|
|
||||||
//go:build linux
|
|
||||||
|
|
||||||
// Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com>
|
|
||||||
// Copyright (C) 2024-2025 SUSE LLC
|
|
||||||
//
|
|
||||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
|
|
||||||
package pathrs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
|
|
||||||
"github.com/cyphar/filepath-securejoin/internal/consts"
|
|
||||||
"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd"
|
|
||||||
"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat"
|
|
||||||
"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux"
|
|
||||||
"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/procfs"
|
|
||||||
)
|
|
||||||
|
|
||||||
type symlinkStackEntry struct {
|
|
||||||
// (dir, remainingPath) is what we would've returned if the link didn't
|
|
||||||
// exist. This matches what openat2(RESOLVE_IN_ROOT) would return in
|
|
||||||
// this case.
|
|
||||||
dir *os.File
|
|
||||||
remainingPath string
|
|
||||||
// linkUnwalked is the remaining path components from the original
|
|
||||||
// Readlink which we have yet to walk. When this slice is empty, we
|
|
||||||
// drop the link from the stack.
|
|
||||||
linkUnwalked []string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (se symlinkStackEntry) String() string {
|
|
||||||
return fmt.Sprintf("<%s>/%s [->%s]", se.dir.Name(), se.remainingPath, strings.Join(se.linkUnwalked, "/"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (se symlinkStackEntry) Close() {
|
|
||||||
_ = se.dir.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
type symlinkStack []*symlinkStackEntry
|
|
||||||
|
|
||||||
func (s *symlinkStack) IsEmpty() bool {
|
|
||||||
return s == nil || len(*s) == 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *symlinkStack) Close() {
|
|
||||||
if s != nil {
|
|
||||||
for _, link := range *s {
|
|
||||||
link.Close()
|
|
||||||
}
|
|
||||||
// TODO: Switch to clear once we switch to Go 1.21.
|
|
||||||
*s = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
errEmptyStack = errors.New("[internal] stack is empty")
|
|
||||||
errBrokenSymlinkStack = errors.New("[internal error] broken symlink stack")
|
|
||||||
)
|
|
||||||
|
|
||||||
func (s *symlinkStack) popPart(part string) error {
|
|
||||||
if s == nil || s.IsEmpty() {
|
|
||||||
// If there is nothing in the symlink stack, then the part was from the
|
|
||||||
// real path provided by the user, and this is a no-op.
|
|
||||||
return errEmptyStack
|
|
||||||
}
|
|
||||||
if part == "." {
|
|
||||||
// "." components are no-ops -- we drop them when doing SwapLink.
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
tailEntry := (*s)[len(*s)-1]
|
|
||||||
|
|
||||||
// Double-check that we are popping the component we expect.
|
|
||||||
if len(tailEntry.linkUnwalked) == 0 {
|
|
||||||
return fmt.Errorf("%w: trying to pop component %q of empty stack entry %s", errBrokenSymlinkStack, part, tailEntry)
|
|
||||||
}
|
|
||||||
headPart := tailEntry.linkUnwalked[0]
|
|
||||||
if headPart != part {
|
|
||||||
return fmt.Errorf("%w: trying to pop component %q but the last stack entry is %s (%q)", errBrokenSymlinkStack, part, tailEntry, headPart)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Drop the component, but keep the entry around in case we are dealing
|
|
||||||
// with a "tail-chained" symlink.
|
|
||||||
tailEntry.linkUnwalked = tailEntry.linkUnwalked[1:]
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *symlinkStack) PopPart(part string) error {
|
|
||||||
if err := s.popPart(part); err != nil {
|
|
||||||
if errors.Is(err, errEmptyStack) {
|
|
||||||
// Skip empty stacks.
|
|
||||||
err = nil
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clean up any of the trailing stack entries that are empty.
|
|
||||||
for lastGood := len(*s) - 1; lastGood >= 0; lastGood-- {
|
|
||||||
entry := (*s)[lastGood]
|
|
||||||
if len(entry.linkUnwalked) > 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
entry.Close()
|
|
||||||
(*s) = (*s)[:lastGood]
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *symlinkStack) push(dir *os.File, remainingPath, linkTarget string) error {
|
|
||||||
if s == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
// Split the link target and clean up any "" parts.
|
|
||||||
linkTargetParts := gocompat.SlicesDeleteFunc(
|
|
||||||
strings.Split(linkTarget, "/"),
|
|
||||||
func(part string) bool { return part == "" || part == "." })
|
|
||||||
|
|
||||||
// Copy the directory so the caller doesn't close our copy.
|
|
||||||
dirCopy, err := fd.Dup(dir)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add to the stack.
|
|
||||||
*s = append(*s, &symlinkStackEntry{
|
|
||||||
dir: dirCopy,
|
|
||||||
remainingPath: remainingPath,
|
|
||||||
linkUnwalked: linkTargetParts,
|
|
||||||
})
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *symlinkStack) SwapLink(linkPart string, dir *os.File, remainingPath, linkTarget string) error {
|
|
||||||
// If we are currently inside a symlink resolution, remove the symlink
|
|
||||||
// component from the last symlink entry, but don't remove the entry even
|
|
||||||
// if it's empty. If we are a "tail-chained" symlink (a trailing symlink we
|
|
||||||
// hit during a symlink resolution) we need to keep the old symlink until
|
|
||||||
// we finish the resolution.
|
|
||||||
if err := s.popPart(linkPart); err != nil {
|
|
||||||
if !errors.Is(err, errEmptyStack) {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// Push the component regardless of whether the stack was empty.
|
|
||||||
}
|
|
||||||
return s.push(dir, remainingPath, linkTarget)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *symlinkStack) PopTopSymlink() (*os.File, string, bool) {
|
|
||||||
if s == nil || s.IsEmpty() {
|
|
||||||
return nil, "", false
|
|
||||||
}
|
|
||||||
tailEntry := (*s)[0]
|
|
||||||
*s = (*s)[1:]
|
|
||||||
return tailEntry.dir, tailEntry.remainingPath, true
|
|
||||||
}
|
|
||||||
|
|
||||||
// partialLookupInRoot tries to lookup as much of the request path as possible
|
|
||||||
// within the provided root (a-la RESOLVE_IN_ROOT) and opens the final existing
|
|
||||||
// component of the requested path, returning a file handle to the final
|
|
||||||
// existing component and a string containing the remaining path components.
|
|
||||||
func partialLookupInRoot(root fd.Fd, unsafePath string) (*os.File, string, error) {
|
|
||||||
return lookupInRoot(root, unsafePath, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func completeLookupInRoot(root fd.Fd, unsafePath string) (*os.File, error) {
|
|
||||||
handle, remainingPath, err := lookupInRoot(root, unsafePath, false)
|
|
||||||
if remainingPath != "" && err == nil {
|
|
||||||
// should never happen
|
|
||||||
err = fmt.Errorf("[bug] non-empty remaining path when doing a non-partial lookup: %q", remainingPath)
|
|
||||||
}
|
|
||||||
// lookupInRoot(partial=false) will always close the handle if an error is
|
|
||||||
// returned, so no need to double-check here.
|
|
||||||
return handle, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func lookupInRoot(root fd.Fd, unsafePath string, partial bool) (Handle *os.File, _ string, _ error) {
|
|
||||||
unsafePath = filepath.ToSlash(unsafePath) // noop
|
|
||||||
|
|
||||||
// This is very similar to SecureJoin, except that we operate on the
|
|
||||||
// components using file descriptors. We then return the last component we
|
|
||||||
// managed open, along with the remaining path components not opened.
|
|
||||||
|
|
||||||
// Try to use openat2 if possible.
|
|
||||||
if linux.HasOpenat2() {
|
|
||||||
return lookupOpenat2(root, unsafePath, partial)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the "actual" root path from /proc/self/fd. This is necessary if the
|
|
||||||
// root is some magic-link like /proc/$pid/root, in which case we want to
|
|
||||||
// make sure when we do procfs.CheckProcSelfFdPath that we are using the
|
|
||||||
// correct root path.
|
|
||||||
logicalRootPath, err := procfs.ProcSelfFdReadlink(root)
|
|
||||||
if err != nil {
|
|
||||||
return nil, "", fmt.Errorf("get real root path: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
currentDir, err := fd.Dup(root)
|
|
||||||
if err != nil {
|
|
||||||
return nil, "", fmt.Errorf("clone root fd: %w", err)
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
// If a handle is not returned, close the internal handle.
|
|
||||||
if Handle == nil {
|
|
||||||
_ = currentDir.Close()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// symlinkStack is used to emulate how openat2(RESOLVE_IN_ROOT) treats
|
|
||||||
// dangling symlinks. If we hit a non-existent path while resolving a
|
|
||||||
// symlink, we need to return the (dir, remainingPath) that we had when we
|
|
||||||
// hit the symlink (treating the symlink as though it were a regular file).
|
|
||||||
// The set of (dir, remainingPath) sets is stored within the symlinkStack
|
|
||||||
// and we add and remove parts when we hit symlink and non-symlink
|
|
||||||
// components respectively. We need a stack because of recursive symlinks
|
|
||||||
// (symlinks that contain symlink components in their target).
|
|
||||||
//
|
|
||||||
// Note that the stack is ONLY used for book-keeping. All of the actual
|
|
||||||
// path walking logic is still based on currentPath/remainingPath and
|
|
||||||
// currentDir (as in SecureJoin).
|
|
||||||
var symStack *symlinkStack
|
|
||||||
if partial {
|
|
||||||
symStack = new(symlinkStack)
|
|
||||||
defer symStack.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
linksWalked int
|
|
||||||
currentPath string
|
|
||||||
remainingPath = unsafePath
|
|
||||||
)
|
|
||||||
for remainingPath != "" {
|
|
||||||
// Save the current remaining path so if the part is not real we can
|
|
||||||
// return the path including the component.
|
|
||||||
oldRemainingPath := remainingPath
|
|
||||||
|
|
||||||
// Get the next path component.
|
|
||||||
var part string
|
|
||||||
if i := strings.IndexByte(remainingPath, '/'); i == -1 {
|
|
||||||
part, remainingPath = remainingPath, ""
|
|
||||||
} else {
|
|
||||||
part, remainingPath = remainingPath[:i], remainingPath[i+1:]
|
|
||||||
}
|
|
||||||
// If we hit an empty component, we need to treat it as though it is
|
|
||||||
// "." so that trailing "/" and "//" components on a non-directory
|
|
||||||
// correctly return the right error code.
|
|
||||||
if part == "" {
|
|
||||||
part = "."
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply the component lexically to the path we are building.
|
|
||||||
// currentPath does not contain any symlinks, and we are lexically
|
|
||||||
// dealing with a single component, so it's okay to do a filepath.Clean
|
|
||||||
// here.
|
|
||||||
nextPath := path.Join("/", currentPath, part)
|
|
||||||
// If we logically hit the root, just clone the root rather than
|
|
||||||
// opening the part and doing all of the other checks.
|
|
||||||
if nextPath == "/" {
|
|
||||||
if err := symStack.PopPart(part); err != nil {
|
|
||||||
return nil, "", fmt.Errorf("walking into root with part %q failed: %w", part, err)
|
|
||||||
}
|
|
||||||
// Jump to root.
|
|
||||||
rootClone, err := fd.Dup(root)
|
|
||||||
if err != nil {
|
|
||||||
return nil, "", fmt.Errorf("clone root fd: %w", err)
|
|
||||||
}
|
|
||||||
_ = currentDir.Close()
|
|
||||||
currentDir = rootClone
|
|
||||||
currentPath = nextPath
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to open the next component.
|
|
||||||
nextDir, err := fd.Openat(currentDir, part, unix.O_PATH|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0)
|
|
||||||
switch err {
|
|
||||||
case nil:
|
|
||||||
st, err := nextDir.Stat()
|
|
||||||
if err != nil {
|
|
||||||
_ = nextDir.Close()
|
|
||||||
return nil, "", fmt.Errorf("stat component %q: %w", part, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch st.Mode() & os.ModeType { //nolint:exhaustive // just a glorified if statement
|
|
||||||
case os.ModeSymlink:
|
|
||||||
// readlinkat implies AT_EMPTY_PATH since Linux 2.6.39. See
|
|
||||||
// Linux commit 65cfc6722361 ("readlinkat(), fchownat() and
|
|
||||||
// fstatat() with empty relative pathnames").
|
|
||||||
linkDest, err := fd.Readlinkat(nextDir, "")
|
|
||||||
// We don't need the handle anymore.
|
|
||||||
_ = nextDir.Close()
|
|
||||||
if err != nil {
|
|
||||||
return nil, "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
linksWalked++
|
|
||||||
if linksWalked > consts.MaxSymlinkLimit {
|
|
||||||
return nil, "", &os.PathError{Op: "securejoin.lookupInRoot", Path: logicalRootPath + "/" + unsafePath, Err: unix.ELOOP}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Swap out the symlink's component for the link entry itself.
|
|
||||||
if err := symStack.SwapLink(part, currentDir, oldRemainingPath, linkDest); err != nil {
|
|
||||||
return nil, "", fmt.Errorf("walking into symlink %q failed: push symlink: %w", part, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update our logical remaining path.
|
|
||||||
remainingPath = linkDest + "/" + remainingPath
|
|
||||||
// Absolute symlinks reset any work we've already done.
|
|
||||||
if path.IsAbs(linkDest) {
|
|
||||||
// Jump to root.
|
|
||||||
rootClone, err := fd.Dup(root)
|
|
||||||
if err != nil {
|
|
||||||
return nil, "", fmt.Errorf("clone root fd: %w", err)
|
|
||||||
}
|
|
||||||
_ = currentDir.Close()
|
|
||||||
currentDir = rootClone
|
|
||||||
currentPath = "/"
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
// If we are dealing with a directory, simply walk into it.
|
|
||||||
_ = currentDir.Close()
|
|
||||||
currentDir = nextDir
|
|
||||||
currentPath = nextPath
|
|
||||||
|
|
||||||
// The part was real, so drop it from the symlink stack.
|
|
||||||
if err := symStack.PopPart(part); err != nil {
|
|
||||||
return nil, "", fmt.Errorf("walking into directory %q failed: %w", part, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we are operating on a .., make sure we haven't escaped.
|
|
||||||
// We only have to check for ".." here because walking down
|
|
||||||
// into a regular component component cannot cause you to
|
|
||||||
// escape. This mirrors the logic in RESOLVE_IN_ROOT, except we
|
|
||||||
// have to check every ".." rather than only checking after a
|
|
||||||
// rename or mount on the system.
|
|
||||||
if part == ".." {
|
|
||||||
// Make sure the root hasn't moved.
|
|
||||||
if err := procfs.CheckProcSelfFdPath(logicalRootPath, root); err != nil {
|
|
||||||
return nil, "", fmt.Errorf("root path moved during lookup: %w", err)
|
|
||||||
}
|
|
||||||
// Make sure the path is what we expect.
|
|
||||||
fullPath := logicalRootPath + nextPath
|
|
||||||
if err := procfs.CheckProcSelfFdPath(fullPath, currentDir); err != nil {
|
|
||||||
return nil, "", fmt.Errorf("walking into %q had unexpected result: %w", part, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
if !partial {
|
|
||||||
return nil, "", err
|
|
||||||
}
|
|
||||||
// If there are any remaining components in the symlink stack, we
|
|
||||||
// are still within a symlink resolution and thus we hit a dangling
|
|
||||||
// symlink. So pretend that the first symlink in the stack we hit
|
|
||||||
// was an ENOENT (to match openat2).
|
|
||||||
if oldDir, remainingPath, ok := symStack.PopTopSymlink(); ok {
|
|
||||||
_ = currentDir.Close()
|
|
||||||
return oldDir, remainingPath, err
|
|
||||||
}
|
|
||||||
// We have hit a final component that doesn't exist, so we have our
|
|
||||||
// partial open result. Note that we have to use the OLD remaining
|
|
||||||
// path, since the lookup failed.
|
|
||||||
return currentDir, oldRemainingPath, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the unsafePath had a trailing slash, we need to make sure we try to
|
|
||||||
// do a relative "." open so that we will correctly return an error when
|
|
||||||
// the final component is a non-directory (to match openat2). In the
|
|
||||||
// context of openat2, a trailing slash and a trailing "/." are completely
|
|
||||||
// equivalent.
|
|
||||||
if strings.HasSuffix(unsafePath, "/") {
|
|
||||||
nextDir, err := fd.Openat(currentDir, ".", unix.O_PATH|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0)
|
|
||||||
if err != nil {
|
|
||||||
if !partial {
|
|
||||||
_ = currentDir.Close()
|
|
||||||
currentDir = nil
|
|
||||||
}
|
|
||||||
return currentDir, "", err
|
|
||||||
}
|
|
||||||
_ = currentDir.Close()
|
|
||||||
currentDir = nextDir
|
|
||||||
}
|
|
||||||
|
|
||||||
// All of the components existed!
|
|
||||||
return currentDir, "", nil
|
|
||||||
}
|
|
||||||
246
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/mkdir_linux.go
generated
vendored
246
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/mkdir_linux.go
generated
vendored
@ -1,246 +0,0 @@
|
|||||||
// SPDX-License-Identifier: MPL-2.0
|
|
||||||
|
|
||||||
//go:build linux
|
|
||||||
|
|
||||||
// Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com>
|
|
||||||
// Copyright (C) 2024-2025 SUSE LLC
|
|
||||||
//
|
|
||||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
|
|
||||||
package pathrs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
|
|
||||||
"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd"
|
|
||||||
"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat"
|
|
||||||
"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux"
|
|
||||||
)
|
|
||||||
|
|
||||||
var errInvalidMode = errors.New("invalid permission mode")
|
|
||||||
|
|
||||||
// modePermExt is like os.ModePerm except that it also includes the set[ug]id
|
|
||||||
// and sticky bits.
|
|
||||||
const modePermExt = os.ModePerm | os.ModeSetuid | os.ModeSetgid | os.ModeSticky
|
|
||||||
|
|
||||||
//nolint:cyclop // this function needs to handle a lot of cases
|
|
||||||
func toUnixMode(mode os.FileMode) (uint32, error) {
|
|
||||||
sysMode := uint32(mode.Perm())
|
|
||||||
if mode&os.ModeSetuid != 0 {
|
|
||||||
sysMode |= unix.S_ISUID
|
|
||||||
}
|
|
||||||
if mode&os.ModeSetgid != 0 {
|
|
||||||
sysMode |= unix.S_ISGID
|
|
||||||
}
|
|
||||||
if mode&os.ModeSticky != 0 {
|
|
||||||
sysMode |= unix.S_ISVTX
|
|
||||||
}
|
|
||||||
// We don't allow file type bits.
|
|
||||||
if mode&os.ModeType != 0 {
|
|
||||||
return 0, fmt.Errorf("%w %+.3o (%s): type bits not permitted", errInvalidMode, mode, mode)
|
|
||||||
}
|
|
||||||
// We don't allow other unknown modes.
|
|
||||||
if mode&^modePermExt != 0 || sysMode&unix.S_IFMT != 0 {
|
|
||||||
return 0, fmt.Errorf("%w %+.3o (%s): unknown mode bits", errInvalidMode, mode, mode)
|
|
||||||
}
|
|
||||||
return sysMode, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MkdirAllHandle is equivalent to [MkdirAll], except that it is safer to use
|
|
||||||
// in two respects:
|
|
||||||
//
|
|
||||||
// - The caller provides the root directory as an *[os.File] (preferably O_PATH)
|
|
||||||
// handle. This means that the caller can be sure which root directory is
|
|
||||||
// being used. Note that this can be emulated by using /proc/self/fd/... as
|
|
||||||
// the root path with [os.MkdirAll].
|
|
||||||
//
|
|
||||||
// - Once all of the directories have been created, an *[os.File] O_PATH handle
|
|
||||||
// to the directory at unsafePath is returned to the caller. This is done in
|
|
||||||
// an effectively-race-free way (an attacker would only be able to swap the
|
|
||||||
// final directory component), which is not possible to emulate with
|
|
||||||
// [MkdirAll].
|
|
||||||
//
|
|
||||||
// In addition, the returned handle is obtained far more efficiently than doing
|
|
||||||
// a brand new lookup of unsafePath (such as with [SecureJoin] or openat2) after
|
|
||||||
// doing [MkdirAll]. If you intend to open the directory after creating it, you
|
|
||||||
// should use MkdirAllHandle.
|
|
||||||
//
|
|
||||||
// [SecureJoin]: https://pkg.go.dev/github.com/cyphar/filepath-securejoin#SecureJoin
|
|
||||||
func MkdirAllHandle(root *os.File, unsafePath string, mode os.FileMode) (_ *os.File, Err error) {
|
|
||||||
unixMode, err := toUnixMode(mode)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// On Linux, mkdirat(2) (and os.Mkdir) silently ignore the suid and sgid
|
|
||||||
// bits. We could also silently ignore them but since we have very few
|
|
||||||
// users it seems more prudent to return an error so users notice that
|
|
||||||
// these bits will not be set.
|
|
||||||
if unixMode&^0o1777 != 0 {
|
|
||||||
return nil, fmt.Errorf("%w for mkdir %+.3o: suid and sgid are ignored by mkdir", errInvalidMode, mode)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to open as much of the path as possible.
|
|
||||||
currentDir, remainingPath, err := partialLookupInRoot(root, unsafePath)
|
|
||||||
defer func() {
|
|
||||||
if Err != nil {
|
|
||||||
_ = currentDir.Close()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
if err != nil && !errors.Is(err, unix.ENOENT) {
|
|
||||||
return nil, fmt.Errorf("find existing subpath of %q: %w", unsafePath, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// If there is an attacker deleting directories as we walk into them,
|
|
||||||
// detect this proactively. Note this is guaranteed to detect if the
|
|
||||||
// attacker deleted any part of the tree up to currentDir.
|
|
||||||
//
|
|
||||||
// Once we walk into a dead directory, partialLookupInRoot would not be
|
|
||||||
// able to walk further down the tree (directories must be empty before
|
|
||||||
// they are deleted), and if the attacker has removed the entire tree we
|
|
||||||
// can be sure that anything that was originally inside a dead directory
|
|
||||||
// must also be deleted and thus is a dead directory in its own right.
|
|
||||||
//
|
|
||||||
// This is mostly a quality-of-life check, because mkdir will simply fail
|
|
||||||
// later if the attacker deletes the tree after this check.
|
|
||||||
if err := fd.IsDeadInode(currentDir); err != nil {
|
|
||||||
return nil, fmt.Errorf("finding existing subpath of %q: %w", unsafePath, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Re-open the path to match the O_DIRECTORY reopen loop later (so that we
|
|
||||||
// always return a non-O_PATH handle). We also check that we actually got a
|
|
||||||
// directory.
|
|
||||||
if reopenDir, err := Reopen(currentDir, unix.O_DIRECTORY|unix.O_CLOEXEC); errors.Is(err, unix.ENOTDIR) {
|
|
||||||
return nil, fmt.Errorf("cannot create subdirectories in %q: %w", currentDir.Name(), unix.ENOTDIR)
|
|
||||||
} else if err != nil {
|
|
||||||
return nil, fmt.Errorf("re-opening handle to %q: %w", currentDir.Name(), err)
|
|
||||||
} else { //nolint:revive // indent-error-flow lint doesn't make sense here
|
|
||||||
_ = currentDir.Close()
|
|
||||||
currentDir = reopenDir
|
|
||||||
}
|
|
||||||
|
|
||||||
remainingParts := strings.Split(remainingPath, string(filepath.Separator))
|
|
||||||
if gocompat.SlicesContains(remainingParts, "..") {
|
|
||||||
// The path contained ".." components after the end of the "real"
|
|
||||||
// components. We could try to safely resolve ".." here but that would
|
|
||||||
// add a bunch of extra logic for something that it's not clear even
|
|
||||||
// needs to be supported. So just return an error.
|
|
||||||
//
|
|
||||||
// If we do filepath.Clean(remainingPath) then we end up with the
|
|
||||||
// problem that ".." can erase a trailing dangling symlink and produce
|
|
||||||
// a path that doesn't quite match what the user asked for.
|
|
||||||
return nil, fmt.Errorf("%w: yet-to-be-created path %q contains '..' components", unix.ENOENT, remainingPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the remaining components.
|
|
||||||
for _, part := range remainingParts {
|
|
||||||
switch part {
|
|
||||||
case "", ".":
|
|
||||||
// Skip over no-op paths.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: mkdir(2) will not follow trailing symlinks, so we can safely
|
|
||||||
// create the final component without worrying about symlink-exchange
|
|
||||||
// attacks.
|
|
||||||
//
|
|
||||||
// If we get -EEXIST, it's possible that another program created the
|
|
||||||
// directory at the same time as us. In that case, just continue on as
|
|
||||||
// if we created it (if the created inode is not a directory, the
|
|
||||||
// following open call will fail).
|
|
||||||
if err := unix.Mkdirat(int(currentDir.Fd()), part, unixMode); err != nil && !errors.Is(err, unix.EEXIST) {
|
|
||||||
err = &os.PathError{Op: "mkdirat", Path: currentDir.Name() + "/" + part, Err: err}
|
|
||||||
// Make the error a bit nicer if the directory is dead.
|
|
||||||
if deadErr := fd.IsDeadInode(currentDir); deadErr != nil {
|
|
||||||
// TODO: Once we bump the minimum Go version to 1.20, we can use
|
|
||||||
// multiple %w verbs for this wrapping. For now we need to use a
|
|
||||||
// compatibility shim for older Go versions.
|
|
||||||
// err = fmt.Errorf("%w (%w)", err, deadErr)
|
|
||||||
err = gocompat.WrapBaseError(err, deadErr)
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get a handle to the next component. O_DIRECTORY means we don't need
|
|
||||||
// to use O_PATH.
|
|
||||||
var nextDir *os.File
|
|
||||||
if linux.HasOpenat2() {
|
|
||||||
nextDir, err = openat2(currentDir, part, &unix.OpenHow{
|
|
||||||
Flags: unix.O_NOFOLLOW | unix.O_DIRECTORY | unix.O_CLOEXEC,
|
|
||||||
Resolve: unix.RESOLVE_BENEATH | unix.RESOLVE_NO_SYMLINKS | unix.RESOLVE_NO_XDEV,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
nextDir, err = fd.Openat(currentDir, part, unix.O_NOFOLLOW|unix.O_DIRECTORY|unix.O_CLOEXEC, 0)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
_ = currentDir.Close()
|
|
||||||
currentDir = nextDir
|
|
||||||
|
|
||||||
// It's possible that the directory we just opened was swapped by an
|
|
||||||
// attacker. Unfortunately there isn't much we can do to protect
|
|
||||||
// against this, and MkdirAll's behaviour is that we will reuse
|
|
||||||
// existing directories anyway so the need to protect against this is
|
|
||||||
// incredibly limited (and arguably doesn't even deserve mention here).
|
|
||||||
//
|
|
||||||
// Ideally we might want to check that the owner and mode match what we
|
|
||||||
// would've created -- unfortunately, it is non-trivial to verify that
|
|
||||||
// the owner and mode of the created directory match. While plain Unix
|
|
||||||
// DAC rules seem simple enough to emulate, there are a bunch of other
|
|
||||||
// factors that can change the mode or owner of created directories
|
|
||||||
// (default POSIX ACLs, mount options like uid=1,gid=2,umask=0 on
|
|
||||||
// filesystems like vfat, etc etc). We used to try to verify this but
|
|
||||||
// it just lead to a series of spurious errors.
|
|
||||||
//
|
|
||||||
// We could also check that the directory is non-empty, but
|
|
||||||
// unfortunately some pseduofilesystems (like cgroupfs) create
|
|
||||||
// non-empty directories, which would result in different spurious
|
|
||||||
// errors.
|
|
||||||
}
|
|
||||||
return currentDir, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MkdirAll is a race-safe alternative to the [os.MkdirAll] function,
|
|
||||||
// where the new directory is guaranteed to be within the root directory (if an
|
|
||||||
// attacker can move directories from inside the root to outside the root, the
|
|
||||||
// created directory tree might be outside of the root but the key constraint
|
|
||||||
// is that at no point will we walk outside of the directory tree we are
|
|
||||||
// creating).
|
|
||||||
//
|
|
||||||
// Effectively, MkdirAll(root, unsafePath, mode) is equivalent to
|
|
||||||
//
|
|
||||||
// path, _ := securejoin.SecureJoin(root, unsafePath)
|
|
||||||
// err := os.MkdirAll(path, mode)
|
|
||||||
//
|
|
||||||
// But is much safer. The above implementation is unsafe because if an attacker
|
|
||||||
// can modify the filesystem tree between [SecureJoin] and [os.MkdirAll], it is
|
|
||||||
// possible for MkdirAll to resolve unsafe symlink components and create
|
|
||||||
// directories outside of the root.
|
|
||||||
//
|
|
||||||
// If you plan to open the directory after you have created it or want to use
|
|
||||||
// an open directory handle as the root, you should use [MkdirAllHandle] instead.
|
|
||||||
// This function is a wrapper around [MkdirAllHandle].
|
|
||||||
//
|
|
||||||
// [SecureJoin]: https://pkg.go.dev/github.com/cyphar/filepath-securejoin#SecureJoin
|
|
||||||
func MkdirAll(root, unsafePath string, mode os.FileMode) error {
|
|
||||||
rootDir, err := os.OpenFile(root, unix.O_PATH|unix.O_DIRECTORY|unix.O_CLOEXEC, 0)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer rootDir.Close() //nolint:errcheck // close failures aren't critical here
|
|
||||||
|
|
||||||
f, err := MkdirAllHandle(rootDir, unsafePath, mode)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_ = f.Close()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
74
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/open_linux.go
generated
vendored
74
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/open_linux.go
generated
vendored
@ -1,74 +0,0 @@
|
|||||||
// SPDX-License-Identifier: MPL-2.0
|
|
||||||
|
|
||||||
//go:build linux
|
|
||||||
|
|
||||||
// Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com>
|
|
||||||
// Copyright (C) 2024-2025 SUSE LLC
|
|
||||||
//
|
|
||||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
|
|
||||||
package pathrs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
|
|
||||||
"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/procfs"
|
|
||||||
)
|
|
||||||
|
|
||||||
// OpenatInRoot is equivalent to [OpenInRoot], except that the root is provided
|
|
||||||
// using an *[os.File] handle, to ensure that the correct root directory is used.
|
|
||||||
func OpenatInRoot(root *os.File, unsafePath string) (*os.File, error) {
|
|
||||||
handle, err := completeLookupInRoot(root, unsafePath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, &os.PathError{Op: "securejoin.OpenInRoot", Path: unsafePath, Err: err}
|
|
||||||
}
|
|
||||||
return handle, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// OpenInRoot safely opens the provided unsafePath within the root.
|
|
||||||
// Effectively, OpenInRoot(root, unsafePath) is equivalent to
|
|
||||||
//
|
|
||||||
// path, _ := securejoin.SecureJoin(root, unsafePath)
|
|
||||||
// handle, err := os.OpenFile(path, unix.O_PATH|unix.O_CLOEXEC)
|
|
||||||
//
|
|
||||||
// But is much safer. The above implementation is unsafe because if an attacker
|
|
||||||
// can modify the filesystem tree between [SecureJoin] and [os.OpenFile], it is
|
|
||||||
// possible for the returned file to be outside of the root.
|
|
||||||
//
|
|
||||||
// Note that the returned handle is an O_PATH handle, meaning that only a very
|
|
||||||
// limited set of operations will work on the handle. This is done to avoid
|
|
||||||
// accidentally opening an untrusted file that could cause issues (such as a
|
|
||||||
// disconnected TTY that could cause a DoS, or some other issue). In order to
|
|
||||||
// use the returned handle, you can "upgrade" it to a proper handle using
|
|
||||||
// [Reopen].
|
|
||||||
//
|
|
||||||
// [SecureJoin]: https://pkg.go.dev/github.com/cyphar/filepath-securejoin#SecureJoin
|
|
||||||
func OpenInRoot(root, unsafePath string) (*os.File, error) {
|
|
||||||
rootDir, err := os.OpenFile(root, unix.O_PATH|unix.O_DIRECTORY|unix.O_CLOEXEC, 0)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer rootDir.Close() //nolint:errcheck // close failures aren't critical here
|
|
||||||
return OpenatInRoot(rootDir, unsafePath)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reopen takes an *[os.File] handle and re-opens it through /proc/self/fd.
|
|
||||||
// Reopen(file, flags) is effectively equivalent to
|
|
||||||
//
|
|
||||||
// fdPath := fmt.Sprintf("/proc/self/fd/%d", file.Fd())
|
|
||||||
// os.OpenFile(fdPath, flags|unix.O_CLOEXEC)
|
|
||||||
//
|
|
||||||
// But with some extra hardenings to ensure that we are not tricked by a
|
|
||||||
// maliciously-configured /proc mount. While this attack scenario is not
|
|
||||||
// common, in container runtimes it is possible for higher-level runtimes to be
|
|
||||||
// tricked into configuring an unsafe /proc that can be used to attack file
|
|
||||||
// operations. See [CVE-2019-19921] for more details.
|
|
||||||
//
|
|
||||||
// [CVE-2019-19921]: https://github.com/advisories/GHSA-fh74-hm69-rqjw
|
|
||||||
func Reopen(handle *os.File, flags int) (*os.File, error) {
|
|
||||||
return procfs.ReopenFd(handle, flags)
|
|
||||||
}
|
|
||||||
101
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/openat2_linux.go
generated
vendored
101
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/openat2_linux.go
generated
vendored
@ -1,101 +0,0 @@
|
|||||||
// SPDX-License-Identifier: MPL-2.0
|
|
||||||
|
|
||||||
//go:build linux
|
|
||||||
|
|
||||||
// Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com>
|
|
||||||
// Copyright (C) 2024-2025 SUSE LLC
|
|
||||||
//
|
|
||||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
|
|
||||||
package pathrs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
|
|
||||||
"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd"
|
|
||||||
"github.com/cyphar/filepath-securejoin/pathrs-lite/procfs"
|
|
||||||
)
|
|
||||||
|
|
||||||
func openat2(dir fd.Fd, path string, how *unix.OpenHow) (*os.File, error) {
|
|
||||||
file, err := fd.Openat2(dir, path, how)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// If we are using RESOLVE_IN_ROOT, the name we generated may be wrong.
|
|
||||||
if how.Resolve&unix.RESOLVE_IN_ROOT == unix.RESOLVE_IN_ROOT {
|
|
||||||
if actualPath, err := procfs.ProcSelfFdReadlink(file); err == nil {
|
|
||||||
// TODO: Ideally we would not need to dup the fd, but you cannot
|
|
||||||
// easily just swap an *os.File with one from the same fd
|
|
||||||
// (the GC will close the old one, and you cannot clear the
|
|
||||||
// finaliser easily because it is associated with an internal
|
|
||||||
// field of *os.File not *os.File itself).
|
|
||||||
newFile, err := fd.DupWithName(file, actualPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
file = newFile
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return file, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func lookupOpenat2(root fd.Fd, unsafePath string, partial bool) (*os.File, string, error) {
|
|
||||||
if !partial {
|
|
||||||
file, err := openat2(root, unsafePath, &unix.OpenHow{
|
|
||||||
Flags: unix.O_PATH | unix.O_CLOEXEC,
|
|
||||||
Resolve: unix.RESOLVE_IN_ROOT | unix.RESOLVE_NO_MAGICLINKS,
|
|
||||||
})
|
|
||||||
return file, "", err
|
|
||||||
}
|
|
||||||
return partialLookupOpenat2(root, unsafePath)
|
|
||||||
}
|
|
||||||
|
|
||||||
// partialLookupOpenat2 is an alternative implementation of
|
|
||||||
// partialLookupInRoot, using openat2(RESOLVE_IN_ROOT) to more safely get a
|
|
||||||
// handle to the deepest existing child of the requested path within the root.
|
|
||||||
func partialLookupOpenat2(root fd.Fd, unsafePath string) (*os.File, string, error) {
|
|
||||||
// TODO: Implement this as a git-bisect-like binary search.
|
|
||||||
|
|
||||||
unsafePath = filepath.ToSlash(unsafePath) // noop
|
|
||||||
endIdx := len(unsafePath)
|
|
||||||
var lastError error
|
|
||||||
for endIdx > 0 {
|
|
||||||
subpath := unsafePath[:endIdx]
|
|
||||||
|
|
||||||
handle, err := openat2(root, subpath, &unix.OpenHow{
|
|
||||||
Flags: unix.O_PATH | unix.O_CLOEXEC,
|
|
||||||
Resolve: unix.RESOLVE_IN_ROOT | unix.RESOLVE_NO_MAGICLINKS,
|
|
||||||
})
|
|
||||||
if err == nil {
|
|
||||||
// Jump over the slash if we have a non-"" remainingPath.
|
|
||||||
if endIdx < len(unsafePath) {
|
|
||||||
endIdx++
|
|
||||||
}
|
|
||||||
// We found a subpath!
|
|
||||||
return handle, unsafePath[endIdx:], lastError
|
|
||||||
}
|
|
||||||
if errors.Is(err, unix.ENOENT) || errors.Is(err, unix.ENOTDIR) {
|
|
||||||
// That path doesn't exist, let's try the next directory up.
|
|
||||||
endIdx = strings.LastIndexByte(subpath, '/')
|
|
||||||
lastError = err
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return nil, "", fmt.Errorf("open subpath: %w", err)
|
|
||||||
}
|
|
||||||
// If we couldn't open anything, the whole subpath is missing. Return a
|
|
||||||
// copy of the root fd so that the caller doesn't close this one by
|
|
||||||
// accident.
|
|
||||||
rootClone, err := fd.Dup(root)
|
|
||||||
if err != nil {
|
|
||||||
return nil, "", err
|
|
||||||
}
|
|
||||||
return rootClone, unsafePath, lastError
|
|
||||||
}
|
|
||||||
157
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/procfs/procfs_linux.go
generated
vendored
157
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/procfs/procfs_linux.go
generated
vendored
@ -1,157 +0,0 @@
|
|||||||
// SPDX-License-Identifier: MPL-2.0
|
|
||||||
|
|
||||||
//go:build linux
|
|
||||||
|
|
||||||
// Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com>
|
|
||||||
// Copyright (C) 2024-2025 SUSE LLC
|
|
||||||
//
|
|
||||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
|
|
||||||
// Package procfs provides a safe API for operating on /proc on Linux.
|
|
||||||
package procfs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/procfs"
|
|
||||||
)
|
|
||||||
|
|
||||||
// This package mostly just wraps internal/procfs APIs. This is necessary
|
|
||||||
// because we are forced to export some things from internal/procfs in order to
|
|
||||||
// avoid some dependency cycle issues, but we don't want users to see or use
|
|
||||||
// them.
|
|
||||||
|
|
||||||
// ProcThreadSelfCloser is a callback that needs to be called when you are done
|
|
||||||
// operating on an [os.File] fetched using [Handle.OpenThreadSelf].
|
|
||||||
//
|
|
||||||
// [os.File]: https://pkg.go.dev/os#File
|
|
||||||
type ProcThreadSelfCloser = procfs.ProcThreadSelfCloser
|
|
||||||
|
|
||||||
// Handle is a wrapper around an *os.File handle to "/proc", which can be used
|
|
||||||
// to do further procfs-related operations in a safe way.
|
|
||||||
type Handle struct {
|
|
||||||
inner *procfs.Handle
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close close the resources associated with this [Handle]. Note that if this
|
|
||||||
// [Handle] was created with [OpenProcRoot], on some kernels the underlying
|
|
||||||
// procfs handle is cached and so this Close operation may be a no-op. However,
|
|
||||||
// you should always call Close on [Handle]s once you are done with them.
|
|
||||||
func (proc *Handle) Close() error { return proc.inner.Close() }
|
|
||||||
|
|
||||||
// OpenProcRoot tries to open a "safer" handle to "/proc" (i.e., one with the
|
|
||||||
// "subset=pid" mount option applied, available from Linux 5.8). Unless you
|
|
||||||
// plan to do many [Handle.OpenRoot] operations, users should prefer to use
|
|
||||||
// this over [OpenUnsafeProcRoot] which is far more dangerous to keep open.
|
|
||||||
//
|
|
||||||
// If a safe handle cannot be opened, OpenProcRoot will fall back to opening a
|
|
||||||
// regular "/proc" handle.
|
|
||||||
//
|
|
||||||
// Note that using [Handle.OpenRoot] will still work with handles returned by
|
|
||||||
// this function. If a subpath cannot be operated on with a safe "/proc"
|
|
||||||
// handle, then [OpenUnsafeProcRoot] will be called internally and a temporary
|
|
||||||
// unsafe handle will be used.
|
|
||||||
func OpenProcRoot() (*Handle, error) {
|
|
||||||
proc, err := procfs.OpenProcRoot()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &Handle{inner: proc}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// OpenUnsafeProcRoot opens a handle to "/proc" without any overmounts or
|
|
||||||
// masked paths. You must be extremely careful to make sure this handle is
|
|
||||||
// never leaked to a container and that you program cannot be tricked into
|
|
||||||
// writing to arbitrary paths within it.
|
|
||||||
//
|
|
||||||
// This is not necessary if you just wish to use [Handle.OpenRoot], as handles
|
|
||||||
// returned by [OpenProcRoot] will fall back to using a *temporary* unsafe
|
|
||||||
// handle in that case. You should only really use this if you need to do many
|
|
||||||
// operations with [Handle.OpenRoot] and the performance overhead of making
|
|
||||||
// many procfs handles is an issue. If you do use OpenUnsafeProcRoot, you
|
|
||||||
// should make sure to close the handle as soon as possible to avoid
|
|
||||||
// known-fd-number attacks.
|
|
||||||
func OpenUnsafeProcRoot() (*Handle, error) {
|
|
||||||
proc, err := procfs.OpenUnsafeProcRoot()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &Handle{inner: proc}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// OpenThreadSelf returns a handle to "/proc/thread-self/<subpath>" (or an
|
|
||||||
// equivalent handle on older kernels where "/proc/thread-self" doesn't exist).
|
|
||||||
// Once finished with the handle, you must call the returned closer function
|
|
||||||
// ([runtime.UnlockOSThread]). You must not pass the returned *os.File to other
|
|
||||||
// Go threads or use the handle after calling the closer.
|
|
||||||
//
|
|
||||||
// [runtime.UnlockOSThread]: https://pkg.go.dev/runtime#UnlockOSThread
|
|
||||||
func (proc *Handle) OpenThreadSelf(subpath string) (*os.File, ProcThreadSelfCloser, error) {
|
|
||||||
return proc.inner.OpenThreadSelf(subpath)
|
|
||||||
}
|
|
||||||
|
|
||||||
// OpenSelf returns a handle to /proc/self/<subpath>.
|
|
||||||
//
|
|
||||||
// Note that in Go programs with non-homogenous threads, this may result in
|
|
||||||
// spurious errors. If you are monkeying around with APIs that are
|
|
||||||
// thread-specific, you probably want to use [Handle.OpenThreadSelf] instead
|
|
||||||
// which will guarantee that the handle refers to the same thread as the caller
|
|
||||||
// is executing on.
|
|
||||||
func (proc *Handle) OpenSelf(subpath string) (*os.File, error) {
|
|
||||||
return proc.inner.OpenSelf(subpath)
|
|
||||||
}
|
|
||||||
|
|
||||||
// OpenRoot returns a handle to /proc/<subpath>.
|
|
||||||
//
|
|
||||||
// You should only use this when you need to operate on global procfs files
|
|
||||||
// (such as sysctls in /proc/sys). Unlike [Handle.OpenThreadSelf],
|
|
||||||
// [Handle.OpenSelf], and [Handle.OpenPid], the procfs handle used internally
|
|
||||||
// for this operation will never use "subset=pid", which makes it a more juicy
|
|
||||||
// target for [CVE-2024-21626]-style attacks (and doing something like opening
|
|
||||||
// a directory with OpenRoot effectively leaks [OpenUnsafeProcRoot] as long as
|
|
||||||
// the file descriptor is open).
|
|
||||||
//
|
|
||||||
// [CVE-2024-21626]: https://github.com/opencontainers/runc/security/advisories/GHSA-xr7r-f8xq-vfvv
|
|
||||||
func (proc *Handle) OpenRoot(subpath string) (*os.File, error) {
|
|
||||||
return proc.inner.OpenRoot(subpath)
|
|
||||||
}
|
|
||||||
|
|
||||||
// OpenPid returns a handle to /proc/$pid/<subpath> (pid can be a pid or tid).
|
|
||||||
// This is mainly intended for usage when operating on other processes.
|
|
||||||
//
|
|
||||||
// You should not use this for the current thread, as special handling is
|
|
||||||
// needed for /proc/thread-self (or /proc/self/task/<tid>) when dealing with
|
|
||||||
// goroutine scheduling -- use [Handle.OpenThreadSelf] instead.
|
|
||||||
//
|
|
||||||
// To refer to the current thread-group, you should use prefer
|
|
||||||
// [Handle.OpenSelf] to passing os.Getpid as the pid argument.
|
|
||||||
func (proc *Handle) OpenPid(pid int, subpath string) (*os.File, error) {
|
|
||||||
return proc.inner.OpenPid(pid, subpath)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ProcSelfFdReadlink gets the real path of the given file by looking at
|
|
||||||
// /proc/self/fd/<fd> with [readlink]. It is effectively just shorthand for
|
|
||||||
// something along the lines of:
|
|
||||||
//
|
|
||||||
// proc, err := procfs.OpenProcRoot()
|
|
||||||
// if err != nil {
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
// link, err := proc.OpenThreadSelf(fmt.Sprintf("fd/%d", f.Fd()))
|
|
||||||
// if err != nil {
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
// defer link.Close()
|
|
||||||
// var buf [4096]byte
|
|
||||||
// n, err := unix.Readlinkat(int(link.Fd()), "", buf[:])
|
|
||||||
// if err != nil {
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
// pathname := buf[:n]
|
|
||||||
//
|
|
||||||
// [readlink]: https://pkg.go.dev/golang.org/x/sys/unix#Readlinkat
|
|
||||||
func ProcSelfFdReadlink(f *os.File) (string, error) {
|
|
||||||
return procfs.ProcSelfFdReadlink(f)
|
|
||||||
}
|
|
||||||
12
vendor/github.com/docker/cli/AUTHORS
generated
vendored
12
vendor/github.com/docker/cli/AUTHORS
generated
vendored
@ -63,6 +63,7 @@ Andreas Köhler <andi5.py@gmx.net>
|
|||||||
Andres G. Aragoneses <knocte@gmail.com>
|
Andres G. Aragoneses <knocte@gmail.com>
|
||||||
Andres Leon Rangel <aleon1220@gmail.com>
|
Andres Leon Rangel <aleon1220@gmail.com>
|
||||||
Andrew France <andrew@avito.co.uk>
|
Andrew France <andrew@avito.co.uk>
|
||||||
|
Andrew He <he.andrew.mail@gmail.com>
|
||||||
Andrew Hsu <andrewhsu@docker.com>
|
Andrew Hsu <andrewhsu@docker.com>
|
||||||
Andrew Macpherson <hopscotch23@gmail.com>
|
Andrew Macpherson <hopscotch23@gmail.com>
|
||||||
Andrew McDonnell <bugs@andrewmcdonnell.net>
|
Andrew McDonnell <bugs@andrewmcdonnell.net>
|
||||||
@ -86,11 +87,12 @@ Archimedes Trajano <developer@trajano.net>
|
|||||||
Arko Dasgupta <arko@tetrate.io>
|
Arko Dasgupta <arko@tetrate.io>
|
||||||
Arnaud Porterie <icecrime@gmail.com>
|
Arnaud Porterie <icecrime@gmail.com>
|
||||||
Arnaud Rebillout <elboulangero@gmail.com>
|
Arnaud Rebillout <elboulangero@gmail.com>
|
||||||
|
Arthur Flageul <arthur.flageul@gmail.com>
|
||||||
Arthur Peka <arthur.peka@outlook.com>
|
Arthur Peka <arthur.peka@outlook.com>
|
||||||
Ashly Mathew <ashly.mathew@sap.com>
|
Ashly Mathew <ashly.mathew@sap.com>
|
||||||
Ashwini Oruganti <ashwini.oruganti@gmail.com>
|
Ashwini Oruganti <ashwini.oruganti@gmail.com>
|
||||||
Aslam Ahemad <aslamahemad@gmail.com>
|
Aslam Ahemad <aslamahemad@gmail.com>
|
||||||
Austin Vazquez <austin.vazquez.dev@gmail.com>
|
Austin Vazquez <austin.vazquez@docker.com>
|
||||||
Azat Khuyiyakhmetov <shadow_uz@mail.ru>
|
Azat Khuyiyakhmetov <shadow_uz@mail.ru>
|
||||||
Bardia Keyoumarsi <bkeyouma@ucsc.edu>
|
Bardia Keyoumarsi <bkeyouma@ucsc.edu>
|
||||||
Barnaby Gray <barnaby@pickle.me.uk>
|
Barnaby Gray <barnaby@pickle.me.uk>
|
||||||
@ -135,10 +137,12 @@ Cao Weiwei <cao.weiwei30@zte.com.cn>
|
|||||||
Carlo Mion <mion00@gmail.com>
|
Carlo Mion <mion00@gmail.com>
|
||||||
Carlos Alexandro Becker <caarlos0@gmail.com>
|
Carlos Alexandro Becker <caarlos0@gmail.com>
|
||||||
Carlos de Paula <me@carlosedp.com>
|
Carlos de Paula <me@carlosedp.com>
|
||||||
|
carsontham <carsontham@outlook.com>
|
||||||
Carston Schilds <Carston.Schilds@visier.com>
|
Carston Schilds <Carston.Schilds@visier.com>
|
||||||
Casey Korver <casey@korver.dev>
|
Casey Korver <casey@korver.dev>
|
||||||
Ce Gao <ce.gao@outlook.com>
|
Ce Gao <ce.gao@outlook.com>
|
||||||
Cedric Davies <cedricda@microsoft.com>
|
Cedric Davies <cedricda@microsoft.com>
|
||||||
|
Cesar Talledo <cesar.talledo@docker.com>
|
||||||
Cezar Sa Espinola <cezarsa@gmail.com>
|
Cezar Sa Espinola <cezarsa@gmail.com>
|
||||||
Chad Faragher <wyckster@hotmail.com>
|
Chad Faragher <wyckster@hotmail.com>
|
||||||
Chao Wang <wangchao.fnst@cn.fujitsu.com>
|
Chao Wang <wangchao.fnst@cn.fujitsu.com>
|
||||||
@ -220,7 +224,7 @@ David Alvarez <david.alvarez@flyeralarm.com>
|
|||||||
David Beitey <david@davidjb.com>
|
David Beitey <david@davidjb.com>
|
||||||
David Calavera <david.calavera@gmail.com>
|
David Calavera <david.calavera@gmail.com>
|
||||||
David Cramer <davcrame@cisco.com>
|
David Cramer <davcrame@cisco.com>
|
||||||
David Dooling <dooling@gmail.com>
|
David Dooling <david.dooling@docker.com>
|
||||||
David Gageot <david@gageot.net>
|
David Gageot <david@gageot.net>
|
||||||
David Karlsson <david.karlsson@docker.com>
|
David Karlsson <david.karlsson@docker.com>
|
||||||
David le Blanc <systemmonkey42@users.noreply.github.com>
|
David le Blanc <systemmonkey42@users.noreply.github.com>
|
||||||
@ -265,6 +269,7 @@ Eli Uriegas <eli.uriegas@docker.com>
|
|||||||
Eli Uriegas <seemethere101@gmail.com>
|
Eli Uriegas <seemethere101@gmail.com>
|
||||||
Elias Faxö <elias.faxo@tre.se>
|
Elias Faxö <elias.faxo@tre.se>
|
||||||
Elliot Luo <956941328@qq.com>
|
Elliot Luo <956941328@qq.com>
|
||||||
|
Eng Zer Jun <engzerjun@gmail.com>
|
||||||
Eric Bode <eric.bode@foundries.io>
|
Eric Bode <eric.bode@foundries.io>
|
||||||
Eric Curtin <ericcurtin17@gmail.com>
|
Eric Curtin <ericcurtin17@gmail.com>
|
||||||
Eric Engestrom <eric@engestrom.ch>
|
Eric Engestrom <eric@engestrom.ch>
|
||||||
@ -345,6 +350,7 @@ Henning Sprang <henning.sprang@gmail.com>
|
|||||||
Henry N <henrynmail-github@yahoo.de>
|
Henry N <henrynmail-github@yahoo.de>
|
||||||
Hernan Garcia <hernandanielg@gmail.com>
|
Hernan Garcia <hernandanielg@gmail.com>
|
||||||
Hongbin Lu <hongbin034@gmail.com>
|
Hongbin Lu <hongbin034@gmail.com>
|
||||||
|
Hossein Abbasi <16090309+hsnabszhdn@users.noreply.github.com>
|
||||||
Hu Keping <hukeping@huawei.com>
|
Hu Keping <hukeping@huawei.com>
|
||||||
Huayi Zhang <irachex@gmail.com>
|
Huayi Zhang <irachex@gmail.com>
|
||||||
Hugo Chastel <Hugo-C@users.noreply.github.com>
|
Hugo Chastel <Hugo-C@users.noreply.github.com>
|
||||||
@ -595,6 +601,7 @@ Michael Prokop <github@michael-prokop.at>
|
|||||||
Michael Scharf <github@scharf.gr>
|
Michael Scharf <github@scharf.gr>
|
||||||
Michael Spetsiotis <michael_spets@hotmail.com>
|
Michael Spetsiotis <michael_spets@hotmail.com>
|
||||||
Michael Steinert <mike.steinert@gmail.com>
|
Michael Steinert <mike.steinert@gmail.com>
|
||||||
|
Michael Tews <michael@tews.dev>
|
||||||
Michael West <mwest@mdsol.com>
|
Michael West <mwest@mdsol.com>
|
||||||
Michal Minář <miminar@redhat.com>
|
Michal Minář <miminar@redhat.com>
|
||||||
Michał Czeraszkiewicz <czerasz@gmail.com>
|
Michał Czeraszkiewicz <czerasz@gmail.com>
|
||||||
@ -896,6 +903,7 @@ Wenlong Zhang <zhangwenlong@loongson.cn>
|
|||||||
Wenzhi Liang <wenzhi.liang@gmail.com>
|
Wenzhi Liang <wenzhi.liang@gmail.com>
|
||||||
Wes Morgan <cap10morgan@gmail.com>
|
Wes Morgan <cap10morgan@gmail.com>
|
||||||
Wewang Xiaorenfine <wang.xiaoren@zte.com.cn>
|
Wewang Xiaorenfine <wang.xiaoren@zte.com.cn>
|
||||||
|
Will Wang <willww64@gmail.com>
|
||||||
William Henry <whenry@redhat.com>
|
William Henry <whenry@redhat.com>
|
||||||
Xianglin Gao <xlgao@zju.edu.cn>
|
Xianglin Gao <xlgao@zju.edu.cn>
|
||||||
Xiaodong Liu <liuxiaodong@loongson.cn>
|
Xiaodong Liu <liuxiaodong@loongson.cn>
|
||||||
|
|||||||
2
vendor/github.com/docker/cli/cli-plugins/metadata/metadata.go
generated
vendored
2
vendor/github.com/docker/cli/cli-plugins/metadata/metadata.go
generated
vendored
@ -33,4 +33,6 @@ type Metadata struct {
|
|||||||
ShortDescription string `json:",omitempty"`
|
ShortDescription string `json:",omitempty"`
|
||||||
// URL is a pointer to the plugin's homepage.
|
// URL is a pointer to the plugin's homepage.
|
||||||
URL string `json:",omitempty"`
|
URL string `json:",omitempty"`
|
||||||
|
// Hidden hides the plugin in completion and help message output.
|
||||||
|
Hidden bool `json:",omitempty"`
|
||||||
}
|
}
|
||||||
|
|||||||
42
vendor/github.com/docker/cli/cli/cobra.go
generated
vendored
42
vendor/github.com/docker/cli/cli/cobra.go
generated
vendored
@ -12,7 +12,6 @@ import (
|
|||||||
"github.com/fvbommel/sortorder"
|
"github.com/fvbommel/sortorder"
|
||||||
"github.com/moby/term"
|
"github.com/moby/term"
|
||||||
"github.com/morikuni/aec"
|
"github.com/morikuni/aec"
|
||||||
"github.com/pkg/errors"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
)
|
)
|
||||||
@ -167,31 +166,6 @@ func (tcmd *TopLevelCommand) Initialize(ops ...command.CLIOption) error {
|
|||||||
return tcmd.dockerCli.Initialize(tcmd.opts, ops...)
|
return tcmd.dockerCli.Initialize(tcmd.opts, ops...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// VisitAll will traverse all commands from the root.
|
|
||||||
//
|
|
||||||
// Deprecated: this utility was only used internally and will be removed in the next release.
|
|
||||||
func VisitAll(root *cobra.Command, fn func(*cobra.Command)) {
|
|
||||||
visitAll(root, fn)
|
|
||||||
}
|
|
||||||
|
|
||||||
func visitAll(root *cobra.Command, fn func(*cobra.Command)) {
|
|
||||||
for _, cmd := range root.Commands() {
|
|
||||||
visitAll(cmd, fn)
|
|
||||||
}
|
|
||||||
fn(root)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DisableFlagsInUseLine sets the DisableFlagsInUseLine flag on all
|
|
||||||
// commands within the tree rooted at cmd.
|
|
||||||
//
|
|
||||||
// Deprecated: this utility was only used internally and will be removed in the next release.
|
|
||||||
func DisableFlagsInUseLine(cmd *cobra.Command) {
|
|
||||||
visitAll(cmd, func(ccmd *cobra.Command) {
|
|
||||||
// do not add a `[flags]` to the end of the usage line.
|
|
||||||
ccmd.DisableFlagsInUseLine = true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
var helpCommand = &cobra.Command{
|
var helpCommand = &cobra.Command{
|
||||||
Use: "help [command]",
|
Use: "help [command]",
|
||||||
Short: "Help about the command",
|
Short: "Help about the command",
|
||||||
@ -200,7 +174,7 @@ var helpCommand = &cobra.Command{
|
|||||||
RunE: func(c *cobra.Command, args []string) error {
|
RunE: func(c *cobra.Command, args []string) error {
|
||||||
cmd, args, e := c.Root().Find(args)
|
cmd, args, e := c.Root().Find(args)
|
||||||
if cmd == nil || e != nil || len(args) > 0 {
|
if cmd == nil || e != nil || len(args) > 0 {
|
||||||
return errors.Errorf("unknown help topic: %v", strings.Join(args, " "))
|
return fmt.Errorf("unknown help topic: %v", strings.Join(args, " "))
|
||||||
}
|
}
|
||||||
helpFunc := cmd.HelpFunc()
|
helpFunc := cmd.HelpFunc()
|
||||||
helpFunc(cmd, args)
|
helpFunc(cmd, args)
|
||||||
@ -276,11 +250,12 @@ func commandAliases(cmd *cobra.Command) string {
|
|||||||
if cmd.HasParent() {
|
if cmd.HasParent() {
|
||||||
parentPath = cmd.Parent().CommandPath() + " "
|
parentPath = cmd.Parent().CommandPath() + " "
|
||||||
}
|
}
|
||||||
aliases := cmd.CommandPath()
|
var aliases strings.Builder
|
||||||
|
aliases.WriteString(cmd.CommandPath())
|
||||||
for _, alias := range cmd.Aliases {
|
for _, alias := range cmd.Aliases {
|
||||||
aliases += ", " + parentPath + alias
|
aliases.WriteString(", " + parentPath + alias)
|
||||||
}
|
}
|
||||||
return aliases
|
return aliases.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func topCommands(cmd *cobra.Command) []*cobra.Command {
|
func topCommands(cmd *cobra.Command) []*cobra.Command {
|
||||||
@ -376,13 +351,10 @@ func orchestratorSubCommands(cmd *cobra.Command) []*cobra.Command {
|
|||||||
func allManagementSubCommands(cmd *cobra.Command) []*cobra.Command {
|
func allManagementSubCommands(cmd *cobra.Command) []*cobra.Command {
|
||||||
cmds := []*cobra.Command{}
|
cmds := []*cobra.Command{}
|
||||||
for _, sub := range cmd.Commands() {
|
for _, sub := range cmd.Commands() {
|
||||||
if isPlugin(sub) {
|
if invalidPluginReason(sub) != "" {
|
||||||
if invalidPluginReason(sub) == "" {
|
|
||||||
cmds = append(cmds, sub)
|
|
||||||
}
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if sub.IsAvailableCommand() && sub.HasSubCommands() {
|
if sub.IsAvailableCommand() && (isPlugin(sub) || sub.HasSubCommands()) {
|
||||||
cmds = append(cmds, sub)
|
cmds = append(cmds, sub)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
91
vendor/github.com/docker/cli/cli/command/cli.go
generated
vendored
91
vendor/github.com/docker/cli/cli/command/cli.go
generated
vendored
@ -1,10 +1,11 @@
|
|||||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||||
//go:build go1.23
|
//go:build go1.24
|
||||||
|
|
||||||
package command
|
package command
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
@ -23,11 +24,8 @@ import (
|
|||||||
"github.com/docker/cli/cli/streams"
|
"github.com/docker/cli/cli/streams"
|
||||||
"github.com/docker/cli/cli/version"
|
"github.com/docker/cli/cli/version"
|
||||||
dopts "github.com/docker/cli/opts"
|
dopts "github.com/docker/cli/opts"
|
||||||
"github.com/docker/docker/api"
|
"github.com/moby/moby/api/types/build"
|
||||||
"github.com/docker/docker/api/types/build"
|
"github.com/moby/moby/client"
|
||||||
"github.com/docker/docker/api/types/swarm"
|
|
||||||
"github.com/docker/docker/client"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -45,12 +43,9 @@ type Cli interface {
|
|||||||
Client() client.APIClient
|
Client() client.APIClient
|
||||||
Streams
|
Streams
|
||||||
SetIn(in *streams.In)
|
SetIn(in *streams.In)
|
||||||
Apply(ops ...CLIOption) error
|
|
||||||
config.Provider
|
config.Provider
|
||||||
ServerInfo() ServerInfo
|
ServerInfo() ServerInfo
|
||||||
DefaultVersion() string
|
|
||||||
CurrentVersion() string
|
CurrentVersion() string
|
||||||
ContentTrustEnabled() bool
|
|
||||||
BuildKitEnabled() (bool, error)
|
BuildKitEnabled() (bool, error)
|
||||||
ContextStore() store.Store
|
ContextStore() store.Store
|
||||||
CurrentContext() string
|
CurrentContext() string
|
||||||
@ -70,7 +65,6 @@ type DockerCli struct {
|
|||||||
err *streams.Out
|
err *streams.Out
|
||||||
client client.APIClient
|
client client.APIClient
|
||||||
serverInfo ServerInfo
|
serverInfo ServerInfo
|
||||||
contentTrust bool
|
|
||||||
contextStore store.Store
|
contextStore store.Store
|
||||||
currentContext string
|
currentContext string
|
||||||
init sync.Once
|
init sync.Once
|
||||||
@ -78,6 +72,7 @@ type DockerCli struct {
|
|||||||
dockerEndpoint docker.Endpoint
|
dockerEndpoint docker.Endpoint
|
||||||
contextStoreConfig *store.Config
|
contextStoreConfig *store.Config
|
||||||
initTimeout time.Duration
|
initTimeout time.Duration
|
||||||
|
userAgent string
|
||||||
res telemetryResource
|
res telemetryResource
|
||||||
|
|
||||||
// baseCtx is the base context used for internal operations. In the future
|
// baseCtx is the base context used for internal operations. In the future
|
||||||
@ -88,17 +83,12 @@ type DockerCli struct {
|
|||||||
enableGlobalMeter, enableGlobalTracer bool
|
enableGlobalMeter, enableGlobalTracer bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultVersion returns [api.DefaultVersion].
|
|
||||||
func (*DockerCli) DefaultVersion() string {
|
|
||||||
return api.DefaultVersion
|
|
||||||
}
|
|
||||||
|
|
||||||
// CurrentVersion returns the API version currently negotiated, or the default
|
// CurrentVersion returns the API version currently negotiated, or the default
|
||||||
// version otherwise.
|
// version otherwise.
|
||||||
func (cli *DockerCli) CurrentVersion() string {
|
func (cli *DockerCli) CurrentVersion() string {
|
||||||
_ = cli.initialize()
|
_ = cli.initialize()
|
||||||
if cli.client == nil {
|
if cli.client == nil {
|
||||||
return api.DefaultVersion
|
return client.MaxAPIVersion
|
||||||
}
|
}
|
||||||
return cli.client.ClientVersion()
|
return cli.client.ClientVersion()
|
||||||
}
|
}
|
||||||
@ -157,19 +147,13 @@ func (cli *DockerCli) ServerInfo() ServerInfo {
|
|||||||
return cli.serverInfo
|
return cli.serverInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
// ContentTrustEnabled returns whether content trust has been enabled by an
|
|
||||||
// environment variable.
|
|
||||||
func (cli *DockerCli) ContentTrustEnabled() bool {
|
|
||||||
return cli.contentTrust
|
|
||||||
}
|
|
||||||
|
|
||||||
// BuildKitEnabled returns buildkit is enabled or not.
|
// BuildKitEnabled returns buildkit is enabled or not.
|
||||||
func (cli *DockerCli) BuildKitEnabled() (bool, error) {
|
func (cli *DockerCli) BuildKitEnabled() (bool, error) {
|
||||||
// use DOCKER_BUILDKIT env var value if set and not empty
|
// use DOCKER_BUILDKIT env var value if set and not empty
|
||||||
if v := os.Getenv("DOCKER_BUILDKIT"); v != "" {
|
if v := os.Getenv("DOCKER_BUILDKIT"); v != "" {
|
||||||
enabled, err := strconv.ParseBool(v)
|
enabled, err := strconv.ParseBool(v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, errors.Wrap(err, "DOCKER_BUILDKIT environment variable expects boolean value")
|
return false, fmt.Errorf("DOCKER_BUILDKIT environment variable expects boolean value: %w", err)
|
||||||
}
|
}
|
||||||
return enabled, nil
|
return enabled, nil
|
||||||
}
|
}
|
||||||
@ -269,7 +253,7 @@ func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions, ops ...CLIOption)
|
|||||||
cli.contextStore = &ContextStoreWithDefault{
|
cli.contextStore = &ContextStoreWithDefault{
|
||||||
Store: store.New(config.ContextStoreDir(), *cli.contextStoreConfig),
|
Store: store.New(config.ContextStoreDir(), *cli.contextStoreConfig),
|
||||||
Resolver: func() (*DefaultContext, error) {
|
Resolver: func() (*DefaultContext, error) {
|
||||||
return ResolveDefaultContext(cli.options, *cli.contextStoreConfig)
|
return resolveDefaultContext(cli.options, *cli.contextStoreConfig)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -306,17 +290,17 @@ func NewAPIClientFromFlags(opts *cliflags.ClientOptions, configFile *configfile.
|
|||||||
contextStore := &ContextStoreWithDefault{
|
contextStore := &ContextStoreWithDefault{
|
||||||
Store: store.New(config.ContextStoreDir(), storeConfig),
|
Store: store.New(config.ContextStoreDir(), storeConfig),
|
||||||
Resolver: func() (*DefaultContext, error) {
|
Resolver: func() (*DefaultContext, error) {
|
||||||
return ResolveDefaultContext(opts, storeConfig)
|
return resolveDefaultContext(opts, storeConfig)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
endpoint, err := resolveDockerEndpoint(contextStore, resolveContextName(opts, configFile))
|
endpoint, err := resolveDockerEndpoint(contextStore, resolveContextName(opts, configFile))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "unable to resolve docker endpoint")
|
return nil, fmt.Errorf("unable to resolve docker endpoint: %w", err)
|
||||||
}
|
}
|
||||||
return newAPIClientFromEndpoint(endpoint, configFile)
|
return newAPIClientFromEndpoint(endpoint, configFile, client.WithUserAgent(UserAgent()))
|
||||||
}
|
}
|
||||||
|
|
||||||
func newAPIClientFromEndpoint(ep docker.Endpoint, configFile *configfile.ConfigFile) (client.APIClient, error) {
|
func newAPIClientFromEndpoint(ep docker.Endpoint, configFile *configfile.ConfigFile, extraOpts ...client.Opt) (client.APIClient, error) {
|
||||||
opts, err := ep.ClientOpts()
|
opts, err := ep.ClientOpts()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -324,8 +308,15 @@ func newAPIClientFromEndpoint(ep docker.Endpoint, configFile *configfile.ConfigF
|
|||||||
if len(configFile.HTTPHeaders) > 0 {
|
if len(configFile.HTTPHeaders) > 0 {
|
||||||
opts = append(opts, client.WithHTTPHeaders(configFile.HTTPHeaders))
|
opts = append(opts, client.WithHTTPHeaders(configFile.HTTPHeaders))
|
||||||
}
|
}
|
||||||
opts = append(opts, withCustomHeadersFromEnv(), client.WithUserAgent(UserAgent()))
|
withCustomHeaders, err := withCustomHeadersFromEnv()
|
||||||
return client.NewClientWithOpts(opts...)
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if withCustomHeaders != nil {
|
||||||
|
opts = append(opts, withCustomHeaders)
|
||||||
|
}
|
||||||
|
opts = append(opts, extraOpts...)
|
||||||
|
return client.New(opts...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func resolveDockerEndpoint(s store.Reader, contextName string) (docker.Endpoint, error) {
|
func resolveDockerEndpoint(s store.Reader, contextName string) (docker.Endpoint, error) {
|
||||||
@ -386,24 +377,21 @@ func (cli *DockerCli) initializeFromClient() {
|
|||||||
ctx, cancel := context.WithTimeout(cli.baseCtx, cli.getInitTimeout())
|
ctx, cancel := context.WithTimeout(cli.baseCtx, cli.getInitTimeout())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
ping, err := cli.client.Ping(ctx)
|
ping, err := cli.client.Ping(ctx, client.PingOptions{
|
||||||
|
NegotiateAPIVersion: true,
|
||||||
|
ForceNegotiate: true,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Default to true if we fail to connect to daemon
|
// Default to true if we fail to connect to daemon
|
||||||
cli.serverInfo = ServerInfo{HasExperimental: true}
|
cli.serverInfo = ServerInfo{HasExperimental: true}
|
||||||
|
|
||||||
if ping.APIVersion != "" {
|
|
||||||
cli.client.NegotiateAPIVersionPing(ping)
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cli.serverInfo = ServerInfo{
|
cli.serverInfo = ServerInfo{
|
||||||
HasExperimental: ping.Experimental,
|
HasExperimental: ping.Experimental,
|
||||||
OSType: ping.OSType,
|
OSType: ping.OSType,
|
||||||
BuildkitVersion: ping.BuilderVersion,
|
BuildkitVersion: ping.BuilderVersion,
|
||||||
SwarmStatus: ping.SwarmStatus,
|
SwarmStatus: ping.SwarmStatus,
|
||||||
}
|
}
|
||||||
cli.client.NegotiateAPIVersionPing(ping)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ContextStore returns the ContextStore
|
// ContextStore returns the ContextStore
|
||||||
@ -541,11 +529,12 @@ func (cli *DockerCli) initialize() error {
|
|||||||
cli.init.Do(func() {
|
cli.init.Do(func() {
|
||||||
cli.dockerEndpoint, cli.initErr = cli.getDockerEndPoint()
|
cli.dockerEndpoint, cli.initErr = cli.getDockerEndPoint()
|
||||||
if cli.initErr != nil {
|
if cli.initErr != nil {
|
||||||
cli.initErr = errors.Wrap(cli.initErr, "unable to resolve docker endpoint")
|
cli.initErr = fmt.Errorf("unable to resolve docker endpoint: %w", cli.initErr)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if cli.client == nil {
|
if cli.client == nil {
|
||||||
if cli.client, cli.initErr = newAPIClientFromEndpoint(cli.dockerEndpoint, cli.configFile); cli.initErr != nil {
|
ops := []client.Opt{client.WithUserAgent(cli.userAgent)}
|
||||||
|
if cli.client, cli.initErr = newAPIClientFromEndpoint(cli.dockerEndpoint, cli.configFile, ops...); cli.initErr != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -557,16 +546,6 @@ func (cli *DockerCli) initialize() error {
|
|||||||
return cli.initErr
|
return cli.initErr
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply all the operation on the cli
|
|
||||||
func (cli *DockerCli) Apply(ops ...CLIOption) error {
|
|
||||||
for _, op := range ops {
|
|
||||||
if err := op(cli); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ServerInfo stores details about the supported features and platform of the
|
// ServerInfo stores details about the supported features and platform of the
|
||||||
// server
|
// server
|
||||||
type ServerInfo struct {
|
type ServerInfo struct {
|
||||||
@ -581,7 +560,7 @@ type ServerInfo struct {
|
|||||||
// in the ping response, or if an error occurred, in which case the client
|
// in the ping response, or if an error occurred, in which case the client
|
||||||
// should use other ways to get the current swarm status, such as the /swarm
|
// should use other ways to get the current swarm status, such as the /swarm
|
||||||
// endpoint.
|
// endpoint.
|
||||||
SwarmStatus *swarm.Status
|
SwarmStatus *client.SwarmStatus
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDockerCli returns a DockerCli instance with all operators applied on it.
|
// NewDockerCli returns a DockerCli instance with all operators applied on it.
|
||||||
@ -589,15 +568,17 @@ type ServerInfo struct {
|
|||||||
// environment.
|
// environment.
|
||||||
func NewDockerCli(ops ...CLIOption) (*DockerCli, error) {
|
func NewDockerCli(ops ...CLIOption) (*DockerCli, error) {
|
||||||
defaultOps := []CLIOption{
|
defaultOps := []CLIOption{
|
||||||
WithContentTrustFromEnv(),
|
|
||||||
WithDefaultContextStoreConfig(),
|
WithDefaultContextStoreConfig(),
|
||||||
WithStandardStreams(),
|
WithStandardStreams(),
|
||||||
|
WithUserAgent(UserAgent()),
|
||||||
}
|
}
|
||||||
ops = append(defaultOps, ops...)
|
ops = append(defaultOps, ops...)
|
||||||
|
|
||||||
cli := &DockerCli{baseCtx: context.Background()}
|
cli := &DockerCli{baseCtx: context.Background()}
|
||||||
if err := cli.Apply(ops...); err != nil {
|
for _, op := range ops {
|
||||||
return nil, err
|
if err := op(cli); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return cli, nil
|
return cli, nil
|
||||||
}
|
}
|
||||||
@ -609,11 +590,11 @@ func getServerHost(hosts []string, defaultToTLS bool) (string, error) {
|
|||||||
case 1:
|
case 1:
|
||||||
return dopts.ParseHost(defaultToTLS, hosts[0])
|
return dopts.ParseHost(defaultToTLS, hosts[0])
|
||||||
default:
|
default:
|
||||||
return "", errors.New("Specify only one -H")
|
return "", errors.New("specify only one -H")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// UserAgent returns the user agent string used for making API requests
|
// UserAgent returns the default user agent string used for making API requests.
|
||||||
func UserAgent() string {
|
func UserAgent() string {
|
||||||
return "Docker-Client/" + version.Version + " (" + runtime.GOOS + ")"
|
return "Docker-Client/" + version.Version + " (" + runtime.GOOS + ")"
|
||||||
}
|
}
|
||||||
|
|||||||
133
vendor/github.com/docker/cli/cli/command/cli_options.go
generated
vendored
133
vendor/github.com/docker/cli/cli/command/cli_options.go
generated
vendored
@ -3,16 +3,16 @@ package command
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/csv"
|
"encoding/csv"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/docker/cli/cli/streams"
|
"github.com/docker/cli/cli/streams"
|
||||||
"github.com/docker/docker/client"
|
"github.com/moby/moby/client"
|
||||||
"github.com/moby/term"
|
"github.com/moby/term"
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// CLIOption is a functional argument to apply options to a [DockerCli]. These
|
// CLIOption is a functional argument to apply options to a [DockerCli]. These
|
||||||
@ -75,28 +75,6 @@ func WithErrorStream(err io.Writer) CLIOption {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithContentTrustFromEnv enables content trust on a cli from environment variable DOCKER_CONTENT_TRUST value.
|
|
||||||
func WithContentTrustFromEnv() CLIOption {
|
|
||||||
return func(cli *DockerCli) error {
|
|
||||||
cli.contentTrust = false
|
|
||||||
if e := os.Getenv("DOCKER_CONTENT_TRUST"); e != "" {
|
|
||||||
if t, err := strconv.ParseBool(e); t || err != nil {
|
|
||||||
// treat any other value as true
|
|
||||||
cli.contentTrust = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithContentTrust enables content trust on a cli.
|
|
||||||
func WithContentTrust(enabled bool) CLIOption {
|
|
||||||
return func(cli *DockerCli) error {
|
|
||||||
cli.contentTrust = enabled
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithDefaultContextStoreConfig configures the cli to use the default context store configuration.
|
// WithDefaultContextStoreConfig configures the cli to use the default context store configuration.
|
||||||
func WithDefaultContextStoreConfig() CLIOption {
|
func WithDefaultContextStoreConfig() CLIOption {
|
||||||
return func(cli *DockerCli) error {
|
return func(cli *DockerCli) error {
|
||||||
@ -180,61 +158,70 @@ const envOverrideHTTPHeaders = "DOCKER_CUSTOM_HEADERS"
|
|||||||
// override headers with the same name).
|
// override headers with the same name).
|
||||||
//
|
//
|
||||||
// TODO(thaJeztah): this is a client Option, and should be moved to the client. It is non-exported for that reason.
|
// TODO(thaJeztah): this is a client Option, and should be moved to the client. It is non-exported for that reason.
|
||||||
func withCustomHeadersFromEnv() client.Opt {
|
func withCustomHeadersFromEnv() (client.Opt, error) {
|
||||||
return func(apiClient *client.Client) error {
|
value := os.Getenv(envOverrideHTTPHeaders)
|
||||||
value := os.Getenv(envOverrideHTTPHeaders)
|
if value == "" {
|
||||||
if value == "" {
|
return nil, nil
|
||||||
return nil
|
}
|
||||||
}
|
csvReader := csv.NewReader(strings.NewReader(value))
|
||||||
csvReader := csv.NewReader(strings.NewReader(value))
|
fields, err := csvReader.Read()
|
||||||
fields, err := csvReader.Read()
|
if err != nil {
|
||||||
if err != nil {
|
return nil, invalidParameter(fmt.Errorf(
|
||||||
return invalidParameter(errors.Errorf(
|
"failed to parse custom headers from %s environment variable: value must be formatted as comma-separated key=value pairs",
|
||||||
"failed to parse custom headers from %s environment variable: value must be formatted as comma-separated key=value pairs",
|
envOverrideHTTPHeaders,
|
||||||
envOverrideHTTPHeaders,
|
))
|
||||||
|
}
|
||||||
|
if len(fields) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
env := map[string]string{}
|
||||||
|
for _, kv := range fields {
|
||||||
|
k, v, hasValue := strings.Cut(kv, "=")
|
||||||
|
|
||||||
|
// Only strip whitespace in keys; preserve whitespace in values.
|
||||||
|
k = strings.TrimSpace(k)
|
||||||
|
|
||||||
|
if k == "" {
|
||||||
|
return nil, invalidParameter(fmt.Errorf(
|
||||||
|
`failed to set custom headers from %s environment variable: value contains a key=value pair with an empty key: '%s'`,
|
||||||
|
envOverrideHTTPHeaders, kv,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
if len(fields) == 0 {
|
|
||||||
return nil
|
// We don't currently allow empty key=value pairs, and produce an error.
|
||||||
|
// This is something we could allow in future (e.g. to read value
|
||||||
|
// from an environment variable with the same name). In the meantime,
|
||||||
|
// produce an error to prevent users from depending on this.
|
||||||
|
if !hasValue {
|
||||||
|
return nil, invalidParameter(fmt.Errorf(
|
||||||
|
`failed to set custom headers from %s environment variable: missing "=" in key=value pair: '%s'`,
|
||||||
|
envOverrideHTTPHeaders, kv,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
env := map[string]string{}
|
env[http.CanonicalHeaderKey(k)] = v
|
||||||
for _, kv := range fields {
|
}
|
||||||
k, v, hasValue := strings.Cut(kv, "=")
|
|
||||||
|
|
||||||
// Only strip whitespace in keys; preserve whitespace in values.
|
if len(env) == 0 {
|
||||||
k = strings.TrimSpace(k)
|
// We should probably not hit this case, as we don't skip values
|
||||||
|
// (only return errors), but we don't want to discard existing
|
||||||
|
// headers with an empty set.
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
if k == "" {
|
// TODO(thaJeztah): add a client.WithExtraHTTPHeaders() function to allow these headers to be _added_ to existing ones, instead of _replacing_
|
||||||
return invalidParameter(errors.Errorf(
|
// see https://github.com/docker/cli/pull/5098#issuecomment-2147403871 (when updating, also update the WARNING in the function and env-var GoDoc)
|
||||||
`failed to set custom headers from %s environment variable: value contains a key=value pair with an empty key: '%s'`,
|
return client.WithHTTPHeaders(env), nil
|
||||||
envOverrideHTTPHeaders, kv,
|
}
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
// We don't currently allow empty key=value pairs, and produce an error.
|
// WithUserAgent configures the User-Agent string for cli HTTP requests.
|
||||||
// This is something we could allow in future (e.g. to read value
|
func WithUserAgent(userAgent string) CLIOption {
|
||||||
// from an environment variable with the same name). In the meantime,
|
return func(cli *DockerCli) error {
|
||||||
// produce an error to prevent users from depending on this.
|
if userAgent == "" {
|
||||||
if !hasValue {
|
return errors.New("user agent cannot be blank")
|
||||||
return invalidParameter(errors.Errorf(
|
|
||||||
`failed to set custom headers from %s environment variable: missing "=" in key=value pair: '%s'`,
|
|
||||||
envOverrideHTTPHeaders, kv,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
env[http.CanonicalHeaderKey(k)] = v
|
|
||||||
}
|
}
|
||||||
|
cli.userAgent = userAgent
|
||||||
if len(env) == 0 {
|
return nil
|
||||||
// We should probably not hit this case, as we don't skip values
|
|
||||||
// (only return errors), but we don't want to discard existing
|
|
||||||
// headers with an empty set.
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(thaJeztah): add a client.WithExtraHTTPHeaders() function to allow these headers to be _added_ to existing ones, instead of _replacing_
|
|
||||||
// see https://github.com/docker/cli/pull/5098#issuecomment-2147403871 (when updating, also update the WARNING in the function and env-var GoDoc)
|
|
||||||
return client.WithHTTPHeaders(env)(apiClient)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
2
vendor/github.com/docker/cli/cli/command/context.go
generated
vendored
2
vendor/github.com/docker/cli/cli/command/context.go
generated
vendored
@ -1,5 +1,5 @@
|
|||||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||||
//go:build go1.23
|
//go:build go1.24
|
||||||
|
|
||||||
package command
|
package command
|
||||||
|
|
||||||
|
|||||||
12
vendor/github.com/docker/cli/cli/command/defaultcontextstore.go
generated
vendored
12
vendor/github.com/docker/cli/cli/command/defaultcontextstore.go
generated
vendored
@ -1,13 +1,15 @@
|
|||||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||||
//go:build go1.23
|
//go:build go1.24
|
||||||
|
|
||||||
package command
|
package command
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/docker/cli/cli/context/docker"
|
"github.com/docker/cli/cli/context/docker"
|
||||||
"github.com/docker/cli/cli/context/store"
|
"github.com/docker/cli/cli/context/store"
|
||||||
cliflags "github.com/docker/cli/cli/flags"
|
cliflags "github.com/docker/cli/cli/flags"
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -51,8 +53,8 @@ type EndpointDefaultResolver interface {
|
|||||||
ResolveDefault() (any, *store.EndpointTLSData, error)
|
ResolveDefault() (any, *store.EndpointTLSData, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResolveDefaultContext creates a Metadata for the current CLI invocation parameters
|
// resolveDefaultContext creates a Metadata for the current CLI invocation parameters
|
||||||
func ResolveDefaultContext(opts *cliflags.ClientOptions, config store.Config) (*DefaultContext, error) {
|
func resolveDefaultContext(opts *cliflags.ClientOptions, config store.Config) (*DefaultContext, error) {
|
||||||
contextTLSData := store.ContextTLSData{
|
contextTLSData := store.ContextTLSData{
|
||||||
Endpoints: make(map[string]store.EndpointTLSData),
|
Endpoints: make(map[string]store.EndpointTLSData),
|
||||||
}
|
}
|
||||||
@ -185,7 +187,7 @@ func (s *ContextStoreWithDefault) GetTLSData(contextName, endpointName, fileName
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if defaultContext.TLS.Endpoints[endpointName].Files[fileName] == nil {
|
if defaultContext.TLS.Endpoints[endpointName].Files[fileName] == nil {
|
||||||
return nil, notFound(errors.Errorf("TLS data for %s/%s/%s does not exist", DefaultContextName, endpointName, fileName))
|
return nil, notFound(fmt.Errorf("TLS data for %s/%s/%s does not exist", DefaultContextName, endpointName, fileName))
|
||||||
}
|
}
|
||||||
return defaultContext.TLS.Endpoints[endpointName].Files[fileName], nil
|
return defaultContext.TLS.Endpoints[endpointName].Files[fileName], nil
|
||||||
}
|
}
|
||||||
|
|||||||
10
vendor/github.com/docker/cli/cli/command/formatter/buildcache.go
generated
vendored
10
vendor/github.com/docker/cli/cli/command/formatter/buildcache.go
generated
vendored
@ -6,8 +6,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types/build"
|
|
||||||
"github.com/docker/go-units"
|
"github.com/docker/go-units"
|
||||||
|
"github.com/moby/moby/api/types/build"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -51,7 +51,7 @@ shared: {{.Shared}}
|
|||||||
return Format(source)
|
return Format(source)
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildCacheSort(buildCache []*build.CacheRecord) {
|
func buildCacheSort(buildCache []build.CacheRecord) {
|
||||||
sort.Slice(buildCache, func(i, j int) bool {
|
sort.Slice(buildCache, func(i, j int) bool {
|
||||||
lui, luj := buildCache[i].LastUsedAt, buildCache[j].LastUsedAt
|
lui, luj := buildCache[i].LastUsedAt, buildCache[j].LastUsedAt
|
||||||
switch {
|
switch {
|
||||||
@ -70,7 +70,7 @@ func buildCacheSort(buildCache []*build.CacheRecord) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// BuildCacheWrite renders the context for a list of containers
|
// BuildCacheWrite renders the context for a list of containers
|
||||||
func BuildCacheWrite(ctx Context, buildCaches []*build.CacheRecord) error {
|
func BuildCacheWrite(ctx Context, buildCaches []build.CacheRecord) error {
|
||||||
render := func(format func(subContext SubContext) error) error {
|
render := func(format func(subContext SubContext) error) error {
|
||||||
buildCacheSort(buildCaches)
|
buildCacheSort(buildCaches)
|
||||||
for _, bc := range buildCaches {
|
for _, bc := range buildCaches {
|
||||||
@ -87,7 +87,7 @@ func BuildCacheWrite(ctx Context, buildCaches []*build.CacheRecord) error {
|
|||||||
type buildCacheContext struct {
|
type buildCacheContext struct {
|
||||||
HeaderContext
|
HeaderContext
|
||||||
trunc bool
|
trunc bool
|
||||||
v *build.CacheRecord
|
v build.CacheRecord
|
||||||
}
|
}
|
||||||
|
|
||||||
func newBuildCacheContext() *buildCacheContext {
|
func newBuildCacheContext() *buildCacheContext {
|
||||||
@ -126,8 +126,6 @@ func (c *buildCacheContext) Parent() string {
|
|||||||
var parent string
|
var parent string
|
||||||
if len(c.v.Parents) > 0 {
|
if len(c.v.Parents) > 0 {
|
||||||
parent = strings.Join(c.v.Parents, ", ")
|
parent = strings.Join(c.v.Parents, ", ")
|
||||||
} else {
|
|
||||||
parent = c.v.Parent //nolint:staticcheck // Ignore SA1019: Field was deprecated in API v1.42, but kept for backward compatibility
|
|
||||||
}
|
}
|
||||||
if c.trunc {
|
if c.trunc {
|
||||||
return TruncateID(parent)
|
return TruncateID(parent)
|
||||||
|
|||||||
56
vendor/github.com/docker/cli/cli/command/formatter/container.go
generated
vendored
56
vendor/github.com/docker/cli/cli/command/formatter/container.go
generated
vendored
@ -1,5 +1,5 @@
|
|||||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||||
//go:build go1.23
|
//go:build go1.24
|
||||||
|
|
||||||
package formatter
|
package formatter
|
||||||
|
|
||||||
@ -13,8 +13,8 @@ import (
|
|||||||
|
|
||||||
"github.com/containerd/platforms"
|
"github.com/containerd/platforms"
|
||||||
"github.com/distribution/reference"
|
"github.com/distribution/reference"
|
||||||
"github.com/docker/docker/api/types/container"
|
|
||||||
"github.com/docker/go-units"
|
"github.com/docker/go-units"
|
||||||
|
"github.com/moby/moby/api/types/container"
|
||||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -170,27 +170,33 @@ func (c *ContainerContext) Image() string {
|
|||||||
if c.c.Image == "" {
|
if c.c.Image == "" {
|
||||||
return "<no image>"
|
return "<no image>"
|
||||||
}
|
}
|
||||||
if c.trunc {
|
if !c.trunc {
|
||||||
if trunc := TruncateID(c.c.ImageID); trunc == TruncateID(c.c.Image) {
|
return c.c.Image
|
||||||
return trunc
|
}
|
||||||
|
if trunc := TruncateID(c.c.ImageID); trunc == TruncateID(c.c.Image) {
|
||||||
|
return trunc
|
||||||
|
}
|
||||||
|
ref, err := reference.ParseNormalizedNamed(c.c.Image)
|
||||||
|
if err != nil {
|
||||||
|
return c.c.Image
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := ref.(reference.Digested); ok {
|
||||||
|
// strip the digest, but preserve the tag (if any)
|
||||||
|
var tag string
|
||||||
|
if t, ok := ref.(reference.Tagged); ok {
|
||||||
|
tag = t.Tag()
|
||||||
}
|
}
|
||||||
// truncate digest if no-trunc option was not selected
|
ref = reference.TrimNamed(ref)
|
||||||
ref, err := reference.ParseNormalizedNamed(c.c.Image)
|
if tag != "" {
|
||||||
if err == nil {
|
if out, err := reference.WithTag(ref, tag); err == nil {
|
||||||
if nt, ok := ref.(reference.NamedTagged); ok {
|
ref = out
|
||||||
// case for when a tag is provided
|
|
||||||
if namedTagged, err := reference.WithTag(reference.TrimNamed(nt), nt.Tag()); err == nil {
|
|
||||||
return reference.FamiliarString(namedTagged)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// case for when a tag is not provided
|
|
||||||
named := reference.TrimNamed(ref)
|
|
||||||
return reference.FamiliarString(named)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.c.Image
|
// Format as "familiar" name with "docker.io[/library]" trimmed.
|
||||||
|
return reference.FamiliarString(ref)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Command returns's the container's command. If the trunc option is set, the
|
// Command returns's the container's command. If the trunc option is set, the
|
||||||
@ -241,7 +247,7 @@ func (c *ContainerContext) Ports() string {
|
|||||||
// State returns the container's current state (e.g. "running" or "paused").
|
// State returns the container's current state (e.g. "running" or "paused").
|
||||||
// Refer to [container.ContainerState] for possible states.
|
// Refer to [container.ContainerState] for possible states.
|
||||||
func (c *ContainerContext) State() string {
|
func (c *ContainerContext) State() string {
|
||||||
return c.c.State
|
return string(c.c.State)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Status returns the container's status in a human readable form (for example,
|
// Status returns the container's status in a human readable form (for example,
|
||||||
@ -338,7 +344,7 @@ func (c *ContainerContext) Networks() string {
|
|||||||
// DisplayablePorts returns formatted string representing open ports of container
|
// DisplayablePorts returns formatted string representing open ports of container
|
||||||
// e.g. "0.0.0.0:80->9090/tcp, 9988/tcp"
|
// e.g. "0.0.0.0:80->9090/tcp, 9988/tcp"
|
||||||
// it's used by command 'docker ps'
|
// it's used by command 'docker ps'
|
||||||
func DisplayablePorts(ports []container.Port) string {
|
func DisplayablePorts(ports []container.PortSummary) string {
|
||||||
type portGroup struct {
|
type portGroup struct {
|
||||||
first uint16
|
first uint16
|
||||||
last uint16
|
last uint16
|
||||||
@ -354,13 +360,13 @@ func DisplayablePorts(ports []container.Port) string {
|
|||||||
for _, port := range ports {
|
for _, port := range ports {
|
||||||
current := port.PrivatePort
|
current := port.PrivatePort
|
||||||
portKey := port.Type
|
portKey := port.Type
|
||||||
if port.IP != "" {
|
if port.IP.IsValid() {
|
||||||
if port.PublicPort != current {
|
if port.PublicPort != current {
|
||||||
hAddrPort := net.JoinHostPort(port.IP, strconv.Itoa(int(port.PublicPort)))
|
hAddrPort := net.JoinHostPort(port.IP.String(), strconv.Itoa(int(port.PublicPort)))
|
||||||
hostMappings = append(hostMappings, fmt.Sprintf("%s->%d/%s", hAddrPort, port.PrivatePort, port.Type))
|
hostMappings = append(hostMappings, fmt.Sprintf("%s->%d/%s", hAddrPort, port.PrivatePort, port.Type))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
portKey = port.IP + "/" + port.Type
|
portKey = port.IP.String() + "/" + port.Type
|
||||||
}
|
}
|
||||||
group := groupMap[portKey]
|
group := groupMap[portKey]
|
||||||
|
|
||||||
@ -404,13 +410,13 @@ func formGroup(key string, start, last uint16) string {
|
|||||||
return group + "/" + groupType
|
return group + "/" + groupType
|
||||||
}
|
}
|
||||||
|
|
||||||
func comparePorts(i, j container.Port) bool {
|
func comparePorts(i, j container.PortSummary) bool {
|
||||||
if i.PrivatePort != j.PrivatePort {
|
if i.PrivatePort != j.PrivatePort {
|
||||||
return i.PrivatePort < j.PrivatePort
|
return i.PrivatePort < j.PrivatePort
|
||||||
}
|
}
|
||||||
|
|
||||||
if i.IP != j.IP {
|
if i.IP != j.IP {
|
||||||
return i.IP < j.IP
|
return i.IP.String() < j.IP.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
if i.PublicPort != j.PublicPort {
|
if i.PublicPort != j.PublicPort {
|
||||||
|
|||||||
2
vendor/github.com/docker/cli/cli/command/formatter/custom.go
generated
vendored
2
vendor/github.com/docker/cli/cli/command/formatter/custom.go
generated
vendored
@ -1,5 +1,5 @@
|
|||||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||||
//go:build go1.23
|
//go:build go1.24
|
||||||
|
|
||||||
package formatter
|
package formatter
|
||||||
|
|
||||||
|
|||||||
253
vendor/github.com/docker/cli/cli/command/formatter/disk_usage.go
generated
vendored
253
vendor/github.com/docker/cli/cli/command/formatter/disk_usage.go
generated
vendored
@ -7,19 +7,20 @@ import (
|
|||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
"github.com/distribution/reference"
|
"github.com/distribution/reference"
|
||||||
"github.com/docker/docker/api/types/build"
|
|
||||||
"github.com/docker/docker/api/types/container"
|
|
||||||
"github.com/docker/docker/api/types/image"
|
|
||||||
"github.com/docker/docker/api/types/volume"
|
|
||||||
"github.com/docker/go-units"
|
"github.com/docker/go-units"
|
||||||
|
"github.com/moby/moby/api/types/build"
|
||||||
|
"github.com/moby/moby/api/types/container"
|
||||||
|
"github.com/moby/moby/api/types/image"
|
||||||
|
"github.com/moby/moby/api/types/volume"
|
||||||
|
"github.com/moby/moby/client"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
defaultDiskUsageImageTableFormat = "table {{.Repository}}\t{{.Tag}}\t{{.ID}}\t{{.CreatedSince}}\t{{.Size}}\t{{.SharedSize}}\t{{.UniqueSize}}\t{{.Containers}}"
|
defaultDiskUsageImageTableFormat Format = "table {{.Repository}}\t{{.Tag}}\t{{.ID}}\t{{.CreatedSince}}\t{{.Size}}\t{{.SharedSize}}\t{{.UniqueSize}}\t{{.Containers}}"
|
||||||
defaultDiskUsageContainerTableFormat = "table {{.ID}}\t{{.Image}}\t{{.Command}}\t{{.LocalVolumes}}\t{{.Size}}\t{{.RunningFor}}\t{{.Status}}\t{{.Names}}"
|
defaultDiskUsageContainerTableFormat Format = "table {{.ID}}\t{{.Image}}\t{{.Command}}\t{{.LocalVolumes}}\t{{.Size}}\t{{.RunningFor}}\t{{.Status}}\t{{.Names}}"
|
||||||
defaultDiskUsageVolumeTableFormat = "table {{.Name}}\t{{.Links}}\t{{.Size}}"
|
defaultDiskUsageVolumeTableFormat Format = "table {{.Name}}\t{{.Links}}\t{{.Size}}"
|
||||||
defaultDiskUsageBuildCacheTableFormat = "table {{.ID}}\t{{.CacheType}}\t{{.Size}}\t{{.CreatedSince}}\t{{.LastUsedSince}}\t{{.UsageCount}}\t{{.Shared}}"
|
defaultDiskUsageBuildCacheTableFormat Format = "table {{.ID}}\t{{.CacheType}}\t{{.Size}}\t{{.CreatedSince}}\t{{.LastUsedSince}}\t{{.UsageCount}}\t{{.Shared}}"
|
||||||
defaultDiskUsageTableFormat = "table {{.Type}}\t{{.TotalCount}}\t{{.Active}}\t{{.Size}}\t{{.Reclaimable}}"
|
defaultDiskUsageTableFormat Format = "table {{.Type}}\t{{.TotalCount}}\t{{.Active}}\t{{.Size}}\t{{.Reclaimable}}"
|
||||||
|
|
||||||
typeHeader = "TYPE"
|
typeHeader = "TYPE"
|
||||||
totalHeader = "TOTAL"
|
totalHeader = "TOTAL"
|
||||||
@ -33,19 +34,18 @@ const (
|
|||||||
// DiskUsageContext contains disk usage specific information required by the formatter, encapsulate a Context struct.
|
// DiskUsageContext contains disk usage specific information required by the formatter, encapsulate a Context struct.
|
||||||
type DiskUsageContext struct {
|
type DiskUsageContext struct {
|
||||||
Context
|
Context
|
||||||
Verbose bool
|
Verbose bool
|
||||||
LayersSize int64
|
|
||||||
Images []*image.Summary
|
ImageDiskUsage client.ImagesDiskUsage
|
||||||
Containers []*container.Summary
|
BuildCacheDiskUsage client.BuildCacheDiskUsage
|
||||||
Volumes []*volume.Volume
|
ContainerDiskUsage client.ContainersDiskUsage
|
||||||
BuildCache []*build.CacheRecord
|
VolumeDiskUsage client.VolumesDiskUsage
|
||||||
BuilderSize int64
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctx *DiskUsageContext) startSubsection(format string) (*template.Template, error) {
|
func (ctx *DiskUsageContext) startSubsection(format Format) (*template.Template, error) {
|
||||||
ctx.buffer = &bytes.Buffer{}
|
ctx.buffer = &bytes.Buffer{}
|
||||||
ctx.header = ""
|
ctx.header = ""
|
||||||
ctx.Format = Format(format)
|
ctx.Format = format
|
||||||
ctx.preFormat()
|
ctx.preFormat()
|
||||||
|
|
||||||
return ctx.parseFormat()
|
return ctx.parseFormat()
|
||||||
@ -69,7 +69,7 @@ func NewDiskUsageFormat(source string, verbose bool) Format {
|
|||||||
{{end -}}`
|
{{end -}}`
|
||||||
return format
|
return format
|
||||||
case !verbose && source == TableFormatKey:
|
case !verbose && source == TableFormatKey:
|
||||||
return Format(defaultDiskUsageTableFormat)
|
return defaultDiskUsageTableFormat
|
||||||
case !verbose && source == RawFormatKey:
|
case !verbose && source == RawFormatKey:
|
||||||
format := `type: {{.Type}}
|
format := `type: {{.Type}}
|
||||||
total: {{.TotalCount}}
|
total: {{.TotalCount}}
|
||||||
@ -96,35 +96,49 @@ func (ctx *DiskUsageContext) Write() (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
err = ctx.contextFormat(tmpl, &diskUsageImagesContext{
|
err = ctx.contextFormat(tmpl, &diskUsageImagesContext{
|
||||||
totalSize: ctx.LayersSize,
|
totalCount: ctx.ImageDiskUsage.TotalCount,
|
||||||
images: ctx.Images,
|
activeCount: ctx.ImageDiskUsage.ActiveCount,
|
||||||
|
totalSize: ctx.ImageDiskUsage.TotalSize,
|
||||||
|
reclaimable: ctx.ImageDiskUsage.Reclaimable,
|
||||||
|
images: ctx.ImageDiskUsage.Items,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = ctx.contextFormat(tmpl, &diskUsageContainersContext{
|
err = ctx.contextFormat(tmpl, &diskUsageContainersContext{
|
||||||
containers: ctx.Containers,
|
totalCount: ctx.ContainerDiskUsage.TotalCount,
|
||||||
|
activeCount: ctx.ContainerDiskUsage.ActiveCount,
|
||||||
|
totalSize: ctx.ContainerDiskUsage.TotalSize,
|
||||||
|
reclaimable: ctx.ContainerDiskUsage.Reclaimable,
|
||||||
|
containers: ctx.ContainerDiskUsage.Items,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = ctx.contextFormat(tmpl, &diskUsageVolumesContext{
|
err = ctx.contextFormat(tmpl, &diskUsageVolumesContext{
|
||||||
volumes: ctx.Volumes,
|
totalCount: ctx.VolumeDiskUsage.TotalCount,
|
||||||
|
activeCount: ctx.VolumeDiskUsage.ActiveCount,
|
||||||
|
totalSize: ctx.VolumeDiskUsage.TotalSize,
|
||||||
|
reclaimable: ctx.VolumeDiskUsage.Reclaimable,
|
||||||
|
volumes: ctx.VolumeDiskUsage.Items,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = ctx.contextFormat(tmpl, &diskUsageBuilderContext{
|
err = ctx.contextFormat(tmpl, &diskUsageBuilderContext{
|
||||||
builderSize: ctx.BuilderSize,
|
totalCount: ctx.BuildCacheDiskUsage.TotalCount,
|
||||||
buildCache: ctx.BuildCache,
|
activeCount: ctx.BuildCacheDiskUsage.ActiveCount,
|
||||||
|
builderSize: ctx.BuildCacheDiskUsage.TotalSize,
|
||||||
|
reclaimable: ctx.BuildCacheDiskUsage.Reclaimable,
|
||||||
|
buildCache: ctx.BuildCacheDiskUsage.Items,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
diskUsageContainersCtx := diskUsageContainersContext{containers: []*container.Summary{}}
|
diskUsageContainersCtx := diskUsageContainersContext{containers: []container.Summary{}}
|
||||||
diskUsageContainersCtx.Header = SubHeaderContext{
|
diskUsageContainersCtx.Header = SubHeaderContext{
|
||||||
"Type": typeHeader,
|
"Type": typeHeader,
|
||||||
"TotalCount": totalHeader,
|
"TotalCount": totalHeader,
|
||||||
@ -146,18 +160,18 @@ type diskUsageContext struct {
|
|||||||
|
|
||||||
func (ctx *DiskUsageContext) verboseWrite() error {
|
func (ctx *DiskUsageContext) verboseWrite() error {
|
||||||
duc := &diskUsageContext{
|
duc := &diskUsageContext{
|
||||||
Images: make([]*imageContext, 0, len(ctx.Images)),
|
Images: make([]*imageContext, 0, len(ctx.ImageDiskUsage.Items)),
|
||||||
Containers: make([]*ContainerContext, 0, len(ctx.Containers)),
|
Containers: make([]*ContainerContext, 0, len(ctx.ContainerDiskUsage.Items)),
|
||||||
Volumes: make([]*volumeContext, 0, len(ctx.Volumes)),
|
Volumes: make([]*volumeContext, 0, len(ctx.VolumeDiskUsage.Items)),
|
||||||
BuildCache: make([]*buildCacheContext, 0, len(ctx.BuildCache)),
|
BuildCache: make([]*buildCacheContext, 0, len(ctx.BuildCacheDiskUsage.Items)),
|
||||||
}
|
}
|
||||||
trunc := ctx.Format.IsTable()
|
trunc := ctx.Format.IsTable()
|
||||||
|
|
||||||
// First images
|
// First images
|
||||||
for _, i := range ctx.Images {
|
for _, i := range ctx.ImageDiskUsage.Items {
|
||||||
repo := "<none>"
|
repo := "<none>"
|
||||||
tag := "<none>"
|
tag := "<none>"
|
||||||
if len(i.RepoTags) > 0 && !isDangling(*i) {
|
if len(i.RepoTags) > 0 && !isDangling(i) {
|
||||||
// Only show the first tag
|
// Only show the first tag
|
||||||
ref, err := reference.ParseNormalizedNamed(i.RepoTags[0])
|
ref, err := reference.ParseNormalizedNamed(i.RepoTags[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -173,25 +187,25 @@ func (ctx *DiskUsageContext) verboseWrite() error {
|
|||||||
repo: repo,
|
repo: repo,
|
||||||
tag: tag,
|
tag: tag,
|
||||||
trunc: trunc,
|
trunc: trunc,
|
||||||
i: *i,
|
i: i,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now containers
|
// Now containers
|
||||||
for _, c := range ctx.Containers {
|
for _, c := range ctx.ContainerDiskUsage.Items {
|
||||||
// Don't display the virtual size
|
// Don't display the virtual size
|
||||||
c.SizeRootFs = 0
|
c.SizeRootFs = 0
|
||||||
duc.Containers = append(duc.Containers, &ContainerContext{trunc: trunc, c: *c})
|
duc.Containers = append(duc.Containers, &ContainerContext{trunc: trunc, c: c})
|
||||||
}
|
}
|
||||||
|
|
||||||
// And volumes
|
// And volumes
|
||||||
for _, v := range ctx.Volumes {
|
for _, v := range ctx.VolumeDiskUsage.Items {
|
||||||
duc.Volumes = append(duc.Volumes, &volumeContext{v: *v})
|
duc.Volumes = append(duc.Volumes, &volumeContext{v: v})
|
||||||
}
|
}
|
||||||
|
|
||||||
// And build cache
|
// And build cache
|
||||||
buildCacheSort(ctx.BuildCache)
|
buildCacheSort(ctx.BuildCacheDiskUsage.Items)
|
||||||
for _, v := range ctx.BuildCache {
|
for _, v := range ctx.BuildCacheDiskUsage.Items {
|
||||||
duc.BuildCache = append(duc.BuildCache, &buildCacheContext{v: v, trunc: trunc})
|
duc.BuildCache = append(duc.BuildCache, &buildCacheContext{v: v, trunc: trunc})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -212,7 +226,7 @@ func (ctx *DiskUsageContext) verboseWriteTable(duc *diskUsageContext) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
ctx.Output.Write([]byte("Images space usage:\n\n"))
|
_, _ = ctx.Output.Write([]byte("Images space usage:\n\n"))
|
||||||
for _, img := range duc.Images {
|
for _, img := range duc.Images {
|
||||||
if err := ctx.contextFormat(tmpl, img); err != nil {
|
if err := ctx.contextFormat(tmpl, img); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -224,7 +238,7 @@ func (ctx *DiskUsageContext) verboseWriteTable(duc *diskUsageContext) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
ctx.Output.Write([]byte("\nContainers space usage:\n\n"))
|
_, _ = ctx.Output.Write([]byte("\nContainers space usage:\n\n"))
|
||||||
for _, c := range duc.Containers {
|
for _, c := range duc.Containers {
|
||||||
if err := ctx.contextFormat(tmpl, c); err != nil {
|
if err := ctx.contextFormat(tmpl, c); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -248,7 +262,7 @@ func (ctx *DiskUsageContext) verboseWriteTable(duc *diskUsageContext) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, _ = fmt.Fprintf(ctx.Output, "\nBuild cache usage: %s\n\n", units.HumanSize(float64(ctx.BuilderSize)))
|
_, _ = fmt.Fprintf(ctx.Output, "\nBuild cache usage: %s\n\n", units.HumanSize(float64(ctx.BuildCacheDiskUsage.TotalSize)))
|
||||||
for _, v := range duc.BuildCache {
|
for _, v := range duc.BuildCache {
|
||||||
if err := ctx.contextFormat(tmpl, v); err != nil {
|
if err := ctx.contextFormat(tmpl, v); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -261,8 +275,11 @@ func (ctx *DiskUsageContext) verboseWriteTable(duc *diskUsageContext) error {
|
|||||||
|
|
||||||
type diskUsageImagesContext struct {
|
type diskUsageImagesContext struct {
|
||||||
HeaderContext
|
HeaderContext
|
||||||
totalSize int64
|
totalSize int64
|
||||||
images []*image.Summary
|
reclaimable int64
|
||||||
|
totalCount int64
|
||||||
|
activeCount int64
|
||||||
|
images []image.Summary
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *diskUsageImagesContext) MarshalJSON() ([]byte, error) {
|
func (c *diskUsageImagesContext) MarshalJSON() ([]byte, error) {
|
||||||
@ -274,18 +291,11 @@ func (*diskUsageImagesContext) Type() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *diskUsageImagesContext) TotalCount() string {
|
func (c *diskUsageImagesContext) TotalCount() string {
|
||||||
return strconv.Itoa(len(c.images))
|
return strconv.FormatInt(c.totalCount, 10)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *diskUsageImagesContext) Active() string {
|
func (c *diskUsageImagesContext) Active() string {
|
||||||
used := 0
|
return strconv.FormatInt(c.activeCount, 10)
|
||||||
for _, i := range c.images {
|
|
||||||
if i.Containers > 0 {
|
|
||||||
used++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return strconv.Itoa(used)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *diskUsageImagesContext) Size() string {
|
func (c *diskUsageImagesContext) Size() string {
|
||||||
@ -293,27 +303,19 @@ func (c *diskUsageImagesContext) Size() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *diskUsageImagesContext) Reclaimable() string {
|
func (c *diskUsageImagesContext) Reclaimable() string {
|
||||||
var used int64
|
|
||||||
|
|
||||||
for _, i := range c.images {
|
|
||||||
if i.Containers != 0 {
|
|
||||||
if i.Size == -1 || i.SharedSize == -1 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
used += i.Size - i.SharedSize
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
reclaimable := c.totalSize - used
|
|
||||||
if c.totalSize > 0 {
|
if c.totalSize > 0 {
|
||||||
return fmt.Sprintf("%s (%v%%)", units.HumanSize(float64(reclaimable)), (reclaimable*100)/c.totalSize)
|
return fmt.Sprintf("%s (%v%%)", units.HumanSize(float64(c.reclaimable)), (c.reclaimable*100)/c.totalSize)
|
||||||
}
|
}
|
||||||
return units.HumanSize(float64(reclaimable))
|
return units.HumanSize(float64(c.reclaimable))
|
||||||
}
|
}
|
||||||
|
|
||||||
type diskUsageContainersContext struct {
|
type diskUsageContainersContext struct {
|
||||||
HeaderContext
|
HeaderContext
|
||||||
containers []*container.Summary
|
totalCount int64
|
||||||
|
activeCount int64
|
||||||
|
totalSize int64
|
||||||
|
reclaimable int64
|
||||||
|
containers []container.Summary
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *diskUsageContainersContext) MarshalJSON() ([]byte, error) {
|
func (c *diskUsageContainersContext) MarshalJSON() ([]byte, error) {
|
||||||
@ -325,62 +327,32 @@ func (*diskUsageContainersContext) Type() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *diskUsageContainersContext) TotalCount() string {
|
func (c *diskUsageContainersContext) TotalCount() string {
|
||||||
return strconv.Itoa(len(c.containers))
|
return strconv.FormatInt(c.totalCount, 10)
|
||||||
}
|
|
||||||
|
|
||||||
func (*diskUsageContainersContext) isActive(ctr container.Summary) bool {
|
|
||||||
switch ctr.State {
|
|
||||||
case container.StateRunning, container.StatePaused, container.StateRestarting:
|
|
||||||
return true
|
|
||||||
case container.StateCreated, container.StateRemoving, container.StateExited, container.StateDead:
|
|
||||||
return false
|
|
||||||
default:
|
|
||||||
// Unknown state (should never happen).
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *diskUsageContainersContext) Active() string {
|
func (c *diskUsageContainersContext) Active() string {
|
||||||
used := 0
|
return strconv.FormatInt(c.activeCount, 10)
|
||||||
for _, ctr := range c.containers {
|
|
||||||
if c.isActive(*ctr) {
|
|
||||||
used++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return strconv.Itoa(used)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *diskUsageContainersContext) Size() string {
|
func (c *diskUsageContainersContext) Size() string {
|
||||||
var size int64
|
return units.HumanSize(float64(c.totalSize))
|
||||||
|
|
||||||
for _, ctr := range c.containers {
|
|
||||||
size += ctr.SizeRw
|
|
||||||
}
|
|
||||||
|
|
||||||
return units.HumanSize(float64(size))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *diskUsageContainersContext) Reclaimable() string {
|
func (c *diskUsageContainersContext) Reclaimable() string {
|
||||||
var reclaimable, totalSize int64
|
if c.totalSize > 0 {
|
||||||
|
return fmt.Sprintf("%s (%v%%)", units.HumanSize(float64(c.reclaimable)), (c.reclaimable*100)/c.totalSize)
|
||||||
for _, ctr := range c.containers {
|
|
||||||
if !c.isActive(*ctr) {
|
|
||||||
reclaimable += ctr.SizeRw
|
|
||||||
}
|
|
||||||
totalSize += ctr.SizeRw
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if totalSize > 0 {
|
return units.HumanSize(float64(c.reclaimable))
|
||||||
return fmt.Sprintf("%s (%v%%)", units.HumanSize(float64(reclaimable)), (reclaimable*100)/totalSize)
|
|
||||||
}
|
|
||||||
|
|
||||||
return units.HumanSize(float64(reclaimable))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type diskUsageVolumesContext struct {
|
type diskUsageVolumesContext struct {
|
||||||
HeaderContext
|
HeaderContext
|
||||||
volumes []*volume.Volume
|
totalCount int64
|
||||||
|
activeCount int64
|
||||||
|
totalSize int64
|
||||||
|
reclaimable int64
|
||||||
|
volumes []volume.Volume
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *diskUsageVolumesContext) MarshalJSON() ([]byte, error) {
|
func (c *diskUsageVolumesContext) MarshalJSON() ([]byte, error) {
|
||||||
@ -392,56 +364,32 @@ func (*diskUsageVolumesContext) Type() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *diskUsageVolumesContext) TotalCount() string {
|
func (c *diskUsageVolumesContext) TotalCount() string {
|
||||||
return strconv.Itoa(len(c.volumes))
|
return strconv.FormatInt(c.totalCount, 10)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *diskUsageVolumesContext) Active() string {
|
func (c *diskUsageVolumesContext) Active() string {
|
||||||
used := 0
|
return strconv.FormatInt(c.activeCount, 10)
|
||||||
for _, v := range c.volumes {
|
|
||||||
if v.UsageData.RefCount > 0 {
|
|
||||||
used++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return strconv.Itoa(used)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *diskUsageVolumesContext) Size() string {
|
func (c *diskUsageVolumesContext) Size() string {
|
||||||
var size int64
|
return units.HumanSize(float64(c.totalSize))
|
||||||
|
|
||||||
for _, v := range c.volumes {
|
|
||||||
if v.UsageData.Size != -1 {
|
|
||||||
size += v.UsageData.Size
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return units.HumanSize(float64(size))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *diskUsageVolumesContext) Reclaimable() string {
|
func (c *diskUsageVolumesContext) Reclaimable() string {
|
||||||
var reclaimable int64
|
if c.totalSize > 0 {
|
||||||
var totalSize int64
|
return fmt.Sprintf("%s (%v%%)", units.HumanSize(float64(c.reclaimable)), (c.reclaimable*100)/c.totalSize)
|
||||||
|
|
||||||
for _, v := range c.volumes {
|
|
||||||
if v.UsageData.Size != -1 {
|
|
||||||
if v.UsageData.RefCount == 0 {
|
|
||||||
reclaimable += v.UsageData.Size
|
|
||||||
}
|
|
||||||
totalSize += v.UsageData.Size
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if totalSize > 0 {
|
return units.HumanSize(float64(c.reclaimable))
|
||||||
return fmt.Sprintf("%s (%v%%)", units.HumanSize(float64(reclaimable)), (reclaimable*100)/totalSize)
|
|
||||||
}
|
|
||||||
|
|
||||||
return units.HumanSize(float64(reclaimable))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type diskUsageBuilderContext struct {
|
type diskUsageBuilderContext struct {
|
||||||
HeaderContext
|
HeaderContext
|
||||||
|
totalCount int64
|
||||||
|
activeCount int64
|
||||||
builderSize int64
|
builderSize int64
|
||||||
buildCache []*build.CacheRecord
|
reclaimable int64
|
||||||
|
buildCache []build.CacheRecord
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *diskUsageBuilderContext) MarshalJSON() ([]byte, error) {
|
func (c *diskUsageBuilderContext) MarshalJSON() ([]byte, error) {
|
||||||
@ -453,17 +401,11 @@ func (*diskUsageBuilderContext) Type() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *diskUsageBuilderContext) TotalCount() string {
|
func (c *diskUsageBuilderContext) TotalCount() string {
|
||||||
return strconv.Itoa(len(c.buildCache))
|
return strconv.FormatInt(c.totalCount, 10)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *diskUsageBuilderContext) Active() string {
|
func (c *diskUsageBuilderContext) Active() string {
|
||||||
numActive := 0
|
return strconv.FormatInt(c.activeCount, 10)
|
||||||
for _, bc := range c.buildCache {
|
|
||||||
if bc.InUse {
|
|
||||||
numActive++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return strconv.Itoa(numActive)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *diskUsageBuilderContext) Size() string {
|
func (c *diskUsageBuilderContext) Size() string {
|
||||||
@ -471,12 +413,5 @@ func (c *diskUsageBuilderContext) Size() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *diskUsageBuilderContext) Reclaimable() string {
|
func (c *diskUsageBuilderContext) Reclaimable() string {
|
||||||
var inUseBytes int64
|
return units.HumanSize(float64(c.reclaimable))
|
||||||
for _, bc := range c.buildCache {
|
|
||||||
if bc.InUse && !bc.Shared {
|
|
||||||
inUseBytes += bc.Size
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return units.HumanSize(float64(c.builderSize - inUseBytes))
|
|
||||||
}
|
}
|
||||||
|
|||||||
18
vendor/github.com/docker/cli/cli/command/formatter/displayutils.go
generated
vendored
18
vendor/github.com/docker/cli/cli/command/formatter/displayutils.go
generated
vendored
@ -1,5 +1,5 @@
|
|||||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||||
//go:build go1.23
|
//go:build go1.24
|
||||||
|
|
||||||
package formatter
|
package formatter
|
||||||
|
|
||||||
@ -8,6 +8,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
|
"github.com/moby/moby/client/pkg/stringid"
|
||||||
"golang.org/x/text/width"
|
"golang.org/x/text/width"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -27,23 +28,12 @@ func charWidth(r rune) int {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const shortLen = 12
|
|
||||||
|
|
||||||
// TruncateID returns a shorthand version of a string identifier for presentation,
|
// TruncateID returns a shorthand version of a string identifier for presentation,
|
||||||
// after trimming digest algorithm prefix (if any).
|
// after trimming digest algorithm prefix (if any).
|
||||||
//
|
//
|
||||||
// This function is a copy of [stringid.TruncateID] for presentation / formatting
|
// This function is a wrapper for [stringid.TruncateID] for convenience.
|
||||||
// purposes.
|
|
||||||
//
|
|
||||||
// [stringid.TruncateID]: https://github.com/moby/moby/blob/v28.3.2/pkg/stringid/stringid.go#L19
|
|
||||||
func TruncateID(id string) string {
|
func TruncateID(id string) string {
|
||||||
if i := strings.IndexRune(id, ':'); i >= 0 {
|
return stringid.TruncateID(id)
|
||||||
id = id[i+1:]
|
|
||||||
}
|
|
||||||
if len(id) > shortLen {
|
|
||||||
id = id[:shortLen]
|
|
||||||
}
|
|
||||||
return id
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ellipsis truncates a string to fit within maxDisplayWidth, and appends ellipsis (…).
|
// Ellipsis truncates a string to fit within maxDisplayWidth, and appends ellipsis (…).
|
||||||
|
|||||||
8
vendor/github.com/docker/cli/cli/command/formatter/formatter.go
generated
vendored
8
vendor/github.com/docker/cli/cli/command/formatter/formatter.go
generated
vendored
@ -1,17 +1,17 @@
|
|||||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||||
//go:build go1.23
|
//go:build go1.24
|
||||||
|
|
||||||
package formatter
|
package formatter
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
"github.com/docker/cli/cli/command/formatter/tabwriter"
|
"github.com/docker/cli/cli/command/formatter/tabwriter"
|
||||||
"github.com/docker/cli/templates"
|
"github.com/docker/cli/templates"
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Format keys used to specify certain kinds of output formats
|
// Format keys used to specify certain kinds of output formats
|
||||||
@ -76,7 +76,7 @@ func (c *Context) preFormat() {
|
|||||||
func (c *Context) parseFormat() (*template.Template, error) {
|
func (c *Context) parseFormat() (*template.Template, error) {
|
||||||
tmpl, err := templates.Parse(c.finalFormat)
|
tmpl, err := templates.Parse(c.finalFormat)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "template parsing error")
|
return nil, fmt.Errorf("template parsing error: %w", err)
|
||||||
}
|
}
|
||||||
return tmpl, nil
|
return tmpl, nil
|
||||||
}
|
}
|
||||||
@ -100,7 +100,7 @@ func (c *Context) postFormat(tmpl *template.Template, subContext SubContext) {
|
|||||||
|
|
||||||
func (c *Context) contextFormat(tmpl *template.Template, subContext SubContext) error {
|
func (c *Context) contextFormat(tmpl *template.Template, subContext SubContext) error {
|
||||||
if err := tmpl.Execute(c.buffer, subContext); err != nil {
|
if err := tmpl.Execute(c.buffer, subContext); err != nil {
|
||||||
return errors.Wrap(err, "template parsing error")
|
return fmt.Errorf("template parsing error: %w", err)
|
||||||
}
|
}
|
||||||
if c.Format.IsTable() && c.header != nil {
|
if c.Format.IsTable() && c.header != nil {
|
||||||
c.header = subContext.FullHeader()
|
c.header = subContext.FullHeader()
|
||||||
|
|||||||
12
vendor/github.com/docker/cli/cli/command/formatter/image.go
generated
vendored
12
vendor/github.com/docker/cli/cli/command/formatter/image.go
generated
vendored
@ -5,8 +5,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/distribution/reference"
|
"github.com/distribution/reference"
|
||||||
"github.com/docker/docker/api/types/image"
|
|
||||||
"github.com/docker/go-units"
|
"github.com/docker/go-units"
|
||||||
|
"github.com/moby/moby/api/types/image"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -202,7 +202,6 @@ func newImageContext() *imageContext {
|
|||||||
"CreatedAt": CreatedAtHeader,
|
"CreatedAt": CreatedAtHeader,
|
||||||
"Size": SizeHeader,
|
"Size": SizeHeader,
|
||||||
"Containers": containersHeader,
|
"Containers": containersHeader,
|
||||||
"VirtualSize": SizeHeader, // Deprecated: VirtualSize is deprecated, and equivalent to Size.
|
|
||||||
"SharedSize": sharedSizeHeader,
|
"SharedSize": sharedSizeHeader,
|
||||||
"UniqueSize": uniqueSizeHeader,
|
"UniqueSize": uniqueSizeHeader,
|
||||||
}
|
}
|
||||||
@ -257,15 +256,6 @@ func (c *imageContext) Containers() string {
|
|||||||
return strconv.FormatInt(c.i.Containers, 10)
|
return strconv.FormatInt(c.i.Containers, 10)
|
||||||
}
|
}
|
||||||
|
|
||||||
// VirtualSize shows the virtual size of the image and all of its parent
|
|
||||||
// images. Starting with docker 1.10, images are self-contained, and
|
|
||||||
// the VirtualSize is identical to Size.
|
|
||||||
//
|
|
||||||
// Deprecated: VirtualSize is deprecated, and equivalent to [imageContext.Size].
|
|
||||||
func (c *imageContext) VirtualSize() string {
|
|
||||||
return units.HumanSize(float64(c.i.Size))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *imageContext) SharedSize() string {
|
func (c *imageContext) SharedSize() string {
|
||||||
if c.i.SharedSize == -1 {
|
if c.i.SharedSize == -1 {
|
||||||
return "N/A"
|
return "N/A"
|
||||||
|
|||||||
14
vendor/github.com/docker/cli/cli/command/formatter/reflect.go
generated
vendored
14
vendor/github.com/docker/cli/cli/command/formatter/reflect.go
generated
vendored
@ -1,14 +1,14 @@
|
|||||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||||
//go:build go1.23
|
//go:build go1.24
|
||||||
|
|
||||||
package formatter
|
package formatter
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"unicode"
|
"unicode"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// MarshalJSON marshals x into json
|
// MarshalJSON marshals x into json
|
||||||
@ -25,14 +25,14 @@ func MarshalJSON(x any) ([]byte, error) {
|
|||||||
func marshalMap(x any) (map[string]any, error) {
|
func marshalMap(x any) (map[string]any, error) {
|
||||||
val := reflect.ValueOf(x)
|
val := reflect.ValueOf(x)
|
||||||
if val.Kind() != reflect.Ptr {
|
if val.Kind() != reflect.Ptr {
|
||||||
return nil, errors.Errorf("expected a pointer to a struct, got %v", val.Kind())
|
return nil, fmt.Errorf("expected a pointer to a struct, got %v", val.Kind())
|
||||||
}
|
}
|
||||||
if val.IsNil() {
|
if val.IsNil() {
|
||||||
return nil, errors.Errorf("expected a pointer to a struct, got nil pointer")
|
return nil, errors.New("expected a pointer to a struct, got nil pointer")
|
||||||
}
|
}
|
||||||
valElem := val.Elem()
|
valElem := val.Elem()
|
||||||
if valElem.Kind() != reflect.Struct {
|
if valElem.Kind() != reflect.Struct {
|
||||||
return nil, errors.Errorf("expected a pointer to a struct, got a pointer to %v", valElem.Kind())
|
return nil, fmt.Errorf("expected a pointer to a struct, got a pointer to %v", valElem.Kind())
|
||||||
}
|
}
|
||||||
typ := val.Type()
|
typ := val.Type()
|
||||||
m := make(map[string]any)
|
m := make(map[string]any)
|
||||||
@ -54,7 +54,7 @@ var unmarshallableNames = map[string]struct{}{"FullHeader": {}}
|
|||||||
// It returns ("", nil, nil) for valid but non-marshallable parameter. (e.g. "unexportedFunc()")
|
// It returns ("", nil, nil) for valid but non-marshallable parameter. (e.g. "unexportedFunc()")
|
||||||
func marshalForMethod(typ reflect.Method, val reflect.Value) (string, any, error) {
|
func marshalForMethod(typ reflect.Method, val reflect.Value) (string, any, error) {
|
||||||
if val.Kind() != reflect.Func {
|
if val.Kind() != reflect.Func {
|
||||||
return "", nil, errors.Errorf("expected func, got %v", val.Kind())
|
return "", nil, fmt.Errorf("expected func, got %v", val.Kind())
|
||||||
}
|
}
|
||||||
name, numIn, numOut := typ.Name, val.Type().NumIn(), val.Type().NumOut()
|
name, numIn, numOut := typ.Name, val.Type().NumIn(), val.Type().NumOut()
|
||||||
_, blackListed := unmarshallableNames[name]
|
_, blackListed := unmarshallableNames[name]
|
||||||
|
|||||||
6
vendor/github.com/docker/cli/cli/command/formatter/volume.go
generated
vendored
6
vendor/github.com/docker/cli/cli/command/formatter/volume.go
generated
vendored
@ -5,8 +5,8 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types/volume"
|
|
||||||
"github.com/docker/go-units"
|
"github.com/docker/go-units"
|
||||||
|
"github.com/moby/moby/api/types/volume"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -40,10 +40,10 @@ func NewVolumeFormat(source string, quiet bool) Format {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// VolumeWrite writes formatted volumes using the Context
|
// VolumeWrite writes formatted volumes using the Context
|
||||||
func VolumeWrite(ctx Context, volumes []*volume.Volume) error {
|
func VolumeWrite(ctx Context, volumes []volume.Volume) error {
|
||||||
render := func(format func(subContext SubContext) error) error {
|
render := func(format func(subContext SubContext) error) error {
|
||||||
for _, vol := range volumes {
|
for _, vol := range volumes {
|
||||||
if err := format(&volumeContext{v: *vol}); err != nil {
|
if err := format(&volumeContext{v: vol}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
82
vendor/github.com/docker/cli/cli/command/registry.go
generated
vendored
82
vendor/github.com/docker/cli/cli/command/registry.go
generated
vendored
@ -2,6 +2,7 @@ package command
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
@ -15,9 +16,9 @@ import (
|
|||||||
"github.com/docker/cli/cli/streams"
|
"github.com/docker/cli/cli/streams"
|
||||||
"github.com/docker/cli/internal/prompt"
|
"github.com/docker/cli/internal/prompt"
|
||||||
"github.com/docker/cli/internal/tui"
|
"github.com/docker/cli/internal/tui"
|
||||||
registrytypes "github.com/docker/docker/api/types/registry"
|
"github.com/moby/moby/api/pkg/authconfig"
|
||||||
|
registrytypes "github.com/moby/moby/api/types/registry"
|
||||||
"github.com/morikuni/aec"
|
"github.com/morikuni/aec"
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -34,42 +35,11 @@ const (
|
|||||||
// [registry.IndexServer]: https://pkg.go.dev/github.com/docker/docker@v28.3.3+incompatible/registry#IndexServer
|
// [registry.IndexServer]: https://pkg.go.dev/github.com/docker/docker@v28.3.3+incompatible/registry#IndexServer
|
||||||
const authConfigKey = "https://index.docker.io/v1/"
|
const authConfigKey = "https://index.docker.io/v1/"
|
||||||
|
|
||||||
// RegistryAuthenticationPrivilegedFunc returns a RequestPrivilegeFunc from the specified registry index info
|
|
||||||
// for the given command to prompt the user for username and password.
|
|
||||||
//
|
|
||||||
// Deprecated: this function is no longer used and will be removed in the next release.
|
|
||||||
func RegistryAuthenticationPrivilegedFunc(cli Cli, index *registrytypes.IndexInfo, cmdName string) registrytypes.RequestAuthConfig {
|
|
||||||
configKey := getAuthConfigKey(index.Name)
|
|
||||||
isDefaultRegistry := configKey == authConfigKey || index.Official
|
|
||||||
return func(ctx context.Context) (string, error) {
|
|
||||||
_, _ = fmt.Fprintf(cli.Out(), "\nLogin prior to %s:\n", cmdName)
|
|
||||||
authConfig, err := GetDefaultAuthConfig(cli.ConfigFile(), true, configKey, isDefaultRegistry)
|
|
||||||
if err != nil {
|
|
||||||
_, _ = fmt.Fprintf(cli.Err(), "Unable to retrieve stored credentials for %s, error: %s.\n", configKey, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return "", ctx.Err()
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
authConfig, err = PromptUserForCredentials(ctx, cli, "", "", authConfig.Username, configKey)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return registrytypes.EncodeAuthConfig(authConfig)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResolveAuthConfig returns auth-config for the given registry from the
|
// ResolveAuthConfig returns auth-config for the given registry from the
|
||||||
// credential-store. It returns an empty AuthConfig if no credentials were
|
// credential-store. It returns an empty AuthConfig if no credentials were
|
||||||
// found.
|
// found.
|
||||||
//
|
//
|
||||||
// It is similar to [registry.ResolveAuthConfig], but uses the credentials-
|
// Deprecated: this function is no longer used, and will be removed in the next release.
|
||||||
// store, instead of looking up credentials from a map.
|
|
||||||
//
|
|
||||||
// [registry.ResolveAuthConfig]: https://pkg.go.dev/github.com/docker/docker@v28.3.3+incompatible/registry#ResolveAuthConfig
|
|
||||||
func ResolveAuthConfig(cfg *configfile.ConfigFile, index *registrytypes.IndexInfo) registrytypes.AuthConfig {
|
func ResolveAuthConfig(cfg *configfile.ConfigFile, index *registrytypes.IndexInfo) registrytypes.AuthConfig {
|
||||||
configKey := index.Name
|
configKey := index.Name
|
||||||
if index.Official {
|
if index.Official {
|
||||||
@ -77,7 +47,16 @@ func ResolveAuthConfig(cfg *configfile.ConfigFile, index *registrytypes.IndexInf
|
|||||||
}
|
}
|
||||||
|
|
||||||
a, _ := cfg.GetAuthConfig(configKey)
|
a, _ := cfg.GetAuthConfig(configKey)
|
||||||
return registrytypes.AuthConfig(a)
|
return registrytypes.AuthConfig{
|
||||||
|
Username: a.Username,
|
||||||
|
Password: a.Password,
|
||||||
|
ServerAddress: a.ServerAddress,
|
||||||
|
|
||||||
|
// TODO(thaJeztah): Are these expected to be included?
|
||||||
|
Auth: a.Auth,
|
||||||
|
IdentityToken: a.IdentityToken,
|
||||||
|
RegistryToken: a.RegistryToken,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDefaultAuthConfig gets the default auth config given a serverAddress
|
// GetDefaultAuthConfig gets the default auth config given a serverAddress
|
||||||
@ -86,19 +65,27 @@ func GetDefaultAuthConfig(cfg *configfile.ConfigFile, checkCredStore bool, serve
|
|||||||
if !isDefaultRegistry {
|
if !isDefaultRegistry {
|
||||||
serverAddress = credentials.ConvertToHostname(serverAddress)
|
serverAddress = credentials.ConvertToHostname(serverAddress)
|
||||||
}
|
}
|
||||||
authconfig := configtypes.AuthConfig{}
|
authCfg := configtypes.AuthConfig{}
|
||||||
var err error
|
var err error
|
||||||
if checkCredStore {
|
if checkCredStore {
|
||||||
authconfig, err = cfg.GetAuthConfig(serverAddress)
|
authCfg, err = cfg.GetAuthConfig(serverAddress)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return registrytypes.AuthConfig{
|
return registrytypes.AuthConfig{
|
||||||
ServerAddress: serverAddress,
|
ServerAddress: serverAddress,
|
||||||
}, err
|
}, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
authconfig.ServerAddress = serverAddress
|
|
||||||
authconfig.IdentityToken = ""
|
return registrytypes.AuthConfig{
|
||||||
return registrytypes.AuthConfig(authconfig), nil
|
Username: authCfg.Username,
|
||||||
|
Password: authCfg.Password,
|
||||||
|
ServerAddress: serverAddress,
|
||||||
|
|
||||||
|
// TODO(thaJeztah): Are these expected to be included?
|
||||||
|
Auth: authCfg.Auth,
|
||||||
|
IdentityToken: "",
|
||||||
|
RegistryToken: authCfg.RegistryToken,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// PromptUserForCredentials handles the CLI prompt for the user to input
|
// PromptUserForCredentials handles the CLI prompt for the user to input
|
||||||
@ -153,7 +140,7 @@ func PromptUserForCredentials(ctx context.Context, cli Cli, argUser, argPassword
|
|||||||
argUser = defaultUsername
|
argUser = defaultUsername
|
||||||
}
|
}
|
||||||
if argUser == "" {
|
if argUser == "" {
|
||||||
return registrytypes.AuthConfig{}, errors.Errorf("Error: Non-null Username Required")
|
return registrytypes.AuthConfig{}, errors.New("error: username is required")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -185,7 +172,7 @@ func PromptUserForCredentials(ctx context.Context, cli Cli, argUser, argPassword
|
|||||||
}
|
}
|
||||||
_, _ = fmt.Fprintln(cli.Out())
|
_, _ = fmt.Fprintln(cli.Out())
|
||||||
if argPassword == "" {
|
if argPassword == "" {
|
||||||
return registrytypes.AuthConfig{}, errors.Errorf("Error: Password Required")
|
return registrytypes.AuthConfig{}, errors.New("error: password is required")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -213,7 +200,16 @@ func RetrieveAuthTokenFromImage(cfg *configfile.ConfigFile, image string) (strin
|
|||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
encodedAuth, err := registrytypes.EncodeAuthConfig(registrytypes.AuthConfig(authConfig))
|
encodedAuth, err := authconfig.Encode(registrytypes.AuthConfig{
|
||||||
|
Username: authConfig.Username,
|
||||||
|
Password: authConfig.Password,
|
||||||
|
ServerAddress: authConfig.ServerAddress,
|
||||||
|
|
||||||
|
// TODO(thaJeztah): Are these expected to be included?
|
||||||
|
Auth: authConfig.Auth,
|
||||||
|
IdentityToken: authConfig.IdentityToken,
|
||||||
|
RegistryToken: authConfig.RegistryToken,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|||||||
70
vendor/github.com/docker/cli/cli/command/service/progress/progress.go
generated
vendored
70
vendor/github.com/docker/cli/cli/command/service/progress/progress.go
generated
vendored
@ -12,11 +12,10 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/docker/cli/cli/command/formatter"
|
"github.com/docker/cli/cli/command/formatter"
|
||||||
"github.com/docker/docker/api/types/filters"
|
"github.com/moby/moby/api/types/swarm"
|
||||||
"github.com/docker/docker/api/types/swarm"
|
"github.com/moby/moby/client"
|
||||||
"github.com/docker/docker/client"
|
"github.com/moby/moby/client/pkg/progress"
|
||||||
"github.com/docker/docker/pkg/progress"
|
"github.com/moby/moby/client/pkg/streamformatter"
|
||||||
"github.com/docker/docker/pkg/streamformatter"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -88,24 +87,24 @@ func ServiceProgress(ctx context.Context, apiClient client.APIClient, serviceID
|
|||||||
)
|
)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
service, _, err := apiClient.ServiceInspectWithRaw(ctx, serviceID, swarm.ServiceInspectOptions{})
|
res, err := apiClient.ServiceInspect(ctx, serviceID, client.ServiceInspectOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if service.Spec.UpdateConfig != nil && service.Spec.UpdateConfig.Monitor != 0 {
|
if res.Service.Spec.UpdateConfig != nil && res.Service.Spec.UpdateConfig.Monitor != 0 {
|
||||||
monitor = service.Spec.UpdateConfig.Monitor
|
monitor = res.Service.Spec.UpdateConfig.Monitor
|
||||||
}
|
}
|
||||||
|
|
||||||
if updater == nil {
|
if updater == nil {
|
||||||
updater, err = initializeUpdater(service, progressOut)
|
updater, err = initializeUpdater(res.Service, progressOut)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if service.UpdateStatus != nil {
|
if res.Service.UpdateStatus != nil {
|
||||||
switch service.UpdateStatus.State {
|
switch res.Service.UpdateStatus.State {
|
||||||
case swarm.UpdateStateUpdating:
|
case swarm.UpdateStateUpdating:
|
||||||
rollback = false
|
rollback = false
|
||||||
case swarm.UpdateStateCompleted:
|
case swarm.UpdateStateCompleted:
|
||||||
@ -113,39 +112,38 @@ func ServiceProgress(ctx context.Context, apiClient client.APIClient, serviceID
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
case swarm.UpdateStatePaused:
|
case swarm.UpdateStatePaused:
|
||||||
return fmt.Errorf("service update paused: %s", service.UpdateStatus.Message)
|
return fmt.Errorf("service update paused: %s", res.Service.UpdateStatus.Message)
|
||||||
case swarm.UpdateStateRollbackStarted:
|
case swarm.UpdateStateRollbackStarted:
|
||||||
if !rollback && service.UpdateStatus.Message != "" {
|
if !rollback && res.Service.UpdateStatus.Message != "" {
|
||||||
progressOut.WriteProgress(progress.Progress{
|
progressOut.WriteProgress(progress.Progress{
|
||||||
ID: "rollback",
|
ID: "rollback",
|
||||||
Action: service.UpdateStatus.Message,
|
Action: res.Service.UpdateStatus.Message,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
rollback = true
|
rollback = true
|
||||||
case swarm.UpdateStateRollbackPaused:
|
case swarm.UpdateStateRollbackPaused:
|
||||||
return fmt.Errorf("service rollback paused: %s", service.UpdateStatus.Message)
|
return fmt.Errorf("service rollback paused: %s", res.Service.UpdateStatus.Message)
|
||||||
case swarm.UpdateStateRollbackCompleted:
|
case swarm.UpdateStateRollbackCompleted:
|
||||||
if !converged {
|
if !converged {
|
||||||
message = &progress.Progress{ID: "rollback", Message: service.UpdateStatus.Message}
|
message = &progress.Progress{ID: "rollback", Message: res.Service.UpdateStatus.Message}
|
||||||
}
|
}
|
||||||
rollback = true
|
rollback = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if converged && time.Since(convergedAt) >= monitor {
|
if converged && time.Since(convergedAt) >= monitor {
|
||||||
progressOut.WriteProgress(progress.Progress{
|
_ = progressOut.WriteProgress(progress.Progress{
|
||||||
ID: "verify",
|
ID: "verify",
|
||||||
Action: fmt.Sprintf("Service %s converged", serviceID),
|
Action: fmt.Sprintf("Service %s converged", serviceID),
|
||||||
})
|
})
|
||||||
if message != nil {
|
if message != nil {
|
||||||
progressOut.WriteProgress(*message)
|
_ = progressOut.WriteProgress(*message)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks, err := apiClient.TaskList(ctx, swarm.TaskListOptions{Filters: filters.NewArgs(
|
tasks, err := apiClient.TaskList(ctx, client.TaskListOptions{
|
||||||
filters.KeyValuePair{Key: "service", Value: service.ID},
|
Filters: make(client.Filters).Add("service", res.Service.ID).Add("_up-to-date", "true"),
|
||||||
filters.KeyValuePair{Key: "_up-to-date", Value: "true"},
|
})
|
||||||
)})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -155,7 +153,7 @@ func ServiceProgress(ctx context.Context, apiClient client.APIClient, serviceID
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
converged, err = updater.update(service, tasks, activeNodes, rollback)
|
converged, err = updater.update(res.Service, tasks.Items, activeNodes, rollback)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -167,7 +165,7 @@ func ServiceProgress(ctx context.Context, apiClient client.APIClient, serviceID
|
|||||||
// only job services have a non-nil job status, which means we can
|
// only job services have a non-nil job status, which means we can
|
||||||
// use the presence of this field to check if the service is a job
|
// use the presence of this field to check if the service is a job
|
||||||
// here.
|
// here.
|
||||||
if service.JobStatus != nil {
|
if res.Service.JobStatus != nil {
|
||||||
progress.Message(progressOut, "", "job complete")
|
progress.Message(progressOut, "", "job complete")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -177,7 +175,7 @@ func ServiceProgress(ctx context.Context, apiClient client.APIClient, serviceID
|
|||||||
}
|
}
|
||||||
wait := monitor - time.Since(convergedAt)
|
wait := monitor - time.Since(convergedAt)
|
||||||
if wait >= 0 {
|
if wait >= 0 {
|
||||||
progressOut.WriteProgress(progress.Progress{
|
_ = progressOut.WriteProgress(progress.Progress{
|
||||||
// Ideally this would have no ID, but
|
// Ideally this would have no ID, but
|
||||||
// the progress rendering code behaves
|
// the progress rendering code behaves
|
||||||
// poorly on an "action" with no ID. It
|
// poorly on an "action" with no ID. It
|
||||||
@ -192,7 +190,7 @@ func ServiceProgress(ctx context.Context, apiClient client.APIClient, serviceID
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if !convergedAt.IsZero() {
|
if !convergedAt.IsZero() {
|
||||||
progressOut.WriteProgress(progress.Progress{
|
_ = progressOut.WriteProgress(progress.Progress{
|
||||||
ID: "verify",
|
ID: "verify",
|
||||||
Action: "Detected task failure",
|
Action: "Detected task failure",
|
||||||
})
|
})
|
||||||
@ -216,13 +214,13 @@ func ServiceProgress(ctx context.Context, apiClient client.APIClient, serviceID
|
|||||||
//
|
//
|
||||||
// TODO(thaJeztah): this should really be a filter on [apiClient.NodeList] instead of being filtered on the client side.
|
// TODO(thaJeztah): this should really be a filter on [apiClient.NodeList] instead of being filtered on the client side.
|
||||||
func getActiveNodes(ctx context.Context, apiClient client.NodeAPIClient) (map[string]struct{}, error) {
|
func getActiveNodes(ctx context.Context, apiClient client.NodeAPIClient) (map[string]struct{}, error) {
|
||||||
nodes, err := apiClient.NodeList(ctx, swarm.NodeListOptions{})
|
res, err := apiClient.NodeList(ctx, client.NodeListOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
activeNodes := make(map[string]struct{})
|
activeNodes := make(map[string]struct{})
|
||||||
for _, n := range nodes {
|
for _, n := range res.Items {
|
||||||
if n.Status.State != swarm.NodeStateDown {
|
if n.Status.State != swarm.NodeStateDown {
|
||||||
activeNodes[n.ID] = struct{}{}
|
activeNodes[n.ID] = struct{}{}
|
||||||
}
|
}
|
||||||
@ -652,7 +650,7 @@ func (u *replicatedJobProgressUpdater) update(_ swarm.Service, tasks []swarm.Tas
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (u *replicatedJobProgressUpdater) writeOverallProgress(active, completed int) {
|
func (u *replicatedJobProgressUpdater) writeOverallProgress(active, completed int) {
|
||||||
u.progressOut.WriteProgress(progress.Progress{
|
_ = u.progressOut.WriteProgress(progress.Progress{
|
||||||
ID: "job progress",
|
ID: "job progress",
|
||||||
Action: fmt.Sprintf(
|
Action: fmt.Sprintf(
|
||||||
// * means "use the next positional arg to compute padding"
|
// * means "use the next positional arg to compute padding"
|
||||||
@ -669,7 +667,7 @@ func (u *replicatedJobProgressUpdater) writeOverallProgress(active, completed in
|
|||||||
actualDesired = u.concurrent
|
actualDesired = u.concurrent
|
||||||
}
|
}
|
||||||
|
|
||||||
u.progressOut.WriteProgress(progress.Progress{
|
_ = u.progressOut.WriteProgress(progress.Progress{
|
||||||
ID: "active tasks",
|
ID: "active tasks",
|
||||||
Action: fmt.Sprintf(
|
Action: fmt.Sprintf(
|
||||||
// [n] notation lets us select a specific argument, 1-indexed
|
// [n] notation lets us select a specific argument, 1-indexed
|
||||||
@ -692,14 +690,14 @@ func (u *replicatedJobProgressUpdater) writeTaskProgress(task swarm.Task) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if task.Status.Err != "" {
|
if task.Status.Err != "" {
|
||||||
u.progressOut.WriteProgress(progress.Progress{
|
_ = u.progressOut.WriteProgress(progress.Progress{
|
||||||
ID: fmt.Sprintf("%d/%d", task.Slot+1, u.total),
|
ID: fmt.Sprintf("%d/%d", task.Slot+1, u.total),
|
||||||
Action: truncError(task.Status.Err),
|
Action: truncError(task.Status.Err),
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
u.progressOut.WriteProgress(progress.Progress{
|
_ = u.progressOut.WriteProgress(progress.Progress{
|
||||||
ID: fmt.Sprintf("%d/%d", task.Slot+1, u.total),
|
ID: fmt.Sprintf("%d/%d", task.Slot+1, u.total),
|
||||||
Action: fmt.Sprintf("%-*s", longestState, task.Status.State),
|
Action: fmt.Sprintf("%-*s", longestState, task.Status.State),
|
||||||
Current: numberedStates[task.Status.State],
|
Current: numberedStates[task.Status.State],
|
||||||
@ -732,7 +730,7 @@ func (u *globalJobProgressUpdater) update(service swarm.Service, tasks []swarm.T
|
|||||||
if !u.initialized {
|
if !u.initialized {
|
||||||
// if there are not yet tasks, then return early.
|
// if there are not yet tasks, then return early.
|
||||||
if len(tasks) == 0 && len(activeNodes) != 0 {
|
if len(tasks) == 0 && len(activeNodes) != 0 {
|
||||||
u.progressOut.WriteProgress(progress.Progress{
|
_ = u.progressOut.WriteProgress(progress.Progress{
|
||||||
ID: "job progress",
|
ID: "job progress",
|
||||||
Action: "waiting for tasks",
|
Action: "waiting for tasks",
|
||||||
})
|
})
|
||||||
@ -810,14 +808,14 @@ func (u *globalJobProgressUpdater) writeTaskProgress(task swarm.Task) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if task.Status.Err != "" {
|
if task.Status.Err != "" {
|
||||||
u.progressOut.WriteProgress(progress.Progress{
|
_ = u.progressOut.WriteProgress(progress.Progress{
|
||||||
ID: task.NodeID,
|
ID: task.NodeID,
|
||||||
Action: truncError(task.Status.Err),
|
Action: truncError(task.Status.Err),
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
u.progressOut.WriteProgress(progress.Progress{
|
_ = u.progressOut.WriteProgress(progress.Progress{
|
||||||
ID: task.NodeID,
|
ID: task.NodeID,
|
||||||
Action: fmt.Sprintf("%-*s", longestState, task.Status.State),
|
Action: fmt.Sprintf("%-*s", longestState, task.Status.State),
|
||||||
Current: numberedStates[task.Status.State],
|
Current: numberedStates[task.Status.State],
|
||||||
@ -829,7 +827,7 @@ func (u *globalJobProgressUpdater) writeTaskProgress(task swarm.Task) {
|
|||||||
func (u *globalJobProgressUpdater) writeOverallProgress(complete int) {
|
func (u *globalJobProgressUpdater) writeOverallProgress(complete int) {
|
||||||
// all tasks for a global job are active at once, so we only write out the
|
// all tasks for a global job are active at once, so we only write out the
|
||||||
// total progress.
|
// total progress.
|
||||||
u.progressOut.WriteProgress(progress.Progress{
|
_ = u.progressOut.WriteProgress(progress.Progress{
|
||||||
// see (*replicatedJobProgressUpdater).writeOverallProgress for an
|
// see (*replicatedJobProgressUpdater).writeOverallProgress for an
|
||||||
// explanation of the advanced fmt use in this function.
|
// explanation of the advanced fmt use in this function.
|
||||||
ID: "job progress",
|
ID: "job progress",
|
||||||
|
|||||||
82
vendor/github.com/docker/cli/cli/command/stack/formatter/formatter.go
generated
vendored
82
vendor/github.com/docker/cli/cli/command/stack/formatter/formatter.go
generated
vendored
@ -1,82 +0,0 @@
|
|||||||
package formatter
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/docker/cli/cli/command/formatter"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// SwarmStackTableFormat is the default Swarm stack format
|
|
||||||
//
|
|
||||||
// Deprecated: this type was for internal use and will be removed in the next release.
|
|
||||||
SwarmStackTableFormat formatter.Format = "table {{.Name}}\t{{.Services}}"
|
|
||||||
|
|
||||||
stackServicesHeader = "SERVICES"
|
|
||||||
|
|
||||||
// TableFormatKey is an alias for formatter.TableFormatKey
|
|
||||||
//
|
|
||||||
// Deprecated: this type was for internal use and will be removed in the next release.
|
|
||||||
TableFormatKey = formatter.TableFormatKey
|
|
||||||
)
|
|
||||||
|
|
||||||
// Context is an alias for formatter.Context
|
|
||||||
//
|
|
||||||
// Deprecated: this type was for internal use and will be removed in the next release.
|
|
||||||
type Context = formatter.Context
|
|
||||||
|
|
||||||
// Format is an alias for formatter.Format
|
|
||||||
//
|
|
||||||
// Deprecated: this type was for internal use and will be removed in the next release.
|
|
||||||
type Format = formatter.Format
|
|
||||||
|
|
||||||
// Stack contains deployed stack information.
|
|
||||||
//
|
|
||||||
// Deprecated: this type was for internal use and will be removed in the next release.
|
|
||||||
type Stack struct {
|
|
||||||
// Name is the name of the stack
|
|
||||||
Name string
|
|
||||||
// Services is the number of the services
|
|
||||||
Services int
|
|
||||||
}
|
|
||||||
|
|
||||||
// StackWrite writes formatted stacks using the Context
|
|
||||||
//
|
|
||||||
// Deprecated: this function was for internal use and will be removed in the next release.
|
|
||||||
func StackWrite(ctx formatter.Context, stacks []*Stack) error {
|
|
||||||
render := func(format func(subContext formatter.SubContext) error) error {
|
|
||||||
for _, stack := range stacks {
|
|
||||||
if err := format(&stackContext{s: stack}); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return ctx.Write(newStackContext(), render)
|
|
||||||
}
|
|
||||||
|
|
||||||
type stackContext struct {
|
|
||||||
formatter.HeaderContext
|
|
||||||
s *Stack
|
|
||||||
}
|
|
||||||
|
|
||||||
func newStackContext() *stackContext {
|
|
||||||
stackCtx := stackContext{}
|
|
||||||
stackCtx.Header = formatter.SubHeaderContext{
|
|
||||||
"Name": formatter.NameHeader,
|
|
||||||
"Services": stackServicesHeader,
|
|
||||||
}
|
|
||||||
return &stackCtx
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stackContext) MarshalJSON() ([]byte, error) {
|
|
||||||
return formatter.MarshalJSON(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stackContext) Name() string {
|
|
||||||
return s.s.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stackContext) Services() string {
|
|
||||||
return strconv.Itoa(s.s.Services)
|
|
||||||
}
|
|
||||||
20
vendor/github.com/docker/cli/cli/command/telemetry.go
generated
vendored
20
vendor/github.com/docker/cli/cli/command/telemetry.go
generated
vendored
@ -11,11 +11,12 @@ import (
|
|||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"go.opentelemetry.io/otel"
|
"go.opentelemetry.io/otel"
|
||||||
"go.opentelemetry.io/otel/metric"
|
"go.opentelemetry.io/otel/metric"
|
||||||
|
otelsdk "go.opentelemetry.io/otel/sdk"
|
||||||
sdkmetric "go.opentelemetry.io/otel/sdk/metric"
|
sdkmetric "go.opentelemetry.io/otel/sdk/metric"
|
||||||
"go.opentelemetry.io/otel/sdk/metric/metricdata"
|
"go.opentelemetry.io/otel/sdk/metric/metricdata"
|
||||||
"go.opentelemetry.io/otel/sdk/resource"
|
"go.opentelemetry.io/otel/sdk/resource"
|
||||||
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
||||||
semconv "go.opentelemetry.io/otel/semconv/v1.26.0"
|
semconv "go.opentelemetry.io/otel/semconv/v1.37.0"
|
||||||
"go.opentelemetry.io/otel/trace"
|
"go.opentelemetry.io/otel/trace"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -146,7 +147,7 @@ func defaultResourceOptions() []resource.Option {
|
|||||||
semconv.ServiceInstanceID(uuid.NewString()),
|
semconv.ServiceInstanceID(uuid.NewString()),
|
||||||
),
|
),
|
||||||
resource.WithFromEnv(),
|
resource.WithFromEnv(),
|
||||||
resource.WithTelemetrySDK(),
|
resource.WithDetectors(telemetrySDK{}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -157,7 +158,10 @@ func (r *telemetryResource) AppendOptions(opts ...resource.Option) {
|
|||||||
r.opts = append(r.opts, opts...)
|
r.opts = append(r.opts, opts...)
|
||||||
}
|
}
|
||||||
|
|
||||||
type serviceNameDetector struct{}
|
type (
|
||||||
|
serviceNameDetector struct{}
|
||||||
|
telemetrySDK struct{}
|
||||||
|
)
|
||||||
|
|
||||||
func (serviceNameDetector) Detect(ctx context.Context) (*resource.Resource, error) {
|
func (serviceNameDetector) Detect(ctx context.Context) (*resource.Resource, error) {
|
||||||
return resource.StringDetector(
|
return resource.StringDetector(
|
||||||
@ -169,6 +173,16 @@ func (serviceNameDetector) Detect(ctx context.Context) (*resource.Resource, erro
|
|||||||
).Detect(ctx)
|
).Detect(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Detect returns a *Resource that describes the OpenTelemetry SDK used.
|
||||||
|
func (telemetrySDK) Detect(context.Context) (*resource.Resource, error) {
|
||||||
|
return resource.NewWithAttributes(
|
||||||
|
semconv.SchemaURL,
|
||||||
|
semconv.TelemetrySDKName("opentelemetry"),
|
||||||
|
semconv.TelemetrySDKLanguageGo,
|
||||||
|
semconv.TelemetrySDKVersion(otelsdk.Version()),
|
||||||
|
), nil
|
||||||
|
}
|
||||||
|
|
||||||
// cliReader is an implementation of Reader that will automatically
|
// cliReader is an implementation of Reader that will automatically
|
||||||
// report to a designated Exporter when Shutdown is called.
|
// report to a designated Exporter when Shutdown is called.
|
||||||
type cliReader struct {
|
type cliReader struct {
|
||||||
|
|||||||
7
vendor/github.com/docker/cli/cli/command/telemetry_docker.go
generated
vendored
7
vendor/github.com/docker/cli/cli/command/telemetry_docker.go
generated
vendored
@ -1,5 +1,5 @@
|
|||||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||||
//go:build go1.23
|
//go:build go1.24
|
||||||
|
|
||||||
package command
|
package command
|
||||||
|
|
||||||
@ -14,7 +14,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"unicode"
|
"unicode"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"go.opentelemetry.io/otel"
|
"go.opentelemetry.io/otel"
|
||||||
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc"
|
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc"
|
||||||
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
|
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
|
||||||
@ -48,7 +47,7 @@ func dockerExporterOTLPEndpoint(cli Cli) (endpoint string, secure bool) {
|
|||||||
if otelCfg != nil {
|
if otelCfg != nil {
|
||||||
otelMap, ok := otelCfg.(map[string]any)
|
otelMap, ok := otelCfg.(map[string]any)
|
||||||
if !ok {
|
if !ok {
|
||||||
otel.Handle(errors.Errorf(
|
otel.Handle(fmt.Errorf(
|
||||||
"unexpected type for field %q: %T (expected: %T)",
|
"unexpected type for field %q: %T (expected: %T)",
|
||||||
otelContextFieldName,
|
otelContextFieldName,
|
||||||
otelCfg,
|
otelCfg,
|
||||||
@ -76,7 +75,7 @@ func dockerExporterOTLPEndpoint(cli Cli) (endpoint string, secure bool) {
|
|||||||
// We pretend we're the same as the environment reader.
|
// We pretend we're the same as the environment reader.
|
||||||
u, err := url.Parse(endpoint)
|
u, err := url.Parse(endpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
otel.Handle(errors.Errorf("docker otel endpoint is invalid: %s", err))
|
otel.Handle(fmt.Errorf("docker otel endpoint is invalid: %s", err))
|
||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user