forked from toolshed/abra
Compare commits
8 Commits
cobra-migr
...
main
Author | SHA1 | Date | |
---|---|---|---|
|
09da474219 | ||
|
7b54c2b5b9 | ||
|
8ee1947fe9 | ||
04aec8232f | |||
2a5985e44e | |||
c65be64e7d | |||
fd8652e26d | |||
518c5795f4 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -6,4 +6,3 @@
|
|||||||
abra
|
abra
|
||||||
dist/
|
dist/
|
||||||
tests/integration/.bats
|
tests/integration/.bats
|
||||||
vendor/
|
|
||||||
|
@ -49,6 +49,8 @@ builds:
|
|||||||
- 5
|
- 5
|
||||||
- 6
|
- 6
|
||||||
- 7
|
- 7
|
||||||
|
gcflags:
|
||||||
|
- "all=-l -B"
|
||||||
ldflags:
|
ldflags:
|
||||||
- "-X 'main.Commit={{ .Commit }}'"
|
- "-X 'main.Commit={{ .Commit }}'"
|
||||||
- "-X 'main.Version={{ .Version }}'"
|
- "-X 'main.Version={{ .Version }}'"
|
||||||
|
13
Makefile
13
Makefile
@ -5,6 +5,7 @@ GOPATH := $(shell go env GOPATH)
|
|||||||
GOVERSION := 1.21
|
GOVERSION := 1.21
|
||||||
LDFLAGS := "-X 'main.Commit=$(COMMIT)'"
|
LDFLAGS := "-X 'main.Commit=$(COMMIT)'"
|
||||||
DIST_LDFLAGS := $(LDFLAGS)" -s -w"
|
DIST_LDFLAGS := $(LDFLAGS)" -s -w"
|
||||||
|
GCFLAGS := "all=-l -B"
|
||||||
|
|
||||||
export GOPRIVATE=coopcloud.tech
|
export GOPRIVATE=coopcloud.tech
|
||||||
|
|
||||||
@ -12,24 +13,24 @@ export GOPRIVATE=coopcloud.tech
|
|||||||
all: format check build-abra test
|
all: format check build-abra test
|
||||||
|
|
||||||
run-abra:
|
run-abra:
|
||||||
@go run -ldflags=$(LDFLAGS) $(ABRA)
|
@go run -gcflags=$(GCFLAGS) -ldflags=$(LDFLAGS) $(ABRA)
|
||||||
|
|
||||||
run-kadabra:
|
run-kadabra:
|
||||||
@go run -ldflags=$(LDFLAGS) $(KADABRA)
|
@go run -gcflags=$(GCFLAGS) -ldflags=$(LDFLAGS) $(KADABRA)
|
||||||
|
|
||||||
install-abra:
|
install-abra:
|
||||||
@go install -ldflags=$(LDFLAGS) $(ABRA)
|
@go install -gcflags=$(GCFLAGS) -ldflags=$(LDFLAGS) $(ABRA)
|
||||||
|
|
||||||
install-kadabra:
|
install-kadabra:
|
||||||
@go install -ldflags=$(LDFLAGS) $(KADABRA)
|
@go install -gcflags=$(GCFLAGS) -ldflags=$(LDFLAGS) $(KADABRA)
|
||||||
|
|
||||||
install: install-abra install-kadabra
|
install: install-abra install-kadabra
|
||||||
|
|
||||||
build-abra:
|
build-abra:
|
||||||
@go build -v -ldflags=$(DIST_LDFLAGS) $(ABRA)
|
@go build -v -gcflags=$(GCFLAGS) -ldflags=$(DIST_LDFLAGS) $(ABRA)
|
||||||
|
|
||||||
build-kadabra:
|
build-kadabra:
|
||||||
@go build -v -ldflags=$(DIST_LDFLAGS) $(KADABRA)
|
@go build -v -gcflags=$(GCFLAGS) -ldflags=$(DIST_LDFLAGS) $(KADABRA)
|
||||||
|
|
||||||
build: build-abra build-kadabra
|
build: build-abra build-kadabra
|
||||||
|
|
||||||
|
@ -258,7 +258,7 @@ EXAMPLE:
|
|||||||
app.Recipe.Version = chaosVersion
|
app.Recipe.Version = chaosVersion
|
||||||
}
|
}
|
||||||
log.Debugf("choosing %s as version to save to env file", app.Recipe.Version)
|
log.Debugf("choosing %s as version to save to env file", app.Recipe.Version)
|
||||||
if err := app.WriteRecipeVersion(app.Recipe.Version); err != nil {
|
if err := app.WriteRecipeVersion(app.Recipe.Version, false); err != nil {
|
||||||
log.Fatalf("writing new recipe version in env file: %s", err)
|
log.Fatalf("writing new recipe version in env file: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -219,7 +219,7 @@ var appNewCommand = cli.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("choosing %s as version to save to env file", version)
|
log.Debugf("choosing %s as version to save to env file", version)
|
||||||
if err := app.WriteRecipeVersion(version); err != nil {
|
if err := app.WriteRecipeVersion(version, false); err != nil {
|
||||||
log.Fatalf("writing new recipe version in env file: %s", err)
|
log.Fatalf("writing new recipe version in env file: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,12 +30,14 @@ var appPsCommand = cli.Command{
|
|||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
internal.MachineReadableFlag,
|
internal.MachineReadableFlag,
|
||||||
internal.DebugFlag,
|
internal.DebugFlag,
|
||||||
|
internal.ChaosFlag,
|
||||||
|
internal.OfflineFlag,
|
||||||
},
|
},
|
||||||
Before: internal.SubCommandBefore,
|
Before: internal.SubCommandBefore,
|
||||||
BashComplete: autocomplete.AppNameComplete,
|
BashComplete: autocomplete.AppNameComplete,
|
||||||
Action: func(c *cli.Context) error {
|
Action: func(c *cli.Context) error {
|
||||||
app := internal.ValidateApp(c)
|
app := internal.ValidateApp(c)
|
||||||
if err := app.Recipe.Ensure(false, false); err != nil {
|
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,12 +58,7 @@ var appPsCommand = cli.Command{
|
|||||||
chaosVersion := config.CHAOS_DEFAULT
|
chaosVersion := config.CHAOS_DEFAULT
|
||||||
statuses, err := appPkg.GetAppStatuses([]appPkg.App{app}, true)
|
statuses, err := appPkg.GetAppStatuses([]appPkg.App{app}, true)
|
||||||
if statusMeta, ok := statuses[app.StackName()]; ok {
|
if statusMeta, ok := statuses[app.StackName()]; ok {
|
||||||
isChaos, exists := statusMeta["chaos"]
|
if isChaos, exists := statusMeta["chaos"]; exists && isChaos == "true" {
|
||||||
if exists && isChaos == "false" {
|
|
||||||
if _, err := app.Recipe.EnsureVersion(deployMeta.Version); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
chaosVersion, err = app.Recipe.ChaosVersion()
|
chaosVersion, err = app.Recipe.ChaosVersion()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
|
@ -225,7 +225,7 @@ EXAMPLE:
|
|||||||
|
|
||||||
app.Recipe.Version = chosenDowngrade
|
app.Recipe.Version = chosenDowngrade
|
||||||
log.Debugf("choosing %s as version to save to env file", app.Recipe.Version)
|
log.Debugf("choosing %s as version to save to env file", app.Recipe.Version)
|
||||||
if err := app.WriteRecipeVersion(app.Recipe.Version); err != nil {
|
if err := app.WriteRecipeVersion(app.Recipe.Version, false); err != nil {
|
||||||
log.Fatalf("writing new recipe version in env file: %s", err)
|
log.Fatalf("writing new recipe version in env file: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,6 +68,7 @@ var appUndeployCommand = cli.Command{
|
|||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
internal.DebugFlag,
|
internal.DebugFlag,
|
||||||
internal.NoInputFlag,
|
internal.NoInputFlag,
|
||||||
|
internal.OfflineFlag,
|
||||||
pruneFlag,
|
pruneFlag,
|
||||||
},
|
},
|
||||||
Before: internal.SubCommandBefore,
|
Before: internal.SubCommandBefore,
|
||||||
@ -82,9 +83,6 @@ any previously attached volumes as eligible for pruning once undeployed.
|
|||||||
Passing "-p/--prune" does not remove those volumes.`,
|
Passing "-p/--prune" does not remove those volumes.`,
|
||||||
Action: func(c *cli.Context) error {
|
Action: func(c *cli.Context) error {
|
||||||
app := internal.ValidateApp(c)
|
app := internal.ValidateApp(c)
|
||||||
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
stackName := app.StackName()
|
stackName := app.StackName()
|
||||||
|
|
||||||
cl, err := client.New(app.Server)
|
cl, err := client.New(app.Server)
|
||||||
|
@ -286,7 +286,7 @@ EXAMPLE:
|
|||||||
|
|
||||||
app.Recipe.Version = chosenUpgrade
|
app.Recipe.Version = chosenUpgrade
|
||||||
log.Debugf("choosing %s as version to save to env file", app.Recipe.Version)
|
log.Debugf("choosing %s as version to save to env file", app.Recipe.Version)
|
||||||
if err := app.WriteRecipeVersion(app.Recipe.Version); err != nil {
|
if err := app.WriteRecipeVersion(app.Recipe.Version, false); err != nil {
|
||||||
log.Fatalf("writing new recipe version in env file: %s", err)
|
log.Fatalf("writing new recipe version in env file: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,17 +115,10 @@ You can then add a server like so:
|
|||||||
If "--local" is passed, then Abra assumes that the current local server is
|
If "--local" is passed, then Abra assumes that the current local server is
|
||||||
intended as the target server. This is useful when you want to have your entire
|
intended as the target server. This is useful when you want to have your entire
|
||||||
Co-op Cloud config located on the server itself, and not on your local
|
Co-op Cloud config located on the server itself, and not on your local
|
||||||
developer machine. The domain is then set to "default".
|
developer machine. The domain is then set to "default".`,
|
||||||
|
|
||||||
You can also pass "--no-domain-checks/-D" flag to use any arbitrary name
|
|
||||||
instead of a real domain. The host will be resolved with the "Hostname" entry
|
|
||||||
of your ~/.ssh/config. Checks for a valid online domain will be skipped:
|
|
||||||
|
|
||||||
abra server add -D example`,
|
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
internal.DebugFlag,
|
internal.DebugFlag,
|
||||||
internal.NoInputFlag,
|
internal.NoInputFlag,
|
||||||
internal.NoDomainChecksFlag,
|
|
||||||
localFlag,
|
localFlag,
|
||||||
},
|
},
|
||||||
Before: internal.SubCommandBefore,
|
Before: internal.SubCommandBefore,
|
||||||
@ -170,10 +163,8 @@ of your ~/.ssh/config. Checks for a valid online domain will be skipped:
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if !internal.NoDomainChecks {
|
|
||||||
if _, err := dns.EnsureIPv4(name); err != nil {
|
if _, err := dns.EnsureIPv4(name); err != nil {
|
||||||
log.Fatal(err)
|
log.Warn(err)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := createServerDir(name)
|
_, err := createServerDir(name)
|
||||||
|
@ -569,7 +569,7 @@ func ReadAbraShCmdNames(abraSh string) ([]string, error) {
|
|||||||
return cmdNames, nil
|
return cmdNames, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a App) WriteRecipeVersion(version string) error {
|
func (a App) WriteRecipeVersion(version string, dryRun bool) error {
|
||||||
file, err := os.Open(a.Path)
|
file, err := os.Open(a.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -581,7 +581,7 @@ func (a App) WriteRecipeVersion(version string) error {
|
|||||||
lines := []string{}
|
lines := []string{}
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
line := scanner.Text()
|
line := scanner.Text()
|
||||||
if !strings.Contains(line, "RECIPE=") && !strings.Contains(line, "TYPE") {
|
if !strings.HasPrefix(line, "RECIPE=") && !strings.HasPrefix(line, "TYPE=") {
|
||||||
lines = append(lines, line)
|
lines = append(lines, line)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -606,9 +606,13 @@ func (a App) WriteRecipeVersion(version string) error {
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !dryRun {
|
||||||
if err := os.WriteFile(a.Path, []byte(strings.Join(lines, "\n")), os.ModePerm); err != nil {
|
if err := os.WriteFile(a.Path, []byte(strings.Join(lines, "\n")), os.ModePerm); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
log.Debugf("skipping writing version %s because dry run", version)
|
||||||
|
}
|
||||||
|
|
||||||
if !skipped {
|
if !skipped {
|
||||||
log.Infof("version %s saved to %s.env", version, a.Domain)
|
log.Infof("version %s saved to %s.env", version, a.Domain)
|
||||||
|
@ -11,6 +11,7 @@ import (
|
|||||||
"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"
|
testPkg "coopcloud.tech/abra/pkg/test"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGetAllFoldersInDirectory(t *testing.T) {
|
func TestGetAllFoldersInDirectory(t *testing.T) {
|
||||||
@ -43,13 +44,7 @@ func TestReadEnv(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if !reflect.DeepEqual(env, testPkg.ExpectedAppEnv) {
|
if !reflect.DeepEqual(env, testPkg.ExpectedAppEnv) {
|
||||||
t.Fatalf(
|
t.Fatal("did not get expected application settings")
|
||||||
"did not get expected application settings. Expected: DOMAIN=%s RECIPE=%s; Got: DOMAIN=%s RECIPE=%s",
|
|
||||||
testPkg.ExpectedAppEnv["DOMAIN"],
|
|
||||||
testPkg.ExpectedAppEnv["RECIPE"],
|
|
||||||
env["DOMAIN"],
|
|
||||||
env["RECIPE"],
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -228,3 +223,21 @@ func TestEnvVarModifiersIncluded(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNoOverwriteNonVersionEnvVars(t *testing.T) {
|
||||||
|
app, err := appPkg.GetApp(testPkg.ExpectedAppFiles, testPkg.AppName)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := app.WriteRecipeVersion("1.3.12", true); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
app, err = appPkg.GetApp(testPkg.ExpectedAppFiles, testPkg.AppName)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.NotEqual(t, app.Env["SMTP_AUTHTYPE"], "login:1.3.12")
|
||||||
|
}
|
||||||
|
@ -29,6 +29,7 @@ var (
|
|||||||
var ExpectedAppEnv = envfile.AppEnv{
|
var ExpectedAppEnv = envfile.AppEnv{
|
||||||
"DOMAIN": "ecloud.evil.corp",
|
"DOMAIN": "ecloud.evil.corp",
|
||||||
"RECIPE": "ecloud",
|
"RECIPE": "ecloud",
|
||||||
|
"SMTP_AUTHTYPE": "login",
|
||||||
}
|
}
|
||||||
|
|
||||||
var ExpectedApp = appPkg.App{
|
var ExpectedApp = appPkg.App{
|
||||||
|
@ -31,6 +31,84 @@ teardown(){
|
|||||||
assert_output --partial 'cannot find app'
|
assert_output --partial 'cannot find app'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# bats test_tags=slow
|
||||||
|
@test "retrieve recipe if missing" {
|
||||||
|
_deploy_app
|
||||||
|
|
||||||
|
run rm -rf "$ABRA_DIR/recipes/$TEST_RECIPE"
|
||||||
|
assert_success
|
||||||
|
assert_not_exists "$ABRA_DIR/recipes/$TEST_RECIPE"
|
||||||
|
|
||||||
|
run $ABRA app ps "$TEST_APP_DOMAIN"
|
||||||
|
assert_success
|
||||||
|
|
||||||
|
assert_exists "$ABRA_DIR/recipes/$TEST_RECIPE"
|
||||||
|
}
|
||||||
|
|
||||||
|
# bats test_tags=slow
|
||||||
|
@test "bail if unstaged changes and no --chaos" {
|
||||||
|
_deploy_app
|
||||||
|
|
||||||
|
run bash -c "echo foo >> $ABRA_DIR/recipes/$TEST_RECIPE/foo"
|
||||||
|
assert_success
|
||||||
|
assert_exists "$ABRA_DIR/recipes/$TEST_RECIPE/foo"
|
||||||
|
|
||||||
|
run $ABRA app ps "$TEST_APP_DOMAIN"
|
||||||
|
assert_failure
|
||||||
|
assert_output --partial 'locally unstaged changes'
|
||||||
|
|
||||||
|
run rm -rf "$ABRA_DIR/recipes/$TEST_RECIPE/foo"
|
||||||
|
assert_not_exists "$ABRA_DIR/recipes/$TEST_RECIPE/foo"
|
||||||
|
}
|
||||||
|
|
||||||
|
# bats test_tags=slow
|
||||||
|
@test "do not bail if unstaged changes and --chaos" {
|
||||||
|
_deploy_app
|
||||||
|
|
||||||
|
run bash -c "echo foo >> $ABRA_DIR/recipes/$TEST_RECIPE/foo"
|
||||||
|
assert_success
|
||||||
|
assert_exists "$ABRA_DIR/recipes/$TEST_RECIPE/foo"
|
||||||
|
|
||||||
|
run $ABRA app ps --chaos "$TEST_APP_DOMAIN"
|
||||||
|
assert_success
|
||||||
|
|
||||||
|
run rm -rf "$ABRA_DIR/recipes/$TEST_RECIPE/foo"
|
||||||
|
assert_not_exists "$ABRA_DIR/recipes/$TEST_RECIPE/foo"
|
||||||
|
}
|
||||||
|
|
||||||
|
# bats test_tags=slow
|
||||||
|
@test "ensure recipe up to date if no --offline" {
|
||||||
|
_deploy_app
|
||||||
|
|
||||||
|
wantHash=$(_get_n_hash 3)
|
||||||
|
|
||||||
|
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" reset --hard HEAD~3
|
||||||
|
assert_success
|
||||||
|
|
||||||
|
assert_equal $(_get_current_hash) "$wantHash"
|
||||||
|
|
||||||
|
run $ABRA app ps "$TEST_APP_DOMAIN"
|
||||||
|
assert_success
|
||||||
|
|
||||||
|
assert_equal $(_get_head_hash) $(_get_current_hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "ensure recipe not up to date if --offline" {
|
||||||
|
_deploy_app
|
||||||
|
|
||||||
|
_ensure_env_version "0.1.0+1.20.0"
|
||||||
|
latestRelease=$(_latest_release)
|
||||||
|
|
||||||
|
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" tag -d "$latestRelease"
|
||||||
|
assert_success
|
||||||
|
|
||||||
|
run $ABRA app ps --offline "$TEST_APP_DOMAIN"
|
||||||
|
assert_success
|
||||||
|
|
||||||
|
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" tag -l
|
||||||
|
refute_output --partial "$latestRelease"
|
||||||
|
}
|
||||||
|
|
||||||
@test "error if not deployed" {
|
@test "error if not deployed" {
|
||||||
run $ABRA app ps "$TEST_APP_DOMAIN"
|
run $ABRA app ps "$TEST_APP_DOMAIN"
|
||||||
assert_failure
|
assert_failure
|
||||||
|
@ -18,6 +18,7 @@ setup(){
|
|||||||
}
|
}
|
||||||
|
|
||||||
teardown(){
|
teardown(){
|
||||||
|
_reset_recipe
|
||||||
_undeploy_app
|
_undeploy_app
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -31,12 +32,61 @@ teardown(){
|
|||||||
assert_output --partial 'cannot find app'
|
assert_output --partial 'cannot find app'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# bats test_tags=slow
|
||||||
|
@test "ensure recipe up to date if no --offline" {
|
||||||
|
_deploy_app
|
||||||
|
|
||||||
|
wantHash=$(_get_n_hash 3)
|
||||||
|
|
||||||
|
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" reset --hard HEAD~3
|
||||||
|
assert_success
|
||||||
|
|
||||||
|
assert_equal $(_get_current_hash) "$wantHash"
|
||||||
|
|
||||||
|
run $ABRA app undeploy "$TEST_APP_DOMAIN" --no-input
|
||||||
|
assert_success
|
||||||
|
|
||||||
|
assert_equal $(_get_head_hash) $(_get_current_hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
# bats test_tags=slow
|
||||||
|
@test "ensure recipe not up to date if --offline" {
|
||||||
|
_deploy_app
|
||||||
|
|
||||||
|
_ensure_env_version "0.1.0+1.20.0"
|
||||||
|
latestRelease=$(_latest_release)
|
||||||
|
|
||||||
|
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" tag -d "$latestRelease"
|
||||||
|
assert_success
|
||||||
|
|
||||||
|
run $ABRA app undeploy "$TEST_APP_DOMAIN" --no-input --offline
|
||||||
|
assert_success
|
||||||
|
|
||||||
|
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" tag -l
|
||||||
|
refute_output --partial "$latestRelease"
|
||||||
|
}
|
||||||
|
|
||||||
@test "error if not deployed" {
|
@test "error if not deployed" {
|
||||||
run $ABRA app undeploy "$TEST_APP_DOMAIN"
|
run $ABRA app undeploy "$TEST_APP_DOMAIN"
|
||||||
assert_failure
|
assert_failure
|
||||||
assert_output --partial 'is not deployed'
|
assert_output --partial 'is not deployed'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# bats test_tags=slow
|
||||||
|
@test "do not bail if unstaged changes (only query runtime)" {
|
||||||
|
_deploy_app
|
||||||
|
|
||||||
|
run bash -c "echo foo >> $ABRA_DIR/recipes/$TEST_RECIPE/foo"
|
||||||
|
assert_success
|
||||||
|
assert_exists "$ABRA_DIR/recipes/$TEST_RECIPE/foo"
|
||||||
|
|
||||||
|
run $ABRA app undeploy "$TEST_APP_DOMAIN" --no-input
|
||||||
|
assert_success
|
||||||
|
|
||||||
|
run rm -rf "$ABRA_DIR/recipes/$TEST_RECIPE/foo"
|
||||||
|
assert_not_exists "$ABRA_DIR/recipes/$TEST_RECIPE/foo"
|
||||||
|
}
|
||||||
|
|
||||||
# bats test_tags=slow
|
# bats test_tags=slow
|
||||||
@test "undeploy app" {
|
@test "undeploy app" {
|
||||||
run $ABRA app deploy "$TEST_APP_DOMAIN" --no-input
|
run $ABRA app deploy "$TEST_APP_DOMAIN" --no-input
|
||||||
|
@ -1,2 +1,3 @@
|
|||||||
RECIPE=ecloud
|
RECIPE=ecloud
|
||||||
DOMAIN=ecloud.evil.corp
|
DOMAIN=ecloud.evil.corp
|
||||||
|
SMTP_AUTHTYPE=login
|
||||||
|
18
vendor/coopcloud.tech/tagcmp/.drone.yml
vendored
Normal file
18
vendor/coopcloud.tech/tagcmp/.drone.yml
vendored
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
---
|
||||||
|
kind: pipeline
|
||||||
|
name: coopcloud.tech/tagcmp
|
||||||
|
steps:
|
||||||
|
- name: gofmt
|
||||||
|
image: golang:1.21
|
||||||
|
commands:
|
||||||
|
- test -z "$(gofmt -l .)"
|
||||||
|
|
||||||
|
- name: go build
|
||||||
|
image: golang:1.21
|
||||||
|
commands:
|
||||||
|
- go build -v .
|
||||||
|
|
||||||
|
- name: go test
|
||||||
|
image: golang:1.21
|
||||||
|
commands:
|
||||||
|
- go test . -cover
|
1
vendor/coopcloud.tech/tagcmp/.gitignore
vendored
Normal file
1
vendor/coopcloud.tech/tagcmp/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
fmtcoverage.html
|
15
vendor/coopcloud.tech/tagcmp/LICENSE
vendored
Normal file
15
vendor/coopcloud.tech/tagcmp/LICENSE
vendored
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
Tagcmp: comparison operations for image tags.
|
||||||
|
Copyright (C) 2023 Co-op Cloud <helo@coopcloud.tech>
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
161
vendor/coopcloud.tech/tagcmp/README.md
vendored
Normal file
161
vendor/coopcloud.tech/tagcmp/README.md
vendored
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
# tagcmp
|
||||||
|
|
||||||
|
[](https://build.coopcloud.tech/coop-cloud/tagcmp)
|
||||||
|
[](https://goreportcard.com/report/git.coopcloud.tech/coop-cloud/tagcmp)
|
||||||
|
[](https://pkg.go.dev/coopcloud.tech/tagcmp)
|
||||||
|
|
||||||
|
Comparison operations for image tags. Because registries aren't doing this for
|
||||||
|
us 🙄
|
||||||
|
|
||||||
|
This library is helpful if you're aiming to use only "stable" and "semver-like"
|
||||||
|
tags and want to be able to do things like compare them, find which tags are
|
||||||
|
more recent, sort them and other types of comparisons. This is a best-effort
|
||||||
|
implementation which follows the wisdom of [Renovate].
|
||||||
|
|
||||||
|
> Docker doesn't really have versioning, instead it supports "tags" and these
|
||||||
|
> are usually used by Docker image authors as a form of versioning ... It's
|
||||||
|
> pretty "wild west" for tagging and not always compliant with SemVer.
|
||||||
|
|
||||||
|
The Renovate implementation allows image tags to be automatically upgraded, is
|
||||||
|
the only show in town, apparently. This library follows that implementation
|
||||||
|
quite closely.
|
||||||
|
|
||||||
|
[renovate]: https://docs.renovatebot.com/docker/
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
```golang
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"coopcloud.tech/tagcmp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
rawTags := []string{
|
||||||
|
"1.7.1",
|
||||||
|
"1.9.4-linux-arm64",
|
||||||
|
"1.14.2-rootless",
|
||||||
|
"linux-arm64-rootless",
|
||||||
|
"1.14.1-rootless",
|
||||||
|
"1.12.4-linux-amd64",
|
||||||
|
"1.14.0-rootless",
|
||||||
|
}
|
||||||
|
|
||||||
|
tag, err := tagcmp.Parse("1.14.0-rootless")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var compatible []tagcmp.Tag
|
||||||
|
for _, rawTag := range rawTags {
|
||||||
|
parsed, _ := tagcmp.Parse(rawTag) // skips unsupported tags
|
||||||
|
if tag.IsCompatible(parsed) {
|
||||||
|
compatible = append(compatible, parsed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Sort(tagcmp.ByTagAsc(compatible))
|
||||||
|
|
||||||
|
fmt.Println(compatible)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
```golang
|
||||||
|
[1.14.0-rootless 1.14.1-rootless 1.14.2-rootless]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Types of versions supported
|
||||||
|
|
||||||
|
```golang
|
||||||
|
// semver
|
||||||
|
"5",
|
||||||
|
"2.6",
|
||||||
|
"4.3.5",
|
||||||
|
|
||||||
|
// semver with 'v'
|
||||||
|
"v1",
|
||||||
|
"v2.3",
|
||||||
|
"v1.0.2",
|
||||||
|
|
||||||
|
// semver with suffix
|
||||||
|
"6-alpine",
|
||||||
|
"6.2-alpine",
|
||||||
|
"6.2.1-alpine",
|
||||||
|
|
||||||
|
// semver with sufix and 'v'
|
||||||
|
"v6-alpine",
|
||||||
|
"v6.2-alpine",
|
||||||
|
"v6.2.1-alpine",
|
||||||
|
"v6.2.1-alpine",
|
||||||
|
|
||||||
|
// semver with multiple suffix values
|
||||||
|
"6.2.1-alpine-foo",
|
||||||
|
|
||||||
|
// semver with multiple suffix values and 'v'
|
||||||
|
"v6.2.1-alpine-foo",
|
||||||
|
```
|
||||||
|
|
||||||
|
## Types of versions not supported
|
||||||
|
|
||||||
|
> Please note, we could support some of these versions if people really need
|
||||||
|
> them to be supported. Some tags are using a unique format which we could
|
||||||
|
> support by implementing a very specific parser for (e.g. `ParseMinioTag`,
|
||||||
|
> `ParseZncTag`). For now, this library tries to provide a `Parse` function
|
||||||
|
> which handles more general cases. Please open an issue, change sets are
|
||||||
|
> welcome.
|
||||||
|
|
||||||
|
```golang
|
||||||
|
// empty
|
||||||
|
"",
|
||||||
|
|
||||||
|
// patametrized
|
||||||
|
"${MAILU_VERSION:-master}",
|
||||||
|
"${PHP_VERSION}-fpm-alpine3.13",
|
||||||
|
|
||||||
|
// commit hash like
|
||||||
|
"0a1b2c3d4e5f6a7b8c9d0a1b2c3d4e5f6a7b8c9d",
|
||||||
|
|
||||||
|
// numeric
|
||||||
|
"20191109",
|
||||||
|
"e02267d",
|
||||||
|
|
||||||
|
// not semver
|
||||||
|
"3.0.6.0",
|
||||||
|
"r1295",
|
||||||
|
"version-r1070",
|
||||||
|
|
||||||
|
// prerelease
|
||||||
|
"3.7.0b1",
|
||||||
|
"3.8.0b1-alpine",
|
||||||
|
|
||||||
|
// multiple versions
|
||||||
|
"5.36-backdrop-php7.4",
|
||||||
|
"v1.0.5_3.4.0",
|
||||||
|
"v1.0.5_3.4.0_openid-sso",
|
||||||
|
|
||||||
|
// tz based
|
||||||
|
"RELEASE.2021-04-22T15-44-28Z",
|
||||||
|
|
||||||
|
// only text
|
||||||
|
"alpine",
|
||||||
|
"latest",
|
||||||
|
"master",
|
||||||
|
|
||||||
|
// multiple - delimters
|
||||||
|
"apache-debian-1.8-prod",
|
||||||
|
"version-znc-1.8.2",
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
[GPLv3+](./LICENSE)
|
||||||
|
|
||||||
|
## Who's using it?
|
||||||
|
|
||||||
|
- [`abra`](https://git.coopcloud.tech/coop-cloud/abra)
|
3
vendor/coopcloud.tech/tagcmp/renovate.json
vendored
Normal file
3
vendor/coopcloud.tech/tagcmp/renovate.json
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://docs.renovatebot.com/renovate-schema.json"
|
||||||
|
}
|
452
vendor/coopcloud.tech/tagcmp/tagcmp.go
vendored
Normal file
452
vendor/coopcloud.tech/tagcmp/tagcmp.go
vendored
Normal file
@ -0,0 +1,452 @@
|
|||||||
|
// Package tagcmp provides image tag comparison operations.
|
||||||
|
package tagcmp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Tag struct {
|
||||||
|
Major string `json:",omitempty"` // major semver part
|
||||||
|
Minor string `json:",omitempty"` // minor semver part
|
||||||
|
MissingMinor bool // whether or not the minor semver part was left out
|
||||||
|
Patch string `json:",omitempty"` // patch semver part
|
||||||
|
MissingPatch bool // whether or not he patch semver part was left out
|
||||||
|
Suffix string // tag suffix (e.g. "-alpine") [would be release candidate in semver]
|
||||||
|
UsesV bool // whether or not the tag uses the "v" prefix
|
||||||
|
Metadata string // metadata: what's after + and after the first "-"
|
||||||
|
}
|
||||||
|
|
||||||
|
type TagDelta struct {
|
||||||
|
Major int // major semver difference
|
||||||
|
Minor int // minor semver difference
|
||||||
|
Patch int // patch semver difference
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByTagAsc sorts tags in ascending order where the last element is the latest tag.
|
||||||
|
type ByTagAsc []Tag
|
||||||
|
|
||||||
|
func (t ByTagAsc) Len() int { return len(t) }
|
||||||
|
func (t ByTagAsc) Swap(i, j int) { t[i], t[j] = t[j], t[i] }
|
||||||
|
func (t ByTagAsc) Less(i, j int) bool {
|
||||||
|
return t[i].IsLessThan(t[j])
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByTagDesc sorts tags in descending order where the first element is the latest tag.
|
||||||
|
type ByTagDesc []Tag
|
||||||
|
|
||||||
|
func (t ByTagDesc) Len() int { return len(t) }
|
||||||
|
func (t ByTagDesc) Swap(i, j int) { t[j], t[i] = t[i], t[j] }
|
||||||
|
func (t ByTagDesc) Less(i, j int) bool {
|
||||||
|
return t[j].IsLessThan(t[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsGreaterThan tests if a tag is greater than another. There are some
|
||||||
|
// tag-isms to take into account here, shorter is bigger (i.e. 2.1 > 2.1.1 ==
|
||||||
|
// true, 2 > 2.1 == true).
|
||||||
|
func (t Tag) IsGreaterThan(tag Tag) bool {
|
||||||
|
// shorter is bigger, i.e. 2.1 > 2.1.1
|
||||||
|
if t.MissingPatch && !tag.MissingPatch || t.MissingMinor && !tag.MissingMinor {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if tag.MissingPatch && !t.MissingPatch || tag.MissingMinor && !t.MissingMinor {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// ignore errors since Parse already handled
|
||||||
|
mj1, _ := strconv.Atoi(t.Major)
|
||||||
|
mj2, _ := strconv.Atoi(tag.Major)
|
||||||
|
if mj1 > mj2 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if mj2 > mj1 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
mn1, _ := strconv.Atoi(t.Minor)
|
||||||
|
mn2, _ := strconv.Atoi(tag.Minor)
|
||||||
|
if mn1 > mn2 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if mn2 > mn1 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
p1, _ := strconv.Atoi(t.Patch)
|
||||||
|
p2, _ := strconv.Atoi(tag.Patch)
|
||||||
|
if p1 > p2 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if p2 > p1 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsLessThan tests if a tag is less than another. There are some tag-isms to
|
||||||
|
// take into account here, shorter is bigger (i.e. 2.1 < 2.1.1 == false, 2 <
|
||||||
|
// 2.1 == false).
|
||||||
|
func (t Tag) IsLessThan(tag Tag) bool {
|
||||||
|
return !t.IsGreaterThan(tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equals tests Tag equality
|
||||||
|
func (t Tag) Equals(tag Tag) bool {
|
||||||
|
if t.MissingPatch && !tag.MissingPatch || t.MissingMinor && !tag.MissingMinor {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if tag.MissingPatch && !t.MissingPatch || tag.MissingMinor && !t.MissingMinor {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.Metadata != tag.Metadata {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// ignore errors since Parse already handled
|
||||||
|
mj1, _ := strconv.Atoi(t.Major)
|
||||||
|
mj2, _ := strconv.Atoi(tag.Major)
|
||||||
|
if mj1 != mj2 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
mn1, _ := strconv.Atoi(t.Minor)
|
||||||
|
mn2, _ := strconv.Atoi(tag.Minor)
|
||||||
|
if mn1 != mn2 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
p1, _ := strconv.Atoi(t.Patch)
|
||||||
|
p2, _ := strconv.Atoi(tag.Patch)
|
||||||
|
return p1 == p2
|
||||||
|
}
|
||||||
|
|
||||||
|
// String formats a Tag correctly in string representation
|
||||||
|
func (t Tag) String() string {
|
||||||
|
var repr string
|
||||||
|
|
||||||
|
if t.UsesV {
|
||||||
|
repr += "v"
|
||||||
|
}
|
||||||
|
|
||||||
|
repr += t.Major
|
||||||
|
|
||||||
|
if !t.MissingMinor {
|
||||||
|
repr += fmt.Sprintf(".%s", t.Minor)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !t.MissingPatch {
|
||||||
|
repr += fmt.Sprintf(".%s", t.Patch)
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.Suffix != "" {
|
||||||
|
repr += fmt.Sprintf("-%s", t.Suffix)
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.Metadata != "" {
|
||||||
|
repr += fmt.Sprintf("+%s", t.Metadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
return repr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t TagDelta) String() string {
|
||||||
|
var repr string
|
||||||
|
repr = fmt.Sprintf("%d.%d.%d", t.Major, t.Minor, t.Patch)
|
||||||
|
return repr
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsCompatible determines if two tags can be compared together
|
||||||
|
func (t Tag) IsCompatible(tag Tag) bool {
|
||||||
|
if t.UsesV && !tag.UsesV || tag.UsesV && !t.UsesV {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.Suffix != "" && tag.Suffix == "" || t.Suffix == "" && tag.Suffix != "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.Suffix != "" && tag.Suffix != "" {
|
||||||
|
if t.Suffix != tag.Suffix {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.MissingMinor && !tag.MissingMinor || tag.MissingMinor && !t.MissingMinor {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.MissingPatch && !tag.MissingPatch || tag.MissingPatch && !t.MissingPatch {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsUpgradeCompatible chekcs if upTag is compatible with a pinned version tag.
|
||||||
|
// I.e. pinning to 22-fpm should return true if upTag is 22.2.0-fpm but not 22.2.0-alpine or 23.0.0-fpm
|
||||||
|
func (pin Tag) IsUpgradeCompatible(upTag Tag) bool {
|
||||||
|
if pin.Suffix != upTag.Suffix {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if pin.Major != upTag.Major {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if pin.MissingMinor {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if pin.Minor != upTag.Minor {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if pin.MissingPatch {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if pin.Patch != upTag.Patch {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpgradeDelta returns a TagDelta object which is the difference between an old and new tag
|
||||||
|
// It can contain negative numbers if comparing with an older tag.
|
||||||
|
func (curTag Tag) UpgradeDelta(newTag Tag) (TagDelta, error) {
|
||||||
|
if !curTag.IsCompatible(newTag) {
|
||||||
|
return TagDelta{}, fmt.Errorf("%s and %s are not compatible with each other", curTag.String(), newTag.String())
|
||||||
|
}
|
||||||
|
diff := TagDelta{
|
||||||
|
Major: 0,
|
||||||
|
Minor: 0,
|
||||||
|
Patch: 0,
|
||||||
|
}
|
||||||
|
// assuming tags are correctly formatted
|
||||||
|
curMajor, _ := strconv.Atoi(curTag.Major)
|
||||||
|
newMajor, _ := strconv.Atoi(newTag.Major)
|
||||||
|
diff.Major = newMajor - curMajor
|
||||||
|
if !curTag.MissingMinor {
|
||||||
|
curMinor, _ := strconv.Atoi(curTag.Minor)
|
||||||
|
newMinor, _ := strconv.Atoi(newTag.Minor)
|
||||||
|
diff.Minor = newMinor - curMinor
|
||||||
|
}
|
||||||
|
if !curTag.MissingPatch {
|
||||||
|
curPatch, _ := strconv.Atoi(curTag.Patch)
|
||||||
|
newPatch, _ := strconv.Atoi(newTag.Patch)
|
||||||
|
diff.Patch = newPatch - curPatch
|
||||||
|
}
|
||||||
|
|
||||||
|
return diff, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpgradeType takes exit from UpgradeElemene and returns a numeric representation of upgrade or downgrade
|
||||||
|
// 1/-1: patch 2/-2: minor 4/-4: major 0: no change
|
||||||
|
func (d TagDelta) UpgradeType() int {
|
||||||
|
if d.Major > 0 {
|
||||||
|
return 4
|
||||||
|
}
|
||||||
|
if d.Major < 0 {
|
||||||
|
return -4
|
||||||
|
}
|
||||||
|
if d.Minor > 0 {
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
if d.Minor < 0 {
|
||||||
|
return -2
|
||||||
|
}
|
||||||
|
if d.Patch > 0 {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
if d.Patch < 0 {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// CommitHashPattern matches commit-like hash tags
|
||||||
|
var CommitHashPattern = "^[a-f0-9]{7,40}$"
|
||||||
|
|
||||||
|
// DotPattern matches tags which contain multiple versions
|
||||||
|
var DotPattern = "([0-9]+)\\.([0-9]+)"
|
||||||
|
|
||||||
|
// EmptyPattern matches when tags are missing
|
||||||
|
var EmptyPattern = "^$"
|
||||||
|
|
||||||
|
// ParametrizedPattern matches when tags are parametrized
|
||||||
|
var ParametrizedPattern = "\\${.+}"
|
||||||
|
|
||||||
|
// StringPattern matches when tags are only made up of alphabetic characters
|
||||||
|
var StringPattern = "^[a-zA-Z]+$"
|
||||||
|
|
||||||
|
// patternMatches determines if a tag matches unsupported patterns
|
||||||
|
func patternMatches(tag string) error {
|
||||||
|
unsupported := []string{
|
||||||
|
CommitHashPattern,
|
||||||
|
EmptyPattern,
|
||||||
|
ParametrizedPattern,
|
||||||
|
StringPattern,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, pattern := range unsupported {
|
||||||
|
if match, _ := regexp.Match(pattern, []byte(tag)); match {
|
||||||
|
return fmt.Errorf("'%s' is not supported (%s)", tag, pattern)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// patternCounts determines if tags match unsupported patterns by counting occurences of matches
|
||||||
|
func patternCounts(tag string) error {
|
||||||
|
v := regexp.MustCompile(DotPattern)
|
||||||
|
tag = strings.Split(tag, "+")[0]
|
||||||
|
if m := v.FindAllStringIndex(tag, -1); len(m) > 1 {
|
||||||
|
return fmt.Errorf("'%s' is not supported (%s)", tag, DotPattern)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseVersionPart converts a semver version part to an integer
|
||||||
|
func parseVersionPart(part string) (int, error) {
|
||||||
|
p, err := strconv.Atoi(part)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseDelta converts a tag difference in the format of X, X.Y or X.Y.Z where
|
||||||
|
// X, Y, Z are positive or negative integers or 0
|
||||||
|
func ParseDelta(delta string) (TagDelta, error) {
|
||||||
|
tagDelta := TagDelta{
|
||||||
|
Major: 0,
|
||||||
|
Minor: 0,
|
||||||
|
Patch: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
splits := strings.Split(delta, ".")
|
||||||
|
if len(splits) > 3 {
|
||||||
|
return TagDelta{}, fmt.Errorf("'%s' has too much dots", delta)
|
||||||
|
}
|
||||||
|
major, err := strconv.Atoi(splits[0])
|
||||||
|
if err != nil {
|
||||||
|
return TagDelta{}, fmt.Errorf("Major part of '%s' is not an integer", delta)
|
||||||
|
}
|
||||||
|
tagDelta.Major = major
|
||||||
|
|
||||||
|
if len(splits) > 1 {
|
||||||
|
minor, err := strconv.Atoi(splits[1])
|
||||||
|
if err != nil {
|
||||||
|
return TagDelta{}, fmt.Errorf("Minor part of '%s' is not an integer", delta)
|
||||||
|
}
|
||||||
|
tagDelta.Minor = minor
|
||||||
|
}
|
||||||
|
if len(splits) > 2 {
|
||||||
|
patch, err := strconv.Atoi(splits[2])
|
||||||
|
if err != nil {
|
||||||
|
return TagDelta{}, fmt.Errorf("Minor part of '%s' is not an integer", delta)
|
||||||
|
}
|
||||||
|
tagDelta.Patch = patch
|
||||||
|
}
|
||||||
|
return tagDelta, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse converts an image tag into a structured data format. It aims to to
|
||||||
|
// support the general case of tags which are "semver-like" and/or stable and
|
||||||
|
// parseable by heuristics. Image tags follow no formal specification and
|
||||||
|
// therefore this is a best-effort implementation. Examples of tags this
|
||||||
|
// function can parse are: "5", "5.2", "v4", "v5.3.6", "4-alpine",
|
||||||
|
// "v3.2.1-debian".
|
||||||
|
func Parse(tag string) (Tag, error) {
|
||||||
|
if err := patternMatches(tag); err != nil {
|
||||||
|
return Tag{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := patternCounts(tag); err != nil {
|
||||||
|
return Tag{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
usesV := false
|
||||||
|
if string(tag[0]) == "v" {
|
||||||
|
tag = strings.TrimPrefix(tag, "v")
|
||||||
|
usesV = true
|
||||||
|
}
|
||||||
|
|
||||||
|
var metadata string
|
||||||
|
splits := strings.Split(tag, "+")
|
||||||
|
if len(splits) > 1 {
|
||||||
|
tag = splits[0]
|
||||||
|
metadata = splits[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
var suffix string
|
||||||
|
splits = strings.SplitN(tag, "-", 2)
|
||||||
|
if len(splits) > 1 {
|
||||||
|
tag = splits[0]
|
||||||
|
suffix = splits[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
var major, minor, patch string
|
||||||
|
var missingMinor, missingPatch bool
|
||||||
|
parts := strings.Split(tag, ".")
|
||||||
|
switch {
|
||||||
|
case len(parts) == 1:
|
||||||
|
if _, err := parseVersionPart(parts[0]); err != nil {
|
||||||
|
return Tag{}, fmt.Errorf("couldn't parse major part of '%s': '%s'", tag, parts[0])
|
||||||
|
}
|
||||||
|
major = parts[0]
|
||||||
|
missingMinor = true
|
||||||
|
missingPatch = true
|
||||||
|
case len(parts) == 2:
|
||||||
|
if _, err := parseVersionPart(parts[0]); err != nil {
|
||||||
|
return Tag{}, fmt.Errorf("couldn't parse major part of '%s': '%s'", tag, parts[0])
|
||||||
|
}
|
||||||
|
major = parts[0]
|
||||||
|
|
||||||
|
if _, err := parseVersionPart(parts[1]); err != nil {
|
||||||
|
return Tag{}, fmt.Errorf("couldn't parse minor part of '%s': '%s'", tag, parts[1])
|
||||||
|
}
|
||||||
|
minor = parts[1]
|
||||||
|
missingPatch = true
|
||||||
|
case len(parts) == 3:
|
||||||
|
if _, err := parseVersionPart(parts[0]); err != nil {
|
||||||
|
return Tag{}, fmt.Errorf("couldn't parse major part of '%s': '%s'", tag, parts[0])
|
||||||
|
}
|
||||||
|
major = parts[0]
|
||||||
|
|
||||||
|
if _, err := parseVersionPart(parts[1]); err != nil {
|
||||||
|
return Tag{}, fmt.Errorf("couldn't parse minor part of '%s': '%s'", tag, parts[1])
|
||||||
|
}
|
||||||
|
minor = parts[1]
|
||||||
|
|
||||||
|
if _, err := parseVersionPart(parts[2]); err != nil {
|
||||||
|
return Tag{}, fmt.Errorf("couldn't parse patch part of '%s': '%s'", tag, parts[2])
|
||||||
|
}
|
||||||
|
patch = parts[2]
|
||||||
|
default:
|
||||||
|
return Tag{}, fmt.Errorf("couldn't parse semver of '%s", tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
parsedTag := Tag{
|
||||||
|
Major: major,
|
||||||
|
Minor: minor,
|
||||||
|
MissingMinor: missingMinor,
|
||||||
|
Patch: patch,
|
||||||
|
MissingPatch: missingPatch,
|
||||||
|
UsesV: usesV,
|
||||||
|
Suffix: suffix,
|
||||||
|
Metadata: metadata,
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsedTag, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsParsable determines if a tag is supported by this library
|
||||||
|
func IsParsable(tag string) bool {
|
||||||
|
if _, err := Parse(tag); err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
12
vendor/dario.cat/mergo/.deepsource.toml
vendored
Normal file
12
vendor/dario.cat/mergo/.deepsource.toml
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
version = 1
|
||||||
|
|
||||||
|
test_patterns = [
|
||||||
|
"*_test.go"
|
||||||
|
]
|
||||||
|
|
||||||
|
[[analyzers]]
|
||||||
|
name = "go"
|
||||||
|
enabled = true
|
||||||
|
|
||||||
|
[analyzers.meta]
|
||||||
|
import_path = "dario.cat/mergo"
|
33
vendor/dario.cat/mergo/.gitignore
vendored
Normal file
33
vendor/dario.cat/mergo/.gitignore
vendored
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
#### joe made this: http://goel.io/joe
|
||||||
|
|
||||||
|
#### go ####
|
||||||
|
# Binaries for programs and plugins
|
||||||
|
*.exe
|
||||||
|
*.dll
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
|
||||||
|
# Test binary, build with `go test -c`
|
||||||
|
*.test
|
||||||
|
|
||||||
|
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||||
|
*.out
|
||||||
|
|
||||||
|
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
|
||||||
|
.glide/
|
||||||
|
|
||||||
|
#### vim ####
|
||||||
|
# Swap
|
||||||
|
[._]*.s[a-v][a-z]
|
||||||
|
[._]*.sw[a-p]
|
||||||
|
[._]s[a-v][a-z]
|
||||||
|
[._]sw[a-p]
|
||||||
|
|
||||||
|
# Session
|
||||||
|
Session.vim
|
||||||
|
|
||||||
|
# Temporary
|
||||||
|
.netrwhist
|
||||||
|
*~
|
||||||
|
# Auto-generated tag files
|
||||||
|
tags
|
12
vendor/dario.cat/mergo/.travis.yml
vendored
Normal file
12
vendor/dario.cat/mergo/.travis.yml
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
language: go
|
||||||
|
arch:
|
||||||
|
- amd64
|
||||||
|
- ppc64le
|
||||||
|
install:
|
||||||
|
- go get -t
|
||||||
|
- go get golang.org/x/tools/cmd/cover
|
||||||
|
- go get github.com/mattn/goveralls
|
||||||
|
script:
|
||||||
|
- go test -race -v ./...
|
||||||
|
after_script:
|
||||||
|
- $HOME/gopath/bin/goveralls -service=travis-ci -repotoken $COVERALLS_TOKEN
|
46
vendor/dario.cat/mergo/CODE_OF_CONDUCT.md
vendored
Normal file
46
vendor/dario.cat/mergo/CODE_OF_CONDUCT.md
vendored
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
# Contributor Covenant Code of Conduct
|
||||||
|
|
||||||
|
## Our Pledge
|
||||||
|
|
||||||
|
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
||||||
|
|
||||||
|
## Our Standards
|
||||||
|
|
||||||
|
Examples of behavior that contributes to creating a positive environment include:
|
||||||
|
|
||||||
|
* Using welcoming and inclusive language
|
||||||
|
* Being respectful of differing viewpoints and experiences
|
||||||
|
* Gracefully accepting constructive criticism
|
||||||
|
* Focusing on what is best for the community
|
||||||
|
* Showing empathy towards other community members
|
||||||
|
|
||||||
|
Examples of unacceptable behavior by participants include:
|
||||||
|
|
||||||
|
* The use of sexualized language or imagery and unwelcome sexual attention or advances
|
||||||
|
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||||
|
* Public or private harassment
|
||||||
|
* Publishing others' private information, such as a physical or electronic address, without explicit permission
|
||||||
|
* Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||||
|
|
||||||
|
## Our Responsibilities
|
||||||
|
|
||||||
|
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
|
||||||
|
|
||||||
|
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
|
||||||
|
|
||||||
|
## Enforcement
|
||||||
|
|
||||||
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at i@dario.im. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
|
||||||
|
|
||||||
|
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
|
||||||
|
|
||||||
|
## Attribution
|
||||||
|
|
||||||
|
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
|
||||||
|
|
||||||
|
[homepage]: http://contributor-covenant.org
|
||||||
|
[version]: http://contributor-covenant.org/version/1/4/
|
112
vendor/dario.cat/mergo/CONTRIBUTING.md
vendored
Normal file
112
vendor/dario.cat/mergo/CONTRIBUTING.md
vendored
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
<!-- omit in toc -->
|
||||||
|
# Contributing to mergo
|
||||||
|
|
||||||
|
First off, thanks for taking the time to contribute! ❤️
|
||||||
|
|
||||||
|
All types of contributions are encouraged and valued. See the [Table of Contents](#table-of-contents) for different ways to help and details about how this project handles them. Please make sure to read the relevant section before making your contribution. It will make it a lot easier for us maintainers and smooth out the experience for all involved. The community looks forward to your contributions. 🎉
|
||||||
|
|
||||||
|
> And if you like the project, but just don't have time to contribute, that's fine. There are other easy ways to support the project and show your appreciation, which we would also be very happy about:
|
||||||
|
> - Star the project
|
||||||
|
> - Tweet about it
|
||||||
|
> - Refer this project in your project's readme
|
||||||
|
> - Mention the project at local meetups and tell your friends/colleagues
|
||||||
|
|
||||||
|
<!-- omit in toc -->
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
|
- [Code of Conduct](#code-of-conduct)
|
||||||
|
- [I Have a Question](#i-have-a-question)
|
||||||
|
- [I Want To Contribute](#i-want-to-contribute)
|
||||||
|
- [Reporting Bugs](#reporting-bugs)
|
||||||
|
- [Suggesting Enhancements](#suggesting-enhancements)
|
||||||
|
|
||||||
|
## Code of Conduct
|
||||||
|
|
||||||
|
This project and everyone participating in it is governed by the
|
||||||
|
[mergo Code of Conduct](https://github.com/imdario/mergoblob/master/CODE_OF_CONDUCT.md).
|
||||||
|
By participating, you are expected to uphold this code. Please report unacceptable behavior
|
||||||
|
to <>.
|
||||||
|
|
||||||
|
|
||||||
|
## I Have a Question
|
||||||
|
|
||||||
|
> If you want to ask a question, we assume that you have read the available [Documentation](https://pkg.go.dev/github.com/imdario/mergo).
|
||||||
|
|
||||||
|
Before you ask a question, it is best to search for existing [Issues](https://github.com/imdario/mergo/issues) that might help you. In case you have found a suitable issue and still need clarification, you can write your question in this issue. It is also advisable to search the internet for answers first.
|
||||||
|
|
||||||
|
If you then still feel the need to ask a question and need clarification, we recommend the following:
|
||||||
|
|
||||||
|
- Open an [Issue](https://github.com/imdario/mergo/issues/new).
|
||||||
|
- Provide as much context as you can about what you're running into.
|
||||||
|
- Provide project and platform versions (nodejs, npm, etc), depending on what seems relevant.
|
||||||
|
|
||||||
|
We will then take care of the issue as soon as possible.
|
||||||
|
|
||||||
|
## I Want To Contribute
|
||||||
|
|
||||||
|
> ### Legal Notice <!-- omit in toc -->
|
||||||
|
> When contributing to this project, you must agree that you have authored 100% of the content, that you have the necessary rights to the content and that the content you contribute may be provided under the project license.
|
||||||
|
|
||||||
|
### Reporting Bugs
|
||||||
|
|
||||||
|
<!-- omit in toc -->
|
||||||
|
#### Before Submitting a Bug Report
|
||||||
|
|
||||||
|
A good bug report shouldn't leave others needing to chase you up for more information. Therefore, we ask you to investigate carefully, collect information and describe the issue in detail in your report. Please complete the following steps in advance to help us fix any potential bug as fast as possible.
|
||||||
|
|
||||||
|
- Make sure that you are using the latest version.
|
||||||
|
- Determine if your bug is really a bug and not an error on your side e.g. using incompatible environment components/versions (Make sure that you have read the [documentation](). If you are looking for support, you might want to check [this section](#i-have-a-question)).
|
||||||
|
- To see if other users have experienced (and potentially already solved) the same issue you are having, check if there is not already a bug report existing for your bug or error in the [bug tracker](https://github.com/imdario/mergoissues?q=label%3Abug).
|
||||||
|
- Also make sure to search the internet (including Stack Overflow) to see if users outside of the GitHub community have discussed the issue.
|
||||||
|
- Collect information about the bug:
|
||||||
|
- Stack trace (Traceback)
|
||||||
|
- OS, Platform and Version (Windows, Linux, macOS, x86, ARM)
|
||||||
|
- Version of the interpreter, compiler, SDK, runtime environment, package manager, depending on what seems relevant.
|
||||||
|
- Possibly your input and the output
|
||||||
|
- Can you reliably reproduce the issue? And can you also reproduce it with older versions?
|
||||||
|
|
||||||
|
<!-- omit in toc -->
|
||||||
|
#### How Do I Submit a Good Bug Report?
|
||||||
|
|
||||||
|
> You must never report security related issues, vulnerabilities or bugs including sensitive information to the issue tracker, or elsewhere in public. Instead sensitive bugs must be sent by email to .
|
||||||
|
<!-- You may add a PGP key to allow the messages to be sent encrypted as well. -->
|
||||||
|
|
||||||
|
We use GitHub issues to track bugs and errors. If you run into an issue with the project:
|
||||||
|
|
||||||
|
- Open an [Issue](https://github.com/imdario/mergo/issues/new). (Since we can't be sure at this point whether it is a bug or not, we ask you not to talk about a bug yet and not to label the issue.)
|
||||||
|
- Explain the behavior you would expect and the actual behavior.
|
||||||
|
- Please provide as much context as possible and describe the *reproduction steps* that someone else can follow to recreate the issue on their own. This usually includes your code. For good bug reports you should isolate the problem and create a reduced test case.
|
||||||
|
- Provide the information you collected in the previous section.
|
||||||
|
|
||||||
|
Once it's filed:
|
||||||
|
|
||||||
|
- The project team will label the issue accordingly.
|
||||||
|
- A team member will try to reproduce the issue with your provided steps. If there are no reproduction steps or no obvious way to reproduce the issue, the team will ask you for those steps and mark the issue as `needs-repro`. Bugs with the `needs-repro` tag will not be addressed until they are reproduced.
|
||||||
|
- If the team is able to reproduce the issue, it will be marked `needs-fix`, as well as possibly other tags (such as `critical`), and the issue will be left to be implemented by someone.
|
||||||
|
|
||||||
|
### Suggesting Enhancements
|
||||||
|
|
||||||
|
This section guides you through submitting an enhancement suggestion for mergo, **including completely new features and minor improvements to existing functionality**. Following these guidelines will help maintainers and the community to understand your suggestion and find related suggestions.
|
||||||
|
|
||||||
|
<!-- omit in toc -->
|
||||||
|
#### Before Submitting an Enhancement
|
||||||
|
|
||||||
|
- Make sure that you are using the latest version.
|
||||||
|
- Read the [documentation]() carefully and find out if the functionality is already covered, maybe by an individual configuration.
|
||||||
|
- Perform a [search](https://github.com/imdario/mergo/issues) to see if the enhancement has already been suggested. If it has, add a comment to the existing issue instead of opening a new one.
|
||||||
|
- Find out whether your idea fits with the scope and aims of the project. It's up to you to make a strong case to convince the project's developers of the merits of this feature. Keep in mind that we want features that will be useful to the majority of our users and not just a small subset. If you're just targeting a minority of users, consider writing an add-on/plugin library.
|
||||||
|
|
||||||
|
<!-- omit in toc -->
|
||||||
|
#### How Do I Submit a Good Enhancement Suggestion?
|
||||||
|
|
||||||
|
Enhancement suggestions are tracked as [GitHub issues](https://github.com/imdario/mergo/issues).
|
||||||
|
|
||||||
|
- Use a **clear and descriptive title** for the issue to identify the suggestion.
|
||||||
|
- Provide a **step-by-step description of the suggested enhancement** in as many details as possible.
|
||||||
|
- **Describe the current behavior** and **explain which behavior you expected to see instead** and why. At this point you can also tell which alternatives do not work for you.
|
||||||
|
- You may want to **include screenshots and animated GIFs** which help you demonstrate the steps or point out the part which the suggestion is related to. You can use [this tool](https://www.cockos.com/licecap/) to record GIFs on macOS and Windows, and [this tool](https://github.com/colinkeenan/silentcast) or [this tool](https://github.com/GNOME/byzanz) on Linux. <!-- this should only be included if the project has a GUI -->
|
||||||
|
- **Explain why this enhancement would be useful** to most mergo users. You may also want to point out the other projects that solved it better and which could serve as inspiration.
|
||||||
|
|
||||||
|
<!-- omit in toc -->
|
||||||
|
## Attribution
|
||||||
|
This guide is based on the **contributing-gen**. [Make your own](https://github.com/bttger/contributing-gen)!
|
28
vendor/dario.cat/mergo/LICENSE
vendored
Normal file
28
vendor/dario.cat/mergo/LICENSE
vendored
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
Copyright (c) 2013 Dario Castañé. All rights reserved.
|
||||||
|
Copyright (c) 2012 The Go Authors. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
248
vendor/dario.cat/mergo/README.md
vendored
Normal file
248
vendor/dario.cat/mergo/README.md
vendored
Normal file
@ -0,0 +1,248 @@
|
|||||||
|
# Mergo
|
||||||
|
|
||||||
|
[![GitHub release][5]][6]
|
||||||
|
[![GoCard][7]][8]
|
||||||
|
[![Test status][1]][2]
|
||||||
|
[![OpenSSF Scorecard][21]][22]
|
||||||
|
[![OpenSSF Best Practices][19]][20]
|
||||||
|
[![Coverage status][9]][10]
|
||||||
|
[![Sourcegraph][11]][12]
|
||||||
|
[![FOSSA status][13]][14]
|
||||||
|
|
||||||
|
[![GoDoc][3]][4]
|
||||||
|
[![Become my sponsor][15]][16]
|
||||||
|
[![Tidelift][17]][18]
|
||||||
|
|
||||||
|
[1]: https://github.com/imdario/mergo/workflows/tests/badge.svg?branch=master
|
||||||
|
[2]: https://github.com/imdario/mergo/actions/workflows/tests.yml
|
||||||
|
[3]: https://godoc.org/github.com/imdario/mergo?status.svg
|
||||||
|
[4]: https://godoc.org/github.com/imdario/mergo
|
||||||
|
[5]: https://img.shields.io/github/release/imdario/mergo.svg
|
||||||
|
[6]: https://github.com/imdario/mergo/releases
|
||||||
|
[7]: https://goreportcard.com/badge/imdario/mergo
|
||||||
|
[8]: https://goreportcard.com/report/github.com/imdario/mergo
|
||||||
|
[9]: https://coveralls.io/repos/github/imdario/mergo/badge.svg?branch=master
|
||||||
|
[10]: https://coveralls.io/github/imdario/mergo?branch=master
|
||||||
|
[11]: https://sourcegraph.com/github.com/imdario/mergo/-/badge.svg
|
||||||
|
[12]: https://sourcegraph.com/github.com/imdario/mergo?badge
|
||||||
|
[13]: https://app.fossa.io/api/projects/git%2Bgithub.com%2Fimdario%2Fmergo.svg?type=shield
|
||||||
|
[14]: https://app.fossa.io/projects/git%2Bgithub.com%2Fimdario%2Fmergo?ref=badge_shield
|
||||||
|
[15]: https://img.shields.io/github/sponsors/imdario
|
||||||
|
[16]: https://github.com/sponsors/imdario
|
||||||
|
[17]: https://tidelift.com/badges/package/go/github.com%2Fimdario%2Fmergo
|
||||||
|
[18]: https://tidelift.com/subscription/pkg/go-github.com-imdario-mergo
|
||||||
|
[19]: https://bestpractices.coreinfrastructure.org/projects/7177/badge
|
||||||
|
[20]: https://bestpractices.coreinfrastructure.org/projects/7177
|
||||||
|
[21]: https://api.securityscorecards.dev/projects/github.com/imdario/mergo/badge
|
||||||
|
[22]: https://api.securityscorecards.dev/projects/github.com/imdario/mergo
|
||||||
|
|
||||||
|
A helper to merge structs and maps in Golang. Useful for configuration default values, avoiding messy if-statements.
|
||||||
|
|
||||||
|
Mergo merges same-type structs and maps by setting default values in zero-value fields. Mergo won't merge unexported (private) fields. It will do recursively any exported one. It also won't merge structs inside maps (because they are not addressable using Go reflection).
|
||||||
|
|
||||||
|
Also a lovely [comune](http://en.wikipedia.org/wiki/Mergo) (municipality) in the Province of Ancona in the Italian region of Marche.
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
It is ready for production use. [It is used in several projects by Docker, Google, The Linux Foundation, VMWare, Shopify, Microsoft, etc](https://github.com/imdario/mergo#mergo-in-the-wild).
|
||||||
|
|
||||||
|
### Important notes
|
||||||
|
|
||||||
|
#### 1.0.0
|
||||||
|
|
||||||
|
In [1.0.0](//github.com/imdario/mergo/releases/tag/1.0.0) Mergo moves to a vanity URL `dario.cat/mergo`.
|
||||||
|
|
||||||
|
#### 0.3.9
|
||||||
|
|
||||||
|
Please keep in mind that a problematic PR broke [0.3.9](//github.com/imdario/mergo/releases/tag/0.3.9). I reverted it in [0.3.10](//github.com/imdario/mergo/releases/tag/0.3.10), and I consider it stable but not bug-free. Also, this version adds support for go modules.
|
||||||
|
|
||||||
|
Keep in mind that in [0.3.2](//github.com/imdario/mergo/releases/tag/0.3.2), Mergo changed `Merge()`and `Map()` signatures to support [transformers](#transformers). I added an optional/variadic argument so that it won't break the existing code.
|
||||||
|
|
||||||
|
If you were using Mergo before April 6th, 2015, please check your project works as intended after updating your local copy with ```go get -u dario.cat/mergo```. I apologize for any issue caused by its previous behavior and any future bug that Mergo could cause in existing projects after the change (release 0.2.0).
|
||||||
|
|
||||||
|
### Donations
|
||||||
|
|
||||||
|
If Mergo is useful to you, consider buying me a coffee, a beer, or making a monthly donation to allow me to keep building great free software. :heart_eyes:
|
||||||
|
|
||||||
|
<a href='https://ko-fi.com/B0B58839' target='_blank'><img height='36' style='border:0px;height:36px;' src='https://az743702.vo.msecnd.net/cdn/kofi1.png?v=0' border='0' alt='Buy Me a Coffee at ko-fi.com' /></a>
|
||||||
|
<a href="https://liberapay.com/dario/donate"><img alt="Donate using Liberapay" src="https://liberapay.com/assets/widgets/donate.svg"></a>
|
||||||
|
<a href='https://github.com/sponsors/imdario' target='_blank'><img alt="Become my sponsor" src="https://img.shields.io/github/sponsors/imdario?style=for-the-badge" /></a>
|
||||||
|
|
||||||
|
### Mergo in the wild
|
||||||
|
|
||||||
|
- [moby/moby](https://github.com/moby/moby)
|
||||||
|
- [kubernetes/kubernetes](https://github.com/kubernetes/kubernetes)
|
||||||
|
- [vmware/dispatch](https://github.com/vmware/dispatch)
|
||||||
|
- [Shopify/themekit](https://github.com/Shopify/themekit)
|
||||||
|
- [imdario/zas](https://github.com/imdario/zas)
|
||||||
|
- [matcornic/hermes](https://github.com/matcornic/hermes)
|
||||||
|
- [OpenBazaar/openbazaar-go](https://github.com/OpenBazaar/openbazaar-go)
|
||||||
|
- [kataras/iris](https://github.com/kataras/iris)
|
||||||
|
- [michaelsauter/crane](https://github.com/michaelsauter/crane)
|
||||||
|
- [go-task/task](https://github.com/go-task/task)
|
||||||
|
- [sensu/uchiwa](https://github.com/sensu/uchiwa)
|
||||||
|
- [ory/hydra](https://github.com/ory/hydra)
|
||||||
|
- [sisatech/vcli](https://github.com/sisatech/vcli)
|
||||||
|
- [dairycart/dairycart](https://github.com/dairycart/dairycart)
|
||||||
|
- [projectcalico/felix](https://github.com/projectcalico/felix)
|
||||||
|
- [resin-os/balena](https://github.com/resin-os/balena)
|
||||||
|
- [go-kivik/kivik](https://github.com/go-kivik/kivik)
|
||||||
|
- [Telefonica/govice](https://github.com/Telefonica/govice)
|
||||||
|
- [supergiant/supergiant](supergiant/supergiant)
|
||||||
|
- [SergeyTsalkov/brooce](https://github.com/SergeyTsalkov/brooce)
|
||||||
|
- [soniah/dnsmadeeasy](https://github.com/soniah/dnsmadeeasy)
|
||||||
|
- [ohsu-comp-bio/funnel](https://github.com/ohsu-comp-bio/funnel)
|
||||||
|
- [EagerIO/Stout](https://github.com/EagerIO/Stout)
|
||||||
|
- [lynndylanhurley/defsynth-api](https://github.com/lynndylanhurley/defsynth-api)
|
||||||
|
- [russross/canvasassignments](https://github.com/russross/canvasassignments)
|
||||||
|
- [rdegges/cryptly-api](https://github.com/rdegges/cryptly-api)
|
||||||
|
- [casualjim/exeggutor](https://github.com/casualjim/exeggutor)
|
||||||
|
- [divshot/gitling](https://github.com/divshot/gitling)
|
||||||
|
- [RWJMurphy/gorl](https://github.com/RWJMurphy/gorl)
|
||||||
|
- [andrerocker/deploy42](https://github.com/andrerocker/deploy42)
|
||||||
|
- [elwinar/rambler](https://github.com/elwinar/rambler)
|
||||||
|
- [tmaiaroto/gopartman](https://github.com/tmaiaroto/gopartman)
|
||||||
|
- [jfbus/impressionist](https://github.com/jfbus/impressionist)
|
||||||
|
- [Jmeyering/zealot](https://github.com/Jmeyering/zealot)
|
||||||
|
- [godep-migrator/rigger-host](https://github.com/godep-migrator/rigger-host)
|
||||||
|
- [Dronevery/MultiwaySwitch-Go](https://github.com/Dronevery/MultiwaySwitch-Go)
|
||||||
|
- [thoas/picfit](https://github.com/thoas/picfit)
|
||||||
|
- [mantasmatelis/whooplist-server](https://github.com/mantasmatelis/whooplist-server)
|
||||||
|
- [jnuthong/item_search](https://github.com/jnuthong/item_search)
|
||||||
|
- [bukalapak/snowboard](https://github.com/bukalapak/snowboard)
|
||||||
|
- [containerssh/containerssh](https://github.com/containerssh/containerssh)
|
||||||
|
- [goreleaser/goreleaser](https://github.com/goreleaser/goreleaser)
|
||||||
|
- [tjpnz/structbot](https://github.com/tjpnz/structbot)
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
go get dario.cat/mergo
|
||||||
|
|
||||||
|
// use in your .go code
|
||||||
|
import (
|
||||||
|
"dario.cat/mergo"
|
||||||
|
)
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
You can only merge same-type structs with exported fields initialized as zero value of their type and same-types maps. Mergo won't merge unexported (private) fields but will do recursively any exported one. It won't merge empty structs value as [they are zero values](https://golang.org/ref/spec#The_zero_value) too. Also, maps will be merged recursively except for structs inside maps (because they are not addressable using Go reflection).
|
||||||
|
|
||||||
|
```go
|
||||||
|
if err := mergo.Merge(&dst, src); err != nil {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Also, you can merge overwriting values using the transformer `WithOverride`.
|
||||||
|
|
||||||
|
```go
|
||||||
|
if err := mergo.Merge(&dst, src, mergo.WithOverride); err != nil {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Additionally, you can map a `map[string]interface{}` to a struct (and otherwise, from struct to map), following the same restrictions as in `Merge()`. Keys are capitalized to find each corresponding exported field.
|
||||||
|
|
||||||
|
```go
|
||||||
|
if err := mergo.Map(&dst, srcMap); err != nil {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Warning: if you map a struct to map, it won't do it recursively. Don't expect Mergo to map struct members of your struct as `map[string]interface{}`. They will be just assigned as values.
|
||||||
|
|
||||||
|
Here is a nice example:
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"dario.cat/mergo"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Foo struct {
|
||||||
|
A string
|
||||||
|
B int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
src := Foo{
|
||||||
|
A: "one",
|
||||||
|
B: 2,
|
||||||
|
}
|
||||||
|
dest := Foo{
|
||||||
|
A: "two",
|
||||||
|
}
|
||||||
|
mergo.Merge(&dest, src)
|
||||||
|
fmt.Println(dest)
|
||||||
|
// Will print
|
||||||
|
// {two 2}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Note: if test are failing due missing package, please execute:
|
||||||
|
|
||||||
|
go get gopkg.in/yaml.v3
|
||||||
|
|
||||||
|
### Transformers
|
||||||
|
|
||||||
|
Transformers allow to merge specific types differently than in the default behavior. In other words, now you can customize how some types are merged. For example, `time.Time` is a struct; it doesn't have zero value but IsZero can return true because it has fields with zero value. How can we merge a non-zero `time.Time`?
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"dario.cat/mergo"
|
||||||
|
"reflect"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type timeTransformer struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t timeTransformer) Transformer(typ reflect.Type) func(dst, src reflect.Value) error {
|
||||||
|
if typ == reflect.TypeOf(time.Time{}) {
|
||||||
|
return func(dst, src reflect.Value) error {
|
||||||
|
if dst.CanSet() {
|
||||||
|
isZero := dst.MethodByName("IsZero")
|
||||||
|
result := isZero.Call([]reflect.Value{})
|
||||||
|
if result[0].Bool() {
|
||||||
|
dst.Set(src)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Snapshot struct {
|
||||||
|
Time time.Time
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
src := Snapshot{time.Now()}
|
||||||
|
dest := Snapshot{}
|
||||||
|
mergo.Merge(&dest, src, mergo.WithTransformers(timeTransformer{}))
|
||||||
|
fmt.Println(dest)
|
||||||
|
// Will print
|
||||||
|
// { 2018-01-12 01:15:00 +0000 UTC m=+0.000000001 }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Contact me
|
||||||
|
|
||||||
|
If I can help you, you have an idea or you are using Mergo in your projects, don't hesitate to drop me a line (or a pull request): [@im_dario](https://twitter.com/im_dario)
|
||||||
|
|
||||||
|
## About
|
||||||
|
|
||||||
|
Written by [Dario Castañé](http://dario.im).
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
[BSD 3-Clause](http://opensource.org/licenses/BSD-3-Clause) license, as [Go language](http://golang.org/LICENSE).
|
||||||
|
|
||||||
|
[](https://app.fossa.io/projects/git%2Bgithub.com%2Fimdario%2Fmergo?ref=badge_large)
|
14
vendor/dario.cat/mergo/SECURITY.md
vendored
Normal file
14
vendor/dario.cat/mergo/SECURITY.md
vendored
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
# Security Policy
|
||||||
|
|
||||||
|
## Supported Versions
|
||||||
|
|
||||||
|
| Version | Supported |
|
||||||
|
| ------- | ------------------ |
|
||||||
|
| 0.3.x | :white_check_mark: |
|
||||||
|
| < 0.3 | :x: |
|
||||||
|
|
||||||
|
## Security contact information
|
||||||
|
|
||||||
|
To report a security vulnerability, please use the
|
||||||
|
[Tidelift security contact](https://tidelift.com/security).
|
||||||
|
Tidelift will coordinate the fix and disclosure.
|
148
vendor/dario.cat/mergo/doc.go
vendored
Normal file
148
vendor/dario.cat/mergo/doc.go
vendored
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
// Copyright 2013 Dario Castañé. All rights reserved.
|
||||||
|
// Copyright 2009 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
/*
|
||||||
|
A helper to merge structs and maps in Golang. Useful for configuration default values, avoiding messy if-statements.
|
||||||
|
|
||||||
|
Mergo merges same-type structs and maps by setting default values in zero-value fields. Mergo won't merge unexported (private) fields. It will do recursively any exported one. It also won't merge structs inside maps (because they are not addressable using Go reflection).
|
||||||
|
|
||||||
|
# Status
|
||||||
|
|
||||||
|
It is ready for production use. It is used in several projects by Docker, Google, The Linux Foundation, VMWare, Shopify, etc.
|
||||||
|
|
||||||
|
# Important notes
|
||||||
|
|
||||||
|
1.0.0
|
||||||
|
|
||||||
|
In 1.0.0 Mergo moves to a vanity URL `dario.cat/mergo`.
|
||||||
|
|
||||||
|
0.3.9
|
||||||
|
|
||||||
|
Please keep in mind that a problematic PR broke 0.3.9. We reverted it in 0.3.10. We consider 0.3.10 as stable but not bug-free. . Also, this version adds suppot for go modules.
|
||||||
|
|
||||||
|
Keep in mind that in 0.3.2, Mergo changed Merge() and Map() signatures to support transformers. We added an optional/variadic argument so that it won't break the existing code.
|
||||||
|
|
||||||
|
If you were using Mergo before April 6th, 2015, please check your project works as intended after updating your local copy with go get -u dario.cat/mergo. I apologize for any issue caused by its previous behavior and any future bug that Mergo could cause in existing projects after the change (release 0.2.0).
|
||||||
|
|
||||||
|
# Install
|
||||||
|
|
||||||
|
Do your usual installation procedure:
|
||||||
|
|
||||||
|
go get dario.cat/mergo
|
||||||
|
|
||||||
|
// use in your .go code
|
||||||
|
import (
|
||||||
|
"dario.cat/mergo"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Usage
|
||||||
|
|
||||||
|
You can only merge same-type structs with exported fields initialized as zero value of their type and same-types maps. Mergo won't merge unexported (private) fields but will do recursively any exported one. It won't merge empty structs value as they are zero values too. Also, maps will be merged recursively except for structs inside maps (because they are not addressable using Go reflection).
|
||||||
|
|
||||||
|
if err := mergo.Merge(&dst, src); err != nil {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
Also, you can merge overwriting values using the transformer WithOverride.
|
||||||
|
|
||||||
|
if err := mergo.Merge(&dst, src, mergo.WithOverride); err != nil {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
Additionally, you can map a map[string]interface{} to a struct (and otherwise, from struct to map), following the same restrictions as in Merge(). Keys are capitalized to find each corresponding exported field.
|
||||||
|
|
||||||
|
if err := mergo.Map(&dst, srcMap); err != nil {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
Warning: if you map a struct to map, it won't do it recursively. Don't expect Mergo to map struct members of your struct as map[string]interface{}. They will be just assigned as values.
|
||||||
|
|
||||||
|
Here is a nice example:
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"dario.cat/mergo"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Foo struct {
|
||||||
|
A string
|
||||||
|
B int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
src := Foo{
|
||||||
|
A: "one",
|
||||||
|
B: 2,
|
||||||
|
}
|
||||||
|
dest := Foo{
|
||||||
|
A: "two",
|
||||||
|
}
|
||||||
|
mergo.Merge(&dest, src)
|
||||||
|
fmt.Println(dest)
|
||||||
|
// Will print
|
||||||
|
// {two 2}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Transformers
|
||||||
|
|
||||||
|
Transformers allow to merge specific types differently than in the default behavior. In other words, now you can customize how some types are merged. For example, time.Time is a struct; it doesn't have zero value but IsZero can return true because it has fields with zero value. How can we merge a non-zero time.Time?
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"dario.cat/mergo"
|
||||||
|
"reflect"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type timeTransformer struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t timeTransformer) Transformer(typ reflect.Type) func(dst, src reflect.Value) error {
|
||||||
|
if typ == reflect.TypeOf(time.Time{}) {
|
||||||
|
return func(dst, src reflect.Value) error {
|
||||||
|
if dst.CanSet() {
|
||||||
|
isZero := dst.MethodByName("IsZero")
|
||||||
|
result := isZero.Call([]reflect.Value{})
|
||||||
|
if result[0].Bool() {
|
||||||
|
dst.Set(src)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Snapshot struct {
|
||||||
|
Time time.Time
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
src := Snapshot{time.Now()}
|
||||||
|
dest := Snapshot{}
|
||||||
|
mergo.Merge(&dest, src, mergo.WithTransformers(timeTransformer{}))
|
||||||
|
fmt.Println(dest)
|
||||||
|
// Will print
|
||||||
|
// { 2018-01-12 01:15:00 +0000 UTC m=+0.000000001 }
|
||||||
|
}
|
||||||
|
|
||||||
|
# Contact me
|
||||||
|
|
||||||
|
If I can help you, you have an idea or you are using Mergo in your projects, don't hesitate to drop me a line (or a pull request): https://twitter.com/im_dario
|
||||||
|
|
||||||
|
# About
|
||||||
|
|
||||||
|
Written by Dario Castañé: https://da.rio.hn
|
||||||
|
|
||||||
|
# License
|
||||||
|
|
||||||
|
BSD 3-Clause license, as Go language.
|
||||||
|
*/
|
||||||
|
package mergo
|
178
vendor/dario.cat/mergo/map.go
vendored
Normal file
178
vendor/dario.cat/mergo/map.go
vendored
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
// Copyright 2014 Dario Castañé. All rights reserved.
|
||||||
|
// Copyright 2009 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Based on src/pkg/reflect/deepequal.go from official
|
||||||
|
// golang's stdlib.
|
||||||
|
|
||||||
|
package mergo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"unicode"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
func changeInitialCase(s string, mapper func(rune) rune) string {
|
||||||
|
if s == "" {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
r, n := utf8.DecodeRuneInString(s)
|
||||||
|
return string(mapper(r)) + s[n:]
|
||||||
|
}
|
||||||
|
|
||||||
|
func isExported(field reflect.StructField) bool {
|
||||||
|
r, _ := utf8.DecodeRuneInString(field.Name)
|
||||||
|
return r >= 'A' && r <= 'Z'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Traverses recursively both values, assigning src's fields values to dst.
|
||||||
|
// The map argument tracks comparisons that have already been seen, which allows
|
||||||
|
// short circuiting on recursive types.
|
||||||
|
func deepMap(dst, src reflect.Value, visited map[uintptr]*visit, depth int, config *Config) (err error) {
|
||||||
|
overwrite := config.Overwrite
|
||||||
|
if dst.CanAddr() {
|
||||||
|
addr := dst.UnsafeAddr()
|
||||||
|
h := 17 * addr
|
||||||
|
seen := visited[h]
|
||||||
|
typ := dst.Type()
|
||||||
|
for p := seen; p != nil; p = p.next {
|
||||||
|
if p.ptr == addr && p.typ == typ {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Remember, remember...
|
||||||
|
visited[h] = &visit{typ, seen, addr}
|
||||||
|
}
|
||||||
|
zeroValue := reflect.Value{}
|
||||||
|
switch dst.Kind() {
|
||||||
|
case reflect.Map:
|
||||||
|
dstMap := dst.Interface().(map[string]interface{})
|
||||||
|
for i, n := 0, src.NumField(); i < n; i++ {
|
||||||
|
srcType := src.Type()
|
||||||
|
field := srcType.Field(i)
|
||||||
|
if !isExported(field) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fieldName := field.Name
|
||||||
|
fieldName = changeInitialCase(fieldName, unicode.ToLower)
|
||||||
|
if v, ok := dstMap[fieldName]; !ok || (isEmptyValue(reflect.ValueOf(v), !config.ShouldNotDereference) || overwrite) {
|
||||||
|
dstMap[fieldName] = src.Field(i).Interface()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.Ptr:
|
||||||
|
if dst.IsNil() {
|
||||||
|
v := reflect.New(dst.Type().Elem())
|
||||||
|
dst.Set(v)
|
||||||
|
}
|
||||||
|
dst = dst.Elem()
|
||||||
|
fallthrough
|
||||||
|
case reflect.Struct:
|
||||||
|
srcMap := src.Interface().(map[string]interface{})
|
||||||
|
for key := range srcMap {
|
||||||
|
config.overwriteWithEmptyValue = true
|
||||||
|
srcValue := srcMap[key]
|
||||||
|
fieldName := changeInitialCase(key, unicode.ToUpper)
|
||||||
|
dstElement := dst.FieldByName(fieldName)
|
||||||
|
if dstElement == zeroValue {
|
||||||
|
// We discard it because the field doesn't exist.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
srcElement := reflect.ValueOf(srcValue)
|
||||||
|
dstKind := dstElement.Kind()
|
||||||
|
srcKind := srcElement.Kind()
|
||||||
|
if srcKind == reflect.Ptr && dstKind != reflect.Ptr {
|
||||||
|
srcElement = srcElement.Elem()
|
||||||
|
srcKind = reflect.TypeOf(srcElement.Interface()).Kind()
|
||||||
|
} else if dstKind == reflect.Ptr {
|
||||||
|
// Can this work? I guess it can't.
|
||||||
|
if srcKind != reflect.Ptr && srcElement.CanAddr() {
|
||||||
|
srcPtr := srcElement.Addr()
|
||||||
|
srcElement = reflect.ValueOf(srcPtr)
|
||||||
|
srcKind = reflect.Ptr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !srcElement.IsValid() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if srcKind == dstKind {
|
||||||
|
if err = deepMerge(dstElement, srcElement, visited, depth+1, config); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else if dstKind == reflect.Interface && dstElement.Kind() == reflect.Interface {
|
||||||
|
if err = deepMerge(dstElement, srcElement, visited, depth+1, config); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else if srcKind == reflect.Map {
|
||||||
|
if err = deepMap(dstElement, srcElement, visited, depth+1, config); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("type mismatch on %s field: found %v, expected %v", fieldName, srcKind, dstKind)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map sets fields' values in dst from src.
|
||||||
|
// src can be a map with string keys or a struct. dst must be the opposite:
|
||||||
|
// if src is a map, dst must be a valid pointer to struct. If src is a struct,
|
||||||
|
// dst must be map[string]interface{}.
|
||||||
|
// It won't merge unexported (private) fields and will do recursively
|
||||||
|
// any exported field.
|
||||||
|
// If dst is a map, keys will be src fields' names in lower camel case.
|
||||||
|
// Missing key in src that doesn't match a field in dst will be skipped. This
|
||||||
|
// doesn't apply if dst is a map.
|
||||||
|
// This is separated method from Merge because it is cleaner and it keeps sane
|
||||||
|
// semantics: merging equal types, mapping different (restricted) types.
|
||||||
|
func Map(dst, src interface{}, opts ...func(*Config)) error {
|
||||||
|
return _map(dst, src, opts...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MapWithOverwrite will do the same as Map except that non-empty dst attributes will be overridden by
|
||||||
|
// non-empty src attribute values.
|
||||||
|
// Deprecated: Use Map(…) with WithOverride
|
||||||
|
func MapWithOverwrite(dst, src interface{}, opts ...func(*Config)) error {
|
||||||
|
return _map(dst, src, append(opts, WithOverride)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _map(dst, src interface{}, opts ...func(*Config)) error {
|
||||||
|
if dst != nil && reflect.ValueOf(dst).Kind() != reflect.Ptr {
|
||||||
|
return ErrNonPointerArgument
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
vDst, vSrc reflect.Value
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
config := &Config{}
|
||||||
|
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
if vDst, vSrc, err = resolveValues(dst, src); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// To be friction-less, we redirect equal-type arguments
|
||||||
|
// to deepMerge. Only because arguments can be anything.
|
||||||
|
if vSrc.Kind() == vDst.Kind() {
|
||||||
|
return deepMerge(vDst, vSrc, make(map[uintptr]*visit), 0, config)
|
||||||
|
}
|
||||||
|
switch vSrc.Kind() {
|
||||||
|
case reflect.Struct:
|
||||||
|
if vDst.Kind() != reflect.Map {
|
||||||
|
return ErrExpectedMapAsDestination
|
||||||
|
}
|
||||||
|
case reflect.Map:
|
||||||
|
if vDst.Kind() != reflect.Struct {
|
||||||
|
return ErrExpectedStructAsDestination
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return ErrNotSupported
|
||||||
|
}
|
||||||
|
return deepMap(vDst, vSrc, make(map[uintptr]*visit), 0, config)
|
||||||
|
}
|
409
vendor/dario.cat/mergo/merge.go
vendored
Normal file
409
vendor/dario.cat/mergo/merge.go
vendored
Normal file
@ -0,0 +1,409 @@
|
|||||||
|
// Copyright 2013 Dario Castañé. All rights reserved.
|
||||||
|
// Copyright 2009 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Based on src/pkg/reflect/deepequal.go from official
|
||||||
|
// golang's stdlib.
|
||||||
|
|
||||||
|
package mergo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
func hasMergeableFields(dst reflect.Value) (exported bool) {
|
||||||
|
for i, n := 0, dst.NumField(); i < n; i++ {
|
||||||
|
field := dst.Type().Field(i)
|
||||||
|
if field.Anonymous && dst.Field(i).Kind() == reflect.Struct {
|
||||||
|
exported = exported || hasMergeableFields(dst.Field(i))
|
||||||
|
} else if isExportedComponent(&field) {
|
||||||
|
exported = exported || len(field.PkgPath) == 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func isExportedComponent(field *reflect.StructField) bool {
|
||||||
|
pkgPath := field.PkgPath
|
||||||
|
if len(pkgPath) > 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
c := field.Name[0]
|
||||||
|
if 'a' <= c && c <= 'z' || c == '_' {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
Transformers Transformers
|
||||||
|
Overwrite bool
|
||||||
|
ShouldNotDereference bool
|
||||||
|
AppendSlice bool
|
||||||
|
TypeCheck bool
|
||||||
|
overwriteWithEmptyValue bool
|
||||||
|
overwriteSliceWithEmptyValue bool
|
||||||
|
sliceDeepCopy bool
|
||||||
|
debug bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type Transformers interface {
|
||||||
|
Transformer(reflect.Type) func(dst, src reflect.Value) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Traverses recursively both values, assigning src's fields values to dst.
|
||||||
|
// The map argument tracks comparisons that have already been seen, which allows
|
||||||
|
// short circuiting on recursive types.
|
||||||
|
func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, config *Config) (err error) {
|
||||||
|
overwrite := config.Overwrite
|
||||||
|
typeCheck := config.TypeCheck
|
||||||
|
overwriteWithEmptySrc := config.overwriteWithEmptyValue
|
||||||
|
overwriteSliceWithEmptySrc := config.overwriteSliceWithEmptyValue
|
||||||
|
sliceDeepCopy := config.sliceDeepCopy
|
||||||
|
|
||||||
|
if !src.IsValid() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if dst.CanAddr() {
|
||||||
|
addr := dst.UnsafeAddr()
|
||||||
|
h := 17 * addr
|
||||||
|
seen := visited[h]
|
||||||
|
typ := dst.Type()
|
||||||
|
for p := seen; p != nil; p = p.next {
|
||||||
|
if p.ptr == addr && p.typ == typ {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Remember, remember...
|
||||||
|
visited[h] = &visit{typ, seen, addr}
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.Transformers != nil && !isReflectNil(dst) && dst.IsValid() {
|
||||||
|
if fn := config.Transformers.Transformer(dst.Type()); fn != nil {
|
||||||
|
err = fn(dst, src)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch dst.Kind() {
|
||||||
|
case reflect.Struct:
|
||||||
|
if hasMergeableFields(dst) {
|
||||||
|
for i, n := 0, dst.NumField(); i < n; i++ {
|
||||||
|
if err = deepMerge(dst.Field(i), src.Field(i), visited, depth+1, config); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if dst.CanSet() && (isReflectNil(dst) || overwrite) && (!isEmptyValue(src, !config.ShouldNotDereference) || overwriteWithEmptySrc) {
|
||||||
|
dst.Set(src)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.Map:
|
||||||
|
if dst.IsNil() && !src.IsNil() {
|
||||||
|
if dst.CanSet() {
|
||||||
|
dst.Set(reflect.MakeMap(dst.Type()))
|
||||||
|
} else {
|
||||||
|
dst = src
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if src.Kind() != reflect.Map {
|
||||||
|
if overwrite && dst.CanSet() {
|
||||||
|
dst.Set(src)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, key := range src.MapKeys() {
|
||||||
|
srcElement := src.MapIndex(key)
|
||||||
|
if !srcElement.IsValid() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
dstElement := dst.MapIndex(key)
|
||||||
|
switch srcElement.Kind() {
|
||||||
|
case reflect.Chan, reflect.Func, reflect.Map, reflect.Interface, reflect.Slice:
|
||||||
|
if srcElement.IsNil() {
|
||||||
|
if overwrite {
|
||||||
|
dst.SetMapIndex(key, srcElement)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fallthrough
|
||||||
|
default:
|
||||||
|
if !srcElement.CanInterface() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch reflect.TypeOf(srcElement.Interface()).Kind() {
|
||||||
|
case reflect.Struct:
|
||||||
|
fallthrough
|
||||||
|
case reflect.Ptr:
|
||||||
|
fallthrough
|
||||||
|
case reflect.Map:
|
||||||
|
srcMapElm := srcElement
|
||||||
|
dstMapElm := dstElement
|
||||||
|
if srcMapElm.CanInterface() {
|
||||||
|
srcMapElm = reflect.ValueOf(srcMapElm.Interface())
|
||||||
|
if dstMapElm.IsValid() {
|
||||||
|
dstMapElm = reflect.ValueOf(dstMapElm.Interface())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err = deepMerge(dstMapElm, srcMapElm, visited, depth+1, config); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case reflect.Slice:
|
||||||
|
srcSlice := reflect.ValueOf(srcElement.Interface())
|
||||||
|
|
||||||
|
var dstSlice reflect.Value
|
||||||
|
if !dstElement.IsValid() || dstElement.IsNil() {
|
||||||
|
dstSlice = reflect.MakeSlice(srcSlice.Type(), 0, srcSlice.Len())
|
||||||
|
} else {
|
||||||
|
dstSlice = reflect.ValueOf(dstElement.Interface())
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isEmptyValue(src, !config.ShouldNotDereference) || overwriteWithEmptySrc || overwriteSliceWithEmptySrc) && (overwrite || isEmptyValue(dst, !config.ShouldNotDereference)) && !config.AppendSlice && !sliceDeepCopy {
|
||||||
|
if typeCheck && srcSlice.Type() != dstSlice.Type() {
|
||||||
|
return fmt.Errorf("cannot override two slices with different type (%s, %s)", srcSlice.Type(), dstSlice.Type())
|
||||||
|
}
|
||||||
|
dstSlice = srcSlice
|
||||||
|
} else if config.AppendSlice {
|
||||||
|
if srcSlice.Type() != dstSlice.Type() {
|
||||||
|
return fmt.Errorf("cannot append two slices with different type (%s, %s)", srcSlice.Type(), dstSlice.Type())
|
||||||
|
}
|
||||||
|
dstSlice = reflect.AppendSlice(dstSlice, srcSlice)
|
||||||
|
} else if sliceDeepCopy {
|
||||||
|
i := 0
|
||||||
|
for ; i < srcSlice.Len() && i < dstSlice.Len(); i++ {
|
||||||
|
srcElement := srcSlice.Index(i)
|
||||||
|
dstElement := dstSlice.Index(i)
|
||||||
|
|
||||||
|
if srcElement.CanInterface() {
|
||||||
|
srcElement = reflect.ValueOf(srcElement.Interface())
|
||||||
|
}
|
||||||
|
if dstElement.CanInterface() {
|
||||||
|
dstElement = reflect.ValueOf(dstElement.Interface())
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = deepMerge(dstElement, srcElement, visited, depth+1, config); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
dst.SetMapIndex(key, dstSlice)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if dstElement.IsValid() && !isEmptyValue(dstElement, !config.ShouldNotDereference) {
|
||||||
|
if reflect.TypeOf(srcElement.Interface()).Kind() == reflect.Slice {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if reflect.TypeOf(srcElement.Interface()).Kind() == reflect.Map && reflect.TypeOf(dstElement.Interface()).Kind() == reflect.Map {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if srcElement.IsValid() && ((srcElement.Kind() != reflect.Ptr && overwrite) || !dstElement.IsValid() || isEmptyValue(dstElement, !config.ShouldNotDereference)) {
|
||||||
|
if dst.IsNil() {
|
||||||
|
dst.Set(reflect.MakeMap(dst.Type()))
|
||||||
|
}
|
||||||
|
dst.SetMapIndex(key, srcElement)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure that all keys in dst are deleted if they are not in src.
|
||||||
|
if overwriteWithEmptySrc {
|
||||||
|
for _, key := range dst.MapKeys() {
|
||||||
|
srcElement := src.MapIndex(key)
|
||||||
|
if !srcElement.IsValid() {
|
||||||
|
dst.SetMapIndex(key, reflect.Value{})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.Slice:
|
||||||
|
if !dst.CanSet() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if (!isEmptyValue(src, !config.ShouldNotDereference) || overwriteWithEmptySrc || overwriteSliceWithEmptySrc) && (overwrite || isEmptyValue(dst, !config.ShouldNotDereference)) && !config.AppendSlice && !sliceDeepCopy {
|
||||||
|
dst.Set(src)
|
||||||
|
} else if config.AppendSlice {
|
||||||
|
if src.Type() != dst.Type() {
|
||||||
|
return fmt.Errorf("cannot append two slice with different type (%s, %s)", src.Type(), dst.Type())
|
||||||
|
}
|
||||||
|
dst.Set(reflect.AppendSlice(dst, src))
|
||||||
|
} else if sliceDeepCopy {
|
||||||
|
for i := 0; i < src.Len() && i < dst.Len(); i++ {
|
||||||
|
srcElement := src.Index(i)
|
||||||
|
dstElement := dst.Index(i)
|
||||||
|
if srcElement.CanInterface() {
|
||||||
|
srcElement = reflect.ValueOf(srcElement.Interface())
|
||||||
|
}
|
||||||
|
if dstElement.CanInterface() {
|
||||||
|
dstElement = reflect.ValueOf(dstElement.Interface())
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = deepMerge(dstElement, srcElement, visited, depth+1, config); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.Ptr:
|
||||||
|
fallthrough
|
||||||
|
case reflect.Interface:
|
||||||
|
if isReflectNil(src) {
|
||||||
|
if overwriteWithEmptySrc && dst.CanSet() && src.Type().AssignableTo(dst.Type()) {
|
||||||
|
dst.Set(src)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if src.Kind() != reflect.Interface {
|
||||||
|
if dst.IsNil() || (src.Kind() != reflect.Ptr && overwrite) {
|
||||||
|
if dst.CanSet() && (overwrite || isEmptyValue(dst, !config.ShouldNotDereference)) {
|
||||||
|
dst.Set(src)
|
||||||
|
}
|
||||||
|
} else if src.Kind() == reflect.Ptr {
|
||||||
|
if !config.ShouldNotDereference {
|
||||||
|
if err = deepMerge(dst.Elem(), src.Elem(), visited, depth+1, config); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if overwriteWithEmptySrc || (overwrite && !src.IsNil()) || dst.IsNil() {
|
||||||
|
dst.Set(src)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if dst.Elem().Type() == src.Type() {
|
||||||
|
if err = deepMerge(dst.Elem(), src, visited, depth+1, config); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return ErrDifferentArgumentsTypes
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if dst.IsNil() || overwrite {
|
||||||
|
if dst.CanSet() && (overwrite || isEmptyValue(dst, !config.ShouldNotDereference)) {
|
||||||
|
dst.Set(src)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if dst.Elem().Kind() == src.Elem().Kind() {
|
||||||
|
if err = deepMerge(dst.Elem(), src.Elem(), visited, depth+1, config); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
mustSet := (isEmptyValue(dst, !config.ShouldNotDereference) || overwrite) && (!isEmptyValue(src, !config.ShouldNotDereference) || overwriteWithEmptySrc)
|
||||||
|
if mustSet {
|
||||||
|
if dst.CanSet() {
|
||||||
|
dst.Set(src)
|
||||||
|
} else {
|
||||||
|
dst = src
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge will fill any empty for value type attributes on the dst struct using corresponding
|
||||||
|
// src attributes if they themselves are not empty. dst and src must be valid same-type structs
|
||||||
|
// and dst must be a pointer to struct.
|
||||||
|
// It won't merge unexported (private) fields and will do recursively any exported field.
|
||||||
|
func Merge(dst, src interface{}, opts ...func(*Config)) error {
|
||||||
|
return merge(dst, src, opts...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MergeWithOverwrite will do the same as Merge except that non-empty dst attributes will be overridden by
|
||||||
|
// non-empty src attribute values.
|
||||||
|
// Deprecated: use Merge(…) with WithOverride
|
||||||
|
func MergeWithOverwrite(dst, src interface{}, opts ...func(*Config)) error {
|
||||||
|
return merge(dst, src, append(opts, WithOverride)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithTransformers adds transformers to merge, allowing to customize the merging of some types.
|
||||||
|
func WithTransformers(transformers Transformers) func(*Config) {
|
||||||
|
return func(config *Config) {
|
||||||
|
config.Transformers = transformers
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithOverride will make merge override non-empty dst attributes with non-empty src attributes values.
|
||||||
|
func WithOverride(config *Config) {
|
||||||
|
config.Overwrite = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithOverwriteWithEmptyValue will make merge override non empty dst attributes with empty src attributes values.
|
||||||
|
func WithOverwriteWithEmptyValue(config *Config) {
|
||||||
|
config.Overwrite = true
|
||||||
|
config.overwriteWithEmptyValue = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithOverrideEmptySlice will make merge override empty dst slice with empty src slice.
|
||||||
|
func WithOverrideEmptySlice(config *Config) {
|
||||||
|
config.overwriteSliceWithEmptyValue = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithoutDereference prevents dereferencing pointers when evaluating whether they are empty
|
||||||
|
// (i.e. a non-nil pointer is never considered empty).
|
||||||
|
func WithoutDereference(config *Config) {
|
||||||
|
config.ShouldNotDereference = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithAppendSlice will make merge append slices instead of overwriting it.
|
||||||
|
func WithAppendSlice(config *Config) {
|
||||||
|
config.AppendSlice = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithTypeCheck will make merge check types while overwriting it (must be used with WithOverride).
|
||||||
|
func WithTypeCheck(config *Config) {
|
||||||
|
config.TypeCheck = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithSliceDeepCopy will merge slice element one by one with Overwrite flag.
|
||||||
|
func WithSliceDeepCopy(config *Config) {
|
||||||
|
config.sliceDeepCopy = true
|
||||||
|
config.Overwrite = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func merge(dst, src interface{}, opts ...func(*Config)) error {
|
||||||
|
if dst != nil && reflect.ValueOf(dst).Kind() != reflect.Ptr {
|
||||||
|
return ErrNonPointerArgument
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
vDst, vSrc reflect.Value
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
config := &Config{}
|
||||||
|
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
if vDst, vSrc, err = resolveValues(dst, src); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if vDst.Type() != vSrc.Type() {
|
||||||
|
return ErrDifferentArgumentsTypes
|
||||||
|
}
|
||||||
|
return deepMerge(vDst, vSrc, make(map[uintptr]*visit), 0, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsReflectNil is the reflect value provided nil
|
||||||
|
func isReflectNil(v reflect.Value) bool {
|
||||||
|
k := v.Kind()
|
||||||
|
switch k {
|
||||||
|
case reflect.Interface, reflect.Slice, reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr:
|
||||||
|
// Both interface and slice are nil if first word is 0.
|
||||||
|
// Both are always bigger than a word; assume flagIndir.
|
||||||
|
return v.IsNil()
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
81
vendor/dario.cat/mergo/mergo.go
vendored
Normal file
81
vendor/dario.cat/mergo/mergo.go
vendored
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
// Copyright 2013 Dario Castañé. All rights reserved.
|
||||||
|
// Copyright 2009 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Based on src/pkg/reflect/deepequal.go from official
|
||||||
|
// golang's stdlib.
|
||||||
|
|
||||||
|
package mergo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Errors reported by Mergo when it finds invalid arguments.
|
||||||
|
var (
|
||||||
|
ErrNilArguments = errors.New("src and dst must not be nil")
|
||||||
|
ErrDifferentArgumentsTypes = errors.New("src and dst must be of same type")
|
||||||
|
ErrNotSupported = errors.New("only structs, maps, and slices are supported")
|
||||||
|
ErrExpectedMapAsDestination = errors.New("dst was expected to be a map")
|
||||||
|
ErrExpectedStructAsDestination = errors.New("dst was expected to be a struct")
|
||||||
|
ErrNonPointerArgument = errors.New("dst must be a pointer")
|
||||||
|
)
|
||||||
|
|
||||||
|
// During deepMerge, must keep track of checks that are
|
||||||
|
// in progress. The comparison algorithm assumes that all
|
||||||
|
// checks in progress are true when it reencounters them.
|
||||||
|
// Visited are stored in a map indexed by 17 * a1 + a2;
|
||||||
|
type visit struct {
|
||||||
|
typ reflect.Type
|
||||||
|
next *visit
|
||||||
|
ptr uintptr
|
||||||
|
}
|
||||||
|
|
||||||
|
// From src/pkg/encoding/json/encode.go.
|
||||||
|
func isEmptyValue(v reflect.Value, shouldDereference bool) bool {
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
|
||||||
|
return v.Len() == 0
|
||||||
|
case reflect.Bool:
|
||||||
|
return !v.Bool()
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
return v.Int() == 0
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||||
|
return v.Uint() == 0
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
return v.Float() == 0
|
||||||
|
case reflect.Interface, reflect.Ptr:
|
||||||
|
if v.IsNil() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if shouldDereference {
|
||||||
|
return isEmptyValue(v.Elem(), shouldDereference)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
case reflect.Func:
|
||||||
|
return v.IsNil()
|
||||||
|
case reflect.Invalid:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func resolveValues(dst, src interface{}) (vDst, vSrc reflect.Value, err error) {
|
||||||
|
if dst == nil || src == nil {
|
||||||
|
err = ErrNilArguments
|
||||||
|
return
|
||||||
|
}
|
||||||
|
vDst = reflect.ValueOf(dst).Elem()
|
||||||
|
if vDst.Kind() != reflect.Struct && vDst.Kind() != reflect.Map && vDst.Kind() != reflect.Slice {
|
||||||
|
err = ErrNotSupported
|
||||||
|
return
|
||||||
|
}
|
||||||
|
vSrc = reflect.ValueOf(src)
|
||||||
|
// We check if vSrc is a pointer to dereference it.
|
||||||
|
if vSrc.Kind() == reflect.Ptr {
|
||||||
|
vSrc = vSrc.Elem()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
1
vendor/git.coopcloud.tech/coop-cloud/godotenv/.gitignore
vendored
Normal file
1
vendor/git.coopcloud.tech/coop-cloud/godotenv/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
.DS_Store
|
23
vendor/git.coopcloud.tech/coop-cloud/godotenv/LICENCE
vendored
Normal file
23
vendor/git.coopcloud.tech/coop-cloud/godotenv/LICENCE
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
Copyright (c) 2013 John Barton
|
||||||
|
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of this software and associated documentation files (the
|
||||||
|
"Software"), to deal in the Software without restriction, including
|
||||||
|
without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be
|
||||||
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
202
vendor/git.coopcloud.tech/coop-cloud/godotenv/README.md
vendored
Normal file
202
vendor/git.coopcloud.tech/coop-cloud/godotenv/README.md
vendored
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
# GoDotEnv  [](https://goreportcard.com/report/github.com/joho/godotenv)
|
||||||
|
|
||||||
|
A Go (golang) port of the Ruby [dotenv](https://github.com/bkeepers/dotenv) project (which loads env vars from a .env file).
|
||||||
|
|
||||||
|
From the original Library:
|
||||||
|
|
||||||
|
> Storing configuration in the environment is one of the tenets of a twelve-factor app. Anything that is likely to change between deployment environments–such as resource handles for databases or credentials for external services–should be extracted from the code into environment variables.
|
||||||
|
>
|
||||||
|
> But it is not always practical to set environment variables on development machines or continuous integration servers where multiple projects are run. Dotenv load variables from a .env file into ENV when the environment is bootstrapped.
|
||||||
|
|
||||||
|
It can be used as a library (for loading in env for your own daemons etc.) or as a bin command.
|
||||||
|
|
||||||
|
There is test coverage and CI for both linuxish and Windows environments, but I make no guarantees about the bin version working on Windows.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
As a library
|
||||||
|
|
||||||
|
```shell
|
||||||
|
go get github.com/joho/godotenv
|
||||||
|
```
|
||||||
|
|
||||||
|
or if you want to use it as a bin command
|
||||||
|
|
||||||
|
go >= 1.17
|
||||||
|
```shell
|
||||||
|
go install github.com/joho/godotenv/cmd/godotenv@latest
|
||||||
|
```
|
||||||
|
|
||||||
|
go < 1.17
|
||||||
|
```shell
|
||||||
|
go get github.com/joho/godotenv/cmd/godotenv
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Add your application configuration to your `.env` file in the root of your project:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
S3_BUCKET=YOURS3BUCKET
|
||||||
|
SECRET_KEY=YOURSECRETKEYGOESHERE
|
||||||
|
```
|
||||||
|
|
||||||
|
Then in your Go app you can do something like
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/joho/godotenv"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
err := godotenv.Load()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error loading .env file")
|
||||||
|
}
|
||||||
|
|
||||||
|
s3Bucket := os.Getenv("S3_BUCKET")
|
||||||
|
secretKey := os.Getenv("SECRET_KEY")
|
||||||
|
|
||||||
|
// now do something with s3 or whatever
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
If you're even lazier than that, you can just take advantage of the autoload package which will read in `.env` on import
|
||||||
|
|
||||||
|
```go
|
||||||
|
import _ "github.com/joho/godotenv/autoload"
|
||||||
|
```
|
||||||
|
|
||||||
|
While `.env` in the project root is the default, you don't have to be constrained, both examples below are 100% legit
|
||||||
|
|
||||||
|
```go
|
||||||
|
godotenv.Load("somerandomfile")
|
||||||
|
godotenv.Load("filenumberone.env", "filenumbertwo.env")
|
||||||
|
```
|
||||||
|
|
||||||
|
If you want to be really fancy with your env file you can do comments and exports (below is a valid env file)
|
||||||
|
|
||||||
|
```shell
|
||||||
|
# I am a comment and that is OK
|
||||||
|
SOME_VAR=someval
|
||||||
|
FOO=BAR # comments at line end are OK too
|
||||||
|
export BAR=BAZ
|
||||||
|
```
|
||||||
|
|
||||||
|
Or finally you can do YAML(ish) style
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
FOO: bar
|
||||||
|
BAR: baz
|
||||||
|
```
|
||||||
|
|
||||||
|
as a final aside, if you don't want godotenv munging your env you can just get a map back instead
|
||||||
|
|
||||||
|
```go
|
||||||
|
var myEnv map[string]string
|
||||||
|
myEnv, err := godotenv.Read()
|
||||||
|
|
||||||
|
s3Bucket := myEnv["S3_BUCKET"]
|
||||||
|
```
|
||||||
|
|
||||||
|
... or from an `io.Reader` instead of a local file
|
||||||
|
|
||||||
|
```go
|
||||||
|
reader := getRemoteFile()
|
||||||
|
myEnv, err := godotenv.Parse(reader)
|
||||||
|
```
|
||||||
|
|
||||||
|
... or from a `string` if you so desire
|
||||||
|
|
||||||
|
```go
|
||||||
|
content := getRemoteFileContent()
|
||||||
|
myEnv, err := godotenv.Unmarshal(content)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Precedence & Conventions
|
||||||
|
|
||||||
|
Existing envs take precedence of envs that are loaded later.
|
||||||
|
|
||||||
|
The [convention](https://github.com/bkeepers/dotenv#what-other-env-files-can-i-use)
|
||||||
|
for managing multiple environments (i.e. development, test, production)
|
||||||
|
is to create an env named `{YOURAPP}_ENV` and load envs in this order:
|
||||||
|
|
||||||
|
```go
|
||||||
|
env := os.Getenv("FOO_ENV")
|
||||||
|
if "" == env {
|
||||||
|
env = "development"
|
||||||
|
}
|
||||||
|
|
||||||
|
godotenv.Load(".env." + env + ".local")
|
||||||
|
if "test" != env {
|
||||||
|
godotenv.Load(".env.local")
|
||||||
|
}
|
||||||
|
godotenv.Load(".env." + env)
|
||||||
|
godotenv.Load() // The Original .env
|
||||||
|
```
|
||||||
|
|
||||||
|
If you need to, you can also use `godotenv.Overload()` to defy this convention
|
||||||
|
and overwrite existing envs instead of only supplanting them. Use with caution.
|
||||||
|
|
||||||
|
### Command Mode
|
||||||
|
|
||||||
|
Assuming you've installed the command as above and you've got `$GOPATH/bin` in your `$PATH`
|
||||||
|
|
||||||
|
```
|
||||||
|
godotenv -f /some/path/to/.env some_command with some args
|
||||||
|
```
|
||||||
|
|
||||||
|
If you don't specify `-f` it will fall back on the default of loading `.env` in `PWD`
|
||||||
|
|
||||||
|
By default, it won't override existing environment variables; you can do that with the `-o` flag.
|
||||||
|
|
||||||
|
### Writing Env Files
|
||||||
|
|
||||||
|
Godotenv can also write a map representing the environment to a correctly-formatted and escaped file
|
||||||
|
|
||||||
|
```go
|
||||||
|
env, err := godotenv.Unmarshal("KEY=value")
|
||||||
|
err := godotenv.Write(env, "./.env")
|
||||||
|
```
|
||||||
|
|
||||||
|
... or to a string
|
||||||
|
|
||||||
|
```go
|
||||||
|
env, err := godotenv.Unmarshal("KEY=value")
|
||||||
|
content, err := godotenv.Marshal(env)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
Contributions are welcome, but with some caveats.
|
||||||
|
|
||||||
|
This library has been declared feature complete (see [#182](https://github.com/joho/godotenv/issues/182) for background) and will not be accepting issues or pull requests adding new functionality or breaking the library API.
|
||||||
|
|
||||||
|
Contributions would be gladly accepted that:
|
||||||
|
|
||||||
|
* bring this library's parsing into closer compatibility with the mainline dotenv implementations, in particular [Ruby's dotenv](https://github.com/bkeepers/dotenv) and [Node.js' dotenv](https://github.com/motdotla/dotenv)
|
||||||
|
* keep the library up to date with the go ecosystem (ie CI bumps, documentation changes, changes in the core libraries)
|
||||||
|
* bug fixes for use cases that pertain to the library's purpose of easing development of codebases deployed into twelve factor environments
|
||||||
|
|
||||||
|
*code changes without tests and references to peer dotenv implementations will not be accepted*
|
||||||
|
|
||||||
|
1. Fork it
|
||||||
|
2. Create your feature branch (`git checkout -b my-new-feature`)
|
||||||
|
3. Commit your changes (`git commit -am 'Added some feature'`)
|
||||||
|
4. Push to the branch (`git push origin my-new-feature`)
|
||||||
|
5. Create new Pull Request
|
||||||
|
|
||||||
|
## Releases
|
||||||
|
|
||||||
|
Releases should follow [Semver](http://semver.org/) though the first couple of releases are `v1` and `v1.1`.
|
||||||
|
|
||||||
|
Use [annotated tags for all releases](https://github.com/joho/godotenv/issues/30). Example `git tag -a v1.2.1`
|
||||||
|
|
||||||
|
## Who?
|
||||||
|
|
||||||
|
The original library [dotenv](https://github.com/bkeepers/dotenv) was written by [Brandon Keepers](http://opensoul.org/), and this port was done by [John Barton](https://johnbarton.co/) based off the tests/fixtures in the original library.
|
234
vendor/git.coopcloud.tech/coop-cloud/godotenv/godotenv.go
vendored
Normal file
234
vendor/git.coopcloud.tech/coop-cloud/godotenv/godotenv.go
vendored
Normal file
@ -0,0 +1,234 @@
|
|||||||
|
// Package godotenv is a go port of the ruby dotenv library (https://github.com/bkeepers/dotenv)
|
||||||
|
//
|
||||||
|
// Examples/readme can be found on the GitHub page at https://github.com/joho/godotenv
|
||||||
|
//
|
||||||
|
// The TL;DR is that you make a .env file that looks something like
|
||||||
|
//
|
||||||
|
// SOME_ENV_VAR=somevalue
|
||||||
|
//
|
||||||
|
// and then in your go code you can call
|
||||||
|
//
|
||||||
|
// godotenv.Load()
|
||||||
|
//
|
||||||
|
// and all the env vars declared in .env will be available through os.Getenv("SOME_ENV_VAR")
|
||||||
|
package godotenv
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const doubleQuoteSpecialChars = "\\\n\r\"!$`"
|
||||||
|
|
||||||
|
// Parse reads an env file from io.Reader, returning a map of keys and values.
|
||||||
|
func Parse(r io.Reader) (map[string]string, map[string]map[string]string, error) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
_, err := io.Copy(&buf, r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return UnmarshalBytes(buf.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load will read your env file(s) and load them into ENV for this process.
|
||||||
|
//
|
||||||
|
// Call this function as close as possible to the start of your program (ideally in main).
|
||||||
|
//
|
||||||
|
// If you call Load without any args it will default to loading .env in the current path.
|
||||||
|
//
|
||||||
|
// You can otherwise tell it which files to load (there can be more than one) like:
|
||||||
|
//
|
||||||
|
// godotenv.Load("fileone", "filetwo")
|
||||||
|
//
|
||||||
|
// It's important to note that it WILL NOT OVERRIDE an env variable that already exists - consider the .env file to set dev vars or sensible defaults.
|
||||||
|
func Load(filenames ...string) (err error) {
|
||||||
|
filenames = filenamesOrDefault(filenames)
|
||||||
|
|
||||||
|
for _, filename := range filenames {
|
||||||
|
err = loadFile(filename, false)
|
||||||
|
if err != nil {
|
||||||
|
return // return early on a spazout
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overload will read your env file(s) and load them into ENV for this process.
|
||||||
|
//
|
||||||
|
// Call this function as close as possible to the start of your program (ideally in main).
|
||||||
|
//
|
||||||
|
// If you call Overload without any args it will default to loading .env in the current path.
|
||||||
|
//
|
||||||
|
// You can otherwise tell it which files to load (there can be more than one) like:
|
||||||
|
//
|
||||||
|
// godotenv.Overload("fileone", "filetwo")
|
||||||
|
//
|
||||||
|
// It's important to note this WILL OVERRIDE an env variable that already exists - consider the .env file to forcefully set all vars.
|
||||||
|
func Overload(filenames ...string) (err error) {
|
||||||
|
filenames = filenamesOrDefault(filenames)
|
||||||
|
|
||||||
|
for _, filename := range filenames {
|
||||||
|
err = loadFile(filename, true)
|
||||||
|
if err != nil {
|
||||||
|
return // return early on a spazout
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read all env (with same file loading semantics as Load) but return values as
|
||||||
|
// a map rather than automatically writing values into env
|
||||||
|
func Read(filenames ...string) (envMap map[string]string, modMap map[string]map[string]string, err error) {
|
||||||
|
filenames = filenamesOrDefault(filenames)
|
||||||
|
envMap = make(map[string]string)
|
||||||
|
modMap = make(map[string]map[string]string)
|
||||||
|
|
||||||
|
for _, filename := range filenames {
|
||||||
|
individualEnvMap, individualModMap, individualErr := readFile(filename)
|
||||||
|
|
||||||
|
if individualErr != nil {
|
||||||
|
err = individualErr
|
||||||
|
return // return early on a spazout
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, value := range individualEnvMap {
|
||||||
|
envMap[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, value := range individualModMap {
|
||||||
|
modMap[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unmarshal reads an env file from a string, returning a map of keys and values.
|
||||||
|
func Unmarshal(str string) (envMap map[string]string, modifierMap map[string]map[string]string, err error) {
|
||||||
|
return UnmarshalBytes([]byte(str))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalBytes parses env file from byte slice of chars, returning a map of keys and values.
|
||||||
|
func UnmarshalBytes(src []byte) (map[string]string, map[string]map[string]string, error) {
|
||||||
|
vars := make(map[string]string)
|
||||||
|
modifiers := make(map[string]map[string]string)
|
||||||
|
err := parseBytes(src, vars, modifiers)
|
||||||
|
|
||||||
|
return vars, modifiers, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exec loads env vars from the specified filenames (empty map falls back to default)
|
||||||
|
// then executes the cmd specified.
|
||||||
|
//
|
||||||
|
// Simply hooks up os.Stdin/err/out to the command and calls Run().
|
||||||
|
//
|
||||||
|
// If you want more fine grained control over your command it's recommended
|
||||||
|
// that you use `Load()`, `Overload()` or `Read()` and the `os/exec` package yourself.
|
||||||
|
func Exec(filenames []string, cmd string, cmdArgs []string, overload bool) error {
|
||||||
|
op := Load
|
||||||
|
if overload {
|
||||||
|
op = Overload
|
||||||
|
}
|
||||||
|
if err := op(filenames...); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
command := exec.Command(cmd, cmdArgs...)
|
||||||
|
command.Stdin = os.Stdin
|
||||||
|
command.Stdout = os.Stdout
|
||||||
|
command.Stderr = os.Stderr
|
||||||
|
return command.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write serializes the given environment and writes it to a file.
|
||||||
|
func Write(envMap map[string]string, filename string) error {
|
||||||
|
content, err := Marshal(envMap)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
file, err := os.Create(filename)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
_, err = file.WriteString(content + "\n")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return file.Sync()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marshal outputs the given environment as a dotenv-formatted environment file.
|
||||||
|
// Each line is in the format: KEY="VALUE" where VALUE is backslash-escaped.
|
||||||
|
func Marshal(envMap map[string]string) (string, error) {
|
||||||
|
lines := make([]string, 0, len(envMap))
|
||||||
|
for k, v := range envMap {
|
||||||
|
if d, err := strconv.Atoi(v); err == nil {
|
||||||
|
lines = append(lines, fmt.Sprintf(`%s=%d`, k, d))
|
||||||
|
} else {
|
||||||
|
lines = append(lines, fmt.Sprintf(`%s="%s"`, k, doubleQuoteEscape(v)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sort.Strings(lines)
|
||||||
|
return strings.Join(lines, "\n"), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func filenamesOrDefault(filenames []string) []string {
|
||||||
|
if len(filenames) == 0 {
|
||||||
|
return []string{".env"}
|
||||||
|
}
|
||||||
|
return filenames
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadFile(filename string, overload bool) error {
|
||||||
|
envMap, _, err := readFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
currentEnv := map[string]bool{}
|
||||||
|
rawEnv := os.Environ()
|
||||||
|
for _, rawEnvLine := range rawEnv {
|
||||||
|
key := strings.Split(rawEnvLine, "=")[0]
|
||||||
|
currentEnv[key] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, value := range envMap {
|
||||||
|
if !currentEnv[key] || overload {
|
||||||
|
_ = os.Setenv(key, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func readFile(filename string) (envMap map[string]string, modMap map[string]map[string]string, err error) {
|
||||||
|
file, err := os.Open(filename)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
return Parse(file)
|
||||||
|
}
|
||||||
|
|
||||||
|
func doubleQuoteEscape(line string) string {
|
||||||
|
for _, c := range doubleQuoteSpecialChars {
|
||||||
|
toReplace := "\\" + string(c)
|
||||||
|
if c == '\n' {
|
||||||
|
toReplace = `\n`
|
||||||
|
}
|
||||||
|
if c == '\r' {
|
||||||
|
toReplace = `\r`
|
||||||
|
}
|
||||||
|
line = strings.Replace(line, string(c), toReplace, -1)
|
||||||
|
}
|
||||||
|
return line
|
||||||
|
}
|
293
vendor/git.coopcloud.tech/coop-cloud/godotenv/parser.go
vendored
Normal file
293
vendor/git.coopcloud.tech/coop-cloud/godotenv/parser.go
vendored
Normal file
@ -0,0 +1,293 @@
|
|||||||
|
package godotenv
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
charComment = '#'
|
||||||
|
prefixSingleQuote = '\''
|
||||||
|
prefixDoubleQuote = '"'
|
||||||
|
|
||||||
|
exportPrefix = "export"
|
||||||
|
)
|
||||||
|
|
||||||
|
func parseBytes(src []byte, vars map[string]string, modifiers map[string]map[string]string) error {
|
||||||
|
src = bytes.Replace(src, []byte("\r\n"), []byte("\n"), -1)
|
||||||
|
cutset := src
|
||||||
|
for {
|
||||||
|
cutset = getStatementStart(cutset)
|
||||||
|
if cutset == nil {
|
||||||
|
// reached end of file
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
key, left, err := locateKeyName(cutset)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
value, mods, left, err := extractVarValue(left, vars)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
vars[key] = value
|
||||||
|
modifiers[key] = mods
|
||||||
|
cutset = left
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getStatementPosition returns position of statement begin.
|
||||||
|
//
|
||||||
|
// It skips any comment line or non-whitespace character.
|
||||||
|
func getStatementStart(src []byte) []byte {
|
||||||
|
pos := indexOfNonSpaceChar(src)
|
||||||
|
if pos == -1 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
src = src[pos:]
|
||||||
|
if src[0] != charComment {
|
||||||
|
return src
|
||||||
|
}
|
||||||
|
|
||||||
|
// skip comment section
|
||||||
|
pos = bytes.IndexFunc(src, isCharFunc('\n'))
|
||||||
|
if pos == -1 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return getStatementStart(src[pos:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// locateKeyName locates and parses key name and returns rest of slice
|
||||||
|
func locateKeyName(src []byte) (key string, cutset []byte, err error) {
|
||||||
|
// trim "export" and space at beginning
|
||||||
|
src = bytes.TrimLeftFunc(src, isSpace)
|
||||||
|
if bytes.HasPrefix(src, []byte(exportPrefix)) {
|
||||||
|
trimmed := bytes.TrimPrefix(src, []byte(exportPrefix))
|
||||||
|
if bytes.IndexFunc(trimmed, isSpace) == 0 {
|
||||||
|
src = bytes.TrimLeftFunc(trimmed, isSpace)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// locate key name end and validate it in single loop
|
||||||
|
offset := 0
|
||||||
|
loop:
|
||||||
|
for i, char := range src {
|
||||||
|
rchar := rune(char)
|
||||||
|
if isSpace(rchar) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch char {
|
||||||
|
case '=', ':':
|
||||||
|
// library also supports yaml-style value declaration
|
||||||
|
key = string(src[0:i])
|
||||||
|
offset = i + 1
|
||||||
|
break loop
|
||||||
|
case '_':
|
||||||
|
default:
|
||||||
|
// variable name should match [A-Za-z0-9_.]
|
||||||
|
if unicode.IsLetter(rchar) || unicode.IsNumber(rchar) || rchar == '.' {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", nil, fmt.Errorf(
|
||||||
|
`unexpected character %q in variable name near %q`,
|
||||||
|
string(char), string(src))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(src) == 0 {
|
||||||
|
return "", nil, errors.New("zero length string")
|
||||||
|
}
|
||||||
|
|
||||||
|
// trim whitespace
|
||||||
|
key = strings.TrimRightFunc(key, unicode.IsSpace)
|
||||||
|
cutset = bytes.TrimLeftFunc(src[offset:], isSpace)
|
||||||
|
return key, cutset, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// extractVarValue extracts variable value and returns rest of slice
|
||||||
|
func extractVarValue(src []byte, vars map[string]string) (value string, modifiers map[string]string, rest []byte, err error) {
|
||||||
|
quote, hasPrefix := hasQuotePrefix(src)
|
||||||
|
// unquoted value - read until end of line
|
||||||
|
endOfLine := bytes.IndexFunc(src, isLineEnd)
|
||||||
|
// Hit EOF without a trailing newline
|
||||||
|
if endOfLine == -1 {
|
||||||
|
endOfLine = len(src)
|
||||||
|
|
||||||
|
if endOfLine == 0 {
|
||||||
|
return "", nil, nil, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !hasPrefix {
|
||||||
|
// Convert line to rune away to do accurate countback of runes
|
||||||
|
line := []rune(string(src[0:endOfLine]))
|
||||||
|
|
||||||
|
// Assume end of line is end of var
|
||||||
|
endOfVar := len(line)
|
||||||
|
if endOfVar == 0 {
|
||||||
|
return "", nil, src[endOfLine:], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
comment := ""
|
||||||
|
// Work backwards to check if the line ends in whitespace then
|
||||||
|
// a comment (ie asdasd # some comment)
|
||||||
|
for i := endOfVar - 1; i >= 0; i-- {
|
||||||
|
if line[i] == charComment && i > 0 {
|
||||||
|
comment = string(line[i+1:])
|
||||||
|
if isSpace(line[i-1]) {
|
||||||
|
endOfVar = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trimmed := strings.TrimFunc(string(line[0:endOfVar]), isSpace)
|
||||||
|
|
||||||
|
return expandVariables(trimmed, vars), extractModifiers(comment), src[endOfLine:], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// lookup quoted string terminator
|
||||||
|
for i := 1; i < len(src); i++ {
|
||||||
|
if char := src[i]; char != quote {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// skip escaped quote symbol (\" or \', depends on quote)
|
||||||
|
if prevChar := src[i-1]; prevChar == '\\' {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// trim quotes
|
||||||
|
trimFunc := isCharFunc(rune(quote))
|
||||||
|
value = string(bytes.TrimLeftFunc(bytes.TrimRightFunc(src[0:i], trimFunc), trimFunc))
|
||||||
|
if quote == prefixDoubleQuote {
|
||||||
|
// unescape newlines for double quote (this is compat feature)
|
||||||
|
// and expand environment variables
|
||||||
|
value = expandVariables(expandEscapes(value), vars)
|
||||||
|
}
|
||||||
|
|
||||||
|
var mods map[string]string
|
||||||
|
if endOfLine > i {
|
||||||
|
mods = extractModifiers(string(src[i+1 : endOfLine]))
|
||||||
|
}
|
||||||
|
return value, mods, src[i+1:], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// return formatted error if quoted string is not terminated
|
||||||
|
valEndIndex := bytes.IndexFunc(src, isCharFunc('\n'))
|
||||||
|
if valEndIndex == -1 {
|
||||||
|
valEndIndex = len(src)
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", nil, nil, fmt.Errorf("unterminated quoted value %s", src[:valEndIndex])
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractModifiers(comment string) map[string]string {
|
||||||
|
if comment == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
comment = strings.TrimSpace(comment)
|
||||||
|
kvpairs := strings.Split(comment, " ")
|
||||||
|
mods := make(map[string]string)
|
||||||
|
for _, kv := range kvpairs {
|
||||||
|
kvsplit := strings.Split(kv, "=")
|
||||||
|
if len(kvsplit) != 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
mods[kvsplit[0]] = kvsplit[1]
|
||||||
|
}
|
||||||
|
return mods
|
||||||
|
}
|
||||||
|
|
||||||
|
func expandEscapes(str string) string {
|
||||||
|
out := escapeRegex.ReplaceAllStringFunc(str, func(match string) string {
|
||||||
|
c := strings.TrimPrefix(match, `\`)
|
||||||
|
switch c {
|
||||||
|
case "n":
|
||||||
|
return "\n"
|
||||||
|
case "r":
|
||||||
|
return "\r"
|
||||||
|
default:
|
||||||
|
return match
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return unescapeCharsRegex.ReplaceAllString(out, "$1")
|
||||||
|
}
|
||||||
|
|
||||||
|
func indexOfNonSpaceChar(src []byte) int {
|
||||||
|
return bytes.IndexFunc(src, func(r rune) bool {
|
||||||
|
return !unicode.IsSpace(r)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// hasQuotePrefix reports whether charset starts with single or double quote and returns quote character
|
||||||
|
func hasQuotePrefix(src []byte) (prefix byte, isQuored bool) {
|
||||||
|
if len(src) == 0 {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
switch prefix := src[0]; prefix {
|
||||||
|
case prefixDoubleQuote, prefixSingleQuote:
|
||||||
|
return prefix, true
|
||||||
|
default:
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func isCharFunc(char rune) func(rune) bool {
|
||||||
|
return func(v rune) bool {
|
||||||
|
return v == char
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// isSpace reports whether the rune is a space character but not line break character
|
||||||
|
//
|
||||||
|
// this differs from unicode.IsSpace, which also applies line break as space
|
||||||
|
func isSpace(r rune) bool {
|
||||||
|
switch r {
|
||||||
|
case '\t', '\v', '\f', '\r', ' ', 0x85, 0xA0:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func isLineEnd(r rune) bool {
|
||||||
|
if r == '\n' || r == '\r' {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
escapeRegex = regexp.MustCompile(`\\.`)
|
||||||
|
expandVarRegex = regexp.MustCompile(`(\\)?(\$)(\()?\{?([A-Z0-9_]+)?\}?`)
|
||||||
|
unescapeCharsRegex = regexp.MustCompile(`\\([^$])`)
|
||||||
|
)
|
||||||
|
|
||||||
|
func expandVariables(v string, m map[string]string) string {
|
||||||
|
return expandVarRegex.ReplaceAllStringFunc(v, func(s string) string {
|
||||||
|
submatch := expandVarRegex.FindStringSubmatch(s)
|
||||||
|
|
||||||
|
if submatch == nil {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
if submatch[1] == "\\" || submatch[2] == "(" {
|
||||||
|
return submatch[0][1:]
|
||||||
|
} else if submatch[4] != "" {
|
||||||
|
return m[submatch[4]]
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
})
|
||||||
|
}
|
55
vendor/github.com/AlecAivazis/survey/v2/CONTRIBUTING.md
generated
vendored
Normal file
55
vendor/github.com/AlecAivazis/survey/v2/CONTRIBUTING.md
generated
vendored
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
# Contributing to Survey
|
||||||
|
|
||||||
|
🎉🎉 First off, thanks for the interest in contributing to `survey`! 🎉🎉
|
||||||
|
|
||||||
|
The following is a set of guidelines to follow when contributing to this package. These are not hard rules, please use common sense and feel free to propose changes to this document in a pull request.
|
||||||
|
|
||||||
|
## Code of Conduct
|
||||||
|
|
||||||
|
This project and its contibutors are expected to uphold the [Go Community Code of Conduct](https://golang.org/conduct). By participating, you are expected to follow these guidelines.
|
||||||
|
|
||||||
|
## Getting help
|
||||||
|
|
||||||
|
* [Open an issue](https://github.com/AlecAivazis/survey/issues/new/choose)
|
||||||
|
* Reach out to `@AlecAivazis` or `@mislav` in the Gophers slack (please use only when urgent)
|
||||||
|
|
||||||
|
## Submitting a contribution
|
||||||
|
|
||||||
|
When submitting a contribution,
|
||||||
|
|
||||||
|
- Try to make a series of smaller changes instead of one large change
|
||||||
|
- Provide a description of each change that you are proposing
|
||||||
|
- Reference the issue addressed by your pull request (if there is one)
|
||||||
|
- Document all new exported Go APIs
|
||||||
|
- Update the project's README when applicable
|
||||||
|
- Include unit tests if possible
|
||||||
|
- Contributions with visual ramifications or interaction changes should be accompanied with an integration test—see below for details.
|
||||||
|
|
||||||
|
## Writing and running tests
|
||||||
|
|
||||||
|
When submitting features, please add as many units tests as necessary to test both positive and negative cases.
|
||||||
|
|
||||||
|
Integration tests for survey uses [go-expect](https://github.com/Netflix/go-expect) to expect a match on stdout and respond on stdin. Since `os.Stdout` in a `go test` process is not a TTY, you need a way to interpret terminal / ANSI escape sequences for things like `CursorLocation`. The stdin/stdout handled by `go-expect` is also multiplexed to a [virtual terminal](https://github.com/hinshun/vt10x).
|
||||||
|
|
||||||
|
For example, you can extend the tests for Input by specifying the following test case:
|
||||||
|
|
||||||
|
```go
|
||||||
|
{
|
||||||
|
"Test Input prompt interaction", // Name of the test.
|
||||||
|
&Input{ // An implementation of the survey.Prompt interface.
|
||||||
|
Message: "What is your name?",
|
||||||
|
},
|
||||||
|
func(c *expect.Console) { // An expect procedure. You can expect strings / regexps and
|
||||||
|
c.ExpectString("What is your name?") // write back strings / bytes to its psuedoterminal for survey.
|
||||||
|
c.SendLine("Johnny Appleseed")
|
||||||
|
c.ExpectEOF() // Nothing is read from the tty without an expect, and once an
|
||||||
|
// expectation is met, no further bytes are read. End your
|
||||||
|
// procedure with `c.ExpectEOF()` to read until survey finishes.
|
||||||
|
},
|
||||||
|
"Johnny Appleseed", // The expected result.
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
If you want to write your own `go-expect` test from scratch, you'll need to instantiate a virtual terminal,
|
||||||
|
multiplex it into an `*expect.Console`, and hook up its tty with survey's optional stdio. Please see `go-expect`
|
||||||
|
[documentation](https://godoc.org/github.com/Netflix/go-expect) for more detail.
|
21
vendor/github.com/AlecAivazis/survey/v2/LICENSE
generated
vendored
Normal file
21
vendor/github.com/AlecAivazis/survey/v2/LICENSE
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2018 Alec Aivazis
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
510
vendor/github.com/AlecAivazis/survey/v2/README.md
generated
vendored
Normal file
510
vendor/github.com/AlecAivazis/survey/v2/README.md
generated
vendored
Normal file
@ -0,0 +1,510 @@
|
|||||||
|
# Survey
|
||||||
|
|
||||||
|
[](https://pkg.go.dev/github.com/AlecAivazis/survey/v2)
|
||||||
|
|
||||||
|
A library for building interactive and accessible prompts on terminals supporting ANSI escape sequences.
|
||||||
|
|
||||||
|
<img width="550" src="https://thumbs.gfycat.com/VillainousGraciousKouprey-size_restricted.gif"/>
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/AlecAivazis/survey/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// the questions to ask
|
||||||
|
var qs = []*survey.Question{
|
||||||
|
{
|
||||||
|
Name: "name",
|
||||||
|
Prompt: &survey.Input{Message: "What is your name?"},
|
||||||
|
Validate: survey.Required,
|
||||||
|
Transform: survey.Title,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "color",
|
||||||
|
Prompt: &survey.Select{
|
||||||
|
Message: "Choose a color:",
|
||||||
|
Options: []string{"red", "blue", "green"},
|
||||||
|
Default: "red",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "age",
|
||||||
|
Prompt: &survey.Input{Message: "How old are you?"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// the answers will be written to this struct
|
||||||
|
answers := struct {
|
||||||
|
Name string // survey will match the question and field names
|
||||||
|
FavoriteColor string `survey:"color"` // or you can tag fields to match a specific name
|
||||||
|
Age int // if the types don't match, survey will convert it
|
||||||
|
}{}
|
||||||
|
|
||||||
|
// perform the questions
|
||||||
|
err := survey.Ask(qs, &answers)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("%s chose %s.", answers.Name, answers.FavoriteColor)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
Examples can be found in the `examples/` directory. Run them
|
||||||
|
to see basic behavior:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go run examples/simple.go
|
||||||
|
go run examples/validation.go
|
||||||
|
```
|
||||||
|
|
||||||
|
## Running the Prompts
|
||||||
|
|
||||||
|
There are two primary ways to execute prompts and start collecting information from your users: `Ask` and
|
||||||
|
`AskOne`. The primary difference is whether you are interested in collecting a single piece of information
|
||||||
|
or if you have a list of questions to ask whose answers should be collected in a single struct.
|
||||||
|
For most basic usecases, `Ask` should be enough. However, for surveys with complicated branching logic,
|
||||||
|
we recommend that you break out your questions into multiple calls to both of these functions to fit your needs.
|
||||||
|
|
||||||
|
### Configuring the Prompts
|
||||||
|
|
||||||
|
Most prompts take fine-grained configuration through fields on the structs you instantiate. It is also
|
||||||
|
possible to change survey's default behaviors by passing `AskOpts` to either `Ask` or `AskOne`. Examples
|
||||||
|
in this document will do both interchangeably:
|
||||||
|
|
||||||
|
```golang
|
||||||
|
prompt := &Select{
|
||||||
|
Message: "Choose a color:",
|
||||||
|
Options: []string{"red", "blue", "green"},
|
||||||
|
// can pass a validator directly
|
||||||
|
Validate: survey.Required,
|
||||||
|
}
|
||||||
|
|
||||||
|
// or define a default for the single call to `AskOne`
|
||||||
|
// the answer will get written to the color variable
|
||||||
|
survey.AskOne(prompt, &color, survey.WithValidator(survey.Required))
|
||||||
|
|
||||||
|
// or define a default for every entry in a list of questions
|
||||||
|
// the answer will get copied into the matching field of the struct as shown above
|
||||||
|
survey.Ask(questions, &answers, survey.WithValidator(survey.Required))
|
||||||
|
```
|
||||||
|
|
||||||
|
## Prompts
|
||||||
|
|
||||||
|
### Input
|
||||||
|
|
||||||
|
<img src="https://thumbs.gfycat.com/LankyBlindAmericanpainthorse-size_restricted.gif" width="400px"/>
|
||||||
|
|
||||||
|
```golang
|
||||||
|
name := ""
|
||||||
|
prompt := &survey.Input{
|
||||||
|
Message: "ping",
|
||||||
|
}
|
||||||
|
survey.AskOne(prompt, &name)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Suggestion Options
|
||||||
|
|
||||||
|
<img src="https://i.imgur.com/Q7POpA1.gif" width="800px"/>
|
||||||
|
|
||||||
|
```golang
|
||||||
|
file := ""
|
||||||
|
prompt := &survey.Input{
|
||||||
|
Message: "inform a file to save:",
|
||||||
|
Suggest: func (toComplete string) []string {
|
||||||
|
files, _ := filepath.Glob(toComplete + "*")
|
||||||
|
return files
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
survey.AskOne(prompt, &file)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Multiline
|
||||||
|
|
||||||
|
<img src="https://thumbs.gfycat.com/ImperfectShimmeringBeagle-size_restricted.gif" width="400px"/>
|
||||||
|
|
||||||
|
```golang
|
||||||
|
text := ""
|
||||||
|
prompt := &survey.Multiline{
|
||||||
|
Message: "ping",
|
||||||
|
}
|
||||||
|
survey.AskOne(prompt, &text)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Password
|
||||||
|
|
||||||
|
<img src="https://thumbs.gfycat.com/CompassionateSevereHypacrosaurus-size_restricted.gif" width="400px" />
|
||||||
|
|
||||||
|
```golang
|
||||||
|
password := ""
|
||||||
|
prompt := &survey.Password{
|
||||||
|
Message: "Please type your password",
|
||||||
|
}
|
||||||
|
survey.AskOne(prompt, &password)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Confirm
|
||||||
|
|
||||||
|
<img src="https://thumbs.gfycat.com/UnkemptCarefulGermanpinscher-size_restricted.gif" width="400px"/>
|
||||||
|
|
||||||
|
```golang
|
||||||
|
name := false
|
||||||
|
prompt := &survey.Confirm{
|
||||||
|
Message: "Do you like pie?",
|
||||||
|
}
|
||||||
|
survey.AskOne(prompt, &name)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Select
|
||||||
|
|
||||||
|
<img src="https://thumbs.gfycat.com/GrimFilthyAmazonparrot-size_restricted.gif" width="450px"/>
|
||||||
|
|
||||||
|
```golang
|
||||||
|
color := ""
|
||||||
|
prompt := &survey.Select{
|
||||||
|
Message: "Choose a color:",
|
||||||
|
Options: []string{"red", "blue", "green"},
|
||||||
|
}
|
||||||
|
survey.AskOne(prompt, &color)
|
||||||
|
```
|
||||||
|
|
||||||
|
Fields and values that come from a `Select` prompt can be one of two different things. If you pass an `int`
|
||||||
|
the field will have the value of the selected index. If you instead pass a string, the string value selected
|
||||||
|
will be written to the field.
|
||||||
|
|
||||||
|
The user can also press `esc` to toggle the ability cycle through the options with the j and k keys to do down and up respectively.
|
||||||
|
|
||||||
|
By default, the select prompt is limited to showing 7 options at a time
|
||||||
|
and will paginate lists of options longer than that. This can be changed a number of ways:
|
||||||
|
|
||||||
|
```golang
|
||||||
|
// as a field on a single select
|
||||||
|
prompt := &survey.MultiSelect{..., PageSize: 10}
|
||||||
|
|
||||||
|
// or as an option to Ask or AskOne
|
||||||
|
survey.AskOne(prompt, &days, survey.WithPageSize(10))
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Select options description
|
||||||
|
|
||||||
|
The optional description text can be used to add extra information to each option listed in the select prompt:
|
||||||
|
|
||||||
|
```golang
|
||||||
|
color := ""
|
||||||
|
prompt := &survey.Select{
|
||||||
|
Message: "Choose a color:",
|
||||||
|
Options: []string{"red", "blue", "green"},
|
||||||
|
Description: func(value string, index int) string {
|
||||||
|
if value == "red" {
|
||||||
|
return "My favorite color"
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
},
|
||||||
|
}
|
||||||
|
survey.AskOne(prompt, &color)
|
||||||
|
|
||||||
|
// Assuming that the user chose "red - My favorite color":
|
||||||
|
fmt.Println(color) //=> "red"
|
||||||
|
```
|
||||||
|
|
||||||
|
### MultiSelect
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
```golang
|
||||||
|
days := []string{}
|
||||||
|
prompt := &survey.MultiSelect{
|
||||||
|
Message: "What days do you prefer:",
|
||||||
|
Options: []string{"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"},
|
||||||
|
}
|
||||||
|
survey.AskOne(prompt, &days)
|
||||||
|
```
|
||||||
|
|
||||||
|
Fields and values that come from a `MultiSelect` prompt can be one of two different things. If you pass an `int`
|
||||||
|
the field will have a slice of the selected indices. If you instead pass a string, a slice of the string values
|
||||||
|
selected will be written to the field.
|
||||||
|
|
||||||
|
The user can also press `esc` to toggle the ability cycle through the options with the j and k keys to do down and up respectively.
|
||||||
|
|
||||||
|
By default, the MultiSelect prompt is limited to showing 7 options at a time
|
||||||
|
and will paginate lists of options longer than that. This can be changed a number of ways:
|
||||||
|
|
||||||
|
```golang
|
||||||
|
// as a field on a single select
|
||||||
|
prompt := &survey.MultiSelect{..., PageSize: 10}
|
||||||
|
|
||||||
|
// or as an option to Ask or AskOne
|
||||||
|
survey.AskOne(prompt, &days, survey.WithPageSize(10))
|
||||||
|
```
|
||||||
|
|
||||||
|
### Editor
|
||||||
|
|
||||||
|
Launches the user's preferred editor (defined by the \$VISUAL or \$EDITOR environment variables) on a
|
||||||
|
temporary file. Once the user exits their editor, the contents of the temporary file are read in as
|
||||||
|
the result. If neither of those are present, notepad (on Windows) or vim (Linux or Mac) is used.
|
||||||
|
|
||||||
|
You can also specify a [pattern](https://golang.org/pkg/io/ioutil/#TempFile) for the name of the temporary file. This
|
||||||
|
can be useful for ensuring syntax highlighting matches your usecase.
|
||||||
|
|
||||||
|
```golang
|
||||||
|
prompt := &survey.Editor{
|
||||||
|
Message: "Shell code snippet",
|
||||||
|
FileName: "*.sh",
|
||||||
|
}
|
||||||
|
|
||||||
|
survey.AskOne(prompt, &content)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Filtering Options
|
||||||
|
|
||||||
|
By default, the user can filter for options in Select and MultiSelects by typing while the prompt
|
||||||
|
is active. This will filter out all options that don't contain the typed string anywhere in their name, ignoring case.
|
||||||
|
|
||||||
|
A custom filter function can also be provided to change this behavior:
|
||||||
|
|
||||||
|
```golang
|
||||||
|
func myFilter(filterValue string, optValue string, optIndex int) bool {
|
||||||
|
// only include the option if it includes the filter and has length greater than 5
|
||||||
|
return strings.Contains(optValue, filterValue) && len(optValue) >= 5
|
||||||
|
}
|
||||||
|
|
||||||
|
// configure it for a specific prompt
|
||||||
|
&Select{
|
||||||
|
Message: "Choose a color:",
|
||||||
|
Options: []string{"red", "blue", "green"},
|
||||||
|
Filter: myFilter,
|
||||||
|
}
|
||||||
|
|
||||||
|
// or define a default for all of the questions
|
||||||
|
survey.AskOne(prompt, &color, survey.WithFilter(myFilter))
|
||||||
|
```
|
||||||
|
|
||||||
|
## Keeping the filter active
|
||||||
|
|
||||||
|
By default the filter will disappear if the user selects one of the filtered elements. Once the user selects one element the filter setting is gone.
|
||||||
|
|
||||||
|
However the user can prevent this from happening and keep the filter active for multiple selections in a e.g. MultiSelect:
|
||||||
|
|
||||||
|
```golang
|
||||||
|
// configure it for a specific prompt
|
||||||
|
&Select{
|
||||||
|
Message: "Choose a color:",
|
||||||
|
Options: []string{"light-green", "green", "dark-green", "red"},
|
||||||
|
KeepFilter: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
// or define a default for all of the questions
|
||||||
|
survey.AskOne(prompt, &color, survey.WithKeepFilter(true))
|
||||||
|
```
|
||||||
|
|
||||||
|
## Validation
|
||||||
|
|
||||||
|
Validating individual responses for a particular question can be done by defining a
|
||||||
|
`Validate` field on the `survey.Question` to be validated. This function takes an
|
||||||
|
`interface{}` type and returns an error to show to the user, prompting them for another
|
||||||
|
response. Like usual, validators can be provided directly to the prompt or with `survey.WithValidator`:
|
||||||
|
|
||||||
|
```golang
|
||||||
|
q := &survey.Question{
|
||||||
|
Prompt: &survey.Input{Message: "Hello world validation"},
|
||||||
|
Validate: func (val interface{}) error {
|
||||||
|
// since we are validating an Input, the assertion will always succeed
|
||||||
|
if str, ok := val.(string) ; !ok || len(str) > 10 {
|
||||||
|
return errors.New("This response cannot be longer than 10 characters.")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
color := ""
|
||||||
|
prompt := &survey.Input{ Message: "Whats your name?" }
|
||||||
|
|
||||||
|
// you can pass multiple validators here and survey will make sure each one passes
|
||||||
|
survey.AskOne(prompt, &color, survey.WithValidator(survey.Required))
|
||||||
|
```
|
||||||
|
|
||||||
|
### Built-in Validators
|
||||||
|
|
||||||
|
`survey` comes prepackaged with a few validators to fit common situations. Currently these
|
||||||
|
validators include:
|
||||||
|
|
||||||
|
| name | valid types | description | notes |
|
||||||
|
| ------------ | -------------- | ---------------------------------------------------------------- | ------------------------------------------------------------------------------------- |
|
||||||
|
| Required | any | Rejects zero values of the response type | Boolean values pass straight through since the zero value (false) is a valid response |
|
||||||
|
| MinLength(n) | string | Enforces that a response is at least the given length | |
|
||||||
|
| MaxLength(n) | string | Enforces that a response is no longer than the given length | |
|
||||||
|
| MaxItems(n) | []OptionAnswer | Enforces that a response has no more selections of the indicated | |
|
||||||
|
| MinItems(n) | []OptionAnswer | Enforces that a response has no less selections of the indicated | |
|
||||||
|
|
||||||
|
## Help Text
|
||||||
|
|
||||||
|
All of the prompts have a `Help` field which can be defined to provide more information to your users:
|
||||||
|
|
||||||
|
<img src="https://thumbs.gfycat.com/CloudyRemorsefulFossa-size_restricted.gif" width="400px" style="margin-top: 8px"/>
|
||||||
|
|
||||||
|
```golang
|
||||||
|
&survey.Input{
|
||||||
|
Message: "What is your phone number:",
|
||||||
|
Help: "Phone number should include the area code",
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Removing the "Select All" and "Select None" options
|
||||||
|
|
||||||
|
By default, users can select all of the multi-select options using the right arrow key. To prevent users from being able to do this (and remove the `<right> to all` message from the prompt), use the option `WithRemoveSelectAll`:
|
||||||
|
|
||||||
|
```golang
|
||||||
|
import (
|
||||||
|
"github.com/AlecAivazis/survey/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
number := ""
|
||||||
|
prompt := &survey.Input{
|
||||||
|
Message: "This question has the select all option removed",
|
||||||
|
}
|
||||||
|
|
||||||
|
survey.AskOne(prompt, &number, survey.WithRemoveSelectAll())
|
||||||
|
```
|
||||||
|
|
||||||
|
Also by default, users can use the left arrow key to unselect all of the options. To prevent users from being able to do this (and remove the `<left> to none` message from the prompt), use the option `WithRemoveSelectNone`:
|
||||||
|
|
||||||
|
```golang
|
||||||
|
import (
|
||||||
|
"github.com/AlecAivazis/survey/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
number := ""
|
||||||
|
prompt := &survey.Input{
|
||||||
|
Message: "This question has the select all option removed",
|
||||||
|
}
|
||||||
|
|
||||||
|
survey.AskOne(prompt, &number, survey.WithRemoveSelectNone())
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Changing the input rune
|
||||||
|
|
||||||
|
In some situations, `?` is a perfectly valid response. To handle this, you can change the rune that survey
|
||||||
|
looks for with `WithHelpInput`:
|
||||||
|
|
||||||
|
```golang
|
||||||
|
import (
|
||||||
|
"github.com/AlecAivazis/survey/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
number := ""
|
||||||
|
prompt := &survey.Input{
|
||||||
|
Message: "If you have this need, please give me a reasonable message.",
|
||||||
|
Help: "I couldn't come up with one.",
|
||||||
|
}
|
||||||
|
|
||||||
|
survey.AskOne(prompt, &number, survey.WithHelpInput('^'))
|
||||||
|
```
|
||||||
|
|
||||||
|
## Changing the Icons
|
||||||
|
|
||||||
|
Changing the icons and their color/format can be done by passing the `WithIcons` option. The format
|
||||||
|
follows the patterns outlined [here](https://github.com/mgutz/ansi#style-format). For example:
|
||||||
|
|
||||||
|
```golang
|
||||||
|
import (
|
||||||
|
"github.com/AlecAivazis/survey/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
number := ""
|
||||||
|
prompt := &survey.Input{
|
||||||
|
Message: "If you have this need, please give me a reasonable message.",
|
||||||
|
Help: "I couldn't come up with one.",
|
||||||
|
}
|
||||||
|
|
||||||
|
survey.AskOne(prompt, &number, survey.WithIcons(func(icons *survey.IconSet) {
|
||||||
|
// you can set any icons
|
||||||
|
icons.Question.Text = "⁇"
|
||||||
|
// for more information on formatting the icons, see here: https://github.com/mgutz/ansi#style-format
|
||||||
|
icons.Question.Format = "yellow+hb"
|
||||||
|
}))
|
||||||
|
```
|
||||||
|
|
||||||
|
The icons and their default text and format are summarized below:
|
||||||
|
|
||||||
|
| name | text | format | description |
|
||||||
|
| -------------- | ---- | ---------- | ------------------------------------------------------------- |
|
||||||
|
| Error | X | red | Before an error |
|
||||||
|
| Help | i | cyan | Before help text |
|
||||||
|
| Question | ? | green+hb | Before the message of a prompt |
|
||||||
|
| SelectFocus | > | green | Marks the current focus in `Select` and `MultiSelect` prompts |
|
||||||
|
| UnmarkedOption | [ ] | default+hb | Marks an unselected option in a `MultiSelect` prompt |
|
||||||
|
| MarkedOption | [x] | cyan+b | Marks a chosen selection in a `MultiSelect` prompt |
|
||||||
|
|
||||||
|
## Custom Types
|
||||||
|
|
||||||
|
survey will assign prompt answers to your custom types if they implement this interface:
|
||||||
|
|
||||||
|
```golang
|
||||||
|
type Settable interface {
|
||||||
|
WriteAnswer(field string, value interface{}) error
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Here is an example how to use them:
|
||||||
|
|
||||||
|
```golang
|
||||||
|
type MyValue struct {
|
||||||
|
value string
|
||||||
|
}
|
||||||
|
func (my *MyValue) WriteAnswer(name string, value interface{}) error {
|
||||||
|
my.value = value.(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
myval := MyValue{}
|
||||||
|
survey.AskOne(
|
||||||
|
&survey.Input{
|
||||||
|
Message: "Enter something:",
|
||||||
|
},
|
||||||
|
&myval
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
You can test your program's interactive prompts using [go-expect](https://github.com/Netflix/go-expect). The library
|
||||||
|
can be used to expect a match on stdout and respond on stdin. Since `os.Stdout` in a `go test` process is not a TTY,
|
||||||
|
if you are manipulating the cursor or using `survey`, you will need a way to interpret terminal / ANSI escape sequences
|
||||||
|
for things like `CursorLocation`. `vt10x.NewVT10XConsole` will create a `go-expect` console that also multiplexes
|
||||||
|
stdio to an in-memory [virtual terminal](https://github.com/hinshun/vt10x).
|
||||||
|
|
||||||
|
For some examples, you can see any of the tests in this repo.
|
||||||
|
|
||||||
|
## FAQ
|
||||||
|
|
||||||
|
### What kinds of IO are supported by `survey`?
|
||||||
|
|
||||||
|
survey aims to support most terminal emulators; it expects support for ANSI escape sequences.
|
||||||
|
This means that reading from piped stdin or writing to piped stdout is **not supported**,
|
||||||
|
and likely to break your application in these situations. See [#337](https://github.com/AlecAivazis/survey/pull/337#issue-581351617)
|
||||||
|
|
||||||
|
### Why isn't Ctrl-C working?
|
||||||
|
|
||||||
|
Ordinarily, when you type Ctrl-C, the terminal recognizes this as the QUIT button and delivers a SIGINT signal to the process, which terminates it.
|
||||||
|
However, Survey temporarily configures the terminal to deliver control codes as ordinary input bytes.
|
||||||
|
When Survey reads a ^C byte (ASCII \x03, "end of text"), it interrupts the current survey and returns a
|
||||||
|
`github.com/AlecAivazis/survey/v2/terminal.InterruptErr` from `Ask` or `AskOne`.
|
||||||
|
If you want to stop the process, handle the returned error in your code:
|
||||||
|
|
||||||
|
```go
|
||||||
|
err := survey.AskOne(prompt, &myVar)
|
||||||
|
if err != nil {
|
||||||
|
if err == terminal.InterruptErr {
|
||||||
|
log.Fatal("interrupted")
|
||||||
|
}
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
154
vendor/github.com/AlecAivazis/survey/v2/confirm.go
generated
vendored
Normal file
154
vendor/github.com/AlecAivazis/survey/v2/confirm.go
generated
vendored
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
package survey
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Confirm is a regular text input that accept yes/no answers. Response type is a bool.
|
||||||
|
type Confirm struct {
|
||||||
|
Renderer
|
||||||
|
Message string
|
||||||
|
Default bool
|
||||||
|
Help string
|
||||||
|
}
|
||||||
|
|
||||||
|
// data available to the templates when processing
|
||||||
|
type ConfirmTemplateData struct {
|
||||||
|
Confirm
|
||||||
|
Answer string
|
||||||
|
ShowHelp bool
|
||||||
|
Config *PromptConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
// Templates with Color formatting. See Documentation: https://github.com/mgutz/ansi#style-format
|
||||||
|
var ConfirmQuestionTemplate = `
|
||||||
|
{{- if .ShowHelp }}{{- color .Config.Icons.Help.Format }}{{ .Config.Icons.Help.Text }} {{ .Help }}{{color "reset"}}{{"\n"}}{{end}}
|
||||||
|
{{- color .Config.Icons.Question.Format }}{{ .Config.Icons.Question.Text }} {{color "reset"}}
|
||||||
|
{{- color "default+hb"}}{{ .Message }} {{color "reset"}}
|
||||||
|
{{- if .Answer}}
|
||||||
|
{{- color "cyan"}}{{.Answer}}{{color "reset"}}{{"\n"}}
|
||||||
|
{{- else }}
|
||||||
|
{{- if and .Help (not .ShowHelp)}}{{color "cyan"}}[{{ .Config.HelpInput }} for help]{{color "reset"}} {{end}}
|
||||||
|
{{- color "white"}}{{if .Default}}(Y/n) {{else}}(y/N) {{end}}{{color "reset"}}
|
||||||
|
{{- end}}`
|
||||||
|
|
||||||
|
// the regex for answers
|
||||||
|
var (
|
||||||
|
yesRx = regexp.MustCompile("^(?i:y(?:es)?)$")
|
||||||
|
noRx = regexp.MustCompile("^(?i:n(?:o)?)$")
|
||||||
|
)
|
||||||
|
|
||||||
|
func yesNo(t bool) string {
|
||||||
|
if t {
|
||||||
|
return "Yes"
|
||||||
|
}
|
||||||
|
return "No"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Confirm) getBool(showHelp bool, config *PromptConfig) (bool, error) {
|
||||||
|
cursor := c.NewCursor()
|
||||||
|
rr := c.NewRuneReader()
|
||||||
|
_ = rr.SetTermMode()
|
||||||
|
defer func() {
|
||||||
|
_ = rr.RestoreTermMode()
|
||||||
|
}()
|
||||||
|
|
||||||
|
// start waiting for input
|
||||||
|
for {
|
||||||
|
line, err := rr.ReadLine(0)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
// move back up a line to compensate for the \n echoed from terminal
|
||||||
|
cursor.PreviousLine(1)
|
||||||
|
val := string(line)
|
||||||
|
|
||||||
|
// get the answer that matches the
|
||||||
|
var answer bool
|
||||||
|
switch {
|
||||||
|
case yesRx.Match([]byte(val)):
|
||||||
|
answer = true
|
||||||
|
case noRx.Match([]byte(val)):
|
||||||
|
answer = false
|
||||||
|
case val == "":
|
||||||
|
answer = c.Default
|
||||||
|
case val == config.HelpInput && c.Help != "":
|
||||||
|
err := c.Render(
|
||||||
|
ConfirmQuestionTemplate,
|
||||||
|
ConfirmTemplateData{
|
||||||
|
Confirm: *c,
|
||||||
|
ShowHelp: true,
|
||||||
|
Config: config,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
// use the default value and bubble up
|
||||||
|
return c.Default, err
|
||||||
|
}
|
||||||
|
showHelp = true
|
||||||
|
continue
|
||||||
|
default:
|
||||||
|
// we didnt get a valid answer, so print error and prompt again
|
||||||
|
//lint:ignore ST1005 it should be fine for this error message to have punctuation
|
||||||
|
if err := c.Error(config, fmt.Errorf("%q is not a valid answer, please try again.", val)); err != nil {
|
||||||
|
return c.Default, err
|
||||||
|
}
|
||||||
|
err := c.Render(
|
||||||
|
ConfirmQuestionTemplate,
|
||||||
|
ConfirmTemplateData{
|
||||||
|
Confirm: *c,
|
||||||
|
ShowHelp: showHelp,
|
||||||
|
Config: config,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
// use the default value and bubble up
|
||||||
|
return c.Default, err
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return answer, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Prompt prompts the user with a simple text field and expects a reply followed
|
||||||
|
by a carriage return.
|
||||||
|
|
||||||
|
likesPie := false
|
||||||
|
prompt := &survey.Confirm{ Message: "What is your name?" }
|
||||||
|
survey.AskOne(prompt, &likesPie)
|
||||||
|
*/
|
||||||
|
func (c *Confirm) Prompt(config *PromptConfig) (interface{}, error) {
|
||||||
|
// render the question template
|
||||||
|
err := c.Render(
|
||||||
|
ConfirmQuestionTemplate,
|
||||||
|
ConfirmTemplateData{
|
||||||
|
Confirm: *c,
|
||||||
|
Config: config,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// get input and return
|
||||||
|
return c.getBool(false, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup overwrite the line with the finalized formatted version
|
||||||
|
func (c *Confirm) Cleanup(config *PromptConfig, val interface{}) error {
|
||||||
|
// if the value was previously true
|
||||||
|
ans := yesNo(val.(bool))
|
||||||
|
|
||||||
|
// render the template
|
||||||
|
return c.Render(
|
||||||
|
ConfirmQuestionTemplate,
|
||||||
|
ConfirmTemplateData{
|
||||||
|
Confirm: *c,
|
||||||
|
Answer: ans,
|
||||||
|
Config: config,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
104
vendor/github.com/AlecAivazis/survey/v2/core/template.go
generated
vendored
Normal file
104
vendor/github.com/AlecAivazis/survey/v2/core/template.go
generated
vendored
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
|
"github.com/mgutz/ansi"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DisableColor can be used to make testing reliable
|
||||||
|
var DisableColor = false
|
||||||
|
|
||||||
|
var TemplateFuncsWithColor = map[string]interface{}{
|
||||||
|
// Templates with Color formatting. See Documentation: https://github.com/mgutz/ansi#style-format
|
||||||
|
"color": ansi.ColorCode,
|
||||||
|
}
|
||||||
|
|
||||||
|
var TemplateFuncsNoColor = map[string]interface{}{
|
||||||
|
// Templates without Color formatting. For layout/ testing.
|
||||||
|
"color": func(color string) string {
|
||||||
|
return ""
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// envColorDisabled returns if output colors are forbid by environment variables
|
||||||
|
func envColorDisabled() bool {
|
||||||
|
return os.Getenv("NO_COLOR") != "" || os.Getenv("CLICOLOR") == "0"
|
||||||
|
}
|
||||||
|
|
||||||
|
// envColorForced returns if output colors are forced from environment variables
|
||||||
|
func envColorForced() bool {
|
||||||
|
val, ok := os.LookupEnv("CLICOLOR_FORCE")
|
||||||
|
return ok && val != "0"
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunTemplate returns two formatted strings given a template and
|
||||||
|
// the data it requires. The first string returned is generated for
|
||||||
|
// user-facing output and may or may not contain ANSI escape codes
|
||||||
|
// for colored output. The second string does not contain escape codes
|
||||||
|
// and can be used by the renderer for layout purposes.
|
||||||
|
func RunTemplate(tmpl string, data interface{}) (string, string, error) {
|
||||||
|
tPair, err := GetTemplatePair(tmpl)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
userBuf := bytes.NewBufferString("")
|
||||||
|
err = tPair[0].Execute(userBuf, data)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
layoutBuf := bytes.NewBufferString("")
|
||||||
|
err = tPair[1].Execute(layoutBuf, data)
|
||||||
|
if err != nil {
|
||||||
|
return userBuf.String(), "", err
|
||||||
|
}
|
||||||
|
return userBuf.String(), layoutBuf.String(), err
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
memoizedGetTemplate = map[string][2]*template.Template{}
|
||||||
|
|
||||||
|
memoMutex = &sync.RWMutex{}
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetTemplatePair returns a pair of compiled templates where the
|
||||||
|
// first template is generated for user-facing output and the
|
||||||
|
// second is generated for use by the renderer. The second
|
||||||
|
// template does not contain any color escape codes, whereas
|
||||||
|
// the first template may or may not depending on DisableColor.
|
||||||
|
func GetTemplatePair(tmpl string) ([2]*template.Template, error) {
|
||||||
|
memoMutex.RLock()
|
||||||
|
if t, ok := memoizedGetTemplate[tmpl]; ok {
|
||||||
|
memoMutex.RUnlock()
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
|
memoMutex.RUnlock()
|
||||||
|
|
||||||
|
templatePair := [2]*template.Template{nil, nil}
|
||||||
|
|
||||||
|
templateNoColor, err := template.New("prompt").Funcs(TemplateFuncsNoColor).Parse(tmpl)
|
||||||
|
if err != nil {
|
||||||
|
return [2]*template.Template{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
templatePair[1] = templateNoColor
|
||||||
|
|
||||||
|
envColorHide := envColorDisabled() && !envColorForced()
|
||||||
|
if DisableColor || envColorHide {
|
||||||
|
templatePair[0] = templatePair[1]
|
||||||
|
} else {
|
||||||
|
templateWithColor, err := template.New("prompt").Funcs(TemplateFuncsWithColor).Parse(tmpl)
|
||||||
|
templatePair[0] = templateWithColor
|
||||||
|
if err != nil {
|
||||||
|
return [2]*template.Template{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
memoMutex.Lock()
|
||||||
|
memoizedGetTemplate[tmpl] = templatePair
|
||||||
|
memoMutex.Unlock()
|
||||||
|
return templatePair, nil
|
||||||
|
}
|
376
vendor/github.com/AlecAivazis/survey/v2/core/write.go
generated
vendored
Normal file
376
vendor/github.com/AlecAivazis/survey/v2/core/write.go
generated
vendored
Normal file
@ -0,0 +1,376 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// the tag used to denote the name of the question
|
||||||
|
const tagName = "survey"
|
||||||
|
|
||||||
|
// Settable allow for configuration when assigning answers
|
||||||
|
type Settable interface {
|
||||||
|
WriteAnswer(field string, value interface{}) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// OptionAnswer is the return type of Selects/MultiSelects that lets the appropriate information
|
||||||
|
// get copied to the user's struct
|
||||||
|
type OptionAnswer struct {
|
||||||
|
Value string
|
||||||
|
Index int
|
||||||
|
}
|
||||||
|
|
||||||
|
type reflectField struct {
|
||||||
|
value reflect.Value
|
||||||
|
fieldType reflect.StructField
|
||||||
|
}
|
||||||
|
|
||||||
|
func OptionAnswerList(incoming []string) []OptionAnswer {
|
||||||
|
list := []OptionAnswer{}
|
||||||
|
for i, opt := range incoming {
|
||||||
|
list = append(list, OptionAnswer{Value: opt, Index: i})
|
||||||
|
}
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
|
||||||
|
func WriteAnswer(t interface{}, name string, v interface{}) (err error) {
|
||||||
|
// if the field is a custom type
|
||||||
|
if s, ok := t.(Settable); ok {
|
||||||
|
// use the interface method
|
||||||
|
return s.WriteAnswer(name, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// the target to write to
|
||||||
|
target := reflect.ValueOf(t)
|
||||||
|
// the value to write from
|
||||||
|
value := reflect.ValueOf(v)
|
||||||
|
|
||||||
|
// make sure we are writing to a pointer
|
||||||
|
if target.Kind() != reflect.Ptr {
|
||||||
|
return errors.New("you must pass a pointer as the target of a Write operation")
|
||||||
|
}
|
||||||
|
// the object "inside" of the target pointer
|
||||||
|
elem := target.Elem()
|
||||||
|
|
||||||
|
// handle the special types
|
||||||
|
switch elem.Kind() {
|
||||||
|
// if we are writing to a struct
|
||||||
|
case reflect.Struct:
|
||||||
|
// if we are writing to an option answer than we want to treat
|
||||||
|
// it like a single thing and not a place to deposit answers
|
||||||
|
if elem.Type().Name() == "OptionAnswer" {
|
||||||
|
// copy the value over to the normal struct
|
||||||
|
return copy(elem, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the name of the field that matches the string we were given
|
||||||
|
field, _, err := findField(elem, name)
|
||||||
|
// if something went wrong
|
||||||
|
if err != nil {
|
||||||
|
// bubble up
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// handle references to the Settable interface aswell
|
||||||
|
if s, ok := field.Interface().(Settable); ok {
|
||||||
|
// use the interface method
|
||||||
|
return s.WriteAnswer(name, v)
|
||||||
|
}
|
||||||
|
if field.CanAddr() {
|
||||||
|
if s, ok := field.Addr().Interface().(Settable); ok {
|
||||||
|
// use the interface method
|
||||||
|
return s.WriteAnswer(name, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy the value over to the normal struct
|
||||||
|
return copy(field, value)
|
||||||
|
case reflect.Map:
|
||||||
|
mapType := reflect.TypeOf(t).Elem()
|
||||||
|
if mapType.Key().Kind() != reflect.String {
|
||||||
|
return errors.New("answer maps key must be of type string")
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy only string value/index value to map if,
|
||||||
|
// map is not of type interface and is 'OptionAnswer'
|
||||||
|
if value.Type().Name() == "OptionAnswer" {
|
||||||
|
if kval := mapType.Elem().Kind(); kval == reflect.String {
|
||||||
|
mt := *t.(*map[string]string)
|
||||||
|
mt[name] = value.FieldByName("Value").String()
|
||||||
|
return nil
|
||||||
|
} else if kval == reflect.Int {
|
||||||
|
mt := *t.(*map[string]int)
|
||||||
|
mt[name] = int(value.FieldByName("Index").Int())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if mapType.Elem().Kind() != reflect.Interface {
|
||||||
|
return errors.New("answer maps must be of type map[string]interface")
|
||||||
|
}
|
||||||
|
mt := *t.(*map[string]interface{})
|
||||||
|
mt[name] = value.Interface()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// otherwise just copy the value to the target
|
||||||
|
return copy(elem, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
type errFieldNotMatch struct {
|
||||||
|
questionName string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err errFieldNotMatch) Error() string {
|
||||||
|
return fmt.Sprintf("could not find field matching %v", err.questionName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err errFieldNotMatch) Is(target error) bool { // implements the dynamic errors.Is interface.
|
||||||
|
if target != nil {
|
||||||
|
if name, ok := IsFieldNotMatch(target); ok {
|
||||||
|
// if have a filled questionName then perform "deeper" comparison.
|
||||||
|
return name == "" || err.questionName == "" || name == err.questionName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsFieldNotMatch reports whether an "err" is caused by a non matching field.
|
||||||
|
// It returns the Question.Name that couldn't be matched with a destination field.
|
||||||
|
//
|
||||||
|
// Usage:
|
||||||
|
//
|
||||||
|
// if err := survey.Ask(qs, &v); err != nil {
|
||||||
|
// if name, ok := core.IsFieldNotMatch(err); ok {
|
||||||
|
// // name is the question name that did not match a field
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
func IsFieldNotMatch(err error) (string, bool) {
|
||||||
|
if err != nil {
|
||||||
|
if v, ok := err.(errFieldNotMatch); ok {
|
||||||
|
return v.questionName, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
// BUG(AlecAivazis): the current implementation might cause weird conflicts if there are
|
||||||
|
// two fields with same name that only differ by casing.
|
||||||
|
func findField(s reflect.Value, name string) (reflect.Value, reflect.StructField, error) {
|
||||||
|
|
||||||
|
fields := flattenFields(s)
|
||||||
|
|
||||||
|
// first look for matching tags so we can overwrite matching field names
|
||||||
|
for _, f := range fields {
|
||||||
|
// the value of the survey tag
|
||||||
|
tag := f.fieldType.Tag.Get(tagName)
|
||||||
|
// if the tag matches the name we are looking for
|
||||||
|
if tag != "" && tag == name {
|
||||||
|
// then we found our index
|
||||||
|
return f.value, f.fieldType, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// then look for matching names
|
||||||
|
for _, f := range fields {
|
||||||
|
// if the name of the field matches what we're looking for
|
||||||
|
if strings.EqualFold(f.fieldType.Name, name) {
|
||||||
|
return f.value, f.fieldType, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// we didn't find the field
|
||||||
|
return reflect.Value{}, reflect.StructField{}, errFieldNotMatch{name}
|
||||||
|
}
|
||||||
|
|
||||||
|
func flattenFields(s reflect.Value) []reflectField {
|
||||||
|
sType := s.Type()
|
||||||
|
numField := sType.NumField()
|
||||||
|
fields := make([]reflectField, 0, numField)
|
||||||
|
for i := 0; i < numField; i++ {
|
||||||
|
fieldType := sType.Field(i)
|
||||||
|
field := s.Field(i)
|
||||||
|
|
||||||
|
if field.Kind() == reflect.Struct && fieldType.Anonymous {
|
||||||
|
// field is a promoted structure
|
||||||
|
fields = append(fields, flattenFields(field)...)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fields = append(fields, reflectField{field, fieldType})
|
||||||
|
}
|
||||||
|
return fields
|
||||||
|
}
|
||||||
|
|
||||||
|
// isList returns true if the element is something we can Len()
|
||||||
|
func isList(v reflect.Value) bool {
|
||||||
|
switch v.Type().Kind() {
|
||||||
|
case reflect.Array, reflect.Slice:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write takes a value and copies it to the target
|
||||||
|
func copy(t reflect.Value, v reflect.Value) (err error) {
|
||||||
|
// if something ends up panicing we need to catch it in a deferred func
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
// if we paniced with an error
|
||||||
|
if _, ok := r.(error); ok {
|
||||||
|
// cast the result to an error object
|
||||||
|
err = r.(error)
|
||||||
|
} else if _, ok := r.(string); ok {
|
||||||
|
// otherwise we could have paniced with a string so wrap it in an error
|
||||||
|
err = errors.New(r.(string))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// if we are copying from a string result to something else
|
||||||
|
if v.Kind() == reflect.String && v.Type() != t.Type() {
|
||||||
|
var castVal interface{}
|
||||||
|
var casterr error
|
||||||
|
vString := v.Interface().(string)
|
||||||
|
|
||||||
|
switch t.Kind() {
|
||||||
|
case reflect.Bool:
|
||||||
|
castVal, casterr = strconv.ParseBool(vString)
|
||||||
|
case reflect.Int:
|
||||||
|
castVal, casterr = strconv.Atoi(vString)
|
||||||
|
case reflect.Int8:
|
||||||
|
var val64 int64
|
||||||
|
val64, casterr = strconv.ParseInt(vString, 10, 8)
|
||||||
|
if casterr == nil {
|
||||||
|
castVal = int8(val64)
|
||||||
|
}
|
||||||
|
case reflect.Int16:
|
||||||
|
var val64 int64
|
||||||
|
val64, casterr = strconv.ParseInt(vString, 10, 16)
|
||||||
|
if casterr == nil {
|
||||||
|
castVal = int16(val64)
|
||||||
|
}
|
||||||
|
case reflect.Int32:
|
||||||
|
var val64 int64
|
||||||
|
val64, casterr = strconv.ParseInt(vString, 10, 32)
|
||||||
|
if casterr == nil {
|
||||||
|
castVal = int32(val64)
|
||||||
|
}
|
||||||
|
case reflect.Int64:
|
||||||
|
if t.Type() == reflect.TypeOf(time.Duration(0)) {
|
||||||
|
castVal, casterr = time.ParseDuration(vString)
|
||||||
|
} else {
|
||||||
|
castVal, casterr = strconv.ParseInt(vString, 10, 64)
|
||||||
|
}
|
||||||
|
case reflect.Uint:
|
||||||
|
var val64 uint64
|
||||||
|
val64, casterr = strconv.ParseUint(vString, 10, 8)
|
||||||
|
if casterr == nil {
|
||||||
|
castVal = uint(val64)
|
||||||
|
}
|
||||||
|
case reflect.Uint8:
|
||||||
|
var val64 uint64
|
||||||
|
val64, casterr = strconv.ParseUint(vString, 10, 8)
|
||||||
|
if casterr == nil {
|
||||||
|
castVal = uint8(val64)
|
||||||
|
}
|
||||||
|
case reflect.Uint16:
|
||||||
|
var val64 uint64
|
||||||
|
val64, casterr = strconv.ParseUint(vString, 10, 16)
|
||||||
|
if casterr == nil {
|
||||||
|
castVal = uint16(val64)
|
||||||
|
}
|
||||||
|
case reflect.Uint32:
|
||||||
|
var val64 uint64
|
||||||
|
val64, casterr = strconv.ParseUint(vString, 10, 32)
|
||||||
|
if casterr == nil {
|
||||||
|
castVal = uint32(val64)
|
||||||
|
}
|
||||||
|
case reflect.Uint64:
|
||||||
|
castVal, casterr = strconv.ParseUint(vString, 10, 64)
|
||||||
|
case reflect.Float32:
|
||||||
|
var val64 float64
|
||||||
|
val64, casterr = strconv.ParseFloat(vString, 32)
|
||||||
|
if casterr == nil {
|
||||||
|
castVal = float32(val64)
|
||||||
|
}
|
||||||
|
case reflect.Float64:
|
||||||
|
castVal, casterr = strconv.ParseFloat(vString, 64)
|
||||||
|
default:
|
||||||
|
//lint:ignore ST1005 allow this error message to be capitalized
|
||||||
|
return fmt.Errorf("Unable to convert from string to type %s", t.Kind())
|
||||||
|
}
|
||||||
|
|
||||||
|
if casterr != nil {
|
||||||
|
return casterr
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Set(reflect.ValueOf(castVal))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we are copying from an OptionAnswer to something
|
||||||
|
if v.Type().Name() == "OptionAnswer" {
|
||||||
|
// copying an option answer to a string
|
||||||
|
if t.Kind() == reflect.String {
|
||||||
|
// copies the Value field of the struct
|
||||||
|
t.Set(reflect.ValueOf(v.FieldByName("Value").Interface()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// copying an option answer to an int
|
||||||
|
if t.Kind() == reflect.Int {
|
||||||
|
// copies the Index field of the struct
|
||||||
|
t.Set(reflect.ValueOf(v.FieldByName("Index").Interface()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// copying an OptionAnswer to an OptionAnswer
|
||||||
|
if t.Type().Name() == "OptionAnswer" {
|
||||||
|
t.Set(v)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// we're copying an option answer to an incorrect type
|
||||||
|
//lint:ignore ST1005 allow this error message to be capitalized
|
||||||
|
return fmt.Errorf("Unable to convert from OptionAnswer to type %s", t.Kind())
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we are copying from one slice or array to another
|
||||||
|
if isList(v) && isList(t) {
|
||||||
|
// loop over every item in the desired value
|
||||||
|
for i := 0; i < v.Len(); i++ {
|
||||||
|
// write to the target given its kind
|
||||||
|
switch t.Kind() {
|
||||||
|
// if its a slice
|
||||||
|
case reflect.Slice:
|
||||||
|
// an object of the correct type
|
||||||
|
obj := reflect.Indirect(reflect.New(t.Type().Elem()))
|
||||||
|
|
||||||
|
// write the appropriate value to the obj and catch any errors
|
||||||
|
if err := copy(obj, v.Index(i)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// just append the value to the end
|
||||||
|
t.Set(reflect.Append(t, obj))
|
||||||
|
// otherwise it could be an array
|
||||||
|
case reflect.Array:
|
||||||
|
// set the index to the appropriate value
|
||||||
|
if err := copy(t.Slice(i, i+1).Index(0), v.Index(i)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// set the value to the target
|
||||||
|
t.Set(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// we're done
|
||||||
|
return
|
||||||
|
}
|
226
vendor/github.com/AlecAivazis/survey/v2/editor.go
generated
vendored
Normal file
226
vendor/github.com/AlecAivazis/survey/v2/editor.go
generated
vendored
Normal file
@ -0,0 +1,226 @@
|
|||||||
|
package survey
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"github.com/AlecAivazis/survey/v2/terminal"
|
||||||
|
shellquote "github.com/kballard/go-shellquote"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
Editor launches an instance of the users preferred editor on a temporary file.
|
||||||
|
The editor to use is determined by reading the $VISUAL or $EDITOR environment
|
||||||
|
variables. If neither of those are present, notepad (on Windows) or vim
|
||||||
|
(others) is used.
|
||||||
|
The launch of the editor is triggered by the enter key. Since the response may
|
||||||
|
be long, it will not be echoed as Input does, instead, it print <Received>.
|
||||||
|
Response type is a string.
|
||||||
|
|
||||||
|
message := ""
|
||||||
|
prompt := &survey.Editor{ Message: "What is your commit message?" }
|
||||||
|
survey.AskOne(prompt, &message)
|
||||||
|
*/
|
||||||
|
type Editor struct {
|
||||||
|
Renderer
|
||||||
|
Message string
|
||||||
|
Default string
|
||||||
|
Help string
|
||||||
|
Editor string
|
||||||
|
HideDefault bool
|
||||||
|
AppendDefault bool
|
||||||
|
FileName string
|
||||||
|
}
|
||||||
|
|
||||||
|
// data available to the templates when processing
|
||||||
|
type EditorTemplateData struct {
|
||||||
|
Editor
|
||||||
|
Answer string
|
||||||
|
ShowAnswer bool
|
||||||
|
ShowHelp bool
|
||||||
|
Config *PromptConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
// Templates with Color formatting. See Documentation: https://github.com/mgutz/ansi#style-format
|
||||||
|
var EditorQuestionTemplate = `
|
||||||
|
{{- if .ShowHelp }}{{- color .Config.Icons.Help.Format }}{{ .Config.Icons.Help.Text }} {{ .Help }}{{color "reset"}}{{"\n"}}{{end}}
|
||||||
|
{{- color .Config.Icons.Question.Format }}{{ .Config.Icons.Question.Text }} {{color "reset"}}
|
||||||
|
{{- color "default+hb"}}{{ .Message }} {{color "reset"}}
|
||||||
|
{{- if .ShowAnswer}}
|
||||||
|
{{- color "cyan"}}{{.Answer}}{{color "reset"}}{{"\n"}}
|
||||||
|
{{- else }}
|
||||||
|
{{- if and .Help (not .ShowHelp)}}{{color "cyan"}}[{{ .Config.HelpInput }} for help]{{color "reset"}} {{end}}
|
||||||
|
{{- if and .Default (not .HideDefault)}}{{color "white"}}({{.Default}}) {{color "reset"}}{{end}}
|
||||||
|
{{- color "cyan"}}[Enter to launch editor] {{color "reset"}}
|
||||||
|
{{- end}}`
|
||||||
|
|
||||||
|
var (
|
||||||
|
bom = []byte{0xef, 0xbb, 0xbf}
|
||||||
|
editor = "vim"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
editor = "notepad"
|
||||||
|
}
|
||||||
|
if v := os.Getenv("VISUAL"); v != "" {
|
||||||
|
editor = v
|
||||||
|
} else if e := os.Getenv("EDITOR"); e != "" {
|
||||||
|
editor = e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Editor) PromptAgain(config *PromptConfig, invalid interface{}, err error) (interface{}, error) {
|
||||||
|
initialValue := invalid.(string)
|
||||||
|
return e.prompt(initialValue, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Editor) Prompt(config *PromptConfig) (interface{}, error) {
|
||||||
|
initialValue := ""
|
||||||
|
if e.Default != "" && e.AppendDefault {
|
||||||
|
initialValue = e.Default
|
||||||
|
}
|
||||||
|
return e.prompt(initialValue, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Editor) prompt(initialValue string, config *PromptConfig) (interface{}, error) {
|
||||||
|
// render the template
|
||||||
|
err := e.Render(
|
||||||
|
EditorQuestionTemplate,
|
||||||
|
EditorTemplateData{
|
||||||
|
Editor: *e,
|
||||||
|
Config: config,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// start reading runes from the standard in
|
||||||
|
rr := e.NewRuneReader()
|
||||||
|
_ = rr.SetTermMode()
|
||||||
|
defer func() {
|
||||||
|
_ = rr.RestoreTermMode()
|
||||||
|
}()
|
||||||
|
|
||||||
|
cursor := e.NewCursor()
|
||||||
|
cursor.Hide()
|
||||||
|
defer cursor.Show()
|
||||||
|
|
||||||
|
for {
|
||||||
|
r, _, err := rr.ReadRune()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if r == '\r' || r == '\n' {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if r == terminal.KeyInterrupt {
|
||||||
|
return "", terminal.InterruptErr
|
||||||
|
}
|
||||||
|
if r == terminal.KeyEndTransmission {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if string(r) == config.HelpInput && e.Help != "" {
|
||||||
|
err = e.Render(
|
||||||
|
EditorQuestionTemplate,
|
||||||
|
EditorTemplateData{
|
||||||
|
Editor: *e,
|
||||||
|
ShowHelp: true,
|
||||||
|
Config: config,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepare the temp file
|
||||||
|
pattern := e.FileName
|
||||||
|
if pattern == "" {
|
||||||
|
pattern = "survey*.txt"
|
||||||
|
}
|
||||||
|
f, err := ioutil.TempFile("", pattern)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
_ = os.Remove(f.Name())
|
||||||
|
}()
|
||||||
|
|
||||||
|
// write utf8 BOM header
|
||||||
|
// The reason why we do this is because notepad.exe on Windows determines the
|
||||||
|
// encoding of an "empty" text file by the locale, for example, GBK in China,
|
||||||
|
// while golang string only handles utf8 well. However, a text file with utf8
|
||||||
|
// BOM header is not considered "empty" on Windows, and the encoding will then
|
||||||
|
// be determined utf8 by notepad.exe, instead of GBK or other encodings.
|
||||||
|
if _, err := f.Write(bom); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// write initial value
|
||||||
|
if _, err := f.WriteString(initialValue); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// close the fd to prevent the editor unable to save file
|
||||||
|
if err := f.Close(); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// check is input editor exist
|
||||||
|
if e.Editor != "" {
|
||||||
|
editor = e.Editor
|
||||||
|
}
|
||||||
|
|
||||||
|
stdio := e.Stdio()
|
||||||
|
|
||||||
|
args, err := shellquote.Split(editor)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
args = append(args, f.Name())
|
||||||
|
|
||||||
|
// open the editor
|
||||||
|
cmd := exec.Command(args[0], args[1:]...)
|
||||||
|
cmd.Stdin = stdio.In
|
||||||
|
cmd.Stdout = stdio.Out
|
||||||
|
cmd.Stderr = stdio.Err
|
||||||
|
cursor.Show()
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// raw is a BOM-unstripped UTF8 byte slice
|
||||||
|
raw, err := ioutil.ReadFile(f.Name())
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// strip BOM header
|
||||||
|
text := string(bytes.TrimPrefix(raw, bom))
|
||||||
|
|
||||||
|
// check length, return default value on empty
|
||||||
|
if len(text) == 0 && !e.AppendDefault {
|
||||||
|
return e.Default, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return text, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Editor) Cleanup(config *PromptConfig, val interface{}) error {
|
||||||
|
return e.Render(
|
||||||
|
EditorQuestionTemplate,
|
||||||
|
EditorTemplateData{
|
||||||
|
Editor: *e,
|
||||||
|
Answer: "<Received>",
|
||||||
|
ShowAnswer: true,
|
||||||
|
Config: config,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
1
vendor/github.com/AlecAivazis/survey/v2/filter.go
generated
vendored
Normal file
1
vendor/github.com/AlecAivazis/survey/v2/filter.go
generated
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
package survey
|
219
vendor/github.com/AlecAivazis/survey/v2/input.go
generated
vendored
Normal file
219
vendor/github.com/AlecAivazis/survey/v2/input.go
generated
vendored
Normal file
@ -0,0 +1,219 @@
|
|||||||
|
package survey
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/AlecAivazis/survey/v2/core"
|
||||||
|
"github.com/AlecAivazis/survey/v2/terminal"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
Input is a regular text input that prints each character the user types on the screen
|
||||||
|
and accepts the input with the enter key. Response type is a string.
|
||||||
|
|
||||||
|
name := ""
|
||||||
|
prompt := &survey.Input{ Message: "What is your name?" }
|
||||||
|
survey.AskOne(prompt, &name)
|
||||||
|
*/
|
||||||
|
type Input struct {
|
||||||
|
Renderer
|
||||||
|
Message string
|
||||||
|
Default string
|
||||||
|
Help string
|
||||||
|
Suggest func(toComplete string) []string
|
||||||
|
answer string
|
||||||
|
typedAnswer string
|
||||||
|
options []core.OptionAnswer
|
||||||
|
selectedIndex int
|
||||||
|
showingHelp bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// data available to the templates when processing
|
||||||
|
type InputTemplateData struct {
|
||||||
|
Input
|
||||||
|
ShowAnswer bool
|
||||||
|
ShowHelp bool
|
||||||
|
Answer string
|
||||||
|
PageEntries []core.OptionAnswer
|
||||||
|
SelectedIndex int
|
||||||
|
Config *PromptConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
// Templates with Color formatting. See Documentation: https://github.com/mgutz/ansi#style-format
|
||||||
|
var InputQuestionTemplate = `
|
||||||
|
{{- if .ShowHelp }}{{- color .Config.Icons.Help.Format }}{{ .Config.Icons.Help.Text }} {{ .Help }}{{color "reset"}}{{"\n"}}{{end}}
|
||||||
|
{{- color .Config.Icons.Question.Format }}{{ .Config.Icons.Question.Text }} {{color "reset"}}
|
||||||
|
{{- color "default+hb"}}{{ .Message }} {{color "reset"}}
|
||||||
|
{{- if .ShowAnswer}}
|
||||||
|
{{- color "cyan"}}{{.Answer}}{{color "reset"}}{{"\n"}}
|
||||||
|
{{- else if .PageEntries -}}
|
||||||
|
{{- .Answer}} [Use arrows to move, enter to select, type to continue]
|
||||||
|
{{- "\n"}}
|
||||||
|
{{- range $ix, $choice := .PageEntries}}
|
||||||
|
{{- if eq $ix $.SelectedIndex }}{{color $.Config.Icons.SelectFocus.Format }}{{ $.Config.Icons.SelectFocus.Text }} {{else}}{{color "default"}} {{end}}
|
||||||
|
{{- $choice.Value}}
|
||||||
|
{{- color "reset"}}{{"\n"}}
|
||||||
|
{{- end}}
|
||||||
|
{{- else }}
|
||||||
|
{{- if or (and .Help (not .ShowHelp)) .Suggest }}{{color "cyan"}}[
|
||||||
|
{{- if and .Help (not .ShowHelp)}}{{ print .Config.HelpInput }} for help {{- if and .Suggest}}, {{end}}{{end -}}
|
||||||
|
{{- if and .Suggest }}{{color "cyan"}}{{ print .Config.SuggestInput }} for suggestions{{end -}}
|
||||||
|
]{{color "reset"}} {{end}}
|
||||||
|
{{- if .Default}}{{color "white"}}({{.Default}}) {{color "reset"}}{{end}}
|
||||||
|
{{- end}}`
|
||||||
|
|
||||||
|
func (i *Input) onRune(config *PromptConfig) terminal.OnRuneFn {
|
||||||
|
return terminal.OnRuneFn(func(key rune, line []rune) ([]rune, bool, error) {
|
||||||
|
if i.options != nil && (key == terminal.KeyEnter || key == '\n') {
|
||||||
|
return []rune(i.answer), true, nil
|
||||||
|
} else if i.options != nil && key == terminal.KeyEscape {
|
||||||
|
i.answer = i.typedAnswer
|
||||||
|
i.options = nil
|
||||||
|
} else if key == terminal.KeyArrowUp && len(i.options) > 0 {
|
||||||
|
if i.selectedIndex == 0 {
|
||||||
|
i.selectedIndex = len(i.options) - 1
|
||||||
|
} else {
|
||||||
|
i.selectedIndex--
|
||||||
|
}
|
||||||
|
i.answer = i.options[i.selectedIndex].Value
|
||||||
|
} else if (key == terminal.KeyArrowDown || key == terminal.KeyTab) && len(i.options) > 0 {
|
||||||
|
if i.selectedIndex == len(i.options)-1 {
|
||||||
|
i.selectedIndex = 0
|
||||||
|
} else {
|
||||||
|
i.selectedIndex++
|
||||||
|
}
|
||||||
|
i.answer = i.options[i.selectedIndex].Value
|
||||||
|
} else if key == terminal.KeyTab && i.Suggest != nil {
|
||||||
|
i.answer = string(line)
|
||||||
|
i.typedAnswer = i.answer
|
||||||
|
options := i.Suggest(i.answer)
|
||||||
|
i.selectedIndex = 0
|
||||||
|
if len(options) == 0 {
|
||||||
|
return line, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
i.answer = options[0]
|
||||||
|
if len(options) == 1 {
|
||||||
|
i.typedAnswer = i.answer
|
||||||
|
i.options = nil
|
||||||
|
} else {
|
||||||
|
i.options = core.OptionAnswerList(options)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if i.options == nil {
|
||||||
|
return line, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if key >= terminal.KeySpace {
|
||||||
|
i.answer += string(key)
|
||||||
|
}
|
||||||
|
i.typedAnswer = i.answer
|
||||||
|
|
||||||
|
i.options = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
pageSize := config.PageSize
|
||||||
|
opts, idx := paginate(pageSize, i.options, i.selectedIndex)
|
||||||
|
err := i.Render(
|
||||||
|
InputQuestionTemplate,
|
||||||
|
InputTemplateData{
|
||||||
|
Input: *i,
|
||||||
|
Answer: i.answer,
|
||||||
|
ShowHelp: i.showingHelp,
|
||||||
|
SelectedIndex: idx,
|
||||||
|
PageEntries: opts,
|
||||||
|
Config: config,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
err = errReadLineAgain
|
||||||
|
}
|
||||||
|
|
||||||
|
return []rune(i.typedAnswer), true, err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var errReadLineAgain = errors.New("read line again")
|
||||||
|
|
||||||
|
func (i *Input) Prompt(config *PromptConfig) (interface{}, error) {
|
||||||
|
// render the template
|
||||||
|
err := i.Render(
|
||||||
|
InputQuestionTemplate,
|
||||||
|
InputTemplateData{
|
||||||
|
Input: *i,
|
||||||
|
Config: config,
|
||||||
|
ShowHelp: i.showingHelp,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// start reading runes from the standard in
|
||||||
|
rr := i.NewRuneReader()
|
||||||
|
_ = rr.SetTermMode()
|
||||||
|
defer func() {
|
||||||
|
_ = rr.RestoreTermMode()
|
||||||
|
}()
|
||||||
|
cursor := i.NewCursor()
|
||||||
|
if !config.ShowCursor {
|
||||||
|
cursor.Hide() // hide the cursor
|
||||||
|
defer cursor.Show() // show the cursor when we're done
|
||||||
|
}
|
||||||
|
|
||||||
|
var line []rune
|
||||||
|
|
||||||
|
for {
|
||||||
|
if i.options != nil {
|
||||||
|
line = []rune{}
|
||||||
|
}
|
||||||
|
|
||||||
|
line, err = rr.ReadLineWithDefault(0, line, i.onRune(config))
|
||||||
|
if err == errReadLineAgain {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
i.answer = string(line)
|
||||||
|
// readline print an empty line, go up before we render the follow up
|
||||||
|
cursor.Up(1)
|
||||||
|
|
||||||
|
// if we ran into the help string
|
||||||
|
if i.answer == config.HelpInput && i.Help != "" {
|
||||||
|
// show the help and prompt again
|
||||||
|
i.showingHelp = true
|
||||||
|
return i.Prompt(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the line is empty
|
||||||
|
if len(i.answer) == 0 {
|
||||||
|
// use the default value
|
||||||
|
return i.Default, err
|
||||||
|
}
|
||||||
|
|
||||||
|
lineStr := i.answer
|
||||||
|
|
||||||
|
i.AppendRenderedText(lineStr)
|
||||||
|
|
||||||
|
// we're done
|
||||||
|
return lineStr, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Input) Cleanup(config *PromptConfig, val interface{}) error {
|
||||||
|
return i.Render(
|
||||||
|
InputQuestionTemplate,
|
||||||
|
InputTemplateData{
|
||||||
|
Input: *i,
|
||||||
|
ShowAnswer: true,
|
||||||
|
Config: config,
|
||||||
|
Answer: val.(string),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
112
vendor/github.com/AlecAivazis/survey/v2/multiline.go
generated
vendored
Normal file
112
vendor/github.com/AlecAivazis/survey/v2/multiline.go
generated
vendored
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
package survey
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/AlecAivazis/survey/v2/terminal"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Multiline struct {
|
||||||
|
Renderer
|
||||||
|
Message string
|
||||||
|
Default string
|
||||||
|
Help string
|
||||||
|
}
|
||||||
|
|
||||||
|
// data available to the templates when processing
|
||||||
|
type MultilineTemplateData struct {
|
||||||
|
Multiline
|
||||||
|
Answer string
|
||||||
|
ShowAnswer bool
|
||||||
|
ShowHelp bool
|
||||||
|
Config *PromptConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
// Templates with Color formatting. See Documentation: https://github.com/mgutz/ansi#style-format
|
||||||
|
var MultilineQuestionTemplate = `
|
||||||
|
{{- if .ShowHelp }}{{- color .Config.Icons.Help.Format }}{{ .Config.Icons.Help.Text }} {{ .Help }}{{color "reset"}}{{"\n"}}{{end}}
|
||||||
|
{{- color .Config.Icons.Question.Format }}{{ .Config.Icons.Question.Text }} {{color "reset"}}
|
||||||
|
{{- color "default+hb"}}{{ .Message }} {{color "reset"}}
|
||||||
|
{{- if .ShowAnswer}}
|
||||||
|
{{- "\n"}}{{color "cyan"}}{{.Answer}}{{color "reset"}}
|
||||||
|
{{- if .Answer }}{{ "\n" }}{{ end }}
|
||||||
|
{{- else }}
|
||||||
|
{{- if .Default}}{{color "white"}}({{.Default}}) {{color "reset"}}{{end}}
|
||||||
|
{{- color "cyan"}}[Enter 2 empty lines to finish]{{color "reset"}}
|
||||||
|
{{- end}}`
|
||||||
|
|
||||||
|
func (i *Multiline) Prompt(config *PromptConfig) (interface{}, error) {
|
||||||
|
// render the template
|
||||||
|
err := i.Render(
|
||||||
|
MultilineQuestionTemplate,
|
||||||
|
MultilineTemplateData{
|
||||||
|
Multiline: *i,
|
||||||
|
Config: config,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// start reading runes from the standard in
|
||||||
|
rr := i.NewRuneReader()
|
||||||
|
_ = rr.SetTermMode()
|
||||||
|
defer func() {
|
||||||
|
_ = rr.RestoreTermMode()
|
||||||
|
}()
|
||||||
|
|
||||||
|
cursor := i.NewCursor()
|
||||||
|
|
||||||
|
multiline := make([]string, 0)
|
||||||
|
|
||||||
|
emptyOnce := false
|
||||||
|
// get the next line
|
||||||
|
for {
|
||||||
|
var line []rune
|
||||||
|
line, err = rr.ReadLine(0)
|
||||||
|
if err != nil {
|
||||||
|
return string(line), err
|
||||||
|
}
|
||||||
|
|
||||||
|
if string(line) == "" {
|
||||||
|
if emptyOnce {
|
||||||
|
numLines := len(multiline) + 2
|
||||||
|
cursor.PreviousLine(numLines)
|
||||||
|
for j := 0; j < numLines; j++ {
|
||||||
|
terminal.EraseLine(i.Stdio().Out, terminal.ERASE_LINE_ALL)
|
||||||
|
cursor.NextLine(1)
|
||||||
|
}
|
||||||
|
cursor.PreviousLine(numLines)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
emptyOnce = true
|
||||||
|
} else {
|
||||||
|
emptyOnce = false
|
||||||
|
}
|
||||||
|
multiline = append(multiline, string(line))
|
||||||
|
}
|
||||||
|
|
||||||
|
val := strings.Join(multiline, "\n")
|
||||||
|
val = strings.TrimSpace(val)
|
||||||
|
|
||||||
|
// if the line is empty
|
||||||
|
if len(val) == 0 {
|
||||||
|
// use the default value
|
||||||
|
return i.Default, err
|
||||||
|
}
|
||||||
|
|
||||||
|
i.AppendRenderedText(val)
|
||||||
|
return val, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Multiline) Cleanup(config *PromptConfig, val interface{}) error {
|
||||||
|
return i.Render(
|
||||||
|
MultilineQuestionTemplate,
|
||||||
|
MultilineTemplateData{
|
||||||
|
Multiline: *i,
|
||||||
|
Answer: val.(string),
|
||||||
|
ShowAnswer: true,
|
||||||
|
Config: config,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
360
vendor/github.com/AlecAivazis/survey/v2/multiselect.go
generated
vendored
Normal file
360
vendor/github.com/AlecAivazis/survey/v2/multiselect.go
generated
vendored
Normal file
@ -0,0 +1,360 @@
|
|||||||
|
package survey
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/AlecAivazis/survey/v2/core"
|
||||||
|
"github.com/AlecAivazis/survey/v2/terminal"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
MultiSelect is a prompt that presents a list of various options to the user
|
||||||
|
for them to select using the arrow keys and enter. Response type is a slice of strings.
|
||||||
|
|
||||||
|
days := []string{}
|
||||||
|
prompt := &survey.MultiSelect{
|
||||||
|
Message: "What days do you prefer:",
|
||||||
|
Options: []string{"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"},
|
||||||
|
}
|
||||||
|
survey.AskOne(prompt, &days)
|
||||||
|
*/
|
||||||
|
type MultiSelect struct {
|
||||||
|
Renderer
|
||||||
|
Message string
|
||||||
|
Options []string
|
||||||
|
Default interface{}
|
||||||
|
Help string
|
||||||
|
PageSize int
|
||||||
|
VimMode bool
|
||||||
|
FilterMessage string
|
||||||
|
Filter func(filter string, value string, index int) bool
|
||||||
|
Description func(value string, index int) string
|
||||||
|
filter string
|
||||||
|
selectedIndex int
|
||||||
|
checked map[int]bool
|
||||||
|
showingHelp bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// data available to the templates when processing
|
||||||
|
type MultiSelectTemplateData struct {
|
||||||
|
MultiSelect
|
||||||
|
Answer string
|
||||||
|
ShowAnswer bool
|
||||||
|
Checked map[int]bool
|
||||||
|
SelectedIndex int
|
||||||
|
ShowHelp bool
|
||||||
|
Description func(value string, index int) string
|
||||||
|
PageEntries []core.OptionAnswer
|
||||||
|
Config *PromptConfig
|
||||||
|
|
||||||
|
// These fields are used when rendering an individual option
|
||||||
|
CurrentOpt core.OptionAnswer
|
||||||
|
CurrentIndex int
|
||||||
|
}
|
||||||
|
|
||||||
|
// IterateOption sets CurrentOpt and CurrentIndex appropriately so a multiselect option can be rendered individually
|
||||||
|
func (m MultiSelectTemplateData) IterateOption(ix int, opt core.OptionAnswer) interface{} {
|
||||||
|
copy := m
|
||||||
|
copy.CurrentIndex = ix
|
||||||
|
copy.CurrentOpt = opt
|
||||||
|
return copy
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m MultiSelectTemplateData) GetDescription(opt core.OptionAnswer) string {
|
||||||
|
if m.Description == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return m.Description(opt.Value, opt.Index)
|
||||||
|
}
|
||||||
|
|
||||||
|
var MultiSelectQuestionTemplate = `
|
||||||
|
{{- define "option"}}
|
||||||
|
{{- if eq .SelectedIndex .CurrentIndex }}{{color .Config.Icons.SelectFocus.Format }}{{ .Config.Icons.SelectFocus.Text }}{{color "reset"}}{{else}} {{end}}
|
||||||
|
{{- if index .Checked .CurrentOpt.Index }}{{color .Config.Icons.MarkedOption.Format }} {{ .Config.Icons.MarkedOption.Text }} {{else}}{{color .Config.Icons.UnmarkedOption.Format }} {{ .Config.Icons.UnmarkedOption.Text }} {{end}}
|
||||||
|
{{- color "reset"}}
|
||||||
|
{{- " "}}{{- .CurrentOpt.Value}}{{ if ne ($.GetDescription .CurrentOpt) "" }} - {{color "cyan"}}{{ $.GetDescription .CurrentOpt }}{{color "reset"}}{{end}}
|
||||||
|
{{end}}
|
||||||
|
{{- if .ShowHelp }}{{- color .Config.Icons.Help.Format }}{{ .Config.Icons.Help.Text }} {{ .Help }}{{color "reset"}}{{"\n"}}{{end}}
|
||||||
|
{{- color .Config.Icons.Question.Format }}{{ .Config.Icons.Question.Text }} {{color "reset"}}
|
||||||
|
{{- color "default+hb"}}{{ .Message }}{{ .FilterMessage }}{{color "reset"}}
|
||||||
|
{{- if .ShowAnswer}}{{color "cyan"}} {{.Answer}}{{color "reset"}}{{"\n"}}
|
||||||
|
{{- else }}
|
||||||
|
{{- " "}}{{- color "cyan"}}[Use arrows to move, space to select,{{- if not .Config.RemoveSelectAll }} <right> to all,{{end}}{{- if not .Config.RemoveSelectNone }} <left> to none,{{end}} type to filter{{- if and .Help (not .ShowHelp)}}, {{ .Config.HelpInput }} for more help{{end}}]{{color "reset"}}
|
||||||
|
{{- "\n"}}
|
||||||
|
{{- range $ix, $option := .PageEntries}}
|
||||||
|
{{- template "option" $.IterateOption $ix $option}}
|
||||||
|
{{- end}}
|
||||||
|
{{- end}}`
|
||||||
|
|
||||||
|
// OnChange is called on every keypress.
|
||||||
|
func (m *MultiSelect) OnChange(key rune, config *PromptConfig) {
|
||||||
|
options := m.filterOptions(config)
|
||||||
|
oldFilter := m.filter
|
||||||
|
|
||||||
|
if key == terminal.KeyArrowUp || (m.VimMode && key == 'k') {
|
||||||
|
// if we are at the top of the list
|
||||||
|
if m.selectedIndex == 0 {
|
||||||
|
// go to the bottom
|
||||||
|
m.selectedIndex = len(options) - 1
|
||||||
|
} else {
|
||||||
|
// decrement the selected index
|
||||||
|
m.selectedIndex--
|
||||||
|
}
|
||||||
|
} else if key == terminal.KeyTab || key == terminal.KeyArrowDown || (m.VimMode && key == 'j') {
|
||||||
|
// if we are at the bottom of the list
|
||||||
|
if m.selectedIndex == len(options)-1 {
|
||||||
|
// start at the top
|
||||||
|
m.selectedIndex = 0
|
||||||
|
} else {
|
||||||
|
// increment the selected index
|
||||||
|
m.selectedIndex++
|
||||||
|
}
|
||||||
|
// if the user pressed down and there is room to move
|
||||||
|
} else if key == terminal.KeySpace {
|
||||||
|
// the option they have selected
|
||||||
|
if m.selectedIndex < len(options) {
|
||||||
|
selectedOpt := options[m.selectedIndex]
|
||||||
|
|
||||||
|
// if we haven't seen this index before
|
||||||
|
if old, ok := m.checked[selectedOpt.Index]; !ok {
|
||||||
|
// set the value to true
|
||||||
|
m.checked[selectedOpt.Index] = true
|
||||||
|
} else {
|
||||||
|
// otherwise just invert the current value
|
||||||
|
m.checked[selectedOpt.Index] = !old
|
||||||
|
}
|
||||||
|
if !config.KeepFilter {
|
||||||
|
m.filter = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// only show the help message if we have one to show
|
||||||
|
} else if string(key) == config.HelpInput && m.Help != "" {
|
||||||
|
m.showingHelp = true
|
||||||
|
} else if key == terminal.KeyEscape {
|
||||||
|
m.VimMode = !m.VimMode
|
||||||
|
} else if key == terminal.KeyDeleteWord || key == terminal.KeyDeleteLine {
|
||||||
|
m.filter = ""
|
||||||
|
} else if key == terminal.KeyDelete || key == terminal.KeyBackspace {
|
||||||
|
if m.filter != "" {
|
||||||
|
runeFilter := []rune(m.filter)
|
||||||
|
m.filter = string(runeFilter[0 : len(runeFilter)-1])
|
||||||
|
}
|
||||||
|
} else if key >= terminal.KeySpace {
|
||||||
|
m.filter += string(key)
|
||||||
|
m.VimMode = false
|
||||||
|
} else if !config.RemoveSelectAll && key == terminal.KeyArrowRight {
|
||||||
|
for _, v := range options {
|
||||||
|
m.checked[v.Index] = true
|
||||||
|
}
|
||||||
|
if !config.KeepFilter {
|
||||||
|
m.filter = ""
|
||||||
|
}
|
||||||
|
} else if !config.RemoveSelectNone && key == terminal.KeyArrowLeft {
|
||||||
|
for _, v := range options {
|
||||||
|
m.checked[v.Index] = false
|
||||||
|
}
|
||||||
|
if !config.KeepFilter {
|
||||||
|
m.filter = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m.FilterMessage = ""
|
||||||
|
if m.filter != "" {
|
||||||
|
m.FilterMessage = " " + m.filter
|
||||||
|
}
|
||||||
|
if oldFilter != m.filter {
|
||||||
|
// filter changed
|
||||||
|
options = m.filterOptions(config)
|
||||||
|
if len(options) > 0 && len(options) <= m.selectedIndex {
|
||||||
|
m.selectedIndex = len(options) - 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// paginate the options
|
||||||
|
// figure out the page size
|
||||||
|
pageSize := m.PageSize
|
||||||
|
// if we dont have a specific one
|
||||||
|
if pageSize == 0 {
|
||||||
|
// grab the global value
|
||||||
|
pageSize = config.PageSize
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO if we have started filtering and were looking at the end of a list
|
||||||
|
// and we have modified the filter then we should move the page back!
|
||||||
|
opts, idx := paginate(pageSize, options, m.selectedIndex)
|
||||||
|
|
||||||
|
tmplData := MultiSelectTemplateData{
|
||||||
|
MultiSelect: *m,
|
||||||
|
SelectedIndex: idx,
|
||||||
|
Checked: m.checked,
|
||||||
|
ShowHelp: m.showingHelp,
|
||||||
|
Description: m.Description,
|
||||||
|
PageEntries: opts,
|
||||||
|
Config: config,
|
||||||
|
}
|
||||||
|
|
||||||
|
// render the options
|
||||||
|
_ = m.RenderWithCursorOffset(MultiSelectQuestionTemplate, tmplData, opts, idx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MultiSelect) filterOptions(config *PromptConfig) []core.OptionAnswer {
|
||||||
|
// the filtered list
|
||||||
|
answers := []core.OptionAnswer{}
|
||||||
|
|
||||||
|
// if there is no filter applied
|
||||||
|
if m.filter == "" {
|
||||||
|
// return all of the options
|
||||||
|
return core.OptionAnswerList(m.Options)
|
||||||
|
}
|
||||||
|
|
||||||
|
// the filter to apply
|
||||||
|
filter := m.Filter
|
||||||
|
if filter == nil {
|
||||||
|
filter = config.Filter
|
||||||
|
}
|
||||||
|
|
||||||
|
// apply the filter to each option
|
||||||
|
for i, opt := range m.Options {
|
||||||
|
// i the filter says to include the option
|
||||||
|
if filter(m.filter, opt, i) {
|
||||||
|
answers = append(answers, core.OptionAnswer{
|
||||||
|
Index: i,
|
||||||
|
Value: opt,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// we're done here
|
||||||
|
return answers
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MultiSelect) Prompt(config *PromptConfig) (interface{}, error) {
|
||||||
|
// compute the default state
|
||||||
|
m.checked = make(map[int]bool)
|
||||||
|
// if there is a default
|
||||||
|
if m.Default != nil {
|
||||||
|
// if the default is string values
|
||||||
|
if defaultValues, ok := m.Default.([]string); ok {
|
||||||
|
for _, dflt := range defaultValues {
|
||||||
|
for i, opt := range m.Options {
|
||||||
|
// if the option corresponds to the default
|
||||||
|
if opt == dflt {
|
||||||
|
// we found our initial value
|
||||||
|
m.checked[i] = true
|
||||||
|
// stop looking
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// if the default value is index values
|
||||||
|
} else if defaultIndices, ok := m.Default.([]int); ok {
|
||||||
|
// go over every index we need to enable by default
|
||||||
|
for _, idx := range defaultIndices {
|
||||||
|
// and enable it
|
||||||
|
m.checked[idx] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if there are no options to render
|
||||||
|
if len(m.Options) == 0 {
|
||||||
|
// we failed
|
||||||
|
return "", errors.New("please provide options to select from")
|
||||||
|
}
|
||||||
|
|
||||||
|
// figure out the page size
|
||||||
|
pageSize := m.PageSize
|
||||||
|
// if we dont have a specific one
|
||||||
|
if pageSize == 0 {
|
||||||
|
// grab the global value
|
||||||
|
pageSize = config.PageSize
|
||||||
|
}
|
||||||
|
// paginate the options
|
||||||
|
// build up a list of option answers
|
||||||
|
opts, idx := paginate(pageSize, core.OptionAnswerList(m.Options), m.selectedIndex)
|
||||||
|
|
||||||
|
cursor := m.NewCursor()
|
||||||
|
cursor.Save() // for proper cursor placement during selection
|
||||||
|
cursor.Hide() // hide the cursor
|
||||||
|
defer cursor.Show() // show the cursor when we're done
|
||||||
|
defer cursor.Restore() // clear any accessibility offsetting on exit
|
||||||
|
|
||||||
|
tmplData := MultiSelectTemplateData{
|
||||||
|
MultiSelect: *m,
|
||||||
|
SelectedIndex: idx,
|
||||||
|
Description: m.Description,
|
||||||
|
Checked: m.checked,
|
||||||
|
PageEntries: opts,
|
||||||
|
Config: config,
|
||||||
|
}
|
||||||
|
|
||||||
|
// ask the question
|
||||||
|
err := m.RenderWithCursorOffset(MultiSelectQuestionTemplate, tmplData, opts, idx)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
rr := m.NewRuneReader()
|
||||||
|
_ = rr.SetTermMode()
|
||||||
|
defer func() {
|
||||||
|
_ = rr.RestoreTermMode()
|
||||||
|
}()
|
||||||
|
|
||||||
|
// start waiting for input
|
||||||
|
for {
|
||||||
|
r, _, err := rr.ReadRune()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if r == '\r' || r == '\n' {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if r == terminal.KeyInterrupt {
|
||||||
|
return "", terminal.InterruptErr
|
||||||
|
}
|
||||||
|
if r == terminal.KeyEndTransmission {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
m.OnChange(r, config)
|
||||||
|
}
|
||||||
|
m.filter = ""
|
||||||
|
m.FilterMessage = ""
|
||||||
|
|
||||||
|
answers := []core.OptionAnswer{}
|
||||||
|
for i, option := range m.Options {
|
||||||
|
if val, ok := m.checked[i]; ok && val {
|
||||||
|
answers = append(answers, core.OptionAnswer{Value: option, Index: i})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return answers, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup removes the options section, and renders the ask like a normal question.
|
||||||
|
func (m *MultiSelect) Cleanup(config *PromptConfig, val interface{}) error {
|
||||||
|
// the answer to show
|
||||||
|
answer := ""
|
||||||
|
for _, ans := range val.([]core.OptionAnswer) {
|
||||||
|
answer = fmt.Sprintf("%s, %s", answer, ans.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we answered anything
|
||||||
|
if len(answer) > 2 {
|
||||||
|
// remove the precending commas
|
||||||
|
answer = answer[2:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// execute the output summary template with the answer
|
||||||
|
return m.Render(
|
||||||
|
MultiSelectQuestionTemplate,
|
||||||
|
MultiSelectTemplateData{
|
||||||
|
MultiSelect: *m,
|
||||||
|
SelectedIndex: m.selectedIndex,
|
||||||
|
Checked: m.checked,
|
||||||
|
Answer: answer,
|
||||||
|
ShowAnswer: true,
|
||||||
|
Description: m.Description,
|
||||||
|
Config: config,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
106
vendor/github.com/AlecAivazis/survey/v2/password.go
generated
vendored
Normal file
106
vendor/github.com/AlecAivazis/survey/v2/password.go
generated
vendored
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
package survey
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/AlecAivazis/survey/v2/core"
|
||||||
|
"github.com/AlecAivazis/survey/v2/terminal"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
Password is like a normal Input but the text shows up as *'s and there is no default. Response
|
||||||
|
type is a string.
|
||||||
|
|
||||||
|
password := ""
|
||||||
|
prompt := &survey.Password{ Message: "Please type your password" }
|
||||||
|
survey.AskOne(prompt, &password)
|
||||||
|
*/
|
||||||
|
type Password struct {
|
||||||
|
Renderer
|
||||||
|
Message string
|
||||||
|
Help string
|
||||||
|
}
|
||||||
|
|
||||||
|
type PasswordTemplateData struct {
|
||||||
|
Password
|
||||||
|
ShowHelp bool
|
||||||
|
Config *PromptConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
// PasswordQuestionTemplate is a template with color formatting. See Documentation: https://github.com/mgutz/ansi#style-format
|
||||||
|
var PasswordQuestionTemplate = `
|
||||||
|
{{- if .ShowHelp }}{{- color .Config.Icons.Help.Format }}{{ .Config.Icons.Help.Text }} {{ .Help }}{{color "reset"}}{{"\n"}}{{end}}
|
||||||
|
{{- color .Config.Icons.Question.Format }}{{ .Config.Icons.Question.Text }} {{color "reset"}}
|
||||||
|
{{- color "default+hb"}}{{ .Message }} {{color "reset"}}
|
||||||
|
{{- if and .Help (not .ShowHelp)}}{{color "cyan"}}[{{ .Config.HelpInput }} for help]{{color "reset"}} {{end}}`
|
||||||
|
|
||||||
|
func (p *Password) Prompt(config *PromptConfig) (interface{}, error) {
|
||||||
|
// render the question template
|
||||||
|
userOut, _, err := core.RunTemplate(
|
||||||
|
PasswordQuestionTemplate,
|
||||||
|
PasswordTemplateData{
|
||||||
|
Password: *p,
|
||||||
|
Config: config,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := fmt.Fprint(terminal.NewAnsiStdout(p.Stdio().Out), userOut); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
rr := p.NewRuneReader()
|
||||||
|
_ = rr.SetTermMode()
|
||||||
|
defer func() {
|
||||||
|
_ = rr.RestoreTermMode()
|
||||||
|
}()
|
||||||
|
|
||||||
|
// no help msg? Just return any response
|
||||||
|
if p.Help == "" {
|
||||||
|
line, err := rr.ReadLine(config.HideCharacter)
|
||||||
|
return string(line), err
|
||||||
|
}
|
||||||
|
|
||||||
|
cursor := p.NewCursor()
|
||||||
|
|
||||||
|
var line []rune
|
||||||
|
// process answers looking for help prompt answer
|
||||||
|
for {
|
||||||
|
line, err = rr.ReadLine(config.HideCharacter)
|
||||||
|
if err != nil {
|
||||||
|
return string(line), err
|
||||||
|
}
|
||||||
|
|
||||||
|
if string(line) == config.HelpInput {
|
||||||
|
// terminal will echo the \n so we need to jump back up one row
|
||||||
|
cursor.PreviousLine(1)
|
||||||
|
|
||||||
|
err = p.Render(
|
||||||
|
PasswordQuestionTemplate,
|
||||||
|
PasswordTemplateData{
|
||||||
|
Password: *p,
|
||||||
|
ShowHelp: true,
|
||||||
|
Config: config,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
lineStr := string(line)
|
||||||
|
p.AppendRenderedText(strings.Repeat(string(config.HideCharacter), len(lineStr)))
|
||||||
|
return lineStr, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup hides the string with a fixed number of characters.
|
||||||
|
func (prompt *Password) Cleanup(config *PromptConfig, val interface{}) error {
|
||||||
|
return nil
|
||||||
|
}
|
195
vendor/github.com/AlecAivazis/survey/v2/renderer.go
generated
vendored
Normal file
195
vendor/github.com/AlecAivazis/survey/v2/renderer.go
generated
vendored
Normal file
@ -0,0 +1,195 @@
|
|||||||
|
package survey
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"github.com/AlecAivazis/survey/v2/core"
|
||||||
|
"github.com/AlecAivazis/survey/v2/terminal"
|
||||||
|
"golang.org/x/term"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Renderer struct {
|
||||||
|
stdio terminal.Stdio
|
||||||
|
renderedErrors bytes.Buffer
|
||||||
|
renderedText bytes.Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
type ErrorTemplateData struct {
|
||||||
|
Error error
|
||||||
|
Icon Icon
|
||||||
|
}
|
||||||
|
|
||||||
|
var ErrorTemplate = `{{color .Icon.Format }}{{ .Icon.Text }} Sorry, your reply was invalid: {{ .Error.Error }}{{color "reset"}}
|
||||||
|
`
|
||||||
|
|
||||||
|
func (r *Renderer) WithStdio(stdio terminal.Stdio) {
|
||||||
|
r.stdio = stdio
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Renderer) Stdio() terminal.Stdio {
|
||||||
|
return r.stdio
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Renderer) NewRuneReader() *terminal.RuneReader {
|
||||||
|
return terminal.NewRuneReader(r.stdio)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Renderer) NewCursor() *terminal.Cursor {
|
||||||
|
return &terminal.Cursor{
|
||||||
|
In: r.stdio.In,
|
||||||
|
Out: r.stdio.Out,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Renderer) Error(config *PromptConfig, invalid error) error {
|
||||||
|
// cleanup the currently rendered errors
|
||||||
|
r.resetPrompt(r.countLines(r.renderedErrors))
|
||||||
|
r.renderedErrors.Reset()
|
||||||
|
|
||||||
|
// cleanup the rest of the prompt
|
||||||
|
r.resetPrompt(r.countLines(r.renderedText))
|
||||||
|
r.renderedText.Reset()
|
||||||
|
|
||||||
|
userOut, layoutOut, err := core.RunTemplate(ErrorTemplate, &ErrorTemplateData{
|
||||||
|
Error: invalid,
|
||||||
|
Icon: config.Icons.Error,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// send the message to the user
|
||||||
|
if _, err := fmt.Fprint(terminal.NewAnsiStdout(r.stdio.Out), userOut); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// add the printed text to the rendered error buffer so we can cleanup later
|
||||||
|
r.appendRenderedError(layoutOut)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Renderer) OffsetCursor(offset int) {
|
||||||
|
cursor := r.NewCursor()
|
||||||
|
for offset > 0 {
|
||||||
|
cursor.PreviousLine(1)
|
||||||
|
offset--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Renderer) Render(tmpl string, data interface{}) error {
|
||||||
|
// cleanup the currently rendered text
|
||||||
|
lineCount := r.countLines(r.renderedText)
|
||||||
|
r.resetPrompt(lineCount)
|
||||||
|
r.renderedText.Reset()
|
||||||
|
|
||||||
|
// render the template summarizing the current state
|
||||||
|
userOut, layoutOut, err := core.RunTemplate(tmpl, data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// print the summary
|
||||||
|
if _, err := fmt.Fprint(terminal.NewAnsiStdout(r.stdio.Out), userOut); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// add the printed text to the rendered text buffer so we can cleanup later
|
||||||
|
r.AppendRenderedText(layoutOut)
|
||||||
|
|
||||||
|
// nothing went wrong
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Renderer) RenderWithCursorOffset(tmpl string, data IterableOpts, opts []core.OptionAnswer, idx int) error {
|
||||||
|
cursor := r.NewCursor()
|
||||||
|
cursor.Restore() // clear any accessibility offsetting
|
||||||
|
|
||||||
|
if err := r.Render(tmpl, data); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cursor.Save()
|
||||||
|
|
||||||
|
offset := computeCursorOffset(MultiSelectQuestionTemplate, data, opts, idx, r.termWidthSafe())
|
||||||
|
r.OffsetCursor(offset)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// appendRenderedError appends text to the renderer's error buffer
|
||||||
|
// which is used to track what has been printed. It is not exported
|
||||||
|
// as errors should only be displayed via Error(config, error).
|
||||||
|
func (r *Renderer) appendRenderedError(text string) {
|
||||||
|
r.renderedErrors.WriteString(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AppendRenderedText appends text to the renderer's text buffer
|
||||||
|
// which is used to track of what has been printed. The buffer is used
|
||||||
|
// to calculate how many lines to erase before updating the prompt.
|
||||||
|
func (r *Renderer) AppendRenderedText(text string) {
|
||||||
|
r.renderedText.WriteString(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Renderer) resetPrompt(lines int) {
|
||||||
|
// clean out current line in case tmpl didnt end in newline
|
||||||
|
cursor := r.NewCursor()
|
||||||
|
cursor.HorizontalAbsolute(0)
|
||||||
|
terminal.EraseLine(r.stdio.Out, terminal.ERASE_LINE_ALL)
|
||||||
|
// clean up what we left behind last time
|
||||||
|
for i := 0; i < lines; i++ {
|
||||||
|
cursor.PreviousLine(1)
|
||||||
|
terminal.EraseLine(r.stdio.Out, terminal.ERASE_LINE_ALL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Renderer) termWidth() (int, error) {
|
||||||
|
fd := int(r.stdio.Out.Fd())
|
||||||
|
termWidth, _, err := term.GetSize(fd)
|
||||||
|
return termWidth, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Renderer) termWidthSafe() int {
|
||||||
|
w, err := r.termWidth()
|
||||||
|
if err != nil || w == 0 {
|
||||||
|
// if we got an error due to terminal.GetSize not being supported
|
||||||
|
// on current platform then just assume a very wide terminal
|
||||||
|
w = 10000
|
||||||
|
}
|
||||||
|
return w
|
||||||
|
}
|
||||||
|
|
||||||
|
// countLines will return the count of `\n` with the addition of any
|
||||||
|
// lines that have wrapped due to narrow terminal width
|
||||||
|
func (r *Renderer) countLines(buf bytes.Buffer) int {
|
||||||
|
w := r.termWidthSafe()
|
||||||
|
|
||||||
|
bufBytes := buf.Bytes()
|
||||||
|
|
||||||
|
count := 0
|
||||||
|
curr := 0
|
||||||
|
for curr < len(bufBytes) {
|
||||||
|
var delim int
|
||||||
|
// read until the next newline or the end of the string
|
||||||
|
relDelim := bytes.IndexRune(bufBytes[curr:], '\n')
|
||||||
|
if relDelim != -1 {
|
||||||
|
count += 1 // new line found, add it to the count
|
||||||
|
delim = curr + relDelim
|
||||||
|
} else {
|
||||||
|
delim = len(bufBytes) // no new line found, read rest of text
|
||||||
|
}
|
||||||
|
|
||||||
|
str := string(bufBytes[curr:delim])
|
||||||
|
if lineWidth := terminal.StringWidth(str); lineWidth > w {
|
||||||
|
// account for word wrapping
|
||||||
|
count += lineWidth / w
|
||||||
|
if (lineWidth % w) == 0 {
|
||||||
|
// content whose width is exactly a multiplier of available width should not
|
||||||
|
// count as having wrapped on the last line
|
||||||
|
count -= 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
curr = delim + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return count
|
||||||
|
}
|
329
vendor/github.com/AlecAivazis/survey/v2/select.go
generated
vendored
Normal file
329
vendor/github.com/AlecAivazis/survey/v2/select.go
generated
vendored
Normal file
@ -0,0 +1,329 @@
|
|||||||
|
package survey
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/AlecAivazis/survey/v2/core"
|
||||||
|
"github.com/AlecAivazis/survey/v2/terminal"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
Select is a prompt that presents a list of various options to the user
|
||||||
|
for them to select using the arrow keys and enter. Response type is a string.
|
||||||
|
|
||||||
|
color := ""
|
||||||
|
prompt := &survey.Select{
|
||||||
|
Message: "Choose a color:",
|
||||||
|
Options: []string{"red", "blue", "green"},
|
||||||
|
}
|
||||||
|
survey.AskOne(prompt, &color)
|
||||||
|
*/
|
||||||
|
type Select struct {
|
||||||
|
Renderer
|
||||||
|
Message string
|
||||||
|
Options []string
|
||||||
|
Default interface{}
|
||||||
|
Help string
|
||||||
|
PageSize int
|
||||||
|
VimMode bool
|
||||||
|
FilterMessage string
|
||||||
|
Filter func(filter string, value string, index int) bool
|
||||||
|
Description func(value string, index int) string
|
||||||
|
filter string
|
||||||
|
selectedIndex int
|
||||||
|
showingHelp bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// SelectTemplateData is the data available to the templates when processing
|
||||||
|
type SelectTemplateData struct {
|
||||||
|
Select
|
||||||
|
PageEntries []core.OptionAnswer
|
||||||
|
SelectedIndex int
|
||||||
|
Answer string
|
||||||
|
ShowAnswer bool
|
||||||
|
ShowHelp bool
|
||||||
|
Description func(value string, index int) string
|
||||||
|
Config *PromptConfig
|
||||||
|
|
||||||
|
// These fields are used when rendering an individual option
|
||||||
|
CurrentOpt core.OptionAnswer
|
||||||
|
CurrentIndex int
|
||||||
|
}
|
||||||
|
|
||||||
|
// IterateOption sets CurrentOpt and CurrentIndex appropriately so a select option can be rendered individually
|
||||||
|
func (s SelectTemplateData) IterateOption(ix int, opt core.OptionAnswer) interface{} {
|
||||||
|
copy := s
|
||||||
|
copy.CurrentIndex = ix
|
||||||
|
copy.CurrentOpt = opt
|
||||||
|
return copy
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s SelectTemplateData) GetDescription(opt core.OptionAnswer) string {
|
||||||
|
if s.Description == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return s.Description(opt.Value, opt.Index)
|
||||||
|
}
|
||||||
|
|
||||||
|
var SelectQuestionTemplate = `
|
||||||
|
{{- define "option"}}
|
||||||
|
{{- if eq .SelectedIndex .CurrentIndex }}{{color .Config.Icons.SelectFocus.Format }}{{ .Config.Icons.SelectFocus.Text }} {{else}}{{color "default"}} {{end}}
|
||||||
|
{{- .CurrentOpt.Value}}{{ if ne ($.GetDescription .CurrentOpt) "" }} - {{color "cyan"}}{{ $.GetDescription .CurrentOpt }}{{end}}
|
||||||
|
{{- color "reset"}}
|
||||||
|
{{end}}
|
||||||
|
{{- if .ShowHelp }}{{- color .Config.Icons.Help.Format }}{{ .Config.Icons.Help.Text }} {{ .Help }}{{color "reset"}}{{"\n"}}{{end}}
|
||||||
|
{{- color .Config.Icons.Question.Format }}{{ .Config.Icons.Question.Text }} {{color "reset"}}
|
||||||
|
{{- color "default+hb"}}{{ .Message }}{{ .FilterMessage }}{{color "reset"}}
|
||||||
|
{{- if .ShowAnswer}}{{color "cyan"}} {{.Answer}}{{color "reset"}}{{"\n"}}
|
||||||
|
{{- else}}
|
||||||
|
{{- " "}}{{- color "cyan"}}[Use arrows to move, type to filter{{- if and .Help (not .ShowHelp)}}, {{ .Config.HelpInput }} for more help{{end}}]{{color "reset"}}
|
||||||
|
{{- "\n"}}
|
||||||
|
{{- range $ix, $option := .PageEntries}}
|
||||||
|
{{- template "option" $.IterateOption $ix $option}}
|
||||||
|
{{- end}}
|
||||||
|
{{- end}}`
|
||||||
|
|
||||||
|
// OnChange is called on every keypress.
|
||||||
|
func (s *Select) OnChange(key rune, config *PromptConfig) bool {
|
||||||
|
options := s.filterOptions(config)
|
||||||
|
oldFilter := s.filter
|
||||||
|
|
||||||
|
// if the user pressed the enter key and the index is a valid option
|
||||||
|
if key == terminal.KeyEnter || key == '\n' {
|
||||||
|
// if the selected index is a valid option
|
||||||
|
if len(options) > 0 && s.selectedIndex < len(options) {
|
||||||
|
|
||||||
|
// we're done (stop prompting the user)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// we're not done (keep prompting)
|
||||||
|
return false
|
||||||
|
|
||||||
|
// if the user pressed the up arrow or 'k' to emulate vim
|
||||||
|
} else if (key == terminal.KeyArrowUp || (s.VimMode && key == 'k')) && len(options) > 0 {
|
||||||
|
// if we are at the top of the list
|
||||||
|
if s.selectedIndex == 0 {
|
||||||
|
// start from the button
|
||||||
|
s.selectedIndex = len(options) - 1
|
||||||
|
} else {
|
||||||
|
// otherwise we are not at the top of the list so decrement the selected index
|
||||||
|
s.selectedIndex--
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the user pressed down or 'j' to emulate vim
|
||||||
|
} else if (key == terminal.KeyTab || key == terminal.KeyArrowDown || (s.VimMode && key == 'j')) && len(options) > 0 {
|
||||||
|
// if we are at the bottom of the list
|
||||||
|
if s.selectedIndex == len(options)-1 {
|
||||||
|
// start from the top
|
||||||
|
s.selectedIndex = 0
|
||||||
|
} else {
|
||||||
|
// increment the selected index
|
||||||
|
s.selectedIndex++
|
||||||
|
}
|
||||||
|
// only show the help message if we have one
|
||||||
|
} else if string(key) == config.HelpInput && s.Help != "" {
|
||||||
|
s.showingHelp = true
|
||||||
|
// if the user wants to toggle vim mode on/off
|
||||||
|
} else if key == terminal.KeyEscape {
|
||||||
|
s.VimMode = !s.VimMode
|
||||||
|
// if the user hits any of the keys that clear the filter
|
||||||
|
} else if key == terminal.KeyDeleteWord || key == terminal.KeyDeleteLine {
|
||||||
|
s.filter = ""
|
||||||
|
// if the user is deleting a character in the filter
|
||||||
|
} else if key == terminal.KeyDelete || key == terminal.KeyBackspace {
|
||||||
|
// if there is content in the filter to delete
|
||||||
|
if s.filter != "" {
|
||||||
|
runeFilter := []rune(s.filter)
|
||||||
|
// subtract a line from the current filter
|
||||||
|
s.filter = string(runeFilter[0 : len(runeFilter)-1])
|
||||||
|
// we removed the last value in the filter
|
||||||
|
}
|
||||||
|
} else if key >= terminal.KeySpace {
|
||||||
|
s.filter += string(key)
|
||||||
|
// make sure vim mode is disabled
|
||||||
|
s.VimMode = false
|
||||||
|
}
|
||||||
|
|
||||||
|
s.FilterMessage = ""
|
||||||
|
if s.filter != "" {
|
||||||
|
s.FilterMessage = " " + s.filter
|
||||||
|
}
|
||||||
|
if oldFilter != s.filter {
|
||||||
|
// filter changed
|
||||||
|
options = s.filterOptions(config)
|
||||||
|
if len(options) > 0 && len(options) <= s.selectedIndex {
|
||||||
|
s.selectedIndex = len(options) - 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// figure out the options and index to render
|
||||||
|
// figure out the page size
|
||||||
|
pageSize := s.PageSize
|
||||||
|
// if we dont have a specific one
|
||||||
|
if pageSize == 0 {
|
||||||
|
// grab the global value
|
||||||
|
pageSize = config.PageSize
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO if we have started filtering and were looking at the end of a list
|
||||||
|
// and we have modified the filter then we should move the page back!
|
||||||
|
opts, idx := paginate(pageSize, options, s.selectedIndex)
|
||||||
|
|
||||||
|
tmplData := SelectTemplateData{
|
||||||
|
Select: *s,
|
||||||
|
SelectedIndex: idx,
|
||||||
|
ShowHelp: s.showingHelp,
|
||||||
|
Description: s.Description,
|
||||||
|
PageEntries: opts,
|
||||||
|
Config: config,
|
||||||
|
}
|
||||||
|
|
||||||
|
// render the options
|
||||||
|
_ = s.RenderWithCursorOffset(SelectQuestionTemplate, tmplData, opts, idx)
|
||||||
|
|
||||||
|
// keep prompting
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Select) filterOptions(config *PromptConfig) []core.OptionAnswer {
|
||||||
|
// the filtered list
|
||||||
|
answers := []core.OptionAnswer{}
|
||||||
|
|
||||||
|
// if there is no filter applied
|
||||||
|
if s.filter == "" {
|
||||||
|
return core.OptionAnswerList(s.Options)
|
||||||
|
}
|
||||||
|
|
||||||
|
// the filter to apply
|
||||||
|
filter := s.Filter
|
||||||
|
if filter == nil {
|
||||||
|
filter = config.Filter
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, opt := range s.Options {
|
||||||
|
// i the filter says to include the option
|
||||||
|
if filter(s.filter, opt, i) {
|
||||||
|
answers = append(answers, core.OptionAnswer{
|
||||||
|
Index: i,
|
||||||
|
Value: opt,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// return the list of answers
|
||||||
|
return answers
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Select) Prompt(config *PromptConfig) (interface{}, error) {
|
||||||
|
// if there are no options to render
|
||||||
|
if len(s.Options) == 0 {
|
||||||
|
// we failed
|
||||||
|
return "", errors.New("please provide options to select from")
|
||||||
|
}
|
||||||
|
|
||||||
|
s.selectedIndex = 0
|
||||||
|
if s.Default != nil {
|
||||||
|
switch defaultValue := s.Default.(type) {
|
||||||
|
case string:
|
||||||
|
var found bool
|
||||||
|
for i, opt := range s.Options {
|
||||||
|
if opt == defaultValue {
|
||||||
|
s.selectedIndex = i
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
return "", fmt.Errorf("default value %q not found in options", defaultValue)
|
||||||
|
}
|
||||||
|
case int:
|
||||||
|
if defaultValue >= len(s.Options) {
|
||||||
|
return "", fmt.Errorf("default index %d exceeds the number of options", defaultValue)
|
||||||
|
}
|
||||||
|
s.selectedIndex = defaultValue
|
||||||
|
default:
|
||||||
|
return "", errors.New("default value of select must be an int or string")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// figure out the page size
|
||||||
|
pageSize := s.PageSize
|
||||||
|
// if we dont have a specific one
|
||||||
|
if pageSize == 0 {
|
||||||
|
// grab the global value
|
||||||
|
pageSize = config.PageSize
|
||||||
|
}
|
||||||
|
|
||||||
|
// figure out the options and index to render
|
||||||
|
opts, idx := paginate(pageSize, core.OptionAnswerList(s.Options), s.selectedIndex)
|
||||||
|
|
||||||
|
cursor := s.NewCursor()
|
||||||
|
cursor.Save() // for proper cursor placement during selection
|
||||||
|
cursor.Hide() // hide the cursor
|
||||||
|
defer cursor.Show() // show the cursor when we're done
|
||||||
|
defer cursor.Restore() // clear any accessibility offsetting on exit
|
||||||
|
|
||||||
|
tmplData := SelectTemplateData{
|
||||||
|
Select: *s,
|
||||||
|
SelectedIndex: idx,
|
||||||
|
Description: s.Description,
|
||||||
|
ShowHelp: s.showingHelp,
|
||||||
|
PageEntries: opts,
|
||||||
|
Config: config,
|
||||||
|
}
|
||||||
|
|
||||||
|
// ask the question
|
||||||
|
err := s.RenderWithCursorOffset(SelectQuestionTemplate, tmplData, opts, idx)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
rr := s.NewRuneReader()
|
||||||
|
_ = rr.SetTermMode()
|
||||||
|
defer func() {
|
||||||
|
_ = rr.RestoreTermMode()
|
||||||
|
}()
|
||||||
|
|
||||||
|
// start waiting for input
|
||||||
|
for {
|
||||||
|
r, _, err := rr.ReadRune()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if r == terminal.KeyInterrupt {
|
||||||
|
return "", terminal.InterruptErr
|
||||||
|
}
|
||||||
|
if r == terminal.KeyEndTransmission {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if s.OnChange(r, config) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
options := s.filterOptions(config)
|
||||||
|
s.filter = ""
|
||||||
|
s.FilterMessage = ""
|
||||||
|
|
||||||
|
if s.selectedIndex < len(options) {
|
||||||
|
return options[s.selectedIndex], err
|
||||||
|
}
|
||||||
|
|
||||||
|
return options[0], err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Select) Cleanup(config *PromptConfig, val interface{}) error {
|
||||||
|
cursor := s.NewCursor()
|
||||||
|
cursor.Restore()
|
||||||
|
return s.Render(
|
||||||
|
SelectQuestionTemplate,
|
||||||
|
SelectTemplateData{
|
||||||
|
Select: *s,
|
||||||
|
Answer: val.(core.OptionAnswer).Value,
|
||||||
|
ShowAnswer: true,
|
||||||
|
Description: s.Description,
|
||||||
|
Config: config,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
474
vendor/github.com/AlecAivazis/survey/v2/survey.go
generated
vendored
Normal file
474
vendor/github.com/AlecAivazis/survey/v2/survey.go
generated
vendored
Normal file
@ -0,0 +1,474 @@
|
|||||||
|
package survey
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
|
"github.com/AlecAivazis/survey/v2/core"
|
||||||
|
"github.com/AlecAivazis/survey/v2/terminal"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DefaultAskOptions is the default options on ask, using the OS stdio.
|
||||||
|
func defaultAskOptions() *AskOptions {
|
||||||
|
return &AskOptions{
|
||||||
|
Stdio: terminal.Stdio{
|
||||||
|
In: os.Stdin,
|
||||||
|
Out: os.Stdout,
|
||||||
|
Err: os.Stderr,
|
||||||
|
},
|
||||||
|
PromptConfig: PromptConfig{
|
||||||
|
PageSize: 7,
|
||||||
|
HelpInput: "?",
|
||||||
|
SuggestInput: "tab",
|
||||||
|
Icons: IconSet{
|
||||||
|
Error: Icon{
|
||||||
|
Text: "X",
|
||||||
|
Format: "red",
|
||||||
|
},
|
||||||
|
Help: Icon{
|
||||||
|
Text: "?",
|
||||||
|
Format: "cyan",
|
||||||
|
},
|
||||||
|
Question: Icon{
|
||||||
|
Text: "?",
|
||||||
|
Format: "green+hb",
|
||||||
|
},
|
||||||
|
MarkedOption: Icon{
|
||||||
|
Text: "[x]",
|
||||||
|
Format: "green",
|
||||||
|
},
|
||||||
|
UnmarkedOption: Icon{
|
||||||
|
Text: "[ ]",
|
||||||
|
Format: "default+hb",
|
||||||
|
},
|
||||||
|
SelectFocus: Icon{
|
||||||
|
Text: ">",
|
||||||
|
Format: "cyan+b",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Filter: func(filter string, value string, index int) (include bool) {
|
||||||
|
filter = strings.ToLower(filter)
|
||||||
|
|
||||||
|
// include this option if it matches
|
||||||
|
return strings.Contains(strings.ToLower(value), filter)
|
||||||
|
},
|
||||||
|
KeepFilter: false,
|
||||||
|
ShowCursor: false,
|
||||||
|
RemoveSelectAll: false,
|
||||||
|
RemoveSelectNone: false,
|
||||||
|
HideCharacter: '*',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func defaultPromptConfig() *PromptConfig {
|
||||||
|
return &defaultAskOptions().PromptConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultIcons() *IconSet {
|
||||||
|
return &defaultPromptConfig().Icons
|
||||||
|
}
|
||||||
|
|
||||||
|
// OptionAnswer is an ergonomic alias for core.OptionAnswer
|
||||||
|
type OptionAnswer = core.OptionAnswer
|
||||||
|
|
||||||
|
// Icon holds the text and format to show for a particular icon
|
||||||
|
type Icon struct {
|
||||||
|
Text string
|
||||||
|
Format string
|
||||||
|
}
|
||||||
|
|
||||||
|
// IconSet holds the icons to use for various prompts
|
||||||
|
type IconSet struct {
|
||||||
|
HelpInput Icon
|
||||||
|
Error Icon
|
||||||
|
Help Icon
|
||||||
|
Question Icon
|
||||||
|
MarkedOption Icon
|
||||||
|
UnmarkedOption Icon
|
||||||
|
SelectFocus Icon
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validator is a function passed to a Question after a user has provided a response.
|
||||||
|
// If the function returns an error, then the user will be prompted again for another
|
||||||
|
// response.
|
||||||
|
type Validator func(ans interface{}) error
|
||||||
|
|
||||||
|
// Transformer is a function passed to a Question after a user has provided a response.
|
||||||
|
// The function can be used to implement a custom logic that will result to return
|
||||||
|
// a different representation of the given answer.
|
||||||
|
//
|
||||||
|
// Look `TransformString`, `ToLower` `Title` and `ComposeTransformers` for more.
|
||||||
|
type Transformer func(ans interface{}) (newAns interface{})
|
||||||
|
|
||||||
|
// Question is the core data structure for a survey questionnaire.
|
||||||
|
type Question struct {
|
||||||
|
Name string
|
||||||
|
Prompt Prompt
|
||||||
|
Validate Validator
|
||||||
|
Transform Transformer
|
||||||
|
}
|
||||||
|
|
||||||
|
// PromptConfig holds the global configuration for a prompt
|
||||||
|
type PromptConfig struct {
|
||||||
|
PageSize int
|
||||||
|
Icons IconSet
|
||||||
|
HelpInput string
|
||||||
|
SuggestInput string
|
||||||
|
Filter func(filter string, option string, index int) bool
|
||||||
|
KeepFilter bool
|
||||||
|
ShowCursor bool
|
||||||
|
RemoveSelectAll bool
|
||||||
|
RemoveSelectNone bool
|
||||||
|
HideCharacter rune
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prompt is the primary interface for the objects that can take user input
|
||||||
|
// and return a response.
|
||||||
|
type Prompt interface {
|
||||||
|
Prompt(config *PromptConfig) (interface{}, error)
|
||||||
|
Cleanup(*PromptConfig, interface{}) error
|
||||||
|
Error(*PromptConfig, error) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// PromptAgainer Interface for Prompts that support prompting again after invalid input
|
||||||
|
type PromptAgainer interface {
|
||||||
|
PromptAgain(config *PromptConfig, invalid interface{}, err error) (interface{}, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AskOpt allows setting optional ask options.
|
||||||
|
type AskOpt func(options *AskOptions) error
|
||||||
|
|
||||||
|
// AskOptions provides additional options on ask.
|
||||||
|
type AskOptions struct {
|
||||||
|
Stdio terminal.Stdio
|
||||||
|
Validators []Validator
|
||||||
|
PromptConfig PromptConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithStdio specifies the standard input, output and error files survey
|
||||||
|
// interacts with. By default, these are os.Stdin, os.Stdout, and os.Stderr.
|
||||||
|
func WithStdio(in terminal.FileReader, out terminal.FileWriter, err io.Writer) AskOpt {
|
||||||
|
return func(options *AskOptions) error {
|
||||||
|
options.Stdio.In = in
|
||||||
|
options.Stdio.Out = out
|
||||||
|
options.Stdio.Err = err
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithFilter specifies the default filter to use when asking questions.
|
||||||
|
func WithFilter(filter func(filter string, value string, index int) (include bool)) AskOpt {
|
||||||
|
return func(options *AskOptions) error {
|
||||||
|
// save the filter internally
|
||||||
|
options.PromptConfig.Filter = filter
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithKeepFilter sets the if the filter is kept after selections
|
||||||
|
func WithKeepFilter(KeepFilter bool) AskOpt {
|
||||||
|
return func(options *AskOptions) error {
|
||||||
|
// set the page size
|
||||||
|
options.PromptConfig.KeepFilter = KeepFilter
|
||||||
|
|
||||||
|
// nothing went wrong
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithRemoveSelectAll remove the select all option in Multiselect
|
||||||
|
func WithRemoveSelectAll() AskOpt {
|
||||||
|
return func(options *AskOptions) error {
|
||||||
|
options.PromptConfig.RemoveSelectAll = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithRemoveSelectNone remove the select none/unselect all in Multiselect
|
||||||
|
func WithRemoveSelectNone() AskOpt {
|
||||||
|
return func(options *AskOptions) error {
|
||||||
|
options.PromptConfig.RemoveSelectNone = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithValidator specifies a validator to use while prompting the user
|
||||||
|
func WithValidator(v Validator) AskOpt {
|
||||||
|
return func(options *AskOptions) error {
|
||||||
|
// add the provided validator to the list
|
||||||
|
options.Validators = append(options.Validators, v)
|
||||||
|
|
||||||
|
// nothing went wrong
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type wantsStdio interface {
|
||||||
|
WithStdio(terminal.Stdio)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithPageSize sets the default page size used by prompts
|
||||||
|
func WithPageSize(pageSize int) AskOpt {
|
||||||
|
return func(options *AskOptions) error {
|
||||||
|
// set the page size
|
||||||
|
options.PromptConfig.PageSize = pageSize
|
||||||
|
|
||||||
|
// nothing went wrong
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithHelpInput changes the character that prompts look for to give the user helpful information.
|
||||||
|
func WithHelpInput(r rune) AskOpt {
|
||||||
|
return func(options *AskOptions) error {
|
||||||
|
// set the input character
|
||||||
|
options.PromptConfig.HelpInput = string(r)
|
||||||
|
|
||||||
|
// nothing went wrong
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithIcons sets the icons that will be used when prompting the user
|
||||||
|
func WithIcons(setIcons func(*IconSet)) AskOpt {
|
||||||
|
return func(options *AskOptions) error {
|
||||||
|
// update the default icons with whatever the user says
|
||||||
|
setIcons(&options.PromptConfig.Icons)
|
||||||
|
|
||||||
|
// nothing went wrong
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithShowCursor sets the show cursor behavior when prompting the user
|
||||||
|
func WithShowCursor(ShowCursor bool) AskOpt {
|
||||||
|
return func(options *AskOptions) error {
|
||||||
|
// set the page size
|
||||||
|
options.PromptConfig.ShowCursor = ShowCursor
|
||||||
|
|
||||||
|
// nothing went wrong
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithHideCharacter sets the default character shown instead of the password for password inputs
|
||||||
|
func WithHideCharacter(char rune) AskOpt {
|
||||||
|
return func(options *AskOptions) error {
|
||||||
|
// set the hide character
|
||||||
|
options.PromptConfig.HideCharacter = char
|
||||||
|
|
||||||
|
// nothing went wrong
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
AskOne performs the prompt for a single prompt and asks for validation if required.
|
||||||
|
Response types should be something that can be casted from the response type designated
|
||||||
|
in the documentation. For example:
|
||||||
|
|
||||||
|
name := ""
|
||||||
|
prompt := &survey.Input{
|
||||||
|
Message: "name",
|
||||||
|
}
|
||||||
|
|
||||||
|
survey.AskOne(prompt, &name)
|
||||||
|
*/
|
||||||
|
func AskOne(p Prompt, response interface{}, opts ...AskOpt) error {
|
||||||
|
err := Ask([]*Question{{Prompt: p}}, response, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Ask performs the prompt loop, asking for validation when appropriate. The response
|
||||||
|
type can be one of two options. If a struct is passed, the answer will be written to
|
||||||
|
the field whose name matches the Name field on the corresponding question. Field types
|
||||||
|
should be something that can be casted from the response type designated in the
|
||||||
|
documentation. Note, a survey tag can also be used to identify a Otherwise, a
|
||||||
|
map[string]interface{} can be passed, responses will be written to the key with the
|
||||||
|
matching name. For example:
|
||||||
|
|
||||||
|
qs := []*survey.Question{
|
||||||
|
{
|
||||||
|
Name: "name",
|
||||||
|
Prompt: &survey.Input{Message: "What is your name?"},
|
||||||
|
Validate: survey.Required,
|
||||||
|
Transform: survey.Title,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
answers := struct{ Name string }{}
|
||||||
|
|
||||||
|
|
||||||
|
err := survey.Ask(qs, &answers)
|
||||||
|
*/
|
||||||
|
func Ask(qs []*Question, response interface{}, opts ...AskOpt) error {
|
||||||
|
// build up the configuration options
|
||||||
|
options := defaultAskOptions()
|
||||||
|
for _, opt := range opts {
|
||||||
|
if opt == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := opt(options); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we weren't passed a place to record the answers
|
||||||
|
if response == nil {
|
||||||
|
// we can't go any further
|
||||||
|
return errors.New("cannot call Ask() with a nil reference to record the answers")
|
||||||
|
}
|
||||||
|
|
||||||
|
validate := func(q *Question, val interface{}) error {
|
||||||
|
if q.Validate != nil {
|
||||||
|
if err := q.Validate(val); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, v := range options.Validators {
|
||||||
|
if err := v(val); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// go over every question
|
||||||
|
for _, q := range qs {
|
||||||
|
// If Prompt implements controllable stdio, pass in specified stdio.
|
||||||
|
if p, ok := q.Prompt.(wantsStdio); ok {
|
||||||
|
p.WithStdio(options.Stdio)
|
||||||
|
}
|
||||||
|
|
||||||
|
var ans interface{}
|
||||||
|
var validationErr error
|
||||||
|
// prompt and validation loop
|
||||||
|
for {
|
||||||
|
if validationErr != nil {
|
||||||
|
if err := q.Prompt.Error(&options.PromptConfig, validationErr); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
if promptAgainer, ok := q.Prompt.(PromptAgainer); ok && validationErr != nil {
|
||||||
|
ans, err = promptAgainer.PromptAgain(&options.PromptConfig, ans, validationErr)
|
||||||
|
} else {
|
||||||
|
ans, err = q.Prompt.Prompt(&options.PromptConfig)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
validationErr = validate(q, ans)
|
||||||
|
if validationErr == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if q.Transform != nil {
|
||||||
|
// check if we have a transformer available, if so
|
||||||
|
// then try to acquire the new representation of the
|
||||||
|
// answer, if the resulting answer is not nil.
|
||||||
|
if newAns := q.Transform(ans); newAns != nil {
|
||||||
|
ans = newAns
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// tell the prompt to cleanup with the validated value
|
||||||
|
if err := q.Prompt.Cleanup(&options.PromptConfig, ans); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// add it to the map
|
||||||
|
if err := core.WriteAnswer(response, q.Name, ans); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// return the response
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// paginate returns a single page of choices given the page size, the total list of
|
||||||
|
// possible choices, and the current selected index in the total list.
|
||||||
|
func paginate(pageSize int, choices []core.OptionAnswer, sel int) ([]core.OptionAnswer, int) {
|
||||||
|
var start, end, cursor int
|
||||||
|
|
||||||
|
if len(choices) < pageSize {
|
||||||
|
// if we dont have enough options to fill a page
|
||||||
|
start = 0
|
||||||
|
end = len(choices)
|
||||||
|
cursor = sel
|
||||||
|
|
||||||
|
} else if sel < pageSize/2 {
|
||||||
|
// if we are in the first half page
|
||||||
|
start = 0
|
||||||
|
end = pageSize
|
||||||
|
cursor = sel
|
||||||
|
|
||||||
|
} else if len(choices)-sel-1 < pageSize/2 {
|
||||||
|
// if we are in the last half page
|
||||||
|
start = len(choices) - pageSize
|
||||||
|
end = len(choices)
|
||||||
|
cursor = sel - start
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// somewhere in the middle
|
||||||
|
above := pageSize / 2
|
||||||
|
below := pageSize - above
|
||||||
|
|
||||||
|
cursor = pageSize / 2
|
||||||
|
start = sel - above
|
||||||
|
end = sel + below
|
||||||
|
}
|
||||||
|
|
||||||
|
// return the subset we care about and the index
|
||||||
|
return choices[start:end], cursor
|
||||||
|
}
|
||||||
|
|
||||||
|
type IterableOpts interface {
|
||||||
|
IterateOption(int, core.OptionAnswer) interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func computeCursorOffset(tmpl string, data IterableOpts, opts []core.OptionAnswer, idx, tWidth int) int {
|
||||||
|
tmpls, err := core.GetTemplatePair(tmpl)
|
||||||
|
if err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
t := tmpls[0]
|
||||||
|
|
||||||
|
renderOpt := func(ix int, opt core.OptionAnswer) string {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
_ = t.ExecuteTemplate(&buf, "option", data.IterateOption(ix, opt))
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
offset := len(opts) - idx
|
||||||
|
|
||||||
|
for i, o := range opts {
|
||||||
|
if i < idx {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
renderedOpt := renderOpt(i, o)
|
||||||
|
valWidth := utf8.RuneCount([]byte(renderedOpt))
|
||||||
|
if valWidth > tWidth {
|
||||||
|
splitCount := valWidth / tWidth
|
||||||
|
if valWidth%tWidth == 0 {
|
||||||
|
splitCount -= 1
|
||||||
|
}
|
||||||
|
offset += splitCount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return offset
|
||||||
|
}
|
22
vendor/github.com/AlecAivazis/survey/v2/terminal/LICENSE.txt
generated
vendored
Normal file
22
vendor/github.com/AlecAivazis/survey/v2/terminal/LICENSE.txt
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
Copyright (c) 2014 Takashi Kokubun
|
||||||
|
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of this software and associated documentation files (the
|
||||||
|
"Software"), to deal in the Software without restriction, including
|
||||||
|
without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be
|
||||||
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
3
vendor/github.com/AlecAivazis/survey/v2/terminal/README.md
generated
vendored
Normal file
3
vendor/github.com/AlecAivazis/survey/v2/terminal/README.md
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# survey/terminal
|
||||||
|
|
||||||
|
This package started as a copy of [kokuban/go-ansi](http://github.com/k0kubun/go-ansi) but has since been modified to fit survey's specific needs.
|
22
vendor/github.com/AlecAivazis/survey/v2/terminal/buffered_reader.go
generated
vendored
Normal file
22
vendor/github.com/AlecAivazis/survey/v2/terminal/buffered_reader.go
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
package terminal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BufferedReader struct {
|
||||||
|
In io.Reader
|
||||||
|
Buffer *bytes.Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (br *BufferedReader) Read(p []byte) (int, error) {
|
||||||
|
n, err := br.Buffer.Read(p)
|
||||||
|
if err != nil && err != io.EOF {
|
||||||
|
return n, err
|
||||||
|
} else if err == nil {
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return br.In.Read(p[n:])
|
||||||
|
}
|
209
vendor/github.com/AlecAivazis/survey/v2/terminal/cursor.go
generated
vendored
Normal file
209
vendor/github.com/AlecAivazis/survey/v2/terminal/cursor.go
generated
vendored
Normal file
@ -0,0 +1,209 @@
|
|||||||
|
//go:build !windows
|
||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package terminal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
var COORDINATE_SYSTEM_BEGIN Short = 1
|
||||||
|
|
||||||
|
var dsrPattern = regexp.MustCompile(`\x1b\[(\d+);(\d+)R$`)
|
||||||
|
|
||||||
|
type Cursor struct {
|
||||||
|
In FileReader
|
||||||
|
Out FileWriter
|
||||||
|
}
|
||||||
|
|
||||||
|
// Up moves the cursor n cells to up.
|
||||||
|
func (c *Cursor) Up(n int) error {
|
||||||
|
_, err := fmt.Fprintf(c.Out, "\x1b[%dA", n)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Down moves the cursor n cells to down.
|
||||||
|
func (c *Cursor) Down(n int) error {
|
||||||
|
_, err := fmt.Fprintf(c.Out, "\x1b[%dB", n)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Forward moves the cursor n cells to right.
|
||||||
|
func (c *Cursor) Forward(n int) error {
|
||||||
|
_, err := fmt.Fprintf(c.Out, "\x1b[%dC", n)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Back moves the cursor n cells to left.
|
||||||
|
func (c *Cursor) Back(n int) error {
|
||||||
|
_, err := fmt.Fprintf(c.Out, "\x1b[%dD", n)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// NextLine moves cursor to beginning of the line n lines down.
|
||||||
|
func (c *Cursor) NextLine(n int) error {
|
||||||
|
if err := c.Down(1); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return c.HorizontalAbsolute(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PreviousLine moves cursor to beginning of the line n lines up.
|
||||||
|
func (c *Cursor) PreviousLine(n int) error {
|
||||||
|
if err := c.Up(1); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return c.HorizontalAbsolute(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HorizontalAbsolute moves cursor horizontally to x.
|
||||||
|
func (c *Cursor) HorizontalAbsolute(x int) error {
|
||||||
|
_, err := fmt.Fprintf(c.Out, "\x1b[%dG", x)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show shows the cursor.
|
||||||
|
func (c *Cursor) Show() error {
|
||||||
|
_, err := fmt.Fprint(c.Out, "\x1b[?25h")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide hide the cursor.
|
||||||
|
func (c *Cursor) Hide() error {
|
||||||
|
_, err := fmt.Fprint(c.Out, "\x1b[?25l")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// move moves the cursor to a specific x,y location.
|
||||||
|
func (c *Cursor) move(x int, y int) error {
|
||||||
|
_, err := fmt.Fprintf(c.Out, "\x1b[%d;%df", x, y)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save saves the current position
|
||||||
|
func (c *Cursor) Save() error {
|
||||||
|
_, err := fmt.Fprint(c.Out, "\x1b7")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore restores the saved position of the cursor
|
||||||
|
func (c *Cursor) Restore() error {
|
||||||
|
_, err := fmt.Fprint(c.Out, "\x1b8")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// for comparability purposes between windows
|
||||||
|
// in unix we need to print out a new line on some terminals
|
||||||
|
func (c *Cursor) MoveNextLine(cur *Coord, terminalSize *Coord) error {
|
||||||
|
if cur.Y == terminalSize.Y {
|
||||||
|
if _, err := fmt.Fprintln(c.Out); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return c.NextLine(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Location returns the current location of the cursor in the terminal
|
||||||
|
func (c *Cursor) Location(buf *bytes.Buffer) (*Coord, error) {
|
||||||
|
// ANSI escape sequence for DSR - Device Status Report
|
||||||
|
// https://en.wikipedia.org/wiki/ANSI_escape_code#CSI_sequences
|
||||||
|
if _, err := fmt.Fprint(c.Out, "\x1b[6n"); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// There may be input in Stdin prior to CursorLocation so make sure we don't
|
||||||
|
// drop those bytes.
|
||||||
|
var loc []int
|
||||||
|
var match string
|
||||||
|
for loc == nil {
|
||||||
|
// Reports the cursor position (CPR) to the application as (as though typed at
|
||||||
|
// the keyboard) ESC[n;mR, where n is the row and m is the column.
|
||||||
|
reader := bufio.NewReader(c.In)
|
||||||
|
text, err := reader.ReadSlice(byte('R'))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
loc = dsrPattern.FindStringIndex(string(text))
|
||||||
|
if loc == nil {
|
||||||
|
// After reading slice to byte 'R', the bufio Reader may have read more
|
||||||
|
// bytes into its internal buffer which will be discarded on next ReadSlice.
|
||||||
|
// We create a temporary buffer to read the remaining buffered slice and
|
||||||
|
// write them to output buffer.
|
||||||
|
buffered := make([]byte, reader.Buffered())
|
||||||
|
_, err = io.ReadFull(reader, buffered)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stdin contains R that doesn't match DSR, so pass the bytes along to
|
||||||
|
// output buffer.
|
||||||
|
buf.Write(text)
|
||||||
|
buf.Write(buffered)
|
||||||
|
} else {
|
||||||
|
// Write the non-matching leading bytes to output buffer.
|
||||||
|
buf.Write(text[:loc[0]])
|
||||||
|
|
||||||
|
// Save the matching bytes to extract the row and column of the cursor.
|
||||||
|
match = string(text[loc[0]:loc[1]])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
matches := dsrPattern.FindStringSubmatch(string(match))
|
||||||
|
if len(matches) != 3 {
|
||||||
|
return nil, fmt.Errorf("incorrect number of matches: %d", len(matches))
|
||||||
|
}
|
||||||
|
|
||||||
|
col, err := strconv.Atoi(matches[2])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
row, err := strconv.Atoi(matches[1])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Coord{Short(col), Short(row)}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cur Coord) CursorIsAtLineEnd(size *Coord) bool {
|
||||||
|
return cur.X == size.X
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cur Coord) CursorIsAtLineBegin() bool {
|
||||||
|
return cur.X == COORDINATE_SYSTEM_BEGIN
|
||||||
|
}
|
||||||
|
|
||||||
|
// Size returns the height and width of the terminal.
|
||||||
|
func (c *Cursor) Size(buf *bytes.Buffer) (*Coord, error) {
|
||||||
|
// the general approach here is to move the cursor to the very bottom
|
||||||
|
// of the terminal, ask for the current location and then move the
|
||||||
|
// cursor back where we started
|
||||||
|
|
||||||
|
// hide the cursor (so it doesn't blink when getting the size of the terminal)
|
||||||
|
c.Hide()
|
||||||
|
defer c.Show()
|
||||||
|
|
||||||
|
// save the current location of the cursor
|
||||||
|
c.Save()
|
||||||
|
defer c.Restore()
|
||||||
|
|
||||||
|
// move the cursor to the very bottom of the terminal
|
||||||
|
c.move(999, 999)
|
||||||
|
|
||||||
|
// ask for the current location
|
||||||
|
bottom, err := c.Location(buf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// since the bottom was calculated in the lower right corner, it
|
||||||
|
// is the dimensions we are looking for
|
||||||
|
return bottom, nil
|
||||||
|
}
|
164
vendor/github.com/AlecAivazis/survey/v2/terminal/cursor_windows.go
generated
vendored
Normal file
164
vendor/github.com/AlecAivazis/survey/v2/terminal/cursor_windows.go
generated
vendored
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
package terminal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
var COORDINATE_SYSTEM_BEGIN Short = 0
|
||||||
|
|
||||||
|
// shared variable to save the cursor location from CursorSave()
|
||||||
|
var cursorLoc Coord
|
||||||
|
|
||||||
|
type Cursor struct {
|
||||||
|
In FileReader
|
||||||
|
Out FileWriter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cursor) Up(n int) error {
|
||||||
|
return c.cursorMove(0, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cursor) Down(n int) error {
|
||||||
|
return c.cursorMove(0, -1*n)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cursor) Forward(n int) error {
|
||||||
|
return c.cursorMove(n, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cursor) Back(n int) error {
|
||||||
|
return c.cursorMove(-1*n, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// save the cursor location
|
||||||
|
func (c *Cursor) Save() error {
|
||||||
|
loc, err := c.Location(nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cursorLoc = *loc
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cursor) Restore() error {
|
||||||
|
handle := syscall.Handle(c.Out.Fd())
|
||||||
|
// restore it to the original position
|
||||||
|
_, _, err := procSetConsoleCursorPosition.Call(uintptr(handle), uintptr(*(*int32)(unsafe.Pointer(&cursorLoc))))
|
||||||
|
return normalizeError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cur Coord) CursorIsAtLineEnd(size *Coord) bool {
|
||||||
|
return cur.X == size.X
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cur Coord) CursorIsAtLineBegin() bool {
|
||||||
|
return cur.X == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cursor) cursorMove(x int, y int) error {
|
||||||
|
handle := syscall.Handle(c.Out.Fd())
|
||||||
|
|
||||||
|
var csbi consoleScreenBufferInfo
|
||||||
|
if _, _, err := procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))); normalizeError(err) != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var cursor Coord
|
||||||
|
cursor.X = csbi.cursorPosition.X + Short(x)
|
||||||
|
cursor.Y = csbi.cursorPosition.Y + Short(y)
|
||||||
|
|
||||||
|
_, _, err := procSetConsoleCursorPosition.Call(uintptr(handle), uintptr(*(*int32)(unsafe.Pointer(&cursor))))
|
||||||
|
return normalizeError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cursor) NextLine(n int) error {
|
||||||
|
if err := c.Up(n); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return c.HorizontalAbsolute(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cursor) PreviousLine(n int) error {
|
||||||
|
if err := c.Down(n); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return c.HorizontalAbsolute(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// for comparability purposes between windows
|
||||||
|
// in windows we don't have to print out a new line
|
||||||
|
func (c *Cursor) MoveNextLine(cur *Coord, terminalSize *Coord) error {
|
||||||
|
return c.NextLine(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cursor) HorizontalAbsolute(x int) error {
|
||||||
|
handle := syscall.Handle(c.Out.Fd())
|
||||||
|
|
||||||
|
var csbi consoleScreenBufferInfo
|
||||||
|
if _, _, err := procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))); normalizeError(err) != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var cursor Coord
|
||||||
|
cursor.X = Short(x)
|
||||||
|
cursor.Y = csbi.cursorPosition.Y
|
||||||
|
|
||||||
|
if csbi.size.X < cursor.X {
|
||||||
|
cursor.X = csbi.size.X
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, err := procSetConsoleCursorPosition.Call(uintptr(handle), uintptr(*(*int32)(unsafe.Pointer(&cursor))))
|
||||||
|
return normalizeError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cursor) Show() error {
|
||||||
|
handle := syscall.Handle(c.Out.Fd())
|
||||||
|
|
||||||
|
var cci consoleCursorInfo
|
||||||
|
if _, _, err := procGetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&cci))); normalizeError(err) != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cci.visible = 1
|
||||||
|
|
||||||
|
_, _, err := procSetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&cci)))
|
||||||
|
return normalizeError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cursor) Hide() error {
|
||||||
|
handle := syscall.Handle(c.Out.Fd())
|
||||||
|
|
||||||
|
var cci consoleCursorInfo
|
||||||
|
if _, _, err := procGetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&cci))); normalizeError(err) != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cci.visible = 0
|
||||||
|
|
||||||
|
_, _, err := procSetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&cci)))
|
||||||
|
return normalizeError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cursor) Location(buf *bytes.Buffer) (*Coord, error) {
|
||||||
|
handle := syscall.Handle(c.Out.Fd())
|
||||||
|
|
||||||
|
var csbi consoleScreenBufferInfo
|
||||||
|
if _, _, err := procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))); normalizeError(err) != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &csbi.cursorPosition, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cursor) Size(buf *bytes.Buffer) (*Coord, error) {
|
||||||
|
handle := syscall.Handle(c.Out.Fd())
|
||||||
|
|
||||||
|
var csbi consoleScreenBufferInfo
|
||||||
|
if _, _, err := procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))); normalizeError(err) != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// windows' coordinate system begins at (0, 0)
|
||||||
|
csbi.size.X--
|
||||||
|
csbi.size.Y--
|
||||||
|
return &csbi.size, nil
|
||||||
|
}
|
9
vendor/github.com/AlecAivazis/survey/v2/terminal/display.go
generated
vendored
Normal file
9
vendor/github.com/AlecAivazis/survey/v2/terminal/display.go
generated
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
package terminal
|
||||||
|
|
||||||
|
type EraseLineMode int
|
||||||
|
|
||||||
|
const (
|
||||||
|
ERASE_LINE_END EraseLineMode = iota
|
||||||
|
ERASE_LINE_START
|
||||||
|
ERASE_LINE_ALL
|
||||||
|
)
|
13
vendor/github.com/AlecAivazis/survey/v2/terminal/display_posix.go
generated
vendored
Normal file
13
vendor/github.com/AlecAivazis/survey/v2/terminal/display_posix.go
generated
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
//go:build !windows
|
||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package terminal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func EraseLine(out FileWriter, mode EraseLineMode) error {
|
||||||
|
_, err := fmt.Fprintf(out, "\x1b[%dK", mode)
|
||||||
|
return err
|
||||||
|
}
|
31
vendor/github.com/AlecAivazis/survey/v2/terminal/display_windows.go
generated
vendored
Normal file
31
vendor/github.com/AlecAivazis/survey/v2/terminal/display_windows.go
generated
vendored
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
package terminal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
func EraseLine(out FileWriter, mode EraseLineMode) error {
|
||||||
|
handle := syscall.Handle(out.Fd())
|
||||||
|
|
||||||
|
var csbi consoleScreenBufferInfo
|
||||||
|
if _, _, err := procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))); normalizeError(err) != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var w uint32
|
||||||
|
var x Short
|
||||||
|
cursor := csbi.cursorPosition
|
||||||
|
switch mode {
|
||||||
|
case ERASE_LINE_END:
|
||||||
|
x = csbi.size.X
|
||||||
|
case ERASE_LINE_START:
|
||||||
|
x = 0
|
||||||
|
case ERASE_LINE_ALL:
|
||||||
|
cursor.X = 0
|
||||||
|
x = csbi.size.X
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, err := procFillConsoleOutputCharacter.Call(uintptr(handle), uintptr(' '), uintptr(x), uintptr(*(*int32)(unsafe.Pointer(&cursor))), uintptr(unsafe.Pointer(&w)))
|
||||||
|
return normalizeError(err)
|
||||||
|
}
|
10
vendor/github.com/AlecAivazis/survey/v2/terminal/error.go
generated
vendored
Normal file
10
vendor/github.com/AlecAivazis/survey/v2/terminal/error.go
generated
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
package terminal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
//lint:ignore ST1012 keeping old name for backwards compatibility
|
||||||
|
InterruptErr = errors.New("interrupt")
|
||||||
|
)
|
20
vendor/github.com/AlecAivazis/survey/v2/terminal/output.go
generated
vendored
Normal file
20
vendor/github.com/AlecAivazis/survey/v2/terminal/output.go
generated
vendored
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
//go:build !windows
|
||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package terminal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewAnsiStdout returns special stdout, which converts escape sequences to Windows API calls
|
||||||
|
// on Windows environment.
|
||||||
|
func NewAnsiStdout(out FileWriter) io.Writer {
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAnsiStderr returns special stderr, which converts escape sequences to Windows API calls
|
||||||
|
// on Windows environment.
|
||||||
|
func NewAnsiStderr(out FileWriter) io.Writer {
|
||||||
|
return out
|
||||||
|
}
|
253
vendor/github.com/AlecAivazis/survey/v2/terminal/output_windows.go
generated
vendored
Normal file
253
vendor/github.com/AlecAivazis/survey/v2/terminal/output_windows.go
generated
vendored
Normal file
@ -0,0 +1,253 @@
|
|||||||
|
package terminal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/mattn/go-isatty"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
foregroundBlue = 0x1
|
||||||
|
foregroundGreen = 0x2
|
||||||
|
foregroundRed = 0x4
|
||||||
|
foregroundIntensity = 0x8
|
||||||
|
foregroundMask = (foregroundRed | foregroundBlue | foregroundGreen | foregroundIntensity)
|
||||||
|
backgroundBlue = 0x10
|
||||||
|
backgroundGreen = 0x20
|
||||||
|
backgroundRed = 0x40
|
||||||
|
backgroundIntensity = 0x80
|
||||||
|
backgroundMask = (backgroundRed | backgroundBlue | backgroundGreen | backgroundIntensity)
|
||||||
|
)
|
||||||
|
|
||||||
|
type Writer struct {
|
||||||
|
out FileWriter
|
||||||
|
handle syscall.Handle
|
||||||
|
orgAttr word
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAnsiStdout(out FileWriter) io.Writer {
|
||||||
|
var csbi consoleScreenBufferInfo
|
||||||
|
if !isatty.IsTerminal(out.Fd()) {
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
handle := syscall.Handle(out.Fd())
|
||||||
|
procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
|
||||||
|
return &Writer{out: out, handle: handle, orgAttr: csbi.attributes}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAnsiStderr(out FileWriter) io.Writer {
|
||||||
|
var csbi consoleScreenBufferInfo
|
||||||
|
if !isatty.IsTerminal(out.Fd()) {
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
handle := syscall.Handle(out.Fd())
|
||||||
|
procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
|
||||||
|
return &Writer{out: out, handle: handle, orgAttr: csbi.attributes}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Writer) Write(data []byte) (n int, err error) {
|
||||||
|
r := bytes.NewReader(data)
|
||||||
|
|
||||||
|
for {
|
||||||
|
var ch rune
|
||||||
|
var size int
|
||||||
|
ch, size, err = r.ReadRune()
|
||||||
|
if err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
n += size
|
||||||
|
|
||||||
|
switch ch {
|
||||||
|
case '\x1b':
|
||||||
|
size, err = w.handleEscape(r)
|
||||||
|
n += size
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
_, err = fmt.Fprint(w.out, string(ch))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Writer) handleEscape(r *bytes.Reader) (n int, err error) {
|
||||||
|
buf := make([]byte, 0, 10)
|
||||||
|
buf = append(buf, "\x1b"...)
|
||||||
|
|
||||||
|
var ch rune
|
||||||
|
var size int
|
||||||
|
// Check '[' continues after \x1b
|
||||||
|
ch, size, err = r.ReadRune()
|
||||||
|
if err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
fmt.Fprint(w.out, string(buf))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
n += size
|
||||||
|
if ch != '[' {
|
||||||
|
fmt.Fprint(w.out, string(buf))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse escape code
|
||||||
|
var code rune
|
||||||
|
argBuf := make([]byte, 0, 10)
|
||||||
|
for {
|
||||||
|
ch, size, err = r.ReadRune()
|
||||||
|
if err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
fmt.Fprint(w.out, string(buf))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
n += size
|
||||||
|
if ('a' <= ch && ch <= 'z') || ('A' <= ch && ch <= 'Z') {
|
||||||
|
code = ch
|
||||||
|
break
|
||||||
|
}
|
||||||
|
argBuf = append(argBuf, string(ch)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = w.applyEscapeCode(buf, string(argBuf), code)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Writer) applyEscapeCode(buf []byte, arg string, code rune) error {
|
||||||
|
c := &Cursor{Out: w.out}
|
||||||
|
|
||||||
|
switch arg + string(code) {
|
||||||
|
case "?25h":
|
||||||
|
return c.Show()
|
||||||
|
case "?25l":
|
||||||
|
return c.Hide()
|
||||||
|
}
|
||||||
|
|
||||||
|
if code >= 'A' && code <= 'G' {
|
||||||
|
if n, err := strconv.Atoi(arg); err == nil {
|
||||||
|
switch code {
|
||||||
|
case 'A':
|
||||||
|
return c.Up(n)
|
||||||
|
case 'B':
|
||||||
|
return c.Down(n)
|
||||||
|
case 'C':
|
||||||
|
return c.Forward(n)
|
||||||
|
case 'D':
|
||||||
|
return c.Back(n)
|
||||||
|
case 'E':
|
||||||
|
return c.NextLine(n)
|
||||||
|
case 'F':
|
||||||
|
return c.PreviousLine(n)
|
||||||
|
case 'G':
|
||||||
|
return c.HorizontalAbsolute(n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch code {
|
||||||
|
case 'm':
|
||||||
|
return w.applySelectGraphicRendition(arg)
|
||||||
|
default:
|
||||||
|
buf = append(buf, string(code)...)
|
||||||
|
_, err := fmt.Fprint(w.out, string(buf))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Original implementation: https://github.com/mattn/go-colorable
|
||||||
|
func (w *Writer) applySelectGraphicRendition(arg string) error {
|
||||||
|
if arg == "" {
|
||||||
|
_, _, err := procSetConsoleTextAttribute.Call(uintptr(w.handle), uintptr(w.orgAttr))
|
||||||
|
return normalizeError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var csbi consoleScreenBufferInfo
|
||||||
|
if _, _, err := procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))); normalizeError(err) != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
attr := csbi.attributes
|
||||||
|
|
||||||
|
for _, param := range strings.Split(arg, ";") {
|
||||||
|
n, err := strconv.Atoi(param)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case n == 0 || n == 100:
|
||||||
|
attr = w.orgAttr
|
||||||
|
case 1 <= n && n <= 5:
|
||||||
|
attr |= foregroundIntensity
|
||||||
|
case 30 <= n && n <= 37:
|
||||||
|
attr = (attr & backgroundMask)
|
||||||
|
if (n-30)&1 != 0 {
|
||||||
|
attr |= foregroundRed
|
||||||
|
}
|
||||||
|
if (n-30)&2 != 0 {
|
||||||
|
attr |= foregroundGreen
|
||||||
|
}
|
||||||
|
if (n-30)&4 != 0 {
|
||||||
|
attr |= foregroundBlue
|
||||||
|
}
|
||||||
|
case 40 <= n && n <= 47:
|
||||||
|
attr = (attr & foregroundMask)
|
||||||
|
if (n-40)&1 != 0 {
|
||||||
|
attr |= backgroundRed
|
||||||
|
}
|
||||||
|
if (n-40)&2 != 0 {
|
||||||
|
attr |= backgroundGreen
|
||||||
|
}
|
||||||
|
if (n-40)&4 != 0 {
|
||||||
|
attr |= backgroundBlue
|
||||||
|
}
|
||||||
|
case 90 <= n && n <= 97:
|
||||||
|
attr = (attr & backgroundMask)
|
||||||
|
attr |= foregroundIntensity
|
||||||
|
if (n-90)&1 != 0 {
|
||||||
|
attr |= foregroundRed
|
||||||
|
}
|
||||||
|
if (n-90)&2 != 0 {
|
||||||
|
attr |= foregroundGreen
|
||||||
|
}
|
||||||
|
if (n-90)&4 != 0 {
|
||||||
|
attr |= foregroundBlue
|
||||||
|
}
|
||||||
|
case 100 <= n && n <= 107:
|
||||||
|
attr = (attr & foregroundMask)
|
||||||
|
attr |= backgroundIntensity
|
||||||
|
if (n-100)&1 != 0 {
|
||||||
|
attr |= backgroundRed
|
||||||
|
}
|
||||||
|
if (n-100)&2 != 0 {
|
||||||
|
attr |= backgroundGreen
|
||||||
|
}
|
||||||
|
if (n-100)&4 != 0 {
|
||||||
|
attr |= backgroundBlue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, err := procSetConsoleTextAttribute.Call(uintptr(w.handle), uintptr(attr))
|
||||||
|
return normalizeError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeError(err error) error {
|
||||||
|
if syserr, ok := err.(syscall.Errno); ok && syserr == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
417
vendor/github.com/AlecAivazis/survey/v2/terminal/runereader.go
generated
vendored
Normal file
417
vendor/github.com/AlecAivazis/survey/v2/terminal/runereader.go
generated
vendored
Normal file
@ -0,0 +1,417 @@
|
|||||||
|
package terminal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
|
"golang.org/x/text/width"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RuneReader struct {
|
||||||
|
stdio Stdio
|
||||||
|
state runeReaderState
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRuneReader(stdio Stdio) *RuneReader {
|
||||||
|
return &RuneReader{
|
||||||
|
stdio: stdio,
|
||||||
|
state: newRuneReaderState(stdio.In),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rr *RuneReader) printChar(char rune, mask rune) error {
|
||||||
|
// if we don't need to mask the input
|
||||||
|
if mask == 0 {
|
||||||
|
// just print the character the user pressed
|
||||||
|
_, err := fmt.Fprintf(rr.stdio.Out, "%c", char)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// otherwise print the mask we were given
|
||||||
|
_, err := fmt.Fprintf(rr.stdio.Out, "%c", mask)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
type OnRuneFn func(rune, []rune) ([]rune, bool, error)
|
||||||
|
|
||||||
|
func (rr *RuneReader) ReadLine(mask rune, onRunes ...OnRuneFn) ([]rune, error) {
|
||||||
|
return rr.ReadLineWithDefault(mask, []rune{}, onRunes...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rr *RuneReader) ReadLineWithDefault(mask rune, d []rune, onRunes ...OnRuneFn) ([]rune, error) {
|
||||||
|
line := []rune{}
|
||||||
|
// we only care about horizontal displacements from the origin so start counting at 0
|
||||||
|
index := 0
|
||||||
|
|
||||||
|
cursor := &Cursor{
|
||||||
|
In: rr.stdio.In,
|
||||||
|
Out: rr.stdio.Out,
|
||||||
|
}
|
||||||
|
|
||||||
|
onRune := func(r rune, line []rune) ([]rune, bool, error) {
|
||||||
|
return line, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the user pressed a key the caller was interested in capturing
|
||||||
|
if len(onRunes) > 0 {
|
||||||
|
onRune = onRunes[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// we get the terminal width and height (if resized after this point the property might become invalid)
|
||||||
|
terminalSize, _ := cursor.Size(rr.Buffer())
|
||||||
|
// we set the current location of the cursor once
|
||||||
|
cursorCurrent, _ := cursor.Location(rr.Buffer())
|
||||||
|
|
||||||
|
increment := func() {
|
||||||
|
if cursorCurrent.CursorIsAtLineEnd(terminalSize) {
|
||||||
|
cursorCurrent.X = COORDINATE_SYSTEM_BEGIN
|
||||||
|
cursorCurrent.Y++
|
||||||
|
} else {
|
||||||
|
cursorCurrent.X++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
decrement := func() {
|
||||||
|
if cursorCurrent.CursorIsAtLineBegin() {
|
||||||
|
cursorCurrent.X = terminalSize.X
|
||||||
|
cursorCurrent.Y--
|
||||||
|
} else {
|
||||||
|
cursorCurrent.X--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(d) > 0 {
|
||||||
|
index = len(d)
|
||||||
|
if _, err := fmt.Fprint(rr.stdio.Out, string(d)); err != nil {
|
||||||
|
return d, err
|
||||||
|
}
|
||||||
|
line = d
|
||||||
|
for range d {
|
||||||
|
increment()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
// wait for some input
|
||||||
|
r, _, err := rr.ReadRune()
|
||||||
|
if err != nil {
|
||||||
|
return line, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if l, stop, err := onRune(r, line); stop || err != nil {
|
||||||
|
return l, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the user pressed enter or some other newline/termination like ctrl+d
|
||||||
|
if r == '\r' || r == '\n' || r == KeyEndTransmission {
|
||||||
|
// delete what's printed out on the console screen (cleanup)
|
||||||
|
for index > 0 {
|
||||||
|
if cursorCurrent.CursorIsAtLineBegin() {
|
||||||
|
EraseLine(rr.stdio.Out, ERASE_LINE_END)
|
||||||
|
cursor.PreviousLine(1)
|
||||||
|
cursor.Forward(int(terminalSize.X))
|
||||||
|
} else {
|
||||||
|
cursor.Back(1)
|
||||||
|
}
|
||||||
|
decrement()
|
||||||
|
index--
|
||||||
|
}
|
||||||
|
// move the cursor the a new line
|
||||||
|
cursor.MoveNextLine(cursorCurrent, terminalSize)
|
||||||
|
|
||||||
|
// we're done processing the input
|
||||||
|
return line, nil
|
||||||
|
}
|
||||||
|
// if the user interrupts (ie with ctrl+c)
|
||||||
|
if r == KeyInterrupt {
|
||||||
|
// go to the beginning of the next line
|
||||||
|
if _, err := fmt.Fprint(rr.stdio.Out, "\r\n"); err != nil {
|
||||||
|
return line, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// we're done processing the input, and treat interrupt like an error
|
||||||
|
return line, InterruptErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// allow for backspace/delete editing of inputs
|
||||||
|
if r == KeyBackspace || r == KeyDelete {
|
||||||
|
// and we're not at the beginning of the line
|
||||||
|
if index > 0 && len(line) > 0 {
|
||||||
|
// if we are at the end of the word
|
||||||
|
if index == len(line) {
|
||||||
|
// just remove the last letter from the internal representation
|
||||||
|
// also count the number of cells the rune before the cursor occupied
|
||||||
|
cells := runeWidth(line[len(line)-1])
|
||||||
|
line = line[:len(line)-1]
|
||||||
|
// go back one
|
||||||
|
if cursorCurrent.X == 1 {
|
||||||
|
cursor.PreviousLine(1)
|
||||||
|
cursor.Forward(int(terminalSize.X))
|
||||||
|
} else {
|
||||||
|
cursor.Back(cells)
|
||||||
|
}
|
||||||
|
|
||||||
|
// clear the rest of the line
|
||||||
|
EraseLine(rr.stdio.Out, ERASE_LINE_END)
|
||||||
|
} else {
|
||||||
|
// we need to remove a character from the middle of the word
|
||||||
|
|
||||||
|
cells := runeWidth(line[index-1])
|
||||||
|
|
||||||
|
// remove the current index from the list
|
||||||
|
line = append(line[:index-1], line[index:]...)
|
||||||
|
|
||||||
|
// save the current position of the cursor, as we have to move the cursor one back to erase the current symbol
|
||||||
|
// and then move the cursor for each symbol in line[index-1:] to print it out, afterwards we want to restore
|
||||||
|
// the cursor to its previous location.
|
||||||
|
cursor.Save()
|
||||||
|
|
||||||
|
// clear the rest of the line
|
||||||
|
cursor.Back(cells)
|
||||||
|
|
||||||
|
// print what comes after
|
||||||
|
for _, char := range line[index-1:] {
|
||||||
|
//Erase symbols which are left over from older print
|
||||||
|
EraseLine(rr.stdio.Out, ERASE_LINE_END)
|
||||||
|
// print characters to the new line appropriately
|
||||||
|
if err := rr.printChar(char, mask); err != nil {
|
||||||
|
return line, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// erase what's left over from last print
|
||||||
|
if cursorCurrent.Y < terminalSize.Y {
|
||||||
|
cursor.NextLine(1)
|
||||||
|
EraseLine(rr.stdio.Out, ERASE_LINE_END)
|
||||||
|
}
|
||||||
|
// restore cursor
|
||||||
|
cursor.Restore()
|
||||||
|
if cursorCurrent.CursorIsAtLineBegin() {
|
||||||
|
cursor.PreviousLine(1)
|
||||||
|
cursor.Forward(int(terminalSize.X))
|
||||||
|
} else {
|
||||||
|
cursor.Back(cells)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// decrement the index
|
||||||
|
index--
|
||||||
|
decrement()
|
||||||
|
} else {
|
||||||
|
// otherwise the user pressed backspace while at the beginning of the line
|
||||||
|
_ = soundBell(rr.stdio.Out)
|
||||||
|
}
|
||||||
|
|
||||||
|
// we're done processing this key
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the left arrow is pressed
|
||||||
|
if r == KeyArrowLeft {
|
||||||
|
// if we have space to the left
|
||||||
|
if index > 0 {
|
||||||
|
//move the cursor to the prev line if necessary
|
||||||
|
if cursorCurrent.CursorIsAtLineBegin() {
|
||||||
|
cursor.PreviousLine(1)
|
||||||
|
cursor.Forward(int(terminalSize.X))
|
||||||
|
} else {
|
||||||
|
cursor.Back(runeWidth(line[index-1]))
|
||||||
|
}
|
||||||
|
//decrement the index
|
||||||
|
index--
|
||||||
|
decrement()
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// otherwise we are at the beginning of where we started reading lines
|
||||||
|
// sound the bell
|
||||||
|
_ = soundBell(rr.stdio.Out)
|
||||||
|
}
|
||||||
|
|
||||||
|
// we're done processing this key press
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the right arrow is pressed
|
||||||
|
if r == KeyArrowRight {
|
||||||
|
// if we have space to the right
|
||||||
|
if index < len(line) {
|
||||||
|
// move the cursor to the next line if necessary
|
||||||
|
if cursorCurrent.CursorIsAtLineEnd(terminalSize) {
|
||||||
|
cursor.NextLine(1)
|
||||||
|
} else {
|
||||||
|
cursor.Forward(runeWidth(line[index]))
|
||||||
|
}
|
||||||
|
index++
|
||||||
|
increment()
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// otherwise we are at the end of the word and can't go past
|
||||||
|
// sound the bell
|
||||||
|
_ = soundBell(rr.stdio.Out)
|
||||||
|
}
|
||||||
|
|
||||||
|
// we're done processing this key press
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// the user pressed one of the special keys
|
||||||
|
if r == SpecialKeyHome {
|
||||||
|
for index > 0 {
|
||||||
|
if cursorCurrent.CursorIsAtLineBegin() {
|
||||||
|
cursor.PreviousLine(1)
|
||||||
|
cursor.Forward(int(terminalSize.X))
|
||||||
|
cursorCurrent.Y--
|
||||||
|
cursorCurrent.X = terminalSize.X
|
||||||
|
} else {
|
||||||
|
cursor.Back(runeWidth(line[index-1]))
|
||||||
|
cursorCurrent.X -= Short(runeWidth(line[index-1]))
|
||||||
|
}
|
||||||
|
index--
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
// user pressed end
|
||||||
|
} else if r == SpecialKeyEnd {
|
||||||
|
for index != len(line) {
|
||||||
|
if cursorCurrent.CursorIsAtLineEnd(terminalSize) {
|
||||||
|
cursor.NextLine(1)
|
||||||
|
cursorCurrent.Y++
|
||||||
|
cursorCurrent.X = COORDINATE_SYSTEM_BEGIN
|
||||||
|
} else {
|
||||||
|
cursor.Forward(runeWidth(line[index]))
|
||||||
|
cursorCurrent.X += Short(runeWidth(line[index]))
|
||||||
|
}
|
||||||
|
index++
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
// user pressed forward delete key
|
||||||
|
} else if r == SpecialKeyDelete {
|
||||||
|
// if index at the end of the line nothing to delete
|
||||||
|
if index != len(line) {
|
||||||
|
// save the current position of the cursor, as we have to erase the current symbol
|
||||||
|
// and then move the cursor for each symbol in line[index:] to print it out, afterwards we want to restore
|
||||||
|
// the cursor to its previous location.
|
||||||
|
cursor.Save()
|
||||||
|
// remove the symbol after the cursor
|
||||||
|
line = append(line[:index], line[index+1:]...)
|
||||||
|
// print the updated line
|
||||||
|
for _, char := range line[index:] {
|
||||||
|
EraseLine(rr.stdio.Out, ERASE_LINE_END)
|
||||||
|
// print out the character
|
||||||
|
if err := rr.printChar(char, mask); err != nil {
|
||||||
|
return line, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// erase what's left on last line
|
||||||
|
if cursorCurrent.Y < terminalSize.Y {
|
||||||
|
cursor.NextLine(1)
|
||||||
|
EraseLine(rr.stdio.Out, ERASE_LINE_END)
|
||||||
|
}
|
||||||
|
// restore cursor
|
||||||
|
cursor.Restore()
|
||||||
|
if len(line) == 0 || index == len(line) {
|
||||||
|
EraseLine(rr.stdio.Out, ERASE_LINE_END)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the letter is another escape sequence
|
||||||
|
if unicode.IsControl(r) || r == IgnoreKey {
|
||||||
|
// ignore it
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// the user pressed a regular key
|
||||||
|
|
||||||
|
// if we are at the end of the line
|
||||||
|
if index == len(line) {
|
||||||
|
// just append the character at the end of the line
|
||||||
|
line = append(line, r)
|
||||||
|
// save the location of the cursor
|
||||||
|
index++
|
||||||
|
increment()
|
||||||
|
// print out the character
|
||||||
|
if err := rr.printChar(r, mask); err != nil {
|
||||||
|
return line, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// we are in the middle of the word so we need to insert the character the user pressed
|
||||||
|
line = append(line[:index], append([]rune{r}, line[index:]...)...)
|
||||||
|
// save the current position of the cursor, as we have to move the cursor back to erase the current symbol
|
||||||
|
// and then move for each symbol in line[index:] to print it out, afterwards we want to restore
|
||||||
|
// cursor's location to its previous one.
|
||||||
|
cursor.Save()
|
||||||
|
EraseLine(rr.stdio.Out, ERASE_LINE_END)
|
||||||
|
// remove the symbol after the cursor
|
||||||
|
// print the updated line
|
||||||
|
for _, char := range line[index:] {
|
||||||
|
EraseLine(rr.stdio.Out, ERASE_LINE_END)
|
||||||
|
// print out the character
|
||||||
|
if err := rr.printChar(char, mask); err != nil {
|
||||||
|
return line, err
|
||||||
|
}
|
||||||
|
increment()
|
||||||
|
}
|
||||||
|
// if we are at the last line, we want to visually insert a new line and append to it.
|
||||||
|
if cursorCurrent.CursorIsAtLineEnd(terminalSize) && cursorCurrent.Y == terminalSize.Y {
|
||||||
|
// add a new line to the terminal
|
||||||
|
if _, err := fmt.Fprintln(rr.stdio.Out); err != nil {
|
||||||
|
return line, err
|
||||||
|
}
|
||||||
|
// restore the position of the cursor horizontally
|
||||||
|
cursor.Restore()
|
||||||
|
// restore the position of the cursor vertically
|
||||||
|
cursor.PreviousLine(1)
|
||||||
|
} else {
|
||||||
|
// restore cursor
|
||||||
|
cursor.Restore()
|
||||||
|
}
|
||||||
|
// check if cursor needs to move to next line
|
||||||
|
cursorCurrent, _ = cursor.Location(rr.Buffer())
|
||||||
|
if cursorCurrent.CursorIsAtLineEnd(terminalSize) {
|
||||||
|
cursor.NextLine(1)
|
||||||
|
} else {
|
||||||
|
cursor.Forward(runeWidth(r))
|
||||||
|
}
|
||||||
|
// increment the index
|
||||||
|
index++
|
||||||
|
increment()
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// runeWidth returns the number of columns spanned by a rune when printed to the terminal
|
||||||
|
func runeWidth(r rune) int {
|
||||||
|
switch width.LookupRune(r).Kind() {
|
||||||
|
case width.EastAsianWide, width.EastAsianFullwidth:
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
|
||||||
|
if !unicode.IsPrint(r) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// isAnsiMarker returns if a rune denotes the start of an ANSI sequence
|
||||||
|
func isAnsiMarker(r rune) bool {
|
||||||
|
return r == '\x1B'
|
||||||
|
}
|
||||||
|
|
||||||
|
// isAnsiTerminator returns if a rune denotes the end of an ANSI sequence
|
||||||
|
func isAnsiTerminator(r rune) bool {
|
||||||
|
return (r >= 0x40 && r <= 0x5a) || (r == 0x5e) || (r >= 0x60 && r <= 0x7e)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StringWidth returns the visible width of a string when printed to the terminal
|
||||||
|
func StringWidth(str string) int {
|
||||||
|
w := 0
|
||||||
|
ansi := false
|
||||||
|
|
||||||
|
for _, r := range str {
|
||||||
|
// increase width only when outside of ANSI escape sequences
|
||||||
|
if ansi || isAnsiMarker(r) {
|
||||||
|
ansi = !isAnsiTerminator(r)
|
||||||
|
} else {
|
||||||
|
w += runeWidth(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return w
|
||||||
|
}
|
14
vendor/github.com/AlecAivazis/survey/v2/terminal/runereader_bsd.go
generated
vendored
Normal file
14
vendor/github.com/AlecAivazis/survey/v2/terminal/runereader_bsd.go
generated
vendored
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
// copied from: https://github.com/golang/crypto/blob/master/ssh/terminal/util_bsd.go
|
||||||
|
// Copyright 2013 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build darwin || dragonfly || freebsd || netbsd || openbsd
|
||||||
|
// +build darwin dragonfly freebsd netbsd openbsd
|
||||||
|
|
||||||
|
package terminal
|
||||||
|
|
||||||
|
import "syscall"
|
||||||
|
|
||||||
|
const ioctlReadTermios = syscall.TIOCGETA
|
||||||
|
const ioctlWriteTermios = syscall.TIOCSETA
|
14
vendor/github.com/AlecAivazis/survey/v2/terminal/runereader_linux.go
generated
vendored
Normal file
14
vendor/github.com/AlecAivazis/survey/v2/terminal/runereader_linux.go
generated
vendored
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
// copied from https://github.com/golang/crypto/blob/master/ssh/terminal/util_linux.go
|
||||||
|
// Copyright 2013 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
//go:build linux && !ppc64le
|
||||||
|
// +build linux,!ppc64le
|
||||||
|
|
||||||
|
package terminal
|
||||||
|
|
||||||
|
// These constants are declared here, rather than importing
|
||||||
|
// them from the syscall package as some syscall packages, even
|
||||||
|
// on linux, for example gccgo, do not declare them.
|
||||||
|
const ioctlReadTermios = 0x5401 // syscall.TCGETS
|
||||||
|
const ioctlWriteTermios = 0x5402 // syscall.TCSETS
|
132
vendor/github.com/AlecAivazis/survey/v2/terminal/runereader_posix.go
generated
vendored
Normal file
132
vendor/github.com/AlecAivazis/survey/v2/terminal/runereader_posix.go
generated
vendored
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
//go:build !windows
|
||||||
|
// +build !windows
|
||||||
|
|
||||||
|
// The terminal mode manipulation code is derived heavily from:
|
||||||
|
// https://github.com/golang/crypto/blob/master/ssh/terminal/util.go:
|
||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package terminal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
normalKeypad = '['
|
||||||
|
applicationKeypad = 'O'
|
||||||
|
)
|
||||||
|
|
||||||
|
type runeReaderState struct {
|
||||||
|
term syscall.Termios
|
||||||
|
reader *bufio.Reader
|
||||||
|
buf *bytes.Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRuneReaderState(input FileReader) runeReaderState {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
return runeReaderState{
|
||||||
|
reader: bufio.NewReader(&BufferedReader{
|
||||||
|
In: input,
|
||||||
|
Buffer: buf,
|
||||||
|
}),
|
||||||
|
buf: buf,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rr *RuneReader) Buffer() *bytes.Buffer {
|
||||||
|
return rr.state.buf
|
||||||
|
}
|
||||||
|
|
||||||
|
// For reading runes we just want to disable echo.
|
||||||
|
func (rr *RuneReader) SetTermMode() error {
|
||||||
|
if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(rr.stdio.In.Fd()), ioctlReadTermios, uintptr(unsafe.Pointer(&rr.state.term)), 0, 0, 0); err != 0 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
newState := rr.state.term
|
||||||
|
newState.Lflag &^= syscall.ECHO | syscall.ECHONL | syscall.ICANON | syscall.ISIG
|
||||||
|
// Because we are clearing canonical mode, we need to ensure VMIN & VTIME are
|
||||||
|
// set to the values we expect. This combination puts things in standard
|
||||||
|
// "blocking read" mode (see termios(3)).
|
||||||
|
newState.Cc[syscall.VMIN] = 1
|
||||||
|
newState.Cc[syscall.VTIME] = 0
|
||||||
|
|
||||||
|
if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(rr.stdio.In.Fd()), ioctlWriteTermios, uintptr(unsafe.Pointer(&newState)), 0, 0, 0); err != 0 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rr *RuneReader) RestoreTermMode() error {
|
||||||
|
if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(rr.stdio.In.Fd()), ioctlWriteTermios, uintptr(unsafe.Pointer(&rr.state.term)), 0, 0, 0); err != 0 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadRune Parse escape sequences such as ESC [ A for arrow keys.
|
||||||
|
// See https://vt100.net/docs/vt102-ug/appendixc.html
|
||||||
|
func (rr *RuneReader) ReadRune() (rune, int, error) {
|
||||||
|
r, size, err := rr.state.reader.ReadRune()
|
||||||
|
if err != nil {
|
||||||
|
return r, size, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if r != KeyEscape {
|
||||||
|
return r, size, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if rr.state.reader.Buffered() == 0 {
|
||||||
|
// no more characters so must be `Esc` key
|
||||||
|
return KeyEscape, 1, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
r, size, err = rr.state.reader.ReadRune()
|
||||||
|
if err != nil {
|
||||||
|
return r, size, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ESC O ... or ESC [ ...?
|
||||||
|
if r != normalKeypad && r != applicationKeypad {
|
||||||
|
return r, size, fmt.Errorf("unexpected escape sequence from terminal: %q", []rune{KeyEscape, r})
|
||||||
|
}
|
||||||
|
|
||||||
|
keypad := r
|
||||||
|
|
||||||
|
r, size, err = rr.state.reader.ReadRune()
|
||||||
|
if err != nil {
|
||||||
|
return r, size, err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch r {
|
||||||
|
case 'A': // ESC [ A or ESC O A
|
||||||
|
return KeyArrowUp, 1, nil
|
||||||
|
case 'B': // ESC [ B or ESC O B
|
||||||
|
return KeyArrowDown, 1, nil
|
||||||
|
case 'C': // ESC [ C or ESC O C
|
||||||
|
return KeyArrowRight, 1, nil
|
||||||
|
case 'D': // ESC [ D or ESC O D
|
||||||
|
return KeyArrowLeft, 1, nil
|
||||||
|
case 'F': // ESC [ F or ESC O F
|
||||||
|
return SpecialKeyEnd, 1, nil
|
||||||
|
case 'H': // ESC [ H or ESC O H
|
||||||
|
return SpecialKeyHome, 1, nil
|
||||||
|
case '3': // ESC [ 3
|
||||||
|
if keypad == normalKeypad {
|
||||||
|
// discard the following '~' key from buffer
|
||||||
|
_, _ = rr.state.reader.Discard(1)
|
||||||
|
return SpecialKeyDelete, 1, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// discard the following '~' key from buffer
|
||||||
|
_, _ = rr.state.reader.Discard(1)
|
||||||
|
return IgnoreKey, 1, nil
|
||||||
|
}
|
8
vendor/github.com/AlecAivazis/survey/v2/terminal/runereader_ppc64le.go
generated
vendored
Normal file
8
vendor/github.com/AlecAivazis/survey/v2/terminal/runereader_ppc64le.go
generated
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
//go:build ppc64le && linux
|
||||||
|
// +build ppc64le,linux
|
||||||
|
|
||||||
|
package terminal
|
||||||
|
|
||||||
|
// Used syscall numbers from https://github.com/golang/go/blob/master/src/syscall/ztypes_linux_ppc64le.go
|
||||||
|
const ioctlReadTermios = 0x402c7413 // syscall.TCGETS
|
||||||
|
const ioctlWriteTermios = 0x802c7414 // syscall.TCSETS
|
142
vendor/github.com/AlecAivazis/survey/v2/terminal/runereader_windows.go
generated
vendored
Normal file
142
vendor/github.com/AlecAivazis/survey/v2/terminal/runereader_windows.go
generated
vendored
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
package terminal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
dll = syscall.NewLazyDLL("kernel32.dll")
|
||||||
|
setConsoleMode = dll.NewProc("SetConsoleMode")
|
||||||
|
getConsoleMode = dll.NewProc("GetConsoleMode")
|
||||||
|
readConsoleInput = dll.NewProc("ReadConsoleInputW")
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
EVENT_KEY = 0x0001
|
||||||
|
|
||||||
|
// key codes for arrow keys
|
||||||
|
// https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx
|
||||||
|
VK_DELETE = 0x2E
|
||||||
|
VK_END = 0x23
|
||||||
|
VK_HOME = 0x24
|
||||||
|
VK_LEFT = 0x25
|
||||||
|
VK_UP = 0x26
|
||||||
|
VK_RIGHT = 0x27
|
||||||
|
VK_DOWN = 0x28
|
||||||
|
|
||||||
|
RIGHT_CTRL_PRESSED = 0x0004
|
||||||
|
LEFT_CTRL_PRESSED = 0x0008
|
||||||
|
|
||||||
|
ENABLE_ECHO_INPUT uint32 = 0x0004
|
||||||
|
ENABLE_LINE_INPUT uint32 = 0x0002
|
||||||
|
ENABLE_PROCESSED_INPUT uint32 = 0x0001
|
||||||
|
)
|
||||||
|
|
||||||
|
type inputRecord struct {
|
||||||
|
eventType uint16
|
||||||
|
padding uint16
|
||||||
|
event [16]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type keyEventRecord struct {
|
||||||
|
bKeyDown int32
|
||||||
|
wRepeatCount uint16
|
||||||
|
wVirtualKeyCode uint16
|
||||||
|
wVirtualScanCode uint16
|
||||||
|
unicodeChar uint16
|
||||||
|
wdControlKeyState uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
type runeReaderState struct {
|
||||||
|
term uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRuneReaderState(input FileReader) runeReaderState {
|
||||||
|
return runeReaderState{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rr *RuneReader) Buffer() *bytes.Buffer {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rr *RuneReader) SetTermMode() error {
|
||||||
|
r, _, err := getConsoleMode.Call(uintptr(rr.stdio.In.Fd()), uintptr(unsafe.Pointer(&rr.state.term)))
|
||||||
|
// windows return 0 on error
|
||||||
|
if r == 0 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
newState := rr.state.term
|
||||||
|
newState &^= ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT
|
||||||
|
r, _, err = setConsoleMode.Call(uintptr(rr.stdio.In.Fd()), uintptr(newState))
|
||||||
|
// windows return 0 on error
|
||||||
|
if r == 0 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rr *RuneReader) RestoreTermMode() error {
|
||||||
|
r, _, err := setConsoleMode.Call(uintptr(rr.stdio.In.Fd()), uintptr(rr.state.term))
|
||||||
|
// windows return 0 on error
|
||||||
|
if r == 0 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rr *RuneReader) ReadRune() (rune, int, error) {
|
||||||
|
ir := &inputRecord{}
|
||||||
|
bytesRead := 0
|
||||||
|
for {
|
||||||
|
rv, _, e := readConsoleInput.Call(rr.stdio.In.Fd(), uintptr(unsafe.Pointer(ir)), 1, uintptr(unsafe.Pointer(&bytesRead)))
|
||||||
|
// windows returns non-zero to indicate success
|
||||||
|
if rv == 0 && e != nil {
|
||||||
|
return 0, 0, e
|
||||||
|
}
|
||||||
|
|
||||||
|
if ir.eventType != EVENT_KEY {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// the event data is really a c struct union, so here we have to do an usafe
|
||||||
|
// cast to put the data into the keyEventRecord (since we have already verified
|
||||||
|
// above that this event does correspond to a key event
|
||||||
|
key := (*keyEventRecord)(unsafe.Pointer(&ir.event[0]))
|
||||||
|
// we only care about key down events
|
||||||
|
if key.bKeyDown == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if key.wdControlKeyState&(LEFT_CTRL_PRESSED|RIGHT_CTRL_PRESSED) != 0 && key.unicodeChar == 'C' {
|
||||||
|
return KeyInterrupt, bytesRead, nil
|
||||||
|
}
|
||||||
|
// not a normal character so look up the input sequence from the
|
||||||
|
// virtual key code mappings (VK_*)
|
||||||
|
if key.unicodeChar == 0 {
|
||||||
|
switch key.wVirtualKeyCode {
|
||||||
|
case VK_DOWN:
|
||||||
|
return KeyArrowDown, bytesRead, nil
|
||||||
|
case VK_LEFT:
|
||||||
|
return KeyArrowLeft, bytesRead, nil
|
||||||
|
case VK_RIGHT:
|
||||||
|
return KeyArrowRight, bytesRead, nil
|
||||||
|
case VK_UP:
|
||||||
|
return KeyArrowUp, bytesRead, nil
|
||||||
|
case VK_DELETE:
|
||||||
|
return SpecialKeyDelete, bytesRead, nil
|
||||||
|
case VK_HOME:
|
||||||
|
return SpecialKeyHome, bytesRead, nil
|
||||||
|
case VK_END:
|
||||||
|
return SpecialKeyEnd, bytesRead, nil
|
||||||
|
default:
|
||||||
|
// not a virtual key that we care about so just continue on to
|
||||||
|
// the next input key
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
r := rune(key.unicodeChar)
|
||||||
|
return r, bytesRead, nil
|
||||||
|
}
|
||||||
|
}
|
32
vendor/github.com/AlecAivazis/survey/v2/terminal/sequences.go
generated
vendored
Normal file
32
vendor/github.com/AlecAivazis/survey/v2/terminal/sequences.go
generated
vendored
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
package terminal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
KeyArrowLeft = '\x02'
|
||||||
|
KeyArrowRight = '\x06'
|
||||||
|
KeyArrowUp = '\x10'
|
||||||
|
KeyArrowDown = '\x0e'
|
||||||
|
KeySpace = ' '
|
||||||
|
KeyEnter = '\r'
|
||||||
|
KeyBackspace = '\b'
|
||||||
|
KeyDelete = '\x7f'
|
||||||
|
KeyInterrupt = '\x03'
|
||||||
|
KeyEndTransmission = '\x04'
|
||||||
|
KeyEscape = '\x1b'
|
||||||
|
KeyDeleteWord = '\x17' // Ctrl+W
|
||||||
|
KeyDeleteLine = '\x18' // Ctrl+X
|
||||||
|
SpecialKeyHome = '\x01'
|
||||||
|
SpecialKeyEnd = '\x11'
|
||||||
|
SpecialKeyDelete = '\x12'
|
||||||
|
IgnoreKey = '\000'
|
||||||
|
KeyTab = '\t'
|
||||||
|
)
|
||||||
|
|
||||||
|
func soundBell(out io.Writer) error {
|
||||||
|
_, err := fmt.Fprint(out, "\a")
|
||||||
|
return err
|
||||||
|
}
|
24
vendor/github.com/AlecAivazis/survey/v2/terminal/stdio.go
generated
vendored
Normal file
24
vendor/github.com/AlecAivazis/survey/v2/terminal/stdio.go
generated
vendored
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
package terminal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Stdio is the standard input/output the terminal reads/writes with.
|
||||||
|
type Stdio struct {
|
||||||
|
In FileReader
|
||||||
|
Out FileWriter
|
||||||
|
Err io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
// FileWriter provides a minimal interface for Stdin.
|
||||||
|
type FileWriter interface {
|
||||||
|
io.Writer
|
||||||
|
Fd() uintptr
|
||||||
|
}
|
||||||
|
|
||||||
|
// FileReader provides a minimal interface for Stdout.
|
||||||
|
type FileReader interface {
|
||||||
|
io.Reader
|
||||||
|
Fd() uintptr
|
||||||
|
}
|
39
vendor/github.com/AlecAivazis/survey/v2/terminal/syscall_windows.go
generated
vendored
Normal file
39
vendor/github.com/AlecAivazis/survey/v2/terminal/syscall_windows.go
generated
vendored
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
package terminal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
kernel32 = syscall.NewLazyDLL("kernel32.dll")
|
||||||
|
procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo")
|
||||||
|
procSetConsoleTextAttribute = kernel32.NewProc("SetConsoleTextAttribute")
|
||||||
|
procSetConsoleCursorPosition = kernel32.NewProc("SetConsoleCursorPosition")
|
||||||
|
procFillConsoleOutputCharacter = kernel32.NewProc("FillConsoleOutputCharacterW")
|
||||||
|
procGetConsoleCursorInfo = kernel32.NewProc("GetConsoleCursorInfo")
|
||||||
|
procSetConsoleCursorInfo = kernel32.NewProc("SetConsoleCursorInfo")
|
||||||
|
)
|
||||||
|
|
||||||
|
type wchar uint16
|
||||||
|
type dword uint32
|
||||||
|
type word uint16
|
||||||
|
|
||||||
|
type smallRect struct {
|
||||||
|
left Short
|
||||||
|
top Short
|
||||||
|
right Short
|
||||||
|
bottom Short
|
||||||
|
}
|
||||||
|
|
||||||
|
type consoleScreenBufferInfo struct {
|
||||||
|
size Coord
|
||||||
|
cursorPosition Coord
|
||||||
|
attributes word
|
||||||
|
window smallRect
|
||||||
|
maximumWindowSize Coord
|
||||||
|
}
|
||||||
|
|
||||||
|
type consoleCursorInfo struct {
|
||||||
|
size dword
|
||||||
|
visible int32
|
||||||
|
}
|
8
vendor/github.com/AlecAivazis/survey/v2/terminal/terminal.go
generated
vendored
Normal file
8
vendor/github.com/AlecAivazis/survey/v2/terminal/terminal.go
generated
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
package terminal
|
||||||
|
|
||||||
|
type Short int16
|
||||||
|
|
||||||
|
type Coord struct {
|
||||||
|
X Short
|
||||||
|
Y Short
|
||||||
|
}
|
82
vendor/github.com/AlecAivazis/survey/v2/transform.go
generated
vendored
Normal file
82
vendor/github.com/AlecAivazis/survey/v2/transform.go
generated
vendored
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
package survey
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/text/cases"
|
||||||
|
"golang.org/x/text/language"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TransformString returns a `Transformer` based on the "f"
|
||||||
|
// function which accepts a string representation of the answer
|
||||||
|
// and returns a new one, transformed, answer.
|
||||||
|
// Take for example the functions inside the std `strings` package,
|
||||||
|
// they can be converted to a compatible `Transformer` by using this function,
|
||||||
|
// i.e: `TransformString(strings.Title)`, `TransformString(strings.ToUpper)`.
|
||||||
|
//
|
||||||
|
// Note that `TransformString` is just a helper, `Transformer` can be used
|
||||||
|
// to transform any type of answer.
|
||||||
|
func TransformString(f func(s string) string) Transformer {
|
||||||
|
return func(ans interface{}) interface{} {
|
||||||
|
// if the answer value passed in is the zero value of the appropriate type
|
||||||
|
if isZero(reflect.ValueOf(ans)) {
|
||||||
|
// skip this `Transformer` by returning a zero value of string.
|
||||||
|
// The original answer will be not affected,
|
||||||
|
// see survey.go#L125.
|
||||||
|
// A zero value of string should be returned to be handled by
|
||||||
|
// next Transformer in a composed Tranformer,
|
||||||
|
// see tranform.go#L75
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// "ans" is never nil here, so we don't have to check that
|
||||||
|
// see survey.go#L338 for more.
|
||||||
|
// Make sure that the the answer's value was a typeof string.
|
||||||
|
s, ok := ans.(string)
|
||||||
|
if !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return f(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToLower is a `Transformer`.
|
||||||
|
// It receives an answer value
|
||||||
|
// and returns a copy of the "ans"
|
||||||
|
// with all Unicode letters mapped to their lower case.
|
||||||
|
//
|
||||||
|
// Note that if "ans" is not a string then it will
|
||||||
|
// return a nil value, meaning that the above answer
|
||||||
|
// will not be affected by this call at all.
|
||||||
|
func ToLower(ans interface{}) interface{} {
|
||||||
|
transformer := TransformString(strings.ToLower)
|
||||||
|
return transformer(ans)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Title is a `Transformer`.
|
||||||
|
// It receives an answer value
|
||||||
|
// and returns a copy of the "ans"
|
||||||
|
// with all Unicode letters that begin words
|
||||||
|
// mapped to their title case.
|
||||||
|
//
|
||||||
|
// Note that if "ans" is not a string then it will
|
||||||
|
// return a nil value, meaning that the above answer
|
||||||
|
// will not be affected by this call at all.
|
||||||
|
func Title(ans interface{}) interface{} {
|
||||||
|
transformer := TransformString(cases.Title(language.English).String)
|
||||||
|
return transformer(ans)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ComposeTransformers is a variadic function used to create one transformer from many.
|
||||||
|
func ComposeTransformers(transformers ...Transformer) Transformer {
|
||||||
|
// return a transformer that calls each one sequentially
|
||||||
|
return func(ans interface{}) interface{} {
|
||||||
|
// execute each transformer
|
||||||
|
for _, t := range transformers {
|
||||||
|
ans = t(ans)
|
||||||
|
}
|
||||||
|
return ans
|
||||||
|
}
|
||||||
|
}
|
128
vendor/github.com/AlecAivazis/survey/v2/validate.go
generated
vendored
Normal file
128
vendor/github.com/AlecAivazis/survey/v2/validate.go
generated
vendored
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
package survey
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/AlecAivazis/survey/v2/core"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Required does not allow an empty value
|
||||||
|
func Required(val interface{}) error {
|
||||||
|
// the reflect value of the result
|
||||||
|
value := reflect.ValueOf(val)
|
||||||
|
|
||||||
|
// if the value passed in is the zero value of the appropriate type
|
||||||
|
if isZero(value) && value.Kind() != reflect.Bool {
|
||||||
|
//lint:ignore ST1005 this error message should render as capitalized
|
||||||
|
return errors.New("Value is required")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MaxLength requires that the string is no longer than the specified value
|
||||||
|
func MaxLength(length int) Validator {
|
||||||
|
// return a validator that checks the length of the string
|
||||||
|
return func(val interface{}) error {
|
||||||
|
if str, ok := val.(string); ok {
|
||||||
|
// if the string is longer than the given value
|
||||||
|
if len([]rune(str)) > length {
|
||||||
|
// yell loudly
|
||||||
|
return fmt.Errorf("value is too long. Max length is %v", length)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// otherwise we cannot convert the value into a string and cannot enforce length
|
||||||
|
return fmt.Errorf("cannot enforce length on response of type %v", reflect.TypeOf(val).Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
// the input is fine
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MinLength requires that the string is longer or equal in length to the specified value
|
||||||
|
func MinLength(length int) Validator {
|
||||||
|
// return a validator that checks the length of the string
|
||||||
|
return func(val interface{}) error {
|
||||||
|
if str, ok := val.(string); ok {
|
||||||
|
// if the string is shorter than the given value
|
||||||
|
if len([]rune(str)) < length {
|
||||||
|
// yell loudly
|
||||||
|
return fmt.Errorf("value is too short. Min length is %v", length)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// otherwise we cannot convert the value into a string and cannot enforce length
|
||||||
|
return fmt.Errorf("cannot enforce length on response of type %v", reflect.TypeOf(val).Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
// the input is fine
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MaxItems requires that the list is no longer than the specified value
|
||||||
|
func MaxItems(numberItems int) Validator {
|
||||||
|
// return a validator that checks the length of the list
|
||||||
|
return func(val interface{}) error {
|
||||||
|
if list, ok := val.([]core.OptionAnswer); ok {
|
||||||
|
// if the list is longer than the given value
|
||||||
|
if len(list) > numberItems {
|
||||||
|
// yell loudly
|
||||||
|
return fmt.Errorf("value is too long. Max items is %v", numberItems)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// otherwise we cannot convert the value into a list of answer and cannot enforce length
|
||||||
|
return fmt.Errorf("cannot impose the length on something other than a list of answers")
|
||||||
|
}
|
||||||
|
// the input is fine
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MinItems requires that the list is longer or equal in length to the specified value
|
||||||
|
func MinItems(numberItems int) Validator {
|
||||||
|
// return a validator that checks the length of the list
|
||||||
|
return func(val interface{}) error {
|
||||||
|
if list, ok := val.([]core.OptionAnswer); ok {
|
||||||
|
// if the list is shorter than the given value
|
||||||
|
if len(list) < numberItems {
|
||||||
|
// yell loudly
|
||||||
|
return fmt.Errorf("value is too short. Min items is %v", numberItems)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// otherwise we cannot convert the value into a list of answer and cannot enforce length
|
||||||
|
return fmt.Errorf("cannot impose the length on something other than a list of answers")
|
||||||
|
}
|
||||||
|
// the input is fine
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ComposeValidators is a variadic function used to create one validator from many.
|
||||||
|
func ComposeValidators(validators ...Validator) Validator {
|
||||||
|
// return a validator that calls each one sequentially
|
||||||
|
return func(val interface{}) error {
|
||||||
|
// execute each validator
|
||||||
|
for _, validator := range validators {
|
||||||
|
// if the answer's value is not valid
|
||||||
|
if err := validator(val); err != nil {
|
||||||
|
// return the error
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// we passed all validators, the answer is valid
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// isZero returns true if the passed value is the zero object
|
||||||
|
func isZero(v reflect.Value) bool {
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.Slice, reflect.Map:
|
||||||
|
return v.Len() == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// compare the types directly with more general coverage
|
||||||
|
return reflect.DeepEqual(v.Interface(), reflect.Zero(v.Type()).Interface())
|
||||||
|
}
|
21
vendor/github.com/Azure/go-ansiterm/LICENSE
generated
vendored
Normal file
21
vendor/github.com/Azure/go-ansiterm/LICENSE
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2015 Microsoft Corporation
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
12
vendor/github.com/Azure/go-ansiterm/README.md
generated
vendored
Normal file
12
vendor/github.com/Azure/go-ansiterm/README.md
generated
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
# go-ansiterm
|
||||||
|
|
||||||
|
This is a cross platform Ansi Terminal Emulation library. It reads a stream of Ansi characters and produces the appropriate function calls. The results of the function calls are platform dependent.
|
||||||
|
|
||||||
|
For example the parser might receive "ESC, [, A" as a stream of three characters. This is the code for Cursor Up (http://www.vt100.net/docs/vt510-rm/CUU). The parser then calls the cursor up function (CUU()) on an event handler. The event handler determines what platform specific work must be done to cause the cursor to move up one position.
|
||||||
|
|
||||||
|
The parser (parser.go) is a partial implementation of this state machine (http://vt100.net/emu/vt500_parser.png). There are also two event handler implementations, one for tests (test_event_handler.go) to validate that the expected events are being produced and called, the other is a Windows implementation (winterm/win_event_handler.go).
|
||||||
|
|
||||||
|
See parser_test.go for examples exercising the state machine and generating appropriate function calls.
|
||||||
|
|
||||||
|
-----
|
||||||
|
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
|
41
vendor/github.com/Azure/go-ansiterm/SECURITY.md
generated
vendored
Normal file
41
vendor/github.com/Azure/go-ansiterm/SECURITY.md
generated
vendored
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
<!-- BEGIN MICROSOFT SECURITY.MD V0.0.8 BLOCK -->
|
||||||
|
|
||||||
|
## Security
|
||||||
|
|
||||||
|
Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/).
|
||||||
|
|
||||||
|
If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below.
|
||||||
|
|
||||||
|
## Reporting Security Issues
|
||||||
|
|
||||||
|
**Please do not report security vulnerabilities through public GitHub issues.**
|
||||||
|
|
||||||
|
Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report).
|
||||||
|
|
||||||
|
If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey).
|
||||||
|
|
||||||
|
You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc).
|
||||||
|
|
||||||
|
Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:
|
||||||
|
|
||||||
|
* Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.)
|
||||||
|
* Full paths of source file(s) related to the manifestation of the issue
|
||||||
|
* The location of the affected source code (tag/branch/commit or direct URL)
|
||||||
|
* Any special configuration required to reproduce the issue
|
||||||
|
* Step-by-step instructions to reproduce the issue
|
||||||
|
* Proof-of-concept or exploit code (if possible)
|
||||||
|
* Impact of the issue, including how an attacker might exploit the issue
|
||||||
|
|
||||||
|
This information will help us triage your report more quickly.
|
||||||
|
|
||||||
|
If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs.
|
||||||
|
|
||||||
|
## Preferred Languages
|
||||||
|
|
||||||
|
We prefer all communications to be in English.
|
||||||
|
|
||||||
|
## Policy
|
||||||
|
|
||||||
|
Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd).
|
||||||
|
|
||||||
|
<!-- END MICROSOFT SECURITY.MD BLOCK -->
|
188
vendor/github.com/Azure/go-ansiterm/constants.go
generated
vendored
Normal file
188
vendor/github.com/Azure/go-ansiterm/constants.go
generated
vendored
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
package ansiterm
|
||||||
|
|
||||||
|
const LogEnv = "DEBUG_TERMINAL"
|
||||||
|
|
||||||
|
// ANSI constants
|
||||||
|
// References:
|
||||||
|
// -- http://www.ecma-international.org/publications/standards/Ecma-048.htm
|
||||||
|
// -- http://man7.org/linux/man-pages/man4/console_codes.4.html
|
||||||
|
// -- http://manpages.ubuntu.com/manpages/intrepid/man4/console_codes.4.html
|
||||||
|
// -- http://en.wikipedia.org/wiki/ANSI_escape_code
|
||||||
|
// -- http://vt100.net/emu/dec_ansi_parser
|
||||||
|
// -- http://vt100.net/emu/vt500_parser.svg
|
||||||
|
// -- http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
|
||||||
|
// -- http://www.inwap.com/pdp10/ansicode.txt
|
||||||
|
const (
|
||||||
|
// ECMA-48 Set Graphics Rendition
|
||||||
|
// Note:
|
||||||
|
// -- Constants leading with an underscore (e.g., _ANSI_xxx) are unsupported or reserved
|
||||||
|
// -- Fonts could possibly be supported via SetCurrentConsoleFontEx
|
||||||
|
// -- Windows does not expose the per-window cursor (i.e., caret) blink times
|
||||||
|
ANSI_SGR_RESET = 0
|
||||||
|
ANSI_SGR_BOLD = 1
|
||||||
|
ANSI_SGR_DIM = 2
|
||||||
|
_ANSI_SGR_ITALIC = 3
|
||||||
|
ANSI_SGR_UNDERLINE = 4
|
||||||
|
_ANSI_SGR_BLINKSLOW = 5
|
||||||
|
_ANSI_SGR_BLINKFAST = 6
|
||||||
|
ANSI_SGR_REVERSE = 7
|
||||||
|
_ANSI_SGR_INVISIBLE = 8
|
||||||
|
_ANSI_SGR_LINETHROUGH = 9
|
||||||
|
_ANSI_SGR_FONT_00 = 10
|
||||||
|
_ANSI_SGR_FONT_01 = 11
|
||||||
|
_ANSI_SGR_FONT_02 = 12
|
||||||
|
_ANSI_SGR_FONT_03 = 13
|
||||||
|
_ANSI_SGR_FONT_04 = 14
|
||||||
|
_ANSI_SGR_FONT_05 = 15
|
||||||
|
_ANSI_SGR_FONT_06 = 16
|
||||||
|
_ANSI_SGR_FONT_07 = 17
|
||||||
|
_ANSI_SGR_FONT_08 = 18
|
||||||
|
_ANSI_SGR_FONT_09 = 19
|
||||||
|
_ANSI_SGR_FONT_10 = 20
|
||||||
|
_ANSI_SGR_DOUBLEUNDERLINE = 21
|
||||||
|
ANSI_SGR_BOLD_DIM_OFF = 22
|
||||||
|
_ANSI_SGR_ITALIC_OFF = 23
|
||||||
|
ANSI_SGR_UNDERLINE_OFF = 24
|
||||||
|
_ANSI_SGR_BLINK_OFF = 25
|
||||||
|
_ANSI_SGR_RESERVED_00 = 26
|
||||||
|
ANSI_SGR_REVERSE_OFF = 27
|
||||||
|
_ANSI_SGR_INVISIBLE_OFF = 28
|
||||||
|
_ANSI_SGR_LINETHROUGH_OFF = 29
|
||||||
|
ANSI_SGR_FOREGROUND_BLACK = 30
|
||||||
|
ANSI_SGR_FOREGROUND_RED = 31
|
||||||
|
ANSI_SGR_FOREGROUND_GREEN = 32
|
||||||
|
ANSI_SGR_FOREGROUND_YELLOW = 33
|
||||||
|
ANSI_SGR_FOREGROUND_BLUE = 34
|
||||||
|
ANSI_SGR_FOREGROUND_MAGENTA = 35
|
||||||
|
ANSI_SGR_FOREGROUND_CYAN = 36
|
||||||
|
ANSI_SGR_FOREGROUND_WHITE = 37
|
||||||
|
_ANSI_SGR_RESERVED_01 = 38
|
||||||
|
ANSI_SGR_FOREGROUND_DEFAULT = 39
|
||||||
|
ANSI_SGR_BACKGROUND_BLACK = 40
|
||||||
|
ANSI_SGR_BACKGROUND_RED = 41
|
||||||
|
ANSI_SGR_BACKGROUND_GREEN = 42
|
||||||
|
ANSI_SGR_BACKGROUND_YELLOW = 43
|
||||||
|
ANSI_SGR_BACKGROUND_BLUE = 44
|
||||||
|
ANSI_SGR_BACKGROUND_MAGENTA = 45
|
||||||
|
ANSI_SGR_BACKGROUND_CYAN = 46
|
||||||
|
ANSI_SGR_BACKGROUND_WHITE = 47
|
||||||
|
_ANSI_SGR_RESERVED_02 = 48
|
||||||
|
ANSI_SGR_BACKGROUND_DEFAULT = 49
|
||||||
|
// 50 - 65: Unsupported
|
||||||
|
|
||||||
|
ANSI_MAX_CMD_LENGTH = 4096
|
||||||
|
|
||||||
|
MAX_INPUT_EVENTS = 128
|
||||||
|
DEFAULT_WIDTH = 80
|
||||||
|
DEFAULT_HEIGHT = 24
|
||||||
|
|
||||||
|
ANSI_BEL = 0x07
|
||||||
|
ANSI_BACKSPACE = 0x08
|
||||||
|
ANSI_TAB = 0x09
|
||||||
|
ANSI_LINE_FEED = 0x0A
|
||||||
|
ANSI_VERTICAL_TAB = 0x0B
|
||||||
|
ANSI_FORM_FEED = 0x0C
|
||||||
|
ANSI_CARRIAGE_RETURN = 0x0D
|
||||||
|
ANSI_ESCAPE_PRIMARY = 0x1B
|
||||||
|
ANSI_ESCAPE_SECONDARY = 0x5B
|
||||||
|
ANSI_OSC_STRING_ENTRY = 0x5D
|
||||||
|
ANSI_COMMAND_FIRST = 0x40
|
||||||
|
ANSI_COMMAND_LAST = 0x7E
|
||||||
|
DCS_ENTRY = 0x90
|
||||||
|
CSI_ENTRY = 0x9B
|
||||||
|
OSC_STRING = 0x9D
|
||||||
|
ANSI_PARAMETER_SEP = ";"
|
||||||
|
ANSI_CMD_G0 = '('
|
||||||
|
ANSI_CMD_G1 = ')'
|
||||||
|
ANSI_CMD_G2 = '*'
|
||||||
|
ANSI_CMD_G3 = '+'
|
||||||
|
ANSI_CMD_DECPNM = '>'
|
||||||
|
ANSI_CMD_DECPAM = '='
|
||||||
|
ANSI_CMD_OSC = ']'
|
||||||
|
ANSI_CMD_STR_TERM = '\\'
|
||||||
|
|
||||||
|
KEY_CONTROL_PARAM_2 = ";2"
|
||||||
|
KEY_CONTROL_PARAM_3 = ";3"
|
||||||
|
KEY_CONTROL_PARAM_4 = ";4"
|
||||||
|
KEY_CONTROL_PARAM_5 = ";5"
|
||||||
|
KEY_CONTROL_PARAM_6 = ";6"
|
||||||
|
KEY_CONTROL_PARAM_7 = ";7"
|
||||||
|
KEY_CONTROL_PARAM_8 = ";8"
|
||||||
|
KEY_ESC_CSI = "\x1B["
|
||||||
|
KEY_ESC_N = "\x1BN"
|
||||||
|
KEY_ESC_O = "\x1BO"
|
||||||
|
|
||||||
|
FILL_CHARACTER = ' '
|
||||||
|
)
|
||||||
|
|
||||||
|
func getByteRange(start byte, end byte) []byte {
|
||||||
|
bytes := make([]byte, 0, 32)
|
||||||
|
for i := start; i <= end; i++ {
|
||||||
|
bytes = append(bytes, byte(i))
|
||||||
|
}
|
||||||
|
|
||||||
|
return bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
var toGroundBytes = getToGroundBytes()
|
||||||
|
var executors = getExecuteBytes()
|
||||||
|
|
||||||
|
// SPACE 20+A0 hex Always and everywhere a blank space
|
||||||
|
// Intermediate 20-2F hex !"#$%&'()*+,-./
|
||||||
|
var intermeds = getByteRange(0x20, 0x2F)
|
||||||
|
|
||||||
|
// Parameters 30-3F hex 0123456789:;<=>?
|
||||||
|
// CSI Parameters 30-39, 3B hex 0123456789;
|
||||||
|
var csiParams = getByteRange(0x30, 0x3F)
|
||||||
|
|
||||||
|
var csiCollectables = append(getByteRange(0x30, 0x39), getByteRange(0x3B, 0x3F)...)
|
||||||
|
|
||||||
|
// Uppercase 40-5F hex @ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_
|
||||||
|
var upperCase = getByteRange(0x40, 0x5F)
|
||||||
|
|
||||||
|
// Lowercase 60-7E hex `abcdefghijlkmnopqrstuvwxyz{|}~
|
||||||
|
var lowerCase = getByteRange(0x60, 0x7E)
|
||||||
|
|
||||||
|
// Alphabetics 40-7E hex (all of upper and lower case)
|
||||||
|
var alphabetics = append(upperCase, lowerCase...)
|
||||||
|
|
||||||
|
var printables = getByteRange(0x20, 0x7F)
|
||||||
|
|
||||||
|
var escapeIntermediateToGroundBytes = getByteRange(0x30, 0x7E)
|
||||||
|
var escapeToGroundBytes = getEscapeToGroundBytes()
|
||||||
|
|
||||||
|
// See http://www.vt100.net/emu/vt500_parser.png for description of the complex
|
||||||
|
// byte ranges below
|
||||||
|
|
||||||
|
func getEscapeToGroundBytes() []byte {
|
||||||
|
escapeToGroundBytes := getByteRange(0x30, 0x4F)
|
||||||
|
escapeToGroundBytes = append(escapeToGroundBytes, getByteRange(0x51, 0x57)...)
|
||||||
|
escapeToGroundBytes = append(escapeToGroundBytes, 0x59)
|
||||||
|
escapeToGroundBytes = append(escapeToGroundBytes, 0x5A)
|
||||||
|
escapeToGroundBytes = append(escapeToGroundBytes, 0x5C)
|
||||||
|
escapeToGroundBytes = append(escapeToGroundBytes, getByteRange(0x60, 0x7E)...)
|
||||||
|
return escapeToGroundBytes
|
||||||
|
}
|
||||||
|
|
||||||
|
func getExecuteBytes() []byte {
|
||||||
|
executeBytes := getByteRange(0x00, 0x17)
|
||||||
|
executeBytes = append(executeBytes, 0x19)
|
||||||
|
executeBytes = append(executeBytes, getByteRange(0x1C, 0x1F)...)
|
||||||
|
return executeBytes
|
||||||
|
}
|
||||||
|
|
||||||
|
func getToGroundBytes() []byte {
|
||||||
|
groundBytes := []byte{0x18}
|
||||||
|
groundBytes = append(groundBytes, 0x1A)
|
||||||
|
groundBytes = append(groundBytes, getByteRange(0x80, 0x8F)...)
|
||||||
|
groundBytes = append(groundBytes, getByteRange(0x91, 0x97)...)
|
||||||
|
groundBytes = append(groundBytes, 0x99)
|
||||||
|
groundBytes = append(groundBytes, 0x9A)
|
||||||
|
groundBytes = append(groundBytes, 0x9C)
|
||||||
|
return groundBytes
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete 7F hex Always and everywhere ignored
|
||||||
|
// C1 Control 80-9F hex 32 additional control characters
|
||||||
|
// G1 Displayable A1-FE hex 94 additional displayable characters
|
||||||
|
// Special A0+FF hex Same as SPACE and DELETE
|
7
vendor/github.com/Azure/go-ansiterm/context.go
generated
vendored
Normal file
7
vendor/github.com/Azure/go-ansiterm/context.go
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
package ansiterm
|
||||||
|
|
||||||
|
type ansiContext struct {
|
||||||
|
currentChar byte
|
||||||
|
paramBuffer []byte
|
||||||
|
interBuffer []byte
|
||||||
|
}
|
49
vendor/github.com/Azure/go-ansiterm/csi_entry_state.go
generated
vendored
Normal file
49
vendor/github.com/Azure/go-ansiterm/csi_entry_state.go
generated
vendored
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
package ansiterm
|
||||||
|
|
||||||
|
type csiEntryState struct {
|
||||||
|
baseState
|
||||||
|
}
|
||||||
|
|
||||||
|
func (csiState csiEntryState) Handle(b byte) (s state, e error) {
|
||||||
|
csiState.parser.logf("CsiEntry::Handle %#x", b)
|
||||||
|
|
||||||
|
nextState, err := csiState.baseState.Handle(b)
|
||||||
|
if nextState != nil || err != nil {
|
||||||
|
return nextState, err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case sliceContains(alphabetics, b):
|
||||||
|
return csiState.parser.ground, nil
|
||||||
|
case sliceContains(csiCollectables, b):
|
||||||
|
return csiState.parser.csiParam, nil
|
||||||
|
case sliceContains(executors, b):
|
||||||
|
return csiState, csiState.parser.execute()
|
||||||
|
}
|
||||||
|
|
||||||
|
return csiState, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (csiState csiEntryState) Transition(s state) error {
|
||||||
|
csiState.parser.logf("CsiEntry::Transition %s --> %s", csiState.Name(), s.Name())
|
||||||
|
csiState.baseState.Transition(s)
|
||||||
|
|
||||||
|
switch s {
|
||||||
|
case csiState.parser.ground:
|
||||||
|
return csiState.parser.csiDispatch()
|
||||||
|
case csiState.parser.csiParam:
|
||||||
|
switch {
|
||||||
|
case sliceContains(csiParams, csiState.parser.context.currentChar):
|
||||||
|
csiState.parser.collectParam()
|
||||||
|
case sliceContains(intermeds, csiState.parser.context.currentChar):
|
||||||
|
csiState.parser.collectInter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (csiState csiEntryState) Enter() error {
|
||||||
|
csiState.parser.clear()
|
||||||
|
return nil
|
||||||
|
}
|
38
vendor/github.com/Azure/go-ansiterm/csi_param_state.go
generated
vendored
Normal file
38
vendor/github.com/Azure/go-ansiterm/csi_param_state.go
generated
vendored
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
package ansiterm
|
||||||
|
|
||||||
|
type csiParamState struct {
|
||||||
|
baseState
|
||||||
|
}
|
||||||
|
|
||||||
|
func (csiState csiParamState) Handle(b byte) (s state, e error) {
|
||||||
|
csiState.parser.logf("CsiParam::Handle %#x", b)
|
||||||
|
|
||||||
|
nextState, err := csiState.baseState.Handle(b)
|
||||||
|
if nextState != nil || err != nil {
|
||||||
|
return nextState, err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case sliceContains(alphabetics, b):
|
||||||
|
return csiState.parser.ground, nil
|
||||||
|
case sliceContains(csiCollectables, b):
|
||||||
|
csiState.parser.collectParam()
|
||||||
|
return csiState, nil
|
||||||
|
case sliceContains(executors, b):
|
||||||
|
return csiState, csiState.parser.execute()
|
||||||
|
}
|
||||||
|
|
||||||
|
return csiState, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (csiState csiParamState) Transition(s state) error {
|
||||||
|
csiState.parser.logf("CsiParam::Transition %s --> %s", csiState.Name(), s.Name())
|
||||||
|
csiState.baseState.Transition(s)
|
||||||
|
|
||||||
|
switch s {
|
||||||
|
case csiState.parser.ground:
|
||||||
|
return csiState.parser.csiDispatch()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
36
vendor/github.com/Azure/go-ansiterm/escape_intermediate_state.go
generated
vendored
Normal file
36
vendor/github.com/Azure/go-ansiterm/escape_intermediate_state.go
generated
vendored
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
package ansiterm
|
||||||
|
|
||||||
|
type escapeIntermediateState struct {
|
||||||
|
baseState
|
||||||
|
}
|
||||||
|
|
||||||
|
func (escState escapeIntermediateState) Handle(b byte) (s state, e error) {
|
||||||
|
escState.parser.logf("escapeIntermediateState::Handle %#x", b)
|
||||||
|
nextState, err := escState.baseState.Handle(b)
|
||||||
|
if nextState != nil || err != nil {
|
||||||
|
return nextState, err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case sliceContains(intermeds, b):
|
||||||
|
return escState, escState.parser.collectInter()
|
||||||
|
case sliceContains(executors, b):
|
||||||
|
return escState, escState.parser.execute()
|
||||||
|
case sliceContains(escapeIntermediateToGroundBytes, b):
|
||||||
|
return escState.parser.ground, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return escState, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (escState escapeIntermediateState) Transition(s state) error {
|
||||||
|
escState.parser.logf("escapeIntermediateState::Transition %s --> %s", escState.Name(), s.Name())
|
||||||
|
escState.baseState.Transition(s)
|
||||||
|
|
||||||
|
switch s {
|
||||||
|
case escState.parser.ground:
|
||||||
|
return escState.parser.escDispatch()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
47
vendor/github.com/Azure/go-ansiterm/escape_state.go
generated
vendored
Normal file
47
vendor/github.com/Azure/go-ansiterm/escape_state.go
generated
vendored
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
package ansiterm
|
||||||
|
|
||||||
|
type escapeState struct {
|
||||||
|
baseState
|
||||||
|
}
|
||||||
|
|
||||||
|
func (escState escapeState) Handle(b byte) (s state, e error) {
|
||||||
|
escState.parser.logf("escapeState::Handle %#x", b)
|
||||||
|
nextState, err := escState.baseState.Handle(b)
|
||||||
|
if nextState != nil || err != nil {
|
||||||
|
return nextState, err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case b == ANSI_ESCAPE_SECONDARY:
|
||||||
|
return escState.parser.csiEntry, nil
|
||||||
|
case b == ANSI_OSC_STRING_ENTRY:
|
||||||
|
return escState.parser.oscString, nil
|
||||||
|
case sliceContains(executors, b):
|
||||||
|
return escState, escState.parser.execute()
|
||||||
|
case sliceContains(escapeToGroundBytes, b):
|
||||||
|
return escState.parser.ground, nil
|
||||||
|
case sliceContains(intermeds, b):
|
||||||
|
return escState.parser.escapeIntermediate, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return escState, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (escState escapeState) Transition(s state) error {
|
||||||
|
escState.parser.logf("Escape::Transition %s --> %s", escState.Name(), s.Name())
|
||||||
|
escState.baseState.Transition(s)
|
||||||
|
|
||||||
|
switch s {
|
||||||
|
case escState.parser.ground:
|
||||||
|
return escState.parser.escDispatch()
|
||||||
|
case escState.parser.escapeIntermediate:
|
||||||
|
return escState.parser.collectInter()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (escState escapeState) Enter() error {
|
||||||
|
escState.parser.clear()
|
||||||
|
return nil
|
||||||
|
}
|
90
vendor/github.com/Azure/go-ansiterm/event_handler.go
generated
vendored
Normal file
90
vendor/github.com/Azure/go-ansiterm/event_handler.go
generated
vendored
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
package ansiterm
|
||||||
|
|
||||||
|
type AnsiEventHandler interface {
|
||||||
|
// Print
|
||||||
|
Print(b byte) error
|
||||||
|
|
||||||
|
// Execute C0 commands
|
||||||
|
Execute(b byte) error
|
||||||
|
|
||||||
|
// CUrsor Up
|
||||||
|
CUU(int) error
|
||||||
|
|
||||||
|
// CUrsor Down
|
||||||
|
CUD(int) error
|
||||||
|
|
||||||
|
// CUrsor Forward
|
||||||
|
CUF(int) error
|
||||||
|
|
||||||
|
// CUrsor Backward
|
||||||
|
CUB(int) error
|
||||||
|
|
||||||
|
// Cursor to Next Line
|
||||||
|
CNL(int) error
|
||||||
|
|
||||||
|
// Cursor to Previous Line
|
||||||
|
CPL(int) error
|
||||||
|
|
||||||
|
// Cursor Horizontal position Absolute
|
||||||
|
CHA(int) error
|
||||||
|
|
||||||
|
// Vertical line Position Absolute
|
||||||
|
VPA(int) error
|
||||||
|
|
||||||
|
// CUrsor Position
|
||||||
|
CUP(int, int) error
|
||||||
|
|
||||||
|
// Horizontal and Vertical Position (depends on PUM)
|
||||||
|
HVP(int, int) error
|
||||||
|
|
||||||
|
// Text Cursor Enable Mode
|
||||||
|
DECTCEM(bool) error
|
||||||
|
|
||||||
|
// Origin Mode
|
||||||
|
DECOM(bool) error
|
||||||
|
|
||||||
|
// 132 Column Mode
|
||||||
|
DECCOLM(bool) error
|
||||||
|
|
||||||
|
// Erase in Display
|
||||||
|
ED(int) error
|
||||||
|
|
||||||
|
// Erase in Line
|
||||||
|
EL(int) error
|
||||||
|
|
||||||
|
// Insert Line
|
||||||
|
IL(int) error
|
||||||
|
|
||||||
|
// Delete Line
|
||||||
|
DL(int) error
|
||||||
|
|
||||||
|
// Insert Character
|
||||||
|
ICH(int) error
|
||||||
|
|
||||||
|
// Delete Character
|
||||||
|
DCH(int) error
|
||||||
|
|
||||||
|
// Set Graphics Rendition
|
||||||
|
SGR([]int) error
|
||||||
|
|
||||||
|
// Pan Down
|
||||||
|
SU(int) error
|
||||||
|
|
||||||
|
// Pan Up
|
||||||
|
SD(int) error
|
||||||
|
|
||||||
|
// Device Attributes
|
||||||
|
DA([]string) error
|
||||||
|
|
||||||
|
// Set Top and Bottom Margins
|
||||||
|
DECSTBM(int, int) error
|
||||||
|
|
||||||
|
// Index
|
||||||
|
IND() error
|
||||||
|
|
||||||
|
// Reverse Index
|
||||||
|
RI() error
|
||||||
|
|
||||||
|
// Flush updates from previous commands
|
||||||
|
Flush() error
|
||||||
|
}
|
24
vendor/github.com/Azure/go-ansiterm/ground_state.go
generated
vendored
Normal file
24
vendor/github.com/Azure/go-ansiterm/ground_state.go
generated
vendored
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
package ansiterm
|
||||||
|
|
||||||
|
type groundState struct {
|
||||||
|
baseState
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gs groundState) Handle(b byte) (s state, e error) {
|
||||||
|
gs.parser.context.currentChar = b
|
||||||
|
|
||||||
|
nextState, err := gs.baseState.Handle(b)
|
||||||
|
if nextState != nil || err != nil {
|
||||||
|
return nextState, err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case sliceContains(printables, b):
|
||||||
|
return gs, gs.parser.print()
|
||||||
|
|
||||||
|
case sliceContains(executors, b):
|
||||||
|
return gs, gs.parser.execute()
|
||||||
|
}
|
||||||
|
|
||||||
|
return gs, nil
|
||||||
|
}
|
31
vendor/github.com/Azure/go-ansiterm/osc_string_state.go
generated
vendored
Normal file
31
vendor/github.com/Azure/go-ansiterm/osc_string_state.go
generated
vendored
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
package ansiterm
|
||||||
|
|
||||||
|
type oscStringState struct {
|
||||||
|
baseState
|
||||||
|
}
|
||||||
|
|
||||||
|
func (oscState oscStringState) Handle(b byte) (s state, e error) {
|
||||||
|
oscState.parser.logf("OscString::Handle %#x", b)
|
||||||
|
nextState, err := oscState.baseState.Handle(b)
|
||||||
|
if nextState != nil || err != nil {
|
||||||
|
return nextState, err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case isOscStringTerminator(b):
|
||||||
|
return oscState.parser.ground, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return oscState, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// See below for OSC string terminators for linux
|
||||||
|
// http://man7.org/linux/man-pages/man4/console_codes.4.html
|
||||||
|
func isOscStringTerminator(b byte) bool {
|
||||||
|
|
||||||
|
if b == ANSI_BEL || b == 0x5C {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
151
vendor/github.com/Azure/go-ansiterm/parser.go
generated
vendored
Normal file
151
vendor/github.com/Azure/go-ansiterm/parser.go
generated
vendored
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
package ansiterm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AnsiParser struct {
|
||||||
|
currState state
|
||||||
|
eventHandler AnsiEventHandler
|
||||||
|
context *ansiContext
|
||||||
|
csiEntry state
|
||||||
|
csiParam state
|
||||||
|
dcsEntry state
|
||||||
|
escape state
|
||||||
|
escapeIntermediate state
|
||||||
|
error state
|
||||||
|
ground state
|
||||||
|
oscString state
|
||||||
|
stateMap []state
|
||||||
|
|
||||||
|
logf func(string, ...interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
type Option func(*AnsiParser)
|
||||||
|
|
||||||
|
func WithLogf(f func(string, ...interface{})) Option {
|
||||||
|
return func(ap *AnsiParser) {
|
||||||
|
ap.logf = f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateParser(initialState string, evtHandler AnsiEventHandler, opts ...Option) *AnsiParser {
|
||||||
|
ap := &AnsiParser{
|
||||||
|
eventHandler: evtHandler,
|
||||||
|
context: &ansiContext{},
|
||||||
|
}
|
||||||
|
for _, o := range opts {
|
||||||
|
o(ap)
|
||||||
|
}
|
||||||
|
|
||||||
|
if isDebugEnv := os.Getenv(LogEnv); isDebugEnv == "1" {
|
||||||
|
logFile, _ := os.Create("ansiParser.log")
|
||||||
|
logger := log.New(logFile, "", log.LstdFlags)
|
||||||
|
if ap.logf != nil {
|
||||||
|
l := ap.logf
|
||||||
|
ap.logf = func(s string, v ...interface{}) {
|
||||||
|
l(s, v...)
|
||||||
|
logger.Printf(s, v...)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ap.logf = logger.Printf
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ap.logf == nil {
|
||||||
|
ap.logf = func(string, ...interface{}) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
ap.csiEntry = csiEntryState{baseState{name: "CsiEntry", parser: ap}}
|
||||||
|
ap.csiParam = csiParamState{baseState{name: "CsiParam", parser: ap}}
|
||||||
|
ap.dcsEntry = dcsEntryState{baseState{name: "DcsEntry", parser: ap}}
|
||||||
|
ap.escape = escapeState{baseState{name: "Escape", parser: ap}}
|
||||||
|
ap.escapeIntermediate = escapeIntermediateState{baseState{name: "EscapeIntermediate", parser: ap}}
|
||||||
|
ap.error = errorState{baseState{name: "Error", parser: ap}}
|
||||||
|
ap.ground = groundState{baseState{name: "Ground", parser: ap}}
|
||||||
|
ap.oscString = oscStringState{baseState{name: "OscString", parser: ap}}
|
||||||
|
|
||||||
|
ap.stateMap = []state{
|
||||||
|
ap.csiEntry,
|
||||||
|
ap.csiParam,
|
||||||
|
ap.dcsEntry,
|
||||||
|
ap.escape,
|
||||||
|
ap.escapeIntermediate,
|
||||||
|
ap.error,
|
||||||
|
ap.ground,
|
||||||
|
ap.oscString,
|
||||||
|
}
|
||||||
|
|
||||||
|
ap.currState = getState(initialState, ap.stateMap)
|
||||||
|
|
||||||
|
ap.logf("CreateParser: parser %p", ap)
|
||||||
|
return ap
|
||||||
|
}
|
||||||
|
|
||||||
|
func getState(name string, states []state) state {
|
||||||
|
for _, el := range states {
|
||||||
|
if el.Name() == name {
|
||||||
|
return el
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ap *AnsiParser) Parse(bytes []byte) (int, error) {
|
||||||
|
for i, b := range bytes {
|
||||||
|
if err := ap.handle(b); err != nil {
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return len(bytes), ap.eventHandler.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ap *AnsiParser) handle(b byte) error {
|
||||||
|
ap.context.currentChar = b
|
||||||
|
newState, err := ap.currState.Handle(b)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if newState == nil {
|
||||||
|
ap.logf("WARNING: newState is nil")
|
||||||
|
return errors.New("New state of 'nil' is invalid.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if newState != ap.currState {
|
||||||
|
if err := ap.changeState(newState); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ap *AnsiParser) changeState(newState state) error {
|
||||||
|
ap.logf("ChangeState %s --> %s", ap.currState.Name(), newState.Name())
|
||||||
|
|
||||||
|
// Exit old state
|
||||||
|
if err := ap.currState.Exit(); err != nil {
|
||||||
|
ap.logf("Exit state '%s' failed with : '%v'", ap.currState.Name(), err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform transition action
|
||||||
|
if err := ap.currState.Transition(newState); err != nil {
|
||||||
|
ap.logf("Transition from '%s' to '%s' failed with: '%v'", ap.currState.Name(), newState.Name, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enter new state
|
||||||
|
if err := newState.Enter(); err != nil {
|
||||||
|
ap.logf("Enter state '%s' failed with: '%v'", newState.Name(), err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ap.currState = newState
|
||||||
|
return nil
|
||||||
|
}
|
99
vendor/github.com/Azure/go-ansiterm/parser_action_helpers.go
generated
vendored
Normal file
99
vendor/github.com/Azure/go-ansiterm/parser_action_helpers.go
generated
vendored
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
package ansiterm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
func parseParams(bytes []byte) ([]string, error) {
|
||||||
|
paramBuff := make([]byte, 0, 0)
|
||||||
|
params := []string{}
|
||||||
|
|
||||||
|
for _, v := range bytes {
|
||||||
|
if v == ';' {
|
||||||
|
if len(paramBuff) > 0 {
|
||||||
|
// Completed parameter, append it to the list
|
||||||
|
s := string(paramBuff)
|
||||||
|
params = append(params, s)
|
||||||
|
paramBuff = make([]byte, 0, 0)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
paramBuff = append(paramBuff, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Last parameter may not be terminated with ';'
|
||||||
|
if len(paramBuff) > 0 {
|
||||||
|
s := string(paramBuff)
|
||||||
|
params = append(params, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
return params, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseCmd(context ansiContext) (string, error) {
|
||||||
|
return string(context.currentChar), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getInt(params []string, dflt int) int {
|
||||||
|
i := getInts(params, 1, dflt)[0]
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
func getInts(params []string, minCount int, dflt int) []int {
|
||||||
|
ints := []int{}
|
||||||
|
|
||||||
|
for _, v := range params {
|
||||||
|
i, _ := strconv.Atoi(v)
|
||||||
|
// Zero is mapped to the default value in VT100.
|
||||||
|
if i == 0 {
|
||||||
|
i = dflt
|
||||||
|
}
|
||||||
|
ints = append(ints, i)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ints) < minCount {
|
||||||
|
remaining := minCount - len(ints)
|
||||||
|
for i := 0; i < remaining; i++ {
|
||||||
|
ints = append(ints, dflt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ints
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ap *AnsiParser) modeDispatch(param string, set bool) error {
|
||||||
|
switch param {
|
||||||
|
case "?3":
|
||||||
|
return ap.eventHandler.DECCOLM(set)
|
||||||
|
case "?6":
|
||||||
|
return ap.eventHandler.DECOM(set)
|
||||||
|
case "?25":
|
||||||
|
return ap.eventHandler.DECTCEM(set)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ap *AnsiParser) hDispatch(params []string) error {
|
||||||
|
if len(params) == 1 {
|
||||||
|
return ap.modeDispatch(params[0], true)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ap *AnsiParser) lDispatch(params []string) error {
|
||||||
|
if len(params) == 1 {
|
||||||
|
return ap.modeDispatch(params[0], false)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getEraseParam(params []string) int {
|
||||||
|
param := getInt(params, 0)
|
||||||
|
if param < 0 || 3 < param {
|
||||||
|
param = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return param
|
||||||
|
}
|
119
vendor/github.com/Azure/go-ansiterm/parser_actions.go
generated
vendored
Normal file
119
vendor/github.com/Azure/go-ansiterm/parser_actions.go
generated
vendored
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
package ansiterm
|
||||||
|
|
||||||
|
func (ap *AnsiParser) collectParam() error {
|
||||||
|
currChar := ap.context.currentChar
|
||||||
|
ap.logf("collectParam %#x", currChar)
|
||||||
|
ap.context.paramBuffer = append(ap.context.paramBuffer, currChar)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ap *AnsiParser) collectInter() error {
|
||||||
|
currChar := ap.context.currentChar
|
||||||
|
ap.logf("collectInter %#x", currChar)
|
||||||
|
ap.context.paramBuffer = append(ap.context.interBuffer, currChar)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ap *AnsiParser) escDispatch() error {
|
||||||
|
cmd, _ := parseCmd(*ap.context)
|
||||||
|
intermeds := ap.context.interBuffer
|
||||||
|
ap.logf("escDispatch currentChar: %#x", ap.context.currentChar)
|
||||||
|
ap.logf("escDispatch: %v(%v)", cmd, intermeds)
|
||||||
|
|
||||||
|
switch cmd {
|
||||||
|
case "D": // IND
|
||||||
|
return ap.eventHandler.IND()
|
||||||
|
case "E": // NEL, equivalent to CRLF
|
||||||
|
err := ap.eventHandler.Execute(ANSI_CARRIAGE_RETURN)
|
||||||
|
if err == nil {
|
||||||
|
err = ap.eventHandler.Execute(ANSI_LINE_FEED)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
case "M": // RI
|
||||||
|
return ap.eventHandler.RI()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ap *AnsiParser) csiDispatch() error {
|
||||||
|
cmd, _ := parseCmd(*ap.context)
|
||||||
|
params, _ := parseParams(ap.context.paramBuffer)
|
||||||
|
ap.logf("Parsed params: %v with length: %d", params, len(params))
|
||||||
|
|
||||||
|
ap.logf("csiDispatch: %v(%v)", cmd, params)
|
||||||
|
|
||||||
|
switch cmd {
|
||||||
|
case "@":
|
||||||
|
return ap.eventHandler.ICH(getInt(params, 1))
|
||||||
|
case "A":
|
||||||
|
return ap.eventHandler.CUU(getInt(params, 1))
|
||||||
|
case "B":
|
||||||
|
return ap.eventHandler.CUD(getInt(params, 1))
|
||||||
|
case "C":
|
||||||
|
return ap.eventHandler.CUF(getInt(params, 1))
|
||||||
|
case "D":
|
||||||
|
return ap.eventHandler.CUB(getInt(params, 1))
|
||||||
|
case "E":
|
||||||
|
return ap.eventHandler.CNL(getInt(params, 1))
|
||||||
|
case "F":
|
||||||
|
return ap.eventHandler.CPL(getInt(params, 1))
|
||||||
|
case "G":
|
||||||
|
return ap.eventHandler.CHA(getInt(params, 1))
|
||||||
|
case "H":
|
||||||
|
ints := getInts(params, 2, 1)
|
||||||
|
x, y := ints[0], ints[1]
|
||||||
|
return ap.eventHandler.CUP(x, y)
|
||||||
|
case "J":
|
||||||
|
param := getEraseParam(params)
|
||||||
|
return ap.eventHandler.ED(param)
|
||||||
|
case "K":
|
||||||
|
param := getEraseParam(params)
|
||||||
|
return ap.eventHandler.EL(param)
|
||||||
|
case "L":
|
||||||
|
return ap.eventHandler.IL(getInt(params, 1))
|
||||||
|
case "M":
|
||||||
|
return ap.eventHandler.DL(getInt(params, 1))
|
||||||
|
case "P":
|
||||||
|
return ap.eventHandler.DCH(getInt(params, 1))
|
||||||
|
case "S":
|
||||||
|
return ap.eventHandler.SU(getInt(params, 1))
|
||||||
|
case "T":
|
||||||
|
return ap.eventHandler.SD(getInt(params, 1))
|
||||||
|
case "c":
|
||||||
|
return ap.eventHandler.DA(params)
|
||||||
|
case "d":
|
||||||
|
return ap.eventHandler.VPA(getInt(params, 1))
|
||||||
|
case "f":
|
||||||
|
ints := getInts(params, 2, 1)
|
||||||
|
x, y := ints[0], ints[1]
|
||||||
|
return ap.eventHandler.HVP(x, y)
|
||||||
|
case "h":
|
||||||
|
return ap.hDispatch(params)
|
||||||
|
case "l":
|
||||||
|
return ap.lDispatch(params)
|
||||||
|
case "m":
|
||||||
|
return ap.eventHandler.SGR(getInts(params, 1, 0))
|
||||||
|
case "r":
|
||||||
|
ints := getInts(params, 2, 1)
|
||||||
|
top, bottom := ints[0], ints[1]
|
||||||
|
return ap.eventHandler.DECSTBM(top, bottom)
|
||||||
|
default:
|
||||||
|
ap.logf("ERROR: Unsupported CSI command: '%s', with full context: %v", cmd, ap.context)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ap *AnsiParser) print() error {
|
||||||
|
return ap.eventHandler.Print(ap.context.currentChar)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ap *AnsiParser) clear() error {
|
||||||
|
ap.context = &ansiContext{}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ap *AnsiParser) execute() error {
|
||||||
|
return ap.eventHandler.Execute(ap.context.currentChar)
|
||||||
|
}
|
71
vendor/github.com/Azure/go-ansiterm/states.go
generated
vendored
Normal file
71
vendor/github.com/Azure/go-ansiterm/states.go
generated
vendored
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
package ansiterm
|
||||||
|
|
||||||
|
type stateID int
|
||||||
|
|
||||||
|
type state interface {
|
||||||
|
Enter() error
|
||||||
|
Exit() error
|
||||||
|
Handle(byte) (state, error)
|
||||||
|
Name() string
|
||||||
|
Transition(state) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type baseState struct {
|
||||||
|
name string
|
||||||
|
parser *AnsiParser
|
||||||
|
}
|
||||||
|
|
||||||
|
func (base baseState) Enter() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (base baseState) Exit() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (base baseState) Handle(b byte) (s state, e error) {
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case b == CSI_ENTRY:
|
||||||
|
return base.parser.csiEntry, nil
|
||||||
|
case b == DCS_ENTRY:
|
||||||
|
return base.parser.dcsEntry, nil
|
||||||
|
case b == ANSI_ESCAPE_PRIMARY:
|
||||||
|
return base.parser.escape, nil
|
||||||
|
case b == OSC_STRING:
|
||||||
|
return base.parser.oscString, nil
|
||||||
|
case sliceContains(toGroundBytes, b):
|
||||||
|
return base.parser.ground, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (base baseState) Name() string {
|
||||||
|
return base.name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (base baseState) Transition(s state) error {
|
||||||
|
if s == base.parser.ground {
|
||||||
|
execBytes := []byte{0x18}
|
||||||
|
execBytes = append(execBytes, 0x1A)
|
||||||
|
execBytes = append(execBytes, getByteRange(0x80, 0x8F)...)
|
||||||
|
execBytes = append(execBytes, getByteRange(0x91, 0x97)...)
|
||||||
|
execBytes = append(execBytes, 0x99)
|
||||||
|
execBytes = append(execBytes, 0x9A)
|
||||||
|
|
||||||
|
if sliceContains(execBytes, base.parser.context.currentChar) {
|
||||||
|
return base.parser.execute()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type dcsEntryState struct {
|
||||||
|
baseState
|
||||||
|
}
|
||||||
|
|
||||||
|
type errorState struct {
|
||||||
|
baseState
|
||||||
|
}
|
21
vendor/github.com/Azure/go-ansiterm/utilities.go
generated
vendored
Normal file
21
vendor/github.com/Azure/go-ansiterm/utilities.go
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package ansiterm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
func sliceContains(bytes []byte, b byte) bool {
|
||||||
|
for _, v := range bytes {
|
||||||
|
if v == b {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertBytesToInteger(bytes []byte) int {
|
||||||
|
s := string(bytes)
|
||||||
|
i, _ := strconv.Atoi(s)
|
||||||
|
return i
|
||||||
|
}
|
197
vendor/github.com/Azure/go-ansiterm/winterm/ansi.go
generated
vendored
Normal file
197
vendor/github.com/Azure/go-ansiterm/winterm/ansi.go
generated
vendored
Normal file
@ -0,0 +1,197 @@
|
|||||||
|
//go:build windows
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package winterm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/Azure/go-ansiterm"
|
||||||
|
windows "golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Windows keyboard constants
|
||||||
|
// See https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx.
|
||||||
|
const (
|
||||||
|
VK_PRIOR = 0x21 // PAGE UP key
|
||||||
|
VK_NEXT = 0x22 // PAGE DOWN key
|
||||||
|
VK_END = 0x23 // END key
|
||||||
|
VK_HOME = 0x24 // HOME key
|
||||||
|
VK_LEFT = 0x25 // LEFT ARROW key
|
||||||
|
VK_UP = 0x26 // UP ARROW key
|
||||||
|
VK_RIGHT = 0x27 // RIGHT ARROW key
|
||||||
|
VK_DOWN = 0x28 // DOWN ARROW key
|
||||||
|
VK_SELECT = 0x29 // SELECT key
|
||||||
|
VK_PRINT = 0x2A // PRINT key
|
||||||
|
VK_EXECUTE = 0x2B // EXECUTE key
|
||||||
|
VK_SNAPSHOT = 0x2C // PRINT SCREEN key
|
||||||
|
VK_INSERT = 0x2D // INS key
|
||||||
|
VK_DELETE = 0x2E // DEL key
|
||||||
|
VK_HELP = 0x2F // HELP key
|
||||||
|
VK_F1 = 0x70 // F1 key
|
||||||
|
VK_F2 = 0x71 // F2 key
|
||||||
|
VK_F3 = 0x72 // F3 key
|
||||||
|
VK_F4 = 0x73 // F4 key
|
||||||
|
VK_F5 = 0x74 // F5 key
|
||||||
|
VK_F6 = 0x75 // F6 key
|
||||||
|
VK_F7 = 0x76 // F7 key
|
||||||
|
VK_F8 = 0x77 // F8 key
|
||||||
|
VK_F9 = 0x78 // F9 key
|
||||||
|
VK_F10 = 0x79 // F10 key
|
||||||
|
VK_F11 = 0x7A // F11 key
|
||||||
|
VK_F12 = 0x7B // F12 key
|
||||||
|
|
||||||
|
RIGHT_ALT_PRESSED = 0x0001
|
||||||
|
LEFT_ALT_PRESSED = 0x0002
|
||||||
|
RIGHT_CTRL_PRESSED = 0x0004
|
||||||
|
LEFT_CTRL_PRESSED = 0x0008
|
||||||
|
SHIFT_PRESSED = 0x0010
|
||||||
|
NUMLOCK_ON = 0x0020
|
||||||
|
SCROLLLOCK_ON = 0x0040
|
||||||
|
CAPSLOCK_ON = 0x0080
|
||||||
|
ENHANCED_KEY = 0x0100
|
||||||
|
)
|
||||||
|
|
||||||
|
type ansiCommand struct {
|
||||||
|
CommandBytes []byte
|
||||||
|
Command string
|
||||||
|
Parameters []string
|
||||||
|
IsSpecial bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func newAnsiCommand(command []byte) *ansiCommand {
|
||||||
|
|
||||||
|
if isCharacterSelectionCmdChar(command[1]) {
|
||||||
|
// Is Character Set Selection commands
|
||||||
|
return &ansiCommand{
|
||||||
|
CommandBytes: command,
|
||||||
|
Command: string(command),
|
||||||
|
IsSpecial: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// last char is command character
|
||||||
|
lastCharIndex := len(command) - 1
|
||||||
|
|
||||||
|
ac := &ansiCommand{
|
||||||
|
CommandBytes: command,
|
||||||
|
Command: string(command[lastCharIndex]),
|
||||||
|
IsSpecial: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
// more than a single escape
|
||||||
|
if lastCharIndex != 0 {
|
||||||
|
start := 1
|
||||||
|
// skip if double char escape sequence
|
||||||
|
if command[0] == ansiterm.ANSI_ESCAPE_PRIMARY && command[1] == ansiterm.ANSI_ESCAPE_SECONDARY {
|
||||||
|
start++
|
||||||
|
}
|
||||||
|
// convert this to GetNextParam method
|
||||||
|
ac.Parameters = strings.Split(string(command[start:lastCharIndex]), ansiterm.ANSI_PARAMETER_SEP)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ac
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ac *ansiCommand) paramAsSHORT(index int, defaultValue int16) int16 {
|
||||||
|
if index < 0 || index >= len(ac.Parameters) {
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
param, err := strconv.ParseInt(ac.Parameters[index], 10, 16)
|
||||||
|
if err != nil {
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
return int16(param)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ac *ansiCommand) String() string {
|
||||||
|
return fmt.Sprintf("0x%v \"%v\" (\"%v\")",
|
||||||
|
bytesToHex(ac.CommandBytes),
|
||||||
|
ac.Command,
|
||||||
|
strings.Join(ac.Parameters, "\",\""))
|
||||||
|
}
|
||||||
|
|
||||||
|
// isAnsiCommandChar returns true if the passed byte falls within the range of ANSI commands.
|
||||||
|
// See http://manpages.ubuntu.com/manpages/intrepid/man4/console_codes.4.html.
|
||||||
|
func isAnsiCommandChar(b byte) bool {
|
||||||
|
switch {
|
||||||
|
case ansiterm.ANSI_COMMAND_FIRST <= b && b <= ansiterm.ANSI_COMMAND_LAST && b != ansiterm.ANSI_ESCAPE_SECONDARY:
|
||||||
|
return true
|
||||||
|
case b == ansiterm.ANSI_CMD_G1 || b == ansiterm.ANSI_CMD_OSC || b == ansiterm.ANSI_CMD_DECPAM || b == ansiterm.ANSI_CMD_DECPNM:
|
||||||
|
// non-CSI escape sequence terminator
|
||||||
|
return true
|
||||||
|
case b == ansiterm.ANSI_CMD_STR_TERM || b == ansiterm.ANSI_BEL:
|
||||||
|
// String escape sequence terminator
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func isXtermOscSequence(command []byte, current byte) bool {
|
||||||
|
return (len(command) >= 2 && command[0] == ansiterm.ANSI_ESCAPE_PRIMARY && command[1] == ansiterm.ANSI_CMD_OSC && current != ansiterm.ANSI_BEL)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isCharacterSelectionCmdChar(b byte) bool {
|
||||||
|
return (b == ansiterm.ANSI_CMD_G0 || b == ansiterm.ANSI_CMD_G1 || b == ansiterm.ANSI_CMD_G2 || b == ansiterm.ANSI_CMD_G3)
|
||||||
|
}
|
||||||
|
|
||||||
|
// bytesToHex converts a slice of bytes to a human-readable string.
|
||||||
|
func bytesToHex(b []byte) string {
|
||||||
|
hex := make([]string, len(b))
|
||||||
|
for i, ch := range b {
|
||||||
|
hex[i] = fmt.Sprintf("%X", ch)
|
||||||
|
}
|
||||||
|
return strings.Join(hex, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensureInRange adjusts the passed value, if necessary, to ensure it is within
|
||||||
|
// the passed min / max range.
|
||||||
|
func ensureInRange(n int16, min int16, max int16) int16 {
|
||||||
|
if n < min {
|
||||||
|
return min
|
||||||
|
} else if n > max {
|
||||||
|
return max
|
||||||
|
} else {
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetStdFile(nFile int) (*os.File, uintptr) {
|
||||||
|
var file *os.File
|
||||||
|
|
||||||
|
// syscall uses negative numbers
|
||||||
|
// windows package uses very big uint32
|
||||||
|
// Keep these switches split so we don't have to convert ints too much.
|
||||||
|
switch uint32(nFile) {
|
||||||
|
case windows.STD_INPUT_HANDLE:
|
||||||
|
file = os.Stdin
|
||||||
|
case windows.STD_OUTPUT_HANDLE:
|
||||||
|
file = os.Stdout
|
||||||
|
case windows.STD_ERROR_HANDLE:
|
||||||
|
file = os.Stderr
|
||||||
|
default:
|
||||||
|
switch nFile {
|
||||||
|
case syscall.STD_INPUT_HANDLE:
|
||||||
|
file = os.Stdin
|
||||||
|
case syscall.STD_OUTPUT_HANDLE:
|
||||||
|
file = os.Stdout
|
||||||
|
case syscall.STD_ERROR_HANDLE:
|
||||||
|
file = os.Stderr
|
||||||
|
default:
|
||||||
|
panic(fmt.Errorf("Invalid standard handle identifier: %v", nFile))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fd, err := syscall.GetStdHandle(nFile)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf("Invalid standard handle identifier: %v -- %v", nFile, err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return file, uintptr(fd)
|
||||||
|
}
|
328
vendor/github.com/Azure/go-ansiterm/winterm/api.go
generated
vendored
Normal file
328
vendor/github.com/Azure/go-ansiterm/winterm/api.go
generated
vendored
Normal file
@ -0,0 +1,328 @@
|
|||||||
|
//go:build windows
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package winterm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
//===========================================================================================================
|
||||||
|
// IMPORTANT NOTE:
|
||||||
|
//
|
||||||
|
// The methods below make extensive use of the "unsafe" package to obtain the required pointers.
|
||||||
|
// Beginning in Go 1.3, the garbage collector may release local variables (e.g., incoming arguments, stack
|
||||||
|
// variables) the pointers reference *before* the API completes.
|
||||||
|
//
|
||||||
|
// As a result, in those cases, the code must hint that the variables remain in active by invoking the
|
||||||
|
// dummy method "use" (see below). Newer versions of Go are planned to change the mechanism to no longer
|
||||||
|
// require unsafe pointers.
|
||||||
|
//
|
||||||
|
// If you add or modify methods, ENSURE protection of local variables through the "use" builtin to inform
|
||||||
|
// the garbage collector the variables remain in use if:
|
||||||
|
//
|
||||||
|
// -- The value is not a pointer (e.g., int32, struct)
|
||||||
|
// -- The value is not referenced by the method after passing the pointer to Windows
|
||||||
|
//
|
||||||
|
// See http://golang.org/doc/go1.3.
|
||||||
|
//===========================================================================================================
|
||||||
|
|
||||||
|
var (
|
||||||
|
kernel32DLL = syscall.NewLazyDLL("kernel32.dll")
|
||||||
|
|
||||||
|
getConsoleCursorInfoProc = kernel32DLL.NewProc("GetConsoleCursorInfo")
|
||||||
|
setConsoleCursorInfoProc = kernel32DLL.NewProc("SetConsoleCursorInfo")
|
||||||
|
setConsoleCursorPositionProc = kernel32DLL.NewProc("SetConsoleCursorPosition")
|
||||||
|
setConsoleModeProc = kernel32DLL.NewProc("SetConsoleMode")
|
||||||
|
getConsoleScreenBufferInfoProc = kernel32DLL.NewProc("GetConsoleScreenBufferInfo")
|
||||||
|
setConsoleScreenBufferSizeProc = kernel32DLL.NewProc("SetConsoleScreenBufferSize")
|
||||||
|
scrollConsoleScreenBufferProc = kernel32DLL.NewProc("ScrollConsoleScreenBufferA")
|
||||||
|
setConsoleTextAttributeProc = kernel32DLL.NewProc("SetConsoleTextAttribute")
|
||||||
|
setConsoleWindowInfoProc = kernel32DLL.NewProc("SetConsoleWindowInfo")
|
||||||
|
writeConsoleOutputProc = kernel32DLL.NewProc("WriteConsoleOutputW")
|
||||||
|
readConsoleInputProc = kernel32DLL.NewProc("ReadConsoleInputW")
|
||||||
|
waitForSingleObjectProc = kernel32DLL.NewProc("WaitForSingleObject")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Windows Console constants
|
||||||
|
const (
|
||||||
|
// Console modes
|
||||||
|
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx.
|
||||||
|
ENABLE_PROCESSED_INPUT = 0x0001
|
||||||
|
ENABLE_LINE_INPUT = 0x0002
|
||||||
|
ENABLE_ECHO_INPUT = 0x0004
|
||||||
|
ENABLE_WINDOW_INPUT = 0x0008
|
||||||
|
ENABLE_MOUSE_INPUT = 0x0010
|
||||||
|
ENABLE_INSERT_MODE = 0x0020
|
||||||
|
ENABLE_QUICK_EDIT_MODE = 0x0040
|
||||||
|
ENABLE_EXTENDED_FLAGS = 0x0080
|
||||||
|
ENABLE_AUTO_POSITION = 0x0100
|
||||||
|
ENABLE_VIRTUAL_TERMINAL_INPUT = 0x0200
|
||||||
|
|
||||||
|
ENABLE_PROCESSED_OUTPUT = 0x0001
|
||||||
|
ENABLE_WRAP_AT_EOL_OUTPUT = 0x0002
|
||||||
|
ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004
|
||||||
|
DISABLE_NEWLINE_AUTO_RETURN = 0x0008
|
||||||
|
ENABLE_LVB_GRID_WORLDWIDE = 0x0010
|
||||||
|
|
||||||
|
// Character attributes
|
||||||
|
// Note:
|
||||||
|
// -- The attributes are combined to produce various colors (e.g., Blue + Green will create Cyan).
|
||||||
|
// Clearing all foreground or background colors results in black; setting all creates white.
|
||||||
|
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms682088(v=vs.85).aspx#_win32_character_attributes.
|
||||||
|
FOREGROUND_BLUE uint16 = 0x0001
|
||||||
|
FOREGROUND_GREEN uint16 = 0x0002
|
||||||
|
FOREGROUND_RED uint16 = 0x0004
|
||||||
|
FOREGROUND_INTENSITY uint16 = 0x0008
|
||||||
|
FOREGROUND_MASK uint16 = 0x000F
|
||||||
|
|
||||||
|
BACKGROUND_BLUE uint16 = 0x0010
|
||||||
|
BACKGROUND_GREEN uint16 = 0x0020
|
||||||
|
BACKGROUND_RED uint16 = 0x0040
|
||||||
|
BACKGROUND_INTENSITY uint16 = 0x0080
|
||||||
|
BACKGROUND_MASK uint16 = 0x00F0
|
||||||
|
|
||||||
|
COMMON_LVB_MASK uint16 = 0xFF00
|
||||||
|
COMMON_LVB_REVERSE_VIDEO uint16 = 0x4000
|
||||||
|
COMMON_LVB_UNDERSCORE uint16 = 0x8000
|
||||||
|
|
||||||
|
// Input event types
|
||||||
|
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms683499(v=vs.85).aspx.
|
||||||
|
KEY_EVENT = 0x0001
|
||||||
|
MOUSE_EVENT = 0x0002
|
||||||
|
WINDOW_BUFFER_SIZE_EVENT = 0x0004
|
||||||
|
MENU_EVENT = 0x0008
|
||||||
|
FOCUS_EVENT = 0x0010
|
||||||
|
|
||||||
|
// WaitForSingleObject return codes
|
||||||
|
WAIT_ABANDONED = 0x00000080
|
||||||
|
WAIT_FAILED = 0xFFFFFFFF
|
||||||
|
WAIT_SIGNALED = 0x0000000
|
||||||
|
WAIT_TIMEOUT = 0x00000102
|
||||||
|
|
||||||
|
// WaitForSingleObject wait duration
|
||||||
|
WAIT_INFINITE = 0xFFFFFFFF
|
||||||
|
WAIT_ONE_SECOND = 1000
|
||||||
|
WAIT_HALF_SECOND = 500
|
||||||
|
WAIT_QUARTER_SECOND = 250
|
||||||
|
)
|
||||||
|
|
||||||
|
// Windows API Console types
|
||||||
|
// -- See https://msdn.microsoft.com/en-us/library/windows/desktop/ms682101(v=vs.85).aspx for Console specific types (e.g., COORD)
|
||||||
|
// -- See https://msdn.microsoft.com/en-us/library/aa296569(v=vs.60).aspx for comments on alignment
|
||||||
|
type (
|
||||||
|
CHAR_INFO struct {
|
||||||
|
UnicodeChar uint16
|
||||||
|
Attributes uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
CONSOLE_CURSOR_INFO struct {
|
||||||
|
Size uint32
|
||||||
|
Visible int32
|
||||||
|
}
|
||||||
|
|
||||||
|
CONSOLE_SCREEN_BUFFER_INFO struct {
|
||||||
|
Size COORD
|
||||||
|
CursorPosition COORD
|
||||||
|
Attributes uint16
|
||||||
|
Window SMALL_RECT
|
||||||
|
MaximumWindowSize COORD
|
||||||
|
}
|
||||||
|
|
||||||
|
COORD struct {
|
||||||
|
X int16
|
||||||
|
Y int16
|
||||||
|
}
|
||||||
|
|
||||||
|
SMALL_RECT struct {
|
||||||
|
Left int16
|
||||||
|
Top int16
|
||||||
|
Right int16
|
||||||
|
Bottom int16
|
||||||
|
}
|
||||||
|
|
||||||
|
// INPUT_RECORD is a C/C++ union of which KEY_EVENT_RECORD is one case, it is also the largest
|
||||||
|
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms683499(v=vs.85).aspx.
|
||||||
|
INPUT_RECORD struct {
|
||||||
|
EventType uint16
|
||||||
|
KeyEvent KEY_EVENT_RECORD
|
||||||
|
}
|
||||||
|
|
||||||
|
KEY_EVENT_RECORD struct {
|
||||||
|
KeyDown int32
|
||||||
|
RepeatCount uint16
|
||||||
|
VirtualKeyCode uint16
|
||||||
|
VirtualScanCode uint16
|
||||||
|
UnicodeChar uint16
|
||||||
|
ControlKeyState uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
WINDOW_BUFFER_SIZE struct {
|
||||||
|
Size COORD
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// boolToBOOL converts a Go bool into a Windows int32.
|
||||||
|
func boolToBOOL(f bool) int32 {
|
||||||
|
if f {
|
||||||
|
return int32(1)
|
||||||
|
} else {
|
||||||
|
return int32(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetConsoleCursorInfo retrieves information about the size and visiblity of the console cursor.
|
||||||
|
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms683163(v=vs.85).aspx.
|
||||||
|
func GetConsoleCursorInfo(handle uintptr, cursorInfo *CONSOLE_CURSOR_INFO) error {
|
||||||
|
r1, r2, err := getConsoleCursorInfoProc.Call(handle, uintptr(unsafe.Pointer(cursorInfo)), 0)
|
||||||
|
return checkError(r1, r2, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetConsoleCursorInfo sets the size and visiblity of the console cursor.
|
||||||
|
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686019(v=vs.85).aspx.
|
||||||
|
func SetConsoleCursorInfo(handle uintptr, cursorInfo *CONSOLE_CURSOR_INFO) error {
|
||||||
|
r1, r2, err := setConsoleCursorInfoProc.Call(handle, uintptr(unsafe.Pointer(cursorInfo)), 0)
|
||||||
|
return checkError(r1, r2, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetConsoleCursorPosition location of the console cursor.
|
||||||
|
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686025(v=vs.85).aspx.
|
||||||
|
func SetConsoleCursorPosition(handle uintptr, coord COORD) error {
|
||||||
|
r1, r2, err := setConsoleCursorPositionProc.Call(handle, coordToPointer(coord))
|
||||||
|
use(coord)
|
||||||
|
return checkError(r1, r2, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetConsoleMode gets the console mode for given file descriptor
|
||||||
|
// See http://msdn.microsoft.com/en-us/library/windows/desktop/ms683167(v=vs.85).aspx.
|
||||||
|
func GetConsoleMode(handle uintptr) (mode uint32, err error) {
|
||||||
|
err = syscall.GetConsoleMode(syscall.Handle(handle), &mode)
|
||||||
|
return mode, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetConsoleMode sets the console mode for given file descriptor
|
||||||
|
// See http://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx.
|
||||||
|
func SetConsoleMode(handle uintptr, mode uint32) error {
|
||||||
|
r1, r2, err := setConsoleModeProc.Call(handle, uintptr(mode), 0)
|
||||||
|
use(mode)
|
||||||
|
return checkError(r1, r2, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetConsoleScreenBufferInfo retrieves information about the specified console screen buffer.
|
||||||
|
// See http://msdn.microsoft.com/en-us/library/windows/desktop/ms683171(v=vs.85).aspx.
|
||||||
|
func GetConsoleScreenBufferInfo(handle uintptr) (*CONSOLE_SCREEN_BUFFER_INFO, error) {
|
||||||
|
info := CONSOLE_SCREEN_BUFFER_INFO{}
|
||||||
|
err := checkError(getConsoleScreenBufferInfoProc.Call(handle, uintptr(unsafe.Pointer(&info)), 0))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &info, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ScrollConsoleScreenBuffer(handle uintptr, scrollRect SMALL_RECT, clipRect SMALL_RECT, destOrigin COORD, char CHAR_INFO) error {
|
||||||
|
r1, r2, err := scrollConsoleScreenBufferProc.Call(handle, uintptr(unsafe.Pointer(&scrollRect)), uintptr(unsafe.Pointer(&clipRect)), coordToPointer(destOrigin), uintptr(unsafe.Pointer(&char)))
|
||||||
|
use(scrollRect)
|
||||||
|
use(clipRect)
|
||||||
|
use(destOrigin)
|
||||||
|
use(char)
|
||||||
|
return checkError(r1, r2, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetConsoleScreenBufferSize sets the size of the console screen buffer.
|
||||||
|
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686044(v=vs.85).aspx.
|
||||||
|
func SetConsoleScreenBufferSize(handle uintptr, coord COORD) error {
|
||||||
|
r1, r2, err := setConsoleScreenBufferSizeProc.Call(handle, coordToPointer(coord))
|
||||||
|
use(coord)
|
||||||
|
return checkError(r1, r2, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetConsoleTextAttribute sets the attributes of characters written to the
|
||||||
|
// console screen buffer by the WriteFile or WriteConsole function.
|
||||||
|
// See http://msdn.microsoft.com/en-us/library/windows/desktop/ms686047(v=vs.85).aspx.
|
||||||
|
func SetConsoleTextAttribute(handle uintptr, attribute uint16) error {
|
||||||
|
r1, r2, err := setConsoleTextAttributeProc.Call(handle, uintptr(attribute), 0)
|
||||||
|
use(attribute)
|
||||||
|
return checkError(r1, r2, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetConsoleWindowInfo sets the size and position of the console screen buffer's window.
|
||||||
|
// Note that the size and location must be within and no larger than the backing console screen buffer.
|
||||||
|
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686125(v=vs.85).aspx.
|
||||||
|
func SetConsoleWindowInfo(handle uintptr, isAbsolute bool, rect SMALL_RECT) error {
|
||||||
|
r1, r2, err := setConsoleWindowInfoProc.Call(handle, uintptr(boolToBOOL(isAbsolute)), uintptr(unsafe.Pointer(&rect)))
|
||||||
|
use(isAbsolute)
|
||||||
|
use(rect)
|
||||||
|
return checkError(r1, r2, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteConsoleOutput writes the CHAR_INFOs from the provided buffer to the active console buffer.
|
||||||
|
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms687404(v=vs.85).aspx.
|
||||||
|
func WriteConsoleOutput(handle uintptr, buffer []CHAR_INFO, bufferSize COORD, bufferCoord COORD, writeRegion *SMALL_RECT) error {
|
||||||
|
r1, r2, err := writeConsoleOutputProc.Call(handle, uintptr(unsafe.Pointer(&buffer[0])), coordToPointer(bufferSize), coordToPointer(bufferCoord), uintptr(unsafe.Pointer(writeRegion)))
|
||||||
|
use(buffer)
|
||||||
|
use(bufferSize)
|
||||||
|
use(bufferCoord)
|
||||||
|
return checkError(r1, r2, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadConsoleInput reads (and removes) data from the console input buffer.
|
||||||
|
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms684961(v=vs.85).aspx.
|
||||||
|
func ReadConsoleInput(handle uintptr, buffer []INPUT_RECORD, count *uint32) error {
|
||||||
|
r1, r2, err := readConsoleInputProc.Call(handle, uintptr(unsafe.Pointer(&buffer[0])), uintptr(len(buffer)), uintptr(unsafe.Pointer(count)))
|
||||||
|
use(buffer)
|
||||||
|
return checkError(r1, r2, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WaitForSingleObject waits for the passed handle to be signaled.
|
||||||
|
// It returns true if the handle was signaled; false otherwise.
|
||||||
|
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms687032(v=vs.85).aspx.
|
||||||
|
func WaitForSingleObject(handle uintptr, msWait uint32) (bool, error) {
|
||||||
|
r1, _, err := waitForSingleObjectProc.Call(handle, uintptr(uint32(msWait)))
|
||||||
|
switch r1 {
|
||||||
|
case WAIT_ABANDONED, WAIT_TIMEOUT:
|
||||||
|
return false, nil
|
||||||
|
case WAIT_SIGNALED:
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
use(msWait)
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// String helpers
|
||||||
|
func (info CONSOLE_SCREEN_BUFFER_INFO) String() string {
|
||||||
|
return fmt.Sprintf("Size(%v) Cursor(%v) Window(%v) Max(%v)", info.Size, info.CursorPosition, info.Window, info.MaximumWindowSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (coord COORD) String() string {
|
||||||
|
return fmt.Sprintf("%v,%v", coord.X, coord.Y)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rect SMALL_RECT) String() string {
|
||||||
|
return fmt.Sprintf("(%v,%v),(%v,%v)", rect.Left, rect.Top, rect.Right, rect.Bottom)
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkError evaluates the results of a Windows API call and returns the error if it failed.
|
||||||
|
func checkError(r1, r2 uintptr, err error) error {
|
||||||
|
// Windows APIs return non-zero to indicate success
|
||||||
|
if r1 != 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the error if provided, otherwise default to EINVAL
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return syscall.EINVAL
|
||||||
|
}
|
||||||
|
|
||||||
|
// coordToPointer converts a COORD into a uintptr (by fooling the type system).
|
||||||
|
func coordToPointer(c COORD) uintptr {
|
||||||
|
// Note: This code assumes the two SHORTs are correctly laid out; the "cast" to uint32 is just to get a pointer to pass.
|
||||||
|
return uintptr(*((*uint32)(unsafe.Pointer(&c))))
|
||||||
|
}
|
||||||
|
|
||||||
|
// use is a no-op, but the compiler cannot see that it is.
|
||||||
|
// Calling use(p) ensures that p is kept live until that point.
|
||||||
|
func use(p interface{}) {}
|
101
vendor/github.com/Azure/go-ansiterm/winterm/attr_translation.go
generated
vendored
Normal file
101
vendor/github.com/Azure/go-ansiterm/winterm/attr_translation.go
generated
vendored
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
//go:build windows
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package winterm
|
||||||
|
|
||||||
|
import "github.com/Azure/go-ansiterm"
|
||||||
|
|
||||||
|
const (
|
||||||
|
FOREGROUND_COLOR_MASK = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE
|
||||||
|
BACKGROUND_COLOR_MASK = BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE
|
||||||
|
)
|
||||||
|
|
||||||
|
// collectAnsiIntoWindowsAttributes modifies the passed Windows text mode flags to reflect the
|
||||||
|
// request represented by the passed ANSI mode.
|
||||||
|
func collectAnsiIntoWindowsAttributes(windowsMode uint16, inverted bool, baseMode uint16, ansiMode int16) (uint16, bool) {
|
||||||
|
switch ansiMode {
|
||||||
|
|
||||||
|
// Mode styles
|
||||||
|
case ansiterm.ANSI_SGR_BOLD:
|
||||||
|
windowsMode = windowsMode | FOREGROUND_INTENSITY
|
||||||
|
|
||||||
|
case ansiterm.ANSI_SGR_DIM, ansiterm.ANSI_SGR_BOLD_DIM_OFF:
|
||||||
|
windowsMode &^= FOREGROUND_INTENSITY
|
||||||
|
|
||||||
|
case ansiterm.ANSI_SGR_UNDERLINE:
|
||||||
|
windowsMode = windowsMode | COMMON_LVB_UNDERSCORE
|
||||||
|
|
||||||
|
case ansiterm.ANSI_SGR_REVERSE:
|
||||||
|
inverted = true
|
||||||
|
|
||||||
|
case ansiterm.ANSI_SGR_REVERSE_OFF:
|
||||||
|
inverted = false
|
||||||
|
|
||||||
|
case ansiterm.ANSI_SGR_UNDERLINE_OFF:
|
||||||
|
windowsMode &^= COMMON_LVB_UNDERSCORE
|
||||||
|
|
||||||
|
// Foreground colors
|
||||||
|
case ansiterm.ANSI_SGR_FOREGROUND_DEFAULT:
|
||||||
|
windowsMode = (windowsMode &^ FOREGROUND_MASK) | (baseMode & FOREGROUND_MASK)
|
||||||
|
|
||||||
|
case ansiterm.ANSI_SGR_FOREGROUND_BLACK:
|
||||||
|
windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK)
|
||||||
|
|
||||||
|
case ansiterm.ANSI_SGR_FOREGROUND_RED:
|
||||||
|
windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) | FOREGROUND_RED
|
||||||
|
|
||||||
|
case ansiterm.ANSI_SGR_FOREGROUND_GREEN:
|
||||||
|
windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) | FOREGROUND_GREEN
|
||||||
|
|
||||||
|
case ansiterm.ANSI_SGR_FOREGROUND_YELLOW:
|
||||||
|
windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) | FOREGROUND_RED | FOREGROUND_GREEN
|
||||||
|
|
||||||
|
case ansiterm.ANSI_SGR_FOREGROUND_BLUE:
|
||||||
|
windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) | FOREGROUND_BLUE
|
||||||
|
|
||||||
|
case ansiterm.ANSI_SGR_FOREGROUND_MAGENTA:
|
||||||
|
windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) | FOREGROUND_RED | FOREGROUND_BLUE
|
||||||
|
|
||||||
|
case ansiterm.ANSI_SGR_FOREGROUND_CYAN:
|
||||||
|
windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) | FOREGROUND_GREEN | FOREGROUND_BLUE
|
||||||
|
|
||||||
|
case ansiterm.ANSI_SGR_FOREGROUND_WHITE:
|
||||||
|
windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) | FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE
|
||||||
|
|
||||||
|
// Background colors
|
||||||
|
case ansiterm.ANSI_SGR_BACKGROUND_DEFAULT:
|
||||||
|
// Black with no intensity
|
||||||
|
windowsMode = (windowsMode &^ BACKGROUND_MASK) | (baseMode & BACKGROUND_MASK)
|
||||||
|
|
||||||
|
case ansiterm.ANSI_SGR_BACKGROUND_BLACK:
|
||||||
|
windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK)
|
||||||
|
|
||||||
|
case ansiterm.ANSI_SGR_BACKGROUND_RED:
|
||||||
|
windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) | BACKGROUND_RED
|
||||||
|
|
||||||
|
case ansiterm.ANSI_SGR_BACKGROUND_GREEN:
|
||||||
|
windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) | BACKGROUND_GREEN
|
||||||
|
|
||||||
|
case ansiterm.ANSI_SGR_BACKGROUND_YELLOW:
|
||||||
|
windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) | BACKGROUND_RED | BACKGROUND_GREEN
|
||||||
|
|
||||||
|
case ansiterm.ANSI_SGR_BACKGROUND_BLUE:
|
||||||
|
windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) | BACKGROUND_BLUE
|
||||||
|
|
||||||
|
case ansiterm.ANSI_SGR_BACKGROUND_MAGENTA:
|
||||||
|
windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) | BACKGROUND_RED | BACKGROUND_BLUE
|
||||||
|
|
||||||
|
case ansiterm.ANSI_SGR_BACKGROUND_CYAN:
|
||||||
|
windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) | BACKGROUND_GREEN | BACKGROUND_BLUE
|
||||||
|
|
||||||
|
case ansiterm.ANSI_SGR_BACKGROUND_WHITE:
|
||||||
|
windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) | BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE
|
||||||
|
}
|
||||||
|
|
||||||
|
return windowsMode, inverted
|
||||||
|
}
|
||||||
|
|
||||||
|
// invertAttributes inverts the foreground and background colors of a Windows attributes value
|
||||||
|
func invertAttributes(windowsMode uint16) uint16 {
|
||||||
|
return (COMMON_LVB_MASK & windowsMode) | ((FOREGROUND_MASK & windowsMode) << 4) | ((BACKGROUND_MASK & windowsMode) >> 4)
|
||||||
|
}
|
102
vendor/github.com/Azure/go-ansiterm/winterm/cursor_helpers.go
generated
vendored
Normal file
102
vendor/github.com/Azure/go-ansiterm/winterm/cursor_helpers.go
generated
vendored
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
//go:build windows
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package winterm
|
||||||
|
|
||||||
|
const (
|
||||||
|
horizontal = iota
|
||||||
|
vertical
|
||||||
|
)
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) getCursorWindow(info *CONSOLE_SCREEN_BUFFER_INFO) SMALL_RECT {
|
||||||
|
if h.originMode {
|
||||||
|
sr := h.effectiveSr(info.Window)
|
||||||
|
return SMALL_RECT{
|
||||||
|
Top: sr.top,
|
||||||
|
Bottom: sr.bottom,
|
||||||
|
Left: 0,
|
||||||
|
Right: info.Size.X - 1,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return SMALL_RECT{
|
||||||
|
Top: info.Window.Top,
|
||||||
|
Bottom: info.Window.Bottom,
|
||||||
|
Left: 0,
|
||||||
|
Right: info.Size.X - 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// setCursorPosition sets the cursor to the specified position, bounded to the screen size
|
||||||
|
func (h *windowsAnsiEventHandler) setCursorPosition(position COORD, window SMALL_RECT) error {
|
||||||
|
position.X = ensureInRange(position.X, window.Left, window.Right)
|
||||||
|
position.Y = ensureInRange(position.Y, window.Top, window.Bottom)
|
||||||
|
err := SetConsoleCursorPosition(h.fd, position)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
h.logf("Cursor position set: (%d, %d)", position.X, position.Y)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) moveCursorVertical(param int) error {
|
||||||
|
return h.moveCursor(vertical, param)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) moveCursorHorizontal(param int) error {
|
||||||
|
return h.moveCursor(horizontal, param)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) moveCursor(moveMode int, param int) error {
|
||||||
|
info, err := GetConsoleScreenBufferInfo(h.fd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
position := info.CursorPosition
|
||||||
|
switch moveMode {
|
||||||
|
case horizontal:
|
||||||
|
position.X += int16(param)
|
||||||
|
case vertical:
|
||||||
|
position.Y += int16(param)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = h.setCursorPosition(position, h.getCursorWindow(info)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) moveCursorLine(param int) error {
|
||||||
|
info, err := GetConsoleScreenBufferInfo(h.fd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
position := info.CursorPosition
|
||||||
|
position.X = 0
|
||||||
|
position.Y += int16(param)
|
||||||
|
|
||||||
|
if err = h.setCursorPosition(position, h.getCursorWindow(info)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) moveCursorColumn(param int) error {
|
||||||
|
info, err := GetConsoleScreenBufferInfo(h.fd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
position := info.CursorPosition
|
||||||
|
position.X = int16(param) - 1
|
||||||
|
|
||||||
|
if err = h.setCursorPosition(position, h.getCursorWindow(info)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
85
vendor/github.com/Azure/go-ansiterm/winterm/erase_helpers.go
generated
vendored
Normal file
85
vendor/github.com/Azure/go-ansiterm/winterm/erase_helpers.go
generated
vendored
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
//go:build windows
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package winterm
|
||||||
|
|
||||||
|
import "github.com/Azure/go-ansiterm"
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) clearRange(attributes uint16, fromCoord COORD, toCoord COORD) error {
|
||||||
|
// Ignore an invalid (negative area) request
|
||||||
|
if toCoord.Y < fromCoord.Y {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
|
||||||
|
var coordStart = COORD{}
|
||||||
|
var coordEnd = COORD{}
|
||||||
|
|
||||||
|
xCurrent, yCurrent := fromCoord.X, fromCoord.Y
|
||||||
|
xEnd, yEnd := toCoord.X, toCoord.Y
|
||||||
|
|
||||||
|
// Clear any partial initial line
|
||||||
|
if xCurrent > 0 {
|
||||||
|
coordStart.X, coordStart.Y = xCurrent, yCurrent
|
||||||
|
coordEnd.X, coordEnd.Y = xEnd, yCurrent
|
||||||
|
|
||||||
|
err = h.clearRect(attributes, coordStart, coordEnd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
xCurrent = 0
|
||||||
|
yCurrent += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear intervening rectangular section
|
||||||
|
if yCurrent < yEnd {
|
||||||
|
coordStart.X, coordStart.Y = xCurrent, yCurrent
|
||||||
|
coordEnd.X, coordEnd.Y = xEnd, yEnd-1
|
||||||
|
|
||||||
|
err = h.clearRect(attributes, coordStart, coordEnd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
xCurrent = 0
|
||||||
|
yCurrent = yEnd
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear remaining partial ending line
|
||||||
|
coordStart.X, coordStart.Y = xCurrent, yCurrent
|
||||||
|
coordEnd.X, coordEnd.Y = xEnd, yEnd
|
||||||
|
|
||||||
|
err = h.clearRect(attributes, coordStart, coordEnd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) clearRect(attributes uint16, fromCoord COORD, toCoord COORD) error {
|
||||||
|
region := SMALL_RECT{Top: fromCoord.Y, Left: fromCoord.X, Bottom: toCoord.Y, Right: toCoord.X}
|
||||||
|
width := toCoord.X - fromCoord.X + 1
|
||||||
|
height := toCoord.Y - fromCoord.Y + 1
|
||||||
|
size := uint32(width) * uint32(height)
|
||||||
|
|
||||||
|
if size <= 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer := make([]CHAR_INFO, size)
|
||||||
|
|
||||||
|
char := CHAR_INFO{ansiterm.FILL_CHARACTER, attributes}
|
||||||
|
for i := 0; i < int(size); i++ {
|
||||||
|
buffer[i] = char
|
||||||
|
}
|
||||||
|
|
||||||
|
err := WriteConsoleOutput(h.fd, buffer, COORD{X: width, Y: height}, COORD{X: 0, Y: 0}, ®ion)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
119
vendor/github.com/Azure/go-ansiterm/winterm/scroll_helper.go
generated
vendored
Normal file
119
vendor/github.com/Azure/go-ansiterm/winterm/scroll_helper.go
generated
vendored
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
//go:build windows
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package winterm
|
||||||
|
|
||||||
|
// effectiveSr gets the current effective scroll region in buffer coordinates
|
||||||
|
func (h *windowsAnsiEventHandler) effectiveSr(window SMALL_RECT) scrollRegion {
|
||||||
|
top := addInRange(window.Top, h.sr.top, window.Top, window.Bottom)
|
||||||
|
bottom := addInRange(window.Top, h.sr.bottom, window.Top, window.Bottom)
|
||||||
|
if top >= bottom {
|
||||||
|
top = window.Top
|
||||||
|
bottom = window.Bottom
|
||||||
|
}
|
||||||
|
return scrollRegion{top: top, bottom: bottom}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) scrollUp(param int) error {
|
||||||
|
info, err := GetConsoleScreenBufferInfo(h.fd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
sr := h.effectiveSr(info.Window)
|
||||||
|
return h.scroll(param, sr, info)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) scrollDown(param int) error {
|
||||||
|
return h.scrollUp(-param)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) deleteLines(param int) error {
|
||||||
|
info, err := GetConsoleScreenBufferInfo(h.fd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
start := info.CursorPosition.Y
|
||||||
|
sr := h.effectiveSr(info.Window)
|
||||||
|
// Lines cannot be inserted or deleted outside the scrolling region.
|
||||||
|
if start >= sr.top && start <= sr.bottom {
|
||||||
|
sr.top = start
|
||||||
|
return h.scroll(param, sr, info)
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) insertLines(param int) error {
|
||||||
|
return h.deleteLines(-param)
|
||||||
|
}
|
||||||
|
|
||||||
|
// scroll scrolls the provided scroll region by param lines. The scroll region is in buffer coordinates.
|
||||||
|
func (h *windowsAnsiEventHandler) scroll(param int, sr scrollRegion, info *CONSOLE_SCREEN_BUFFER_INFO) error {
|
||||||
|
h.logf("scroll: scrollTop: %d, scrollBottom: %d", sr.top, sr.bottom)
|
||||||
|
h.logf("scroll: windowTop: %d, windowBottom: %d", info.Window.Top, info.Window.Bottom)
|
||||||
|
|
||||||
|
// Copy from and clip to the scroll region (full buffer width)
|
||||||
|
scrollRect := SMALL_RECT{
|
||||||
|
Top: sr.top,
|
||||||
|
Bottom: sr.bottom,
|
||||||
|
Left: 0,
|
||||||
|
Right: info.Size.X - 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Origin to which area should be copied
|
||||||
|
destOrigin := COORD{
|
||||||
|
X: 0,
|
||||||
|
Y: sr.top - int16(param),
|
||||||
|
}
|
||||||
|
|
||||||
|
char := CHAR_INFO{
|
||||||
|
UnicodeChar: ' ',
|
||||||
|
Attributes: h.attributes,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ScrollConsoleScreenBuffer(h.fd, scrollRect, scrollRect, destOrigin, char); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) deleteCharacters(param int) error {
|
||||||
|
info, err := GetConsoleScreenBufferInfo(h.fd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return h.scrollLine(param, info.CursorPosition, info)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) insertCharacters(param int) error {
|
||||||
|
return h.deleteCharacters(-param)
|
||||||
|
}
|
||||||
|
|
||||||
|
// scrollLine scrolls a line horizontally starting at the provided position by a number of columns.
|
||||||
|
func (h *windowsAnsiEventHandler) scrollLine(columns int, position COORD, info *CONSOLE_SCREEN_BUFFER_INFO) error {
|
||||||
|
// Copy from and clip to the scroll region (full buffer width)
|
||||||
|
scrollRect := SMALL_RECT{
|
||||||
|
Top: position.Y,
|
||||||
|
Bottom: position.Y,
|
||||||
|
Left: position.X,
|
||||||
|
Right: info.Size.X - 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Origin to which area should be copied
|
||||||
|
destOrigin := COORD{
|
||||||
|
X: position.X - int16(columns),
|
||||||
|
Y: position.Y,
|
||||||
|
}
|
||||||
|
|
||||||
|
char := CHAR_INFO{
|
||||||
|
UnicodeChar: ' ',
|
||||||
|
Attributes: h.attributes,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ScrollConsoleScreenBuffer(h.fd, scrollRect, scrollRect, destOrigin, char); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user