diff --git a/cli/cmd/connect.go b/cli/cmd/connect.go index 345ae5b3c..ec7e6443f 100644 --- a/cli/cmd/connect.go +++ b/cli/cmd/connect.go @@ -1,11 +1,14 @@ package cmd import ( + "syscall" + "github.com/apex/log" "github.com/spf13/cobra" "github.com/tarantool/tt/cli/cmdcontext" "github.com/tarantool/tt/cli/connect" "github.com/tarantool/tt/cli/modules" + "golang.org/x/crypto/ssh/terminal" ) var ( @@ -38,7 +41,9 @@ func internalConnectModule(cmdCtx *cmdcontext.CmdCtx, args []string) error { cmdCtx.Connect.Username = connectUser cmdCtx.Connect.Password = connectPassword - log.Info("Connecting to the instance...") + if terminal.IsTerminal(syscall.Stdin) { + log.Info("Connecting to the instance...") + } if err := connect.Connect(cmdCtx, args); err != nil { return err } diff --git a/cli/cmd/help.go b/cli/cmd/help.go index fc0a6a802..49a79753a 100644 --- a/cli/cmd/help.go +++ b/cli/cmd/help.go @@ -79,7 +79,7 @@ func getInternalHelpFunc(cmd *cobra.Command, help DefaultHelpFunc) modules.Inter // helpFlagError prints some help information if the user entered an invalid flag. func helpFlagError(cmd *cobra.Command, errMsg error) error { - templatedStr, err := util.GetTemplatedStr(&errorUsageTemplate, map[string]interface{}{ + templatedStr, err := util.GetHTMLTemplatedStr(&errorUsageTemplate, map[string]interface{}{ "ErrorMsg": errMsg, "Cmd": cmd.CommandPath(), "HasFlags": cmd.HasAvailableFlags(), diff --git a/cli/codegen/generate_code.go b/cli/codegen/generate_code.go index 118c44692..325d46bbb 100644 --- a/cli/codegen/generate_code.go +++ b/cli/codegen/generate_code.go @@ -29,7 +29,6 @@ var luaCodeFiles = []generateLuaCodeOpts{ VariablesMap: map[string]string{ "evalFuncBody": "cli/connect/lua/eval_func_body.lua", "getSuggestionsFuncBody": "cli/connect/lua/get_suggestions_func_body.lua", - "getTitleFuncBody": "cli/connect/lua/get_title_func_body.lua", }, }, { diff --git a/cli/connect/common.go b/cli/connect/common.go deleted file mode 100644 index 46e016b9f..000000000 --- a/cli/connect/common.go +++ /dev/null @@ -1,70 +0,0 @@ -package connect - -import ( - "strings" - - "github.com/tarantool/tt/cli/cmdcontext" -) - -const ( - MaxHistoryLines = 10000 - - TCPNetwork = "tcp" - UnixNetwork = "unix" -) - -type ConnOpts struct { - Network string - Address string - Username string - Password string -} - -func getConnOpts(connString string, cmdCtx *cmdcontext.CmdCtx) (*ConnOpts, error) { - connOpts := ConnOpts{ - Username: cmdCtx.Connect.Username, - Password: cmdCtx.Connect.Password, - } - - connStringParts := strings.SplitN(connString, "@", 2) - address := connStringParts[len(connStringParts)-1] - - if len(connStringParts) > 1 { - authString := connStringParts[0] - authStringParts := strings.SplitN(authString, ":", 2) - - if connOpts.Username == "" { - connOpts.Username = authStringParts[0] - } - if len(authStringParts) > 1 && connOpts.Password == "" { - connOpts.Password = authStringParts[1] - } - } - - addrLen := len(address) - switch { - case addrLen > 0 && (address[0] == '.' || address[0] == '/'): - connOpts.Network = UnixNetwork - connOpts.Address = address - case addrLen >= 7 && address[0:7] == "unix://": - connOpts.Network = UnixNetwork - connOpts.Address = address[7:] - case addrLen >= 5 && address[0:5] == "unix:": - connOpts.Network = UnixNetwork - connOpts.Address = address[5:] - case addrLen >= 6 && address[0:6] == "unix/:": - connOpts.Network = UnixNetwork - connOpts.Address = address[6:] - case addrLen >= 6 && address[0:6] == "tcp://": - connOpts.Network = TCPNetwork - connOpts.Address = address[6:] - case addrLen >= 4 && address[0:4] == "tcp:": - connOpts.Network = TCPNetwork - connOpts.Address = address[4:] - default: - connOpts.Network = TCPNetwork - connOpts.Address = address - } - - return &connOpts, nil -} diff --git a/cli/connect/connect.go b/cli/connect/connect.go index 399d5d640..f321d306b 100644 --- a/cli/connect/connect.go +++ b/cli/connect/connect.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/tarantool/tt/cli/cmdcontext" + "github.com/tarantool/tt/cli/connector" ) const ( @@ -11,17 +12,18 @@ const ( tarantoolWordSeparators = "\t\r\n !\"#$%&'()*+,-/;<=>?@[\\]^`{|}~" ) +func getConnOpts(connString string, cmdCtx *cmdcontext.CmdCtx) *connector.ConnOpts { + return connector.GetConnOpts(connString, cmdCtx.Connect.Username, cmdCtx.Connect.Password) +} + +// Connect establishes a connection to the instance and starts the console. func Connect(cmdCtx *cmdcontext.CmdCtx, args []string) error { if len(args) != 1 { return fmt.Errorf("Should be specified one connection string") } connString := args[0] - - connOpts, err := getConnOpts(connString, cmdCtx) - if err != nil { - return fmt.Errorf("Failed to get connection opts: %s", err) - } + connOpts := getConnOpts(connString, cmdCtx) if err := runConsole(connOpts, ""); err != nil { return fmt.Errorf("Failed to run interactive console: %s", err) @@ -30,7 +32,8 @@ func Connect(cmdCtx *cmdcontext.CmdCtx, args []string) error { return nil } -func runConsole(connOpts *ConnOpts, title string) error { +// runConsole run a new console. +func runConsole(connOpts *connector.ConnOpts, title string) error { console, err := NewConsole(connOpts, title) if err != nil { return fmt.Errorf("Failed to create new console: %s", err) diff --git a/cli/connect/console.go b/cli/connect/console.go index a724d347a..209aff368 100644 --- a/cli/connect/console.go +++ b/cli/connect/console.go @@ -8,11 +8,13 @@ import ( "path/filepath" "sort" "strings" + "syscall" "time" "github.com/adam-hanna/arrayOperations" "github.com/apex/log" lua "github.com/yuin/gopher-lua" + "golang.org/x/crypto/ssh/terminal" "gopkg.in/yaml.v2" "github.com/c-bata/go-prompt" @@ -20,12 +22,14 @@ import ( "github.com/tarantool/tt/cli/util" ) +// EvalFunc defines a function type for evaluating an expression via connection. type EvalFunc func(console *Console, funcBodyFmt string, args ...interface{}) (interface{}, error) const ( HistoryFileName = ".tarantool_history" MaxLivePrefixIndent = 15 + MaxHistoryLines = 10000 ) var ( @@ -38,6 +42,7 @@ func init() { ControlRightBytes = []byte{0x1b, 0x66} } +// Console describes the console connected to the tarantool instance. type Console struct { input string @@ -52,7 +57,7 @@ type Console struct { livePrefix string livePrefixFunc func() (string, bool) - connOpts *ConnOpts + connOpts *connector.ConnOpts conn *connector.Conn executor func(in string) @@ -63,7 +68,8 @@ type Console struct { prompt *prompt.Prompt } -func NewConsole(connOpts *ConnOpts, title string) (*Console, error) { +// NewConsole creates a new console connected to the tarantool instance. +func NewConsole(connOpts *connector.ConnOpts, title string) (*Console, error) { console := &Console{ title: title, connOpts: connOpts, @@ -72,60 +78,48 @@ func NewConsole(connOpts *ConnOpts, title string) (*Console, error) { var err error - // load Tarantool console history from file + // Load Tarantool console history from file. if err := loadHistory(console); err != nil { log.Debugf("Failed to load Tarantool console history: %s", err) } - // connect to specified address - console.conn, err = connector.Connect(connOpts.Address, connector.Opts{ - Username: connOpts.Username, - Password: connOpts.Password, - }) + // Connect to specified address. + console.conn, err = connector.Connect(connOpts.Address, connOpts.Username, connOpts.Password) if err != nil { return nil, fmt.Errorf("Failed to connect: %s", err) } - // initialize user commands executor + // Initialize user commands executor. console.executor = getExecutor(console) - // initialize commands completer + // Initialize commands completer. console.completer = getCompleter(console) - // set title and prompt prefix - // . for Cartridge application instances - // : otherwise + // Set title and prompt prefix. setTitle(console) setPrefix(console) return console, nil } +// Run starts console. func (console *Console) Run() error { - var err error - - fmt.Printf("connected to %s\n", console.title) - - pipedInputIsFound, err := util.StdinHasUnreadData() - if err != nil { - return fmt.Errorf("Failed to check unread data from stdin: %s", err) - } - - if pipedInputIsFound { + if !terminal.IsTerminal(syscall.Stdin) { log.Debugf("Found piped input") - // e.g. `echo "box.info()" | cartridge enter router` pipedInputScanner := bufio.NewScanner(os.Stdin) for pipedInputScanner.Scan() { line := pipedInputScanner.Text() console.executor(line) } return nil + } else { + log.Infof("Connected to %s\n", console.title) } - // get options for Prompt instance + // Get options for Prompt instance. options := getPromptOptions(console) - // create Prompt instance + // Create Prompt instance. console.prompt = prompt.New( console.executor, console.completer, @@ -137,6 +131,7 @@ func (console *Console) Run() error { return nil } +// Close frees up resources used by the console. func (console *Console) Close() { if console.historyFile != nil { console.historyFile.Close() @@ -158,8 +153,8 @@ func loadHistory(console *Console) error { return fmt.Errorf("Failed to read history from file: %s", err) } - // open history file for appending - // see https://unix.stackexchange.com/questions/346062/concurrent-writing-to-a-log-file-from-many-processes + // Open history file for appending. + // See https://unix.stackexchange.com/questions/346062/concurrent-writing-to-a-log-file-from-many-processes console.historyFile, err = os.OpenFile( console.historyFilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, @@ -219,16 +214,14 @@ func getExecutor(console *Console) prompt.Executor { } func inputIsCompleted(input string, luaState *lua.LState) bool { - // see https://github.com/tarantool/tarantool/blob/b53cb2aeceedc39f356ceca30bd0087ee8de7c16/src/box/lua/console.lua#L575 + // See https://github.com/tarantool/tarantool/blob/b53cb2aeceedc39f356ceca30bd0087ee8de7c16/src/box/lua/console.lua#L575 if _, err := luaState.LoadString(input); err == nil || !strings.Contains(err.Error(), "at EOF") { - // valid Lua code or a syntax error not due to - // an incomplete input + // Valid Lua code or a syntax error not due to an incomplete input. return true } if _, err := luaState.LoadString(fmt.Sprintf("return %s", input)); err == nil { - // certain obscure inputs like '(42\n)' yield the - // same error as incomplete statement + // Certain obscure inputs like '(42\n)' yield the same error as incomplete statement. return true } @@ -279,18 +272,7 @@ func getCompleter(console *Console) prompt.Completer { func setTitle(console *Console) { if console.title != "" { return - } - - req := connector.EvalReq(getTitleFuncBody) - - var titlesSlice []string - if err := console.conn.ExecTyped(req, &titlesSlice); err != nil { - log.Debugf("Failed to get instance title: %s", err) } else { - console.title = titlesSlice[0] - } - - if console.title == "" { console.title = console.connOpts.Address } } @@ -324,7 +306,8 @@ func getPromptOptions(console *Console) []prompt.Option { prompt.OptionCompletionWordSeparator(tarantoolWordSeparators), prompt.OptionAddASCIICodeBind( - prompt.ASCIICodeBind{ // move to one word left + // Move to one word left. + prompt.ASCIICodeBind{ ASCIICode: ControlLeftBytes, Fn: func(buf *prompt.Buffer) { d := buf.Document() @@ -332,7 +315,8 @@ func getPromptOptions(console *Console) []prompt.Option { buf.CursorLeft(wordLen) }, }, - prompt.ASCIICodeBind{ // move to one word right + // Move to one word right. + prompt.ASCIICodeBind{ ASCIICode: ControlRightBytes, Fn: func(buf *prompt.Buffer) { d := buf.Document() @@ -341,9 +325,9 @@ func getPromptOptions(console *Console) []prompt.Option { }, }, ), - + // Interrupt current unfinished expression. prompt.OptionAddKeyBind( - prompt.KeyBind{ // Interrupt current unfinished expression + prompt.KeyBind{ Key: prompt.ControlC, Fn: func(buf *prompt.Buffer) { console.input = "" diff --git a/cli/connect/lua/get_title_func_body.lua b/cli/connect/lua/get_title_func_body.lua deleted file mode 100644 index cbb6b4f39..000000000 --- a/cli/connect/lua/get_title_func_body.lua +++ /dev/null @@ -1,11 +0,0 @@ -local ok, api_topology = pcall(require, 'cartridge.lua-api.topology') -if not ok then - return '' -end - -local self = api_topology.get_self() -if self.app_name == nil or self.instance_name == nil then - return '' -end - -return string.format('%s.%s', self.app_name, self.instance_name) diff --git a/cli/connector/conn_opts.go b/cli/connector/conn_opts.go index 4bf0e36f3..cbda05763 100644 --- a/cli/connector/conn_opts.go +++ b/cli/connector/conn_opts.go @@ -9,14 +9,20 @@ const ( UnixNetwork = "unix" ) +// ConnOpts describes the connection to a tarantool instance. type ConnOpts struct { - Network string - Address string + // Network is a characteristic of a connection like "type" ("tcp" and "unix" are used). + Network string + // Address of an instance. + Address string + // Username of the tarantool user. Username string + // Password of the user. Password string } -func getConnOpts(connString, username, password string) *ConnOpts { +// GetConnOpts returns the connection parameters according to the passed arguments. +func GetConnOpts(connString, username, password string) *ConnOpts { connOpts := ConnOpts{ Username: username, Password: password, diff --git a/cli/connector/connector.go b/cli/connector/connector.go index 2ae276d63..1b0132abb 100644 --- a/cli/connector/connector.go +++ b/cli/connector/connector.go @@ -9,8 +9,20 @@ import ( "github.com/FZambia/tarantool" ) +// Protocol describes the type of protocol used (plain text or IPROTO). type Protocol string +// ExecOpts describes the parameters of the operation to be executed. +type ExecOpts struct { + // PushCallback is the cb that will be called when a "push" message is received. + PushCallback func(interface{}) + // ReadTimeout timeout for the operation. + ReadTimeout time.Duration + // ResData describes the typed result of the operation executed. + ResData interface{} +} + +// Conn describes the connection to the tarantool instance. type Conn struct { protocol Protocol @@ -21,17 +33,6 @@ type Conn struct { callFunc func(conn *Conn, funcName string, args []interface{}, execOpts ExecOpts) ([]interface{}, error) } -type Opts struct { - Username string - Password string -} - -type ExecOpts struct { - PushCallback func(interface{}) - ReadTimeout time.Duration - ResData interface{} -} - const ( // https://www.tarantool.io/en/doc/1.10/dev_guide/internals_index/#greeting-packet greetingSize = 1024 @@ -42,26 +43,27 @@ const ( SimpleOperationTimeout = 3 * time.Second ) -func Connect(connString string, opts Opts) (*Conn, error) { +// Connect connects to the tarantool instance according to "connString". +func Connect(connString string, username string, password string) (*Conn, error) { var err error conn := &Conn{} - connOpts := getConnOpts(connString, opts.Username, opts.Password) + connOpts := GetConnOpts(connString, username, password) - // connect to specified address + // Connect to specified address. plainTextConn, err := net.Dial(connOpts.Network, connOpts.Address) if err != nil { return nil, fmt.Errorf("Failed to dial: %s", err) } - // detect protocol + // Detect protocol. conn.protocol, err = getProtocol(plainTextConn) if err != nil { return nil, fmt.Errorf("Failed to get protocol: %s", err) } - // initialize connection + // Initialize connection. switch conn.protocol { case PlainTextProtocol: if err := initPlainTextConn(conn, plainTextConn); err != nil { @@ -78,14 +80,17 @@ func Connect(connString string, opts Opts) (*Conn, error) { return conn, nil } +// Exec executes an operation. func (conn *Conn) Exec(req *Request) ([]interface{}, error) { return req.execFunc(conn) } +// ExecTyped executes an operation and returns the typed result. func (conn *Conn) ExecTyped(req *Request, resData interface{}) error { return req.execTypedFunc(conn, resData) } +// Close closes the connection. func (conn *Conn) Close() error { switch conn.protocol { case PlainTextProtocol: diff --git a/cli/connector/eval_plain_text.go b/cli/connector/eval_plain_text.go index 7f709fc2f..4e615d931 100644 --- a/cli/connector/eval_plain_text.go +++ b/cli/connector/eval_plain_text.go @@ -41,7 +41,7 @@ type PlainTextEvalRes struct { // Function should return `interface{}`, `string` (res, err) // to be correctly processed. func callPlainTextConn(conn net.Conn, funcName string, args []interface{}, opts EvalPlainTextOpts) ([]interface{}, error) { - evalFunc, err := util.GetTemplatedStr(&callFuncTmpl, map[string]string{ + evalFunc, err := util.GetTextTemplatedStr(&callFuncTmpl, map[string]string{ "FunctionName": funcName, }) @@ -88,7 +88,7 @@ func formatAndSendEvalFunc(conn net.Conn, funcBody string, args []interface{}, e return fmt.Errorf("Failed to encode args: %s", err) } - evalFunc, err := util.GetTemplatedStr(&evalFuncTmpl, map[string]string{ + evalFunc, err := util.GetTextTemplatedStr(&evalFuncTmpl, map[string]string{ "FunctionBody": funcBody, "ArgsEncoded": fmt.Sprintf("%x", argsEncoded), }) diff --git a/cli/connector/request.go b/cli/connector/request.go index 6bf968ea0..4fbed7dff 100644 --- a/cli/connector/request.go +++ b/cli/connector/request.go @@ -4,6 +4,7 @@ import ( "time" ) +// Request describes ways and conditions of interaction with the instance. type Request struct { execFunc func(conn *Conn) ([]interface{}, error) execTypedFunc func(conn *Conn, resData interface{}) error @@ -12,16 +13,19 @@ type Request struct { readTimeout time.Duration } +// SetPushCallback sets the callback that will be called when a "push" message is received. func (req *Request) SetPushCallback(pushCallback func(interface{})) *Request { req.pushCallback = pushCallback return req } +// SetReadTimeout sets timeout for the operation. func (req *Request) SetReadTimeout(readTimeout time.Duration) *Request { req.readTimeout = readTimeout return req } +// EvalReq prepares "Request" to evaluate an expression. func EvalReq(funcBody string, args ...interface{}) *Request { if args == nil { args = []interface{}{} @@ -48,26 +52,3 @@ func EvalReq(funcBody string, args ...interface{}) *Request { return req } - -func CallReq(funcName string, args ...interface{}) *Request { - req := &Request{} - - req.execFunc = func(conn *Conn) ([]interface{}, error) { - return conn.callFunc(conn, funcName, args, ExecOpts{ - PushCallback: req.pushCallback, - ReadTimeout: req.readTimeout, - }) - } - - req.execTypedFunc = func(conn *Conn, resData interface{}) error { - _, err := conn.callFunc(conn, funcName, args, ExecOpts{ - PushCallback: req.pushCallback, - ReadTimeout: req.readTimeout, - ResData: resData, - }) - - return err - } - - return req -} diff --git a/cli/util/templates.go b/cli/util/templates.go index 6baf2e020..845ee75f2 100644 --- a/cli/util/templates.go +++ b/cli/util/templates.go @@ -23,7 +23,7 @@ func GetHTMLTemplatedStr(text *string, obj interface{}) (string, error) { } // GetTextTemplatedStr returns the processed string text template. -func GetTemplatedStr(text *string, obj interface{}) (string, error) { +func GetTextTemplatedStr(text *string, obj interface{}) (string, error) { funcMap := textTemplate.FuncMap{ "ToLower": strings.ToLower, } diff --git a/cli/util/util.go b/cli/util/util.go index 434200493..75dc32774 100644 --- a/cli/util/util.go +++ b/cli/util/util.go @@ -10,12 +10,11 @@ import ( "path/filepath" "runtime/debug" - "github.com/apex/log" "github.com/spf13/cobra" "gopkg.in/yaml.v2" ) -var bufSize int64 = 10000 +const bufSize int64 = 10000 // VersionFunc is a type of function that return // string with current Tarantool CLI version. @@ -54,32 +53,6 @@ func GetFileContent(path string) (string, error) { return string(fileContentBytes), nil } -func writeFileToWriter(filePath string, writer io.Writer) error { - file, err := os.Open(filePath) - if err != nil { - return err - } - defer file.Close() - - // copy file data into tar writer - if _, err := io.Copy(writer, file); err != nil { - return err - } - - return nil -} - -func PrintFromStart(file *os.File) error { - if _, err := file.Seek(0, io.SeekStart); err != nil { - return fmt.Errorf("Failed to seek file begin: %s", err) - } - if _, err := io.Copy(os.Stdout, file); err != nil { - log.Warnf("Failed to print file content: %s", err) - } - - return nil -} - // JoinAbspath concat paths and makes the resulting path absolute. func JoinAbspath(paths ...string) (string, error) { var err error @@ -196,7 +169,7 @@ func GetLastNLinesBegin(filepath string, lines int) (int64, error) { var lastNewLinePos int64 = 0 var newLinesN int = 0 - // check last symbol of the last line + // Check last symbol of the last line. if _, err := readFromPos(f, fileSize-1, &buf); err != nil { return 0, err @@ -242,6 +215,7 @@ Loop: return lastNewLinePos, nil } +// GetLastNLines returns the last N lines fromthe file. func GetLastNLines(filepath string, linesN int) ([]string, error) { lastNLinesBeginPos, err := GetLastNLinesBegin(filepath, linesN) if err != nil { @@ -266,11 +240,3 @@ func GetLastNLines(filepath string, linesN int) ([]string, error) { return lines, nil } - -func StdinHasUnreadData() (bool, error) { - stdinStat, err := os.Stdin.Stat() - if err != nil { - return false, err - } - return (stdinStat.Mode() & os.ModeCharDevice) == 0, nil -} diff --git a/go.mod b/go.mod index cbce0d39e..8d23a001c 100644 --- a/go.mod +++ b/go.mod @@ -16,6 +16,7 @@ require ( github.com/stretchr/testify v1.7.0 github.com/vmihailenco/msgpack/v5 v5.3.5 github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9 + golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 gopkg.in/natefinch/lumberjack.v2 v2.0.0 gopkg.in/yaml.v2 v2.4.0 ) @@ -34,5 +35,6 @@ require ( github.com/spf13/pflag v1.0.5 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect golang.org/x/sys v0.0.0-20211205182925-97ca703d548d // indirect + golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 // indirect gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect ) diff --git a/go.sum b/go.sum index 2e61535d3..47dba300e 100644 --- a/go.sum +++ b/go.sum @@ -428,6 +428,7 @@ golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -609,6 +610,7 @@ golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211205182925-97ca703d548d h1:FjkYO/PPp4Wi0EAUOVLxePm7qVW4r4ctbWpURyuOD0E= golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=