diff --git a/api/contract/openapi b/api/contract/openapi index 9b93b9e..920d508 160000 --- a/api/contract/openapi +++ b/api/contract/openapi @@ -1 +1 @@ -Subproject commit 9b93b9e414c45537f6ebed0757ff4f764518e2b5 +Subproject commit 920d508ef5bb61e09885bd625c6e81734fc5cc99 diff --git a/clients/v1_shell/autocomplete.go b/clients/v1_shell/autocomplete.go new file mode 100644 index 0000000..80d9dba --- /dev/null +++ b/clients/v1_shell/autocomplete.go @@ -0,0 +1,213 @@ +package v1shell + +import ( + "fmt" + "regexp" + "sort" + "strings" + + "github.com/influxdata/go-prompt" +) + +type subsuggestFnType = func(string) (map[string]SuggestNode, string) + +type SuggestNode struct { + Description string + subsuggestFn subsuggestFnType +} + +func (c *Client) suggestUse(remainder string) (map[string]SuggestNode, string) { + s := map[string]SuggestNode{} + for _, db := range c.Databases { + s["\""+db+"\""] = SuggestNode{Description: "Table Name"} + } + return s, remainder +} + +func (c *Client) suggestDropMeasurement(remainder string) (map[string]SuggestNode, string) { + s := map[string]SuggestNode{} + if c.Database != "" && c.RetentionPolicy != "" { + for _, m := range c.Measurements { + s["\""+m+"\""] = SuggestNode{Description: fmt.Sprintf("Measurement on \"%s\".\"%s\"", c.Database, c.RetentionPolicy)} + } + } + return s, remainder +} + +func (c *Client) suggestDelete(remainder string) (map[string]SuggestNode, string) { + s := map[string]SuggestNode{} + fromReg := regexp.MustCompile(`(?i)FROM(\s+)` + identRegex("from_clause") + `?$`) + matches := reSubMatchMap(fromReg, remainder) + if matches != nil { + if c.Database != "" && c.RetentionPolicy != "" { + for _, m := range c.Measurements { + s["\""+m+"\""] = SuggestNode{Description: fmt.Sprintf("Measurement on \"%s\".\"%s\"", c.Database, c.RetentionPolicy)} + } + } + return s, getIdentFromMatches(matches, "from_clause") + } + return s, remainder +} + +func (c *Client) suggestSelect(remainder string) (map[string]SuggestNode, string) { + s := map[string]SuggestNode{} + fromReg := regexp.MustCompile(`(?i)FROM(\s+)` + identRegex("from_clause") + `?$`) + matches := reSubMatchMap(fromReg, remainder) + if matches != nil { + if c.Database != "" && c.RetentionPolicy != "" { + for _, m := range c.Measurements { + s["\""+m+"\""] = SuggestNode{Description: fmt.Sprintf("Measurement on \"%s\".\"%s\"", c.Database, c.RetentionPolicy)} + } + } + if c.Database != "" { + for _, rp := range c.RetentionPolicies { + s["\""+rp+"\""] = SuggestNode{Description: "Retention Policy on " + c.Database} + } + } + for _, db := range c.Databases { + s["\""+db+"\""] = SuggestNode{Description: "Table Name"} + } + return s, getIdentFromMatches(matches, "from_clause") + } + return s, remainder +} + +func getSuggestions(remainder string, s map[string]SuggestNode) ([]prompt.Suggest, string) { + if len(remainder) > 0 { + for term, node := range s { + if strings.HasPrefix(strings.ToLower(remainder), term) || strings.HasPrefix(strings.ToUpper(remainder), term) { + // if the cursor is at the end of a just completed word without trailing space, don't give autosuggestions yet + if len(term) >= len(remainder) { + return []prompt.Suggest{}, "" + } + // remainder is everything after the just-matched term + rem := strings.TrimLeft(remainder[len(term):], " ") + // if sugsuggestFn is nil, this is a terminating suggestion (leaf node with no more following suggestions) + if node.subsuggestFn == nil { + return []prompt.Suggest{}, rem + } + sugs, rem := node.subsuggestFn(rem) + return getSuggestions(rem, sugs) + } + } + } + // if no match was found, convert the s map into suggestions and filter by the remaining characters + var sugs []prompt.Suggest + for text, node := range s { + sugs = append(sugs, prompt.Suggest{Text: text, Description: node.Description}) + } + return prompt.FilterFuzzy(sugs, remainder, true), remainder +} + +// recursively creates a SuggestNode for each space-delimited word for each subsuggestion +func newSugNodeFn(subsugs ...string) subsuggestFnType { + s := make(map[string]SuggestNode) + for _, sug := range subsugs { + word, rest, found := strings.Cut(sug, " ") + if !found { + s[word] = SuggestNode{} + } else { + s[word] = SuggestNode{subsuggestFn: newSugNodeFn(rest)} + } + } + return func(rem string) (map[string]SuggestNode, string) { + return s, rem + } +} + +func (c *Client) completer(d prompt.Document) []prompt.Suggest { + // the commented-out lines are unsupported in 2.x + suggestions := map[string]SuggestNode{ + "use": {Description: "Set current database", subsuggestFn: c.suggestUse}, + "pretty": {Description: "Toggle pretty print for the json format"}, + "precision": {Description: "Specify the format of the timestamp", + subsuggestFn: newSugNodeFn( + "rfc3339", + "ns", + "u", + "ms", + "s", + "m", + "h", + )}, + "history": {Description: "Display shell history"}, + "settings": {Description: "Output the current shell settings"}, + "clear": {Description: "Clears settings such as database", + subsuggestFn: newSugNodeFn( + "db", + "database", + "retention policy", + "rp", + )}, + "exit": {Description: "Exit the InfluxQL shell"}, + "quit": {Description: "Exit the InfluxQL shell"}, + "gopher": {Description: "Display the Go Gopher"}, + "help": {Description: "Display help options"}, + "format": {Description: "Specify the data display format", + subsuggestFn: newSugNodeFn( + "column", + "csv", + "json", + )}, + "SELECT": {subsuggestFn: c.suggestSelect}, + "INSERT": {}, + "INSERT INTO": {}, + "DELETE": {subsuggestFn: c.suggestDelete}, + "SHOW": {subsuggestFn: newSugNodeFn( + // "CONTINUOUS QUERIES", + "DATABASES", + // "DIAGNOSTICS", + "FIELD KEY CARDINALITY", + "FIELD KEYS", + // "GRANTS", + // "MEASUREMENT CARDINALITY", + "MEASUREMENT EXACT CARDINALITY", + "MEASUREMENTS", + // "QUERIES", + // "RETENTION POLICIES", + "SERIES", + // "SERIES CARDINALITY", + "SERIES EXACT CARDINALITY", + // "SHARD GROUPS", + // "SHARDS", + // "STATS", + // "SUBSCRIPTIONS", + "TAG KEY CARDINALITY", + "TAG KEY EXACT CARDINALITY", + "TAG KEYS", + "TAG VALUES", + // "USERS", + )}, + // "CREATE": {subsuggestFn: newSugNodeFn( + // "CONTINUOUS QUERY", + // "DATABASE", + // "USER", + // "RETENTION POLICY", + // "SUBSCRIPTION", + // )}, + "DROP": {subsuggestFn: func(rem string) (map[string]SuggestNode, string) { + return map[string]SuggestNode{ + // "CONTINUOUS QUERY": {}, + // "DATABASE": {}, + "MEASUREMENT": {subsuggestFn: c.suggestDropMeasurement}, + // "RETENTION POLICY": {}, + // "SERIES": {}, + // "SHARD": {}, + // "SUBSCRIPTION": {}, + // "USER": {}, + }, rem + }}, + "EXPLAIN": {subsuggestFn: newSugNodeFn("ANALYZE")}, + // "GRANT": {}, + // "REVOKE": {}, + // "ALTER RETENTION POLICY": {}, + // "SET PASSOWRD FOR": {}, + // "KILL QUERY": {}, + } + line := d.CurrentLineBeforeCursor() + currentSuggestions, _ := getSuggestions(line, suggestions) + sort.Slice(currentSuggestions, func(i, j int) bool { + return strings.ToLower(currentSuggestions[i].Text) < strings.ToLower(currentSuggestions[j].Text) + }) + return currentSuggestions +} diff --git a/clients/v1_repl/gopher.go b/clients/v1_shell/gopher.go similarity index 99% rename from clients/v1_repl/gopher.go rename to clients/v1_shell/gopher.go index 201095d..5f3bade 100644 --- a/clients/v1_repl/gopher.go +++ b/clients/v1_shell/gopher.go @@ -1,4 +1,4 @@ -package v1repl +package v1shell var Gopher string = ` .-::-::://:-::- .:/++/' diff --git a/clients/v1_repl/v1_repl.go b/clients/v1_shell/v1_shell.go similarity index 90% rename from clients/v1_repl/v1_repl.go rename to clients/v1_shell/v1_shell.go index bcbe0ae..49d7848 100644 --- a/clients/v1_repl/v1_repl.go +++ b/clients/v1_shell/v1_shell.go @@ -1,4 +1,4 @@ -package v1repl +package v1shell import ( "bufio" @@ -10,13 +10,16 @@ import ( "errors" "fmt" "os" + "path/filepath" "reflect" "regexp" + "runtime" "sort" "strings" "text/tabwriter" "github.com/fatih/color" + "github.com/influxdata/go-prompt" "github.com/influxdata/influx-cli/v2/api" "github.com/influxdata/influx-cli/v2/clients" ) @@ -39,11 +42,59 @@ type PersistentQueryParams struct { Format FormatType Pretty bool // Autocompletion Storage + historyFilePath string + historyLimit int Databases []string RetentionPolicies []string Measurements []string } +func DefaultPersistentQueryParams() PersistentQueryParams { + return PersistentQueryParams{ + Format: ColumnFormat, + Precision: "ns", + historyLimit: 1000, + } +} + +func (c *Client) readHistory() []string { + // Attempt to load the history file. + if c.historyFilePath != "" { + if historyFile, err := os.Open(c.historyFilePath); err == nil { + var history []string + scanner := bufio.NewScanner(historyFile) + for scanner.Scan() { + history = append(history, scanner.Text()) + } + historyFile.Close() + // Limit to last n elements + if len(history) > c.historyLimit { + history = history[len(history)-c.historyLimit:] + } + return history + } + } + return []string{} +} + +func (c *Client) rewriteHistoryFile(history []string) { + if c.historyFilePath != "" { + if historyFile, err := os.Create(c.historyFilePath); err == nil { + historyFile.WriteString(strings.Join(history, "\n")) + historyFile.Close() + } + } +} + +func (c *Client) writeCommandToHistory(cmd string) { + if c.historyFilePath != "" { + if historyFile, err := os.OpenFile(c.historyFilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666); err == nil { + historyFile.WriteString(cmd + "\n") + historyFile.Close() + } + } +} + func (c *Client) clear(cmd string) { args := strings.Split(strings.TrimSuffix(strings.TrimSpace(cmd), ";"), " ") v := strings.ToLower(strings.Join(args[1:], " ")) @@ -73,13 +124,6 @@ func (c *Client) clear(cmd string) { } } -func DefaultPersistentQueryParams() PersistentQueryParams { - return PersistentQueryParams{ - Format: ColumnFormat, - Precision: "ns", - } -} - func (c *Client) Create(ctx context.Context) error { res, err := c.GetPing(ctx).ExecuteWithHttpInfo() if err != nil { @@ -90,15 +134,35 @@ func (c *Client) Create(ctx context.Context) error { version := res.Header.Get("X-Influxdb-Version") color.Cyan("Connected to InfluxDB %s %s", build, version) - c.Databases, _ = c.GetDatabases(ctx) - color.Cyan("InfluxQL Shell") - - scanner := bufio.NewScanner(os.Stdin) - fmt.Printf("%s", color.GreenString("> ")) - for scanner.Scan() { - c.executor(scanner.Text()) - fmt.Printf("%s", color.GreenString("> ")) + // compute historyFilePath at REPL start + // Only load/write history if HOME environment variable is set. + var historyDir string + if runtime.GOOS == "windows" { + if userDir := os.Getenv("USERPROFILE"); userDir != "" { + historyDir = userDir + } } + if homeDir := os.Getenv("HOME"); homeDir != "" { + historyDir = homeDir + } + var history []string + if historyDir != "" { + c.historyFilePath = filepath.Join(historyDir, ".influx_history") + history = c.readHistory() + // rewriting history now truncates the history file down to c.historyLimit lines of history + c.rewriteHistoryFile(history) + } + + p := prompt.New(c.executor, + c.completer, + prompt.OptionTitle("InfluxQL Shell"), + prompt.OptionHistory(history), + prompt.OptionDescriptionTextColor(prompt.Cyan), + prompt.OptionPrefixTextColor(prompt.Green), + prompt.OptionCompletionWordSeparator(" ", "."), + ) + c.Databases, _ = c.GetDatabases(ctx) + p.Run() return nil } @@ -111,6 +175,7 @@ func (c *Client) executor(cmd string) { if cmd == "" { return } + defer c.writeCommandToHistory(cmd) cmdArgs := strings.Split(cmd, " ") switch strings.ToLower(cmdArgs[0]) { case "quit", "exit": @@ -125,7 +190,7 @@ func (c *Client) executor(cmd string) { case "help": c.help() case "history": - color.Yellow("The 'history' command is not yet implemented in 2.x") + color.HiBlack(strings.Join(c.readHistory(), "\n")) case "format": c.setFormat(cmdArgs) case "precision": @@ -225,7 +290,7 @@ func (c Client) insert(cmd string) { switch c.Precision { case "h", "m", "rfc3339": - color.Red("Current precision %q unsupported for writes. Use [s, ms, ns, us]", c.Precision) + color.Red("Current precision %q unsupported for writes. Use precision [s, ms, ns, us]", c.Precision) return } ctx := context.Background() diff --git a/clients/v1_repl/v1_repl_test.go b/clients/v1_shell/v1_shell_test.go similarity index 93% rename from clients/v1_repl/v1_repl_test.go rename to clients/v1_shell/v1_shell_test.go index 33e96c7..cde91b2 100644 --- a/clients/v1_repl/v1_repl_test.go +++ b/clients/v1_shell/v1_shell_test.go @@ -1,9 +1,9 @@ -package v1repl_test +package v1shell_test import ( "testing" - v1repl "github.com/influxdata/influx-cli/v2/clients/v1_repl" + v1shell "github.com/influxdata/influx-cli/v2/clients/v1_shell" ) var point = `weather,location=us-midwest temperature=82 1465839830100400200` @@ -20,7 +20,7 @@ type InsertTestCase struct { } func (it *InsertTestCase) Test(t *testing.T) { - db, rp, point, isInsert := v1repl.ParseInsert(it.cmd) + db, rp, point, isInsert := v1shell.ParseInsert(it.cmd) if !isInsert { t.Errorf("%q should be a valid INSERT command", it.cmd) } else { @@ -135,7 +135,7 @@ func TestParseInsertInto(t *testing.T) { func TestParseInsertInvalid(t *testing.T) { t.Parallel() for _, cmd := range invalidCmds { - if _, _, _, isValid := v1repl.ParseInsert(cmd); isValid { + if _, _, _, isValid := v1shell.ParseInsert(cmd); isValid { t.Errorf("%q should be an invalid INSERT command", cmd) } } diff --git a/cmd/influx/v1_commands.go b/cmd/influx/v1_commands.go index d8086d4..0f0039c 100644 --- a/cmd/influx/v1_commands.go +++ b/cmd/influx/v1_commands.go @@ -13,7 +13,7 @@ func newV1SubCommand() cli.Command { Subcommands: []cli.Command{ newV1DBRPCmd(), newV1AuthCommand(), - newV1ReplCmd(), + newV1ShellCmd(), }, } } diff --git a/cmd/influx/v1_repl.go b/cmd/influx/v1_shell.go similarity index 77% rename from cmd/influx/v1_repl.go rename to cmd/influx/v1_shell.go index 2b0ae9b..9d2fdf0 100644 --- a/cmd/influx/v1_repl.go +++ b/cmd/influx/v1_shell.go @@ -4,7 +4,7 @@ import ( "github.com/fatih/color" "github.com/influxdata/influx-cli/v2/api" "github.com/influxdata/influx-cli/v2/clients" - repl "github.com/influxdata/influx-cli/v2/clients/v1_repl" + shell "github.com/influxdata/influx-cli/v2/clients/v1_shell" "github.com/influxdata/influx-cli/v2/pkg/cli/middleware" "github.com/urfave/cli" ) @@ -14,13 +14,13 @@ type Client struct { api.LegacyQueryApi } -func newV1ReplCmd() cli.Command { +func newV1ShellCmd() cli.Command { var orgParams clients.OrgParams - persistentQueryParams := repl.DefaultPersistentQueryParams() + persistentQueryParams := shell.DefaultPersistentQueryParams() return cli.Command{ - Name: "repl", - Usage: "Start an InfluxQL REPL", - Description: "Start an InfluxQL REPL", + Name: "shell", + Usage: "Start an InfluxQL shell", + Description: "Start an InfluxQL shell", Before: middleware.WithBeforeFns(withCli(), withApi(true)), Flags: append(commonFlagsNoPrint(), getOrgFlags(&orgParams)...), Action: func(ctx *cli.Context) error { @@ -28,7 +28,7 @@ func newV1ReplCmd() cli.Command { return err } api := getAPI(ctx) - c := repl.Client{ + c := shell.Client{ CLI: getCLI(ctx), PersistentQueryParams: persistentQueryParams, PingApi: api.PingApi, diff --git a/go.mod b/go.mod index 1276f5a..98b37aa 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,7 @@ require ( github.com/gocarina/gocsv v0.0.0-20210408192840-02d7211d929d github.com/golang/mock v1.5.0 github.com/google/go-jsonnet v0.17.0 + github.com/influxdata/go-prompt v0.2.7 github.com/mattn/go-isatty v0.0.14 github.com/olekukonko/tablewriter v0.0.5 github.com/stretchr/testify v1.7.0 @@ -30,7 +31,9 @@ require ( github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect github.com/mattn/go-colorable v0.1.12 // indirect github.com/mattn/go-runewidth v0.0.13 // indirect + github.com/mattn/go-tty v0.0.4 // indirect github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect + github.com/pkg/term v1.2.0-beta.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rivo/uniseg v0.2.0 // indirect github.com/russross/blackfriday/v2 v2.0.1 // indirect diff --git a/go.sum b/go.sum index abf0fa3..de3dba7 100644 --- a/go.sum +++ b/go.sum @@ -33,6 +33,8 @@ github.com/google/go-jsonnet v0.17.0 h1:/9NIEfhK1NQRKl3sP2536b2+x5HnZMdql7x3yK/l github.com/google/go-jsonnet v0.17.0/go.mod h1:sOcuej3UW1vpPTZOr8L7RQimqai1a57bt5j22LzGZCw= github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog= github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68= +github.com/influxdata/go-prompt v0.2.7 h1:LoJCo+imRHw4Md1Y6SWFtLRpuukAHXkQ6pWS+DVTiMM= +github.com/influxdata/go-prompt v0.2.7/go.mod h1:sK0TeJSbAWCKVby14Tx85GJE4BZpIZpguBBmFqAqruE= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= @@ -45,16 +47,22 @@ github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVc github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +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.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-tty v0.0.4 h1:NVikla9X8MN0SQAqCYzpGyXv0jY7MNl3HOWD2dkle7E= +github.com/mattn/go-tty v0.0.4/go.mod h1:u5GGXBtZU6RQoKV8gY5W6UhMudbR5vXnUe7j3pxse28= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/pkg/term v1.2.0-beta.2 h1:L3y/h2jkuBVFdWiJvNfYfKmzcCnILw7mJWm2JQuMppw= +github.com/pkg/term v1.2.0-beta.2/go.mod h1:E25nymQcrSllhX42Ok8MRm1+hyBdHY0dCeiKZ9jpNGw= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= @@ -92,7 +100,10 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=