-
Notifications
You must be signed in to change notification settings - Fork 0
/
texttable.go
151 lines (132 loc) · 3.32 KB
/
texttable.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
package texttable
import (
"bytes"
"fmt"
"unicode/utf8"
)
// Table containing column titles and rows.
type Table struct {
titles Row
rows []Row
}
// Table row containing cells.
type Row []Cell
// Table cell containing string content, internal padding and alignment.
type Cell struct {
Content string
PadLeft uint
PadRight uint
Align Alignment
}
// Cell alignment type
type Alignment int
// Cell alignment variants.
const (
AlignLeft Alignment = iota
AlignRight
AlignCenter
)
// Set row that is to be rendered as column titles.
func (table *Table) SetTitles(titles Row) {
table.titles = titles
}
// Add row to the table.
func (table *Table) AddRow(row Row) {
table.rows = append(table.rows, row)
}
// Update column widths to that of the largest encountered cell.
func updateColumnWidths(widths *[]int, row Row) {
n := len(row) - len(*widths)
for i := 0; i < n; i++ {
*widths = append(*widths, 0)
}
for i, cell := range row {
l := utf8.RuneCountInString(cell.Content) + int(cell.PadLeft+cell.PadRight)
if l > (*widths)[i] {
(*widths)[i] = l
}
}
}
// Render table as text.
func (table *Table) Render() []byte {
var buf bytes.Buffer
var columnWidths []int
updateColumnWidths(&columnWidths, table.titles)
for _, row := range table.rows {
updateColumnWidths(&columnWidths, row)
}
if table.titles != nil {
fmt.Fprintf(&buf, "%s", table.titles.render(columnWidths))
// Render the titles underlining.
for i := 0; i < len(columnWidths); i++ {
begPad := "-+-"
if i == 0 {
begPad = ""
}
fmt.Fprintf(&buf, "%s%s", begPad, fill('-', columnWidths[i]))
}
fmt.Fprintf(&buf, "\n")
}
for _, row := range table.rows {
fmt.Fprintf(&buf, "%s", row.render(columnWidths))
}
return buf.Bytes()
}
// Implements fmt.Stringer
func (table *Table) String() string {
return string(table.Render())
}
// Render row as text.
func (row *Row) render(columnWidths []int) []byte {
var buf bytes.Buffer
for i := 0; i < len(columnWidths); i++ {
begPad := " | "
if i == 0 {
begPad = ""
}
fmt.Fprintf(&buf, "%s%s", begPad, row.cellAt(i).render(columnWidths[i]))
}
return append(bytes.TrimRight(buf.Bytes(), " "), '\n')
}
// Return cell at the specified column or nil if there is no such column.
func (row *Row) cellAt(column int) *Cell {
if column >= len(*row) {
return nil
}
return &(*row)[column]
}
// Render cell as text.
// As a special case a cell that is nil is treated as an empty cell.
func (cell *Cell) render(cellWidth int) []byte {
if cell == nil {
return fill(' ', cellWidth)
}
buf := make([]byte, 0)
if cell.PadLeft > 0 {
buf = append(buf, fill(' ', int(cell.PadLeft))...)
}
buf = append(buf, cell.Content...)
if cell.PadRight > 0 {
buf = append(buf, fill(' ', int(cell.PadRight))...)
}
pad := cellWidth - utf8.RuneCount(buf)
var padLeft, padRight int
switch cell.Align {
case AlignRight:
padLeft = pad
case AlignCenter:
// Pad with a bias of more padding to the right.
padLeft = pad / 2
padRight = pad - padLeft
default:
// Since it's possible to pass values other than the specified
// constants use AlignLeft as the default.
padRight = pad
}
buf = append(buf, fill(' ', padRight)...)
return append(fill(' ', padLeft), buf...)
}
// Return a byte slice containing count copies of char.
func fill(char byte, count int) []byte {
return bytes.Repeat([]byte{char}, count)
}