Compare commits

..

102 Commits

Author SHA1 Message Date
45af67d22d chore: make deps 2025-11-11 14:18:57 +01:00
db7c4042d0 chore: go mod vendor 2025-11-09 11:52:00 +01:00
ed1a66dc5f chore: publish next tag 0.12.0-beta
Some checks failed
continuous-integration/drone/tag Build is failing
continuous-integration/drone/push Build is passing
2025-11-09 11:46:38 +01:00
bb93e4266a fix: show domain with https (clickable)
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
See #643
2025-11-09 10:56:21 +01:00
a2cc70b2f5 test: reinstate debug
Some checks failed
continuous-integration/drone/push Build is failing
2025-11-09 10:43:09 +01:00
ce1aa3d870 test: ensure recipe sync is robust
All checks were successful
continuous-integration/drone/push Build is passing
2025-11-09 10:41:32 +01:00
d75700c8a9 test: ensure env vars updated
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
See #723
2025-11-09 09:32:52 +01:00
0ccc4aae72 chore: remove --debug 2025-11-09 09:32:42 +01:00
ec22d5d51d test: remove old tests
All checks were successful
continuous-integration/drone/push Build is passing
See #716
2025-11-05 09:52:28 +01:00
ab42584d05 docs: update var name 2025-11-05 09:52:28 +01:00
f
40eb6e9a18 fix: shorter hyphen
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-11-04 11:49:44 -03:00
35eb9d4a89 chore: update translation files
All checks were successful
continuous-integration/drone/push Build is passing
Updated by "Update PO files to match POT (msgmerge)" add-on in Weblate.

Translation: Co-op Cloud/abra
Translate-URL: https://translate.coopcloud.tech/projects/co-op-cloud/abra/
2025-11-04 14:34:39 +00:00
08cc63d523 chore: make i18n
All checks were successful
continuous-integration/drone/push Build is passing
2025-11-04 15:34:27 +01:00
797b8d899b chore: update translation files
All checks were successful
continuous-integration/drone/push Build is passing
Updated by "Update PO files to match POT (msgmerge)" add-on in Weblate.

Translation: Co-op Cloud/abra
Translate-URL: https://translate.coopcloud.tech/projects/co-op-cloud/abra/
2025-11-04 13:53:26 +00:00
fb786306b5 chore: make i18n
All checks were successful
continuous-integration/drone/push Build is passing
2025-11-04 14:53:15 +01:00
c3a2048eba fix: throw away unknown version
Some checks failed
continuous-integration/drone/push Build is failing
See #715
2025-11-04 13:52:49 +00:00
1bdc11ba62 fix no-input app deployment when no tty is present
Some checks failed
continuous-integration/drone/push Build is failing
2025-11-04 13:52:28 +00:00
cc8703310c chore: update translation files
All checks were successful
continuous-integration/drone/push Build is passing
Updated by "Update PO files to match POT (msgmerge)" add-on in Weblate.

Translation: Co-op Cloud/abra
Translate-URL: https://translate.coopcloud.tech/projects/co-op-cloud/abra/
2025-11-04 07:35:14 +00:00
fcd5bd863d chore: make i18n
Some checks failed
continuous-integration/drone/push Build is failing
2025-11-04 08:35:04 +01:00
e6af2da9dd refactor: named note, merge if clause 2025-11-04 08:34:49 +01:00
4b688825e0 feat: create docker context when server folder does exist
Some checks failed
continuous-integration/drone/pr Build is failing
continuous-integration/drone/push Build is failing
2025-11-03 17:29:04 +01:00
b0cf2a1f8e chore: make i18n
All checks were successful
continuous-integration/drone/push Build is passing
2025-11-02 10:44:34 +00:00
6b7020d457 test: env version to .config.env 2025-11-02 10:44:34 +00:00
efdac610bd fix: skip local server on it's own 2025-11-02 10:44:34 +00:00
cd6021f116 fix: expose new version
See #713
2025-11-02 10:44:34 +00:00
ee8de8ef5c chore: update translation files
All checks were successful
continuous-integration/drone/push Build is passing
Updated by "Update PO files to match POT (msgmerge)" add-on in Weblate.

Translation: Co-op Cloud/abra
Translate-URL: https://translate.coopcloud.tech/projects/co-op-cloud/abra/
2025-10-31 20:38:18 +00:00
e5a653c002 chore: make i18n
All checks were successful
continuous-integration/drone/push Build is passing
2025-10-31 21:38:01 +01:00
2cca04de90 fix(move): does not error when secret already exists on new server
See #709
2025-10-31 21:37:55 +01:00
f2f79e2df8 chore: update translation files
All checks were successful
continuous-integration/drone/push Build is passing
Updated by "Update PO files to match POT (msgmerge)" add-on in Weblate.

Translation: Co-op Cloud/abra
Translate-URL: https://translate.coopcloud.tech/projects/co-op-cloud/abra/
2025-10-31 20:33:36 +00:00
dd83741a9f chore: i18n
All checks were successful
continuous-integration/drone/push Build is passing
2025-10-31 21:31:49 +01:00
dc2cd85d91 feat!: abra app env pull
`abra app env` -> `abra app env list`.

See #497
2025-10-31 21:31:43 +01:00
96e59cf196 test: adjust to match new reality [ci skip] 2025-10-31 14:35:57 +01:00
11656c009d test: don't wat to converge [ci skip] 2025-10-26 11:49:57 +01:00
e4e1b58501 test: update matches in old tests [ci skip] 2025-10-26 11:40:25 +01:00
3b8f12643c test: use new target
All checks were successful
continuous-integration/drone/push Build is passing
2025-10-25 20:01:23 +02:00
e5f5154197 test: kadabra is gone
All checks were successful
continuous-integration/drone/push Build is passing
2025-10-23 20:58:08 +02:00
6c1c0a8a8a refactor: use xgettext-go from makefile variable
All checks were successful
continuous-integration/drone/push Build is passing
Easier to hack when customising xgettext-go.
2025-10-23 09:26:51 +02:00
662f45008c chore: update translation files
All checks were successful
continuous-integration/drone/push Build is passing
Updated by "Update PO files to match POT (msgmerge)" add-on in Weblate.

Translation: Co-op Cloud/abra
Translate-URL: https://translate.coopcloud.tech/projects/co-op-cloud/abra/
2025-10-23 07:14:28 +00:00
708c5f5223 chore: make i18n
All checks were successful
continuous-integration/drone/push Build is passing
See #688
2025-10-23 09:14:15 +02:00
d58552b748 chore: update translation files
All checks were successful
continuous-integration/drone/push Build is passing
Updated by "Update PO files to match POT (msgmerge)" add-on in Weblate.

Translation: Co-op Cloud/abra
Translate-URL: https://translate.coopcloud.tech/projects/co-op-cloud/abra/
2025-10-23 07:10:59 +00:00
51fe809851 chore: make i18n
All checks were successful
continuous-integration/drone/push Build is passing
See #688
2025-10-23 09:10:36 +02:00
3f6a22747f chore: update translation files
All checks were successful
continuous-integration/drone/push Build is passing
Updated by "Update PO files to match POT (msgmerge)" add-on in Weblate.

Translation: Co-op Cloud/abra
Translate-URL: https://translate.coopcloud.tech/projects/co-op-cloud/abra/
2025-10-23 07:09:11 +00:00
4e75b96914 chore: make i18n
All checks were successful
continuous-integration/drone/push Build is passing
See #688
2025-10-23 09:08:50 +02:00
fd4ee75ab7 chore: update translation files
All checks were successful
continuous-integration/drone/push Build is passing
Updated by "Update PO files to match POT (msgmerge)" add-on in Weblate.

Translation: Co-op Cloud/abra
Translate-URL: https://translate.coopcloud.tech/projects/co-op-cloud/abra/
2025-10-19 13:47:17 +00:00
964ed834ee refactor!: remove autoupdate (kadabra)
All checks were successful
continuous-integration/drone/push Build is passing
2025-10-19 15:46:18 +02:00
fcb3167394 chore: update translation files
All checks were successful
continuous-integration/drone/push Build is passing
Updated by "Update PO files to match POT (msgmerge)" add-on in Weblate.

Translation: Co-op Cloud/abra
Translate-URL: https://translate.coopcloud.tech/projects/co-op-cloud/abra/
2025-10-19 13:36:39 +00:00
3845b40aa3 refactor!: archive kadabra
All checks were successful
continuous-integration/drone/push Build is passing
2025-10-19 15:32:38 +02:00
3wc
0dc5c307af chore: update i18n
All checks were successful
continuous-integration/drone/push Build is passing
2025-10-18 16:03:11 -04:00
3wc
fc5855ff28 feat: Add hexadecimal secret generation
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
Closes #695
2025-10-18 15:03:02 -04:00
5b504a1550 Revert "feat: cctuip lands in main"
All checks were successful
continuous-integration/drone/push Build is passing
See #691 (comment)
2025-10-17 19:27:23 +02:00
fc16a21f1c chore: update translation files
All checks were successful
continuous-integration/drone/push Build is passing
Updated by "Update PO files to match POT (msgmerge)" add-on in Weblate.

Translation: Co-op Cloud/abra
Translate-URL: https://translate.coopcloud.tech/projects/co-op-cloud/abra/
2025-10-03 18:39:54 +00:00
7b4d2d7230 chore: make i18n
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-10-03 20:35:47 +02:00
d0ccb805c6 refactor: isolate expensive IsDirty() call
Some checks failed
continuous-integration/drone/push Build is failing
See #689
2025-10-03 20:35:09 +02:00
2460dd9438 fix: pagination with multiline(true)
See #689
2025-10-03 20:13:35 +02:00
9c648a2566 chore: update translation files
All checks were successful
continuous-integration/drone/push Build is passing
Updated by "Update PO files to match POT (msgmerge)" add-on in Weblate.

Translation: Co-op Cloud/abra
Translate-URL: https://translate.coopcloud.tech/projects/co-op-cloud/abra/
2025-10-02 09:05:45 +00:00
22ecfb9c4c test: remove old non-tui tests
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-10-02 10:58:53 +02:00
9f3cf718be chore: make i18n 2025-10-02 10:54:07 +02:00
b737ce2107 feat: cctuip lands in main
See toolshed/organising#657
2025-10-02 10:53:44 +02:00
a3d0ece7cb refactor: single missing value 2025-10-02 10:53:31 +02:00
d63a1c28ea chore: go mod tidy / vendor / make deps 2025-10-02 10:35:46 +02:00
1c10e64c58 chore: update translation files
All checks were successful
continuous-integration/drone/push Build is passing
Updated by "Update PO files to match POT (msgmerge)" add-on in Weblate.

Translation: Co-op Cloud/abra
Translate-URL: https://translate.coopcloud.tech/projects/co-op-cloud/abra/
2025-10-01 19:16:08 +00:00
21826ec555 chore: make i18n
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-10-01 21:13:41 +02:00
4b4c56d406 fix: skip borked tags on app list
See #656
2025-10-01 21:13:18 +02:00
4314195dd7 test: dont run xgettext-go on release
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
See #663
2025-10-01 12:20:16 +02:00
df4447b038 chore: update translation files
All checks were successful
continuous-integration/drone/push Build is passing
Updated by "Update PO files to match POT (msgmerge)" add-on in Weblate.

Translation: Co-op Cloud/abra
Translate-URL: https://translate.coopcloud.tech/projects/co-op-cloud/abra/
2025-10-01 10:12:54 +00:00
3fa660e579 chore: make i18n
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-10-01 12:12:07 +02:00
a430b1e4fd fix: dont show unchanged images/tags
See #677
2025-10-01 12:11:29 +02:00
896c434f38 chore: update translation files
All checks were successful
continuous-integration/drone/push Build is passing
Updated by "Update PO files to match POT (msgmerge)" add-on in Weblate.

Translation: Co-op Cloud/abra
Translate-URL: https://translate.coopcloud.tech/projects/co-op-cloud/abra/
2025-10-01 09:50:52 +00:00
847b7238c5 chore: make i18n
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-10-01 11:49:19 +02:00
89d5fc91b0 fix: gracefully explode of missing context
See #675
2025-10-01 11:48:51 +02:00
5af3c5f56e chore: update translation files
All checks were successful
continuous-integration/drone/push Build is passing
Updated by "Update PO files to match POT (msgmerge)" add-on in Weblate.

Translation: Co-op Cloud/abra
Translate-URL: https://translate.coopcloud.tech/projects/co-op-cloud/abra/
2025-10-01 09:20:19 +00:00
beb3864b2d chore: make i18n
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-10-01 11:18:51 +02:00
581e6ef538 chore: lowercase 2025-10-01 11:18:42 +02:00
fd642ddb84 fix: fail if release conflicts
See toolshed/organising#638
2025-10-01 11:18:30 +02:00
1ad8c127d9 fix: point to the catalogue
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
See #669
2025-10-01 09:09:40 +02:00
40aab6a6c1 chore: update translation files
All checks were successful
continuous-integration/drone/push Build is passing
Updated by "Update PO files to match POT (msgmerge)" add-on in Weblate.

Translation: Co-op Cloud/abra
Translate-URL: https://translate.coopcloud.tech/projects/co-op-cloud/abra/
2025-10-01 07:01:05 +00:00
4d33a24a07 refactor: less EN specific value
All checks were successful
continuous-integration/drone/push Build is passing
2025-10-01 08:54:09 +02:00
ee59eb350b chore: make i18n 2025-10-01 08:54:08 +02:00
5da13ff15a test: adjust integration suite 2025-10-01 08:54:05 +02:00
491c594ad3 fix: better message for redeploying chaos version
See #668
2025-10-01 08:19:47 +02:00
c794d533be fix: avoid hanging when tasks randomly surge
See #557
2025-10-01 08:19:46 +02:00
a6daf7030e fix: show chaos version on upgrade 2025-10-01 08:19:45 +02:00
fe3b7ffa9c fix: write correct undeploy version 2025-10-01 08:19:44 +02:00
4c066a92d8 fix: show chaos version on rollback overview 2025-10-01 08:19:43 +02:00
7899b57781 fix: show chaos version on deploy overview 2025-10-01 08:19:42 +02:00
6e0a901887 chore: spacing for readability 2025-10-01 08:19:41 +02:00
713fdebc90 fix: show chaos version on undeploy 2025-10-01 08:19:40 +02:00
6944d138c6 refactor: chaos-y handling
See #659
2025-10-01 08:19:39 +02:00
fbb1f16470 fix: dont overwrite label when chaos
See #668
2025-10-01 08:19:38 +02:00
2473cafdf5 test: use new CI option for xgettext-go
All checks were successful
continuous-integration/drone/push Build is passing
2025-09-30 19:23:23 +02:00
0ccfbd253e chore: update translation files
Some checks failed
continuous-integration/drone/push Build is failing
Updated by "Update PO files to match POT (msgmerge)" add-on in Weblate.

Translation: Co-op Cloud/abra
Translate-URL: https://translate.coopcloud.tech/projects/co-op-cloud/abra/
2025-09-30 17:20:47 +00:00
6c4bee0ac7 chore: make i18n
Some checks failed
continuous-integration/drone/push Build is failing
2025-09-30 19:20:35 +02:00
4fa9f536eb chore: update translation files
Some checks failed
continuous-integration/drone/push Build is failing
Updated by "Update PO files to match POT (msgmerge)" add-on in Weblate.

Translation: Co-op Cloud/abra
Translate-URL: https://translate.coopcloud.tech/projects/co-op-cloud/abra/
2025-09-30 17:10:26 +00:00
033c9bfc13 feat: msgctxt support
Some checks failed
continuous-integration/drone/push Build is failing
See #647
See toolshed/xgettext-go#1
2025-09-30 19:08:52 +02:00
0db1ee87fc chore: update translation files
All checks were successful
continuous-integration/drone/push Build is passing
Updated by "Update PO files to match POT (msgmerge)" add-on in Weblate.

Translation: Co-op Cloud/abra
Translate-URL: https://translate.coopcloud.tech/projects/co-op-cloud/abra/
2025-09-30 17:03:14 +00:00
d180bb924f chore: make i18n
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-09-29 10:33:56 +02:00
d50d68d95a test: further generate=false secre tests 2025-09-29 10:33:40 +02:00
f468bc7443 fix: collect local name also 2025-09-29 10:33:26 +02:00
dee2d9d104 fix: nuance of generate=false for app deploy 2025-09-29 10:32:29 +02:00
5c892b1d6a fix: nuance of generate=false for app new 2025-09-29 10:32:04 +02:00
81b96fc7b1 docs: better wording 2025-09-29 10:31:46 +02:00
c92a0d0703 feat: Add cloud-init file
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-09-14 13:02:21 -04:00
1018 changed files with 51565 additions and 51348 deletions

View File

@ -4,5 +4,4 @@
Dockerfile Dockerfile
abra abra
dist dist
kadabra
tags tags

View File

@ -11,10 +11,15 @@ steps:
image: git.coopcloud.tech/toolshed/drone-xgettext-go:latest image: git.coopcloud.tech/toolshed/drone-xgettext-go:latest
settings: settings:
keyword: i18n.G keyword: i18n.G
keyword_ctx: i18n.GC
out: pkg/i18n/locales/abra.pot out: pkg/i18n/locales/abra.pot
comments_tag: translators comments_tag: translators
depends_on: depends_on:
- make check - make check
when:
event:
exclude:
- tag
- name: xgettext-go status - name: xgettext-go status
image: golang:1.24-alpine3.22 image: golang:1.24-alpine3.22
@ -27,6 +32,10 @@ steps:
- git diff-files --exit-code - git diff-files --exit-code
depends_on: depends_on:
- xgettext-go - xgettext-go
when:
event:
exclude:
- tag
- name: make test - name: make test
image: golang:1.24 image: golang:1.24

2
.gitignore vendored
View File

@ -4,6 +4,6 @@
.envrc .envrc
.vscode/ .vscode/
/abra /abra
/kadabra /bin
dist/ dist/
tests/integration/.bats tests/integration/.bats

View File

@ -32,31 +32,6 @@ builds:
- "-s" - "-s"
- "-w" - "-w"
- id: kadabra
binary: kadabra
dir: cmd/kadabra
env:
- CGO_ENABLED=0
goos:
- linux
- darwin
goarch:
- 386
- amd64
- arm
- arm64
goarm:
- 5
- 6
- 7
gcflags:
- "all=-l -B"
ldflags:
- "-X 'main.Commit={{ .Commit }}'"
- "-X 'main.Version={{ .Version }}'"
- "-s"
- "-w"
checksum: checksum:
name_template: "checksums.txt" name_template: "checksums.txt"

View File

@ -1,5 +1,5 @@
ABRA := ./cmd/abra ABRA := ./cmd/abra
KADABRA := ./cmd/kadabra XGETTEXT := ./bin/xgettext-go
COMMIT := $(shell git rev-list -1 HEAD) COMMIT := $(shell git rev-list -1 HEAD)
GOPATH := $(shell go env GOPATH) GOPATH := $(shell go env GOPATH)
GOVERSION := 1.24 GOVERSION := 1.24
@ -13,40 +13,23 @@ LINGUAS := $(basename $(POFILES))
export GOPRIVATE=coopcloud.tech export GOPRIVATE=coopcloud.tech
# NOTE(d1): default `make` optimised for Abra hacking all: format check build
all: format check build-abra test
run-abra: run:
@go run -gcflags=$(GCFLAGS) -ldflags=$(LDFLAGS) $(ABRA) @go run -gcflags=$(GCFLAGS) -ldflags=$(LDFLAGS) $(ABRA)
run-kadabra: install:
@go run -gcflags=$(GCFLAGS) -ldflags=$(LDFLAGS) $(KADABRA)
install-abra:
@go install -gcflags=$(GCFLAGS) -ldflags=$(LDFLAGS) $(ABRA) @go install -gcflags=$(GCFLAGS) -ldflags=$(LDFLAGS) $(ABRA)
install-kadabra: build:
@go install -gcflags=$(GCFLAGS) -ldflags=$(LDFLAGS) $(KADABRA)
install: install-abra install-kadabra
build-abra:
@go build -v -gcflags=$(GCFLAGS) -ldflags=$(DIST_LDFLAGS) $(ABRA) @go build -v -gcflags=$(GCFLAGS) -ldflags=$(DIST_LDFLAGS) $(ABRA)
build-kadabra: build-docker:
@go build -v -gcflags=$(GCFLAGS) -ldflags=$(DIST_LDFLAGS) $(KADABRA)
build: build-abra build-kadabra
build-docker-abra:
@docker run -it -v $(PWD):/abra golang:$(GOVERSION) \ @docker run -it -v $(PWD):/abra golang:$(GOVERSION) \
bash -c 'cd /abra; ./scripts/docker/build.sh' bash -c 'cd /abra; ./scripts/docker/build.sh'
build-docker: build-docker-abra
clean: clean:
@rm '$(GOPATH)/bin/abra' @rm '$(GOPATH)/bin/abra'
@rm '$(GOPATH)/bin/kadabra'
format: format:
@gofmt -s -w $$(find . -type f -name '*.go' | grep -v "/vendor/") @gofmt -s -w $$(find . -type f -name '*.go' | grep -v "/vendor/")
@ -78,14 +61,20 @@ update-po:
done done
.PHONY: update-pot .PHONY: update-pot
update-pot: update-pot: $(XGETTEXT)
@xgettext-go \ @${XGETTEXT} \
-o pkg/i18n/locales/$(DOMAIN).pot \ -o pkg/i18n/locales/$(DOMAIN).pot \
--keyword=i18n.G \ --keyword=i18n.G \
--keyword-ctx=i18n.GC \
--sort-output \ --sort-output \
--add-comments-tag="translators" \ --add-comments-tag="translators" \
$$(find . -name "*.go" -not -path "*vendor*" | sort) $$(find . -name "*.go" -not -path "*vendor*" | sort)
${XGETTEXT}:
@mkdir -p ./bin && \
wget -O ./bin/xgettext-go https://git.coopcloud.tech/toolshed/xgettext-go/raw/branch/main/xgettext-go && \
chmod +x ./bin/xgettext-go
.PHONY: update-pot-po-metadata .PHONY: update-pot-po-metadata
update-pot-po-metadata: update-pot-po-metadata:
@sed -i "s/charset=CHARSET/charset=UTF-8/g" pkg/i18n/locales/*.po pkg/i18n/locales/*.pot @sed -i "s/charset=CHARSET/charset=UTF-8/g" pkg/i18n/locales/*.po pkg/i18n/locales/*.pot

View File

@ -9,7 +9,7 @@ import (
// translators: `abra app` aliases. use a comma separated list of aliases with // translators: `abra app` aliases. use a comma separated list of aliases with
// no spaces in between // no spaces in between
var appAliases = i18n.G("a") var appAliases = i18n.GC("a", "abra app")
var AppCommand = &cobra.Command{ var AppCommand = &cobra.Command{
// translators: `app` command group // translators: `app` command group

View File

@ -268,7 +268,7 @@ func init() {
AppBackupListCommand.Flags().BoolVarP( AppBackupListCommand.Flags().BoolVarP(
&showAllPaths, &showAllPaths,
i18n.G("all"), i18n.G("all"),
i18n.G("a"), i18n.GC("a", "app backup list"),
false, false,
i18n.G("show all paths"), i18n.G("show all paths"),
) )

View File

@ -3,6 +3,7 @@ package app
import ( import (
"context" "context"
"errors" "errors"
"fmt"
"strings" "strings"
"coopcloud.tech/abra/cli/internal" "coopcloud.tech/abra/cli/internal"
@ -103,18 +104,17 @@ checkout as-is. Recipe commit hashes are also supported as values for
toDeployVersion, err = getDeployVersion(args, deployMeta, app) toDeployVersion, err = getDeployVersion(args, deployMeta, app)
if err != nil { if err != nil {
log.Fatal(i18n.G("get deploy version: %s", err)) log.Fatal(err)
} }
versionIsChaos := false
if !internal.Chaos { if !internal.Chaos {
var err error isChaosCommit, err := app.Recipe.EnsureVersion(toDeployVersion)
versionIsChaos, err = app.Recipe.EnsureVersion(toDeployVersion)
if err != nil { if err != nil {
log.Fatal(i18n.G("ensure recipe: %s", err)) log.Fatal(i18n.G("ensure recipe: %s", err))
} }
if versionIsChaos { if isChaosCommit {
log.Warnf(i18n.G("version '%s' appears to be a chaos commit, but --chaos/-C was not provided", toDeployVersion)) log.Warnf(i18n.G("version '%s' appears to be a chaos commit, but --chaos/-C was not provided", toDeployVersion))
internal.Chaos = true
} }
} }
@ -152,14 +152,26 @@ checkout as-is. Recipe commit hashes are also supported as values for
log.Fatal(err) log.Fatal(err)
} }
appPkg.ExposeAllEnv(stackName, compose, app.Env)
appPkg.SetRecipeLabel(compose, stackName, app.Recipe.Name) appPkg.SetRecipeLabel(compose, stackName, app.Recipe.Name)
appPkg.SetChaosLabel(compose, stackName, internal.Chaos || versionIsChaos) appPkg.SetChaosLabel(compose, stackName, internal.Chaos)
if internal.Chaos { if internal.Chaos {
appPkg.SetChaosVersionLabel(compose, stackName, toDeployVersion) appPkg.SetChaosVersionLabel(compose, stackName, toDeployVersion)
} }
appPkg.SetUpdateLabel(compose, stackName, app.Env)
appPkg.SetVersionLabel(compose, stackName, toDeployVersion) versionLabel := toDeployVersion
if internal.Chaos {
for _, service := range compose.Services {
if service.Name == "app" {
labelKey := fmt.Sprintf("coop-cloud.%s.version", stackName)
// NOTE(d1): keep non-chaos version labbeling when doing chaos ops
versionLabel = service.Deploy.Labels[labelKey]
}
}
}
appPkg.SetVersionLabel(compose, stackName, versionLabel)
newRecipeWithDeployVersion := fmt.Sprintf("%s:%s", app.Recipe.Name, toDeployVersion)
appPkg.ExposeAllEnv(stackName, compose, app.Env, newRecipeWithDeployVersion)
envVars, err := appPkg.CheckEnv(app) envVars, err := appPkg.CheckEnv(app)
if err != nil { if err != nil {
@ -186,9 +198,12 @@ checkout as-is. Recipe commit hashes are also supported as values for
log.Debug(i18n.G("skipping domain checks")) log.Debug(i18n.G("skipping domain checks"))
} }
deployedVersion := config.NO_VERSION_DEFAULT deployedVersion := config.MISSING_DEFAULT
if deployMeta.IsDeployed { if deployMeta.IsDeployed {
deployedVersion = deployMeta.Version deployedVersion = deployMeta.Version
if deployMeta.IsChaos {
deployedVersion = deployMeta.ChaosVersion
}
} }
// Gather secrets // Gather secrets
@ -245,6 +260,7 @@ checkout as-is. Recipe commit hashes are also supported as values for
app.Name, app.Name,
app.Server, app.Server,
internal.DontWaitConverge, internal.DontWaitConverge,
internal.NoInput,
f, f,
); err != nil { ); err != nil {
log.Fatal(err) log.Fatal(err)
@ -300,6 +316,16 @@ func validateArgsAndFlags(args []string) error {
} }
func validateSecrets(cl *dockerClient.Client, app appPkg.App) error { func validateSecrets(cl *dockerClient.Client, app appPkg.App) error {
composeFiles, err := app.Recipe.GetComposeFiles(app.Env)
if err != nil {
return err
}
secretsConfig, err := secret.ReadSecretsConfig(app.Path, composeFiles, app.StackName())
if err != nil {
return err
}
secStats, err := secret.PollSecretsStatus(cl, app) secStats, err := secret.PollSecretsStatus(cl, app)
if err != nil { if err != nil {
return err return err
@ -307,6 +333,10 @@ func validateSecrets(cl *dockerClient.Client, app appPkg.App) error {
for _, secStat := range secStats { for _, secStat := range secStats {
if !secStat.CreatedOnRemote { if !secStat.CreatedOnRemote {
secretConfig := secretsConfig[secStat.LocalName]
if secretConfig.SkipGenerate {
return errors.New(i18n.G("secret not inserted (#generate=false): %s", secStat.LocalName))
}
return errors.New(i18n.G("secret not generated: %s", secStat.LocalName)) return errors.New(i18n.G("secret not generated: %s", secStat.LocalName))
} }
} }
@ -334,7 +364,12 @@ func getDeployVersion(cliArgs []string, deployMeta stack.DeployMeta, app appPkg.
// Check if the recipe has a version in the .env file // Check if the recipe has a version in the .env file
if app.Recipe.EnvVersion != "" && !internal.DeployLatest { if app.Recipe.EnvVersion != "" && !internal.DeployLatest {
if strings.HasSuffix(app.Recipe.EnvVersionRaw, "+U") { if strings.HasSuffix(app.Recipe.EnvVersionRaw, "+U") {
return "", errors.New(i18n.G("version: can not redeploy chaos version %s", app.Recipe.EnvVersionRaw)) // NOTE(d1): use double-line 5 spaces ("FATA ") trick to make a more
// informative error message. it's ugly but that's our logging situation
// atm
return "", errors.New(i18n.G(`cannot redeploy previous chaos version (%s), did you mean to use "--chaos"?
to return to a regular release, specify a release tag, commit SHA or use "--latest"`,
formatter.BoldDirtyDefault(app.Recipe.EnvVersionRaw)))
} }
log.Debug(i18n.G("version: taking version from .env file: %s", app.Recipe.EnvVersion)) log.Debug(i18n.G("version: taking version from .env file: %s", app.Recipe.EnvVersion))
return app.Recipe.EnvVersion, nil return app.Recipe.EnvVersion, nil

View File

@ -1,28 +1,50 @@
package app package app
import ( import (
"context"
"fmt" "fmt"
"os"
"path"
"path/filepath"
"regexp"
"sort" "sort"
"strings" "strings"
"coopcloud.tech/abra/cli/internal" "coopcloud.tech/abra/cli/internal"
"coopcloud.tech/abra/pkg/app"
appPkg "coopcloud.tech/abra/pkg/app"
"coopcloud.tech/abra/pkg/autocomplete" "coopcloud.tech/abra/pkg/autocomplete"
"coopcloud.tech/abra/pkg/client"
"coopcloud.tech/abra/pkg/config"
containerPkg "coopcloud.tech/abra/pkg/container"
contextPkg "coopcloud.tech/abra/pkg/context"
"coopcloud.tech/abra/pkg/formatter" "coopcloud.tech/abra/pkg/formatter"
"coopcloud.tech/abra/pkg/i18n" "coopcloud.tech/abra/pkg/i18n"
"coopcloud.tech/abra/pkg/log"
"coopcloud.tech/abra/pkg/upstream/stack"
"github.com/docker/docker/api/types/filters"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
// translators: `abra app env` aliases. use a comma separated list of aliases with // translators: `abra app env` aliases. use a comma separated list of aliases
// no spaces in between // with no spaces in between
var appEnvAliases = i18n.G("e") var appEnvAliases = i18n.G("e")
var AppEnvCommand = &cobra.Command{ // translators: `abra app env list` aliases. use a comma separated list of
// translators: `app env` command // aliases with no spaces in between
Use: i18n.G("env <domain> [flags]"), var appEnvListAliases = i18n.G("l,ls")
Aliases: strings.Split(appEnvAliases, ","),
// translators: Short description for `app env` command // translators: `abra app env pull` aliases. use a comma separated list of
Short: i18n.G("Show app .env values"), // aliases with no spaces in between
Example: i18n.G(" abra app env 1312.net"), var appEnvPullAliases = i18n.G("pl,p")
var AppEnvListCommand = &cobra.Command{
// translators: `app env list` command
Use: i18n.G("list <domain> [flags]"),
Aliases: strings.Split(appEnvListAliases, ","),
// translators: Short description for `app env list` command
Short: i18n.G("List all app environment values"),
Example: i18n.G(" abra app env list 1312.net"),
Args: cobra.ExactArgs(1), Args: cobra.ExactArgs(1),
ValidArgsFunction: func( ValidArgsFunction: func(
cmd *cobra.Command, cmd *cobra.Command,
@ -49,3 +71,274 @@ var AppEnvCommand = &cobra.Command{
fmt.Println(overview) fmt.Println(overview)
}, },
} }
var AppEnvPullCommand = &cobra.Command{
// translators: `app pull` command
Use: i18n.G("pull <domain> [flags]"),
Aliases: strings.Split(appEnvPullAliases, ","),
// translators: Short description for `app env pull` command
Short: i18n.G("Pull app environment values from a deployed app"),
Long: i18n.G(`Pull app environment values from a deploymed app.
A convenient command for when you've lost your app environment file or want to
synchronize your local app environment values with what is deployed live.`),
Example: i18n.G(` # pull existing .env file and overwrite local values
abra app env pull 1312.net --force
# pull lost app .env file
abra app env pull my.gitea.net --server 1312.net`),
Args: cobra.MaximumNArgs(2),
ValidArgsFunction: func(
cmd *cobra.Command,
args []string,
toComplete string) ([]string, cobra.ShellCompDirective) {
return autocomplete.AppNameComplete()
},
Run: func(cmd *cobra.Command, args []string) {
appName := args[0]
appEnvPath := path.Join(config.ABRA_DIR, "servers", server, fmt.Sprintf("%s.env", appName))
if _, err := os.Stat(appEnvPath); !os.IsNotExist(err) {
log.Fatal(i18n.G("%s already exists?", appEnvPath))
}
if server == "" {
log.Fatal(i18n.G("unable to determine server of app %s, please pass --server/-s", appName))
}
serverDir := filepath.Join(config.SERVERS_DIR, server)
if _, err := os.Stat(serverDir); os.IsNotExist(err) {
log.Fatal(i18n.G("unknown server %s, run \"abra server add %s\"?", server, server))
}
store := contextPkg.NewDefaultDockerContextStore()
contexts, err := store.Store.List()
if err != nil {
log.Fatal(i18n.G("unable to look up server context for %s: %s", server, err))
}
var contextCreated bool
if server == "default" {
contextCreated = true
}
for _, context := range contexts {
if context.Name == server {
contextCreated = true
}
}
if !contextCreated {
log.Fatal(i18n.G("%s missing context, run \"abra server add %s\"?", server, server))
}
cl, err := client.New(server)
if err != nil {
log.Fatal(err)
}
deployMeta, err := stack.IsDeployed(context.Background(), cl, appPkg.StackName(appName))
if err != nil {
log.Fatal(err)
}
if !deployMeta.IsDeployed {
log.Fatal(i18n.G("%s is not deployed?", appName))
}
filters := filters.NewArgs()
filters.Add("name", fmt.Sprintf("^%s_%s", app.StackName(appName), "app"))
targetContainer, err := containerPkg.GetContainer(context.Background(), cl, filters, internal.NoInput)
if err != nil {
log.Fatal(i18n.G("unable to retrieve container for %s: %s", appName, err))
}
inspectResult, err := cl.ContainerInspect(context.Background(), targetContainer.ID)
if err != nil {
log.Fatal(i18n.G("unable to inspect container for %s: %s", appName, err))
}
deploymentEnv := make(map[string]string)
for _, envVar := range inspectResult.Config.Env {
split := strings.SplitN(envVar, "=", 2)
if len(split) != 2 {
log.Debug(i18n.G("no value attached to %s", envVar))
continue
}
key, val := split[0], split[1]
deploymentEnv[key] = val
}
log.Debug(i18n.G("pulled env values from %s deployment: %s", appName, deploymentEnv))
var (
recipeEnvVar string
recipeKey string
)
if r, ok := deploymentEnv["TYPE"]; ok {
recipeKey = "TYPE"
recipeEnvVar = r
}
if r, ok := deploymentEnv["RECIPE"]; ok {
recipeKey = "RECIPE"
recipeEnvVar = r
}
if recipeEnvVar == "" {
log.Fatal(i18n.G("unable to determine recipe type from %s, env: %v", appName, inspectResult.Config.Env))
}
var recipeName = recipeEnvVar
if strings.Contains(recipeEnvVar, ":") {
split := strings.Split(recipeEnvVar, ":")
recipeName = split[0]
}
recipe := internal.ValidateRecipe(
[]string{recipeName},
cmd.Name(),
)
version := deployMeta.Version
if deployMeta.IsChaos {
version = deployMeta.ChaosVersion
}
if _, err := recipe.EnsureVersion(version); err != nil {
log.Fatal(err)
}
mergedEnv, err := recipe.SampleEnv()
if err != nil {
log.Fatal(err)
}
log.Debug(i18n.G("retrieved env values from .env.sample of %s: %s", recipe.Name, mergedEnv))
for k, v := range deploymentEnv {
mergedEnv[k] = v
}
if !strings.Contains(recipeEnvVar, ":") {
mergedEnv[recipeKey] = fmt.Sprintf("%s:%s", mergedEnv[recipeKey], version)
}
log.Debug(i18n.G("final merged env values for %s are: %s", appName, mergedEnv))
envSample, err := os.ReadFile(recipe.SampleEnvPath)
if err != nil {
log.Fatal(err)
}
err = os.WriteFile(appEnvPath, envSample, 0o664)
if err != nil {
log.Fatal(i18n.G("unable to write new env %s: %s", appEnvPath, err))
}
read, err := os.ReadFile(appEnvPath)
if err != nil {
log.Fatal(i18n.G("unable to read new env %s: %s", appEnvPath, err))
}
sampleEnv, err := recipe.SampleEnv()
if err != nil {
log.Fatal(err)
}
var composeFileUpdated bool
newContents := string(read)
for key, val := range mergedEnv {
if sampleEnv[key] == val {
continue
}
if key == "COMPOSE_FILE" {
composeFileUpdated = true
continue
}
if m, _ := regexp.MatchString(fmt.Sprintf(`#%s=`, key), newContents); m {
log.Debug(i18n.G("uncommenting %s", key))
re := regexp.MustCompile(fmt.Sprintf(`#%s=`, key))
newContents = re.ReplaceAllString(newContents, fmt.Sprintf("%s=", key))
}
if m, _ := regexp.MatchString(fmt.Sprintf(`# %s=`, key), newContents); m {
log.Debug(i18n.G("uncommenting %s", key))
re := regexp.MustCompile(fmt.Sprintf(`# %s=`, key))
newContents = re.ReplaceAllString(newContents, fmt.Sprintf("%s=", key))
}
if m, _ := regexp.MatchString(fmt.Sprintf(`%s=".*"`, key), newContents); m {
log.Debug(i18n.G(`inserting %s="%s" (double quotes)`, key, val))
re := regexp.MustCompile(fmt.Sprintf(`%s=".*"`, key))
newContents = re.ReplaceAllString(newContents, fmt.Sprintf(`%s="%s"`, key, val))
continue
}
if m, _ := regexp.MatchString(fmt.Sprintf(`%s='.*'`, key), newContents); m {
log.Debug(i18n.G(`inserting %s='%s' (single quotes)`, key, val))
re := regexp.MustCompile(fmt.Sprintf(`%s='.*'`, key))
newContents = re.ReplaceAllString(newContents, fmt.Sprintf(`%s='%s'`, key, val))
continue
}
if m, _ := regexp.MatchString(fmt.Sprintf("%s=.*", key), newContents); m {
log.Debug(i18n.G("inserting %s=%s (no quotes)", key, val))
re := regexp.MustCompile(fmt.Sprintf("%s=.*", key))
newContents = re.ReplaceAllString(newContents, fmt.Sprintf("%s=%s", key, val))
}
}
err = os.WriteFile(appEnvPath, []byte(newContents), 0)
if err != nil {
log.Fatal(i18n.G("unable to write new env %s: %s", appEnvPath, err))
}
log.Info(i18n.G("%s successfully created", appEnvPath))
if composeFileUpdated {
log.Warn(i18n.G("manual update required: COMPOSE_FILE=\"%s\"", mergedEnv["COMPOSE_FILE"]))
}
},
}
var AppEnvCommand = &cobra.Command{
// translators: `app env` command group
Use: i18n.G("env [cmd] [args] [flags]"),
Aliases: strings.Split(appEnvAliases, ","),
// translators: Short description for `app env` command group
Short: i18n.G("Manage app environment values"),
}
var (
server string
)
func init() {
AppEnvPullCommand.Flags().BoolVarP(
&internal.Force,
i18n.G("force"),
i18n.G("f"),
false,
i18n.G("perform action without further prompt"),
)
AppEnvPullCommand.Flags().StringVarP(
&server,
i18n.G("server"),
i18n.G("s"),
"",
i18n.G("server associated with deployed app"),
)
AppEnvPullCommand.RegisterFlagCompletionFunc(
i18n.G("server"),
func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return autocomplete.ServerNameComplete()
},
)
}

View File

@ -25,7 +25,6 @@ type appStatus struct {
Status string `json:"status"` Status string `json:"status"`
Chaos string `json:"chaos"` Chaos string `json:"chaos"`
ChaosVersion string `json:"chaosVersion"` ChaosVersion string `json:"chaosVersion"`
AutoUpdate string `json:"autoUpdate"`
Version string `json:"version"` Version string `json:"version"`
Upgrade string `json:"upgrade"` Upgrade string `json:"upgrade"`
} }
@ -118,7 +117,6 @@ Use "--status/-S" flag to query all servers for the live deployment status.`),
version := i18n.G("unknown") version := i18n.G("unknown")
chaos := i18n.G("unknown") chaos := i18n.G("unknown")
chaosVersion := i18n.G("unknown") chaosVersion := i18n.G("unknown")
autoUpdate := i18n.G("unknown")
if statusMeta, ok := statuses[app.StackName()]; ok { if statusMeta, ok := statuses[app.StackName()]; ok {
if currentVersion, exists := statusMeta["version"]; exists { if currentVersion, exists := statusMeta["version"]; exists {
if currentVersion != "" { if currentVersion != "" {
@ -131,9 +129,6 @@ Use "--status/-S" flag to query all servers for the live deployment status.`),
if chaosDeployVersion, exists := statusMeta["chaosVersion"]; exists { if chaosDeployVersion, exists := statusMeta["chaosVersion"]; exists {
chaosVersion = chaosDeployVersion chaosVersion = chaosDeployVersion
} }
if autoUpdateState, exists := statusMeta["autoUpdate"]; exists {
autoUpdate = autoUpdateState
}
if statusMeta["status"] != "" { if statusMeta["status"] != "" {
status = statusMeta["status"] status = statusMeta["status"]
} }
@ -146,7 +141,6 @@ Use "--status/-S" flag to query all servers for the live deployment status.`),
appStats.Chaos = chaos appStats.Chaos = chaos
appStats.ChaosVersion = chaosVersion appStats.ChaosVersion = chaosVersion
appStats.Version = version appStats.Version = version
appStats.AutoUpdate = autoUpdate
var newUpdates []string var newUpdates []string
if version != "unknown" && chaos == "false" { if version != "unknown" && chaos == "false" {
@ -165,6 +159,11 @@ Use "--status/-S" flag to query all servers for the live deployment status.`),
} }
for _, update := range updates { for _, update := range updates {
if ok := tagcmp.IsParsable(update); !ok {
log.Debug(i18n.G("unable to parse %s, skipping as upgrade option", update))
continue
}
parsedUpdate, err := tagcmp.Parse(update) parsedUpdate, err := tagcmp.Parse(update)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
@ -226,7 +225,6 @@ Use "--status/-S" flag to query all servers for the live deployment status.`),
i18n.G("CHAOS"), i18n.G("CHAOS"),
i18n.G("VERSION"), i18n.G("VERSION"),
i18n.G("UPGRADE"), i18n.G("UPGRADE"),
i18n.G("AUTOUPDATE"),
}..., }...,
) )
} }
@ -257,8 +255,7 @@ Use "--status/-S" flag to query all servers for the live deployment status.`),
appStat.Status, appStat.Status,
chaosStatus, chaosStatus,
appStat.Version, appStat.Version,
appStat.Upgrade, appStat.Upgrade}...,
appStat.AutoUpdate}...,
) )
} }

View File

@ -128,6 +128,10 @@ Use "--dry-run/-r" to see which secrets and volumes will be moved.`),
secretName := strings.Join(sname[:len(sname)-1], "_") secretName := strings.Join(sname[:len(sname)-1], "_")
data := resources.Secrets[secretName] data := resources.Secrets[secretName]
if err := client.StoreSecret(newServerClient, s.Spec.Name, data); err != nil { if err := client.StoreSecret(newServerClient, s.Spec.Name, data); err != nil {
if strings.Contains(err.Error(), "already exists") {
log.Info(i18n.G("skipping secret (because it already exists) on %s: %s", s.Spec.Name, newServer))
continue
}
log.Fatal(i18n.G("failed to store secret on %s: %s", err, newServer)) log.Fatal(i18n.G("failed to store secret on %s: %s", err, newServer))
} }
log.Info(i18n.G("created secret on %s: %s", s.Spec.Name, newServer)) log.Info(i18n.G("created secret on %s: %s", s.Spec.Name, newServer))

View File

@ -192,7 +192,27 @@ var AppNewCommand = &cobra.Command{
log.Info(i18n.G("%s created (version: %s)", appDomain, recipeVersion)) log.Info(i18n.G("%s created (version: %s)", appDomain, recipeVersion))
if len(secretsConfig) > 0 { if len(secretsConfig) > 0 {
log.Warn(i18n.G("%s requires secret generation before deploying, run \"abra app secret generate %s --all\"", recipe.Name, appDomain)) var (
hasSecretToGenerate bool
hasSecretToSkip bool
)
for _, secretConfig := range secretsConfig {
if secretConfig.SkipGenerate {
hasSecretToSkip = true
continue
}
hasSecretToGenerate = true
}
if hasSecretToGenerate && !generateSecrets {
log.Warn(i18n.G("%s requires secret generation before deploy, run \"abra app secret generate %s --all\"", recipe.Name, appDomain))
}
if hasSecretToSkip {
log.Warn(i18n.G("%s requires secret insertion before deploy (#generate=false)", recipe.Name))
}
} }
if len(appSecrets) > 0 { if len(appSecrets) > 0 {

View File

@ -128,6 +128,7 @@ Pass "--all-services/-a" to restart all services.`),
AppName: app.Name, AppName: app.Name,
ServerName: app.Server, ServerName: app.Server,
Filters: f, Filters: f,
NoInput: internal.NoInput,
NoLog: true, NoLog: true,
Quiet: true, Quiet: true,
} }
@ -166,7 +167,7 @@ func init() {
AppRestartCommand.Flags().BoolVarP( AppRestartCommand.Flags().BoolVarP(
&allServices, &allServices,
i18n.G("all-services"), i18n.G("all-services"),
i18n.G("a"), i18n.GC("a", "app restart"),
false, false,
i18n.G("restart all services"), i18n.G("restart all services"),
) )

View File

@ -2,6 +2,7 @@ package app
import ( import (
"errors" "errors"
"fmt"
"strings" "strings"
appPkg "coopcloud.tech/abra/pkg/app" appPkg "coopcloud.tech/abra/pkg/app"
@ -177,13 +178,14 @@ beforehand. See "abra app backup" for more.`),
log.Fatal(err) log.Fatal(err)
} }
appPkg.ExposeAllEnv(stackName, compose, app.Env) newRecipeWithDowngradeVersion := fmt.Sprintf("%s:%s", app.Recipe.Name, chosenDowngrade)
appPkg.ExposeAllEnv(stackName, compose, app.Env, newRecipeWithDowngradeVersion)
appPkg.SetRecipeLabel(compose, stackName, app.Recipe.Name) appPkg.SetRecipeLabel(compose, stackName, app.Recipe.Name)
appPkg.SetChaosLabel(compose, stackName, internal.Chaos) appPkg.SetChaosLabel(compose, stackName, internal.Chaos)
if internal.Chaos { if internal.Chaos {
appPkg.SetChaosVersionLabel(compose, stackName, chosenDowngrade) appPkg.SetChaosVersionLabel(compose, stackName, chosenDowngrade)
} }
appPkg.SetUpdateLabel(compose, stackName, app.Env)
// Gather secrets // Gather secrets
secretInfo, err := deploy.GatherSecretsForDeploy(cl, app, internal.ShowUnchanged) secretInfo, err := deploy.GatherSecretsForDeploy(cl, app, internal.ShowUnchanged)
@ -203,10 +205,15 @@ beforehand. See "abra app backup" for more.`),
log.Fatal(err) log.Fatal(err)
} }
deployedVersion := deployMeta.Version
if deployMeta.IsChaos {
deployedVersion = deployMeta.ChaosVersion
}
// NOTE(d1): no release notes implemeneted for rolling back // NOTE(d1): no release notes implemeneted for rolling back
if err := internal.DeployOverview( if err := internal.DeployOverview(
app, app,
deployMeta.Version, deployedVersion,
chosenDowngrade, chosenDowngrade,
"", "",
downgradeWarnMessages, downgradeWarnMessages,
@ -239,6 +246,7 @@ beforehand. See "abra app backup" for more.`),
stackName, stackName,
app.Server, app.Server,
internal.DontWaitConverge, internal.DontWaitConverge,
internal.NoInput,
f, f,
); err != nil { ); err != nil {
log.Fatal(err) log.Fatal(err)

View File

@ -165,7 +165,7 @@ var AppSecretInsertCommand = &cobra.Command{
Arbitrary secret insertion is not supported. Secrets that are inserted must Arbitrary secret insertion is not supported. Secrets that are inserted must
match those configured in the recipe beforehand. match those configured in the recipe beforehand.
This can be useful when you want to manually generate secrets for an app This command can be useful when you want to manually generate secrets for an app
environment. Typically, you can let Abra generate them for you on app creation environment. Typically, you can let Abra generate them for you on app creation
(see "abra app new --secrets/-S" for more).`), (see "abra app new --secrets/-S" for more).`),
Example: i18n.G(` # insert regular secret Example: i18n.G(` # insert regular secret
@ -574,7 +574,7 @@ func init() {
AppSecretGenerateCommand.Flags().BoolVarP( AppSecretGenerateCommand.Flags().BoolVarP(
&generateAllSecrets, &generateAllSecrets,
i18n.G("all"), i18n.G("all"),
i18n.G("a"), i18n.GC("a", "app secret generate"),
false, false,
i18n.G("generate all secrets"), i18n.G("generate all secrets"),
) )
@ -614,7 +614,7 @@ func init() {
AppSecretRmCommand.Flags().BoolVarP( AppSecretRmCommand.Flags().BoolVarP(
&rmAllSecrets, &rmAllSecrets,
i18n.G("all"), i18n.G("all"),
i18n.G("a"), i18n.GC("a", "app secret rm"),
false, false,
i18n.G("remove all secrets"), i18n.G("remove all secrets"),
) )

View File

@ -28,6 +28,7 @@ var AppUndeployCommand = &cobra.Command{
Use: i18n.G("undeploy <domain> [flags]"), Use: i18n.G("undeploy <domain> [flags]"),
// translators: Short description for `app undeploy` command // translators: Short description for `app undeploy` command
Aliases: strings.Split(appUndeployAliases, ","), Aliases: strings.Split(appUndeployAliases, ","),
Short: i18n.G("Undeploy a deployed app"),
Long: i18n.G(`This does not destroy any application data. Long: i18n.G(`This does not destroy any application data.
However, you should remain vigilant, as your swarm installation will consider However, you should remain vigilant, as your swarm installation will consider
@ -65,10 +66,15 @@ Passing "--prune/-p" does not remove those volumes.`),
log.Fatal(i18n.G("%s is not deployed?", app.Name)) log.Fatal(i18n.G("%s is not deployed?", app.Name))
} }
version := deployMeta.Version
if deployMeta.IsChaos {
version = deployMeta.ChaosVersion
}
if err := internal.DeployOverview( if err := internal.DeployOverview(
app, app,
deployMeta.Version, version,
config.NO_DOMAIN_DEFAULT, config.MISSING_DEFAULT,
"", "",
nil, nil,
nil, nil,
@ -110,7 +116,7 @@ Passing "--prune/-p" does not remove those volumes.`),
log.Info(i18n.G("undeploy succeeded 🟢")) log.Info(i18n.G("undeploy succeeded 🟢"))
if err := app.WriteRecipeVersion(deployMeta.Version, false); err != nil { if err := app.WriteRecipeVersion(version, false); err != nil {
log.Fatal(i18n.G("writing recipe version failed: %s", err)) log.Fatal(i18n.G("writing recipe version failed: %s", err))
} }
}, },

View File

@ -190,13 +190,14 @@ beforehand. See "abra app backup" for more.`),
log.Fatal(err) log.Fatal(err)
} }
appPkg.ExposeAllEnv(stackName, compose, app.Env) newRecipeWithUpgradeVersion := fmt.Sprintf("%s:%s", app.Recipe.Name, chosenUpgrade)
appPkg.ExposeAllEnv(stackName, compose, app.Env, newRecipeWithUpgradeVersion)
appPkg.SetRecipeLabel(compose, stackName, app.Recipe.Name) appPkg.SetRecipeLabel(compose, stackName, app.Recipe.Name)
appPkg.SetChaosLabel(compose, stackName, internal.Chaos) appPkg.SetChaosLabel(compose, stackName, internal.Chaos)
if internal.Chaos { if internal.Chaos {
appPkg.SetChaosVersionLabel(compose, stackName, chosenUpgrade) appPkg.SetChaosVersionLabel(compose, stackName, chosenUpgrade)
} }
appPkg.SetUpdateLabel(compose, stackName, app.Env)
envVars, err := appPkg.CheckEnv(app) envVars, err := appPkg.CheckEnv(app)
if err != nil { if err != nil {
@ -241,9 +242,14 @@ beforehand. See "abra app backup" for more.`),
) )
} }
deployedVersion := deployMeta.Version
if deployMeta.IsChaos {
deployedVersion = deployMeta.ChaosVersion
}
if err := internal.DeployOverview( if err := internal.DeployOverview(
app, app,
deployMeta.Version, deployedVersion,
chosenUpgrade, chosenUpgrade,
upgradeReleaseNotes, upgradeReleaseNotes,
upgradeWarnMessages, upgradeWarnMessages,
@ -276,6 +282,7 @@ beforehand. See "abra app backup" for more.`),
stackName, stackName,
app.Server, app.Server,
internal.DontWaitConverge, internal.DontWaitConverge,
internal.NoInput,
f, f,
); err != nil { ); err != nil {
log.Fatal(err) log.Fatal(err)

View File

@ -64,14 +64,14 @@ func DeployOverview(
server = "local" server = "local"
} }
domain := app.Domain domain := fmt.Sprintf("https://%s", app.Domain)
if domain == "" { if domain == "" {
domain = config.NO_DOMAIN_DEFAULT domain = config.MISSING_DEFAULT
} }
envVersion := app.Recipe.EnvVersionRaw envVersion := app.Recipe.EnvVersionRaw
if envVersion == "" { if envVersion == "" {
envVersion = config.NO_VERSION_DEFAULT envVersion = config.MISSING_DEFAULT
} }
rows := [][]string{ rows := [][]string{
@ -140,32 +140,40 @@ func DeployOverview(
} }
func getDeployType(currentVersion, newVersion string) string { func getDeployType(currentVersion, newVersion string) string {
if newVersion == config.NO_DOMAIN_DEFAULT { if newVersion == config.MISSING_DEFAULT {
return i18n.G("UNDEPLOY") return i18n.G("UNDEPLOY")
} }
if strings.Contains(newVersion, "+U") { if strings.Contains(newVersion, "+U") {
return i18n.G("CHAOS DEPLOY") return i18n.G("CHAOS DEPLOY")
} }
if strings.Contains(currentVersion, "+U") { if strings.Contains(currentVersion, "+U") {
return i18n.G("UNCHAOS DEPLOY") return i18n.G("UNCHAOS DEPLOY")
} }
if currentVersion == newVersion { if currentVersion == newVersion {
return ("REDEPLOY") return ("REDEPLOY")
} }
if currentVersion == config.NO_VERSION_DEFAULT {
if currentVersion == config.MISSING_DEFAULT {
return i18n.G("NEW DEPLOY") return i18n.G("NEW DEPLOY")
} }
currentParsed, err := tagcmp.Parse(currentVersion) currentParsed, err := tagcmp.Parse(currentVersion)
if err != nil { if err != nil {
return i18n.G("DEPLOY") return i18n.G("DEPLOY")
} }
newParsed, err := tagcmp.Parse(newVersion) newParsed, err := tagcmp.Parse(newVersion)
if err != nil { if err != nil {
return i18n.G("DEPLOY") return i18n.G("DEPLOY")
} }
if currentParsed.IsLessThan(newParsed) { if currentParsed.IsLessThan(newParsed) {
return i18n.G("UPGRADE") return i18n.G("UPGRADE")
} }
return i18n.G("DOWNGRADE") return i18n.G("DOWNGRADE")
} }
@ -183,17 +191,17 @@ func MoveOverview(
domain := app.Domain domain := app.Domain
if domain == "" { if domain == "" {
domain = config.NO_DOMAIN_DEFAULT domain = config.MISSING_DEFAULT
} }
secretsOverview := strings.Join(secrets, "\n") secretsOverview := strings.Join(secrets, "\n")
if len(secrets) == 0 { if len(secrets) == 0 {
secretsOverview = config.NO_SECRETS_DEFAULT secretsOverview = config.MISSING_DEFAULT
} }
volumesOverview := strings.Join(volumes, "\n") volumesOverview := strings.Join(volumes, "\n")
if len(volumes) == 0 { if len(volumes) == 0 {
volumesOverview = config.NO_VOLUMES_DEFAULT volumesOverview = config.MISSING_DEFAULT
} }
rows := [][]string{ rows := [][]string{

View File

@ -119,7 +119,7 @@ func init() {
RecipeFetchCommand.Flags().BoolVarP( RecipeFetchCommand.Flags().BoolVarP(
&fetchAllRecipes, &fetchAllRecipes,
i18n.G("all"), i18n.G("all"),
i18n.G("a"), i18n.GC("a", "recipe fetch"),
false, false,
i18n.G("fetch all recipes"), i18n.G("fetch all recipes"),
) )

View File

@ -135,14 +135,23 @@ your private key and enter your passphrase beforehand.
log.Fatal(err) log.Fatal(err)
} }
if tagString == "" && (!internal.Major && !internal.Minor && !internal.Patch) { labelVersion, err := getLabelVersion(recipe, false)
var err error if err != nil {
tagString, err = getLabelVersion(recipe, false) log.Fatal(err)
if err != nil { }
log.Fatal(err)
for _, tag := range tags {
previousTagLeftHand := strings.Split(tag, "+")[0]
newTagStringLeftHand := strings.Split(labelVersion, "+")[0]
if previousTagLeftHand == newTagStringLeftHand {
log.Fatal(i18n.G("%s+... conflicts with a previous release: %s", newTagStringLeftHand, tag))
} }
} }
if tagString == "" && (!internal.Major && !internal.Minor && !internal.Patch) {
tagString = labelVersion
}
isClean, err := gitPkg.IsClean(recipe.Dir) isClean, err := gitPkg.IsClean(recipe.Dir)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
@ -321,7 +330,7 @@ func addReleaseNotes(recipe recipe.Recipe, tag string) error {
if !internal.NoInput { if !internal.NoInput {
prompt := &survey.Confirm{ prompt := &survey.Confirm{
Message: i18n.G("Use release note in release/next?"), Message: i18n.G("use release note in release/next?"),
} }
if err := survey.AskOne(prompt, &addNextAsReleaseNotes); err != nil { if err := survey.AskOne(prompt, &addNextAsReleaseNotes); err != nil {

View File

@ -140,13 +140,15 @@ likely to change.
if serviceVersions, ok := recipeVersion[latestRecipeVersion]; ok { if serviceVersions, ok := recipeVersion[latestRecipeVersion]; ok {
for serviceName := range serviceVersions { for serviceName := range serviceVersions {
serviceMeta := serviceVersions[serviceName] serviceMeta := serviceVersions[serviceName]
changesTable.Row(
[]string{ existingImageTag := fmt.Sprintf("%s:%s", serviceMeta.Image, serviceMeta.Tag)
serviceName, newImageTag := fmt.Sprintf("%s:%s", serviceMeta.Image, imagesTmp[serviceMeta.Image])
fmt.Sprintf("%s:%s", serviceMeta.Image, serviceMeta.Tag),
fmt.Sprintf("%s:%s", serviceMeta.Image, imagesTmp[serviceMeta.Image]), if existingImageTag == newImageTag {
}..., continue
) }
changesTable.Row([]string{serviceName, existingImageTag, newImageTag}...)
} }
} }
} }

View File

@ -381,7 +381,7 @@ func init() {
RecipeUpgradeCommand.Flags().BoolVarP( RecipeUpgradeCommand.Flags().BoolVarP(
&allTags, &allTags,
i18n.G("all-tags"), i18n.G("all-tags"),
i18n.G("a"), i18n.GC("a", "recipe upgrade"),
false, false,
i18n.G("list all tags, not just upgrades"), i18n.G("list all tags, not just upgrades"),
) )

View File

@ -283,6 +283,11 @@ Config:
app.AppBackupSnapshotsCommand, app.AppBackupSnapshotsCommand,
) )
app.AppEnvCommand.AddCommand(
app.AppEnvListCommand,
app.AppEnvPullCommand,
)
app.AppCommand.AddCommand( app.AppCommand.AddCommand(
app.AppBackupCommand, app.AppBackupCommand,
app.AppCheckCommand, app.AppCheckCommand,

View File

@ -20,7 +20,7 @@ import (
// translators: `abra server add` aliases. use a comma separated list of // translators: `abra server add` aliases. use a comma separated list of
// aliases with no spaces in between // aliases with no spaces in between
var serverAddAliases = i18n.G("a") var serverAddAliases = i18n.GC("a", "server add")
var ServerAddCommand = &cobra.Command{ var ServerAddCommand = &cobra.Command{
// translators: `server add` command // translators: `server add` command

View File

@ -96,7 +96,7 @@ func init() {
ServerPruneCommand.Flags().BoolVarP( ServerPruneCommand.Flags().BoolVarP(
&allFilter, &allFilter,
i18n.G("all"), i18n.G("all"),
i18n.G("a"), i18n.GC("a", "server prune"),
false, false,
i18n.G("remove all unused images"), i18n.G("remove all unused images"),
) )

View File

@ -1,558 +0,0 @@
package updater
import (
"context"
"errors"
"fmt"
"os"
"strconv"
"strings"
"coopcloud.tech/abra/cli/internal"
appPkg "coopcloud.tech/abra/pkg/app"
"coopcloud.tech/abra/pkg/client"
"coopcloud.tech/abra/pkg/deploy"
"coopcloud.tech/abra/pkg/envfile"
"coopcloud.tech/abra/pkg/i18n"
"coopcloud.tech/abra/pkg/lint"
"coopcloud.tech/abra/pkg/recipe"
"coopcloud.tech/abra/pkg/upstream/convert"
"coopcloud.tech/abra/pkg/upstream/stack"
"coopcloud.tech/tagcmp"
charmLog "github.com/charmbracelet/log"
composetypes "github.com/docker/cli/cli/compose/types"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
dockerclient "github.com/docker/docker/client"
"github.com/spf13/cobra"
"coopcloud.tech/abra/pkg/log"
)
const SERVER = "localhost"
// translators: `kadabra notify` aliases. use a comma separated list of aliases
// with no spaces in between
var notifyAliases = i18n.G("n")
// NotifyCommand checks for available upgrades.
var NotifyCommand = &cobra.Command{
// translators: `notify` command
Use: i18n.G("notify [flags]"),
Aliases: strings.Split(notifyAliases, ","),
// translators: Short description for `notify` command
Short: i18n.G("Check for available upgrades"),
Long: i18n.G(`Notify on new versions for deployed apps.
If a new patch/minor version is available, a notification is printed.
Use "--major/-m" to include new major versions.`),
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
cl, err := client.New("default")
if err != nil {
log.Fatal(err)
}
stacks, err := stack.GetStacks(cl)
if err != nil {
log.Fatal(err)
}
for _, stackInfo := range stacks {
stackName := stackInfo.Name
recipeName, err := getLabel(cl, stackName, "recipe")
if err != nil {
log.Fatal(err)
}
if recipeName != "" {
_, err = getLatestUpgrade(cl, stackName, recipeName)
if err != nil {
log.Fatal(err)
}
}
}
},
}
// translators: `kadabra upgrade` aliases. use a comma separated list of aliases with
// no spaces in between
var upgradeAliases = i18n.G("u")
// UpgradeCommand upgrades apps.
var UpgradeCommand = &cobra.Command{
// translators: `app upgrade` command
Use: i18n.G("upgrade [[stack] [recipe] | --all] [flags]"),
Aliases: strings.Split(upgradeAliases, ","),
// translators: Short description for `app upgrade` command
Short: i18n.G("Upgrade apps"),
Long: i18n.G(`Upgrade an app by specifying stack name and recipe.
Use "--all" to upgrade every deployed app.
For each app with auto updates enabled, the deployed version is compared with
the current recipe catalogue version. If a new patch/minor version is
available, the app is upgraded.
To include major versions use the "--major/-m" flag. You probably don't want
that as it will break things. Only apps that are not deployed with "--chaos/-C"
are upgraded, to update chaos deployments use the "--chaos/-C" flag. Use it
with care.`),
Args: cobra.RangeArgs(0, 2),
// TODO(d1): complete stack/recipe
// ValidArgsFunction: func(
// cmd *cobra.Command,
// args []string,
// toComplete string) ([]string, cobra.ShellCompDirective) {
// },
Run: func(cmd *cobra.Command, args []string) {
cl, err := client.New("default")
if err != nil {
log.Fatal(err)
}
if !updateAll && len(args) != 2 {
log.Fatal(i18n.G("missing arguments or --all/-a flag"))
}
if !updateAll {
stackName := args[0]
recipeName := args[1]
err = tryUpgrade(cl, stackName, recipeName)
if err != nil {
log.Fatal(err)
}
return
}
stacks, err := stack.GetStacks(cl)
if err != nil {
log.Fatal(err)
}
for _, stackInfo := range stacks {
stackName := stackInfo.Name
recipeName, err := getLabel(cl, stackName, "recipe")
if err != nil {
log.Fatal(err)
}
err = tryUpgrade(cl, stackName, recipeName)
if err != nil {
log.Fatal(err)
}
}
},
}
// getLabel reads docker labels from running services in the format of "coop-cloud.${STACK_NAME}.${LABEL}".
func getLabel(cl *dockerclient.Client, stackName string, label string) (string, error) {
filter := filters.NewArgs()
filter.Add("label", fmt.Sprintf("%s=%s", convert.LabelNamespace, stackName))
services, err := cl.ServiceList(context.Background(), types.ServiceListOptions{Filters: filter})
if err != nil {
return "", err
}
for _, service := range services {
labelKey := fmt.Sprintf("coop-cloud.%s.%s", stackName, label)
if labelValue, ok := service.Spec.Labels[labelKey]; ok {
return labelValue, nil
}
}
log.Debug(i18n.G("no %s label found for %s", label, stackName))
return "", nil
}
// getBoolLabel reads a boolean docker label from running services
func getBoolLabel(cl *dockerclient.Client, stackName string, label string) (bool, error) {
lableValue, err := getLabel(cl, stackName, label)
if err != nil {
return false, err
}
if lableValue != "" {
value, err := strconv.ParseBool(lableValue)
if err != nil {
return false, err
}
return value, nil
}
log.Debug(i18n.G("boolean label %s could not be found for %s, set default to false.", label, stackName))
return false, nil
}
// getEnv reads env variables from docker services.
func getEnv(cl *dockerclient.Client, stackName string) (envfile.AppEnv, error) {
envMap := make(map[string]string)
filter := filters.NewArgs()
filter.Add("label", fmt.Sprintf("%s=%s", convert.LabelNamespace, stackName))
services, err := cl.ServiceList(context.Background(), types.ServiceListOptions{Filters: filter})
if err != nil {
return nil, err
}
for _, service := range services {
envList := service.Spec.TaskTemplate.ContainerSpec.Env
for _, envString := range envList {
splitString := strings.SplitN(envString, "=", 2)
if len(splitString) != 2 {
log.Debug(i18n.G("can't separate key from value: %s (this variable is probably unset)", envString))
continue
}
k := splitString[0]
v := splitString[1]
log.Debugf(i18n.G("for %s read env %s with value: %s from docker service", stackName, k, v))
envMap[k] = v
}
}
return envMap, nil
}
// getLatestUpgrade returns the latest available version for an app respecting
// the "--major" flag if it is newer than the currently deployed version.
func getLatestUpgrade(cl *dockerclient.Client, stackName string, recipeName string) (string, error) {
deployedVersion, err := getDeployedVersion(cl, stackName, recipeName)
if err != nil {
return "", err
}
availableUpgrades, err := getAvailableUpgrades(cl, stackName, recipeName, deployedVersion)
if err != nil {
return "", err
}
if len(availableUpgrades) == 0 {
log.Debugf(i18n.G("no available upgrades for %s", stackName))
return "", nil
}
var chosenUpgrade string
if len(availableUpgrades) > 0 {
chosenUpgrade = availableUpgrades[len(availableUpgrades)-1]
log.Info(i18n.G("%s (%s) can be upgraded from version %s to %s", stackName, recipeName, deployedVersion, chosenUpgrade))
}
return chosenUpgrade, nil
}
// getDeployedVersion returns the currently deployed version of an app.
func getDeployedVersion(cl *dockerclient.Client, stackName string, recipeName string) (string, error) {
log.Debug(i18n.G("retrieve deployed version whether %s is already deployed", stackName))
deployMeta, err := stack.IsDeployed(context.Background(), cl, stackName)
if err != nil {
return "", err
}
if !deployMeta.IsDeployed {
return "", errors.New(i18n.G("%s is not deployed?", stackName))
}
if deployMeta.Version == "unknown" {
return "", errors.New(i18n.G("failed to determine deployed version of %s", stackName))
}
return deployMeta.Version, nil
}
// getAvailableUpgrades returns all available versions of an app that are newer
// than the deployed version. It only includes major upgrades if the "--major"
// flag is set.
func getAvailableUpgrades(cl *dockerclient.Client, stackName string, recipeName string,
deployedVersion string) ([]string, error) {
catl, err := recipe.ReadRecipeCatalogue(internal.Offline)
if err != nil {
return nil, err
}
versions, err := recipe.GetRecipeCatalogueVersions(recipeName, catl)
if err != nil {
return nil, err
}
if len(versions) == 0 {
log.Warn(i18n.G("no published releases for %s in the recipe catalogue?", recipeName))
return nil, nil
}
var availableUpgrades []string
for _, version := range versions {
parsedDeployedVersion, err := tagcmp.Parse(deployedVersion)
if err != nil {
return nil, err
}
parsedVersion, err := tagcmp.Parse(version)
if err != nil {
return nil, err
}
versionDelta, err := parsedDeployedVersion.UpgradeDelta(parsedVersion)
if err != nil {
return nil, err
}
if 0 < versionDelta.UpgradeType() && (versionDelta.UpgradeType() < 4 || includeMajorUpdates) {
availableUpgrades = append(availableUpgrades, version)
}
}
log.Debug(i18n.G("available updates for %s: %s", stackName, availableUpgrades))
return availableUpgrades, nil
}
// processRecipeRepoVersion clones, pulls, checks out the version and lints the
// recipe repository.
func processRecipeRepoVersion(r recipe.Recipe, version string) error {
if err := r.EnsureExists(); err != nil {
return err
}
if err := r.EnsureUpToDate(); err != nil {
return err
}
if _, err := r.EnsureVersion(version); err != nil {
return err
}
if err := lint.LintForErrors(r); err != nil {
return err
}
return nil
}
// createDeployConfig merges and enriches the compose config for the deployment.
func createDeployConfig(r recipe.Recipe, stackName string, env envfile.AppEnv) (*composetypes.Config, stack.Deploy, error) {
env["STACK_NAME"] = stackName
deployOpts := stack.Deploy{
Namespace: stackName,
Prune: false,
ResolveImage: stack.ResolveImageAlways,
Detach: false,
}
composeFiles, err := r.GetComposeFiles(env)
if err != nil {
return nil, deployOpts, err
}
deployOpts.Composefiles = composeFiles
compose, err := appPkg.GetAppComposeConfig(stackName, deployOpts, env)
if err != nil {
return nil, deployOpts, err
}
appPkg.ExposeAllEnv(stackName, compose, env)
// after the upgrade the deployment won't be in chaos state anymore
appPkg.SetChaosLabel(compose, stackName, false)
appPkg.SetRecipeLabel(compose, stackName, r.Name)
appPkg.SetUpdateLabel(compose, stackName, env)
return compose, deployOpts, nil
}
// tryUpgrade performs the upgrade if all the requirements are fulfilled.
func tryUpgrade(cl *dockerclient.Client, stackName, recipeName string) error {
if recipeName == "" {
log.Debug(i18n.G("don't update %s due to missing recipe name", stackName))
return nil
}
chaos, err := getBoolLabel(cl, stackName, "chaos")
if err != nil {
return err
}
if chaos && !internal.Chaos {
log.Debug(i18n.G("don't update %s due to chaos deployment", stackName))
return nil
}
updatesEnabled, err := getBoolLabel(cl, stackName, "autoupdate")
if err != nil {
return err
}
if !updatesEnabled {
log.Debug(i18n.G("don't update %s due to disabled auto updates or missing ENABLE_AUTO_UPDATE env", stackName))
return nil
}
upgradeVersion, err := getLatestUpgrade(cl, stackName, recipeName)
if err != nil {
return err
}
if upgradeVersion == "" {
log.Debug(i18n.G("don't update %s due to no new version", stackName))
return nil
}
err = upgrade(cl, stackName, recipeName, upgradeVersion)
return err
}
// upgrade performs all necessary steps to upgrade an app.
func upgrade(cl *dockerclient.Client, stackName, recipeName, upgradeVersion string) error {
env, err := getEnv(cl, stackName)
if err != nil {
return err
}
app := appPkg.App{
Name: stackName,
Recipe: recipe.Get(recipeName),
Server: SERVER,
Env: env,
}
r := recipe.Get(recipeName)
if err = processRecipeRepoVersion(r, upgradeVersion); err != nil {
return err
}
if err = deploy.MergeAbraShEnv(app.Recipe, app.Env); err != nil {
return err
}
compose, deployOpts, err := createDeployConfig(r, stackName, app.Env)
if err != nil {
return err
}
log.Info(i18n.G("upgrade %s (%s) to version %s", stackName, recipeName, upgradeVersion))
serviceNames, err := appPkg.GetAppServiceNames(app.Name)
if err != nil {
return err
}
f, err := app.Filters(true, false, serviceNames...)
if err != nil {
return err
}
err = stack.RunDeploy(
cl,
deployOpts,
compose,
stackName,
app.Server,
true,
f,
)
return err
}
func newKadabraApp(version, commit string) *cobra.Command {
rootCmd := &cobra.Command{
// translators: `kadabra` binary name
Use: i18n.G("kadabra [cmd] [flags]"),
Version: fmt.Sprintf("%s-%s", version, commit[:7]),
// translators: Short description for `kababra` binary
Short: i18n.G("The Co-op Cloud auto-updater 🤖 🚀"),
PersistentPreRun: func(cmd *cobra.Command, args []string) {
log.Logger.SetStyles(charmLog.DefaultStyles())
charmLog.SetDefault(log.Logger)
if internal.Debug {
log.SetLevel(log.DebugLevel)
log.SetOutput(os.Stderr)
log.SetReportCaller(true)
}
log.Debug(i18n.G("kadabra version %s, commit %s", version, commit))
},
}
rootCmd.PersistentFlags().BoolVarP(
&internal.Debug,
i18n.G("debug"),
i18n.G("d"),
false,
i18n.G("show debug messages"),
)
rootCmd.PersistentFlags().BoolVarP(
&internal.NoInput,
i18n.G("no-input"),
i18n.G("n"),
false,
i18n.G("toggle non-interactive mode"),
)
rootCmd.AddCommand(
NotifyCommand,
UpgradeCommand,
)
return rootCmd
}
// RunApp runs CLI abra app.
func RunApp(version, commit string) {
app := newKadabraApp(version, commit)
if err := app.Execute(); err != nil {
log.Fatal(err)
}
}
var (
includeMajorUpdates bool
updateAll bool
)
func init() {
NotifyCommand.Flags().BoolVarP(
&includeMajorUpdates,
"major",
"m",
false,
"check for major updates",
)
UpgradeCommand.Flags().BoolVarP(
&internal.Chaos,
i18n.G("chaos"),
i18n.G("C"),
false,
i18n.G("ignore uncommitted recipes changes"),
)
UpgradeCommand.Flags().BoolVarP(
&includeMajorUpdates,
i18n.G("major"),
i18n.G("m"),
false,
i18n.G("check for major updates"),
)
UpgradeCommand.Flags().BoolVarP(
&updateAll,
i18n.G("all"),
i18n.G("a"),
false,
i18n.G("update all deployed apps"),
)
}

View File

@ -1,23 +0,0 @@
// Package main provides the command-line entrypoint.
package main
import (
"coopcloud.tech/abra/cli/updater"
)
// Version is the current version of Kadabra.
var Version string
// Commit is the current git commit of Kadabra.
var Commit string
func main() {
if Version == "" {
Version = "dev"
}
if Commit == "" {
Commit = " "
}
updater.RunApp(Version, Commit)
}

106
go.mod
View File

@ -1,28 +1,26 @@
module coopcloud.tech/abra module coopcloud.tech/abra
go 1.24.0 go 1.24.2
toolchain go1.24.1
require ( require (
coopcloud.tech/tagcmp v0.0.0-20250818180036-0ec1b205b5ca coopcloud.tech/tagcmp v0.0.0-20250818180036-0ec1b205b5ca
git.coopcloud.tech/toolshed/godotenv v1.5.2-0.20250103171850-4d0ca41daa5c git.coopcloud.tech/toolshed/godotenv v1.5.2-0.20250103171850-4d0ca41daa5c
github.com/AlecAivazis/survey/v2 v2.3.7 github.com/AlecAivazis/survey/v2 v2.3.7
github.com/charmbracelet/bubbletea v1.3.6 github.com/charmbracelet/bubbletea v1.3.10
github.com/charmbracelet/lipgloss v1.1.0 github.com/charmbracelet/lipgloss v1.1.0
github.com/charmbracelet/log v0.4.2 github.com/charmbracelet/log v0.4.2
github.com/distribution/reference v0.6.0 github.com/distribution/reference v0.6.0
github.com/docker/cli v28.3.3+incompatible github.com/docker/cli v29.0.0+incompatible
github.com/docker/docker v28.3.3+incompatible github.com/docker/docker v28.5.2+incompatible
github.com/docker/go-units v0.5.0 github.com/docker/go-units v0.5.0
github.com/go-git/go-git/v5 v5.16.2 github.com/go-git/go-git/v5 v5.16.3
github.com/google/go-cmp v0.7.0 github.com/google/go-cmp v0.7.0
github.com/leonelquinteros/gotext v1.7.2 github.com/leonelquinteros/gotext v1.7.2
github.com/moby/sys/signal v0.7.1 github.com/moby/sys/signal v0.7.1
github.com/moby/term v0.5.2 github.com/moby/term v0.5.2
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1
github.com/schollz/progressbar/v3 v3.18.0 github.com/schollz/progressbar/v3 v3.18.0
golang.org/x/term v0.34.0 golang.org/x/term v0.36.0
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
gotest.tools/v3 v3.5.2 gotest.tools/v3 v3.5.2
) )
@ -38,17 +36,21 @@ require (
github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cenkalti/backoff/v5 v5.0.3 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/charmbracelet/colorprofile v0.3.2 // indirect github.com/charmbracelet/colorprofile v0.3.3 // indirect
github.com/charmbracelet/x/ansi v0.10.1 // indirect github.com/charmbracelet/x/ansi v0.11.0 // indirect
github.com/charmbracelet/x/cellbuf v0.0.13 // indirect github.com/charmbracelet/x/cellbuf v0.0.14 // indirect
github.com/charmbracelet/x/term v0.2.1 // 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/cloudflare/circl v1.6.1 // indirect github.com/cloudflare/circl v1.6.1 // indirect
github.com/containerd/errdefs v1.0.0 // indirect github.com/containerd/errdefs v1.0.0 // indirect
github.com/containerd/errdefs/pkg v0.3.0 // indirect github.com/containerd/errdefs/pkg v0.3.0 // indirect
github.com/containerd/log v0.1.0 // indirect github.com/containerd/log v0.1.0 // indirect
github.com/containerd/platforms v0.2.1 // indirect github.com/containerd/platforms v0.2.1 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
github.com/cyphar/filepath-securejoin v0.4.1 // indirect github.com/cyphar/filepath-securejoin v0.6.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/docker/distribution v2.8.3+incompatible // indirect github.com/docker/distribution v2.8.3+incompatible // indirect
github.com/docker/go-connections v0.6.0 // indirect github.com/docker/go-connections v0.6.0 // indirect
@ -60,30 +62,32 @@ require (
github.com/ghodss/yaml v1.0.0 // indirect github.com/ghodss/yaml v1.0.0 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-git/go-billy/v5 v5.6.2 // indirect github.com/go-git/go-billy/v5 v5.6.2 // indirect
github.com/go-logfmt/logfmt v0.6.0 // indirect github.com/go-logfmt/logfmt v0.6.1 // indirect
github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/google/uuid v1.6.0 // indirect github.com/google/uuid v1.6.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect github.com/kevinburke/ssh_config v1.4.0 // indirect
github.com/klauspost/compress v1.18.0 // indirect github.com/klauspost/compress v1.18.1 // indirect
github.com/lucasb-eyer/go-colorful v1.2.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 github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect github.com/mattn/go-localereader v0.0.1 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect github.com/mattn/go-runewidth v0.0.19 // indirect
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
github.com/miekg/pkcs11 v1.1.1 // indirect github.com/miekg/pkcs11 v1.1.1 // indirect
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/moby/go-archive v0.1.0 // indirect github.com/moby/go-archive v0.1.0 // indirect
github.com/moby/moby/api v1.52.0 // indirect
github.com/moby/moby/client v0.1.0 // indirect
github.com/moby/sys/atomicwriter v0.1.0 // indirect github.com/moby/sys/atomicwriter v0.1.0 // indirect
github.com/moby/sys/user v0.4.0 // indirect github.com/moby/sys/user v0.4.0 // indirect
github.com/moby/sys/userns v0.1.0 // indirect github.com/moby/sys/userns v0.1.0 // indirect
@ -95,42 +99,42 @@ require (
github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/runc v1.1.13 // indirect github.com/opencontainers/runc v1.1.13 // indirect
github.com/opencontainers/runtime-spec v1.1.0 // indirect github.com/opencontainers/runtime-spec v1.1.0 // indirect
github.com/pjbgf/sha1cd v0.4.0 // indirect github.com/pjbgf/sha1cd v0.5.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.65.0 // indirect github.com/prometheus/common v0.67.2 // indirect
github.com/prometheus/procfs v0.17.0 // indirect github.com/prometheus/procfs v0.19.2 // indirect
github.com/rivo/uniseg v0.4.7 // indirect github.com/rivo/uniseg v0.4.7 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect github.com/sirupsen/logrus v1.9.3 // indirect
github.com/skeema/knownhosts v1.3.1 // indirect github.com/skeema/knownhosts v1.3.2 // indirect
github.com/spf13/pflag v1.0.7 // indirect github.com/spf13/pflag v1.0.10 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect github.com/xeipuuv/gojsonschema v1.2.0 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // indirect
go.opentelemetry.io/otel v1.37.0 // indirect go.opentelemetry.io/otel v1.38.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.37.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 // indirect
go.opentelemetry.io/otel/metric v1.37.0 // indirect go.opentelemetry.io/otel/metric v1.38.0 // indirect
go.opentelemetry.io/otel/sdk v1.37.0 // indirect go.opentelemetry.io/otel/sdk v1.38.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.37.0 // indirect go.opentelemetry.io/otel/sdk/metric v1.38.0 // indirect
go.opentelemetry.io/otel/trace v1.37.0 // indirect go.opentelemetry.io/otel/trace v1.38.0 // indirect
go.opentelemetry.io/proto/otlp v1.7.1 // indirect go.opentelemetry.io/proto/otlp v1.9.0 // indirect
golang.org/x/crypto v0.41.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect
golang.org/x/exp v0.0.0-20250813145105-42675adae3e6 // indirect golang.org/x/crypto v0.43.0 // indirect
golang.org/x/net v0.43.0 // indirect golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect
golang.org/x/sync v0.16.0 // indirect golang.org/x/net v0.46.0 // indirect
golang.org/x/text v0.28.0 // indirect golang.org/x/text v0.30.0 // indirect
golang.org/x/time v0.12.0 // indirect golang.org/x/time v0.14.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250811230008-5f3141c8851a // indirect google.golang.org/genproto/googleapis/api v0.0.0-20251110190251-83f479183930 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20251110190251-83f479183930 // indirect
google.golang.org/grpc v1.74.2 // indirect google.golang.org/grpc v1.76.0 // indirect
google.golang.org/protobuf v1.36.7 // indirect google.golang.org/protobuf v1.36.10 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect
) )
@ -139,7 +143,7 @@ require (
github.com/containers/image v3.0.2+incompatible github.com/containers/image v3.0.2+incompatible
github.com/containers/storage v1.38.2 // indirect github.com/containers/storage v1.38.2 // indirect
github.com/decentral1se/passgen v1.0.1 github.com/decentral1se/passgen v1.0.1
github.com/docker/docker-credential-helpers v0.9.3 // indirect github.com/docker/docker-credential-helpers v0.9.4 // indirect
github.com/fvbommel/sortorder v1.1.0 // indirect github.com/fvbommel/sortorder v1.1.0 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/gorilla/mux v1.8.1 // indirect github.com/gorilla/mux v1.8.1 // indirect
@ -147,11 +151,11 @@ require (
github.com/moby/patternmatcher v0.6.0 // indirect github.com/moby/patternmatcher v0.6.0 // indirect
github.com/moby/sys/sequential v0.6.0 // indirect github.com/moby/sys/sequential v0.6.0 // indirect
github.com/opencontainers/image-spec v1.1.1 // indirect github.com/opencontainers/image-spec v1.1.1 // indirect
github.com/prometheus/client_golang v1.23.0 // indirect github.com/prometheus/client_golang v1.23.2 // indirect
github.com/sergi/go-diff v1.4.0 // indirect github.com/sergi/go-diff v1.4.0 // indirect
github.com/spf13/cobra v1.9.1 github.com/spf13/cobra v1.10.1
github.com/stretchr/testify v1.10.0 github.com/stretchr/testify v1.11.1
github.com/theupdateframework/notary v0.7.0 // indirect github.com/theupdateframework/notary v0.7.0 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
golang.org/x/sys v0.35.0 golang.org/x/sys v0.38.0
) )

235
go.sum
View File

@ -129,26 +129,35 @@ github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyY
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/charmbracelet/bubbletea v1.3.6 h1:VkHIxPJQeDt0aFJIsVxw8BQdh/F/L2KKZGsK6et5taU= github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw=
github.com/charmbracelet/bubbletea v1.3.6/go.mod h1:oQD9VCRQFF8KplacJLo28/jofOI2ToOfGYeFgBBxHOc= github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4=
github.com/charmbracelet/colorprofile v0.3.2 h1:9J27WdztfJQVAQKX2WOlSSRB+5gaKqqITmrvb1uTIiI= github.com/charmbracelet/colorprofile v0.3.2 h1:9J27WdztfJQVAQKX2WOlSSRB+5gaKqqITmrvb1uTIiI=
github.com/charmbracelet/colorprofile v0.3.2/go.mod h1:mTD5XzNeWHj8oqHb+S1bssQb7vIHbepiebQ2kPKVKbI= github.com/charmbracelet/colorprofile v0.3.2/go.mod h1:mTD5XzNeWHj8oqHb+S1bssQb7vIHbepiebQ2kPKVKbI=
github.com/charmbracelet/colorprofile v0.3.3 h1:DjJzJtLP6/NZ8p7Cgjno0CKGr7wwRJGxWUwh2IyhfAI=
github.com/charmbracelet/colorprofile v0.3.3/go.mod h1:nB1FugsAbzq284eJcjfah2nhdSLppN2NqvfotkfRYP4=
github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY= github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30= github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
github.com/charmbracelet/log v0.4.2 h1:hYt8Qj6a8yLnvR+h7MwsJv/XvmBJXiueUcI3cIxsyig= github.com/charmbracelet/log v0.4.2 h1:hYt8Qj6a8yLnvR+h7MwsJv/XvmBJXiueUcI3cIxsyig=
github.com/charmbracelet/log v0.4.2/go.mod h1:qifHGX/tc7eluv2R6pWIpyHDDrrb/AG71Pf2ysQu5nw= github.com/charmbracelet/log v0.4.2/go.mod h1:qifHGX/tc7eluv2R6pWIpyHDDrrb/AG71Pf2ysQu5nw=
github.com/charmbracelet/x/ansi v0.10.1 h1:rL3Koar5XvX0pHGfovN03f5cxLbCF2YvLeyz7D2jVDQ= github.com/charmbracelet/x/ansi v0.10.2 h1:ith2ArZS0CJG30cIUfID1LXN7ZFXRCww6RUvAPA+Pzw=
github.com/charmbracelet/x/ansi v0.10.1/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE= github.com/charmbracelet/x/ansi v0.10.2/go.mod h1:HbLdJjQH4UH4AqA2HpRWuWNluRE6zxJH/yteYEYCFa8=
github.com/charmbracelet/x/ansi v0.11.0 h1:uuIVK7GIplwX6UBIz8S2TF8nkr7xRlygSsBRjSJqIvA=
github.com/charmbracelet/x/ansi v0.11.0/go.mod h1:uQt8bOrq/xgXjlGcFMc8U2WYbnxyjrKhnvTQluvfCaE=
github.com/charmbracelet/x/cellbuf v0.0.13 h1:/KBBKHuVRbq1lYx5BzEHBAFBP8VcQzJejZ/IA3iR28k= github.com/charmbracelet/x/cellbuf v0.0.13 h1:/KBBKHuVRbq1lYx5BzEHBAFBP8VcQzJejZ/IA3iR28k=
github.com/charmbracelet/x/cellbuf v0.0.13/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs= github.com/charmbracelet/x/cellbuf v0.0.13/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a h1:G99klV19u0QnhiizODirwVksQB91TJKV/UaTnACcG30= github.com/charmbracelet/x/cellbuf v0.0.14 h1:iUEMryGyFTelKW3THW4+FfPgi4fkmKnnaLOXuc+/Kj4=
github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U= 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 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg= github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk=
github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI=
github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw= github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw=
github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M= github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M=
github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E= github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E=
@ -164,6 +173,14 @@ github.com/cilium/ebpf v0.4.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJ
github.com/cilium/ebpf v0.6.2/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= github.com/cilium/ebpf v0.6.2/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs=
github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA= github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/clipperhouse/displaywidth v0.5.0 h1:AIG5vQaSL2EKqzt0M9JMnvNxOCRTKUc4vUnLWGgP89I=
github.com/clipperhouse/displaywidth v0.5.0/go.mod h1:R+kHuzaYWFkTm7xoMmK1lFydbci4X2CicfbGstSGg0o=
github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs=
github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA=
github.com/clipperhouse/uax29/v2 v2.2.0 h1:ChwIKnQN3kcZteTXMgb1wztSgaU+ZemkgWdohwgs8tY=
github.com/clipperhouse/uax29/v2 v2.2.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
github.com/clipperhouse/uax29/v2 v2.3.0 h1:SNdx9DVUqMoBuBoW3iLOj4FQv3dN5mDtuqwuhIGpJy4=
github.com/clipperhouse/uax29/v2 v2.3.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g=
github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004/go.mod h1:yMWuSON2oQp+43nFtAV/uvKQIFpSPerB57DCt9t8sSA= github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004/go.mod h1:yMWuSON2oQp+43nFtAV/uvKQIFpSPerB57DCt9t8sSA=
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
@ -296,8 +313,10 @@ github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4=
github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s= github.com/cyphar/filepath-securejoin v0.5.0 h1:hIAhkRBMQ8nIeuVwcAoymp7MY4oherZdAxD+m0u9zaw=
github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= github.com/cyphar/filepath-securejoin v0.5.0/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
github.com/cyphar/filepath-securejoin v0.6.0 h1:BtGB77njd6SVO6VztOHfPxKitJvd/VPT+OFBFMOi1Is=
github.com/cyphar/filepath-securejoin v0.6.0/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc=
github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ= github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ=
github.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s= github.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s=
github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5/go.mod h1:Eo87+Kg/IX2hfWJfwxMzLyuSZyxSoAug2nGa1G2QAi8= github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5/go.mod h1:Eo87+Kg/IX2hfWJfwxMzLyuSZyxSoAug2nGa1G2QAi8=
@ -316,19 +335,25 @@ github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5Qvfr
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
github.com/docker/cli v0.0.0-20191017083524-a8ff7f821017/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/cli v0.0.0-20191017083524-a8ff7f821017/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/cli v28.3.3+incompatible h1:fp9ZHAr1WWPGdIWBM1b3zLtgCF+83gRdVMTJsUeiyAo= github.com/docker/cli v28.4.0+incompatible h1:RBcf3Kjw2pMtwui5V0DIMdyeab8glEw5QY0UUU4C9kY=
github.com/docker/cli v28.3.3+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/cli v28.4.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/cli v29.0.0+incompatible h1:KgsN2RUFMNM8wChxryicn4p46BdQWpXOA1XLGBGPGAw=
github.com/docker/cli v29.0.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/distribution v0.0.0-20190905152932-14b96e55d84c/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY= github.com/docker/distribution v0.0.0-20190905152932-14b96e55d84c/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY=
github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v1.4.2-0.20190924003213-a8608b5b67c7/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v1.4.2-0.20190924003213-a8608b5b67c7/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v28.3.3+incompatible h1:Dypm25kh4rmk49v1eiVbsAtpAsYURjYkaKubwuBdxEI= github.com/docker/docker v28.4.0+incompatible h1:KVC7bz5zJY/4AZe/78BIvCnPsLaC9T/zh72xnlrTTOk=
github.com/docker/docker v28.3.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v28.4.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v28.5.2+incompatible h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM=
github.com/docker/docker v28.5.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y= github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y=
github.com/docker/docker-credential-helpers v0.9.3 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqIjCBS4wrsOh9yRqcz8= github.com/docker/docker-credential-helpers v0.9.3 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqIjCBS4wrsOh9yRqcz8=
github.com/docker/docker-credential-helpers v0.9.3/go.mod h1:x+4Gbw9aGmChi3qTLZj8Dfn0TD20M/fuWy0E5+WDeCo= github.com/docker/docker-credential-helpers v0.9.3/go.mod h1:x+4Gbw9aGmChi3qTLZj8Dfn0TD20M/fuWy0E5+WDeCo=
github.com/docker/docker-credential-helpers v0.9.4 h1:76ItO69/AP/V4yT9V4uuuItG0B1N8hvt0T0c0NN/DzI=
github.com/docker/docker-credential-helpers v0.9.4/go.mod h1:v1S+hepowrQXITkEfw6o4+BMbGot02wiKpzWhGUZK6c=
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c h1:lzqkGL9b3znc+ZUgi7FlLnqjQhcXxkNM/quxIjBVMD0= github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c h1:lzqkGL9b3znc+ZUgi7FlLnqjQhcXxkNM/quxIjBVMD0=
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c/go.mod h1:CADgU4DSXK5QUlFslkQu2yW2TKzFZcXq/leZfM0UH5Q= github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c/go.mod h1:CADgU4DSXK5QUlFslkQu2yW2TKzFZcXq/leZfM0UH5Q=
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
@ -393,6 +418,8 @@ github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMj
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
github.com/go-git/go-git/v5 v5.16.2 h1:fT6ZIOjE5iEnkzKyxTHK1W4HGAsPhqEqiSAssSO77hM= github.com/go-git/go-git/v5 v5.16.2 h1:fT6ZIOjE5iEnkzKyxTHK1W4HGAsPhqEqiSAssSO77hM=
github.com/go-git/go-git/v5 v5.16.2/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8= github.com/go-git/go-git/v5 v5.16.2/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8=
github.com/go-git/go-git/v5 v5.16.3 h1:Z8BtvxZ09bYm/yYNgPKCzgWtaRqDTgIKRgIRHBfU6Z8=
github.com/go-git/go-git/v5 v5.16.3/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
@ -403,6 +430,8 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/go-logfmt/logfmt v0.6.1 h1:4hvbpePJKnIzH1B+8OR/JPbTx37NktoI9LE2QZBBkvE=
github.com/go-logfmt/logfmt v0.6.1/go.mod h1:EV2pOAQoZaT1ZXZbqDl5hrymndi4SY9ED9/z6CO0XAk=
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
@ -439,7 +468,6 @@ github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zV
github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
@ -528,9 +556,12 @@ github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63DSVPFklWpGX4OWAc9bFuaH4=
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4= github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4=
github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
@ -579,8 +610,8 @@ github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8/go.mod h1:vgyd7OREkbtVE
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kevinburke/ssh_config v1.4.0 h1:6xxtP5bZ2E4NF5tuQulISpTO2z8XbtH8cg1PWkxoFkQ=
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kevinburke/ssh_config v1.4.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
@ -590,6 +621,10 @@ github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdY
github.com/klauspost/compress v1.14.2/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.14.2/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/klauspost/compress v1.18.1 h1:bcSGx7UbpBqMChDtsF28Lw6v/G94LPrrbMbdC3JH2co=
github.com/klauspost/compress v1.18.1/go.mod h1:ZQFFVG+MdnR0P+l6wpXgIL4NTtwiKIdBnrBd8Nrxr+0=
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
@ -611,8 +646,8 @@ github.com/leonelquinteros/gotext v1.7.2 h1:bDPndU8nt+/kRo1m4l/1OXiiy2v7Z7dfPQ9+
github.com/leonelquinteros/gotext v1.7.2/go.mod h1:9/haCkm5P7Jay1sxKDGJ5WIg4zkz8oZKw4ekNpALob8= github.com/leonelquinteros/gotext v1.7.2/go.mod h1:9/haCkm5P7Jay1sxKDGJ5WIg4zkz8oZKw4ekNpALob8=
github.com/lib/pq v0.0.0-20150723085316-0dad96c0b94f/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v0.0.0-20150723085316-0dad96c0b94f/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/linuxkit/virtsock v0.0.0-20201010232012-f8cee7dfc7a3/go.mod h1:3r6x7q95whyfWQpmGZTu3gk3v2YkMi05HEzl7Tf7YEo= github.com/linuxkit/virtsock v0.0.0-20201010232012-f8cee7dfc7a3/go.mod h1:3r6x7q95whyfWQpmGZTu3gk3v2YkMi05HEzl7Tf7YEo=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/magiconair/properties v1.5.3/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.5.3/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
@ -631,8 +666,8 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
github.com/mattn/go-shellwords v1.0.6/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= github.com/mattn/go-shellwords v1.0.6/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
@ -659,6 +694,10 @@ github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6U
github.com/moby/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ= github.com/moby/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ=
github.com/moby/go-archive v0.1.0/go.mod h1:G9B+YoujNohJmrIYFBpSd54GTUB4lt9S+xVQvsJyFuo= github.com/moby/go-archive v0.1.0/go.mod h1:G9B+YoujNohJmrIYFBpSd54GTUB4lt9S+xVQvsJyFuo=
github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc=
github.com/moby/moby/api v1.52.0 h1:00BtlJY4MXkkt84WhUZPRqt5TvPbgig2FZvTbe3igYg=
github.com/moby/moby/api v1.52.0/go.mod h1:8mb+ReTlisw4pS6BRzCMts5M49W5M7bKt1cJy/YbAqc=
github.com/moby/moby/client v0.1.0 h1:nt+hn6O9cyJQqq5UWnFGqsZRTS/JirUqzPjEl0Bdc/8=
github.com/moby/moby/client v0.1.0/go.mod h1:O+/tw5d4a1Ha/ZA/tPxIZJapJRUS6LNZ1wiVRxYHyUE=
github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk=
github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw= github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw=
@ -758,8 +797,8 @@ github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFSt
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
github.com/pjbgf/sha1cd v0.4.0 h1:NXzbL1RvjTUi6kgYZCX3fPwwl27Q1LJndxtUDVfJGRY= github.com/pjbgf/sha1cd v0.5.0 h1:a+UkboSi1znleCDUNT3M5YxjOnN1fz2FhN48FlwCxs0=
github.com/pjbgf/sha1cd v0.4.0/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A= github.com/pjbgf/sha1cd v0.5.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@ -775,8 +814,8 @@ github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDf
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g=
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc= github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE= github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
@ -790,8 +829,10 @@ github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y8
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE= github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs=
github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
github.com/prometheus/common v0.67.2 h1:PcBAckGFTIHt2+L3I33uNRTlKTplNzFctXcWhPyAEN8=
github.com/prometheus/common v0.67.2/go.mod h1:63W3KZb1JOKgcjlIr64WW/LvFGAqKPj0atm+knVGEko=
github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
@ -805,8 +846,9 @@ github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0= github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0=
github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw=
github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws=
github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
@ -814,6 +856,8 @@ github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6L
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww=
github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
@ -839,6 +883,8 @@ github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8= github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8=
github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY= github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY=
github.com/skeema/knownhosts v1.3.2 h1:EDL9mgf4NzwMXCTfaxSD/o/a5fxDw/xL9nkU28JjdBg=
github.com/skeema/knownhosts v1.3.2/go.mod h1:bEg3iQAuw+jyiw+484wwFJoKSLwcfd7fqRy+N0QTiow=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
@ -851,8 +897,8 @@ github.com/spf13/cobra v0.0.1/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3
github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s=
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0=
github.com/spf13/jwalterweatherman v0.0.0-20141219030609-3d60171a6431/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v0.0.0-20141219030609-3d60171a6431/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
@ -861,9 +907,9 @@ github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bd
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v0.0.0-20150530192845-be5ff3e4840c/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM= github.com/spf13/viper v0.0.0-20150530192845-be5ff3e4840c/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM=
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8= github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8=
@ -878,8 +924,8 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 h1:kdXcSzyDtseVEc4yCz2qF8ZrQvIDBJLl4S1c3GCXmoI= github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 h1:kdXcSzyDtseVEc4yCz2qF8ZrQvIDBJLl4S1c3GCXmoI=
@ -937,37 +983,41 @@ go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 h1:Hf9xI/XLML9ElpiHVDNwvqI0hIFlzV8dgIr35kV1kRU= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0/go.mod h1:NfchwuyNoMcZ5MLHwPrODwUF1HWCXWrL31s8gSAdIKY= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg=
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.37.0 h1:zG8GlgXCJQd5BU98C0hZnBbElszTmUgCNCfYneaDL0A= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 h1:vl9obrcoWVKp/lwl8tRE33853I8Xru9HFbw/skNeLs8=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.37.0/go.mod h1:hOfBCz8kv/wuq73Mx2H2QnWokh/kHZxkh6SNF2bdKtw= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0/go.mod h1:GAXRxmLJcVM3u22IjTg74zWBrRCKq8BnOqUVLodpcpw=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 h1:Ahq7pZmv87yiyn3jeFz/LekZmPLLdKejuO3NcK9MssM= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0/go.mod h1:MJTqhM0im3mRLw1i8uGHnCvUEeS7VwRyxlLC78PA18M= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 h1:EtFWSnwW9hGObjkIdmlnWSydO+Qs8OwzfzXLUPg4xOc= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0/go.mod h1:QjUEoiGCPkvFZ/MjK6ZZfNOS6mfVEVKYE99dFhuN2LI= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU=
go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=
go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4= go.opentelemetry.io/proto/otlp v1.8.0 h1:fRAZQDcAFHySxpJ1TwlA1cJ4tvcrw7nXl9xWWC8N5CE=
go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE= go.opentelemetry.io/proto/otlp v1.8.0/go.mod h1:tIeYOeNBU4cvmPqpaji1P+KbB4Oloai8wN4rWzRrFF0=
go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A=
go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
@ -986,8 +1036,10 @@ golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9/go.mod h1:jdWPYTVW3xRLrWP
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@ -998,8 +1050,10 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/exp v0.0.0-20250813145105-42675adae3e6 h1:SbTAbRFnd5kjQXbczszQ0hdk3ctwYf3qBNH9jIsGclE= golang.org/x/exp v0.0.0-20250911091902-df9299821621 h1:2id6c1/gto0kaHYyrixvknJ8tUK/Qs5IsmBtrc+FtgU=
golang.org/x/exp v0.0.0-20250813145105-42675adae3e6/go.mod h1:4QTo5u+SEIbbKW1RacMZq1YEfOBqeXa19JeshGi+zc4= golang.org/x/exp v0.0.0-20250911091902-df9299821621/go.mod h1:TwQYMMnGpvZyc+JpB/UAuTNIsVJifOlSkrZkhcvpVUk=
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2wtOnGAHZWCHUM4KGzY=
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@ -1063,8 +1117,10 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b
golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I=
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -1082,8 +1138,6 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -1162,13 +1216,17 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4= golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ=
golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw= golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA=
golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q=
golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -1178,16 +1236,20 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= golang.org/x/time v0.13.0 h1:eUlYslOIt32DgYD6utsuUeHs4d7AsEYLuIAdg7FlYgI=
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/time v0.13.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@ -1237,6 +1299,8 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/api v0.0.0-20160322025152-9bf6e6e569ff/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.0.0-20160322025152-9bf6e6e569ff/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
@ -1281,10 +1345,14 @@ google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfG
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200527145253-8367513e4ece/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= google.golang.org/genproto v0.0.0-20200527145253-8367513e4ece/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto/googleapis/api v0.0.0-20250811230008-5f3141c8851a h1:DMCgtIAIQGZqJXMVzJF4MV8BlWoJh2ZuFiRdAleyr58= google.golang.org/genproto/googleapis/api v0.0.0-20250929231259-57b25ae835d4 h1:8XJ4pajGwOlasW+L13MnEGA8W4115jJySQtVfS2/IBU=
google.golang.org/genproto/googleapis/api v0.0.0-20250811230008-5f3141c8851a/go.mod h1:y2yVLIE/CSMCPXaHnSKXxu1spLPnglFLegmgdY23uuE= google.golang.org/genproto/googleapis/api v0.0.0-20250929231259-57b25ae835d4/go.mod h1:NnuHhy+bxcg30o7FnVAZbXsPHUDQ9qKWAQKCD7VxFtk=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a h1:tPE/Kp+x9dMSwUm/uM0JKK0IfdiJkwAbSMSeZBXXJXc= google.golang.org/genproto/googleapis/api v0.0.0-20251110190251-83f479183930 h1:8BWFtrvJRbplrKV5VHlIm4YM726eeBPPAL2QDNWhRrU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a/go.mod h1:gw1tLEfykwDz2ET4a12jcXt4couGAm7IwsVaTy0Sflo= 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 v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.0.5/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.0.5/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
@ -1304,8 +1372,10 @@ google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTp
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4= google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI=
google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM= google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A=
google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@ -1319,8 +1389,10 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A= google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/cenkalti/backoff.v2 v2.2.1/go.mod h1:S0QdOvT2AlerfSBkp0O+dk+bbIMaNbEmVk876gPCthU= gopkg.in/cenkalti/backoff.v2 v2.2.1/go.mod h1:S0QdOvT2AlerfSBkp0O+dk+bbIMaNbEmVk876gPCthU=
@ -1358,6 +1430,7 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8=

View File

@ -471,13 +471,6 @@ func GetAppStatuses(apps []App, MachineReadable bool) (map[string]map[string]str
result["chaosVersion"] = chaosVersion result["chaosVersion"] = chaosVersion
} }
labelKey = fmt.Sprintf("coop-cloud.%s.autoupdate", name)
if autoUpdate, ok := service.Spec.Labels[labelKey]; ok {
result["autoUpdate"] = autoUpdate
} else {
result["autoUpdate"] = "false"
}
labelKey = fmt.Sprintf("coop-cloud.%s.version", name) labelKey = fmt.Sprintf("coop-cloud.%s.version", name)
if version, ok := service.Spec.Labels[labelKey]; ok { if version, ok := service.Spec.Labels[labelKey]; ok {
result["version"] = version result["version"] = version
@ -509,7 +502,11 @@ func GetAppComposeConfig(recipe string, opts stack.Deploy, appEnv envfile.AppEnv
} }
// ExposeAllEnv exposes all env variables to the app container // ExposeAllEnv exposes all env variables to the app container
func ExposeAllEnv(stackName string, compose *composetypes.Config, appEnv envfile.AppEnv) { func ExposeAllEnv(
stackName string,
compose *composetypes.Config,
appEnv envfile.AppEnv,
toDeployVersion string) {
for _, service := range compose.Services { for _, service := range compose.Services {
if service.Name == "app" { if service.Name == "app" {
log.Debug(i18n.G("adding env vars to %s service config", stackName)) log.Debug(i18n.G("adding env vars to %s service config", stackName))
@ -517,6 +514,11 @@ func ExposeAllEnv(stackName string, compose *composetypes.Config, appEnv envfile
_, exists := service.Environment[k] _, exists := service.Environment[k]
if !exists { if !exists {
value := v value := v
if k == "TYPE" || k == "RECIPE" {
// NOTE(d1): don't use the wrong version from the app env
// since we are deploying a new version
value = toDeployVersion
}
service.Environment[k] = &value service.Environment[k] = &value
log.Debug(i18n.G("%s: %s: %s", stackName, k, value)) log.Debug(i18n.G("%s: %s: %s", stackName, k, value))
} }
@ -631,6 +633,11 @@ func (a App) WipeRecipeVersion() error {
// WriteRecipeVersion writes the recipe version to the app .env file. // WriteRecipeVersion writes the recipe version to the app .env file.
func (a App) WriteRecipeVersion(version string, dryRun bool) error { 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) file, err := os.Open(a.Path)
if err != nil { if err != nil {
return err return err

View File

@ -224,3 +224,16 @@ func TestWriteRecipeVersionOverwrite(t *testing.T) {
assert.Equal(t, "foo", app.Recipe.EnvVersion) 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)
}

View File

@ -5,7 +5,6 @@ import (
"fmt" "fmt"
"strconv" "strconv"
"coopcloud.tech/abra/pkg/envfile"
"coopcloud.tech/abra/pkg/i18n" "coopcloud.tech/abra/pkg/i18n"
"coopcloud.tech/abra/pkg/log" "coopcloud.tech/abra/pkg/log"
composetypes "github.com/docker/cli/cli/compose/types" composetypes "github.com/docker/cli/cli/compose/types"
@ -56,23 +55,6 @@ func SetVersionLabel(compose *composetypes.Config, stackName string, version str
} }
} }
// SetUpdateLabel adds env ENABLE_AUTO_UPDATE as label to enable/disable the
// auto update process for this app. The default if this variable is not set is to disable
// the auto update process.
func SetUpdateLabel(compose *composetypes.Config, stackName string, appEnv envfile.AppEnv) {
for _, service := range compose.Services {
if service.Name == "app" {
enable_auto_update, exists := appEnv["ENABLE_AUTO_UPDATE"]
if !exists {
enable_auto_update = "false"
}
log.Debug(i18n.G("set label 'coop-cloud.%s.autoupdate' to %s for %s", stackName, enable_auto_update, stackName))
labelKey := fmt.Sprintf("coop-cloud.%s.autoupdate", stackName)
service.Deploy.Labels[labelKey] = enable_auto_update
}
}
}
// GetLabel reads docker labels in the format of "coop-cloud.${STACK_NAME}.${LABEL}" from the local compose files // GetLabel reads docker labels in the format of "coop-cloud.${STACK_NAME}.${LABEL}" from the local compose files
func GetLabel(compose *composetypes.Config, stackName string, label string) string { func GetLabel(compose *composetypes.Config, stackName string, label string) string {
for _, service := range compose.Services { for _, service := range compose.Services {

View File

@ -6,9 +6,11 @@ import (
"errors" "errors"
"net/http" "net/http"
"os" "os"
"path"
"strings" "strings"
"time" "time"
"coopcloud.tech/abra/pkg/config"
contextPkg "coopcloud.tech/abra/pkg/context" contextPkg "coopcloud.tech/abra/pkg/context"
"coopcloud.tech/abra/pkg/i18n" "coopcloud.tech/abra/pkg/i18n"
"coopcloud.tech/abra/pkg/log" "coopcloud.tech/abra/pkg/log"
@ -35,13 +37,27 @@ func WithTimeout(timeout int) Opt {
// New initiates a new Docker client. New client connections are validated so // 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 // 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. // account that you may only want the local client and not communicate via SSH.
// For this use-case, please pass "default" as the contextName. // For this use-case, please pass "default" as the serverName.
func New(serverName string, opts ...Opt) (*client.Client, error) { func New(serverName string, opts ...Opt) (*client.Client, error) {
var clientOpts []client.Opt var clientOpts []client.Opt
ctx, err := GetContext(serverName) ctx, err := GetContext(serverName)
if err != nil { if err != nil {
return nil, errors.New(i18n.G("unknown server, run \"abra server add %s\"?", serverName)) 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 {
return nil, errors.New(i18n.G("server missing context, run \"abra server add %s\"?", serverName))
}
} }
ctxEndpoint, err := contextPkg.GetContextEndpoint(ctx) ctxEndpoint, err := contextPkg.GetContextEndpoint(ctx)

View File

@ -116,10 +116,7 @@ var (
DIRTY_DEFAULT = "+U" DIRTY_DEFAULT = "+U"
NO_DOMAIN_DEFAULT = "N/A" MISSING_DEFAULT = "-"
NO_VERSION_DEFAULT = "N/A"
NO_SECRETS_DEFAULT = "N/A"
NO_VOLUMES_DEFAULT = "N/A"
UNKNOWN_DEFAULT = "unknown" UNKNOWN_DEFAULT = "unknown"
) )

View File

@ -20,6 +20,7 @@ var (
Locale = DefaultLocale Locale = DefaultLocale
_, Mo = LoadLocale() _, Mo = LoadLocale()
G = Mo.Get G = Mo.Get
GC = Mo.GetC
) )
func LoadLocale() (string, *gotext.Mo) { func LoadLocale() (string, *gotext.Mo) {

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -49,7 +49,7 @@ func (r Recipe) Ensure(ctx EnsureContext) error {
if r.EnvVersion != "" && !ctx.IgnoreEnvVersion { if r.EnvVersion != "" && !ctx.IgnoreEnvVersion {
log.Debug(i18n.G("ensuring env version %s", r.EnvVersion)) log.Debug(i18n.G("ensuring env version %s", r.EnvVersion))
if strings.Contains(r.EnvVersion, "+U") { if strings.Contains(r.EnvVersion, "+U") {
return errors.New(i18n.G("can not redeploy chaos version (%s) without --chaos", r.EnvVersion)) return errors.New(i18n.G(`cannot redeploy previous chaos version (%s), did you mean to use "--chaos"?`))
} }
if _, err := r.EnsureVersion(r.EnvVersion); err != nil { if _, err := r.EnsureVersion(r.EnvVersion); err != nil {

View File

@ -379,7 +379,7 @@ func ReadRecipeCatalogue(offline bool) (RecipeCatalogue, error) {
if !offline { if !offline {
if err := catalogue.EnsureUpToDate(); err != nil { if err := catalogue.EnsureUpToDate(); err != nil {
return nil, err return nil, fmt.Errorf("unable to update catalogue: %s", err)
} }
} }

View File

@ -50,6 +50,11 @@ type Secret struct {
// Will have this remote name: // Will have this remote name:
// test_example_com_test_pass_two_v2 // test_example_com_test_pass_two_v2
RemoteName string RemoteName string
// LocalName iis the name of the secret in the recipe config. This is also
// the name that you pass to `abra app secret insert` and is shown on `abra
// app secret list`
LocalName string
} }
// GeneratePassword generates passwords. // GeneratePassword generates passwords.
@ -133,7 +138,12 @@ func ReadSecretsConfig(appEnvPath string, composeFiles []string, stackName strin
lastIdx := strings.LastIndex(secretConfig.Name, "_") lastIdx := strings.LastIndex(secretConfig.Name, "_")
secretVersion := secretConfig.Name[lastIdx+1:] secretVersion := secretConfig.Name[lastIdx+1:]
value := Secret{Version: secretVersion, RemoteName: secretConfig.Name}
value := Secret{
Version: secretVersion,
RemoteName: secretConfig.Name,
LocalName: secretId,
}
if len(value.RemoteName) > config.MAX_DOCKER_SECRET_LENGTH { if len(value.RemoteName) > config.MAX_DOCKER_SECRET_LENGTH {
return nil, errors.New(i18n.G("secret %s is > %d chars when combined with %s", secretId, config.MAX_DOCKER_SECRET_LENGTH, stackName)) return nil, errors.New(i18n.G("secret %s is > %d chars when combined with %s", secretId, config.MAX_DOCKER_SECRET_LENGTH, stackName))
@ -178,6 +188,8 @@ func ReadSecretsConfig(appEnvPath string, composeFiles []string, stackName strin
// resolveCharset sets the passgen Alphabet required for a secret // resolveCharset sets the passgen Alphabet required for a secret
func resolveCharset(input string) string { func resolveCharset(input string) string {
switch strings.ToLower(input) { switch strings.ToLower(input) {
case "hex":
return passgen.AlphabetNumericAmbiguous + "abcdef"
case "special": case "special":
return passgen.AlphabetSpecial return passgen.AlphabetSpecial
case "safespecial": case "safespecial":

View File

@ -48,6 +48,12 @@ func TestReadSecretsConfig(t *testing.T) {
assert.Equal(t, "v1", secretsFromConfig["test_pass_six"].Version) assert.Equal(t, "v1", secretsFromConfig["test_pass_six"].Version)
assert.Equal(t, 0, secretsFromConfig["test_pass_six"].Length) assert.Equal(t, 0, secretsFromConfig["test_pass_six"].Length)
assert.Equal(t, "abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789!@#$%^&*_-+=", secretsFromConfig["test_pass_six"].Charset) assert.Equal(t, "abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789!@#$%^&*_-+=", secretsFromConfig["test_pass_six"].Charset)
// Has a length modifier and a charset=hex modifier
assert.Equal(t, "test_example_com_test_pass_seven_v1", secretsFromConfig["test_pass_seven"].RemoteName)
assert.Equal(t, "v1", secretsFromConfig["test_pass_seven"].Version)
assert.Equal(t, 32, secretsFromConfig["test_pass_seven"].Length)
assert.Equal(t, "0123456789abcdef", secretsFromConfig["test_pass_seven"].Charset)
} }
func TestReadSecretsConfigWithLongDomain(t *testing.T) { func TestReadSecretsConfigWithLongDomain(t *testing.T) {

View File

@ -4,3 +4,4 @@ SECRET_TEST_PASS_THREE_VERSION=v2
SECRET_TEST_PASS_FOUR_VERSION=v1 # length=12 charset=default,safespecial SECRET_TEST_PASS_FOUR_VERSION=v1 # length=12 charset=default,safespecial
SECRET_TEST_PASS_FIVE_VERSION=v1 # length=12 charset=default,special SECRET_TEST_PASS_FIVE_VERSION=v1 # length=12 charset=default,special
SECRET_TEST_PASS_SIX_VERSION=v1 # charset=default,special SECRET_TEST_PASS_SIX_VERSION=v1 # charset=default,special
SECRET_TEST_PASS_SEVEN_VERSION=v1 # length=32 charset=hex

View File

@ -11,6 +11,7 @@ services:
- test_pass_four - test_pass_four
- test_pass_five - test_pass_five
- test_pass_six - test_pass_six
- test_pass_seven
secrets: secrets:
test_pass_one: test_pass_one:
@ -31,3 +32,6 @@ secrets:
test_pass_six: test_pass_six:
external: true external: true
name: ${STACK_NAME}_test_pass_six_${SECRET_TEST_PASS_SIX_VERSION} name: ${STACK_NAME}_test_pass_six_${SECRET_TEST_PASS_SIX_VERSION}
test_pass_seven:
external: true
name: ${STACK_NAME}_test_pass_seven_${SECRET_TEST_PASS_SEVEN_VERSION}

View File

@ -247,7 +247,7 @@ func waitOnTasks(ctx context.Context, client apiclient.APIClient, namespace stri
} }
} }
if terminalStatesReached == len(tasks) { if terminalStatesReached >= len(tasks) {
log.Debug(i18n.G("all tasks reached terminal state")) log.Debug(i18n.G("all tasks reached terminal state"))
break break
} }

View File

@ -19,7 +19,7 @@ import (
"coopcloud.tech/abra/pkg/ui" "coopcloud.tech/abra/pkg/ui"
"coopcloud.tech/abra/pkg/upstream/convert" "coopcloud.tech/abra/pkg/upstream/convert"
"github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/stack/formatter" "github.com/docker/cli/cli/command/formatter"
composetypes "github.com/docker/cli/cli/compose/types" composetypes "github.com/docker/cli/cli/compose/types"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/container"
@ -201,6 +201,7 @@ func RunDeploy(
appName string, appName string,
serverName string, serverName string,
dontWait bool, dontWait bool,
noInput bool,
filters filters.Args, filters filters.Args,
) error { ) error {
log.Info(i18n.G("initialising deployment")) log.Info(i18n.G("initialising deployment"))
@ -226,6 +227,7 @@ func RunDeploy(
appName, appName,
serverName, serverName,
dontWait, dontWait,
noInput,
filters, filters,
) )
} }
@ -248,6 +250,7 @@ func deployCompose(
appName string, appName string,
serverName string, serverName string,
dontWait bool, dontWait bool,
noInput bool,
filters filters.Args, filters filters.Args,
) error { ) error {
namespace := convert.NewNamespace(opts.Namespace) namespace := convert.NewNamespace(opts.Namespace)
@ -311,6 +314,7 @@ func deployCompose(
Services: serviceIDs, Services: serviceIDs,
AppName: appName, AppName: appName,
ServerName: serverName, ServerName: serverName,
NoInput: noInput,
Filters: filters, Filters: filters,
} }
@ -561,6 +565,7 @@ func timestamp() string {
type WaitOpts struct { type WaitOpts struct {
AppName string AppName string
Filters filters.Args Filters filters.Args
NoInput bool
NoLog bool NoLog bool
Quiet bool Quiet bool
ServerName string ServerName string
@ -570,7 +575,13 @@ type WaitOpts struct {
func WaitOnServices(ctx context.Context, cl *dockerClient.Client, opts WaitOpts) error { func WaitOnServices(ctx context.Context, cl *dockerClient.Client, opts WaitOpts) error {
timeout := time.Duration(WaitTimeout) * time.Second timeout := time.Duration(WaitTimeout) * time.Second
model := ui.DeployInitialModel(ctx, cl, opts.Services, opts.AppName, timeout, opts.Filters) model := ui.DeployInitialModel(ctx, cl, opts.Services, opts.AppName, timeout, opts.Filters)
tui := tea.NewProgram(model)
var tui *tea.Program
if opts.NoInput {
tui = tea.NewProgram(model, tea.WithoutRenderer(), tea.WithInput(nil))
} else {
tui = tea.NewProgram(model)
}
if !opts.Quiet { if !opts.Quiet {
log.Info(i18n.G("polling deployment status")) log.Info(i18n.G("polling deployment status"))

View File

@ -0,0 +1,5 @@
# cloud-init
This folder contains cloud-init files for installing Abra and its dependencies.
For more information, see <https://cloudinit.readthedocs.io/en/latest/index.html>

View File

@ -0,0 +1,49 @@
#cloud-config
package_update: true
package_upgrade: true
package_reboot_if_required: true
# https://packages.debian.org/bookworm/docker.io
packages:
- ca-certificates
- curl
- docker.io
- docker-compose
# https://stackoverflow.com/a/74084180
- apparmor
# https://docs.coopcloud.tech/operators/tutorial/#server-setup
runcmd:
- curl -fsSL https://install.abra.coopcloud.tech | env HOME=/root bash
- docker swarm init
- docker network create -d overlay proxy
write_files:
# Add abra to PATH and set EDITOR
- path: /etc/profile.d/custom_path.sh
content: |
export PATH=$PATH:$HOME/.local/bin
export EDITOR=vim
owner: root:root
permissions: '0755'
# Send container log to journald: https://docs.coopcloud.tech/operators/handbook/#how-do-i-persist-container-logs-after-they-go-away
- path: /etc/docker/daemon.json
content: |
{
"log-driver": "journald",
"log-opts": {
"labels":"com.docker.swarm.service.name"
}
}
owner: root:root
permissions: '0644'
# Rotate logs
- path: /etc/systemd/journald.conf
content: |
[Journal]
Storage=persistent
SystemMaxUse=5G
MaxFileSec=1month
owner: root:root
permissions: '0644'

View File

@ -1,8 +1,8 @@
#!/usr/bin/env bash #!/usr/bin/env bash
ABRA_VERSION="0.11.0-beta" ABRA_VERSION="0.12.0-beta"
ABRA_RELEASE_URL="https://git.coopcloud.tech/api/v1/repos/toolshed/abra/releases/tags/$ABRA_VERSION" ABRA_RELEASE_URL="https://git.coopcloud.tech/api/v1/repos/toolshed/abra/releases/tags/$ABRA_VERSION"
RC_VERSION="0.11.0-beta" RC_VERSION="0.12.0-beta"
RC_VERSION_URL="https://git.coopcloud.tech/api/v1/repos/toolshed/abra/releases/tags/$RC_VERSION" RC_VERSION_URL="https://git.coopcloud.tech/api/v1/repos/toolshed/abra/releases/tags/$RC_VERSION"
for arg in "$@"; do for arg in "$@"; do
@ -14,15 +14,15 @@ done
function show_banner { function show_banner {
echo "" echo ""
echo " ____ ____ _ _ " 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 ""
echo "" echo ""
} }

View File

@ -51,7 +51,7 @@ echo "========================================================================"
echo "BUILDING ABRA" echo "BUILDING ABRA"
echo "========================================================================" echo "========================================================================"
export PATH="/usr/lib/go-1.21/bin:$PATH" export PATH="/usr/lib/go-1.21/bin:$PATH"
make build-abra make build
echo "========================================================================" echo "========================================================================"
echo "========================================================================" echo "========================================================================"

View File

@ -175,7 +175,7 @@ teardown(){
} }
# bats test_tags=slow # bats test_tags=slow
@test "bail if env has a hash but no --chaos" { @test "do not bail if env version is a hash but no --chaos" {
wantHash=$(_get_n_hash 3) wantHash=$(_get_n_hash 3)
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" reset --hard HEAD~3 run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" reset --hard HEAD~3
@ -250,6 +250,7 @@ teardown(){
run $ABRA app deploy "$TEST_APP_DOMAIN" \ run $ABRA app deploy "$TEST_APP_DOMAIN" \
--no-input --no-converge-checks --chaos --no-input --no-converge-checks --chaos
assert_success assert_success
assert_output --regexp "NEW DEPLOYMENT.*${_get_head_hash:0:8}"
} }
# bats test_tags=slow # bats test_tags=slow
@ -367,6 +368,21 @@ teardown(){
run $ABRA app deploy "$TEST_APP_DOMAIN" --no-input --no-converge-checks run $ABRA app deploy "$TEST_APP_DOMAIN" --no-input --no-converge-checks
assert_failure assert_failure
assert_output --partial "secret not generated"
}
@test "error if secret not inserted" {
run sed -i 's/COMPOSE_FILE="compose.yml"/COMPOSE_FILE="compose.yml:compose.skip_pass.yml"/g' \
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
assert_success
run sed -i 's/#SECRET_TEST_SKIP_PASS_VERSION=v1/SECRET_TEST_SKIP_PASS_VERSION=v1/g' \
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
assert_success
run $ABRA app deploy "$TEST_APP_DOMAIN" --no-input --no-converge-checks
assert_failure
assert_output --partial "secret not inserted"
} }
# bats test_tags=slow # bats test_tags=slow
@ -527,7 +543,7 @@ teardown(){
# bats test_tags=slow # bats test_tags=slow
@test "ignore timeout when not present in env" { @test "ignore timeout when not present in env" {
run $ABRA app deploy "$TEST_APP_DOMAIN" --no-input --no-converge-checks --debug run $ABRA app deploy "$TEST_APP_DOMAIN" --no-input --no-converge-checks
assert_success assert_success
refute_output --partial "timeout: set to" refute_output --partial "timeout: set to"
} }
@ -538,6 +554,7 @@ teardown(){
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env" "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
assert_success assert_success
# NOTE(d1}: --debug required
run $ABRA app deploy "$TEST_APP_DOMAIN" --no-input --no-converge-checks --debug run $ABRA app deploy "$TEST_APP_DOMAIN" --no-input --no-converge-checks --debug
assert_success assert_success
assert_output --partial "timeout: set to 120" assert_output --partial "timeout: set to 120"
@ -561,3 +578,27 @@ teardown(){
assert_success assert_success
refute_output --partial "IMAGES" refute_output --partial "IMAGES"
} }
# bats test_tags=slow
@test "re-deploy updates existing env vars" {
run $ABRA app deploy "$TEST_APP_DOMAIN" --no-input
assert_success
run docker inspect --format='{{range .Config.Env}}{{println .}}{{end}}' \
$(docker ps -f name="$TEST_APP_DOMAIN_$TEST_SERVER" -q)
assert_success
assert_output --partial "WITH_COMMENT=foo"
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"
}

View File

@ -127,3 +127,14 @@ teardown(){
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env" "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
assert_success assert_success
} }
# bats test_tags=slow
@test "new env version written to container env" {
run $ABRA app deploy "$TEST_APP_DOMAIN" "0.1.0+1.20.0" --no-input
assert_success
run docker inspect --format='{{range .Config.Env}}{{println .}}{{end}}' \
$(docker ps -f name="$TEST_APP_DOMAIN_$TEST_SERVER" -q)
assert_success
assert_output --partial "$TEST_RECIIPE:0.1.0+1.20.0"
}

View File

@ -38,8 +38,8 @@ teardown(){
assert_success assert_success
assert_output --partial 'NEW DEPLOY OVERVIEW' assert_output --partial 'NEW DEPLOY OVERVIEW'
assert_output --partial 'CURRENT DEPLOYMENT N/A' assert_output --partial 'CURRENT DEPLOYMENT -'
assert_output --partial 'ENV VERSION N/A' assert_output --partial 'ENV VERSION -'
assert_output --partial "NEW DEPLOYMENT ${latestRelease}" assert_output --partial "NEW DEPLOYMENT ${latestRelease}"
assert_output --partial "IMAGES nginx: ${latestRelease##*+} (new)" assert_output --partial "IMAGES nginx: ${latestRelease##*+} (new)"
assert_output --partial "CONFIGS test_conf: v1 (new)" assert_output --partial "CONFIGS test_conf: v1 (new)"
@ -57,7 +57,7 @@ teardown(){
assert_success assert_success
assert_output --partial 'NEW DEPLOY OVERVIEW' assert_output --partial 'NEW DEPLOY OVERVIEW'
assert_output --partial "CURRENT DEPLOYMENT N/A" assert_output --partial "CURRENT DEPLOYMENT -"
assert_output --partial "ENV VERSION ${latestRelease}" assert_output --partial "ENV VERSION ${latestRelease}"
assert_output --partial "NEW DEPLOYMENT ${latestRelease}" assert_output --partial "NEW DEPLOYMENT ${latestRelease}"
assert_output --partial "IMAGES nginx: ${latestRelease##*+} (new)" assert_output --partial "IMAGES nginx: ${latestRelease##*+} (new)"
@ -68,6 +68,13 @@ teardown(){
assert_success 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 # bats test_tags=slow
@test "show changed config version on re-deploy" { @test "show changed config version on re-deploy" {
run $ABRA app deploy "$TEST_APP_DOMAIN" \ run $ABRA app deploy "$TEST_APP_DOMAIN" \
@ -102,7 +109,7 @@ teardown(){
assert_success assert_success
assert_output --partial 'NEW DEPLOY OVERVIEW' assert_output --partial 'NEW DEPLOY OVERVIEW'
assert_output --partial "CURRENT DEPLOYMENT N/A" assert_output --partial "CURRENT DEPLOYMENT -"
assert_output --partial "ENV VERSION 0.1.1+1.20.2" assert_output --partial "ENV VERSION 0.1.1+1.20.2"
assert_output --partial "NEW DEPLOYMENT 0.1.1+1.20.2" assert_output --partial "NEW DEPLOYMENT 0.1.1+1.20.2"
@ -125,7 +132,7 @@ teardown(){
assert_success assert_success
assert_output --partial 'NEW DEPLOY OVERVIEW' assert_output --partial 'NEW DEPLOY OVERVIEW'
assert_output --partial "CURRENT DEPLOYMENT N/A" assert_output --partial "CURRENT DEPLOYMENT -"
assert_output --partial "ENV VERSION 0.1.1+1.20.2" assert_output --partial "ENV VERSION 0.1.1+1.20.2"
assert_output --partial "NEW DEPLOYMENT ${latestRelease}" assert_output --partial "NEW DEPLOYMENT ${latestRelease}"
@ -163,7 +170,7 @@ teardown(){
assert_success assert_success
} }
@test "can not redeploy chaos version without --chaos" { @test "cannot redeploy chaos version without --chaos" {
headHash=$(_get_head_hash) headHash=$(_get_head_hash)
latestRelease=$(_latest_release) latestRelease=$(_latest_release)
@ -181,7 +188,7 @@ teardown(){
run $ABRA app deploy "$TEST_APP_DOMAIN" \ run $ABRA app deploy "$TEST_APP_DOMAIN" \
--no-input --no-converge-checks --force --debug --no-input --no-converge-checks --force --debug
assert_failure assert_failure
assert_output --regexp 'can not redeploy chaos version .*' + "${headHash:0:8}+U" assert_output --regexp 'cannot redeploy previous chaos version .*' + "${headHash:0:8}+U"
} }
@test "deploy then force commit deploy" { @test "deploy then force commit deploy" {
@ -219,7 +226,7 @@ teardown(){
assert_success assert_success
assert_output --partial 'NEW DEPLOY OVERVIEW' assert_output --partial 'NEW DEPLOY OVERVIEW'
assert_output --partial "CURRENT DEPLOYMENT N/A" assert_output --partial "CURRENT DEPLOYMENT -"
assert_output --partial "ENV VERSION ${latestRelease}" assert_output --partial "ENV VERSION ${latestRelease}"
assert_output --partial "NEW DEPLOYMENT ${headHash:0:8}" assert_output --partial "NEW DEPLOYMENT ${headHash:0:8}"

View File

@ -28,17 +28,17 @@ teardown(){
} }
@test "validate app argument" { @test "validate app argument" {
run $ABRA app env run $ABRA app env list
assert_failure assert_failure
run $ABRA app env DOESNTEXIST run $ABRA app env list DOESNTEXIST
assert_failure assert_failure
} }
@test "show env version" { @test "show env version" {
latestRelease=$(_latest_release) latestRelease=$(_latest_release)
run $ABRA app env "$TEST_APP_DOMAIN" run $ABRA app env list "$TEST_APP_DOMAIN"
assert_success assert_success
assert_output --partial "$latestRelease" assert_output --partial "$latestRelease"
} }
@ -48,7 +48,7 @@ teardown(){
assert_success assert_success
assert_exists "$ABRA_DIR/recipes/$TEST_RECIPE/foo" assert_exists "$ABRA_DIR/recipes/$TEST_RECIPE/foo"
run $ABRA app env "$TEST_APP_DOMAIN" run $ABRA app env list "$TEST_APP_DOMAIN"
assert_success assert_success
assert_exists "$ABRA_DIR/recipes/$TEST_RECIPE/foo" assert_exists "$ABRA_DIR/recipes/$TEST_RECIPE/foo"
@ -57,3 +57,44 @@ teardown(){
run rm -rf "$ABRA_DIR/recipes/$TEST_RECIPE/foo" run rm -rf "$ABRA_DIR/recipes/$TEST_RECIPE/foo"
assert_not_exists "$ABRA_DIR/recipes/$TEST_RECIPE/foo" assert_not_exists "$ABRA_DIR/recipes/$TEST_RECIPE/foo"
} }
@test "app env pull explodes when no deployed app" {
run $ABRA app env pull "$TEST_APP_DOMAIN" -s "$TEST_SERVER"
assert_failure
}
# bats test_tags=slow
@test "app env pull recreates app env when missing" {
run $ABRA app deploy "$TEST_APP_DOMAIN" --no-input
assert_success
run rm -rf "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
assert_success
assert_not_exists "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
run $ABRA app env pull "$TEST_APP_DOMAIN" -s "$TEST_SERVER"
assert_success
assert_exists "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
}
# bats test_tags=slow
@test "app env pull recreates correct version" {
run $ABRA app deploy "$TEST_APP_DOMAIN" "0.1.0+1.20.0" --no-input
assert_success
run grep -q "TYPE=$TEST_RECIPE:0.1.0+1.20.0" \
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
assert_success
run rm -rf "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
assert_success
assert_not_exists "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
run $ABRA app env pull "$TEST_APP_DOMAIN" -s "$TEST_SERVER"
assert_success
assert_exists "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
run grep -q "TYPE=$TEST_RECIPE:0.1.0+1.20.0" \
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
assert_success
}

View File

@ -15,6 +15,7 @@ teardown_file(){
_undeploy_app _undeploy_app
_rm_app _rm_app
_rm_server _rm_server
_reset_recipe
if [[ -d "$ABRA_DIR/servers/foo" ]]; then if [[ -d "$ABRA_DIR/servers/foo" ]]; then
run rm -rf "$ABRA_DIR/servers/foo" run rm -rf "$ABRA_DIR/servers/foo"
@ -159,23 +160,6 @@ teardown(){
assert_not_exists "$ABRA_DIR/servers/foo.com" 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 "unknown server"
}
# bats test_tags=slow # bats test_tags=slow
@test "list does not fail if missing .env" { @test "list does not fail if missing .env" {
_deploy_app _deploy_app
@ -193,3 +177,16 @@ teardown(){
<(jq -S "." <(echo '{}')) <(jq -S "." <(echo '{}'))
assert_success assert_success
} }
# bats test_tags=slow
@test "list ignores borked tags" {
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" tag \
-a "2.4.8_1" -m "feat: completely borked tag"
assert_success
_deploy_app
run $ABRA app ls --status --debug
assert_success
assert_output --partial "unable to parse 2.4.8_1"
}

View File

@ -22,8 +22,15 @@ teardown(){
_reset_recipe _reset_recipe
_reset_tags _reset_tags
run rm -rf "$ABRA_DIR/recipes/$TEST_RECIPE/foo" if [[ -f "$ABRA_DIR/recipes/$TEST_RECIPE/foo" ]]; then
assert_not_exists "$ABRA_DIR/recipes/$TEST_RECIPE/foo" run rm -rf "$ABRA_DIR/recipes/$TEST_RECIPE/foo"
assert_not_exists "$ABRA_DIR/recipes/$TEST_RECIPE/foo"
fi
if [[ -f "$ABRA_DIR/servers/$TEST_SERVER/rauthy.$TEST_APP_DOMAIN.env" ]]; then
run rm -rf "$ABRA_DIR/servers/$TEST_SERVER/rauthy.$TEST_APP_DOMAIN.env"
assert_not_exists "$ABRA_DIR/servers/$TEST_SERVER/rauthy.$TEST_APP_DOMAIN.env"
fi
} }
@test "create new app" { @test "create new app" {
@ -270,3 +277,32 @@ teardown(){
assert_success assert_success
refute_output --partial "requires secret generation" refute_output --partial "requires secret generation"
} }
@test "do not warn about generation when generate=false" {
run $ABRA app new --domain "$TEST_APP_DOMAIN" renovate "1.0.1+41-full"
assert_success
refute_output --partial "requires secret generation"
}
@test "warn about insertion when generate=false" {
run $ABRA app new --domain "$TEST_APP_DOMAIN" renovate "1.0.1+41-full"
assert_success
assert_output --partial "requires secret insertion"
}
@test "warn about both insert/generate when generate=false/true" {
run $ABRA app new rauthy "1.0.0+0.32.3" \
--no-input \
--server "$TEST_SERVER" \
--domain "rauthy.$TEST_APP_DOMAIN"
assert_success
assert_exists "$ABRA_DIR/servers/$TEST_SERVER/rauthy.$TEST_APP_DOMAIN.env"
assert_output --partial "requires secret generation"
assert_output --partial "requires secret insertion"
}
@test "no warn about generation if already generated" {
run $ABRA app new "$TEST_RECIPE" --domain "$TEST_APP_DOMAIN" --secrets
assert_success
refute_output --partial "requires secret generation"
}

View File

@ -181,7 +181,7 @@ teardown(){
} }
# bats test_tags=slow # bats test_tags=slow
@test "rollback chaos deployment is not possible" { @test "rollback chaos deployment is possible" {
tagHash=$(_get_tag_hash "0.2.0+1.21.0") tagHash=$(_get_tag_hash "0.2.0+1.21.0")
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" checkout "$tagHash" run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" checkout "$tagHash"
assert_success assert_success
@ -191,12 +191,13 @@ teardown(){
assert_output --partial "${tagHash:0:8}" assert_output --partial "${tagHash:0:8}"
run $ABRA app rollback "$TEST_APP_DOMAIN" "0.1.1+1.20.2" --no-input --no-converge-checks run $ABRA app rollback "$TEST_APP_DOMAIN" "0.1.1+1.20.2" --no-input --no-converge-checks
assert_failure assert_success
assert_output --partial 'current deployment' + "${tagHash:0:8}" + 'is not a known version' assert_output --regexp "CURRENT DEPLOYMENT.*${tagHash:0:8}"
assert_output --regexp "ENV VERSION.*${tagHash:0:8}"
} }
# bats test_tags=slow # bats test_tags=slow
@test "chaos commit rollback not possible" { @test "specific chaos commit rollback not possible" {
_deploy_app _deploy_app
tagHash=$(_get_tag_hash "0.2.0+1.21.0") tagHash=$(_get_tag_hash "0.2.0+1.21.0")

View File

@ -33,10 +33,29 @@ teardown(){
assert_success assert_success
run $ABRA app rollback "$TEST_APP_DOMAIN" "0.1.0+1.20.0" \ run $ABRA app rollback "$TEST_APP_DOMAIN" "0.1.0+1.20.0" \
--no-input --no-converge-checks --debug --no-input --no-converge-checks
assert_success assert_success
run grep -q "TYPE=abra-test-recipe:0.1.0+1.20.0" \ run grep -q "TYPE=abra-test-recipe:0.1.0+1.20.0" \
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env" "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
assert_success assert_success
} }
# bats test_tags=slow
@test "new env version written to container env" {
run $ABRA app deploy "$TEST_APP_DOMAIN" "0.2.0+1.21.0" --no-input
assert_success
run grep -q "TYPE=abra-test-recipe:0.2.0+1.21.0" \
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
assert_success
run $ABRA app rollback "$TEST_APP_DOMAIN" "0.1.0+1.20.0" \
--no-input
assert_success
run docker inspect --format='{{range .Config.Env}}{{println .}}{{end}}' \
$(docker ps -f name="$TEST_APP_DOMAIN_$TEST_SERVER" -q)
assert_success
assert_output --partial "$TEST_RECIIPE:0.1.0+1.20.0"
}

View File

@ -75,7 +75,7 @@ teardown(){
assert_output --partial 'DOWNGRADE OVERVIEW' assert_output --partial 'DOWNGRADE OVERVIEW'
assert_output --partial 'CURRENT DEPLOYMENT 0.2.0+1.21.0' assert_output --partial 'CURRENT DEPLOYMENT 0.2.0+1.21.0'
assert_output --partial 'ENV VERSION N/A' assert_output --partial 'ENV VERSION -'
assert_output --partial 'NEW DEPLOYMENT 0.1.0+1.20.0' assert_output --partial 'NEW DEPLOYMENT 0.1.0+1.20.0'
run grep -q "TYPE=$TEST_RECIPE:${latestRelease}" \ run grep -q "TYPE=$TEST_RECIPE:${latestRelease}" \

View File

@ -106,6 +106,7 @@ teardown(){
run $ABRA app undeploy "$TEST_APP_DOMAIN" --no-input run $ABRA app undeploy "$TEST_APP_DOMAIN" --no-input
assert_success assert_success
assert_output --regexp "CURRENT DEPLOYMENT.*${_get_head_hash:0:8}"
} }
# bats test_tags=slow # bats test_tags=slow

View File

@ -36,7 +36,7 @@ teardown(){
assert_output --partial 'UNDEPLOY OVERVIEW' assert_output --partial 'UNDEPLOY OVERVIEW'
assert_output --partial 'CURRENT DEPLOYMENT 0.1.0+1.20.0' assert_output --partial 'CURRENT DEPLOYMENT 0.1.0+1.20.0'
assert_output --partial 'ENV VERSION 0.1.0+1.20.0' assert_output --partial 'ENV VERSION 0.1.0+1.20.0'
assert_output --partial 'NEW DEPLOYMENT N/A' assert_output --partial 'NEW DEPLOYMENT -'
run grep -q "TYPE=$TEST_RECIPE:0.1.0+1.20.0" \ run grep -q "TYPE=$TEST_RECIPE:0.1.0+1.20.0" \
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env" "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
@ -57,7 +57,7 @@ teardown(){
assert_output --partial 'UNDEPLOY OVERVIEW' assert_output --partial 'UNDEPLOY OVERVIEW'
assert_output --partial "CURRENT DEPLOYMENT ${headHash:0:8}" assert_output --partial "CURRENT DEPLOYMENT ${headHash:0:8}"
assert_output --partial "ENV VERSION ${headHash:0:8}" assert_output --partial "ENV VERSION ${headHash:0:8}"
assert_output --partial 'NEW DEPLOYMENT N/A' assert_output --partial 'NEW DEPLOYMENT -'
run grep -q "TYPE=$TEST_RECIPE:${headHash:0:8}" \ run grep -q "TYPE=$TEST_RECIPE:${headHash:0:8}" \
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env" "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
@ -81,7 +81,7 @@ teardown(){
assert_output --partial 'UNDEPLOY OVERVIEW' assert_output --partial 'UNDEPLOY OVERVIEW'
assert_output --partial "CURRENT DEPLOYMENT ${headHash:0:8}+U" assert_output --partial "CURRENT DEPLOYMENT ${headHash:0:8}+U"
assert_output --partial "ENV VERSION ${headHash:0:8}+U" assert_output --partial "ENV VERSION ${headHash:0:8}+U"
assert_output --partial 'NEW DEPLOYMENT N/A' assert_output --partial 'NEW DEPLOYMENT -'
run grep -q "TYPE=$TEST_RECIPE:${headHash:0:8}" \ run grep -q "TYPE=$TEST_RECIPE:${headHash:0:8}" \
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env" "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"

View File

@ -256,7 +256,7 @@ teardown(){
} }
# bats test_tags=slow # bats test_tags=slow
@test "upgrade commit deployment not possible" { @test "specific version upgrade after chaos deploy" {
tagHash=$(_get_tag_hash "0.1.0+1.20.0") tagHash=$(_get_tag_hash "0.1.0+1.20.0")
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" checkout "$tagHash" run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" checkout "$tagHash"
assert_success assert_success
@ -266,20 +266,29 @@ teardown(){
assert_output --partial "${tagHash:0:8}" assert_output --partial "${tagHash:0:8}"
run $ABRA app upgrade "$TEST_APP_DOMAIN" "0.1.1+1.20.2" --no-input --no-converge-checks run $ABRA app upgrade "$TEST_APP_DOMAIN" "0.1.1+1.20.2" --no-input --no-converge-checks
assert_failure assert_success
assert_output --partial "not a known version" assert_output --regexp "CURRENT DEPLOYMENT.*${tagHash:0:8}"
assert_output --regexp "ENV VERSION.*${tagHash:0:8}"
assert_output --regexp "NEW DEPLOYMENT.*0\.1\.1\+1\.20\.2"
} }
@test "chaos commit upgrade not possible" { # bats test_tags=slow
run $ABRA app deploy "$TEST_APP_DOMAIN" "0.1.0+1.20.0" --no-input --no-converge-checks @test "upgrade to latest after chaos deploy" {
latestRelease=$(_latest_release)
tagHash=$(_get_tag_hash "0.1.0+1.20.0")
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" checkout "$tagHash"
assert_success assert_success
assert_output --partial '0.1.0+1.20.0'
tagHash=$(_get_tag_hash "0.2.0+1.21.0") run $ABRA app deploy "$TEST_APP_DOMAIN" --no-input --no-converge-checks --chaos
assert_success
assert_output --partial "${tagHash:0:8}"
run $ABRA app upgrade "$TEST_APP_DOMAIN" "$tagHash" --no-input --no-converge-checks run $ABRA app upgrade "$TEST_APP_DOMAIN" --no-input --no-converge-checks
assert_failure assert_success
assert_output --partial "not a known version" assert_output --regexp "CURRENT DEPLOYMENT.*${tagHash:0:8}"
assert_output --regexp "ENV VERSION.*${tagHash:0:8}"
assert_output --partial "${latestRelease}"
} }
# bats test_tags=slow # bats test_tags=slow

View File

@ -40,3 +40,21 @@ teardown(){
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env" "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
assert_success assert_success
} }
# bats test_tags=slow
@test "new env version written to container env" {
run $ABRA app deploy "$TEST_APP_DOMAIN" "0.1.0+1.20.0" --no-input
assert_success
run grep -q "TYPE=abra-test-recipe:0.1.0+1.20.0" \
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
assert_success
run $ABRA app upgrade "$TEST_APP_DOMAIN" "0.2.0+1.21.0" --no-input
assert_success
run docker inspect --format='{{range .Config.Env}}{{println .}}{{end}}' \
$(docker ps -f name="$TEST_APP_DOMAIN_$TEST_SERVER" -q)
assert_success
assert_output --partial "$TEST_RECIIPE:0.2.0+1.21.0"
}

View File

@ -75,7 +75,7 @@ teardown(){
assert_output --partial 'UPGRADE OVERVIEW' assert_output --partial 'UPGRADE OVERVIEW'
assert_output --partial 'CURRENT DEPLOYMENT 0.2.0+1.21.0' assert_output --partial 'CURRENT DEPLOYMENT 0.2.0+1.21.0'
assert_output --partial 'ENV VERSION N/A' assert_output --partial 'ENV VERSION -'
assert_output --partial "NEW DEPLOYMENT $latestRelease" assert_output --partial "NEW DEPLOYMENT $latestRelease"
run grep -q "TYPE=$TEST_RECIPE:${latestRelease}" \ run grep -q "TYPE=$TEST_RECIPE:${latestRelease}" \

View File

@ -53,7 +53,8 @@ teardown(){
--domain "foobar.$TEST_SERVER" --domain "foobar.$TEST_SERVER"
assert_success assert_success
run $ABRA app deploy "foobar.$TEST_SERVER" --no-input run $ABRA app deploy "foobar.$TEST_SERVER" \
--no-input --no-converge-checks
assert_success assert_success
} }

View File

@ -101,6 +101,9 @@ teardown() {
assert_success assert_success
assert_exists "$ABRA_DIR/recipes/$TEST_RECIPE/foo" assert_exists "$ABRA_DIR/recipes/$TEST_RECIPE/foo"
run $ABRA recipe sync "$TEST_RECIPE" --no-input --patch
assert_success
run $ABRA recipe release "$TEST_RECIPE" --no-input --patch run $ABRA recipe release "$TEST_RECIPE" --no-input --patch
assert_success assert_success
assert_output --partial 'no -p/--publish passed, not publishing' assert_output --partial 'no -p/--publish passed, not publishing'
@ -119,6 +122,9 @@ teardown() {
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" commit -m "added some release notes" run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" commit -m "added some release notes"
assert_success assert_success
run $ABRA recipe sync "$TEST_RECIPE" --no-input --patch
assert_success
run $ABRA recipe release "$TEST_RECIPE" --no-input --minor run $ABRA recipe release "$TEST_RECIPE" --no-input --minor
assert_success assert_success
assert_output --partial 'no -p/--publish passed, not publishing' assert_output --partial 'no -p/--publish passed, not publishing'
@ -127,3 +133,27 @@ teardown() {
assert_exists "$ABRA_DIR/recipes/$TEST_RECIPE/release/0.4.0+1.21.0" assert_exists "$ABRA_DIR/recipes/$TEST_RECIPE/release/0.4.0+1.21.0"
assert_file_contains "$ABRA_DIR/recipes/$TEST_RECIPE/release/0.4.0+1.21.0" "those are some release notes for the next release" assert_file_contains "$ABRA_DIR/recipes/$TEST_RECIPE/release/0.4.0+1.21.0" "those are some release notes for the next release"
} }
@test "recipe release conflict fails" {
tagHash=$(_get_tag_hash "0.2.0+1.21.0")
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" checkout "$tagHash"
assert_success
run sed -i "s/nginx:1.21.0/nginx:1.29.1/g" "$ABRA_DIR/recipes/$TEST_RECIPE/compose.yml"
assert_success
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" diff
assert_success
assert_output --regexp 'nginx:1.29.1'
run sed -i "s/0.2.0+1.21.0/0.2.0+1.29.1/g" "$ABRA_DIR/recipes/$TEST_RECIPE/compose.yml"
assert_success
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" diff
assert_success
assert_output --regexp 'coop-cloud\.\$\{STACK_NAME\}\.version=0\.2\.0\+1\.29\.1'
run $ABRA recipe release "$TEST_RECIPE" --no-input --minor
assert_failure
assert_output --partial '0.2.0+... conflicts with a previous release: 0.2.0+1.21.0'
}

View File

@ -19,6 +19,10 @@ setup(){
teardown(){ teardown(){
_reset_recipe _reset_recipe
_reset_tags _reset_tags
if [[ -d "$ABRA_DIR/recipes/foobar" ]]; then
run rm -rf "$ABRA_DIR/recipes/foobar"
assert_success
fi
} }
@test "validate recipe argument" { @test "validate recipe argument" {
@ -126,3 +130,71 @@ teardown(){
assert_line --index 0 --partial 'synced label' assert_line --index 0 --partial 'synced label'
refute_line --index 1 --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"
}

View File

@ -12,11 +12,7 @@ setup_suite(){
fi fi
if [[ ! -f "$PWD/abra" ]]; then if [[ ! -f "$PWD/abra" ]]; then
make build-abra make
fi
if [[ ! -f "$PWD/kadabra" ]]; then
make build-kadabra
fi fi
if [[ -d "$ABRA_DIR" ]]; then if [[ -d "$ABRA_DIR" ]]; then

View File

@ -26,6 +26,10 @@ linters:
- whitespace - whitespace
- wrapcheck - wrapcheck
exclusions: exclusions:
rules:
- text: '(slog|log)\.\w+'
linters:
- noctx
generated: lax generated: lax
presets: presets:
- common-false-positives - common-false-positives

View File

@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2020-2023 Charmbracelet, Inc Copyright (c) 2020-2025 Charmbracelet, Inc
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@ -9,7 +9,7 @@
<br> <br>
<a href="https://github.com/charmbracelet/bubbletea/releases"><img src="https://img.shields.io/github/release/charmbracelet/bubbletea.svg" alt="Latest Release"></a> <a href="https://github.com/charmbracelet/bubbletea/releases"><img src="https://img.shields.io/github/release/charmbracelet/bubbletea.svg" alt="Latest Release"></a>
<a href="https://pkg.go.dev/github.com/charmbracelet/bubbletea?tab=doc"><img src="https://godoc.org/github.com/charmbracelet/bubbletea?status.svg" alt="GoDoc"></a> <a href="https://pkg.go.dev/github.com/charmbracelet/bubbletea?tab=doc"><img src="https://godoc.org/github.com/charmbracelet/bubbletea?status.svg" alt="GoDoc"></a>
<a href="https://github.com/charmbracelet/bubbletea/actions"><img src="https://github.com/charmbracelet/bubbletea/actions/workflows/build.yml/badge.svg" alt="Build Status"></a> <a href="https://github.com/charmbracelet/bubbletea/actions"><img src="https://github.com/charmbracelet/bubbletea/actions/workflows/build.yml/badge.svg?branch=main" alt="Build Status"></a>
</p> </p>
The fun, functional and stateful way to build terminal apps. A Go framework The fun, functional and stateful way to build terminal apps. A Go framework
@ -395,6 +395,6 @@ of days past.
Part of [Charm](https://charm.sh). Part of [Charm](https://charm.sh).
<a href="https://charm.sh/"><img alt="The Charm logo" src="https://stuff.charm.sh/charm-badge.jpg" width="400"></a> <a href="https://charm.sh/"><img alt="The Charm logo" src="https://stuff.charm.sh/charm-banner-next.jpg" width="400"></a>
Charm热爱开源 • Charm loves open source • نحنُ نحب المصادر المفتوحة Charm热爱开源 • Charm loves open source • نحنُ نحب المصادر المفتوحة

View File

@ -13,6 +13,27 @@ import (
// return tea.Batch(someCommand, someOtherCommand) // return tea.Batch(someCommand, someOtherCommand)
// } // }
func Batch(cmds ...Cmd) Cmd { func Batch(cmds ...Cmd) Cmd {
return compactCmds[BatchMsg](cmds)
}
// BatchMsg is a message used to perform a bunch of commands concurrently with
// no ordering guarantees. You can send a BatchMsg with Batch.
type BatchMsg []Cmd
// Sequence runs the given commands one at a time, in order. Contrast this with
// Batch, which runs commands concurrently.
func Sequence(cmds ...Cmd) Cmd {
return compactCmds[sequenceMsg](cmds)
}
// sequenceMsg is used internally to run the given commands in order.
type sequenceMsg []Cmd
// compactCmds ignores any nil commands in cmds, and returns the most direct
// command possible. That is, considering the non-nil commands, if there are
// none it returns nil, if there is exactly one it returns that command
// directly, else it returns the non-nil commands as type T.
func compactCmds[T ~[]Cmd](cmds []Cmd) Cmd {
var validCmds []Cmd //nolint:prealloc var validCmds []Cmd //nolint:prealloc
for _, c := range cmds { for _, c := range cmds {
if c == nil { if c == nil {
@ -27,26 +48,11 @@ func Batch(cmds ...Cmd) Cmd {
return validCmds[0] return validCmds[0]
default: default:
return func() Msg { return func() Msg {
return BatchMsg(validCmds) return T(validCmds)
} }
} }
} }
// BatchMsg is a message used to perform a bunch of commands concurrently with
// no ordering guarantees. You can send a BatchMsg with Batch.
type BatchMsg []Cmd
// Sequence runs the given commands one at a time, in order. Contrast this with
// Batch, which runs commands concurrently.
func Sequence(cmds ...Cmd) Cmd {
return func() Msg {
return sequenceMsg(cmds)
}
}
// sequenceMsg is used internally to run the given commands in order.
type sequenceMsg []Cmd
// Every is a command that ticks in sync with the system clock. So, if you // Every is a command that ticks in sync with the system clock. So, if you
// wanted to tick with the system clock every second, minute or hour you // wanted to tick with the system clock every second, minute or hour you
// could use this. It's also handy for having different things tick in sync. // could use this. It's also handy for having different things tick in sync.

View File

@ -108,7 +108,7 @@ func prepareConsole(input windows.Handle, modes ...uint32) (originalMode uint32,
return originalMode, nil return originalMode, nil
} }
// cancelMixin represents a goroutine-safe cancelation status. // cancelMixin represents a goroutine-safe cancellation status.
type cancelMixin struct { type cancelMixin struct {
unsafeCanceled bool unsafeCanceled bool
lock sync.Mutex lock sync.Mutex

View File

@ -109,12 +109,12 @@ func peekAndReadConsInput(con *conInputReader) ([]coninput.InputRecord, error) {
return events, nil return events, nil
} }
// Convert i to unit32 or panic if it cannot be converted. Check satisifes lint G115. // Convert i to unit32 or panic if it cannot be converted. Check satisfies lint G115.
func intToUint32OrDie(i int) uint32 { func intToUint32OrDie(i int) uint32 {
if i < 0 { if i < 0 {
panic("cannot convert numEvents " + fmt.Sprint(i) + " to uint32") panic("cannot convert numEvents " + fmt.Sprint(i) + " to uint32")
} }
return uint32(i) return uint32(i) //nolint:gosec
} }
// Keeps peeking until there is data or the input is cancelled. // Keeps peeking until there is data or the input is cancelled.
@ -158,16 +158,16 @@ func mouseEventButton(p, s coninput.ButtonState) (button MouseButton, action Mou
return button, action return button, action
} }
switch { switch btn {
case btn == coninput.FROM_LEFT_1ST_BUTTON_PRESSED: // left button case coninput.FROM_LEFT_1ST_BUTTON_PRESSED: // left button
button = MouseButtonLeft button = MouseButtonLeft
case btn == coninput.RIGHTMOST_BUTTON_PRESSED: // right button case coninput.RIGHTMOST_BUTTON_PRESSED: // right button
button = MouseButtonRight button = MouseButtonRight
case btn == coninput.FROM_LEFT_2ND_BUTTON_PRESSED: // middle button case coninput.FROM_LEFT_2ND_BUTTON_PRESSED: // middle button
button = MouseButtonMiddle button = MouseButtonMiddle
case btn == coninput.FROM_LEFT_3RD_BUTTON_PRESSED: // unknown (possibly mouse backward) case coninput.FROM_LEFT_3RD_BUTTON_PRESSED: // unknown (possibly mouse backward)
button = MouseButtonBackward button = MouseButtonBackward
case btn == coninput.FROM_LEFT_4TH_BUTTON_PRESSED: // unknown (possibly mouse forward) case coninput.FROM_LEFT_4TH_BUTTON_PRESSED: // unknown (possibly mouse forward)
button = MouseButtonForward button = MouseButtonForward
} }

View File

@ -131,7 +131,7 @@ func EnableBracketedPaste() Msg {
type enableBracketedPasteMsg struct{} type enableBracketedPasteMsg struct{}
// DisableBracketedPaste is a special command that tells the Bubble Tea program // DisableBracketedPaste is a special command that tells the Bubble Tea program
// to accept bracketed paste input. // to stop processing bracketed paste input.
// //
// Note that bracketed paste will be automatically disabled when the // Note that bracketed paste will be automatically disabled when the
// program quits. // program quits.

View File

@ -277,7 +277,7 @@ func (r *standardRenderer) flush() {
// using the full terminal window. // using the full terminal window.
buf.WriteString(ansi.CursorPosition(0, len(newLines))) buf.WriteString(ansi.CursorPosition(0, len(newLines)))
} else { } else {
buf.WriteString(ansi.CursorBackward(r.width)) buf.WriteByte('\r')
} }
_, _ = r.out.Write(buf.Bytes()) _, _ = r.out.Write(buf.Bytes())

View File

@ -24,7 +24,6 @@ import (
"github.com/charmbracelet/x/term" "github.com/charmbracelet/x/term"
"github.com/muesli/cancelreader" "github.com/muesli/cancelreader"
"golang.org/x/sync/errgroup"
) )
// ErrProgramPanic is returned by [Program.Run] when the program recovers from a panic. // ErrProgramPanic is returned by [Program.Run] when the program recovers from a panic.
@ -73,7 +72,7 @@ const (
customInput customInput
) )
// String implements the stringer interface for [inputType]. It is inteded to // String implements the stringer interface for [inputType]. It is intended to
// be used in testing. // be used in testing.
func (i inputType) String() string { func (i inputType) String() string {
return [...]string{ return [...]string{
@ -220,7 +219,7 @@ func Suspend() Msg {
// You can send this message with [Suspend()]. // You can send this message with [Suspend()].
type SuspendMsg struct{} type SuspendMsg struct{}
// ResumeMsg can be listen to to do something once a program is resumed back // ResumeMsg can be listen to do something once a program is resumed back
// from a suspend state. // from a suspend state.
type ResumeMsg struct{} type ResumeMsg struct{}
@ -472,42 +471,12 @@ func (p *Program) eventLoop(model Model, cmds chan Cmd) (Model, error) {
p.exec(msg.cmd, msg.fn) p.exec(msg.cmd, msg.fn)
case BatchMsg: case BatchMsg:
for _, cmd := range msg { go p.execBatchMsg(msg)
select {
case <-p.ctx.Done():
return model, nil
case cmds <- cmd:
}
}
continue continue
case sequenceMsg: case sequenceMsg:
go func() { go p.execSequenceMsg(msg)
// Execute commands one at a time, in order. continue
for _, cmd := range msg {
if cmd == nil {
continue
}
msg := cmd()
if batchMsg, ok := msg.(BatchMsg); ok {
g, _ := errgroup.WithContext(p.ctx)
for _, cmd := range batchMsg {
cmd := cmd
g.Go(func() error {
p.Send(cmd())
return nil
})
}
//nolint:errcheck,gosec
g.Wait() // wait for all commands from batch msg to finish
continue
}
p.Send(msg)
}
}()
case setWindowTitleMsg: case setWindowTitleMsg:
p.SetWindowTitle(string(msg)) p.SetWindowTitle(string(msg))
@ -535,6 +504,74 @@ func (p *Program) eventLoop(model Model, cmds chan Cmd) (Model, error) {
} }
} }
func (p *Program) execSequenceMsg(msg sequenceMsg) {
if !p.startupOptions.has(withoutCatchPanics) {
defer func() {
if r := recover(); r != nil {
p.recoverFromGoPanic(r)
}
}()
}
// Execute commands one at a time, in order.
for _, cmd := range msg {
if cmd == nil {
continue
}
msg := cmd()
switch msg := msg.(type) {
case BatchMsg:
p.execBatchMsg(msg)
case sequenceMsg:
p.execSequenceMsg(msg)
default:
p.Send(msg)
}
}
}
func (p *Program) execBatchMsg(msg BatchMsg) {
if !p.startupOptions.has(withoutCatchPanics) {
defer func() {
if r := recover(); r != nil {
p.recoverFromGoPanic(r)
}
}()
}
// Execute commands one at a time.
var wg sync.WaitGroup
for _, cmd := range msg {
if cmd == nil {
continue
}
wg.Add(1)
go func() {
defer wg.Done()
if !p.startupOptions.has(withoutCatchPanics) {
defer func() {
if r := recover(); r != nil {
p.recoverFromGoPanic(r)
}
}()
}
msg := cmd()
switch msg := msg.(type) {
case BatchMsg:
p.execBatchMsg(msg)
case sequenceMsg:
p.execSequenceMsg(msg)
default:
p.Send(msg)
}
}()
}
wg.Wait() // wait for all commands from batch msg to finish
}
// Run initializes the program and runs its event loops, blocking until it gets // Run initializes the program and runs its event loops, blocking until it gets
// terminated by either [Program.Quit], [Program.Kill], or its signal handler. // terminated by either [Program.Quit], [Program.Kill], or its signal handler.
// Returns the final model. // Returns the final model.

View File

@ -56,7 +56,7 @@ func (p *Program) initInput() (err error) {
// Open the Windows equivalent of a TTY. // Open the Windows equivalent of a TTY.
func openInputTTY() (*os.File, error) { func openInputTTY() (*os.File, error) {
f, err := os.OpenFile("CONIN$", os.O_RDWR, 0o644) f, err := os.OpenFile("CONIN$", os.O_RDWR, 0o644) //nolint:gosec
if err != nil { if err != nil {
return nil, fmt.Errorf("error opening file: %w", err) return nil, fmt.Errorf("error opening file: %w", err)
} }

View File

@ -153,29 +153,24 @@ func envColorProfile(env environ) (p Profile) {
p = ANSI p = ANSI
} }
parts := strings.Split(term, "-") switch {
switch parts[0] { case strings.Contains(term, "alacritty"),
case "alacritty", strings.Contains(term, "contour"),
"contour", strings.Contains(term, "foot"),
"foot", strings.Contains(term, "ghostty"),
"ghostty", strings.Contains(term, "kitty"),
"kitty", strings.Contains(term, "rio"),
"rio", strings.Contains(term, "st"),
"st", strings.Contains(term, "wezterm"):
"wezterm":
return TrueColor return TrueColor
case "xterm": case strings.HasPrefix(term, "tmux"), strings.HasPrefix(term, "screen"):
if len(parts) > 1 {
switch parts[1] {
case "ghostty", "kitty":
// These terminals can be defined as xterm-TERMNAME
return TrueColor
}
}
case "tmux", "screen":
if p < ANSI256 { if p < ANSI256 {
p = ANSI256 p = ANSI256
} }
case strings.HasPrefix(term, "xterm"):
if p < ANSI {
p = ANSI
}
} }
if isCloudShell, _ := strconv.ParseBool(env.get("GOOGLE_CLOUD_SHELL")); isCloudShell { if isCloudShell, _ := strconv.ParseBool(env.get("GOOGLE_CLOUD_SHELL")); isCloudShell {

24
vendor/github.com/charmbracelet/x/ansi/inband.go generated vendored Normal file
View File

@ -0,0 +1,24 @@
package ansi
import "fmt"
// InBandResize encodes an in-band terminal resize event sequence.
//
// CSI 48 ; height_cells ; widht_cells ; height_pixels ; width_pixels t
//
// See https://gist.github.com/rockorager/e695fb2924d36b2bcf1fff4a3704bd83
func InBandResize(heightCells, widthCells, heightPixels, widthPixels int) string {
if heightCells < 0 {
heightCells = 0
}
if widthCells < 0 {
widthCells = 0
}
if heightPixels < 0 {
heightPixels = 0
}
if widthPixels < 0 {
widthPixels = 0
}
return fmt.Sprintf("\x1b[48;%d;%d;%d;%dt", heightCells, widthCells, heightPixels, widthPixels)
}

View File

@ -108,7 +108,7 @@ func DECRST(modes ...Mode) string {
func setMode(reset bool, modes ...Mode) (s string) { func setMode(reset bool, modes ...Mode) (s string) {
if len(modes) == 0 { if len(modes) == 0 {
return //nolint:nakedret return s
} }
cmd := "h" cmd := "h"
@ -142,7 +142,7 @@ func setMode(reset bool, modes ...Mode) (s string) {
if len(dec) > 0 { if len(dec) > 0 {
s += seq + "?" + strings.Join(dec, ";") + cmd s += seq + "?" + strings.Join(dec, ";") + cmd
} }
return //nolint:nakedret return s
} }
// RequestMode (DECRQM) returns a sequence to request a mode from the terminal. // RequestMode (DECRQM) returns a sequence to request a mode from the terminal.
@ -228,12 +228,12 @@ func (m DECMode) Mode() int {
// //
// See: https://vt100.net/docs/vt510-rm/KAM.html // See: https://vt100.net/docs/vt510-rm/KAM.html
const ( const (
KeyboardActionMode = ANSIMode(2) ModeKeyboardAction = ANSIMode(2)
KAM = KeyboardActionMode KAM = ModeKeyboardAction
SetKeyboardActionMode = "\x1b[2h" SetModeKeyboardAction = "\x1b[2h"
ResetKeyboardActionMode = "\x1b[2l" ResetModeKeyboardAction = "\x1b[2l"
RequestKeyboardActionMode = "\x1b[2$p" RequestModeKeyboardAction = "\x1b[2$p"
) )
// Insert/Replace Mode (IRM) is a mode that determines whether characters are // Insert/Replace Mode (IRM) is a mode that determines whether characters are
@ -245,12 +245,12 @@ const (
// //
// See: https://vt100.net/docs/vt510-rm/IRM.html // See: https://vt100.net/docs/vt510-rm/IRM.html
const ( const (
InsertReplaceMode = ANSIMode(4) ModeInsertReplace = ANSIMode(4)
IRM = InsertReplaceMode IRM = ModeInsertReplace
SetInsertReplaceMode = "\x1b[4h" SetModeInsertReplace = "\x1b[4h"
ResetInsertReplaceMode = "\x1b[4l" ResetModeInsertReplace = "\x1b[4l"
RequestInsertReplaceMode = "\x1b[4$p" RequestModeInsertReplace = "\x1b[4$p"
) )
// BiDirectional Support Mode (BDSM) is a mode that determines whether the // BiDirectional Support Mode (BDSM) is a mode that determines whether the
@ -260,12 +260,12 @@ const (
// //
// See ECMA-48 7.2.1. // See ECMA-48 7.2.1.
const ( const (
BiDirectionalSupportMode = ANSIMode(8) ModeBiDirectionalSupport = ANSIMode(8)
BDSM = BiDirectionalSupportMode BDSM = ModeBiDirectionalSupport
SetBiDirectionalSupportMode = "\x1b[8h" SetModeBiDirectionalSupport = "\x1b[8h"
ResetBiDirectionalSupportMode = "\x1b[8l" ResetModeBiDirectionalSupport = "\x1b[8l"
RequestBiDirectionalSupportMode = "\x1b[8$p" RequestModeBiDirectionalSupport = "\x1b[8$p"
) )
// Send Receive Mode (SRM) or Local Echo Mode is a mode that determines whether // Send Receive Mode (SRM) or Local Echo Mode is a mode that determines whether
@ -274,17 +274,17 @@ const (
// //
// See: https://vt100.net/docs/vt510-rm/SRM.html // See: https://vt100.net/docs/vt510-rm/SRM.html
const ( const (
SendReceiveMode = ANSIMode(12) ModeSendReceive = ANSIMode(12)
LocalEchoMode = SendReceiveMode ModeLocalEcho = ModeSendReceive
SRM = SendReceiveMode SRM = ModeSendReceive
SetSendReceiveMode = "\x1b[12h" SetModeSendReceive = "\x1b[12h"
ResetSendReceiveMode = "\x1b[12l" ResetModeSendReceive = "\x1b[12l"
RequestSendReceiveMode = "\x1b[12$p" RequestModeSendReceive = "\x1b[12$p"
SetLocalEchoMode = "\x1b[12h" SetModeLocalEcho = "\x1b[12h"
ResetLocalEchoMode = "\x1b[12l" ResetModeLocalEcho = "\x1b[12l"
RequestLocalEchoMode = "\x1b[12$p" RequestModeLocalEcho = "\x1b[12$p"
) )
// Line Feed/New Line Mode (LNM) is a mode that determines whether the terminal // Line Feed/New Line Mode (LNM) is a mode that determines whether the terminal
@ -299,12 +299,12 @@ const (
// //
// See: https://vt100.net/docs/vt510-rm/LNM.html // See: https://vt100.net/docs/vt510-rm/LNM.html
const ( const (
LineFeedNewLineMode = ANSIMode(20) ModeLineFeedNewLine = ANSIMode(20)
LNM = LineFeedNewLineMode LNM = ModeLineFeedNewLine
SetLineFeedNewLineMode = "\x1b[20h" SetModeLineFeedNewLine = "\x1b[20h"
ResetLineFeedNewLineMode = "\x1b[20l" ResetModeLineFeedNewLine = "\x1b[20l"
RequestLineFeedNewLineMode = "\x1b[20$p" RequestModeLineFeedNewLine = "\x1b[20$p"
) )
// Cursor Keys Mode (DECCKM) is a mode that determines whether the cursor keys // Cursor Keys Mode (DECCKM) is a mode that determines whether the cursor keys
@ -312,18 +312,12 @@ const (
// //
// See: https://vt100.net/docs/vt510-rm/DECCKM.html // See: https://vt100.net/docs/vt510-rm/DECCKM.html
const ( const (
CursorKeysMode = DECMode(1) ModeCursorKeys = DECMode(1)
DECCKM = CursorKeysMode DECCKM = ModeCursorKeys
SetCursorKeysMode = "\x1b[?1h" SetModeCursorKeys = "\x1b[?1h"
ResetCursorKeysMode = "\x1b[?1l" ResetModeCursorKeys = "\x1b[?1l"
RequestCursorKeysMode = "\x1b[?1$p" RequestModeCursorKeys = "\x1b[?1$p"
)
// Deprecated: use [SetCursorKeysMode] and [ResetCursorKeysMode] instead.
const (
EnableCursorKeys = "\x1b[?1h" //nolint:revive // grouped constants
DisableCursorKeys = "\x1b[?1l"
) )
// Origin Mode (DECOM) is a mode that determines whether the cursor moves to the // Origin Mode (DECOM) is a mode that determines whether the cursor moves to the
@ -331,12 +325,12 @@ const (
// //
// See: https://vt100.net/docs/vt510-rm/DECOM.html // See: https://vt100.net/docs/vt510-rm/DECOM.html
const ( const (
OriginMode = DECMode(6) ModeOrigin = DECMode(6)
DECOM = OriginMode DECOM = ModeOrigin
SetOriginMode = "\x1b[?6h" SetModeOrigin = "\x1b[?6h"
ResetOriginMode = "\x1b[?6l" ResetModeOrigin = "\x1b[?6l"
RequestOriginMode = "\x1b[?6$p" RequestModeOrigin = "\x1b[?6$p"
) )
// Auto Wrap Mode (DECAWM) is a mode that determines whether the cursor wraps // Auto Wrap Mode (DECAWM) is a mode that determines whether the cursor wraps
@ -344,12 +338,12 @@ const (
// //
// See: https://vt100.net/docs/vt510-rm/DECAWM.html // See: https://vt100.net/docs/vt510-rm/DECAWM.html
const ( const (
AutoWrapMode = DECMode(7) ModeAutoWrap = DECMode(7)
DECAWM = AutoWrapMode DECAWM = ModeAutoWrap
SetAutoWrapMode = "\x1b[?7h" SetModeAutoWrap = "\x1b[?7h"
ResetAutoWrapMode = "\x1b[?7l" ResetModeAutoWrap = "\x1b[?7l"
RequestAutoWrapMode = "\x1b[?7$p" RequestModeAutoWrap = "\x1b[?7$p"
) )
// X10 Mouse Mode is a mode that determines whether the mouse reports on button // X10 Mouse Mode is a mode that determines whether the mouse reports on button
@ -364,39 +358,29 @@ const (
// //
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking // See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
const ( const (
X10MouseMode = DECMode(9) ModeMouseX10 = DECMode(9)
SetX10MouseMode = "\x1b[?9h" SetModeMouseX10 = "\x1b[?9h"
ResetX10MouseMode = "\x1b[?9l" ResetModeMouseX10 = "\x1b[?9l"
RequestX10MouseMode = "\x1b[?9$p" RequestModeMouseX10 = "\x1b[?9$p"
) )
// Text Cursor Enable Mode (DECTCEM) is a mode that shows/hides the cursor. // Text Cursor Enable Mode (DECTCEM) is a mode that shows/hides the cursor.
// //
// See: https://vt100.net/docs/vt510-rm/DECTCEM.html // See: https://vt100.net/docs/vt510-rm/DECTCEM.html
const ( const (
TextCursorEnableMode = DECMode(25) ModeTextCursorEnable = DECMode(25)
DECTCEM = TextCursorEnableMode DECTCEM = ModeTextCursorEnable
SetTextCursorEnableMode = "\x1b[?25h" SetModeTextCursorEnable = "\x1b[?25h"
ResetTextCursorEnableMode = "\x1b[?25l" ResetModeTextCursorEnable = "\x1b[?25l"
RequestTextCursorEnableMode = "\x1b[?25$p" RequestModeTextCursorEnable = "\x1b[?25$p"
) )
// These are aliases for [SetTextCursorEnableMode] and [ResetTextCursorEnableMode]. // These are aliases for [SetModeTextCursorEnable] and [ResetModeTextCursorEnable].
const ( const (
ShowCursor = SetTextCursorEnableMode ShowCursor = SetModeTextCursorEnable
HideCursor = ResetTextCursorEnableMode HideCursor = ResetModeTextCursorEnable
)
// Text Cursor Enable Mode (DECTCEM) is a mode that shows/hides the cursor.
//
// See: https://vt100.net/docs/vt510-rm/DECTCEM.html
//
// Deprecated: use [SetTextCursorEnableMode] and [ResetTextCursorEnableMode] instead.
const (
CursorEnableMode = DECMode(25)
RequestCursorVisibility = "\x1b[?25$p"
) )
// Numeric Keypad Mode (DECNKM) is a mode that determines whether the keypad // Numeric Keypad Mode (DECNKM) is a mode that determines whether the keypad
@ -406,12 +390,12 @@ const (
// //
// See: https://vt100.net/docs/vt510-rm/DECNKM.html // See: https://vt100.net/docs/vt510-rm/DECNKM.html
const ( const (
NumericKeypadMode = DECMode(66) ModeNumericKeypad = DECMode(66)
DECNKM = NumericKeypadMode DECNKM = ModeNumericKeypad
SetNumericKeypadMode = "\x1b[?66h" SetModeNumericKeypad = "\x1b[?66h"
ResetNumericKeypadMode = "\x1b[?66l" ResetModeNumericKeypad = "\x1b[?66l"
RequestNumericKeypadMode = "\x1b[?66$p" RequestModeNumericKeypad = "\x1b[?66$p"
) )
// Backarrow Key Mode (DECBKM) is a mode that determines whether the backspace // Backarrow Key Mode (DECBKM) is a mode that determines whether the backspace
@ -419,12 +403,12 @@ const (
// //
// See: https://vt100.net/docs/vt510-rm/DECBKM.html // See: https://vt100.net/docs/vt510-rm/DECBKM.html
const ( const (
BackarrowKeyMode = DECMode(67) ModeBackarrowKey = DECMode(67)
DECBKM = BackarrowKeyMode DECBKM = ModeBackarrowKey
SetBackarrowKeyMode = "\x1b[?67h" SetModeBackarrowKey = "\x1b[?67h"
ResetBackarrowKeyMode = "\x1b[?67l" ResetModeBackarrowKey = "\x1b[?67l"
RequestBackarrowKeyMode = "\x1b[?67$p" RequestModeBackarrowKey = "\x1b[?67$p"
) )
// Left Right Margin Mode (DECLRMM) is a mode that determines whether the left // Left Right Margin Mode (DECLRMM) is a mode that determines whether the left
@ -432,47 +416,33 @@ const (
// //
// See: https://vt100.net/docs/vt510-rm/DECLRMM.html // See: https://vt100.net/docs/vt510-rm/DECLRMM.html
const ( const (
LeftRightMarginMode = DECMode(69) ModeLeftRightMargin = DECMode(69)
DECLRMM = LeftRightMarginMode DECLRMM = ModeLeftRightMargin
SetLeftRightMarginMode = "\x1b[?69h" SetModeLeftRightMargin = "\x1b[?69h"
ResetLeftRightMarginMode = "\x1b[?69l" ResetModeLeftRightMargin = "\x1b[?69l"
RequestLeftRightMarginMode = "\x1b[?69$p" RequestModeLeftRightMargin = "\x1b[?69$p"
) )
// Normal Mouse Mode is a mode that determines whether the mouse reports on // Normal Mouse Mode is a mode that determines whether the mouse reports on
// button presses and releases. It will also report modifier keys, wheel // button presses and releases. It will also report modifier keys, wheel
// events, and extra buttons. // events, and extra buttons.
// //
// It uses the same encoding as [X10MouseMode] with a few differences: // It uses the same encoding as [ModeMouseX10] with a few differences:
// //
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking // See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
const ( const (
NormalMouseMode = DECMode(1000) ModeMouseNormal = DECMode(1000)
SetNormalMouseMode = "\x1b[?1000h" SetModeMouseNormal = "\x1b[?1000h"
ResetNormalMouseMode = "\x1b[?1000l" ResetModeMouseNormal = "\x1b[?1000l"
RequestNormalMouseMode = "\x1b[?1000$p" RequestModeMouseNormal = "\x1b[?1000$p"
)
// VT Mouse Tracking is a mode that determines whether the mouse reports on
// button press and release.
//
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
//
// Deprecated: use [NormalMouseMode] instead.
const (
MouseMode = DECMode(1000)
EnableMouse = "\x1b[?1000h"
DisableMouse = "\x1b[?1000l"
RequestMouse = "\x1b[?1000$p"
) )
// Highlight Mouse Tracking is a mode that determines whether the mouse reports // Highlight Mouse Tracking is a mode that determines whether the mouse reports
// on button presses, releases, and highlighted cells. // on button presses, releases, and highlighted cells.
// //
// It uses the same encoding as [NormalMouseMode] with a few differences: // It uses the same encoding as [ModeMouseNormal] with a few differences:
// //
// On highlight events, the terminal responds with the following encoding: // On highlight events, the terminal responds with the following encoding:
// //
@ -481,11 +451,11 @@ const (
// //
// Where the parameters are startx, starty, endx, endy, mousex, and mousey. // Where the parameters are startx, starty, endx, endy, mousex, and mousey.
const ( const (
HighlightMouseMode = DECMode(1001) ModeMouseHighlight = DECMode(1001)
SetHighlightMouseMode = "\x1b[?1001h" SetModeMouseHighlight = "\x1b[?1001h"
ResetHighlightMouseMode = "\x1b[?1001l" ResetModeMouseHighlight = "\x1b[?1001l"
RequestHighlightMouseMode = "\x1b[?1001$p" RequestModeMouseHighlight = "\x1b[?1001$p"
) )
// VT Hilite Mouse Tracking is a mode that determines whether the mouse reports on // VT Hilite Mouse Tracking is a mode that determines whether the mouse reports on
@ -493,65 +463,29 @@ const (
// //
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking // See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
// //
// Deprecated: use [HighlightMouseMode] instead.
const (
MouseHiliteMode = DECMode(1001)
EnableMouseHilite = "\x1b[?1001h" // Button Event Mouse Tracking is essentially the same as [ModeMouseNormal],
DisableMouseHilite = "\x1b[?1001l"
RequestMouseHilite = "\x1b[?1001$p"
)
// Button Event Mouse Tracking is essentially the same as [NormalMouseMode],
// but it also reports button-motion events when a button is pressed. // but it also reports button-motion events when a button is pressed.
// //
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking // See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
const ( const (
ButtonEventMouseMode = DECMode(1002) ModeMouseButtonEvent = DECMode(1002)
SetButtonEventMouseMode = "\x1b[?1002h" SetModeMouseButtonEvent = "\x1b[?1002h"
ResetButtonEventMouseMode = "\x1b[?1002l" ResetModeMouseButtonEvent = "\x1b[?1002l"
RequestButtonEventMouseMode = "\x1b[?1002$p" RequestModeMouseButtonEvent = "\x1b[?1002$p"
) )
// Cell Motion Mouse Tracking is a mode that determines whether the mouse // Any Event Mouse Tracking is the same as [ModeMouseButtonEvent], except that
// reports on button press, release, and motion events.
//
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
//
// Deprecated: use [ButtonEventMouseMode] instead.
const (
MouseCellMotionMode = DECMode(1002)
EnableMouseCellMotion = "\x1b[?1002h"
DisableMouseCellMotion = "\x1b[?1002l"
RequestMouseCellMotion = "\x1b[?1002$p"
)
// Any Event Mouse Tracking is the same as [ButtonEventMouseMode], except that
// all motion events are reported even if no mouse buttons are pressed. // all motion events are reported even if no mouse buttons are pressed.
// //
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking // See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
const ( const (
AnyEventMouseMode = DECMode(1003) ModeMouseAnyEvent = DECMode(1003)
SetAnyEventMouseMode = "\x1b[?1003h" SetModeMouseAnyEvent = "\x1b[?1003h"
ResetAnyEventMouseMode = "\x1b[?1003l" ResetModeMouseAnyEvent = "\x1b[?1003l"
RequestAnyEventMouseMode = "\x1b[?1003$p" RequestModeMouseAnyEvent = "\x1b[?1003$p"
)
// All Mouse Tracking is a mode that determines whether the mouse reports on
// button press, release, motion, and highlight events.
//
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
//
// Deprecated: use [AnyEventMouseMode] instead.
const (
MouseAllMotionMode = DECMode(1003)
EnableMouseAllMotion = "\x1b[?1003h"
DisableMouseAllMotion = "\x1b[?1003l"
RequestMouseAllMotion = "\x1b[?1003$p"
) )
// Focus Event Mode is a mode that determines whether the terminal reports focus // Focus Event Mode is a mode that determines whether the terminal reports focus
@ -564,22 +498,11 @@ const (
// //
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Focus-Tracking // See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Focus-Tracking
const ( const (
FocusEventMode = DECMode(1004) ModeFocusEvent = DECMode(1004)
SetFocusEventMode = "\x1b[?1004h" SetModeFocusEvent = "\x1b[?1004h"
ResetFocusEventMode = "\x1b[?1004l" ResetModeFocusEvent = "\x1b[?1004l"
RequestFocusEventMode = "\x1b[?1004$p" RequestModeFocusEvent = "\x1b[?1004$p"
)
// Deprecated: use [SetFocusEventMode], [ResetFocusEventMode], and
// [RequestFocusEventMode] instead.
// Focus reporting mode constants.
const (
ReportFocusMode = DECMode(1004) //nolint:revive // grouped constants
EnableReportFocus = "\x1b[?1004h"
DisableReportFocus = "\x1b[?1004l"
RequestReportFocus = "\x1b[?1004$p"
) )
// SGR Extended Mouse Mode is a mode that changes the mouse tracking encoding // SGR Extended Mouse Mode is a mode that changes the mouse tracking encoding
@ -589,24 +512,15 @@ const (
// //
// CSI < Cb ; Cx ; Cy M // CSI < Cb ; Cx ; Cy M
// //
// Where Cb is the same as [NormalMouseMode], and Cx and Cy are the x and y. // Where Cb is the same as [ModeMouseNormal], and Cx and Cy are the x and y.
// //
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking // See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
const ( const (
SgrExtMouseMode = DECMode(1006) ModeMouseExtSgr = DECMode(1006)
SetSgrExtMouseMode = "\x1b[?1006h" SetModeMouseExtSgr = "\x1b[?1006h"
ResetSgrExtMouseMode = "\x1b[?1006l" ResetModeMouseExtSgr = "\x1b[?1006l"
RequestSgrExtMouseMode = "\x1b[?1006$p" RequestModeMouseExtSgr = "\x1b[?1006$p"
)
// Deprecated: use [SgrExtMouseMode] [SetSgrExtMouseMode],
// [ResetSgrExtMouseMode], and [RequestSgrExtMouseMode] instead.
const (
MouseSgrExtMode = DECMode(1006) //nolint:revive // grouped constants
EnableMouseSgrExt = "\x1b[?1006h"
DisableMouseSgrExt = "\x1b[?1006l"
RequestMouseSgrExt = "\x1b[?1006$p"
) )
// UTF-8 Extended Mouse Mode is a mode that changes the mouse tracking encoding // UTF-8 Extended Mouse Mode is a mode that changes the mouse tracking encoding
@ -614,11 +528,11 @@ const (
// //
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking // See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
const ( const (
Utf8ExtMouseMode = DECMode(1005) ModeMouseExtUtf8 = DECMode(1005)
SetUtf8ExtMouseMode = "\x1b[?1005h" SetModeMouseExtUtf8 = "\x1b[?1005h"
ResetUtf8ExtMouseMode = "\x1b[?1005l" ResetModeMouseExtUtf8 = "\x1b[?1005l"
RequestUtf8ExtMouseMode = "\x1b[?1005$p" RequestModeMouseExtUtf8 = "\x1b[?1005$p"
) )
// URXVT Extended Mouse Mode is a mode that changes the mouse tracking encoding // URXVT Extended Mouse Mode is a mode that changes the mouse tracking encoding
@ -626,25 +540,25 @@ const (
// //
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking // See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
const ( const (
UrxvtExtMouseMode = DECMode(1015) ModeMouseExtUrxvt = DECMode(1015)
SetUrxvtExtMouseMode = "\x1b[?1015h" SetModeMouseExtUrxvt = "\x1b[?1015h"
ResetUrxvtExtMouseMode = "\x1b[?1015l" ResetModeMouseExtUrxvt = "\x1b[?1015l"
RequestUrxvtExtMouseMode = "\x1b[?1015$p" RequestModeMouseExtUrxvt = "\x1b[?1015$p"
) )
// SGR Pixel Extended Mouse Mode is a mode that changes the mouse tracking // SGR Pixel Extended Mouse Mode is a mode that changes the mouse tracking
// encoding to use SGR parameters with pixel coordinates. // encoding to use SGR parameters with pixel coordinates.
// //
// This is similar to [SgrExtMouseMode], but also reports pixel coordinates. // This is similar to [ModeMouseExtSgr], but also reports pixel coordinates.
// //
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking // See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
const ( const (
SgrPixelExtMouseMode = DECMode(1016) ModeMouseExtSgrPixel = DECMode(1016)
SetSgrPixelExtMouseMode = "\x1b[?1016h" SetModeMouseExtSgrPixel = "\x1b[?1016h"
ResetSgrPixelExtMouseMode = "\x1b[?1016l" ResetModeMouseExtSgrPixel = "\x1b[?1016l"
RequestSgrPixelExtMouseMode = "\x1b[?1016$p" RequestModeMouseExtSgrPixel = "\x1b[?1016$p"
) )
// Alternate Screen Mode is a mode that determines whether the alternate screen // Alternate Screen Mode is a mode that determines whether the alternate screen
@ -653,11 +567,11 @@ const (
// //
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-The-Alternate-Screen-Buffer // See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-The-Alternate-Screen-Buffer
const ( const (
AltScreenMode = DECMode(1047) ModeAltScreen = DECMode(1047)
SetAltScreenMode = "\x1b[?1047h" SetModeAltScreen = "\x1b[?1047h"
ResetAltScreenMode = "\x1b[?1047l" ResetModeAltScreen = "\x1b[?1047l"
RequestAltScreenMode = "\x1b[?1047$p" RequestModeAltScreen = "\x1b[?1047$p"
) )
// Save Cursor Mode is a mode that saves the cursor position. // Save Cursor Mode is a mode that saves the cursor position.
@ -665,42 +579,24 @@ const (
// //
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-The-Alternate-Screen-Buffer // See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-The-Alternate-Screen-Buffer
const ( const (
SaveCursorMode = DECMode(1048) ModeSaveCursor = DECMode(1048)
SetSaveCursorMode = "\x1b[?1048h" SetModeSaveCursor = "\x1b[?1048h"
ResetSaveCursorMode = "\x1b[?1048l" ResetModeSaveCursor = "\x1b[?1048l"
RequestSaveCursorMode = "\x1b[?1048$p" RequestModeSaveCursor = "\x1b[?1048$p"
) )
// Alternate Screen Save Cursor Mode is a mode that saves the cursor position as in // Alternate Screen Save Cursor Mode is a mode that saves the cursor position as in
// [SaveCursorMode], switches to the alternate screen buffer as in [AltScreenMode], // [ModeSaveCursor], switches to the alternate screen buffer as in [ModeAltScreen],
// and clears the screen on switch. // and clears the screen on switch.
// //
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-The-Alternate-Screen-Buffer // See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-The-Alternate-Screen-Buffer
const ( const (
AltScreenSaveCursorMode = DECMode(1049) ModeAltScreenSaveCursor = DECMode(1049)
SetAltScreenSaveCursorMode = "\x1b[?1049h" SetModeAltScreenSaveCursor = "\x1b[?1049h"
ResetAltScreenSaveCursorMode = "\x1b[?1049l" ResetModeAltScreenSaveCursor = "\x1b[?1049l"
RequestAltScreenSaveCursorMode = "\x1b[?1049$p" RequestModeAltScreenSaveCursor = "\x1b[?1049$p"
)
// Alternate Screen Buffer is a mode that determines whether the alternate screen
// buffer is active.
//
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-The-Alternate-Screen-Buffer
//
// Deprecated: use [AltScreenSaveCursorMode] instead.
const (
AltScreenBufferMode = DECMode(1049)
SetAltScreenBufferMode = "\x1b[?1049h"
ResetAltScreenBufferMode = "\x1b[?1049l"
RequestAltScreenBufferMode = "\x1b[?1049$p"
EnableAltScreenBuffer = "\x1b[?1049h"
DisableAltScreenBuffer = "\x1b[?1049l"
RequestAltScreenBuffer = "\x1b[?1049$p"
) )
// Bracketed Paste Mode is a mode that determines whether pasted text is // Bracketed Paste Mode is a mode that determines whether pasted text is
@ -709,19 +605,11 @@ const (
// See: https://cirw.in/blog/bracketed-paste // See: https://cirw.in/blog/bracketed-paste
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Bracketed-Paste-Mode // See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Bracketed-Paste-Mode
const ( const (
BracketedPasteMode = DECMode(2004) ModeBracketedPaste = DECMode(2004)
SetBracketedPasteMode = "\x1b[?2004h" SetModeBracketedPaste = "\x1b[?2004h"
ResetBracketedPasteMode = "\x1b[?2004l" ResetModeBracketedPaste = "\x1b[?2004l"
RequestBracketedPasteMode = "\x1b[?2004$p" RequestModeBracketedPaste = "\x1b[?2004$p"
)
// Deprecated: use [SetBracketedPasteMode], [ResetBracketedPasteMode], and
// [RequestBracketedPasteMode] instead.
const (
EnableBracketedPaste = "\x1b[?2004h" //nolint:revive // grouped constants
DisableBracketedPaste = "\x1b[?2004l"
RequestBracketedPaste = "\x1b[?2004$p"
) )
// Synchronized Output Mode is a mode that determines whether output is // Synchronized Output Mode is a mode that determines whether output is
@ -729,23 +617,11 @@ const (
// //
// See: https://gist.github.com/christianparpart/d8a62cc1ab659194337d73e399004036 // See: https://gist.github.com/christianparpart/d8a62cc1ab659194337d73e399004036
const ( const (
SynchronizedOutputMode = DECMode(2026) ModeSynchronizedOutput = DECMode(2026)
SetSynchronizedOutputMode = "\x1b[?2026h" SetModeSynchronizedOutput = "\x1b[?2026h"
ResetSynchronizedOutputMode = "\x1b[?2026l" ResetModeSynchronizedOutput = "\x1b[?2026l"
RequestSynchronizedOutputMode = "\x1b[?2026$p" RequestModeSynchronizedOutput = "\x1b[?2026$p"
)
// Synchronized Output Mode. See [SynchronizedOutputMode].
//
// Deprecated: use [SynchronizedOutputMode], [SetSynchronizedOutputMode], and
// [ResetSynchronizedOutputMode], and [RequestSynchronizedOutputMode] instead.
const (
SyncdOutputMode = DECMode(2026)
EnableSyncdOutput = "\x1b[?2026h"
DisableSyncdOutput = "\x1b[?2026l"
RequestSyncdOutput = "\x1b[?2026$p"
) )
// Unicode Core Mode is a mode that determines whether the terminal should use // Unicode Core Mode is a mode that determines whether the terminal should use
@ -754,41 +630,16 @@ const (
// //
// See: https://github.com/contour-terminal/terminal-unicode-core // See: https://github.com/contour-terminal/terminal-unicode-core
const ( const (
UnicodeCoreMode = DECMode(2027) ModeUnicodeCore = DECMode(2027)
SetUnicodeCoreMode = "\x1b[?2027h" SetModeUnicodeCore = "\x1b[?2027h"
ResetUnicodeCoreMode = "\x1b[?2027l" ResetModeUnicodeCore = "\x1b[?2027l"
RequestUnicodeCoreMode = "\x1b[?2027$p" RequestModeUnicodeCore = "\x1b[?2027$p"
) )
// Grapheme Clustering Mode is a mode that determines whether the terminal
// should look for grapheme clusters instead of single runes in the rendered
// text. This makes the terminal properly render combining characters such as
// emojis.
// //
// See: https://github.com/contour-terminal/terminal-unicode-core
//
// Deprecated: use [GraphemeClusteringMode], [SetUnicodeCoreMode],
// [ResetUnicodeCoreMode], and [RequestUnicodeCoreMode] instead.
const (
GraphemeClusteringMode = DECMode(2027)
SetGraphemeClusteringMode = "\x1b[?2027h" // ModeLightDark is a mode that enables reporting the operating system's color
ResetGraphemeClusteringMode = "\x1b[?2027l"
RequestGraphemeClusteringMode = "\x1b[?2027$p"
)
// Grapheme Clustering Mode. See [GraphemeClusteringMode].
//
// Deprecated: use [SetUnicodeCoreMode], [ResetUnicodeCoreMode], and
// [RequestUnicodeCoreMode] instead.
const (
EnableGraphemeClustering = "\x1b[?2027h"
DisableGraphemeClustering = "\x1b[?2027l"
RequestGraphemeClustering = "\x1b[?2027$p"
)
// LightDarkMode is a mode that enables reporting the operating system's color
// scheme (light or dark) preference. It reports the color scheme as a [DSR] // scheme (light or dark) preference. It reports the color scheme as a [DSR]
// and [LightDarkReport] escape sequences encoded as follows: // and [LightDarkReport] escape sequences encoded as follows:
// //
@ -802,14 +653,14 @@ const (
// //
// See: https://contour-terminal.org/vt-extensions/color-palette-update-notifications/ // See: https://contour-terminal.org/vt-extensions/color-palette-update-notifications/
const ( const (
LightDarkMode = DECMode(2031) ModeLightDark = DECMode(2031)
SetLightDarkMode = "\x1b[?2031h" SetModeLightDark = "\x1b[?2031h"
ResetLightDarkMode = "\x1b[?2031l" ResetModeLightDark = "\x1b[?2031l"
RequestLightDarkMode = "\x1b[?2031$p" RequestModeLightDark = "\x1b[?2031$p"
) )
// InBandResizeMode is a mode that reports terminal resize events as escape // ModeInBandResize is a mode that reports terminal resize events as escape
// sequences. This is useful for systems that do not support [SIGWINCH] like // sequences. This is useful for systems that do not support [SIGWINCH] like
// Windows. // Windows.
// //
@ -819,11 +670,11 @@ const (
// //
// See: https://gist.github.com/rockorager/e695fb2924d36b2bcf1fff4a3704bd83 // See: https://gist.github.com/rockorager/e695fb2924d36b2bcf1fff4a3704bd83
const ( const (
InBandResizeMode = DECMode(2048) ModeInBandResize = DECMode(2048)
SetInBandResizeMode = "\x1b[?2048h" SetModeInBandResize = "\x1b[?2048h"
ResetInBandResizeMode = "\x1b[?2048l" ResetModeInBandResize = "\x1b[?2048l"
RequestInBandResizeMode = "\x1b[?2048$p" RequestModeInBandResize = "\x1b[?2048$p"
) )
// Win32Input is a mode that determines whether input is processed by the // Win32Input is a mode that determines whether input is processed by the
@ -831,17 +682,9 @@ const (
// //
// See: https://github.com/microsoft/terminal/blob/main/doc/specs/%234999%20-%20Improved%20keyboard%20handling%20in%20Conpty.md // See: https://github.com/microsoft/terminal/blob/main/doc/specs/%234999%20-%20Improved%20keyboard%20handling%20in%20Conpty.md
const ( const (
Win32InputMode = DECMode(9001) ModeWin32Input = DECMode(9001)
SetWin32InputMode = "\x1b[?9001h" SetModeWin32Input = "\x1b[?9001h"
ResetWin32InputMode = "\x1b[?9001l" ResetModeWin32Input = "\x1b[?9001l"
RequestWin32InputMode = "\x1b[?9001$p" RequestModeWin32Input = "\x1b[?9001$p"
)
// Deprecated: use [SetWin32InputMode], [ResetWin32InputMode], and
// [RequestWin32InputMode] instead.
const (
EnableWin32Input = "\x1b[?9001h" //nolint:revive // grouped constants
DisableWin32Input = "\x1b[?9001l"
RequestWin32Input = "\x1b[?9001$p"
) )

View File

@ -0,0 +1,495 @@
package ansi
// Keyboard Action Mode (KAM) controls locking of the keyboard.
//
// Deprecated: use [ModeKeyboardAction] instead.
const (
KeyboardActionMode = ANSIMode(2)
SetKeyboardActionMode = "\x1b[2h"
ResetKeyboardActionMode = "\x1b[2l"
RequestKeyboardActionMode = "\x1b[2$p"
)
// Insert/Replace Mode (IRM) determines whether characters are inserted or replaced.
//
// Deprecated: use [ModeInsertReplace] instead.
const (
InsertReplaceMode = ANSIMode(4)
SetInsertReplaceMode = "\x1b[4h"
ResetInsertReplaceMode = "\x1b[4l"
RequestInsertReplaceMode = "\x1b[4$p"
)
// BiDirectional Support Mode (BDSM) determines whether the terminal supports bidirectional text.
//
// Deprecated: use [ModeBiDirectionalSupport] instead.
const (
BiDirectionalSupportMode = ANSIMode(8)
SetBiDirectionalSupportMode = "\x1b[8h"
ResetBiDirectionalSupportMode = "\x1b[8l"
RequestBiDirectionalSupportMode = "\x1b[8$p"
)
// Send Receive Mode (SRM) or Local Echo Mode determines whether the terminal echoes characters.
//
// Deprecated: use [ModeSendReceive] instead.
const (
SendReceiveMode = ANSIMode(12)
LocalEchoMode = SendReceiveMode
SetSendReceiveMode = "\x1b[12h"
ResetSendReceiveMode = "\x1b[12l"
RequestSendReceiveMode = "\x1b[12$p"
SetLocalEchoMode = "\x1b[12h"
ResetLocalEchoMode = "\x1b[12l"
RequestLocalEchoMode = "\x1b[12$p"
)
// Line Feed/New Line Mode (LNM) determines whether the terminal interprets line feed as new line.
//
// Deprecated: use [ModeLineFeedNewLine] instead.
const (
LineFeedNewLineMode = ANSIMode(20)
SetLineFeedNewLineMode = "\x1b[20h"
ResetLineFeedNewLineMode = "\x1b[20l"
RequestLineFeedNewLineMode = "\x1b[20$p"
)
// Cursor Keys Mode (DECCKM) determines whether cursor keys send ANSI or application sequences.
//
// Deprecated: use [ModeCursorKeys] instead.
const (
CursorKeysMode = DECMode(1)
SetCursorKeysMode = "\x1b[?1h"
ResetCursorKeysMode = "\x1b[?1l"
RequestCursorKeysMode = "\x1b[?1$p"
)
// Cursor Keys mode.
//
// Deprecated: use [SetModeCursorKeys] and [ResetModeCursorKeys] instead.
const (
EnableCursorKeys = "\x1b[?1h"
DisableCursorKeys = "\x1b[?1l"
)
// Origin Mode (DECOM) determines whether the cursor moves to home or margin position.
//
// Deprecated: use [ModeOrigin] instead.
const (
OriginMode = DECMode(6)
SetOriginMode = "\x1b[?6h"
ResetOriginMode = "\x1b[?6l"
RequestOriginMode = "\x1b[?6$p"
)
// Auto Wrap Mode (DECAWM) determines whether the cursor wraps to the next line.
//
// Deprecated: use [ModeAutoWrap] instead.
const (
AutoWrapMode = DECMode(7)
SetAutoWrapMode = "\x1b[?7h"
ResetAutoWrapMode = "\x1b[?7l"
RequestAutoWrapMode = "\x1b[?7$p"
)
// X10 Mouse Mode determines whether the mouse reports on button presses.
//
// Deprecated: use [ModeMouseX10] instead.
const (
X10MouseMode = DECMode(9)
SetX10MouseMode = "\x1b[?9h"
ResetX10MouseMode = "\x1b[?9l"
RequestX10MouseMode = "\x1b[?9$p"
)
// Text Cursor Enable Mode (DECTCEM) shows/hides the cursor.
//
// Deprecated: use [ModeTextCursorEnable] instead.
const (
TextCursorEnableMode = DECMode(25)
SetTextCursorEnableMode = "\x1b[?25h"
ResetTextCursorEnableMode = "\x1b[?25l"
RequestTextCursorEnableMode = "\x1b[?25$p"
)
// Text Cursor Enable mode.
//
// Deprecated: use [SetModeTextCursorEnable] and [ResetModeTextCursorEnable] instead.
const (
CursorEnableMode = DECMode(25)
RequestCursorVisibility = "\x1b[?25$p"
)
// Numeric Keypad Mode (DECNKM) determines whether the keypad sends application or numeric sequences.
//
// Deprecated: use [ModeNumericKeypad] instead.
const (
NumericKeypadMode = DECMode(66)
SetNumericKeypadMode = "\x1b[?66h"
ResetNumericKeypadMode = "\x1b[?66l"
RequestNumericKeypadMode = "\x1b[?66$p"
)
// Backarrow Key Mode (DECBKM) determines whether the backspace key sends backspace or delete.
//
// Deprecated: use [ModeBackarrowKey] instead.
const (
BackarrowKeyMode = DECMode(67)
SetBackarrowKeyMode = "\x1b[?67h"
ResetBackarrowKeyMode = "\x1b[?67l"
RequestBackarrowKeyMode = "\x1b[?67$p"
)
// Left Right Margin Mode (DECLRMM) determines whether left and right margins can be set.
//
// Deprecated: use [ModeLeftRightMargin] instead.
const (
LeftRightMarginMode = DECMode(69)
SetLeftRightMarginMode = "\x1b[?69h"
ResetLeftRightMarginMode = "\x1b[?69l"
RequestLeftRightMarginMode = "\x1b[?69$p"
)
// Normal Mouse Mode determines whether the mouse reports on button presses and releases.
//
// Deprecated: use [ModeMouseNormal] instead.
const (
NormalMouseMode = DECMode(1000)
SetNormalMouseMode = "\x1b[?1000h"
ResetNormalMouseMode = "\x1b[?1000l"
RequestNormalMouseMode = "\x1b[?1000$p"
)
// VT Mouse Tracking mode.
//
// Deprecated: use [ModeMouseNormal] instead.
const (
MouseMode = DECMode(1000)
EnableMouse = "\x1b[?1000h"
DisableMouse = "\x1b[?1000l"
RequestMouse = "\x1b[?1000$p"
)
// Highlight Mouse Tracking determines whether the mouse reports on button presses and highlighted cells.
//
// Deprecated: use [ModeMouseHighlight] instead.
const (
HighlightMouseMode = DECMode(1001)
SetHighlightMouseMode = "\x1b[?1001h"
ResetHighlightMouseMode = "\x1b[?1001l"
RequestHighlightMouseMode = "\x1b[?1001$p"
)
// VT Hilite Mouse Tracking mode.
//
// Deprecated: use [ModeMouseHighlight] instead.
const (
MouseHiliteMode = DECMode(1001)
EnableMouseHilite = "\x1b[?1001h"
DisableMouseHilite = "\x1b[?1001l"
RequestMouseHilite = "\x1b[?1001$p"
)
// Button Event Mouse Tracking reports button-motion events when a button is pressed.
//
// Deprecated: use [ModeMouseButtonEvent] instead.
const (
ButtonEventMouseMode = DECMode(1002)
SetButtonEventMouseMode = "\x1b[?1002h"
ResetButtonEventMouseMode = "\x1b[?1002l"
RequestButtonEventMouseMode = "\x1b[?1002$p"
)
// Cell Motion Mouse Tracking mode.
//
// Deprecated: use [ModeMouseButtonEvent] instead.
const (
MouseCellMotionMode = DECMode(1002)
EnableMouseCellMotion = "\x1b[?1002h"
DisableMouseCellMotion = "\x1b[?1002l"
RequestMouseCellMotion = "\x1b[?1002$p"
)
// Any Event Mouse Tracking reports all motion events.
//
// Deprecated: use [ModeMouseAnyEvent] instead.
const (
AnyEventMouseMode = DECMode(1003)
SetAnyEventMouseMode = "\x1b[?1003h"
ResetAnyEventMouseMode = "\x1b[?1003l"
RequestAnyEventMouseMode = "\x1b[?1003$p"
)
// All Mouse Tracking mode.
//
// Deprecated: use [ModeMouseAnyEvent] instead.
const (
MouseAllMotionMode = DECMode(1003)
EnableMouseAllMotion = "\x1b[?1003h"
DisableMouseAllMotion = "\x1b[?1003l"
RequestMouseAllMotion = "\x1b[?1003$p"
)
// Focus Event Mode determines whether the terminal reports focus and blur events.
//
// Deprecated: use [ModeFocusEvent] instead.
const (
FocusEventMode = DECMode(1004)
SetFocusEventMode = "\x1b[?1004h"
ResetFocusEventMode = "\x1b[?1004l"
RequestFocusEventMode = "\x1b[?1004$p"
)
// Focus reporting mode.
//
// Deprecated: use [SetModeFocusEvent], [ResetModeFocusEvent], and
// [RequestModeFocusEvent] instead.
const (
ReportFocusMode = DECMode(1004)
EnableReportFocus = "\x1b[?1004h"
DisableReportFocus = "\x1b[?1004l"
RequestReportFocus = "\x1b[?1004$p"
)
// UTF-8 Extended Mouse Mode changes the mouse tracking encoding to use UTF-8 parameters.
//
// Deprecated: use [ModeMouseExtUtf8] instead.
const (
Utf8ExtMouseMode = DECMode(1005)
SetUtf8ExtMouseMode = "\x1b[?1005h"
ResetUtf8ExtMouseMode = "\x1b[?1005l"
RequestUtf8ExtMouseMode = "\x1b[?1005$p"
)
// SGR Extended Mouse Mode changes the mouse tracking encoding to use SGR parameters.
//
// Deprecated: use [ModeMouseExtSgr] instead.
const (
SgrExtMouseMode = DECMode(1006)
SetSgrExtMouseMode = "\x1b[?1006h"
ResetSgrExtMouseMode = "\x1b[?1006l"
RequestSgrExtMouseMode = "\x1b[?1006$p"
)
// Mouse SGR Extended mode.
//
// Deprecated: use [ModeMouseExtSgr], [SetModeMouseExtSgr],
// [ResetModeMouseExtSgr], and [RequestModeMouseExtSgr] instead.
const (
MouseSgrExtMode = DECMode(1006)
EnableMouseSgrExt = "\x1b[?1006h"
DisableMouseSgrExt = "\x1b[?1006l"
RequestMouseSgrExt = "\x1b[?1006$p"
)
// URXVT Extended Mouse Mode changes the mouse tracking encoding to use an alternate encoding.
//
// Deprecated: use [ModeMouseUrxvtExt] instead.
const (
UrxvtExtMouseMode = DECMode(1015)
SetUrxvtExtMouseMode = "\x1b[?1015h"
ResetUrxvtExtMouseMode = "\x1b[?1015l"
RequestUrxvtExtMouseMode = "\x1b[?1015$p"
)
// SGR Pixel Extended Mouse Mode changes the mouse tracking encoding to use SGR parameters with pixel coordinates.
//
// Deprecated: use [ModeMouseExtSgrPixel] instead.
const (
SgrPixelExtMouseMode = DECMode(1016)
SetSgrPixelExtMouseMode = "\x1b[?1016h"
ResetSgrPixelExtMouseMode = "\x1b[?1016l"
RequestSgrPixelExtMouseMode = "\x1b[?1016$p"
)
// Alternate Screen Mode determines whether the alternate screen buffer is active.
//
// Deprecated: use [ModeAltScreen] instead.
const (
AltScreenMode = DECMode(1047)
SetAltScreenMode = "\x1b[?1047h"
ResetAltScreenMode = "\x1b[?1047l"
RequestAltScreenMode = "\x1b[?1047$p"
)
// Save Cursor Mode saves the cursor position.
//
// Deprecated: use [ModeSaveCursor] instead.
const (
SaveCursorMode = DECMode(1048)
SetSaveCursorMode = "\x1b[?1048h"
ResetSaveCursorMode = "\x1b[?1048l"
RequestSaveCursorMode = "\x1b[?1048$p"
)
// Alternate Screen Save Cursor Mode saves the cursor position and switches to alternate screen.
//
// Deprecated: use [ModeAltScreenSaveCursor] instead.
const (
AltScreenSaveCursorMode = DECMode(1049)
SetAltScreenSaveCursorMode = "\x1b[?1049h"
ResetAltScreenSaveCursorMode = "\x1b[?1049l"
RequestAltScreenSaveCursorMode = "\x1b[?1049$p"
)
// Alternate Screen Buffer mode.
//
// Deprecated: use [ModeAltScreenSaveCursor] instead.
const (
AltScreenBufferMode = DECMode(1049)
SetAltScreenBufferMode = "\x1b[?1049h"
ResetAltScreenBufferMode = "\x1b[?1049l"
RequestAltScreenBufferMode = "\x1b[?1049$p"
EnableAltScreenBuffer = "\x1b[?1049h"
DisableAltScreenBuffer = "\x1b[?1049l"
RequestAltScreenBuffer = "\x1b[?1049$p"
)
// Bracketed Paste Mode determines whether pasted text is bracketed with escape sequences.
//
// Deprecated: use [ModeBracketedPaste] instead.
const (
BracketedPasteMode = DECMode(2004)
SetBracketedPasteMode = "\x1b[?2004h"
ResetBracketedPasteMode = "\x1b[?2004l"
RequestBracketedPasteMode = "\x1b[?2004$p"
)
// Deprecated: use [SetModeBracketedPaste], [ResetModeBracketedPaste], and
// [RequestModeBracketedPaste] instead.
const (
EnableBracketedPaste = "\x1b[?2004h" //nolint:revive
DisableBracketedPaste = "\x1b[?2004l"
RequestBracketedPaste = "\x1b[?2004$p"
)
// Synchronized Output Mode determines whether output is synchronized with the terminal.
//
// Deprecated: use [ModeSynchronizedOutput] instead.
const (
SynchronizedOutputMode = DECMode(2026)
SetSynchronizedOutputMode = "\x1b[?2026h"
ResetSynchronizedOutputMode = "\x1b[?2026l"
RequestSynchronizedOutputMode = "\x1b[?2026$p"
)
// Synchronized output mode.
//
// Deprecated: use [ModeSynchronizedOutput], [SetModeSynchronizedOutput],
// [ResetModeSynchronizedOutput], and [RequestModeSynchronizedOutput] instead.
const (
SyncdOutputMode = DECMode(2026)
EnableSyncdOutput = "\x1b[?2026h"
DisableSyncdOutput = "\x1b[?2026l"
RequestSyncdOutput = "\x1b[?2026$p"
)
// Unicode Core Mode determines whether the terminal uses Unicode grapheme clustering.
//
// Deprecated: use [ModeUnicodeCore] instead.
const (
UnicodeCoreMode = DECMode(2027)
SetUnicodeCoreMode = "\x1b[?2027h"
ResetUnicodeCoreMode = "\x1b[?2027l"
RequestUnicodeCoreMode = "\x1b[?2027$p"
)
// Grapheme Clustering Mode determines whether the terminal looks for grapheme clusters.
//
// Deprecated: use [ModeUnicodeCore], [SetModeUnicodeCore],
// [ResetModeUnicodeCore], and [RequestModeUnicodeCore] instead.
const (
GraphemeClusteringMode = DECMode(2027)
SetGraphemeClusteringMode = "\x1b[?2027h"
ResetGraphemeClusteringMode = "\x1b[?2027l"
RequestGraphemeClusteringMode = "\x1b[?2027$p"
)
// Unicode Core mode.
//
// Deprecated: use [SetModeUnicodeCore], [ResetModeUnicodeCore], and
// [RequestModeUnicodeCore] instead.
const (
EnableGraphemeClustering = "\x1b[?2027h"
DisableGraphemeClustering = "\x1b[?2027l"
RequestGraphemeClustering = "\x1b[?2027$p"
)
// Light Dark Mode enables reporting the operating system's color scheme preference.
//
// Deprecated: use [ModeLightDark] instead.
const (
LightDarkMode = DECMode(2031)
SetLightDarkMode = "\x1b[?2031h"
ResetLightDarkMode = "\x1b[?2031l"
RequestLightDarkMode = "\x1b[?2031$p"
)
// In Band Resize Mode reports terminal resize events as escape sequences.
//
// Deprecated: use [ModeInBandResize] instead.
const (
InBandResizeMode = DECMode(2048)
SetInBandResizeMode = "\x1b[?2048h"
ResetInBandResizeMode = "\x1b[?2048l"
RequestInBandResizeMode = "\x1b[?2048$p"
)
// Win32Input determines whether input is processed by the Win32 console and Conpty.
//
// Deprecated: use [ModeWin32Input] instead.
const (
Win32InputMode = DECMode(9001)
SetWin32InputMode = "\x1b[?9001h"
ResetWin32InputMode = "\x1b[?9001l"
RequestWin32InputMode = "\x1b[?9001$p"
)
// Deprecated: use [SetModeWin32Input], [ResetModeWin32Input], and
// [RequestModeWin32Input] instead.
const (
EnableWin32Input = "\x1b[?9001h" //nolint:revive
DisableWin32Input = "\x1b[?9001l"
RequestWin32Input = "\x1b[?9001$p"
)

View File

@ -134,7 +134,7 @@ func EncodeMouseButton(b MouseButton, motion, shift, alt, ctrl bool) (m byte) {
m |= bitMotion m |= bitMotion
} }
return //nolint:nakedret return m
} }
// x10Offset is the offset for X10 mouse events. // x10Offset is the offset for X10 mouse events.

View File

@ -1,5 +1,10 @@
package ansi package ansi
import (
"fmt"
"strings"
)
// Notify sends a desktop notification using iTerm's OSC 9. // Notify sends a desktop notification using iTerm's OSC 9.
// //
// OSC 9 ; Mc ST // OSC 9 ; Mc ST
@ -11,3 +16,17 @@ package ansi
func Notify(s string) string { func Notify(s string) string {
return "\x1b]9;" + s + "\x07" return "\x1b]9;" + s + "\x07"
} }
// DesktopNotification sends a desktop notification based on the extensible OSC
// 99 escape code.
//
// OSC 99 ; <metadata> ; <payload> ST
// OSC 99 ; <metadata> ; <payload> BEL
//
// Where <metadata> is a colon-separated list of key-value pairs, and
// <payload> is the notification body.
//
// See: https://sw.kovidgoyal.net/kitty/desktop-notifications/
func DesktopNotification(payload string, metadata ...string) string {
return fmt.Sprintf("\x1b]99;%s;%s\x07", strings.Join(metadata, ":"), payload)
}

34
vendor/github.com/charmbracelet/x/ansi/palette.go generated vendored Normal file
View File

@ -0,0 +1,34 @@
package ansi
import (
"fmt"
"image/color"
)
// SetPalette sets the palette color for the given index. The index is a 16
// color index between 0 and 15. The color is a 24-bit RGB color.
//
// OSC P n rrggbb BEL
//
// Where n is the color index in hex (0-f), and rrggbb is the color in
// hexadecimal format (e.g., ff0000 for red).
//
// This sequence is specific to the Linux Console and may not work in other
// terminal emulators.
//
// See https://man7.org/linux/man-pages/man4/console_codes.4.html
func SetPalette(i int, c color.Color) string {
if c == nil || i < 0 || i > 15 {
return ""
}
r, g, b, _ := c.RGBA()
return fmt.Sprintf("\x1b]P%x%02x%02x%02x\x07", i, r>>8, g>>8, b>>8)
}
// ResetPalette resets the color palette to the default values.
//
// This sequence is specific to the Linux Console and may not work in other
// terminal emulators.
//
// See https://man7.org/linux/man-pages/man4/console_codes.4.html
const ResetPalette = "\x1b]R\x07"

View File

@ -4,8 +4,9 @@ import (
"unicode/utf8" "unicode/utf8"
"github.com/charmbracelet/x/ansi/parser" "github.com/charmbracelet/x/ansi/parser"
"github.com/clipperhouse/displaywidth"
"github.com/clipperhouse/uax29/v2/graphemes"
"github.com/mattn/go-runewidth" "github.com/mattn/go-runewidth"
"github.com/rivo/uniseg"
) )
// State represents the state of the ANSI escape sequence parser used by // State represents the state of the ANSI escape sequence parser used by
@ -176,10 +177,7 @@ func decodeSequence[T string | []byte](m Method, b T, state State, p *Parser) (s
} }
if utf8.RuneStart(c) { if utf8.RuneStart(c) {
seq, _, width, _ = FirstGraphemeCluster(b, -1) seq, width = FirstGraphemeCluster(b, m)
if m == WcWidth {
width = runewidth.StringWidth(string(seq))
}
i += len(seq) i += len(seq)
return b[:i], width, i, NormalState return b[:i], width, i, NormalState
} }
@ -434,17 +432,22 @@ func HasEscPrefix[T string | []byte](b T) bool {
return len(b) > 0 && b[0] == ESC return len(b) > 0 && b[0] == ESC
} }
// FirstGraphemeCluster returns the first grapheme cluster in the given string or byte slice. // FirstGraphemeCluster returns the first grapheme cluster in the given string
// This is a syntactic sugar function that wraps // or byte slice, and its monospace display width.
// uniseg.FirstGraphemeClusterInString and uniseg.FirstGraphemeCluster. func FirstGraphemeCluster[T string | []byte](b T, m Method) (T, int) {
func FirstGraphemeCluster[T string | []byte](b T, state int) (T, T, int, int) {
switch b := any(b).(type) { switch b := any(b).(type) {
case string: case string:
cluster, rest, width, newState := uniseg.FirstGraphemeClusterInString(b, state) cluster := graphemes.FromString(b).First()
return T(cluster), T(rest), width, newState if m == WcWidth {
return T(cluster), runewidth.StringWidth(cluster)
}
return T(cluster), displaywidth.String(cluster)
case []byte: case []byte:
cluster, rest, width, newState := uniseg.FirstGraphemeCluster(b, state) cluster := graphemes.FromBytes(b).First()
return T(cluster), T(rest), width, newState if m == WcWidth {
return T(cluster), runewidth.StringWidth(string(cluster))
}
return T(cluster), displaywidth.Bytes(cluster)
} }
panic("unreachable") panic("unreachable")
} }
@ -490,7 +493,7 @@ func Command(prefix, inter, final byte) (c int) {
c = int(final) c = int(final)
c |= int(prefix) << parser.PrefixShift c |= int(prefix) << parser.PrefixShift
c |= int(inter) << parser.IntermedShift c |= int(inter) << parser.IntermedShift
return return c
} }
// Param represents a sequence parameter. Sequence parameters with // Param represents a sequence parameter. Sequence parameters with
@ -520,5 +523,5 @@ func Parameter(p int, hasMore bool) (s int) {
if hasMore { if hasMore {
s |= parser.HasMoreFlag s |= parser.HasMoreFlag
} }
return return s
} }

49
vendor/github.com/charmbracelet/x/ansi/progress.go generated vendored Normal file
View File

@ -0,0 +1,49 @@
package ansi
import "strconv"
// ResetProgressBar is a sequence that resets the progress bar to its default
// state (hidden).
//
// OSC 9 ; 4 ; 0 BEL
//
// See: https://learn.microsoft.com/en-us/windows/terminal/tutorials/progress-bar-sequences
const ResetProgressBar = "\x1b]9;4;0\x07"
// SetProgressBar returns a sequence for setting the progress bar to a specific
// percentage (0-100) in the "default" state.
//
// OSC 9 ; 4 ; 1 Percentage BEL
//
// See: https://learn.microsoft.com/en-us/windows/terminal/tutorials/progress-bar-sequences
func SetProgressBar(percentage int) string {
return "\x1b]9;4;1;" + strconv.Itoa(min(max(0, percentage), 100)) + "\x07"
}
// SetErrorProgressBar returns a sequence for setting the progress bar to a
// specific percentage (0-100) in the "Error" state..
//
// OSC 9 ; 4 ; 2 Percentage BEL
//
// See: https://learn.microsoft.com/en-us/windows/terminal/tutorials/progress-bar-sequences
func SetErrorProgressBar(percentage int) string {
return "\x1b]9;4;2;" + strconv.Itoa(min(max(0, percentage), 100)) + "\x07"
}
// SetIndeterminateProgressBar is a sequence that sets the progress bar to the
// indeterminate state.
//
// OSC 9 ; 4 ; 3 BEL
//
// See: https://learn.microsoft.com/en-us/windows/terminal/tutorials/progress-bar-sequences
const SetIndeterminateProgressBar = "\x1b]9;4;3\x07"
// SetWarningProgressBar is a sequence that sets the progress bar to the
// "Warning" state.
//
// OSC 9 ; 4 ; 4 Percentage BEL
//
// See: https://learn.microsoft.com/en-us/windows/terminal/tutorials/progress-bar-sequences
func SetWarningProgressBar(percentage int) string {
return "\x1b]9;4;4;" + strconv.Itoa(min(max(0, percentage), 100)) + "\x07"
}

View File

@ -21,59 +21,59 @@ func SGR(ps ...Attr) string {
} }
var attrStrings = map[int]string{ var attrStrings = map[int]string{
ResetAttr: resetAttr, AttrReset: attrReset,
BoldAttr: boldAttr, AttrBold: attrBold,
FaintAttr: faintAttr, AttrFaint: attrFaint,
ItalicAttr: italicAttr, AttrItalic: attrItalic,
UnderlineAttr: underlineAttr, AttrUnderline: attrUnderline,
SlowBlinkAttr: slowBlinkAttr, AttrBlink: attrBlink,
RapidBlinkAttr: rapidBlinkAttr, AttrRapidBlink: attrRapidBlink,
ReverseAttr: reverseAttr, AttrReverse: attrReverse,
ConcealAttr: concealAttr, AttrConceal: attrConceal,
StrikethroughAttr: strikethroughAttr, AttrStrikethrough: attrStrikethrough,
NormalIntensityAttr: normalIntensityAttr, AttrNormalIntensity: attrNormalIntensity,
NoItalicAttr: noItalicAttr, AttrNoItalic: attrNoItalic,
NoUnderlineAttr: noUnderlineAttr, AttrNoUnderline: attrNoUnderline,
NoBlinkAttr: noBlinkAttr, AttrNoBlink: attrNoBlink,
NoReverseAttr: noReverseAttr, AttrNoReverse: attrNoReverse,
NoConcealAttr: noConcealAttr, AttrNoConceal: attrNoConceal,
NoStrikethroughAttr: noStrikethroughAttr, AttrNoStrikethrough: attrNoStrikethrough,
BlackForegroundColorAttr: blackForegroundColorAttr, AttrBlackForegroundColor: attrBlackForegroundColor,
RedForegroundColorAttr: redForegroundColorAttr, AttrRedForegroundColor: attrRedForegroundColor,
GreenForegroundColorAttr: greenForegroundColorAttr, AttrGreenForegroundColor: attrGreenForegroundColor,
YellowForegroundColorAttr: yellowForegroundColorAttr, AttrYellowForegroundColor: attrYellowForegroundColor,
BlueForegroundColorAttr: blueForegroundColorAttr, AttrBlueForegroundColor: attrBlueForegroundColor,
MagentaForegroundColorAttr: magentaForegroundColorAttr, AttrMagentaForegroundColor: attrMagentaForegroundColor,
CyanForegroundColorAttr: cyanForegroundColorAttr, AttrCyanForegroundColor: attrCyanForegroundColor,
WhiteForegroundColorAttr: whiteForegroundColorAttr, AttrWhiteForegroundColor: attrWhiteForegroundColor,
ExtendedForegroundColorAttr: extendedForegroundColorAttr, AttrExtendedForegroundColor: attrExtendedForegroundColor,
DefaultForegroundColorAttr: defaultForegroundColorAttr, AttrDefaultForegroundColor: attrDefaultForegroundColor,
BlackBackgroundColorAttr: blackBackgroundColorAttr, AttrBlackBackgroundColor: attrBlackBackgroundColor,
RedBackgroundColorAttr: redBackgroundColorAttr, AttrRedBackgroundColor: attrRedBackgroundColor,
GreenBackgroundColorAttr: greenBackgroundColorAttr, AttrGreenBackgroundColor: attrGreenBackgroundColor,
YellowBackgroundColorAttr: yellowBackgroundColorAttr, AttrYellowBackgroundColor: attrYellowBackgroundColor,
BlueBackgroundColorAttr: blueBackgroundColorAttr, AttrBlueBackgroundColor: attrBlueBackgroundColor,
MagentaBackgroundColorAttr: magentaBackgroundColorAttr, AttrMagentaBackgroundColor: attrMagentaBackgroundColor,
CyanBackgroundColorAttr: cyanBackgroundColorAttr, AttrCyanBackgroundColor: attrCyanBackgroundColor,
WhiteBackgroundColorAttr: whiteBackgroundColorAttr, AttrWhiteBackgroundColor: attrWhiteBackgroundColor,
ExtendedBackgroundColorAttr: extendedBackgroundColorAttr, AttrExtendedBackgroundColor: attrExtendedBackgroundColor,
DefaultBackgroundColorAttr: defaultBackgroundColorAttr, AttrDefaultBackgroundColor: attrDefaultBackgroundColor,
ExtendedUnderlineColorAttr: extendedUnderlineColorAttr, AttrExtendedUnderlineColor: attrExtendedUnderlineColor,
DefaultUnderlineColorAttr: defaultUnderlineColorAttr, AttrDefaultUnderlineColor: attrDefaultUnderlineColor,
BrightBlackForegroundColorAttr: brightBlackForegroundColorAttr, AttrBrightBlackForegroundColor: attrBrightBlackForegroundColor,
BrightRedForegroundColorAttr: brightRedForegroundColorAttr, AttrBrightRedForegroundColor: attrBrightRedForegroundColor,
BrightGreenForegroundColorAttr: brightGreenForegroundColorAttr, AttrBrightGreenForegroundColor: attrBrightGreenForegroundColor,
BrightYellowForegroundColorAttr: brightYellowForegroundColorAttr, AttrBrightYellowForegroundColor: attrBrightYellowForegroundColor,
BrightBlueForegroundColorAttr: brightBlueForegroundColorAttr, AttrBrightBlueForegroundColor: attrBrightBlueForegroundColor,
BrightMagentaForegroundColorAttr: brightMagentaForegroundColorAttr, AttrBrightMagentaForegroundColor: attrBrightMagentaForegroundColor,
BrightCyanForegroundColorAttr: brightCyanForegroundColorAttr, AttrBrightCyanForegroundColor: attrBrightCyanForegroundColor,
BrightWhiteForegroundColorAttr: brightWhiteForegroundColorAttr, AttrBrightWhiteForegroundColor: attrBrightWhiteForegroundColor,
BrightBlackBackgroundColorAttr: brightBlackBackgroundColorAttr, AttrBrightBlackBackgroundColor: attrBrightBlackBackgroundColor,
BrightRedBackgroundColorAttr: brightRedBackgroundColorAttr, AttrBrightRedBackgroundColor: attrBrightRedBackgroundColor,
BrightGreenBackgroundColorAttr: brightGreenBackgroundColorAttr, AttrBrightGreenBackgroundColor: attrBrightGreenBackgroundColor,
BrightYellowBackgroundColorAttr: brightYellowBackgroundColorAttr, AttrBrightYellowBackgroundColor: attrBrightYellowBackgroundColor,
BrightBlueBackgroundColorAttr: brightBlueBackgroundColorAttr, AttrBrightBlueBackgroundColor: attrBrightBlueBackgroundColor,
BrightMagentaBackgroundColorAttr: brightMagentaBackgroundColorAttr, AttrBrightMagentaBackgroundColor: attrBrightMagentaBackgroundColor,
BrightCyanBackgroundColorAttr: brightCyanBackgroundColorAttr, AttrBrightCyanBackgroundColor: attrBrightCyanBackgroundColor,
BrightWhiteBackgroundColorAttr: brightWhiteBackgroundColorAttr, AttrBrightWhiteBackgroundColor: attrBrightWhiteBackgroundColor,
} }

View File

@ -17,7 +17,9 @@ type Attr = int
// Style represents an ANSI SGR (Select Graphic Rendition) style. // Style represents an ANSI SGR (Select Graphic Rendition) style.
type Style []string type Style []string
// NewStyle returns a new style with the given attributes. // NewStyle returns a new style with the given attributes. Attributes are SGR
// (Select Graphic Rendition) codes that control text formatting like bold,
// italic, colors, etc.
func NewStyle(attrs ...Attr) Style { func NewStyle(attrs ...Attr) Style {
if len(attrs) == 0 { if len(attrs) == 0 {
return Style{} return Style{}
@ -46,7 +48,8 @@ func (s Style) String() string {
return "\x1b[" + strings.Join(s, ";") + "m" return "\x1b[" + strings.Join(s, ";") + "m"
} }
// Styled returns a styled string with the given style applied. // Styled returns a styled string with the given style applied. The style is
// applied at the beginning and reset at the end of the string.
func (s Style) Styled(str string) string { func (s Style) Styled(str string) string {
if len(s) == 0 { if len(s) == 0 {
return str return str
@ -54,161 +57,211 @@ func (s Style) Styled(str string) string {
return s.String() + str + ResetStyle return s.String() + str + ResetStyle
} }
// Reset appends the reset style attribute to the style. // Reset appends the reset style attribute to the style. This resets all
// formatting attributes to their defaults.
func (s Style) Reset() Style { func (s Style) Reset() Style {
return append(s, resetAttr) return append(s, attrReset)
} }
// Bold appends the bold style attribute to the style. // Bold appends the bold or normal intensity style attribute to the style.
// You can use [Style.Normal] to reset to normal intensity.
func (s Style) Bold() Style { func (s Style) Bold() Style {
return append(s, boldAttr) return append(s, attrBold)
} }
// Faint appends the faint style attribute to the style. // Faint appends the faint or normal intensity style attribute to the style.
// You can use [Style.Normal] to reset to normal intensity.
func (s Style) Faint() Style { func (s Style) Faint() Style {
return append(s, faintAttr) return append(s, attrFaint)
} }
// Italic appends the italic style attribute to the style. // Italic appends the italic or no italic style attribute to the style.
func (s Style) Italic() Style { // When v is true, text is rendered in italic. When false, italic is disabled.
return append(s, italicAttr) func (s Style) Italic(v bool) Style {
if v {
return append(s, attrItalic)
}
return append(s, attrNoItalic)
} }
// Underline appends the underline style attribute to the style. // Underline appends the underline or no underline style attribute to the style.
func (s Style) Underline() Style { // When v is true, text is underlined. When false, underline is disabled.
return append(s, underlineAttr) func (s Style) Underline(v bool) Style {
if v {
return append(s, attrUnderline)
}
return append(s, attrNoUnderline)
} }
// UnderlineStyle appends the underline style attribute to the style. // UnderlineStyle appends the underline style attribute to the style.
// Supports various underline styles including single, double, curly, dotted,
// and dashed.
func (s Style) UnderlineStyle(u UnderlineStyle) Style { func (s Style) UnderlineStyle(u UnderlineStyle) Style {
switch u { switch u {
case NoUnderlineStyle: case UnderlineStyleNone:
return s.NoUnderline() return s.Underline(false)
case SingleUnderlineStyle: case UnderlineStyleSingle:
return s.Underline() return s.Underline(true)
case DoubleUnderlineStyle: case UnderlineStyleDouble:
return append(s, doubleUnderlineStyle) return append(s, underlineStyleDouble)
case CurlyUnderlineStyle: case UnderlineStyleCurly:
return append(s, curlyUnderlineStyle) return append(s, underlineStyleCurly)
case DottedUnderlineStyle: case UnderlineStyleDotted:
return append(s, dottedUnderlineStyle) return append(s, underlineStyleDotted)
case DashedUnderlineStyle: case UnderlineStyleDashed:
return append(s, dashedUnderlineStyle) return append(s, underlineStyleDashed)
} }
return s return s
} }
// DoubleUnderline appends the double underline style attribute to the style. // Blink appends the slow blink or no blink style attribute to the style.
// This is a convenience method for UnderlineStyle(DoubleUnderlineStyle). // When v is true, text blinks slowly (less than 150 per minute). When false,
func (s Style) DoubleUnderline() Style { // blinking is disabled.
return s.UnderlineStyle(DoubleUnderlineStyle) func (s Style) Blink(v bool) Style {
if v {
return append(s, attrBlink)
}
return append(s, attrNoBlink)
} }
// CurlyUnderline appends the curly underline style attribute to the style. // RapidBlink appends the rapid blink or no blink style attribute to the style.
// This is a convenience method for UnderlineStyle(CurlyUnderlineStyle). // When v is true, text blinks rapidly (150+ per minute). When false, blinking
func (s Style) CurlyUnderline() Style { // is disabled.
return s.UnderlineStyle(CurlyUnderlineStyle) //
// Note that this is not widely supported in terminal emulators.
func (s Style) RapidBlink(v bool) Style {
if v {
return append(s, attrRapidBlink)
}
return append(s, attrNoBlink)
} }
// DottedUnderline appends the dotted underline style attribute to the style. // Reverse appends the reverse or no reverse style attribute to the style.
// This is a convenience method for UnderlineStyle(DottedUnderlineStyle). // When v is true, foreground and background colors are swapped. When false,
func (s Style) DottedUnderline() Style { // reverse video is disabled.
return s.UnderlineStyle(DottedUnderlineStyle) func (s Style) Reverse(v bool) Style {
if v {
return append(s, attrReverse)
}
return append(s, attrNoReverse)
} }
// DashedUnderline appends the dashed underline style attribute to the style. // Conceal appends the conceal or no conceal style attribute to the style.
// This is a convenience method for UnderlineStyle(DashedUnderlineStyle). // When v is true, text is hidden/concealed. When false, concealment is
func (s Style) DashedUnderline() Style { // disabled.
return s.UnderlineStyle(DashedUnderlineStyle) func (s Style) Conceal(v bool) Style {
if v {
return append(s, attrConceal)
}
return append(s, attrNoConceal)
} }
// SlowBlink appends the slow blink style attribute to the style. // Strikethrough appends the strikethrough or no strikethrough style attribute
func (s Style) SlowBlink() Style { // to the style. When v is true, text is rendered with a horizontal line through
return append(s, slowBlinkAttr) // it. When false, strikethrough is disabled.
func (s Style) Strikethrough(v bool) Style {
if v {
return append(s, attrStrikethrough)
}
return append(s, attrNoStrikethrough)
} }
// RapidBlink appends the rapid blink style attribute to the style. // Normal appends the normal intensity style attribute to the style. This
func (s Style) RapidBlink() Style { // resets [Style.Bold] and [Style.Faint] attributes.
return append(s, rapidBlinkAttr) func (s Style) Normal() Style {
} return append(s, attrNormalIntensity)
// Reverse appends the reverse style attribute to the style.
func (s Style) Reverse() Style {
return append(s, reverseAttr)
}
// Conceal appends the conceal style attribute to the style.
func (s Style) Conceal() Style {
return append(s, concealAttr)
}
// Strikethrough appends the strikethrough style attribute to the style.
func (s Style) Strikethrough() Style {
return append(s, strikethroughAttr)
}
// NormalIntensity appends the normal intensity style attribute to the style.
func (s Style) NormalIntensity() Style {
return append(s, normalIntensityAttr)
} }
// NoItalic appends the no italic style attribute to the style. // NoItalic appends the no italic style attribute to the style.
//
// Deprecated: use [Style.Italic](false) instead.
func (s Style) NoItalic() Style { func (s Style) NoItalic() Style {
return append(s, noItalicAttr) return append(s, attrNoItalic)
} }
// NoUnderline appends the no underline style attribute to the style. // NoUnderline appends the no underline style attribute to the style.
//
// Deprecated: use [Style.Underline](false) instead.
func (s Style) NoUnderline() Style { func (s Style) NoUnderline() Style {
return append(s, noUnderlineAttr) return append(s, attrNoUnderline)
} }
// NoBlink appends the no blink style attribute to the style. // NoBlink appends the no blink style attribute to the style.
//
// Deprecated: use [Style.Blink](false) or [Style.RapidBlink](false) instead.
func (s Style) NoBlink() Style { func (s Style) NoBlink() Style {
return append(s, noBlinkAttr) return append(s, attrNoBlink)
} }
// NoReverse appends the no reverse style attribute to the style. // NoReverse appends the no reverse style attribute to the style.
//
// Deprecated: use [Style.Reverse](false) instead.
func (s Style) NoReverse() Style { func (s Style) NoReverse() Style {
return append(s, noReverseAttr) return append(s, attrNoReverse)
} }
// NoConceal appends the no conceal style attribute to the style. // NoConceal appends the no conceal style attribute to the style.
//
// Deprecated: use [Style.Conceal](false) instead.
func (s Style) NoConceal() Style { func (s Style) NoConceal() Style {
return append(s, noConcealAttr) return append(s, attrNoConceal)
} }
// NoStrikethrough appends the no strikethrough style attribute to the style. // NoStrikethrough appends the no strikethrough style attribute to the style.
//
// Deprecated: use [Style.Strikethrough](false) instead.
func (s Style) NoStrikethrough() Style { func (s Style) NoStrikethrough() Style {
return append(s, noStrikethroughAttr) return append(s, attrNoStrikethrough)
} }
// DefaultForegroundColor appends the default foreground color style attribute to the style. // DefaultForegroundColor appends the default foreground color style attribute to the style.
//
// Deprecated: use [Style.ForegroundColor](nil) instead.
func (s Style) DefaultForegroundColor() Style { func (s Style) DefaultForegroundColor() Style {
return append(s, defaultForegroundColorAttr) return append(s, attrDefaultForegroundColor)
} }
// DefaultBackgroundColor appends the default background color style attribute to the style. // DefaultBackgroundColor appends the default background color style attribute to the style.
//
// Deprecated: use [Style.BackgroundColor](nil) instead.
func (s Style) DefaultBackgroundColor() Style { func (s Style) DefaultBackgroundColor() Style {
return append(s, defaultBackgroundColorAttr) return append(s, attrDefaultBackgroundColor)
} }
// DefaultUnderlineColor appends the default underline color style attribute to the style. // DefaultUnderlineColor appends the default underline color style attribute to the style.
//
// Deprecated: use [Style.UnderlineColor](nil) instead.
func (s Style) DefaultUnderlineColor() Style { func (s Style) DefaultUnderlineColor() Style {
return append(s, defaultUnderlineColorAttr) return append(s, attrDefaultUnderlineColor)
} }
// ForegroundColor appends the foreground color style attribute to the style. // ForegroundColor appends the foreground color style attribute to the style.
// If c is nil, the default foreground color is used. Supports [BasicColor],
// [IndexedColor] (256-color), and [color.Color] (24-bit RGB).
func (s Style) ForegroundColor(c Color) Style { func (s Style) ForegroundColor(c Color) Style {
if c == nil {
return append(s, attrDefaultForegroundColor)
}
return append(s, foregroundColorString(c)) return append(s, foregroundColorString(c))
} }
// BackgroundColor appends the background color style attribute to the style. // BackgroundColor appends the background color style attribute to the style.
// If c is nil, the default background color is used. Supports [BasicColor],
// [IndexedColor] (256-color), and [color.Color] (24-bit RGB).
func (s Style) BackgroundColor(c Color) Style { func (s Style) BackgroundColor(c Color) Style {
if c == nil {
return append(s, attrDefaultBackgroundColor)
}
return append(s, backgroundColorString(c)) return append(s, backgroundColorString(c))
} }
// UnderlineColor appends the underline color style attribute to the style. // UnderlineColor appends the underline color style attribute to the style.
// If c is nil, the default underline color is used. Supports [BasicColor],
// [IndexedColor] (256-color), and [color.Color] (24-bit RGB).
func (s Style) UnderlineColor(c Color) Style { func (s Style) UnderlineColor(c Color) Style {
if c == nil {
return append(s, attrDefaultUnderlineColor)
}
return append(s, underlineColorString(c)) return append(s, underlineColorString(c))
} }
@ -217,146 +270,216 @@ func (s Style) UnderlineColor(c Color) Style {
type UnderlineStyle = byte type UnderlineStyle = byte
const ( const (
doubleUnderlineStyle = "4:2" underlineStyleDouble = "4:2"
curlyUnderlineStyle = "4:3" underlineStyleCurly = "4:3"
dottedUnderlineStyle = "4:4" underlineStyleDotted = "4:4"
dashedUnderlineStyle = "4:5" underlineStyleDashed = "4:5"
) )
// Underline styles constants.
const (
UnderlineStyleNone UnderlineStyle = iota
UnderlineStyleSingle
UnderlineStyleDouble
UnderlineStyleCurly
UnderlineStyleDotted
UnderlineStyleDashed
)
// Underline styles constants.
//
// Deprecated: use [UnderlineStyleNone], [UnderlineStyleSingle], etc. instead.
const ( const (
// NoUnderlineStyle is the default underline style.
NoUnderlineStyle UnderlineStyle = iota NoUnderlineStyle UnderlineStyle = iota
// SingleUnderlineStyle is a single underline style.
SingleUnderlineStyle SingleUnderlineStyle
// DoubleUnderlineStyle is a double underline style.
DoubleUnderlineStyle DoubleUnderlineStyle
// CurlyUnderlineStyle is a curly underline style.
CurlyUnderlineStyle CurlyUnderlineStyle
// DottedUnderlineStyle is a dotted underline style.
DottedUnderlineStyle DottedUnderlineStyle
// DashedUnderlineStyle is a dashed underline style.
DashedUnderlineStyle DashedUnderlineStyle
) )
// SGR (Select Graphic Rendition) style attributes. // SGR (Select Graphic Rendition) style attributes.
// See: https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters // See: https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters
const ( const (
ResetAttr Attr = 0 AttrReset Attr = 0
BoldAttr Attr = 1 AttrBold Attr = 1
FaintAttr Attr = 2 AttrFaint Attr = 2
ItalicAttr Attr = 3 AttrItalic Attr = 3
UnderlineAttr Attr = 4 AttrUnderline Attr = 4
SlowBlinkAttr Attr = 5 AttrBlink Attr = 5
RapidBlinkAttr Attr = 6 AttrRapidBlink Attr = 6
ReverseAttr Attr = 7 AttrReverse Attr = 7
ConcealAttr Attr = 8 AttrConceal Attr = 8
StrikethroughAttr Attr = 9 AttrStrikethrough Attr = 9
NormalIntensityAttr Attr = 22 AttrNormalIntensity Attr = 22
NoItalicAttr Attr = 23 AttrNoItalic Attr = 23
NoUnderlineAttr Attr = 24 AttrNoUnderline Attr = 24
NoBlinkAttr Attr = 25 AttrNoBlink Attr = 25
NoReverseAttr Attr = 27 AttrNoReverse Attr = 27
NoConcealAttr Attr = 28 AttrNoConceal Attr = 28
NoStrikethroughAttr Attr = 29 AttrNoStrikethrough Attr = 29
BlackForegroundColorAttr Attr = 30 AttrBlackForegroundColor Attr = 30
RedForegroundColorAttr Attr = 31 AttrRedForegroundColor Attr = 31
GreenForegroundColorAttr Attr = 32 AttrGreenForegroundColor Attr = 32
YellowForegroundColorAttr Attr = 33 AttrYellowForegroundColor Attr = 33
BlueForegroundColorAttr Attr = 34 AttrBlueForegroundColor Attr = 34
MagentaForegroundColorAttr Attr = 35 AttrMagentaForegroundColor Attr = 35
CyanForegroundColorAttr Attr = 36 AttrCyanForegroundColor Attr = 36
WhiteForegroundColorAttr Attr = 37 AttrWhiteForegroundColor Attr = 37
ExtendedForegroundColorAttr Attr = 38 AttrExtendedForegroundColor Attr = 38
DefaultForegroundColorAttr Attr = 39 AttrDefaultForegroundColor Attr = 39
BlackBackgroundColorAttr Attr = 40 AttrBlackBackgroundColor Attr = 40
RedBackgroundColorAttr Attr = 41 AttrRedBackgroundColor Attr = 41
GreenBackgroundColorAttr Attr = 42 AttrGreenBackgroundColor Attr = 42
YellowBackgroundColorAttr Attr = 43 AttrYellowBackgroundColor Attr = 43
BlueBackgroundColorAttr Attr = 44 AttrBlueBackgroundColor Attr = 44
MagentaBackgroundColorAttr Attr = 45 AttrMagentaBackgroundColor Attr = 45
CyanBackgroundColorAttr Attr = 46 AttrCyanBackgroundColor Attr = 46
WhiteBackgroundColorAttr Attr = 47 AttrWhiteBackgroundColor Attr = 47
ExtendedBackgroundColorAttr Attr = 48 AttrExtendedBackgroundColor Attr = 48
DefaultBackgroundColorAttr Attr = 49 AttrDefaultBackgroundColor Attr = 49
ExtendedUnderlineColorAttr Attr = 58 AttrExtendedUnderlineColor Attr = 58
DefaultUnderlineColorAttr Attr = 59 AttrDefaultUnderlineColor Attr = 59
BrightBlackForegroundColorAttr Attr = 90 AttrBrightBlackForegroundColor Attr = 90
BrightRedForegroundColorAttr Attr = 91 AttrBrightRedForegroundColor Attr = 91
BrightGreenForegroundColorAttr Attr = 92 AttrBrightGreenForegroundColor Attr = 92
BrightYellowForegroundColorAttr Attr = 93 AttrBrightYellowForegroundColor Attr = 93
BrightBlueForegroundColorAttr Attr = 94 AttrBrightBlueForegroundColor Attr = 94
BrightMagentaForegroundColorAttr Attr = 95 AttrBrightMagentaForegroundColor Attr = 95
BrightCyanForegroundColorAttr Attr = 96 AttrBrightCyanForegroundColor Attr = 96
BrightWhiteForegroundColorAttr Attr = 97 AttrBrightWhiteForegroundColor Attr = 97
BrightBlackBackgroundColorAttr Attr = 100 AttrBrightBlackBackgroundColor Attr = 100
BrightRedBackgroundColorAttr Attr = 101 AttrBrightRedBackgroundColor Attr = 101
BrightGreenBackgroundColorAttr Attr = 102 AttrBrightGreenBackgroundColor Attr = 102
BrightYellowBackgroundColorAttr Attr = 103 AttrBrightYellowBackgroundColor Attr = 103
BrightBlueBackgroundColorAttr Attr = 104 AttrBrightBlueBackgroundColor Attr = 104
BrightMagentaBackgroundColorAttr Attr = 105 AttrBrightMagentaBackgroundColor Attr = 105
BrightCyanBackgroundColorAttr Attr = 106 AttrBrightCyanBackgroundColor Attr = 106
BrightWhiteBackgroundColorAttr Attr = 107 AttrBrightWhiteBackgroundColor Attr = 107
RGBColorIntroducerAttr Attr = 2 AttrRGBColorIntroducer Attr = 2
ExtendedColorIntroducerAttr Attr = 5 AttrExtendedColorIntroducer Attr = 5
)
// SGR (Select Graphic Rendition) style attributes.
//
// Deprecated: use Attr* constants instead.
const (
ResetAttr = AttrReset
BoldAttr = AttrBold
FaintAttr = AttrFaint
ItalicAttr = AttrItalic
UnderlineAttr = AttrUnderline
SlowBlinkAttr = AttrBlink
RapidBlinkAttr = AttrRapidBlink
ReverseAttr = AttrReverse
ConcealAttr = AttrConceal
StrikethroughAttr = AttrStrikethrough
NormalIntensityAttr = AttrNormalIntensity
NoItalicAttr = AttrNoItalic
NoUnderlineAttr = AttrNoUnderline
NoBlinkAttr = AttrNoBlink
NoReverseAttr = AttrNoReverse
NoConcealAttr = AttrNoConceal
NoStrikethroughAttr = AttrNoStrikethrough
BlackForegroundColorAttr = AttrBlackForegroundColor
RedForegroundColorAttr = AttrRedForegroundColor
GreenForegroundColorAttr = AttrGreenForegroundColor
YellowForegroundColorAttr = AttrYellowForegroundColor
BlueForegroundColorAttr = AttrBlueForegroundColor
MagentaForegroundColorAttr = AttrMagentaForegroundColor
CyanForegroundColorAttr = AttrCyanForegroundColor
WhiteForegroundColorAttr = AttrWhiteForegroundColor
ExtendedForegroundColorAttr = AttrExtendedForegroundColor
DefaultForegroundColorAttr = AttrDefaultForegroundColor
BlackBackgroundColorAttr = AttrBlackBackgroundColor
RedBackgroundColorAttr = AttrRedBackgroundColor
GreenBackgroundColorAttr = AttrGreenBackgroundColor
YellowBackgroundColorAttr = AttrYellowBackgroundColor
BlueBackgroundColorAttr = AttrBlueBackgroundColor
MagentaBackgroundColorAttr = AttrMagentaBackgroundColor
CyanBackgroundColorAttr = AttrCyanBackgroundColor
WhiteBackgroundColorAttr = AttrWhiteBackgroundColor
ExtendedBackgroundColorAttr = AttrExtendedBackgroundColor
DefaultBackgroundColorAttr = AttrDefaultBackgroundColor
ExtendedUnderlineColorAttr = AttrExtendedUnderlineColor
DefaultUnderlineColorAttr = AttrDefaultUnderlineColor
BrightBlackForegroundColorAttr = AttrBrightBlackForegroundColor
BrightRedForegroundColorAttr = AttrBrightRedForegroundColor
BrightGreenForegroundColorAttr = AttrBrightGreenForegroundColor
BrightYellowForegroundColorAttr = AttrBrightYellowForegroundColor
BrightBlueForegroundColorAttr = AttrBrightBlueForegroundColor
BrightMagentaForegroundColorAttr = AttrBrightMagentaForegroundColor
BrightCyanForegroundColorAttr = AttrBrightCyanForegroundColor
BrightWhiteForegroundColorAttr = AttrBrightWhiteForegroundColor
BrightBlackBackgroundColorAttr = AttrBrightBlackBackgroundColor
BrightRedBackgroundColorAttr = AttrBrightRedBackgroundColor
BrightGreenBackgroundColorAttr = AttrBrightGreenBackgroundColor
BrightYellowBackgroundColorAttr = AttrBrightYellowBackgroundColor
BrightBlueBackgroundColorAttr = AttrBrightBlueBackgroundColor
BrightMagentaBackgroundColorAttr = AttrBrightMagentaBackgroundColor
BrightCyanBackgroundColorAttr = AttrBrightCyanBackgroundColor
BrightWhiteBackgroundColorAttr = AttrBrightWhiteBackgroundColor
RGBColorIntroducerAttr = AttrRGBColorIntroducer
ExtendedColorIntroducerAttr = AttrExtendedColorIntroducer
) )
const ( const (
resetAttr = "0" attrReset = "0"
boldAttr = "1" attrBold = "1"
faintAttr = "2" attrFaint = "2"
italicAttr = "3" attrItalic = "3"
underlineAttr = "4" attrUnderline = "4"
slowBlinkAttr = "5" attrBlink = "5"
rapidBlinkAttr = "6" attrRapidBlink = "6"
reverseAttr = "7" attrReverse = "7"
concealAttr = "8" attrConceal = "8"
strikethroughAttr = "9" attrStrikethrough = "9"
normalIntensityAttr = "22" attrNormalIntensity = "22"
noItalicAttr = "23" attrNoItalic = "23"
noUnderlineAttr = "24" attrNoUnderline = "24"
noBlinkAttr = "25" attrNoBlink = "25"
noReverseAttr = "27" attrNoReverse = "27"
noConcealAttr = "28" attrNoConceal = "28"
noStrikethroughAttr = "29" attrNoStrikethrough = "29"
blackForegroundColorAttr = "30" attrBlackForegroundColor = "30"
redForegroundColorAttr = "31" attrRedForegroundColor = "31"
greenForegroundColorAttr = "32" attrGreenForegroundColor = "32"
yellowForegroundColorAttr = "33" attrYellowForegroundColor = "33"
blueForegroundColorAttr = "34" attrBlueForegroundColor = "34"
magentaForegroundColorAttr = "35" attrMagentaForegroundColor = "35"
cyanForegroundColorAttr = "36" attrCyanForegroundColor = "36"
whiteForegroundColorAttr = "37" attrWhiteForegroundColor = "37"
extendedForegroundColorAttr = "38" attrExtendedForegroundColor = "38"
defaultForegroundColorAttr = "39" attrDefaultForegroundColor = "39"
blackBackgroundColorAttr = "40" attrBlackBackgroundColor = "40"
redBackgroundColorAttr = "41" attrRedBackgroundColor = "41"
greenBackgroundColorAttr = "42" attrGreenBackgroundColor = "42"
yellowBackgroundColorAttr = "43" attrYellowBackgroundColor = "43"
blueBackgroundColorAttr = "44" attrBlueBackgroundColor = "44"
magentaBackgroundColorAttr = "45" attrMagentaBackgroundColor = "45"
cyanBackgroundColorAttr = "46" attrCyanBackgroundColor = "46"
whiteBackgroundColorAttr = "47" attrWhiteBackgroundColor = "47"
extendedBackgroundColorAttr = "48" attrExtendedBackgroundColor = "48"
defaultBackgroundColorAttr = "49" attrDefaultBackgroundColor = "49"
extendedUnderlineColorAttr = "58" attrExtendedUnderlineColor = "58"
defaultUnderlineColorAttr = "59" attrDefaultUnderlineColor = "59"
brightBlackForegroundColorAttr = "90" attrBrightBlackForegroundColor = "90"
brightRedForegroundColorAttr = "91" attrBrightRedForegroundColor = "91"
brightGreenForegroundColorAttr = "92" attrBrightGreenForegroundColor = "92"
brightYellowForegroundColorAttr = "93" attrBrightYellowForegroundColor = "93"
brightBlueForegroundColorAttr = "94" attrBrightBlueForegroundColor = "94"
brightMagentaForegroundColorAttr = "95" attrBrightMagentaForegroundColor = "95"
brightCyanForegroundColorAttr = "96" attrBrightCyanForegroundColor = "96"
brightWhiteForegroundColorAttr = "97" attrBrightWhiteForegroundColor = "97"
brightBlackBackgroundColorAttr = "100" attrBrightBlackBackgroundColor = "100"
brightRedBackgroundColorAttr = "101" attrBrightRedBackgroundColor = "101"
brightGreenBackgroundColorAttr = "102" attrBrightGreenBackgroundColor = "102"
brightYellowBackgroundColorAttr = "103" attrBrightYellowBackgroundColor = "103"
brightBlueBackgroundColorAttr = "104" attrBrightBlueBackgroundColor = "104"
brightMagentaBackgroundColorAttr = "105" attrBrightMagentaBackgroundColor = "105"
brightCyanBackgroundColorAttr = "106" attrBrightCyanBackgroundColor = "106"
brightWhiteBackgroundColorAttr = "107" attrBrightWhiteBackgroundColor = "107"
) )
// foregroundColorString returns the style SGR attribute for the given // foregroundColorString returns the style SGR attribute for the given
@ -369,37 +492,37 @@ func foregroundColorString(c Color) string {
// "3<n>" or "9<n>" where n is the color number from 0 to 7 // "3<n>" or "9<n>" where n is the color number from 0 to 7
switch c { switch c {
case Black: case Black:
return blackForegroundColorAttr return attrBlackForegroundColor
case Red: case Red:
return redForegroundColorAttr return attrRedForegroundColor
case Green: case Green:
return greenForegroundColorAttr return attrGreenForegroundColor
case Yellow: case Yellow:
return yellowForegroundColorAttr return attrYellowForegroundColor
case Blue: case Blue:
return blueForegroundColorAttr return attrBlueForegroundColor
case Magenta: case Magenta:
return magentaForegroundColorAttr return attrMagentaForegroundColor
case Cyan: case Cyan:
return cyanForegroundColorAttr return attrCyanForegroundColor
case White: case White:
return whiteForegroundColorAttr return attrWhiteForegroundColor
case BrightBlack: case BrightBlack:
return brightBlackForegroundColorAttr return attrBrightBlackForegroundColor
case BrightRed: case BrightRed:
return brightRedForegroundColorAttr return attrBrightRedForegroundColor
case BrightGreen: case BrightGreen:
return brightGreenForegroundColorAttr return attrBrightGreenForegroundColor
case BrightYellow: case BrightYellow:
return brightYellowForegroundColorAttr return attrBrightYellowForegroundColor
case BrightBlue: case BrightBlue:
return brightBlueForegroundColorAttr return attrBrightBlueForegroundColor
case BrightMagenta: case BrightMagenta:
return brightMagentaForegroundColorAttr return attrBrightMagentaForegroundColor
case BrightCyan: case BrightCyan:
return brightCyanForegroundColorAttr return attrBrightCyanForegroundColor
case BrightWhite: case BrightWhite:
return brightWhiteForegroundColorAttr return attrBrightWhiteForegroundColor
} }
case ExtendedColor: case ExtendedColor:
// 256-color ANSI foreground // 256-color ANSI foreground
@ -414,7 +537,7 @@ func foregroundColorString(c Color) string {
strconv.FormatUint(uint64(shift(g)), 10) + ";" + strconv.FormatUint(uint64(shift(g)), 10) + ";" +
strconv.FormatUint(uint64(shift(b)), 10) strconv.FormatUint(uint64(shift(b)), 10)
} }
return defaultForegroundColorAttr return attrDefaultForegroundColor
} }
// backgroundColorString returns the style SGR attribute for the given // backgroundColorString returns the style SGR attribute for the given
@ -427,37 +550,37 @@ func backgroundColorString(c Color) string {
// "4<n>" or "10<n>" where n is the color number from 0 to 7 // "4<n>" or "10<n>" where n is the color number from 0 to 7
switch c { switch c {
case Black: case Black:
return blackBackgroundColorAttr return attrBlackBackgroundColor
case Red: case Red:
return redBackgroundColorAttr return attrRedBackgroundColor
case Green: case Green:
return greenBackgroundColorAttr return attrGreenBackgroundColor
case Yellow: case Yellow:
return yellowBackgroundColorAttr return attrYellowBackgroundColor
case Blue: case Blue:
return blueBackgroundColorAttr return attrBlueBackgroundColor
case Magenta: case Magenta:
return magentaBackgroundColorAttr return attrMagentaBackgroundColor
case Cyan: case Cyan:
return cyanBackgroundColorAttr return attrCyanBackgroundColor
case White: case White:
return whiteBackgroundColorAttr return attrWhiteBackgroundColor
case BrightBlack: case BrightBlack:
return brightBlackBackgroundColorAttr return attrBrightBlackBackgroundColor
case BrightRed: case BrightRed:
return brightRedBackgroundColorAttr return attrBrightRedBackgroundColor
case BrightGreen: case BrightGreen:
return brightGreenBackgroundColorAttr return attrBrightGreenBackgroundColor
case BrightYellow: case BrightYellow:
return brightYellowBackgroundColorAttr return attrBrightYellowBackgroundColor
case BrightBlue: case BrightBlue:
return brightBlueBackgroundColorAttr return attrBrightBlueBackgroundColor
case BrightMagenta: case BrightMagenta:
return brightMagentaBackgroundColorAttr return attrBrightMagentaBackgroundColor
case BrightCyan: case BrightCyan:
return brightCyanBackgroundColorAttr return attrBrightCyanBackgroundColor
case BrightWhite: case BrightWhite:
return brightWhiteBackgroundColorAttr return attrBrightWhiteBackgroundColor
} }
case ExtendedColor: case ExtendedColor:
// 256-color ANSI foreground // 256-color ANSI foreground
@ -472,7 +595,7 @@ func backgroundColorString(c Color) string {
strconv.FormatUint(uint64(shift(g)), 10) + ";" + strconv.FormatUint(uint64(shift(g)), 10) + ";" +
strconv.FormatUint(uint64(shift(b)), 10) strconv.FormatUint(uint64(shift(b)), 10)
} }
return defaultBackgroundColorAttr return attrDefaultBackgroundColor
} }
// underlineColorString returns the style SGR attribute for the given underline // underlineColorString returns the style SGR attribute for the given underline
@ -498,7 +621,7 @@ func underlineColorString(c Color) string {
strconv.FormatUint(uint64(shift(g)), 10) + ";" + strconv.FormatUint(uint64(shift(g)), 10) + ";" +
strconv.FormatUint(uint64(shift(b)), 10) strconv.FormatUint(uint64(shift(b)), 10)
} }
return defaultUnderlineColorAttr return attrDefaultUnderlineColor
} }
// ReadStyleColor decodes a color from a slice of parameters. It returns the // ReadStyleColor decodes a color from a slice of parameters. It returns the
@ -526,7 +649,7 @@ func underlineColorString(c Color) string {
// 2. Support ignoring and omitting the color space id (second parameter) with respect to RGB colors // 2. Support ignoring and omitting the color space id (second parameter) with respect to RGB colors
// 3. Support ignoring and omitting the 6th parameter with respect to RGB and CMY colors // 3. Support ignoring and omitting the 6th parameter with respect to RGB and CMY colors
// 4. Support reading RGBA colors // 4. Support reading RGBA colors
func ReadStyleColor(params Params, co *color.Color) (n int) { func ReadStyleColor(params Params, co *color.Color) int {
if len(params) < 2 { // Need at least SGR type and color type if len(params) < 2 { // Need at least SGR type and color type
return 0 return 0
} }
@ -535,7 +658,7 @@ func ReadStyleColor(params Params, co *color.Color) (n int) {
s := params[0] s := params[0]
p := params[1] p := params[1]
colorType := p.Param(0) colorType := p.Param(0)
n = 2 n := 2
paramsfn := func() (p1, p2, p3, p4 int) { paramsfn := func() (p1, p2, p3, p4 int) {
// Where should we start reading the color? // Where should we start reading the color?
@ -594,7 +717,7 @@ func ReadStyleColor(params Params, co *color.Color) (n int) {
B: uint8(b), //nolint:gosec B: uint8(b), //nolint:gosec
A: 0xff, A: 0xff,
} }
return //nolint:nakedret return n
case 3: // CMY direct color case 3: // CMY direct color
if len(params) < 5 { if len(params) < 5 {
@ -612,7 +735,7 @@ func ReadStyleColor(params Params, co *color.Color) (n int) {
Y: uint8(y), //nolint:gosec Y: uint8(y), //nolint:gosec
K: 0, K: 0,
} }
return //nolint:nakedret return n
case 4: // CMYK direct color case 4: // CMYK direct color
if len(params) < 6 { if len(params) < 6 {
@ -630,7 +753,7 @@ func ReadStyleColor(params Params, co *color.Color) (n int) {
Y: uint8(y), //nolint:gosec Y: uint8(y), //nolint:gosec
K: uint8(k), //nolint:gosec K: uint8(k), //nolint:gosec
} }
return //nolint:nakedret return n
case 5: // indexed color case 5: // indexed color
if len(params) < 3 { if len(params) < 3 {
@ -665,7 +788,7 @@ func ReadStyleColor(params Params, co *color.Color) (n int) {
B: uint8(b), //nolint:gosec B: uint8(b), //nolint:gosec
A: uint8(a), //nolint:gosec A: uint8(a), //nolint:gosec
} }
return //nolint:nakedret return n
default: default:
return 0 return 0

View File

@ -1,11 +1,11 @@
package ansi package ansi
import ( import (
"bytes" "strings"
"github.com/charmbracelet/x/ansi/parser" "github.com/charmbracelet/x/ansi/parser"
"github.com/mattn/go-runewidth" "github.com/clipperhouse/displaywidth"
"github.com/rivo/uniseg" "github.com/clipperhouse/uax29/v2/graphemes"
) )
// Cut the string, without adding any prefix or tail strings. This function is // Cut the string, without adding any prefix or tail strings. This function is
@ -74,12 +74,11 @@ func truncate(m Method, s string, length int, tail string) string {
return "" return ""
} }
var cluster []byte var cluster string
var buf bytes.Buffer var buf strings.Builder
curWidth := 0 curWidth := 0
ignoring := false ignoring := false
pstate := parser.GroundState // initial state pstate := parser.GroundState // initial state
b := []byte(s)
i := 0 i := 0
// Here we iterate over the bytes of the string and collect printable // Here we iterate over the bytes of the string and collect printable
@ -88,16 +87,12 @@ func truncate(m Method, s string, length int, tail string) string {
// //
// Once we reach the given length, we start ignoring characters and only // Once we reach the given length, we start ignoring characters and only
// collect ANSI escape codes until we reach the end of string. // collect ANSI escape codes until we reach the end of string.
for i < len(b) { for i < len(s) {
state, action := parser.Table.Transition(pstate, b[i]) state, action := parser.Table.Transition(pstate, s[i])
if state == parser.Utf8State { if state == parser.Utf8State {
// This action happens when we transition to the Utf8State. // This action happens when we transition to the Utf8State.
var width int var width int
cluster, _, width, _ = uniseg.FirstGraphemeCluster(b[i:], -1) cluster, width = FirstGraphemeCluster(s[i:], m)
if m == WcWidth {
width = runewidth.StringWidth(string(cluster))
}
// increment the index by the length of the cluster // increment the index by the length of the cluster
i += len(cluster) i += len(cluster)
curWidth += width curWidth += width
@ -118,7 +113,7 @@ func truncate(m Method, s string, length int, tail string) string {
continue continue
} }
buf.Write(cluster) buf.WriteString(cluster)
// Done collecting, now we're back in the ground state. // Done collecting, now we're back in the ground state.
pstate = parser.GroundState pstate = parser.GroundState
@ -152,7 +147,7 @@ func truncate(m Method, s string, length int, tail string) string {
} }
fallthrough fallthrough
default: default:
buf.WriteByte(b[i]) buf.WriteByte(s[i])
i++ i++
} }
@ -193,27 +188,23 @@ func truncateLeft(m Method, s string, n int, prefix string) string {
return s return s
} }
var cluster []byte var cluster string
var buf bytes.Buffer var buf strings.Builder
curWidth := 0 curWidth := 0
ignoring := true ignoring := true
pstate := parser.GroundState pstate := parser.GroundState
b := []byte(s)
i := 0 i := 0
for i < len(b) { for i < len(s) {
if !ignoring { if !ignoring {
buf.Write(b[i:]) buf.WriteString(s[i:])
break break
} }
state, action := parser.Table.Transition(pstate, b[i]) state, action := parser.Table.Transition(pstate, s[i])
if state == parser.Utf8State { if state == parser.Utf8State {
var width int var width int
cluster, _, width, _ = uniseg.FirstGraphemeCluster(b[i:], -1) cluster, width = FirstGraphemeCluster(s[i:], m)
if m == WcWidth {
width = runewidth.StringWidth(string(cluster))
}
i += len(cluster) i += len(cluster)
curWidth += width curWidth += width
@ -224,7 +215,7 @@ func truncateLeft(m Method, s string, n int, prefix string) string {
} }
if curWidth > n { if curWidth > n {
buf.Write(cluster) buf.WriteString(cluster)
} }
if ignoring { if ignoring {
@ -259,7 +250,7 @@ func truncateLeft(m Method, s string, n int, prefix string) string {
} }
fallthrough fallthrough
default: default:
buf.WriteByte(b[i]) buf.WriteByte(s[i])
i++ i++
} }
@ -278,22 +269,22 @@ func truncateLeft(m Method, s string, n int, prefix string) string {
// You can use this with [Truncate], [TruncateLeft], and [Cut]. // You can use this with [Truncate], [TruncateLeft], and [Cut].
func ByteToGraphemeRange(str string, byteStart, byteStop int) (charStart, charStop int) { func ByteToGraphemeRange(str string, byteStart, byteStop int) (charStart, charStop int) {
bytePos, charPos := 0, 0 bytePos, charPos := 0, 0
gr := uniseg.NewGraphemes(str) gr := graphemes.FromString(str)
for byteStart > bytePos { for byteStart > bytePos {
if !gr.Next() { if !gr.Next() {
break break
} }
bytePos += len(gr.Str()) bytePos += len(gr.Value())
charPos += max(1, gr.Width()) charPos += max(1, displaywidth.String(gr.Value()))
} }
charStart = charPos charStart = charPos
for byteStop > bytePos { for byteStop > bytePos {
if !gr.Next() { if !gr.Next() {
break break
} }
bytePos += len(gr.Str()) bytePos += len(gr.Value())
charPos += max(1, gr.Width()) charPos += max(1, displaywidth.String(gr.Value()))
} }
charStop = charPos charStop = charPos
return return charStart, charStop
} }

17
vendor/github.com/charmbracelet/x/ansi/urxvt.go generated vendored Normal file
View File

@ -0,0 +1,17 @@
package ansi
import (
"fmt"
"strings"
)
// URxvtExt returns an escape sequence for calling a URxvt perl extension with
// the given name and parameters.
//
// OSC 777 ; extension_name ; param1 ; param2 ; ... ST
// OSC 777 ; extension_name ; param1 ; param2 ; ... BEL
//
// See: https://man.archlinux.org/man/extra/rxvt-unicode/urxvt.7.en#XTerm_Operating_System_Commands
func URxvtExt(extension string, params ...string) string {
return fmt.Sprintf("\x1b]777;%s;%s\x07", extension, strings.Join(params, ";"))
}

View File

@ -4,8 +4,6 @@ import (
"bytes" "bytes"
"github.com/charmbracelet/x/ansi/parser" "github.com/charmbracelet/x/ansi/parser"
"github.com/mattn/go-runewidth"
"github.com/rivo/uniseg"
) )
// Strip removes ANSI escape codes from a string. // Strip removes ANSI escape codes from a string.
@ -83,20 +81,16 @@ func stringWidth(m Method, s string) int {
} }
var ( var (
pstate = parser.GroundState // initial state pstate = parser.GroundState // initial state
cluster string width int
width int
) )
for i := 0; i < len(s); i++ { for i := 0; i < len(s); i++ {
state, action := parser.Table.Transition(pstate, s[i]) state, action := parser.Table.Transition(pstate, s[i])
if state == parser.Utf8State { if state == parser.Utf8State {
var w int cluster, w := FirstGraphemeCluster(s[i:], m)
cluster, _, w, _ = uniseg.FirstGraphemeClusterInString(s[i:], -1)
if m == WcWidth {
w = runewidth.StringWidth(cluster)
}
width += w width += w
i += len(cluster) - 1 i += len(cluster) - 1
pstate = parser.GroundState pstate = parser.GroundState
continue continue

View File

@ -8,16 +8,22 @@ import (
const ( const (
// ResizeWindowWinOp is a window operation that resizes the terminal // ResizeWindowWinOp is a window operation that resizes the terminal
// window. // window.
//
// Deprecated: Use constant number directly with [WindowOp].
ResizeWindowWinOp = 4 ResizeWindowWinOp = 4
// RequestWindowSizeWinOp is a window operation that requests a report of // RequestWindowSizeWinOp is a window operation that requests a report of
// the size of the terminal window in pixels. The response is in the form: // the size of the terminal window in pixels. The response is in the form:
// CSI 4 ; height ; width t // CSI 4 ; height ; width t
//
// Deprecated: Use constant number directly with [WindowOp].
RequestWindowSizeWinOp = 14 RequestWindowSizeWinOp = 14
// RequestCellSizeWinOp is a window operation that requests a report of // RequestCellSizeWinOp is a window operation that requests a report of
// the size of the terminal cell size in pixels. The response is in the form: // the size of the terminal cell size in pixels. The response is in the form:
// CSI 6 ; height ; width t // CSI 6 ; height ; width t
//
// Deprecated: Use constant number directly with [WindowOp].
RequestCellSizeWinOp = 16 RequestCellSizeWinOp = 16
) )

View File

@ -2,12 +2,11 @@ package ansi
import ( import (
"bytes" "bytes"
"strings"
"unicode" "unicode"
"unicode/utf8" "unicode/utf8"
"github.com/charmbracelet/x/ansi/parser" "github.com/charmbracelet/x/ansi/parser"
"github.com/mattn/go-runewidth"
"github.com/rivo/uniseg"
) )
// nbsp is a non-breaking space. // nbsp is a non-breaking space.
@ -55,12 +54,9 @@ func hardwrap(m Method, s string, limit int, preserveSpace bool) string {
i := 0 i := 0
for i < len(b) { for i < len(b) {
state, action := parser.Table.Transition(pstate, b[i]) state, action := parser.Table.Transition(pstate, b[i])
if state == parser.Utf8State { //nolint:nestif if state == parser.Utf8State {
var width int var width int
cluster, _, width, _ = uniseg.FirstGraphemeCluster(b[i:], -1) cluster, width = FirstGraphemeCluster(b[i:], m)
if m == WcWidth {
width = runewidth.StringWidth(string(cluster))
}
i += len(cluster) i += len(cluster)
if curWidth+width > limit { if curWidth+width > limit {
@ -192,10 +188,7 @@ func wordwrap(m Method, s string, limit int, breakpoints string) string {
state, action := parser.Table.Transition(pstate, b[i]) state, action := parser.Table.Transition(pstate, b[i])
if state == parser.Utf8State { //nolint:nestif if state == parser.Utf8State { //nolint:nestif
var width int var width int
cluster, _, width, _ = uniseg.FirstGraphemeCluster(b[i:], -1) cluster, width = FirstGraphemeCluster(b[i:], m)
if m == WcWidth {
width = runewidth.StringWidth(string(cluster))
}
i += len(cluster) i += len(cluster)
r, _ := utf8.DecodeRune(cluster) r, _ := utf8.DecodeRune(cluster)
@ -303,7 +296,7 @@ func wrap(m Method, s string, limit int, breakpoints string) string {
} }
var ( var (
cluster []byte cluster string
buf bytes.Buffer buf bytes.Buffer
word bytes.Buffer word bytes.Buffer
space bytes.Buffer space bytes.Buffer
@ -311,10 +304,12 @@ func wrap(m Method, s string, limit int, breakpoints string) string {
curWidth int // written width of the line curWidth int // written width of the line
wordLen int // word buffer len without ANSI escape codes wordLen int // word buffer len without ANSI escape codes
pstate = parser.GroundState // initial state pstate = parser.GroundState // initial state
b = []byte(s)
) )
addSpace := func() { addSpace := func() {
if spaceWidth == 0 && space.Len() == 0 {
return
}
curWidth += spaceWidth curWidth += spaceWidth
buf.Write(space.Bytes()) buf.Write(space.Bytes())
space.Reset() space.Reset()
@ -341,30 +336,27 @@ func wrap(m Method, s string, limit int, breakpoints string) string {
} }
i := 0 i := 0
for i < len(b) { for i < len(s) {
state, action := parser.Table.Transition(pstate, b[i]) state, action := parser.Table.Transition(pstate, s[i])
if state == parser.Utf8State { //nolint:nestif if state == parser.Utf8State { //nolint:nestif
var width int var width int
cluster, _, width, _ = uniseg.FirstGraphemeCluster(b[i:], -1) cluster, width = FirstGraphemeCluster(s[i:], m)
if m == WcWidth {
width = runewidth.StringWidth(string(cluster))
}
i += len(cluster) i += len(cluster)
r, _ := utf8.DecodeRune(cluster) r, _ := utf8.DecodeRuneInString(cluster)
switch { switch {
case r != utf8.RuneError && unicode.IsSpace(r) && r != nbsp: // nbsp is a non-breaking space case r != utf8.RuneError && unicode.IsSpace(r) && r != nbsp: // nbsp is a non-breaking space
addWord() addWord()
space.WriteRune(r) space.WriteRune(r)
spaceWidth += width spaceWidth += width
case bytes.ContainsAny(cluster, breakpoints): case strings.ContainsAny(cluster, breakpoints):
addSpace() addSpace()
if curWidth+wordLen+width > limit { if curWidth+wordLen+width > limit {
word.Write(cluster) word.WriteString(cluster)
wordLen += width wordLen += width
} else { } else {
addWord() addWord()
buf.Write(cluster) buf.WriteString(cluster)
curWidth += width curWidth += width
} }
default: default:
@ -373,12 +365,17 @@ func wrap(m Method, s string, limit int, breakpoints string) string {
addWord() addWord()
} }
word.Write(cluster) word.WriteString(cluster)
wordLen += width wordLen += width
if curWidth+wordLen+spaceWidth > limit { if curWidth+wordLen+spaceWidth > limit {
addNewline() addNewline()
} }
if wordLen == limit {
// Hardwrap the word if it's too long
addWord()
}
} }
pstate = parser.GroundState pstate = parser.GroundState
@ -387,7 +384,7 @@ func wrap(m Method, s string, limit int, breakpoints string) string {
switch action { switch action {
case parser.PrintAction, parser.ExecuteAction: case parser.PrintAction, parser.ExecuteAction:
switch r := rune(b[i]); { switch r := rune(s[i]); {
case r == '\n': case r == '\n':
if wordLen == 0 { if wordLen == 0 {
if curWidth+spaceWidth > limit { if curWidth+spaceWidth > limit {
@ -424,6 +421,7 @@ func wrap(m Method, s string, limit int, breakpoints string) string {
if curWidth == limit { if curWidth == limit {
addNewline() addNewline()
} }
word.WriteRune(r) word.WriteRune(r)
wordLen++ wordLen++
@ -438,7 +436,7 @@ func wrap(m Method, s string, limit int, breakpoints string) string {
} }
default: default:
word.WriteByte(b[i]) word.WriteByte(s[i])
} }
// We manage the UTF8 state separately manually above. // We manage the UTF8 state separately manually above.

View File

@ -1,3 +1,4 @@
// Package cellbuf provides terminal cell buffer functionality.
package cellbuf package cellbuf
import ( import (
@ -24,7 +25,7 @@ func NewCell(r rune, comb ...rune) (c *Cell) {
} }
c.Comb = comb c.Comb = comb
c.Width = runewidth.StringWidth(string(append([]rune{r}, comb...))) c.Width = runewidth.StringWidth(string(append([]rune{r}, comb...)))
return return c
} }
// NewCellString returns a new cell with the given string content. This is a // NewCellString returns a new cell with the given string content. This is a
@ -46,7 +47,7 @@ func NewCellString(s string) (c *Cell) {
c.Comb = append(c.Comb, r) c.Comb = append(c.Comb, r)
} }
} }
return return c
} }
// NewGraphemeCell returns a new cell. This is a convenience function that // NewGraphemeCell returns a new cell. This is a convenience function that
@ -71,7 +72,7 @@ func newGraphemeCell(s string, w int) (c *Cell) {
c.Comb = append(c.Comb, r) c.Comb = append(c.Comb, r)
} }
} }
return return c
} }
// Line represents a line in the terminal. // Line represents a line in the terminal.
@ -104,7 +105,7 @@ func (l Line) String() (s string) {
} }
} }
s = strings.TrimRight(s, " ") s = strings.TrimRight(s, " ")
return return s
} }
// At returns the cell at the given x position. // At returns the cell at the given x position.
@ -150,7 +151,7 @@ func (l Line) set(x int, c *Cell, clone bool) bool {
for j := 1; j < maxCellWidth && x-j >= 0; j++ { for j := 1; j < maxCellWidth && x-j >= 0; j++ {
wide := l.At(x - j) wide := l.At(x - j)
if wide != nil && wide.Width > 1 && j < wide.Width { if wide != nil && wide.Width > 1 && j < wide.Width {
for k := 0; k < wide.Width; k++ { for k := range wide.Width {
l[x-j+k] = wide.Clone().Blank() l[x-j+k] = wide.Clone().Blank()
} }
break break
@ -206,7 +207,7 @@ func (b *Buffer) String() (s string) {
s += "\r\n" s += "\r\n"
} }
} }
return return s
} }
// Line returns a pointer to the line at the given y position. // Line returns a pointer to the line at the given y position.
@ -296,7 +297,7 @@ func (b *Buffer) FillRect(c *Cell, rect Rectangle) {
} }
for y := rect.Min.Y; y < rect.Max.Y; y++ { for y := rect.Min.Y; y < rect.Max.Y; y++ {
for x := rect.Min.X; x < rect.Max.X; x += cellWidth { for x := rect.Min.X; x < rect.Max.X; x += cellWidth {
b.setCell(x, y, c, false) //nolint:errcheck b.setCell(x, y, c, false)
} }
} }
} }

View File

@ -96,7 +96,7 @@ func (c *Cell) Clear() bool {
func (c *Cell) Clone() (n *Cell) { func (c *Cell) Clone() (n *Cell) {
n = new(Cell) n = new(Cell)
*n = *c *n = *c
return return n
} }
// Blank makes the cell a blank cell by setting the rune to a space, comb to // Blank makes the cell a blank cell by setting the rune to a space, comb to
@ -164,12 +164,12 @@ type UnderlineStyle = ansi.UnderlineStyle
// These are the available underline styles. // These are the available underline styles.
const ( const (
NoUnderline = ansi.NoUnderlineStyle NoUnderline = ansi.UnderlineStyleNone
SingleUnderline = ansi.SingleUnderlineStyle SingleUnderline = ansi.UnderlineStyleSingle
DoubleUnderline = ansi.DoubleUnderlineStyle DoubleUnderline = ansi.UnderlineStyleDouble
CurlyUnderline = ansi.CurlyUnderlineStyle CurlyUnderline = ansi.UnderlineStyleCurly
DottedUnderline = ansi.DottedUnderlineStyle DottedUnderline = ansi.UnderlineStyleDotted
DashedUnderline = ansi.DashedUnderlineStyle DashedUnderline = ansi.UnderlineStyleDashed
) )
// Style represents the Style of a cell. // Style represents the Style of a cell.
@ -189,7 +189,7 @@ func (s Style) Sequence() string {
var b ansi.Style var b ansi.Style
if s.Attrs != 0 { if s.Attrs != 0 { //nolint:nestif
if s.Attrs&BoldAttr != 0 { if s.Attrs&BoldAttr != 0 {
b = b.Bold() b = b.Bold()
} }
@ -197,36 +197,31 @@ func (s Style) Sequence() string {
b = b.Faint() b = b.Faint()
} }
if s.Attrs&ItalicAttr != 0 { if s.Attrs&ItalicAttr != 0 {
b = b.Italic() b = b.Italic(true)
} }
if s.Attrs&SlowBlinkAttr != 0 { if s.Attrs&SlowBlinkAttr != 0 {
b = b.SlowBlink() b = b.Blink(true)
} }
if s.Attrs&RapidBlinkAttr != 0 { if s.Attrs&RapidBlinkAttr != 0 {
b = b.RapidBlink() b = b.RapidBlink(true)
} }
if s.Attrs&ReverseAttr != 0 { if s.Attrs&ReverseAttr != 0 {
b = b.Reverse() b = b.Reverse(true)
} }
if s.Attrs&ConcealAttr != 0 { if s.Attrs&ConcealAttr != 0 {
b = b.Conceal() b = b.Conceal(true)
} }
if s.Attrs&StrikethroughAttr != 0 { if s.Attrs&StrikethroughAttr != 0 {
b = b.Strikethrough() b = b.Strikethrough(true)
} }
} }
if s.UlStyle != NoUnderline { if s.UlStyle != NoUnderline {
switch s.UlStyle { switch u := s.UlStyle; u {
case SingleUnderline: case NoUnderline:
b = b.Underline() b = b.Underline(false)
case DoubleUnderline: default:
b = b.DoubleUnderline() b = b.Underline(true)
case CurlyUnderline: b = b.UnderlineStyle(u)
b = b.CurlyUnderline()
case DottedUnderline:
b = b.DottedUnderline()
case DashedUnderline:
b = b.DashedUnderline()
} }
} }
if s.Fg != nil { if s.Fg != nil {
@ -268,64 +263,48 @@ func (s Style) DiffSequence(o Style) string {
isNormal bool isNormal bool
) )
if s.Attrs != o.Attrs { if s.Attrs != o.Attrs { //nolint:nestif
if s.Attrs&BoldAttr != o.Attrs&BoldAttr { if s.Attrs&BoldAttr != o.Attrs&BoldAttr {
if s.Attrs&BoldAttr != 0 { if s.Attrs&BoldAttr != 0 {
b = b.Bold() b = b.Bold()
} else if !isNormal { } else if !isNormal {
isNormal = true isNormal = true
b = b.NormalIntensity() b = b.Normal()
} }
} }
if s.Attrs&FaintAttr != o.Attrs&FaintAttr { if s.Attrs&FaintAttr != o.Attrs&FaintAttr {
if s.Attrs&FaintAttr != 0 { if s.Attrs&FaintAttr != 0 {
b = b.Faint() b = b.Faint()
} else if !isNormal { } else if !isNormal {
b = b.NormalIntensity() b = b.Normal()
} }
} }
if s.Attrs&ItalicAttr != o.Attrs&ItalicAttr { if s.Attrs&ItalicAttr != o.Attrs&ItalicAttr {
if s.Attrs&ItalicAttr != 0 { b = b.Italic(s.Attrs&ItalicAttr != 0)
b = b.Italic()
} else {
b = b.NoItalic()
}
} }
if s.Attrs&SlowBlinkAttr != o.Attrs&SlowBlinkAttr { if s.Attrs&SlowBlinkAttr != o.Attrs&SlowBlinkAttr {
if s.Attrs&SlowBlinkAttr != 0 { if s.Attrs&SlowBlinkAttr != 0 {
b = b.SlowBlink() b = b.Blink(true)
} else if !noBlink { } else if !noBlink {
noBlink = true noBlink = true
b = b.NoBlink() b = b.Blink(false)
} }
} }
if s.Attrs&RapidBlinkAttr != o.Attrs&RapidBlinkAttr { if s.Attrs&RapidBlinkAttr != o.Attrs&RapidBlinkAttr {
if s.Attrs&RapidBlinkAttr != 0 { if s.Attrs&RapidBlinkAttr != 0 {
b = b.RapidBlink() b = b.RapidBlink(true)
} else if !noBlink { } else if !noBlink {
b = b.NoBlink() b = b.Blink(false)
} }
} }
if s.Attrs&ReverseAttr != o.Attrs&ReverseAttr { if s.Attrs&ReverseAttr != o.Attrs&ReverseAttr {
if s.Attrs&ReverseAttr != 0 { b = b.Reverse(s.Attrs&ReverseAttr != 0)
b = b.Reverse()
} else {
b = b.NoReverse()
}
} }
if s.Attrs&ConcealAttr != o.Attrs&ConcealAttr { if s.Attrs&ConcealAttr != o.Attrs&ConcealAttr {
if s.Attrs&ConcealAttr != 0 { b = b.Conceal(s.Attrs&ConcealAttr != 0)
b = b.Conceal()
} else {
b = b.NoConceal()
}
} }
if s.Attrs&StrikethroughAttr != o.Attrs&StrikethroughAttr { if s.Attrs&StrikethroughAttr != o.Attrs&StrikethroughAttr {
if s.Attrs&StrikethroughAttr != 0 { b = b.Strikethrough(s.Attrs&StrikethroughAttr != 0)
b = b.Strikethrough()
} else {
b = b.NoStrikethrough()
}
} }
} }

View File

@ -12,7 +12,7 @@ func Pos(x, y int) Position {
return image.Pt(x, y) return image.Pt(x, y)
} }
// Rectange represents a rectangle. // Rectangle represents a rectangle.
type Rectangle = image.Rectangle type Rectangle = image.Rectangle
// Rect is a shorthand for Rectangle. // Rect is a shorthand for Rectangle.

View File

@ -75,7 +75,7 @@ func (s *Screen) scrolln(n, top, bot, maxY int) (v bool) { //nolint:unparam
) )
blank := s.clearBlank() blank := s.clearBlank()
if n > 0 { if n > 0 { //nolint:nestif
// Scroll up (forward) // Scroll up (forward)
v = s.scrollUp(n, top, bot, 0, maxY, blank) v = s.scrollUp(n, top, bot, 0, maxY, blank)
if !v { if !v {
@ -99,7 +99,7 @@ func (s *Screen) scrolln(n, top, bot, maxY int) (v bool) { //nolint:unparam
s.move(0, bot-n+1) s.move(0, bot-n+1)
s.clearToBottom(nil) s.clearToBottom(nil)
} else { } else {
for i := 0; i < n; i++ { for i := range n {
s.move(0, bot-i) s.move(0, bot-i)
s.clearToEnd(nil, false) s.clearToEnd(nil, false)
} }
@ -124,7 +124,7 @@ func (s *Screen) scrolln(n, top, bot, maxY int) (v bool) { //nolint:unparam
// Clear newly shifted-in lines. // Clear newly shifted-in lines.
if v && if v &&
(nonDestScrollRegion || (memoryBelow && top == 0)) { (nonDestScrollRegion || (memoryBelow && top == 0)) {
for i := 0; i < -n; i++ { for i := range -n {
s.move(0, top+i) s.move(0, top+i)
s.clearToEnd(nil, false) s.clearToEnd(nil, false)
} }
@ -133,7 +133,7 @@ func (s *Screen) scrolln(n, top, bot, maxY int) (v bool) { //nolint:unparam
} }
if !v { if !v {
return return v
} }
s.scrollBuffer(s.curbuf, n, top, bot, blank) s.scrollBuffer(s.curbuf, n, top, bot, blank)
@ -193,7 +193,7 @@ func (s *Screen) touchLine(width, height, y, n int, changed bool) {
// scrollUp scrolls the screen up by n lines. // scrollUp scrolls the screen up by n lines.
func (s *Screen) scrollUp(n, top, bot, minY, maxY int, blank *Cell) bool { func (s *Screen) scrollUp(n, top, bot, minY, maxY int, blank *Cell) bool {
if n == 1 && top == minY && bot == maxY { if n == 1 && top == minY && bot == maxY { //nolint:nestif
s.move(0, bot) s.move(0, bot)
s.updatePen(blank) s.updatePen(blank)
s.buf.WriteByte('\n') s.buf.WriteByte('\n')
@ -202,13 +202,14 @@ func (s *Screen) scrollUp(n, top, bot, minY, maxY int, blank *Cell) bool {
s.updatePen(blank) s.updatePen(blank)
s.buf.WriteString(ansi.DeleteLine(1)) s.buf.WriteString(ansi.DeleteLine(1))
} else if top == minY && bot == maxY { } else if top == minY && bot == maxY {
if s.xtermLike { supportsSU := s.caps.Contains(capSU)
if supportsSU {
s.move(0, bot) s.move(0, bot)
} else { } else {
s.move(0, top) s.move(0, top)
} }
s.updatePen(blank) s.updatePen(blank)
if s.xtermLike { if supportsSU {
s.buf.WriteString(ansi.ScrollUp(n)) s.buf.WriteString(ansi.ScrollUp(n))
} else { } else {
s.buf.WriteString(strings.Repeat("\n", n)) s.buf.WriteString(strings.Repeat("\n", n))
@ -225,7 +226,7 @@ func (s *Screen) scrollUp(n, top, bot, minY, maxY int, blank *Cell) bool {
// scrollDown scrolls the screen down by n lines. // scrollDown scrolls the screen down by n lines.
func (s *Screen) scrollDown(n, top, bot, minY, maxY int, blank *Cell) bool { func (s *Screen) scrollDown(n, top, bot, minY, maxY int, blank *Cell) bool {
if n == 1 && top == minY && bot == maxY { if n == 1 && top == minY && bot == maxY { //nolint:nestif
s.move(0, top) s.move(0, top)
s.updatePen(blank) s.updatePen(blank)
s.buf.WriteString(ansi.ReverseIndex) s.buf.WriteString(ansi.ReverseIndex)
@ -236,7 +237,7 @@ func (s *Screen) scrollDown(n, top, bot, minY, maxY int, blank *Cell) bool {
} else if top == minY && bot == maxY { } else if top == minY && bot == maxY {
s.move(0, top) s.move(0, top)
s.updatePen(blank) s.updatePen(blank)
if s.xtermLike { if s.caps.Contains(capSD) {
s.buf.WriteString(ansi.ScrollDown(n)) s.buf.WriteString(ansi.ScrollDown(n))
} else { } else {
s.buf.WriteString(strings.Repeat(ansi.ReverseIndex, n)) s.buf.WriteString(strings.Repeat(ansi.ReverseIndex, n))

View File

@ -15,7 +15,7 @@ func hash(l Line) (h uint64) {
} }
h += (h << 5) + uint64(r) h += (h << 5) + uint64(r)
} }
return return h
} }
// hashmap represents a single [Line] hash. // hashmap represents a single [Line] hash.
@ -33,7 +33,7 @@ func (s *Screen) updateHashmap() {
height := s.newbuf.Height() height := s.newbuf.Height()
if len(s.oldhash) >= height && len(s.newhash) >= height { if len(s.oldhash) >= height && len(s.newhash) >= height {
// rehash changed lines // rehash changed lines
for i := 0; i < height; i++ { for i := range height {
_, ok := s.touch[i] _, ok := s.touch[i]
if ok { if ok {
s.oldhash[i] = hash(s.curbuf.Line(i)) s.oldhash[i] = hash(s.curbuf.Line(i))
@ -48,14 +48,14 @@ func (s *Screen) updateHashmap() {
if len(s.newhash) != height { if len(s.newhash) != height {
s.newhash = make([]uint64, height) s.newhash = make([]uint64, height)
} }
for i := 0; i < height; i++ { for i := range height {
s.oldhash[i] = hash(s.curbuf.Line(i)) s.oldhash[i] = hash(s.curbuf.Line(i))
s.newhash[i] = hash(s.newbuf.Line(i)) s.newhash[i] = hash(s.newbuf.Line(i))
} }
} }
s.hashtab = make([]hashmap, height*2) s.hashtab = make([]hashmap, height*2)
for i := 0; i < height; i++ { for i := range height {
hashval := s.oldhash[i] hashval := s.oldhash[i]
// Find matching hash or empty slot // Find matching hash or empty slot
@ -71,7 +71,7 @@ func (s *Screen) updateHashmap() {
s.hashtab[idx].oldcount++ s.hashtab[idx].oldcount++
s.hashtab[idx].oldindex = i s.hashtab[idx].oldindex = i
} }
for i := 0; i < height; i++ { for i := range height {
hashval := s.newhash[i] hashval := s.newhash[i]
// Find matching hash or empty slot // Find matching hash or empty slot
@ -130,7 +130,7 @@ func (s *Screen) updateHashmap() {
s.growHunks() s.growHunks()
} }
// scrollOldhash // scrollOldhash.
func (s *Screen) scrollOldhash(n, top, bot int) { func (s *Screen) scrollOldhash(n, top, bot int) {
if len(s.oldhash) == 0 { if len(s.oldhash) == 0 {
return return
@ -287,7 +287,7 @@ func (s *Screen) updateCost(from, to Line) (cost int) {
cost++ cost++
} }
} }
return return cost
} }
func (s *Screen) updateCostBlank(to Line) (cost int) { func (s *Screen) updateCostBlank(to Line) (cost int) {
@ -297,5 +297,5 @@ func (s *Screen) updateCostBlank(to Line) (cost int) {
cost++ cost++
} }
} }
return return cost
} }

View File

@ -4,7 +4,7 @@ import (
"github.com/charmbracelet/colorprofile" "github.com/charmbracelet/colorprofile"
) )
// Convert converts a hyperlink to respect the given color profile. // ConvertLink converts a hyperlink to respect the given color profile.
func ConvertLink(h Link, p colorprofile.Profile) Link { func ConvertLink(h Link, p colorprofile.Profile) Link {
if p == colorprofile.NoTTY { if p == colorprofile.NoTTY {
return Link{} return Link{}

Some files were not shown because too many files have changed in this diff Show More