feat: port table-printers from influxdb (#145)

This commit is contained in:
Daniel Moran 2021-06-24 11:31:19 -04:00 committed by GitHub
parent 1183d3780b
commit fb2d19c884
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 452 additions and 0 deletions

2
go.mod
View File

@ -7,12 +7,14 @@ require (
github.com/BurntSushi/toml v0.3.1
github.com/MakeNowJust/heredoc/v2 v2.0.1
github.com/daixiang0/gci v0.2.8
github.com/fatih/color v1.9.0
github.com/fujiwara/shapeio v1.0.0
github.com/gocarina/gocsv v0.0.0-20210408192840-02d7211d929d
github.com/golang/mock v1.5.0
github.com/google/go-cmp v0.5.5
github.com/google/go-jsonnet v0.17.0
github.com/mattn/go-isatty v0.0.13
github.com/olekukonko/tablewriter v0.0.5
github.com/stretchr/testify v1.7.0
github.com/urfave/cli/v2 v2.3.0
golang.org/x/text v0.3.3

5
go.sum
View File

@ -15,6 +15,7 @@ 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/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=
github.com/fujiwara/shapeio v1.0.0/go.mod h1:LmEmu6L/8jetyj1oewewFb7bZCNRwE7wLCUNzDLaLVA=
@ -45,8 +46,12 @@ github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA=
github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=

18
pkg/template/color.go Normal file
View File

@ -0,0 +1,18 @@
package template
import (
"github.com/fatih/color"
"github.com/olekukonko/tablewriter"
)
var (
colorTitle = color.New(color.FgYellow, color.Bold)
colorTotalAdd = color.New(color.FgHiGreen, color.Bold)
colorTotalRemove = color.New(color.FgRed, color.Bold)
noColor = tablewriter.Colors{}
colorAdd = tablewriter.Colors{tablewriter.FgHiGreenColor, tablewriter.Bold}
colorFooter = tablewriter.Color(tablewriter.FgHiBlueColor, tablewriter.Bold)
colorHeader = tablewriter.Colors{tablewriter.FgHiCyanColor, tablewriter.Bold}
colorRemove = tablewriter.Colors{tablewriter.FgRedColor, tablewriter.Bold}
)

View File

@ -0,0 +1,180 @@
package template
import (
"fmt"
"io"
"strconv"
"strings"
"github.com/olekukonko/tablewriter"
)
type DiffPrinter struct {
w io.Writer
writer *tablewriter.Table
useColor bool
title string
appendCalls int
headerLen int
}
func NewDiffPrinter(w io.Writer, hasColor, hasBorder bool) *DiffPrinter {
wr := tablewriter.NewWriter(w)
wr.SetBorder(hasBorder)
wr.SetRowLine(hasBorder)
return &DiffPrinter{
w: w,
writer: wr,
useColor: hasColor,
}
}
func (d *DiffPrinter) Render() {
if d.appendCalls == 0 {
return
}
// set the title and the add/remove legend
title := strings.ToUpper(d.title)
add := "+add"
remove := "-remove"
if d.useColor {
title = colorTitle.Sprint(title)
add = colorTotalAdd.Sprint(add)
remove = colorTotalRemove.Sprint(remove)
}
fmt.Fprintf(d.w, "%s %s | %s | unchanged\n", title, add, remove)
d.setFooter()
d.writer.Render()
}
func (d *DiffPrinter) Title(title string) *DiffPrinter {
d.title = title
return d
}
func (d *DiffPrinter) SetHeaders(headers ...string) *DiffPrinter {
headers = d.prepend(headers, "+/-")
d.headerLen = len(headers)
d.writer.SetHeader(headers)
headerColors := make([]tablewriter.Colors, d.headerLen)
color := noColor
if d.useColor {
color = colorHeader
}
for i := range headerColors {
headerColors[i] = color
}
d.writer.SetHeaderColor(headerColors...)
return d
}
func (d *DiffPrinter) setFooter() *DiffPrinter {
footers := make([]string, d.headerLen)
if d.headerLen > 1 {
footers[len(footers)-2] = "TOTAL"
footers[len(footers)-1] = strconv.Itoa(d.appendCalls)
} else {
footers[0] = "TOTAL: " + strconv.Itoa(d.appendCalls)
}
d.writer.SetFooter(footers)
colors := make([]tablewriter.Colors, d.headerLen)
color := noColor
if d.useColor {
color = colorFooter
}
if d.headerLen > 1 {
colors[len(colors)-2] = color
colors[len(colors)-1] = color
} else {
colors[0] = color
}
d.writer.SetFooterColor(colors...)
return d
}
func (d *DiffPrinter) Append(slc []string) {
d.writer.Append(d.prepend(slc, ""))
}
func (d *DiffPrinter) AppendDiff(remove, add []string) {
defer func() { d.appendCalls++ }()
if d.appendCalls > 0 {
d.appendBufferLine()
}
lenAdd, lenRemove := len(add), len(remove)
preppedAdd, preppedRemove := d.prepend(add, "+"), d.prepend(remove, "-")
if lenRemove > 0 && lenAdd == 0 {
d.writer.Rich(preppedRemove, d.redRow(len(preppedRemove)))
return
}
if lenAdd > 0 && lenRemove == 0 {
d.writer.Rich(preppedAdd, d.greenRow(len(preppedAdd)))
return
}
var (
addColors = make([]tablewriter.Colors, len(preppedAdd))
removeColors = make([]tablewriter.Colors, len(preppedRemove))
hasDiff bool
)
addColor, removeColor := noColor, noColor
if d.useColor {
addColor, removeColor = colorAdd, colorRemove
}
for i := 0; i < lenRemove; i++ {
if add[i] != remove[i] {
hasDiff = true
// offset to skip prepended +/- column
addColors[i+1], removeColors[i+1] = addColor, removeColor
}
}
if !hasDiff {
d.writer.Append(d.prepend(add, ""))
return
}
addColors[0], removeColors[0] = addColor, removeColor
d.writer.Rich(d.prepend(remove, "-"), removeColors)
d.writer.Rich(d.prepend(add, "+"), addColors)
}
func (d *DiffPrinter) appendBufferLine() {
d.writer.Append([]string{})
}
func (d *DiffPrinter) redRow(i int) []tablewriter.Colors {
return d.colorRow(colorRemove, i)
}
func (d *DiffPrinter) greenRow(i int) []tablewriter.Colors {
return d.colorRow(colorAdd, i)
}
func (d *DiffPrinter) prepend(slc []string, val string) []string {
return append([]string{val}, slc...)
}
func (d *DiffPrinter) colorRow(color tablewriter.Colors, i int) []tablewriter.Colors {
colors := make([]tablewriter.Colors, i)
for i := range colors {
if d.useColor {
colors[i] = color
} else {
colors[i] = noColor
}
}
return colors
}

View File

@ -0,0 +1,67 @@
package template_test
import (
"bytes"
"testing"
"github.com/influxdata/influx-cli/v2/pkg/template"
"github.com/stretchr/testify/require"
)
func TestDiffPrinter_Empty(t *testing.T) {
t.Parallel()
out := bytes.Buffer{}
printer := template.NewDiffPrinter(&out, false, true).
Title("Example").
SetHeaders("Wow", "Such", "A", "Fancy", "Printer")
printer.Render()
require.Empty(t, out.String())
}
func TestDiffPrinter(t *testing.T) {
t.Parallel()
out := bytes.Buffer{}
printer := template.NewDiffPrinter(&out, false, true).
Title("Example").
SetHeaders("Wow", "Such", "A", "Fancy", "Printer")
// Add
printer.AppendDiff(nil, []string{"A", "B", "C", "D", "E"})
// No change
printer.Append([]string{"foo", "bar", "baz", "qux", "wat"})
// Replace
printer.AppendDiff(
[]string{"1", "200000000000000", "3", "4", "5"},
[]string{"9", "8", "7", "6", "5"},
)
// Remove
printer.AppendDiff([]string{"x y", "z x", "x y z", "", "y z"}, nil)
printer.Render()
expected := `EXAMPLE +add | -remove | unchanged
+-----+-----+-----------------+-------+-------+---------+
| +/- | WOW | SUCH | A | FANCY | PRINTER |
+-----+-----+-----------------+-------+-------+---------+
| + | A | B | C | D | E |
+-----+-----+-----------------+-------+-------+---------+
| | foo | bar | baz | qux | wat |
+-----+-----+-----------------+-------+-------+---------+
+-----+-----+-----------------+-------+-------+---------+
| - | 1 | 200000000000000 | 3 | 4 | 5 |
+-----+-----+-----------------+-------+-------+---------+
| + | 9 | 8 | 7 | 6 | 5 |
+-----+-----+-----------------+-------+-------+---------+
+-----+-----+-----------------+-------+-------+---------+
| - | x y | z x | x y z | | y z |
+-----+-----+-----------------+-------+-------+---------+
| TOTAL | 3 |
+-----+-----+-----------------+-------+-------+---------+
`
require.Equal(t, expected, out.String())
}

View File

@ -0,0 +1,110 @@
package template
import (
"fmt"
"io"
"strconv"
"strings"
"github.com/olekukonko/tablewriter"
)
type TablePrinter struct {
w io.Writer
writer *tablewriter.Table
useColor bool
title string
headerLen int
appendCalls int
}
func NewTablePrinter(w io.Writer, hasColor, hasBorder bool) *TablePrinter {
wr := tablewriter.NewWriter(w)
wr.SetBorder(hasBorder)
wr.SetRowLine(hasBorder)
return &TablePrinter{
w: w,
writer: wr,
useColor: hasColor,
}
}
func (t *TablePrinter) Render() {
if t.appendCalls == 0 {
return
}
title := strings.ToUpper(t.title)
if t.useColor {
title = colorTitle.Sprint(title)
}
fmt.Fprintln(t.w, title)
t.setFooter()
t.writer.Render()
}
func (t *TablePrinter) Title(title string) *TablePrinter {
t.title = title
return t
}
func (t *TablePrinter) SetHeaders(headers ...string) *TablePrinter {
t.headerLen = len(headers)
t.writer.SetHeader(headers)
headerColors := make([]tablewriter.Colors, t.headerLen)
alignments := make([]int, t.headerLen)
color := noColor
if t.useColor {
color = colorHeader
}
for i, header := range headers {
headerColors[i] = color
if strings.EqualFold("description", header) {
t.writer.SetColMinWidth(i, 30)
alignments[i] = tablewriter.ALIGN_LEFT
} else {
alignments[i] = tablewriter.ALIGN_CENTER
}
}
t.writer.SetHeaderColor(headerColors...)
t.writer.SetColumnAlignment(alignments)
return t
}
func (t *TablePrinter) setFooter() *TablePrinter {
footers := make([]string, t.headerLen)
if t.headerLen > 1 {
footers[len(footers)-2] = "TOTAL"
footers[len(footers)-1] = strconv.Itoa(t.appendCalls)
} else {
footers[0] = "TOTAL: " + strconv.Itoa(t.appendCalls)
}
t.writer.SetFooter(footers)
colors := make([]tablewriter.Colors, t.headerLen)
color := noColor
if t.useColor {
color = colorFooter
}
if t.headerLen > 1 {
colors[len(colors)-2] = color
colors[len(colors)-1] = color
} else {
colors[0] = color
}
t.writer.SetFooterColor(colors...)
return t
}
func (t *TablePrinter) Append(slc []string) {
t.appendCalls++
t.writer.Append(slc)
}

View File

@ -0,0 +1,70 @@
package template_test
import (
"bytes"
"testing"
"github.com/influxdata/influx-cli/v2/pkg/template"
"github.com/stretchr/testify/require"
)
func TestTablePrinter_Empty(t *testing.T) {
t.Parallel()
out := bytes.Buffer{}
printer := template.NewTablePrinter(&out, false, true).
Title("Example").
SetHeaders("Wow", "Such", "A", "Fancy", "Printer")
printer.Render()
require.Empty(t, out.String())
}
func TestTablePrinter(t *testing.T) {
t.Parallel()
out := bytes.Buffer{}
printer := template.NewTablePrinter(&out, false, true).
Title("Example").
SetHeaders("Wow", "Such", "A", "Fancy", "Printer")
printer.Append([]string{"foo", "bar", "baz", "qux", "wat"})
printer.Append([]string{"veryveryverylongggg", "", "a", "b", "c"})
printer.Render()
expected := `EXAMPLE
+---------------------+------+-----+-------+---------+
| WOW | SUCH | A | FANCY | PRINTER |
+---------------------+------+-----+-------+---------+
| foo | bar | baz | qux | wat |
+---------------------+------+-----+-------+---------+
| veryveryverylongggg | | a | b | c |
+---------------------+------+-----+-------+---------+
| TOTAL | 2 |
+---------------------+------+-----+-------+---------+
`
require.Equal(t, expected, out.String())
}
func TestTablePrinter_Description(t *testing.T) {
t.Parallel()
out := bytes.Buffer{}
printer := template.NewTablePrinter(&out, false, true).
Title("Example").
SetHeaders("Wow", "Such", "A", "Fancy", "Description")
printer.Append([]string{"once", "upon", "a", "time", "short description"})
printer.Render()
// Expect that the description is left-aligned with a min width.
expected := `EXAMPLE
+------+------+---+-------+--------------------------------+
| WOW | SUCH | A | FANCY | DESCRIPTION |
+------+------+---+-------+--------------------------------+
| once | upon | a | time | short description |
+------+------+---+-------+--------------------------------+
| TOTAL | 1 |
+------+------+---+-------+--------------------------------+
`
require.Equal(t, expected, out.String())
}