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
This commit is contained in:
Andrew Lee 2022-06-24 09:08:51 -06:00 committed by GitHub
parent 85c690f1f1
commit 9dc1b8e4b1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 354 additions and 12 deletions

View File

@ -148,6 +148,7 @@ func (c *Client) completer(d prompt.Document) []prompt.Suggest {
"column",
"csv",
"json",
"table",
)},
"SELECT": {subsuggestFn: c.suggestSelect},
"INSERT": {},

View File

@ -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()
}

View File

@ -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 <db_name> sets current database
format <format> specifies the format of the server responses: json, csv, column
format <format> specifies the format of the server responses: json, csv, column, table
precision <format> 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() {
<CTRL+D> exit
<CTRL+L> clear screen
<UP ARROW> previous command
<DOWN ARROW> next command`)
<DOWN ARROW> next command
<TAB> next suggestion
<SHIFT+TAB> 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)

13
go.mod
View File

@ -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
)

41
go.sum
View File

@ -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=