mirror of
https://codeberg.org/scip/tablizer.git
synced 2025-12-16 20:20:57 +01:00
@@ -28,7 +28,7 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
Version = "v1.5.9"
|
||||
Version = "v1.5.10"
|
||||
MAXPARTS = 2
|
||||
)
|
||||
|
||||
@@ -93,6 +93,8 @@ type Config struct {
|
||||
UseHighlight bool
|
||||
Interactive bool
|
||||
InputJSON bool
|
||||
AutoHeaders bool
|
||||
CustomHeaders []string
|
||||
|
||||
SortMode string
|
||||
SortDescending bool
|
||||
@@ -413,6 +415,12 @@ func (conf *Config) PreparePattern(patterns []*Pattern) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (conf *Config) PrepareCustomHeaders(custom string) {
|
||||
if len(custom) > 0 {
|
||||
conf.CustomHeaders = strings.Split(custom, ",")
|
||||
}
|
||||
}
|
||||
|
||||
// Parse config file. Ignore if the file doesn't exist but return an
|
||||
// error if it exists but fails to read or parse
|
||||
func (conf *Config) ParseConfigfile() error {
|
||||
|
||||
@@ -59,6 +59,7 @@ func Execute() {
|
||||
ShowCompletion string
|
||||
modeflag cfg.Modeflag
|
||||
sortmode cfg.Sortmode
|
||||
headers string
|
||||
)
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
@@ -91,6 +92,7 @@ func Execute() {
|
||||
conf.CheckEnv()
|
||||
conf.PrepareModeFlags(modeflag)
|
||||
conf.PrepareSortFlags(sortmode)
|
||||
conf.PrepareCustomHeaders(headers)
|
||||
|
||||
wrapE(conf.PrepareFilters())
|
||||
|
||||
@@ -137,6 +139,10 @@ func Execute() {
|
||||
"Output field separator (' ' for ascii table, ',' for CSV)")
|
||||
rootCmd.PersistentFlags().BoolVarP(&conf.InputJSON, "json", "j", false,
|
||||
"JSON input mode")
|
||||
rootCmd.PersistentFlags().BoolVarP(&conf.AutoHeaders, "auto-headers", "", false,
|
||||
"Generate headers automatically")
|
||||
rootCmd.PersistentFlags().StringVarP(&headers, "custom-headers", "", "",
|
||||
"Custom headers")
|
||||
|
||||
// sort options
|
||||
rootCmd.PersistentFlags().StringVarP(&conf.SortByColumn, "sort-by", "k", "",
|
||||
|
||||
@@ -22,6 +22,8 @@ SYNOPSIS
|
||||
-R, --regex-transposer </from/to/> Apply /search/replace/ regexp to fields given in -T
|
||||
-j, --json Read JSON input (must be array of hashes)
|
||||
-I, --interactive Interactively filter and select rows
|
||||
--auto-headers Generate headers if there are none present in input
|
||||
--custom-headers a,b,... Use custom headers, separated by comma
|
||||
|
||||
Output Flags (mutually exclusive):
|
||||
-X, --extended Enable extended output
|
||||
@@ -517,6 +519,8 @@ Operational Flags:
|
||||
-R, --regex-transposer </from/to/> Apply /search/replace/ regexp to fields given in -T
|
||||
-j, --json Read JSON input (must be array of hashes)
|
||||
-I, --interactive Interactively filter and select rows
|
||||
--auto-headers Generate headers if there are none present in input
|
||||
--custom-headers a,b,... Use custom headers, separated by comma
|
||||
|
||||
Output Flags (mutually exclusive):
|
||||
-X, --extended Enable extended output
|
||||
|
||||
@@ -66,6 +66,43 @@ func Parse(conf cfg.Config, input io.Reader) (Tabdata, error) {
|
||||
return data, err
|
||||
}
|
||||
|
||||
/*
|
||||
* Setup headers, given headers might be usable headers or just the
|
||||
* first row, which we use to determine how many headers to generate,
|
||||
* if enabled.
|
||||
*/
|
||||
func SetHeaders(conf cfg.Config, headers []string) []string {
|
||||
if !conf.AutoHeaders && len(conf.CustomHeaders) == 0 {
|
||||
return headers
|
||||
}
|
||||
|
||||
if conf.AutoHeaders {
|
||||
heads := make([]string, len(headers))
|
||||
for idx := range headers {
|
||||
heads[idx] = fmt.Sprintf("%d", idx+1)
|
||||
}
|
||||
|
||||
return heads
|
||||
}
|
||||
|
||||
if len(conf.CustomHeaders) == len(headers) {
|
||||
return conf.CustomHeaders
|
||||
}
|
||||
|
||||
// use as much custom ones we have, generate the remainder
|
||||
heads := make([]string, len(headers))
|
||||
|
||||
for idx := range headers {
|
||||
if idx < len(conf.CustomHeaders) {
|
||||
heads[idx] = conf.CustomHeaders[idx]
|
||||
} else {
|
||||
heads[idx] = fmt.Sprintf("%d", idx+1)
|
||||
}
|
||||
}
|
||||
|
||||
return heads
|
||||
}
|
||||
|
||||
/*
|
||||
Parse CSV input.
|
||||
*/
|
||||
@@ -87,7 +124,7 @@ func parseCSV(conf cfg.Config, input io.Reader) (Tabdata, error) {
|
||||
}
|
||||
|
||||
if len(records) >= 1 {
|
||||
data.headers = records[0]
|
||||
data.headers = SetHeaders(conf, records[0])
|
||||
data.columns = len(records)
|
||||
|
||||
for _, head := range data.headers {
|
||||
@@ -98,9 +135,14 @@ func parseCSV(conf cfg.Config, input io.Reader) (Tabdata, error) {
|
||||
}
|
||||
}
|
||||
|
||||
if len(records) > 1 {
|
||||
data.entries = records[1:]
|
||||
if len(records) >= 1 {
|
||||
if conf.AutoHeaders || len(conf.CustomHeaders) > 0 {
|
||||
data.entries = records
|
||||
} else {
|
||||
data.entries = records[1:]
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return data, nil
|
||||
@@ -128,7 +170,9 @@ func parseTabular(conf cfg.Config, input io.Reader) (Tabdata, error) {
|
||||
data.columns = len(parts)
|
||||
|
||||
// process all header fields
|
||||
for _, part := range parts {
|
||||
firstrow := make([]string, len(parts))
|
||||
|
||||
for idx, part := range parts {
|
||||
// register widest header field
|
||||
headerlen := len(part)
|
||||
if headerlen > data.maxwidthHeader {
|
||||
@@ -136,11 +180,22 @@ func parseTabular(conf cfg.Config, input io.Reader) (Tabdata, error) {
|
||||
}
|
||||
|
||||
// register fields data
|
||||
data.headers = append(data.headers, strings.TrimSpace(part))
|
||||
firstrow[idx] = strings.TrimSpace(part)
|
||||
|
||||
// done
|
||||
hadFirst = true
|
||||
}
|
||||
|
||||
data.headers = SetHeaders(conf, firstrow)
|
||||
|
||||
if conf.AutoHeaders || len(conf.CustomHeaders) > 0 {
|
||||
// we do not use generated headers, consider as row
|
||||
if matchPattern(conf, line) == conf.InvertMatch {
|
||||
continue
|
||||
}
|
||||
|
||||
data.entries = append(data.entries, firstrow)
|
||||
}
|
||||
} else {
|
||||
// data processing
|
||||
if matchPattern(conf, line) == conf.InvertMatch {
|
||||
|
||||
@@ -366,6 +366,56 @@ func TestParserSeparators(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestParserSetHeaders(t *testing.T) {
|
||||
row := []string{"c", "b", "c", "d", "e"}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
custom []string
|
||||
expect []string
|
||||
auto bool
|
||||
}{
|
||||
{
|
||||
name: "default",
|
||||
expect: row,
|
||||
},
|
||||
{
|
||||
name: "auto",
|
||||
expect: strings.Split("1 2 3 4 5", " "),
|
||||
auto: true,
|
||||
},
|
||||
{
|
||||
name: "custom-complete",
|
||||
custom: strings.Split("A B C D E", " "),
|
||||
expect: strings.Split("A B C D E", " "),
|
||||
},
|
||||
{
|
||||
name: "custom-too-short",
|
||||
custom: strings.Split("A B", " "),
|
||||
expect: strings.Split("A B 3 4 5", " "),
|
||||
},
|
||||
{
|
||||
name: "custom-too-long",
|
||||
custom: strings.Split("A B C D E F G", " "),
|
||||
expect: strings.Split("A B C D E", " "),
|
||||
},
|
||||
}
|
||||
|
||||
for _, testdata := range tests {
|
||||
testname := fmt.Sprintf("parse-%s", testdata.name)
|
||||
t.Run(testname, func(t *testing.T) {
|
||||
conf := cfg.Config{
|
||||
AutoHeaders: testdata.auto,
|
||||
CustomHeaders: testdata.custom,
|
||||
}
|
||||
headers := SetHeaders(conf, row)
|
||||
|
||||
assert.NotNil(t, headers)
|
||||
assert.EqualValues(t, testdata.expect, headers)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func wrapValidateParser(conf cfg.Config, input io.Reader) (Tabdata, error) {
|
||||
data, err := Parse(conf, input)
|
||||
|
||||
|
||||
@@ -133,7 +133,7 @@
|
||||
.\" ========================================================================
|
||||
.\"
|
||||
.IX Title "TABLIZER 1"
|
||||
.TH TABLIZER 1 "2025-10-09" "1" "User Commands"
|
||||
.TH TABLIZER 1 "2025-10-10" "1" "User Commands"
|
||||
.\" For nroff, turn off justification. Always turn off hyphenation; it makes
|
||||
.\" way too many mistakes in technical documents.
|
||||
.if n .ad l
|
||||
@@ -160,6 +160,8 @@ tablizer \- Manipulate tabular output of other programs
|
||||
\& \-R, \-\-regex\-transposer </from/to/> Apply /search/replace/ regexp to fields given in \-T
|
||||
\& \-j, \-\-json Read JSON input (must be array of hashes)
|
||||
\& \-I, \-\-interactive Interactively filter and select rows
|
||||
\& \-\-auto\-headers Generate headers if there are none present in input
|
||||
\& \-\-custom\-headers a,b,... Use custom headers, separated by comma
|
||||
\&
|
||||
\& Output Flags (mutually exclusive):
|
||||
\& \-X, \-\-extended Enable extended output
|
||||
|
||||
@@ -21,6 +21,8 @@ tablizer - Manipulate tabular output of other programs
|
||||
-R, --regex-transposer </from/to/> Apply /search/replace/ regexp to fields given in -T
|
||||
-j, --json Read JSON input (must be array of hashes)
|
||||
-I, --interactive Interactively filter and select rows
|
||||
--auto-headers Generate headers if there are none present in input
|
||||
--custom-headers a,b,... Use custom headers, separated by comma
|
||||
|
||||
Output Flags (mutually exclusive):
|
||||
-X, --extended Enable extended output
|
||||
|
||||
Reference in New Issue
Block a user