mirror of
https://codeberg.org/scip/tablizer.git
synced 2025-12-17 04:30:56 +01:00
add -k<name> and sort by multiple columns support, fixes #34
This commit is contained in:
@@ -99,6 +99,19 @@ func PrepareTransposerColumns(conf *cfg.Config, data *Tabdata) error {
|
||||
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) {
|
||||
if columns == "" {
|
||||
return nil, nil
|
||||
|
||||
@@ -48,6 +48,11 @@ func ProcessFiles(conf *cfg.Config, args []string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
err = PrepareSortColumns(conf, &data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = PrepareColumns(conf, &data)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -33,15 +33,17 @@ import (
|
||||
)
|
||||
|
||||
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)
|
||||
|
||||
// remove unwanted columns, if any
|
||||
reduceColumns(conf, data)
|
||||
|
||||
// sort the data
|
||||
sortTable(conf, data)
|
||||
|
||||
switch conf.OutputMode {
|
||||
case cfg.Extended:
|
||||
printExtendedData(writer, conf, data)
|
||||
|
||||
@@ -63,7 +63,7 @@ var tests = []struct {
|
||||
name string // so we can identify which one fails, can be the same
|
||||
// for multiple tests, because flags will be appended to the name
|
||||
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
|
||||
nonum bool // hide numbering
|
||||
mode int // shell, orgtbl, etc. empty == default: ascii
|
||||
@@ -162,7 +162,7 @@ DURATION(2): 33d12h
|
||||
|
||||
//------------------------ SORT TESTS
|
||||
{
|
||||
name: "sortbycolumn",
|
||||
name: "sortbycolumn3",
|
||||
column: 3,
|
||||
sortby: "numeric",
|
||||
desc: false,
|
||||
@@ -173,7 +173,7 @@ beta 1d10h5m1s 33 3/1/2014
|
||||
alpha 4h35m 170 2013-Feb-03`,
|
||||
},
|
||||
{
|
||||
name: "sortbycolumn",
|
||||
name: "sortbycolumn4",
|
||||
column: 4,
|
||||
sortby: "time",
|
||||
desc: false,
|
||||
@@ -184,7 +184,7 @@ alpha 4h35m 170 2013-Feb-03
|
||||
beta 1d10h5m1s 33 3/1/2014`,
|
||||
},
|
||||
{
|
||||
name: "sortbycolumn",
|
||||
name: "sortbycolumn2",
|
||||
column: 2,
|
||||
sortby: "duration",
|
||||
desc: false,
|
||||
@@ -251,15 +251,14 @@ DURATION(2) WHEN(4)
|
||||
|
||||
func TestPrinter(t *testing.T) {
|
||||
for _, testdata := range tests {
|
||||
testname := fmt.Sprintf("print-sortcol-%d-desc-%t-sortby-%s-mode-%d-usecolumns-%s",
|
||||
testdata.column, testdata.desc, testdata.sortby, testdata.mode, testdata.usecolstr)
|
||||
testname := fmt.Sprintf("print-%s-%d-desc-%t-sortby-%s-mode-%d-usecolumns-%s",
|
||||
testdata.name, testdata.column, testdata.desc, testdata.sortby, testdata.mode, testdata.usecolstr)
|
||||
t.Run(testname, func(t *testing.T) {
|
||||
// replaces os.Stdout, but we ignore it
|
||||
var writer bytes.Buffer
|
||||
|
||||
// cmd flags
|
||||
conf := cfg.Config{
|
||||
SortByColumn: testdata.column,
|
||||
SortDescending: testdata.desc,
|
||||
SortMode: testdata.sortby,
|
||||
OutputMode: testdata.mode,
|
||||
@@ -268,6 +267,10 @@ func TestPrinter(t *testing.T) {
|
||||
NoColor: true,
|
||||
}
|
||||
|
||||
if testdata.column > 0 {
|
||||
conf.UseSortByColumn = []int{testdata.column}
|
||||
}
|
||||
|
||||
conf.ApplyDefaults()
|
||||
|
||||
// the test checks the len!
|
||||
|
||||
41
lib/sort.go
41
lib/sort.go
@@ -18,6 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
package lib
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
@@ -27,34 +28,41 @@ import (
|
||||
)
|
||||
|
||||
func sortTable(conf cfg.Config, data *Tabdata) {
|
||||
if conf.SortByColumn <= 0 {
|
||||
if len(conf.UseSortByColumn) == 0 {
|
||||
// no sorting wanted
|
||||
return
|
||||
}
|
||||
|
||||
// slightly modified here to match internal array indicies
|
||||
col := conf.SortByColumn
|
||||
|
||||
col-- // ui starts counting by 1, but use 0 internally
|
||||
|
||||
// sanity checks
|
||||
if len(data.entries) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if col >= len(data.headers) {
|
||||
// fall back to default column
|
||||
col = 0
|
||||
}
|
||||
|
||||
// actual sorting
|
||||
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
|
||||
func compare(conf *cfg.Config, left string, right string) bool {
|
||||
func compare(conf *cfg.Config, left string, right string) int {
|
||||
var comp bool
|
||||
|
||||
switch conf.SortMode {
|
||||
@@ -88,7 +96,12 @@ func compare(conf *cfg.Config, left string, right string) bool {
|
||||
comp = !comp
|
||||
}
|
||||
|
||||
return comp
|
||||
switch comp {
|
||||
case true:
|
||||
return 0
|
||||
default:
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -53,18 +53,18 @@ func TestCompare(t *testing.T) {
|
||||
mode string
|
||||
a string
|
||||
b string
|
||||
want bool
|
||||
want int
|
||||
desc bool
|
||||
}{
|
||||
// ascending
|
||||
{"numeric", "10", "20", true, false},
|
||||
{"duration", "2d4h5m", "45m", false, false},
|
||||
{"time", "12/24/2022", "1/1/1970", false, false},
|
||||
{"numeric", "10", "20", 0, false},
|
||||
{"duration", "2d4h5m", "45m", 1, false},
|
||||
{"time", "12/24/2022", "1/1/1970", 1, false},
|
||||
|
||||
// descending
|
||||
{"numeric", "10", "20", false, true},
|
||||
{"duration", "2d4h5m", "45m", true, true},
|
||||
{"time", "12/24/2022", "1/1/1970", true, true},
|
||||
{"numeric", "10", "20", 1, true},
|
||||
{"duration", "2d4h5m", "45m", 0, true},
|
||||
{"time", "12/24/2022", "1/1/1970", 0, true},
|
||||
}
|
||||
|
||||
for _, testdata := range tests {
|
||||
@@ -75,7 +75,7 @@ func TestCompare(t *testing.T) {
|
||||
c := cfg.Config{SortMode: testdata.mode, SortDescending: testdata.desc}
|
||||
got := compare(&c, testdata.a, testdata.b)
|
||||
if got != testdata.want {
|
||||
t.Errorf("got %t, want %t", got, testdata.want)
|
||||
t.Errorf("got %d, want %d", got, testdata.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user