Skip to content

Commit

Permalink
table: colorize borders/separators too (#66)
Browse files Browse the repository at this point in the history
  • Loading branch information
jedib0t authored Sep 3, 2018
1 parent 06e05ef commit a06b0e8
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 42 deletions.
111 changes: 71 additions & 40 deletions table/render.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,11 @@ func (t *Table) Render() string {
return t.render(&out)
}

func (t *Table) renderColumn(out *strings.Builder, rowNum int, row rowStr, colIdx int, maxColumnLength int, hint renderHint) {
func (t *Table) renderColumn(out *strings.Builder, row rowStr, colIdx int, maxColumnLength int, hint renderHint) {
// when working on the first column, and autoIndex is true, insert a new
// column with the row number on it.
if colIdx == 0 && t.autoIndex {
t.renderColumnAutoIndex(out, rowNum, hint)
t.renderColumnAutoIndex(out, hint)
}

// when working on column number 2 or more, render the column separator
Expand All @@ -71,10 +71,10 @@ func (t *Table) renderColumn(out *strings.Builder, rowNum int, row rowStr, colId
colStr = t.style.Box.PaddingLeft + colStr + t.style.Box.PaddingRight
}

t.renderColumnColorized(out, rowNum, colIdx, colStr, hint)
t.renderColumnColorized(out, colIdx, colStr, hint)
}

func (t *Table) renderColumnAutoIndex(out *strings.Builder, rowNum int, hint renderHint) {
func (t *Table) renderColumnAutoIndex(out *strings.Builder, hint renderHint) {
var outAutoIndex strings.Builder
outAutoIndex.Grow(t.maxColumnLengths[0])

Expand All @@ -84,7 +84,7 @@ func (t *Table) renderColumnAutoIndex(out *strings.Builder, rowNum int, hint ren
outAutoIndex.WriteString(text.RepeatAndTrim(t.style.Box.MiddleHorizontal, numChars))
} else {
outAutoIndex.WriteString(t.style.Box.PaddingLeft)
rowNumStr := fmt.Sprint(rowNum)
rowNumStr := fmt.Sprint(hint.rowNumber)
if hint.isHeaderRow || hint.isFooterRow || hint.rowLineNumber > 1 {
rowNumStr = strings.Repeat(" ", t.autoIndexVIndexMaxLength)
}
Expand All @@ -93,54 +93,67 @@ func (t *Table) renderColumnAutoIndex(out *strings.Builder, rowNum int, hint ren
}

if t.style.Color.IndexColumn != nil {
out.WriteString(t.style.Color.IndexColumn.Sprintf(outAutoIndex.String()))
colors := t.style.Color.IndexColumn
if hint.isFooterRow {
colors = t.style.Color.Footer
}
out.WriteString(colors.Sprint(outAutoIndex.String()))
} else {
out.WriteString(outAutoIndex.String())
}
hint.isAutoIndexColumn = true
t.renderColumnSeparator(out, hint)
}

func (t *Table) renderColumnColorized(out *strings.Builder, rowNum int, colIdx int, colStr string, hint renderHint) {
func (t *Table) renderColumnColorized(out *strings.Builder, colIdx int, colStr string, hint renderHint) {
colors := t.getColors(hint)
if colIdx < len(colors) && colors[colIdx] != nil {
out.WriteString(colors[colIdx].Sprint(colStr))
} else if hint.isHeaderRow && t.style.Color.Header != nil {
} else if hint.isHeaderRow {
out.WriteString(t.style.Color.Header.Sprint(colStr))
} else if hint.isFooterRow && t.style.Color.Footer != nil {
} else if hint.isFooterRow {
out.WriteString(t.style.Color.Footer.Sprint(colStr))
} else if hint.isRegularRow() {
if colIdx == t.indexColumn-1 && t.style.Color.IndexColumn != nil {
out.WriteString(t.style.Color.IndexColumn.Sprint(colStr))
} else if rowNum%2 == 0 && t.style.Color.RowAlternate != nil {
out.WriteString(t.style.Color.RowAlternate.Sprint(colStr))
} else if t.style.Color.Row != nil {
out.WriteString(t.style.Color.Row.Sprint(colStr))
} else {
out.WriteString(colStr)
}
} else if colIdx == t.indexColumn-1 {
out.WriteString(t.style.Color.IndexColumn.Sprint(colStr))
} else if hint.rowNumber%2 == 0 {
out.WriteString(t.style.Color.RowAlternate.Sprint(colStr))
} else if t.style.Color.Row != nil {
out.WriteString(t.style.Color.Row.Sprint(colStr))
} else {
out.WriteString(colStr)
}
}

func (t *Table) renderColumnSeparator(out *strings.Builder, hint renderHint) {
if t.style.Options.SeparateColumns {
// colorize the separators too
colors := t.style.Color.Row
if hint.isHeaderRow {
colors = t.style.Color.Header
} else if hint.isFooterRow {
colors = t.style.Color.Footer
} else if hint.isAutoIndexColumn {
colors = t.style.Color.IndexColumn
} else if hint.rowNumber > 0 && hint.rowNumber%2 == 0 {
colors = t.style.Color.RowAlternate
}

// type of row determines the character used (top/bottom/separator)
if hint.isSeparatorRow {
if hint.isBorderTop {
out.WriteString(t.style.Box.TopSeparator)
out.WriteString(colors.Sprint(t.style.Box.TopSeparator))
} else if hint.isBorderBottom {
out.WriteString(t.style.Box.BottomSeparator)
out.WriteString(colors.Sprint(t.style.Box.BottomSeparator))
} else {
out.WriteString(t.style.Box.MiddleSeparator)
out.WriteString(colors.Sprint(t.style.Box.MiddleSeparator))
}
} else {
out.WriteString(t.style.Box.MiddleVertical)
out.WriteString(colors.Sprint(t.style.Box.MiddleVertical))
}
}
}

func (t *Table) renderLine(out *strings.Builder, rowNum int, row rowStr, hint renderHint) {
func (t *Table) renderLine(out *strings.Builder, row rowStr, hint renderHint) {
// if the output has content, it means that this call is working on line
// number 2 or more; separate them with a newline
if out.Len() > 0 {
Expand All @@ -159,7 +172,7 @@ func (t *Table) renderLine(out *strings.Builder, rowNum int, row rowStr, hint re

t.renderMarginLeft(outLine, hint)
for colIdx, maxColumnLength := range t.maxColumnLengths {
t.renderColumn(outLine, rowNum, row, colIdx, maxColumnLength, hint)
t.renderColumn(outLine, row, colIdx, maxColumnLength, hint)
}
t.renderMarginRight(outLine, hint)

Expand Down Expand Up @@ -194,30 +207,44 @@ func (t *Table) renderLine(out *strings.Builder, rowNum int, row rowStr, hint re

func (t *Table) renderMarginLeft(out *strings.Builder, hint renderHint) {
if t.style.Options.DrawBorder {
// colorize the separators too
colors := t.style.Color.Header
if hint.isFooterRow {
colors = t.style.Color.Footer
} else if t.autoIndex {
colors = t.style.Color.IndexColumn
}

// type of row determines the character used (top/bottom/separator/etc.)
if hint.isBorderTop {
out.WriteString(t.style.Box.TopLeft)
out.WriteString(colors.Sprint(t.style.Box.TopLeft))
} else if hint.isBorderBottom {
out.WriteString(t.style.Box.BottomLeft)
out.WriteString(colors.Sprint(t.style.Box.BottomLeft))
} else if hint.isSeparatorRow {
out.WriteString(t.style.Box.LeftSeparator)
out.WriteString(colors.Sprint(t.style.Box.LeftSeparator))
} else {
out.WriteString(t.style.Box.Left)
out.WriteString(colors.Sprint(t.style.Box.Left))
}
}
}

func (t *Table) renderMarginRight(out *strings.Builder, hint renderHint) {
if t.style.Options.DrawBorder {
// colorize the separators too
colors := t.style.Color.Header
if hint.isFooterRow {
colors = t.style.Color.Footer
}

// type of row determines the character used (top/bottom/separator/etc.)
if hint.isBorderTop {
out.WriteString(t.style.Box.TopRight)
out.WriteString(colors.Sprint(t.style.Box.TopRight))
} else if hint.isBorderBottom {
out.WriteString(t.style.Box.BottomRight)
out.WriteString(colors.Sprint(t.style.Box.BottomRight))
} else if hint.isSeparatorRow {
out.WriteString(t.style.Box.RightSeparator)
out.WriteString(colors.Sprint(t.style.Box.RightSeparator))
} else {
out.WriteString(t.style.Box.Right)
out.WriteString(colors.Sprint(t.style.Box.Right))
}
}
}
Expand All @@ -240,7 +267,7 @@ func (t *Table) renderRow(out *strings.Builder, rowNum int, row rowStr, hint ren
// each column into individual lines and render them one-by-one
if colMaxLines == 1 {
hint.isLastLineOfRow = true
t.renderLine(out, rowNum, row, hint)
t.renderLine(out, row, hint)
} else {
// convert one row into N # of rows based on colMaxLines
rowLines := make([]rowStr, len(row))
Expand All @@ -254,22 +281,25 @@ func (t *Table) renderRow(out *strings.Builder, rowNum int, row rowStr, hint ren
}
hint.isLastLineOfRow = bool(colLineIdx == colMaxLines-1)
hint.rowLineNumber = colLineIdx + 1
t.renderLine(out, rowNum, rowLine, hint)
t.renderLine(out, rowLine, hint)
}
}
}
}

func (t *Table) renderRowSeparator(out *strings.Builder, hint renderHint) {
if (hint.isBorderTop || hint.isBorderBottom) && !t.style.Options.DrawBorder {
return
if hint.isBorderTop || hint.isBorderBottom {
if !t.style.Options.DrawBorder {
return
}
} else if hint.isHeaderRow && !t.style.Options.SeparateHeader {
return
} else if hint.isFooterRow && !t.style.Options.SeparateFooter {
return
}
hint.isSeparatorRow = true
t.renderLine(out, -1, t.rowSeparator, hint)
hint.rowNumber = -1
t.renderLine(out, t.rowSeparator, hint)
}

func (t *Table) renderRows(out *strings.Builder, rows []rowStr, hint renderHint) {
Expand All @@ -279,6 +309,7 @@ func (t *Table) renderRows(out *strings.Builder, rows []rowStr, hint renderHint)
for idx, row := range rows {
hint.isFirstRow = bool(idx == 0)
hint.isLastRow = bool(idx == len(rows)-1)
hint.rowNumber = idx + 1

t.renderRow(out, idx+1, row, hint)
if t.style.Options.SeparateRows && idx < len(rows)-1 {
Expand All @@ -288,11 +319,11 @@ func (t *Table) renderRows(out *strings.Builder, rows []rowStr, hint renderHint)
}

func (t *Table) renderRowsBorderBottom(out *strings.Builder) {
t.renderRowSeparator(out, renderHint{isBorderBottom: true})
t.renderRowSeparator(out, renderHint{isBorderBottom: true, isFooterRow: true})
}

func (t *Table) renderRowsBorderTop(out *strings.Builder) {
t.renderRowSeparator(out, renderHint{isBorderTop: true})
t.renderRowSeparator(out, renderHint{isBorderTop: true, isHeaderRow: true})
}

func (t *Table) renderRowsFooter(out *strings.Builder) {
Expand Down
37 changes: 36 additions & 1 deletion table/render_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,41 @@ func TestTable_Render_BorderAndSeparators(t *testing.T) {
assert.Equal(t, expectedOut, table.Render())
}

func TestTable_Render_Colored(t *testing.T) {
tw := NewWriter()
tw.AppendHeader(testHeader)
tw.AppendRows(testRows)
tw.AppendRow(testRowMultiLine)
tw.AppendFooter(testFooter)
tw.SetAlign(testAlign)
tw.SetAutoIndex(true)
tw.SetStyle(StyleColoredBright)
tw.Style().Options.DrawBorder = true
tw.Style().Options.SeparateColumns = true
tw.Style().Options.SeparateFooter = true
tw.Style().Options.SeparateHeader = true
tw.Style().Options.SeparateRows = true

expectedOut := []string{
"\x1b[106;30m+\x1b[0m\x1b[106;30m---\x1b[0m\x1b[106;30m+\x1b[0m\x1b[106;30m-----\x1b[0m\x1b[106;30m+\x1b[0m\x1b[106;30m------------\x1b[0m\x1b[106;30m+\x1b[0m\x1b[106;30m-----------\x1b[0m\x1b[106;30m+\x1b[0m\x1b[106;30m--------\x1b[0m\x1b[106;30m+\x1b[0m\x1b[106;30m-----------------------------\x1b[0m\x1b[106;30m+\x1b[0m",
"\x1b[106;30m|\x1b[0m\x1b[106;30m \x1b[0m\x1b[106;30m|\x1b[0m\x1b[106;30m # \x1b[0m\x1b[106;30m|\x1b[0m\x1b[106;30m FIRST NAME \x1b[0m\x1b[106;30m|\x1b[0m\x1b[106;30m LAST NAME \x1b[0m\x1b[106;30m|\x1b[0m\x1b[106;30m SALARY \x1b[0m\x1b[106;30m|\x1b[0m\x1b[106;30m \x1b[0m\x1b[106;30m|\x1b[0m",
"\x1b[106;30m+\x1b[0m\x1b[106;30m---\x1b[0m\x1b[106;30m+\x1b[0m\x1b[106;30m-----\x1b[0m\x1b[106;30m+\x1b[0m\x1b[106;30m------------\x1b[0m\x1b[106;30m+\x1b[0m\x1b[106;30m-----------\x1b[0m\x1b[106;30m+\x1b[0m\x1b[106;30m--------\x1b[0m\x1b[106;30m+\x1b[0m\x1b[106;30m-----------------------------\x1b[0m\x1b[106;30m+\x1b[0m",
"\x1b[106;30m|\x1b[0m\x1b[106;30m 1 \x1b[0m\x1b[106;30m|\x1b[0m\x1b[107;30m 1 \x1b[0m\x1b[107;30m|\x1b[0m\x1b[107;30m Arya \x1b[0m\x1b[107;30m|\x1b[0m\x1b[107;30m Stark \x1b[0m\x1b[107;30m|\x1b[0m\x1b[107;30m 3000 \x1b[0m\x1b[107;30m|\x1b[0m\x1b[107;30m \x1b[0m\x1b[106;30m|\x1b[0m",
"\x1b[106;30m+\x1b[0m\x1b[106;30m---\x1b[0m\x1b[106;30m+\x1b[0m\x1b[107;30m-----\x1b[0m\x1b[107;30m+\x1b[0m\x1b[107;30m------------\x1b[0m\x1b[107;30m+\x1b[0m\x1b[107;30m-----------\x1b[0m\x1b[107;30m+\x1b[0m\x1b[107;30m--------\x1b[0m\x1b[107;30m+\x1b[0m\x1b[107;30m-----------------------------\x1b[0m\x1b[106;30m+\x1b[0m",
"\x1b[106;30m|\x1b[0m\x1b[106;30m 2 \x1b[0m\x1b[106;30m|\x1b[0m\x1b[47;30m 20 \x1b[0m\x1b[47;30m|\x1b[0m\x1b[47;30m Jon \x1b[0m\x1b[47;30m|\x1b[0m\x1b[47;30m Snow \x1b[0m\x1b[47;30m|\x1b[0m\x1b[47;30m 2000 \x1b[0m\x1b[47;30m|\x1b[0m\x1b[47;30m You know nothing, Jon Snow! \x1b[0m\x1b[106;30m|\x1b[0m",
"\x1b[106;30m+\x1b[0m\x1b[106;30m---\x1b[0m\x1b[106;30m+\x1b[0m\x1b[107;30m-----\x1b[0m\x1b[107;30m+\x1b[0m\x1b[107;30m------------\x1b[0m\x1b[107;30m+\x1b[0m\x1b[107;30m-----------\x1b[0m\x1b[107;30m+\x1b[0m\x1b[107;30m--------\x1b[0m\x1b[107;30m+\x1b[0m\x1b[107;30m-----------------------------\x1b[0m\x1b[106;30m+\x1b[0m",
"\x1b[106;30m|\x1b[0m\x1b[106;30m 3 \x1b[0m\x1b[106;30m|\x1b[0m\x1b[107;30m 300 \x1b[0m\x1b[107;30m|\x1b[0m\x1b[107;30m Tyrion \x1b[0m\x1b[107;30m|\x1b[0m\x1b[107;30m Lannister \x1b[0m\x1b[107;30m|\x1b[0m\x1b[107;30m 5000 \x1b[0m\x1b[107;30m|\x1b[0m\x1b[107;30m \x1b[0m\x1b[106;30m|\x1b[0m",
"\x1b[106;30m+\x1b[0m\x1b[106;30m---\x1b[0m\x1b[106;30m+\x1b[0m\x1b[107;30m-----\x1b[0m\x1b[107;30m+\x1b[0m\x1b[107;30m------------\x1b[0m\x1b[107;30m+\x1b[0m\x1b[107;30m-----------\x1b[0m\x1b[107;30m+\x1b[0m\x1b[107;30m--------\x1b[0m\x1b[107;30m+\x1b[0m\x1b[107;30m-----------------------------\x1b[0m\x1b[106;30m+\x1b[0m",
"\x1b[106;30m|\x1b[0m\x1b[106;30m 4 \x1b[0m\x1b[106;30m|\x1b[0m\x1b[47;30m 0 \x1b[0m\x1b[47;30m|\x1b[0m\x1b[47;30m Winter \x1b[0m\x1b[47;30m|\x1b[0m\x1b[47;30m Is \x1b[0m\x1b[47;30m|\x1b[0m\x1b[47;30m 0 \x1b[0m\x1b[47;30m|\x1b[0m\x1b[47;30m Coming. \x1b[0m\x1b[106;30m|\x1b[0m",
"\x1b[106;30m|\x1b[0m\x1b[106;30m \x1b[0m\x1b[106;30m|\x1b[0m\x1b[47;30m \x1b[0m\x1b[47;30m|\x1b[0m\x1b[47;30m \x1b[0m\x1b[47;30m|\x1b[0m\x1b[47;30m \x1b[0m\x1b[47;30m|\x1b[0m\x1b[47;30m \x1b[0m\x1b[47;30m|\x1b[0m\x1b[47;30m The North Remembers! \x1b[0m\x1b[106;30m|\x1b[0m",
"\x1b[106;30m|\x1b[0m\x1b[106;30m \x1b[0m\x1b[106;30m|\x1b[0m\x1b[47;30m \x1b[0m\x1b[47;30m|\x1b[0m\x1b[47;30m \x1b[0m\x1b[47;30m|\x1b[0m\x1b[47;30m \x1b[0m\x1b[47;30m|\x1b[0m\x1b[47;30m \x1b[0m\x1b[47;30m|\x1b[0m\x1b[47;30m This is known. \x1b[0m\x1b[106;30m|\x1b[0m",
"\x1b[46;30m+\x1b[0m\x1b[46;30m---\x1b[0m\x1b[46;30m+\x1b[0m\x1b[46;30m-----\x1b[0m\x1b[46;30m+\x1b[0m\x1b[46;30m------------\x1b[0m\x1b[46;30m+\x1b[0m\x1b[46;30m-----------\x1b[0m\x1b[46;30m+\x1b[0m\x1b[46;30m--------\x1b[0m\x1b[46;30m+\x1b[0m\x1b[46;30m-----------------------------\x1b[0m\x1b[46;30m+\x1b[0m",
"\x1b[46;30m|\x1b[0m\x1b[46;30m \x1b[0m\x1b[46;30m|\x1b[0m\x1b[46;30m \x1b[0m\x1b[46;30m|\x1b[0m\x1b[46;30m \x1b[0m\x1b[46;30m|\x1b[0m\x1b[46;30m TOTAL \x1b[0m\x1b[46;30m|\x1b[0m\x1b[46;30m 10000 \x1b[0m\x1b[46;30m|\x1b[0m\x1b[46;30m \x1b[0m\x1b[46;30m|\x1b[0m",
"\x1b[46;30m+\x1b[0m\x1b[46;30m---\x1b[0m\x1b[46;30m+\x1b[0m\x1b[46;30m-----\x1b[0m\x1b[46;30m+\x1b[0m\x1b[46;30m------------\x1b[0m\x1b[46;30m+\x1b[0m\x1b[46;30m-----------\x1b[0m\x1b[46;30m+\x1b[0m\x1b[46;30m--------\x1b[0m\x1b[46;30m+\x1b[0m\x1b[46;30m-----------------------------\x1b[0m\x1b[46;30m+\x1b[0m",
}
assert.Equal(t, strings.Join(expectedOut, "\n"), tw.Render())
}

func TestTable_Render_ColoredCustom(t *testing.T) {
tw := NewWriter()
tw.AppendHeader(testHeader)
Expand Down Expand Up @@ -297,7 +332,7 @@ func TestTable_Render_ColoredStyleAutoIndex(t *testing.T) {
"\x1b[96;100m 1 \x1b[0m\x1b[97;40m 1 \x1b[0m\x1b[97;40m Arya \x1b[0m\x1b[97;40m Stark \x1b[0m\x1b[97;40m 3000 \x1b[0m\x1b[97;40m \x1b[0m",
"\x1b[96;100m 2 \x1b[0m\x1b[37;40m 20 \x1b[0m\x1b[37;40m Jon \x1b[0m\x1b[37;40m Snow \x1b[0m\x1b[37;40m 2000 \x1b[0m\x1b[37;40m You know nothing, Jon Snow! \x1b[0m",
"\x1b[96;100m 3 \x1b[0m\x1b[97;40m 300 \x1b[0m\x1b[97;40m Tyrion \x1b[0m\x1b[97;40m Lannister \x1b[0m\x1b[97;40m 5000 \x1b[0m\x1b[97;40m \x1b[0m",
"\x1b[96;100m \x1b[0m\x1b[36;100m \x1b[0m\x1b[36;100m \x1b[0m\x1b[36;100m TOTAL \x1b[0m\x1b[36;100m 10000 \x1b[0m\x1b[36;100m \x1b[0m",
"\x1b[36;100m \x1b[0m\x1b[36;100m \x1b[0m\x1b[36;100m \x1b[0m\x1b[36;100m TOTAL \x1b[0m\x1b[36;100m 10000 \x1b[0m\x1b[36;100m \x1b[0m",
}, "\n")
out := table.Render()
assert.Equal(t, expectedOut, out)
Expand Down
3 changes: 2 additions & 1 deletion table/table.go
Original file line number Diff line number Diff line change
Expand Up @@ -426,10 +426,11 @@ type renderHint struct {
isLastRow bool // last-row of header/footer/regular-rows?
isSeparatorRow bool // separator row?
rowLineNumber int // the line number for a multi-line row
rowNumber int // the row number/index
}

func (h *renderHint) isRegularRow() bool {
return !h.isHeaderRow && !h.isFooterRow && !h.isSeparatorRow
return !h.isHeaderRow && !h.isFooterRow
}

func (h *renderHint) isLastLineOfLastRow() bool {
Expand Down

0 comments on commit a06b0e8

Please sign in to comment.