mirror of
https://github.com/edgelesssys/constellation.git
synced 2025-02-04 17:15:26 -05:00
Hide cursor and fix dots (#217)
* Hide cursor and fix dots spinner * Allow restarting of spinner * Don't spin on non TTY output Signed-off-by: Daniel Weiße <dw@edgeless.systems>
This commit is contained in:
parent
56981a709e
commit
c82d5ccba9
@ -41,9 +41,9 @@ func NewCreateCmd() *cobra.Command {
|
|||||||
|
|
||||||
func runCreate(cmd *cobra.Command, args []string) error {
|
func runCreate(cmd *cobra.Command, args []string) error {
|
||||||
fileHandler := file.NewHandler(afero.NewOsFs())
|
fileHandler := file.NewHandler(afero.NewOsFs())
|
||||||
spinner, writer := newSpinner(cmd, cmd.OutOrStdout())
|
spinner := newSpinner(cmd.OutOrStdout())
|
||||||
defer spinner.Stop()
|
defer spinner.Stop()
|
||||||
creator := cloudcmd.NewCreator(writer)
|
creator := cloudcmd.NewCreator(spinner)
|
||||||
|
|
||||||
return create(cmd, creator, fileHandler, spinner)
|
return create(cmd, creator, fileHandler, spinner)
|
||||||
}
|
}
|
||||||
|
@ -60,7 +60,7 @@ func runInitialize(cmd *cobra.Command, args []string) error {
|
|||||||
return dialer.New(nil, validator.V(cmd), &net.Dialer{})
|
return dialer.New(nil, validator.V(cmd), &net.Dialer{})
|
||||||
}
|
}
|
||||||
helmLoader := &helm.ChartLoader{}
|
helmLoader := &helm.ChartLoader{}
|
||||||
spinner, _ := newSpinner(cmd, cmd.OutOrStdout())
|
spinner := newSpinner(cmd.OutOrStdout())
|
||||||
defer spinner.Stop()
|
defer spinner.Stop()
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(cmd.Context(), time.Hour)
|
ctx, cancel := context.WithTimeout(cmd.Context(), time.Hour)
|
||||||
|
@ -51,13 +51,14 @@ func newMiniUpCmd() *cobra.Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func runUp(cmd *cobra.Command, args []string) error {
|
func runUp(cmd *cobra.Command, args []string) error {
|
||||||
spinner, _ := newSpinner(cmd, cmd.OutOrStdout())
|
spinner := newSpinner(cmd.OutOrStdout())
|
||||||
defer spinner.Stop()
|
defer spinner.Stop()
|
||||||
|
creator := cloudcmd.NewCreator(spinner)
|
||||||
|
|
||||||
return up(cmd, spinner)
|
return up(cmd, creator, spinner)
|
||||||
}
|
}
|
||||||
|
|
||||||
func up(cmd *cobra.Command, spinner spinnerInterf) error {
|
func up(cmd *cobra.Command, creator cloudCreator, spinner spinnerInterf) error {
|
||||||
if err := checkSystemRequirements(cmd.OutOrStdout()); err != nil {
|
if err := checkSystemRequirements(cmd.OutOrStdout()); err != nil {
|
||||||
return fmt.Errorf("system requirements not met: %w", err)
|
return fmt.Errorf("system requirements not met: %w", err)
|
||||||
}
|
}
|
||||||
@ -72,7 +73,7 @@ func up(cmd *cobra.Command, spinner spinnerInterf) error {
|
|||||||
|
|
||||||
// create cluster
|
// create cluster
|
||||||
spinner.Start("Creating cluster in QEMU ", false)
|
spinner.Start("Creating cluster in QEMU ", false)
|
||||||
err = createMiniCluster(cmd.Context(), fileHandler, cloudcmd.NewCreator(cmd.OutOrStdout()), config)
|
err = createMiniCluster(cmd.Context(), fileHandler, creator, config)
|
||||||
spinner.Stop()
|
spinner.Stop()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("creating cluster: %w", err)
|
return fmt.Errorf("creating cluster: %w", err)
|
||||||
@ -86,7 +87,7 @@ func up(cmd *cobra.Command, spinner spinnerInterf) error {
|
|||||||
cmd.Printf("\tvirsh -c %s\n\n", connectURI)
|
cmd.Printf("\tvirsh -c %s\n\n", connectURI)
|
||||||
|
|
||||||
// initialize cluster
|
// initialize cluster
|
||||||
if err := initializeMiniCluster(cmd, fileHandler); err != nil {
|
if err := initializeMiniCluster(cmd, fileHandler, spinner); err != nil {
|
||||||
return fmt.Errorf("initializing cluster: %w", err)
|
return fmt.Errorf("initializing cluster: %w", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@ -222,7 +223,7 @@ func createMiniCluster(ctx context.Context, fileHandler file.Handler, creator cl
|
|||||||
}
|
}
|
||||||
|
|
||||||
// initializeMiniCluster initializes a QEMU cluster.
|
// initializeMiniCluster initializes a QEMU cluster.
|
||||||
func initializeMiniCluster(cmd *cobra.Command, fileHandler file.Handler) (retErr error) {
|
func initializeMiniCluster(cmd *cobra.Command, fileHandler file.Handler, spinner spinnerInterf) (retErr error) {
|
||||||
// clean up cluster resources if initialization fails
|
// clean up cluster resources if initialization fails
|
||||||
defer func() {
|
defer func() {
|
||||||
if retErr != nil {
|
if retErr != nil {
|
||||||
@ -241,7 +242,7 @@ func initializeMiniCluster(cmd *cobra.Command, fileHandler file.Handler) (retErr
|
|||||||
cmd.Flags().String("endpoint", "", "")
|
cmd.Flags().String("endpoint", "", "")
|
||||||
cmd.Flags().Bool("conformance", false, "")
|
cmd.Flags().Bool("conformance", false, "")
|
||||||
|
|
||||||
if err := initialize(cmd, newDialer, fileHandler, helmLoader, license.NewClient(), nopSpinner{}); err != nil {
|
if err := initialize(cmd, newDialer, fileHandler, helmLoader, license.NewClient(), spinner); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -9,16 +9,23 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
tty "github.com/mattn/go-isatty"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// hideCursor and showCursor are ANSI escape sequences to hide and show the cursor.
|
||||||
|
hideCursor = "\033[?25l"
|
||||||
|
showCursor = "\033[?25h"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
spinnerStates = []string{"⣾", "⣽", "⣻", "⢿", "⡿", "⣟", "⣯", "⣷"}
|
spinnerStates = []string{"⣷", "⣯", "⣟", "⡿", "⢿", "⣻", "⣽", "⣾"}
|
||||||
dotsStates = []string{".", "..", "..."}
|
dotsStates = []string{". ", ".. ", "..."}
|
||||||
)
|
)
|
||||||
|
|
||||||
type spinnerInterf interface {
|
type spinnerInterf interface {
|
||||||
@ -27,71 +34,77 @@ type spinnerInterf interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type spinner struct {
|
type spinner struct {
|
||||||
out *cobra.Command
|
out io.Writer
|
||||||
delay time.Duration
|
delay time.Duration
|
||||||
wg *sync.WaitGroup
|
wg *sync.WaitGroup
|
||||||
stop int32
|
stop int32
|
||||||
|
spinFunc func(out io.Writer, wg *sync.WaitGroup, stop *int32, delay time.Duration, text string, showDots bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
func newSpinner(c *cobra.Command, writer io.Writer) (*spinner, *interruptSpinWriter) {
|
func newSpinner(writer io.Writer) *spinner {
|
||||||
spinner := &spinner{
|
s := &spinner{
|
||||||
out: c,
|
out: writer,
|
||||||
wg: &sync.WaitGroup{},
|
wg: &sync.WaitGroup{},
|
||||||
delay: 100 * time.Millisecond,
|
delay: time.Millisecond * 100,
|
||||||
stop: 0,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if writer != nil {
|
s.spinFunc = spinTTY
|
||||||
interruptWriter := &interruptSpinWriter{
|
//
|
||||||
writer: writer,
|
if !(writer == os.Stdout && tty.IsTerminal(os.Stdout.Fd())) {
|
||||||
spinner: spinner,
|
s.spinFunc = spinNoTTY
|
||||||
}
|
|
||||||
return spinner, interruptWriter
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return spinner, nil
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Start starts the spinner using the given text.
|
||||||
func (s *spinner) Start(text string, showDots bool) {
|
func (s *spinner) Start(text string, showDots bool) {
|
||||||
s.wg.Add(1)
|
s.wg.Add(1)
|
||||||
go func() {
|
|
||||||
defer s.wg.Done()
|
|
||||||
|
|
||||||
for i := 0; ; i = (i + 1) % len(spinnerStates) {
|
go s.spinFunc(s.out, s.wg, &s.stop, s.delay, text, showDots)
|
||||||
if atomic.LoadInt32(&s.stop) != 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
dotsState := ""
|
|
||||||
if showDots {
|
|
||||||
dotsState = dotsStates[i%len(dotsStates)]
|
|
||||||
}
|
|
||||||
state := fmt.Sprintf("\r%s %s%s", spinnerStates[i], text, dotsState)
|
|
||||||
s.out.Print(state)
|
|
||||||
time.Sleep(s.delay)
|
|
||||||
}
|
|
||||||
|
|
||||||
dotsState := ""
|
|
||||||
if showDots {
|
|
||||||
dotsState = dotsStates[len(dotsStates)-1]
|
|
||||||
}
|
|
||||||
finalState := fmt.Sprintf("\r%s%s ", text, dotsState)
|
|
||||||
s.out.Println(finalState)
|
|
||||||
}()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Stop stops the spinner.
|
||||||
func (s *spinner) Stop() {
|
func (s *spinner) Stop() {
|
||||||
atomic.StoreInt32(&s.stop, 1)
|
atomic.StoreInt32(&s.stop, 1)
|
||||||
s.wg.Wait()
|
s.wg.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
type interruptSpinWriter struct {
|
// Write stops the spinner and writes the given bytes to the underlying writer.
|
||||||
spinner *spinner
|
func (s *spinner) Write(p []byte) (n int, err error) {
|
||||||
writer io.Writer
|
s.Stop()
|
||||||
|
return s.out.Write(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *interruptSpinWriter) Write(p []byte) (n int, err error) {
|
func spinTTY(out io.Writer, wg *sync.WaitGroup, stop *int32, delay time.Duration, text string, showDots bool) {
|
||||||
w.spinner.Stop()
|
defer wg.Done()
|
||||||
return w.writer.Write(p)
|
|
||||||
|
fmt.Fprint(out, hideCursor)
|
||||||
|
|
||||||
|
for i := 0; ; i = (i + 1) % len(spinnerStates) {
|
||||||
|
if atomic.LoadInt32(stop) != 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
dotsState := ""
|
||||||
|
if showDots {
|
||||||
|
dotsState = dotsStates[i%len(dotsStates)]
|
||||||
|
}
|
||||||
|
state := fmt.Sprintf("\r%s %s%s", spinnerStates[i], text, dotsState)
|
||||||
|
fmt.Fprint(out, state)
|
||||||
|
time.Sleep(delay)
|
||||||
|
}
|
||||||
|
dotsState := ""
|
||||||
|
if showDots {
|
||||||
|
dotsState = dotsStates[len(dotsStates)-1]
|
||||||
|
}
|
||||||
|
finalState := fmt.Sprintf("\r%s%s ", text, dotsState)
|
||||||
|
fmt.Fprintln(out, finalState)
|
||||||
|
fmt.Fprint(out, showCursor)
|
||||||
|
}
|
||||||
|
|
||||||
|
func spinNoTTY(out io.Writer, wg *sync.WaitGroup, _ *int32, _ time.Duration, text string, _ bool) {
|
||||||
|
defer wg.Done()
|
||||||
|
fmt.Fprintln(out, text+"...")
|
||||||
}
|
}
|
||||||
|
|
||||||
type nopSpinner struct{}
|
type nopSpinner struct{}
|
||||||
|
@ -10,7 +10,6 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"sync/atomic"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -18,90 +17,91 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
baseWait = 1
|
baseWait = 100
|
||||||
baseText = "Loading"
|
baseText = "Loading"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestSpinnerInitialState(t *testing.T) {
|
func TestSpinnerInitialState(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
cmd := NewInitCmd()
|
out := &bytes.Buffer{}
|
||||||
var out bytes.Buffer
|
|
||||||
cmd.SetOut(&out)
|
|
||||||
var errOut bytes.Buffer
|
|
||||||
cmd.SetErr(&errOut)
|
|
||||||
|
|
||||||
s, _ := newSpinner(cmd, nil)
|
s := newSpinner(out)
|
||||||
|
s.delay = time.Millisecond * 10
|
||||||
|
s.spinFunc = spinTTY
|
||||||
s.Start(baseText, true)
|
s.Start(baseText, true)
|
||||||
time.Sleep(baseWait * time.Second)
|
time.Sleep(baseWait * time.Millisecond)
|
||||||
s.Stop()
|
s.Stop()
|
||||||
assert.True(out.Len() > 0)
|
assert.Greater(out.Len(), 0)
|
||||||
assert.True(errOut.Len() == 0)
|
|
||||||
|
|
||||||
outStr := out.String()
|
outStr := out.String()
|
||||||
assert.True(strings.HasPrefix(outStr, generateAllStatesAsString(baseText, true)))
|
assert.True(strings.HasPrefix(outStr, hideCursor+generateAllStatesAsString(t, baseText, true)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSpinnerFinalState(t *testing.T) {
|
func TestSpinnerFinalState(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
out := &bytes.Buffer{}
|
||||||
|
|
||||||
cmd := NewInitCmd()
|
s := newSpinner(out)
|
||||||
var out bytes.Buffer
|
s.delay = time.Millisecond * 10
|
||||||
cmd.SetOut(&out)
|
s.spinFunc = spinTTY
|
||||||
var errOut bytes.Buffer
|
|
||||||
cmd.SetErr(&errOut)
|
|
||||||
|
|
||||||
s, _ := newSpinner(cmd, nil)
|
|
||||||
s.Start(baseText, true)
|
s.Start(baseText, true)
|
||||||
time.Sleep(baseWait * time.Second)
|
time.Sleep(baseWait * time.Millisecond)
|
||||||
s.Stop()
|
s.Stop()
|
||||||
assert.True(out.Len() > 0)
|
assert.Greater(out.Len(), 0)
|
||||||
assert.True(errOut.Len() == 0)
|
|
||||||
|
|
||||||
outStr := out.String()
|
outStr := out.String()
|
||||||
assert.True(strings.HasSuffix(outStr, baseText+"... \n"))
|
assert.True(strings.HasSuffix(outStr, baseText+"... \n"+showCursor))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSpinnerDisabledShowDotsFlag(t *testing.T) {
|
func TestSpinnerDisabledShowDotsFlag(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
out := &bytes.Buffer{}
|
||||||
|
|
||||||
cmd := NewInitCmd()
|
s := newSpinner(out)
|
||||||
var out bytes.Buffer
|
s.delay = time.Millisecond * 10
|
||||||
cmd.SetOut(&out)
|
s.spinFunc = spinTTY
|
||||||
var errOut bytes.Buffer
|
|
||||||
cmd.SetErr(&errOut)
|
|
||||||
|
|
||||||
s, _ := newSpinner(cmd, nil)
|
|
||||||
s.Start(baseText, false)
|
s.Start(baseText, false)
|
||||||
time.Sleep(baseWait * time.Second)
|
time.Sleep(baseWait * time.Millisecond)
|
||||||
s.Stop()
|
s.Stop()
|
||||||
assert.True(out.Len() > 0)
|
assert.True(out.Len() > 0)
|
||||||
assert.True(errOut.Len() == 0)
|
|
||||||
|
|
||||||
outStr := out.String()
|
outStr := out.String()
|
||||||
assert.True(strings.HasPrefix(outStr, generateAllStatesAsString(baseText, false)))
|
assert.True(strings.HasPrefix(outStr, hideCursor+generateAllStatesAsString(t, baseText, false)))
|
||||||
assert.True(strings.HasSuffix(outStr, baseText+" \n"))
|
assert.True(strings.HasSuffix(outStr, baseText+" \n"+showCursor))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSpinnerInterruptWriter(t *testing.T) {
|
func TestSpinnerInterruptWriter(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
cmd := NewInitCmd()
|
out := &bytes.Buffer{}
|
||||||
var out bytes.Buffer
|
|
||||||
cmd.SetOut(&out)
|
|
||||||
var errOut bytes.Buffer
|
|
||||||
cmd.SetErr(&errOut)
|
|
||||||
|
|
||||||
s, interruptWriter := newSpinner(cmd, &out)
|
s := newSpinner(out)
|
||||||
|
s.spinFunc = spinTTY
|
||||||
s.Start(baseText, false)
|
s.Start(baseText, false)
|
||||||
time.Sleep(200 * time.Millisecond)
|
time.Sleep(200 * time.Millisecond)
|
||||||
_, err := interruptWriter.Write([]byte("test"))
|
_, err := s.Write([]byte("test"))
|
||||||
assert.NoError(err)
|
assert.NoError(err)
|
||||||
assert.Equal(int32(1), atomic.LoadInt32(&s.stop))
|
|
||||||
assert.True(strings.HasSuffix(out.String(), "test"))
|
assert.True(strings.HasSuffix(out.String(), "test"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateAllStatesAsString(text string, showDots bool) string {
|
func TestSpinNoTTY(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
out := &bytes.Buffer{}
|
||||||
|
|
||||||
|
s := newSpinner(out)
|
||||||
|
s.spinFunc = spinNoTTY
|
||||||
|
s.Start(baseText, true)
|
||||||
|
time.Sleep(baseWait * time.Millisecond)
|
||||||
|
s.Stop()
|
||||||
|
assert.Greater(out.Len(), 0)
|
||||||
|
assert.Equal(baseText+"...\n", out.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateAllStatesAsString(t *testing.T, text string, showDots bool) string {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
var builder strings.Builder
|
var builder strings.Builder
|
||||||
|
|
||||||
for i := 0; i < len(spinnerStates); i++ {
|
for i := 0; i < len(spinnerStates); i++ {
|
||||||
|
@ -36,7 +36,7 @@ func NewTerminateCmd() *cobra.Command {
|
|||||||
// runTerminate runs the terminate command.
|
// runTerminate runs the terminate command.
|
||||||
func runTerminate(cmd *cobra.Command, args []string) error {
|
func runTerminate(cmd *cobra.Command, args []string) error {
|
||||||
fileHandler := file.NewHandler(afero.NewOsFs())
|
fileHandler := file.NewHandler(afero.NewOsFs())
|
||||||
spinner, _ := newSpinner(cmd, cmd.OutOrStdout())
|
spinner := newSpinner(cmd.OutOrStdout())
|
||||||
defer spinner.Stop()
|
defer spinner.Stop()
|
||||||
terminator := cloudcmd.NewTerminator()
|
terminator := cloudcmd.NewTerminator()
|
||||||
|
|
||||||
|
2
go.mod
2
go.mod
@ -74,6 +74,7 @@ require (
|
|||||||
github.com/icholy/replace v0.5.0
|
github.com/icholy/replace v0.5.0
|
||||||
github.com/manifoldco/promptui v0.9.0
|
github.com/manifoldco/promptui v0.9.0
|
||||||
github.com/martinjungblut/go-cryptsetup v0.0.0-20220520180014-fd0874fd07a6
|
github.com/martinjungblut/go-cryptsetup v0.0.0-20220520180014-fd0874fd07a6
|
||||||
|
github.com/mattn/go-isatty v0.0.14
|
||||||
github.com/microsoft/ApplicationInsights-Go v0.4.4
|
github.com/microsoft/ApplicationInsights-Go v0.4.4
|
||||||
github.com/operator-framework/api v0.15.0
|
github.com/operator-framework/api v0.15.0
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
@ -223,7 +224,6 @@ require (
|
|||||||
github.com/magiconair/properties v1.8.6 // indirect
|
github.com/magiconair/properties v1.8.6 // indirect
|
||||||
github.com/mailru/easyjson v0.7.7 // indirect
|
github.com/mailru/easyjson v0.7.7 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.12 // indirect
|
github.com/mattn/go-colorable v0.1.12 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
|
||||||
github.com/mattn/go-runewidth v0.0.13 // indirect
|
github.com/mattn/go-runewidth v0.0.13 // indirect
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
|
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
|
||||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
|
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
|
||||||
|
@ -164,6 +164,7 @@ require (
|
|||||||
github.com/magiconair/properties v1.8.6 // indirect
|
github.com/magiconair/properties v1.8.6 // indirect
|
||||||
github.com/mailru/easyjson v0.7.7 // indirect
|
github.com/mailru/easyjson v0.7.7 // indirect
|
||||||
github.com/manifoldco/promptui v0.9.0 // indirect
|
github.com/manifoldco/promptui v0.9.0 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||||
github.com/mattn/go-runewidth v0.0.13 // indirect
|
github.com/mattn/go-runewidth v0.0.13 // indirect
|
||||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
|
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
|
||||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||||
|
@ -965,6 +965,7 @@ github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2y
|
|||||||
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
|
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
|
||||||
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
||||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||||
|
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
||||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||||
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||||
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||||
|
Loading…
x
Reference in New Issue
Block a user