@@ -3,10 +3,18 @@ package terminal
3
3
import (
4
4
"encoding/csv"
5
5
"fmt"
6
- "io"
6
+ "os"
7
+ "strconv"
7
8
"strings"
8
9
10
+ "golang.org/x/term"
11
+
9
12
. "github.com/IBM-Cloud/ibm-cloud-cli-sdk/i18n"
13
+
14
+ "io"
15
+
16
+ "github.com/jedib0t/go-pretty/v6/table"
17
+ "github.com/jedib0t/go-pretty/v6/text"
10
18
"github.com/mattn/go-runewidth"
11
19
)
12
20
@@ -24,11 +32,10 @@ type Table interface {
24
32
}
25
33
26
34
type PrintableTable struct {
27
- writer io.Writer
28
- headers []string
29
- headerPrinted bool
30
- maxSizes []int
31
- rows [][]string //each row is single line
35
+ writer io.Writer
36
+ headers []string
37
+ maxSizes []int
38
+ rows [][]string //each row is single line
32
39
}
33
40
34
41
func NewTable (w io.Writer , headers []string ) Table {
@@ -69,58 +76,160 @@ func (t *PrintableTable) Add(row ...string) {
69
76
}
70
77
}
71
78
79
+ func isWideColumn (col string ) bool {
80
+ // list of common columns that are usually wide
81
+ largeColumnTypes := []string {T ("ID" ), T ("Description" )}
82
+
83
+ for _ , largeColn := range largeColumnTypes {
84
+ if strings .Contains (largeColn , col ) {
85
+ return true
86
+ }
87
+ }
88
+
89
+ return false
90
+
91
+ }
92
+
93
+ func terminalWidth () int {
94
+ var err error
95
+ terminalWidth , _ , err := term .GetSize (int (os .Stdin .Fd ()))
96
+
97
+ if err != nil {
98
+ // Assume normal 80 char width line
99
+ terminalWidth = 80
100
+ }
101
+
102
+ testTerminalWidth , envSet := os .LookupEnv ("TEST_TERMINAL_WIDTH" )
103
+ if envSet {
104
+ envWidth , err := strconv .Atoi (testTerminalWidth )
105
+ if err == nil {
106
+ terminalWidth = envWidth
107
+ }
108
+ }
109
+ return terminalWidth
110
+ }
111
+
72
112
func (t * PrintableTable ) Print () {
73
113
for _ , row := range append (t .rows , t .headers ) {
74
114
t .calculateMaxSize (row )
75
115
}
76
116
77
- if t .headerPrinted == false {
78
- t .printHeader ()
79
- t .headerPrinted = true
117
+ tbl := table .NewWriter ()
118
+ tbl .SetOutputMirror (t .writer )
119
+ tbl .SuppressTrailingSpaces ()
120
+ // remove padding from the left to keep the table aligned to the left
121
+ tbl .Style ().Box .PaddingLeft = ""
122
+ tbl .Style ().Box .PaddingRight = strings .Repeat (" " , minSpace )
123
+ // remove all border and column and row separators
124
+ tbl .Style ().Options .DrawBorder = false
125
+ tbl .Style ().Options .SeparateColumns = false
126
+ tbl .Style ().Options .SeparateFooter = false
127
+ tbl .Style ().Options .SeparateHeader = false
128
+ tbl .Style ().Options .SeparateRows = false
129
+ tbl .Style ().Format .Header = text .FormatDefault
130
+
131
+ headerRow , rows := t .createPrettyRowsAndHeaders ()
132
+ columnConfig := t .createColumnConfigs ()
133
+
134
+ tbl .SetColumnConfigs (columnConfig )
135
+ tbl .AppendHeader (headerRow )
136
+ tbl .AppendRows (rows )
137
+ tbl .Render ()
138
+ }
139
+
140
+ func (t * PrintableTable ) createColumnConfigs () []table.ColumnConfig {
141
+ // there must be at row in order to configure column
142
+ if len (t .rows ) == 0 {
143
+ return []table.ColumnConfig {}
80
144
}
81
145
82
- for _ , line := range t .rows {
83
- t .printRow (line )
146
+ colCount := len (t .rows [0 ])
147
+ var (
148
+ widestColIndicies []int
149
+ terminalWidth = terminalWidth ()
150
+ // total amount padding space that a row will take up
151
+ totalPaddingSpace = (colCount - 1 ) * minSpace
152
+ remainingSpace = max (0 , terminalWidth - totalPaddingSpace )
153
+ // the estimated max column width by dividing the remaining space evenly across the columns
154
+ maxColWidth = remainingSpace / colCount
155
+ )
156
+ columnConfig := make ([]table.ColumnConfig , colCount )
157
+
158
+ for colIndex := range columnConfig {
159
+ columnConfig [colIndex ] = table.ColumnConfig {
160
+ AlignHeader : text .AlignLeft ,
161
+ Align : text .AlignLeft ,
162
+ WidthMax : maxColWidth ,
163
+ Number : colIndex + 1 ,
164
+ }
165
+
166
+ // assuming the table has headers: store columns with wide content where the max width may need to be adjusted
167
+ // using the remaining space
168
+ if t .maxSizes [colIndex ] > maxColWidth && (colIndex < len (t .headers ) && isWideColumn (t .headers [colIndex ])) {
169
+ widestColIndicies = append (widestColIndicies , colIndex )
170
+ } else if t .maxSizes [colIndex ] < maxColWidth {
171
+ // use the max column width instead of the estimated max column width
172
+ // if it is shorter
173
+ columnConfig [colIndex ].WidthMax = t .maxSizes [colIndex ]
174
+ remainingSpace -= t .maxSizes [colIndex ]
175
+ } else {
176
+ remainingSpace -= maxColWidth
177
+ }
84
178
}
85
179
86
- t .rows = [][]string {}
87
- }
180
+ // if only one wide column use the remaining space as the max column width
181
+ if len (widestColIndicies ) == 1 {
182
+ widestColIndx := widestColIndicies [0 ]
183
+ columnConfig [widestColIndx ].WidthMax = remainingSpace
184
+ }
88
185
89
- func (t * PrintableTable ) calculateMaxSize (row []string ) {
90
- for index , value := range row {
91
- cellLength := runewidth .StringWidth (Decolorize (value ))
92
- if t .maxSizes [index ] < cellLength {
93
- t .maxSizes [index ] = cellLength
186
+ // if more than one wide column, spread the remaining space between the columns
187
+ if len (widestColIndicies ) > 1 {
188
+ remainingSpace /= len (widestColIndicies )
189
+ for _ , columnCfgIdx := range widestColIndicies {
190
+ columnConfig [columnCfgIdx ].WidthMax = remainingSpace
191
+ }
192
+
193
+ origRemainingSpace := remainingSpace
194
+ moreRemainingSpace := origRemainingSpace % len (widestColIndicies )
195
+ if moreRemainingSpace != 0 {
196
+ columnConfig [0 ].WidthMax += moreRemainingSpace
94
197
}
95
198
}
199
+
200
+ return columnConfig
96
201
}
97
202
98
- func (t * PrintableTable ) printHeader () {
99
- output := ""
100
- for col , value := range t .headers {
101
- output = output + t .cellValue (col , HeaderColor (value ))
203
+ func (t * PrintableTable ) createPrettyRowsAndHeaders () (headerRow table.Row , rows []table.Row ) {
204
+ for _ , header := range t .headers {
205
+ headerRow = append (headerRow , header )
102
206
}
103
- fmt .Fprintln (t .writer , output )
104
- }
105
207
106
- func (t * PrintableTable ) printRow (row []string ) {
107
- output := ""
108
- for columnIndex , value := range row {
109
- if columnIndex == 0 {
110
- value = TableContentHeaderColor (value )
208
+ for i := range t .rows {
209
+ var row , emptyRow table.Row
210
+ for j , cell := range t .rows [i ] {
211
+ if j == 0 {
212
+ cell = TableContentHeaderColor (cell )
213
+ }
214
+ row = append (row , cell )
215
+ emptyRow = append (emptyRow , "" )
111
216
}
112
-
113
- output = output + t .cellValue (columnIndex , value )
217
+ if i == 0 && len (t .headers ) == 0 {
218
+ rows = append (rows , emptyRow )
219
+ }
220
+ rows = append (rows , row )
114
221
}
115
- fmt .Fprintln (t .writer , output )
222
+
223
+ return
116
224
}
117
225
118
- func (t * PrintableTable ) cellValue (col int , value string ) string {
119
- padding := ""
120
- if col < len (t .maxSizes )- 1 {
121
- padding = strings .Repeat (" " , t .maxSizes [col ]- runewidth .StringWidth (Decolorize (value ))+ minSpace )
226
+ func (t * PrintableTable ) calculateMaxSize (row []string ) {
227
+ for index , value := range row {
228
+ cellLength := runewidth .StringWidth (Decolorize (value ))
229
+ if t .maxSizes [index ] < cellLength {
230
+ t .maxSizes [index ] = cellLength
231
+ }
122
232
}
123
- return fmt .Sprintf ("%s%s" , value , padding )
124
233
}
125
234
126
235
// Prints out a nicely/human formatted Json string instead of a table structure
0 commit comments