From 9dc1b8e4b1e0f1039077a537853a2ca74bdba453 Mon Sep 17 00:00:00 2001 From: Andrew Lee <32912555+candrewlee14@users.noreply.github.com> Date: Fri, 24 Jun 2022 09:08:51 -0600 Subject: [PATCH] feat: add pretty table format to REPL (#393) * add v1-compatible query path and refactor other paths to de-duplicate "/query" * add initial influxQL repl * add ping endpoint to schema * improve prompt UX, implement some commands * fix json column type in schema and improve completion * feat: add table formatter and move to forked go-prompt * improve formatting and add table pagination * implement more REPL commands, including insert and history * implement "INSERT INTO" * move repl command to "v1 repl" * refactor and improve documentation * clean up v1_repl cmd * update to latest openapi, use some openapi paths instead of overrides * remove additional files that were moved to openapi * compute historyFilePath at REPL start * clean up REPL use command logic flow * clean up comments for TODOs now in issues * move gopher (chonky boi) * remove autocompletion for separate PR * run go mod tidy * add rfc3339 precision option * allow left and right column scrolling to display whole table * add error to JSON query response * add tags and partial to JSON response series schema * fix csv formatting and add column formatting * remove table format for separate PR * add pretty table format to REPL * fix getDatabases * move from write to legacy write endpoint for INSERT * remove history vestiges * allow multiple spaces in INSERT commands * add precision comment * remove auth for separate PR * separate parseInsert and add unit test * add additional test case and improve error messages * fix missing errors import * update for simpler horizontal scrolling in table mode * improve dialog printing * improve table format and interactions * jump to first page on shift-up * add keybinding info * change wording from result to table, flip --- clients/v1_shell/autocomplete.go | 1 + clients/v1_shell/table_model.go | 231 +++++++++++++++++++++++++++++++ clients/v1_shell/v1_shell.go | 80 +++++++++-- go.mod | 13 +- go.sum | 41 +++++- 5 files changed, 354 insertions(+), 12 deletions(-) create mode 100644 clients/v1_shell/table_model.go diff --git a/clients/v1_shell/autocomplete.go b/clients/v1_shell/autocomplete.go index 80d9dba..47d5ce4 100644 --- a/clients/v1_shell/autocomplete.go +++ b/clients/v1_shell/autocomplete.go @@ -148,6 +148,7 @@ func (c *Client) completer(d prompt.Document) []prompt.Suggest { "column", "csv", "json", + "table", )}, "SELECT": {subsuggestFn: c.suggestSelect}, "INSERT": {}, diff --git a/clients/v1_shell/table_model.go b/clients/v1_shell/table_model.go new file mode 100644 index 0000000..8877646 --- /dev/null +++ b/clients/v1_shell/table_model.go @@ -0,0 +1,231 @@ +package v1shell + +import ( + "fmt" + "os" + "strings" + + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" + "github.com/evertras/bubble-table/table" + "github.com/fatih/color" + "github.com/influxdata/influx-cli/v2/api" + "github.com/mattn/go-isatty" +) + +const ( + colPadding int = 2 +) + +type EndingStatus uint + +const ( + quitStatus EndingStatus = iota + goToPrevTableStatus + goToPrevTableJumpFirstPageStatus + goToNextTableStatus +) + +type Model struct { + name string + tags map[string]string + curResult int + resultMax int + curSeries int + seriesMax int + rows []table.Row + allCols []table.Column + colNames []string + simpleTable table.Model + totalWidth int + endingStatus EndingStatus + tableScreenPadding int +} + +func NewModel( + res api.InfluxqlJsonResponseSeries, + jumpToFirstPage bool, + name string, + tags map[string]string, + curRes int, + resMax int, + curSer int, + serMax int) Model { + + cols := make([]table.Column, len(*res.Columns)+1) + colWidths := make([]int, len(*res.Columns)+1) + rows := make([]table.Row, len(*res.Values)) + colNames := *res.Columns + for rowI, row := range *res.Values { + rd := table.RowData{} + rd["index"] = fmt.Sprintf("%d", rowI+1) + colWidths[0] = len("index") + colPadding + for colI, rowVal := range row { + var item string + switch val := rowVal.(type) { + case int: + item = fmt.Sprintf("%d", val) + case string: + item = fmt.Sprintf("%q", val) + default: + item = fmt.Sprintf("%v", val) + } + rd[colNames[colI]] = item + if colWidths[colI+1] < len(item)+colPadding { + colWidths[colI+1] = len(item) + colPadding + } + } + rows[rowI] = table.NewRow(rd). + WithStyle(lipgloss.NewStyle().Align(lipgloss.Center)) + } + cols[0] = table.NewColumn("index", "index", colWidths[0]) + indexStyle := lipgloss.NewStyle() + if isatty.IsTerminal(os.Stdout.Fd()) { + indexStyle = indexStyle. + Faint(true). + Align(lipgloss.Center) + } + cols[0] = cols[0].WithStyle(indexStyle) + for colI, colTitle := range colNames { + if colWidths[colI+1] < len(colTitle)+colPadding { + colWidths[colI+1] = len(colTitle) + colPadding + } + cols[colI+1] = table.NewColumn(colTitle, color.HiCyanString(colTitle), colWidths[colI+1]). + WithStyle(lipgloss.NewStyle().Align(lipgloss.Center)) + } + colNames = append([]string{"index"}, colNames...) + screenPadding := 10 + if len(tags) > 0 { + screenPadding++ + } + m := Model{ + name: name, + tags: tags, + curResult: curRes, + resultMax: resMax, + curSeries: curSer, + seriesMax: serMax, + rows: rows, + allCols: cols, + colNames: colNames, + tableScreenPadding: screenPadding, + } + keybind := table.DefaultKeyMap() + keybind.RowUp.SetEnabled(false) + keybind.RowDown.SetEnabled(false) + keybind.PageUp.SetEnabled(false) + keybind.PageDown.SetEnabled(false) + keybind.ScrollLeft.SetKeys("left") + keybind.ScrollRight.SetKeys("right") + keybind.Filter.Unbind() + keybind.FilterBlur.Unbind() + keybind.FilterClear.Unbind() + + m.simpleTable = table.New(m.allCols). + WithRows(m.rows). + WithPageSize(15). + WithMaxTotalWidth(500). + WithHorizontalFreezeColumnCount(1). + WithStaticFooter( + fmt.Sprintf("%d Columns, %d Rows, Page %d/%d", + len(m.allCols), len(m.rows), m.simpleTable.CurrentPage(), m.simpleTable.MaxPages())). + HighlightStyle(lipgloss.NewStyle()). + Focused(true). + SelectableRows(false). + WithKeyMap(keybind) + + if jumpToFirstPage { + m.simpleTable = m.simpleTable.PageLast() + } + return m +} + +func (m Model) Init() tea.Cmd { + color.Magenta("Interactive Table View (press q to exit mode, shift+up/down to navigate tables):") + builder := strings.Builder{} + fmt.Printf("Name: %s\n", color.GreenString(m.name)) + if len(m.tags) > 0 { + fmt.Print("Tags: ") + for key, val := range m.tags { + if key == "" || val == "" { + continue + } + builder.WriteString(fmt.Sprintf("%s=%s, ", color.YellowString(key), color.CyanString(val))) + } + tagline := builder.String() + fmt.Print(tagline[:len(tagline)-2]) + fmt.Println("") + } + return nil +} + +func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + var ( + cmd tea.Cmd + cmds []tea.Cmd + ) + + m.simpleTable, cmd = m.simpleTable.Update(msg) + cmds = append(cmds, cmd) + + switch msg := msg.(type) { + case tea.KeyMsg: + switch msg.String() { + case "ctrl+c", "ctrl+d", "esc", "q": + m.simpleTable = m.simpleTable.Focused(false) + fmt.Printf("\n") + m.endingStatus = quitStatus + cmds = append(cmds, tea.Quit) + case "shift+up": + if !(m.curResult == 1 && m.curSeries == 1) { + m.endingStatus = goToPrevTableJumpFirstPageStatus + cmds = append(cmds, tea.Quit) + } + case "shift+down": + if !(m.curResult == m.resultMax && m.curSeries == m.seriesMax) { + m.endingStatus = goToNextTableStatus + cmds = append(cmds, tea.Quit) + } + case "up": + if m.simpleTable.CurrentPage() == 1 { + if !(m.curResult == 1 && m.curSeries == 1) { + m.endingStatus = goToPrevTableStatus + cmds = append(cmds, tea.Quit) + } + } else { + m.simpleTable = m.simpleTable.PageUp() + } + case "down": + if m.simpleTable.CurrentPage() == m.simpleTable.MaxPages() { + if !(m.curResult == m.resultMax && m.curSeries == m.seriesMax) { + m.endingStatus = goToNextTableStatus + cmds = append(cmds, tea.Quit) + } + } else { + m.simpleTable = m.simpleTable.PageDown() + } + case "<": + m.simpleTable = m.simpleTable.PageFirst() + case ">": + m.simpleTable = m.simpleTable.PageLast() + } + case tea.WindowSizeMsg: + m.totalWidth = msg.Width + m.simpleTable = m.simpleTable.WithPageSize(msg.Height - m.tableScreenPadding). + WithMaxTotalWidth(msg.Width) + } + m.simpleTable = m.simpleTable.WithStaticFooter( + fmt.Sprintf("%d Columns, %d Rows, Page %d/%d\nTable %d/%d, Statement %d/%d", + len(m.allCols), len(m.rows), m.simpleTable.CurrentPage(), m.simpleTable.MaxPages(), + m.curSeries, m.seriesMax, + m.curResult, m.resultMax)) + return m, tea.Batch(cmds...) +} + +func (m Model) View() string { + body := strings.Builder{} + + body.WriteString(m.simpleTable.View()) + body.WriteString("\n") + return body.String() +} diff --git a/clients/v1_shell/v1_shell.go b/clients/v1_shell/v1_shell.go index 49d7848..8a82759 100644 --- a/clients/v1_shell/v1_shell.go +++ b/clients/v1_shell/v1_shell.go @@ -18,6 +18,7 @@ import ( "strings" "text/tabwriter" + tea "github.com/charmbracelet/bubbletea" "github.com/fatih/color" "github.com/influxdata/go-prompt" "github.com/influxdata/influx-cli/v2/api" @@ -51,7 +52,7 @@ type PersistentQueryParams struct { func DefaultPersistentQueryParams() PersistentQueryParams { return PersistentQueryParams{ - Format: ColumnFormat, + Format: TableFormat, Precision: "ns", historyLimit: 1000, } @@ -89,7 +90,7 @@ func (c *Client) rewriteHistoryFile(history []string) { 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.WriteString(strings.TrimSpace(cmd) + "\n") historyFile.Close() } } @@ -326,6 +327,7 @@ var ( CsvFormat FormatType = "csv" JsonFormat FormatType = "json" ColumnFormat FormatType = "column" + TableFormat FormatType = "table" ) func (c *Client) runAndShowQuery(query string) { @@ -357,6 +359,7 @@ func (c *Client) runAndShowQuery(query string) { CsvFormat: c.outputCsv, JsonFormat: c.outputJson, ColumnFormat: c.outputColumns, + TableFormat: c.outputTable, } displayFunc := displayMap[c.Format] displayFunc(response) @@ -366,7 +369,7 @@ func (c *Client) help() { fmt.Println(`Usage: pretty toggles pretty print for the json format use sets current database - format specifies the format of the server responses: json, csv, column + format specifies the format of the server responses: json, csv, column, table precision specifies the format of the timestamp: rfc3339, h, m, s, ms, u or ns history displays command history settings outputs the current settings for the shell @@ -387,7 +390,9 @@ func (c *Client) help() { exit clear screen previous command - next command`) + next command + next suggestion + previous suggestion`) } func (c *Client) settings() { @@ -424,16 +429,16 @@ func (c *Client) query(ctx context.Context, query string) (string, error) { func (c *Client) setFormat(args []string) { // args[0] is "format" if len(args) != 2 { - color.Red("Expected a format [csv, json, column]") + color.Red("Expected a format [csv, json, column, table]") return } newFormat := FormatType(args[1]) switch newFormat { - case CsvFormat, JsonFormat, ColumnFormat: + case CsvFormat, JsonFormat, ColumnFormat, TableFormat: c.Format = newFormat default: color.HiRed("Unimplemented format %q, keeping %s format.", newFormat, c.Format) - color.HiBlack("Choose a format from [csv, json, column]") + color.HiBlack("Choose a format from [csv, json, column, table]") } } @@ -556,7 +561,7 @@ func (c *Client) outputCsv(response api.InfluxqlJsonResponse) { var previousHeaders api.InfluxqlJsonResponseSeries for _, result := range response.GetResults() { if result.Error != nil { - color.Red("Query Error: %v", result.GetError()) + color.Red("Error: %v", result.GetError()) continue } series := result.GetSeries() @@ -596,7 +601,7 @@ func (c *Client) outputColumns(response api.InfluxqlJsonResponse) { var previousHeaders api.InfluxqlJsonResponseSeries for i, result := range response.GetResults() { if result.Error != nil { - color.Red("Query Error: %v", result.GetError()) + color.Red("Error: %v", result.GetError()) continue } @@ -621,6 +626,63 @@ func (c *Client) outputColumns(response api.InfluxqlJsonResponse) { writer.Flush() } +func (c *Client) outputTable(response api.InfluxqlJsonResponse) { + allResults := response.GetResults() + resIdx := 0 + seriesIdx := 0 + jumpToLastPage := false +outer: + for resIdx < len(allResults) { + res := allResults[resIdx] + if res.Error != nil { + color.Red("Error: %v", res.GetError()) + resIdx++ + } + allSeries := res.GetSeries() + for seriesIdx < len(allSeries) { + series := allSeries[seriesIdx] + p := tea.NewProgram(NewModel(series, + jumpToLastPage, + series.GetName(), + series.GetTags(), + resIdx+1, + len(allResults), + seriesIdx+1, + len(allSeries))) + model, err := p.StartReturningModel() + jumpToLastPage = false + if err != nil { + color.Red("Failed to display table") + seriesIdx++ + } else { + if tableModel, ok := model.(Model); ok { + switch tableModel.endingStatus { + case goToNextTableStatus: + seriesIdx++ + case goToPrevTableStatus: + jumpToLastPage = true + seriesIdx-- + case goToPrevTableJumpFirstPageStatus: + seriesIdx-- + case quitStatus: + break outer + } + } + } + fmt.Printf("\n") + if seriesIdx >= len(allSeries) { + resIdx++ + seriesIdx = 0 + break + } else if seriesIdx < 0 { + resIdx-- + seriesIdx = len(allResults[resIdx].GetSeries()) - 1 + break + } + } + } +} + func (c *Client) togglePretty() { c.Pretty = !c.Pretty color.HiBlack("Pretty: %v", c.Pretty) diff --git a/go.mod b/go.mod index 98b37aa..09791f2 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,10 @@ require ( github.com/AlecAivazis/survey/v2 v2.3.4 github.com/BurntSushi/toml v0.4.1 github.com/MakeNowJust/heredoc/v2 v2.0.1 + github.com/charmbracelet/bubbletea v0.21.0 + github.com/charmbracelet/lipgloss v0.5.0 github.com/daixiang0/gci v0.2.8 + github.com/evertras/bubble-table v0.13.7 github.com/fatih/color v1.9.0 github.com/fujiwara/shapeio v1.0.0 github.com/gocarina/gocsv v0.0.0-20210408192840-02d7211d929d @@ -26,13 +29,21 @@ require ( ) require ( + github.com/atotto/clipboard v0.1.4 // indirect + github.com/charmbracelet/bubbles v0.11.0 // indirect + github.com/containerd/console v1.0.3 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect + github.com/lucasb-eyer/go-colorful v1.2.0 // 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/muesli/ansi v0.0.0-20211031195517-c9f0611b6c70 // indirect + github.com/muesli/cancelreader v0.2.0 // indirect + github.com/muesli/reflow v0.3.0 // indirect + github.com/muesli/termenv v0.12.0 // 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 @@ -42,7 +53,7 @@ require ( golang.org/x/exp/typeparams v0.0.0-20220218215828-6cf2b201936e // indirect golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect - golang.org/x/term v0.0.0-20220411215600-e5f449aeb171 // indirect + golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 // indirect golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect ) diff --git a/go.sum b/go.sum index de3dba7..ebc02c5 100644 --- a/go.sum +++ b/go.sum @@ -7,6 +7,17 @@ github.com/MakeNowJust/heredoc/v2 v2.0.1 h1:rlCHh70XXXv7toz95ajQWOWQnN4WNLt0TdpZ github.com/MakeNowJust/heredoc/v2 v2.0.1/go.mod h1:6/2Abh5s+hc3g9nbWLe9ObDIOhaRrqsyY9MWy+4JdRM= github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s= github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w= +github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= +github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= +github.com/charmbracelet/bubbles v0.11.0 h1:fBLyY0PvJnd56Vlu5L84JJH6f4axhgIJ9P3NET78f0Q= +github.com/charmbracelet/bubbles v0.11.0/go.mod h1:bbeTiXwPww4M031aGi8UK2HT9RDWoiNibae+1yCMtcc= +github.com/charmbracelet/bubbletea v0.21.0 h1:f3y+kanzgev5PA916qxmDybSHU3N804uOnKnhRPXTcI= +github.com/charmbracelet/bubbletea v0.21.0/go.mod h1:GgmJMec61d08zXsOhqRC/AiOx4K4pmz+VIcRIm1FKr4= +github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao= +github.com/charmbracelet/lipgloss v0.5.0 h1:lulQHuVeodSgDez+3rGiuxlPVXSnhth442DATR2/8t8= +github.com/charmbracelet/lipgloss v0.5.0/go.mod h1:EZLha/HbzEt7cYqdFPovlqy5FZPj0xFhg5SaqxScmgs= +github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw= +github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.17 h1:QeVUsEDNrLBW4tMgZHvxy18sKtr6VI492kBhUfhDJNI= @@ -18,6 +29,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/evertras/bubble-table v0.13.7 h1:XFwiax3ZEOG8P0qlZE3vgXsfYMJNunz5M4pZg4YPJU4= +github.com/evertras/bubble-table v0.13.7/go.mod h1:SPOZKbIpyYWPHBNki3fyNpiPBQkvkULAtOT7NTD5fKY= github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fujiwara/shapeio v1.0.0 h1:xG5D9oNqCSUUbryZ/jQV3cqe1v2suEjwPIcEg1gKM8M= @@ -42,6 +55,9 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= +github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= @@ -53,22 +69,38 @@ github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9 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.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= +github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= 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/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho= +github.com/muesli/ansi v0.0.0-20211031195517-c9f0611b6c70 h1:kMlmsLSbjkikxQJ1IPwaM+7LJ9ltFu/fi8CRzvSnQmA= +github.com/muesli/ansi v0.0.0-20211031195517-c9f0611b6c70/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho= +github.com/muesli/cancelreader v0.2.0 h1:SOpr+CfyVNce341kKqvbhhzQhBPyJRXQaCtn03Pae1Q= +github.com/muesli/cancelreader v0.2.0/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= +github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68/go.mod h1:Xk+z4oIWdQqJzsxyjgl3P22oYZnHdZ8FFTHAQQt5BMQ= +github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= +github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= +github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs= +github.com/muesli/termenv v0.11.1-0.20220212125758-44cd13922739/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs= +github.com/muesli/termenv v0.12.0 h1:KuQRUE3PgxRFWhq4gHvZtPSLCGDqM5q/cYr1pZ39ytc= +github.com/muesli/termenv v0.12.0/go.mod h1:WCCv32tusQ/EEZ5S8oUIIrC/nIuBcxCVqlN4Xfkv+7A= 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.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= @@ -107,13 +139,18 @@ golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7w 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= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20210503060354-a79de5458b56/go.mod h1:tfny5GFUkzUvx4ps4ajbZsCe5lw1metzhBm9T3x7oIY= -golang.org/x/term v0.0.0-20220411215600-e5f449aeb171 h1:EH1Deb8WZJ0xc0WK//leUHXcX9aLE5SymusoTmMZye8= -golang.org/x/term v0.0.0-20220411215600-e5f449aeb171/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 h1:CBpWXWQpIRjzmkkA+M7q9Fqnwd2mZr3AFqexg8YTfoM= +golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=