mirror of
https://codeberg.org/scip/tablizer.git
synced 2025-12-17 04:30:56 +01:00
@@ -28,7 +28,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
Version = "v1.5.9"
|
Version = "v1.5.10"
|
||||||
MAXPARTS = 2
|
MAXPARTS = 2
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -93,6 +93,8 @@ type Config struct {
|
|||||||
UseHighlight bool
|
UseHighlight bool
|
||||||
Interactive bool
|
Interactive bool
|
||||||
InputJSON bool
|
InputJSON bool
|
||||||
|
AutoHeaders bool
|
||||||
|
CustomHeaders []string
|
||||||
|
|
||||||
SortMode string
|
SortMode string
|
||||||
SortDescending bool
|
SortDescending bool
|
||||||
@@ -413,6 +415,12 @@ func (conf *Config) PreparePattern(patterns []*Pattern) error {
|
|||||||
return nil
|
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
|
// Parse config file. Ignore if the file doesn't exist but return an
|
||||||
// error if it exists but fails to read or parse
|
// error if it exists but fails to read or parse
|
||||||
func (conf *Config) ParseConfigfile() error {
|
func (conf *Config) ParseConfigfile() error {
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ func Execute() {
|
|||||||
ShowCompletion string
|
ShowCompletion string
|
||||||
modeflag cfg.Modeflag
|
modeflag cfg.Modeflag
|
||||||
sortmode cfg.Sortmode
|
sortmode cfg.Sortmode
|
||||||
|
headers string
|
||||||
)
|
)
|
||||||
|
|
||||||
var rootCmd = &cobra.Command{
|
var rootCmd = &cobra.Command{
|
||||||
@@ -91,6 +92,7 @@ func Execute() {
|
|||||||
conf.CheckEnv()
|
conf.CheckEnv()
|
||||||
conf.PrepareModeFlags(modeflag)
|
conf.PrepareModeFlags(modeflag)
|
||||||
conf.PrepareSortFlags(sortmode)
|
conf.PrepareSortFlags(sortmode)
|
||||||
|
conf.PrepareCustomHeaders(headers)
|
||||||
|
|
||||||
wrapE(conf.PrepareFilters())
|
wrapE(conf.PrepareFilters())
|
||||||
|
|
||||||
@@ -137,6 +139,10 @@ func Execute() {
|
|||||||
"Output field separator (' ' for ascii table, ',' for CSV)")
|
"Output field separator (' ' for ascii table, ',' for CSV)")
|
||||||
rootCmd.PersistentFlags().BoolVarP(&conf.InputJSON, "json", "j", false,
|
rootCmd.PersistentFlags().BoolVarP(&conf.InputJSON, "json", "j", false,
|
||||||
"JSON input mode")
|
"JSON input mode")
|
||||||
|
rootCmd.PersistentFlags().BoolVarP(&conf.AutoHeaders, "auto-headers", "", false,
|
||||||
|
"Generate headers automatically")
|
||||||
|
rootCmd.PersistentFlags().StringVarP(&headers, "custom-headers", "", "",
|
||||||
|
"Custom headers")
|
||||||
|
|
||||||
// sort options
|
// sort options
|
||||||
rootCmd.PersistentFlags().StringVarP(&conf.SortByColumn, "sort-by", "k", "",
|
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
|
-R, --regex-transposer </from/to/> Apply /search/replace/ regexp to fields given in -T
|
||||||
-j, --json Read JSON input (must be array of hashes)
|
-j, --json Read JSON input (must be array of hashes)
|
||||||
-I, --interactive Interactively filter and select rows
|
-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):
|
Output Flags (mutually exclusive):
|
||||||
-X, --extended Enable extended output
|
-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
|
-R, --regex-transposer </from/to/> Apply /search/replace/ regexp to fields given in -T
|
||||||
-j, --json Read JSON input (must be array of hashes)
|
-j, --json Read JSON input (must be array of hashes)
|
||||||
-I, --interactive Interactively filter and select rows
|
-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):
|
Output Flags (mutually exclusive):
|
||||||
-X, --extended Enable extended output
|
-X, --extended Enable extended output
|
||||||
|
|||||||
@@ -66,6 +66,43 @@ func Parse(conf cfg.Config, input io.Reader) (Tabdata, error) {
|
|||||||
return data, err
|
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.
|
Parse CSV input.
|
||||||
*/
|
*/
|
||||||
@@ -87,7 +124,7 @@ func parseCSV(conf cfg.Config, input io.Reader) (Tabdata, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(records) >= 1 {
|
if len(records) >= 1 {
|
||||||
data.headers = records[0]
|
data.headers = SetHeaders(conf, records[0])
|
||||||
data.columns = len(records)
|
data.columns = len(records)
|
||||||
|
|
||||||
for _, head := range data.headers {
|
for _, head := range data.headers {
|
||||||
@@ -98,9 +135,14 @@ func parseCSV(conf cfg.Config, input io.Reader) (Tabdata, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(records) > 1 {
|
if len(records) >= 1 {
|
||||||
data.entries = records[1:]
|
if conf.AutoHeaders || len(conf.CustomHeaders) > 0 {
|
||||||
|
data.entries = records
|
||||||
|
} else {
|
||||||
|
data.entries = records[1:]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return data, nil
|
return data, nil
|
||||||
@@ -128,7 +170,9 @@ func parseTabular(conf cfg.Config, input io.Reader) (Tabdata, error) {
|
|||||||
data.columns = len(parts)
|
data.columns = len(parts)
|
||||||
|
|
||||||
// process all header fields
|
// process all header fields
|
||||||
for _, part := range parts {
|
firstrow := make([]string, len(parts))
|
||||||
|
|
||||||
|
for idx, part := range parts {
|
||||||
// register widest header field
|
// register widest header field
|
||||||
headerlen := len(part)
|
headerlen := len(part)
|
||||||
if headerlen > data.maxwidthHeader {
|
if headerlen > data.maxwidthHeader {
|
||||||
@@ -136,11 +180,22 @@ func parseTabular(conf cfg.Config, input io.Reader) (Tabdata, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// register fields data
|
// register fields data
|
||||||
data.headers = append(data.headers, strings.TrimSpace(part))
|
firstrow[idx] = strings.TrimSpace(part)
|
||||||
|
|
||||||
// done
|
// done
|
||||||
hadFirst = true
|
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 {
|
} else {
|
||||||
// data processing
|
// data processing
|
||||||
if matchPattern(conf, line) == conf.InvertMatch {
|
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) {
|
func wrapValidateParser(conf cfg.Config, input io.Reader) (Tabdata, error) {
|
||||||
data, err := Parse(conf, input)
|
data, err := Parse(conf, input)
|
||||||
|
|
||||||
|
|||||||
@@ -133,7 +133,7 @@
|
|||||||
.\" ========================================================================
|
.\" ========================================================================
|
||||||
.\"
|
.\"
|
||||||
.IX Title "TABLIZER 1"
|
.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
|
.\" For nroff, turn off justification. Always turn off hyphenation; it makes
|
||||||
.\" way too many mistakes in technical documents.
|
.\" way too many mistakes in technical documents.
|
||||||
.if n .ad l
|
.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
|
\& \-R, \-\-regex\-transposer </from/to/> Apply /search/replace/ regexp to fields given in \-T
|
||||||
\& \-j, \-\-json Read JSON input (must be array of hashes)
|
\& \-j, \-\-json Read JSON input (must be array of hashes)
|
||||||
\& \-I, \-\-interactive Interactively filter and select rows
|
\& \-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):
|
\& Output Flags (mutually exclusive):
|
||||||
\& \-X, \-\-extended Enable extended output
|
\& \-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
|
-R, --regex-transposer </from/to/> Apply /search/replace/ regexp to fields given in -T
|
||||||
-j, --json Read JSON input (must be array of hashes)
|
-j, --json Read JSON input (must be array of hashes)
|
||||||
-I, --interactive Interactively filter and select rows
|
-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):
|
Output Flags (mutually exclusive):
|
||||||
-X, --extended Enable extended output
|
-X, --extended Enable extended output
|
||||||
|
|||||||
Reference in New Issue
Block a user