@@ -10,7 +10,6 @@ import (
10
10
"math"
11
11
"slices"
12
12
"strconv"
13
- "strings"
14
13
15
14
"github.com/cockroachdb/crlib/crhumanize"
16
15
"github.com/cockroachdb/pebble/internal/ascii"
@@ -21,7 +20,7 @@ import (
21
20
//
22
21
// Example:
23
22
//
24
- // wb := ascii.Make(1 , 10)
23
+ // wb := ascii.Make(10 , 10)
25
24
// type Cat struct {
26
25
// Name string
27
26
// Age int
@@ -38,7 +37,7 @@ import (
38
37
// Int("cuteness", 8, AlignRight, func(c Cat) int { return c.Cuteness }),
39
38
// )
40
39
//
41
- // wb.Reset(def.CumulativeFieldWidth )
40
+ // wb.Reset(10 )
42
41
// def.Render(wb.At(0, 0), RenderOptions{}, cats)
43
42
//
44
43
// Output of wb.String():
@@ -48,32 +47,21 @@ import (
48
47
// Mai 2 10
49
48
// Yuumi 5 10
50
49
func Define [T any ](fields ... Element ) Layout [T ] {
51
- maxFieldWidth := 0
52
- for i := range len (fields ) {
53
- maxFieldWidth = max (maxFieldWidth , fields [i ].width ())
54
- }
55
-
56
- cumulativeFieldWidth := 0
57
- for i := range len (fields ) {
58
- w := fields [i ].width ()
59
- h := fields [i ].header (Vertically , maxFieldWidth )
60
- if len (h ) > w {
61
- panic (fmt .Sprintf ("header %q is too long for column %d" , h , i ))
50
+ for i := range fields {
51
+ if f , ok := fields [i ].(Field [T ]); ok {
52
+ if h := f .header (); len (h ) > f .width () {
53
+ panic (fmt .Sprintf ("header %q is too long for column %d" , h , i ))
54
+ }
62
55
}
63
- cumulativeFieldWidth += w
64
56
}
65
57
return Layout [T ]{
66
- CumulativeFieldWidth : cumulativeFieldWidth ,
67
- MaxFieldWidth : maxFieldWidth ,
68
- fields : fields ,
58
+ fields : fields ,
69
59
}
70
60
}
71
61
72
62
// A Layout defines the layout of a table.
73
63
type Layout [T any ] struct {
74
- CumulativeFieldWidth int
75
- MaxFieldWidth int
76
- fields []Element
64
+ fields []Element
77
65
}
78
66
79
67
// RenderOptions specifies the options for rendering a table.
@@ -85,9 +73,9 @@ type RenderOptions struct {
85
73
// returning the modified cursor.
86
74
func (d * Layout [T ]) Render (start ascii.Cursor , opts RenderOptions , rows iter.Seq [T ]) ascii.Cursor {
87
75
cur := start
88
- tuples := slices .Collect (rows )
89
76
90
77
if opts .Orientation == Vertically {
78
+ tuples := slices .Collect (rows )
91
79
vals := make ([]string , len (tuples ))
92
80
for fieldIdx , c := range d .fields {
93
81
if fieldIdx > 0 {
@@ -105,8 +93,9 @@ func (d *Layout[T]) Render(start ascii.Cursor, opts RenderOptions, rows iter.Seq
105
93
cur = cur .Offset (0 , 1 )
106
94
continue
107
95
}
96
+ f := c .(Field [T ])
108
97
for i , t := range tuples {
109
- vals [i ] = c .( Field [ T ]) .renderValue (i , t )
98
+ vals [i ] = f .renderValue (i , t )
110
99
}
111
100
112
101
width := c .width ()
@@ -115,128 +104,95 @@ func (d *Layout[T]) Render(start ascii.Cursor, opts RenderOptions, rows iter.Seq
115
104
for i := range vals {
116
105
width = max (width , len (vals [i ]))
117
106
}
118
- header := c .header (Vertically , width )
119
- align := c .align ()
120
- padding := width - len (header )
121
- cur .Offset (0 , 0 ).WriteString (
122
- align .maybePadding (AlignRight , padding ) + header + align .maybePadding (AlignLeft , padding ),
123
- )
124
- cur .Offset (1 , 0 ).WriteString (strings .Repeat ("-" , width ))
125
-
107
+ header := f .header ()
108
+ align := f .align ()
109
+ pad (cur , width , align , header )
110
+ cur .Down (1 ).RepeatByte (width , '-' )
126
111
for i := range vals {
127
- ctx := RenderContext [T ]{
128
- Orientation : Vertically ,
129
- Pos : cur .Offset (2 + i , 0 ),
130
- MaxFieldWidth : d .MaxFieldWidth ,
131
- }
132
- spec := widthStr (width , c .align ()) + "s"
133
- ctx .PaddedPos (width ).Printf (spec , vals [i ])
112
+ pad (cur .Down (2 + i ), width , align , vals [i ])
134
113
}
135
- cur = cur .Offset ( 0 , width )
114
+ cur = cur .Right ( width )
136
115
}
137
- return start .Offset (2 + len (tuples ), 0 )
116
+ return start .Down (2 + len (tuples ))
117
+ }
118
+
119
+ headerColumnWidth := 1
120
+ for i := range d .fields {
121
+ headerColumnWidth = max (headerColumnWidth , d .fields [i ].width ())
138
122
}
139
123
140
124
for i := range d .fields {
141
- cur .Offset (i , 0 ).WriteString (d .fields [i ].header (Horizontally , d .MaxFieldWidth ))
142
125
if _ , ok := d .fields [i ].(divider ); ok {
143
- cur .Offset ( i , d . MaxFieldWidth ). WriteString ( "-+-" )
126
+ cur .Down ( i ). RepeatByte ( headerColumnWidth , '-' )
144
127
} else {
145
- cur .Offset ( i , d . MaxFieldWidth ). WriteString ( " | " )
128
+ pad ( cur .Down ( i ), headerColumnWidth , AlignRight , d . fields [ i ].( Field [ T ]). header () )
146
129
}
147
130
}
131
+ cur = cur .Right (headerColumnWidth )
132
+ for i := range d .fields {
133
+ if _ , ok := d .fields [i ].(divider ); ok {
134
+ cur .Down (i ).WriteString ("-+-" )
135
+ } else {
136
+ cur .Down (i ).WriteString (" | " )
137
+ }
138
+ }
139
+ cur = cur .Right (3 )
140
+
148
141
tupleIndex := 0
149
- c := d . MaxFieldWidth + 3
142
+ colSpacing := 0
150
143
for t := range rows {
144
+ width := 1
145
+ for i := range d .fields {
146
+ if f , ok := d .fields [i ].(Field [T ]); ok {
147
+ width = max (width , len (f .renderValue (tupleIndex , t )))
148
+ }
149
+ }
151
150
for i := range d .fields {
152
- if div , ok := d .fields [i ].(divider ); ok {
153
- div . renderStatic ( Horizontally , d . MaxFieldWidth , cur .Offset ( i , c ) )
151
+ if _ , ok := d .fields [i ].(divider ); ok {
152
+ cur .Down ( i ). RepeatByte ( width + colSpacing , '-' )
154
153
} else {
155
- ctx := RenderContext [T ]{
156
- Orientation : Horizontally ,
157
- Pos : cur .Offset (i , c ),
158
- MaxFieldWidth : d .MaxFieldWidth ,
159
- }
160
154
f := d .fields [i ].(Field [T ])
161
- width := f .width ()
162
- spec := widthStr (width , f .align ()) + "s"
163
- ctx .PaddedPos (width ).Printf (spec , f .renderValue (tupleIndex , t ))
155
+ pad (cur .Down (i ).Right (colSpacing ), width , d .fields [i ].align (), f .renderValue (tupleIndex , t ))
164
156
}
165
157
}
166
158
tupleIndex ++
167
- c += d .MaxFieldWidth
159
+ cur = cur .Right (width + colSpacing )
160
+ colSpacing = 2
168
161
}
169
- return cur .Offset (len (d .fields ), c )
170
- }
171
-
172
- // A RenderContext provides the context for rendering a table.
173
- type RenderContext [T any ] struct {
174
- Orientation Orientation
175
- Pos ascii.Cursor
176
- MaxFieldWidth int
177
- }
178
-
179
- func (c * RenderContext [T ]) PaddedPos (width int ) ascii.Cursor {
180
- if c .Orientation == Vertically {
181
- return c .Pos
182
- }
183
- // Horizontally, we need to pad the width to the max field width.
184
- return c .Pos .Offset (0 , c .MaxFieldWidth - width )
162
+ return start .Down (len (d .fields ))
185
163
}
186
164
187
165
// Element is the base interface, common to all table elements.
188
166
type Element interface {
189
- header (o Orientation , maxWidth int ) string
190
167
width () int
191
168
align () Align
192
169
}
193
170
194
- // StaticElement is an Element that doesn't depend on the tuple value for
195
- // rendering.
196
- type StaticElement interface {
197
- Element
198
- renderStatic (o Orientation , maxWidth int , pos ascii.Cursor )
199
- }
200
-
201
171
// Field is an Element that depends on the tuple value for rendering.
202
172
type Field [T any ] interface {
203
173
Element
174
+ header () string
204
175
renderValue (tupleIndex int , tuple T ) string
205
176
}
206
177
207
178
// Div creates a divider field used to visually separate regions of the table.
208
- func Div () StaticElement {
179
+ func Div () Element {
209
180
return divider {}
210
181
}
211
182
212
183
type divider struct {}
213
184
214
185
var (
215
- _ StaticElement = (* divider )(nil )
186
+ _ Element = (* divider )(nil )
216
187
217
188
// TODO(jackson): The staticcheck tool doesn't recognize that these are used to
218
189
// satisfy the Field interface. Why not?
219
- _ = divider .header
220
190
_ = divider .width
221
191
_ = divider .align
222
- _ = divider .renderStatic
223
192
)
224
193
225
- func (d divider ) header (o Orientation , maxWidth int ) string {
226
- if o == Horizontally {
227
- return strings .Repeat ("-" , maxWidth )
228
- }
229
- return " | "
230
- }
231
- func (d divider ) width () int { return 3 }
194
+ func (d divider ) width () int { return 1 }
232
195
func (d divider ) align () Align { return AlignLeft }
233
- func (d divider ) renderStatic (o Orientation , maxWidth int , pos ascii.Cursor ) {
234
- if o == Horizontally {
235
- pos .RepeatByte (maxWidth , '-' )
236
- } else {
237
- pos .WriteString (" | " )
238
- }
239
- }
240
196
241
197
func Literal [T any ](s string ) Field [T ] {
242
198
return literal [T ](s )
@@ -255,9 +211,9 @@ var (
255
211
_ = literal [any ].renderValue
256
212
)
257
213
258
- func (l literal [T ]) header (o Orientation , maxWidth int ) string { return " " }
259
- func (l literal [T ]) width () int { return len (l ) }
260
- func (l literal [T ]) align () Align { return AlignLeft }
214
+ func (l literal [T ]) header () string { return " " }
215
+ func (l literal [T ]) width () int { return len (l ) }
216
+ func (l literal [T ]) align () Align { return AlignLeft }
261
217
func (l literal [T ]) renderValue (tupleIndex int , tuple T ) string {
262
218
return string (l )
263
219
}
@@ -269,11 +225,18 @@ const (
269
225
270
226
type Align uint8
271
227
272
- func (a Align ) maybePadding (ifAlign Align , width int ) string {
273
- if a == ifAlign {
274
- return strings .Repeat (" " , width )
228
+ // pad writes the given string to the cursor, padding it to the given width
229
+ // (according to the alignment).
230
+ func pad (cur ascii.Cursor , toWidth int , align Align , s string ) ascii.Cursor {
231
+ if len (s ) >= toWidth {
232
+ return cur .WriteString (s )
275
233
}
276
- return ""
234
+ startCur := cur
235
+ if align == AlignRight {
236
+ cur = cur .Right (toWidth - len (s ))
237
+ }
238
+ cur .WriteString (s )
239
+ return startCur .Right (toWidth )
277
240
}
278
241
279
242
const (
@@ -354,20 +317,13 @@ var (
354
317
_ = (& funcField [any ]{}).renderValue
355
318
)
356
319
357
- func (c * funcField [T ]) header (o Orientation , maxWidth int ) string { return c .headerValue }
358
- func (c * funcField [T ]) width () int { return c .widthValue }
359
- func (c * funcField [T ]) align () Align { return c .alignValue }
320
+ func (c * funcField [T ]) header () string { return c .headerValue }
321
+ func (c * funcField [T ]) width () int { return c .widthValue }
322
+ func (c * funcField [T ]) align () Align { return c .alignValue }
360
323
func (c * funcField [T ]) renderValue (tupleIndex int , tuple T ) string {
361
324
return c .toStringFn (tupleIndex , tuple )
362
325
}
363
326
364
- func widthStr (width int , align Align ) string {
365
- if align == AlignLeft {
366
- return "%-" + strconv .Itoa (width )
367
- }
368
- return "%" + strconv .Itoa (width )
369
- }
370
-
371
327
// humanizeFloat formats a float64 value as a string. It shows up to two
372
328
// decimals, depending on the target length. NaN is shown as "-".
373
329
func humanizeFloat (v float64 , targetLength int ) string {
0 commit comments