Compare commits

...

16 Commits

Author SHA1 Message Date
94624bb16d WIP: feat: use compose-go
All checks were successful
continuous-integration/drone/push Build is passing
See #492
2026-04-04 19:39:24 +02:00
aae20f07cc fix: general golang path [ci skip] 2026-04-03 22:59:21 +02:00
6ef8e1ff52 test: temporarily skip flaky test
All checks were successful
continuous-integration/drone/push Build is passing
See #814
2026-04-01 11:37:07 +02:00
7fb9675b1e chore: golang 1.26 && make deps
Some checks failed
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is failing
2026-04-01 11:25:57 +02:00
d88b478503 test: unit tests clean up themselves
All checks were successful
continuous-integration/drone/push Build is passing
See #792
2026-04-01 10:47:32 +02:00
7a735043cd fix: unbork json [ci skip] 2026-03-30 09:40:13 +02:00
e610f32c35 fix: ignore new forked deps [ci skip] 2026-03-29 18:15:47 +02:00
e04a1e15c4 feat: add filename to error message when yaml file is invalid
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
Before:
FATA <internal/validate.go:84> unable to validate recipe: yaml: line 3: did not find expected key

After:
FATA <internal/validate.go:84> unable to validate recipe: <redacted>/recipes/monitoring-ng/compose.grafana-oidc.yml: yaml: line 3: did not find expected key
2026-03-20 14:42:14 +01:00
9d401202b4 test: ensure "release, fail, release" works
All checks were successful
continuous-integration/drone/push Build is passing
See #794
2026-03-05 11:28:34 +01:00
6504be6403 test: ensure reproducible version 2026-03-05 11:28:33 +01:00
d4944dbf35 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/
2026-03-03 00:03:00 +00:00
8d8d4f799d chore: i18n
All checks were successful
continuous-integration/drone/push Build is passing
2026-03-03 01:02:49 +01:00
0633f24d1b feat: recipe release reverts completely on failure
Some checks failed
continuous-integration/drone/push Build is failing
2026-03-03 00:02:15 +00:00
2e062899c7 feat: release make target
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2026-03-01 09:42:59 +01:00
fbd7275f03 build: remove broken goreleaser automagic
All checks were successful
continuous-integration/drone/push Build is passing
See #780
2026-03-01 09:40:29 +01:00
cedf185e97 chore: translation using Weblate (Spanish)
All checks were successful
continuous-integration/drone/push Build is passing
Currently translated at 91.1% (1048 of 1150 strings)

Translation: Co-op Cloud/abra
Translate-URL: https://translate.coopcloud.tech/projects/co-op-cloud/abra/es/
2026-02-28 13:52:56 +00:00
904 changed files with 94670 additions and 81106 deletions

View File

@ -3,7 +3,7 @@ kind: pipeline
name: coopcloud.tech/abra name: coopcloud.tech/abra
steps: steps:
- name: make check - name: make check
image: golang:1.24 image: golang:1.26
commands: commands:
- make check - make check
@ -22,7 +22,7 @@ steps:
- tag - tag
- name: xgettext-go status - name: xgettext-go status
image: golang:1.24-alpine3.22 image: golang:1.26-alpine3.22
commands: commands:
- apk add patchutils git make - apk add patchutils git make
- cd /drone/src - cd /drone/src
@ -38,42 +38,14 @@ steps:
- tag - tag
- name: make test - name: make test
image: golang:1.24 image: golang:1.26
environment: environment:
ABRA_DIR: $HOME/.abra ABRA_DIR: /root/.abra_test
CATL_URL: https://git.coopcloud.tech/toolshed/recipes-catalogue-json.git
commands: commands:
- mkdir -p $HOME/.abra
- git clone $CATL_URL $HOME/.abra/catalogue
- make test - make test
depends_on: depends_on:
- make check - make check
- name: fetch
image: docker:git
commands:
- git fetch --tags
depends_on:
- make check
- make test
when:
event: tag
- name: release
image: goreleaser/goreleaser:v2.5.1
environment:
GITEA_TOKEN:
from_secret: goreleaser_gitea_token
volumes:
- name: deps
path: /go
commands:
- goreleaser release
depends_on:
- fetch
when:
event: tag
- name: publish image - name: publish image
image: plugins/docker image: plugins/docker
settings: settings:

View File

@ -1,5 +1,5 @@
# Build image # Build image
FROM golang:1.24-alpine AS build FROM golang:1.26-alpine AS build
ENV GOPRIVATE=coopcloud.tech ENV GOPRIVATE=coopcloud.tech
@ -15,8 +15,7 @@ WORKDIR /app
RUN CGO_ENABLED=0 make build RUN CGO_ENABLED=0 make build
# Release image ("slim") FROM alpine:3.22
FROM alpine:3.19.1
RUN apk add --no-cache \ RUN apk add --no-cache \
ca-certificates \ ca-certificates \

View File

@ -2,7 +2,7 @@ ABRA := ./cmd/abra
XGETTEXT := ./bin/xgettext-go 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.26
LDFLAGS := "-X 'main.Commit=$(COMMIT)'" LDFLAGS := "-X 'main.Commit=$(COMMIT)'"
DIST_LDFLAGS := $(LDFLAGS)" -s -w" DIST_LDFLAGS := $(LDFLAGS)" -s -w"
BFLAGS := -v -trimpath BFLAGS := -v -trimpath
@ -40,7 +40,7 @@ check:
(echo "gofmt: formatting issue - run 'make format' to resolve" && exit 1) (echo "gofmt: formatting issue - run 'make format' to resolve" && exit 1)
test: test:
@go test ./... -cover -v @go test ./... -cover -v -p 1
find-tests: find-tests:
@find . -name "*_test.go" @find . -name "*_test.go"
@ -86,3 +86,6 @@ build-mo:
for lang in $(POFILES); do \ for lang in $(POFILES); do \
msgfmt $$lang -o $$(echo $$lang | sed 's/.po/.mo/g') --statistics; \ msgfmt $$lang -o $$(echo $$lang | sed 's/.po/.mo/g') --statistics; \
done done
release:
@goreleaser release --clean

View File

@ -157,7 +157,8 @@ checkout as-is. Recipe commit hashes are also supported as values for
ResolveImage: stack.ResolveImageAlways, ResolveImage: stack.ResolveImageAlways,
Detach: false, Detach: false,
} }
compose, err := appPkg.GetAppComposeConfig(app.Name, deployOpts, app.Env)
compose, err := appPkg.GetAppComposeConfig(composeFiles, app.Env)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }

View File

@ -13,7 +13,7 @@ import (
"coopcloud.tech/abra/pkg/i18n" "coopcloud.tech/abra/pkg/i18n"
"coopcloud.tech/abra/pkg/log" "coopcloud.tech/abra/pkg/log"
"coopcloud.tech/abra/pkg/upstream/convert" "coopcloud.tech/abra/pkg/upstream/convert"
composetypes "github.com/docker/cli/cli/compose/types" composeGoTypes "github.com/compose-spec/compose-go/v2/types"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/filters"
dockerClient "github.com/docker/docker/client" dockerClient "github.com/docker/docker/client"
@ -80,13 +80,13 @@ var AppLabelsCommand = &cobra.Command{
rows = append(rows, []string{i18n.G("RECIPE LABELS"), "---"}) rows = append(rows, []string{i18n.G("RECIPE LABELS"), "---"})
config, err := app.Recipe.GetComposeConfig(app.Env) config, err := app.Recipe.GetComposeConfig()
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
var localLabelKeys []string var localLabelKeys []string
var appServiceConfig composetypes.ServiceConfig var appServiceConfig composeGoTypes.ServiceConfig
for _, service := range config.Services { for _, service := range config.Services {
if service.Name == "app" { if service.Name == "app" {
appServiceConfig = service appServiceConfig = service

View File

@ -262,8 +262,7 @@ func getAppResources(cl *dockerclient.Client, app app.App) (*AppResources, error
return nil, err return nil, err
} }
opts := stack.Deploy{Composefiles: composeFiles, Namespace: app.StackName()} compose, err := appPkg.GetAppComposeConfig(composeFiles, app.Env)
compose, err := appPkg.GetAppComposeConfig(app.Name, opts, app.Env)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -4,7 +4,8 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"sort" "maps"
"slices"
"strings" "strings"
"coopcloud.tech/abra/cli/internal" "coopcloud.tech/abra/cli/internal"
@ -87,26 +88,19 @@ func showPSOutput(app appPkg.App, cl *dockerClient.Client, deployedVersion, chao
return return
} }
deployOpts := stack.Deploy{ compose, err := appPkg.GetAppComposeConfig(composeFiles, app.Env)
Composefiles: composeFiles,
Namespace: app.StackName(),
Prune: false,
ResolveImage: stack.ResolveImageAlways,
}
compose, err := appPkg.GetAppComposeConfig(app.Name, deployOpts, app.Env)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
return return
} }
services := compose.Services services := compose.Services
sort.Slice(services, func(i, j int) bool {
return services[i].Name < services[j].Name
})
var rows [][]string var rows [][]string
allContainerStats := make(map[string]map[string]string) allContainerStats := make(map[string]map[string]string)
for _, service := range services { for _, serviceName := range slices.Sorted(maps.Keys(compose.Services)) {
service := services[serviceName]
filters := filters.NewArgs() filters := filters.NewArgs()
filters.Add("name", fmt.Sprintf("^%s_%s", app.StackName(), service.Name)) filters.Add("name", fmt.Sprintf("^%s_%s", app.StackName(), service.Name))

View File

@ -173,7 +173,7 @@ beforehand. See "abra app backup" for more.`),
Detach: false, Detach: false,
} }
compose, err := appPkg.GetAppComposeConfig(app.Name, deployOpts, app.Env) compose, err := appPkg.GetAppComposeConfig(composeFiles, app.Env)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }

View File

@ -89,8 +89,7 @@ Passing "--prune/-p" does not remove those volumes.`),
log.Fatal(err) log.Fatal(err)
} }
opts := stack.Deploy{Composefiles: composeFiles, Namespace: stackName} compose, err := appPkg.GetAppComposeConfig(composeFiles, app.Env)
compose, err := appPkg.GetAppComposeConfig(app.Name, opts, app.Env)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }

View File

@ -185,7 +185,7 @@ beforehand. See "abra app backup" for more.`),
Detach: false, Detach: false,
} }
compose, err := appPkg.GetAppComposeConfig(app.Name, deployOpts, app.Env) compose, err := appPkg.GetAppComposeConfig(composeFiles, app.Env)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }

View File

@ -92,10 +92,11 @@ func SetBumpType(bumpType string) {
func GetMainAppImage(recipe recipe.Recipe) (string, error) { func GetMainAppImage(recipe recipe.Recipe) (string, error) {
var path string var path string
config, err := recipe.GetComposeConfig(nil) config, err := recipe.GetComposeConfig()
if err != nil { if err != nil {
return "", err return "", err
} }
for _, service := range config.Services { for _, service := range config.Services {
if service.Name == "app" { if service.Name == "app" {
img, err := reference.ParseNormalizedNamed(service.Image) img, err := reference.ParseNormalizedNamed(service.Image)

View File

@ -70,21 +70,6 @@ func ValidateRecipe(args []string, cmdName string) recipe.Recipe {
log.Fatal(err) log.Fatal(err)
} }
_, err = chosenRecipe.GetComposeConfig(nil)
if err != nil {
if cmdName == i18n.G("generate") {
if strings.Contains(err.Error(), "missing a compose") {
log.Fatal(err)
}
log.Warn(err)
} else {
if strings.Contains(err.Error(), "template_driver is not allowed") {
log.Warn(i18n.G("ensure %s recipe compose.* files include \"version: '3.8'\"", recipeName))
}
log.Fatal(i18n.G("unable to validate recipe: %s", err))
}
}
log.Debug(i18n.G("validated %s as recipe argument", recipeName)) log.Debug(i18n.G("validated %s as recipe argument", recipeName))
return chosenRecipe return chosenRecipe

View File

@ -97,6 +97,16 @@ your private key and enter your passphrase beforehand.
log.Fatal(i18n.G("main app service version for %s is empty?", recipe.Name)) log.Fatal(i18n.G("main app service version for %s is empty?", recipe.Name))
} }
repo, err := git.PlainOpen(recipe.Dir)
if err != nil {
log.Fatal(err)
}
preCommitHead, err := repo.Head()
if err != nil {
log.Fatal(err)
}
isClean, err := gitPkg.IsClean(recipe.Dir) isClean, err := gitPkg.IsClean(recipe.Dir)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
@ -201,11 +211,6 @@ likely to change.
} }
if tagString == "" { if tagString == "" {
repo, err := git.PlainOpen(recipe.Dir)
if err != nil {
log.Fatal(err)
}
var lastGitTag tagcmp.Tag var lastGitTag tagcmp.Tag
iter, err := repo.Tags() iter, err := repo.Tags()
if err != nil { if err != nil {
@ -282,16 +287,6 @@ likely to change.
log.Fatal(i18n.G("invalid version %s specified", tagString)) log.Fatal(i18n.G("invalid version %s specified", tagString))
} }
mainService := "app"
label := i18n.G("coop-cloud.${STACK_NAME}.version=%s", tagString)
if !internal.Dry {
if err := recipe.UpdateLabel("compose.y*ml", mainService, label); err != nil {
log.Fatal(err)
}
} else {
log.Info(i18n.G("dry run: not syncing label %s for recipe %s", tagString, recipe.Name))
}
for _, tag := range tags { for _, tag := range tags {
previousTagLeftHand := strings.Split(tag, "+")[0] previousTagLeftHand := strings.Split(tag, "+")[0]
newTagStringLeftHand := strings.Split(tagString, "+")[0] newTagStringLeftHand := strings.Split(tagString, "+")[0]
@ -300,24 +295,15 @@ likely to change.
} }
} }
repo, err := git.PlainOpen(recipe.Dir)
if err != nil {
log.Fatal(err)
}
preCommitHead, err := repo.Head()
if err != nil {
log.Fatal(err)
}
if err := createReleaseFromTag(recipe, tagString, mainAppVersion); err != nil { if err := createReleaseFromTag(recipe, tagString, mainAppVersion); err != nil {
if cleanErr := cleanTag(recipe, tagString); cleanErr != nil { if cleanErr := cleanTag(recipe, tagString); cleanErr != nil {
log.Fatal(cleanErr) log.Fatal(i18n.G("unable to clean up tag after failed release attempt: %s", cleanErr))
} }
if cleanErr := cleanCommit(recipe, preCommitHead); cleanErr != nil { if resetErr := resetCommit(recipe, preCommitHead); resetErr != nil {
log.Fatal(cleanErr) log.Fatal(i18n.G("unable to reset commit after failed release attempt: %s", resetErr))
} }
log.Fatal(err) log.Error(err)
log.Fatal(i18n.G("release failed. any changes made have been reverted"))
} }
}, },
} }
@ -326,10 +312,11 @@ likely to change.
func GetImageVersions(recipe recipePkg.Recipe) (map[string]string, error) { func GetImageVersions(recipe recipePkg.Recipe) (map[string]string, error) {
services := make(map[string]string) services := make(map[string]string)
config, err := recipe.GetComposeConfig(nil) config, err := recipe.GetComposeConfig()
if err != nil { if err != nil {
return nil, err return nil, err
} }
missingTag := false missingTag := false
for _, service := range config.Services { for _, service := range config.Services {
if service.Image == "" { if service.Image == "" {
@ -375,23 +362,14 @@ func createReleaseFromTag(recipe recipePkg.Recipe, tagString, mainAppVersion str
return err return err
} }
tag, err := tagcmp.Parse(tagString) mainService := "app"
if err != nil { label := fmt.Sprintf("coop-cloud.${STACK_NAME}.version=%s", tagString)
return err if !internal.Dry {
} if err := recipe.UpdateLabel("compose.y*ml", mainService, label); err != nil {
log.Fatal(err)
if tag.MissingMinor { }
tag.Minor = "0" } else {
tag.MissingMinor = false log.Info(i18n.G("dry run: not syncing label %s for recipe %s", tagString, recipe.Name))
}
if tag.MissingPatch {
tag.Patch = "0"
tag.MissingPatch = false
}
if tagString == "" {
tagString = fmt.Sprintf("%s+%s", tag.String(), mainAppVersion)
} }
if err := addReleaseNotes(recipe, tagString); err != nil { if err := addReleaseNotes(recipe, tagString); err != nil {
@ -587,9 +565,10 @@ func pushRelease(recipe recipePkg.Recipe, tagString string) error {
return nil return nil
} }
// cleanCommit soft removes the latest release commit. No change are lost the // resetCommit hard resets to the state before release was started.
// the commit itself is removed. This is the equivalent of `git reset HEAD~1`. // This will only remove changes made by the release process due to requiring
func cleanCommit(recipe recipePkg.Recipe, head *plumbing.Reference) error { // a clean working directory.
func resetCommit(recipe recipePkg.Recipe, head *plumbing.Reference) error {
repo, err := git.PlainOpen(recipe.Dir) repo, err := git.PlainOpen(recipe.Dir)
if err != nil { if err != nil {
return errors.New(i18n.G("unable to open repo in %s: %s", recipe.Dir, err)) return errors.New(i18n.G("unable to open repo in %s: %s", recipe.Dir, err))
@ -600,12 +579,12 @@ func cleanCommit(recipe recipePkg.Recipe, head *plumbing.Reference) error {
return errors.New(i18n.G("unable to open work tree in %s: %s", recipe.Dir, err)) return errors.New(i18n.G("unable to open work tree in %s: %s", recipe.Dir, err))
} }
opts := &git.ResetOptions{Commit: head.Hash(), Mode: git.MixedReset} opts := &git.ResetOptions{Commit: head.Hash(), Mode: git.HardReset}
if err := worktree.Reset(opts); err != nil { if err := worktree.Reset(opts); err != nil {
return errors.New(i18n.G("unable to soft reset %s: %s", recipe.Dir, err)) return errors.New(i18n.G("unable to hard reset %s: %s", recipe.Dir, err))
} }
log.Debug(i18n.G("removed freshly created commit")) log.Debug(i18n.G("reset commit to pre-release state"))
return nil return nil
} }

View File

@ -124,7 +124,7 @@ interface.`),
log.Debug(i18n.G("did not find versions file for %s", recipe.Name)) log.Debug(i18n.G("did not find versions file for %s", recipe.Name))
} }
config, err := recipe.GetComposeConfig(nil) config, err := recipe.GetComposeConfig()
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }

109
go.mod
View File

@ -1,8 +1,6 @@
module coopcloud.tech/abra module coopcloud.tech/abra
go 1.24.0 go 1.26.0
toolchain go1.24.1
require ( require (
coopcloud.tech/tagcmp v0.0.0-20250818180036-0ec1b205b5ca coopcloud.tech/tagcmp v0.0.0-20250818180036-0ec1b205b5ca
@ -10,19 +8,20 @@ require (
github.com/AlecAivazis/survey/v2 v2.3.7 github.com/AlecAivazis/survey/v2 v2.3.7
github.com/charmbracelet/bubbletea v1.3.10 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 v1.0.0
github.com/compose-spec/compose-go/v2 v2.10.1
github.com/distribution/reference v0.6.0 github.com/distribution/reference v0.6.0
github.com/docker/cli v28.4.0+incompatible github.com/docker/cli v28.4.0+incompatible
github.com/docker/docker v28.5.2+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.17.2
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.19.0
golang.org/x/term v0.35.0 golang.org/x/term v0.41.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
) )
@ -30,27 +29,28 @@ require (
require ( require (
dario.cat/mergo v1.0.2 // indirect dario.cat/mergo v1.0.2 // indirect
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
github.com/BurntSushi/toml v1.5.0 // indirect github.com/BurntSushi/toml v1.6.0 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/ProtonMail/go-crypto v1.3.0 // indirect github.com/ProtonMail/go-crypto v1.4.1 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/beorn7/perks v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect
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.4.3 // indirect
github.com/charmbracelet/x/ansi v0.10.2 // indirect github.com/charmbracelet/x/ansi v0.11.6 // indirect
github.com/charmbracelet/x/cellbuf v0.0.13 // indirect github.com/charmbracelet/x/cellbuf v0.0.15 // indirect
github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 // indirect github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 // indirect
github.com/charmbracelet/x/term v0.2.1 // indirect github.com/charmbracelet/x/term v0.2.2 // indirect
github.com/clipperhouse/uax29/v2 v2.2.0 // indirect github.com/clipperhouse/displaywidth v0.11.0 // indirect
github.com/cloudflare/circl v1.6.1 // indirect github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
github.com/cloudflare/circl v1.6.3 // indirect
github.com/containerd/errdefs v1.0.0 // indirect github.com/containerd/errdefs v1.0.0 // indirect
github.com/containerd/errdefs/pkg v0.3.0 // indirect github.com/containerd/errdefs/pkg v0.3.0 // indirect
github.com/containerd/log v0.1.0 // indirect github.com/containerd/log v0.1.0 // indirect
github.com/containerd/platforms v0.2.1 // indirect github.com/containerd/platforms v0.2.1 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
github.com/cyphar/filepath-securejoin v0.5.0 // indirect github.com/cyphar/filepath-securejoin v0.6.1 // 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
@ -61,26 +61,27 @@ require (
github.com/felixge/httpsnoop v1.0.4 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect
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.8.0 // 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.5.0 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/google/uuid v1.6.0 // indirect github.com/google/uuid v1.6.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/kevinburke/ssh_config v1.4.0 // indirect github.com/kevinburke/ssh_config v1.6.0 // indirect
github.com/klauspost/compress v1.18.0 // indirect github.com/klauspost/compress v1.18.5 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/lucasb-eyer/go-colorful v1.3.0 // indirect github.com/lucasb-eyer/go-colorful v1.4.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.19 // indirect github.com/mattn/go-runewidth v0.0.21 // indirect
github.com/mattn/go-shellwords v1.0.12 // 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
@ -89,7 +90,7 @@ require (
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
github.com/morikuni/aec v1.0.0 // indirect github.com/morikuni/aec v1.1.0 // indirect
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/termenv v0.16.0 // indirect github.com/muesli/termenv v0.16.0 // indirect
@ -100,40 +101,44 @@ require (
github.com/pjbgf/sha1cd v0.5.0 // indirect github.com/pjbgf/sha1cd v0.5.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.66.1 // indirect github.com/prometheus/common v0.67.5 // indirect
github.com/prometheus/procfs v0.17.0 // indirect github.com/prometheus/procfs v0.20.1 // 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/santhosh-tekuri/jsonschema/v6 v6.0.1 // indirect
github.com/skeema/knownhosts v1.3.1 // indirect github.com/sirupsen/logrus v1.9.4 // indirect
github.com/skeema/knownhosts v1.3.2 // indirect
github.com/spf13/pflag v1.0.10 // indirect github.com/spf13/pflag v1.0.10 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect github.com/xeipuuv/gojsonschema v1.2.0 // indirect
github.com/xhit/go-str2duration/v2 v2.1.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.2.1 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 // indirect
go.opentelemetry.io/otel v1.38.0 // indirect go.opentelemetry.io/otel v1.42.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.42.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.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.38.0 // indirect go.opentelemetry.io/otel/metric v1.42.0 // indirect
go.opentelemetry.io/otel/sdk v1.38.0 // indirect go.opentelemetry.io/otel/sdk v1.42.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.38.0 // indirect go.opentelemetry.io/otel/sdk/metric v1.42.0 // indirect
go.opentelemetry.io/otel/trace v1.38.0 // indirect go.opentelemetry.io/otel/trace v1.42.0 // indirect
go.opentelemetry.io/proto/otlp v1.8.0 // indirect go.opentelemetry.io/proto/otlp v1.10.0 // indirect
go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v2 v2.4.4 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/crypto v0.42.0 // indirect go.yaml.in/yaml/v4 v4.0.0-rc.3 // indirect
golang.org/x/exp v0.0.0-20250911091902-df9299821621 // indirect golang.org/x/crypto v0.49.0 // indirect
golang.org/x/net v0.44.0 // indirect golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90 // indirect
golang.org/x/text v0.29.0 // indirect golang.org/x/net v0.52.0 // indirect
golang.org/x/time v0.13.0 // indirect golang.org/x/sync v0.20.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250929231259-57b25ae835d4 // indirect golang.org/x/text v0.35.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250929231259-57b25ae835d4 // indirect golang.org/x/time v0.15.0 // indirect
google.golang.org/grpc v1.75.1 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 // indirect
google.golang.org/protobuf v1.36.9 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 // indirect
google.golang.org/grpc v1.80.0 // indirect
google.golang.org/protobuf v1.36.11 // 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
) )
@ -142,12 +147,12 @@ 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.5 // 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
github.com/hashicorp/go-retryablehttp v0.7.8 github.com/hashicorp/go-retryablehttp v0.7.8
github.com/moby/patternmatcher v0.6.0 // indirect github.com/moby/patternmatcher v0.6.1 // 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.2 // indirect github.com/prometheus/client_golang v1.23.2 // indirect
@ -156,7 +161,7 @@ require (
github.com/stretchr/testify v1.11.1 github.com/stretchr/testify v1.11.1
github.com/theupdateframework/notary v0.7.0 // indirect github.com/theupdateframework/notary v0.7.0 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
golang.org/x/sys v0.36.0 golang.org/x/sys v0.42.0
) )
replace github.com/docker/cli v28.4.0+incompatible => git.coopcloud.tech/toolshed/docker-cli v28.5.3-0.20260202112816-30df2d0b3a00+incompatible replace github.com/docker/cli v28.4.0+incompatible => git.coopcloud.tech/toolshed/docker-cli v28.5.3-0.20260202112816-30df2d0b3a00+incompatible

214
go.sum
View File

@ -51,8 +51,8 @@ github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZ
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk=
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
@ -81,8 +81,8 @@ github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb0
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s= github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s=
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w= github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw= github.com/ProtonMail/go-crypto v1.4.1 h1:9RfcZHqEQUvP8RzecWEUafnZVtEvrBVL9BiF67IQOfM=
github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE= github.com/ProtonMail/go-crypto v1.4.1/go.mod h1:e1OaTyu5SYVrO9gKOEhTc+5UcXtTUa+P3uLudwcgPqo=
github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
@ -136,20 +136,20 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF
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.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw= github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw=
github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4= github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4=
github.com/charmbracelet/colorprofile v0.3.2 h1:9J27WdztfJQVAQKX2WOlSSRB+5gaKqqITmrvb1uTIiI= github.com/charmbracelet/colorprofile v0.4.3 h1:QPa1IWkYI+AOB+fE+mg/5/4HRMZcaXex9t5KX76i20Q=
github.com/charmbracelet/colorprofile v0.3.2/go.mod h1:mTD5XzNeWHj8oqHb+S1bssQb7vIHbepiebQ2kPKVKbI= github.com/charmbracelet/colorprofile v0.4.3/go.mod h1:/zT4BhpD5aGFpqQQqw7a+VtHCzu+zrQtt1zhMt9mR4Q=
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 v1.0.0 h1:HVVVMmfOorfj3BA9i8X8UL69Hoz9lI0PYwXfJvOdRc4=
github.com/charmbracelet/log v0.4.2/go.mod h1:qifHGX/tc7eluv2R6pWIpyHDDrrb/AG71Pf2ysQu5nw= github.com/charmbracelet/log v1.0.0/go.mod h1:uYgY3SmLpwJWxmlrPwXvzVYujxis1vAKRV/0VQB7yWA=
github.com/charmbracelet/x/ansi v0.10.2 h1:ith2ArZS0CJG30cIUfID1LXN7ZFXRCww6RUvAPA+Pzw= github.com/charmbracelet/x/ansi v0.11.6 h1:GhV21SiDz/45W9AnV2R61xZMRri5NlLnl6CVF7ihZW8=
github.com/charmbracelet/x/ansi v0.10.2/go.mod h1:HbLdJjQH4UH4AqA2HpRWuWNluRE6zxJH/yteYEYCFa8= github.com/charmbracelet/x/ansi v0.11.6/go.mod h1:2JNYLgQUsyqaiLovhU2Rv/pb8r6ydXKS3NIttu3VGZQ=
github.com/charmbracelet/x/cellbuf v0.0.13 h1:/KBBKHuVRbq1lYx5BzEHBAFBP8VcQzJejZ/IA3iR28k= github.com/charmbracelet/x/cellbuf v0.0.15 h1:ur3pZy0o6z/R7EylET877CBxaiE1Sp1GMxoFPAIztPI=
github.com/charmbracelet/x/cellbuf v0.0.13/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs= github.com/charmbracelet/x/cellbuf v0.0.15/go.mod h1:J1YVbR7MUuEGIFPCaaZ96KDl5NoS0DAWkskup+mOY+Q=
github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 h1:payRxjMjKgx2PaCWLZ4p3ro9y97+TVLZNaRZgJwSVDQ= github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 h1:payRxjMjKgx2PaCWLZ4p3ro9y97+TVLZNaRZgJwSVDQ=
github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U= github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk=
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg= 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=
@ -165,15 +165,19 @@ 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/uax29/v2 v2.2.0 h1:ChwIKnQN3kcZteTXMgb1wztSgaU+ZemkgWdohwgs8tY= github.com/clipperhouse/displaywidth v0.11.0 h1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSEFgwIwO+UVM8=
github.com/clipperhouse/uax29/v2 v2.2.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM= github.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0=
github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk=
github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
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.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8=
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
github.com/compose-spec/compose-go/v2 v2.10.1 h1:mFbXobojGRFIVi1UknrvaDAZ+PkJfyjqkA1yseh+vAU=
github.com/compose-spec/compose-go/v2 v2.10.1/go.mod h1:Ohac1SzhO/4fXXrzWIztIVB6ckmKBv1Nt5Z5mGVESUg=
github.com/containerd/aufs v0.0.0-20200908144142-dab0cbea06f4/go.mod h1:nukgQABAEopAHvB6j7cnP5zJ+/3aVcE7hCYqvIwAHyE= github.com/containerd/aufs v0.0.0-20200908144142-dab0cbea06f4/go.mod h1:nukgQABAEopAHvB6j7cnP5zJ+/3aVcE7hCYqvIwAHyE=
github.com/containerd/aufs v0.0.0-20201003224125-76a6863f2989/go.mod h1:AkGGQs9NM2vtYHaUen+NljV0/baGCAPELGm2q9ZXpWU= github.com/containerd/aufs v0.0.0-20201003224125-76a6863f2989/go.mod h1:AkGGQs9NM2vtYHaUen+NljV0/baGCAPELGm2q9ZXpWU=
github.com/containerd/aufs v0.0.0-20210316121734-20793ff83c97/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU= github.com/containerd/aufs v0.0.0-20210316121734-20793ff83c97/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU=
@ -297,8 +301,8 @@ 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.5.0 h1:hIAhkRBMQ8nIeuVwcAoymp7MY4oherZdAxD+m0u9zaw= github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE=
github.com/cyphar/filepath-securejoin v0.5.0/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= github.com/cyphar/filepath-securejoin v0.6.1/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,6 +320,8 @@ github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
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/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
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/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=
@ -327,8 +333,8 @@ github.com/docker/docker v1.4.2-0.20190924003213-a8608b5b67c7/go.mod h1:eEKB0N0r
github.com/docker/docker v28.5.2+incompatible h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM= 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 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.5 h1:EFNN8DHvaiK8zVqFA2DT6BjXE0GzfLOZ38ggPTKePkY=
github.com/docker/docker-credential-helpers v0.9.3/go.mod h1:x+4Gbw9aGmChi3qTLZj8Dfn0TD20M/fuWy0E5+WDeCo= github.com/docker/docker-credential-helpers v0.9.5/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=
@ -387,12 +393,12 @@ github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM= github.com/go-git/go-billy/v5 v5.8.0 h1:I8hjc3LbBlXTtVuFNJuwYuMiHvQJDq1AT6u4DwDzZG0=
github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU= github.com/go-git/go-billy/v5 v5.8.0/go.mod h1:RpvI/rw4Vr5QA+Z60c6d6LXH0rYJo0uD5SqfmrrheCY=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
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.17.2 h1:B+nkdlxdYrvyFK4GPXVU8w1U+YkbsgciIR7f2sZJ104=
github.com/go-git/go-git/v5 v5.16.2/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8= github.com/go-git/go-git/v5 v5.17.2/go.mod h1:pW/VmeqkanRFqR6AljLcs7EA7FbZaN5MQqO7oZADXpo=
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=
@ -401,8 +407,8 @@ github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
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.1 h1:4hvbpePJKnIzH1B+8OR/JPbTx37NktoI9LE2QZBBkvE=
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= 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=
@ -423,8 +429,8 @@ github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-sql-driver/mysql v1.3.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.3.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/godbus/dbus v0.0.0-20151105175453-c7fdd8b5cd55/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= github.com/godbus/dbus v0.0.0-20151105175453-c7fdd8b5cd55/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4=
@ -524,8 +530,8 @@ 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.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/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU= github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs= github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c=
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=
@ -573,8 +579,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.4.0 h1:6xxtP5bZ2E4NF5tuQulISpTO2z8XbtH8cg1PWkxoFkQ= github.com/kevinburke/ssh_config v1.6.0 h1:J1FBfmuVosPHf5GRdltRLhPJtJpTlMdKTBjRgTaQBFY=
github.com/kevinburke/ssh_config v1.4.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M= github.com/kevinburke/ssh_config v1.6.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=
@ -582,8 +588,8 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o
github.com/klauspost/compress v1.11.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.11.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
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.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
@ -607,8 +613,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.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag= github.com/lucasb-eyer/go-colorful v1.4.0 h1:UtrWVfLdarDgc44HcS7pYloGHJUjHV/4FwW4TvVgFr4=
github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/lucasb-eyer/go-colorful v1.4.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/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
@ -626,10 +632,11 @@ 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.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw= github.com/mattn/go-runewidth v0.0.21 h1:jJKAZiQH+2mIinzCJIaIG9Be1+0NR+5sz/lYEEjdM8w=
github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= github.com/mattn/go-runewidth v0.0.21/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 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk=
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=
github.com/mattn/go-sqlite3 v1.6.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.6.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
@ -653,8 +660,8 @@ 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/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= github.com/moby/patternmatcher v0.6.1 h1:qlhtafmr6kgMIJjKJMDmMWq7WLkKIo23hsrpR3x084U=
github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/patternmatcher v0.6.1/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=
github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs= github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs=
github.com/moby/sys/mountinfo v0.4.0/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= github.com/moby/sys/mountinfo v0.4.0/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A=
@ -679,8 +686,9 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/morikuni/aec v1.1.0 h1:vBBl0pUnvi/Je71dsRrhMBtreIqNMYErSAbEeb8jrXQ=
github.com/morikuni/aec v1.1.0/go.mod h1:xDRgiq/iw5l+zkao76YTKzKttOp2cwPEne25HDkJnBw=
github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ=
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
@ -779,8 +787,8 @@ github.com/prometheus/common v0.0.0-20180110214958-89604d197083/go.mod h1:daVV7q
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.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4=
github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw=
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-20190522114515-bc1a522cf7b1/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.0-20190522114515-bc1a522cf7b1/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
@ -791,8 +799,8 @@ github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+Gx
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
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.20.1 h1:XwbrGOIplXW/AU3YhIhLODXMJYyC1isLFfYCsTEycfc=
github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= github.com/prometheus/procfs v0.20.1/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4UlK79eF5TGGo=
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=
@ -804,9 +812,11 @@ github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD
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=
github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4=
github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 h1:PKK9DyHxif4LZo+uQSgXNqs0jj5+xZwwfKHgph2lxBw=
github.com/santhosh-tekuri/jsonschema/v6 v6.0.1/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/schollz/progressbar/v3 v3.18.0 h1:uXdoHABRFmNIjUfte/Ex7WtuyVslrw2wVPQmCN62HpA= github.com/schollz/progressbar/v3 v3.19.0 h1:Ea18xuIRQXLAUidVDox3AbwfUhD0/1IvohyTutOIFoc=
github.com/schollz/progressbar/v3 v3.18.0/go.mod h1:IsO3lpbaGuzh8zIMzgY3+J8l4C8GjO0Y9S69eFvNsec= github.com/schollz/progressbar/v3 v3.19.0/go.mod h1:IsO3lpbaGuzh8zIMzgY3+J8l4C8GjO0Y9S69eFvNsec=
github.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U= github.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U=
github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo=
github.com/seccomp/libseccomp-golang v0.9.2-0.20210429002308-3879420cc921/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= github.com/seccomp/libseccomp-golang v0.9.2-0.20210429002308-3879420cc921/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg=
@ -821,10 +831,10 @@ github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6Mwd
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=
github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8= github.com/skeema/knownhosts v1.3.2 h1:EDL9mgf4NzwMXCTfaxSD/o/a5fxDw/xL9nkU28JjdBg=
github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY= 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=
@ -891,6 +901,8 @@ github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:
github.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= github.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs=
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc=
github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
@ -912,39 +924,41 @@ 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.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 h1:OyrsyzuttWTSur2qN/Lm0m2a8yqyIjUVBZcxFPuXq2o=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0/go.mod h1:C2NGBr+kAB4bk3xtMXfZ94gqFDtg/GkI7e9zqGh5Beg=
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 h1:vl9obrcoWVKp/lwl8tRE33853I8Xru9HFbw/skNeLs8= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.42.0 h1:MdKucPl/HbzckWWEisiNqMPhRrAOQX8r4jTuGr636gk=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0/go.mod h1:GAXRxmLJcVM3u22IjTg74zWBrRCKq8BnOqUVLodpcpw= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.42.0/go.mod h1:RolT8tWtfHcjajEH5wFIZ4Dgh5jpPdFXYV9pTAk/qjc=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0 h1:THuZiwpQZuHPul65w4WcwEnkX2QIuMT+UFoOrygtoJw=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0/go.mod h1:J2pvYM5NGHofZ2/Ru6zw/TNWnEQp5crgyDeSrYpXkAw=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0 h1:zWWrB1U6nqhS/k6zYB74CjRpuiitRtLLi68VcgmOEto=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0/go.mod h1:2qXPNBX1OVRC0IwOnfo1ljoid+RD0QK3443EaqVlsOU=
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.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=
go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo=
go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= go.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts=
go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= go.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9RKCAZ3YGuA=
go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= go.opentelemetry.io/otel/sdk/metric v1.42.0/go.mod h1:Ua6AAlDKdZ7tdvaQKfSmnFTdHx37+J4ba8MwVCYM5hc=
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
go.opentelemetry.io/proto/otlp v1.8.0 h1:fRAZQDcAFHySxpJ1TwlA1cJ4tvcrw7nXl9xWWC8N5CE= go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g=
go.opentelemetry.io/proto/otlp v1.8.0/go.mod h1:tIeYOeNBU4cvmPqpaji1P+KbB4Oloai8wN4rWzRrFF0= go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk=
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.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ=
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
go.yaml.in/yaml/v4 v4.0.0-rc.3 h1:3h1fjsh1CTAPjW7q/EMe+C8shx5d8ctzZTrLcs/j8Go=
go.yaml.in/yaml/v4 v4.0.0-rc.3/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0=
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=
@ -963,8 +977,8 @@ 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.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8= golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
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=
@ -975,8 +989,8 @@ 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-20250911091902-df9299821621 h1:2id6c1/gto0kaHYyrixvknJ8tUK/Qs5IsmBtrc+FtgU= golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90 h1:jiDhWWeC7jfWqR9c/uplMOqJ0sbNlNWv0UkzE0vX1MA=
golang.org/x/exp v0.0.0-20250911091902-df9299821621/go.mod h1:TwQYMMnGpvZyc+JpB/UAuTNIsVJifOlSkrZkhcvpVUk= golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90/go.mod h1:xE1HEv6b+1SCZ5/uscMRjUBKtIxworgEcEi+/n9NQDQ=
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=
@ -1040,8 +1054,8 @@ 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.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I= golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
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=
@ -1059,6 +1073,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-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.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
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=
@ -1137,13 +1153,13 @@ 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.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ= golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU=
golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA= golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A=
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=
@ -1153,16 +1169,16 @@ 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.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
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.13.0 h1:eUlYslOIt32DgYD6utsuUeHs4d7AsEYLuIAdg7FlYgI= golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
golang.org/x/time v0.13.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
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=
@ -1212,8 +1228,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.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
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=
@ -1258,10 +1274,10 @@ 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-20250929231259-57b25ae835d4 h1:8XJ4pajGwOlasW+L13MnEGA8W4115jJySQtVfS2/IBU= google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 h1:VPWxll4HlMw1Vs/qXtN7BvhZqsS9cdAittCNvVENElA=
google.golang.org/genproto/googleapis/api v0.0.0-20250929231259-57b25ae835d4/go.mod h1:NnuHhy+bxcg30o7FnVAZbXsPHUDQ9qKWAQKCD7VxFtk= google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:7QBABkRtR8z+TEnmXTqIqwJLlzrZKVfAUm7tY3yGv0M=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250929231259-57b25ae835d4 h1:i8QOKZfYg6AbGVZzUAY3LrNWCKF8O6zFisU9Wl9RER4= google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 h1:m8qni9SQFH0tJc1X0vmnpw/0t+AImlSvp30sEupozUg=
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-20260401024825-9d38bb4040a9/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
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=
@ -1281,8 +1297,8 @@ 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.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI= google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM=
google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4=
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=
@ -1296,8 +1312,8 @@ 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.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= google.golang.org/protobuf v1.36.11/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=

View File

@ -18,10 +18,10 @@ import (
"coopcloud.tech/abra/pkg/recipe" "coopcloud.tech/abra/pkg/recipe"
"coopcloud.tech/abra/pkg/upstream/convert" "coopcloud.tech/abra/pkg/upstream/convert"
"coopcloud.tech/abra/pkg/upstream/stack" "coopcloud.tech/abra/pkg/upstream/stack"
composeGoTypes "github.com/compose-spec/compose-go/v2/types"
"coopcloud.tech/abra/pkg/log" "coopcloud.tech/abra/pkg/log"
loader "coopcloud.tech/abra/pkg/upstream/stack" loader "coopcloud.tech/abra/pkg/upstream/stack"
composetypes "github.com/docker/cli/cli/compose/types"
"github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/filters"
"github.com/schollz/progressbar/v3" "github.com/schollz/progressbar/v3"
) )
@ -179,8 +179,7 @@ func (a App) Filters(appendServiceNames, exactMatch bool, services ...string) (f
return filters, err return filters, err
} }
opts := stack.Deploy{Composefiles: composeFiles} compose, err := GetAppComposeConfig(composeFiles, a.Env)
compose, err := GetAppComposeConfig(a.Recipe.Name, opts, a.Env)
if err != nil { if err != nil {
return filters, err return filters, err
} }
@ -333,8 +332,7 @@ func GetAppServiceNames(appName string) ([]string, error) {
return serviceNames, err return serviceNames, err
} }
opts := stack.Deploy{Composefiles: composeFiles} compose, err := GetAppComposeConfig(composeFiles, app.Env)
compose, err := GetAppComposeConfig(app.Recipe.Name, opts, app.Env)
if err != nil { if err != nil {
return serviceNames, err return serviceNames, err
} }
@ -490,13 +488,18 @@ func GetAppStatuses(apps []App, MachineReadable bool) (map[string]map[string]str
// GetAppComposeConfig retrieves a compose specification for a recipe. This // GetAppComposeConfig retrieves a compose specification for a recipe. This
// specification is the result of a merge of all the compose.**.yml files in // specification is the result of a merge of all the compose.**.yml files in
// the recipe repository. // the recipe repository.
func GetAppComposeConfig(recipe string, opts stack.Deploy, appEnv envfile.AppEnv) (*composetypes.Config, error) { func GetAppComposeConfig(composeFiles []string, appEnv envfile.AppEnv) (*composeGoTypes.Project, error) {
compose, err := loader.LoadComposefile(opts, appEnv) compose, err := loader.LoadCompose(loader.LoadConf{ComposeFiles: composeFiles, AppEnv: appEnv})
if err != nil { if err != nil {
return &composetypes.Config{}, err return &composeGoTypes.Project{}, err
} }
log.Debug(i18n.G("retrieved %s for %s", compose.Filename, recipe)) recipeName, exists := appEnv["RECIPE"]
if !exists {
recipeName, _ = appEnv["TYPE"]
}
log.Debug(i18n.G("retrieved %s for %s", compose.Name, recipeName))
return compose, nil return compose, nil
} }
@ -504,7 +507,7 @@ 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( func ExposeAllEnv(
stackName string, stackName string,
compose *composetypes.Config, compose *composeGoTypes.Project,
appEnv envfile.AppEnv, appEnv envfile.AppEnv,
toDeployVersion string) { toDeployVersion string) {
for _, service := range compose.Services { for _, service := range compose.Services {

View File

@ -10,46 +10,83 @@ import (
"coopcloud.tech/abra/pkg/config" "coopcloud.tech/abra/pkg/config"
"coopcloud.tech/abra/pkg/envfile" "coopcloud.tech/abra/pkg/envfile"
"coopcloud.tech/abra/pkg/recipe" "coopcloud.tech/abra/pkg/recipe"
testPkg "coopcloud.tech/abra/pkg/test" recipePkg "coopcloud.tech/abra/pkg/recipe"
"coopcloud.tech/abra/pkg/test"
"github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/filters"
"github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
var (
expectedAppEnv = envfile.AppEnv{
"DOMAIN": test.AppName,
"RECIPE": test.RecipeName,
}
expectedApp = appPkg.App{
Name: test.AppName,
Recipe: recipePkg.Get(expectedAppEnv["RECIPE"]),
Domain: expectedAppEnv["DOMAIN"],
Env: expectedAppEnv,
Path: expectedAppFile.Path,
Server: expectedAppFile.Server,
}
expectedAppFile = appPkg.AppFile{
Path: test.AppEnvPath,
Server: test.ServerName,
}
expectedAppFiles = map[string]appPkg.AppFile{
test.AppName: expectedAppFile,
}
)
func TestNewApp(t *testing.T) { func TestNewApp(t *testing.T) {
app, err := appPkg.NewApp(testPkg.ExpectedAppEnv, testPkg.AppName, testPkg.ExpectedAppFile) app, err := appPkg.NewApp(expectedAppEnv, test.AppName, expectedAppFile)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if !reflect.DeepEqual(app, testPkg.ExpectedApp) {
t.Fatalf("did not get expected app type. Expected: %s; Got: %s", app, testPkg.ExpectedApp) if !reflect.DeepEqual(app, expectedApp) {
t.Fatalf("did not get expected app type. Expected: %s; Got: %s", app, expectedApp)
} }
} }
func TestReadAppEnvFile(t *testing.T) { func TestReadAppEnvFile(t *testing.T) {
app, err := appPkg.ReadAppEnvFile(testPkg.ExpectedAppFile, testPkg.AppName) test.Setup()
t.Cleanup(func() { test.Teardown() })
app, err := appPkg.ReadAppEnvFile(expectedAppFile, test.AppName)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if !reflect.DeepEqual(app, testPkg.ExpectedApp) {
t.Fatalf("did not get expected app type. Expected: %s; Got: %s", app, testPkg.ExpectedApp) if !reflect.DeepEqual(app, expectedApp) {
t.Fatalf("did not get expected app type. Expected: %s; Got: %s", app, expectedApp)
} }
} }
func TestGetApp(t *testing.T) { func TestGetApp(t *testing.T) {
app, err := appPkg.GetApp(testPkg.ExpectedAppFiles, testPkg.AppName) test.Setup()
t.Cleanup(func() { test.Teardown() })
app, err := appPkg.GetApp(expectedAppFiles, test.AppName)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if !reflect.DeepEqual(app, testPkg.ExpectedApp) {
t.Fatalf("did not get expected app type. Expected: %s; Got: %s", app, testPkg.ExpectedApp) if !reflect.DeepEqual(app, expectedApp) {
t.Fatalf("did not get expected app type. Expected: %s; Got: %s", app, expectedApp)
} }
} }
func TestGetComposeFiles(t *testing.T) { func TestGetComposeFiles(t *testing.T) {
r := recipe.Get("abra-test-recipe") test.Setup()
err := r.EnsureExists() t.Cleanup(func() { test.Teardown() })
if err != nil {
r := recipe.Get(test.AbraTestRecipe)
if err := r.EnsureExists(); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -94,7 +131,10 @@ func TestGetComposeFiles(t *testing.T) {
} }
func TestGetComposeFilesError(t *testing.T) { func TestGetComposeFilesError(t *testing.T) {
r := recipe.Get("abra-test-recipe") test.Setup()
t.Cleanup(func() { test.Teardown() })
r := recipe.Get(test.AbraTestRecipe)
err := r.EnsureExists() err := r.EnsureExists()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -186,26 +226,32 @@ func TestFilters(t *testing.T) {
func compareFilter(t *testing.T, f1 filters.Args, f2 map[string]map[string]bool) { func compareFilter(t *testing.T, f1 filters.Args, f2 map[string]map[string]bool) {
t.Helper() t.Helper()
j1, err := f1.MarshalJSON() j1, err := f1.MarshalJSON()
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
j2, err := json.Marshal(f2) j2, err := json.Marshal(f2)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
if diff := cmp.Diff(string(j2), string(j1)); diff != "" { if diff := cmp.Diff(string(j2), string(j1)); diff != "" {
t.Errorf("filters mismatch (-want +got):\n%s", diff) t.Errorf("filters mismatch (-want +got):\n%s", diff)
} }
} }
func TestWriteRecipeVersionOverwrite(t *testing.T) { func TestWriteRecipeVersionOverwrite(t *testing.T) {
app, err := appPkg.GetApp(testPkg.ExpectedAppFiles, testPkg.AppName) test.Setup()
t.Cleanup(func() { test.Teardown() })
app, err := appPkg.GetApp(expectedAppFiles, test.AppName)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer t.Cleanup(func() { t.Cleanup(func() {
if err := app.WipeRecipeVersion(); err != nil { if err := app.WipeRecipeVersion(); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -217,7 +263,7 @@ func TestWriteRecipeVersionOverwrite(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
app, err = appPkg.GetApp(testPkg.ExpectedAppFiles, testPkg.AppName) app, err = appPkg.GetApp(expectedAppFiles, test.AppName)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -226,7 +272,10 @@ func TestWriteRecipeVersionOverwrite(t *testing.T) {
} }
func TestWriteRecipeVersionUnknown(t *testing.T) { func TestWriteRecipeVersionUnknown(t *testing.T) {
app, err := appPkg.GetApp(testPkg.ExpectedAppFiles, testPkg.AppName) test.Setup()
t.Cleanup(func() { test.Teardown() })
app, err := appPkg.GetApp(expectedAppFiles, test.AppName)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View File

@ -7,12 +7,12 @@ import (
"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" composeGoTypes "github.com/compose-spec/compose-go/v2/types"
) )
// SetRecipeLabel adds the label 'coop-cloud.${STACK_NAME}.recipe=${RECIPE}' to the app container // SetRecipeLabel adds the label 'coop-cloud.${STACK_NAME}.recipe=${RECIPE}' to the app container
// to signal which recipe is connected to the deployed app // to signal which recipe is connected to the deployed app
func SetRecipeLabel(compose *composetypes.Config, stackName string, recipe string) { func SetRecipeLabel(compose *composeGoTypes.Project, stackName string, recipe string) {
for _, service := range compose.Services { for _, service := range compose.Services {
if service.Name == "app" { if service.Name == "app" {
log.Debug(i18n.G("set recipe label 'coop-cloud.%s.recipe' to %s for %s", stackName, recipe, stackName)) log.Debug(i18n.G("set recipe label 'coop-cloud.%s.recipe' to %s for %s", stackName, recipe, stackName))
@ -24,7 +24,7 @@ func SetRecipeLabel(compose *composetypes.Config, stackName string, recipe strin
// SetChaosLabel adds the label 'coop-cloud.${STACK_NAME}.chaos=true/false' to the app container // SetChaosLabel adds the label 'coop-cloud.${STACK_NAME}.chaos=true/false' to the app container
// to signal if the app is deployed in chaos mode // to signal if the app is deployed in chaos mode
func SetChaosLabel(compose *composetypes.Config, stackName string, chaos bool) { func SetChaosLabel(compose *composeGoTypes.Project, stackName string, chaos bool) {
for _, service := range compose.Services { for _, service := range compose.Services {
if service.Name == "app" { if service.Name == "app" {
log.Debug(i18n.G("set label 'coop-cloud.%s.chaos' to %v for %s", stackName, chaos, stackName)) log.Debug(i18n.G("set label 'coop-cloud.%s.chaos' to %v for %s", stackName, chaos, stackName))
@ -35,7 +35,7 @@ func SetChaosLabel(compose *composetypes.Config, stackName string, chaos bool) {
} }
// SetChaosVersionLabel adds the label 'coop-cloud.${STACK_NAME}.chaos-version=$(GIT_COMMIT)' to the app container // SetChaosVersionLabel adds the label 'coop-cloud.${STACK_NAME}.chaos-version=$(GIT_COMMIT)' to the app container
func SetChaosVersionLabel(compose *composetypes.Config, stackName string, chaosVersion string) { func SetChaosVersionLabel(compose *composeGoTypes.Project, stackName string, chaosVersion string) {
for _, service := range compose.Services { for _, service := range compose.Services {
if service.Name == "app" { if service.Name == "app" {
log.Debug(i18n.G("set label 'coop-cloud.%s.chaos-version' to %v for %s", stackName, chaosVersion, stackName)) log.Debug(i18n.G("set label 'coop-cloud.%s.chaos-version' to %v for %s", stackName, chaosVersion, stackName))
@ -45,7 +45,7 @@ func SetChaosVersionLabel(compose *composetypes.Config, stackName string, chaosV
} }
} }
func SetVersionLabel(compose *composetypes.Config, stackName string, version string) { func SetVersionLabel(compose *composeGoTypes.Project, stackName string, version string) {
for _, service := range compose.Services { for _, service := range compose.Services {
if service.Name == "app" { if service.Name == "app" {
log.Debug(i18n.G("set label 'coop-cloud.%s.version' to %v for %s", stackName, version, stackName)) log.Debug(i18n.G("set label 'coop-cloud.%s.version' to %v for %s", stackName, version, stackName))
@ -56,7 +56,7 @@ func SetVersionLabel(compose *composetypes.Config, stackName string, version str
} }
// 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 *composeGoTypes.Project, stackName string, label string) string {
for _, service := range compose.Services { for _, service := range compose.Services {
if service.Name == "app" { if service.Name == "app" {
labelKey := fmt.Sprintf("coop-cloud.%s.%s", stackName, label) labelKey := fmt.Sprintf("coop-cloud.%s.%s", stackName, label)
@ -73,7 +73,7 @@ func GetLabel(compose *composetypes.Config, stackName string, label string) stri
// GetTimeoutFromLabel reads the timeout value from docker label // GetTimeoutFromLabel reads the timeout value from docker label
// `coop-cloud.${STACK_NAME}.timeout=...` if present. A value is present if the // `coop-cloud.${STACK_NAME}.timeout=...` if present. A value is present if the
// operator uses a `TIMEOUT=...` in their app env. // operator uses a `TIMEOUT=...` in their app env.
func GetTimeoutFromLabel(compose *composetypes.Config, stackName string) (int, error) { func GetTimeoutFromLabel(compose *composeGoTypes.Project, stackName string) (int, error) {
var timeout int var timeout int
if timeoutLabel := GetLabel(compose, stackName, "timeout"); timeoutLabel != "" { if timeoutLabel := GetLabel(compose, stackName, "timeout"); timeoutLabel != "" {

View File

@ -4,15 +4,15 @@ import (
"testing" "testing"
appPkg "coopcloud.tech/abra/pkg/app" appPkg "coopcloud.tech/abra/pkg/app"
"coopcloud.tech/abra/pkg/test"
testPkg "coopcloud.tech/abra/pkg/test" testPkg "coopcloud.tech/abra/pkg/test"
stack "coopcloud.tech/abra/pkg/upstream/stack"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestGetTimeoutFromLabel(t *testing.T) { func TestGetTimeoutFromLabel(t *testing.T) {
testPkg.MkServerAppRecipe() test.Setup()
defer testPkg.RmServerAppRecipe() t.Cleanup(func() { test.Teardown() })
tests := []struct { tests := []struct {
configuredTimeout string configuredTimeout string
@ -25,7 +25,7 @@ func TestGetTimeoutFromLabel(t *testing.T) {
} }
for _, test := range tests { for _, test := range tests {
app, err := appPkg.GetApp(testPkg.ExpectedAppFiles, testPkg.AppName) app, err := appPkg.GetApp(expectedAppFiles, testPkg.AppName)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -39,15 +39,8 @@ func TestGetTimeoutFromLabel(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
deployOpts := stack.Deploy{ app.Env["STACK_NAME"] = app.StackName()
Composefiles: composeFiles, compose, err := appPkg.GetAppComposeConfig(composeFiles, app.Env)
Namespace: app.StackName(),
Prune: false,
ResolveImage: stack.ResolveImageAlways,
Detach: false,
}
compose, err := appPkg.GetAppComposeConfig(app.Name, deployOpts, app.Env)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View File

@ -120,7 +120,7 @@ func CommandNameComplete(appName string) ([]string, cobra.ShellCompDirective) {
func SecretComplete(recipeName string) ([]string, cobra.ShellCompDirective) { func SecretComplete(recipeName string) ([]string, cobra.ShellCompDirective) {
r := recipe.Get(recipeName) r := recipe.Get(recipeName)
config, err := r.GetComposeConfig(nil) config, err := r.GetComposeConfig()
if err != nil { if err != nil {
err := i18n.G("autocomplete failed: %s", err) err := i18n.G("autocomplete failed: %s", err)
return []string{err}, cobra.ShellCompDirectiveError return []string{err}, cobra.ShellCompDirectiveError

View File

@ -10,8 +10,9 @@ import (
func TestFindAbraConfig(t *testing.T) { func TestFindAbraConfig(t *testing.T) {
wd, err := os.Getwd() wd, err := os.Getwd()
if err != nil { if err != nil {
log.Fatal(err) t.Fatal(err)
} }
tests := []struct { tests := []struct {
Dir string Dir string
Config string Config string
@ -51,8 +52,9 @@ func TestFindAbraConfig(t *testing.T) {
func TestLoadAbraConfigGetAbraDir(t *testing.T) { func TestLoadAbraConfigGetAbraDir(t *testing.T) {
wd, err := os.Getwd() wd, err := os.Getwd()
if err != nil { if err != nil {
log.Fatal(err) t.Fatal(err)
} }
t.Setenv("ABRA_DIR", "") t.Setenv("ABRA_DIR", "")
t.Run("default", func(t *testing.T) { t.Run("default", func(t *testing.T) {
@ -67,7 +69,7 @@ func TestLoadAbraConfigGetAbraDir(t *testing.T) {
t.Cleanup(func() { os.Chdir(wd) }) t.Cleanup(func() { os.Chdir(wd) })
err = os.Chdir(filepath.Join(wd, "testdata/abraconfig1")) err = os.Chdir(filepath.Join(wd, "testdata/abraconfig1"))
if err != nil { if err != nil {
log.Fatal(err) t.Fatal(err)
} }
cfg := LoadAbraConfig() cfg := LoadAbraConfig()
@ -81,7 +83,7 @@ func TestLoadAbraConfigGetAbraDir(t *testing.T) {
t.Cleanup(func() { os.Chdir(wd) }) t.Cleanup(func() { os.Chdir(wd) })
err := os.Chdir(filepath.Join(wd, "testdata/abraconfig2")) err := os.Chdir(filepath.Join(wd, "testdata/abraconfig2"))
if err != nil { if err != nil {
log.Fatal(err) t.Fatal(err)
} }
cfg := LoadAbraConfig() cfg := LoadAbraConfig()
@ -104,8 +106,9 @@ func TestLoadAbraConfigGetAbraDir(t *testing.T) {
func TestLoadAbraConfigServersDir(t *testing.T) { func TestLoadAbraConfigServersDir(t *testing.T) {
wd, err := os.Getwd() wd, err := os.Getwd()
if err != nil { if err != nil {
log.Fatal(err) t.Fatal(err)
} }
t.Setenv("ABRA_DIR", "") t.Setenv("ABRA_DIR", "")
t.Run("default", func(t *testing.T) { t.Run("default", func(t *testing.T) {
@ -120,7 +123,7 @@ func TestLoadAbraConfigServersDir(t *testing.T) {
t.Cleanup(func() { os.Chdir(wd) }) t.Cleanup(func() { os.Chdir(wd) })
err = os.Chdir(filepath.Join(wd, "testdata/abraconfig1")) err = os.Chdir(filepath.Join(wd, "testdata/abraconfig1"))
if err != nil { if err != nil {
log.Fatal(err) t.Fatal(err)
} }
cfg := LoadAbraConfig() cfg := LoadAbraConfig()

View File

@ -14,8 +14,8 @@ import (
"coopcloud.tech/abra/pkg/recipe" "coopcloud.tech/abra/pkg/recipe"
"coopcloud.tech/abra/pkg/secret" "coopcloud.tech/abra/pkg/secret"
composeGoTypes "github.com/compose-spec/compose-go/v2/types"
"github.com/distribution/reference" "github.com/distribution/reference"
composetypes "github.com/docker/cli/cli/compose/types"
"github.com/docker/docker/api/types/swarm" "github.com/docker/docker/api/types/swarm"
dockerClient "github.com/docker/docker/client" dockerClient "github.com/docker/docker/client"
) )
@ -229,7 +229,7 @@ func GatherSecretsForDeploy(cl *dockerClient.Client, app appPkg.App, showUnchang
return secretInfo, nil return secretInfo, nil
} }
func GatherConfigsForDeploy(cl *dockerClient.Client, app appPkg.App, compose *composetypes.Config, abraShEnv map[string]string, showUnchanged bool) ([]string, error) { func GatherConfigsForDeploy(cl *dockerClient.Client, app appPkg.App, compose *composeGoTypes.Project, abraShEnv map[string]string, showUnchanged bool) ([]string, error) {
// Get current configs from existing deployment // Get current configs from existing deployment
currentConfigs, err := GetConfigsForStack(cl, app) currentConfigs, err := GetConfigsForStack(cl, app)
if err != nil { if err != nil {
@ -268,7 +268,7 @@ func GatherConfigsForDeploy(cl *dockerClient.Client, app appPkg.App, compose *co
return configInfo, nil return configInfo, nil
} }
func GatherImagesForDeploy(cl *dockerClient.Client, app appPkg.App, compose *composetypes.Config, showUnchanged bool) ([]string, error) { func GatherImagesForDeploy(cl *dockerClient.Client, app appPkg.App, compose *composeGoTypes.Project, showUnchanged bool) ([]string, error) {
// Get current images from existing deployment // Get current images from existing deployment
currentImages, err := GetImagesForStack(cl, app) currentImages, err := GetImagesForStack(cl, app)
if err != nil { if err != nil {

View File

@ -10,48 +10,73 @@ import (
"coopcloud.tech/abra/pkg/config" "coopcloud.tech/abra/pkg/config"
"coopcloud.tech/abra/pkg/envfile" "coopcloud.tech/abra/pkg/envfile"
"coopcloud.tech/abra/pkg/recipe" "coopcloud.tech/abra/pkg/recipe"
testPkg "coopcloud.tech/abra/pkg/test" "coopcloud.tech/abra/pkg/test"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
var (
expectedAppEnv = envfile.AppEnv{
"DOMAIN": test.AppName,
"RECIPE": test.RecipeName,
}
expectedAppFile = appPkg.AppFile{
Path: test.AppEnvPath,
Server: test.ServerName,
}
expectedAppFiles = map[string]appPkg.AppFile{
test.AppName: expectedAppFile,
}
)
func TestGetAllFoldersInDirectory(t *testing.T) { func TestGetAllFoldersInDirectory(t *testing.T) {
folders, err := config.GetAllFoldersInDirectory(testPkg.TestDir) folders, err := config.GetAllFoldersInDirectory(test.TestDir)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if !reflect.DeepEqual(folders, testPkg.TFolders) {
t.Fatalf("did not get expected folders. Expected: (%s), Got: (%s)", strings.Join(testPkg.TFolders, ","), strings.Join(folders, ",")) if !reflect.DeepEqual(folders, test.TFolders) {
t.Fatalf("did not get expected folders. Expected: (%s), Got: (%s)", strings.Join(test.TFolders, ","), strings.Join(folders, ","))
} }
} }
func TestGetAllFilesInDirectory(t *testing.T) { func TestGetAllFilesInDirectory(t *testing.T) {
files, err := config.GetAllFilesInDirectory(testPkg.TestDir) files, err := config.GetAllFilesInDirectory(test.TestDir)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
var fileNames []string var fileNames []string
for _, file := range files { for _, file := range files {
fileNames = append(fileNames, file.Name()) fileNames = append(fileNames, file.Name())
} }
if !reflect.DeepEqual(fileNames, testPkg.TFiles) {
t.Fatalf("did not get expected files. Expected: (%s), Got: (%s)", strings.Join(testPkg.TFiles, ","), strings.Join(fileNames, ",")) if !reflect.DeepEqual(fileNames, test.TFiles) {
t.Fatalf("did not get expected files. Expected: (%s), Got: (%s)", strings.Join(test.TFiles, ","), strings.Join(fileNames, ","))
} }
} }
func TestReadEnv(t *testing.T) { func TestReadEnv(t *testing.T) {
env, err := envfile.ReadEnv(testPkg.ExpectedAppFile.Path) test.Setup()
t.Cleanup(func() { test.Teardown() })
env, err := envfile.ReadEnv(expectedAppFile.Path)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if !reflect.DeepEqual(env, testPkg.ExpectedAppEnv) {
if !reflect.DeepEqual(env, expectedAppEnv) {
t.Fatal("did not get expected application settings") t.Fatal("did not get expected application settings")
} }
} }
func TestReadAbraShEnvVars(t *testing.T) { func TestReadAbraShEnvVars(t *testing.T) {
r := recipe.Get("abra-test-recipe") test.Setup()
err := r.EnsureExists() t.Cleanup(func() { test.Teardown() })
if err != nil {
r := recipe.Get(test.AbraTestRecipe)
if err := r.EnsureExists(); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -78,9 +103,11 @@ func TestReadAbraShEnvVars(t *testing.T) {
} }
func TestReadAbraShCmdNames(t *testing.T) { func TestReadAbraShCmdNames(t *testing.T) {
r := recipe.Get("abra-test-recipe") test.Setup()
err := r.EnsureExists() t.Cleanup(func() { test.Teardown() })
if err != nil {
r := recipe.Get(test.AbraTestRecipe)
if err := r.EnsureExists(); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -102,9 +129,11 @@ func TestReadAbraShCmdNames(t *testing.T) {
} }
func TestCheckEnv(t *testing.T) { func TestCheckEnv(t *testing.T) {
r := recipe.Get("abra-test-recipe") test.Setup()
err := r.EnsureExists() t.Cleanup(func() { test.Teardown() })
if err != nil {
r := recipe.Get(test.AbraTestRecipe)
if err := r.EnsureExists(); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -135,9 +164,11 @@ func TestCheckEnv(t *testing.T) {
} }
func TestCheckEnvError(t *testing.T) { func TestCheckEnvError(t *testing.T) {
r := recipe.Get("abra-test-recipe") test.Setup()
err := r.EnsureExists() t.Cleanup(func() { test.Teardown() })
if err != nil {
r := recipe.Get(test.AbraTestRecipe)
if err := r.EnsureExists(); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -170,9 +201,11 @@ func TestCheckEnvError(t *testing.T) {
} }
func TestEnvVarCommentsRemoved(t *testing.T) { func TestEnvVarCommentsRemoved(t *testing.T) {
r := recipe.Get("abra-test-recipe") test.Setup()
err := r.EnsureExists() t.Cleanup(func() { test.Teardown() })
if err != nil {
r := recipe.Get(test.AbraTestRecipe)
if err := r.EnsureExists(); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -201,9 +234,11 @@ func TestEnvVarCommentsRemoved(t *testing.T) {
} }
func TestEnvVarModifiersIncluded(t *testing.T) { func TestEnvVarModifiersIncluded(t *testing.T) {
r := recipe.Get("abra-test-recipe") test.Setup()
err := r.EnsureExists() t.Cleanup(func() { test.Teardown() })
if err != nil {
r := recipe.Get(test.AbraTestRecipe)
if err := r.EnsureExists(); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -225,7 +260,10 @@ func TestEnvVarModifiersIncluded(t *testing.T) {
} }
func TestNoOverwriteNonVersionEnvVars(t *testing.T) { func TestNoOverwriteNonVersionEnvVars(t *testing.T) {
app, err := appPkg.GetApp(testPkg.ExpectedAppFiles, testPkg.AppName) test.Setup()
t.Cleanup(func() { test.Teardown() })
app, err := appPkg.GetApp(expectedAppFiles, test.AppName)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -234,7 +272,7 @@ func TestNoOverwriteNonVersionEnvVars(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
app, err = appPkg.GetApp(testPkg.ExpectedAppFiles, testPkg.AppName) app, err = appPkg.GetApp(expectedAppFiles, test.AppName)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View File

@ -8,11 +8,44 @@ import (
"testing" "testing"
"coopcloud.tech/abra/pkg/config" "coopcloud.tech/abra/pkg/config"
"coopcloud.tech/abra/pkg/log"
) )
func setup() {
teardown()
if err := os.Mkdir(os.ExpandEnv("$ABRA_DIR"), 0764); err != nil {
if !os.IsExist(err) {
log.Fatal(err)
}
}
if err := os.Mkdir(os.ExpandEnv("$ABRA_DIR/recipes"), 0764); err != nil {
if !os.IsExist(err) {
log.Fatal(err)
}
}
}
func teardown() {
abraDir := os.ExpandEnv("$ABRA_DIR")
if abraDir == fmt.Sprintf("%s/.abra", os.ExpandEnv("$HOME")) {
log.Fatal("set $ABRA_DIR before running the test suite")
}
if err := os.RemoveAll(abraDir); err != nil {
log.Fatal(err)
}
}
func TestClone(t *testing.T) { func TestClone(t *testing.T) {
setup()
t.Cleanup(func() { teardown() })
dir := path.Join(config.RECIPES_DIR, "gitea") dir := path.Join(config.RECIPES_DIR, "gitea")
os.RemoveAll(dir) if err := os.RemoveAll(dir); err != nil {
t.Fatal(err)
}
gitURL := fmt.Sprintf("%s/%s.git", config.REPOS_BASE_URL, "gitea") gitURL := fmt.Sprintf("%s/%s.git", config.REPOS_BASE_URL, "gitea")
if err := Clone(dir, gitURL); err != nil { if err := Clone(dir, gitURL); err != nil {
@ -25,6 +58,11 @@ func TestClone(t *testing.T) {
} }
func TestCancelGitClone(t *testing.T) { func TestCancelGitClone(t *testing.T) {
t.Skip("https://git.coopcloud.tech/toolshed/abra/issues/814")
setup()
t.Cleanup(func() { teardown() })
dir := path.Join(config.RECIPES_DIR, "gitea") dir := path.Join(config.RECIPES_DIR, "gitea")
os.RemoveAll(dir) os.RemoveAll(dir)

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

@ -62,13 +62,6 @@ func (l LintRule) Skip(recipe recipe.Recipe) bool {
var LintRules = map[string][]LintRule{ var LintRules = map[string][]LintRule{
"warn": { "warn": {
{
Ref: "R001",
Level: i18n.G("warn"),
Description: i18n.G("compose config has expected version"),
HowToResolve: i18n.G("ensure 'version: \"3.8\"' in compose configs"),
Function: LintComposeVersion,
},
{ {
Ref: "R002", Ref: "R002",
Level: i18n.G("warn"), Level: i18n.G("warn"),
@ -217,18 +210,6 @@ func LintForErrors(recipe recipe.Recipe) error {
return nil return nil
} }
func LintComposeVersion(recipe recipe.Recipe) (bool, error) {
config, err := recipe.GetComposeConfig(nil)
if err != nil {
return false, err
}
if config.Version == "3.8" {
return true, nil
}
return true, nil
}
func LintEnvConfigPresent(r recipe.Recipe) (bool, error) { func LintEnvConfigPresent(r recipe.Recipe) (bool, error) {
if _, err := os.Stat(r.SampleEnvPath); !os.IsNotExist(err) { if _, err := os.Stat(r.SampleEnvPath); !os.IsNotExist(err) {
return true, nil return true, nil
@ -238,7 +219,7 @@ func LintEnvConfigPresent(r recipe.Recipe) (bool, error) {
} }
func LintAppService(recipe recipe.Recipe) (bool, error) { func LintAppService(recipe recipe.Recipe) (bool, error) {
config, err := recipe.GetComposeConfig(nil) config, err := recipe.GetComposeConfig()
if err != nil { if err != nil {
return false, err return false, err
} }
@ -269,7 +250,7 @@ func LintTraefikEnabledSkipCondition(r recipe.Recipe) (bool, error) {
} }
func LintTraefikEnabled(recipe recipe.Recipe) (bool, error) { func LintTraefikEnabled(recipe recipe.Recipe) (bool, error) {
config, err := recipe.GetComposeConfig(nil) config, err := recipe.GetComposeConfig()
if err != nil { if err != nil {
return false, err return false, err
} }
@ -287,7 +268,7 @@ func LintTraefikEnabled(recipe recipe.Recipe) (bool, error) {
} }
func LintDeployLabelsPresent(recipe recipe.Recipe) (bool, error) { func LintDeployLabelsPresent(recipe recipe.Recipe) (bool, error) {
config, err := recipe.GetComposeConfig(nil) config, err := recipe.GetComposeConfig()
if err != nil { if err != nil {
return false, err return false, err
} }
@ -302,7 +283,7 @@ func LintDeployLabelsPresent(recipe recipe.Recipe) (bool, error) {
} }
func LintHealthchecks(recipe recipe.Recipe) (bool, error) { func LintHealthchecks(recipe recipe.Recipe) (bool, error) {
config, err := recipe.GetComposeConfig(nil) config, err := recipe.GetComposeConfig()
if err != nil { if err != nil {
return false, err return false, err
} }
@ -316,7 +297,7 @@ func LintHealthchecks(recipe recipe.Recipe) (bool, error) {
} }
func LintAllImagesTagged(recipe recipe.Recipe) (bool, error) { func LintAllImagesTagged(recipe recipe.Recipe) (bool, error) {
config, err := recipe.GetComposeConfig(nil) config, err := recipe.GetComposeConfig()
if err != nil { if err != nil {
return false, err return false, err
} }
@ -334,7 +315,7 @@ func LintAllImagesTagged(recipe recipe.Recipe) (bool, error) {
} }
func LintNoUnstableTags(recipe recipe.Recipe) (bool, error) { func LintNoUnstableTags(recipe recipe.Recipe) (bool, error) {
config, err := recipe.GetComposeConfig(nil) config, err := recipe.GetComposeConfig()
if err != nil { if err != nil {
return false, err return false, err
} }
@ -361,7 +342,7 @@ func LintNoUnstableTags(recipe recipe.Recipe) (bool, error) {
} }
func LintSemverLikeTags(recipe recipe.Recipe) (bool, error) { func LintSemverLikeTags(recipe recipe.Recipe) (bool, error) {
config, err := recipe.GetComposeConfig(nil) config, err := recipe.GetComposeConfig()
if err != nil { if err != nil {
return false, err return false, err
} }
@ -388,7 +369,7 @@ func LintSemverLikeTags(recipe recipe.Recipe) (bool, error) {
} }
func LintImagePresent(recipe recipe.Recipe) (bool, error) { func LintImagePresent(recipe recipe.Recipe) (bool, error) {
config, err := recipe.GetComposeConfig(nil) config, err := recipe.GetComposeConfig()
if err != nil { if err != nil {
return false, err return false, err
} }
@ -440,7 +421,7 @@ func LintMetadataFilledIn(r recipe.Recipe) (bool, error) {
} }
func LintAbraShVendors(recipe recipe.Recipe) (bool, error) { func LintAbraShVendors(recipe recipe.Recipe) (bool, error) {
config, err := recipe.GetComposeConfig(nil) config, err := recipe.GetComposeConfig()
if err != nil { if err != nil {
return false, err return false, err
} }
@ -472,7 +453,7 @@ func LintHasRecipeRepo(recipe recipe.Recipe) (bool, error) {
} }
func LintSecretLengths(recipe recipe.Recipe) (bool, error) { func LintSecretLengths(recipe recipe.Recipe) (bool, error) {
config, err := recipe.GetComposeConfig(nil) config, err := recipe.GetComposeConfig()
if err != nil { if err != nil {
return false, err return false, err
} }

View File

@ -11,10 +11,9 @@ import (
"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/log"
"coopcloud.tech/abra/pkg/upstream/stack"
loader "coopcloud.tech/abra/pkg/upstream/stack" loader "coopcloud.tech/abra/pkg/upstream/stack"
composeGoTypes "github.com/compose-spec/compose-go/v2/types"
"github.com/distribution/reference" "github.com/distribution/reference"
composetypes "github.com/docker/cli/cli/compose/types"
) )
// GetComposeFiles gets the list of compose files for an app (or recipe if you // GetComposeFiles gets the list of compose files for an app (or recipe if you
@ -61,7 +60,7 @@ func (r Recipe) GetComposeFiles(appEnv map[string]string) ([]string, error) {
return composeFiles, nil return composeFiles, nil
} }
func (r Recipe) GetComposeConfig(env map[string]string) (*composetypes.Config, error) { func (r Recipe) GetComposeConfig() (*composeGoTypes.Project, error) {
pattern := fmt.Sprintf("%s/compose**yml", r.Dir) pattern := fmt.Sprintf("%s/compose**yml", r.Dir)
composeFiles, err := filepath.Glob(pattern) composeFiles, err := filepath.Glob(pattern)
if err != nil { if err != nil {
@ -72,25 +71,18 @@ func (r Recipe) GetComposeConfig(env map[string]string) (*composetypes.Config, e
return nil, errors.New(i18n.G("%s is missing a compose.yml or compose.*.yml file?", r.Name)) return nil, errors.New(i18n.G("%s is missing a compose.yml or compose.*.yml file?", r.Name))
} }
if env == nil { config, err := loader.LoadCompose(loader.LoadConf{ComposeFiles: composeFiles})
env, err = r.SampleEnv()
if err != nil {
return nil, err
}
}
opts := stack.Deploy{Composefiles: composeFiles}
config, err := loader.LoadComposefile(opts, env)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return config, nil return config, nil
} }
// GetVersionLabelLocal retrieves the version label on the local recipe config // GetVersionLabelLocal retrieves the version label on the local recipe config
func (r Recipe) GetVersionLabelLocal() (string, error) { func (r Recipe) GetVersionLabelLocal() (string, error) {
var label string var label string
config, err := r.GetComposeConfig(nil) config, err := r.GetComposeConfig()
if err != nil { if err != nil {
return "", err return "", err
} }
@ -123,14 +115,7 @@ func (r Recipe) UpdateTag(image, tag string) (bool, error) {
log.Debug(i18n.G("considering %s config(s) for tag update", strings.Join(composeFiles, ", "))) log.Debug(i18n.G("considering %s config(s) for tag update", strings.Join(composeFiles, ", ")))
for _, composeFile := range composeFiles { for _, composeFile := range composeFiles {
opts := stack.Deploy{Composefiles: []string{composeFile}} compose, err := loader.LoadCompose(loader.LoadConf{ComposeFiles: []string{composeFile}})
sampleEnv, err := r.SampleEnv()
if err != nil {
return false, err
}
compose, err := loader.LoadComposefile(opts, sampleEnv)
if err != nil { if err != nil {
return false, err return false, err
} }
@ -168,9 +153,9 @@ func (r Recipe) UpdateTag(image, tag string) (bool, error) {
new := fmt.Sprintf("%s:%s", composeImage, tag) new := fmt.Sprintf("%s:%s", composeImage, tag)
replacedBytes := strings.Replace(string(bytes), old, new, -1) replacedBytes := strings.Replace(string(bytes), old, new, -1)
log.Debug(i18n.G("updating %s to %s in %s", old, new, compose.Filename)) log.Debug(i18n.G("updating %s to %s in %s", old, new, compose.Name))
if err := os.WriteFile(compose.Filename, []byte(replacedBytes), 0o764); err != nil { if err := os.WriteFile(compose.Name, []byte(replacedBytes), 0o764); err != nil {
return false, err return false, err
} }
} }
@ -191,20 +176,13 @@ func (r Recipe) UpdateLabel(pattern, serviceName, label string) error {
log.Debug(i18n.G("considering %s config(s) for label update", strings.Join(composeFiles, ", "))) log.Debug(i18n.G("considering %s config(s) for label update", strings.Join(composeFiles, ", ")))
for _, composeFile := range composeFiles { for _, composeFile := range composeFiles {
opts := stack.Deploy{Composefiles: []string{composeFile}} compose, err := loader.LoadCompose(loader.LoadConf{ComposeFiles: []string{composeFile}})
sampleEnv, err := r.SampleEnv()
if err != nil {
return err
}
compose, err := loader.LoadComposefile(opts, sampleEnv)
if err != nil { if err != nil {
return err return err
} }
serviceExists := false serviceExists := false
var service composetypes.ServiceConfig var service composeGoTypes.ServiceConfig
for _, s := range compose.Services { for _, s := range compose.Services {
if s.Name == serviceName { if s.Name == serviceName {
service = s service = s
@ -234,9 +212,9 @@ func (r Recipe) UpdateLabel(pattern, serviceName, label string) error {
return nil return nil
} }
log.Debug(i18n.G("updating %s to %s in %s", old, label, compose.Filename)) log.Debug(i18n.G("updating %s to %s in %s", old, label, compose.Name))
if err := ioutil.WriteFile(compose.Filename, []byte(replacedBytes), 0o764); err != nil { if err := ioutil.WriteFile(compose.Name, []byte(replacedBytes), 0o764); err != nil {
return err return err
} }

View File

@ -416,7 +416,7 @@ func (r Recipe) GetRecipeVersions() (RecipeVersions, []string, error) {
log.Debug(i18n.G("git checkout: %s in %s", ref.Name(), r.Dir)) log.Debug(i18n.G("git checkout: %s in %s", ref.Name(), r.Dir))
config, err := r.GetComposeConfig(nil) config, err := r.GetComposeConfig()
if err != nil { if err != nil {
log.Debug(i18n.G("failed to get compose config for %s: %s", tag, err)) log.Debug(i18n.G("failed to get compose config for %s: %s", tag, err))
warnMsg = append(warnMsg, i18n.G("skipping tag %s: invalid compose config: %s", tag, err)) warnMsg = append(warnMsg, i18n.G("skipping tag %s: invalid compose config: %s", tag, err))

View File

@ -5,12 +5,15 @@ import (
"path/filepath" "path/filepath"
"testing" "testing"
"coopcloud.tech/abra/pkg/test"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestIsDirty(t *testing.T) { func TestIsDirty(t *testing.T) {
r := Get("abra-test-recipe") test.Setup()
t.Cleanup(func() { test.Teardown() })
r := Get(test.RecipeName)
if err := r.EnsureExists(); err != nil { if err := r.EnsureExists(); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -22,10 +25,9 @@ func TestIsDirty(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer f.Close() defer f.Close()
defer t.Cleanup(func() { t.Cleanup(func() { os.Remove(fpath) })
os.Remove(fpath)
})
dirty, err := r.IsDirty() dirty, err := r.IsDirty()
if err != nil { if err != nil {

View File

@ -5,6 +5,7 @@ import (
"testing" "testing"
"coopcloud.tech/abra/pkg/config" "coopcloud.tech/abra/pkg/config"
"coopcloud.tech/abra/pkg/test"
"github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -101,24 +102,24 @@ func TestGet(t *testing.T) {
} }
func TestGetVersionLabelLocalDoesNotUseTimeoutLabel(t *testing.T) { func TestGetVersionLabelLocalDoesNotUseTimeoutLabel(t *testing.T) {
r := Get("traefik") test.Setup()
t.Cleanup(func() { test.Teardown() })
r := Get(test.RecipeName)
if err := r.EnsureExists(); err != nil { if err := r.EnsureExists(); err != nil {
t.Fatal(err) t.Fatal(err)
} }
for i := 1; i < 50; i++ { timeout := "120"
if err := test.AddEnv("TIMEOUT", timeout); err != nil {
t.Fatal(err)
}
for i := 1; i < 3; i++ {
label, err := r.GetVersionLabelLocal() label, err := r.GetVersionLabelLocal()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
assert.NotEqual(t, label, timeout)
// NOTE(d1): this is potentially quite a brittle unit test as it needs to
// hardcode the default timeout label to ensure that the label parser never
// returns it. hopefully this won't fail too often! if you're here because
// of a failure, just update the `defaultTimeoutLabel` value & permalink
// below
// https://git.coopcloud.tech/coop-cloud/traefik/src/commit/ac3a47fe8ca3ef92db84f64cfedfbb348000faee/.env.sample#L2
defaultTimeoutLabel := "300"
assert.NotEqual(t, label, defaultTimeoutLabel)
} }
} }

View File

@ -20,7 +20,6 @@ import (
"coopcloud.tech/abra/pkg/envfile" "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"
"coopcloud.tech/abra/pkg/upstream/stack"
loader "coopcloud.tech/abra/pkg/upstream/stack" loader "coopcloud.tech/abra/pkg/upstream/stack"
"github.com/decentral1se/passgen" "github.com/decentral1se/passgen"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
@ -122,14 +121,13 @@ func ReadSecretsConfig(appEnvPath string, composeFiles []string, stackName strin
// Set the STACK_NAME to be able to generate the remote name correctly. // Set the STACK_NAME to be able to generate the remote name correctly.
appEnv["STACK_NAME"] = stackName appEnv["STACK_NAME"] = stackName
opts := stack.Deploy{Composefiles: composeFiles} composeConfig, err := loader.LoadCompose(loader.LoadConf{ComposeFiles: composeFiles, AppEnv: appEnv})
composeConfig, err := loader.LoadComposefile(opts, appEnv)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Read the compose files without injecting environment variables. // Read the compose files without injecting environment variables.
configWithoutEnv, err := loader.LoadComposefile(opts, map[string]string{}, loader.SkipInterpolation) configWithoutEnv, err := loader.LoadCompose(loader.LoadConf{ComposeFiles: composeFiles})
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -2,57 +2,53 @@ package test
import ( import (
"fmt" "fmt"
"log"
"os" "os"
"path" "path"
"path/filepath"
"runtime"
appPkg "coopcloud.tech/abra/pkg/app" gitPkg "coopcloud.tech/abra/pkg/git"
"coopcloud.tech/abra/pkg/envfile" "git.coopcloud.tech/toolshed/godotenv"
"coopcloud.tech/abra/pkg/log"
"coopcloud.tech/abra/pkg/recipe"
) )
var ( var (
AppName = "test_app.example.com" AppName = "test_app.example.com"
StackName = "test_app_example_com"
ServerName = "test_server" ServerName = "test_server"
TFiles = []string{"bar.env", "foo.env"} RecipeName = "test_recipe"
TFolders = []string{"dir1", "dir2"}
TestServer = os.ExpandEnv("$PWD/../../tests/resources/test_server")
TestDir = os.ExpandEnv("$PWD/../../tests/resources/test_dir")
ExpectedAppEnv = envfile.AppEnv{ TFiles = []string{"bar.env", "foo.env"}
"DOMAIN": "test_app.example.com", TFolders = []string{"dir1", "dir2"}
"RECIPE": "test_recipe",
}
ExpectedApp = appPkg.App{ ServerDir = os.ExpandEnv("$ABRA_DIR/servers/test_server")
Name: AppName, RecipeDir = os.ExpandEnv("$ABRA_DIR/recipes/test_recipe")
Recipe: recipe.Get(ExpectedAppEnv["RECIPE"]), TestDir = os.ExpandEnv("$PWD/../../tests/resources/test_dir")
Domain: ExpectedAppEnv["DOMAIN"],
Env: ExpectedAppEnv,
Path: ExpectedAppFile.Path,
Server: ExpectedAppFile.Server,
}
ExpectedAppFile = appPkg.AppFile{ AppEnvPath = path.Join(ServerDir, fmt.Sprintf("%s.env", AppName))
Path: path.Join(TestServer, fmt.Sprintf("%s.env", AppName)),
Server: ServerName,
}
ExpectedAppFiles = map[string]appPkg.AppFile{ AbraTestRecipe = "abra-test-recipe"
AppName: ExpectedAppFile,
}
) )
func RmServerAppRecipe() { func Teardown() {
testAppLink := os.ExpandEnv("$ABRA_DIR/servers/test_server") abraDir := os.ExpandEnv("$ABRA_DIR")
os.Remove(testAppLink) if abraDir == fmt.Sprintf("%s/.abra", os.ExpandEnv("$HOME")) {
log.Fatal("set $ABRA_DIR before running the test suite")
}
testRecipeLink := os.ExpandEnv("$ABRA_DIR/recipes/test_recipe") if err := os.RemoveAll(abraDir); err != nil {
os.Remove(testRecipeLink) log.Fatal(err)
}
} }
func MkServerAppRecipe() { func Setup() {
RmServerAppRecipe() Teardown()
if err := os.Mkdir(os.ExpandEnv("$ABRA_DIR"), 0764); err != nil {
if !os.IsExist(err) {
log.Fatal(err)
}
}
if err := os.Mkdir(os.ExpandEnv("$ABRA_DIR/servers"), 0700); err != nil { if err := os.Mkdir(os.ExpandEnv("$ABRA_DIR/servers"), 0700); err != nil {
if !os.IsExist(err) { if !os.IsExist(err) {
@ -66,15 +62,42 @@ func MkServerAppRecipe() {
} }
} }
testAppDir := os.ExpandEnv("$PWD/../../tests/resources/test_server") _, f, _, ok := runtime.Caller(0)
testAppLink := os.ExpandEnv("$ABRA_DIR/servers/test_server") if !ok {
if err := os.Symlink(testAppDir, testAppLink); err != nil { log.Fatal("Setup: unable to discover current working directory of file")
}
pwd := filepath.Dir(f)
serverSrcDir := filepath.Join(pwd, "/../../tests/resources/test_server")
serverDestDir := os.ExpandEnv("$ABRA_DIR/servers/test_server")
if err := os.CopyFS(serverDestDir, os.DirFS(serverSrcDir)); err != nil {
log.Fatal(err) log.Fatal(err)
} }
testRecipeDir := os.ExpandEnv("$PWD/../../tests/resources/test_recipe") recipeSrcDir := filepath.Join(pwd, "/../../tests/resources/test_recipe")
testRecipeLink := os.ExpandEnv("$ABRA_DIR/recipes/test_recipe") recipeDestDir := os.ExpandEnv("$ABRA_DIR/recipes/test_recipe")
if err := os.Symlink(testRecipeDir, testRecipeLink); err != nil { if err := os.CopyFS(recipeDestDir, os.DirFS(recipeSrcDir)); err != nil {
log.Fatal(err)
}
if err := gitPkg.Init(recipeDestDir, true, "tester", "helo@coopcloud.tech"); err != nil {
log.Fatal(err) log.Fatal(err)
} }
} }
func AddEnv(envKey, envValue string) error {
filePath := os.ExpandEnv(fmt.Sprintf("$ABRA_DIR/servers/%s/%s.env", ServerName, AppName))
envVars, _, err := godotenv.Read(filePath)
if err != nil {
return err
}
envVars[envKey] = envValue
if err := godotenv.Write(envVars, filePath); err != nil {
return err
}
return nil
}

View File

@ -4,7 +4,7 @@ import (
"io/ioutil" "io/ioutil"
"strings" "strings"
composetypes "github.com/docker/cli/cli/compose/types" composeGoTypes "github.com/compose-spec/compose-go/v2/types"
networktypes "github.com/docker/docker/api/types/network" networktypes "github.com/docker/docker/api/types/network"
"github.com/docker/docker/api/types/swarm" "github.com/docker/docker/api/types/swarm"
) )
@ -48,19 +48,17 @@ func AddStackLabel(namespace Namespace, labels map[string]string) map[string]str
return labels return labels
} }
type networkMap map[string]composetypes.NetworkConfig
// Networks from the compose-file type to the engine API type // Networks from the compose-file type to the engine API type
func Networks(namespace Namespace, networks networkMap, servicesNetworks map[string]struct{}) (map[string]networktypes.CreateOptions, []string) { func Networks(namespace Namespace, networks map[string]composeGoTypes.NetworkConfig, servicesNetworks map[string]struct{}) (map[string]networktypes.CreateOptions, []string) {
if networks == nil { if networks == nil {
networks = make(map[string]composetypes.NetworkConfig) networks = make(map[string]composeGoTypes.NetworkConfig)
} }
externalNetworks := []string{} externalNetworks := []string{}
result := make(map[string]networktypes.CreateOptions) result := make(map[string]networktypes.CreateOptions)
for internalName := range servicesNetworks { for internalName := range servicesNetworks {
network := networks[internalName] network := networks[internalName]
if network.External.External { if network.External {
externalNetworks = append(externalNetworks, network.Name) externalNetworks = append(externalNetworks, network.Name)
continue continue
} }
@ -98,19 +96,19 @@ func Networks(namespace Namespace, networks networkMap, servicesNetworks map[str
} }
// Secrets converts secrets from the Compose type to the engine API type // Secrets converts secrets from the Compose type to the engine API type
func Secrets(namespace Namespace, secrets map[string]composetypes.SecretConfig) ([]swarm.SecretSpec, error) { func Secrets(namespace Namespace, secrets map[string]composeGoTypes.SecretConfig) ([]swarm.SecretSpec, error) {
result := []swarm.SecretSpec{} result := []swarm.SecretSpec{}
for name, secret := range secrets { for name, secret := range secrets {
if secret.External.External { if secret.External {
continue continue
} }
var obj swarmFileObject var obj swarmFileObject
var err error var err error
if secret.Driver != "" { if secret.Driver != "" {
obj = driverObjectConfig(namespace, name, composetypes.FileObjectConfig(secret)) obj = driverObjectConfig(namespace, name, composeGoTypes.FileObjectConfig(secret))
} else { } else {
obj, err = fileObjectConfig(namespace, name, composetypes.FileObjectConfig(secret)) obj, err = fileObjectConfig(namespace, name, composeGoTypes.FileObjectConfig(secret))
} }
if err != nil { if err != nil {
return nil, err return nil, err
@ -133,14 +131,14 @@ func Secrets(namespace Namespace, secrets map[string]composetypes.SecretConfig)
} }
// Configs converts config objects from the Compose type to the engine API type // Configs converts config objects from the Compose type to the engine API type
func Configs(namespace Namespace, configs map[string]composetypes.ConfigObjConfig) ([]swarm.ConfigSpec, error) { func Configs(namespace Namespace, configs map[string]composeGoTypes.ConfigObjConfig) ([]swarm.ConfigSpec, error) {
result := []swarm.ConfigSpec{} result := []swarm.ConfigSpec{}
for name, config := range configs { for name, config := range configs {
if config.External.External { if config.External {
continue continue
} }
obj, err := fileObjectConfig(namespace, name, composetypes.FileObjectConfig(config)) obj, err := fileObjectConfig(namespace, name, composeGoTypes.FileObjectConfig(config))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -160,7 +158,7 @@ type swarmFileObject struct {
Data []byte Data []byte
} }
func driverObjectConfig(namespace Namespace, name string, obj composetypes.FileObjectConfig) swarmFileObject { func driverObjectConfig(namespace Namespace, name string, obj composeGoTypes.FileObjectConfig) swarmFileObject {
if obj.Name != "" { if obj.Name != "" {
name = obj.Name name = obj.Name
} else { } else {
@ -176,7 +174,7 @@ func driverObjectConfig(namespace Namespace, name string, obj composetypes.FileO
} }
} }
func fileObjectConfig(namespace Namespace, name string, obj composetypes.FileObjectConfig) (swarmFileObject, error) { func fileObjectConfig(namespace Namespace, name string, obj composeGoTypes.FileObjectConfig) (swarmFileObject, error) {
data, err := ioutil.ReadFile(obj.File) data, err := ioutil.ReadFile(obj.File)
if err != nil { if err != nil {
return swarmFileObject{}, err return swarmFileObject{}, err

View File

@ -1,170 +0,0 @@
package convert // https://github.com/docker/cli/blob/master/cli/compose/convert/compose_test.go
import (
"testing"
composetypes "github.com/docker/cli/cli/compose/types"
"github.com/docker/docker/api/types/network"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
"gotest.tools/v3/fs"
)
func TestNamespaceScope(t *testing.T) {
scoped := Namespace{name: "foo"}.Scope("bar")
assert.Check(t, is.Equal("foo_bar", scoped))
}
func TestAddStackLabel(t *testing.T) {
labels := map[string]string{
"something": "labeled",
}
actual := AddStackLabel(Namespace{name: "foo"}, labels)
expected := map[string]string{
"something": "labeled",
LabelNamespace: "foo",
}
assert.Check(t, is.DeepEqual(expected, actual))
}
func TestNetworks(t *testing.T) {
namespace := Namespace{name: "foo"}
serviceNetworks := map[string]struct{}{
"normal": {},
"outside": {},
"default": {},
"attachablenet": {},
"named": {},
}
source := networkMap{
"normal": composetypes.NetworkConfig{
Driver: "overlay",
DriverOpts: map[string]string{
"opt": "value",
},
Ipam: composetypes.IPAMConfig{
Driver: "driver",
Config: []*composetypes.IPAMPool{
{
Subnet: "10.0.0.0",
},
},
},
Labels: map[string]string{
"something": "labeled",
},
},
"outside": composetypes.NetworkConfig{
External: composetypes.External{External: true},
Name: "special",
},
"attachablenet": composetypes.NetworkConfig{
Driver: "overlay",
Attachable: true,
},
"named": composetypes.NetworkConfig{
Name: "othername",
},
}
expected := map[string]network.CreateOptions{
"foo_default": {
Labels: map[string]string{
LabelNamespace: "foo",
},
},
"foo_normal": {
Driver: "overlay",
IPAM: &network.IPAM{
Driver: "driver",
Config: []network.IPAMConfig{
{
Subnet: "10.0.0.0",
},
},
},
Options: map[string]string{
"opt": "value",
},
Labels: map[string]string{
LabelNamespace: "foo",
"something": "labeled",
},
},
"foo_attachablenet": {
Driver: "overlay",
Attachable: true,
Labels: map[string]string{
LabelNamespace: "foo",
},
},
"othername": {
Labels: map[string]string{LabelNamespace: "foo"},
},
}
networks, externals := Networks(namespace, source, serviceNetworks)
assert.DeepEqual(t, expected, networks)
assert.DeepEqual(t, []string{"special"}, externals)
}
func TestSecrets(t *testing.T) {
namespace := Namespace{name: "foo"}
secretText := "this is the first secret"
secretFile := fs.NewFile(t, "convert-secrets", fs.WithContent(secretText))
defer secretFile.Remove()
source := map[string]composetypes.SecretConfig{
"one": {
File: secretFile.Path(),
Labels: map[string]string{"monster": "mash"},
},
"ext": {
External: composetypes.External{
External: true,
},
},
}
specs, err := Secrets(namespace, source)
assert.NilError(t, err)
assert.Assert(t, is.Len(specs, 1))
secret := specs[0]
assert.Check(t, is.Equal("foo_one", secret.Name))
assert.Check(t, is.DeepEqual(map[string]string{
"monster": "mash",
LabelNamespace: "foo",
}, secret.Labels))
assert.Check(t, is.DeepEqual([]byte(secretText), secret.Data))
}
func TestConfigs(t *testing.T) {
namespace := Namespace{name: "foo"}
configText := "this is the first config"
configFile := fs.NewFile(t, "convert-configs", fs.WithContent(configText))
defer configFile.Remove()
source := map[string]composetypes.ConfigObjConfig{
"one": {
File: configFile.Path(),
Labels: map[string]string{"monster": "mash"},
},
"ext": {
External: composetypes.External{
External: true,
},
},
}
specs, err := Configs(namespace, source)
assert.NilError(t, err)
assert.Assert(t, is.Len(specs, 1))
config := specs[0]
assert.Check(t, is.Equal("foo_one", config.Name))
assert.Check(t, is.DeepEqual(map[string]string{
"monster": "mash",
LabelNamespace: "foo",
}, config.Labels))
assert.Check(t, is.DeepEqual([]byte(configText), config.Data))
}

View File

@ -5,11 +5,13 @@ import (
"fmt" "fmt"
"os" "os"
"sort" "sort"
"strconv"
"strings" "strings"
"time" "time"
"unsafe"
"coopcloud.tech/abra/pkg/i18n" "coopcloud.tech/abra/pkg/i18n"
composetypes "github.com/docker/cli/cli/compose/types" composeGoTypes "github.com/compose-spec/compose-go/v2/types"
"github.com/docker/cli/opts" "github.com/docker/cli/opts"
"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"
@ -178,7 +180,7 @@ func ParseConfigs(client client.ConfigAPIClient, requestedConfigs []*swarmtypes.
// Services from compose-file types to engine API types // Services from compose-file types to engine API types
func Services( func Services(
namespace Namespace, namespace Namespace,
config *composetypes.Config, config *composeGoTypes.Project,
client client.CommonAPIClient, client client.CommonAPIClient,
) (map[string]swarm.ServiceSpec, error) { ) (map[string]swarm.ServiceSpec, error) {
result := make(map[string]swarm.ServiceSpec) result := make(map[string]swarm.ServiceSpec)
@ -211,14 +213,17 @@ func Services(
func Service( func Service(
apiVersion string, apiVersion string,
namespace Namespace, namespace Namespace,
service composetypes.ServiceConfig, service composeGoTypes.ServiceConfig,
networkConfigs map[string]composetypes.NetworkConfig, networkConfigs map[string]composeGoTypes.NetworkConfig,
volumes map[string]composetypes.VolumeConfig, volumes map[string]composeGoTypes.VolumeConfig,
secrets []*swarm.SecretReference, secrets []*swarm.SecretReference,
configs []*swarm.ConfigReference, configs []*swarm.ConfigReference,
) (swarm.ServiceSpec, error) { ) (swarm.ServiceSpec, error) {
name := namespace.Scope(service.Name) name := namespace.Scope(service.Name)
endpoint := convertEndpointSpec(service.Deploy.EndpointMode, service.Ports) endpoint, err := convertEndpointSpec(service.Deploy.EndpointMode, service.Ports)
if err != nil {
return swarm.ServiceSpec{}, err
}
mode, err := convertDeployMode(service.Deploy.Mode, service.Deploy.Replicas) mode, err := convertDeployMode(service.Deploy.Mode, service.Deploy.Replicas)
if err != nil { if err != nil {
@ -254,9 +259,16 @@ func Service(
dnsConfig := convertDNSConfig(service.DNS, service.DNSSearch) dnsConfig := convertDNSConfig(service.DNS, service.DNSSearch)
var privileges swarm.Privileges var privileges swarm.Privileges
credSpec := service.CredentialSpec
if credSpec == nil {
credSpec = &composeGoTypes.CredentialSpecConfig{}
}
privileges.CredentialSpec, err = convertCredentialSpec( privileges.CredentialSpec, err = convertCredentialSpec(
namespace, service.CredentialSpec, configs, namespace, *credSpec, configs,
) )
if err != nil { if err != nil {
return swarm.ServiceSpec{}, err return swarm.ServiceSpec{}, err
} }
@ -271,6 +283,11 @@ func Service(
capAdd, capDrop := opts.EffectiveCapAddCapDrop(service.CapAdd, service.CapDrop) capAdd, capDrop := opts.EffectiveCapAddCapDrop(service.CapAdd, service.CapDrop)
var stopGracePtr time.Duration
if service.StopGracePeriod != nil {
stopGracePtr = time.Duration(*service.StopGracePeriod)
}
serviceSpec := swarm.ServiceSpec{ serviceSpec := swarm.ServiceSpec{
Annotations: swarm.Annotations{ Annotations: swarm.Annotations{
Name: name, Name: name,
@ -290,7 +307,7 @@ func Service(
Dir: service.WorkingDir, Dir: service.WorkingDir,
User: service.User, User: service.User,
Mounts: mounts, Mounts: mounts,
StopGracePeriod: composetypes.ConvertDurationPtr(service.StopGracePeriod), StopGracePeriod: &stopGracePtr,
StopSignal: service.StopSignal, StopSignal: service.StopSignal,
TTY: service.Tty, TTY: service.Tty,
OpenStdin: service.StdinOpen, OpenStdin: service.StdinOpen,
@ -338,7 +355,7 @@ func Service(
return serviceSpec, nil return serviceSpec, nil
} }
func getPlacementPreference(preferences []composetypes.PlacementPreferences) []swarm.PlacementPreference { func getPlacementPreference(preferences []composeGoTypes.PlacementPreferences) []swarm.PlacementPreference {
result := []swarm.PlacementPreference{} result := []swarm.PlacementPreference{}
for _, preference := range preferences { for _, preference := range preferences {
spreadDescriptor := preference.Spread spreadDescriptor := preference.Spread
@ -357,13 +374,13 @@ func sortStrings(strs []string) []string {
} }
func convertServiceNetworks( func convertServiceNetworks(
networks map[string]*composetypes.ServiceNetworkConfig, networks map[string]*composeGoTypes.ServiceNetworkConfig,
networkConfigs networkMap, networkConfigs map[string]composeGoTypes.NetworkConfig,
namespace Namespace, namespace Namespace,
name string, name string,
) ([]swarm.NetworkAttachmentConfig, error) { ) ([]swarm.NetworkAttachmentConfig, error) {
if len(networks) == 0 { if len(networks) == 0 {
networks = map[string]*composetypes.ServiceNetworkConfig{ networks = map[string]*composeGoTypes.ServiceNetworkConfig{
defaultNetwork: {}, defaultNetwork: {},
} }
} }
@ -403,20 +420,20 @@ func convertServiceNetworks(
func convertServiceSecrets( func convertServiceSecrets(
client client.SecretAPIClient, client client.SecretAPIClient,
namespace Namespace, namespace Namespace,
secrets []composetypes.ServiceSecretConfig, secrets []composeGoTypes.ServiceSecretConfig,
secretSpecs map[string]composetypes.SecretConfig, secretSpecs map[string]composeGoTypes.SecretConfig,
) ([]*swarm.SecretReference, error) { ) ([]*swarm.SecretReference, error) {
refs := []*swarm.SecretReference{} refs := []*swarm.SecretReference{}
lookup := func(key string) (composetypes.FileObjectConfig, error) { lookup := func(key string) (composeGoTypes.FileObjectConfig, error) {
secretSpec, exists := secretSpecs[key] secretSpec, exists := secretSpecs[key]
if !exists { if !exists {
return composetypes.FileObjectConfig{}, errors.New(i18n.G("undefined secret %q", key)) return composeGoTypes.FileObjectConfig{}, errors.New(i18n.G("undefined secret %q", key))
} }
return composetypes.FileObjectConfig(secretSpec), nil return composeGoTypes.FileObjectConfig(secretSpec), nil
} }
for _, secret := range secrets { for _, secret := range secrets {
obj, err := convertFileObject(namespace, composetypes.FileReferenceConfig(secret), lookup) obj, err := convertFileObject(namespace, composeGoTypes.FileReferenceConfig(secret), lookup)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -451,20 +468,20 @@ func convertServiceSecrets(
func convertServiceConfigObjs( func convertServiceConfigObjs(
client client.ConfigAPIClient, client client.ConfigAPIClient,
namespace Namespace, namespace Namespace,
service composetypes.ServiceConfig, service composeGoTypes.ServiceConfig,
configSpecs map[string]composetypes.ConfigObjConfig, configSpecs map[string]composeGoTypes.ConfigObjConfig,
) ([]*swarm.ConfigReference, error) { ) ([]*swarm.ConfigReference, error) {
refs := []*swarm.ConfigReference{} refs := []*swarm.ConfigReference{}
lookup := func(key string) (composetypes.FileObjectConfig, error) { lookup := func(key string) (composeGoTypes.FileObjectConfig, error) {
configSpec, exists := configSpecs[key] configSpec, exists := configSpecs[key]
if !exists { if !exists {
return composetypes.FileObjectConfig{}, errors.New(i18n.G("undefined config %q", key)) return composeGoTypes.FileObjectConfig{}, errors.New(i18n.G("undefined config %q", key))
} }
return composetypes.FileObjectConfig(configSpec), nil return composeGoTypes.FileObjectConfig(configSpec), nil
} }
for _, config := range service.Configs { for _, config := range service.Configs {
obj, err := convertFileObject(namespace, composetypes.FileReferenceConfig(config), lookup) obj, err := convertFileObject(namespace, composeGoTypes.FileReferenceConfig(config), lookup)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -487,7 +504,7 @@ func convertServiceConfigObjs(
// if the credSpec uses a config, then we should grab the config name, and // if the credSpec uses a config, then we should grab the config name, and
// create a config reference for it. A File or Registry-type CredentialSpec // create a config reference for it. A File or Registry-type CredentialSpec
// does not need this operation. // does not need this operation.
if credSpec.Config != "" { if credSpec != nil && credSpec.Config != "" {
// look up the config in the configSpecs. // look up the config in the configSpecs.
obj, err := lookup(credSpec.Config) obj, err := lookup(credSpec.Config)
if err != nil { if err != nil {
@ -532,8 +549,8 @@ type swarmReferenceObject struct {
func convertFileObject( func convertFileObject(
namespace Namespace, namespace Namespace,
config composetypes.FileReferenceConfig, config composeGoTypes.FileReferenceConfig,
lookup func(key string) (composetypes.FileObjectConfig, error), lookup func(key string) (composeGoTypes.FileObjectConfig, error),
) (swarmReferenceObject, error) { ) (swarmReferenceObject, error) {
obj, err := lookup(config.Source) obj, err := lookup(config.Source)
if err != nil { if err != nil {
@ -558,40 +575,37 @@ func convertFileObject(
if gid == "" { if gid == "" {
gid = "0" gid = "0"
} }
mode := config.Mode
if mode == nil {
mode = uint32Ptr(0444)
}
return swarmReferenceObject{ ref := swarmReferenceObject{
File: swarmReferenceTarget{ File: swarmReferenceTarget{
Name: target, Name: target,
UID: uid, UID: uid,
GID: gid, GID: gid,
Mode: os.FileMode(*mode),
}, },
Name: source, Name: source,
}, nil }
}
func uint32Ptr(value uint32) *uint32 { if config.Mode == nil {
return &value defaultMode := 0444
ref.File.Mode = os.FileMode(defaultMode)
} else {
ref.File.Mode = os.FileMode(*config.Mode)
}
return ref, nil
} }
// convertExtraHosts converts <host>:<ip> mappings to SwarmKit notation: // convertExtraHosts converts <host>:<ip> mappings to SwarmKit notation:
// "IP-address hostname(s)". The original order of mappings is preserved. // "IP-address hostname(s)". The original order of mappings is preserved.
func convertExtraHosts(extraHosts composetypes.HostsList) []string { func convertExtraHosts(extraHosts composeGoTypes.HostsList) []string {
hosts := []string{} hosts := []string{}
for _, hostIP := range extraHosts { for hostName, hostIP := range extraHosts {
if v := strings.SplitN(hostIP, ":", 2); len(v) == 2 { hosts = append(hosts, fmt.Sprintf("%s %s", hostIP, hostName))
// Convert to SwarmKit notation: IP-address hostname(s)
hosts = append(hosts, fmt.Sprintf("%s %s", v[1], v[0]))
}
} }
return hosts return hosts
} }
func convertHealthcheck(healthcheck *composetypes.HealthCheckConfig) (*container.HealthConfig, error) { func convertHealthcheck(healthcheck *composeGoTypes.HealthCheckConfig) (*container.HealthConfig, error) {
if healthcheck == nil { if healthcheck == nil {
return nil, nil return nil, nil
} }
@ -629,7 +643,7 @@ func convertHealthcheck(healthcheck *composetypes.HealthCheckConfig) (*container
}, nil }, nil
} }
func convertRestartPolicy(restart string, source *composetypes.RestartPolicy) (*swarm.RestartPolicy, error) { func convertRestartPolicy(restart string, source *composeGoTypes.RestartPolicy) (*swarm.RestartPolicy, error) {
if source == nil { if source == nil {
policy, err := opts.ParseRestartPolicy(restart) policy, err := opts.ParseRestartPolicy(restart)
if err != nil { if err != nil {
@ -653,15 +667,25 @@ func convertRestartPolicy(restart string, source *composetypes.RestartPolicy) (*
} }
} }
var windowPtr time.Duration
if source.Window != nil {
windowPtr = time.Duration(*source.Window)
}
var delayPtr time.Duration
if source.Delay != nil {
delayPtr = time.Duration(*source.Delay)
}
return &swarm.RestartPolicy{ return &swarm.RestartPolicy{
Condition: swarm.RestartPolicyCondition(source.Condition), Condition: swarm.RestartPolicyCondition(source.Condition),
Delay: composetypes.ConvertDurationPtr(source.Delay), Delay: &delayPtr,
MaxAttempts: source.MaxAttempts, MaxAttempts: source.MaxAttempts,
Window: composetypes.ConvertDurationPtr(source.Window), Window: &windowPtr,
}, nil }, nil
} }
func convertUpdateConfig(source *composetypes.UpdateConfig) *swarm.UpdateConfig { func convertUpdateConfig(source *composeGoTypes.UpdateConfig) *swarm.UpdateConfig {
if source == nil { if source == nil {
return nil return nil
} }
@ -679,13 +703,13 @@ func convertUpdateConfig(source *composetypes.UpdateConfig) *swarm.UpdateConfig
} }
} }
func convertResources(source composetypes.Resources) (*swarm.ResourceRequirements, error) { func convertResources(source composeGoTypes.Resources) (*swarm.ResourceRequirements, error) {
resources := &swarm.ResourceRequirements{} resources := &swarm.ResourceRequirements{}
var err error var err error
if source.Limits != nil { if source.Limits != nil {
var cpus int64 var cpus int64
if source.Limits.NanoCPUs != "" { if source.Limits.NanoCPUs > 0 {
cpus, err = opts.ParseCPUs(source.Limits.NanoCPUs) cpus, err = opts.ParseCPUs(fmt.Sprintf("%f", source.Limits.NanoCPUs))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -698,8 +722,8 @@ func convertResources(source composetypes.Resources) (*swarm.ResourceRequirement
} }
if source.Reservations != nil { if source.Reservations != nil {
var cpus int64 var cpus int64
if source.Reservations.NanoCPUs != "" { if source.Reservations.NanoCPUs > 0 {
cpus, err = opts.ParseCPUs(source.Reservations.NanoCPUs) cpus, err = opts.ParseCPUs(fmt.Sprintf("%f", source.Reservations.NanoCPUs))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -728,13 +752,29 @@ func convertResources(source composetypes.Resources) (*swarm.ResourceRequirement
return resources, nil return resources, nil
} }
func convertEndpointSpec(endpointMode string, source []composetypes.ServicePortConfig) *swarm.EndpointSpec { func str2uint32(s string) (uint32, error) {
var u32 uint32
u64, err := strconv.ParseUint(s, 10, 32)
if err != nil {
return u32, err
}
return uint32(u64), nil
}
func convertEndpointSpec(endpointMode string, source []composeGoTypes.ServicePortConfig) (*swarm.EndpointSpec, error) {
portConfigs := []swarm.PortConfig{} portConfigs := []swarm.PortConfig{}
for _, port := range source { for _, port := range source {
published, err := str2uint32(port.Published)
if err != nil {
return &swarm.EndpointSpec{}, err
}
portConfig := swarm.PortConfig{ portConfig := swarm.PortConfig{
Protocol: swarm.PortConfigProtocol(port.Protocol), Protocol: swarm.PortConfigProtocol(port.Protocol),
TargetPort: port.Target, TargetPort: port.Target,
PublishedPort: port.Published, PublishedPort: published,
PublishMode: swarm.PortConfigPublishMode(port.Mode), PublishMode: swarm.PortConfigPublishMode(port.Mode),
} }
portConfigs = append(portConfigs, portConfig) portConfigs = append(portConfigs, portConfig)
@ -747,7 +787,7 @@ func convertEndpointSpec(endpointMode string, source []composetypes.ServicePortC
return &swarm.EndpointSpec{ return &swarm.EndpointSpec{
Mode: swarm.ResolutionMode(strings.ToLower(endpointMode)), Mode: swarm.ResolutionMode(strings.ToLower(endpointMode)),
Ports: portConfigs, Ports: portConfigs,
} }, nil
} }
func convertEnvironment(source map[string]*string) []string { func convertEnvironment(source map[string]*string) []string {
@ -765,7 +805,7 @@ func convertEnvironment(source map[string]*string) []string {
return output return output
} }
func convertDeployMode(mode string, replicas *uint64) (swarm.ServiceMode, error) { func convertDeployMode(mode string, replicas *int) (swarm.ServiceMode, error) {
serviceMode := swarm.ServiceMode{} serviceMode := swarm.ServiceMode{}
switch mode { switch mode {
@ -775,7 +815,8 @@ func convertDeployMode(mode string, replicas *uint64) (swarm.ServiceMode, error)
} }
serviceMode.Global = &swarm.GlobalService{} serviceMode.Global = &swarm.GlobalService{}
case "replicated", "": case "replicated", "":
serviceMode.Replicated = &swarm.ReplicatedService{Replicas: replicas} convReplicas := (*uint64)(unsafe.Pointer(replicas))
serviceMode.Replicated = &swarm.ReplicatedService{Replicas: convReplicas}
default: default:
return serviceMode, errors.New(i18n.G("unknown mode: %s", mode)) return serviceMode, errors.New(i18n.G("unknown mode: %s", mode))
} }
@ -792,7 +833,7 @@ func convertDNSConfig(DNS []string, DNSSearch []string) *swarm.DNSConfig {
return nil return nil
} }
func convertCredentialSpec(namespace Namespace, spec composetypes.CredentialSpecConfig, refs []*swarm.ConfigReference) (*swarm.CredentialSpec, error) { func convertCredentialSpec(namespace Namespace, spec composeGoTypes.CredentialSpecConfig, refs []*swarm.ConfigReference) (*swarm.CredentialSpec, error) {
var o []string var o []string
// Config was added in API v1.40 // Config was added in API v1.40
@ -814,7 +855,13 @@ func convertCredentialSpec(namespace Namespace, spec composetypes.CredentialSpec
case l > 2: case l > 2:
return nil, errors.New(i18n.G("invalid credential spec: cannot specify both %s, and %s", strings.Join(o[:l-1], ", "), o[l-1])) return nil, errors.New(i18n.G("invalid credential spec: cannot specify both %s, and %s", strings.Join(o[:l-1], ", "), o[l-1]))
} }
swarmCredSpec := swarm.CredentialSpec(spec)
swarmCredSpec := swarm.CredentialSpec{
Config: spec.Config,
File: spec.File,
Registry: spec.Registry,
}
// if we're using a swarm Config for the credential spec, over-write it // if we're using a swarm Config for the credential spec, over-write it
// here with the config ID // here with the config ID
if swarmCredSpec.Config != "" { if swarmCredSpec.Config != "" {
@ -836,7 +883,7 @@ func convertCredentialSpec(namespace Namespace, spec composetypes.CredentialSpec
return &swarmCredSpec, nil return &swarmCredSpec, nil
} }
func convertUlimits(origUlimits map[string]*composetypes.UlimitsConfig) []*units.Ulimit { func convertUlimits(origUlimits map[string]*composeGoTypes.UlimitsConfig) []*units.Ulimit {
newUlimits := make(map[string]*units.Ulimit) newUlimits := make(map[string]*units.Ulimit)
for name, u := range origUlimits { for name, u := range origUlimits {
if u.Single != 0 { if u.Single != 0 {

View File

@ -1,678 +0,0 @@
package convert // https://github.com/docker/cli/blob/master/cli/compose/convert/service_test.go
import (
"context"
"os"
"sort"
"strings"
"testing"
"time"
composetypes "github.com/docker/cli/cli/compose/types"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/client"
"github.com/pkg/errors"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
)
func TestConvertRestartPolicyFromNone(t *testing.T) {
policy, err := convertRestartPolicy("no", nil)
assert.NilError(t, err)
assert.Check(t, is.DeepEqual((*swarm.RestartPolicy)(nil), policy))
}
func TestConvertRestartPolicyFromUnknown(t *testing.T) {
_, err := convertRestartPolicy("unknown", nil)
assert.Error(t, err, "unknown restart policy: unknown")
}
func TestConvertRestartPolicyFromAlways(t *testing.T) {
policy, err := convertRestartPolicy("always", nil)
expected := &swarm.RestartPolicy{
Condition: swarm.RestartPolicyConditionAny,
}
assert.NilError(t, err)
assert.Check(t, is.DeepEqual(expected, policy))
}
func TestConvertRestartPolicyFromFailure(t *testing.T) {
policy, err := convertRestartPolicy("on-failure:4", nil)
attempts := uint64(4)
expected := &swarm.RestartPolicy{
Condition: swarm.RestartPolicyConditionOnFailure,
MaxAttempts: &attempts,
}
assert.NilError(t, err)
assert.Check(t, is.DeepEqual(expected, policy))
}
func strPtr(val string) *string {
return &val
}
func TestConvertEnvironment(t *testing.T) {
source := map[string]*string{
"foo": strPtr("bar"),
"key": strPtr("value"),
}
env := convertEnvironment(source)
sort.Strings(env)
assert.Check(t, is.DeepEqual([]string{"foo=bar", "key=value"}, env))
}
func TestConvertExtraHosts(t *testing.T) {
source := composetypes.HostsList{
"zulu:127.0.0.2",
"alpha:127.0.0.1",
"zulu:ff02::1",
}
assert.Check(t, is.DeepEqual([]string{"127.0.0.2 zulu", "127.0.0.1 alpha", "ff02::1 zulu"}, convertExtraHosts(source)))
}
func TestConvertResourcesFull(t *testing.T) {
source := composetypes.Resources{
Limits: &composetypes.ResourceLimit{
NanoCPUs: "0.003",
MemoryBytes: composetypes.UnitBytes(300000000),
},
Reservations: &composetypes.Resource{
NanoCPUs: "0.002",
MemoryBytes: composetypes.UnitBytes(200000000),
},
}
resources, err := convertResources(source)
assert.NilError(t, err)
expected := &swarm.ResourceRequirements{
Limits: &swarm.Limit{
NanoCPUs: 3000000,
MemoryBytes: 300000000,
},
Reservations: &swarm.Resources{
NanoCPUs: 2000000,
MemoryBytes: 200000000,
},
}
assert.Check(t, is.DeepEqual(expected, resources))
}
func TestConvertResourcesOnlyMemory(t *testing.T) {
source := composetypes.Resources{
Limits: &composetypes.ResourceLimit{
MemoryBytes: composetypes.UnitBytes(300000000),
},
Reservations: &composetypes.Resource{
MemoryBytes: composetypes.UnitBytes(200000000),
},
}
resources, err := convertResources(source)
assert.NilError(t, err)
expected := &swarm.ResourceRequirements{
Limits: &swarm.Limit{
MemoryBytes: 300000000,
},
Reservations: &swarm.Resources{
MemoryBytes: 200000000,
},
}
assert.Check(t, is.DeepEqual(expected, resources))
}
func TestConvertHealthcheck(t *testing.T) {
retries := uint64(10)
timeout := composetypes.Duration(30 * time.Second)
interval := composetypes.Duration(2 * time.Millisecond)
source := &composetypes.HealthCheckConfig{
Test: []string{"EXEC", "touch", "/foo"},
Timeout: &timeout,
Interval: &interval,
Retries: &retries,
}
expected := &container.HealthConfig{
Test: source.Test,
Timeout: time.Duration(timeout),
Interval: time.Duration(interval),
Retries: 10,
}
healthcheck, err := convertHealthcheck(source)
assert.NilError(t, err)
assert.Check(t, is.DeepEqual(expected, healthcheck))
}
func TestConvertHealthcheckDisable(t *testing.T) {
source := &composetypes.HealthCheckConfig{Disable: true}
expected := &container.HealthConfig{
Test: []string{"NONE"},
}
healthcheck, err := convertHealthcheck(source)
assert.NilError(t, err)
assert.Check(t, is.DeepEqual(expected, healthcheck))
}
func TestConvertHealthcheckDisableWithTest(t *testing.T) {
source := &composetypes.HealthCheckConfig{
Disable: true,
Test: []string{"EXEC", "touch"},
}
_, err := convertHealthcheck(source)
assert.Error(t, err, "test and disable can't be set at the same time")
}
func TestConvertEndpointSpec(t *testing.T) {
source := []composetypes.ServicePortConfig{
{
Protocol: "udp",
Target: 53,
Published: 1053,
Mode: "host",
},
{
Target: 8080,
Published: 80,
},
}
endpoint := convertEndpointSpec("vip", source)
expected := swarm.EndpointSpec{
Mode: swarm.ResolutionMode(strings.ToLower("vip")),
Ports: []swarm.PortConfig{
{
TargetPort: 8080,
PublishedPort: 80,
},
{
Protocol: "udp",
TargetPort: 53,
PublishedPort: 1053,
PublishMode: "host",
},
},
}
assert.Check(t, is.DeepEqual(expected, *endpoint))
}
func TestConvertServiceNetworksOnlyDefault(t *testing.T) {
networkConfigs := networkMap{}
configs, err := convertServiceNetworks(
nil, networkConfigs, NewNamespace("foo"), "service")
expected := []swarm.NetworkAttachmentConfig{
{
Target: "foo_default",
Aliases: []string{"service"},
},
}
assert.NilError(t, err)
assert.Check(t, is.DeepEqual(expected, configs))
}
func TestConvertServiceNetworks(t *testing.T) {
networkConfigs := networkMap{
"front": composetypes.NetworkConfig{
External: composetypes.External{External: true},
Name: "fronttier",
},
"back": composetypes.NetworkConfig{},
}
networks := map[string]*composetypes.ServiceNetworkConfig{
"front": {
Aliases: []string{"something"},
},
"back": {
Aliases: []string{"other"},
},
}
configs, err := convertServiceNetworks(
networks, networkConfigs, NewNamespace("foo"), "service")
expected := []swarm.NetworkAttachmentConfig{
{
Target: "foo_back",
Aliases: []string{"other", "service"},
},
{
Target: "fronttier",
Aliases: []string{"something", "service"},
},
}
assert.NilError(t, err)
assert.Check(t, is.DeepEqual(expected, configs))
}
func TestConvertServiceNetworksCustomDefault(t *testing.T) {
networkConfigs := networkMap{
"default": composetypes.NetworkConfig{
External: composetypes.External{External: true},
Name: "custom",
},
}
networks := map[string]*composetypes.ServiceNetworkConfig{}
configs, err := convertServiceNetworks(
networks, networkConfigs, NewNamespace("foo"), "service")
expected := []swarm.NetworkAttachmentConfig{
{
Target: "custom",
Aliases: []string{"service"},
},
}
assert.NilError(t, err)
assert.Check(t, is.DeepEqual(expected, configs))
}
func TestConvertDNSConfigEmpty(t *testing.T) {
dnsConfig := convertDNSConfig(nil, nil)
assert.Check(t, is.DeepEqual((*swarm.DNSConfig)(nil), dnsConfig))
}
var (
nameservers = []string{"8.8.8.8", "9.9.9.9"}
search = []string{"dc1.example.com", "dc2.example.com"}
)
func TestConvertDNSConfigAll(t *testing.T) {
dnsConfig := convertDNSConfig(nameservers, search)
assert.Check(t, is.DeepEqual(&swarm.DNSConfig{
Nameservers: nameservers,
Search: search,
}, dnsConfig))
}
func TestConvertDNSConfigNameservers(t *testing.T) {
dnsConfig := convertDNSConfig(nameservers, nil)
assert.Check(t, is.DeepEqual(&swarm.DNSConfig{
Nameservers: nameservers,
Search: nil,
}, dnsConfig))
}
func TestConvertDNSConfigSearch(t *testing.T) {
dnsConfig := convertDNSConfig(nil, search)
assert.Check(t, is.DeepEqual(&swarm.DNSConfig{
Nameservers: nil,
Search: search,
}, dnsConfig))
}
func TestConvertCredentialSpec(t *testing.T) {
tests := []struct {
name string
in composetypes.CredentialSpecConfig
out *swarm.CredentialSpec
configs []*swarm.ConfigReference
expectedErr string
}{
{
name: "empty",
},
{
name: "config-and-file",
in: composetypes.CredentialSpecConfig{Config: "0bt9dmxjvjiqermk6xrop3ekq", File: "somefile.json"},
expectedErr: `invalid credential spec: cannot specify both "Config" and "File"`,
},
{
name: "config-and-registry",
in: composetypes.CredentialSpecConfig{Config: "0bt9dmxjvjiqermk6xrop3ekq", Registry: "testing"},
expectedErr: `invalid credential spec: cannot specify both "Config" and "Registry"`,
},
{
name: "file-and-registry",
in: composetypes.CredentialSpecConfig{File: "somefile.json", Registry: "testing"},
expectedErr: `invalid credential spec: cannot specify both "File" and "Registry"`,
},
{
name: "config-and-file-and-registry",
in: composetypes.CredentialSpecConfig{Config: "0bt9dmxjvjiqermk6xrop3ekq", File: "somefile.json", Registry: "testing"},
expectedErr: `invalid credential spec: cannot specify both "Config", "File", and "Registry"`,
},
{
name: "missing-config-reference",
in: composetypes.CredentialSpecConfig{Config: "missing"},
expectedErr: "invalid credential spec: spec specifies config missing, but no such config can be found",
configs: []*swarm.ConfigReference{
{
ConfigName: "someName",
ConfigID: "missing",
},
},
},
{
name: "namespaced-config",
in: composetypes.CredentialSpecConfig{Config: "name"},
configs: []*swarm.ConfigReference{
{
ConfigName: "namespaced-config_name",
ConfigID: "someID",
},
},
out: &swarm.CredentialSpec{Config: "someID"},
},
{
name: "config",
in: composetypes.CredentialSpecConfig{Config: "someName"},
configs: []*swarm.ConfigReference{
{
ConfigName: "someOtherName",
ConfigID: "someOtherID",
}, {
ConfigName: "someName",
ConfigID: "someID",
},
},
out: &swarm.CredentialSpec{Config: "someID"},
},
{
name: "file",
in: composetypes.CredentialSpecConfig{File: "somefile.json"},
out: &swarm.CredentialSpec{File: "somefile.json"},
},
{
name: "registry",
in: composetypes.CredentialSpecConfig{Registry: "testing"},
out: &swarm.CredentialSpec{Registry: "testing"},
},
}
for _, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
namespace := NewNamespace(tc.name)
swarmSpec, err := convertCredentialSpec(namespace, tc.in, tc.configs)
if tc.expectedErr != "" {
assert.Error(t, err, tc.expectedErr)
} else {
assert.NilError(t, err)
}
assert.DeepEqual(t, swarmSpec, tc.out)
})
}
}
func TestConvertUpdateConfigOrder(t *testing.T) {
// test default behavior
updateConfig := convertUpdateConfig(&composetypes.UpdateConfig{})
assert.Check(t, is.Equal("", updateConfig.Order))
// test start-first
updateConfig = convertUpdateConfig(&composetypes.UpdateConfig{
Order: "start-first",
})
assert.Check(t, is.Equal(updateConfig.Order, "start-first"))
// test stop-first
updateConfig = convertUpdateConfig(&composetypes.UpdateConfig{
Order: "stop-first",
})
assert.Check(t, is.Equal(updateConfig.Order, "stop-first"))
}
func TestConvertFileObject(t *testing.T) {
namespace := NewNamespace("testing")
config := composetypes.FileReferenceConfig{
Source: "source",
Target: "target",
UID: "user",
GID: "group",
Mode: uint32Ptr(0644),
}
swarmRef, err := convertFileObject(namespace, config, lookupConfig)
assert.NilError(t, err)
expected := swarmReferenceObject{
Name: "testing_source",
File: swarmReferenceTarget{
Name: config.Target,
UID: config.UID,
GID: config.GID,
Mode: os.FileMode(0644),
},
}
assert.Check(t, is.DeepEqual(expected, swarmRef))
}
func lookupConfig(key string) (composetypes.FileObjectConfig, error) {
if key != "source" {
return composetypes.FileObjectConfig{}, errors.New("bad key")
}
return composetypes.FileObjectConfig{}, nil
}
func TestConvertFileObjectDefaults(t *testing.T) {
namespace := NewNamespace("testing")
config := composetypes.FileReferenceConfig{Source: "source"}
swarmRef, err := convertFileObject(namespace, config, lookupConfig)
assert.NilError(t, err)
expected := swarmReferenceObject{
Name: "testing_source",
File: swarmReferenceTarget{
Name: config.Source,
UID: "0",
GID: "0",
Mode: os.FileMode(0444),
},
}
assert.Check(t, is.DeepEqual(expected, swarmRef))
}
func TestServiceConvertsIsolation(t *testing.T) {
src := composetypes.ServiceConfig{
Isolation: "hyperv",
}
result, err := Service("1.35", Namespace{name: "foo"}, src, nil, nil, nil, nil)
assert.NilError(t, err)
assert.Check(t, is.Equal(container.IsolationHyperV, result.TaskTemplate.ContainerSpec.Isolation))
}
func TestConvertServiceSecrets(t *testing.T) {
namespace := Namespace{name: "foo"}
secrets := []composetypes.ServiceSecretConfig{
{Source: "foo_secret"},
{Source: "bar_secret"},
}
secretSpecs := map[string]composetypes.SecretConfig{
"foo_secret": {
Name: "foo_secret",
},
"bar_secret": {
Name: "bar_secret",
},
}
client := &fakeClient{
secretListFunc: func(opts types.SecretListOptions) ([]swarm.Secret, error) {
assert.Check(t, is.Contains(opts.Filters.Get("name"), "foo_secret"))
assert.Check(t, is.Contains(opts.Filters.Get("name"), "bar_secret"))
return []swarm.Secret{
{Spec: swarm.SecretSpec{Annotations: swarm.Annotations{Name: "foo_secret"}}},
{Spec: swarm.SecretSpec{Annotations: swarm.Annotations{Name: "bar_secret"}}},
}, nil
},
}
refs, err := convertServiceSecrets(client, namespace, secrets, secretSpecs)
assert.NilError(t, err)
expected := []*swarm.SecretReference{
{
SecretName: "bar_secret",
File: &swarm.SecretReferenceFileTarget{
Name: "bar_secret",
UID: "0",
GID: "0",
Mode: 0444,
},
},
{
SecretName: "foo_secret",
File: &swarm.SecretReferenceFileTarget{
Name: "foo_secret",
UID: "0",
GID: "0",
Mode: 0444,
},
},
}
assert.DeepEqual(t, expected, refs)
}
func TestConvertServiceConfigs(t *testing.T) {
namespace := Namespace{name: "foo"}
service := composetypes.ServiceConfig{
Configs: []composetypes.ServiceConfigObjConfig{
{Source: "foo_config"},
{Source: "bar_config"},
},
CredentialSpec: composetypes.CredentialSpecConfig{
Config: "baz_config",
},
}
configSpecs := map[string]composetypes.ConfigObjConfig{
"foo_config": {
Name: "foo_config",
},
"bar_config": {
Name: "bar_config",
},
"baz_config": {
Name: "baz_config",
},
}
client := &fakeClient{
configListFunc: func(opts types.ConfigListOptions) ([]swarm.Config, error) {
assert.Check(t, is.Contains(opts.Filters.Get("name"), "foo_config"))
assert.Check(t, is.Contains(opts.Filters.Get("name"), "bar_config"))
assert.Check(t, is.Contains(opts.Filters.Get("name"), "baz_config"))
return []swarm.Config{
{Spec: swarm.ConfigSpec{Annotations: swarm.Annotations{Name: "foo_config"}}},
{Spec: swarm.ConfigSpec{Annotations: swarm.Annotations{Name: "bar_config"}}},
{Spec: swarm.ConfigSpec{Annotations: swarm.Annotations{Name: "baz_config"}}},
}, nil
},
}
refs, err := convertServiceConfigObjs(client, namespace, service, configSpecs)
assert.NilError(t, err)
expected := []*swarm.ConfigReference{
{
ConfigName: "bar_config",
File: &swarm.ConfigReferenceFileTarget{
Name: "bar_config",
UID: "0",
GID: "0",
Mode: 0444,
},
},
{
ConfigName: "baz_config",
Runtime: &swarm.ConfigReferenceRuntimeTarget{},
},
{
ConfigName: "foo_config",
File: &swarm.ConfigReferenceFileTarget{
Name: "foo_config",
UID: "0",
GID: "0",
Mode: 0444,
},
},
}
assert.DeepEqual(t, expected, refs)
}
type fakeClient struct {
client.Client
secretListFunc func(types.SecretListOptions) ([]swarm.Secret, error)
configListFunc func(types.ConfigListOptions) ([]swarm.Config, error)
}
func (c *fakeClient) SecretList(ctx context.Context, options types.SecretListOptions) ([]swarm.Secret, error) {
if c.secretListFunc != nil {
return c.secretListFunc(options)
}
return []swarm.Secret{}, nil
}
func (c *fakeClient) ConfigList(ctx context.Context, options types.ConfigListOptions) ([]swarm.Config, error) {
if c.configListFunc != nil {
return c.configListFunc(options)
}
return []swarm.Config{}, nil
}
func TestConvertUpdateConfigParallelism(t *testing.T) {
parallel := uint64(4)
// test default behavior
updateConfig := convertUpdateConfig(&composetypes.UpdateConfig{})
assert.Check(t, is.Equal(uint64(1), updateConfig.Parallelism))
// Non default value
updateConfig = convertUpdateConfig(&composetypes.UpdateConfig{
Parallelism: &parallel,
})
assert.Check(t, is.Equal(parallel, updateConfig.Parallelism))
}
func TestConvertServiceCapAddAndCapDrop(t *testing.T) {
tests := []struct {
title string
in, out composetypes.ServiceConfig
}{
{
title: "default behavior",
},
{
title: "some values",
in: composetypes.ServiceConfig{
CapAdd: []string{"SYS_NICE", "CAP_NET_ADMIN"},
CapDrop: []string{"CHOWN", "CAP_NET_ADMIN", "DAC_OVERRIDE", "CAP_FSETID", "CAP_FOWNER"},
},
out: composetypes.ServiceConfig{
CapAdd: []string{"CAP_NET_ADMIN", "CAP_SYS_NICE"},
CapDrop: []string{"CAP_CHOWN", "CAP_DAC_OVERRIDE", "CAP_FOWNER", "CAP_FSETID"},
},
},
{
title: "adding ALL capabilities",
in: composetypes.ServiceConfig{
CapAdd: []string{"ALL", "CAP_NET_ADMIN"},
CapDrop: []string{"CHOWN", "CAP_NET_ADMIN", "DAC_OVERRIDE", "CAP_FSETID", "CAP_FOWNER"},
},
out: composetypes.ServiceConfig{
CapAdd: []string{"ALL"},
CapDrop: []string{"CAP_CHOWN", "CAP_DAC_OVERRIDE", "CAP_FOWNER", "CAP_FSETID", "CAP_NET_ADMIN"},
},
},
{
title: "dropping ALL capabilities",
in: composetypes.ServiceConfig{
CapAdd: []string{"CHOWN", "CAP_NET_ADMIN", "DAC_OVERRIDE", "CAP_FSETID", "CAP_FOWNER"},
CapDrop: []string{"ALL", "CAP_NET_ADMIN", "CAP_FOO"},
},
out: composetypes.ServiceConfig{
CapAdd: []string{"CAP_CHOWN", "CAP_DAC_OVERRIDE", "CAP_FOWNER", "CAP_FSETID", "CAP_NET_ADMIN"},
CapDrop: []string{"ALL"},
},
},
}
for _, tc := range tests {
tc := tc
t.Run(tc.title, func(t *testing.T) {
result, err := Service("1.41", Namespace{name: "foo"}, tc.in, nil, nil, nil, nil)
assert.NilError(t, err)
assert.Check(t, is.DeepEqual(result.TaskTemplate.ContainerSpec.CapabilityAdd, tc.out.CapAdd))
assert.Check(t, is.DeepEqual(result.TaskTemplate.ContainerSpec.CapabilityDrop, tc.out.CapDrop))
})
}
}

View File

@ -2,15 +2,15 @@ package convert // https://github.com/docker/cli/blob/master/cli/compose/convert
import ( import (
"coopcloud.tech/abra/pkg/i18n" "coopcloud.tech/abra/pkg/i18n"
composetypes "github.com/docker/cli/cli/compose/types" composeGoTypes "github.com/compose-spec/compose-go/v2/types"
"github.com/docker/docker/api/types/mount" "github.com/docker/docker/api/types/mount"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
type volumes map[string]composetypes.VolumeConfig type volumes map[string]composeGoTypes.VolumeConfig
// Volumes from compose-file types to engine api types // Volumes from compose-file types to engine api types
func Volumes(serviceVolumes []composetypes.ServiceVolumeConfig, stackVolumes volumes, namespace Namespace) ([]mount.Mount, error) { func Volumes(serviceVolumes []composeGoTypes.ServiceVolumeConfig, stackVolumes volumes, namespace Namespace) ([]mount.Mount, error) {
var mounts []mount.Mount var mounts []mount.Mount
for _, volumeConfig := range serviceVolumes { for _, volumeConfig := range serviceVolumes {
@ -23,7 +23,7 @@ func Volumes(serviceVolumes []composetypes.ServiceVolumeConfig, stackVolumes vol
return mounts, nil return mounts, nil
} }
func createMountFromVolume(volume composetypes.ServiceVolumeConfig) mount.Mount { func createMountFromVolume(volume composeGoTypes.ServiceVolumeConfig) mount.Mount {
return mount.Mount{ return mount.Mount{
Type: mount.Type(volume.Type), Type: mount.Type(volume.Type),
Target: volume.Target, Target: volume.Target,
@ -34,7 +34,7 @@ func createMountFromVolume(volume composetypes.ServiceVolumeConfig) mount.Mount
} }
func handleVolumeToMount( func handleVolumeToMount(
volume composetypes.ServiceVolumeConfig, volume composeGoTypes.ServiceVolumeConfig,
stackVolumes volumes, stackVolumes volumes,
namespace Namespace, namespace Namespace,
) (mount.Mount, error) { ) (mount.Mount, error) {
@ -68,7 +68,7 @@ func handleVolumeToMount(
} }
// External named volumes // External named volumes
if stackVolume.External.External { if stackVolume.External {
return result, nil return result, nil
} }
@ -83,7 +83,7 @@ func handleVolumeToMount(
return result, nil return result, nil
} }
func handleBindToMount(volume composetypes.ServiceVolumeConfig) (mount.Mount, error) { func handleBindToMount(volume composeGoTypes.ServiceVolumeConfig) (mount.Mount, error) {
result := createMountFromVolume(volume) result := createMountFromVolume(volume)
if volume.Source == "" { if volume.Source == "" {
@ -103,7 +103,7 @@ func handleBindToMount(volume composetypes.ServiceVolumeConfig) (mount.Mount, er
return result, nil return result, nil
} }
func handleTmpfsToMount(volume composetypes.ServiceVolumeConfig) (mount.Mount, error) { func handleTmpfsToMount(volume composeGoTypes.ServiceVolumeConfig) (mount.Mount, error) {
result := createMountFromVolume(volume) result := createMountFromVolume(volume)
if volume.Source != "" { if volume.Source != "" {
@ -117,13 +117,13 @@ func handleTmpfsToMount(volume composetypes.ServiceVolumeConfig) (mount.Mount, e
} }
if volume.Tmpfs != nil { if volume.Tmpfs != nil {
result.TmpfsOptions = &mount.TmpfsOptions{ result.TmpfsOptions = &mount.TmpfsOptions{
SizeBytes: volume.Tmpfs.Size, SizeBytes: int64(volume.Tmpfs.Size),
} }
} }
return result, nil return result, nil
} }
func handleNpipeToMount(volume composetypes.ServiceVolumeConfig) (mount.Mount, error) { func handleNpipeToMount(volume composeGoTypes.ServiceVolumeConfig) (mount.Mount, error) {
result := createMountFromVolume(volume) result := createMountFromVolume(volume)
if volume.Source == "" { if volume.Source == "" {
@ -144,7 +144,7 @@ func handleNpipeToMount(volume composetypes.ServiceVolumeConfig) (mount.Mount, e
} }
func convertVolumeToMount( func convertVolumeToMount(
volume composetypes.ServiceVolumeConfig, volume composeGoTypes.ServiceVolumeConfig,
stackVolumes volumes, stackVolumes volumes,
namespace Namespace, namespace Namespace,
) (mount.Mount, error) { ) (mount.Mount, error) {

View File

@ -1,361 +0,0 @@
package convert // https://github.com/docker/cli/blob/master/cli/compose/convert/volume_test.go
import (
"testing"
composetypes "github.com/docker/cli/cli/compose/types"
"github.com/docker/docker/api/types/mount"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
)
func TestConvertVolumeToMountAnonymousVolume(t *testing.T) {
config := composetypes.ServiceVolumeConfig{
Type: "volume",
Target: "/foo/bar",
}
expected := mount.Mount{
Type: mount.TypeVolume,
Target: "/foo/bar",
}
mount, err := convertVolumeToMount(config, volumes{}, NewNamespace("foo"))
assert.NilError(t, err)
assert.Check(t, is.DeepEqual(expected, mount))
}
func TestConvertVolumeToMountAnonymousBind(t *testing.T) {
config := composetypes.ServiceVolumeConfig{
Type: "bind",
Target: "/foo/bar",
Bind: &composetypes.ServiceVolumeBind{
Propagation: "slave",
},
}
_, err := convertVolumeToMount(config, volumes{}, NewNamespace("foo"))
assert.Error(t, err, "invalid bind source, source cannot be empty")
}
func TestConvertVolumeToMountUnapprovedType(t *testing.T) {
config := composetypes.ServiceVolumeConfig{
Type: "foo",
Target: "/foo/bar",
}
_, err := convertVolumeToMount(config, volumes{}, NewNamespace("foo"))
assert.Error(t, err, "volume type must be volume, bind, tmpfs or npipe")
}
func TestConvertVolumeToMountConflictingOptionsBindInVolume(t *testing.T) {
namespace := NewNamespace("foo")
config := composetypes.ServiceVolumeConfig{
Type: "volume",
Source: "foo",
Target: "/target",
Bind: &composetypes.ServiceVolumeBind{
Propagation: "slave",
},
}
_, err := convertVolumeToMount(config, volumes{}, namespace)
assert.Error(t, err, "bind options are incompatible with type volume")
}
func TestConvertVolumeToMountConflictingOptionsTmpfsInVolume(t *testing.T) {
namespace := NewNamespace("foo")
config := composetypes.ServiceVolumeConfig{
Type: "volume",
Source: "foo",
Target: "/target",
Tmpfs: &composetypes.ServiceVolumeTmpfs{
Size: 1000,
},
}
_, err := convertVolumeToMount(config, volumes{}, namespace)
assert.Error(t, err, "tmpfs options are incompatible with type volume")
}
func TestConvertVolumeToMountConflictingOptionsVolumeInBind(t *testing.T) {
namespace := NewNamespace("foo")
config := composetypes.ServiceVolumeConfig{
Type: "bind",
Source: "/foo",
Target: "/target",
Volume: &composetypes.ServiceVolumeVolume{
NoCopy: true,
},
}
_, err := convertVolumeToMount(config, volumes{}, namespace)
assert.Error(t, err, "volume options are incompatible with type bind")
}
func TestConvertVolumeToMountConflictingOptionsTmpfsInBind(t *testing.T) {
namespace := NewNamespace("foo")
config := composetypes.ServiceVolumeConfig{
Type: "bind",
Source: "/foo",
Target: "/target",
Tmpfs: &composetypes.ServiceVolumeTmpfs{
Size: 1000,
},
}
_, err := convertVolumeToMount(config, volumes{}, namespace)
assert.Error(t, err, "tmpfs options are incompatible with type bind")
}
func TestConvertVolumeToMountConflictingOptionsBindInTmpfs(t *testing.T) {
namespace := NewNamespace("foo")
config := composetypes.ServiceVolumeConfig{
Type: "tmpfs",
Target: "/target",
Bind: &composetypes.ServiceVolumeBind{
Propagation: "slave",
},
}
_, err := convertVolumeToMount(config, volumes{}, namespace)
assert.Error(t, err, "bind options are incompatible with type tmpfs")
}
func TestConvertVolumeToMountConflictingOptionsVolumeInTmpfs(t *testing.T) {
namespace := NewNamespace("foo")
config := composetypes.ServiceVolumeConfig{
Type: "tmpfs",
Target: "/target",
Volume: &composetypes.ServiceVolumeVolume{
NoCopy: true,
},
}
_, err := convertVolumeToMount(config, volumes{}, namespace)
assert.Error(t, err, "volume options are incompatible with type tmpfs")
}
func TestConvertVolumeToMountNamedVolume(t *testing.T) {
stackVolumes := volumes{
"normal": composetypes.VolumeConfig{
Driver: "glusterfs",
DriverOpts: map[string]string{
"opt": "value",
},
Labels: map[string]string{
"something": "labeled",
},
},
}
namespace := NewNamespace("foo")
expected := mount.Mount{
Type: mount.TypeVolume,
Source: "foo_normal",
Target: "/foo",
ReadOnly: true,
VolumeOptions: &mount.VolumeOptions{
Labels: map[string]string{
LabelNamespace: "foo",
"something": "labeled",
},
DriverConfig: &mount.Driver{
Name: "glusterfs",
Options: map[string]string{
"opt": "value",
},
},
NoCopy: true,
},
}
config := composetypes.ServiceVolumeConfig{
Type: "volume",
Source: "normal",
Target: "/foo",
ReadOnly: true,
Volume: &composetypes.ServiceVolumeVolume{
NoCopy: true,
},
}
mount, err := convertVolumeToMount(config, stackVolumes, namespace)
assert.NilError(t, err)
assert.Check(t, is.DeepEqual(expected, mount))
}
func TestConvertVolumeToMountNamedVolumeWithNameCustomizd(t *testing.T) {
stackVolumes := volumes{
"normal": composetypes.VolumeConfig{
Name: "user_specified_name",
Driver: "vsphere",
DriverOpts: map[string]string{
"opt": "value",
},
Labels: map[string]string{
"something": "labeled",
},
},
}
namespace := NewNamespace("foo")
expected := mount.Mount{
Type: mount.TypeVolume,
Source: "user_specified_name",
Target: "/foo",
ReadOnly: true,
VolumeOptions: &mount.VolumeOptions{
Labels: map[string]string{
LabelNamespace: "foo",
"something": "labeled",
},
DriverConfig: &mount.Driver{
Name: "vsphere",
Options: map[string]string{
"opt": "value",
},
},
NoCopy: true,
},
}
config := composetypes.ServiceVolumeConfig{
Type: "volume",
Source: "normal",
Target: "/foo",
ReadOnly: true,
Volume: &composetypes.ServiceVolumeVolume{
NoCopy: true,
},
}
mount, err := convertVolumeToMount(config, stackVolumes, namespace)
assert.NilError(t, err)
assert.Check(t, is.DeepEqual(expected, mount))
}
func TestConvertVolumeToMountNamedVolumeExternal(t *testing.T) {
stackVolumes := volumes{
"outside": composetypes.VolumeConfig{
Name: "special",
External: composetypes.External{External: true},
},
}
namespace := NewNamespace("foo")
expected := mount.Mount{
Type: mount.TypeVolume,
Source: "special",
Target: "/foo",
VolumeOptions: &mount.VolumeOptions{NoCopy: false},
}
config := composetypes.ServiceVolumeConfig{
Type: "volume",
Source: "outside",
Target: "/foo",
}
mount, err := convertVolumeToMount(config, stackVolumes, namespace)
assert.NilError(t, err)
assert.Check(t, is.DeepEqual(expected, mount))
}
func TestConvertVolumeToMountNamedVolumeExternalNoCopy(t *testing.T) {
stackVolumes := volumes{
"outside": composetypes.VolumeConfig{
Name: "special",
External: composetypes.External{External: true},
},
}
namespace := NewNamespace("foo")
expected := mount.Mount{
Type: mount.TypeVolume,
Source: "special",
Target: "/foo",
VolumeOptions: &mount.VolumeOptions{
NoCopy: true,
},
}
config := composetypes.ServiceVolumeConfig{
Type: "volume",
Source: "outside",
Target: "/foo",
Volume: &composetypes.ServiceVolumeVolume{
NoCopy: true,
},
}
mount, err := convertVolumeToMount(config, stackVolumes, namespace)
assert.NilError(t, err)
assert.Check(t, is.DeepEqual(expected, mount))
}
func TestConvertVolumeToMountBind(t *testing.T) {
stackVolumes := volumes{}
namespace := NewNamespace("foo")
expected := mount.Mount{
Type: mount.TypeBind,
Source: "/bar",
Target: "/foo",
ReadOnly: true,
BindOptions: &mount.BindOptions{Propagation: mount.PropagationShared},
}
config := composetypes.ServiceVolumeConfig{
Type: "bind",
Source: "/bar",
Target: "/foo",
ReadOnly: true,
Bind: &composetypes.ServiceVolumeBind{Propagation: "shared"},
}
mount, err := convertVolumeToMount(config, stackVolumes, namespace)
assert.NilError(t, err)
assert.Check(t, is.DeepEqual(expected, mount))
}
func TestConvertVolumeToMountVolumeDoesNotExist(t *testing.T) {
namespace := NewNamespace("foo")
config := composetypes.ServiceVolumeConfig{
Type: "volume",
Source: "unknown",
Target: "/foo",
ReadOnly: true,
}
_, err := convertVolumeToMount(config, volumes{}, namespace)
assert.Error(t, err, "undefined volume \"unknown\"")
}
func TestConvertTmpfsToMountVolume(t *testing.T) {
config := composetypes.ServiceVolumeConfig{
Type: "tmpfs",
Target: "/foo/bar",
Tmpfs: &composetypes.ServiceVolumeTmpfs{
Size: 1000,
},
}
expected := mount.Mount{
Type: mount.TypeTmpfs,
Target: "/foo/bar",
TmpfsOptions: &mount.TmpfsOptions{SizeBytes: 1000},
}
mount, err := convertVolumeToMount(config, volumes{}, NewNamespace("foo"))
assert.NilError(t, err)
assert.Check(t, is.DeepEqual(expected, mount))
}
func TestConvertTmpfsToMountVolumeWithSource(t *testing.T) {
config := composetypes.ServiceVolumeConfig{
Type: "tmpfs",
Source: "/bar",
Target: "/foo/bar",
Tmpfs: &composetypes.ServiceVolumeTmpfs{
Size: 1000,
},
}
_, err := convertVolumeToMount(config, volumes{}, NewNamespace("foo"))
assert.Error(t, err, "invalid tmpfs source, source must be empty")
}
func TestConvertVolumeToMountAnonymousNpipe(t *testing.T) {
config := composetypes.ServiceVolumeConfig{
Type: "npipe",
Source: `\\.\pipe\foo`,
Target: `\\.\pipe\foo`,
}
expected := mount.Mount{
Type: mount.TypeNamedPipe,
Source: `\\.\pipe\foo`,
Target: `\\.\pipe\foo`,
}
mount, err := convertVolumeToMount(config, volumes{}, NewNamespace("foo"))
assert.NilError(t, err)
assert.Check(t, is.DeepEqual(expected, mount))
}

View File

@ -1,6 +1,7 @@
package stack // https://github.com/docker/cli/blob/master/cli/command/stack/loader/loader.go package stack // https://github.com/docker/cli/blob/master/cli/command/stack/loader/loader.go
import ( import (
"context"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"path/filepath" "path/filepath"
@ -8,58 +9,62 @@ import (
"strings" "strings"
"coopcloud.tech/abra/pkg/i18n" "coopcloud.tech/abra/pkg/i18n"
"coopcloud.tech/abra/pkg/log" composeGoCli "github.com/compose-spec/compose-go/v2/cli"
composeGoTypes "github.com/compose-spec/compose-go/v2/types"
"github.com/docker/cli/cli/compose/loader" "github.com/docker/cli/cli/compose/loader"
"github.com/docker/cli/cli/compose/schema" "github.com/docker/cli/cli/compose/schema"
composetypes "github.com/docker/cli/cli/compose/types" composetypes "github.com/docker/cli/cli/compose/types"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/sirupsen/logrus"
) )
// DontSkipValidation ensures validation is done for compose file loading type LoadConf struct {
func DontSkipValidation(opts *loader.Options) { ComposeFiles []string
opts.SkipValidation = false AppEnv map[string]string
} }
// SkipInterpolation skip interpolating environment variables. func LoadCompose(conf LoadConf) (*composeGoTypes.Project, error) {
func SkipInterpolation(opts *loader.Options) { var project *composeGoTypes.Project
opts.SkipInterpolation = true
}
// LoadComposefile parse the composefile specified in the cli and returns its Config and version. // NOTE(d1): silence compose-go internal logger
func LoadComposefile(opts Deploy, appEnv map[string]string, options ...func(*loader.Options)) (*composetypes.Config, error) { logrus.SetOutput(ioutil.Discard)
configDetails, err := getConfigDetails(opts.Composefiles, appEnv)
if err != nil { var projectOptions *composeGoCli.ProjectOptions
return nil, err if len(conf.ComposeFiles) == 0 {
return project, errors.New(i18n.G("LoadCompose: provide compose files"))
} }
if options == nil { if len(conf.AppEnv) == 0 {
options = []func(*loader.Options){DontSkipValidation} var err error
} projectOptions, err = composeGoCli.NewProjectOptions(
conf.ComposeFiles,
dicts := getDictsFrom(configDetails.ConfigFiles) composeGoCli.WithInterpolation(false),
config, err := loader.Load(configDetails, options...) )
if err != nil { if err != nil {
if fpe, ok := err.(*loader.ForbiddenPropertiesError); ok { return project, err
return nil, errors.New(i18n.G("compose file contains unsupported options: %s", propertyWarnings(fpe.Properties))) }
} else {
var env []string
for k, v := range conf.AppEnv {
env = append(env, fmt.Sprintf("%s=%s", k, v))
}
var err error
projectOptions, err = composeGoCli.NewProjectOptions(
conf.ComposeFiles,
composeGoCli.WithEnv(env),
)
if err != nil {
return project, err
} }
return nil, err
} }
recipeName, exists := appEnv["RECIPE"] project, err := projectOptions.LoadProject(context.Background())
if !exists { if err != nil {
recipeName, _ = appEnv["TYPE"] return project, err
} }
unsupportedProperties := loader.GetUnsupportedProperties(dicts...) return project, nil
if len(unsupportedProperties) > 0 {
log.Warn(i18n.G("%s: ignoring unsupported options: %s", recipeName, strings.Join(unsupportedProperties, ", ")))
}
deprecatedProperties := loader.GetDeprecatedProperties(dicts...)
if len(deprecatedProperties) > 0 {
log.Warn(i18n.G("%s: ignoring deprecated options: %s", recipeName, propertyWarnings(deprecatedProperties)))
}
return config, nil
} }
func getDictsFrom(configFiles []composetypes.ConfigFile) []map[string]interface{} { func getDictsFrom(configFiles []composetypes.ConfigFile) []map[string]interface{} {
@ -138,7 +143,7 @@ func loadConfigFile(filename string) (*composetypes.ConfigFile, error) {
config, err := loader.ParseYAML(bytes) config, err := loader.ParseYAML(bytes)
if err != nil { if err != nil {
return nil, err return nil, fmt.Errorf("%s: %s", filename, err)
} }
return &composetypes.ConfigFile{ return &composetypes.ConfigFile{

View File

@ -0,0 +1,26 @@
package stack_test // https://github.com/docker/cli/blob/master/cli/command/stack/loader/loader.go
import (
"testing"
"coopcloud.tech/abra/pkg/app"
"coopcloud.tech/abra/pkg/test"
)
func TestSkipInterpolation(t *testing.T) {
test.Setup()
t.Cleanup(func() { test.Teardown() })
a, err := app.Get(test.AppName)
if err != nil {
t.Fatal(err)
}
_, err = a.Recipe.GetComposeConfig()
if err != nil {
t.Fatal(err)
}
// TODO: ensure compose has a port with no interpolated value
// TODO: ensure compose has port with interpolated value
}

View File

@ -12,6 +12,7 @@ import (
stdlibErr "errors" stdlibErr "errors"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
composeGoTypes "github.com/compose-spec/compose-go/v2/types"
"coopcloud.tech/abra/pkg/config" "coopcloud.tech/abra/pkg/config"
"coopcloud.tech/abra/pkg/i18n" "coopcloud.tech/abra/pkg/i18n"
@ -20,7 +21,6 @@ import (
"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/stack/formatter"
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"
"github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/filters"
@ -197,7 +197,7 @@ func pruneServices(ctx context.Context, cl *dockerClient.Client, namespace conve
func RunDeploy( func RunDeploy(
cl *dockerClient.Client, cl *dockerClient.Client,
opts Deploy, opts Deploy,
cfg *composetypes.Config, cfg *composeGoTypes.Project,
appName string, appName string,
serverName string, serverName string,
dontWait bool, dontWait bool,
@ -246,7 +246,7 @@ func deployCompose(
ctx context.Context, ctx context.Context,
cl *dockerClient.Client, cl *dockerClient.Client,
opts Deploy, opts Deploy,
config *composetypes.Config, config *composeGoTypes.Project,
appName string, appName string,
serverName string, serverName string,
dontWait bool, dontWait bool,
@ -325,7 +325,7 @@ func deployCompose(
return nil return nil
} }
func getServicesDeclaredNetworks(serviceConfigs []composetypes.ServiceConfig) map[string]struct{} { func getServicesDeclaredNetworks(serviceConfigs map[string]composeGoTypes.ServiceConfig) map[string]struct{} {
serviceNetworks := map[string]struct{}{} serviceNetworks := map[string]struct{}{}
for _, serviceConfig := range serviceConfigs { for _, serviceConfig := range serviceConfigs {
if len(serviceConfig.Networks) == 0 { if len(serviceConfig.Networks) == 0 {

View File

@ -8,7 +8,7 @@
"gomodTidy" "gomodTidy"
], ],
"ignoreDeps": [ "ignoreDeps": [
"github.com/urfave/cli", "github.com/docker/cli",
"goreleaser/goreleaser" "github.com/spf13/cobra"
] ]
} }

View File

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

View File

@ -217,3 +217,53 @@ teardown() {
assert_failure assert_failure
assert_output --partial "automagic insertion not supported yet" assert_output --partial "automagic insertion not supported yet"
} }
@test "push during release fails" {
tagHash=$(_get_tag_hash "0.2.0+1.21.0")
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" checkout "$tagHash"
assert_success
run $ABRA recipe upgrade "$TEST_RECIPE" --no-input --patch --commit
assert_success
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" show
assert_success
assert_output --partial 'image: nginx:1.21.6'
wantHash="$(_get_current_hash)"
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" remote set-url origin-ssh "$ABRA_DIR/does/not/exist"
assert_success
run $ABRA recipe release "$TEST_RECIPE" --no-input --patch
assert_failure
assert_output --partial 'failed to publish new release:'
assert_output --partial 'any changes made have been reverted'
assert_equal "$wantHash" "$(_get_current_hash)"
assert_equal "$(_git_status)" ""
}
@test "release, fail, release: works" {
tagHash=$(_get_tag_hash "0.2.0+1.21.0")
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" checkout "$tagHash"
assert_success
run $ABRA recipe upgrade "$TEST_RECIPE" --no-input --patch --commit
assert_success
# NOTE(d1): fake broken remote so the release fails
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" remote set-url origin-ssh "$ABRA_DIR/does/not/exist"
assert_success
run $ABRA recipe release "$TEST_RECIPE" --no-input --patch
assert_failure
# NOTE(d1): correct remote so release can proceed
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" remote set-url origin-ssh "$ABRA_DIR/origin-recipes/$TEST_RECIPE.git"
assert_success
run $ABRA recipe release "$TEST_RECIPE" --no-input --patch
assert_success
}

View File

@ -1,6 +1,9 @@
RECIPE=test_recipe RECIPE=test_recipe
DOMAIN=test_app.example.com DOMAIN=test_app.example.com
# NOTE(d1): ensure commented out TIMEOUT doesn't get included #
# see TestReadEnv in ./pkg/envfile # NOTE(d1): for new changes, you *MUST* also update ../test_server/test_app.example.com.env
# TIMEOUT=120 #
# NOTE(d1): TestReadEnv
# FOO=BAR

View File

@ -0,0 +1,7 @@
---
services:
app:
ports:
- target: 22
published: ${PORT}

View File

@ -1,5 +1,4 @@
--- ---
version: "3.8"
services: services:
app: app:
@ -8,6 +7,7 @@ services:
- proxy - proxy
deploy: deploy:
labels: labels:
- "coop-cloud.${STACK_NAME}.version=0.1.0+0.1.0"
- "coop-cloud.${STACK_NAME}.timeout=${TIMEOUT}" - "coop-cloud.${STACK_NAME}.timeout=${TIMEOUT}"
networks: networks:

View File

@ -1,6 +1,9 @@
RECIPE=test_recipe RECIPE=test_recipe
DOMAIN=test_app.example.com DOMAIN=test_app.example.com
# NOTE(d1): ensure commented out TIMEOUT doesn't get included #
# see TestReadEnv in ./pkg/envfile # NOTE(d1): for new changes, you *MUST* also update ../test_recipe/.env.sample
# TIMEOUT=120 #
# NOTE(d1): TestReadEnv
# FOO=BAR

View File

@ -1,7 +1,7 @@
TOML stands for Tom's Obvious, Minimal Language. This Go package provides a TOML stands for Tom's Obvious, Minimal Language. This Go package provides a
reflection interface similar to Go's standard library `json` and `xml` packages. reflection interface similar to Go's standard library `json` and `xml` packages.
Compatible with TOML version [v1.0.0](https://toml.io/en/v1.0.0). Compatible with TOML version [v1.1.0](https://toml.io/en/v1.1.0).
Documentation: https://pkg.go.dev/github.com/BurntSushi/toml Documentation: https://pkg.go.dev/github.com/BurntSushi/toml

View File

@ -206,6 +206,13 @@ func markDecodedRecursive(md *MetaData, tmap map[string]any) {
markDecodedRecursive(md, tmap) markDecodedRecursive(md, tmap)
md.context = md.context[0 : len(md.context)-1] md.context = md.context[0 : len(md.context)-1]
} }
if tarr, ok := tmap[key].([]map[string]any); ok {
for _, elm := range tarr {
md.context = append(md.context, key)
markDecodedRecursive(md, elm)
md.context = md.context[0 : len(md.context)-1]
}
}
} }
} }
@ -423,7 +430,7 @@ func (md *MetaData) unifyString(data any, rv reflect.Value) error {
if i, ok := data.(int64); ok { if i, ok := data.(int64); ok {
rv.SetString(strconv.FormatInt(i, 10)) rv.SetString(strconv.FormatInt(i, 10))
} else if f, ok := data.(float64); ok { } else if f, ok := data.(float64); ok {
rv.SetString(strconv.FormatFloat(f, 'f', -1, 64)) rv.SetString(strconv.FormatFloat(f, 'g', -1, 64))
} else { } else {
return md.badtype("string", data) return md.badtype("string", data)
} }

View File

@ -228,9 +228,9 @@ func (enc *Encoder) eElement(rv reflect.Value) {
} }
switch v.Location() { switch v.Location() {
default: default:
enc.wf(v.Format(format)) enc.write(v.Format(format))
case internal.LocalDatetime, internal.LocalDate, internal.LocalTime: case internal.LocalDatetime, internal.LocalDate, internal.LocalTime:
enc.wf(v.In(time.UTC).Format(format)) enc.write(v.In(time.UTC).Format(format))
} }
return return
case Marshaler: case Marshaler:
@ -279,40 +279,40 @@ func (enc *Encoder) eElement(rv reflect.Value) {
case reflect.String: case reflect.String:
enc.writeQuoted(rv.String()) enc.writeQuoted(rv.String())
case reflect.Bool: case reflect.Bool:
enc.wf(strconv.FormatBool(rv.Bool())) enc.write(strconv.FormatBool(rv.Bool()))
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
enc.wf(strconv.FormatInt(rv.Int(), 10)) enc.write(strconv.FormatInt(rv.Int(), 10))
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
enc.wf(strconv.FormatUint(rv.Uint(), 10)) enc.write(strconv.FormatUint(rv.Uint(), 10))
case reflect.Float32: case reflect.Float32:
f := rv.Float() f := rv.Float()
if math.IsNaN(f) { if math.IsNaN(f) {
if math.Signbit(f) { if math.Signbit(f) {
enc.wf("-") enc.write("-")
} }
enc.wf("nan") enc.write("nan")
} else if math.IsInf(f, 0) { } else if math.IsInf(f, 0) {
if math.Signbit(f) { if math.Signbit(f) {
enc.wf("-") enc.write("-")
} }
enc.wf("inf") enc.write("inf")
} else { } else {
enc.wf(floatAddDecimal(strconv.FormatFloat(f, 'f', -1, 32))) enc.write(floatAddDecimal(strconv.FormatFloat(f, 'g', -1, 32)))
} }
case reflect.Float64: case reflect.Float64:
f := rv.Float() f := rv.Float()
if math.IsNaN(f) { if math.IsNaN(f) {
if math.Signbit(f) { if math.Signbit(f) {
enc.wf("-") enc.write("-")
} }
enc.wf("nan") enc.write("nan")
} else if math.IsInf(f, 0) { } else if math.IsInf(f, 0) {
if math.Signbit(f) { if math.Signbit(f) {
enc.wf("-") enc.write("-")
} }
enc.wf("inf") enc.write("inf")
} else { } else {
enc.wf(floatAddDecimal(strconv.FormatFloat(f, 'f', -1, 64))) enc.write(floatAddDecimal(strconv.FormatFloat(f, 'g', -1, 64)))
} }
case reflect.Array, reflect.Slice: case reflect.Array, reflect.Slice:
enc.eArrayOrSliceElement(rv) enc.eArrayOrSliceElement(rv)
@ -330,27 +330,32 @@ func (enc *Encoder) eElement(rv reflect.Value) {
// By the TOML spec, all floats must have a decimal with at least one number on // By the TOML spec, all floats must have a decimal with at least one number on
// either side. // either side.
func floatAddDecimal(fstr string) string { func floatAddDecimal(fstr string) string {
if !strings.Contains(fstr, ".") { for _, c := range fstr {
return fstr + ".0" if c == 'e' { // Exponent syntax
return fstr
}
if c == '.' {
return fstr
}
} }
return fstr return fstr + ".0"
} }
func (enc *Encoder) writeQuoted(s string) { func (enc *Encoder) writeQuoted(s string) {
enc.wf("\"%s\"", dblQuotedReplacer.Replace(s)) enc.write(`"` + dblQuotedReplacer.Replace(s) + `"`)
} }
func (enc *Encoder) eArrayOrSliceElement(rv reflect.Value) { func (enc *Encoder) eArrayOrSliceElement(rv reflect.Value) {
length := rv.Len() length := rv.Len()
enc.wf("[") enc.write("[")
for i := 0; i < length; i++ { for i := 0; i < length; i++ {
elem := eindirect(rv.Index(i)) elem := eindirect(rv.Index(i))
enc.eElement(elem) enc.eElement(elem)
if i != length-1 { if i != length-1 {
enc.wf(", ") enc.write(", ")
} }
} }
enc.wf("]") enc.write("]")
} }
func (enc *Encoder) eArrayOfTables(key Key, rv reflect.Value) { func (enc *Encoder) eArrayOfTables(key Key, rv reflect.Value) {
@ -363,7 +368,7 @@ func (enc *Encoder) eArrayOfTables(key Key, rv reflect.Value) {
continue continue
} }
enc.newline() enc.newline()
enc.wf("%s[[%s]]", enc.indentStr(key), key) enc.writef("%s[[%s]]", enc.indentStr(key), key)
enc.newline() enc.newline()
enc.eMapOrStruct(key, trv, false) enc.eMapOrStruct(key, trv, false)
} }
@ -376,7 +381,7 @@ func (enc *Encoder) eTable(key Key, rv reflect.Value) {
enc.newline() enc.newline()
} }
if len(key) > 0 { if len(key) > 0 {
enc.wf("%s[%s]", enc.indentStr(key), key) enc.writef("%s[%s]", enc.indentStr(key), key)
enc.newline() enc.newline()
} }
enc.eMapOrStruct(key, rv, false) enc.eMapOrStruct(key, rv, false)
@ -422,7 +427,7 @@ func (enc *Encoder) eMap(key Key, rv reflect.Value, inline bool) {
if inline { if inline {
enc.writeKeyValue(Key{mapKey.String()}, val, true) enc.writeKeyValue(Key{mapKey.String()}, val, true)
if trailC || i != len(mapKeys)-1 { if trailC || i != len(mapKeys)-1 {
enc.wf(", ") enc.write(", ")
} }
} else { } else {
enc.encode(key.add(mapKey.String()), val) enc.encode(key.add(mapKey.String()), val)
@ -431,12 +436,12 @@ func (enc *Encoder) eMap(key Key, rv reflect.Value, inline bool) {
} }
if inline { if inline {
enc.wf("{") enc.write("{")
} }
writeMapKeys(mapKeysDirect, len(mapKeysSub) > 0) writeMapKeys(mapKeysDirect, len(mapKeysSub) > 0)
writeMapKeys(mapKeysSub, false) writeMapKeys(mapKeysSub, false)
if inline { if inline {
enc.wf("}") enc.write("}")
} }
} }
@ -534,7 +539,7 @@ func (enc *Encoder) eStruct(key Key, rv reflect.Value, inline bool) {
if inline { if inline {
enc.writeKeyValue(Key{keyName}, fieldVal, true) enc.writeKeyValue(Key{keyName}, fieldVal, true)
if fieldIndex[0] != totalFields-1 { if fieldIndex[0] != totalFields-1 {
enc.wf(", ") enc.write(", ")
} }
} else { } else {
enc.encode(key.add(keyName), fieldVal) enc.encode(key.add(keyName), fieldVal)
@ -543,14 +548,14 @@ func (enc *Encoder) eStruct(key Key, rv reflect.Value, inline bool) {
} }
if inline { if inline {
enc.wf("{") enc.write("{")
} }
l := len(fieldsDirect) + len(fieldsSub) l := len(fieldsDirect) + len(fieldsSub)
writeFields(fieldsDirect, l) writeFields(fieldsDirect, l)
writeFields(fieldsSub, l) writeFields(fieldsSub, l)
if inline { if inline {
enc.wf("}") enc.write("}")
} }
} }
@ -700,7 +705,7 @@ func isEmpty(rv reflect.Value) bool {
func (enc *Encoder) newline() { func (enc *Encoder) newline() {
if enc.hasWritten { if enc.hasWritten {
enc.wf("\n") enc.write("\n")
} }
} }
@ -722,14 +727,22 @@ func (enc *Encoder) writeKeyValue(key Key, val reflect.Value, inline bool) {
enc.eElement(val) enc.eElement(val)
return return
} }
enc.wf("%s%s = ", enc.indentStr(key), key.maybeQuoted(len(key)-1)) enc.writef("%s%s = ", enc.indentStr(key), key.maybeQuoted(len(key)-1))
enc.eElement(val) enc.eElement(val)
if !inline { if !inline {
enc.newline() enc.newline()
} }
} }
func (enc *Encoder) wf(format string, v ...any) { func (enc *Encoder) write(s string) {
_, err := enc.w.WriteString(s)
if err != nil {
encPanic(err)
}
enc.hasWritten = true
}
func (enc *Encoder) writef(format string, v ...any) {
_, err := fmt.Fprintf(enc.w, format, v...) _, err := fmt.Fprintf(enc.w, format, v...)
if err != nil { if err != nil {
encPanic(err) encPanic(err)

View File

@ -13,7 +13,6 @@ type itemType int
const ( const (
itemError itemType = iota itemError itemType = iota
itemNIL // used in the parser to indicate no type
itemEOF itemEOF
itemText itemText
itemString itemString
@ -47,14 +46,13 @@ func (p Position) String() string {
} }
type lexer struct { type lexer struct {
input string input string
start int start int
pos int pos int
line int line int
state stateFn state stateFn
items chan item items chan item
tomlNext bool esc bool
esc bool
// Allow for backing up up to 4 runes. This is necessary because TOML // Allow for backing up up to 4 runes. This is necessary because TOML
// contains 3-rune tokens (""" and '''). // contains 3-rune tokens (""" and ''').
@ -90,14 +88,13 @@ func (lx *lexer) nextItem() item {
} }
} }
func lex(input string, tomlNext bool) *lexer { func lex(input string) *lexer {
lx := &lexer{ lx := &lexer{
input: input, input: input,
state: lexTop, state: lexTop,
items: make(chan item, 10), items: make(chan item, 10),
stack: make([]stateFn, 0, 10), stack: make([]stateFn, 0, 10),
line: 1, line: 1,
tomlNext: tomlNext,
} }
return lx return lx
} }
@ -108,7 +105,7 @@ func (lx *lexer) push(state stateFn) {
func (lx *lexer) pop() stateFn { func (lx *lexer) pop() stateFn {
if len(lx.stack) == 0 { if len(lx.stack) == 0 {
return lx.errorf("BUG in lexer: no states to pop") panic("BUG in lexer: no states to pop")
} }
last := lx.stack[len(lx.stack)-1] last := lx.stack[len(lx.stack)-1]
lx.stack = lx.stack[0 : len(lx.stack)-1] lx.stack = lx.stack[0 : len(lx.stack)-1]
@ -305,6 +302,8 @@ func lexTop(lx *lexer) stateFn {
return lexTableStart return lexTableStart
case eof: case eof:
if lx.pos > lx.start { if lx.pos > lx.start {
// TODO: never reached? I think this can only occur on a bug in the
// lexer(?)
return lx.errorf("unexpected EOF") return lx.errorf("unexpected EOF")
} }
lx.emit(itemEOF) lx.emit(itemEOF)
@ -392,8 +391,6 @@ func lexTableNameStart(lx *lexer) stateFn {
func lexTableNameEnd(lx *lexer) stateFn { func lexTableNameEnd(lx *lexer) stateFn {
lx.skip(isWhitespace) lx.skip(isWhitespace)
switch r := lx.next(); { switch r := lx.next(); {
case isWhitespace(r):
return lexTableNameEnd
case r == '.': case r == '.':
lx.ignore() lx.ignore()
return lexTableNameStart return lexTableNameStart
@ -412,7 +409,7 @@ func lexTableNameEnd(lx *lexer) stateFn {
// Lexes only one part, e.g. only 'a' inside 'a.b'. // Lexes only one part, e.g. only 'a' inside 'a.b'.
func lexBareName(lx *lexer) stateFn { func lexBareName(lx *lexer) stateFn {
r := lx.next() r := lx.next()
if isBareKeyChar(r, lx.tomlNext) { if isBareKeyChar(r) {
return lexBareName return lexBareName
} }
lx.backup() lx.backup()
@ -420,23 +417,23 @@ func lexBareName(lx *lexer) stateFn {
return lx.pop() return lx.pop()
} }
// lexBareName lexes one part of a key or table. // lexQuotedName lexes one part of a quoted key or table name. It assumes that
// // it starts lexing at the quote itself (" or ').
// It assumes that at least one valid character for the table has already been
// read.
// //
// Lexes only one part, e.g. only '"a"' inside '"a".b'. // Lexes only one part, e.g. only '"a"' inside '"a".b'.
func lexQuotedName(lx *lexer) stateFn { func lexQuotedName(lx *lexer) stateFn {
r := lx.next() r := lx.next()
switch { switch {
case isWhitespace(r):
return lexSkip(lx, lexValue)
case r == '"': case r == '"':
lx.ignore() // ignore the '"' lx.ignore() // ignore the '"'
return lexString return lexString
case r == '\'': case r == '\'':
lx.ignore() // ignore the "'" lx.ignore() // ignore the "'"
return lexRawString return lexRawString
// TODO: I don't think any of the below conditions can ever be reached?
case isWhitespace(r):
return lexSkip(lx, lexValue)
case r == eof: case r == eof:
return lx.errorf("unexpected EOF; expected value") return lx.errorf("unexpected EOF; expected value")
default: default:
@ -464,17 +461,19 @@ func lexKeyStart(lx *lexer) stateFn {
func lexKeyNameStart(lx *lexer) stateFn { func lexKeyNameStart(lx *lexer) stateFn {
lx.skip(isWhitespace) lx.skip(isWhitespace)
switch r := lx.peek(); { switch r := lx.peek(); {
case r == '=' || r == eof: default:
return lx.errorf("unexpected '='") lx.push(lexKeyEnd)
case r == '.': return lexBareName
return lx.errorf("unexpected '.'")
case r == '"' || r == '\'': case r == '"' || r == '\'':
lx.ignore() lx.ignore()
lx.push(lexKeyEnd) lx.push(lexKeyEnd)
return lexQuotedName return lexQuotedName
default:
lx.push(lexKeyEnd) // TODO: I think these can never be reached?
return lexBareName case r == '=' || r == eof:
return lx.errorf("unexpected '='")
case r == '.':
return lx.errorf("unexpected '.'")
} }
} }
@ -485,7 +484,7 @@ func lexKeyEnd(lx *lexer) stateFn {
switch r := lx.next(); { switch r := lx.next(); {
case isWhitespace(r): case isWhitespace(r):
return lexSkip(lx, lexKeyEnd) return lexSkip(lx, lexKeyEnd)
case r == eof: case r == eof: // TODO: never reached
return lx.errorf("unexpected EOF; expected key separator '='") return lx.errorf("unexpected EOF; expected key separator '='")
case r == '.': case r == '.':
lx.ignore() lx.ignore()
@ -628,10 +627,7 @@ func lexInlineTableValue(lx *lexer) stateFn {
case isWhitespace(r): case isWhitespace(r):
return lexSkip(lx, lexInlineTableValue) return lexSkip(lx, lexInlineTableValue)
case isNL(r): case isNL(r):
if lx.tomlNext { return lexSkip(lx, lexInlineTableValue)
return lexSkip(lx, lexInlineTableValue)
}
return lx.errorPrevLine(errLexInlineTableNL{})
case r == '#': case r == '#':
lx.push(lexInlineTableValue) lx.push(lexInlineTableValue)
return lexCommentStart return lexCommentStart
@ -653,10 +649,7 @@ func lexInlineTableValueEnd(lx *lexer) stateFn {
case isWhitespace(r): case isWhitespace(r):
return lexSkip(lx, lexInlineTableValueEnd) return lexSkip(lx, lexInlineTableValueEnd)
case isNL(r): case isNL(r):
if lx.tomlNext { return lexSkip(lx, lexInlineTableValueEnd)
return lexSkip(lx, lexInlineTableValueEnd)
}
return lx.errorPrevLine(errLexInlineTableNL{})
case r == '#': case r == '#':
lx.push(lexInlineTableValueEnd) lx.push(lexInlineTableValueEnd)
return lexCommentStart return lexCommentStart
@ -664,10 +657,7 @@ func lexInlineTableValueEnd(lx *lexer) stateFn {
lx.ignore() lx.ignore()
lx.skip(isWhitespace) lx.skip(isWhitespace)
if lx.peek() == '}' { if lx.peek() == '}' {
if lx.tomlNext { return lexInlineTableValueEnd
return lexInlineTableValueEnd
}
return lx.errorf("trailing comma not allowed in inline tables")
} }
return lexInlineTableValue return lexInlineTableValue
case r == '}': case r == '}':
@ -855,9 +845,6 @@ func lexStringEscape(lx *lexer) stateFn {
r := lx.next() r := lx.next()
switch r { switch r {
case 'e': case 'e':
if !lx.tomlNext {
return lx.error(errLexEscape{r})
}
fallthrough fallthrough
case 'b': case 'b':
fallthrough fallthrough
@ -878,9 +865,6 @@ func lexStringEscape(lx *lexer) stateFn {
case '\\': case '\\':
return lx.pop() return lx.pop()
case 'x': case 'x':
if !lx.tomlNext {
return lx.error(errLexEscape{r})
}
return lexHexEscape return lexHexEscape
case 'u': case 'u':
return lexShortUnicodeEscape return lexShortUnicodeEscape
@ -928,19 +912,9 @@ func lexLongUnicodeEscape(lx *lexer) stateFn {
// lexBaseNumberOrDate can differentiate base prefixed integers from other // lexBaseNumberOrDate can differentiate base prefixed integers from other
// types. // types.
func lexNumberOrDateStart(lx *lexer) stateFn { func lexNumberOrDateStart(lx *lexer) stateFn {
r := lx.next() if lx.next() == '0' {
switch r {
case '0':
return lexBaseNumberOrDate return lexBaseNumberOrDate
} }
if !isDigit(r) {
// The only way to reach this state is if the value starts
// with a digit, so specifically treat anything else as an
// error.
return lx.errorf("expected a digit but got %q", r)
}
return lexNumberOrDate return lexNumberOrDate
} }
@ -1196,13 +1170,13 @@ func lexSkip(lx *lexer, nextState stateFn) stateFn {
} }
func (s stateFn) String() string { func (s stateFn) String() string {
if s == nil {
return "<nil>"
}
name := runtime.FuncForPC(reflect.ValueOf(s).Pointer()).Name() name := runtime.FuncForPC(reflect.ValueOf(s).Pointer()).Name()
if i := strings.LastIndexByte(name, '.'); i > -1 { if i := strings.LastIndexByte(name, '.'); i > -1 {
name = name[i+1:] name = name[i+1:]
} }
if s == nil {
name = "<nil>"
}
return name + "()" return name + "()"
} }
@ -1210,8 +1184,6 @@ func (itype itemType) String() string {
switch itype { switch itype {
case itemError: case itemError:
return "Error" return "Error"
case itemNIL:
return "NIL"
case itemEOF: case itemEOF:
return "EOF" return "EOF"
case itemText: case itemText:
@ -1226,18 +1198,22 @@ func (itype itemType) String() string {
return "Float" return "Float"
case itemDatetime: case itemDatetime:
return "DateTime" return "DateTime"
case itemTableStart:
return "TableStart"
case itemTableEnd:
return "TableEnd"
case itemKeyStart:
return "KeyStart"
case itemKeyEnd:
return "KeyEnd"
case itemArray: case itemArray:
return "Array" return "Array"
case itemArrayEnd: case itemArrayEnd:
return "ArrayEnd" return "ArrayEnd"
case itemTableStart:
return "TableStart"
case itemTableEnd:
return "TableEnd"
case itemArrayTableStart:
return "ArrayTableStart"
case itemArrayTableEnd:
return "ArrayTableEnd"
case itemKeyStart:
return "KeyStart"
case itemKeyEnd:
return "KeyEnd"
case itemCommentStart: case itemCommentStart:
return "CommentStart" return "CommentStart"
case itemInlineTableStart: case itemInlineTableStart:
@ -1266,7 +1242,7 @@ func isDigit(r rune) bool { return r >= '0' && r <= '9' }
func isBinary(r rune) bool { return r == '0' || r == '1' } func isBinary(r rune) bool { return r == '0' || r == '1' }
func isOctal(r rune) bool { return r >= '0' && r <= '7' } func isOctal(r rune) bool { return r >= '0' && r <= '7' }
func isHex(r rune) bool { return (r >= '0' && r <= '9') || (r|0x20 >= 'a' && r|0x20 <= 'f') } func isHex(r rune) bool { return (r >= '0' && r <= '9') || (r|0x20 >= 'a' && r|0x20 <= 'f') }
func isBareKeyChar(r rune, tomlNext bool) bool { func isBareKeyChar(r rune) bool {
return (r >= 'A' && r <= 'Z') || (r >= 'a' && r <= 'z') || return (r >= 'A' && r <= 'Z') || (r >= 'a' && r <= 'z') ||
(r >= '0' && r <= '9') || r == '_' || r == '-' (r >= '0' && r <= '9') || r == '_' || r == '-'
} }

View File

@ -3,7 +3,6 @@ package toml
import ( import (
"fmt" "fmt"
"math" "math"
"os"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@ -17,7 +16,6 @@ type parser struct {
context Key // Full key for the current hash in scope. context Key // Full key for the current hash in scope.
currentKey string // Base key name for everything except hashes. currentKey string // Base key name for everything except hashes.
pos Position // Current position in the TOML file. pos Position // Current position in the TOML file.
tomlNext bool
ordered []Key // List of keys in the order that they appear in the TOML data. ordered []Key // List of keys in the order that they appear in the TOML data.
@ -32,8 +30,6 @@ type keyInfo struct {
} }
func parse(data string) (p *parser, err error) { func parse(data string) (p *parser, err error) {
_, tomlNext := os.LookupEnv("BURNTSUSHI_TOML_110")
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
if pErr, ok := r.(ParseError); ok { if pErr, ok := r.(ParseError); ok {
@ -73,10 +69,9 @@ func parse(data string) (p *parser, err error) {
p = &parser{ p = &parser{
keyInfo: make(map[string]keyInfo), keyInfo: make(map[string]keyInfo),
mapping: make(map[string]any), mapping: make(map[string]any),
lx: lex(data, tomlNext), lx: lex(data),
ordered: make([]Key, 0), ordered: make([]Key, 0),
implicits: make(map[string]struct{}), implicits: make(map[string]struct{}),
tomlNext: tomlNext,
} }
for { for {
item := p.next() item := p.next()
@ -350,17 +345,14 @@ func (p *parser) valueFloat(it item) (any, tomlType) {
var dtTypes = []struct { var dtTypes = []struct {
fmt string fmt string
zone *time.Location zone *time.Location
next bool
}{ }{
{time.RFC3339Nano, time.Local, false}, {time.RFC3339Nano, time.Local},
{"2006-01-02T15:04:05.999999999", internal.LocalDatetime, false}, {"2006-01-02T15:04:05.999999999", internal.LocalDatetime},
{"2006-01-02", internal.LocalDate, false}, {"2006-01-02", internal.LocalDate},
{"15:04:05.999999999", internal.LocalTime, false}, {"15:04:05.999999999", internal.LocalTime},
{"2006-01-02T15:04Z07:00", time.Local},
// tomlNext {"2006-01-02T15:04", internal.LocalDatetime},
{"2006-01-02T15:04Z07:00", time.Local, true}, {"15:04", internal.LocalTime},
{"2006-01-02T15:04", internal.LocalDatetime, true},
{"15:04", internal.LocalTime, true},
} }
func (p *parser) valueDatetime(it item) (any, tomlType) { func (p *parser) valueDatetime(it item) (any, tomlType) {
@ -371,9 +363,6 @@ func (p *parser) valueDatetime(it item) (any, tomlType) {
err error err error
) )
for _, dt := range dtTypes { for _, dt := range dtTypes {
if dt.next && !p.tomlNext {
continue
}
t, err = time.ParseInLocation(dt.fmt, it.val, dt.zone) t, err = time.ParseInLocation(dt.fmt, it.val, dt.zone)
if err == nil { if err == nil {
if missingLeadingZero(it.val, dt.fmt) { if missingLeadingZero(it.val, dt.fmt) {
@ -644,6 +633,11 @@ func (p *parser) setValue(key string, value any) {
// Note that since it has already been defined (as a hash), we don't // Note that since it has already been defined (as a hash), we don't
// want to overwrite it. So our business is done. // want to overwrite it. So our business is done.
if p.isArray(keyContext) { if p.isArray(keyContext) {
if !p.isImplicit(keyContext) {
if _, ok := hash[key]; ok {
p.panicf("Key '%s' has already been defined.", keyContext)
}
}
p.removeImplicit(keyContext) p.removeImplicit(keyContext)
hash[key] = value hash[key] = value
return return
@ -802,10 +796,8 @@ func (p *parser) replaceEscapes(it item, str string) string {
b.WriteByte(0x0d) b.WriteByte(0x0d)
skip = 1 skip = 1
case 'e': case 'e':
if p.tomlNext { b.WriteByte(0x1b)
b.WriteByte(0x1b) skip = 1
skip = 1
}
case '"': case '"':
b.WriteByte(0x22) b.WriteByte(0x22)
skip = 1 skip = 1
@ -815,11 +807,9 @@ func (p *parser) replaceEscapes(it item, str string) string {
// The lexer guarantees the correct number of characters are present; // The lexer guarantees the correct number of characters are present;
// don't need to check here. // don't need to check here.
case 'x': case 'x':
if p.tomlNext { escaped := p.asciiEscapeToUnicode(it, str[i+2:i+4])
escaped := p.asciiEscapeToUnicode(it, str[i+2:i+4]) b.WriteRune(escaped)
b.WriteRune(escaped) skip = 3
skip = 3
}
case 'u': case 'u':
escaped := p.asciiEscapeToUnicode(it, str[i+2:i+6]) escaped := p.asciiEscapeToUnicode(it, str[i+2:i+6])
b.WriteRune(escaped) b.WriteRune(escaped)

View File

@ -69,6 +69,8 @@ func (l *lineReader) Read(p []byte) (n int, err error) {
if isPrefix { if isPrefix {
return 0, ArmorCorrupt return 0, ArmorCorrupt
} }
// Trim the line to remove any whitespace
line = bytes.TrimSpace(line)
if bytes.HasPrefix(line, armorEnd) { if bytes.HasPrefix(line, armorEnd) {
l.eof = true l.eof = true

View File

@ -125,7 +125,10 @@ func (c *curve25519) Encaps(rand io.Reader, point []byte) (ephemeral, sharedSecr
// "VB = convert point V to the octet string" // "VB = convert point V to the octet string"
// sharedPoint corresponds to `VB`. // sharedPoint corresponds to `VB`.
var sharedPoint x25519lib.Key var sharedPoint x25519lib.Key
x25519lib.Shared(&sharedPoint, &ephemeralPrivate, &pubKey) ok := x25519lib.Shared(&sharedPoint, &ephemeralPrivate, &pubKey)
if !ok {
return nil, nil, errors.KeyInvalidError("ecc: the public key is a low order point")
}
return ephemeralPublic[:], sharedPoint[:], nil return ephemeralPublic[:], sharedPoint[:], nil
} }
@ -146,7 +149,10 @@ func (c *curve25519) Decaps(vsG, secret []byte) (sharedSecret []byte, err error)
// RFC6637 §8: "Note that the recipient obtains the shared secret by calculating // RFC6637 §8: "Note that the recipient obtains the shared secret by calculating
// S = rV = rvG, where (r,R) is the recipient's key pair." // S = rV = rvG, where (r,R) is the recipient's key pair."
// sharedPoint corresponds to `S`. // sharedPoint corresponds to `S`.
x25519lib.Shared(&sharedPoint, &decodedPrivate, &ephemeralPublic) ok := x25519lib.Shared(&sharedPoint, &decodedPrivate, &ephemeralPublic)
if !ok {
return nil, errors.KeyInvalidError("ecc: the public key is a low order point")
}
return sharedPoint[:], nil return sharedPoint[:], nil
} }

View File

@ -78,7 +78,7 @@ func (c *genericCurve) GenerateECDSA(rand io.Reader) (x, y, secret *big.Int, err
func (c *genericCurve) Encaps(rand io.Reader, point []byte) (ephemeral, sharedSecret []byte, err error) { func (c *genericCurve) Encaps(rand io.Reader, point []byte) (ephemeral, sharedSecret []byte, err error) {
xP, yP := elliptic.Unmarshal(c.Curve, point) xP, yP := elliptic.Unmarshal(c.Curve, point)
if xP == nil { if xP == nil {
panic("invalid point") return nil, nil, errors.KeyInvalidError(fmt.Sprintf("ecc (%s): invalid point", c.Curve.Params().Name))
} }
d, x, y, err := elliptic.GenerateKey(c.Curve, rand) d, x, y, err := elliptic.GenerateKey(c.Curve, rand)
@ -99,6 +99,9 @@ func (c *genericCurve) Encaps(rand io.Reader, point []byte) (ephemeral, sharedSe
func (c *genericCurve) Decaps(ephemeral, secret []byte) (sharedSecret []byte, err error) { func (c *genericCurve) Decaps(ephemeral, secret []byte) (sharedSecret []byte, err error) {
x, y := elliptic.Unmarshal(c.Curve, ephemeral) x, y := elliptic.Unmarshal(c.Curve, ephemeral)
if x == nil {
return nil, errors.KeyInvalidError(fmt.Sprintf("ecc (%s): invalid point", c.Curve.Params().Name))
}
zbBig, _ := c.Curve.ScalarMult(x, y, secret) zbBig, _ := c.Curve.ScalarMult(x, y, secret)
byteLen := (c.Curve.Params().BitSize + 7) >> 3 byteLen := (c.Curve.Params().BitSize + 7) >> 3
zb := make([]byte, byteLen) zb := make([]byte, byteLen)

View File

@ -178,6 +178,18 @@ type Config struct {
// When set to true, a key without flags is treated as if all flags are enabled. // When set to true, a key without flags is treated as if all flags are enabled.
// This behavior is consistent with GPG. // This behavior is consistent with GPG.
InsecureAllowAllKeyFlagsWhenMissing bool InsecureAllowAllKeyFlagsWhenMissing bool
// InsecureGenerateNonCriticalKeyFlags causes the "Key Flags" signature subpacket
// to be non-critical in newly generated signatures.
// This may be needed for keys to be accepted by older clients who do not recognize
// the subpacket.
// For example, rpm 4.14.3-150400.59.3.1 in OpenSUSE Leap 15.4 does not recognize it.
InsecureGenerateNonCriticalKeyFlags bool
// InsecureGenerateNonCriticalSignatureCreationTime causes the "Signature Creation Time" signature subpacket
// to be non-critical in newly generated signatures.
// This may be needed for keys to be accepted by older clients who do not recognize
// the subpacket.
// For example, yum 3.4.3-168 in CentOS 7 and yum 3.4.3-158 in Amazon Linux 2 do not recognize it.
InsecureGenerateNonCriticalSignatureCreationTime bool
// MaxDecompressedMessageSize specifies the maximum number of bytes that can be // MaxDecompressedMessageSize specifies the maximum number of bytes that can be
// read from a compressed packet. This serves as an upper limit to prevent // read from a compressed packet. This serves as an upper limit to prevent
@ -420,6 +432,20 @@ func (c *Config) AllowAllKeyFlagsWhenMissing() bool {
return c.InsecureAllowAllKeyFlagsWhenMissing return c.InsecureAllowAllKeyFlagsWhenMissing
} }
func (c *Config) GenerateNonCriticalKeyFlags() bool {
if c == nil {
return false
}
return c.InsecureGenerateNonCriticalKeyFlags
}
func (c *Config) GenerateNonCriticalSignatureCreationTime() bool {
if c == nil {
return false
}
return c.InsecureGenerateNonCriticalSignatureCreationTime
}
func (c *Config) DecompressedMessageSizeLimit() *int64 { func (c *Config) DecompressedMessageSizeLimit() *int64 {
if c == nil { if c == nil {
return nil return nil

View File

@ -933,7 +933,7 @@ func (sig *Signature) Sign(h hash.Hash, priv *PrivateKey, config *Config) (err e
} }
sig.Notations = append(sig.Notations, &notation) sig.Notations = append(sig.Notations, &notation)
} }
sig.outSubpackets, err = sig.buildSubpackets(priv.PublicKey) sig.outSubpackets, err = sig.buildSubpackets(priv.PublicKey, config)
if err != nil { if err != nil {
return err return err
} }
@ -1254,11 +1254,11 @@ type outputSubpacket struct {
contents []byte contents []byte
} }
func (sig *Signature) buildSubpackets(issuer PublicKey) (subpackets []outputSubpacket, err error) { func (sig *Signature) buildSubpackets(issuer PublicKey, config *Config) (subpackets []outputSubpacket, err error) {
creationTime := make([]byte, 4) creationTime := make([]byte, 4)
binary.BigEndian.PutUint32(creationTime, uint32(sig.CreationTime.Unix())) binary.BigEndian.PutUint32(creationTime, uint32(sig.CreationTime.Unix()))
// Signature Creation Time // Signature Creation Time
subpackets = append(subpackets, outputSubpacket{true, creationTimeSubpacket, true, creationTime}) subpackets = append(subpackets, outputSubpacket{true, creationTimeSubpacket, !config.GenerateNonCriticalSignatureCreationTime(), creationTime})
// Signature Expiration Time // Signature Expiration Time
if sig.SigLifetimeSecs != nil && *sig.SigLifetimeSecs != 0 { if sig.SigLifetimeSecs != nil && *sig.SigLifetimeSecs != 0 {
sigLifetime := make([]byte, 4) sigLifetime := make([]byte, 4)
@ -1357,7 +1357,7 @@ func (sig *Signature) buildSubpackets(issuer PublicKey) (subpackets []outputSubp
if sig.FlagGroupKey { if sig.FlagGroupKey {
flags |= KeyFlagGroupKey flags |= KeyFlagGroupKey
} }
subpackets = append(subpackets, outputSubpacket{true, keyFlagsSubpacket, true, []byte{flags}}) subpackets = append(subpackets, outputSubpacket{true, keyFlagsSubpacket, !config.GenerateNonCriticalKeyFlags(), []byte{flags}})
} }
// Signer's User ID // Signer's User ID
if sig.SignerUserId != nil { if sig.SignerUserId != nil {

View File

@ -33,6 +33,9 @@ linters:
generated: lax generated: lax
presets: presets:
- common-false-positives - common-false-positives
settings:
exhaustive:
default-signifies-exhaustive: true
issues: issues:
max-issues-per-linter: 0 max-issues-per-linter: 0
max-same-issues: 0 max-same-issues: 0

View File

@ -2,6 +2,7 @@ package colorprofile
import ( import (
"bytes" "bytes"
"context"
"io" "io"
"os/exec" "os/exec"
"runtime" "runtime"
@ -83,8 +84,8 @@ func colorProfile(isatty bool, env environ) (p Profile) {
} }
if envNoColor(env) && isatty { if envNoColor(env) && isatty {
if p > Ascii { if p > ASCII {
p = Ascii p = ASCII
} }
return //nolint:nakedret return //nolint:nakedret
} }
@ -153,29 +154,29 @@ 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 len(env["WT_SESSION"]) > 0 {
// Windows Terminal supports TrueColor
return TrueColor
} }
if isCloudShell, _ := strconv.ParseBool(env.get("GOOGLE_CLOUD_SHELL")); isCloudShell { if isCloudShell, _ := strconv.ParseBool(env.get("GOOGLE_CLOUD_SHELL")); isCloudShell {
@ -244,13 +245,13 @@ func tmux(env environ) (p Profile) {
// Check if tmux has either Tc or RGB capabilities. Otherwise, return // Check if tmux has either Tc or RGB capabilities. Otherwise, return
// ANSI256. // ANSI256.
p = ANSI256 p = ANSI256
cmd := exec.Command("tmux", "info") cmd := exec.CommandContext(context.Background(), "tmux", "info")
out, err := cmd.Output() out, err := cmd.Output()
if err != nil { if err != nil {
return return
} }
for _, line := range bytes.Split(out, []byte("\n")) { for line := range bytes.SplitSeq(out, []byte("\n")) {
if (bytes.Contains(line, []byte("Tc")) || bytes.Contains(line, []byte("RGB"))) && if (bytes.Contains(line, []byte("Tc")) || bytes.Contains(line, []byte("RGB"))) &&
bytes.Contains(line, []byte("true")) { bytes.Contains(line, []byte("true")) {
return TrueColor return TrueColor

View File

@ -14,11 +14,6 @@ func windowsColorProfile(env map[string]string) (Profile, bool) {
return TrueColor, true return TrueColor, true
} }
if len(env["WT_SESSION"]) > 0 {
// Windows Terminal supports TrueColor
return TrueColor, true
}
major, _, build := windows.RtlGetNtVersionNumbers() major, _, build := windows.RtlGetNtVersionNumbers()
if build < 10586 || major < 10 { if build < 10586 || major < 10 {
// No ANSI support before WindowsNT 10 build 10586 // No ANSI support before WindowsNT 10 build 10586

View File

@ -11,10 +11,12 @@ import (
type Profile byte type Profile byte
const ( const (
// Unknown is a profile that represents the absence of a profile.
Unknown Profile = iota
// NoTTY is a profile with no terminal support. // NoTTY is a profile with no terminal support.
NoTTY Profile = iota NoTTY
// Ascii is a profile with no color support. // ASCII is a profile with no color support.
Ascii //nolint:revive ASCII
// ANSI is a profile with 16 colors (4-bit). // ANSI is a profile with 16 colors (4-bit).
ANSI ANSI
// ANSI256 is a profile with 256 colors (8-bit). // ANSI256 is a profile with 256 colors (8-bit).
@ -23,6 +25,9 @@ const (
TrueColor TrueColor
) )
// Ascii is an alias for the [ASCII] profile for backwards compatibility.
const Ascii = ASCII //nolint:revive
// String returns the string representation of a Profile. // String returns the string representation of a Profile.
func (p Profile) String() string { func (p Profile) String() string {
switch p { switch p {
@ -32,12 +37,13 @@ func (p Profile) String() string {
return "ANSI256" return "ANSI256"
case ANSI: case ANSI:
return "ANSI" return "ANSI"
case Ascii: case ASCII:
return "Ascii" return "Ascii"
case NoTTY: case NoTTY:
return "NoTTY" return "NoTTY"
default:
return "Unknown"
} }
return "Unknown"
} }
var ( var (
@ -50,7 +56,7 @@ var (
// Convert transforms a given Color to a Color supported within the Profile. // Convert transforms a given Color to a Color supported within the Profile.
func (p Profile) Convert(c color.Color) (cc color.Color) { func (p Profile) Convert(c color.Color) (cc color.Color) {
if p <= Ascii { if p <= ASCII {
return nil return nil
} }
if p == TrueColor { if p == TrueColor {
@ -90,11 +96,13 @@ func (p Profile) Convert(c color.Color) (cc color.Color) {
return c return c
default: default:
if p == ANSI256 { switch p {
case ANSI256:
return ansi.Convert256(c) return ansi.Convert256(c)
} else if p == ANSI { case ANSI:
return ansi.Convert16(c) return ansi.Convert16(c)
default:
return c
} }
return c
} }
} }

View File

@ -36,13 +36,15 @@ type Writer struct {
// Write writes the given text to the underlying writer. // Write writes the given text to the underlying writer.
func (w *Writer) Write(p []byte) (int, error) { func (w *Writer) Write(p []byte) (int, error) {
switch w.Profile { switch {
case TrueColor: case w.Profile == TrueColor:
return w.Forward.Write(p) //nolint:wrapcheck return w.Forward.Write(p) //nolint:wrapcheck
case NoTTY: case w.Profile <= NoTTY:
return io.WriteString(w.Forward, ansi.Strip(string(p))) //nolint:wrapcheck _, err := io.WriteString(w.Forward, ansi.Strip(string(p)))
case Ascii, ANSI, ANSI256: return len(p), err
return w.downsample(p) case w.Profile == ASCII, w.Profile == ANSI, w.Profile == ANSI256:
_, err := w.downsample(p)
return len(p), err
default: default:
return 0, fmt.Errorf("invalid profile: %v", w.Profile) return 0, fmt.Errorf("invalid profile: %v", w.Profile)
} }
@ -112,7 +114,7 @@ func handleSgr(w *Writer, p *ansi.Parser, buf *bytes.Buffer) {
if w.Profile < ANSI { if w.Profile < ANSI {
continue continue
} }
style = style.DefaultForegroundColor() style = style.ForegroundColor(nil)
case 40, 41, 42, 43, 44, 45, 46, 47: // 8-bit background color case 40, 41, 42, 43, 44, 45, 46, 47: // 8-bit background color
if w.Profile < ANSI { if w.Profile < ANSI {
continue continue
@ -132,7 +134,7 @@ func handleSgr(w *Writer, p *ansi.Parser, buf *bytes.Buffer) {
if w.Profile < ANSI { if w.Profile < ANSI {
continue continue
} }
style = style.DefaultBackgroundColor() style = style.BackgroundColor(nil)
case 58: // 16 or 24-bit underline color case 58: // 16 or 24-bit underline color
var c color.Color var c color.Color
if n := ansi.ReadStyleColor(params[i:], &c); n > 0 { if n := ansi.ReadStyleColor(params[i:], &c); n > 0 {
@ -146,7 +148,7 @@ func handleSgr(w *Writer, p *ansi.Parser, buf *bytes.Buffer) {
if w.Profile < ANSI { if w.Profile < ANSI {
continue continue
} }
style = style.DefaultUnderlineColor() style = style.UnderlineColor(nil)
case 90, 91, 92, 93, 94, 95, 96, 97: // 8-bit bright foreground color case 90, 91, 92, 93, 94, 95, 96, 97: // 8-bit bright foreground color
if w.Profile < ANSI { if w.Profile < ANSI {
continue continue

View File

@ -7,7 +7,6 @@ linters:
- exhaustive - exhaustive
- goconst - goconst
- godot - godot
- godox
- gomoddirectives - gomoddirectives
- goprintffuncname - goprintffuncname
- gosec - gosec
@ -27,9 +26,16 @@ 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
settings:
exhaustive:
default-signifies-exhaustive: true
issues: issues:
max-issues-per-linter: 0 max-issues-per-linter: 0
max-same-issues: 0 max-same-issues: 0

View File

@ -7,7 +7,7 @@ import (
"time" "time"
) )
func (l *Logger) jsonFormatter(keyvals ...interface{}) { func (l *Logger) jsonFormatter(keyvals ...any) {
jw := &jsonWriter{w: &l.b} jw := &jsonWriter{w: &l.b}
jw.start() jw.start()

View File

@ -8,7 +8,7 @@ import (
"github.com/go-logfmt/logfmt" "github.com/go-logfmt/logfmt"
) )
func (l *Logger) logfmtFormatter(keyvals ...interface{}) { func (l *Logger) logfmtFormatter(keyvals ...any) {
e := logfmt.NewEncoder(&l.b) e := logfmt.NewEncoder(&l.b)
for i := 0; i < len(keyvals); i += 2 { for i := 0; i < len(keyvals); i += 2 {

View File

@ -41,19 +41,19 @@ type Logger struct {
reportCaller bool reportCaller bool
reportTimestamp bool reportTimestamp bool
fields []interface{} fields []any
helpers *sync.Map helpers *sync.Map
styles *Styles styles *Styles
} }
// Logf logs a message with formatting. // Logf logs a message with formatting.
func (l *Logger) Logf(level Level, format string, args ...interface{}) { func (l *Logger) Logf(level Level, format string, args ...any) {
l.Log(level, fmt.Sprintf(format, args...)) l.Log(level, fmt.Sprintf(format, args...))
} }
// Log logs the given message with the given keyvals for the given level. // Log logs the given message with the given keyvals for the given level.
func (l *Logger) Log(level Level, msg interface{}, keyvals ...interface{}) { func (l *Logger) Log(level Level, msg any, keyvals ...any) {
if atomic.LoadUint32(&l.isDiscard) != 0 { if atomic.LoadUint32(&l.isDiscard) != 0 {
return return
} }
@ -81,8 +81,8 @@ func (l *Logger) Log(level Level, msg interface{}, keyvals ...interface{}) {
l.handle(level, l.timeFunc(time.Now()), []runtime.Frame{frame}, msg, keyvals...) l.handle(level, l.timeFunc(time.Now()), []runtime.Frame{frame}, msg, keyvals...)
} }
func (l *Logger) handle(level Level, ts time.Time, frames []runtime.Frame, msg interface{}, keyvals ...interface{}) { func (l *Logger) handle(level Level, ts time.Time, frames []runtime.Frame, msg any, keyvals ...any) {
var kvs []interface{} var kvs []any
if l.reportTimestamp && !ts.IsZero() { if l.reportTimestamp && !ts.IsZero() {
kvs = append(kvs, TimestampKey, ts) kvs = append(kvs, TimestampKey, ts)
} }
@ -327,7 +327,7 @@ func (l *Logger) SetStyles(s *Styles) {
} }
// With returns a new logger with the given keyvals added. // With returns a new logger with the given keyvals added.
func (l *Logger) With(keyvals ...interface{}) *Logger { func (l *Logger) With(keyvals ...any) *Logger {
var st Styles var st Styles
l.mu.Lock() l.mu.Lock()
sl := *l sl := *l
@ -336,7 +336,7 @@ func (l *Logger) With(keyvals ...interface{}) *Logger {
sl.b = bytes.Buffer{} sl.b = bytes.Buffer{}
sl.mu = &sync.RWMutex{} sl.mu = &sync.RWMutex{}
sl.helpers = &sync.Map{} sl.helpers = &sync.Map{}
sl.fields = append(make([]interface{}, 0, len(l.fields)+len(keyvals)), l.fields...) sl.fields = append(make([]any, 0, len(l.fields)+len(keyvals)), l.fields...)
sl.fields = append(sl.fields, keyvals...) sl.fields = append(sl.fields, keyvals...)
sl.styles = &st sl.styles = &st
return &sl return &sl
@ -350,63 +350,63 @@ func (l *Logger) WithPrefix(prefix string) *Logger {
} }
// Debug prints a debug message. // Debug prints a debug message.
func (l *Logger) Debug(msg interface{}, keyvals ...interface{}) { func (l *Logger) Debug(msg any, keyvals ...any) {
l.Log(DebugLevel, msg, keyvals...) l.Log(DebugLevel, msg, keyvals...)
} }
// Info prints an info message. // Info prints an info message.
func (l *Logger) Info(msg interface{}, keyvals ...interface{}) { func (l *Logger) Info(msg any, keyvals ...any) {
l.Log(InfoLevel, msg, keyvals...) l.Log(InfoLevel, msg, keyvals...)
} }
// Warn prints a warning message. // Warn prints a warning message.
func (l *Logger) Warn(msg interface{}, keyvals ...interface{}) { func (l *Logger) Warn(msg any, keyvals ...any) {
l.Log(WarnLevel, msg, keyvals...) l.Log(WarnLevel, msg, keyvals...)
} }
// Error prints an error message. // Error prints an error message.
func (l *Logger) Error(msg interface{}, keyvals ...interface{}) { func (l *Logger) Error(msg any, keyvals ...any) {
l.Log(ErrorLevel, msg, keyvals...) l.Log(ErrorLevel, msg, keyvals...)
} }
// Fatal prints a fatal message and exits. // Fatal prints a fatal message and exits.
func (l *Logger) Fatal(msg interface{}, keyvals ...interface{}) { func (l *Logger) Fatal(msg any, keyvals ...any) {
l.Log(FatalLevel, msg, keyvals...) l.Log(FatalLevel, msg, keyvals...)
os.Exit(1) os.Exit(1)
} }
// Print prints a message with no level. // Print prints a message with no level.
func (l *Logger) Print(msg interface{}, keyvals ...interface{}) { func (l *Logger) Print(msg any, keyvals ...any) {
l.Log(noLevel, msg, keyvals...) l.Log(noLevel, msg, keyvals...)
} }
// Debugf prints a debug message with formatting. // Debugf prints a debug message with formatting.
func (l *Logger) Debugf(format string, args ...interface{}) { func (l *Logger) Debugf(format string, args ...any) {
l.Log(DebugLevel, fmt.Sprintf(format, args...)) l.Log(DebugLevel, fmt.Sprintf(format, args...))
} }
// Infof prints an info message with formatting. // Infof prints an info message with formatting.
func (l *Logger) Infof(format string, args ...interface{}) { func (l *Logger) Infof(format string, args ...any) {
l.Log(InfoLevel, fmt.Sprintf(format, args...)) l.Log(InfoLevel, fmt.Sprintf(format, args...))
} }
// Warnf prints a warning message with formatting. // Warnf prints a warning message with formatting.
func (l *Logger) Warnf(format string, args ...interface{}) { func (l *Logger) Warnf(format string, args ...any) {
l.Log(WarnLevel, fmt.Sprintf(format, args...)) l.Log(WarnLevel, fmt.Sprintf(format, args...))
} }
// Errorf prints an error message with formatting. // Errorf prints an error message with formatting.
func (l *Logger) Errorf(format string, args ...interface{}) { func (l *Logger) Errorf(format string, args ...any) {
l.Log(ErrorLevel, fmt.Sprintf(format, args...)) l.Log(ErrorLevel, fmt.Sprintf(format, args...))
} }
// Fatalf prints a fatal message with formatting and exits. // Fatalf prints a fatal message with formatting and exits.
func (l *Logger) Fatalf(format string, args ...interface{}) { func (l *Logger) Fatalf(format string, args ...any) {
l.Log(FatalLevel, fmt.Sprintf(format, args...)) l.Log(FatalLevel, fmt.Sprintf(format, args...))
os.Exit(1) os.Exit(1)
} }
// Printf prints a message with no level and formatting. // Printf prints a message with no level and formatting.
func (l *Logger) Printf(format string, args ...interface{}) { func (l *Logger) Printf(format string, args ...any) {
l.Log(noLevel, fmt.Sprintf(format, args...)) l.Log(noLevel, fmt.Sprintf(format, args...))
} }

View File

@ -36,7 +36,7 @@ func (l *Logger) Handle(ctx context.Context, record slog.Record) error {
return nil return nil
} }
fields := make([]interface{}, 0, record.NumAttrs()*2) fields := make([]any, 0, record.NumAttrs()*2)
record.Attrs(func(a slog.Attr) bool { record.Attrs(func(a slog.Attr) bool {
fields = append(fields, a.Key, a.Value) fields = append(fields, a.Key, a.Value)
return true return true
@ -52,7 +52,7 @@ func (l *Logger) Handle(ctx context.Context, record slog.Record) error {
// //
// Implements slog.Handler. // Implements slog.Handler.
func (l *Logger) WithAttrs(attrs []slog.Attr) slog.Handler { func (l *Logger) WithAttrs(attrs []slog.Attr) slog.Handler {
fields := make([]interface{}, 0, len(attrs)*2) fields := make([]any, 0, len(attrs)*2)
for _, attr := range attrs { for _, attr := range attrs {
fields = append(fields, attr.Key, attr.Value) fields = append(fields, attr.Key, attr.Value)
} }

View File

@ -33,7 +33,7 @@ func (l *Logger) Enabled(_ context.Context, level slog.Level) bool {
// //
// Implements slog.Handler. // Implements slog.Handler.
func (l *Logger) Handle(_ context.Context, record slog.Record) error { func (l *Logger) Handle(_ context.Context, record slog.Record) error {
fields := make([]interface{}, 0, record.NumAttrs()*2) fields := make([]any, 0, record.NumAttrs()*2)
record.Attrs(func(a slog.Attr) bool { record.Attrs(func(a slog.Attr) bool {
fields = append(fields, a.Key, a.Value) fields = append(fields, a.Key, a.Value)
return true return true
@ -49,7 +49,7 @@ func (l *Logger) Handle(_ context.Context, record slog.Record) error {
// //
// Implements slog.Handler. // Implements slog.Handler.
func (l *Logger) WithAttrs(attrs []slog.Attr) slog.Handler { func (l *Logger) WithAttrs(attrs []slog.Attr) slog.Handler {
fields := make([]interface{}, 0, len(attrs)*2) fields := make([]any, 0, len(attrs)*2)
for _, attr := range attrs { for _, attr := range attrs {
fields = append(fields, attr.Key, attr.Value) fields = append(fields, attr.Key, attr.Value)
} }

View File

@ -55,7 +55,7 @@ type Options struct {
// CallerOffset is the caller format for the logger. The default is 0. // CallerOffset is the caller format for the logger. The default is 0.
CallerOffset int CallerOffset int
// Fields is the fields for the logger. The default is no fields. // Fields is the fields for the logger. The default is no fields.
Fields []interface{} Fields []any
// Formatter is the formatter for the logger. The default is TextFormatter. // Formatter is the formatter for the logger. The default is TextFormatter.
Formatter Formatter Formatter Formatter
} }

View File

@ -155,7 +155,7 @@ func GetPrefix() string {
} }
// With returns a new logger with the given keyvals. // With returns a new logger with the given keyvals.
func With(keyvals ...interface{}) *Logger { func With(keyvals ...any) *Logger {
return Default().With(keyvals...) return Default().With(keyvals...)
} }
@ -172,74 +172,74 @@ func Helper() {
} }
// Log logs a message with the given level. // Log logs a message with the given level.
func Log(level Level, msg interface{}, keyvals ...interface{}) { func Log(level Level, msg any, keyvals ...any) {
Default().Log(level, msg, keyvals...) Default().Log(level, msg, keyvals...)
} }
// Debug logs a debug message. // Debug logs a debug message.
func Debug(msg interface{}, keyvals ...interface{}) { func Debug(msg any, keyvals ...any) {
Default().Log(DebugLevel, msg, keyvals...) Default().Log(DebugLevel, msg, keyvals...)
} }
// Info logs an info message. // Info logs an info message.
func Info(msg interface{}, keyvals ...interface{}) { func Info(msg any, keyvals ...any) {
Default().Log(InfoLevel, msg, keyvals...) Default().Log(InfoLevel, msg, keyvals...)
} }
// Warn logs a warning message. // Warn logs a warning message.
func Warn(msg interface{}, keyvals ...interface{}) { func Warn(msg any, keyvals ...any) {
Default().Log(WarnLevel, msg, keyvals...) Default().Log(WarnLevel, msg, keyvals...)
} }
// Error logs an error message. // Error logs an error message.
func Error(msg interface{}, keyvals ...interface{}) { func Error(msg any, keyvals ...any) {
Default().Log(ErrorLevel, msg, keyvals...) Default().Log(ErrorLevel, msg, keyvals...)
} }
// Fatal logs a fatal message and exit. // Fatal logs a fatal message and exit.
func Fatal(msg interface{}, keyvals ...interface{}) { func Fatal(msg any, keyvals ...any) {
Default().Log(FatalLevel, msg, keyvals...) Default().Log(FatalLevel, msg, keyvals...)
os.Exit(1) os.Exit(1)
} }
// Print logs a message with no level. // Print logs a message with no level.
func Print(msg interface{}, keyvals ...interface{}) { func Print(msg any, keyvals ...any) {
Default().Log(noLevel, msg, keyvals...) Default().Log(noLevel, msg, keyvals...)
} }
// Logf logs a message with formatting and level. // Logf logs a message with formatting and level.
func Logf(level Level, format string, args ...interface{}) { func Logf(level Level, format string, args ...any) {
Default().Logf(level, format, args...) Default().Logf(level, format, args...)
} }
// Debugf logs a debug message with formatting. // Debugf logs a debug message with formatting.
func Debugf(format string, args ...interface{}) { func Debugf(format string, args ...any) {
Default().Log(DebugLevel, fmt.Sprintf(format, args...)) Default().Log(DebugLevel, fmt.Sprintf(format, args...))
} }
// Infof logs an info message with formatting. // Infof logs an info message with formatting.
func Infof(format string, args ...interface{}) { func Infof(format string, args ...any) {
Default().Log(InfoLevel, fmt.Sprintf(format, args...)) Default().Log(InfoLevel, fmt.Sprintf(format, args...))
} }
// Warnf logs a warning message with formatting. // Warnf logs a warning message with formatting.
func Warnf(format string, args ...interface{}) { func Warnf(format string, args ...any) {
Default().Log(WarnLevel, fmt.Sprintf(format, args...)) Default().Log(WarnLevel, fmt.Sprintf(format, args...))
} }
// Errorf logs an error message with formatting. // Errorf logs an error message with formatting.
func Errorf(format string, args ...interface{}) { func Errorf(format string, args ...any) {
Default().Log(ErrorLevel, fmt.Sprintf(format, args...)) Default().Log(ErrorLevel, fmt.Sprintf(format, args...))
} }
// Fatalf logs a fatal message with formatting and exit. // Fatalf logs a fatal message with formatting and exit.
func Fatalf(format string, args ...interface{}) { func Fatalf(format string, args ...any) {
Default().Log(FatalLevel, fmt.Sprintf(format, args...)) Default().Log(FatalLevel, fmt.Sprintf(format, args...))
os.Exit(1) os.Exit(1)
} }
// Printf logs a message with formatting and no level. // Printf logs a message with formatting and no level.
func Printf(format string, args ...interface{}) { func Printf(format string, args ...any) {
Default().Log(noLevel, fmt.Sprintf(format, args...)) Default().Log(noLevel, fmt.Sprintf(format, args...))
} }

View File

@ -62,7 +62,7 @@ const (
) )
var bufPool = sync.Pool{ var bufPool = sync.Pool{
New: func() interface{} { New: func() any {
return new(strings.Builder) return new(strings.Builder)
}, },
} }
@ -164,7 +164,7 @@ func writeSpace(w io.Writer, first bool) {
} }
} }
func (l *Logger) textFormatter(keyvals ...interface{}) { func (l *Logger) textFormatter(keyvals ...any) {
st := l.styles st := l.styles
lenKeyvals := len(keyvals) lenKeyvals := len(keyvals)

View File

@ -261,7 +261,7 @@ func CHA(col int) string {
// //
// See: https://vt100.net/docs/vt510-rm/CUP.html // See: https://vt100.net/docs/vt510-rm/CUP.html
func CursorPosition(col, row int) string { func CursorPosition(col, row int) string {
if row <= 0 && col <= 0 { if row <= 1 && col <= 1 {
return CursorHomePosition return CursorHomePosition
} }
@ -281,7 +281,9 @@ func CUP(col, row int) string {
} }
// CursorHomePosition is a sequence for moving the cursor to the upper left // CursorHomePosition is a sequence for moving the cursor to the upper left
// corner of the scrolling region. This is equivalent to `CursorPosition(1, 1)`. // corner of the scrolling region.
//
// This is equivalent to [CursorPosition](1, 1).
const CursorHomePosition = "\x1b[H" const CursorHomePosition = "\x1b[H"
// SetCursorPosition (CUP) returns a sequence for setting the cursor to the // SetCursorPosition (CUP) returns a sequence for setting the cursor to the

View File

@ -1,5 +1,29 @@
package ansi package ansi
import (
"os"
"strconv"
"github.com/clipperhouse/displaywidth"
"github.com/mattn/go-runewidth"
)
var wcOptions = &runewidth.Condition{
EastAsianWidth: false,
StrictEmojiNeutral: true,
}
var dwOptions = &displaywidth.Options{
EastAsianWidth: false,
}
func init() {
if ea, err := strconv.ParseBool(os.Getenv("RUNEWIDTH_EASTASIAN")); err == nil && ea {
wcOptions.EastAsianWidth = true
dwOptions.EastAsianWidth = true
}
}
// Method is a type that represents the how the renderer should calculate the // Method is a type that represents the how the renderer should calculate the
// display width of cells. // display width of cells.
type Method uint8 type Method uint8

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)
}

View File

@ -4,8 +4,7 @@ import (
"unicode/utf8" "unicode/utf8"
"github.com/charmbracelet/x/ansi/parser" "github.com/charmbracelet/x/ansi/parser"
"github.com/mattn/go-runewidth" "github.com/clipperhouse/uax29/v2/graphemes"
"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 +175,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 +430,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), wcOptions.StringWidth(cluster)
}
return T(cluster), dwOptions.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), wcOptions.StringWidth(string(cluster))
}
return T(cluster), dwOptions.Bytes(cluster)
} }
panic("unreachable") panic("unreachable")
} }
@ -490,7 +491,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 +521,5 @@ func Parameter(p int, hasMore bool) (s int) {
if hasMore { if hasMore {
s |= parser.HasMoreFlag s |= parser.HasMoreFlag
} }
return return s
} }

View File

@ -10,7 +10,7 @@ var parserPool = sync.Pool{
New: func() any { New: func() any {
p := NewParser() p := NewParser()
p.SetParamsSize(parser.MaxParamsSize) p.SetParamsSize(parser.MaxParamsSize)
p.SetDataSize(1024 * 1024 * 4) // 4MB of data buffer p.SetDataSize(1024 * 4) // 4KB of data buffer
return p return p
}, },
} }

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,309 +57,446 @@ 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.
func (s Style) UnderlineStyle(u UnderlineStyle) Style { // Supports various underline styles including single, double, curly, dotted,
// and dashed.
func (s Style) UnderlineStyle(u Underline) Style {
switch u { switch u {
case NoUnderlineStyle: case UnderlineNone:
return s.NoUnderline() return s.Underline(false)
case SingleUnderlineStyle: case UnderlineSingle:
return s.Underline() return s.Underline(true)
case DoubleUnderlineStyle: case UnderlineDouble:
return append(s, doubleUnderlineStyle) return append(s, underlineDouble)
case CurlyUnderlineStyle: case UnderlineCurly:
return append(s, curlyUnderlineStyle) return append(s, underlineCurly)
case DottedUnderlineStyle: case UnderlineDotted:
return append(s, dottedUnderlineStyle) return append(s, underlineDotted)
case DashedUnderlineStyle: case UnderlineDashed:
return append(s, dashedUnderlineStyle) return append(s, underlineDashed)
} }
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))
} }
// Underline represents an ANSI SGR (Select Graphic Rendition) underline style.
type Underline = byte
// UnderlineStyle represents an ANSI SGR (Select Graphic Rendition) underline // UnderlineStyle represents an ANSI SGR (Select Graphic Rendition) underline
// style. // style.
//
// Deprecated: use [Underline] instead.
type UnderlineStyle = byte type UnderlineStyle = byte
const ( const (
doubleUnderlineStyle = "4:2" underlineDouble = "4:2"
curlyUnderlineStyle = "4:3" underlineCurly = "4:3"
dottedUnderlineStyle = "4:4" underlineDotted = "4:4"
dashedUnderlineStyle = "4:5" underlineDashed = "4:5"
) )
// Underline styles constants.
const ( const (
// NoUnderlineStyle is the default underline style. UnderlineNone Underline = iota
NoUnderlineStyle UnderlineStyle = iota UnderlineSingle
// SingleUnderlineStyle is a single underline style. UnderlineDouble
UnderlineCurly
UnderlineDotted
UnderlineDashed
)
// Underline styles constants.
//
// Deprecated: use [UnderlineNone], [UnderlineSingle], etc. instead.
const (
NoUnderlineStyle Underline = iota
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
) )
// Underline styles constants.
//
// Deprecated: use [UnderlineNone], [UnderlineSingle], etc. instead.
const (
UnderlineStyleNone Underline = iota
UnderlineStyleSingle
UnderlineStyleDouble
UnderlineStyleCurly
UnderlineStyleDotted
UnderlineStyleDashed
)
// 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
@ -364,42 +504,44 @@ const (
// 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
func foregroundColorString(c Color) string { func foregroundColorString(c Color) string {
switch c := c.(type) { switch c := c.(type) {
case nil:
return attrDefaultForegroundColor
case BasicColor: case BasicColor:
// 3-bit or 4-bit ANSI foreground // 3-bit or 4-bit ANSI foreground
// "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 +556,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
@ -422,42 +564,44 @@ func foregroundColorString(c Color) string {
// 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
func backgroundColorString(c Color) string { func backgroundColorString(c Color) string {
switch c := c.(type) { switch c := c.(type) {
case nil:
return attrDefaultBackgroundColor
case BasicColor: case BasicColor:
// 3-bit or 4-bit ANSI foreground // 3-bit or 4-bit ANSI foreground
// "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 +616,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
@ -480,6 +624,8 @@ func backgroundColorString(c Color) string {
// 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
func underlineColorString(c Color) string { func underlineColorString(c Color) string {
switch c := c.(type) { switch c := c.(type) {
case nil:
return attrDefaultUnderlineColor
// NOTE: we can't use 3-bit and 4-bit ANSI color codes with underline // NOTE: we can't use 3-bit and 4-bit ANSI color codes with underline
// color, use 256-color instead. // color, use 256-color instead.
// //
@ -498,7 +644,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 +672,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 +681,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 +740,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 +758,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 +776,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 +811,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

@ -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{}

92
vendor/github.com/charmbracelet/x/cellbuf/pen.go generated vendored Normal file
View File

@ -0,0 +1,92 @@
package cellbuf
import (
"io"
"github.com/charmbracelet/x/ansi"
)
// PenWriter is a writer that writes to a buffer and keeps track of the current
// pen style and link state for the purpose of wrapping with newlines.
type PenWriter struct {
w io.Writer
p *ansi.Parser
style Style
link Link
}
// NewPenWriter returns a new PenWriter.
func NewPenWriter(w io.Writer) *PenWriter {
pw := &PenWriter{w: w}
pw.p = ansi.GetParser()
handleCsi := func(cmd ansi.Cmd, params ansi.Params) {
if cmd == 'm' {
ReadStyle(params, &pw.style)
}
}
handleOsc := func(cmd int, data []byte) {
if cmd == 8 {
ReadLink(data, &pw.link)
}
}
pw.p.SetHandler(ansi.Handler{
HandleCsi: handleCsi,
HandleOsc: handleOsc,
})
return pw
}
// Style returns the current pen style.
func (w *PenWriter) Style() Style {
return w.style
}
// Link returns the current pen link.
func (w *PenWriter) Link() Link {
return w.link
}
// Write writes to the buffer.
func (w *PenWriter) Write(p []byte) (int, error) {
for i := range p {
b := p[i]
w.p.Advance(b)
if b == '\n' {
if !w.style.Empty() {
_, _ = w.w.Write([]byte(ansi.ResetStyle))
}
if !w.link.Empty() {
_, _ = w.w.Write([]byte(ansi.ResetHyperlink()))
}
}
_, _ = w.w.Write([]byte{b})
if b == '\n' {
if !w.link.Empty() {
_, _ = w.w.Write([]byte(ansi.SetHyperlink(w.link.URL, w.link.Params)))
}
if !w.style.Empty() {
_, _ = w.w.Write([]byte(w.style.Sequence()))
}
}
}
return len(p), nil
}
// Close closes the writer, resets the style and link if necessary, and releases
// its parser. Calling it is performance critical, but forgetting it does not
// cause safety issues or leaks.
func (w *PenWriter) Close() error {
if !w.style.Empty() {
_, _ = w.w.Write([]byte(ansi.ResetStyle))
}
if !w.link.Empty() {
_, _ = w.w.Write([]byte(ansi.ResetHyperlink()))
}
if w.p != nil {
ansi.PutParser(w.p)
w.p = nil
}
return nil
}

View File

@ -39,9 +39,9 @@ func relativeCursorMove(s *Screen, fx, fy, tx, ty int, overwrite, useTabs, useBa
var seq strings.Builder var seq strings.Builder
width, height := s.newbuf.Width(), s.newbuf.Height() width, height := s.newbuf.Width(), s.newbuf.Height()
if ty != fy { if ty != fy { //nolint:nestif
var yseq string var yseq string
if s.xtermLike && !s.opts.RelativeCursor { if s.caps.Contains(capVPA) && !s.opts.RelativeCursor {
yseq = ansi.VerticalPositionAbsolute(ty + 1) yseq = ansi.VerticalPositionAbsolute(ty + 1)
} }
@ -54,9 +54,13 @@ func relativeCursorMove(s *Screen, fx, fy, tx, ty int, overwrite, useTabs, useBa
} }
shouldScroll := !s.opts.AltScreen && fy+n >= s.scrollHeight shouldScroll := !s.opts.AltScreen && fy+n >= s.scrollHeight
if lf := strings.Repeat("\n", n); shouldScroll || (fy+n < height && len(lf) < len(yseq)) { if lf := strings.Repeat("\n", n); shouldScroll || (fy+n < height && len(lf) < len(yseq)) {
//nolint:godox
// TODO: Ensure we're not unintentionally scrolling the screen down. // TODO: Ensure we're not unintentionally scrolling the screen down.
yseq = lf yseq = lf
s.scrollHeight = max(s.scrollHeight, fy+n) s.scrollHeight = max(s.scrollHeight, fy+n)
if s.opts.MapNL {
fx = 0
}
} }
} else if ty < fy { } else if ty < fy {
n := fy - ty n := fy - ty
@ -64,6 +68,7 @@ func relativeCursorMove(s *Screen, fx, fy, tx, ty int, overwrite, useTabs, useBa
yseq = cuu yseq = cuu
} }
if n == 1 && fy-1 > 0 { if n == 1 && fy-1 > 0 {
//nolint:godox
// TODO: Ensure we're not unintentionally scrolling the screen up. // TODO: Ensure we're not unintentionally scrolling the screen up.
yseq = ansi.ReverseIndex yseq = ansi.ReverseIndex
} }
@ -72,9 +77,9 @@ func relativeCursorMove(s *Screen, fx, fy, tx, ty int, overwrite, useTabs, useBa
seq.WriteString(yseq) seq.WriteString(yseq)
} }
if tx != fx { if tx != fx { //nolint:nestif
var xseq string var xseq string
if s.xtermLike && !s.opts.RelativeCursor { if s.caps.Contains(capHPA) && !s.opts.RelativeCursor {
xseq = ansi.HorizontalPositionAbsolute(tx + 1) xseq = ansi.HorizontalPositionAbsolute(tx + 1)
} }
@ -93,7 +98,8 @@ func relativeCursorMove(s *Screen, fx, fy, tx, ty int, overwrite, useTabs, useBa
if tabs > 0 { if tabs > 0 {
cht := ansi.CursorHorizontalForwardTab(tabs) cht := ansi.CursorHorizontalForwardTab(tabs)
tab := strings.Repeat("\t", tabs) tab := strings.Repeat("\t", tabs)
if false && s.xtermLike && len(cht) < len(tab) { if false && s.caps.Contains(capCHT) && len(cht) < len(tab) {
//nolint:godox
// TODO: The linux console and some terminals such as // TODO: The linux console and some terminals such as
// Alacritty don't support [ansi.CHT]. Enable this when // Alacritty don't support [ansi.CHT]. Enable this when
// we have a way to detect this, or after 5 years when // we have a way to detect this, or after 5 years when
@ -144,7 +150,7 @@ func relativeCursorMove(s *Screen, fx, fy, tx, ty int, overwrite, useTabs, useBa
} }
} else if tx < fx { } else if tx < fx {
n := fx - tx n := fx - tx
if useTabs && s.xtermLike { if useTabs && s.caps.Contains(capCBT) {
// VT100 does not support backward tabs [ansi.CBT]. // VT100 does not support backward tabs [ansi.CBT].
col := fx col := fx
@ -190,7 +196,7 @@ func moveCursor(s *Screen, x, y int, overwrite bool) (seq string) {
// Method #0: Use [ansi.CUP] if the distance is long. // Method #0: Use [ansi.CUP] if the distance is long.
seq = ansi.CursorPosition(x+1, y+1) seq = ansi.CursorPosition(x+1, y+1)
if fx == -1 || fy == -1 || notLocal(s.newbuf.Width(), fx, fy, x, y) { if fx == -1 || fy == -1 || notLocal(s.newbuf.Width(), fx, fy, x, y) {
return return seq
} }
} }
@ -234,7 +240,7 @@ func moveCursor(s *Screen, x, y int, overwrite bool) (seq string) {
} }
} }
return return seq
} }
// moveCursor moves the cursor to the specified position. // moveCursor moves the cursor to the specified position.
@ -242,10 +248,10 @@ func (s *Screen) moveCursor(x, y int, overwrite bool) {
if !s.opts.AltScreen && s.cur.X == -1 && s.cur.Y == -1 { if !s.opts.AltScreen && s.cur.X == -1 && s.cur.Y == -1 {
// First cursor movement in inline mode, move the cursor to the first // First cursor movement in inline mode, move the cursor to the first
// column before moving to the target position. // column before moving to the target position.
s.buf.WriteByte('\r') //nolint:errcheck s.buf.WriteByte('\r')
s.cur.X, s.cur.Y = 0, 0 s.cur.X, s.cur.Y = 0, 0
} }
s.buf.WriteString(moveCursor(s, x, y, overwrite)) //nolint:errcheck s.buf.WriteString(moveCursor(s, x, y, overwrite))
s.cur.X, s.cur.Y = x, y s.cur.X, s.cur.Y = x, y
} }
@ -274,10 +280,11 @@ func (s *Screen) move(x, y int) {
// Reset wrap around (phantom cursor) state // Reset wrap around (phantom cursor) state
if s.atPhantom { if s.atPhantom {
s.cur.X = 0 s.cur.X = 0
s.buf.WriteByte('\r') //nolint:errcheck s.buf.WriteByte('\r')
s.atPhantom = false // reset phantom cell state s.atPhantom = false // reset phantom cell state
} }
//nolint:godox
// TODO: Investigate if we need to handle this case and/or if we need the // TODO: Investigate if we need to handle this case and/or if we need the
// following code. // following code.
// //
@ -291,7 +298,7 @@ func (s *Screen) move(x, y int) {
// //
// if l > 0 { // if l > 0 {
// s.cur.X = 0 // s.cur.X = 0
// s.buf.WriteString("\r" + strings.Repeat("\n", l)) //nolint:errcheck // s.buf.WriteString("\r" + strings.Repeat("\n", l))
// } // }
// } // }
@ -339,6 +346,10 @@ type ScreenOptions struct {
HardTabs bool HardTabs bool
// Backspace is whether to use backspace characters to move the cursor. // Backspace is whether to use backspace characters to move the cursor.
Backspace bool Backspace bool
// MapNL whether we have ONLCR mapping enabled. When we set the terminal to
// raw mode, the ONLCR mode gets disabled. ONLCR maps any newline/linefeed
// (`\n`) character to carriage return + line feed (`\r\n`).
MapNL bool
} }
// lineData represents the metadata for a line. // lineData represents the metadata for a line.
@ -365,13 +376,13 @@ type Screen struct {
opts ScreenOptions opts ScreenOptions
mu sync.Mutex mu sync.Mutex
method ansi.Method method ansi.Method
scrollHeight int // keeps track of how many lines we've scrolled down (inline mode) scrollHeight int // keeps track of how many lines we've scrolled down (inline mode)
altScreenMode bool // whether alternate screen mode is enabled altScreenMode bool // whether alternate screen mode is enabled
cursorHidden bool // whether text cursor mode is enabled cursorHidden bool // whether text cursor mode is enabled
clear bool // whether to force clear the screen clear bool // whether to force clear the screen
xtermLike bool // whether to use xterm-like optimizations, otherwise, it uses vt100 only caps capabilities // terminal control sequence capabilities
queuedText bool // whether we have queued non-zero width text queued up queuedText bool // whether we have queued non-zero width text queued up
atPhantom bool // whether the cursor is out of bounds and at a phantom cell atPhantom bool // whether the cursor is out of bounds and at a phantom cell
} }
// SetMethod sets the method used to calculate the width of cells. // SetMethod sets the method used to calculate the width of cells.
@ -491,36 +502,77 @@ func (s *Screen) FillRect(cell *Cell, r Rectangle) bool {
return true return true
} }
// isXtermLike returns whether the terminal is xterm-like. This means that the // capabilities represents a mask of supported ANSI escape sequences.
type capabilities uint
const (
// Vertical Position Absolute [ansi.VPA].
capVPA capabilities = 1 << iota
// Horizontal Position Absolute [ansi.HPA].
capHPA
// Cursor Horizontal Tab [ansi.CHT].
capCHT
// Cursor Backward Tab [ansi.CBT].
capCBT
// Repeat Previous Character [ansi.REP].
capREP
// Erase Character [ansi.ECH].
capECH
// Insert Character [ansi.ICH].
capICH
// Scroll Down [ansi.SD].
capSD
// Scroll Up [ansi.SU].
capSU
noCaps capabilities = 0
allCaps = capVPA | capHPA | capCHT | capCBT | capREP | capECH | capICH |
capSD | capSU
)
// Contains returns whether the capabilities contains the given capability.
func (v capabilities) Contains(c capabilities) bool {
return v&c == c
}
// xtermCaps returns whether the terminal is xterm-like. This means that the
// terminal supports ECMA-48 and ANSI X3.64 escape sequences. // terminal supports ECMA-48 and ANSI X3.64 escape sequences.
// TODO: Should this be a lookup table into each $TERM terminfo database? Like // xtermCaps returns a list of control sequence capabilities for the given
// we could keep a map of ANSI escape sequence to terminfo capability name and // terminal type. This only supports a subset of sequences that can
// check if the database supports the escape sequence. Instead of keeping a // be different among terminals.
// list of terminal names here. // NOTE: A hybrid approach would be to support Terminfo databases for a full
func isXtermLike(termtype string) (v bool) { // set of capabilities.
func xtermCaps(termtype string) (v capabilities) {
parts := strings.Split(termtype, "-") parts := strings.Split(termtype, "-")
if len(parts) == 0 { if len(parts) == 0 {
return return v
} }
switch parts[0] { switch parts[0] {
case case
"alacritty",
"contour", "contour",
"foot", "foot",
"ghostty", "ghostty",
"kitty", "kitty",
"linux",
"rio", "rio",
"screen",
"st", "st",
"tmux", "tmux",
"wezterm", "wezterm",
"xterm": "xterm":
v = true v = allCaps
case "alacritty":
v = allCaps
v &^= capCHT // NOTE: alacritty added support for [ansi.CHT] in 2024-12-28 #62d5b13.
case "screen":
// See https://www.gnu.org/software/screen/manual/screen.html#Control-Sequences-1
v = allCaps
v &^= capREP
case "linux":
// See https://man7.org/linux/man-pages/man4/console_codes.4.html
v = capVPA | capHPA | capECH | capICH
} }
return return v
} }
// NewScreen creates a new Screen. // NewScreen creates a new Screen.
@ -548,14 +600,14 @@ func NewScreen(w io.Writer, width, height int, opts *ScreenOptions) (s *Screen)
} }
s.buf = new(bytes.Buffer) s.buf = new(bytes.Buffer)
s.xtermLike = isXtermLike(s.opts.Term) s.caps = xtermCaps(s.opts.Term)
s.curbuf = NewBuffer(width, height) s.curbuf = NewBuffer(width, height)
s.newbuf = NewBuffer(width, height) s.newbuf = NewBuffer(width, height)
s.cur = Cursor{Position: Pos(-1, -1)} // start at -1 to force a move s.cur = Cursor{Position: Pos(-1, -1)} // start at -1 to force a move
s.saved = s.cur s.saved = s.cur
s.reset() s.reset()
return return s
} }
// Width returns the width of the screen. // Width returns the width of the screen.
@ -595,7 +647,7 @@ func (s *Screen) putCell(cell *Cell) {
// wrapCursor wraps the cursor to the next line. // wrapCursor wraps the cursor to the next line.
// //
//nolint:unused
func (s *Screen) wrapCursor() { func (s *Screen) wrapCursor() {
const autoRightMargin = true const autoRightMargin = true
if autoRightMargin { if autoRightMargin {
@ -628,9 +680,9 @@ func (s *Screen) putAttrCell(cell *Cell) {
} }
s.updatePen(cell) s.updatePen(cell)
s.buf.WriteRune(cell.Rune) //nolint:errcheck s.buf.WriteRune(cell.Rune)
for _, c := range cell.Comb { for _, c := range cell.Comb {
s.buf.WriteRune(c) //nolint:errcheck s.buf.WriteRune(c)
} }
s.cur.X += cell.Width s.cur.X += cell.Width
@ -649,12 +701,12 @@ func (s *Screen) putCellLR(cell *Cell) {
// Optimize for the lower right corner cell. // Optimize for the lower right corner cell.
curX := s.cur.X curX := s.cur.X
if cell == nil || !cell.Empty() { if cell == nil || !cell.Empty() {
s.buf.WriteString(ansi.ResetAutoWrapMode) //nolint:errcheck s.buf.WriteString(ansi.ResetModeAutoWrap)
s.putAttrCell(cell) s.putAttrCell(cell)
// Writing to lower-right corner cell should not wrap. // Writing to lower-right corner cell should not wrap.
s.atPhantom = false s.atPhantom = false
s.cur.X = curX s.cur.X = curX
s.buf.WriteString(ansi.SetAutoWrapMode) //nolint:errcheck s.buf.WriteString(ansi.SetModeAutoWrap)
} }
} }
@ -675,11 +727,11 @@ func (s *Screen) updatePen(cell *Cell) {
if cell.Style.Empty() && len(seq) > len(ansi.ResetStyle) { if cell.Style.Empty() && len(seq) > len(ansi.ResetStyle) {
seq = ansi.ResetStyle seq = ansi.ResetStyle
} }
s.buf.WriteString(seq) //nolint:errcheck s.buf.WriteString(seq)
s.cur.Style = cell.Style s.cur.Style = cell.Style
} }
if !cell.Link.Equal(&s.cur.Link) { if !cell.Link.Equal(&s.cur.Link) {
s.buf.WriteString(ansi.SetHyperlink(cell.Link.URL, cell.Link.Params)) //nolint:errcheck s.buf.WriteString(ansi.SetHyperlink(cell.Link.URL, cell.Link.Params))
s.cur.Link = cell.Link s.cur.Link = cell.Link
} }
} }
@ -712,9 +764,9 @@ func (s *Screen) emitRange(line Line, n int) (eoi bool) {
ech := ansi.EraseCharacter(count) ech := ansi.EraseCharacter(count)
cup := ansi.CursorPosition(s.cur.X+count, s.cur.Y) cup := ansi.CursorPosition(s.cur.X+count, s.cur.Y)
rep := ansi.RepeatPreviousCharacter(count) rep := ansi.RepeatPreviousCharacter(count)
if s.xtermLike && count > len(ech)+len(cup) && cell0 != nil && cell0.Clear() { if s.caps.Contains(capECH) && count > len(ech)+len(cup) && cell0 != nil && cell0.Clear() { //nolint:nestif
s.updatePen(cell0) s.updatePen(cell0)
s.buf.WriteString(ech) //nolint:errcheck s.buf.WriteString(ech)
// If this is the last cell, we don't need to move the cursor. // If this is the last cell, we don't need to move the cursor.
if count < n { if count < n {
@ -722,7 +774,7 @@ func (s *Screen) emitRange(line Line, n int) (eoi bool) {
} else { } else {
return true // cursor in the middle return true // cursor in the middle
} }
} else if s.xtermLike && count > len(rep) && } else if s.caps.Contains(capREP) && count > len(rep) &&
(cell0 == nil || (len(cell0.Comb) == 0 && cell0.Rune < 256)) { (cell0 == nil || (len(cell0.Comb) == 0 && cell0.Rune < 256)) {
// We only support ASCII characters. Most terminals will handle // We only support ASCII characters. Most terminals will handle
// non-ASCII characters correctly, but some might not, ahem xterm. // non-ASCII characters correctly, but some might not, ahem xterm.
@ -740,13 +792,13 @@ func (s *Screen) emitRange(line Line, n int) (eoi bool) {
s.putCell(cell0) s.putCell(cell0)
repCount-- // cell0 is a single width cell ASCII character repCount-- // cell0 is a single width cell ASCII character
s.buf.WriteString(ansi.RepeatPreviousCharacter(repCount)) //nolint:errcheck s.buf.WriteString(ansi.RepeatPreviousCharacter(repCount))
s.cur.X += repCount s.cur.X += repCount
if wrapPossible { if wrapPossible {
s.putCell(cell0) s.putCell(cell0)
} }
} else { } else {
for i := 0; i < count; i++ { for i := range count {
s.putCell(line.At(i)) s.putCell(line.At(i))
} }
} }
@ -755,7 +807,7 @@ func (s *Screen) emitRange(line Line, n int) (eoi bool) {
n -= count n -= count
} }
return return eoi
} }
// putRange puts a range of cells from the old line to the new line. // putRange puts a range of cells from the old line to the new line.
@ -765,7 +817,7 @@ func (s *Screen) putRange(oldLine, newLine Line, y, start, end int) (eoi bool) {
inline := min(len(ansi.CursorPosition(start+1, y+1)), inline := min(len(ansi.CursorPosition(start+1, y+1)),
min(len(ansi.HorizontalPositionAbsolute(start+1)), min(len(ansi.HorizontalPositionAbsolute(start+1)),
len(ansi.CursorForward(start+1)))) len(ansi.CursorForward(start+1))))
if (end - start + 1) > inline { if (end - start + 1) > inline { //nolint:nestif
var j, same int var j, same int
for j, same = start, 0; j <= end; j++ { for j, same = start, 0; j <= end; j++ {
oldCell, newCell := oldLine.At(j), newLine.At(j) oldCell, newCell := oldLine.At(j), newLine.At(j)
@ -817,9 +869,9 @@ func (s *Screen) clearToEnd(blank *Cell, force bool) { //nolint:unparam
s.updatePen(blank) s.updatePen(blank)
count := s.newbuf.Width() - s.cur.X count := s.newbuf.Width() - s.cur.X
if s.el0Cost() <= count { if s.el0Cost() <= count {
s.buf.WriteString(ansi.EraseLineRight) //nolint:errcheck s.buf.WriteString(ansi.EraseLineRight)
} else { } else {
for i := 0; i < count; i++ { for range count {
s.putCell(blank) s.putCell(blank)
} }
} }
@ -839,12 +891,13 @@ func (s *Screen) clearBlank() *Cell {
// insertCells inserts the count cells pointed by the given line at the current // insertCells inserts the count cells pointed by the given line at the current
// cursor position. // cursor position.
func (s *Screen) insertCells(line Line, count int) { func (s *Screen) insertCells(line Line, count int) {
if s.xtermLike { supportsICH := s.caps.Contains(capICH)
if supportsICH {
// Use [ansi.ICH] as an optimization. // Use [ansi.ICH] as an optimization.
s.buf.WriteString(ansi.InsertCharacter(count)) //nolint:errcheck s.buf.WriteString(ansi.InsertCharacter(count))
} else { } else {
// Otherwise, use [ansi.IRM] mode. // Otherwise, use [ansi.IRM] mode.
s.buf.WriteString(ansi.SetInsertReplaceMode) //nolint:errcheck s.buf.WriteString(ansi.SetModeInsertReplace)
} }
for i := 0; count > 0; i++ { for i := 0; count > 0; i++ {
@ -852,8 +905,8 @@ func (s *Screen) insertCells(line Line, count int) {
count-- count--
} }
if !s.xtermLike { if !supportsICH {
s.buf.WriteString(ansi.ResetInsertReplaceMode) //nolint:errcheck s.buf.WriteString(ansi.ResetModeInsertReplace)
} }
} }
@ -862,7 +915,7 @@ func (s *Screen) insertCells(line Line, count int) {
// [ansi.EL] 0 i.e. [ansi.EraseLineRight] to clear // [ansi.EL] 0 i.e. [ansi.EraseLineRight] to clear
// trailing spaces. // trailing spaces.
func (s *Screen) el0Cost() int { func (s *Screen) el0Cost() int {
if s.xtermLike { if s.caps != noCaps {
return 0 return 0
} }
return len(ansi.EraseLineRight) return len(ansi.EraseLineRight)
@ -878,7 +931,7 @@ func (s *Screen) transformLine(y int) {
// Find the first changed cell in the line // Find the first changed cell in the line
var lineChanged bool var lineChanged bool
for i := 0; i < s.newbuf.Width(); i++ { for i := range s.newbuf.Width() {
if !cellEqual(newLine.At(i), oldLine.At(i)) { if !cellEqual(newLine.At(i), oldLine.At(i)) {
lineChanged = true lineChanged = true
break break
@ -886,7 +939,7 @@ func (s *Screen) transformLine(y int) {
} }
const ceolStandoutGlitch = false const ceolStandoutGlitch = false
if ceolStandoutGlitch && lineChanged { if ceolStandoutGlitch && lineChanged { //nolint:nestif
s.move(0, y) s.move(0, y)
s.clearToEnd(nil, false) s.clearToEnd(nil, false)
s.putRange(oldLine, newLine, y, 0, s.newbuf.Width()-1) s.putRange(oldLine, newLine, y, 0, s.newbuf.Width()-1)
@ -897,12 +950,12 @@ func (s *Screen) transformLine(y int) {
// [ansi.EraseLineLeft]. // [ansi.EraseLineLeft].
if blank == nil || blank.Clear() { if blank == nil || blank.Clear() {
var oFirstCell, nFirstCell int var oFirstCell, nFirstCell int
for oFirstCell = 0; oFirstCell < s.curbuf.Width(); oFirstCell++ { for oFirstCell = range s.curbuf.Width() {
if !cellEqual(oldLine.At(oFirstCell), blank) { if !cellEqual(oldLine.At(oFirstCell), blank) {
break break
} }
} }
for nFirstCell = 0; nFirstCell < s.newbuf.Width(); nFirstCell++ { for nFirstCell = range s.newbuf.Width() {
if !cellEqual(newLine.At(nFirstCell), blank) { if !cellEqual(newLine.At(nFirstCell), blank) {
break break
} }
@ -925,11 +978,11 @@ func (s *Screen) transformLine(y int) {
if nFirstCell >= s.newbuf.Width() { if nFirstCell >= s.newbuf.Width() {
s.move(0, y) s.move(0, y)
s.updatePen(blank) s.updatePen(blank)
s.buf.WriteString(ansi.EraseLineRight) //nolint:errcheck s.buf.WriteString(ansi.EraseLineRight)
} else { } else {
s.move(nFirstCell-1, y) s.move(nFirstCell-1, y)
s.updatePen(blank) s.updatePen(blank)
s.buf.WriteString(ansi.EraseLineLeft) //nolint:errcheck s.buf.WriteString(ansi.EraseLineLeft)
} }
for firstCell < nFirstCell { for firstCell < nFirstCell {
@ -1045,7 +1098,7 @@ func (s *Screen) transformLine(y int) {
s.move(n+1, y) s.move(n+1, y)
ichCost := 3 + nLastCell - oLastCell ichCost := 3 + nLastCell - oLastCell
if s.xtermLike && (nLastCell < nLastNonBlank || ichCost > (m-n)) { if s.caps.Contains(capICH) && (nLastCell < nLastNonBlank || ichCost > (m-n)) {
s.putRange(oldLine, newLine, y, n+1, m) s.putRange(oldLine, newLine, y, n+1, m)
} else { } else {
s.insertCells(newLine[n+1:], nLastCell-oLastCell) s.insertCells(newLine[n+1:], nLastCell-oLastCell)
@ -1079,7 +1132,7 @@ func (s *Screen) transformLine(y int) {
func (s *Screen) deleteCells(count int) { func (s *Screen) deleteCells(count int) {
// [ansi.DCH] will shift in cells from the right margin so we need to // [ansi.DCH] will shift in cells from the right margin so we need to
// ensure that they are the right style. // ensure that they are the right style.
s.buf.WriteString(ansi.DeleteCharacter(count)) //nolint:errcheck s.buf.WriteString(ansi.DeleteCharacter(count))
} }
// clearToBottom clears the screen from the current cursor position to the end // clearToBottom clears the screen from the current cursor position to the end
@ -1091,7 +1144,7 @@ func (s *Screen) clearToBottom(blank *Cell) {
} }
s.updatePen(blank) s.updatePen(blank)
s.buf.WriteString(ansi.EraseScreenBelow) //nolint:errcheck s.buf.WriteString(ansi.EraseScreenBelow)
// Clear the rest of the current line // Clear the rest of the current line
s.curbuf.ClearRect(Rect(col, row, s.curbuf.Width()-col, 1)) s.curbuf.ClearRect(Rect(col, row, s.curbuf.Width()-col, 1))
// Clear everything below the current line // Clear everything below the current line
@ -1104,7 +1157,7 @@ func (s *Screen) clearToBottom(blank *Cell) {
// It returns the top line. // It returns the top line.
func (s *Screen) clearBottom(total int) (top int) { func (s *Screen) clearBottom(total int) (top int) {
if total <= 0 { if total <= 0 {
return return top
} }
top = total top = total
@ -1112,7 +1165,7 @@ func (s *Screen) clearBottom(total int) (top int) {
blank := s.clearBlank() blank := s.clearBlank()
canClearWithBlank := blank == nil || blank.Clear() canClearWithBlank := blank == nil || blank.Clear()
if canClearWithBlank { if canClearWithBlank { //nolint:nestif
var row int var row int
for row = total - 1; row >= 0; row-- { for row = total - 1; row >= 0; row-- {
oldLine := s.curbuf.Line(row) oldLine := s.curbuf.Line(row)
@ -1147,14 +1200,14 @@ func (s *Screen) clearBottom(total int) (top int) {
} }
} }
return return top
} }
// clearScreen clears the screen and put cursor at home. // clearScreen clears the screen and put cursor at home.
func (s *Screen) clearScreen(blank *Cell) { func (s *Screen) clearScreen(blank *Cell) {
s.updatePen(blank) s.updatePen(blank)
s.buf.WriteString(ansi.CursorHomePosition) //nolint:errcheck s.buf.WriteString(ansi.CursorHomePosition)
s.buf.WriteString(ansi.EraseEntireScreen) //nolint:errcheck s.buf.WriteString(ansi.EraseEntireScreen)
s.cur.X, s.cur.Y = 0, 0 s.cur.X, s.cur.Y = 0, 0
s.curbuf.Fill(blank) s.curbuf.Fill(blank)
} }
@ -1179,7 +1232,7 @@ func (s *Screen) clearUpdate() {
s.clearBelow(blank, 0) s.clearBelow(blank, 0)
} }
nonEmpty = s.clearBottom(nonEmpty) nonEmpty = s.clearBottom(nonEmpty)
for i := 0; i < nonEmpty; i++ { for i := range nonEmpty {
s.transformLine(i) s.transformLine(i)
} }
} }
@ -1194,13 +1247,13 @@ func (s *Screen) Flush() (err error) {
func (s *Screen) flush() (err error) { func (s *Screen) flush() (err error) {
// Write the buffer // Write the buffer
if s.buf.Len() > 0 { if s.buf.Len() > 0 {
_, err = s.w.Write(s.buf.Bytes()) //nolint:errcheck _, err = s.w.Write(s.buf.Bytes())
if err == nil { if err == nil {
s.buf.Reset() s.buf.Reset()
} }
} }
return return err //nolint:wrapcheck
} }
// Render renders changes of the screen to the internal buffer. Call // Render renders changes of the screen to the internal buffer. Call
@ -1221,6 +1274,7 @@ func (s *Screen) render() {
return return
} }
//nolint:godox
// TODO: Investigate whether this is necessary. Theoretically, terminals // TODO: Investigate whether this is necessary. Theoretically, terminals
// can add/remove tab stops and we should be able to handle that. We could // can add/remove tab stops and we should be able to handle that. We could
// use [ansi.DECTABSR] to read the tab stops, but that's not implemented in // use [ansi.DECTABSR] to read the tab stops, but that's not implemented in
@ -1235,9 +1289,9 @@ func (s *Screen) render() {
// Do we need alt-screen mode? // Do we need alt-screen mode?
if s.opts.AltScreen != s.altScreenMode { if s.opts.AltScreen != s.altScreenMode {
if s.opts.AltScreen { if s.opts.AltScreen {
s.buf.WriteString(ansi.SetAltScreenSaveCursorMode) s.buf.WriteString(ansi.SetModeAltScreenSaveCursor)
} else { } else {
s.buf.WriteString(ansi.ResetAltScreenSaveCursorMode) s.buf.WriteString(ansi.ResetModeAltScreenSaveCursor)
} }
s.altScreenMode = s.opts.AltScreen s.altScreenMode = s.opts.AltScreen
} }
@ -1252,7 +1306,9 @@ func (s *Screen) render() {
// Do we have queued strings to write above the screen? // Do we have queued strings to write above the screen?
if len(s.queueAbove) > 0 { if len(s.queueAbove) > 0 {
//nolint:godox
// TODO: Use scrolling region if available. // TODO: Use scrolling region if available.
//nolint:godox
// TODO: Use [Screen.Write] [io.Writer] interface. // TODO: Use [Screen.Write] [io.Writer] interface.
// We need to scroll the screen up by the number of lines in the queue. // We need to scroll the screen up by the number of lines in the queue.
@ -1290,12 +1346,13 @@ func (s *Screen) render() {
s.clearBelow(nil, s.newbuf.Height()-1) s.clearBelow(nil, s.newbuf.Height()-1)
} }
if s.clear { if s.clear { //nolint:nestif
s.clearUpdate() s.clearUpdate()
s.clear = false s.clear = false
} else if len(s.touch) > 0 { } else if len(s.touch) > 0 {
if s.opts.AltScreen { if s.opts.AltScreen {
// Optimize scrolling for the alternate screen buffer. // Optimize scrolling for the alternate screen buffer.
//nolint:godox
// TODO: Should we optimize for inline mode as well? If so, we need // TODO: Should we optimize for inline mode as well? If so, we need
// to know the actual cursor position to use [ansi.DECSTBM]. // to know the actual cursor position to use [ansi.DECSTBM].
s.scrollOptimize() s.scrollOptimize()
@ -1311,7 +1368,7 @@ func (s *Screen) render() {
} }
nonEmpty = s.clearBottom(nonEmpty) nonEmpty = s.clearBottom(nonEmpty)
for i = 0; i < nonEmpty; i++ { for i = range nonEmpty {
_, ok := s.touch[i] _, ok := s.touch[i]
if ok { if ok {
s.transformLine(i) s.transformLine(i)
@ -1359,7 +1416,7 @@ func (s *Screen) Close() (err error) {
s.move(0, s.newbuf.Height()-1) s.move(0, s.newbuf.Height()-1)
if s.altScreenMode { if s.altScreenMode {
s.buf.WriteString(ansi.ResetAltScreenSaveCursorMode) s.buf.WriteString(ansi.ResetModeAltScreenSaveCursor)
s.altScreenMode = false s.altScreenMode = false
} }
@ -1371,11 +1428,11 @@ func (s *Screen) Close() (err error) {
// Write the buffer // Write the buffer
err = s.flush() err = s.flush()
if err != nil { if err != nil {
return return err
} }
s.reset() s.reset()
return return err
} }
// reset resets the screen to its initial state. // reset resets the screen to its initial state.
@ -1420,9 +1477,9 @@ func (s *Screen) Resize(width, height int) bool {
} }
if height > oldh { if height > oldh {
s.ClearRect(Rect(0, max(oldh-1, 0), width, height-oldh)) s.ClearRect(Rect(0, max(oldh, 0), width, height-oldh))
} else if height < oldh { } else if height < oldh {
s.ClearRect(Rect(0, max(height-1, 0), width, oldh-height)) s.ClearRect(Rect(0, max(height, 0), width, oldh-height))
} }
s.mu.Lock() s.mu.Lock()

View File

@ -4,9 +4,9 @@ import (
"github.com/charmbracelet/colorprofile" "github.com/charmbracelet/colorprofile"
) )
// Convert converts a style to respect the given color profile. // ConvertStyle converts a style to respect the given color profile.
func ConvertStyle(s Style, p colorprofile.Profile) Style { func ConvertStyle(s Style, p colorprofile.Profile) Style {
switch p { switch p { //nolint:exhaustive
case colorprofile.TrueColor: case colorprofile.TrueColor:
return s return s
case colorprofile.Ascii: case colorprofile.Ascii:

View File

@ -9,20 +9,6 @@ func Height(s string) int {
return strings.Count(s, "\n") + 1 return strings.Count(s, "\n") + 1
} }
func min(a, b int) int { //nolint:predeclared
if a > b {
return b
}
return a
}
func max(a, b int) int { //nolint:predeclared
if a > b {
return a
}
return b
}
func clamp(v, low, high int) int { func clamp(v, low, high int) int {
if high < low { if high < low {
low, high = high, low low, high = high, low

View File

@ -2,6 +2,7 @@ package cellbuf
import ( import (
"bytes" "bytes"
"slices"
"unicode" "unicode"
"unicode/utf8" "unicode/utf8"
@ -20,6 +21,16 @@ const nbsp = '\u00a0'
// //
// Note: breakpoints must be a string of 1-cell wide rune characters. // Note: breakpoints must be a string of 1-cell wide rune characters.
func Wrap(s string, limit int, breakpoints string) string { func Wrap(s string, limit int, breakpoints string) string {
//nolint:godox
// TODO: Use [PenWriter] once we get
// https://github.com/charmbracelet/lipgloss/pull/489 out the door and
// released.
// The problem is that [ansi.Wrap] doesn't keep track of style and link
// state, so combining both breaks styled space cells. To fix this, we use
// non-breaking space cells for padding and styled blank cells. And since
// both wrapping methods respect non-breaking spaces, we can use them to
// preserve styled spaces in the output.
if len(s) == 0 { if len(s) == 0 {
return "" return ""
} }
@ -90,7 +101,7 @@ func Wrap(s string, limit int, breakpoints string) string {
seq, width, n, newState := ansi.DecodeSequence(s, state, p) seq, width, n, newState := ansi.DecodeSequence(s, state, p)
switch width { switch width {
case 0: case 0:
if ansi.Equal(seq, "\t") { if ansi.Equal(seq, "\t") { //nolint:nestif
addWord() addWord()
space.WriteString(seq) space.WriteString(seq)
break break
@ -176,10 +187,5 @@ func Wrap(s string, limit int, breakpoints string) string {
} }
func runeContainsAny[T string | []rune](r rune, s T) bool { func runeContainsAny[T string | []rune](r rune, s T) bool {
for _, c := range []rune(s) { return slices.Contains([]rune(s), r)
if c == r {
return true
}
}
return false
} }

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