-
Notifications
You must be signed in to change notification settings - Fork 404
/
print.go
304 lines (259 loc) · 7.28 KB
/
print.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
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
package pretty
import (
"bytes"
"fmt"
"io"
"github.com/fatih/color"
)
// NB(directxman12): this isn't particularly elegant, but it's also
// sufficiently simple as to be maintained here. Man (roff) would've
// probably worked, but it's not necessarily on Windows by default.
// Span is a chunk of content that is writable to an output, but knows how to
// calculate its apparent visual "width" on the terminal (not to be confused
// with the raw length, which may include zero-width coloring sequences).
type Span interface {
// VisualLength reports the "width" as perceived by the user on the terminal
// (i.e. widest line, ignoring ANSI escape characters).
VisualLength() int
// WriteTo writes the full span contents to the given writer.
WriteTo(io.Writer) error
}
// Table is a Span that writes its data in table form, with sizing controlled
// by the given table calculator. Rows are started with StartRow, followed by
// some calls to Column, followed by a call to EndRow. Once all rows are
// added, the table can be used as a Span.
type Table struct {
Sizing *TableCalculator
cellsByRow [][]Span
colSizes []int
}
// StartRow starts a new row.
// It must eventually be followed by EndRow.
func (t *Table) StartRow() {
t.cellsByRow = append(t.cellsByRow, []Span(nil))
}
// EndRow ends the currently started row.
func (t *Table) EndRow() {
lastRow := t.cellsByRow[len(t.cellsByRow)-1]
sizes := make([]int, len(lastRow))
for i, cell := range lastRow {
sizes[i] = cell.VisualLength()
}
t.Sizing.AddRowSizes(sizes...)
}
// Column adds the given span as a new column to the current row.
func (t *Table) Column(contents Span) {
currentRowInd := len(t.cellsByRow) - 1
t.cellsByRow[currentRowInd] = append(t.cellsByRow[currentRowInd], contents)
}
// SkipRow prints a span without having it contribute to the table calculation.
func (t *Table) SkipRow(contents Span) {
t.cellsByRow = append(t.cellsByRow, []Span{contents})
}
func (t *Table) WriteTo(out io.Writer) error {
if t.colSizes == nil {
t.colSizes = t.Sizing.ColumnWidths()
}
for _, cells := range t.cellsByRow {
currentPosition := 0
for colInd, cell := range cells {
colSize := t.colSizes[colInd]
diff := colSize - cell.VisualLength()
if err := cell.WriteTo(out); err != nil {
return err
}
if diff > 0 {
if err := writePadding(out, columnPadding, diff); err != nil {
return err
}
}
currentPosition += colSize
}
if _, err := fmt.Fprint(out, "\n"); err != nil {
return err
}
}
return nil
}
func (t *Table) VisualLength() int {
if t.colSizes == nil {
t.colSizes = t.Sizing.ColumnWidths()
}
res := 0
for _, colSize := range t.colSizes {
res += colSize
}
return res
}
// Text is a span that simply contains raw text. It's a good starting point.
type Text string
func (t Text) VisualLength() int { return len(t) }
func (t Text) WriteTo(w io.Writer) error {
_, err := w.Write([]byte(t))
return err
}
// indented is a span that indents all lines by the given number of tabs.
type indented struct {
Amount int
Content Span
}
func (i *indented) VisualLength() int { return i.Content.VisualLength() }
func (i *indented) WriteTo(w io.Writer) error {
var out bytes.Buffer
if err := i.Content.WriteTo(&out); err != nil {
return err
}
lines := bytes.Split(out.Bytes(), []byte("\n"))
for lineInd, line := range lines {
if lineInd != 0 {
if _, err := w.Write([]byte("\n")); err != nil {
return err
}
}
if len(line) == 0 {
continue
}
if err := writePadding(w, indentPadding, i.Amount); err != nil {
return err
}
if _, err := w.Write(line); err != nil {
return err
}
}
return nil
}
// Indented returns a span that indents all lines by the given number of tabs.
func Indented(amt int, content Span) Span {
return &indented{Amount: amt, Content: content}
}
// fromWriter is a span that takes content from a function expecting a Writer.
type fromWriter struct {
cache []byte
cacheError error
run func(io.Writer) error
}
func (f *fromWriter) VisualLength() int {
if f.cache == nil {
var buf bytes.Buffer
if err := f.run(&buf); err != nil {
f.cacheError = err
}
f.cache = buf.Bytes()
}
return len(f.cache)
}
func (f *fromWriter) WriteTo(w io.Writer) error {
if f.cache != nil {
if f.cacheError != nil {
return f.cacheError
}
_, err := w.Write(f.cache)
return err
}
return f.run(w)
}
// FromWriter returns a span that takes content from a function expecting a Writer.
func FromWriter(run func(io.Writer) error) Span {
return &fromWriter{run: run}
}
// Decoration represents a terminal decoration.
type Decoration color.Color
// Containing returns a Span that has the given decoration applied.
func (d Decoration) Containing(contents Span) Span {
return &decorated{
Contents: contents,
Attributes: color.Color(d),
}
}
// decorated is a span that has some terminal decoration applied.
type decorated struct {
Contents Span
Attributes color.Color
}
func (d *decorated) VisualLength() int { return d.Contents.VisualLength() }
func (d *decorated) WriteTo(w io.Writer) error {
oldOut := color.Output
color.Output = w
defer func() { color.Output = oldOut }()
d.Attributes.Set()
defer color.Unset()
return d.Contents.WriteTo(w)
}
// SpanWriter is a span that contains multiple sub-spans.
type SpanWriter struct {
contents []Span
}
func (m *SpanWriter) VisualLength() int {
res := 0
for _, span := range m.contents {
res += span.VisualLength()
}
return res
}
func (m *SpanWriter) WriteTo(w io.Writer) error {
for _, span := range m.contents {
if err := span.WriteTo(w); err != nil {
return err
}
}
return nil
}
// Print adds a new span to this SpanWriter.
func (m *SpanWriter) Print(s Span) {
m.contents = append(m.contents, s)
}
// lines is a span that adds some newlines, optionally followed by some content.
type lines struct {
content Span
amountBefore int
}
func (l *lines) VisualLength() int {
if l.content == nil {
return 0
}
return l.content.VisualLength()
}
func (l *lines) WriteTo(w io.Writer) error {
if err := writePadding(w, linesPadding, l.amountBefore); err != nil {
return err
}
if l.content != nil {
if err := l.content.WriteTo(w); err != nil {
return err
}
}
return nil
}
// Newlines returns a span just containing some newlines.
func Newlines(amt int) Span {
return &lines{amountBefore: amt}
}
// Line returns a span that emits a newline, followed by the given content.
func Line(content Span) Span {
return &lines{amountBefore: 1, content: content}
}
var (
columnPadding = []byte(" ")
indentPadding = []byte("\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t")
linesPadding = []byte("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n")
)
// writePadding writes out padding of the given type in the given amount to the writer.
// Each byte in the padding buffer contributes 1 to the amount -- the padding being
// a buffer is just for efficiency.
func writePadding(out io.Writer, typ []byte, amt int) error {
if amt <= len(typ) {
_, err := out.Write(typ[:amt])
return err
}
num := amt / len(typ)
rem := amt % len(typ)
for i := 0; i < num; i++ {
if _, err := out.Write(typ); err != nil {
return err
}
}
if _, err := out.Write(typ[:rem]); err != nil {
return err
}
return nil
}