@@ -57,7 +57,7 @@ func filterTableColumns(header table.Row, columns []string) []table.ColumnConfig
57
57
// DisplayTable renders a table as a string. The input argument can be:
58
58
// - a struct slice.
59
59
// - an interface slice, where the first element is a struct,
60
- // and all other elements are of the same type, or a TableSeperator .
60
+ // and all other elements are of the same type, or a TableSeparator .
61
61
//
62
62
// At least one field in the struct must have a `table:""` tag
63
63
// containing the name of the column in the outputted table.
@@ -77,12 +77,12 @@ func DisplayTable(out any, sort string, filterColumns []string) (string, error)
77
77
v := reflect .Indirect (reflect .ValueOf (out ))
78
78
79
79
if v .Kind () != reflect .Slice {
80
- return "" , xerrors .Errorf ("DisplayTable called with a non-slice type" )
80
+ return "" , xerrors .New ("DisplayTable called with a non-slice type" )
81
81
}
82
82
var tableType reflect.Type
83
83
if v .Type ().Elem ().Kind () == reflect .Interface {
84
84
if v .Len () == 0 {
85
- return "" , xerrors .Errorf ("DisplayTable called with empty interface slice" )
85
+ return "" , xerrors .New ("DisplayTable called with empty interface slice" )
86
86
}
87
87
tableType = reflect .Indirect (reflect .ValueOf (v .Index (0 ).Interface ())).Type ()
88
88
} else {
@@ -100,6 +100,10 @@ func DisplayTable(out any, sort string, filterColumns []string) (string, error)
100
100
if sort == "" {
101
101
sort = defaultSort
102
102
}
103
+ headers := make (table.Row , len (headersRaw ))
104
+ for i , header := range headersRaw {
105
+ headers [i ] = strings .ReplaceAll (header , "_" , " " )
106
+ }
103
107
// Verify that the given sort column and filter columns are valid.
104
108
if sort != "" || len (filterColumns ) != 0 {
105
109
headersMap := make (map [string ]string , len (headersRaw ))
@@ -145,16 +149,12 @@ func DisplayTable(out any, sort string, filterColumns []string) (string, error)
145
149
return "" , xerrors .Errorf ("specified sort column %q not found in table headers, available columns are %q" , sort , strings .Join (headersRaw , `", "` ))
146
150
}
147
151
}
148
- return renderTable (out , sort , headersRaw , filterColumns )
152
+ return renderTable (out , sort , headers , filterColumns )
149
153
}
150
154
151
- func renderTable (out any , sort string , headersRaw [] string , filterColumns []string ) (string , error ) {
155
+ func renderTable (out any , sort string , headers table. Row , filterColumns []string ) (string , error ) {
152
156
v := reflect .Indirect (reflect .ValueOf (out ))
153
157
154
- headers := make (table.Row , len (headersRaw ))
155
- for i , header := range headersRaw {
156
- headers [i ] = header
157
- }
158
158
// Setup the table formatter.
159
159
tw := Table ()
160
160
tw .AppendHeader (headers )
@@ -181,8 +181,8 @@ func renderTable(out any, sort string, headersRaw []string, filterColumns []stri
181
181
}
182
182
183
183
rowSlice := make ([]any , len (headers ))
184
- for i , h := range headersRaw {
185
- v , ok := rowMap [h ]
184
+ for i , h := range headers {
185
+ v , ok := rowMap [h .( string ) ]
186
186
if ! ok {
187
187
v = nil
188
188
}
@@ -219,25 +219,28 @@ func renderTable(out any, sort string, headersRaw []string, filterColumns []stri
219
219
// returned. If the table tag is malformed, an error is returned.
220
220
//
221
221
// The returned name is transformed from "snake_case" to "normal text".
222
- func parseTableStructTag (field reflect.StructField ) (name string , defaultSort , recursive bool , skipParentName bool , err error ) {
222
+ func parseTableStructTag (field reflect.StructField ) (name string , defaultSort , noSortOpt , recursive , skipParentName bool , err error ) {
223
223
tags , err := structtag .Parse (string (field .Tag ))
224
224
if err != nil {
225
- return "" , false , false , false , xerrors .Errorf ("parse struct field tag %q: %w" , string (field .Tag ), err )
225
+ return "" , false , false , false , false , xerrors .Errorf ("parse struct field tag %q: %w" , string (field .Tag ), err )
226
226
}
227
227
228
228
tag , err := tags .Get ("table" )
229
229
if err != nil || tag .Name == "-" {
230
230
// tags.Get only returns an error if the tag is not found.
231
- return "" , false , false , false , nil
231
+ return "" , false , false , false , false , nil
232
232
}
233
233
234
234
defaultSortOpt := false
235
+ noSortOpt = false
235
236
recursiveOpt := false
236
237
skipParentNameOpt := false
237
238
for _ , opt := range tag .Options {
238
239
switch opt {
239
240
case "default_sort" :
240
241
defaultSortOpt = true
242
+ case "nosort" :
243
+ noSortOpt = true
241
244
case "recursive" :
242
245
recursiveOpt = true
243
246
case "recursive_inline" :
@@ -247,11 +250,11 @@ func parseTableStructTag(field reflect.StructField) (name string, defaultSort, r
247
250
recursiveOpt = true
248
251
skipParentNameOpt = true
249
252
default :
250
- return "" , false , false , false , xerrors .Errorf ("unknown option %q in struct field tag" , opt )
253
+ return "" , false , false , false , false , xerrors .Errorf ("unknown option %q in struct field tag" , opt )
251
254
}
252
255
}
253
256
254
- return strings .ReplaceAll (tag .Name , "_" , " " ), defaultSortOpt , recursiveOpt , skipParentNameOpt , nil
257
+ return strings .ReplaceAll (tag .Name , "_" , " " ), defaultSortOpt , noSortOpt , recursiveOpt , skipParentNameOpt , nil
255
258
}
256
259
257
260
func isStructOrStructPointer (t reflect.Type ) bool {
@@ -275,12 +278,16 @@ func typeToTableHeaders(t reflect.Type, requireDefault bool) ([]string, string,
275
278
276
279
headers := []string {}
277
280
defaultSortName := ""
281
+ noSortOpt := false
278
282
for i := 0 ; i < t .NumField (); i ++ {
279
283
field := t .Field (i )
280
- name , defaultSort , recursive , skip , err := parseTableStructTag (field )
284
+ name , defaultSort , noSort , recursive , skip , err := parseTableStructTag (field )
281
285
if err != nil {
282
286
return nil , "" , xerrors .Errorf ("parse struct tags for field %q in type %q: %w" , field .Name , t .String (), err )
283
287
}
288
+ if requireDefault && noSort {
289
+ noSortOpt = true
290
+ }
284
291
285
292
if name == "" && (recursive && skip ) {
286
293
return nil , "" , xerrors .Errorf ("a name is required for the field %q. " +
@@ -323,8 +330,8 @@ func typeToTableHeaders(t reflect.Type, requireDefault bool) ([]string, string,
323
330
headers = append (headers , name )
324
331
}
325
332
326
- if defaultSortName == "" && requireDefault {
327
- return nil , "" , xerrors .Errorf ("no field marked as default_sort in type %q" , t .String ())
333
+ if defaultSortName == "" && requireDefault && ! noSortOpt {
334
+ return nil , "" , xerrors .Errorf ("no field marked as default_sort or nosort in type %q" , t .String ())
328
335
}
329
336
330
337
return headers , defaultSortName , nil
@@ -351,7 +358,7 @@ func valueToTableMap(val reflect.Value) (map[string]any, error) {
351
358
for i := 0 ; i < val .NumField (); i ++ {
352
359
field := val .Type ().Field (i )
353
360
fieldVal := val .Field (i )
354
- name , _ , recursive , skip , err := parseTableStructTag (field )
361
+ name , _ , _ , recursive , skip , err := parseTableStructTag (field )
355
362
if err != nil {
356
363
return nil , xerrors .Errorf ("parse struct tags for field %q in type %T: %w" , field .Name , val , err )
357
364
}
0 commit comments