@@ -22,10 +22,10 @@ func Table() table.Writer {
22
22
return tableWriter
23
23
}
24
24
25
- // FilterTableColumns returns configurations to hide columns
25
+ // filterTableColumns returns configurations to hide columns
26
26
// that are not provided in the array. If the array is empty,
27
27
// no filtering will occur!
28
- func FilterTableColumns (header table.Row , columns []string ) []table.ColumnConfig {
28
+ func filterTableColumns (header table.Row , columns []string ) []table.ColumnConfig {
29
29
if len (columns ) == 0 {
30
30
return nil
31
31
}
@@ -51,6 +51,9 @@ func FilterTableColumns(header table.Row, columns []string) []table.ColumnConfig
51
51
// of structs. At least one field in the struct must have a `table:""` tag
52
52
// containing the name of the column in the outputted table.
53
53
//
54
+ // If `sort` is not specified, the field with the `table:"$NAME,default_sort"`
55
+ // tag will be used to sort. An error will be returned if no field has this tag.
56
+ //
54
57
// Nested structs are processed if the field has the `table:"$NAME,recursive"`
55
58
// tag and their fields will be named as `$PARENT_NAME $NAME`. If the tag is
56
59
// malformed or a field is marked as recursive but does not contain a struct or
@@ -67,13 +70,16 @@ func DisplayTable(out any, sort string, filterColumns []string) (string, error)
67
70
}
68
71
69
72
// Get the list of table column headers.
70
- headersRaw , err := typeToTableHeaders (v .Type ().Elem ())
73
+ headersRaw , defaultSort , err := typeToTableHeaders (v .Type ().Elem ())
71
74
if err != nil {
72
75
return "" , xerrors .Errorf ("get table headers recursively for type %q: %w" , v .Type ().Elem ().String (), err )
73
76
}
74
77
if len (headersRaw ) == 0 {
75
78
return "" , xerrors .New (`no table headers found on the input type, make sure there is at least one "table" struct tag` )
76
79
}
80
+ if sort == "" {
81
+ sort = defaultSort
82
+ }
77
83
headers := make (table.Row , len (headersRaw ))
78
84
for i , header := range headersRaw {
79
85
headers [i ] = header
@@ -128,7 +134,7 @@ func DisplayTable(out any, sort string, filterColumns []string) (string, error)
128
134
// Setup the table formatter.
129
135
tw := Table ()
130
136
tw .AppendHeader (headers )
131
- tw .SetColumnConfigs (FilterTableColumns (headers , filterColumns ))
137
+ tw .SetColumnConfigs (filterTableColumns (headers , filterColumns ))
132
138
if sort != "" {
133
139
tw .SortBy ([]table.SortBy {{
134
140
Name : sort ,
@@ -182,29 +188,32 @@ func DisplayTable(out any, sort string, filterColumns []string) (string, error)
182
188
// returned. If the table tag is malformed, an error is returned.
183
189
//
184
190
// The returned name is transformed from "snake_case" to "normal text".
185
- func parseTableStructTag (field reflect.StructField ) (name string , recurse bool , err error ) {
191
+ func parseTableStructTag (field reflect.StructField ) (name string , defaultSort , recursive bool , err error ) {
186
192
tags , err := structtag .Parse (string (field .Tag ))
187
193
if err != nil {
188
- return "" , false , xerrors .Errorf ("parse struct field tag %q: %w" , string (field .Tag ), err )
194
+ return "" , false , false , xerrors .Errorf ("parse struct field tag %q: %w" , string (field .Tag ), err )
189
195
}
190
196
191
197
tag , err := tags .Get ("table" )
192
198
if err != nil || tag .Name == "-" {
193
199
// tags.Get only returns an error if the tag is not found.
194
- return "" , false , nil
200
+ return "" , false , false , nil
195
201
}
196
202
197
- recursive := false
203
+ defaultSortOpt := false
204
+ recursiveOpt := false
198
205
for _ , opt := range tag .Options {
199
- if opt == "recursive" {
200
- recursive = true
201
- continue
206
+ switch opt {
207
+ case "default_sort" :
208
+ defaultSortOpt = true
209
+ case "recursive" :
210
+ recursiveOpt = true
211
+ default :
212
+ return "" , false , false , xerrors .Errorf ("unknown option %q in struct field tag" , opt )
202
213
}
203
-
204
- return "" , false , xerrors .Errorf ("unknown option %q in struct field tag" , opt )
205
214
}
206
215
207
- return strings .ReplaceAll (tag .Name , "_" , " " ), recursive , nil
216
+ return strings .ReplaceAll (tag .Name , "_" , " " ), defaultSortOpt , recursiveOpt , nil
208
217
}
209
218
210
219
func isStructOrStructPointer (t reflect.Type ) bool {
@@ -214,34 +223,41 @@ func isStructOrStructPointer(t reflect.Type) bool {
214
223
// typeToTableHeaders converts a type to a slice of column names. If the given
215
224
// type is invalid (not a struct or a pointer to a struct, has invalid table
216
225
// tags, etc.), an error is returned.
217
- func typeToTableHeaders (t reflect.Type ) ([]string , error ) {
226
+ func typeToTableHeaders (t reflect.Type ) ([]string , string , error ) {
218
227
if ! isStructOrStructPointer (t ) {
219
- return nil , xerrors .Errorf ("typeToTableHeaders called with a non-struct or a non-pointer-to-a-struct type" )
228
+ return nil , "" , xerrors .Errorf ("typeToTableHeaders called with a non-struct or a non-pointer-to-a-struct type" )
220
229
}
221
230
if t .Kind () == reflect .Pointer {
222
231
t = t .Elem ()
223
232
}
224
233
225
234
headers := []string {}
235
+ defaultSortName := ""
226
236
for i := 0 ; i < t .NumField (); i ++ {
227
237
field := t .Field (i )
228
- name , recursive , err := parseTableStructTag (field )
238
+ name , defaultSort , recursive , err := parseTableStructTag (field )
229
239
if err != nil {
230
- return nil , xerrors .Errorf ("parse struct tags for field %q in type %q: %w" , field .Name , t .String (), err )
240
+ return nil , "" , xerrors .Errorf ("parse struct tags for field %q in type %q: %w" , field .Name , t .String (), err )
231
241
}
232
242
if name == "" {
233
243
continue
234
244
}
245
+ if defaultSort {
246
+ if defaultSortName != "" {
247
+ return nil , "" , xerrors .Errorf ("multiple fields marked as default sort in type %q" , t .String ())
248
+ }
249
+ defaultSortName = name
250
+ }
235
251
236
252
fieldType := field .Type
237
253
if recursive {
238
254
if ! isStructOrStructPointer (fieldType ) {
239
- return nil , xerrors .Errorf ("field %q in type %q is marked as recursive but does not contain a struct or a pointer to a struct" , field .Name , t .String ())
255
+ return nil , "" , xerrors .Errorf ("field %q in type %q is marked as recursive but does not contain a struct or a pointer to a struct" , field .Name , t .String ())
240
256
}
241
257
242
- childNames , err := typeToTableHeaders (fieldType )
258
+ childNames , _ , err := typeToTableHeaders (fieldType )
243
259
if err != nil {
244
- return nil , xerrors .Errorf ("get child field header names for field %q in type %q: %w" , field .Name , fieldType .String (), err )
260
+ return nil , "" , xerrors .Errorf ("get child field header names for field %q in type %q: %w" , field .Name , fieldType .String (), err )
245
261
}
246
262
for _ , childName := range childNames {
247
263
headers = append (headers , fmt .Sprintf ("%s %s" , name , childName ))
@@ -252,7 +268,11 @@ func typeToTableHeaders(t reflect.Type) ([]string, error) {
252
268
headers = append (headers , name )
253
269
}
254
270
255
- return headers , nil
271
+ if defaultSortName == "" {
272
+ return nil , "" , xerrors .Errorf ("no field marked as default_sort in type %q" , t .String ())
273
+ }
274
+
275
+ return headers , defaultSortName , nil
256
276
}
257
277
258
278
// valueToTableMap converts a struct to a map of column name to value. If the
@@ -276,7 +296,7 @@ func valueToTableMap(val reflect.Value) (map[string]any, error) {
276
296
for i := 0 ; i < val .NumField (); i ++ {
277
297
field := val .Type ().Field (i )
278
298
fieldVal := val .Field (i )
279
- name , recursive , err := parseTableStructTag (field )
299
+ name , _ , recursive , err := parseTableStructTag (field )
280
300
if err != nil {
281
301
return nil , xerrors .Errorf ("parse struct tags for field %q in type %T: %w" , field .Name , val , err )
282
302
}
@@ -309,18 +329,3 @@ func valueToTableMap(val reflect.Value) (map[string]any, error) {
309
329
310
330
return row , nil
311
331
}
312
-
313
- // TableHeaders returns the table header names of all
314
- // fields in tSlice. tSlice must be a slice of some type.
315
- func TableHeaders (tSlice any ) ([]string , error ) {
316
- v := reflect .Indirect (reflect .ValueOf (tSlice ))
317
- rawHeaders , err := typeToTableHeaders (v .Type ().Elem ())
318
- if err != nil {
319
- return nil , xerrors .Errorf ("type to table headers: %w" , err )
320
- }
321
- out := make ([]string , 0 , len (rawHeaders ))
322
- for _ , hdr := range rawHeaders {
323
- out = append (out , strings .Replace (hdr , " " , "_" , - 1 ))
324
- }
325
- return out , nil
326
- }
0 commit comments