Skip to content

Commit 6a0d537

Browse files
committed
adding auto header for struct
1 parent 97c640a commit 6a0d537

File tree

5 files changed

+63
-12
lines changed

5 files changed

+63
-12
lines changed

config.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -688,6 +688,12 @@ func (bb *BehaviorConfigBuilder) WithCompactMerge(state tw.State) *BehaviorConfi
688688
return bb
689689
}
690690

691+
// WithAutoHeader enables/disables automatic header extraction for structs in Bulk.
692+
func (bb *BehaviorConfigBuilder) WithAutoHeader(state tw.State) *BehaviorConfigBuilder {
693+
bb.config.AutoHeader = state
694+
return bb
695+
}
696+
691697
// ColumnConfigBuilder configures column-specific settings
692698
type ColumnConfigBuilder struct {
693699
parent *ConfigBuilder

option.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -715,8 +715,9 @@ func defaultConfig() Config {
715715
},
716716
Debug: false,
717717
Behavior: tw.Behavior{
718-
AutoHide: tw.Off,
719-
TrimSpace: tw.On,
718+
AutoHide: tw.Off,
719+
TrimSpace: tw.On,
720+
AutoHeader: tw.Off,
720721
},
721722
}
722723
}

tablewriter.go

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -219,26 +219,34 @@ func (t *Table) Append(rows ...interface{}) error { // rows is already []interfa
219219
return nil
220220
}
221221

222-
// Bulk adds multiple rows from a slice to the table (legacy method).
223-
// Parameter rows must be a slice compatible with stringer or []string.
224-
// Returns an error if the input is invalid or appending fails.
222+
// Bulk adds multiple rows from a slice to the table.
223+
// If Behavior.AutoHeader is enabled, no headers set, and rows is a slice of structs,
224+
// automatically extracts/sets headers from the first struct.
225225
func (t *Table) Bulk(rows interface{}) error {
226-
t.logger.Debug("Starting Bulk operation")
227226
rv := reflect.ValueOf(rows)
228227
if rv.Kind() != reflect.Slice {
229-
err := errors.Newf("Bulk expects a slice, got %T", rows)
230-
t.logger.Debugf("Bulk error: %v", err)
231-
return err
228+
return errors.Newf("Bulk expects a slice, got %T", rows)
229+
}
230+
if rv.Len() == 0 {
231+
return nil
232+
}
233+
234+
if t.config.Behavior.AutoHeader.Enabled() && len(t.headers) == 0 {
235+
first := rv.Index(0).Interface()
236+
if reflect.TypeOf(first).Kind() == reflect.Struct {
237+
headers := t.extractHeadersFromStruct(first)
238+
if len(headers) > 0 {
239+
t.Header(headers)
240+
}
241+
}
232242
}
243+
233244
for i := 0; i < rv.Len(); i++ {
234245
row := rv.Index(i).Interface()
235-
t.logger.Debugf("Processing bulk row %d: %v", i, row)
236246
if err := t.appendSingle(row); err != nil {
237-
t.logger.Debugf("Bulk append failed at index %d: %v", i, err)
238247
return err
239248
}
240249
}
241-
t.logger.Debugf("Bulk completed, processed %d rows", rv.Len())
242250
return nil
243251
}
244252

tw/types.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,11 @@ type Behavior struct {
152152
// Compact enables optimized width calculation for merged cells, such as in horizontal merges,
153153
// by systematically determining the most efficient width instead of scaling by the number of columns.
154154
Compact Compact
155+
156+
// AutoHeader automatically extracts and sets headers from struct fields when Bulk is called with a slice of structs.
157+
// Uses JSON tags if present, falls back to field names (title-cased). Skips unexported or json:"-" fields.
158+
// Enabled by default for convenience.
159+
AutoHeader State
155160
}
156161

157162
// Padding defines the spacing characters around cell content in all four directions.

zoo.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1694,3 +1694,34 @@ func (t *Table) updateWidths(row []string, widths tw.Mapper[int, int], padding t
16941694
}
16951695
}
16961696
}
1697+
1698+
// extractHeadersFromStruct generates table headers from a sample struct.
1699+
// Internal: Uses JSON tags if available (splitting on ","), falls back to field name (title-cased).
1700+
// Skips unexported or json:"-" fields.
1701+
func (t *Table) extractHeadersFromStruct(sample interface{}) []string {
1702+
v := reflect.ValueOf(sample)
1703+
if v.Kind() == reflect.Ptr {
1704+
v = v.Elem()
1705+
}
1706+
if v.Kind() != reflect.Struct {
1707+
return nil
1708+
}
1709+
typ := v.Type()
1710+
headers := make([]string, 0, typ.NumField())
1711+
for i := 0; i < typ.NumField(); i++ {
1712+
field := typ.Field(i)
1713+
if field.PkgPath != "" { // Unexported
1714+
continue
1715+
}
1716+
jsonTag := field.Tag.Get("json")
1717+
if jsonTag == "-" {
1718+
continue
1719+
}
1720+
name := field.Name
1721+
if jsonTag != "" {
1722+
name = strings.Split(jsonTag, ",")[0]
1723+
}
1724+
headers = append(headers, tw.Title(name))
1725+
}
1726+
return headers
1727+
}

0 commit comments

Comments
 (0)