Skip to content
This repository was archived by the owner on Dec 13, 2025. It is now read-only.

Commit 0a02670

Browse files
author
Thomas von Dein
committed
fix #85: add --auto-headers and --custom-headers
1 parent 4ce6c30 commit 0a02670

File tree

7 files changed

+134
-7
lines changed

7 files changed

+134
-7
lines changed

cfg/config.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import (
2828
)
2929

3030
const (
31-
Version = "v1.5.9"
31+
Version = "v1.5.10"
3232
MAXPARTS = 2
3333
)
3434

@@ -93,6 +93,8 @@ type Config struct {
9393
UseHighlight bool
9494
Interactive bool
9595
InputJSON bool
96+
AutoHeaders bool
97+
CustomHeaders []string
9698

9799
SortMode string
98100
SortDescending bool
@@ -413,6 +415,12 @@ func (conf *Config) PreparePattern(patterns []*Pattern) error {
413415
return nil
414416
}
415417

418+
func (conf *Config) PrepareCustomHeaders(custom string) {
419+
if len(custom) > 0 {
420+
conf.CustomHeaders = strings.Split(custom, ",")
421+
}
422+
}
423+
416424
// Parse config file. Ignore if the file doesn't exist but return an
417425
// error if it exists but fails to read or parse
418426
func (conf *Config) ParseConfigfile() error {

cmd/root.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ func Execute() {
5959
ShowCompletion string
6060
modeflag cfg.Modeflag
6161
sortmode cfg.Sortmode
62+
headers string
6263
)
6364

6465
var rootCmd = &cobra.Command{
@@ -91,6 +92,7 @@ func Execute() {
9192
conf.CheckEnv()
9293
conf.PrepareModeFlags(modeflag)
9394
conf.PrepareSortFlags(sortmode)
95+
conf.PrepareCustomHeaders(headers)
9496

9597
wrapE(conf.PrepareFilters())
9698

@@ -137,6 +139,10 @@ func Execute() {
137139
"Output field separator (' ' for ascii table, ',' for CSV)")
138140
rootCmd.PersistentFlags().BoolVarP(&conf.InputJSON, "json", "j", false,
139141
"JSON input mode")
142+
rootCmd.PersistentFlags().BoolVarP(&conf.AutoHeaders, "auto-headers", "", false,
143+
"Generate headers automatically")
144+
rootCmd.PersistentFlags().StringVarP(&headers, "custom-headers", "", "",
145+
"Custom headers")
140146

141147
// sort options
142148
rootCmd.PersistentFlags().StringVarP(&conf.SortByColumn, "sort-by", "k", "",

cmd/tablizer.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ SYNOPSIS
2222
-R, --regex-transposer </from/to/> Apply /search/replace/ regexp to fields given in -T
2323
-j, --json Read JSON input (must be array of hashes)
2424
-I, --interactive Interactively filter and select rows
25+
--auto-headers Generate headers if there are none present in input
26+
--custom-headers a,b,... Use custom headers, separated by comma
2527
2628
Output Flags (mutually exclusive):
2729
-X, --extended Enable extended output
@@ -517,6 +519,8 @@ Operational Flags:
517519
-R, --regex-transposer </from/to/> Apply /search/replace/ regexp to fields given in -T
518520
-j, --json Read JSON input (must be array of hashes)
519521
-I, --interactive Interactively filter and select rows
522+
--auto-headers Generate headers if there are none present in input
523+
--custom-headers a,b,... Use custom headers, separated by comma
520524
521525
Output Flags (mutually exclusive):
522526
-X, --extended Enable extended output

lib/parser.go

Lines changed: 60 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,43 @@ func Parse(conf cfg.Config, input io.Reader) (Tabdata, error) {
6666
return data, err
6767
}
6868

69+
/*
70+
* Setup headers, given headers might be usable headers or just the
71+
* first row, which we use to determine how many headers to generate,
72+
* if enabled.
73+
*/
74+
func SetHeaders(conf cfg.Config, headers []string) []string {
75+
if !conf.AutoHeaders && len(conf.CustomHeaders) == 0 {
76+
return headers
77+
}
78+
79+
if conf.AutoHeaders {
80+
heads := make([]string, len(headers))
81+
for idx := range headers {
82+
heads[idx] = fmt.Sprintf("%d", idx+1)
83+
}
84+
85+
return heads
86+
}
87+
88+
if len(conf.CustomHeaders) == len(headers) {
89+
return conf.CustomHeaders
90+
}
91+
92+
// use as much custom ones we have, generate the remainder
93+
heads := make([]string, len(headers))
94+
95+
for idx := range headers {
96+
if idx < len(conf.CustomHeaders) {
97+
heads[idx] = conf.CustomHeaders[idx]
98+
} else {
99+
heads[idx] = fmt.Sprintf("%d", idx+1)
100+
}
101+
}
102+
103+
return heads
104+
}
105+
69106
/*
70107
Parse CSV input.
71108
*/
@@ -87,7 +124,7 @@ func parseCSV(conf cfg.Config, input io.Reader) (Tabdata, error) {
87124
}
88125

89126
if len(records) >= 1 {
90-
data.headers = records[0]
127+
data.headers = SetHeaders(conf, records[0])
91128
data.columns = len(records)
92129

93130
for _, head := range data.headers {
@@ -98,9 +135,14 @@ func parseCSV(conf cfg.Config, input io.Reader) (Tabdata, error) {
98135
}
99136
}
100137

101-
if len(records) > 1 {
102-
data.entries = records[1:]
138+
if len(records) >= 1 {
139+
if conf.AutoHeaders || len(conf.CustomHeaders) > 0 {
140+
data.entries = records
141+
} else {
142+
data.entries = records[1:]
143+
}
103144
}
145+
104146
}
105147

106148
return data, nil
@@ -128,19 +170,32 @@ func parseTabular(conf cfg.Config, input io.Reader) (Tabdata, error) {
128170
data.columns = len(parts)
129171

130172
// process all header fields
131-
for _, part := range parts {
173+
firstrow := make([]string, len(parts))
174+
175+
for idx, part := range parts {
132176
// register widest header field
133177
headerlen := len(part)
134178
if headerlen > data.maxwidthHeader {
135179
data.maxwidthHeader = headerlen
136180
}
137181

138182
// register fields data
139-
data.headers = append(data.headers, strings.TrimSpace(part))
183+
firstrow[idx] = strings.TrimSpace(part)
140184

141185
// done
142186
hadFirst = true
143187
}
188+
189+
data.headers = SetHeaders(conf, firstrow)
190+
191+
if conf.AutoHeaders || len(conf.CustomHeaders) > 0 {
192+
// we do not use generated headers, consider as row
193+
if matchPattern(conf, line) == conf.InvertMatch {
194+
continue
195+
}
196+
197+
data.entries = append(data.entries, firstrow)
198+
}
144199
} else {
145200
// data processing
146201
if matchPattern(conf, line) == conf.InvertMatch {

lib/parser_test.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,56 @@ func TestParserSeparators(t *testing.T) {
366366
}
367367
}
368368

369+
func TestParserSetHeaders(t *testing.T) {
370+
row := []string{"c", "b", "c", "d", "e"}
371+
372+
tests := []struct {
373+
name string
374+
custom []string
375+
expect []string
376+
auto bool
377+
}{
378+
{
379+
name: "default",
380+
expect: row,
381+
},
382+
{
383+
name: "auto",
384+
expect: strings.Split("1 2 3 4 5", " "),
385+
auto: true,
386+
},
387+
{
388+
name: "custom-complete",
389+
custom: strings.Split("A B C D E", " "),
390+
expect: strings.Split("A B C D E", " "),
391+
},
392+
{
393+
name: "custom-too-short",
394+
custom: strings.Split("A B", " "),
395+
expect: strings.Split("A B 3 4 5", " "),
396+
},
397+
{
398+
name: "custom-too-long",
399+
custom: strings.Split("A B C D E F G", " "),
400+
expect: strings.Split("A B C D E", " "),
401+
},
402+
}
403+
404+
for _, testdata := range tests {
405+
testname := fmt.Sprintf("parse-%s", testdata.name)
406+
t.Run(testname, func(t *testing.T) {
407+
conf := cfg.Config{
408+
AutoHeaders: testdata.auto,
409+
CustomHeaders: testdata.custom,
410+
}
411+
headers := SetHeaders(conf, row)
412+
413+
assert.NotNil(t, headers)
414+
assert.EqualValues(t, testdata.expect, headers)
415+
})
416+
}
417+
}
418+
369419
func wrapValidateParser(conf cfg.Config, input io.Reader) (Tabdata, error) {
370420
data, err := Parse(conf, input)
371421

tablizer.1

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@
133133
.\" ========================================================================
134134
.\"
135135
.IX Title "TABLIZER 1"
136-
.TH TABLIZER 1 "2025-10-09" "1" "User Commands"
136+
.TH TABLIZER 1 "2025-10-10" "1" "User Commands"
137137
.\" For nroff, turn off justification. Always turn off hyphenation; it makes
138138
.\" way too many mistakes in technical documents.
139139
.if n .ad l
@@ -160,6 +160,8 @@ tablizer \- Manipulate tabular output of other programs
160160
\& \-R, \-\-regex\-transposer </from/to/> Apply /search/replace/ regexp to fields given in \-T
161161
\& \-j, \-\-json Read JSON input (must be array of hashes)
162162
\& \-I, \-\-interactive Interactively filter and select rows
163+
\& \-\-auto\-headers Generate headers if there are none present in input
164+
\& \-\-custom\-headers a,b,... Use custom headers, separated by comma
163165
\&
164166
\& Output Flags (mutually exclusive):
165167
\& \-X, \-\-extended Enable extended output

tablizer.pod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ tablizer - Manipulate tabular output of other programs
2121
-R, --regex-transposer </from/to/> Apply /search/replace/ regexp to fields given in -T
2222
-j, --json Read JSON input (must be array of hashes)
2323
-I, --interactive Interactively filter and select rows
24+
--auto-headers Generate headers if there are none present in input
25+
--custom-headers a,b,... Use custom headers, separated by comma
2426

2527
Output Flags (mutually exclusive):
2628
-X, --extended Enable extended output

0 commit comments

Comments
 (0)