Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
4ef849767f
|
@ -260,7 +260,6 @@ checkout as-is. Recipe commit hashes are also supported as values for
|
||||
app.Name,
|
||||
app.Server,
|
||||
internal.DontWaitConverge,
|
||||
internal.NoInput,
|
||||
f,
|
||||
); err != nil {
|
||||
log.Fatal(err)
|
||||
|
||||
@ -128,7 +128,6 @@ Pass "--all-services/-a" to restart all services.`),
|
||||
AppName: app.Name,
|
||||
ServerName: app.Server,
|
||||
Filters: f,
|
||||
NoInput: internal.NoInput,
|
||||
NoLog: true,
|
||||
Quiet: true,
|
||||
}
|
||||
|
||||
@ -246,7 +246,6 @@ beforehand. See "abra app backup" for more.`),
|
||||
stackName,
|
||||
app.Server,
|
||||
internal.DontWaitConverge,
|
||||
internal.NoInput,
|
||||
f,
|
||||
); err != nil {
|
||||
log.Fatal(err)
|
||||
|
||||
@ -282,7 +282,6 @@ beforehand. See "abra app backup" for more.`),
|
||||
stackName,
|
||||
app.Server,
|
||||
internal.DontWaitConverge,
|
||||
internal.NoInput,
|
||||
f,
|
||||
); err != nil {
|
||||
log.Fatal(err)
|
||||
|
||||
@ -64,7 +64,7 @@ func DeployOverview(
|
||||
server = "local"
|
||||
}
|
||||
|
||||
domain := fmt.Sprintf("https://%s", app.Domain)
|
||||
domain := app.Domain
|
||||
if domain == "" {
|
||||
domain = config.MISSING_DEFAULT
|
||||
}
|
||||
|
||||
66
go.mod
66
go.mod
@ -1,26 +1,29 @@
|
||||
module coopcloud.tech/abra
|
||||
|
||||
go 1.24.2
|
||||
go 1.24.0
|
||||
|
||||
toolchain go1.24.1
|
||||
|
||||
require (
|
||||
coopcloud.tech/tagcmp v0.0.0-20250818180036-0ec1b205b5ca
|
||||
git.coopcloud.tech/toolshed/godotenv v1.5.2-0.20250103171850-4d0ca41daa5c
|
||||
github.com/AlecAivazis/survey/v2 v2.3.7
|
||||
github.com/charmbracelet/bubbles v0.21.0
|
||||
github.com/charmbracelet/bubbletea v1.3.10
|
||||
github.com/charmbracelet/lipgloss v1.1.0
|
||||
github.com/charmbracelet/log v0.4.2
|
||||
github.com/distribution/reference v0.6.0
|
||||
github.com/docker/cli v29.0.0+incompatible
|
||||
github.com/docker/docker v28.5.2+incompatible
|
||||
github.com/docker/cli v28.4.0+incompatible
|
||||
github.com/docker/docker v28.4.0+incompatible
|
||||
github.com/docker/go-units v0.5.0
|
||||
github.com/go-git/go-git/v5 v5.16.3
|
||||
github.com/go-git/go-git/v5 v5.16.2
|
||||
github.com/google/go-cmp v0.7.0
|
||||
github.com/leonelquinteros/gotext v1.7.2
|
||||
github.com/moby/sys/signal v0.7.1
|
||||
github.com/moby/term v0.5.2
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/schollz/progressbar/v3 v3.18.0
|
||||
golang.org/x/term v0.36.0
|
||||
golang.org/x/term v0.35.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
gotest.tools/v3 v3.5.2
|
||||
)
|
||||
@ -36,21 +39,18 @@ require (
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/charmbracelet/colorprofile v0.3.3 // indirect
|
||||
github.com/charmbracelet/x/ansi v0.11.0 // 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/term v0.2.2 // 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/charmbracelet/colorprofile v0.3.2 // indirect
|
||||
github.com/charmbracelet/x/ansi v0.10.2 // indirect
|
||||
github.com/charmbracelet/x/cellbuf v0.0.13 // indirect
|
||||
github.com/charmbracelet/x/term v0.2.1 // indirect
|
||||
github.com/clipperhouse/uax29/v2 v2.2.0 // indirect
|
||||
github.com/cloudflare/circl v1.6.1 // indirect
|
||||
github.com/containerd/errdefs v1.0.0 // indirect
|
||||
github.com/containerd/errdefs/pkg v0.3.0 // indirect
|
||||
github.com/containerd/log v0.1.0 // indirect
|
||||
github.com/containerd/platforms v0.2.1 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.6.0 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.5.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/docker/distribution v2.8.3+incompatible // 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/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
||||
github.com/go-git/go-billy/v5 v5.6.2 // indirect
|
||||
github.com/go-logfmt/logfmt v0.6.1 // indirect
|
||||
github.com/go-logfmt/logfmt v0.6.0 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // 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/kevinburke/ssh_config v1.4.0 // indirect
|
||||
github.com/klauspost/compress v1.18.1 // indirect
|
||||
github.com/klauspost/compress v1.18.0 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
@ -86,8 +86,6 @@ require (
|
||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
|
||||
github.com/moby/docker-image-spec v1.3.1 // 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/user v0.4.0 // indirect
|
||||
github.com/moby/sys/userns v0.1.0 // indirect
|
||||
@ -102,12 +100,12 @@ require (
|
||||
github.com/pjbgf/sha1cd v0.5.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/client_model v0.6.2 // indirect
|
||||
github.com/prometheus/common v0.67.2 // indirect
|
||||
github.com/prometheus/procfs v0.19.2 // indirect
|
||||
github.com/prometheus/common v0.66.1 // indirect
|
||||
github.com/prometheus/procfs v0.17.0 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/skeema/knownhosts v1.3.2 // indirect
|
||||
github.com/skeema/knownhosts v1.3.1 // indirect
|
||||
github.com/spf13/pflag v1.0.10 // indirect
|
||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||
@ -124,17 +122,17 @@ require (
|
||||
go.opentelemetry.io/otel/sdk 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/proto/otlp v1.9.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.8.0 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
||||
golang.org/x/crypto v0.43.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect
|
||||
golang.org/x/net v0.46.0 // indirect
|
||||
golang.org/x/text v0.30.0 // indirect
|
||||
golang.org/x/time v0.14.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251110190251-83f479183930 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251110190251-83f479183930 // indirect
|
||||
google.golang.org/grpc v1.76.0 // indirect
|
||||
google.golang.org/protobuf v1.36.10 // indirect
|
||||
golang.org/x/crypto v0.42.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20250911091902-df9299821621 // indirect
|
||||
golang.org/x/net v0.44.0 // indirect
|
||||
golang.org/x/text v0.29.0 // indirect
|
||||
golang.org/x/time v0.13.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250929231259-57b25ae835d4 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250929231259-57b25ae835d4 // indirect
|
||||
google.golang.org/grpc v1.75.1 // indirect
|
||||
google.golang.org/protobuf v1.36.9 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
)
|
||||
@ -143,7 +141,7 @@ require (
|
||||
github.com/containers/image v3.0.2+incompatible
|
||||
github.com/containers/storage v1.38.2 // indirect
|
||||
github.com/decentral1se/passgen v1.0.1
|
||||
github.com/docker/docker-credential-helpers v0.9.4 // indirect
|
||||
github.com/docker/docker-credential-helpers v0.9.3 // indirect
|
||||
github.com/fvbommel/sortorder v1.1.0 // indirect
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||
github.com/gorilla/mux v1.8.1 // indirect
|
||||
@ -157,5 +155,5 @@ require (
|
||||
github.com/stretchr/testify v1.11.1
|
||||
github.com/theupdateframework/notary v0.7.0 // indirect
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
||||
golang.org/x/sys v0.38.0
|
||||
golang.org/x/sys v0.36.0
|
||||
)
|
||||
|
||||
71
go.sum
71
go.sum
@ -129,35 +129,28 @@ 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/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
|
||||
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/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/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/charmbracelet/bubbles v0.21.0 h1:9TdC97SdRVg/1aaXNVWfFH3nnLAwOXr8Fn6u6mfQdFs=
|
||||
github.com/charmbracelet/bubbles v0.21.0/go.mod h1:HF+v6QUR4HkEpz62dx7ym2xc71/KBHg+zKwJtMw+qtg=
|
||||
github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw=
|
||||
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/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/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
|
||||
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/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.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/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/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=
|
||||
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.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/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M=
|
||||
github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E=
|
||||
@ -173,14 +166,8 @@ 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.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA=
|
||||
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/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/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
|
||||
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
||||
@ -315,8 +302,6 @@ 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.5.0 h1:hIAhkRBMQ8nIeuVwcAoymp7MY4oherZdAxD+m0u9zaw=
|
||||
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/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s=
|
||||
github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5/go.mod h1:Eo87+Kg/IX2hfWJfwxMzLyuSZyxSoAug2nGa1G2QAi8=
|
||||
@ -337,8 +322,6 @@ 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 v28.4.0+incompatible h1:RBcf3Kjw2pMtwui5V0DIMdyeab8glEw5QY0UUU4C9kY=
|
||||
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 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=
|
||||
@ -347,13 +330,9 @@ 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 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.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.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.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/go.mod h1:CADgU4DSXK5QUlFslkQu2yW2TKzFZcXq/leZfM0UH5Q=
|
||||
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
||||
@ -418,8 +397,6 @@ 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/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.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/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=
|
||||
@ -430,8 +407,6 @@ 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.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.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.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
@ -556,12 +531,9 @@ 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/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.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/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.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/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=
|
||||
@ -621,8 +593,6 @@ 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.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.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/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
||||
@ -694,10 +664,6 @@ 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/go.mod h1:G9B+YoujNohJmrIYFBpSd54GTUB4lt9S+xVQvsJyFuo=
|
||||
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/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
|
||||
github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw=
|
||||
@ -831,8 +797,6 @@ 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.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.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-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
@ -846,8 +810,6 @@ 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.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.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/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
@ -856,8 +818,6 @@ 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.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
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.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
@ -883,8 +843,6 @@ github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ
|
||||
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/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/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=
|
||||
@ -1008,8 +966,6 @@ 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 v1.8.0 h1:fRAZQDcAFHySxpJ1TwlA1cJ4tvcrw7nXl9xWWC8N5CE=
|
||||
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.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
@ -1038,8 +994,6 @@ 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.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
|
||||
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-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
@ -1052,8 +1006,6 @@ 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-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-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-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
@ -1119,8 +1071,6 @@ 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.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I=
|
||||
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-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@ -1218,15 +1168,11 @@ 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.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
|
||||
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-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.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ=
|
||||
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.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@ -1238,8 +1184,6 @@ 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.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
|
||||
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-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
@ -1248,8 +1192,6 @@ 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.13.0 h1:eUlYslOIt32DgYD6utsuUeHs4d7AsEYLuIAdg7FlYgI=
|
||||
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-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
@ -1347,12 +1289,8 @@ 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/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-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/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 v1.0.5/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
@ -1374,8 +1312,6 @@ 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.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI=
|
||||
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-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
@ -1391,8 +1327,6 @@ 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.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
|
||||
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/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=
|
||||
@ -1430,7 +1364,6 @@ 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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
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/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
|
||||
gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8=
|
||||
|
||||
@ -633,11 +633,6 @@ func (a App) WipeRecipeVersion() error {
|
||||
|
||||
// WriteRecipeVersion writes the recipe version to the app .env file.
|
||||
func (a App) WriteRecipeVersion(version string, dryRun bool) error {
|
||||
if version == config.UNKNOWN_DEFAULT {
|
||||
log.Debug(i18n.G("version is unknown, skipping env write"))
|
||||
return nil
|
||||
}
|
||||
|
||||
file, err := os.Open(a.Path)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@ -224,16 +224,3 @@ func TestWriteRecipeVersionOverwrite(t *testing.T) {
|
||||
|
||||
assert.Equal(t, "foo", app.Recipe.EnvVersion)
|
||||
}
|
||||
|
||||
func TestWriteRecipeVersionUnknown(t *testing.T) {
|
||||
app, err := appPkg.GetApp(testPkg.ExpectedAppFiles, testPkg.AppName)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := app.WriteRecipeVersion(config.UNKNOWN_DEFAULT, false); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assert.NotEqual(t, config.UNKNOWN_DEFAULT, app.Recipe.EnvVersion)
|
||||
}
|
||||
|
||||
@ -37,27 +37,18 @@ func WithTimeout(timeout int) Opt {
|
||||
// New initiates a new Docker client. New client connections are validated so
|
||||
// that we ensure connections via SSH to the daemon can succeed. It takes into
|
||||
// account that you may only want the local client and not communicate via SSH.
|
||||
// For this use-case, please pass "default" as the serverName.
|
||||
// For this use-case, please pass "default" as the contextName.
|
||||
func New(serverName string, opts ...Opt) (*client.Client, error) {
|
||||
var clientOpts []client.Opt
|
||||
|
||||
ctx, err := GetContext(serverName)
|
||||
if err != nil {
|
||||
serverDir := path.Join(config.SERVERS_DIR, serverName)
|
||||
if _, err := os.Stat(serverDir); err != nil {
|
||||
return nil, errors.New(i18n.G("server missing, run \"abra server add %s\"?", serverName))
|
||||
}
|
||||
|
||||
// NOTE(p4u1): when the docker context does not exist but the server folder
|
||||
// is there, let's create a new docker context.
|
||||
if err = CreateContext(serverName); err != nil {
|
||||
return nil, errors.New(i18n.G("server missing context, context creation failed: %s", err))
|
||||
}
|
||||
|
||||
ctx, err = GetContext(serverName)
|
||||
if err != nil {
|
||||
if _, err := os.Stat(serverDir); err == nil {
|
||||
return nil, errors.New(i18n.G("server missing context, run \"abra server add %s\"?", serverName))
|
||||
}
|
||||
|
||||
return nil, errors.New(i18n.G("unknown server, run \"abra server add %s\"?", serverName))
|
||||
}
|
||||
|
||||
ctxEndpoint, err := contextPkg.GetContextEndpoint(ctx)
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
msgid ""
|
||||
msgstr "Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: EMAIL\n"
|
||||
"POT-Creation-Date: 2025-11-04 15:34+0100\n"
|
||||
"POT-Creation-Date: 2025-11-02 14:13+0100\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@ -274,12 +274,12 @@ msgstr ""
|
||||
msgid "%s has been detected as not deployed"
|
||||
msgstr ""
|
||||
|
||||
#: ./cli/app/restart.go:140
|
||||
#: ./cli/app/restart.go:139
|
||||
#, c-format
|
||||
msgid "%s has been scaled to 0"
|
||||
msgstr ""
|
||||
|
||||
#: ./cli/app/restart.go:151
|
||||
#: ./cli/app/restart.go:150
|
||||
#, c-format
|
||||
msgid "%s has been scaled to 1"
|
||||
msgstr ""
|
||||
@ -339,17 +339,17 @@ msgstr ""
|
||||
msgid "%s is missing the TYPE env var?"
|
||||
msgstr ""
|
||||
|
||||
#: ./cli/app/rollback.go:309 ./cli/app/rollback.go:313
|
||||
#: ./cli/app/rollback.go:308 ./cli/app/rollback.go:312
|
||||
#, c-format
|
||||
msgid "%s is not a downgrade for %s?"
|
||||
msgstr ""
|
||||
|
||||
#: ./cli/app/upgrade.go:429 ./cli/app/upgrade.go:433
|
||||
#: ./cli/app/upgrade.go:428 ./cli/app/upgrade.go:432
|
||||
#, c-format
|
||||
msgid "%s is not an upgrade for %s?"
|
||||
msgstr ""
|
||||
|
||||
#: ./cli/app/env.go:146 ./cli/app/logs.go:65 ./cli/app/ps.go:62 ./cli/app/restart.go:100 ./cli/app/services.go:55 ./cli/app/undeploy.go:66 ./cli/app/upgrade.go:450
|
||||
#: ./cli/app/env.go:146 ./cli/app/logs.go:65 ./cli/app/ps.go:62 ./cli/app/restart.go:100 ./cli/app/services.go:55 ./cli/app/undeploy.go:66 ./cli/app/upgrade.go:449
|
||||
#, c-format
|
||||
msgid "%s is not deployed?"
|
||||
msgstr ""
|
||||
@ -424,7 +424,7 @@ msgstr ""
|
||||
msgid "%s service is missing image tag?"
|
||||
msgstr ""
|
||||
|
||||
#: ./cli/app/restart.go:152
|
||||
#: ./cli/app/restart.go:151
|
||||
#, c-format
|
||||
msgid "%s service successfully restarted"
|
||||
msgstr ""
|
||||
@ -459,7 +459,7 @@ msgstr ""
|
||||
msgid "%s/example.git"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/upstream/stack/stack.go:613
|
||||
#: ./pkg/upstream/stack/stack.go:602
|
||||
#, c-format
|
||||
msgid "%s: %s"
|
||||
msgstr ""
|
||||
@ -469,7 +469,7 @@ msgstr ""
|
||||
msgid "%s: %s (new)"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/ui/deploy.go:344
|
||||
#: ./pkg/ui/deploy.go:406
|
||||
#, c-format
|
||||
msgid "%s: %s (retries: %v, healthcheck: %s)"
|
||||
msgstr ""
|
||||
@ -549,12 +549,12 @@ msgstr ""
|
||||
msgid "%s: waiting %d seconds before next retry"
|
||||
msgstr ""
|
||||
|
||||
#: ./cli/app/upgrade.go:424
|
||||
#: ./cli/app/upgrade.go:423
|
||||
#, c-format
|
||||
msgid "'%s' is not a known version"
|
||||
msgstr ""
|
||||
|
||||
#: ./cli/app/rollback.go:304 ./cli/app/upgrade.go:419
|
||||
#: ./cli/app/rollback.go:303 ./cli/app/upgrade.go:418
|
||||
#, c-format
|
||||
msgid "'%s' is not a known version for %s"
|
||||
msgstr ""
|
||||
@ -621,7 +621,7 @@ msgstr ""
|
||||
msgid "Both local recipe and live deployment labels are shown."
|
||||
msgstr ""
|
||||
|
||||
#: ./cli/app/backup.go:319 ./cli/app/backup.go:335 ./cli/app/check.go:95 ./cli/app/cmd.go:285 ./cli/app/cp.go:385 ./cli/app/deploy.go:396 ./cli/app/labels.go:143 ./cli/app/new.go:397 ./cli/app/ps.go:213 ./cli/app/restart.go:163 ./cli/app/restore.go:138 ./cli/app/secret.go:569 ./cli/app/secret.go:609 ./cli/app/secret.go:633 ./cli/app/secret.go:641 ./cli/catalogue/catalogue.go:318 ./cli/recipe/lint.go:137
|
||||
#: ./cli/app/backup.go:319 ./cli/app/backup.go:335 ./cli/app/check.go:95 ./cli/app/cmd.go:285 ./cli/app/cp.go:385 ./cli/app/deploy.go:395 ./cli/app/labels.go:143 ./cli/app/new.go:397 ./cli/app/ps.go:213 ./cli/app/restart.go:162 ./cli/app/restore.go:138 ./cli/app/secret.go:569 ./cli/app/secret.go:609 ./cli/app/secret.go:633 ./cli/app/secret.go:641 ./cli/catalogue/catalogue.go:318 ./cli/recipe/lint.go:137
|
||||
msgid "C"
|
||||
msgstr ""
|
||||
|
||||
@ -762,7 +762,7 @@ msgid "Creates a new app from a default recipe.\n"
|
||||
"on your $PATH."
|
||||
msgstr ""
|
||||
|
||||
#: ./cli/app/deploy.go:412 ./cli/app/new.go:373 ./cli/app/rollback.go:361 ./cli/app/upgrade.go:470
|
||||
#: ./cli/app/deploy.go:411 ./cli/app/new.go:373 ./cli/app/rollback.go:360 ./cli/app/upgrade.go:469
|
||||
msgid "D"
|
||||
msgstr ""
|
||||
|
||||
@ -1469,7 +1469,7 @@ msgid "To load completions:\n"
|
||||
" # and source this file from your PowerShell profile."
|
||||
msgstr ""
|
||||
|
||||
#: ./cli/app/deploy.go:436 ./cli/app/rollback.go:377 ./cli/app/upgrade.go:494
|
||||
#: ./cli/app/deploy.go:435 ./cli/app/rollback.go:376 ./cli/app/upgrade.go:493
|
||||
msgid "U"
|
||||
msgstr ""
|
||||
|
||||
@ -1659,7 +1659,7 @@ msgid "\n"
|
||||
"lint %s: %s"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/ui/deploy.go:121
|
||||
#: ./pkg/ui/deploy.go:132
|
||||
#, c-format
|
||||
msgid "^%s"
|
||||
msgstr ""
|
||||
@ -1676,7 +1676,7 @@ msgctxt "app backup list"
|
||||
msgid "a"
|
||||
msgstr ""
|
||||
|
||||
#: ./cli/app/restart.go:170
|
||||
#: ./cli/app/restart.go:169
|
||||
msgctxt "app restart"
|
||||
msgid "a"
|
||||
msgstr ""
|
||||
@ -1785,7 +1785,7 @@ msgstr ""
|
||||
msgid "all tasks reached terminal state"
|
||||
msgstr ""
|
||||
|
||||
#: ./cli/app/restart.go:169
|
||||
#: ./cli/app/restart.go:168
|
||||
msgid "all-services"
|
||||
msgstr ""
|
||||
|
||||
@ -1844,7 +1844,7 @@ msgstr ""
|
||||
msgid "attempting to run %s"
|
||||
msgstr ""
|
||||
|
||||
#: ./cli/app/deploy.go:273 ./cli/app/upgrade.go:296
|
||||
#: ./cli/app/deploy.go:272 ./cli/app/upgrade.go:295
|
||||
#, c-format
|
||||
msgid "attempting to run post deploy commands, saw: %s"
|
||||
msgstr ""
|
||||
@ -1854,7 +1854,7 @@ msgstr ""
|
||||
msgid "attempting to scale %s to 0"
|
||||
msgstr ""
|
||||
|
||||
#: ./cli/app/restart.go:141
|
||||
#: ./cli/app/restart.go:140
|
||||
#, c-format
|
||||
msgid "attempting to scale %s to 1"
|
||||
msgstr ""
|
||||
@ -1924,7 +1924,7 @@ msgstr ""
|
||||
#. no spaces in between
|
||||
#. translators: `abra app cp` aliases. use a comma separated list of aliases with
|
||||
#. no spaces in between
|
||||
#: ./cli/app/backup.go:148 ./cli/app/cp.go:30 ./cli/app/deploy.go:420 ./cli/app/rollback.go:369 ./cli/app/upgrade.go:478
|
||||
#: ./cli/app/backup.go:148 ./cli/app/cp.go:30 ./cli/app/deploy.go:419 ./cli/app/rollback.go:368 ./cli/app/upgrade.go:477
|
||||
msgid "c"
|
||||
msgstr ""
|
||||
|
||||
@ -1965,7 +1965,7 @@ msgstr ""
|
||||
msgid "cannot find app with name %s"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/upstream/stack/stack.go:668
|
||||
#: ./pkg/upstream/stack/stack.go:657
|
||||
#, c-format
|
||||
msgid "cannot get label %s for %s"
|
||||
msgstr ""
|
||||
@ -1980,7 +1980,7 @@ msgstr ""
|
||||
msgid "cannot redeploy previous chaos version (%s), did you mean to use \"--chaos\"?"
|
||||
msgstr ""
|
||||
|
||||
#: ./cli/app/deploy.go:370
|
||||
#: ./cli/app/deploy.go:369
|
||||
#, c-format
|
||||
msgid "cannot redeploy previous chaos version (%s), did you mean to use \"--chaos\"?\n"
|
||||
" to return to a regular release, specify a release tag, commit SHA or use \"--latest\""
|
||||
@ -1999,7 +1999,7 @@ msgstr ""
|
||||
msgid "cannot use '[secret] [version]' and '--all' together"
|
||||
msgstr ""
|
||||
|
||||
#: ./cli/app/deploy.go:312
|
||||
#: ./cli/app/deploy.go:311
|
||||
msgid "cannot use --chaos and --latest together"
|
||||
msgstr ""
|
||||
|
||||
@ -2023,11 +2023,11 @@ msgstr ""
|
||||
msgid "cannot use [service] and --all-services/-a together"
|
||||
msgstr ""
|
||||
|
||||
#: ./cli/app/deploy.go:304 ./cli/app/new.go:76
|
||||
#: ./cli/app/deploy.go:303 ./cli/app/new.go:76
|
||||
msgid "cannot use [version] and --chaos together"
|
||||
msgstr ""
|
||||
|
||||
#: ./cli/app/deploy.go:308
|
||||
#: ./cli/app/deploy.go:307
|
||||
msgid "cannot use [version] and --latest together"
|
||||
msgstr ""
|
||||
|
||||
@ -2059,7 +2059,7 @@ msgstr ""
|
||||
msgid "cfg"
|
||||
msgstr ""
|
||||
|
||||
#: ./cli/app/backup.go:318 ./cli/app/backup.go:334 ./cli/app/check.go:94 ./cli/app/cmd.go:284 ./cli/app/cp.go:384 ./cli/app/deploy.go:395 ./cli/app/labels.go:142 ./cli/app/new.go:396 ./cli/app/ps.go:212 ./cli/app/restart.go:162 ./cli/app/restore.go:137 ./cli/app/secret.go:568 ./cli/app/secret.go:608 ./cli/app/secret.go:632 ./cli/app/secret.go:640 ./cli/catalogue/catalogue.go:317 ./cli/recipe/lint.go:136
|
||||
#: ./cli/app/backup.go:318 ./cli/app/backup.go:334 ./cli/app/check.go:94 ./cli/app/cmd.go:284 ./cli/app/cp.go:384 ./cli/app/deploy.go:394 ./cli/app/labels.go:142 ./cli/app/new.go:396 ./cli/app/ps.go:212 ./cli/app/restart.go:161 ./cli/app/restore.go:137 ./cli/app/secret.go:568 ./cli/app/secret.go:608 ./cli/app/secret.go:632 ./cli/app/secret.go:640 ./cli/catalogue/catalogue.go:317 ./cli/recipe/lint.go:136
|
||||
msgid "chaos"
|
||||
msgstr ""
|
||||
|
||||
@ -2068,7 +2068,7 @@ msgstr ""
|
||||
msgid "check <domain> [flags]"
|
||||
msgstr ""
|
||||
|
||||
#: ./cli/app/deploy.go:94 ./cli/app/undeploy.go:58 ./cli/app/upgrade.go:442
|
||||
#: ./cli/app/deploy.go:94 ./cli/app/undeploy.go:58 ./cli/app/upgrade.go:441
|
||||
#, c-format
|
||||
msgid "checking whether %s is already deployed"
|
||||
msgstr ""
|
||||
@ -2291,7 +2291,7 @@ msgstr ""
|
||||
msgid "create remote directory: %s"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/client/client.go:111
|
||||
#: ./pkg/client/client.go:102
|
||||
#, c-format
|
||||
msgid "created client for %s"
|
||||
msgstr ""
|
||||
@ -2311,7 +2311,7 @@ msgstr ""
|
||||
msgid "created the %s context"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/upstream/stack/stack.go:524
|
||||
#: ./pkg/upstream/stack/stack.go:520
|
||||
#, c-format
|
||||
msgid "creating %s"
|
||||
msgstr ""
|
||||
@ -2326,12 +2326,12 @@ msgstr ""
|
||||
msgid "creating context with domain %s"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/upstream/stack/stack.go:426
|
||||
#: ./pkg/upstream/stack/stack.go:422
|
||||
#, c-format
|
||||
msgid "creating network %s"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/upstream/stack/stack.go:373
|
||||
#: ./pkg/upstream/stack/stack.go:369
|
||||
#, c-format
|
||||
msgid "creating secret %s"
|
||||
msgstr ""
|
||||
@ -2350,7 +2350,7 @@ msgstr ""
|
||||
msgid "critical errors present in %s config"
|
||||
msgstr ""
|
||||
|
||||
#: ./cli/app/rollback.go:299
|
||||
#: ./cli/app/rollback.go:298
|
||||
#, c-format
|
||||
msgid "current deployment '%s' is not a known version for %s"
|
||||
msgstr ""
|
||||
@ -2390,11 +2390,11 @@ msgstr ""
|
||||
msgid "deploy <domain> [version] [flags]"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/upstream/stack/stack.go:604
|
||||
#: ./pkg/upstream/stack/stack.go:593
|
||||
msgid "deploy failed 🛑"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/upstream/stack/stack.go:608
|
||||
#: ./pkg/upstream/stack/stack.go:597
|
||||
msgid "deploy in progress 🟠"
|
||||
msgstr ""
|
||||
|
||||
@ -2402,15 +2402,15 @@ msgstr ""
|
||||
msgid "deploy labels stanza present"
|
||||
msgstr ""
|
||||
|
||||
#: ./cli/app/deploy.go:430
|
||||
#: ./cli/app/deploy.go:429
|
||||
msgid "deploy latest recipe version"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/upstream/stack/stack.go:648
|
||||
#: ./pkg/upstream/stack/stack.go:637
|
||||
msgid "deploy succeeded 🟢"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/upstream/stack/stack.go:606
|
||||
#: ./pkg/upstream/stack/stack.go:595
|
||||
msgid "deploy timed out 🟠"
|
||||
msgstr ""
|
||||
|
||||
@ -2504,11 +2504,11 @@ msgstr ""
|
||||
msgid "dirty: %v, "
|
||||
msgstr ""
|
||||
|
||||
#: ./cli/app/deploy.go:422 ./cli/app/rollback.go:371 ./cli/app/upgrade.go:480
|
||||
#: ./cli/app/deploy.go:421 ./cli/app/rollback.go:370 ./cli/app/upgrade.go:479
|
||||
msgid "disable converge logic checks"
|
||||
msgstr ""
|
||||
|
||||
#: ./cli/app/deploy.go:414 ./cli/app/rollback.go:363 ./cli/app/upgrade.go:472
|
||||
#: ./cli/app/deploy.go:413 ./cli/app/rollback.go:362 ./cli/app/upgrade.go:471
|
||||
msgid "disable public DNS checks"
|
||||
msgstr ""
|
||||
|
||||
@ -2662,7 +2662,7 @@ msgstr ""
|
||||
msgid "env file for %s has issues: %s"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/ui/deploy.go:83
|
||||
#: ./pkg/ui/deploy.go:94
|
||||
#, c-format
|
||||
msgid "err: %v, "
|
||||
msgstr ""
|
||||
@ -2726,7 +2726,7 @@ msgstr ""
|
||||
|
||||
#. translators: `abra recipe fetch` aliases. use a comma separated list of aliases
|
||||
#. with no spaces in between
|
||||
#: ./cli/app/deploy.go:404 ./cli/app/env.go:325 ./cli/app/remove.go:163 ./cli/app/rollback.go:353 ./cli/app/secret.go:593 ./cli/app/upgrade.go:462 ./cli/app/volume.go:217 ./cli/recipe/fetch.go:20 ./cli/recipe/fetch.go:138
|
||||
#: ./cli/app/deploy.go:403 ./cli/app/env.go:325 ./cli/app/remove.go:163 ./cli/app/rollback.go:352 ./cli/app/secret.go:593 ./cli/app/upgrade.go:461 ./cli/app/volume.go:217 ./cli/recipe/fetch.go:20 ./cli/recipe/fetch.go:138
|
||||
msgid "f"
|
||||
msgstr ""
|
||||
|
||||
@ -2760,22 +2760,22 @@ msgstr ""
|
||||
msgid "failed to copy %s from local machine to %s: output:%s err:%s"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/upstream/stack/stack.go:535
|
||||
#: ./pkg/upstream/stack/stack.go:531
|
||||
#, c-format
|
||||
msgid "failed to create %s"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/upstream/stack/stack.go:397
|
||||
#: ./pkg/upstream/stack/stack.go:393
|
||||
#, c-format
|
||||
msgid "failed to create config %s"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/upstream/stack/stack.go:428
|
||||
#: ./pkg/upstream/stack/stack.go:424
|
||||
#, c-format
|
||||
msgid "failed to create network %s"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/upstream/stack/stack.go:375
|
||||
#: ./pkg/upstream/stack/stack.go:371
|
||||
#, c-format
|
||||
msgid "failed to create secret %s"
|
||||
msgstr ""
|
||||
@ -2872,7 +2872,7 @@ msgstr ""
|
||||
msgid "failed to retrieve latest commit for %s: %s"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/upstream/stack/stack.go:472
|
||||
#: ./pkg/upstream/stack/stack.go:468
|
||||
#, c-format
|
||||
msgid "failed to retrieve registry auth for image %s: %s"
|
||||
msgstr ""
|
||||
@ -2892,17 +2892,17 @@ msgstr ""
|
||||
msgid "failed to tag release: %s"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/upstream/stack/stack.go:512
|
||||
#: ./pkg/upstream/stack/stack.go:508
|
||||
#, c-format
|
||||
msgid "failed to update %s"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/upstream/stack/stack.go:391
|
||||
#: ./pkg/upstream/stack/stack.go:387
|
||||
#, c-format
|
||||
msgid "failed to update config %s"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/upstream/stack/stack.go:369
|
||||
#: ./pkg/upstream/stack/stack.go:365
|
||||
#, c-format
|
||||
msgid "failed to update secret %s"
|
||||
msgstr ""
|
||||
@ -2957,7 +2957,7 @@ msgstr ""
|
||||
msgid "final merged env values for %s are: %s"
|
||||
msgstr ""
|
||||
|
||||
#: ./cli/app/deploy.go:403 ./cli/app/env.go:324 ./cli/app/remove.go:162 ./cli/app/rollback.go:352 ./cli/app/upgrade.go:461 ./cli/app/volume.go:216 ./cli/recipe/fetch.go:137
|
||||
#: ./cli/app/deploy.go:402 ./cli/app/env.go:324 ./cli/app/remove.go:162 ./cli/app/rollback.go:351 ./cli/app/upgrade.go:460 ./cli/app/volume.go:216 ./cli/recipe/fetch.go:137
|
||||
msgid "force"
|
||||
msgstr ""
|
||||
|
||||
@ -3166,12 +3166,12 @@ msgstr ""
|
||||
msgid "i"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/ui/deploy.go:84
|
||||
#: ./pkg/ui/deploy.go:95
|
||||
#, c-format
|
||||
msgid "id: %s, "
|
||||
msgstr ""
|
||||
|
||||
#: ./cli/app/backup.go:321 ./cli/app/backup.go:337 ./cli/app/check.go:97 ./cli/app/cmd.go:287 ./cli/app/cp.go:387 ./cli/app/deploy.go:398 ./cli/app/labels.go:145 ./cli/app/new.go:399 ./cli/app/ps.go:215 ./cli/app/restart.go:165 ./cli/app/restore.go:140 ./cli/app/secret.go:571 ./cli/app/secret.go:611 ./cli/app/secret.go:635 ./cli/app/secret.go:643 ./cli/catalogue/catalogue.go:320 ./cli/recipe/lint.go:139
|
||||
#: ./cli/app/backup.go:321 ./cli/app/backup.go:337 ./cli/app/check.go:97 ./cli/app/cmd.go:287 ./cli/app/cp.go:387 ./cli/app/deploy.go:397 ./cli/app/labels.go:145 ./cli/app/new.go:399 ./cli/app/ps.go:215 ./cli/app/restart.go:164 ./cli/app/restore.go:140 ./cli/app/secret.go:571 ./cli/app/secret.go:611 ./cli/app/secret.go:635 ./cli/app/secret.go:643 ./cli/catalogue/catalogue.go:320 ./cli/recipe/lint.go:139
|
||||
msgid "ignore uncommitted recipes changes"
|
||||
msgstr ""
|
||||
|
||||
@ -3282,7 +3282,7 @@ msgstr ""
|
||||
msgid "initialised new git repo in %s"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/upstream/stack/stack.go:207
|
||||
#: ./pkg/upstream/stack/stack.go:206
|
||||
msgid "initialising deployment"
|
||||
msgstr ""
|
||||
|
||||
@ -3346,7 +3346,7 @@ msgstr ""
|
||||
msgid "invalid npipe source, source cannot be empty"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/upstream/stack/stack.go:241
|
||||
#: ./pkg/upstream/stack/stack.go:239
|
||||
#, c-format
|
||||
msgid "invalid option %s for flag --resolve-image"
|
||||
msgstr ""
|
||||
@ -3369,7 +3369,7 @@ msgstr ""
|
||||
#. no spaces in between
|
||||
#. translators: `abra recipe lint` aliases. use a comma separated list of
|
||||
#. aliases with no spaces in between
|
||||
#: ./cli/app/cmd.go:261 ./cli/app/deploy.go:428 ./cli/app/logs.go:20 ./cli/recipe/lint.go:17 ./cli/server/add.go:207
|
||||
#: ./cli/app/cmd.go:261 ./cli/app/deploy.go:427 ./cli/app/logs.go:20 ./cli/recipe/lint.go:17 ./cli/server/add.go:207
|
||||
msgid "l"
|
||||
msgstr ""
|
||||
|
||||
@ -3384,7 +3384,7 @@ msgstr ""
|
||||
msgid "labels <domain> [flags]"
|
||||
msgstr ""
|
||||
|
||||
#: ./cli/app/deploy.go:427 ./cli/app/list.go:182
|
||||
#: ./cli/app/deploy.go:426 ./cli/app/list.go:182
|
||||
msgid "latest"
|
||||
msgstr ""
|
||||
|
||||
@ -3473,12 +3473,12 @@ msgstr ""
|
||||
msgid "logs <domain> [service] [flags]"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/upstream/stack/stack.go:639
|
||||
#: ./pkg/upstream/stack/stack.go:628
|
||||
#, c-format
|
||||
msgid "logs: %s"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/upstream/stack/stack.go:641
|
||||
#: ./pkg/upstream/stack/stack.go:630
|
||||
msgid "logs: no log output received from deployment"
|
||||
msgstr ""
|
||||
|
||||
@ -3605,7 +3605,7 @@ msgstr ""
|
||||
msgid "name a servce 'app'"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/ui/deploy.go:85
|
||||
#: ./pkg/ui/deploy.go:96
|
||||
#, c-format
|
||||
msgid "name: %s, "
|
||||
msgstr ""
|
||||
@ -3614,12 +3614,12 @@ msgstr ""
|
||||
msgid "need 3 or 4 arguments"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/upstream/stack/stack.go:352
|
||||
#: ./pkg/upstream/stack/stack.go:348
|
||||
#, c-format
|
||||
msgid "network %q is declared as external, but could not be found. You need to create a swarm-scoped network before the stack is deployed, which you can do by running this on the server: docker network create -d overlay proxy"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/upstream/stack/stack.go:356
|
||||
#: ./pkg/upstream/stack/stack.go:352
|
||||
#, c-format
|
||||
msgid "network %q is declared as external, but it is not in the right scope: %q instead of \"swarm\""
|
||||
msgstr ""
|
||||
@ -3842,11 +3842,11 @@ msgstr ""
|
||||
msgid "no volumes to remove"
|
||||
msgstr ""
|
||||
|
||||
#: ./cli/app/deploy.go:419 ./cli/app/rollback.go:368 ./cli/app/upgrade.go:477
|
||||
#: ./cli/app/deploy.go:418 ./cli/app/rollback.go:367 ./cli/app/upgrade.go:476
|
||||
msgid "no-converge-checks"
|
||||
msgstr ""
|
||||
|
||||
#: ./cli/app/deploy.go:411 ./cli/app/rollback.go:360 ./cli/app/upgrade.go:469
|
||||
#: ./cli/app/deploy.go:410 ./cli/app/rollback.go:359 ./cli/app/upgrade.go:468
|
||||
msgid "no-domain-checks"
|
||||
msgstr ""
|
||||
|
||||
@ -3902,7 +3902,7 @@ msgstr ""
|
||||
msgid "only show errors"
|
||||
msgstr ""
|
||||
|
||||
#: ./cli/app/upgrade.go:488
|
||||
#: ./cli/app/upgrade.go:487
|
||||
msgid "only show release notes"
|
||||
msgstr ""
|
||||
|
||||
@ -3933,22 +3933,22 @@ msgstr ""
|
||||
msgid "parsed following command arguments: %s"
|
||||
msgstr ""
|
||||
|
||||
#: ./cli/app/upgrade.go:345
|
||||
#: ./cli/app/upgrade.go:344
|
||||
#, c-format
|
||||
msgid "parsing chosen upgrade version failed: %s"
|
||||
msgstr ""
|
||||
|
||||
#: ./cli/app/upgrade.go:389
|
||||
#: ./cli/app/upgrade.go:388
|
||||
#, c-format
|
||||
msgid "parsing deployed version failed: %s"
|
||||
msgstr ""
|
||||
|
||||
#: ./cli/app/upgrade.go:350
|
||||
#: ./cli/app/upgrade.go:349
|
||||
#, c-format
|
||||
msgid "parsing deployment version failed: %s"
|
||||
msgstr ""
|
||||
|
||||
#: ./cli/app/upgrade.go:356 ./cli/app/upgrade.go:395
|
||||
#: ./cli/app/upgrade.go:355 ./cli/app/upgrade.go:394
|
||||
#, c-format
|
||||
msgid "parsing recipe version failed: %s"
|
||||
msgstr ""
|
||||
@ -3973,7 +3973,7 @@ msgstr ""
|
||||
msgid "pattern"
|
||||
msgstr ""
|
||||
|
||||
#: ./cli/app/deploy.go:406 ./cli/app/env.go:327 ./cli/app/remove.go:165 ./cli/app/rollback.go:355 ./cli/app/upgrade.go:464 ./cli/app/volume.go:219
|
||||
#: ./cli/app/deploy.go:405 ./cli/app/env.go:327 ./cli/app/remove.go:165 ./cli/app/rollback.go:354 ./cli/app/upgrade.go:463 ./cli/app/volume.go:219
|
||||
msgid "perform action without further prompt"
|
||||
msgstr ""
|
||||
|
||||
@ -3988,27 +3988,27 @@ msgstr ""
|
||||
msgid "please fix your synced label for %s and re-run this command"
|
||||
msgstr ""
|
||||
|
||||
#: ./cli/app/rollback.go:267
|
||||
#: ./cli/app/rollback.go:266
|
||||
#, c-format
|
||||
msgid "please select a downgrade (version: %s):"
|
||||
msgstr ""
|
||||
|
||||
#: ./cli/app/rollback.go:272
|
||||
#: ./cli/app/rollback.go:271
|
||||
#, c-format
|
||||
msgid "please select a downgrade (version: %s, chaos: %s):"
|
||||
msgstr ""
|
||||
|
||||
#: ./cli/app/upgrade.go:312
|
||||
#: ./cli/app/upgrade.go:311
|
||||
#, c-format
|
||||
msgid "please select an upgrade (version: %s):"
|
||||
msgstr ""
|
||||
|
||||
#: ./cli/app/upgrade.go:317
|
||||
#: ./cli/app/upgrade.go:316
|
||||
#, c-format
|
||||
msgid "please select an upgrade (version: %s, chaos: %s):"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/upstream/stack/stack.go:587
|
||||
#: ./pkg/upstream/stack/stack.go:576
|
||||
msgid "polling deployment status"
|
||||
msgstr ""
|
||||
|
||||
@ -4094,7 +4094,7 @@ msgstr ""
|
||||
#. with no spaces in between
|
||||
#. translators: `abra recipe` aliases. use a comma separated list of aliases
|
||||
#. with no spaces in between
|
||||
#: ./cli/app/backup.go:327 ./cli/app/list.go:300 ./cli/app/move.go:350 ./cli/app/run.go:23 ./cli/app/upgrade.go:486 ./cli/catalogue/catalogue.go:302 ./cli/recipe/recipe.go:12 ./cli/recipe/release.go:649 ./cli/recipe/sync.go:272
|
||||
#: ./cli/app/backup.go:327 ./cli/app/list.go:300 ./cli/app/move.go:350 ./cli/app/run.go:23 ./cli/app/upgrade.go:485 ./cli/catalogue/catalogue.go:302 ./cli/recipe/recipe.go:12 ./cli/recipe/release.go:649 ./cli/recipe/sync.go:272
|
||||
msgid "r"
|
||||
msgstr ""
|
||||
|
||||
@ -4147,7 +4147,7 @@ msgstr ""
|
||||
msgid "read v:%s k: %s"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/ui/deploy.go:86
|
||||
#: ./pkg/ui/deploy.go:97
|
||||
#, c-format
|
||||
msgid "reader: %v, "
|
||||
msgstr ""
|
||||
@ -4210,7 +4210,7 @@ msgstr ""
|
||||
msgid "release <recipe> [version] [flags]"
|
||||
msgstr ""
|
||||
|
||||
#: ./cli/app/upgrade.go:485
|
||||
#: ./cli/app/upgrade.go:484
|
||||
msgid "releasenotes"
|
||||
msgstr ""
|
||||
|
||||
@ -4376,7 +4376,7 @@ msgstr ""
|
||||
msgid "restart <domain> [[service] | --all-services] [flags]"
|
||||
msgstr ""
|
||||
|
||||
#: ./cli/app/restart.go:172
|
||||
#: ./cli/app/restart.go:171
|
||||
msgid "restart all services"
|
||||
msgstr ""
|
||||
|
||||
@ -4441,7 +4441,7 @@ msgstr ""
|
||||
msgid "retrieved versions from local recipe repository"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/upstream/stack/stack.go:468
|
||||
#: ./pkg/upstream/stack/stack.go:464
|
||||
#, c-format
|
||||
msgid "retrieving docker auth token: failed create docker cli: %s"
|
||||
msgstr ""
|
||||
@ -4475,7 +4475,7 @@ msgstr ""
|
||||
msgid "rollback <domain> [version] [flags]"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/ui/deploy.go:336
|
||||
#: ./pkg/ui/deploy.go:398
|
||||
msgid "rolled back"
|
||||
msgstr ""
|
||||
|
||||
@ -4514,7 +4514,7 @@ msgstr ""
|
||||
msgid "run command locally"
|
||||
msgstr ""
|
||||
|
||||
#: ./cli/app/deploy.go:271 ./cli/app/upgrade.go:293
|
||||
#: ./cli/app/deploy.go:270 ./cli/app/upgrade.go:292
|
||||
#, c-format
|
||||
msgid "run the following post-deploy commands: %s"
|
||||
msgstr ""
|
||||
@ -4599,12 +4599,12 @@ msgstr ""
|
||||
msgid "secret not found: %s"
|
||||
msgstr ""
|
||||
|
||||
#: ./cli/app/deploy.go:340
|
||||
#: ./cli/app/deploy.go:339
|
||||
#, c-format
|
||||
msgid "secret not generated: %s"
|
||||
msgstr ""
|
||||
|
||||
#: ./cli/app/deploy.go:338
|
||||
#: ./cli/app/deploy.go:337
|
||||
#, c-format
|
||||
msgid "secret not inserted (#generate=false): %s"
|
||||
msgstr ""
|
||||
@ -4651,19 +4651,9 @@ msgstr ""
|
||||
msgid "server doesn't exist?"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/client/client.go:54
|
||||
#, c-format
|
||||
msgid "server missing context, context creation failed: %s"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/client/client.go:59
|
||||
#, c-format
|
||||
msgid "server missing context, run \"abra server add %s\"?"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/client/client.go:48
|
||||
#, c-format
|
||||
msgid "server missing, run \"abra server add %s\"?"
|
||||
msgid "server missing context, run \"abra server add %s\"?"
|
||||
msgstr ""
|
||||
|
||||
#: ./cli/server/add.go:148
|
||||
@ -4738,7 +4728,7 @@ msgstr ""
|
||||
msgid "severity"
|
||||
msgstr ""
|
||||
|
||||
#: ./cli/app/deploy.go:438 ./cli/app/rollback.go:379 ./cli/app/upgrade.go:496
|
||||
#: ./cli/app/deploy.go:437 ./cli/app/rollback.go:378 ./cli/app/upgrade.go:495
|
||||
msgid "show all configs & images, including unchanged ones"
|
||||
msgstr ""
|
||||
|
||||
@ -4762,7 +4752,7 @@ msgstr ""
|
||||
msgid "show debug messages"
|
||||
msgstr ""
|
||||
|
||||
#: ./cli/app/deploy.go:435 ./cli/app/rollback.go:376 ./cli/app/upgrade.go:493
|
||||
#: ./cli/app/deploy.go:434 ./cli/app/rollback.go:375 ./cli/app/upgrade.go:492
|
||||
msgid "show-unchanged"
|
||||
msgstr ""
|
||||
|
||||
@ -4806,7 +4796,7 @@ msgstr ""
|
||||
msgid "skipping as requested, undeploy still in progress 🟠"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/upstream/stack/stack.go:309
|
||||
#: ./pkg/upstream/stack/stack.go:306
|
||||
msgid "skipping converge logic checks"
|
||||
msgstr ""
|
||||
|
||||
@ -4828,12 +4818,12 @@ msgstr ""
|
||||
msgid "skipping secret (because it already exists) on %s: %s"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/app/app.go:697
|
||||
#: ./pkg/app/app.go:692
|
||||
#, c-format
|
||||
msgid "skipping version %s write as already exists in %s.env"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/app/app.go:691
|
||||
#: ./pkg/app/app.go:686
|
||||
#, c-format
|
||||
msgid "skipping writing version %s because dry run"
|
||||
msgstr ""
|
||||
@ -4900,7 +4890,7 @@ msgstr ""
|
||||
msgid "status"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/ui/deploy.go:88
|
||||
#: ./pkg/ui/deploy.go:99
|
||||
#, c-format
|
||||
msgid "status: %s}"
|
||||
msgstr ""
|
||||
@ -4922,7 +4912,7 @@ msgstr ""
|
||||
msgid "stripped %s to %s for parsing"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/ui/deploy.go:333
|
||||
#: ./pkg/ui/deploy.go:395
|
||||
msgid "succeeded"
|
||||
msgstr ""
|
||||
|
||||
@ -4941,12 +4931,12 @@ msgstr ""
|
||||
msgid "successfully created %s"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/client/client.go:120
|
||||
#: ./pkg/client/client.go:111
|
||||
#, c-format
|
||||
msgid "swarm mode not enabled on %s?"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/client/client.go:123
|
||||
#: ./pkg/client/client.go:114
|
||||
msgid "swarm mode not enabled on local server?"
|
||||
msgstr ""
|
||||
|
||||
@ -5022,7 +5012,7 @@ msgstr ""
|
||||
msgid "timeout label: %s"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/upstream/stack/remove.go:29 ./pkg/upstream/stack/stack.go:210
|
||||
#: ./pkg/upstream/stack/remove.go:29 ./pkg/upstream/stack/stack.go:209
|
||||
#, c-format
|
||||
msgid "timeout: set to %d second(s)"
|
||||
msgstr ""
|
||||
@ -5445,6 +5435,11 @@ msgstr ""
|
||||
msgid "unknown server %s, run \"abra server add %s\"?"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/client/client.go:51
|
||||
#, c-format
|
||||
msgid "unknown server, run \"abra server add %s\"?"
|
||||
msgstr ""
|
||||
|
||||
#: ./cli/app/cp.go:259
|
||||
#, c-format
|
||||
msgid "untar: %s"
|
||||
@ -5456,7 +5451,7 @@ msgstr ""
|
||||
msgid "up"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/upstream/stack/stack.go:477
|
||||
#: ./pkg/upstream/stack/stack.go:473
|
||||
#, c-format
|
||||
msgid "updating %s"
|
||||
msgstr ""
|
||||
@ -5568,7 +5563,7 @@ msgstr ""
|
||||
msgid "version"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/app/app.go:695
|
||||
#: ./pkg/app/app.go:690
|
||||
#, c-format
|
||||
msgid "version %s saved to %s.env"
|
||||
msgstr ""
|
||||
@ -5587,10 +5582,6 @@ msgstr ""
|
||||
msgid "version for abra"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/app/app.go:637
|
||||
msgid "version is unknown, skipping env write"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/recipe/recipe.go:130
|
||||
#, c-format
|
||||
msgid "version seems invalid: %s"
|
||||
@ -5601,27 +5592,27 @@ msgstr ""
|
||||
msgid "version wiped from %s.env"
|
||||
msgstr ""
|
||||
|
||||
#: ./cli/app/deploy.go:354
|
||||
#: ./cli/app/deploy.go:353
|
||||
#, c-format
|
||||
msgid "version: taking chaos version: %s"
|
||||
msgstr ""
|
||||
|
||||
#: ./cli/app/deploy.go:380
|
||||
#: ./cli/app/deploy.go:379
|
||||
#, c-format
|
||||
msgid "version: taking deployed version: %s"
|
||||
msgstr ""
|
||||
|
||||
#: ./cli/app/deploy.go:385
|
||||
#: ./cli/app/deploy.go:384
|
||||
#, c-format
|
||||
msgid "version: taking new recipe version: %s"
|
||||
msgstr ""
|
||||
|
||||
#: ./cli/app/deploy.go:374
|
||||
#: ./cli/app/deploy.go:373
|
||||
#, c-format
|
||||
msgid "version: taking version from .env file: %s"
|
||||
msgstr ""
|
||||
|
||||
#: ./cli/app/deploy.go:360
|
||||
#: ./cli/app/deploy.go:359
|
||||
#, c-format
|
||||
msgid "version: taking version from cli arg: %s"
|
||||
msgstr ""
|
||||
@ -5681,22 +5672,22 @@ msgstr ""
|
||||
msgid "volumes pruned: %d; space reclaimed: %s"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/upstream/stack/stack.go:625
|
||||
#: ./pkg/upstream/stack/stack.go:614
|
||||
#, c-format
|
||||
msgid "waitOnServices: error creating log dir: %s"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/upstream/stack/stack.go:630
|
||||
#: ./pkg/upstream/stack/stack.go:619
|
||||
#, c-format
|
||||
msgid "waitOnServices: error opening file: %s"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/upstream/stack/stack.go:596
|
||||
#: ./pkg/upstream/stack/stack.go:585
|
||||
#, c-format
|
||||
msgid "waitOnServices: error running TUI: %s"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/upstream/stack/stack.go:636
|
||||
#: ./pkg/upstream/stack/stack.go:625
|
||||
#, c-format
|
||||
msgid "waitOnServices: writeFile: %s"
|
||||
msgstr ""
|
||||
@ -5739,12 +5730,12 @@ msgstr ""
|
||||
msgid "wire up healthchecks"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/ui/deploy.go:87
|
||||
#: ./pkg/ui/deploy.go:98
|
||||
#, c-format
|
||||
msgid "writer: %v, "
|
||||
msgstr ""
|
||||
|
||||
#: ./cli/app/deploy.go:278 ./cli/app/new.go:241 ./cli/app/rollback.go:256 ./cli/app/undeploy.go:120 ./cli/app/upgrade.go:301
|
||||
#: ./cli/app/deploy.go:277 ./cli/app/new.go:241 ./cli/app/rollback.go:255 ./cli/app/undeploy.go:120 ./cli/app/upgrade.go:300
|
||||
#, c-format
|
||||
msgid "writing recipe version failed: %s"
|
||||
msgstr ""
|
||||
@ -5773,7 +5764,7 @@ msgstr ""
|
||||
msgid "z"
|
||||
msgstr ""
|
||||
|
||||
#: ./pkg/ui/deploy.go:82
|
||||
#: ./pkg/ui/deploy.go:93
|
||||
#, c-format
|
||||
msgid "{decoder: %v, "
|
||||
msgstr ""
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -11,6 +11,7 @@ import (
|
||||
"coopcloud.tech/abra/pkg/formatter"
|
||||
"coopcloud.tech/abra/pkg/i18n"
|
||||
"coopcloud.tech/abra/pkg/logs"
|
||||
"github.com/charmbracelet/bubbles/viewport"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/docker/cli/cli/command/service/progress"
|
||||
containerTypes "github.com/docker/docker/api/types/container"
|
||||
@ -41,6 +42,12 @@ type ServiceMeta struct {
|
||||
ID string
|
||||
}
|
||||
|
||||
const (
|
||||
statusMode = iota
|
||||
logsMode = iota
|
||||
errorsMode = iota
|
||||
)
|
||||
|
||||
type Model struct {
|
||||
appName string
|
||||
cl *dockerClient.Client
|
||||
@ -49,6 +56,10 @@ type Model struct {
|
||||
timeout time.Duration
|
||||
width int
|
||||
filters filters.Args
|
||||
mode int
|
||||
|
||||
logsViewport viewport.Model
|
||||
logsViewportReady bool
|
||||
|
||||
Streams *[]stream
|
||||
Logs *[]string
|
||||
@ -236,7 +247,10 @@ func deployTimeout(m Model) tea.Msg {
|
||||
}
|
||||
|
||||
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
var cmds []tea.Cmd
|
||||
var (
|
||||
cmd tea.Cmd
|
||||
cmds []tea.Cmd
|
||||
)
|
||||
|
||||
switch msg := msg.(type) {
|
||||
case tea.KeyMsg:
|
||||
@ -244,11 +258,25 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
case "ctrl+c", "q":
|
||||
m.Quit = true
|
||||
return m, tea.Quit
|
||||
case "s":
|
||||
m.mode = statusMode
|
||||
case "l":
|
||||
m.mode = logsMode
|
||||
case "e":
|
||||
m.mode = errorsMode
|
||||
}
|
||||
|
||||
case tea.WindowSizeMsg:
|
||||
m.width = msg.Width
|
||||
|
||||
if !m.logsViewportReady {
|
||||
m.logsViewport = viewport.New(msg.Width, 20)
|
||||
m.logsViewportReady = true
|
||||
} else {
|
||||
m.logsViewport.Width = msg.Width
|
||||
m.logsViewport.Height = 20
|
||||
}
|
||||
|
||||
case progressCompleteMsg:
|
||||
if msg.failed {
|
||||
m.Failed = true
|
||||
@ -256,9 +284,9 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
|
||||
m.count += 1
|
||||
|
||||
if m.complete() {
|
||||
return m, tea.Quit
|
||||
}
|
||||
// if m.complete() {
|
||||
// return m, tea.Quit
|
||||
// }
|
||||
|
||||
case timeoutMsg:
|
||||
m.TimedOut = true
|
||||
@ -318,12 +346,46 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
)
|
||||
}
|
||||
|
||||
m.logsViewport, cmd = m.logsViewport.Update(msg)
|
||||
cmds = append(cmds, cmd)
|
||||
|
||||
return m, tea.Batch(cmds...)
|
||||
}
|
||||
|
||||
func (m Model) View() string {
|
||||
body := strings.Builder{}
|
||||
|
||||
body.WriteString("menu: [s]tatus [l]ogs [e]rrors\n")
|
||||
|
||||
var res string
|
||||
switch {
|
||||
case m.mode == statusMode:
|
||||
res = statusView(m)
|
||||
case m.mode == logsMode:
|
||||
res = logsView(m)
|
||||
}
|
||||
|
||||
return body.String() + res
|
||||
|
||||
}
|
||||
|
||||
func logsView(m Model) string {
|
||||
body := strings.Builder{}
|
||||
m.logsViewport.SetContent(strings.Join(*m.Logs, "\n"))
|
||||
m.logsViewport.GotoBottom()
|
||||
body.WriteString(m.logsViewport.View())
|
||||
return body.String()
|
||||
}
|
||||
|
||||
func errorsView(m Model) string {
|
||||
body := strings.Builder{}
|
||||
body.WriteString("ERRORS COMING SOON")
|
||||
return body.String()
|
||||
}
|
||||
|
||||
func statusView(m Model) string {
|
||||
body := strings.Builder{}
|
||||
|
||||
for _, stream := range *m.Streams {
|
||||
split := strings.Split(stream.Name, "_")
|
||||
short := split[len(split)-1]
|
||||
|
||||
@ -19,7 +19,7 @@ import (
|
||||
"coopcloud.tech/abra/pkg/ui"
|
||||
"coopcloud.tech/abra/pkg/upstream/convert"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/formatter"
|
||||
"github.com/docker/cli/cli/command/stack/formatter"
|
||||
composetypes "github.com/docker/cli/cli/compose/types"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
@ -201,7 +201,6 @@ func RunDeploy(
|
||||
appName string,
|
||||
serverName string,
|
||||
dontWait bool,
|
||||
noInput bool,
|
||||
filters filters.Args,
|
||||
) error {
|
||||
log.Info(i18n.G("initialising deployment"))
|
||||
@ -227,7 +226,6 @@ func RunDeploy(
|
||||
appName,
|
||||
serverName,
|
||||
dontWait,
|
||||
noInput,
|
||||
filters,
|
||||
)
|
||||
}
|
||||
@ -250,7 +248,6 @@ func deployCompose(
|
||||
appName string,
|
||||
serverName string,
|
||||
dontWait bool,
|
||||
noInput bool,
|
||||
filters filters.Args,
|
||||
) error {
|
||||
namespace := convert.NewNamespace(opts.Namespace)
|
||||
@ -314,7 +311,6 @@ func deployCompose(
|
||||
Services: serviceIDs,
|
||||
AppName: appName,
|
||||
ServerName: serverName,
|
||||
NoInput: noInput,
|
||||
Filters: filters,
|
||||
}
|
||||
|
||||
@ -565,7 +561,6 @@ func timestamp() string {
|
||||
type WaitOpts struct {
|
||||
AppName string
|
||||
Filters filters.Args
|
||||
NoInput bool
|
||||
NoLog bool
|
||||
Quiet bool
|
||||
ServerName string
|
||||
@ -575,13 +570,7 @@ type WaitOpts struct {
|
||||
func WaitOnServices(ctx context.Context, cl *dockerClient.Client, opts WaitOpts) error {
|
||||
timeout := time.Duration(WaitTimeout) * time.Second
|
||||
model := ui.DeployInitialModel(ctx, cl, opts.Services, opts.AppName, timeout, opts.Filters)
|
||||
|
||||
var tui *tea.Program
|
||||
if opts.NoInput {
|
||||
tui = tea.NewProgram(model, tea.WithoutRenderer(), tea.WithInput(nil))
|
||||
} else {
|
||||
tui = tea.NewProgram(model)
|
||||
}
|
||||
tui := tea.NewProgram(model)
|
||||
|
||||
if !opts.Quiet {
|
||||
log.Info(i18n.G("polling deployment status"))
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
ABRA_VERSION="0.12.0-beta"
|
||||
ABRA_VERSION="0.11.0-beta"
|
||||
ABRA_RELEASE_URL="https://git.coopcloud.tech/api/v1/repos/toolshed/abra/releases/tags/$ABRA_VERSION"
|
||||
RC_VERSION="0.12.0-beta"
|
||||
RC_VERSION="0.11.0-beta"
|
||||
RC_VERSION_URL="https://git.coopcloud.tech/api/v1/repos/toolshed/abra/releases/tags/$RC_VERSION"
|
||||
|
||||
for arg in "$@"; do
|
||||
@ -14,15 +14,15 @@ done
|
||||
|
||||
function show_banner {
|
||||
echo ""
|
||||
echo " ____ ____ _ _ "
|
||||
echo " / ___|___ ___ _ __ / ___| | ___ _ _ __| |"
|
||||
echo " | | / _ \ ___ / _ \| '_ \ | | | |/ _ \| | | |/ _' |"
|
||||
echo " | |__| (_) |___| (_) | |_) | | |___| | (_) | |_| | (_| |"
|
||||
echo " \____\___/ \___/| .__/ \____|_|\___/ \__,_|\__,_|"
|
||||
echo " |_|"
|
||||
echo " ____ ____ _ _ "
|
||||
echo " / ___|___ ___ _ __ / ___| | ___ _ _ __| |"
|
||||
echo " | | / _ \ _____ / _ \| '_ \ | | | |/ _ \| | | |/ _' |"
|
||||
echo " | |__| (_) |_____| (_) | |_) | | |___| | (_) | |_| | (_| |"
|
||||
echo " \____\___/ \___/| .__/ \____|_|\___/ \__,_|\__,_|"
|
||||
echo " |_|"
|
||||
echo ""
|
||||
echo ""
|
||||
echo " === Public interest infrastructure === "
|
||||
echo " === Public interest infrastructure === "
|
||||
echo ""
|
||||
echo ""
|
||||
}
|
||||
|
||||
@ -543,7 +543,7 @@ teardown(){
|
||||
|
||||
# bats test_tags=slow
|
||||
@test "ignore timeout when not present in env" {
|
||||
run $ABRA app deploy "$TEST_APP_DOMAIN" --no-input --no-converge-checks
|
||||
run $ABRA app deploy "$TEST_APP_DOMAIN" --no-input --no-converge-checks --debug
|
||||
assert_success
|
||||
refute_output --partial "timeout: set to"
|
||||
}
|
||||
@ -554,7 +554,6 @@ teardown(){
|
||||
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
|
||||
assert_success
|
||||
|
||||
# NOTE(d1}: --debug required
|
||||
run $ABRA app deploy "$TEST_APP_DOMAIN" --no-input --no-converge-checks --debug
|
||||
assert_success
|
||||
assert_output --partial "timeout: set to 120"
|
||||
@ -580,25 +579,16 @@ teardown(){
|
||||
}
|
||||
|
||||
# bats test_tags=slow
|
||||
@test "re-deploy updates existing env vars" {
|
||||
run $ABRA app deploy "$TEST_APP_DOMAIN" --no-input
|
||||
@test "manually created server without context bails gracefully" {
|
||||
run mkdir -p "$ABRA_DIR/servers/default2"
|
||||
assert_success
|
||||
assert_exists "$ABRA_DIR/servers/default2"
|
||||
|
||||
run docker inspect --format='{{range .Config.Env}}{{println .}}{{end}}' \
|
||||
$(docker ps -f name="$TEST_APP_DOMAIN_$TEST_SERVER" -q)
|
||||
run cp "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env" "$ABRA_DIR/servers/default2/$TEST_APP_DOMAIN_2.env"
|
||||
assert_success
|
||||
assert_output --partial "WITH_COMMENT=foo"
|
||||
assert_exists "$ABRA_DIR/servers/default2/$TEST_APP_DOMAIN_2.env"
|
||||
|
||||
run sed -i 's/WITH_COMMENT=foo/WITH_COMMENT=bar/g' \
|
||||
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
|
||||
assert_success
|
||||
|
||||
run $ABRA app deploy "$TEST_APP_DOMAIN" --no-input --force
|
||||
assert_success
|
||||
|
||||
run docker inspect --format='{{range .Config.Env}}{{println .}}{{end}}' \
|
||||
$(docker ps -f name="$TEST_APP_DOMAIN_$TEST_SERVER" -q)
|
||||
assert_success
|
||||
refute_output --partial "WITH_COMMENT=foo"
|
||||
assert_output --partial "WITH_COMMENT=bar"
|
||||
run $ABRA app deploy "$TEST_APP_DOMAIN_2" --no-input --no-converge-checks
|
||||
assert_failure
|
||||
assert_output --partial "server missing context"
|
||||
}
|
||||
|
||||
@ -68,13 +68,6 @@ teardown(){
|
||||
assert_success
|
||||
}
|
||||
|
||||
@test "domain shown with https" {
|
||||
run $ABRA app deploy "$TEST_APP_DOMAIN" \
|
||||
--no-input --no-converge-checks
|
||||
assert_success
|
||||
assert_output --partial "https://$TEST_DOMAIN"
|
||||
}
|
||||
|
||||
# bats test_tags=slow
|
||||
@test "show changed config version on re-deploy" {
|
||||
run $ABRA app deploy "$TEST_APP_DOMAIN" \
|
||||
|
||||
@ -160,6 +160,23 @@ teardown(){
|
||||
assert_not_exists "$ABRA_DIR/servers/foo.com"
|
||||
}
|
||||
|
||||
@test "list with status skips unknown servers" {
|
||||
if [[ ! -d "$ABRA_DIR/servers/foo" ]]; then
|
||||
run mkdir -p "$ABRA_DIR/servers/foo"
|
||||
assert_success
|
||||
assert_exists "$ABRA_DIR/servers/foo"
|
||||
|
||||
run cp "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env" \
|
||||
"$ABRA_DIR/servers/foo/$TEST_APP_DOMAIN.env"
|
||||
assert_success
|
||||
assert_exists "$ABRA_DIR/servers/foo/$TEST_APP_DOMAIN.env"
|
||||
fi
|
||||
|
||||
run $ABRA app ls --status
|
||||
assert_success
|
||||
assert_output --partial "server missing context"
|
||||
}
|
||||
|
||||
# bats test_tags=slow
|
||||
@test "list does not fail if missing .env" {
|
||||
_deploy_app
|
||||
|
||||
@ -19,10 +19,6 @@ setup(){
|
||||
teardown(){
|
||||
_reset_recipe
|
||||
_reset_tags
|
||||
if [[ -d "$ABRA_DIR/recipes/foobar" ]]; then
|
||||
run rm -rf "$ABRA_DIR/recipes/foobar"
|
||||
assert_success
|
||||
fi
|
||||
}
|
||||
|
||||
@test "validate recipe argument" {
|
||||
@ -130,71 +126,3 @@ teardown(){
|
||||
assert_line --index 0 --partial 'synced label'
|
||||
refute_line --index 1 --partial 'synced label'
|
||||
}
|
||||
|
||||
@test "sync with no tags or previous release" {
|
||||
_remove_tags
|
||||
|
||||
run $ABRA recipe upgrade "$TEST_RECIPE" --no-input --patch
|
||||
assert_success
|
||||
|
||||
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" diff
|
||||
assert_success
|
||||
assert_output --partial 'image: nginx:1.21.6'
|
||||
|
||||
# NOTE(d1): ensure the latest tag is the one we expect
|
||||
_remove_tags
|
||||
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" tag \
|
||||
-a "0.3.0+1.21.0" -m "fake: 0.3.0+1.21.0"
|
||||
assert_success
|
||||
|
||||
run $ABRA recipe sync "$TEST_RECIPE" --no-input --patch
|
||||
assert_success
|
||||
|
||||
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" diff
|
||||
assert_success
|
||||
assert_output --regexp 'coop-cloud\.\$\{STACK_NAME\}\.version=0\.3\.1\+1\.2.*'
|
||||
}
|
||||
|
||||
@test "sync recipe without input fails with prompt" {
|
||||
run $ABRA recipe new foobar
|
||||
assert_success
|
||||
assert_exists "$ABRA_DIR/recipes/foobar"
|
||||
|
||||
run $ABRA recipe sync foobar --no-input --patch
|
||||
assert_failure
|
||||
assert_output --partial "input required for initial version"
|
||||
}
|
||||
|
||||
@test "sync new recipe: development release" {
|
||||
run $ABRA recipe new foobar
|
||||
assert_success
|
||||
assert_exists "$ABRA_DIR/recipes/foobar"
|
||||
|
||||
run bash -c "echo 0.1.0 | $ABRA recipe sync foobar --patch"
|
||||
assert_success
|
||||
assert_output --regexp 'coop-cloud\.\$\{STACK_NAME\}\.version=0\.1\.0\+1\.2.*'
|
||||
}
|
||||
|
||||
@test "sync new recipe: public release" {
|
||||
run $ABRA recipe new foobar
|
||||
assert_success
|
||||
assert_exists "$ABRA_DIR/recipes/foobar"
|
||||
|
||||
run bash -c "echo 1.0.0 | $ABRA recipe sync foobar --patch"
|
||||
assert_success
|
||||
assert_output --regexp 'coop-cloud\.\$\{STACK_NAME\}\.version=1\.0\.0\+1\.2.*'
|
||||
}
|
||||
|
||||
@test "sync newly created recipe with no version label" {
|
||||
run $ABRA recipe new foobar
|
||||
assert_success
|
||||
assert_exists "$ABRA_DIR/recipes/foobar"
|
||||
|
||||
run sed -i 's/- "coop-cloud.${STACK_NAME}.version="/#- "coop-cloud.${STACK_NAME}.version="/g' \
|
||||
"$ABRA_DIR/recipes/foobar/compose.yml"
|
||||
assert_success
|
||||
|
||||
run bash -c "echo 0.1.0 | $ABRA recipe sync foobar --patch"
|
||||
assert_failure
|
||||
assert_output --partial "automagic insertion not supported yet"
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 Matt Sherman
|
||||
Copyright (c) 2020-2023 Charmbracelet, Inc
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
140
vendor/github.com/charmbracelet/bubbles/key/key.go
generated
vendored
Normal file
140
vendor/github.com/charmbracelet/bubbles/key/key.go
generated
vendored
Normal file
@ -0,0 +1,140 @@
|
||||
// Package key provides some types and functions for generating user-definable
|
||||
// keymappings useful in Bubble Tea components. There are a few different ways
|
||||
// you can define a keymapping with this package. Here's one example:
|
||||
//
|
||||
// type KeyMap struct {
|
||||
// Up key.Binding
|
||||
// Down key.Binding
|
||||
// }
|
||||
//
|
||||
// var DefaultKeyMap = KeyMap{
|
||||
// Up: key.NewBinding(
|
||||
// key.WithKeys("k", "up"), // actual keybindings
|
||||
// key.WithHelp("↑/k", "move up"), // corresponding help text
|
||||
// ),
|
||||
// Down: key.NewBinding(
|
||||
// key.WithKeys("j", "down"),
|
||||
// key.WithHelp("↓/j", "move down"),
|
||||
// ),
|
||||
// }
|
||||
//
|
||||
// func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
// switch msg := msg.(type) {
|
||||
// case tea.KeyMsg:
|
||||
// switch {
|
||||
// case key.Matches(msg, DefaultKeyMap.Up):
|
||||
// // The user pressed up
|
||||
// case key.Matches(msg, DefaultKeyMap.Down):
|
||||
// // The user pressed down
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // ...
|
||||
// }
|
||||
//
|
||||
// The help information, which is not used in the example above, can be used
|
||||
// to render help text for keystrokes in your views.
|
||||
package key
|
||||
|
||||
import "fmt"
|
||||
|
||||
// Binding describes a set of keybindings and, optionally, their associated
|
||||
// help text.
|
||||
type Binding struct {
|
||||
keys []string
|
||||
help Help
|
||||
disabled bool
|
||||
}
|
||||
|
||||
// BindingOpt is an initialization option for a keybinding. It's used as an
|
||||
// argument to NewBinding.
|
||||
type BindingOpt func(*Binding)
|
||||
|
||||
// NewBinding returns a new keybinding from a set of BindingOpt options.
|
||||
func NewBinding(opts ...BindingOpt) Binding {
|
||||
b := &Binding{}
|
||||
for _, opt := range opts {
|
||||
opt(b)
|
||||
}
|
||||
return *b
|
||||
}
|
||||
|
||||
// WithKeys initializes a keybinding with the given keystrokes.
|
||||
func WithKeys(keys ...string) BindingOpt {
|
||||
return func(b *Binding) {
|
||||
b.keys = keys
|
||||
}
|
||||
}
|
||||
|
||||
// WithHelp initializes a keybinding with the given help text.
|
||||
func WithHelp(key, desc string) BindingOpt {
|
||||
return func(b *Binding) {
|
||||
b.help = Help{Key: key, Desc: desc}
|
||||
}
|
||||
}
|
||||
|
||||
// WithDisabled initializes a disabled keybinding.
|
||||
func WithDisabled() BindingOpt {
|
||||
return func(b *Binding) {
|
||||
b.disabled = true
|
||||
}
|
||||
}
|
||||
|
||||
// SetKeys sets the keys for the keybinding.
|
||||
func (b *Binding) SetKeys(keys ...string) {
|
||||
b.keys = keys
|
||||
}
|
||||
|
||||
// Keys returns the keys for the keybinding.
|
||||
func (b Binding) Keys() []string {
|
||||
return b.keys
|
||||
}
|
||||
|
||||
// SetHelp sets the help text for the keybinding.
|
||||
func (b *Binding) SetHelp(key, desc string) {
|
||||
b.help = Help{Key: key, Desc: desc}
|
||||
}
|
||||
|
||||
// Help returns the Help information for the keybinding.
|
||||
func (b Binding) Help() Help {
|
||||
return b.help
|
||||
}
|
||||
|
||||
// Enabled returns whether or not the keybinding is enabled. Disabled
|
||||
// keybindings won't be activated and won't show up in help. Keybindings are
|
||||
// enabled by default.
|
||||
func (b Binding) Enabled() bool {
|
||||
return !b.disabled && b.keys != nil
|
||||
}
|
||||
|
||||
// SetEnabled enables or disables the keybinding.
|
||||
func (b *Binding) SetEnabled(v bool) {
|
||||
b.disabled = !v
|
||||
}
|
||||
|
||||
// Unbind removes the keys and help from this binding, effectively nullifying
|
||||
// it. This is a step beyond disabling it, since applications can enable
|
||||
// or disable key bindings based on application state.
|
||||
func (b *Binding) Unbind() {
|
||||
b.keys = nil
|
||||
b.help = Help{}
|
||||
}
|
||||
|
||||
// Help is help information for a given keybinding.
|
||||
type Help struct {
|
||||
Key string
|
||||
Desc string
|
||||
}
|
||||
|
||||
// Matches checks if the given key matches the given bindings.
|
||||
func Matches[Key fmt.Stringer](k Key, b ...Binding) bool {
|
||||
keys := k.String()
|
||||
for _, binding := range b {
|
||||
for _, v := range binding.keys {
|
||||
if keys == v && binding.Enabled() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
60
vendor/github.com/charmbracelet/bubbles/viewport/keymap.go
generated
vendored
Normal file
60
vendor/github.com/charmbracelet/bubbles/viewport/keymap.go
generated
vendored
Normal file
@ -0,0 +1,60 @@
|
||||
// Package viewport provides a component for rendering a viewport in a Bubble
|
||||
// Tea.
|
||||
package viewport
|
||||
|
||||
import "github.com/charmbracelet/bubbles/key"
|
||||
|
||||
const spacebar = " "
|
||||
|
||||
// KeyMap defines the keybindings for the viewport. Note that you don't
|
||||
// necessary need to use keybindings at all; the viewport can be controlled
|
||||
// programmatically with methods like Model.LineDown(1). See the GoDocs for
|
||||
// details.
|
||||
type KeyMap struct {
|
||||
PageDown key.Binding
|
||||
PageUp key.Binding
|
||||
HalfPageUp key.Binding
|
||||
HalfPageDown key.Binding
|
||||
Down key.Binding
|
||||
Up key.Binding
|
||||
Left key.Binding
|
||||
Right key.Binding
|
||||
}
|
||||
|
||||
// DefaultKeyMap returns a set of pager-like default keybindings.
|
||||
func DefaultKeyMap() KeyMap {
|
||||
return KeyMap{
|
||||
PageDown: key.NewBinding(
|
||||
key.WithKeys("pgdown", spacebar, "f"),
|
||||
key.WithHelp("f/pgdn", "page down"),
|
||||
),
|
||||
PageUp: key.NewBinding(
|
||||
key.WithKeys("pgup", "b"),
|
||||
key.WithHelp("b/pgup", "page up"),
|
||||
),
|
||||
HalfPageUp: key.NewBinding(
|
||||
key.WithKeys("u", "ctrl+u"),
|
||||
key.WithHelp("u", "½ page up"),
|
||||
),
|
||||
HalfPageDown: key.NewBinding(
|
||||
key.WithKeys("d", "ctrl+d"),
|
||||
key.WithHelp("d", "½ page down"),
|
||||
),
|
||||
Up: key.NewBinding(
|
||||
key.WithKeys("up", "k"),
|
||||
key.WithHelp("↑/k", "up"),
|
||||
),
|
||||
Down: key.NewBinding(
|
||||
key.WithKeys("down", "j"),
|
||||
key.WithHelp("↓/j", "down"),
|
||||
),
|
||||
Left: key.NewBinding(
|
||||
key.WithKeys("left", "h"),
|
||||
key.WithHelp("←/h", "move left"),
|
||||
),
|
||||
Right: key.NewBinding(
|
||||
key.WithKeys("right", "l"),
|
||||
key.WithHelp("→/l", "move right"),
|
||||
),
|
||||
}
|
||||
}
|
||||
544
vendor/github.com/charmbracelet/bubbles/viewport/viewport.go
generated
vendored
Normal file
544
vendor/github.com/charmbracelet/bubbles/viewport/viewport.go
generated
vendored
Normal file
@ -0,0 +1,544 @@
|
||||
package viewport
|
||||
|
||||
import (
|
||||
"math"
|
||||
"strings"
|
||||
|
||||
"github.com/charmbracelet/bubbles/key"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"github.com/charmbracelet/x/ansi"
|
||||
)
|
||||
|
||||
// New returns a new model with the given width and height as well as default
|
||||
// key mappings.
|
||||
func New(width, height int) (m Model) {
|
||||
m.Width = width
|
||||
m.Height = height
|
||||
m.setInitialValues()
|
||||
return m
|
||||
}
|
||||
|
||||
// Model is the Bubble Tea model for this viewport element.
|
||||
type Model struct {
|
||||
Width int
|
||||
Height int
|
||||
KeyMap KeyMap
|
||||
|
||||
// Whether or not to respond to the mouse. The mouse must be enabled in
|
||||
// Bubble Tea for this to work. For details, see the Bubble Tea docs.
|
||||
MouseWheelEnabled bool
|
||||
|
||||
// The number of lines the mouse wheel will scroll. By default, this is 3.
|
||||
MouseWheelDelta int
|
||||
|
||||
// YOffset is the vertical scroll position.
|
||||
YOffset int
|
||||
|
||||
// xOffset is the horizontal scroll position.
|
||||
xOffset int
|
||||
|
||||
// horizontalStep is the number of columns we move left or right during a
|
||||
// default horizontal scroll.
|
||||
horizontalStep int
|
||||
|
||||
// YPosition is the position of the viewport in relation to the terminal
|
||||
// window. It's used in high performance rendering only.
|
||||
YPosition int
|
||||
|
||||
// Style applies a lipgloss style to the viewport. Realistically, it's most
|
||||
// useful for setting borders, margins and padding.
|
||||
Style lipgloss.Style
|
||||
|
||||
// HighPerformanceRendering bypasses the normal Bubble Tea renderer to
|
||||
// provide higher performance rendering. Most of the time the normal Bubble
|
||||
// Tea rendering methods will suffice, but if you're passing content with
|
||||
// a lot of ANSI escape codes you may see improved rendering in certain
|
||||
// terminals with this enabled.
|
||||
//
|
||||
// This should only be used in program occupying the entire terminal,
|
||||
// which is usually via the alternate screen buffer.
|
||||
//
|
||||
// Deprecated: high performance rendering is now deprecated in Bubble Tea.
|
||||
HighPerformanceRendering bool
|
||||
|
||||
initialized bool
|
||||
lines []string
|
||||
longestLineWidth int
|
||||
}
|
||||
|
||||
func (m *Model) setInitialValues() {
|
||||
m.KeyMap = DefaultKeyMap()
|
||||
m.MouseWheelEnabled = true
|
||||
m.MouseWheelDelta = 3
|
||||
m.initialized = true
|
||||
}
|
||||
|
||||
// Init exists to satisfy the tea.Model interface for composability purposes.
|
||||
func (m Model) Init() tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
// AtTop returns whether or not the viewport is at the very top position.
|
||||
func (m Model) AtTop() bool {
|
||||
return m.YOffset <= 0
|
||||
}
|
||||
|
||||
// AtBottom returns whether or not the viewport is at or past the very bottom
|
||||
// position.
|
||||
func (m Model) AtBottom() bool {
|
||||
return m.YOffset >= m.maxYOffset()
|
||||
}
|
||||
|
||||
// PastBottom returns whether or not the viewport is scrolled beyond the last
|
||||
// line. This can happen when adjusting the viewport height.
|
||||
func (m Model) PastBottom() bool {
|
||||
return m.YOffset > m.maxYOffset()
|
||||
}
|
||||
|
||||
// ScrollPercent returns the amount scrolled as a float between 0 and 1.
|
||||
func (m Model) ScrollPercent() float64 {
|
||||
if m.Height >= len(m.lines) {
|
||||
return 1.0
|
||||
}
|
||||
y := float64(m.YOffset)
|
||||
h := float64(m.Height)
|
||||
t := float64(len(m.lines))
|
||||
v := y / (t - h)
|
||||
return math.Max(0.0, math.Min(1.0, v))
|
||||
}
|
||||
|
||||
// HorizontalScrollPercent returns the amount horizontally scrolled as a float
|
||||
// between 0 and 1.
|
||||
func (m Model) HorizontalScrollPercent() float64 {
|
||||
if m.xOffset >= m.longestLineWidth-m.Width {
|
||||
return 1.0
|
||||
}
|
||||
y := float64(m.xOffset)
|
||||
h := float64(m.Width)
|
||||
t := float64(m.longestLineWidth)
|
||||
v := y / (t - h)
|
||||
return math.Max(0.0, math.Min(1.0, v))
|
||||
}
|
||||
|
||||
// SetContent set the pager's text content.
|
||||
func (m *Model) SetContent(s string) {
|
||||
s = strings.ReplaceAll(s, "\r\n", "\n") // normalize line endings
|
||||
m.lines = strings.Split(s, "\n")
|
||||
m.longestLineWidth = findLongestLineWidth(m.lines)
|
||||
|
||||
if m.YOffset > len(m.lines)-1 {
|
||||
m.GotoBottom()
|
||||
}
|
||||
}
|
||||
|
||||
// maxYOffset returns the maximum possible value of the y-offset based on the
|
||||
// viewport's content and set height.
|
||||
func (m Model) maxYOffset() int {
|
||||
return max(0, len(m.lines)-m.Height+m.Style.GetVerticalFrameSize())
|
||||
}
|
||||
|
||||
// visibleLines returns the lines that should currently be visible in the
|
||||
// viewport.
|
||||
func (m Model) visibleLines() (lines []string) {
|
||||
h := m.Height - m.Style.GetVerticalFrameSize()
|
||||
w := m.Width - m.Style.GetHorizontalFrameSize()
|
||||
|
||||
if len(m.lines) > 0 {
|
||||
top := max(0, m.YOffset)
|
||||
bottom := clamp(m.YOffset+h, top, len(m.lines))
|
||||
lines = m.lines[top:bottom]
|
||||
}
|
||||
|
||||
if (m.xOffset == 0 && m.longestLineWidth <= w) || w == 0 {
|
||||
return lines
|
||||
}
|
||||
|
||||
cutLines := make([]string, len(lines))
|
||||
for i := range lines {
|
||||
cutLines[i] = ansi.Cut(lines[i], m.xOffset, m.xOffset+w)
|
||||
}
|
||||
return cutLines
|
||||
}
|
||||
|
||||
// scrollArea returns the scrollable boundaries for high performance rendering.
|
||||
//
|
||||
// Deprecated: high performance rendering is deprecated in Bubble Tea.
|
||||
func (m Model) scrollArea() (top, bottom int) {
|
||||
top = max(0, m.YPosition)
|
||||
bottom = max(top, top+m.Height)
|
||||
if top > 0 && bottom > top {
|
||||
bottom--
|
||||
}
|
||||
return top, bottom
|
||||
}
|
||||
|
||||
// SetYOffset sets the Y offset.
|
||||
func (m *Model) SetYOffset(n int) {
|
||||
m.YOffset = clamp(n, 0, m.maxYOffset())
|
||||
}
|
||||
|
||||
// ViewDown moves the view down by the number of lines in the viewport.
|
||||
// Basically, "page down".
|
||||
//
|
||||
// Deprecated: use [Model.PageDown] instead.
|
||||
func (m *Model) ViewDown() []string {
|
||||
return m.PageDown()
|
||||
}
|
||||
|
||||
// PageDown moves the view down by the number of lines in the viewport.
|
||||
func (m *Model) PageDown() []string {
|
||||
if m.AtBottom() {
|
||||
return nil
|
||||
}
|
||||
|
||||
return m.ScrollDown(m.Height)
|
||||
}
|
||||
|
||||
// ViewUp moves the view up by one height of the viewport.
|
||||
// Basically, "page up".
|
||||
//
|
||||
// Deprecated: use [Model.PageUp] instead.
|
||||
func (m *Model) ViewUp() []string {
|
||||
return m.PageUp()
|
||||
}
|
||||
|
||||
// PageUp moves the view up by one height of the viewport.
|
||||
func (m *Model) PageUp() []string {
|
||||
if m.AtTop() {
|
||||
return nil
|
||||
}
|
||||
|
||||
return m.ScrollUp(m.Height)
|
||||
}
|
||||
|
||||
// HalfViewDown moves the view down by half the height of the viewport.
|
||||
//
|
||||
// Deprecated: use [Model.HalfPageDown] instead.
|
||||
func (m *Model) HalfViewDown() (lines []string) {
|
||||
return m.HalfPageDown()
|
||||
}
|
||||
|
||||
// HalfPageDown moves the view down by half the height of the viewport.
|
||||
func (m *Model) HalfPageDown() (lines []string) {
|
||||
if m.AtBottom() {
|
||||
return nil
|
||||
}
|
||||
|
||||
return m.ScrollDown(m.Height / 2) //nolint:mnd
|
||||
}
|
||||
|
||||
// HalfViewUp moves the view up by half the height of the viewport.
|
||||
//
|
||||
// Deprecated: use [Model.HalfPageUp] instead.
|
||||
func (m *Model) HalfViewUp() (lines []string) {
|
||||
return m.HalfPageUp()
|
||||
}
|
||||
|
||||
// HalfPageUp moves the view up by half the height of the viewport.
|
||||
func (m *Model) HalfPageUp() (lines []string) {
|
||||
if m.AtTop() {
|
||||
return nil
|
||||
}
|
||||
|
||||
return m.ScrollUp(m.Height / 2) //nolint:mnd
|
||||
}
|
||||
|
||||
// LineDown moves the view down by the given number of lines.
|
||||
//
|
||||
// Deprecated: use [Model.ScrollDown] instead.
|
||||
func (m *Model) LineDown(n int) (lines []string) {
|
||||
return m.ScrollDown(n)
|
||||
}
|
||||
|
||||
// ScrollDown moves the view down by the given number of lines.
|
||||
func (m *Model) ScrollDown(n int) (lines []string) {
|
||||
if m.AtBottom() || n == 0 || len(m.lines) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Make sure the number of lines by which we're going to scroll isn't
|
||||
// greater than the number of lines we actually have left before we reach
|
||||
// the bottom.
|
||||
m.SetYOffset(m.YOffset + n)
|
||||
|
||||
// Gather lines to send off for performance scrolling.
|
||||
//
|
||||
// XXX: high performance rendering is deprecated in Bubble Tea.
|
||||
bottom := clamp(m.YOffset+m.Height, 0, len(m.lines))
|
||||
top := clamp(m.YOffset+m.Height-n, 0, bottom)
|
||||
return m.lines[top:bottom]
|
||||
}
|
||||
|
||||
// LineUp moves the view down by the given number of lines. Returns the new
|
||||
// lines to show.
|
||||
//
|
||||
// Deprecated: use [Model.ScrollUp] instead.
|
||||
func (m *Model) LineUp(n int) (lines []string) {
|
||||
return m.ScrollUp(n)
|
||||
}
|
||||
|
||||
// ScrollUp moves the view down by the given number of lines. Returns the new
|
||||
// lines to show.
|
||||
func (m *Model) ScrollUp(n int) (lines []string) {
|
||||
if m.AtTop() || n == 0 || len(m.lines) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Make sure the number of lines by which we're going to scroll isn't
|
||||
// greater than the number of lines we are from the top.
|
||||
m.SetYOffset(m.YOffset - n)
|
||||
|
||||
// Gather lines to send off for performance scrolling.
|
||||
//
|
||||
// XXX: high performance rendering is deprecated in Bubble Tea.
|
||||
top := max(0, m.YOffset)
|
||||
bottom := clamp(m.YOffset+n, 0, m.maxYOffset())
|
||||
return m.lines[top:bottom]
|
||||
}
|
||||
|
||||
// SetHorizontalStep sets the default amount of columns to scroll left or right
|
||||
// with the default viewport key map.
|
||||
//
|
||||
// If set to 0 or less, horizontal scrolling is disabled.
|
||||
//
|
||||
// On v1, horizontal scrolling is disabled by default.
|
||||
func (m *Model) SetHorizontalStep(n int) {
|
||||
m.horizontalStep = max(n, 0)
|
||||
}
|
||||
|
||||
// SetXOffset sets the X offset.
|
||||
func (m *Model) SetXOffset(n int) {
|
||||
m.xOffset = clamp(n, 0, m.longestLineWidth-m.Width)
|
||||
}
|
||||
|
||||
// ScrollLeft moves the viewport to the left by the given number of columns.
|
||||
func (m *Model) ScrollLeft(n int) {
|
||||
m.SetXOffset(m.xOffset - n)
|
||||
}
|
||||
|
||||
// ScrollRight moves viewport to the right by the given number of columns.
|
||||
func (m *Model) ScrollRight(n int) {
|
||||
m.SetXOffset(m.xOffset + n)
|
||||
}
|
||||
|
||||
// TotalLineCount returns the total number of lines (both hidden and visible) within the viewport.
|
||||
func (m Model) TotalLineCount() int {
|
||||
return len(m.lines)
|
||||
}
|
||||
|
||||
// VisibleLineCount returns the number of the visible lines within the viewport.
|
||||
func (m Model) VisibleLineCount() int {
|
||||
return len(m.visibleLines())
|
||||
}
|
||||
|
||||
// GotoTop sets the viewport to the top position.
|
||||
func (m *Model) GotoTop() (lines []string) {
|
||||
if m.AtTop() {
|
||||
return nil
|
||||
}
|
||||
|
||||
m.SetYOffset(0)
|
||||
return m.visibleLines()
|
||||
}
|
||||
|
||||
// GotoBottom sets the viewport to the bottom position.
|
||||
func (m *Model) GotoBottom() (lines []string) {
|
||||
m.SetYOffset(m.maxYOffset())
|
||||
return m.visibleLines()
|
||||
}
|
||||
|
||||
// Sync tells the renderer where the viewport will be located and requests
|
||||
// a render of the current state of the viewport. It should be called for the
|
||||
// first render and after a window resize.
|
||||
//
|
||||
// For high performance rendering only.
|
||||
//
|
||||
// Deprecated: high performance rendering is deprecated in Bubble Tea.
|
||||
func Sync(m Model) tea.Cmd {
|
||||
if len(m.lines) == 0 {
|
||||
return nil
|
||||
}
|
||||
top, bottom := m.scrollArea()
|
||||
return tea.SyncScrollArea(m.visibleLines(), top, bottom)
|
||||
}
|
||||
|
||||
// ViewDown is a high performance command that moves the viewport up by a given
|
||||
// number of lines. Use Model.ViewDown to get the lines that should be rendered.
|
||||
// For example:
|
||||
//
|
||||
// lines := model.ViewDown(1)
|
||||
// cmd := ViewDown(m, lines)
|
||||
//
|
||||
// Deprecated: high performance rendering is deprecated in Bubble Tea.
|
||||
func ViewDown(m Model, lines []string) tea.Cmd {
|
||||
if len(lines) == 0 {
|
||||
return nil
|
||||
}
|
||||
top, bottom := m.scrollArea()
|
||||
|
||||
// XXX: high performance rendering is deprecated in Bubble Tea. In a v2 we
|
||||
// won't need to return a command here.
|
||||
return tea.ScrollDown(lines, top, bottom)
|
||||
}
|
||||
|
||||
// ViewUp is a high performance command the moves the viewport down by a given
|
||||
// number of lines height. Use Model.ViewUp to get the lines that should be
|
||||
// rendered.
|
||||
//
|
||||
// Deprecated: high performance rendering is deprecated in Bubble Tea.
|
||||
func ViewUp(m Model, lines []string) tea.Cmd {
|
||||
if len(lines) == 0 {
|
||||
return nil
|
||||
}
|
||||
top, bottom := m.scrollArea()
|
||||
|
||||
// XXX: high performance rendering is deprecated in Bubble Tea. In a v2 we
|
||||
// won't need to return a command here.
|
||||
return tea.ScrollUp(lines, top, bottom)
|
||||
}
|
||||
|
||||
// Update handles standard message-based viewport updates.
|
||||
func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
|
||||
var cmd tea.Cmd
|
||||
m, cmd = m.updateAsModel(msg)
|
||||
return m, cmd
|
||||
}
|
||||
|
||||
// Author's note: this method has been broken out to make it easier to
|
||||
// potentially transition Update to satisfy tea.Model.
|
||||
func (m Model) updateAsModel(msg tea.Msg) (Model, tea.Cmd) {
|
||||
if !m.initialized {
|
||||
m.setInitialValues()
|
||||
}
|
||||
|
||||
var cmd tea.Cmd
|
||||
|
||||
switch msg := msg.(type) {
|
||||
case tea.KeyMsg:
|
||||
switch {
|
||||
case key.Matches(msg, m.KeyMap.PageDown):
|
||||
lines := m.PageDown()
|
||||
if m.HighPerformanceRendering {
|
||||
cmd = ViewDown(m, lines)
|
||||
}
|
||||
|
||||
case key.Matches(msg, m.KeyMap.PageUp):
|
||||
lines := m.PageUp()
|
||||
if m.HighPerformanceRendering {
|
||||
cmd = ViewUp(m, lines)
|
||||
}
|
||||
|
||||
case key.Matches(msg, m.KeyMap.HalfPageDown):
|
||||
lines := m.HalfPageDown()
|
||||
if m.HighPerformanceRendering {
|
||||
cmd = ViewDown(m, lines)
|
||||
}
|
||||
|
||||
case key.Matches(msg, m.KeyMap.HalfPageUp):
|
||||
lines := m.HalfPageUp()
|
||||
if m.HighPerformanceRendering {
|
||||
cmd = ViewUp(m, lines)
|
||||
}
|
||||
|
||||
case key.Matches(msg, m.KeyMap.Down):
|
||||
lines := m.ScrollDown(1)
|
||||
if m.HighPerformanceRendering {
|
||||
cmd = ViewDown(m, lines)
|
||||
}
|
||||
|
||||
case key.Matches(msg, m.KeyMap.Up):
|
||||
lines := m.ScrollUp(1)
|
||||
if m.HighPerformanceRendering {
|
||||
cmd = ViewUp(m, lines)
|
||||
}
|
||||
|
||||
case key.Matches(msg, m.KeyMap.Left):
|
||||
m.ScrollLeft(m.horizontalStep)
|
||||
|
||||
case key.Matches(msg, m.KeyMap.Right):
|
||||
m.ScrollRight(m.horizontalStep)
|
||||
}
|
||||
|
||||
case tea.MouseMsg:
|
||||
if !m.MouseWheelEnabled || msg.Action != tea.MouseActionPress {
|
||||
break
|
||||
}
|
||||
switch msg.Button { //nolint:exhaustive
|
||||
case tea.MouseButtonWheelUp:
|
||||
if msg.Shift {
|
||||
// Note that not every terminal emulator sends the shift event for mouse actions by default (looking at you Konsole)
|
||||
m.ScrollLeft(m.horizontalStep)
|
||||
} else {
|
||||
lines := m.ScrollUp(m.MouseWheelDelta)
|
||||
if m.HighPerformanceRendering {
|
||||
cmd = ViewUp(m, lines)
|
||||
}
|
||||
}
|
||||
|
||||
case tea.MouseButtonWheelDown:
|
||||
if msg.Shift {
|
||||
m.ScrollRight(m.horizontalStep)
|
||||
} else {
|
||||
lines := m.ScrollDown(m.MouseWheelDelta)
|
||||
if m.HighPerformanceRendering {
|
||||
cmd = ViewDown(m, lines)
|
||||
}
|
||||
}
|
||||
// Note that not every terminal emulator sends the horizontal wheel events by default (looking at you Konsole)
|
||||
case tea.MouseButtonWheelLeft:
|
||||
m.ScrollLeft(m.horizontalStep)
|
||||
case tea.MouseButtonWheelRight:
|
||||
m.ScrollRight(m.horizontalStep)
|
||||
}
|
||||
}
|
||||
|
||||
return m, cmd
|
||||
}
|
||||
|
||||
// View renders the viewport into a string.
|
||||
func (m Model) View() string {
|
||||
if m.HighPerformanceRendering {
|
||||
// Just send newlines since we're going to be rendering the actual
|
||||
// content separately. We still need to send something that equals the
|
||||
// height of this view so that the Bubble Tea standard renderer can
|
||||
// position anything below this view properly.
|
||||
return strings.Repeat("\n", max(0, m.Height-1))
|
||||
}
|
||||
|
||||
w, h := m.Width, m.Height
|
||||
if sw := m.Style.GetWidth(); sw != 0 {
|
||||
w = min(w, sw)
|
||||
}
|
||||
if sh := m.Style.GetHeight(); sh != 0 {
|
||||
h = min(h, sh)
|
||||
}
|
||||
contentWidth := w - m.Style.GetHorizontalFrameSize()
|
||||
contentHeight := h - m.Style.GetVerticalFrameSize()
|
||||
contents := lipgloss.NewStyle().
|
||||
Width(contentWidth). // pad to width.
|
||||
Height(contentHeight). // pad to height.
|
||||
MaxHeight(contentHeight). // truncate height if taller.
|
||||
MaxWidth(contentWidth). // truncate width if wider.
|
||||
Render(strings.Join(m.visibleLines(), "\n"))
|
||||
return m.Style.
|
||||
UnsetWidth().UnsetHeight(). // Style size already applied in contents.
|
||||
Render(contents)
|
||||
}
|
||||
|
||||
func clamp(v, low, high int) int {
|
||||
if high < low {
|
||||
low, high = high, low
|
||||
}
|
||||
return min(high, max(low, v))
|
||||
}
|
||||
|
||||
func findLongestLineWidth(lines []string) int {
|
||||
w := 0
|
||||
for _, l := range lines {
|
||||
if ww := ansi.StringWidth(l); ww > w {
|
||||
w = ww
|
||||
}
|
||||
}
|
||||
return w
|
||||
}
|
||||
33
vendor/github.com/charmbracelet/colorprofile/env.go
generated
vendored
33
vendor/github.com/charmbracelet/colorprofile/env.go
generated
vendored
@ -153,24 +153,29 @@ func envColorProfile(env environ) (p Profile) {
|
||||
p = ANSI
|
||||
}
|
||||
|
||||
switch {
|
||||
case strings.Contains(term, "alacritty"),
|
||||
strings.Contains(term, "contour"),
|
||||
strings.Contains(term, "foot"),
|
||||
strings.Contains(term, "ghostty"),
|
||||
strings.Contains(term, "kitty"),
|
||||
strings.Contains(term, "rio"),
|
||||
strings.Contains(term, "st"),
|
||||
strings.Contains(term, "wezterm"):
|
||||
parts := strings.Split(term, "-")
|
||||
switch parts[0] {
|
||||
case "alacritty",
|
||||
"contour",
|
||||
"foot",
|
||||
"ghostty",
|
||||
"kitty",
|
||||
"rio",
|
||||
"st",
|
||||
"wezterm":
|
||||
return TrueColor
|
||||
case strings.HasPrefix(term, "tmux"), strings.HasPrefix(term, "screen"):
|
||||
case "xterm":
|
||||
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 {
|
||||
p = ANSI256
|
||||
}
|
||||
case strings.HasPrefix(term, "xterm"):
|
||||
if p < ANSI {
|
||||
p = ANSI
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
if len(modes) == 0 {
|
||||
return s
|
||||
return //nolint:nakedret
|
||||
}
|
||||
|
||||
cmd := "h"
|
||||
@ -142,7 +142,7 @@ func setMode(reset bool, modes ...Mode) (s string) {
|
||||
if len(dec) > 0 {
|
||||
s += seq + "?" + strings.Join(dec, ";") + cmd
|
||||
}
|
||||
return s
|
||||
return //nolint:nakedret
|
||||
}
|
||||
|
||||
// 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
|
||||
const (
|
||||
ModeKeyboardAction = ANSIMode(2)
|
||||
KAM = ModeKeyboardAction
|
||||
KeyboardActionMode = ANSIMode(2)
|
||||
KAM = KeyboardActionMode
|
||||
|
||||
SetModeKeyboardAction = "\x1b[2h"
|
||||
ResetModeKeyboardAction = "\x1b[2l"
|
||||
RequestModeKeyboardAction = "\x1b[2$p"
|
||||
SetKeyboardActionMode = "\x1b[2h"
|
||||
ResetKeyboardActionMode = "\x1b[2l"
|
||||
RequestKeyboardActionMode = "\x1b[2$p"
|
||||
)
|
||||
|
||||
// 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
|
||||
const (
|
||||
ModeInsertReplace = ANSIMode(4)
|
||||
IRM = ModeInsertReplace
|
||||
InsertReplaceMode = ANSIMode(4)
|
||||
IRM = InsertReplaceMode
|
||||
|
||||
SetModeInsertReplace = "\x1b[4h"
|
||||
ResetModeInsertReplace = "\x1b[4l"
|
||||
RequestModeInsertReplace = "\x1b[4$p"
|
||||
SetInsertReplaceMode = "\x1b[4h"
|
||||
ResetInsertReplaceMode = "\x1b[4l"
|
||||
RequestInsertReplaceMode = "\x1b[4$p"
|
||||
)
|
||||
|
||||
// BiDirectional Support Mode (BDSM) is a mode that determines whether the
|
||||
@ -260,12 +260,12 @@ const (
|
||||
//
|
||||
// See ECMA-48 7.2.1.
|
||||
const (
|
||||
ModeBiDirectionalSupport = ANSIMode(8)
|
||||
BDSM = ModeBiDirectionalSupport
|
||||
BiDirectionalSupportMode = ANSIMode(8)
|
||||
BDSM = BiDirectionalSupportMode
|
||||
|
||||
SetModeBiDirectionalSupport = "\x1b[8h"
|
||||
ResetModeBiDirectionalSupport = "\x1b[8l"
|
||||
RequestModeBiDirectionalSupport = "\x1b[8$p"
|
||||
SetBiDirectionalSupportMode = "\x1b[8h"
|
||||
ResetBiDirectionalSupportMode = "\x1b[8l"
|
||||
RequestBiDirectionalSupportMode = "\x1b[8$p"
|
||||
)
|
||||
|
||||
// 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
|
||||
const (
|
||||
ModeSendReceive = ANSIMode(12)
|
||||
ModeLocalEcho = ModeSendReceive
|
||||
SRM = ModeSendReceive
|
||||
SendReceiveMode = ANSIMode(12)
|
||||
LocalEchoMode = SendReceiveMode
|
||||
SRM = SendReceiveMode
|
||||
|
||||
SetModeSendReceive = "\x1b[12h"
|
||||
ResetModeSendReceive = "\x1b[12l"
|
||||
RequestModeSendReceive = "\x1b[12$p"
|
||||
SetSendReceiveMode = "\x1b[12h"
|
||||
ResetSendReceiveMode = "\x1b[12l"
|
||||
RequestSendReceiveMode = "\x1b[12$p"
|
||||
|
||||
SetModeLocalEcho = "\x1b[12h"
|
||||
ResetModeLocalEcho = "\x1b[12l"
|
||||
RequestModeLocalEcho = "\x1b[12$p"
|
||||
SetLocalEchoMode = "\x1b[12h"
|
||||
ResetLocalEchoMode = "\x1b[12l"
|
||||
RequestLocalEchoMode = "\x1b[12$p"
|
||||
)
|
||||
|
||||
// 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
|
||||
const (
|
||||
ModeLineFeedNewLine = ANSIMode(20)
|
||||
LNM = ModeLineFeedNewLine
|
||||
LineFeedNewLineMode = ANSIMode(20)
|
||||
LNM = LineFeedNewLineMode
|
||||
|
||||
SetModeLineFeedNewLine = "\x1b[20h"
|
||||
ResetModeLineFeedNewLine = "\x1b[20l"
|
||||
RequestModeLineFeedNewLine = "\x1b[20$p"
|
||||
SetLineFeedNewLineMode = "\x1b[20h"
|
||||
ResetLineFeedNewLineMode = "\x1b[20l"
|
||||
RequestLineFeedNewLineMode = "\x1b[20$p"
|
||||
)
|
||||
|
||||
// Cursor Keys Mode (DECCKM) is a mode that determines whether the cursor keys
|
||||
@ -312,12 +312,18 @@ const (
|
||||
//
|
||||
// See: https://vt100.net/docs/vt510-rm/DECCKM.html
|
||||
const (
|
||||
ModeCursorKeys = DECMode(1)
|
||||
DECCKM = ModeCursorKeys
|
||||
CursorKeysMode = DECMode(1)
|
||||
DECCKM = CursorKeysMode
|
||||
|
||||
SetModeCursorKeys = "\x1b[?1h"
|
||||
ResetModeCursorKeys = "\x1b[?1l"
|
||||
RequestModeCursorKeys = "\x1b[?1$p"
|
||||
SetCursorKeysMode = "\x1b[?1h"
|
||||
ResetCursorKeysMode = "\x1b[?1l"
|
||||
RequestCursorKeysMode = "\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
|
||||
@ -325,12 +331,12 @@ const (
|
||||
//
|
||||
// See: https://vt100.net/docs/vt510-rm/DECOM.html
|
||||
const (
|
||||
ModeOrigin = DECMode(6)
|
||||
DECOM = ModeOrigin
|
||||
OriginMode = DECMode(6)
|
||||
DECOM = OriginMode
|
||||
|
||||
SetModeOrigin = "\x1b[?6h"
|
||||
ResetModeOrigin = "\x1b[?6l"
|
||||
RequestModeOrigin = "\x1b[?6$p"
|
||||
SetOriginMode = "\x1b[?6h"
|
||||
ResetOriginMode = "\x1b[?6l"
|
||||
RequestOriginMode = "\x1b[?6$p"
|
||||
)
|
||||
|
||||
// Auto Wrap Mode (DECAWM) is a mode that determines whether the cursor wraps
|
||||
@ -338,12 +344,12 @@ const (
|
||||
//
|
||||
// See: https://vt100.net/docs/vt510-rm/DECAWM.html
|
||||
const (
|
||||
ModeAutoWrap = DECMode(7)
|
||||
DECAWM = ModeAutoWrap
|
||||
AutoWrapMode = DECMode(7)
|
||||
DECAWM = AutoWrapMode
|
||||
|
||||
SetModeAutoWrap = "\x1b[?7h"
|
||||
ResetModeAutoWrap = "\x1b[?7l"
|
||||
RequestModeAutoWrap = "\x1b[?7$p"
|
||||
SetAutoWrapMode = "\x1b[?7h"
|
||||
ResetAutoWrapMode = "\x1b[?7l"
|
||||
RequestAutoWrapMode = "\x1b[?7$p"
|
||||
)
|
||||
|
||||
// X10 Mouse Mode is a mode that determines whether the mouse reports on button
|
||||
@ -358,29 +364,39 @@ const (
|
||||
//
|
||||
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
|
||||
const (
|
||||
ModeMouseX10 = DECMode(9)
|
||||
X10MouseMode = DECMode(9)
|
||||
|
||||
SetModeMouseX10 = "\x1b[?9h"
|
||||
ResetModeMouseX10 = "\x1b[?9l"
|
||||
RequestModeMouseX10 = "\x1b[?9$p"
|
||||
SetX10MouseMode = "\x1b[?9h"
|
||||
ResetX10MouseMode = "\x1b[?9l"
|
||||
RequestX10MouseMode = "\x1b[?9$p"
|
||||
)
|
||||
|
||||
// Text Cursor Enable Mode (DECTCEM) is a mode that shows/hides the cursor.
|
||||
//
|
||||
// See: https://vt100.net/docs/vt510-rm/DECTCEM.html
|
||||
const (
|
||||
ModeTextCursorEnable = DECMode(25)
|
||||
DECTCEM = ModeTextCursorEnable
|
||||
TextCursorEnableMode = DECMode(25)
|
||||
DECTCEM = TextCursorEnableMode
|
||||
|
||||
SetModeTextCursorEnable = "\x1b[?25h"
|
||||
ResetModeTextCursorEnable = "\x1b[?25l"
|
||||
RequestModeTextCursorEnable = "\x1b[?25$p"
|
||||
SetTextCursorEnableMode = "\x1b[?25h"
|
||||
ResetTextCursorEnableMode = "\x1b[?25l"
|
||||
RequestTextCursorEnableMode = "\x1b[?25$p"
|
||||
)
|
||||
|
||||
// These are aliases for [SetModeTextCursorEnable] and [ResetModeTextCursorEnable].
|
||||
// These are aliases for [SetTextCursorEnableMode] and [ResetTextCursorEnableMode].
|
||||
const (
|
||||
ShowCursor = SetModeTextCursorEnable
|
||||
HideCursor = ResetModeTextCursorEnable
|
||||
ShowCursor = SetTextCursorEnableMode
|
||||
HideCursor = ResetTextCursorEnableMode
|
||||
)
|
||||
|
||||
// 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
|
||||
@ -390,12 +406,12 @@ const (
|
||||
//
|
||||
// See: https://vt100.net/docs/vt510-rm/DECNKM.html
|
||||
const (
|
||||
ModeNumericKeypad = DECMode(66)
|
||||
DECNKM = ModeNumericKeypad
|
||||
NumericKeypadMode = DECMode(66)
|
||||
DECNKM = NumericKeypadMode
|
||||
|
||||
SetModeNumericKeypad = "\x1b[?66h"
|
||||
ResetModeNumericKeypad = "\x1b[?66l"
|
||||
RequestModeNumericKeypad = "\x1b[?66$p"
|
||||
SetNumericKeypadMode = "\x1b[?66h"
|
||||
ResetNumericKeypadMode = "\x1b[?66l"
|
||||
RequestNumericKeypadMode = "\x1b[?66$p"
|
||||
)
|
||||
|
||||
// Backarrow Key Mode (DECBKM) is a mode that determines whether the backspace
|
||||
@ -403,12 +419,12 @@ const (
|
||||
//
|
||||
// See: https://vt100.net/docs/vt510-rm/DECBKM.html
|
||||
const (
|
||||
ModeBackarrowKey = DECMode(67)
|
||||
DECBKM = ModeBackarrowKey
|
||||
BackarrowKeyMode = DECMode(67)
|
||||
DECBKM = BackarrowKeyMode
|
||||
|
||||
SetModeBackarrowKey = "\x1b[?67h"
|
||||
ResetModeBackarrowKey = "\x1b[?67l"
|
||||
RequestModeBackarrowKey = "\x1b[?67$p"
|
||||
SetBackarrowKeyMode = "\x1b[?67h"
|
||||
ResetBackarrowKeyMode = "\x1b[?67l"
|
||||
RequestBackarrowKeyMode = "\x1b[?67$p"
|
||||
)
|
||||
|
||||
// Left Right Margin Mode (DECLRMM) is a mode that determines whether the left
|
||||
@ -416,33 +432,47 @@ const (
|
||||
//
|
||||
// See: https://vt100.net/docs/vt510-rm/DECLRMM.html
|
||||
const (
|
||||
ModeLeftRightMargin = DECMode(69)
|
||||
DECLRMM = ModeLeftRightMargin
|
||||
LeftRightMarginMode = DECMode(69)
|
||||
DECLRMM = LeftRightMarginMode
|
||||
|
||||
SetModeLeftRightMargin = "\x1b[?69h"
|
||||
ResetModeLeftRightMargin = "\x1b[?69l"
|
||||
RequestModeLeftRightMargin = "\x1b[?69$p"
|
||||
SetLeftRightMarginMode = "\x1b[?69h"
|
||||
ResetLeftRightMarginMode = "\x1b[?69l"
|
||||
RequestLeftRightMarginMode = "\x1b[?69$p"
|
||||
)
|
||||
|
||||
// Normal Mouse Mode is a mode that determines whether the mouse reports on
|
||||
// button presses and releases. It will also report modifier keys, wheel
|
||||
// events, and extra buttons.
|
||||
//
|
||||
// It uses the same encoding as [ModeMouseX10] with a few differences:
|
||||
// It uses the same encoding as [X10MouseMode] with a few differences:
|
||||
//
|
||||
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
|
||||
const (
|
||||
ModeMouseNormal = DECMode(1000)
|
||||
NormalMouseMode = DECMode(1000)
|
||||
|
||||
SetModeMouseNormal = "\x1b[?1000h"
|
||||
ResetModeMouseNormal = "\x1b[?1000l"
|
||||
RequestModeMouseNormal = "\x1b[?1000$p"
|
||||
SetNormalMouseMode = "\x1b[?1000h"
|
||||
ResetNormalMouseMode = "\x1b[?1000l"
|
||||
RequestNormalMouseMode = "\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
|
||||
// on button presses, releases, and highlighted cells.
|
||||
//
|
||||
// It uses the same encoding as [ModeMouseNormal] with a few differences:
|
||||
// It uses the same encoding as [NormalMouseMode] with a few differences:
|
||||
//
|
||||
// On highlight events, the terminal responds with the following encoding:
|
||||
//
|
||||
@ -451,11 +481,11 @@ const (
|
||||
//
|
||||
// Where the parameters are startx, starty, endx, endy, mousex, and mousey.
|
||||
const (
|
||||
ModeMouseHighlight = DECMode(1001)
|
||||
HighlightMouseMode = DECMode(1001)
|
||||
|
||||
SetModeMouseHighlight = "\x1b[?1001h"
|
||||
ResetModeMouseHighlight = "\x1b[?1001l"
|
||||
RequestModeMouseHighlight = "\x1b[?1001$p"
|
||||
SetHighlightMouseMode = "\x1b[?1001h"
|
||||
ResetHighlightMouseMode = "\x1b[?1001l"
|
||||
RequestHighlightMouseMode = "\x1b[?1001$p"
|
||||
)
|
||||
|
||||
// VT Hilite Mouse Tracking is a mode that determines whether the mouse reports on
|
||||
@ -463,29 +493,65 @@ const (
|
||||
//
|
||||
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
|
||||
//
|
||||
// Deprecated: use [HighlightMouseMode] instead.
|
||||
const (
|
||||
MouseHiliteMode = DECMode(1001)
|
||||
|
||||
// Button Event Mouse Tracking is essentially the same as [ModeMouseNormal],
|
||||
EnableMouseHilite = "\x1b[?1001h"
|
||||
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.
|
||||
//
|
||||
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
|
||||
const (
|
||||
ModeMouseButtonEvent = DECMode(1002)
|
||||
ButtonEventMouseMode = DECMode(1002)
|
||||
|
||||
SetModeMouseButtonEvent = "\x1b[?1002h"
|
||||
ResetModeMouseButtonEvent = "\x1b[?1002l"
|
||||
RequestModeMouseButtonEvent = "\x1b[?1002$p"
|
||||
SetButtonEventMouseMode = "\x1b[?1002h"
|
||||
ResetButtonEventMouseMode = "\x1b[?1002l"
|
||||
RequestButtonEventMouseMode = "\x1b[?1002$p"
|
||||
)
|
||||
|
||||
// Any Event Mouse Tracking is the same as [ModeMouseButtonEvent], except that
|
||||
// Cell Motion Mouse Tracking is a mode that determines whether the mouse
|
||||
// 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.
|
||||
//
|
||||
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
|
||||
const (
|
||||
ModeMouseAnyEvent = DECMode(1003)
|
||||
AnyEventMouseMode = DECMode(1003)
|
||||
|
||||
SetModeMouseAnyEvent = "\x1b[?1003h"
|
||||
ResetModeMouseAnyEvent = "\x1b[?1003l"
|
||||
RequestModeMouseAnyEvent = "\x1b[?1003$p"
|
||||
SetAnyEventMouseMode = "\x1b[?1003h"
|
||||
ResetAnyEventMouseMode = "\x1b[?1003l"
|
||||
RequestAnyEventMouseMode = "\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
|
||||
@ -498,11 +564,22 @@ const (
|
||||
//
|
||||
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Focus-Tracking
|
||||
const (
|
||||
ModeFocusEvent = DECMode(1004)
|
||||
FocusEventMode = DECMode(1004)
|
||||
|
||||
SetModeFocusEvent = "\x1b[?1004h"
|
||||
ResetModeFocusEvent = "\x1b[?1004l"
|
||||
RequestModeFocusEvent = "\x1b[?1004$p"
|
||||
SetFocusEventMode = "\x1b[?1004h"
|
||||
ResetFocusEventMode = "\x1b[?1004l"
|
||||
RequestFocusEventMode = "\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
|
||||
@ -512,15 +589,24 @@ const (
|
||||
//
|
||||
// CSI < Cb ; Cx ; Cy M
|
||||
//
|
||||
// Where Cb is the same as [ModeMouseNormal], and Cx and Cy are the x and y.
|
||||
// Where Cb is the same as [NormalMouseMode], and Cx and Cy are the x and y.
|
||||
//
|
||||
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
|
||||
const (
|
||||
ModeMouseExtSgr = DECMode(1006)
|
||||
SgrExtMouseMode = DECMode(1006)
|
||||
|
||||
SetModeMouseExtSgr = "\x1b[?1006h"
|
||||
ResetModeMouseExtSgr = "\x1b[?1006l"
|
||||
RequestModeMouseExtSgr = "\x1b[?1006$p"
|
||||
SetSgrExtMouseMode = "\x1b[?1006h"
|
||||
ResetSgrExtMouseMode = "\x1b[?1006l"
|
||||
RequestSgrExtMouseMode = "\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
|
||||
@ -528,11 +614,11 @@ const (
|
||||
//
|
||||
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
|
||||
const (
|
||||
ModeMouseExtUtf8 = DECMode(1005)
|
||||
Utf8ExtMouseMode = DECMode(1005)
|
||||
|
||||
SetModeMouseExtUtf8 = "\x1b[?1005h"
|
||||
ResetModeMouseExtUtf8 = "\x1b[?1005l"
|
||||
RequestModeMouseExtUtf8 = "\x1b[?1005$p"
|
||||
SetUtf8ExtMouseMode = "\x1b[?1005h"
|
||||
ResetUtf8ExtMouseMode = "\x1b[?1005l"
|
||||
RequestUtf8ExtMouseMode = "\x1b[?1005$p"
|
||||
)
|
||||
|
||||
// URXVT Extended Mouse Mode is a mode that changes the mouse tracking encoding
|
||||
@ -540,25 +626,25 @@ const (
|
||||
//
|
||||
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
|
||||
const (
|
||||
ModeMouseExtUrxvt = DECMode(1015)
|
||||
UrxvtExtMouseMode = DECMode(1015)
|
||||
|
||||
SetModeMouseExtUrxvt = "\x1b[?1015h"
|
||||
ResetModeMouseExtUrxvt = "\x1b[?1015l"
|
||||
RequestModeMouseExtUrxvt = "\x1b[?1015$p"
|
||||
SetUrxvtExtMouseMode = "\x1b[?1015h"
|
||||
ResetUrxvtExtMouseMode = "\x1b[?1015l"
|
||||
RequestUrxvtExtMouseMode = "\x1b[?1015$p"
|
||||
)
|
||||
|
||||
// SGR Pixel Extended Mouse Mode is a mode that changes the mouse tracking
|
||||
// encoding to use SGR parameters with pixel coordinates.
|
||||
//
|
||||
// This is similar to [ModeMouseExtSgr], but also reports pixel coordinates.
|
||||
// This is similar to [SgrExtMouseMode], but also reports pixel coordinates.
|
||||
//
|
||||
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
|
||||
const (
|
||||
ModeMouseExtSgrPixel = DECMode(1016)
|
||||
SgrPixelExtMouseMode = DECMode(1016)
|
||||
|
||||
SetModeMouseExtSgrPixel = "\x1b[?1016h"
|
||||
ResetModeMouseExtSgrPixel = "\x1b[?1016l"
|
||||
RequestModeMouseExtSgrPixel = "\x1b[?1016$p"
|
||||
SetSgrPixelExtMouseMode = "\x1b[?1016h"
|
||||
ResetSgrPixelExtMouseMode = "\x1b[?1016l"
|
||||
RequestSgrPixelExtMouseMode = "\x1b[?1016$p"
|
||||
)
|
||||
|
||||
// Alternate Screen Mode is a mode that determines whether the alternate screen
|
||||
@ -567,11 +653,11 @@ const (
|
||||
//
|
||||
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-The-Alternate-Screen-Buffer
|
||||
const (
|
||||
ModeAltScreen = DECMode(1047)
|
||||
AltScreenMode = DECMode(1047)
|
||||
|
||||
SetModeAltScreen = "\x1b[?1047h"
|
||||
ResetModeAltScreen = "\x1b[?1047l"
|
||||
RequestModeAltScreen = "\x1b[?1047$p"
|
||||
SetAltScreenMode = "\x1b[?1047h"
|
||||
ResetAltScreenMode = "\x1b[?1047l"
|
||||
RequestAltScreenMode = "\x1b[?1047$p"
|
||||
)
|
||||
|
||||
// Save Cursor Mode is a mode that saves the cursor position.
|
||||
@ -579,24 +665,42 @@ const (
|
||||
//
|
||||
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-The-Alternate-Screen-Buffer
|
||||
const (
|
||||
ModeSaveCursor = DECMode(1048)
|
||||
SaveCursorMode = DECMode(1048)
|
||||
|
||||
SetModeSaveCursor = "\x1b[?1048h"
|
||||
ResetModeSaveCursor = "\x1b[?1048l"
|
||||
RequestModeSaveCursor = "\x1b[?1048$p"
|
||||
SetSaveCursorMode = "\x1b[?1048h"
|
||||
ResetSaveCursorMode = "\x1b[?1048l"
|
||||
RequestSaveCursorMode = "\x1b[?1048$p"
|
||||
)
|
||||
|
||||
// Alternate Screen Save Cursor Mode is a mode that saves the cursor position as in
|
||||
// [ModeSaveCursor], switches to the alternate screen buffer as in [ModeAltScreen],
|
||||
// [SaveCursorMode], switches to the alternate screen buffer as in [AltScreenMode],
|
||||
// and clears the screen on switch.
|
||||
//
|
||||
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-The-Alternate-Screen-Buffer
|
||||
const (
|
||||
ModeAltScreenSaveCursor = DECMode(1049)
|
||||
AltScreenSaveCursorMode = DECMode(1049)
|
||||
|
||||
SetModeAltScreenSaveCursor = "\x1b[?1049h"
|
||||
ResetModeAltScreenSaveCursor = "\x1b[?1049l"
|
||||
RequestModeAltScreenSaveCursor = "\x1b[?1049$p"
|
||||
SetAltScreenSaveCursorMode = "\x1b[?1049h"
|
||||
ResetAltScreenSaveCursorMode = "\x1b[?1049l"
|
||||
RequestAltScreenSaveCursorMode = "\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
|
||||
@ -605,11 +709,19 @@ const (
|
||||
// See: https://cirw.in/blog/bracketed-paste
|
||||
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Bracketed-Paste-Mode
|
||||
const (
|
||||
ModeBracketedPaste = DECMode(2004)
|
||||
BracketedPasteMode = DECMode(2004)
|
||||
|
||||
SetModeBracketedPaste = "\x1b[?2004h"
|
||||
ResetModeBracketedPaste = "\x1b[?2004l"
|
||||
RequestModeBracketedPaste = "\x1b[?2004$p"
|
||||
SetBracketedPasteMode = "\x1b[?2004h"
|
||||
ResetBracketedPasteMode = "\x1b[?2004l"
|
||||
RequestBracketedPasteMode = "\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
|
||||
@ -617,11 +729,23 @@ const (
|
||||
//
|
||||
// See: https://gist.github.com/christianparpart/d8a62cc1ab659194337d73e399004036
|
||||
const (
|
||||
ModeSynchronizedOutput = DECMode(2026)
|
||||
SynchronizedOutputMode = DECMode(2026)
|
||||
|
||||
SetModeSynchronizedOutput = "\x1b[?2026h"
|
||||
ResetModeSynchronizedOutput = "\x1b[?2026l"
|
||||
RequestModeSynchronizedOutput = "\x1b[?2026$p"
|
||||
SetSynchronizedOutputMode = "\x1b[?2026h"
|
||||
ResetSynchronizedOutputMode = "\x1b[?2026l"
|
||||
RequestSynchronizedOutputMode = "\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
|
||||
@ -630,16 +754,41 @@ const (
|
||||
//
|
||||
// See: https://github.com/contour-terminal/terminal-unicode-core
|
||||
const (
|
||||
ModeUnicodeCore = DECMode(2027)
|
||||
UnicodeCoreMode = DECMode(2027)
|
||||
|
||||
SetModeUnicodeCore = "\x1b[?2027h"
|
||||
ResetModeUnicodeCore = "\x1b[?2027l"
|
||||
RequestModeUnicodeCore = "\x1b[?2027$p"
|
||||
SetUnicodeCoreMode = "\x1b[?2027h"
|
||||
ResetUnicodeCoreMode = "\x1b[?2027l"
|
||||
RequestUnicodeCoreMode = "\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)
|
||||
|
||||
// ModeLightDark is a mode that enables reporting the operating system's color
|
||||
SetGraphemeClusteringMode = "\x1b[?2027h"
|
||||
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]
|
||||
// and [LightDarkReport] escape sequences encoded as follows:
|
||||
//
|
||||
@ -653,14 +802,14 @@ const (
|
||||
//
|
||||
// See: https://contour-terminal.org/vt-extensions/color-palette-update-notifications/
|
||||
const (
|
||||
ModeLightDark = DECMode(2031)
|
||||
LightDarkMode = DECMode(2031)
|
||||
|
||||
SetModeLightDark = "\x1b[?2031h"
|
||||
ResetModeLightDark = "\x1b[?2031l"
|
||||
RequestModeLightDark = "\x1b[?2031$p"
|
||||
SetLightDarkMode = "\x1b[?2031h"
|
||||
ResetLightDarkMode = "\x1b[?2031l"
|
||||
RequestLightDarkMode = "\x1b[?2031$p"
|
||||
)
|
||||
|
||||
// ModeInBandResize is a mode that reports terminal resize events as escape
|
||||
// InBandResizeMode is a mode that reports terminal resize events as escape
|
||||
// sequences. This is useful for systems that do not support [SIGWINCH] like
|
||||
// Windows.
|
||||
//
|
||||
@ -670,11 +819,11 @@ const (
|
||||
//
|
||||
// See: https://gist.github.com/rockorager/e695fb2924d36b2bcf1fff4a3704bd83
|
||||
const (
|
||||
ModeInBandResize = DECMode(2048)
|
||||
InBandResizeMode = DECMode(2048)
|
||||
|
||||
SetModeInBandResize = "\x1b[?2048h"
|
||||
ResetModeInBandResize = "\x1b[?2048l"
|
||||
RequestModeInBandResize = "\x1b[?2048$p"
|
||||
SetInBandResizeMode = "\x1b[?2048h"
|
||||
ResetInBandResizeMode = "\x1b[?2048l"
|
||||
RequestInBandResizeMode = "\x1b[?2048$p"
|
||||
)
|
||||
|
||||
// Win32Input is a mode that determines whether input is processed by the
|
||||
@ -682,9 +831,17 @@ const (
|
||||
//
|
||||
// See: https://github.com/microsoft/terminal/blob/main/doc/specs/%234999%20-%20Improved%20keyboard%20handling%20in%20Conpty.md
|
||||
const (
|
||||
ModeWin32Input = DECMode(9001)
|
||||
Win32InputMode = DECMode(9001)
|
||||
|
||||
SetModeWin32Input = "\x1b[?9001h"
|
||||
ResetModeWin32Input = "\x1b[?9001l"
|
||||
RequestModeWin32Input = "\x1b[?9001$p"
|
||||
SetWin32InputMode = "\x1b[?9001h"
|
||||
ResetWin32InputMode = "\x1b[?9001l"
|
||||
RequestWin32InputMode = "\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
495
vendor/github.com/charmbracelet/x/ansi/mode_deprecated.go
generated
vendored
@ -1,495 +0,0 @@
|
||||
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
|
||||
}
|
||||
|
||||
return m
|
||||
return //nolint:nakedret
|
||||
}
|
||||
|
||||
// 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,10 +1,5 @@
|
||||
package ansi
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Notify sends a desktop notification using iTerm's OSC 9.
|
||||
//
|
||||
// OSC 9 ; Mc ST
|
||||
@ -16,17 +11,3 @@ import (
|
||||
func Notify(s string) string {
|
||||
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,9 +4,8 @@ import (
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/charmbracelet/x/ansi/parser"
|
||||
"github.com/clipperhouse/displaywidth"
|
||||
"github.com/clipperhouse/uax29/v2/graphemes"
|
||||
"github.com/mattn/go-runewidth"
|
||||
"github.com/rivo/uniseg"
|
||||
)
|
||||
|
||||
// State represents the state of the ANSI escape sequence parser used by
|
||||
@ -177,7 +176,10 @@ func decodeSequence[T string | []byte](m Method, b T, state State, p *Parser) (s
|
||||
}
|
||||
|
||||
if utf8.RuneStart(c) {
|
||||
seq, width = FirstGraphemeCluster(b, m)
|
||||
seq, _, width, _ = FirstGraphemeCluster(b, -1)
|
||||
if m == WcWidth {
|
||||
width = runewidth.StringWidth(string(seq))
|
||||
}
|
||||
i += len(seq)
|
||||
return b[:i], width, i, NormalState
|
||||
}
|
||||
@ -432,22 +434,17 @@ func HasEscPrefix[T string | []byte](b T) bool {
|
||||
return len(b) > 0 && b[0] == ESC
|
||||
}
|
||||
|
||||
// FirstGraphemeCluster returns the first grapheme cluster in the given string
|
||||
// or byte slice, and its monospace display width.
|
||||
func FirstGraphemeCluster[T string | []byte](b T, m Method) (T, int) {
|
||||
// FirstGraphemeCluster returns the first grapheme cluster in the given string or byte slice.
|
||||
// This is a syntactic sugar function that wraps
|
||||
// uniseg.FirstGraphemeClusterInString and uniseg.FirstGraphemeCluster.
|
||||
func FirstGraphemeCluster[T string | []byte](b T, state int) (T, T, int, int) {
|
||||
switch b := any(b).(type) {
|
||||
case string:
|
||||
cluster := graphemes.FromString(b).First()
|
||||
if m == WcWidth {
|
||||
return T(cluster), runewidth.StringWidth(cluster)
|
||||
}
|
||||
return T(cluster), displaywidth.String(cluster)
|
||||
cluster, rest, width, newState := uniseg.FirstGraphemeClusterInString(b, state)
|
||||
return T(cluster), T(rest), width, newState
|
||||
case []byte:
|
||||
cluster := graphemes.FromBytes(b).First()
|
||||
if m == WcWidth {
|
||||
return T(cluster), runewidth.StringWidth(string(cluster))
|
||||
}
|
||||
return T(cluster), displaywidth.Bytes(cluster)
|
||||
cluster, rest, width, newState := uniseg.FirstGraphemeCluster(b, state)
|
||||
return T(cluster), T(rest), width, newState
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
@ -493,7 +490,7 @@ func Command(prefix, inter, final byte) (c int) {
|
||||
c = int(final)
|
||||
c |= int(prefix) << parser.PrefixShift
|
||||
c |= int(inter) << parser.IntermedShift
|
||||
return c
|
||||
return
|
||||
}
|
||||
|
||||
// Param represents a sequence parameter. Sequence parameters with
|
||||
@ -523,5 +520,5 @@ func Parameter(p int, hasMore bool) (s int) {
|
||||
if hasMore {
|
||||
s |= parser.HasMoreFlag
|
||||
}
|
||||
return s
|
||||
return
|
||||
}
|
||||
|
||||
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{
|
||||
AttrReset: attrReset,
|
||||
AttrBold: attrBold,
|
||||
AttrFaint: attrFaint,
|
||||
AttrItalic: attrItalic,
|
||||
AttrUnderline: attrUnderline,
|
||||
AttrBlink: attrBlink,
|
||||
AttrRapidBlink: attrRapidBlink,
|
||||
AttrReverse: attrReverse,
|
||||
AttrConceal: attrConceal,
|
||||
AttrStrikethrough: attrStrikethrough,
|
||||
AttrNormalIntensity: attrNormalIntensity,
|
||||
AttrNoItalic: attrNoItalic,
|
||||
AttrNoUnderline: attrNoUnderline,
|
||||
AttrNoBlink: attrNoBlink,
|
||||
AttrNoReverse: attrNoReverse,
|
||||
AttrNoConceal: attrNoConceal,
|
||||
AttrNoStrikethrough: attrNoStrikethrough,
|
||||
AttrBlackForegroundColor: attrBlackForegroundColor,
|
||||
AttrRedForegroundColor: attrRedForegroundColor,
|
||||
AttrGreenForegroundColor: attrGreenForegroundColor,
|
||||
AttrYellowForegroundColor: attrYellowForegroundColor,
|
||||
AttrBlueForegroundColor: attrBlueForegroundColor,
|
||||
AttrMagentaForegroundColor: attrMagentaForegroundColor,
|
||||
AttrCyanForegroundColor: attrCyanForegroundColor,
|
||||
AttrWhiteForegroundColor: attrWhiteForegroundColor,
|
||||
AttrExtendedForegroundColor: attrExtendedForegroundColor,
|
||||
AttrDefaultForegroundColor: attrDefaultForegroundColor,
|
||||
AttrBlackBackgroundColor: attrBlackBackgroundColor,
|
||||
AttrRedBackgroundColor: attrRedBackgroundColor,
|
||||
AttrGreenBackgroundColor: attrGreenBackgroundColor,
|
||||
AttrYellowBackgroundColor: attrYellowBackgroundColor,
|
||||
AttrBlueBackgroundColor: attrBlueBackgroundColor,
|
||||
AttrMagentaBackgroundColor: attrMagentaBackgroundColor,
|
||||
AttrCyanBackgroundColor: attrCyanBackgroundColor,
|
||||
AttrWhiteBackgroundColor: attrWhiteBackgroundColor,
|
||||
AttrExtendedBackgroundColor: attrExtendedBackgroundColor,
|
||||
AttrDefaultBackgroundColor: attrDefaultBackgroundColor,
|
||||
AttrExtendedUnderlineColor: attrExtendedUnderlineColor,
|
||||
AttrDefaultUnderlineColor: attrDefaultUnderlineColor,
|
||||
AttrBrightBlackForegroundColor: attrBrightBlackForegroundColor,
|
||||
AttrBrightRedForegroundColor: attrBrightRedForegroundColor,
|
||||
AttrBrightGreenForegroundColor: attrBrightGreenForegroundColor,
|
||||
AttrBrightYellowForegroundColor: attrBrightYellowForegroundColor,
|
||||
AttrBrightBlueForegroundColor: attrBrightBlueForegroundColor,
|
||||
AttrBrightMagentaForegroundColor: attrBrightMagentaForegroundColor,
|
||||
AttrBrightCyanForegroundColor: attrBrightCyanForegroundColor,
|
||||
AttrBrightWhiteForegroundColor: attrBrightWhiteForegroundColor,
|
||||
AttrBrightBlackBackgroundColor: attrBrightBlackBackgroundColor,
|
||||
AttrBrightRedBackgroundColor: attrBrightRedBackgroundColor,
|
||||
AttrBrightGreenBackgroundColor: attrBrightGreenBackgroundColor,
|
||||
AttrBrightYellowBackgroundColor: attrBrightYellowBackgroundColor,
|
||||
AttrBrightBlueBackgroundColor: attrBrightBlueBackgroundColor,
|
||||
AttrBrightMagentaBackgroundColor: attrBrightMagentaBackgroundColor,
|
||||
AttrBrightCyanBackgroundColor: attrBrightCyanBackgroundColor,
|
||||
AttrBrightWhiteBackgroundColor: attrBrightWhiteBackgroundColor,
|
||||
ResetAttr: resetAttr,
|
||||
BoldAttr: boldAttr,
|
||||
FaintAttr: faintAttr,
|
||||
ItalicAttr: italicAttr,
|
||||
UnderlineAttr: underlineAttr,
|
||||
SlowBlinkAttr: slowBlinkAttr,
|
||||
RapidBlinkAttr: rapidBlinkAttr,
|
||||
ReverseAttr: reverseAttr,
|
||||
ConcealAttr: concealAttr,
|
||||
StrikethroughAttr: strikethroughAttr,
|
||||
NormalIntensityAttr: normalIntensityAttr,
|
||||
NoItalicAttr: noItalicAttr,
|
||||
NoUnderlineAttr: noUnderlineAttr,
|
||||
NoBlinkAttr: noBlinkAttr,
|
||||
NoReverseAttr: noReverseAttr,
|
||||
NoConcealAttr: noConcealAttr,
|
||||
NoStrikethroughAttr: noStrikethroughAttr,
|
||||
BlackForegroundColorAttr: blackForegroundColorAttr,
|
||||
RedForegroundColorAttr: redForegroundColorAttr,
|
||||
GreenForegroundColorAttr: greenForegroundColorAttr,
|
||||
YellowForegroundColorAttr: yellowForegroundColorAttr,
|
||||
BlueForegroundColorAttr: blueForegroundColorAttr,
|
||||
MagentaForegroundColorAttr: magentaForegroundColorAttr,
|
||||
CyanForegroundColorAttr: cyanForegroundColorAttr,
|
||||
WhiteForegroundColorAttr: whiteForegroundColorAttr,
|
||||
ExtendedForegroundColorAttr: extendedForegroundColorAttr,
|
||||
DefaultForegroundColorAttr: defaultForegroundColorAttr,
|
||||
BlackBackgroundColorAttr: blackBackgroundColorAttr,
|
||||
RedBackgroundColorAttr: redBackgroundColorAttr,
|
||||
GreenBackgroundColorAttr: greenBackgroundColorAttr,
|
||||
YellowBackgroundColorAttr: yellowBackgroundColorAttr,
|
||||
BlueBackgroundColorAttr: blueBackgroundColorAttr,
|
||||
MagentaBackgroundColorAttr: magentaBackgroundColorAttr,
|
||||
CyanBackgroundColorAttr: cyanBackgroundColorAttr,
|
||||
WhiteBackgroundColorAttr: whiteBackgroundColorAttr,
|
||||
ExtendedBackgroundColorAttr: extendedBackgroundColorAttr,
|
||||
DefaultBackgroundColorAttr: defaultBackgroundColorAttr,
|
||||
ExtendedUnderlineColorAttr: extendedUnderlineColorAttr,
|
||||
DefaultUnderlineColorAttr: defaultUnderlineColorAttr,
|
||||
BrightBlackForegroundColorAttr: brightBlackForegroundColorAttr,
|
||||
BrightRedForegroundColorAttr: brightRedForegroundColorAttr,
|
||||
BrightGreenForegroundColorAttr: brightGreenForegroundColorAttr,
|
||||
BrightYellowForegroundColorAttr: brightYellowForegroundColorAttr,
|
||||
BrightBlueForegroundColorAttr: brightBlueForegroundColorAttr,
|
||||
BrightMagentaForegroundColorAttr: brightMagentaForegroundColorAttr,
|
||||
BrightCyanForegroundColorAttr: brightCyanForegroundColorAttr,
|
||||
BrightWhiteForegroundColorAttr: brightWhiteForegroundColorAttr,
|
||||
BrightBlackBackgroundColorAttr: brightBlackBackgroundColorAttr,
|
||||
BrightRedBackgroundColorAttr: brightRedBackgroundColorAttr,
|
||||
BrightGreenBackgroundColorAttr: brightGreenBackgroundColorAttr,
|
||||
BrightYellowBackgroundColorAttr: brightYellowBackgroundColorAttr,
|
||||
BrightBlueBackgroundColorAttr: brightBlueBackgroundColorAttr,
|
||||
BrightMagentaBackgroundColorAttr: brightMagentaBackgroundColorAttr,
|
||||
BrightCyanBackgroundColorAttr: brightCyanBackgroundColorAttr,
|
||||
BrightWhiteBackgroundColorAttr: brightWhiteBackgroundColorAttr,
|
||||
}
|
||||
|
||||
603
vendor/github.com/charmbracelet/x/ansi/style.go
generated
vendored
603
vendor/github.com/charmbracelet/x/ansi/style.go
generated
vendored
@ -17,9 +17,7 @@ type Attr = int
|
||||
// Style represents an ANSI SGR (Select Graphic Rendition) style.
|
||||
type Style []string
|
||||
|
||||
// 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.
|
||||
// NewStyle returns a new style with the given attributes.
|
||||
func NewStyle(attrs ...Attr) Style {
|
||||
if len(attrs) == 0 {
|
||||
return Style{}
|
||||
@ -48,8 +46,7 @@ func (s Style) String() string {
|
||||
return "\x1b[" + strings.Join(s, ";") + "m"
|
||||
}
|
||||
|
||||
// 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.
|
||||
// Styled returns a styled string with the given style applied.
|
||||
func (s Style) Styled(str string) string {
|
||||
if len(s) == 0 {
|
||||
return str
|
||||
@ -57,211 +54,161 @@ func (s Style) Styled(str string) string {
|
||||
return s.String() + str + ResetStyle
|
||||
}
|
||||
|
||||
// Reset appends the reset style attribute to the style. This resets all
|
||||
// formatting attributes to their defaults.
|
||||
// Reset appends the reset style attribute to the style.
|
||||
func (s Style) Reset() Style {
|
||||
return append(s, attrReset)
|
||||
return append(s, resetAttr)
|
||||
}
|
||||
|
||||
// Bold appends the bold or normal intensity style attribute to the style.
|
||||
// You can use [Style.Normal] to reset to normal intensity.
|
||||
// Bold appends the bold style attribute to the style.
|
||||
func (s Style) Bold() Style {
|
||||
return append(s, attrBold)
|
||||
return append(s, boldAttr)
|
||||
}
|
||||
|
||||
// Faint appends the faint or normal intensity style attribute to the style.
|
||||
// You can use [Style.Normal] to reset to normal intensity.
|
||||
// Faint appends the faint style attribute to the style.
|
||||
func (s Style) Faint() Style {
|
||||
return append(s, attrFaint)
|
||||
return append(s, faintAttr)
|
||||
}
|
||||
|
||||
// Italic appends the italic or no italic style attribute to the style.
|
||||
// When v is true, text is rendered in italic. When false, italic is disabled.
|
||||
func (s Style) Italic(v bool) Style {
|
||||
if v {
|
||||
return append(s, attrItalic)
|
||||
}
|
||||
return append(s, attrNoItalic)
|
||||
// Italic appends the italic style attribute to the style.
|
||||
func (s Style) Italic() Style {
|
||||
return append(s, italicAttr)
|
||||
}
|
||||
|
||||
// Underline appends the underline or no underline style attribute to the style.
|
||||
// When v is true, text is underlined. When false, underline is disabled.
|
||||
func (s Style) Underline(v bool) Style {
|
||||
if v {
|
||||
return append(s, attrUnderline)
|
||||
}
|
||||
return append(s, attrNoUnderline)
|
||||
// Underline appends the underline style attribute to the style.
|
||||
func (s Style) Underline() Style {
|
||||
return append(s, underlineAttr)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
switch u {
|
||||
case UnderlineStyleNone:
|
||||
return s.Underline(false)
|
||||
case UnderlineStyleSingle:
|
||||
return s.Underline(true)
|
||||
case UnderlineStyleDouble:
|
||||
return append(s, underlineStyleDouble)
|
||||
case UnderlineStyleCurly:
|
||||
return append(s, underlineStyleCurly)
|
||||
case UnderlineStyleDotted:
|
||||
return append(s, underlineStyleDotted)
|
||||
case UnderlineStyleDashed:
|
||||
return append(s, underlineStyleDashed)
|
||||
case NoUnderlineStyle:
|
||||
return s.NoUnderline()
|
||||
case SingleUnderlineStyle:
|
||||
return s.Underline()
|
||||
case DoubleUnderlineStyle:
|
||||
return append(s, doubleUnderlineStyle)
|
||||
case CurlyUnderlineStyle:
|
||||
return append(s, curlyUnderlineStyle)
|
||||
case DottedUnderlineStyle:
|
||||
return append(s, dottedUnderlineStyle)
|
||||
case DashedUnderlineStyle:
|
||||
return append(s, dashedUnderlineStyle)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Blink appends the slow blink or no blink style attribute to the style.
|
||||
// When v is true, text blinks slowly (less than 150 per minute). When false,
|
||||
// blinking is disabled.
|
||||
func (s Style) Blink(v bool) Style {
|
||||
if v {
|
||||
return append(s, attrBlink)
|
||||
}
|
||||
return append(s, attrNoBlink)
|
||||
// DoubleUnderline appends the double underline style attribute to the style.
|
||||
// This is a convenience method for UnderlineStyle(DoubleUnderlineStyle).
|
||||
func (s Style) DoubleUnderline() Style {
|
||||
return s.UnderlineStyle(DoubleUnderlineStyle)
|
||||
}
|
||||
|
||||
// RapidBlink appends the rapid blink or no blink style attribute to the style.
|
||||
// When v is true, text blinks rapidly (150+ per minute). When false, blinking
|
||||
// is disabled.
|
||||
//
|
||||
// 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)
|
||||
// CurlyUnderline appends the curly underline style attribute to the style.
|
||||
// This is a convenience method for UnderlineStyle(CurlyUnderlineStyle).
|
||||
func (s Style) CurlyUnderline() Style {
|
||||
return s.UnderlineStyle(CurlyUnderlineStyle)
|
||||
}
|
||||
|
||||
// Reverse appends the reverse or no reverse style attribute to the style.
|
||||
// When v is true, foreground and background colors are swapped. When false,
|
||||
// reverse video is disabled.
|
||||
func (s Style) Reverse(v bool) Style {
|
||||
if v {
|
||||
return append(s, attrReverse)
|
||||
}
|
||||
return append(s, attrNoReverse)
|
||||
// DottedUnderline appends the dotted underline style attribute to the style.
|
||||
// This is a convenience method for UnderlineStyle(DottedUnderlineStyle).
|
||||
func (s Style) DottedUnderline() Style {
|
||||
return s.UnderlineStyle(DottedUnderlineStyle)
|
||||
}
|
||||
|
||||
// Conceal appends the conceal or no conceal style attribute to the style.
|
||||
// When v is true, text is hidden/concealed. When false, concealment is
|
||||
// disabled.
|
||||
func (s Style) Conceal(v bool) Style {
|
||||
if v {
|
||||
return append(s, attrConceal)
|
||||
}
|
||||
return append(s, attrNoConceal)
|
||||
// DashedUnderline appends the dashed underline style attribute to the style.
|
||||
// This is a convenience method for UnderlineStyle(DashedUnderlineStyle).
|
||||
func (s Style) DashedUnderline() Style {
|
||||
return s.UnderlineStyle(DashedUnderlineStyle)
|
||||
}
|
||||
|
||||
// Strikethrough appends the strikethrough or no strikethrough style attribute
|
||||
// to the style. When v is true, text is rendered with a horizontal line through
|
||||
// it. When false, strikethrough is disabled.
|
||||
func (s Style) Strikethrough(v bool) Style {
|
||||
if v {
|
||||
return append(s, attrStrikethrough)
|
||||
}
|
||||
return append(s, attrNoStrikethrough)
|
||||
// SlowBlink appends the slow blink style attribute to the style.
|
||||
func (s Style) SlowBlink() Style {
|
||||
return append(s, slowBlinkAttr)
|
||||
}
|
||||
|
||||
// Normal appends the normal intensity style attribute to the style. This
|
||||
// resets [Style.Bold] and [Style.Faint] attributes.
|
||||
func (s Style) Normal() Style {
|
||||
return append(s, attrNormalIntensity)
|
||||
// RapidBlink appends the rapid blink style attribute to the style.
|
||||
func (s Style) RapidBlink() Style {
|
||||
return append(s, rapidBlinkAttr)
|
||||
}
|
||||
|
||||
// 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.
|
||||
//
|
||||
// Deprecated: use [Style.Italic](false) instead.
|
||||
func (s Style) NoItalic() Style {
|
||||
return append(s, attrNoItalic)
|
||||
return append(s, noItalicAttr)
|
||||
}
|
||||
|
||||
// NoUnderline appends the no underline style attribute to the style.
|
||||
//
|
||||
// Deprecated: use [Style.Underline](false) instead.
|
||||
func (s Style) NoUnderline() Style {
|
||||
return append(s, attrNoUnderline)
|
||||
return append(s, noUnderlineAttr)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return append(s, attrNoBlink)
|
||||
return append(s, noBlinkAttr)
|
||||
}
|
||||
|
||||
// NoReverse appends the no reverse style attribute to the style.
|
||||
//
|
||||
// Deprecated: use [Style.Reverse](false) instead.
|
||||
func (s Style) NoReverse() Style {
|
||||
return append(s, attrNoReverse)
|
||||
return append(s, noReverseAttr)
|
||||
}
|
||||
|
||||
// NoConceal appends the no conceal style attribute to the style.
|
||||
//
|
||||
// Deprecated: use [Style.Conceal](false) instead.
|
||||
func (s Style) NoConceal() Style {
|
||||
return append(s, attrNoConceal)
|
||||
return append(s, noConcealAttr)
|
||||
}
|
||||
|
||||
// NoStrikethrough appends the no strikethrough style attribute to the style.
|
||||
//
|
||||
// Deprecated: use [Style.Strikethrough](false) instead.
|
||||
func (s Style) NoStrikethrough() Style {
|
||||
return append(s, attrNoStrikethrough)
|
||||
return append(s, noStrikethroughAttr)
|
||||
}
|
||||
|
||||
// DefaultForegroundColor appends the default foreground color style attribute to the style.
|
||||
//
|
||||
// Deprecated: use [Style.ForegroundColor](nil) instead.
|
||||
func (s Style) DefaultForegroundColor() Style {
|
||||
return append(s, attrDefaultForegroundColor)
|
||||
return append(s, defaultForegroundColorAttr)
|
||||
}
|
||||
|
||||
// DefaultBackgroundColor appends the default background color style attribute to the style.
|
||||
//
|
||||
// Deprecated: use [Style.BackgroundColor](nil) instead.
|
||||
func (s Style) DefaultBackgroundColor() Style {
|
||||
return append(s, attrDefaultBackgroundColor)
|
||||
return append(s, defaultBackgroundColorAttr)
|
||||
}
|
||||
|
||||
// DefaultUnderlineColor appends the default underline color style attribute to the style.
|
||||
//
|
||||
// Deprecated: use [Style.UnderlineColor](nil) instead.
|
||||
func (s Style) DefaultUnderlineColor() Style {
|
||||
return append(s, attrDefaultUnderlineColor)
|
||||
return append(s, defaultUnderlineColorAttr)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
if c == nil {
|
||||
return append(s, attrDefaultForegroundColor)
|
||||
}
|
||||
return append(s, foregroundColorString(c))
|
||||
}
|
||||
|
||||
// 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 {
|
||||
if c == nil {
|
||||
return append(s, attrDefaultBackgroundColor)
|
||||
}
|
||||
return append(s, backgroundColorString(c))
|
||||
}
|
||||
|
||||
// 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 {
|
||||
if c == nil {
|
||||
return append(s, attrDefaultUnderlineColor)
|
||||
}
|
||||
return append(s, underlineColorString(c))
|
||||
}
|
||||
|
||||
@ -270,216 +217,146 @@ func (s Style) UnderlineColor(c Color) Style {
|
||||
type UnderlineStyle = byte
|
||||
|
||||
const (
|
||||
underlineStyleDouble = "4:2"
|
||||
underlineStyleCurly = "4:3"
|
||||
underlineStyleDotted = "4:4"
|
||||
underlineStyleDashed = "4:5"
|
||||
doubleUnderlineStyle = "4:2"
|
||||
curlyUnderlineStyle = "4:3"
|
||||
dottedUnderlineStyle = "4:4"
|
||||
dashedUnderlineStyle = "4:5"
|
||||
)
|
||||
|
||||
// Underline styles constants.
|
||||
const (
|
||||
UnderlineStyleNone UnderlineStyle = iota
|
||||
UnderlineStyleSingle
|
||||
UnderlineStyleDouble
|
||||
UnderlineStyleCurly
|
||||
UnderlineStyleDotted
|
||||
UnderlineStyleDashed
|
||||
)
|
||||
|
||||
// Underline styles constants.
|
||||
//
|
||||
// Deprecated: use [UnderlineStyleNone], [UnderlineStyleSingle], etc. instead.
|
||||
const (
|
||||
// NoUnderlineStyle is the default underline style.
|
||||
NoUnderlineStyle UnderlineStyle = iota
|
||||
// SingleUnderlineStyle is a single underline style.
|
||||
SingleUnderlineStyle
|
||||
// DoubleUnderlineStyle is a double underline style.
|
||||
DoubleUnderlineStyle
|
||||
// CurlyUnderlineStyle is a curly underline style.
|
||||
CurlyUnderlineStyle
|
||||
// DottedUnderlineStyle is a dotted underline style.
|
||||
DottedUnderlineStyle
|
||||
// DashedUnderlineStyle is a dashed underline style.
|
||||
DashedUnderlineStyle
|
||||
)
|
||||
|
||||
// SGR (Select Graphic Rendition) style attributes.
|
||||
// See: https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters
|
||||
const (
|
||||
AttrReset Attr = 0
|
||||
AttrBold Attr = 1
|
||||
AttrFaint Attr = 2
|
||||
AttrItalic Attr = 3
|
||||
AttrUnderline Attr = 4
|
||||
AttrBlink Attr = 5
|
||||
AttrRapidBlink Attr = 6
|
||||
AttrReverse Attr = 7
|
||||
AttrConceal Attr = 8
|
||||
AttrStrikethrough Attr = 9
|
||||
AttrNormalIntensity Attr = 22
|
||||
AttrNoItalic Attr = 23
|
||||
AttrNoUnderline Attr = 24
|
||||
AttrNoBlink Attr = 25
|
||||
AttrNoReverse Attr = 27
|
||||
AttrNoConceal Attr = 28
|
||||
AttrNoStrikethrough Attr = 29
|
||||
AttrBlackForegroundColor Attr = 30
|
||||
AttrRedForegroundColor Attr = 31
|
||||
AttrGreenForegroundColor Attr = 32
|
||||
AttrYellowForegroundColor Attr = 33
|
||||
AttrBlueForegroundColor Attr = 34
|
||||
AttrMagentaForegroundColor Attr = 35
|
||||
AttrCyanForegroundColor Attr = 36
|
||||
AttrWhiteForegroundColor Attr = 37
|
||||
AttrExtendedForegroundColor Attr = 38
|
||||
AttrDefaultForegroundColor Attr = 39
|
||||
AttrBlackBackgroundColor Attr = 40
|
||||
AttrRedBackgroundColor Attr = 41
|
||||
AttrGreenBackgroundColor Attr = 42
|
||||
AttrYellowBackgroundColor Attr = 43
|
||||
AttrBlueBackgroundColor Attr = 44
|
||||
AttrMagentaBackgroundColor Attr = 45
|
||||
AttrCyanBackgroundColor Attr = 46
|
||||
AttrWhiteBackgroundColor Attr = 47
|
||||
AttrExtendedBackgroundColor Attr = 48
|
||||
AttrDefaultBackgroundColor Attr = 49
|
||||
AttrExtendedUnderlineColor Attr = 58
|
||||
AttrDefaultUnderlineColor Attr = 59
|
||||
AttrBrightBlackForegroundColor Attr = 90
|
||||
AttrBrightRedForegroundColor Attr = 91
|
||||
AttrBrightGreenForegroundColor Attr = 92
|
||||
AttrBrightYellowForegroundColor Attr = 93
|
||||
AttrBrightBlueForegroundColor Attr = 94
|
||||
AttrBrightMagentaForegroundColor Attr = 95
|
||||
AttrBrightCyanForegroundColor Attr = 96
|
||||
AttrBrightWhiteForegroundColor Attr = 97
|
||||
AttrBrightBlackBackgroundColor Attr = 100
|
||||
AttrBrightRedBackgroundColor Attr = 101
|
||||
AttrBrightGreenBackgroundColor Attr = 102
|
||||
AttrBrightYellowBackgroundColor Attr = 103
|
||||
AttrBrightBlueBackgroundColor Attr = 104
|
||||
AttrBrightMagentaBackgroundColor Attr = 105
|
||||
AttrBrightCyanBackgroundColor Attr = 106
|
||||
AttrBrightWhiteBackgroundColor Attr = 107
|
||||
ResetAttr Attr = 0
|
||||
BoldAttr Attr = 1
|
||||
FaintAttr Attr = 2
|
||||
ItalicAttr Attr = 3
|
||||
UnderlineAttr Attr = 4
|
||||
SlowBlinkAttr Attr = 5
|
||||
RapidBlinkAttr Attr = 6
|
||||
ReverseAttr Attr = 7
|
||||
ConcealAttr Attr = 8
|
||||
StrikethroughAttr Attr = 9
|
||||
NormalIntensityAttr Attr = 22
|
||||
NoItalicAttr Attr = 23
|
||||
NoUnderlineAttr Attr = 24
|
||||
NoBlinkAttr Attr = 25
|
||||
NoReverseAttr Attr = 27
|
||||
NoConcealAttr Attr = 28
|
||||
NoStrikethroughAttr Attr = 29
|
||||
BlackForegroundColorAttr Attr = 30
|
||||
RedForegroundColorAttr Attr = 31
|
||||
GreenForegroundColorAttr Attr = 32
|
||||
YellowForegroundColorAttr Attr = 33
|
||||
BlueForegroundColorAttr Attr = 34
|
||||
MagentaForegroundColorAttr Attr = 35
|
||||
CyanForegroundColorAttr Attr = 36
|
||||
WhiteForegroundColorAttr Attr = 37
|
||||
ExtendedForegroundColorAttr Attr = 38
|
||||
DefaultForegroundColorAttr Attr = 39
|
||||
BlackBackgroundColorAttr Attr = 40
|
||||
RedBackgroundColorAttr Attr = 41
|
||||
GreenBackgroundColorAttr Attr = 42
|
||||
YellowBackgroundColorAttr Attr = 43
|
||||
BlueBackgroundColorAttr Attr = 44
|
||||
MagentaBackgroundColorAttr Attr = 45
|
||||
CyanBackgroundColorAttr Attr = 46
|
||||
WhiteBackgroundColorAttr Attr = 47
|
||||
ExtendedBackgroundColorAttr Attr = 48
|
||||
DefaultBackgroundColorAttr Attr = 49
|
||||
ExtendedUnderlineColorAttr Attr = 58
|
||||
DefaultUnderlineColorAttr Attr = 59
|
||||
BrightBlackForegroundColorAttr Attr = 90
|
||||
BrightRedForegroundColorAttr Attr = 91
|
||||
BrightGreenForegroundColorAttr Attr = 92
|
||||
BrightYellowForegroundColorAttr Attr = 93
|
||||
BrightBlueForegroundColorAttr Attr = 94
|
||||
BrightMagentaForegroundColorAttr Attr = 95
|
||||
BrightCyanForegroundColorAttr Attr = 96
|
||||
BrightWhiteForegroundColorAttr Attr = 97
|
||||
BrightBlackBackgroundColorAttr Attr = 100
|
||||
BrightRedBackgroundColorAttr Attr = 101
|
||||
BrightGreenBackgroundColorAttr Attr = 102
|
||||
BrightYellowBackgroundColorAttr Attr = 103
|
||||
BrightBlueBackgroundColorAttr Attr = 104
|
||||
BrightMagentaBackgroundColorAttr Attr = 105
|
||||
BrightCyanBackgroundColorAttr Attr = 106
|
||||
BrightWhiteBackgroundColorAttr Attr = 107
|
||||
|
||||
AttrRGBColorIntroducer Attr = 2
|
||||
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
|
||||
RGBColorIntroducerAttr Attr = 2
|
||||
ExtendedColorIntroducerAttr Attr = 5
|
||||
)
|
||||
|
||||
const (
|
||||
attrReset = "0"
|
||||
attrBold = "1"
|
||||
attrFaint = "2"
|
||||
attrItalic = "3"
|
||||
attrUnderline = "4"
|
||||
attrBlink = "5"
|
||||
attrRapidBlink = "6"
|
||||
attrReverse = "7"
|
||||
attrConceal = "8"
|
||||
attrStrikethrough = "9"
|
||||
attrNormalIntensity = "22"
|
||||
attrNoItalic = "23"
|
||||
attrNoUnderline = "24"
|
||||
attrNoBlink = "25"
|
||||
attrNoReverse = "27"
|
||||
attrNoConceal = "28"
|
||||
attrNoStrikethrough = "29"
|
||||
attrBlackForegroundColor = "30"
|
||||
attrRedForegroundColor = "31"
|
||||
attrGreenForegroundColor = "32"
|
||||
attrYellowForegroundColor = "33"
|
||||
attrBlueForegroundColor = "34"
|
||||
attrMagentaForegroundColor = "35"
|
||||
attrCyanForegroundColor = "36"
|
||||
attrWhiteForegroundColor = "37"
|
||||
attrExtendedForegroundColor = "38"
|
||||
attrDefaultForegroundColor = "39"
|
||||
attrBlackBackgroundColor = "40"
|
||||
attrRedBackgroundColor = "41"
|
||||
attrGreenBackgroundColor = "42"
|
||||
attrYellowBackgroundColor = "43"
|
||||
attrBlueBackgroundColor = "44"
|
||||
attrMagentaBackgroundColor = "45"
|
||||
attrCyanBackgroundColor = "46"
|
||||
attrWhiteBackgroundColor = "47"
|
||||
attrExtendedBackgroundColor = "48"
|
||||
attrDefaultBackgroundColor = "49"
|
||||
attrExtendedUnderlineColor = "58"
|
||||
attrDefaultUnderlineColor = "59"
|
||||
attrBrightBlackForegroundColor = "90"
|
||||
attrBrightRedForegroundColor = "91"
|
||||
attrBrightGreenForegroundColor = "92"
|
||||
attrBrightYellowForegroundColor = "93"
|
||||
attrBrightBlueForegroundColor = "94"
|
||||
attrBrightMagentaForegroundColor = "95"
|
||||
attrBrightCyanForegroundColor = "96"
|
||||
attrBrightWhiteForegroundColor = "97"
|
||||
attrBrightBlackBackgroundColor = "100"
|
||||
attrBrightRedBackgroundColor = "101"
|
||||
attrBrightGreenBackgroundColor = "102"
|
||||
attrBrightYellowBackgroundColor = "103"
|
||||
attrBrightBlueBackgroundColor = "104"
|
||||
attrBrightMagentaBackgroundColor = "105"
|
||||
attrBrightCyanBackgroundColor = "106"
|
||||
attrBrightWhiteBackgroundColor = "107"
|
||||
resetAttr = "0"
|
||||
boldAttr = "1"
|
||||
faintAttr = "2"
|
||||
italicAttr = "3"
|
||||
underlineAttr = "4"
|
||||
slowBlinkAttr = "5"
|
||||
rapidBlinkAttr = "6"
|
||||
reverseAttr = "7"
|
||||
concealAttr = "8"
|
||||
strikethroughAttr = "9"
|
||||
normalIntensityAttr = "22"
|
||||
noItalicAttr = "23"
|
||||
noUnderlineAttr = "24"
|
||||
noBlinkAttr = "25"
|
||||
noReverseAttr = "27"
|
||||
noConcealAttr = "28"
|
||||
noStrikethroughAttr = "29"
|
||||
blackForegroundColorAttr = "30"
|
||||
redForegroundColorAttr = "31"
|
||||
greenForegroundColorAttr = "32"
|
||||
yellowForegroundColorAttr = "33"
|
||||
blueForegroundColorAttr = "34"
|
||||
magentaForegroundColorAttr = "35"
|
||||
cyanForegroundColorAttr = "36"
|
||||
whiteForegroundColorAttr = "37"
|
||||
extendedForegroundColorAttr = "38"
|
||||
defaultForegroundColorAttr = "39"
|
||||
blackBackgroundColorAttr = "40"
|
||||
redBackgroundColorAttr = "41"
|
||||
greenBackgroundColorAttr = "42"
|
||||
yellowBackgroundColorAttr = "43"
|
||||
blueBackgroundColorAttr = "44"
|
||||
magentaBackgroundColorAttr = "45"
|
||||
cyanBackgroundColorAttr = "46"
|
||||
whiteBackgroundColorAttr = "47"
|
||||
extendedBackgroundColorAttr = "48"
|
||||
defaultBackgroundColorAttr = "49"
|
||||
extendedUnderlineColorAttr = "58"
|
||||
defaultUnderlineColorAttr = "59"
|
||||
brightBlackForegroundColorAttr = "90"
|
||||
brightRedForegroundColorAttr = "91"
|
||||
brightGreenForegroundColorAttr = "92"
|
||||
brightYellowForegroundColorAttr = "93"
|
||||
brightBlueForegroundColorAttr = "94"
|
||||
brightMagentaForegroundColorAttr = "95"
|
||||
brightCyanForegroundColorAttr = "96"
|
||||
brightWhiteForegroundColorAttr = "97"
|
||||
brightBlackBackgroundColorAttr = "100"
|
||||
brightRedBackgroundColorAttr = "101"
|
||||
brightGreenBackgroundColorAttr = "102"
|
||||
brightYellowBackgroundColorAttr = "103"
|
||||
brightBlueBackgroundColorAttr = "104"
|
||||
brightMagentaBackgroundColorAttr = "105"
|
||||
brightCyanBackgroundColorAttr = "106"
|
||||
brightWhiteBackgroundColorAttr = "107"
|
||||
)
|
||||
|
||||
// foregroundColorString returns the style SGR attribute for the given
|
||||
@ -492,37 +369,37 @@ func foregroundColorString(c Color) string {
|
||||
// "3<n>" or "9<n>" where n is the color number from 0 to 7
|
||||
switch c {
|
||||
case Black:
|
||||
return attrBlackForegroundColor
|
||||
return blackForegroundColorAttr
|
||||
case Red:
|
||||
return attrRedForegroundColor
|
||||
return redForegroundColorAttr
|
||||
case Green:
|
||||
return attrGreenForegroundColor
|
||||
return greenForegroundColorAttr
|
||||
case Yellow:
|
||||
return attrYellowForegroundColor
|
||||
return yellowForegroundColorAttr
|
||||
case Blue:
|
||||
return attrBlueForegroundColor
|
||||
return blueForegroundColorAttr
|
||||
case Magenta:
|
||||
return attrMagentaForegroundColor
|
||||
return magentaForegroundColorAttr
|
||||
case Cyan:
|
||||
return attrCyanForegroundColor
|
||||
return cyanForegroundColorAttr
|
||||
case White:
|
||||
return attrWhiteForegroundColor
|
||||
return whiteForegroundColorAttr
|
||||
case BrightBlack:
|
||||
return attrBrightBlackForegroundColor
|
||||
return brightBlackForegroundColorAttr
|
||||
case BrightRed:
|
||||
return attrBrightRedForegroundColor
|
||||
return brightRedForegroundColorAttr
|
||||
case BrightGreen:
|
||||
return attrBrightGreenForegroundColor
|
||||
return brightGreenForegroundColorAttr
|
||||
case BrightYellow:
|
||||
return attrBrightYellowForegroundColor
|
||||
return brightYellowForegroundColorAttr
|
||||
case BrightBlue:
|
||||
return attrBrightBlueForegroundColor
|
||||
return brightBlueForegroundColorAttr
|
||||
case BrightMagenta:
|
||||
return attrBrightMagentaForegroundColor
|
||||
return brightMagentaForegroundColorAttr
|
||||
case BrightCyan:
|
||||
return attrBrightCyanForegroundColor
|
||||
return brightCyanForegroundColorAttr
|
||||
case BrightWhite:
|
||||
return attrBrightWhiteForegroundColor
|
||||
return brightWhiteForegroundColorAttr
|
||||
}
|
||||
case ExtendedColor:
|
||||
// 256-color ANSI foreground
|
||||
@ -537,7 +414,7 @@ func foregroundColorString(c Color) string {
|
||||
strconv.FormatUint(uint64(shift(g)), 10) + ";" +
|
||||
strconv.FormatUint(uint64(shift(b)), 10)
|
||||
}
|
||||
return attrDefaultForegroundColor
|
||||
return defaultForegroundColorAttr
|
||||
}
|
||||
|
||||
// backgroundColorString returns the style SGR attribute for the given
|
||||
@ -550,37 +427,37 @@ func backgroundColorString(c Color) string {
|
||||
// "4<n>" or "10<n>" where n is the color number from 0 to 7
|
||||
switch c {
|
||||
case Black:
|
||||
return attrBlackBackgroundColor
|
||||
return blackBackgroundColorAttr
|
||||
case Red:
|
||||
return attrRedBackgroundColor
|
||||
return redBackgroundColorAttr
|
||||
case Green:
|
||||
return attrGreenBackgroundColor
|
||||
return greenBackgroundColorAttr
|
||||
case Yellow:
|
||||
return attrYellowBackgroundColor
|
||||
return yellowBackgroundColorAttr
|
||||
case Blue:
|
||||
return attrBlueBackgroundColor
|
||||
return blueBackgroundColorAttr
|
||||
case Magenta:
|
||||
return attrMagentaBackgroundColor
|
||||
return magentaBackgroundColorAttr
|
||||
case Cyan:
|
||||
return attrCyanBackgroundColor
|
||||
return cyanBackgroundColorAttr
|
||||
case White:
|
||||
return attrWhiteBackgroundColor
|
||||
return whiteBackgroundColorAttr
|
||||
case BrightBlack:
|
||||
return attrBrightBlackBackgroundColor
|
||||
return brightBlackBackgroundColorAttr
|
||||
case BrightRed:
|
||||
return attrBrightRedBackgroundColor
|
||||
return brightRedBackgroundColorAttr
|
||||
case BrightGreen:
|
||||
return attrBrightGreenBackgroundColor
|
||||
return brightGreenBackgroundColorAttr
|
||||
case BrightYellow:
|
||||
return attrBrightYellowBackgroundColor
|
||||
return brightYellowBackgroundColorAttr
|
||||
case BrightBlue:
|
||||
return attrBrightBlueBackgroundColor
|
||||
return brightBlueBackgroundColorAttr
|
||||
case BrightMagenta:
|
||||
return attrBrightMagentaBackgroundColor
|
||||
return brightMagentaBackgroundColorAttr
|
||||
case BrightCyan:
|
||||
return attrBrightCyanBackgroundColor
|
||||
return brightCyanBackgroundColorAttr
|
||||
case BrightWhite:
|
||||
return attrBrightWhiteBackgroundColor
|
||||
return brightWhiteBackgroundColorAttr
|
||||
}
|
||||
case ExtendedColor:
|
||||
// 256-color ANSI foreground
|
||||
@ -595,7 +472,7 @@ func backgroundColorString(c Color) string {
|
||||
strconv.FormatUint(uint64(shift(g)), 10) + ";" +
|
||||
strconv.FormatUint(uint64(shift(b)), 10)
|
||||
}
|
||||
return attrDefaultBackgroundColor
|
||||
return defaultBackgroundColorAttr
|
||||
}
|
||||
|
||||
// underlineColorString returns the style SGR attribute for the given underline
|
||||
@ -621,7 +498,7 @@ func underlineColorString(c Color) string {
|
||||
strconv.FormatUint(uint64(shift(g)), 10) + ";" +
|
||||
strconv.FormatUint(uint64(shift(b)), 10)
|
||||
}
|
||||
return attrDefaultUnderlineColor
|
||||
return defaultUnderlineColorAttr
|
||||
}
|
||||
|
||||
// ReadStyleColor decodes a color from a slice of parameters. It returns the
|
||||
@ -649,7 +526,7 @@ func underlineColorString(c Color) string {
|
||||
// 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
|
||||
// 4. Support reading RGBA colors
|
||||
func ReadStyleColor(params Params, co *color.Color) int {
|
||||
func ReadStyleColor(params Params, co *color.Color) (n int) {
|
||||
if len(params) < 2 { // Need at least SGR type and color type
|
||||
return 0
|
||||
}
|
||||
@ -658,7 +535,7 @@ func ReadStyleColor(params Params, co *color.Color) int {
|
||||
s := params[0]
|
||||
p := params[1]
|
||||
colorType := p.Param(0)
|
||||
n := 2
|
||||
n = 2
|
||||
|
||||
paramsfn := func() (p1, p2, p3, p4 int) {
|
||||
// Where should we start reading the color?
|
||||
@ -717,7 +594,7 @@ func ReadStyleColor(params Params, co *color.Color) int {
|
||||
B: uint8(b), //nolint:gosec
|
||||
A: 0xff,
|
||||
}
|
||||
return n
|
||||
return //nolint:nakedret
|
||||
|
||||
case 3: // CMY direct color
|
||||
if len(params) < 5 {
|
||||
@ -735,7 +612,7 @@ func ReadStyleColor(params Params, co *color.Color) int {
|
||||
Y: uint8(y), //nolint:gosec
|
||||
K: 0,
|
||||
}
|
||||
return n
|
||||
return //nolint:nakedret
|
||||
|
||||
case 4: // CMYK direct color
|
||||
if len(params) < 6 {
|
||||
@ -753,7 +630,7 @@ func ReadStyleColor(params Params, co *color.Color) int {
|
||||
Y: uint8(y), //nolint:gosec
|
||||
K: uint8(k), //nolint:gosec
|
||||
}
|
||||
return n
|
||||
return //nolint:nakedret
|
||||
|
||||
case 5: // indexed color
|
||||
if len(params) < 3 {
|
||||
@ -788,7 +665,7 @@ func ReadStyleColor(params Params, co *color.Color) int {
|
||||
B: uint8(b), //nolint:gosec
|
||||
A: uint8(a), //nolint:gosec
|
||||
}
|
||||
return n
|
||||
return //nolint:nakedret
|
||||
|
||||
default:
|
||||
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
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"bytes"
|
||||
|
||||
"github.com/charmbracelet/x/ansi/parser"
|
||||
"github.com/clipperhouse/displaywidth"
|
||||
"github.com/clipperhouse/uax29/v2/graphemes"
|
||||
"github.com/mattn/go-runewidth"
|
||||
"github.com/rivo/uniseg"
|
||||
)
|
||||
|
||||
// Cut the string, without adding any prefix or tail strings. This function is
|
||||
@ -74,11 +74,12 @@ func truncate(m Method, s string, length int, tail string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
var cluster string
|
||||
var buf strings.Builder
|
||||
var cluster []byte
|
||||
var buf bytes.Buffer
|
||||
curWidth := 0
|
||||
ignoring := false
|
||||
pstate := parser.GroundState // initial state
|
||||
b := []byte(s)
|
||||
i := 0
|
||||
|
||||
// Here we iterate over the bytes of the string and collect printable
|
||||
@ -87,12 +88,16 @@ func truncate(m Method, s string, length int, tail string) string {
|
||||
//
|
||||
// Once we reach the given length, we start ignoring characters and only
|
||||
// collect ANSI escape codes until we reach the end of string.
|
||||
for i < len(s) {
|
||||
state, action := parser.Table.Transition(pstate, s[i])
|
||||
for i < len(b) {
|
||||
state, action := parser.Table.Transition(pstate, b[i])
|
||||
if state == parser.Utf8State {
|
||||
// This action happens when we transition to the Utf8State.
|
||||
var width int
|
||||
cluster, width = FirstGraphemeCluster(s[i:], m)
|
||||
cluster, _, width, _ = uniseg.FirstGraphemeCluster(b[i:], -1)
|
||||
if m == WcWidth {
|
||||
width = runewidth.StringWidth(string(cluster))
|
||||
}
|
||||
|
||||
// increment the index by the length of the cluster
|
||||
i += len(cluster)
|
||||
curWidth += width
|
||||
@ -113,7 +118,7 @@ func truncate(m Method, s string, length int, tail string) string {
|
||||
continue
|
||||
}
|
||||
|
||||
buf.WriteString(cluster)
|
||||
buf.Write(cluster)
|
||||
|
||||
// Done collecting, now we're back in the ground state.
|
||||
pstate = parser.GroundState
|
||||
@ -147,7 +152,7 @@ func truncate(m Method, s string, length int, tail string) string {
|
||||
}
|
||||
fallthrough
|
||||
default:
|
||||
buf.WriteByte(s[i])
|
||||
buf.WriteByte(b[i])
|
||||
i++
|
||||
}
|
||||
|
||||
@ -188,23 +193,27 @@ func truncateLeft(m Method, s string, n int, prefix string) string {
|
||||
return s
|
||||
}
|
||||
|
||||
var cluster string
|
||||
var buf strings.Builder
|
||||
var cluster []byte
|
||||
var buf bytes.Buffer
|
||||
curWidth := 0
|
||||
ignoring := true
|
||||
pstate := parser.GroundState
|
||||
b := []byte(s)
|
||||
i := 0
|
||||
|
||||
for i < len(s) {
|
||||
for i < len(b) {
|
||||
if !ignoring {
|
||||
buf.WriteString(s[i:])
|
||||
buf.Write(b[i:])
|
||||
break
|
||||
}
|
||||
|
||||
state, action := parser.Table.Transition(pstate, s[i])
|
||||
state, action := parser.Table.Transition(pstate, b[i])
|
||||
if state == parser.Utf8State {
|
||||
var width int
|
||||
cluster, width = FirstGraphemeCluster(s[i:], m)
|
||||
cluster, _, width, _ = uniseg.FirstGraphemeCluster(b[i:], -1)
|
||||
if m == WcWidth {
|
||||
width = runewidth.StringWidth(string(cluster))
|
||||
}
|
||||
|
||||
i += len(cluster)
|
||||
curWidth += width
|
||||
@ -215,7 +224,7 @@ func truncateLeft(m Method, s string, n int, prefix string) string {
|
||||
}
|
||||
|
||||
if curWidth > n {
|
||||
buf.WriteString(cluster)
|
||||
buf.Write(cluster)
|
||||
}
|
||||
|
||||
if ignoring {
|
||||
@ -250,7 +259,7 @@ func truncateLeft(m Method, s string, n int, prefix string) string {
|
||||
}
|
||||
fallthrough
|
||||
default:
|
||||
buf.WriteByte(s[i])
|
||||
buf.WriteByte(b[i])
|
||||
i++
|
||||
}
|
||||
|
||||
@ -269,22 +278,22 @@ func truncateLeft(m Method, s string, n int, prefix string) string {
|
||||
// You can use this with [Truncate], [TruncateLeft], and [Cut].
|
||||
func ByteToGraphemeRange(str string, byteStart, byteStop int) (charStart, charStop int) {
|
||||
bytePos, charPos := 0, 0
|
||||
gr := graphemes.FromString(str)
|
||||
gr := uniseg.NewGraphemes(str)
|
||||
for byteStart > bytePos {
|
||||
if !gr.Next() {
|
||||
break
|
||||
}
|
||||
bytePos += len(gr.Value())
|
||||
charPos += max(1, displaywidth.String(gr.Value()))
|
||||
bytePos += len(gr.Str())
|
||||
charPos += max(1, gr.Width())
|
||||
}
|
||||
charStart = charPos
|
||||
for byteStop > bytePos {
|
||||
if !gr.Next() {
|
||||
break
|
||||
}
|
||||
bytePos += len(gr.Value())
|
||||
charPos += max(1, displaywidth.String(gr.Value()))
|
||||
bytePos += len(gr.Str())
|
||||
charPos += max(1, gr.Width())
|
||||
}
|
||||
charStop = charPos
|
||||
return charStart, charStop
|
||||
return
|
||||
}
|
||||
|
||||
17
vendor/github.com/charmbracelet/x/ansi/urxvt.go
generated
vendored
17
vendor/github.com/charmbracelet/x/ansi/urxvt.go
generated
vendored
@ -1,17 +0,0 @@
|
||||
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,6 +4,8 @@ import (
|
||||
"bytes"
|
||||
|
||||
"github.com/charmbracelet/x/ansi/parser"
|
||||
"github.com/mattn/go-runewidth"
|
||||
"github.com/rivo/uniseg"
|
||||
)
|
||||
|
||||
// Strip removes ANSI escape codes from a string.
|
||||
@ -81,16 +83,20 @@ func stringWidth(m Method, s string) int {
|
||||
}
|
||||
|
||||
var (
|
||||
pstate = parser.GroundState // initial state
|
||||
width int
|
||||
pstate = parser.GroundState // initial state
|
||||
cluster string
|
||||
width int
|
||||
)
|
||||
|
||||
for i := 0; i < len(s); i++ {
|
||||
state, action := parser.Table.Transition(pstate, s[i])
|
||||
if state == parser.Utf8State {
|
||||
cluster, w := FirstGraphemeCluster(s[i:], m)
|
||||
var w int
|
||||
cluster, _, w, _ = uniseg.FirstGraphemeClusterInString(s[i:], -1)
|
||||
if m == WcWidth {
|
||||
w = runewidth.StringWidth(cluster)
|
||||
}
|
||||
width += w
|
||||
|
||||
i += len(cluster) - 1
|
||||
pstate = parser.GroundState
|
||||
continue
|
||||
|
||||
50
vendor/github.com/charmbracelet/x/ansi/wrap.go
generated
vendored
50
vendor/github.com/charmbracelet/x/ansi/wrap.go
generated
vendored
@ -2,11 +2,12 @@ package ansi
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/charmbracelet/x/ansi/parser"
|
||||
"github.com/mattn/go-runewidth"
|
||||
"github.com/rivo/uniseg"
|
||||
)
|
||||
|
||||
// nbsp is a non-breaking space.
|
||||
@ -54,9 +55,12 @@ func hardwrap(m Method, s string, limit int, preserveSpace bool) string {
|
||||
i := 0
|
||||
for i < len(b) {
|
||||
state, action := parser.Table.Transition(pstate, b[i])
|
||||
if state == parser.Utf8State {
|
||||
if state == parser.Utf8State { //nolint:nestif
|
||||
var width int
|
||||
cluster, width = FirstGraphemeCluster(b[i:], m)
|
||||
cluster, _, width, _ = uniseg.FirstGraphemeCluster(b[i:], -1)
|
||||
if m == WcWidth {
|
||||
width = runewidth.StringWidth(string(cluster))
|
||||
}
|
||||
i += len(cluster)
|
||||
|
||||
if curWidth+width > limit {
|
||||
@ -188,7 +192,10 @@ func wordwrap(m Method, s string, limit int, breakpoints string) string {
|
||||
state, action := parser.Table.Transition(pstate, b[i])
|
||||
if state == parser.Utf8State { //nolint:nestif
|
||||
var width int
|
||||
cluster, width = FirstGraphemeCluster(b[i:], m)
|
||||
cluster, _, width, _ = uniseg.FirstGraphemeCluster(b[i:], -1)
|
||||
if m == WcWidth {
|
||||
width = runewidth.StringWidth(string(cluster))
|
||||
}
|
||||
i += len(cluster)
|
||||
|
||||
r, _ := utf8.DecodeRune(cluster)
|
||||
@ -296,7 +303,7 @@ func wrap(m Method, s string, limit int, breakpoints string) string {
|
||||
}
|
||||
|
||||
var (
|
||||
cluster string
|
||||
cluster []byte
|
||||
buf bytes.Buffer
|
||||
word bytes.Buffer
|
||||
space bytes.Buffer
|
||||
@ -304,12 +311,10 @@ func wrap(m Method, s string, limit int, breakpoints string) string {
|
||||
curWidth int // written width of the line
|
||||
wordLen int // word buffer len without ANSI escape codes
|
||||
pstate = parser.GroundState // initial state
|
||||
b = []byte(s)
|
||||
)
|
||||
|
||||
addSpace := func() {
|
||||
if spaceWidth == 0 && space.Len() == 0 {
|
||||
return
|
||||
}
|
||||
curWidth += spaceWidth
|
||||
buf.Write(space.Bytes())
|
||||
space.Reset()
|
||||
@ -336,27 +341,30 @@ func wrap(m Method, s string, limit int, breakpoints string) string {
|
||||
}
|
||||
|
||||
i := 0
|
||||
for i < len(s) {
|
||||
state, action := parser.Table.Transition(pstate, s[i])
|
||||
for i < len(b) {
|
||||
state, action := parser.Table.Transition(pstate, b[i])
|
||||
if state == parser.Utf8State { //nolint:nestif
|
||||
var width int
|
||||
cluster, width = FirstGraphemeCluster(s[i:], m)
|
||||
cluster, _, width, _ = uniseg.FirstGraphemeCluster(b[i:], -1)
|
||||
if m == WcWidth {
|
||||
width = runewidth.StringWidth(string(cluster))
|
||||
}
|
||||
i += len(cluster)
|
||||
|
||||
r, _ := utf8.DecodeRuneInString(cluster)
|
||||
r, _ := utf8.DecodeRune(cluster)
|
||||
switch {
|
||||
case r != utf8.RuneError && unicode.IsSpace(r) && r != nbsp: // nbsp is a non-breaking space
|
||||
addWord()
|
||||
space.WriteRune(r)
|
||||
spaceWidth += width
|
||||
case strings.ContainsAny(cluster, breakpoints):
|
||||
case bytes.ContainsAny(cluster, breakpoints):
|
||||
addSpace()
|
||||
if curWidth+wordLen+width > limit {
|
||||
word.WriteString(cluster)
|
||||
word.Write(cluster)
|
||||
wordLen += width
|
||||
} else {
|
||||
addWord()
|
||||
buf.WriteString(cluster)
|
||||
buf.Write(cluster)
|
||||
curWidth += width
|
||||
}
|
||||
default:
|
||||
@ -365,17 +373,12 @@ func wrap(m Method, s string, limit int, breakpoints string) string {
|
||||
addWord()
|
||||
}
|
||||
|
||||
word.WriteString(cluster)
|
||||
word.Write(cluster)
|
||||
wordLen += width
|
||||
|
||||
if curWidth+wordLen+spaceWidth > limit {
|
||||
addNewline()
|
||||
}
|
||||
|
||||
if wordLen == limit {
|
||||
// Hardwrap the word if it's too long
|
||||
addWord()
|
||||
}
|
||||
}
|
||||
|
||||
pstate = parser.GroundState
|
||||
@ -384,7 +387,7 @@ func wrap(m Method, s string, limit int, breakpoints string) string {
|
||||
|
||||
switch action {
|
||||
case parser.PrintAction, parser.ExecuteAction:
|
||||
switch r := rune(s[i]); {
|
||||
switch r := rune(b[i]); {
|
||||
case r == '\n':
|
||||
if wordLen == 0 {
|
||||
if curWidth+spaceWidth > limit {
|
||||
@ -421,7 +424,6 @@ func wrap(m Method, s string, limit int, breakpoints string) string {
|
||||
if curWidth == limit {
|
||||
addNewline()
|
||||
}
|
||||
|
||||
word.WriteRune(r)
|
||||
wordLen++
|
||||
|
||||
@ -436,7 +438,7 @@ func wrap(m Method, s string, limit int, breakpoints string) string {
|
||||
}
|
||||
|
||||
default:
|
||||
word.WriteByte(s[i])
|
||||
word.WriteByte(b[i])
|
||||
}
|
||||
|
||||
// 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,4 +1,3 @@
|
||||
// Package cellbuf provides terminal cell buffer functionality.
|
||||
package cellbuf
|
||||
|
||||
import (
|
||||
@ -25,7 +24,7 @@ func NewCell(r rune, comb ...rune) (c *Cell) {
|
||||
}
|
||||
c.Comb = comb
|
||||
c.Width = runewidth.StringWidth(string(append([]rune{r}, comb...)))
|
||||
return c
|
||||
return
|
||||
}
|
||||
|
||||
// NewCellString returns a new cell with the given string content. This is a
|
||||
@ -47,7 +46,7 @@ func NewCellString(s string) (c *Cell) {
|
||||
c.Comb = append(c.Comb, r)
|
||||
}
|
||||
}
|
||||
return c
|
||||
return
|
||||
}
|
||||
|
||||
// NewGraphemeCell returns a new cell. This is a convenience function that
|
||||
@ -72,7 +71,7 @@ func newGraphemeCell(s string, w int) (c *Cell) {
|
||||
c.Comb = append(c.Comb, r)
|
||||
}
|
||||
}
|
||||
return c
|
||||
return
|
||||
}
|
||||
|
||||
// Line represents a line in the terminal.
|
||||
@ -105,7 +104,7 @@ func (l Line) String() (s string) {
|
||||
}
|
||||
}
|
||||
s = strings.TrimRight(s, " ")
|
||||
return s
|
||||
return
|
||||
}
|
||||
|
||||
// At returns the cell at the given x position.
|
||||
@ -151,7 +150,7 @@ func (l Line) set(x int, c *Cell, clone bool) bool {
|
||||
for j := 1; j < maxCellWidth && x-j >= 0; j++ {
|
||||
wide := l.At(x - j)
|
||||
if wide != nil && wide.Width > 1 && j < wide.Width {
|
||||
for k := range wide.Width {
|
||||
for k := 0; k < wide.Width; k++ {
|
||||
l[x-j+k] = wide.Clone().Blank()
|
||||
}
|
||||
break
|
||||
@ -207,7 +206,7 @@ func (b *Buffer) String() (s string) {
|
||||
s += "\r\n"
|
||||
}
|
||||
}
|
||||
return s
|
||||
return
|
||||
}
|
||||
|
||||
// Line returns a pointer to the line at the given y position.
|
||||
@ -297,7 +296,7 @@ func (b *Buffer) FillRect(c *Cell, rect Rectangle) {
|
||||
}
|
||||
for y := rect.Min.Y; y < rect.Max.Y; y++ {
|
||||
for x := rect.Min.X; x < rect.Max.X; x += cellWidth {
|
||||
b.setCell(x, y, c, false)
|
||||
b.setCell(x, y, c, false) //nolint:errcheck
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
n = new(Cell)
|
||||
*n = *c
|
||||
return n
|
||||
return
|
||||
}
|
||||
|
||||
// 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.
|
||||
const (
|
||||
NoUnderline = ansi.UnderlineStyleNone
|
||||
SingleUnderline = ansi.UnderlineStyleSingle
|
||||
DoubleUnderline = ansi.UnderlineStyleDouble
|
||||
CurlyUnderline = ansi.UnderlineStyleCurly
|
||||
DottedUnderline = ansi.UnderlineStyleDotted
|
||||
DashedUnderline = ansi.UnderlineStyleDashed
|
||||
NoUnderline = ansi.NoUnderlineStyle
|
||||
SingleUnderline = ansi.SingleUnderlineStyle
|
||||
DoubleUnderline = ansi.DoubleUnderlineStyle
|
||||
CurlyUnderline = ansi.CurlyUnderlineStyle
|
||||
DottedUnderline = ansi.DottedUnderlineStyle
|
||||
DashedUnderline = ansi.DashedUnderlineStyle
|
||||
)
|
||||
|
||||
// Style represents the Style of a cell.
|
||||
@ -189,7 +189,7 @@ func (s Style) Sequence() string {
|
||||
|
||||
var b ansi.Style
|
||||
|
||||
if s.Attrs != 0 { //nolint:nestif
|
||||
if s.Attrs != 0 {
|
||||
if s.Attrs&BoldAttr != 0 {
|
||||
b = b.Bold()
|
||||
}
|
||||
@ -197,31 +197,36 @@ func (s Style) Sequence() string {
|
||||
b = b.Faint()
|
||||
}
|
||||
if s.Attrs&ItalicAttr != 0 {
|
||||
b = b.Italic(true)
|
||||
b = b.Italic()
|
||||
}
|
||||
if s.Attrs&SlowBlinkAttr != 0 {
|
||||
b = b.Blink(true)
|
||||
b = b.SlowBlink()
|
||||
}
|
||||
if s.Attrs&RapidBlinkAttr != 0 {
|
||||
b = b.RapidBlink(true)
|
||||
b = b.RapidBlink()
|
||||
}
|
||||
if s.Attrs&ReverseAttr != 0 {
|
||||
b = b.Reverse(true)
|
||||
b = b.Reverse()
|
||||
}
|
||||
if s.Attrs&ConcealAttr != 0 {
|
||||
b = b.Conceal(true)
|
||||
b = b.Conceal()
|
||||
}
|
||||
if s.Attrs&StrikethroughAttr != 0 {
|
||||
b = b.Strikethrough(true)
|
||||
b = b.Strikethrough()
|
||||
}
|
||||
}
|
||||
if s.UlStyle != NoUnderline {
|
||||
switch u := s.UlStyle; u {
|
||||
case NoUnderline:
|
||||
b = b.Underline(false)
|
||||
default:
|
||||
b = b.Underline(true)
|
||||
b = b.UnderlineStyle(u)
|
||||
switch s.UlStyle {
|
||||
case SingleUnderline:
|
||||
b = b.Underline()
|
||||
case DoubleUnderline:
|
||||
b = b.DoubleUnderline()
|
||||
case CurlyUnderline:
|
||||
b = b.CurlyUnderline()
|
||||
case DottedUnderline:
|
||||
b = b.DottedUnderline()
|
||||
case DashedUnderline:
|
||||
b = b.DashedUnderline()
|
||||
}
|
||||
}
|
||||
if s.Fg != nil {
|
||||
@ -263,48 +268,64 @@ func (s Style) DiffSequence(o Style) string {
|
||||
isNormal bool
|
||||
)
|
||||
|
||||
if s.Attrs != o.Attrs { //nolint:nestif
|
||||
if s.Attrs != o.Attrs {
|
||||
if s.Attrs&BoldAttr != o.Attrs&BoldAttr {
|
||||
if s.Attrs&BoldAttr != 0 {
|
||||
b = b.Bold()
|
||||
} else if !isNormal {
|
||||
isNormal = true
|
||||
b = b.Normal()
|
||||
b = b.NormalIntensity()
|
||||
}
|
||||
}
|
||||
if s.Attrs&FaintAttr != o.Attrs&FaintAttr {
|
||||
if s.Attrs&FaintAttr != 0 {
|
||||
b = b.Faint()
|
||||
} else if !isNormal {
|
||||
b = b.Normal()
|
||||
b = b.NormalIntensity()
|
||||
}
|
||||
}
|
||||
if s.Attrs&ItalicAttr != o.Attrs&ItalicAttr {
|
||||
b = b.Italic(s.Attrs&ItalicAttr != 0)
|
||||
if s.Attrs&ItalicAttr != 0 {
|
||||
b = b.Italic()
|
||||
} else {
|
||||
b = b.NoItalic()
|
||||
}
|
||||
}
|
||||
if s.Attrs&SlowBlinkAttr != o.Attrs&SlowBlinkAttr {
|
||||
if s.Attrs&SlowBlinkAttr != 0 {
|
||||
b = b.Blink(true)
|
||||
b = b.SlowBlink()
|
||||
} else if !noBlink {
|
||||
noBlink = true
|
||||
b = b.Blink(false)
|
||||
b = b.NoBlink()
|
||||
}
|
||||
}
|
||||
if s.Attrs&RapidBlinkAttr != o.Attrs&RapidBlinkAttr {
|
||||
if s.Attrs&RapidBlinkAttr != 0 {
|
||||
b = b.RapidBlink(true)
|
||||
b = b.RapidBlink()
|
||||
} else if !noBlink {
|
||||
b = b.Blink(false)
|
||||
b = b.NoBlink()
|
||||
}
|
||||
}
|
||||
if s.Attrs&ReverseAttr != o.Attrs&ReverseAttr {
|
||||
b = b.Reverse(s.Attrs&ReverseAttr != 0)
|
||||
if s.Attrs&ReverseAttr != 0 {
|
||||
b = b.Reverse()
|
||||
} else {
|
||||
b = b.NoReverse()
|
||||
}
|
||||
}
|
||||
if s.Attrs&ConcealAttr != o.Attrs&ConcealAttr {
|
||||
b = b.Conceal(s.Attrs&ConcealAttr != 0)
|
||||
if s.Attrs&ConcealAttr != 0 {
|
||||
b = b.Conceal()
|
||||
} else {
|
||||
b = b.NoConceal()
|
||||
}
|
||||
}
|
||||
if s.Attrs&StrikethroughAttr != o.Attrs&StrikethroughAttr {
|
||||
b = b.Strikethrough(s.Attrs&StrikethroughAttr != 0)
|
||||
if 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)
|
||||
}
|
||||
|
||||
// Rectangle represents a rectangle.
|
||||
// Rectange represents a rectangle.
|
||||
type Rectangle = image.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()
|
||||
if n > 0 { //nolint:nestif
|
||||
if n > 0 {
|
||||
// Scroll up (forward)
|
||||
v = s.scrollUp(n, top, bot, 0, maxY, blank)
|
||||
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.clearToBottom(nil)
|
||||
} else {
|
||||
for i := range n {
|
||||
for i := 0; i < n; i++ {
|
||||
s.move(0, bot-i)
|
||||
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.
|
||||
if v &&
|
||||
(nonDestScrollRegion || (memoryBelow && top == 0)) {
|
||||
for i := range -n {
|
||||
for i := 0; i < -n; i++ {
|
||||
s.move(0, top+i)
|
||||
s.clearToEnd(nil, false)
|
||||
}
|
||||
@ -133,7 +133,7 @@ func (s *Screen) scrolln(n, top, bot, maxY int) (v bool) { //nolint:unparam
|
||||
}
|
||||
|
||||
if !v {
|
||||
return v
|
||||
return
|
||||
}
|
||||
|
||||
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.
|
||||
func (s *Screen) scrollUp(n, top, bot, minY, maxY int, blank *Cell) bool {
|
||||
if n == 1 && top == minY && bot == maxY { //nolint:nestif
|
||||
if n == 1 && top == minY && bot == maxY {
|
||||
s.move(0, bot)
|
||||
s.updatePen(blank)
|
||||
s.buf.WriteByte('\n')
|
||||
@ -202,14 +202,13 @@ func (s *Screen) scrollUp(n, top, bot, minY, maxY int, blank *Cell) bool {
|
||||
s.updatePen(blank)
|
||||
s.buf.WriteString(ansi.DeleteLine(1))
|
||||
} else if top == minY && bot == maxY {
|
||||
supportsSU := s.caps.Contains(capSU)
|
||||
if supportsSU {
|
||||
if s.xtermLike {
|
||||
s.move(0, bot)
|
||||
} else {
|
||||
s.move(0, top)
|
||||
}
|
||||
s.updatePen(blank)
|
||||
if supportsSU {
|
||||
if s.xtermLike {
|
||||
s.buf.WriteString(ansi.ScrollUp(n))
|
||||
} else {
|
||||
s.buf.WriteString(strings.Repeat("\n", n))
|
||||
@ -226,7 +225,7 @@ func (s *Screen) scrollUp(n, top, bot, minY, maxY int, blank *Cell) bool {
|
||||
|
||||
// scrollDown scrolls the screen down by n lines.
|
||||
func (s *Screen) scrollDown(n, top, bot, minY, maxY int, blank *Cell) bool {
|
||||
if n == 1 && top == minY && bot == maxY { //nolint:nestif
|
||||
if n == 1 && top == minY && bot == maxY {
|
||||
s.move(0, top)
|
||||
s.updatePen(blank)
|
||||
s.buf.WriteString(ansi.ReverseIndex)
|
||||
@ -237,7 +236,7 @@ func (s *Screen) scrollDown(n, top, bot, minY, maxY int, blank *Cell) bool {
|
||||
} else if top == minY && bot == maxY {
|
||||
s.move(0, top)
|
||||
s.updatePen(blank)
|
||||
if s.caps.Contains(capSD) {
|
||||
if s.xtermLike {
|
||||
s.buf.WriteString(ansi.ScrollDown(n))
|
||||
} else {
|
||||
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)
|
||||
}
|
||||
return h
|
||||
return
|
||||
}
|
||||
|
||||
// hashmap represents a single [Line] hash.
|
||||
@ -33,7 +33,7 @@ func (s *Screen) updateHashmap() {
|
||||
height := s.newbuf.Height()
|
||||
if len(s.oldhash) >= height && len(s.newhash) >= height {
|
||||
// rehash changed lines
|
||||
for i := range height {
|
||||
for i := 0; i < height; i++ {
|
||||
_, ok := s.touch[i]
|
||||
if ok {
|
||||
s.oldhash[i] = hash(s.curbuf.Line(i))
|
||||
@ -48,14 +48,14 @@ func (s *Screen) updateHashmap() {
|
||||
if len(s.newhash) != height {
|
||||
s.newhash = make([]uint64, height)
|
||||
}
|
||||
for i := range height {
|
||||
for i := 0; i < height; i++ {
|
||||
s.oldhash[i] = hash(s.curbuf.Line(i))
|
||||
s.newhash[i] = hash(s.newbuf.Line(i))
|
||||
}
|
||||
}
|
||||
|
||||
s.hashtab = make([]hashmap, height*2)
|
||||
for i := range height {
|
||||
for i := 0; i < height; i++ {
|
||||
hashval := s.oldhash[i]
|
||||
|
||||
// Find matching hash or empty slot
|
||||
@ -71,7 +71,7 @@ func (s *Screen) updateHashmap() {
|
||||
s.hashtab[idx].oldcount++
|
||||
s.hashtab[idx].oldindex = i
|
||||
}
|
||||
for i := range height {
|
||||
for i := 0; i < height; i++ {
|
||||
hashval := s.newhash[i]
|
||||
|
||||
// Find matching hash or empty slot
|
||||
@ -130,7 +130,7 @@ func (s *Screen) updateHashmap() {
|
||||
s.growHunks()
|
||||
}
|
||||
|
||||
// scrollOldhash.
|
||||
// scrollOldhash
|
||||
func (s *Screen) scrollOldhash(n, top, bot int) {
|
||||
if len(s.oldhash) == 0 {
|
||||
return
|
||||
@ -287,7 +287,7 @@ func (s *Screen) updateCost(from, to Line) (cost int) {
|
||||
cost++
|
||||
}
|
||||
}
|
||||
return cost
|
||||
return
|
||||
}
|
||||
|
||||
func (s *Screen) updateCostBlank(to Line) (cost int) {
|
||||
@ -297,5 +297,5 @@ func (s *Screen) updateCostBlank(to Line) (cost int) {
|
||||
cost++
|
||||
}
|
||||
}
|
||||
return cost
|
||||
return
|
||||
}
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
// ConvertLink converts a hyperlink to respect the given color profile.
|
||||
// Convert converts a hyperlink to respect the given color profile.
|
||||
func ConvertLink(h Link, p colorprofile.Profile) Link {
|
||||
if p == colorprofile.NoTTY {
|
||||
return Link{}
|
||||
|
||||
92
vendor/github.com/charmbracelet/x/cellbuf/pen.go
generated
vendored
92
vendor/github.com/charmbracelet/x/cellbuf/pen.go
generated
vendored
@ -1,92 +0,0 @@
|
||||
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
|
||||
|
||||
width, height := s.newbuf.Width(), s.newbuf.Height()
|
||||
if ty != fy { //nolint:nestif
|
||||
if ty != fy {
|
||||
var yseq string
|
||||
if s.caps.Contains(capVPA) && !s.opts.RelativeCursor {
|
||||
if s.xtermLike && !s.opts.RelativeCursor {
|
||||
yseq = ansi.VerticalPositionAbsolute(ty + 1)
|
||||
}
|
||||
|
||||
@ -54,13 +54,9 @@ func relativeCursorMove(s *Screen, fx, fy, tx, ty int, overwrite, useTabs, useBa
|
||||
}
|
||||
shouldScroll := !s.opts.AltScreen && fy+n >= s.scrollHeight
|
||||
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.
|
||||
yseq = lf
|
||||
s.scrollHeight = max(s.scrollHeight, fy+n)
|
||||
if s.opts.MapNL {
|
||||
fx = 0
|
||||
}
|
||||
}
|
||||
} else if ty < fy {
|
||||
n := fy - ty
|
||||
@ -68,7 +64,6 @@ func relativeCursorMove(s *Screen, fx, fy, tx, ty int, overwrite, useTabs, useBa
|
||||
yseq = cuu
|
||||
}
|
||||
if n == 1 && fy-1 > 0 {
|
||||
//nolint:godox
|
||||
// TODO: Ensure we're not unintentionally scrolling the screen up.
|
||||
yseq = ansi.ReverseIndex
|
||||
}
|
||||
@ -77,9 +72,9 @@ func relativeCursorMove(s *Screen, fx, fy, tx, ty int, overwrite, useTabs, useBa
|
||||
seq.WriteString(yseq)
|
||||
}
|
||||
|
||||
if tx != fx { //nolint:nestif
|
||||
if tx != fx {
|
||||
var xseq string
|
||||
if s.caps.Contains(capHPA) && !s.opts.RelativeCursor {
|
||||
if s.xtermLike && !s.opts.RelativeCursor {
|
||||
xseq = ansi.HorizontalPositionAbsolute(tx + 1)
|
||||
}
|
||||
|
||||
@ -98,8 +93,7 @@ func relativeCursorMove(s *Screen, fx, fy, tx, ty int, overwrite, useTabs, useBa
|
||||
if tabs > 0 {
|
||||
cht := ansi.CursorHorizontalForwardTab(tabs)
|
||||
tab := strings.Repeat("\t", tabs)
|
||||
if false && s.caps.Contains(capCHT) && len(cht) < len(tab) {
|
||||
//nolint:godox
|
||||
if false && s.xtermLike && len(cht) < len(tab) {
|
||||
// TODO: The linux console and some terminals such as
|
||||
// Alacritty don't support [ansi.CHT]. Enable this when
|
||||
// we have a way to detect this, or after 5 years when
|
||||
@ -150,7 +144,7 @@ func relativeCursorMove(s *Screen, fx, fy, tx, ty int, overwrite, useTabs, useBa
|
||||
}
|
||||
} else if tx < fx {
|
||||
n := fx - tx
|
||||
if useTabs && s.caps.Contains(capCBT) {
|
||||
if useTabs && s.xtermLike {
|
||||
// VT100 does not support backward tabs [ansi.CBT].
|
||||
|
||||
col := fx
|
||||
@ -196,7 +190,7 @@ func moveCursor(s *Screen, x, y int, overwrite bool) (seq string) {
|
||||
// Method #0: Use [ansi.CUP] if the distance is long.
|
||||
seq = ansi.CursorPosition(x+1, y+1)
|
||||
if fx == -1 || fy == -1 || notLocal(s.newbuf.Width(), fx, fy, x, y) {
|
||||
return seq
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@ -240,7 +234,7 @@ func moveCursor(s *Screen, x, y int, overwrite bool) (seq string) {
|
||||
}
|
||||
}
|
||||
|
||||
return seq
|
||||
return
|
||||
}
|
||||
|
||||
// moveCursor moves the cursor to the specified position.
|
||||
@ -248,10 +242,10 @@ func (s *Screen) moveCursor(x, y int, overwrite bool) {
|
||||
if !s.opts.AltScreen && s.cur.X == -1 && s.cur.Y == -1 {
|
||||
// First cursor movement in inline mode, move the cursor to the first
|
||||
// column before moving to the target position.
|
||||
s.buf.WriteByte('\r')
|
||||
s.buf.WriteByte('\r') //nolint:errcheck
|
||||
s.cur.X, s.cur.Y = 0, 0
|
||||
}
|
||||
s.buf.WriteString(moveCursor(s, x, y, overwrite))
|
||||
s.buf.WriteString(moveCursor(s, x, y, overwrite)) //nolint:errcheck
|
||||
s.cur.X, s.cur.Y = x, y
|
||||
}
|
||||
|
||||
@ -280,11 +274,10 @@ func (s *Screen) move(x, y int) {
|
||||
// Reset wrap around (phantom cursor) state
|
||||
if s.atPhantom {
|
||||
s.cur.X = 0
|
||||
s.buf.WriteByte('\r')
|
||||
s.atPhantom = false // reset phantom cell state
|
||||
s.buf.WriteByte('\r') //nolint:errcheck
|
||||
s.atPhantom = false // reset phantom cell state
|
||||
}
|
||||
|
||||
//nolint:godox
|
||||
// TODO: Investigate if we need to handle this case and/or if we need the
|
||||
// following code.
|
||||
//
|
||||
@ -298,7 +291,7 @@ func (s *Screen) move(x, y int) {
|
||||
//
|
||||
// if l > 0 {
|
||||
// s.cur.X = 0
|
||||
// s.buf.WriteString("\r" + strings.Repeat("\n", l))
|
||||
// s.buf.WriteString("\r" + strings.Repeat("\n", l)) //nolint:errcheck
|
||||
// }
|
||||
// }
|
||||
|
||||
@ -346,10 +339,6 @@ type ScreenOptions struct {
|
||||
HardTabs bool
|
||||
// Backspace is whether to use backspace characters to move the cursor.
|
||||
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.
|
||||
@ -376,13 +365,13 @@ type Screen struct {
|
||||
opts ScreenOptions
|
||||
mu sync.Mutex
|
||||
method ansi.Method
|
||||
scrollHeight int // keeps track of how many lines we've scrolled down (inline mode)
|
||||
altScreenMode bool // whether alternate screen mode is enabled
|
||||
cursorHidden bool // whether text cursor mode is enabled
|
||||
clear bool // whether to force clear the screen
|
||||
caps capabilities // terminal control sequence capabilities
|
||||
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
|
||||
scrollHeight int // keeps track of how many lines we've scrolled down (inline mode)
|
||||
altScreenMode bool // whether alternate screen mode is enabled
|
||||
cursorHidden bool // whether text cursor mode is enabled
|
||||
clear bool // whether to force clear the screen
|
||||
xtermLike bool // whether to use xterm-like optimizations, otherwise, it uses vt100 only
|
||||
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
|
||||
}
|
||||
|
||||
// SetMethod sets the method used to calculate the width of cells.
|
||||
@ -502,77 +491,36 @@ func (s *Screen) FillRect(cell *Cell, r Rectangle) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// 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
|
||||
// isXtermLike returns whether the terminal is xterm-like. This means that the
|
||||
// terminal supports ECMA-48 and ANSI X3.64 escape sequences.
|
||||
// xtermCaps returns a list of control sequence capabilities for the given
|
||||
// terminal type. This only supports a subset of sequences that can
|
||||
// be different among terminals.
|
||||
// NOTE: A hybrid approach would be to support Terminfo databases for a full
|
||||
// set of capabilities.
|
||||
func xtermCaps(termtype string) (v capabilities) {
|
||||
// TODO: Should this be a lookup table into each $TERM terminfo database? Like
|
||||
// we could keep a map of ANSI escape sequence to terminfo capability name and
|
||||
// check if the database supports the escape sequence. Instead of keeping a
|
||||
// list of terminal names here.
|
||||
func isXtermLike(termtype string) (v bool) {
|
||||
parts := strings.Split(termtype, "-")
|
||||
if len(parts) == 0 {
|
||||
return v
|
||||
return
|
||||
}
|
||||
|
||||
switch parts[0] {
|
||||
case
|
||||
"alacritty",
|
||||
"contour",
|
||||
"foot",
|
||||
"ghostty",
|
||||
"kitty",
|
||||
"linux",
|
||||
"rio",
|
||||
"screen",
|
||||
"st",
|
||||
"tmux",
|
||||
"wezterm",
|
||||
"xterm":
|
||||
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
|
||||
v = true
|
||||
}
|
||||
|
||||
return v
|
||||
return
|
||||
}
|
||||
|
||||
// NewScreen creates a new Screen.
|
||||
@ -600,14 +548,14 @@ func NewScreen(w io.Writer, width, height int, opts *ScreenOptions) (s *Screen)
|
||||
}
|
||||
|
||||
s.buf = new(bytes.Buffer)
|
||||
s.caps = xtermCaps(s.opts.Term)
|
||||
s.xtermLike = isXtermLike(s.opts.Term)
|
||||
s.curbuf = NewBuffer(width, height)
|
||||
s.newbuf = NewBuffer(width, height)
|
||||
s.cur = Cursor{Position: Pos(-1, -1)} // start at -1 to force a move
|
||||
s.saved = s.cur
|
||||
s.reset()
|
||||
|
||||
return s
|
||||
return
|
||||
}
|
||||
|
||||
// Width returns the width of the screen.
|
||||
@ -647,7 +595,7 @@ func (s *Screen) putCell(cell *Cell) {
|
||||
|
||||
// wrapCursor wraps the cursor to the next line.
|
||||
//
|
||||
|
||||
//nolint:unused
|
||||
func (s *Screen) wrapCursor() {
|
||||
const autoRightMargin = true
|
||||
if autoRightMargin {
|
||||
@ -680,9 +628,9 @@ func (s *Screen) putAttrCell(cell *Cell) {
|
||||
}
|
||||
|
||||
s.updatePen(cell)
|
||||
s.buf.WriteRune(cell.Rune)
|
||||
s.buf.WriteRune(cell.Rune) //nolint:errcheck
|
||||
for _, c := range cell.Comb {
|
||||
s.buf.WriteRune(c)
|
||||
s.buf.WriteRune(c) //nolint:errcheck
|
||||
}
|
||||
|
||||
s.cur.X += cell.Width
|
||||
@ -701,12 +649,12 @@ func (s *Screen) putCellLR(cell *Cell) {
|
||||
// Optimize for the lower right corner cell.
|
||||
curX := s.cur.X
|
||||
if cell == nil || !cell.Empty() {
|
||||
s.buf.WriteString(ansi.ResetModeAutoWrap)
|
||||
s.buf.WriteString(ansi.ResetAutoWrapMode) //nolint:errcheck
|
||||
s.putAttrCell(cell)
|
||||
// Writing to lower-right corner cell should not wrap.
|
||||
s.atPhantom = false
|
||||
s.cur.X = curX
|
||||
s.buf.WriteString(ansi.SetModeAutoWrap)
|
||||
s.buf.WriteString(ansi.SetAutoWrapMode) //nolint:errcheck
|
||||
}
|
||||
}
|
||||
|
||||
@ -727,11 +675,11 @@ func (s *Screen) updatePen(cell *Cell) {
|
||||
if cell.Style.Empty() && len(seq) > len(ansi.ResetStyle) {
|
||||
seq = ansi.ResetStyle
|
||||
}
|
||||
s.buf.WriteString(seq)
|
||||
s.buf.WriteString(seq) //nolint:errcheck
|
||||
s.cur.Style = cell.Style
|
||||
}
|
||||
if !cell.Link.Equal(&s.cur.Link) {
|
||||
s.buf.WriteString(ansi.SetHyperlink(cell.Link.URL, cell.Link.Params))
|
||||
s.buf.WriteString(ansi.SetHyperlink(cell.Link.URL, cell.Link.Params)) //nolint:errcheck
|
||||
s.cur.Link = cell.Link
|
||||
}
|
||||
}
|
||||
@ -764,9 +712,9 @@ func (s *Screen) emitRange(line Line, n int) (eoi bool) {
|
||||
ech := ansi.EraseCharacter(count)
|
||||
cup := ansi.CursorPosition(s.cur.X+count, s.cur.Y)
|
||||
rep := ansi.RepeatPreviousCharacter(count)
|
||||
if s.caps.Contains(capECH) && count > len(ech)+len(cup) && cell0 != nil && cell0.Clear() { //nolint:nestif
|
||||
if s.xtermLike && count > len(ech)+len(cup) && cell0 != nil && cell0.Clear() {
|
||||
s.updatePen(cell0)
|
||||
s.buf.WriteString(ech)
|
||||
s.buf.WriteString(ech) //nolint:errcheck
|
||||
|
||||
// If this is the last cell, we don't need to move the cursor.
|
||||
if count < n {
|
||||
@ -774,7 +722,7 @@ func (s *Screen) emitRange(line Line, n int) (eoi bool) {
|
||||
} else {
|
||||
return true // cursor in the middle
|
||||
}
|
||||
} else if s.caps.Contains(capREP) && count > len(rep) &&
|
||||
} else if s.xtermLike && count > len(rep) &&
|
||||
(cell0 == nil || (len(cell0.Comb) == 0 && cell0.Rune < 256)) {
|
||||
// We only support ASCII characters. Most terminals will handle
|
||||
// non-ASCII characters correctly, but some might not, ahem xterm.
|
||||
@ -792,13 +740,13 @@ func (s *Screen) emitRange(line Line, n int) (eoi bool) {
|
||||
s.putCell(cell0)
|
||||
repCount-- // cell0 is a single width cell ASCII character
|
||||
|
||||
s.buf.WriteString(ansi.RepeatPreviousCharacter(repCount))
|
||||
s.buf.WriteString(ansi.RepeatPreviousCharacter(repCount)) //nolint:errcheck
|
||||
s.cur.X += repCount
|
||||
if wrapPossible {
|
||||
s.putCell(cell0)
|
||||
}
|
||||
} else {
|
||||
for i := range count {
|
||||
for i := 0; i < count; i++ {
|
||||
s.putCell(line.At(i))
|
||||
}
|
||||
}
|
||||
@ -807,7 +755,7 @@ func (s *Screen) emitRange(line Line, n int) (eoi bool) {
|
||||
n -= count
|
||||
}
|
||||
|
||||
return eoi
|
||||
return
|
||||
}
|
||||
|
||||
// putRange puts a range of cells from the old line to the new line.
|
||||
@ -817,7 +765,7 @@ func (s *Screen) putRange(oldLine, newLine Line, y, start, end int) (eoi bool) {
|
||||
inline := min(len(ansi.CursorPosition(start+1, y+1)),
|
||||
min(len(ansi.HorizontalPositionAbsolute(start+1)),
|
||||
len(ansi.CursorForward(start+1))))
|
||||
if (end - start + 1) > inline { //nolint:nestif
|
||||
if (end - start + 1) > inline {
|
||||
var j, same int
|
||||
for j, same = start, 0; j <= end; j++ {
|
||||
oldCell, newCell := oldLine.At(j), newLine.At(j)
|
||||
@ -869,9 +817,9 @@ func (s *Screen) clearToEnd(blank *Cell, force bool) { //nolint:unparam
|
||||
s.updatePen(blank)
|
||||
count := s.newbuf.Width() - s.cur.X
|
||||
if s.el0Cost() <= count {
|
||||
s.buf.WriteString(ansi.EraseLineRight)
|
||||
s.buf.WriteString(ansi.EraseLineRight) //nolint:errcheck
|
||||
} else {
|
||||
for range count {
|
||||
for i := 0; i < count; i++ {
|
||||
s.putCell(blank)
|
||||
}
|
||||
}
|
||||
@ -891,13 +839,12 @@ func (s *Screen) clearBlank() *Cell {
|
||||
// insertCells inserts the count cells pointed by the given line at the current
|
||||
// cursor position.
|
||||
func (s *Screen) insertCells(line Line, count int) {
|
||||
supportsICH := s.caps.Contains(capICH)
|
||||
if supportsICH {
|
||||
if s.xtermLike {
|
||||
// Use [ansi.ICH] as an optimization.
|
||||
s.buf.WriteString(ansi.InsertCharacter(count))
|
||||
s.buf.WriteString(ansi.InsertCharacter(count)) //nolint:errcheck
|
||||
} else {
|
||||
// Otherwise, use [ansi.IRM] mode.
|
||||
s.buf.WriteString(ansi.SetModeInsertReplace)
|
||||
s.buf.WriteString(ansi.SetInsertReplaceMode) //nolint:errcheck
|
||||
}
|
||||
|
||||
for i := 0; count > 0; i++ {
|
||||
@ -905,8 +852,8 @@ func (s *Screen) insertCells(line Line, count int) {
|
||||
count--
|
||||
}
|
||||
|
||||
if !supportsICH {
|
||||
s.buf.WriteString(ansi.ResetModeInsertReplace)
|
||||
if !s.xtermLike {
|
||||
s.buf.WriteString(ansi.ResetInsertReplaceMode) //nolint:errcheck
|
||||
}
|
||||
}
|
||||
|
||||
@ -915,7 +862,7 @@ func (s *Screen) insertCells(line Line, count int) {
|
||||
// [ansi.EL] 0 i.e. [ansi.EraseLineRight] to clear
|
||||
// trailing spaces.
|
||||
func (s *Screen) el0Cost() int {
|
||||
if s.caps != noCaps {
|
||||
if s.xtermLike {
|
||||
return 0
|
||||
}
|
||||
return len(ansi.EraseLineRight)
|
||||
@ -931,7 +878,7 @@ func (s *Screen) transformLine(y int) {
|
||||
|
||||
// Find the first changed cell in the line
|
||||
var lineChanged bool
|
||||
for i := range s.newbuf.Width() {
|
||||
for i := 0; i < s.newbuf.Width(); i++ {
|
||||
if !cellEqual(newLine.At(i), oldLine.At(i)) {
|
||||
lineChanged = true
|
||||
break
|
||||
@ -939,7 +886,7 @@ func (s *Screen) transformLine(y int) {
|
||||
}
|
||||
|
||||
const ceolStandoutGlitch = false
|
||||
if ceolStandoutGlitch && lineChanged { //nolint:nestif
|
||||
if ceolStandoutGlitch && lineChanged {
|
||||
s.move(0, y)
|
||||
s.clearToEnd(nil, false)
|
||||
s.putRange(oldLine, newLine, y, 0, s.newbuf.Width()-1)
|
||||
@ -950,12 +897,12 @@ func (s *Screen) transformLine(y int) {
|
||||
// [ansi.EraseLineLeft].
|
||||
if blank == nil || blank.Clear() {
|
||||
var oFirstCell, nFirstCell int
|
||||
for oFirstCell = range s.curbuf.Width() {
|
||||
for oFirstCell = 0; oFirstCell < s.curbuf.Width(); oFirstCell++ {
|
||||
if !cellEqual(oldLine.At(oFirstCell), blank) {
|
||||
break
|
||||
}
|
||||
}
|
||||
for nFirstCell = range s.newbuf.Width() {
|
||||
for nFirstCell = 0; nFirstCell < s.newbuf.Width(); nFirstCell++ {
|
||||
if !cellEqual(newLine.At(nFirstCell), blank) {
|
||||
break
|
||||
}
|
||||
@ -978,11 +925,11 @@ func (s *Screen) transformLine(y int) {
|
||||
if nFirstCell >= s.newbuf.Width() {
|
||||
s.move(0, y)
|
||||
s.updatePen(blank)
|
||||
s.buf.WriteString(ansi.EraseLineRight)
|
||||
s.buf.WriteString(ansi.EraseLineRight) //nolint:errcheck
|
||||
} else {
|
||||
s.move(nFirstCell-1, y)
|
||||
s.updatePen(blank)
|
||||
s.buf.WriteString(ansi.EraseLineLeft)
|
||||
s.buf.WriteString(ansi.EraseLineLeft) //nolint:errcheck
|
||||
}
|
||||
|
||||
for firstCell < nFirstCell {
|
||||
@ -1098,7 +1045,7 @@ func (s *Screen) transformLine(y int) {
|
||||
|
||||
s.move(n+1, y)
|
||||
ichCost := 3 + nLastCell - oLastCell
|
||||
if s.caps.Contains(capICH) && (nLastCell < nLastNonBlank || ichCost > (m-n)) {
|
||||
if s.xtermLike && (nLastCell < nLastNonBlank || ichCost > (m-n)) {
|
||||
s.putRange(oldLine, newLine, y, n+1, m)
|
||||
} else {
|
||||
s.insertCells(newLine[n+1:], nLastCell-oLastCell)
|
||||
@ -1132,7 +1079,7 @@ func (s *Screen) transformLine(y int) {
|
||||
func (s *Screen) deleteCells(count int) {
|
||||
// [ansi.DCH] will shift in cells from the right margin so we need to
|
||||
// ensure that they are the right style.
|
||||
s.buf.WriteString(ansi.DeleteCharacter(count))
|
||||
s.buf.WriteString(ansi.DeleteCharacter(count)) //nolint:errcheck
|
||||
}
|
||||
|
||||
// clearToBottom clears the screen from the current cursor position to the end
|
||||
@ -1144,7 +1091,7 @@ func (s *Screen) clearToBottom(blank *Cell) {
|
||||
}
|
||||
|
||||
s.updatePen(blank)
|
||||
s.buf.WriteString(ansi.EraseScreenBelow)
|
||||
s.buf.WriteString(ansi.EraseScreenBelow) //nolint:errcheck
|
||||
// Clear the rest of the current line
|
||||
s.curbuf.ClearRect(Rect(col, row, s.curbuf.Width()-col, 1))
|
||||
// Clear everything below the current line
|
||||
@ -1157,7 +1104,7 @@ func (s *Screen) clearToBottom(blank *Cell) {
|
||||
// It returns the top line.
|
||||
func (s *Screen) clearBottom(total int) (top int) {
|
||||
if total <= 0 {
|
||||
return top
|
||||
return
|
||||
}
|
||||
|
||||
top = total
|
||||
@ -1165,7 +1112,7 @@ func (s *Screen) clearBottom(total int) (top int) {
|
||||
blank := s.clearBlank()
|
||||
canClearWithBlank := blank == nil || blank.Clear()
|
||||
|
||||
if canClearWithBlank { //nolint:nestif
|
||||
if canClearWithBlank {
|
||||
var row int
|
||||
for row = total - 1; row >= 0; row-- {
|
||||
oldLine := s.curbuf.Line(row)
|
||||
@ -1200,14 +1147,14 @@ func (s *Screen) clearBottom(total int) (top int) {
|
||||
}
|
||||
}
|
||||
|
||||
return top
|
||||
return
|
||||
}
|
||||
|
||||
// clearScreen clears the screen and put cursor at home.
|
||||
func (s *Screen) clearScreen(blank *Cell) {
|
||||
s.updatePen(blank)
|
||||
s.buf.WriteString(ansi.CursorHomePosition)
|
||||
s.buf.WriteString(ansi.EraseEntireScreen)
|
||||
s.buf.WriteString(ansi.CursorHomePosition) //nolint:errcheck
|
||||
s.buf.WriteString(ansi.EraseEntireScreen) //nolint:errcheck
|
||||
s.cur.X, s.cur.Y = 0, 0
|
||||
s.curbuf.Fill(blank)
|
||||
}
|
||||
@ -1232,7 +1179,7 @@ func (s *Screen) clearUpdate() {
|
||||
s.clearBelow(blank, 0)
|
||||
}
|
||||
nonEmpty = s.clearBottom(nonEmpty)
|
||||
for i := range nonEmpty {
|
||||
for i := 0; i < nonEmpty; i++ {
|
||||
s.transformLine(i)
|
||||
}
|
||||
}
|
||||
@ -1247,13 +1194,13 @@ func (s *Screen) Flush() (err error) {
|
||||
func (s *Screen) flush() (err error) {
|
||||
// Write the buffer
|
||||
if s.buf.Len() > 0 {
|
||||
_, err = s.w.Write(s.buf.Bytes())
|
||||
_, err = s.w.Write(s.buf.Bytes()) //nolint:errcheck
|
||||
if err == nil {
|
||||
s.buf.Reset()
|
||||
}
|
||||
}
|
||||
|
||||
return err //nolint:wrapcheck
|
||||
return
|
||||
}
|
||||
|
||||
// Render renders changes of the screen to the internal buffer. Call
|
||||
@ -1274,7 +1221,6 @@ func (s *Screen) render() {
|
||||
return
|
||||
}
|
||||
|
||||
//nolint:godox
|
||||
// TODO: Investigate whether this is necessary. Theoretically, terminals
|
||||
// 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
|
||||
@ -1289,9 +1235,9 @@ func (s *Screen) render() {
|
||||
// Do we need alt-screen mode?
|
||||
if s.opts.AltScreen != s.altScreenMode {
|
||||
if s.opts.AltScreen {
|
||||
s.buf.WriteString(ansi.SetModeAltScreenSaveCursor)
|
||||
s.buf.WriteString(ansi.SetAltScreenSaveCursorMode)
|
||||
} else {
|
||||
s.buf.WriteString(ansi.ResetModeAltScreenSaveCursor)
|
||||
s.buf.WriteString(ansi.ResetAltScreenSaveCursorMode)
|
||||
}
|
||||
s.altScreenMode = s.opts.AltScreen
|
||||
}
|
||||
@ -1306,9 +1252,7 @@ func (s *Screen) render() {
|
||||
|
||||
// Do we have queued strings to write above the screen?
|
||||
if len(s.queueAbove) > 0 {
|
||||
//nolint:godox
|
||||
// TODO: Use scrolling region if available.
|
||||
//nolint:godox
|
||||
// TODO: Use [Screen.Write] [io.Writer] interface.
|
||||
|
||||
// We need to scroll the screen up by the number of lines in the queue.
|
||||
@ -1346,13 +1290,12 @@ func (s *Screen) render() {
|
||||
s.clearBelow(nil, s.newbuf.Height()-1)
|
||||
}
|
||||
|
||||
if s.clear { //nolint:nestif
|
||||
if s.clear {
|
||||
s.clearUpdate()
|
||||
s.clear = false
|
||||
} else if len(s.touch) > 0 {
|
||||
if s.opts.AltScreen {
|
||||
// Optimize scrolling for the alternate screen buffer.
|
||||
//nolint:godox
|
||||
// TODO: Should we optimize for inline mode as well? If so, we need
|
||||
// to know the actual cursor position to use [ansi.DECSTBM].
|
||||
s.scrollOptimize()
|
||||
@ -1368,7 +1311,7 @@ func (s *Screen) render() {
|
||||
}
|
||||
|
||||
nonEmpty = s.clearBottom(nonEmpty)
|
||||
for i = range nonEmpty {
|
||||
for i = 0; i < nonEmpty; i++ {
|
||||
_, ok := s.touch[i]
|
||||
if ok {
|
||||
s.transformLine(i)
|
||||
@ -1416,7 +1359,7 @@ func (s *Screen) Close() (err error) {
|
||||
s.move(0, s.newbuf.Height()-1)
|
||||
|
||||
if s.altScreenMode {
|
||||
s.buf.WriteString(ansi.ResetModeAltScreenSaveCursor)
|
||||
s.buf.WriteString(ansi.ResetAltScreenSaveCursorMode)
|
||||
s.altScreenMode = false
|
||||
}
|
||||
|
||||
@ -1428,11 +1371,11 @@ func (s *Screen) Close() (err error) {
|
||||
// Write the buffer
|
||||
err = s.flush()
|
||||
if err != nil {
|
||||
return err
|
||||
return
|
||||
}
|
||||
|
||||
s.reset()
|
||||
return err
|
||||
return
|
||||
}
|
||||
|
||||
// reset resets the screen to its initial state.
|
||||
@ -1477,9 +1420,9 @@ func (s *Screen) Resize(width, height int) bool {
|
||||
}
|
||||
|
||||
if height > oldh {
|
||||
s.ClearRect(Rect(0, max(oldh, 0), width, height-oldh))
|
||||
s.ClearRect(Rect(0, max(oldh-1, 0), width, height-oldh))
|
||||
} else if height < oldh {
|
||||
s.ClearRect(Rect(0, max(height, 0), width, oldh-height))
|
||||
s.ClearRect(Rect(0, max(height-1, 0), width, oldh-height))
|
||||
}
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
// ConvertStyle converts a style to respect the given color profile.
|
||||
// Convert converts a style to respect the given color profile.
|
||||
func ConvertStyle(s Style, p colorprofile.Profile) Style {
|
||||
switch p { //nolint:exhaustive
|
||||
switch p {
|
||||
case colorprofile.TrueColor:
|
||||
return s
|
||||
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,6 +9,20 @@ func Height(s string) int {
|
||||
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 {
|
||||
if 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,7 +2,6 @@ package cellbuf
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"slices"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
@ -21,16 +20,6 @@ const nbsp = '\u00a0'
|
||||
//
|
||||
// Note: breakpoints must be a string of 1-cell wide rune characters.
|
||||
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 {
|
||||
return ""
|
||||
}
|
||||
@ -101,7 +90,7 @@ func Wrap(s string, limit int, breakpoints string) string {
|
||||
seq, width, n, newState := ansi.DecodeSequence(s, state, p)
|
||||
switch width {
|
||||
case 0:
|
||||
if ansi.Equal(seq, "\t") { //nolint:nestif
|
||||
if ansi.Equal(seq, "\t") {
|
||||
addWord()
|
||||
space.WriteString(seq)
|
||||
break
|
||||
@ -187,5 +176,10 @@ func Wrap(s string, limit int, breakpoints string) string {
|
||||
}
|
||||
|
||||
func runeContainsAny[T string | []rune](r rune, s T) bool {
|
||||
return slices.Contains([]rune(s), r)
|
||||
for _, c := range []rune(s) {
|
||||
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) {
|
||||
for y := rect.Min.Y; y < rect.Max.Y; y++ {
|
||||
for x := rect.Min.X; x < rect.Max.X; x++ {
|
||||
s.SetCell(x, y, c)
|
||||
s.SetCell(x, y, c) //nolint:errcheck
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -68,7 +68,7 @@ func SetContent(s CellBuffer, str string) {
|
||||
func Render(d CellBuffer) string {
|
||||
var buf bytes.Buffer
|
||||
height := d.Bounds().Dy()
|
||||
for y := range height {
|
||||
for y := 0; y < height; y++ {
|
||||
_, line := RenderLine(d, y)
|
||||
buf.WriteString(line)
|
||||
if y < height-1 {
|
||||
@ -98,32 +98,32 @@ func RenderLine(d CellBuffer, n int) (w int, line string) {
|
||||
pendingLine = ""
|
||||
}
|
||||
|
||||
for x := range d.Bounds().Dx() {
|
||||
if cell := d.Cell(x, n); cell != nil && cell.Width > 0 { //nolint:nestif
|
||||
for x := 0; x < d.Bounds().Dx(); x++ {
|
||||
if cell := d.Cell(x, n); cell != nil && cell.Width > 0 {
|
||||
// Convert the cell's style and link to the given color profile.
|
||||
cellStyle := cell.Style
|
||||
cellLink := cell.Link
|
||||
if cellStyle.Empty() && !pen.Empty() {
|
||||
writePending()
|
||||
buf.WriteString(ansi.ResetStyle)
|
||||
buf.WriteString(ansi.ResetStyle) //nolint:errcheck
|
||||
pen.Reset()
|
||||
}
|
||||
if !cellStyle.Equal(&pen) {
|
||||
writePending()
|
||||
seq := cellStyle.DiffSequence(pen)
|
||||
buf.WriteString(seq)
|
||||
buf.WriteString(seq) // nolint:errcheck
|
||||
pen = cellStyle
|
||||
}
|
||||
|
||||
// Write the URL escape sequence
|
||||
if cellLink != link && link.URL != "" {
|
||||
writePending()
|
||||
buf.WriteString(ansi.ResetHyperlink())
|
||||
buf.WriteString(ansi.ResetHyperlink()) //nolint:errcheck
|
||||
link.Reset()
|
||||
}
|
||||
if cellLink != link {
|
||||
writePending()
|
||||
buf.WriteString(ansi.SetHyperlink(cellLink.URL, cellLink.Params))
|
||||
buf.WriteString(ansi.SetHyperlink(cellLink.URL, cellLink.Params)) //nolint:errcheck
|
||||
link = cellLink
|
||||
}
|
||||
|
||||
@ -140,10 +140,10 @@ func RenderLine(d CellBuffer, n int) (w int, line string) {
|
||||
}
|
||||
}
|
||||
if link.URL != "" {
|
||||
buf.WriteString(ansi.ResetHyperlink())
|
||||
buf.WriteString(ansi.ResetHyperlink()) //nolint:errcheck
|
||||
}
|
||||
if !pen.Empty() {
|
||||
buf.WriteString(ansi.ResetStyle)
|
||||
buf.WriteString(ansi.ResetStyle) //nolint:errcheck
|
||||
}
|
||||
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.
|
||||
// This will recognize ANSI [ansi.SGR] style and [ansi.SetHyperlink] escape
|
||||
// sequences.
|
||||
func (s *ScreenWriter) Print(str string, v ...any) {
|
||||
func (s *ScreenWriter) Print(str string, v ...interface{}) {
|
||||
if len(v) > 0 {
|
||||
str = fmt.Sprintf(str, v...)
|
||||
}
|
||||
@ -214,7 +214,7 @@ func (s *ScreenWriter) Print(str string, v ...any) {
|
||||
// the width of the screen if it exceeds the width of the screen.
|
||||
// This will recognize ANSI [ansi.SGR] style and [ansi.SetHyperlink] escape
|
||||
// sequences.
|
||||
func (s *ScreenWriter) PrintAt(x, y int, str string, v ...any) {
|
||||
func (s *ScreenWriter) PrintAt(x, y int, str string, v ...interface{}) {
|
||||
if len(v) > 0 {
|
||||
str = fmt.Sprintf(str, v...)
|
||||
}
|
||||
@ -299,7 +299,7 @@ func printString[T []byte | string](
|
||||
// Print the cell to the screen
|
||||
cell.Style = style
|
||||
cell.Link = link
|
||||
s.SetCell(x, y, &cell)
|
||||
s.SetCell(x, y, &cell) //nolint:errcheck
|
||||
x += width
|
||||
}
|
||||
}
|
||||
@ -309,7 +309,6 @@ func printString[T []byte | string](
|
||||
cell.Reset()
|
||||
default:
|
||||
// Valid sequences always have a non-zero Cmd.
|
||||
//nolint:godox
|
||||
// TODO: Handle cursor movement and other sequences
|
||||
switch {
|
||||
case ansi.HasCsiPrefix(seq) && p.Command() == 'm':
|
||||
@ -334,7 +333,7 @@ func printString[T []byte | string](
|
||||
|
||||
// Make sure to set the last cell if it's not empty.
|
||||
if !cell.Empty() {
|
||||
s.SetCell(x, y, &cell)
|
||||
s.SetCell(x, y, &cell) //nolint:errcheck
|
||||
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,5 +1,3 @@
|
||||
// Package term provides a platform-independent interfaces for interacting with
|
||||
// Terminal and TTY devices.
|
||||
package term
|
||||
|
||||
// State contains platform-specific state of a terminal.
|
||||
|
||||
118
vendor/github.com/charmbracelet/x/term/term_plan9.go
generated
vendored
118
vendor/github.com/charmbracelet/x/term/term_plan9.go
generated
vendored
@ -1,118 +0,0 @@
|
||||
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) {
|
||||
termios, err := unix.IoctlGetTermios(int(fd), ioctlReadTermios)
|
||||
if err != nil {
|
||||
return nil, err //nolint:wrapcheck
|
||||
return nil, err
|
||||
}
|
||||
|
||||
oldState := State{state{Termios: *termios}}
|
||||
@ -34,7 +34,7 @@ func makeRaw(fd uintptr) (*State, error) {
|
||||
termios.Cc[unix.VMIN] = 1
|
||||
termios.Cc[unix.VTIME] = 0
|
||||
if err := unix.IoctlSetTermios(int(fd), ioctlWriteTermios, termios); err != nil {
|
||||
return nil, err //nolint:wrapcheck
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &oldState, nil
|
||||
@ -45,26 +45,26 @@ func setState(fd uintptr, state *State) error {
|
||||
if state != nil {
|
||||
termios = &state.Termios
|
||||
}
|
||||
return unix.IoctlSetTermios(int(fd), ioctlWriteTermios, termios) //nolint:wrapcheck
|
||||
return unix.IoctlSetTermios(int(fd), ioctlWriteTermios, termios)
|
||||
}
|
||||
|
||||
func getState(fd uintptr) (*State, error) {
|
||||
termios, err := unix.IoctlGetTermios(int(fd), ioctlReadTermios)
|
||||
if err != nil {
|
||||
return nil, err //nolint:wrapcheck
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &State{state{Termios: *termios}}, nil
|
||||
}
|
||||
|
||||
func restore(fd uintptr, state *State) error {
|
||||
return unix.IoctlSetTermios(int(fd), ioctlWriteTermios, &state.Termios) //nolint:wrapcheck
|
||||
return unix.IoctlSetTermios(int(fd), ioctlWriteTermios, &state.Termios)
|
||||
}
|
||||
|
||||
func getSize(fd uintptr) (width, height int, err error) {
|
||||
ws, err := unix.IoctlGetWinsize(int(fd), unix.TIOCGWINSZ)
|
||||
if err != nil {
|
||||
return 0, 0, err //nolint:wrapcheck
|
||||
return 0, 0, err
|
||||
}
|
||||
return int(ws.Col), int(ws.Row), nil
|
||||
}
|
||||
@ -73,13 +73,13 @@ func getSize(fd uintptr) (width, height int, err error) {
|
||||
type passwordReader int
|
||||
|
||||
func (r passwordReader) Read(buf []byte) (int, error) {
|
||||
return unix.Read(int(r), buf) //nolint:wrapcheck
|
||||
return unix.Read(int(r), buf)
|
||||
}
|
||||
|
||||
func readPassword(fd uintptr) ([]byte, error) {
|
||||
termios, err := unix.IoctlGetTermios(int(fd), ioctlReadTermios)
|
||||
if err != nil {
|
||||
return nil, err //nolint:wrapcheck
|
||||
return nil, err
|
||||
}
|
||||
|
||||
newState := *termios
|
||||
@ -87,10 +87,10 @@ func readPassword(fd uintptr) ([]byte, error) {
|
||||
newState.Lflag |= unix.ICANON | unix.ISIG
|
||||
newState.Iflag |= unix.ICRNL
|
||||
if err := unix.IoctlSetTermios(int(fd), ioctlWriteTermios, &newState); err != nil {
|
||||
return nil, err //nolint:wrapcheck
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer unix.IoctlSetTermios(int(fd), ioctlWriteTermios, termios) //nolint:errcheck
|
||||
defer unix.IoctlSetTermios(int(fd), ioctlWriteTermios, termios)
|
||||
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
raw := st &^ (windows.ENABLE_ECHO_INPUT | windows.ENABLE_PROCESSED_INPUT | windows.ENABLE_LINE_INPUT)
|
||||
raw := st &^ (windows.ENABLE_ECHO_INPUT | windows.ENABLE_PROCESSED_INPUT | windows.ENABLE_LINE_INPUT | windows.ENABLE_PROCESSED_OUTPUT)
|
||||
raw |= windows.ENABLE_VIRTUAL_TERMINAL_INPUT
|
||||
if err := windows.SetConsoleMode(windows.Handle(fd), raw); err != nil {
|
||||
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 {
|
||||
return ret, nil
|
||||
}
|
||||
return ret, err //nolint:wrapcheck
|
||||
return ret, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
1
vendor/github.com/clipperhouse/displaywidth/.gitignore
generated
vendored
1
vendor/github.com/clipperhouse/displaywidth/.gitignore
generated
vendored
@ -1 +0,0 @@
|
||||
.DS_Store
|
||||
37
vendor/github.com/clipperhouse/displaywidth/AGENTS.md
generated
vendored
37
vendor/github.com/clipperhouse/displaywidth/AGENTS.md
generated
vendored
@ -1,37 +0,0 @@
|
||||
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
49
vendor/github.com/clipperhouse/displaywidth/CHANGELOG.md
generated
vendored
@ -1,49 +0,0 @@
|
||||
# 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
21
vendor/github.com/clipperhouse/displaywidth/LICENSE
generated
vendored
@ -1,21 +0,0 @@
|
||||
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
123
vendor/github.com/clipperhouse/displaywidth/README.md
generated
vendored
@ -1,123 +0,0 @@
|
||||
# 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
3
vendor/github.com/clipperhouse/displaywidth/gen.go
generated
vendored
@ -1,3 +0,0 @@
|
||||
package displaywidth
|
||||
|
||||
//go:generate go run -C internal/gen .
|
||||
1716
vendor/github.com/clipperhouse/displaywidth/trie.go
generated
vendored
1716
vendor/github.com/clipperhouse/displaywidth/trie.go
generated
vendored
File diff suppressed because it is too large
Load Diff
210
vendor/github.com/clipperhouse/displaywidth/width.go
generated
vendored
210
vendor/github.com/clipperhouse/displaywidth/width.go
generated
vendored
@ -1,210 +0,0 @@
|
||||
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
2
vendor/github.com/clipperhouse/stringish/.gitignore
generated
vendored
@ -1,2 +0,0 @@
|
||||
.DS_Store
|
||||
*.test
|
||||
64
vendor/github.com/clipperhouse/stringish/README.md
generated
vendored
64
vendor/github.com/clipperhouse/stringish/README.md
generated
vendored
@ -1,64 +0,0 @@
|
||||
# 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
5
vendor/github.com/clipperhouse/stringish/interface.go
generated
vendored
@ -1,5 +0,0 @@
|
||||
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,9 +1,5 @@
|
||||
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
|
||||
|
||||
```
|
||||
@ -22,14 +18,15 @@ 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._
|
||||
|
||||
## Conformance
|
||||
|
||||
We use the Unicode [test suite](https://unicode.org/reports/tr41/tr41-26.html#Tests29).
|
||||
We use the Unicode [test suite](https://unicode.org/reports/tr41/tr41-26.html#Tests29). Status:
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
## APIs
|
||||
|
||||
@ -74,18 +71,9 @@ for tokens.Next() { // Next() returns true until end of data
|
||||
}
|
||||
```
|
||||
|
||||
### Benchmarks
|
||||
### Performance
|
||||
|
||||
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
|
||||
```
|
||||
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.
|
||||
|
||||
### 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,11 +1,8 @@
|
||||
package graphemes
|
||||
|
||||
import (
|
||||
"github.com/clipperhouse/stringish"
|
||||
"github.com/clipperhouse/uax29/v2/internal/iterators"
|
||||
)
|
||||
import "github.com/clipperhouse/uax29/v2/internal/iterators"
|
||||
|
||||
type Iterator[T stringish.Interface] struct {
|
||||
type Iterator[T iterators.Stringish] struct {
|
||||
*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 (
|
||||
"bufio"
|
||||
|
||||
"github.com/clipperhouse/stringish"
|
||||
"github.com/clipperhouse/uax29/v2/internal/iterators"
|
||||
)
|
||||
|
||||
// is determines if lookup intersects propert(ies)
|
||||
@ -18,7 +18,7 @@ const _Ignore = _Extend
|
||||
// See https://unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries.
|
||||
var SplitFunc bufio.SplitFunc = splitFunc[[]byte]
|
||||
|
||||
func splitFunc[T stringish.Interface](data T, atEOF bool) (advance int, token T, err error) {
|
||||
func splitFunc[T iterators.Stringish](data T, atEOF bool) (advance int, token T, err error) {
|
||||
var empty T
|
||||
if len(data) == 0 {
|
||||
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
|
||||
|
||||
import "github.com/clipperhouse/stringish"
|
||||
|
||||
// generated by github.com/clipperhouse/uax29/v2
|
||||
// from https://www.unicode.org/Public/15.0.0/ucd/auxiliary/GraphemeBreakProperty.txt
|
||||
|
||||
import "github.com/clipperhouse/uax29/v2/internal/iterators"
|
||||
|
||||
type property uint16
|
||||
|
||||
const (
|
||||
@ -27,7 +27,7 @@ const (
|
||||
// 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
|
||||
// hold enough bytes to complete the encoding. len(s) must be greater than 0.
|
||||
func lookup[T stringish.Interface](s T) (v property, sz int) {
|
||||
func lookup[T iterators.Stringish](s T) (v property, sz int) {
|
||||
c0 := s[0]
|
||||
switch {
|
||||
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,12 +1,14 @@
|
||||
package iterators
|
||||
|
||||
import "github.com/clipperhouse/stringish"
|
||||
type Stringish interface {
|
||||
[]byte | string
|
||||
}
|
||||
|
||||
type SplitFunc[T stringish.Interface] func(T, bool) (int, T, error)
|
||||
type SplitFunc[T Stringish] func(T, bool) (int, T, error)
|
||||
|
||||
// Iterator is a generic iterator for words that are either []byte or string.
|
||||
// Iterate while Next() is true, and access the word via Value().
|
||||
type Iterator[T stringish.Interface] struct {
|
||||
type Iterator[T Stringish] struct {
|
||||
split SplitFunc[T]
|
||||
data T
|
||||
start int
|
||||
@ -14,7 +16,7 @@ type Iterator[T stringish.Interface] struct {
|
||||
}
|
||||
|
||||
// New creates a new Iterator for the given data and SplitFunc.
|
||||
func New[T stringish.Interface](split SplitFunc[T], data T) *Iterator[T] {
|
||||
func New[T Stringish](split SplitFunc[T], data T) *Iterator[T] {
|
||||
return &Iterator[T]{
|
||||
split: split,
|
||||
data: data,
|
||||
@ -81,20 +83,3 @@ func (iter *Iterator[T]) Reset() {
|
||||
iter.start = 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,10 +9,6 @@
|
||||
|
||||
version: "2"
|
||||
|
||||
run:
|
||||
build-tags:
|
||||
- libpathrs
|
||||
|
||||
linters:
|
||||
enable:
|
||||
- asasalint
|
||||
|
||||
90
vendor/github.com/cyphar/filepath-securejoin/CHANGELOG.md
generated
vendored
90
vendor/github.com/cyphar/filepath-securejoin/CHANGELOG.md
generated
vendored
@ -6,92 +6,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
## [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 ##
|
||||
|
||||
> Let the past die. Kill it if you have to.
|
||||
@ -440,9 +354,7 @@ This is our first release of `github.com/cyphar/filepath-securejoin`,
|
||||
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).
|
||||
|
||||
[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
|
||||
[Unreleased]: https://github.com/cyphar/filepath-securejoin/compare/v0.5.0...HEAD
|
||||
[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.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.6.0
|
||||
0.5.0
|
||||
|
||||
48
vendor/github.com/cyphar/filepath-securejoin/deprecated_linux.go
generated
vendored
Normal file
48
vendor/github.com/cyphar/filepath-securejoin/deprecated_linux.go
generated
vendored
Normal file
@ -0,0 +1,48 @@
|
||||
// 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
Normal file
33
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/README.md
generated
vendored
Normal file
@ -0,0 +1,33 @@
|
||||
## `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
Normal file
14
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/doc.go
generated
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
// 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
Normal file
30
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/assert/assert.go
generated
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
// 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
Normal file
30
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/errors.go
generated
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
// 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
Normal file
148
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/at_linux.go
generated
vendored
Normal file
@ -0,0 +1,148 @@
|
||||
// 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
Normal file
55
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/fd.go
generated
vendored
Normal file
@ -0,0 +1,55 @@
|
||||
// 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
Normal file
78
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/fd_linux.go
generated
vendored
Normal file
@ -0,0 +1,78 @@
|
||||
// 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
Normal file
54
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/mount_linux.go
generated
vendored
Normal file
@ -0,0 +1,54 @@
|
||||
// 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
Normal file
62
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/openat2_linux.go
generated
vendored
Normal file
@ -0,0 +1,62 @@
|
||||
// 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
Normal file
10
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/README.md
generated
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
## 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
Normal file
13
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/doc.go
generated
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
// 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
|
||||
19
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/gocompat_errors_go120.go
generated
vendored
Normal file
19
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/gocompat_errors_go120.go
generated
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
// 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)
|
||||
}
|
||||
40
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/gocompat_errors_unsupported.go
generated
vendored
Normal file
40
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/gocompat_errors_unsupported.go
generated
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
// 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,
|
||||
}
|
||||
}
|
||||
53
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/gocompat_generics_go121.go
generated
vendored
Normal file
53
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/gocompat_generics_go121.go
generated
vendored
Normal file
@ -0,0 +1,53 @@
|
||||
// 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)
|
||||
}
|
||||
187
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/gocompat_generics_unsupported.go
generated
vendored
Normal file
187
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/gocompat_generics_unsupported.go
generated
vendored
Normal file
@ -0,0 +1,187 @@
|
||||
// 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
|
||||
}
|
||||
123
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/kernelversion/kernel_linux.go
generated
vendored
Normal file
123
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/kernelversion/kernel_linux.go
generated
vendored
Normal file
@ -0,0 +1,123 @@
|
||||
// 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
Normal file
12
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux/doc.go
generated
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
// 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
Normal file
47
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux/mount_linux.go
generated
vendored
Normal file
@ -0,0 +1,47 @@
|
||||
// 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
Normal file
31
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux/openat2_linux.go
generated
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
// 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
Normal file
544
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/procfs/procfs_linux.go
generated
vendored
Normal file
@ -0,0 +1,544 @@
|
||||
// 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 }
|
||||
222
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/procfs/procfs_lookup_linux.go
generated
vendored
Normal file
222
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/procfs/procfs_lookup_linux.go
generated
vendored
Normal file
@ -0,0 +1,222 @@
|
||||
// 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
Normal file
399
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/lookup_linux.go
generated
vendored
Normal file
@ -0,0 +1,399 @@
|
||||
// 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
Normal file
246
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/mkdir_linux.go
generated
vendored
Normal file
@ -0,0 +1,246 @@
|
||||
// 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
Normal file
74
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/open_linux.go
generated
vendored
Normal file
@ -0,0 +1,74 @@
|
||||
// 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
Normal file
101
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/openat2_linux.go
generated
vendored
Normal file
@ -0,0 +1,101 @@
|
||||
// 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
Normal file
157
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/procfs/procfs_linux.go
generated
vendored
Normal file
@ -0,0 +1,157 @@
|
||||
// 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,7 +63,6 @@ Andreas Köhler <andi5.py@gmx.net>
|
||||
Andres G. Aragoneses <knocte@gmail.com>
|
||||
Andres Leon Rangel <aleon1220@gmail.com>
|
||||
Andrew France <andrew@avito.co.uk>
|
||||
Andrew He <he.andrew.mail@gmail.com>
|
||||
Andrew Hsu <andrewhsu@docker.com>
|
||||
Andrew Macpherson <hopscotch23@gmail.com>
|
||||
Andrew McDonnell <bugs@andrewmcdonnell.net>
|
||||
@ -87,12 +86,11 @@ Archimedes Trajano <developer@trajano.net>
|
||||
Arko Dasgupta <arko@tetrate.io>
|
||||
Arnaud Porterie <icecrime@gmail.com>
|
||||
Arnaud Rebillout <elboulangero@gmail.com>
|
||||
Arthur Flageul <arthur.flageul@gmail.com>
|
||||
Arthur Peka <arthur.peka@outlook.com>
|
||||
Ashly Mathew <ashly.mathew@sap.com>
|
||||
Ashwini Oruganti <ashwini.oruganti@gmail.com>
|
||||
Aslam Ahemad <aslamahemad@gmail.com>
|
||||
Austin Vazquez <austin.vazquez@docker.com>
|
||||
Austin Vazquez <austin.vazquez.dev@gmail.com>
|
||||
Azat Khuyiyakhmetov <shadow_uz@mail.ru>
|
||||
Bardia Keyoumarsi <bkeyouma@ucsc.edu>
|
||||
Barnaby Gray <barnaby@pickle.me.uk>
|
||||
@ -137,12 +135,10 @@ Cao Weiwei <cao.weiwei30@zte.com.cn>
|
||||
Carlo Mion <mion00@gmail.com>
|
||||
Carlos Alexandro Becker <caarlos0@gmail.com>
|
||||
Carlos de Paula <me@carlosedp.com>
|
||||
carsontham <carsontham@outlook.com>
|
||||
Carston Schilds <Carston.Schilds@visier.com>
|
||||
Casey Korver <casey@korver.dev>
|
||||
Ce Gao <ce.gao@outlook.com>
|
||||
Cedric Davies <cedricda@microsoft.com>
|
||||
Cesar Talledo <cesar.talledo@docker.com>
|
||||
Cezar Sa Espinola <cezarsa@gmail.com>
|
||||
Chad Faragher <wyckster@hotmail.com>
|
||||
Chao Wang <wangchao.fnst@cn.fujitsu.com>
|
||||
@ -224,7 +220,7 @@ David Alvarez <david.alvarez@flyeralarm.com>
|
||||
David Beitey <david@davidjb.com>
|
||||
David Calavera <david.calavera@gmail.com>
|
||||
David Cramer <davcrame@cisco.com>
|
||||
David Dooling <david.dooling@docker.com>
|
||||
David Dooling <dooling@gmail.com>
|
||||
David Gageot <david@gageot.net>
|
||||
David Karlsson <david.karlsson@docker.com>
|
||||
David le Blanc <systemmonkey42@users.noreply.github.com>
|
||||
@ -269,7 +265,6 @@ Eli Uriegas <eli.uriegas@docker.com>
|
||||
Eli Uriegas <seemethere101@gmail.com>
|
||||
Elias Faxö <elias.faxo@tre.se>
|
||||
Elliot Luo <956941328@qq.com>
|
||||
Eng Zer Jun <engzerjun@gmail.com>
|
||||
Eric Bode <eric.bode@foundries.io>
|
||||
Eric Curtin <ericcurtin17@gmail.com>
|
||||
Eric Engestrom <eric@engestrom.ch>
|
||||
@ -350,7 +345,6 @@ Henning Sprang <henning.sprang@gmail.com>
|
||||
Henry N <henrynmail-github@yahoo.de>
|
||||
Hernan Garcia <hernandanielg@gmail.com>
|
||||
Hongbin Lu <hongbin034@gmail.com>
|
||||
Hossein Abbasi <16090309+hsnabszhdn@users.noreply.github.com>
|
||||
Hu Keping <hukeping@huawei.com>
|
||||
Huayi Zhang <irachex@gmail.com>
|
||||
Hugo Chastel <Hugo-C@users.noreply.github.com>
|
||||
@ -601,7 +595,6 @@ Michael Prokop <github@michael-prokop.at>
|
||||
Michael Scharf <github@scharf.gr>
|
||||
Michael Spetsiotis <michael_spets@hotmail.com>
|
||||
Michael Steinert <mike.steinert@gmail.com>
|
||||
Michael Tews <michael@tews.dev>
|
||||
Michael West <mwest@mdsol.com>
|
||||
Michal Minář <miminar@redhat.com>
|
||||
Michał Czeraszkiewicz <czerasz@gmail.com>
|
||||
@ -903,7 +896,6 @@ Wenlong Zhang <zhangwenlong@loongson.cn>
|
||||
Wenzhi Liang <wenzhi.liang@gmail.com>
|
||||
Wes Morgan <cap10morgan@gmail.com>
|
||||
Wewang Xiaorenfine <wang.xiaoren@zte.com.cn>
|
||||
Will Wang <willww64@gmail.com>
|
||||
William Henry <whenry@redhat.com>
|
||||
Xianglin Gao <xlgao@zju.edu.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,6 +33,4 @@ type Metadata struct {
|
||||
ShortDescription string `json:",omitempty"`
|
||||
// URL is a pointer to the plugin's homepage.
|
||||
URL string `json:",omitempty"`
|
||||
// Hidden hides the plugin in completion and help message output.
|
||||
Hidden bool `json:",omitempty"`
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user