Skip to content

Commit

Permalink
Redesign TablePrinter to avoid SetContentWidth / FitColumns steps
Browse files Browse the repository at this point in the history
The API is now:
- AddField;
- EndRow;
- Render.
  • Loading branch information
mislav committed Nov 20, 2019
1 parent 2022f8e commit 97a6dc4
Show file tree
Hide file tree
Showing 2 changed files with 137 additions and 77 deletions.
42 changes: 23 additions & 19 deletions command/pr.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,33 +167,37 @@ func prList(cmd *cobra.Command, args []string) error {
}

table := utils.NewTablePrinter(cmd.OutOrStdout())
for _, pr := range prs {
table.SetContentWidth(0, len(strconv.Itoa(pr.Number))+1)
table.SetContentWidth(1, len(pr.Title))
table.SetContentWidth(2, len(pr.HeadLabel()))
}

table.FitColumns()
table.SetColorFunc(2, utils.Cyan)

for _, pr := range prs {
prNum := strconv.Itoa(pr.Number)
if table.IsTTY {
if table.IsTTY() {
prNum = "#" + prNum
}
switch pr.State {
case "OPEN":
table.SetColorFunc(0, utils.Green)
case "CLOSED":
table.SetColorFunc(0, utils.Red)
case "MERGED":
table.SetColorFunc(0, utils.Magenta)
}
table.WriteRow(prNum, pr.Title, pr.HeadLabel())
table.AddField(prNum, nil, colorFuncForState(pr.State))
table.AddField(pr.Title, nil, nil)
table.AddField(pr.HeadLabel(), nil, utils.Cyan)
table.EndRow()
}
err = table.Render()
if err != nil {
return err
}

return nil
}

func colorFuncForState(state string) func(string) string {
switch state {
case "OPEN":
return utils.Green
case "CLOSED":
return utils.Red
case "MERGED":
return utils.Magenta
default:
return nil
}
}

func prView(cmd *cobra.Command, args []string) error {
ctx := contextForCommand(cmd)
baseRepo, err := ctx.BaseRepo()
Expand Down
172 changes: 114 additions & 58 deletions utils/table_printer.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,101 +8,157 @@ import (
"golang.org/x/crypto/ssh/terminal"
)

func NewTablePrinter(w io.Writer) *TTYTablePrinter {
tty := false
ttyWidth := 80
type TablePrinter interface {
IsTTY() bool
AddField(string, func(int, string) string, func(string) string)
EndRow()
Render() error
}

func NewTablePrinter(w io.Writer) TablePrinter {
if outFile, isFile := w.(*os.File); isFile {
fd := int(outFile.Fd())
tty = terminal.IsTerminal(fd)
if w, _, err := terminal.GetSize(fd); err == nil {
ttyWidth = w
if terminal.IsTerminal(fd) {
ttyWidth := 80
if w, _, err := terminal.GetSize(fd); err == nil {
ttyWidth = w
}
return &ttyTablePrinter{
out: w,
maxWidth: ttyWidth,
}
}
}
return &TTYTablePrinter{
out: w,
IsTTY: tty,
maxWidth: ttyWidth,
colWidths: make(map[int]int),
colFuncs: make(map[int]func(string) string),
return &tsvTablePrinter{
out: w,
}
}

type TTYTablePrinter struct {
out io.Writer
IsTTY bool
maxWidth int
colWidths map[int]int
colFuncs map[int]func(string) string
type tableField struct {
Text string
TruncateFunc func(int, string) string
ColorFunc func(string) string
}

func (t *TTYTablePrinter) SetContentWidth(col, width int) {
if width > t.colWidths[col] {
t.colWidths[col] = width
type ttyTablePrinter struct {
out io.Writer
maxWidth int
rows [][]tableField
}

func (t ttyTablePrinter) IsTTY() bool {
return true
}

func (t *ttyTablePrinter) AddField(text string, truncateFunc func(int, string) string, colorFunc func(string) string) {
if truncateFunc == nil {
truncateFunc = truncate
}
if t.rows == nil {
t.rows = [][]tableField{[]tableField{}}
}
rowI := len(t.rows) - 1
field := tableField{
Text: text,
TruncateFunc: truncateFunc,
ColorFunc: colorFunc,
}
t.rows[rowI] = append(t.rows[rowI], field)
}

func (t *TTYTablePrinter) SetColorFunc(col int, colorize func(string) string) {
t.colFuncs[col] = colorize
func (t *ttyTablePrinter) EndRow() {
t.rows = append(t.rows, []tableField{})
}

// FitColumns caps all but first column to fit available terminal width.
func (t *TTYTablePrinter) FitColumns() {
numCols := len(t.colWidths)
delimWidth := 2
availWidth := t.maxWidth - t.colWidths[0] - ((numCols - 1) * delimWidth)
func (t *ttyTablePrinter) Render() error {
if len(t.rows) == 0 {
return nil
}

numCols := len(t.rows[0])
colWidths := make([]int, numCols)
// measure maximum content width per column
for _, row := range t.rows {
for col, field := range row {
textLen := len(field.Text)
if textLen > colWidths[col] {
colWidths[col] = textLen
}
}
}

delim := " "
availWidth := t.maxWidth - colWidths[0] - ((numCols - 1) * len(delim))
// add extra space from columns that are already narrower than threshold
for col := 1; col < len(t.colWidths); col++ {
for col := 1; col < numCols; col++ {
availColWidth := availWidth / (numCols - 1)
if extra := availColWidth - t.colWidths[col]; extra > 0 {
if extra := availColWidth - colWidths[col]; extra > 0 {
availWidth += extra
}
}
// cap all but first column to fit available terminal width
// TODO: support weighted instead of even redistribution
for col := 1; col < len(t.colWidths); col++ {
for col := 1; col < numCols; col++ {
availColWidth := availWidth / (numCols - 1)
if t.colWidths[col] > availColWidth {
t.colWidths[col] = availColWidth
if colWidths[col] > availColWidth {
colWidths[col] = availColWidth
}
}
}

func (t *TTYTablePrinter) WriteRow(fields ...string) error {
lastCol := len(fields) - 1
delim := "\t"
if t.IsTTY {
delim = " "
}

for col, val := range fields {
if col > 0 {
_, err := fmt.Fprint(t.out, delim)
if err != nil {
return err
for _, row := range t.rows {
for col, field := range row {
if col > 0 {
_, err := fmt.Fprint(t.out, delim)
if err != nil {
return err
}
}
}
if t.IsTTY {
truncVal := truncate(t.colWidths[col], val)
if col != lastCol {
truncVal = fmt.Sprintf("%-*s", t.colWidths[col], truncVal)
truncVal := field.TruncateFunc(colWidths[col], field.Text)
if col < numCols-1 {
// pad value with spaces on the right
truncVal = fmt.Sprintf("%-*s", colWidths[col], truncVal)
}
if t.colFuncs[col] != nil {
truncVal = t.colFuncs[col](truncVal)
if field.ColorFunc != nil {
truncVal = field.ColorFunc(truncVal)
}
_, err := fmt.Fprint(t.out, truncVal)
if err != nil {
return err
}
} else {
_, err := fmt.Fprint(t.out, val)
}
if len(row) > 0 {
_, err := fmt.Fprint(t.out, "\n")
if err != nil {
return err
}
}
}
_, err := fmt.Fprint(t.out, "\n")
if err != nil {
return err
return nil
}

type tsvTablePrinter struct {
out io.Writer
currentCol int
}

func (t tsvTablePrinter) IsTTY() bool {
return false
}

func (t *tsvTablePrinter) AddField(text string, _ func(int, string) string, _ func(string) string) {
if t.currentCol > 0 {
fmt.Fprint(t.out, "\t")
}
fmt.Fprint(t.out, text)
t.currentCol++
}

func (t *tsvTablePrinter) EndRow() {
fmt.Fprint(t.out, "\n")
t.currentCol = 0
}

func (t *tsvTablePrinter) Render() error {
return nil
}

Expand Down

0 comments on commit 97a6dc4

Please sign in to comment.