add -k<name> and sort by multiple columns support, fixes #34

This commit is contained in:
2025-01-15 18:51:15 +01:00
parent c2e7d8037a
commit 35de2fea2f
13 changed files with 131 additions and 57 deletions

View File

@@ -28,7 +28,7 @@ import (
) )
const DefaultSeparator string = `(\s\s+|\t)` const DefaultSeparator string = `(\s\s+|\t)`
const Version string = "v1.3.0" const Version string = "v1.3.1"
const MAXPARTS = 2 const MAXPARTS = 2
var DefaultConfigfile = os.Getenv("HOME") + "/.config/tablizer/config" var DefaultConfigfile = os.Getenv("HOME") + "/.config/tablizer/config"
@@ -69,7 +69,8 @@ type Config struct {
SortMode string SortMode string
SortDescending bool SortDescending bool
SortByColumn int SortByColumn string // 1,2
UseSortByColumn []int // []int{1,2}
TransposeColumns string // 1,2 TransposeColumns string // 1,2
UseTransposeColumns []int // []int{1,2} UseTransposeColumns []int // []int{1,2}

View File

@@ -151,7 +151,7 @@ func Execute() {
"Transpose the speficied columns (separated by ,)") "Transpose the speficied columns (separated by ,)")
// sort options // sort options
rootCmd.PersistentFlags().IntVarP(&conf.SortByColumn, "sort-by", "k", 0, rootCmd.PersistentFlags().StringVarP(&conf.SortByColumn, "sort-by", "k", "",
"Sort by column (default: 1)") "Sort by column (default: 1)")
// sort mode, only 1 allowed // sort mode, only 1 allowed

View File

@@ -15,7 +15,7 @@ SYNOPSIS
-N, --no-color Disable pattern highlighting -N, --no-color Disable pattern highlighting
-H, --no-headers Disable headers display -H, --no-headers Disable headers display
-s, --separator string Custom field separator -s, --separator string Custom field separator
-k, --sort-by int Sort by column (default: 1) -k, --sort-by int|name Sort by column (default: 1)
-z, --fuzzy Use fuzzy search [experimental] -z, --fuzzy Use fuzzy search [experimental]
-F, --filter field=reg Filter given field with regex, can be used multiple times -F, --filter field=reg Filter given field with regex, can be used multiple times
-T, --transpose-columns string Transpose the speficied columns (separated by ,) -T, --transpose-columns string Transpose the speficied columns (separated by ,)
@@ -103,10 +103,19 @@ DESCRIPTION
highlighted. You can disable this behavior with the -N option. highlighted. You can disable this behavior with the -N option.
Use the -k option to specify by which column to sort the tabular data Use the -k option to specify by which column to sort the tabular data
(as in GNU sort(1)). The default sort column is the first one. To (as in GNU sort(1)). The default sort column is the first one. You can
disable sorting at all, supply 0 (Zero) to -k. The default sort order is specify column numbers or names. Column numbers start with 1, names are
ascending. You can change this to descending order using the option -D. case insensitive. You can specify multiple columns separated by comma to
The default sort order is by string, but there are other sort modes: sort, but the type must be the same. For example if you want to sort
numerically, all columns must be numbers. If you use column numbers,
then be aware, that these are the numbers before column extraction. For
example if you have a table with 4 columns and specify "-c4", then only
1 column (the fourth) will be printed, however if you want to sort by
this column, you'll have to specify "-k4".
The default sort order is ascending. You can change this to descending
order using the option -D. The default sort order is by alphanumeric
string, but there are other sort modes:
-a --sort-age -a --sort-age
Sorts duration strings like "1d4h32m51s". Sorts duration strings like "1d4h32m51s".
@@ -392,7 +401,7 @@ Operational Flags:
-N, --no-color Disable pattern highlighting -N, --no-color Disable pattern highlighting
-H, --no-headers Disable headers display -H, --no-headers Disable headers display
-s, --separator string Custom field separator -s, --separator string Custom field separator
-k, --sort-by int Sort by column (default: 1) -k, --sort-by int|name Sort by column (default: 1)
-z, --fuzzy Use fuzzy search [experimental] -z, --fuzzy Use fuzzy search [experimental]
-F, --filter field=reg Filter given field with regex, can be used multiple times -F, --filter field=reg Filter given field with regex, can be used multiple times
-T, --transpose-columns string Transpose the speficied columns (separated by ,) -T, --transpose-columns string Transpose the speficied columns (separated by ,)

View File

@@ -99,6 +99,19 @@ func PrepareTransposerColumns(conf *cfg.Config, data *Tabdata) error {
return nil return nil
} }
// output option, prepare -k1,2 sort fields
func PrepareSortColumns(conf *cfg.Config, data *Tabdata) error {
// -c columns
usecolumns, err := PrepareColumnVars(conf.SortByColumn, data)
if err != nil {
return err
}
conf.UseSortByColumn = usecolumns
return nil
}
func PrepareColumnVars(columns string, data *Tabdata) ([]int, error) { func PrepareColumnVars(columns string, data *Tabdata) ([]int, error) {
if columns == "" { if columns == "" {
return nil, nil return nil, nil

View File

@@ -48,6 +48,11 @@ func ProcessFiles(conf *cfg.Config, args []string) error {
return err return err
} }
err = PrepareSortColumns(conf, &data)
if err != nil {
return err
}
err = PrepareColumns(conf, &data) err = PrepareColumns(conf, &data)
if err != nil { if err != nil {
return err return err

View File

@@ -33,15 +33,17 @@ import (
) )
func printData(writer io.Writer, conf cfg.Config, data *Tabdata) { func printData(writer io.Writer, conf cfg.Config, data *Tabdata) {
// add numbers to headers and remove this we're not interested in // Sort the data first, before headers+entries are being
// reduced. That way the user can specify any valid column to sort
// by, independently if it's being used for display or not.
sortTable(conf, data)
// add numbers to headers and remove those we're not interested in
numberizeAndReduceHeaders(conf, data) numberizeAndReduceHeaders(conf, data)
// remove unwanted columns, if any // remove unwanted columns, if any
reduceColumns(conf, data) reduceColumns(conf, data)
// sort the data
sortTable(conf, data)
switch conf.OutputMode { switch conf.OutputMode {
case cfg.Extended: case cfg.Extended:
printExtendedData(writer, conf, data) printExtendedData(writer, conf, data)

View File

@@ -63,7 +63,7 @@ var tests = []struct {
name string // so we can identify which one fails, can be the same name string // so we can identify which one fails, can be the same
// for multiple tests, because flags will be appended to the name // for multiple tests, because flags will be appended to the name
sortby string // empty == default sortby string // empty == default
column int // sort by this column, 0 == default first or NO Sort column int // sort by this column (numbers start by 1)
desc bool // sort in descending order, default == ascending desc bool // sort in descending order, default == ascending
nonum bool // hide numbering nonum bool // hide numbering
mode int // shell, orgtbl, etc. empty == default: ascii mode int // shell, orgtbl, etc. empty == default: ascii
@@ -162,7 +162,7 @@ DURATION(2): 33d12h
//------------------------ SORT TESTS //------------------------ SORT TESTS
{ {
name: "sortbycolumn", name: "sortbycolumn3",
column: 3, column: 3,
sortby: "numeric", sortby: "numeric",
desc: false, desc: false,
@@ -173,7 +173,7 @@ beta 1d10h5m1s 33 3/1/2014
alpha 4h35m 170 2013-Feb-03`, alpha 4h35m 170 2013-Feb-03`,
}, },
{ {
name: "sortbycolumn", name: "sortbycolumn4",
column: 4, column: 4,
sortby: "time", sortby: "time",
desc: false, desc: false,
@@ -184,7 +184,7 @@ alpha 4h35m 170 2013-Feb-03
beta 1d10h5m1s 33 3/1/2014`, beta 1d10h5m1s 33 3/1/2014`,
}, },
{ {
name: "sortbycolumn", name: "sortbycolumn2",
column: 2, column: 2,
sortby: "duration", sortby: "duration",
desc: false, desc: false,
@@ -251,15 +251,14 @@ DURATION(2) WHEN(4)
func TestPrinter(t *testing.T) { func TestPrinter(t *testing.T) {
for _, testdata := range tests { for _, testdata := range tests {
testname := fmt.Sprintf("print-sortcol-%d-desc-%t-sortby-%s-mode-%d-usecolumns-%s", testname := fmt.Sprintf("print-%s-%d-desc-%t-sortby-%s-mode-%d-usecolumns-%s",
testdata.column, testdata.desc, testdata.sortby, testdata.mode, testdata.usecolstr) testdata.name, testdata.column, testdata.desc, testdata.sortby, testdata.mode, testdata.usecolstr)
t.Run(testname, func(t *testing.T) { t.Run(testname, func(t *testing.T) {
// replaces os.Stdout, but we ignore it // replaces os.Stdout, but we ignore it
var writer bytes.Buffer var writer bytes.Buffer
// cmd flags // cmd flags
conf := cfg.Config{ conf := cfg.Config{
SortByColumn: testdata.column,
SortDescending: testdata.desc, SortDescending: testdata.desc,
SortMode: testdata.sortby, SortMode: testdata.sortby,
OutputMode: testdata.mode, OutputMode: testdata.mode,
@@ -268,6 +267,10 @@ func TestPrinter(t *testing.T) {
NoColor: true, NoColor: true,
} }
if testdata.column > 0 {
conf.UseSortByColumn = []int{testdata.column}
}
conf.ApplyDefaults() conf.ApplyDefaults()
// the test checks the len! // the test checks the len!

View File

@@ -18,6 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
package lib package lib
import ( import (
"cmp"
"regexp" "regexp"
"sort" "sort"
"strconv" "strconv"
@@ -27,34 +28,41 @@ import (
) )
func sortTable(conf cfg.Config, data *Tabdata) { func sortTable(conf cfg.Config, data *Tabdata) {
if conf.SortByColumn <= 0 { if len(conf.UseSortByColumn) == 0 {
// no sorting wanted // no sorting wanted
return return
} }
// slightly modified here to match internal array indicies
col := conf.SortByColumn
col-- // ui starts counting by 1, but use 0 internally
// sanity checks // sanity checks
if len(data.entries) == 0 { if len(data.entries) == 0 {
return return
} }
if col >= len(data.headers) {
// fall back to default column
col = 0
}
// actual sorting // actual sorting
sort.SliceStable(data.entries, func(i, j int) bool { sort.SliceStable(data.entries, func(i, j int) bool {
return compare(&conf, data.entries[i][col], data.entries[j][col]) // holds the result of a sort of one column
comparators := []int{}
// iterate over all columns to be sorted, conf.SortMode must be identical!
for _, column := range conf.UseSortByColumn {
comparators = append(comparators, compare(&conf, data.entries[i][column-1], data.entries[j][column-1]))
}
// return the combined result
res := cmp.Or(comparators...)
switch res {
case 0:
return true
default:
return false
}
}) })
} }
// config is not modified here, but it would be inefficient to copy it every loop // config is not modified here, but it would be inefficient to copy it every loop
func compare(conf *cfg.Config, left string, right string) bool { func compare(conf *cfg.Config, left string, right string) int {
var comp bool var comp bool
switch conf.SortMode { switch conf.SortMode {
@@ -88,7 +96,12 @@ func compare(conf *cfg.Config, left string, right string) bool {
comp = !comp comp = !comp
} }
return comp switch comp {
case true:
return 0
default:
return 1
}
} }
/* /*

View File

@@ -53,18 +53,18 @@ func TestCompare(t *testing.T) {
mode string mode string
a string a string
b string b string
want bool want int
desc bool desc bool
}{ }{
// ascending // ascending
{"numeric", "10", "20", true, false}, {"numeric", "10", "20", 0, false},
{"duration", "2d4h5m", "45m", false, false}, {"duration", "2d4h5m", "45m", 1, false},
{"time", "12/24/2022", "1/1/1970", false, false}, {"time", "12/24/2022", "1/1/1970", 1, false},
// descending // descending
{"numeric", "10", "20", false, true}, {"numeric", "10", "20", 1, true},
{"duration", "2d4h5m", "45m", true, true}, {"duration", "2d4h5m", "45m", 0, true},
{"time", "12/24/2022", "1/1/1970", true, true}, {"time", "12/24/2022", "1/1/1970", 0, true},
} }
for _, testdata := range tests { for _, testdata := range tests {
@@ -75,7 +75,7 @@ func TestCompare(t *testing.T) {
c := cfg.Config{SortMode: testdata.mode, SortDescending: testdata.desc} c := cfg.Config{SortMode: testdata.mode, SortDescending: testdata.desc}
got := compare(&c, testdata.a, testdata.b) got := compare(&c, testdata.a, testdata.b)
if got != testdata.want { if got != testdata.want {
t.Errorf("got %t, want %t", got, testdata.want) t.Errorf("got %d, want %d", got, testdata.want)
} }
}) })
} }

6
t/testtable3 Normal file
View File

@@ -0,0 +1,6 @@
NAME READY STATUS STARTS AGE
alertmanager-kube-prometheus-alertmanager-0 2/2 Running 35 11d
kube-prometheus-blackbox-exporter-5d85b5d8f4-tskh7 1/1 Running 17 1h44m
grafana-fcc54cbc9-bk7s8 1/1 Running 17 1d
kube-prometheus-kube-state-metrics-b4cd9487-75p7f 1/1 Running 20 45m
kube-prometheus-node-exporter-bfzpl 1/1 Running 17 54s

4
t/testtable4 Normal file
View File

@@ -0,0 +1,4 @@
ONE TWO
1 4
3 1
5 2

View File

@@ -133,7 +133,7 @@
.\" ======================================================================== .\" ========================================================================
.\" .\"
.IX Title "TABLIZER 1" .IX Title "TABLIZER 1"
.TH TABLIZER 1 "2025-01-14" "1" "User Commands" .TH TABLIZER 1 "2025-01-15" "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
@@ -153,7 +153,7 @@ tablizer \- Manipulate tabular output of other programs
\& \-N, \-\-no\-color Disable pattern highlighting \& \-N, \-\-no\-color Disable pattern highlighting
\& \-H, \-\-no\-headers Disable headers display \& \-H, \-\-no\-headers Disable headers display
\& \-s, \-\-separator string Custom field separator \& \-s, \-\-separator string Custom field separator
\& \-k, \-\-sort\-by int Sort by column (default: 1) \& \-k, \-\-sort\-by int|name Sort by column (default: 1)
\& \-z, \-\-fuzzy Use fuzzy search [experimental] \& \-z, \-\-fuzzy Use fuzzy search [experimental]
\& \-F, \-\-filter field=reg Filter given field with regex, can be used multiple times \& \-F, \-\-filter field=reg Filter given field with regex, can be used multiple times
\& \-T, \-\-transpose\-columns string Transpose the speficied columns (separated by ,) \& \-T, \-\-transpose\-columns string Transpose the speficied columns (separated by ,)
@@ -250,11 +250,20 @@ By default, if a \fBpattern\fR has been speficied, matches will be
highlighted. You can disable this behavior with the \fB\-N\fR option. highlighted. You can disable this behavior with the \fB\-N\fR option.
.PP .PP
Use the \fB\-k\fR option to specify by which column to sort the tabular Use the \fB\-k\fR option to specify by which column to sort the tabular
data (as in \s-1GNU\s0 \fBsort\fR\|(1)). The default sort column is the first one. To data (as in \s-1GNU\s0 \fBsort\fR\|(1)). The default sort column is the first
disable sorting at all, supply 0 (Zero) to \-k. The default sort order one. You can specify column numbers or names. Column numbers start
is ascending. You can change this to descending order using the option with 1, names are case insensitive. You can specify multiple columns
\&\fB\-D\fR. The default sort order is by string, but there are other sort separated by comma to sort, but the type must be the same. For example
modes: if you want to sort numerically, all columns must be numbers. If you
use column numbers, then be aware, that these are the numbers before
column extraction. For example if you have a table with 4 columns and
specify \f(CW\*(C`\-c4\*(C'\fR, then only 1 column (the fourth) will be printed,
however if you want to sort by this column, you'll have to specify
\&\f(CW\*(C`\-k4\*(C'\fR.
.PP
The default sort order is ascending. You can change this to
descending order using the option \fB\-D\fR. The default sort order is by
alphanumeric string, but there are other sort modes:
.IP "\fB\-a \-\-sort\-age\fR" 4 .IP "\fB\-a \-\-sort\-age\fR" 4
.IX Item "-a --sort-age" .IX Item "-a --sort-age"
Sorts duration strings like \*(L"1d4h32m51s\*(R". Sorts duration strings like \*(L"1d4h32m51s\*(R".

View File

@@ -14,7 +14,7 @@ tablizer - Manipulate tabular output of other programs
-N, --no-color Disable pattern highlighting -N, --no-color Disable pattern highlighting
-H, --no-headers Disable headers display -H, --no-headers Disable headers display
-s, --separator string Custom field separator -s, --separator string Custom field separator
-k, --sort-by int Sort by column (default: 1) -k, --sort-by int|name Sort by column (default: 1)
-z, --fuzzy Use fuzzy search [experimental] -z, --fuzzy Use fuzzy search [experimental]
-F, --filter field=reg Filter given field with regex, can be used multiple times -F, --filter field=reg Filter given field with regex, can be used multiple times
-T, --transpose-columns string Transpose the speficied columns (separated by ,) -T, --transpose-columns string Transpose the speficied columns (separated by ,)
@@ -106,11 +106,20 @@ By default, if a B<pattern> has been speficied, matches will be
highlighted. You can disable this behavior with the B<-N> option. highlighted. You can disable this behavior with the B<-N> option.
Use the B<-k> option to specify by which column to sort the tabular Use the B<-k> option to specify by which column to sort the tabular
data (as in GNU sort(1)). The default sort column is the first one. To data (as in GNU sort(1)). The default sort column is the first
disable sorting at all, supply 0 (Zero) to -k. The default sort order one. You can specify column numbers or names. Column numbers start
is ascending. You can change this to descending order using the option with 1, names are case insensitive. You can specify multiple columns
B<-D>. The default sort order is by string, but there are other sort separated by comma to sort, but the type must be the same. For example
modes: if you want to sort numerically, all columns must be numbers. If you
use column numbers, then be aware, that these are the numbers before
column extraction. For example if you have a table with 4 columns and
specify C<-c4>, then only 1 column (the fourth) will be printed,
however if you want to sort by this column, you'll have to specify
C<-k4>.
The default sort order is ascending. You can change this to
descending order using the option B<-D>. The default sort order is by
alphanumeric string, but there are other sort modes:
=over =over