added -k parameter to sort by columns

This commit is contained in:
2022-10-13 18:56:34 +02:00
parent 6eedb60a6a
commit 8e2ba58ddb
9 changed files with 195 additions and 22 deletions

View File

@@ -90,6 +90,7 @@ func init() {
rootCmd.PersistentFlags().BoolVarP(&ShowManual, "man", "m", false, "Display manual page") rootCmd.PersistentFlags().BoolVarP(&ShowManual, "man", "m", false, "Display manual page")
rootCmd.PersistentFlags().StringVarP(&lib.Separator, "separator", "s", lib.DefaultSeparator, "Custom field separator") rootCmd.PersistentFlags().StringVarP(&lib.Separator, "separator", "s", lib.DefaultSeparator, "Custom field separator")
rootCmd.PersistentFlags().StringVarP(&lib.Columns, "columns", "c", "", "Only show the speficied columns (separated by ,)") rootCmd.PersistentFlags().StringVarP(&lib.Columns, "columns", "c", "", "Only show the speficied columns (separated by ,)")
rootCmd.PersistentFlags().IntVarP(&lib.SortByColumn, "sort-by", "k", 0, "Sort by column (default: 1)")
// output flags, only 1 allowed, hidden, since just short cuts // output flags, only 1 allowed, hidden, since just short cuts
rootCmd.PersistentFlags().BoolVarP(&lib.OutflagExtended, "extended", "X", false, "Enable extended output") rootCmd.PersistentFlags().BoolVarP(&lib.OutflagExtended, "extended", "X", false, "Enable extended output")

View File

@@ -20,6 +20,7 @@ SYNOPSIS
-M, --markdown Enable markdown table output -M, --markdown Enable markdown table output
-O, --orgtbl Enable org-mode table output -O, --orgtbl Enable org-mode table output
-s, --separator string Custom field separator -s, --separator string Custom field separator
-k, --sort-by int Sort by column (default: 1)
-v, --version Print program version -v, --version Print program version
DESCRIPTION DESCRIPTION
@@ -74,6 +75,10 @@ DESCRIPTION
By default, if a pattern has been speficied, matches will be By default, if a pattern has been speficied, matches will be
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
(as in GNU sort(1)). The default sort column is the first one. To
disable sorting at all, supply 0 (Zero) to -k.
Finally the -d option enables debugging output which is mostly usefull Finally the -d option enables debugging output which is mostly usefull
for the developer. for the developer.

View File

@@ -68,10 +68,22 @@ var (
validOutputmodes = "(orgtbl|markdown|extended|ascii)" validOutputmodes = "(orgtbl|markdown|extended|ascii)"
// main program version // main program version
Version = "v1.0.7" Version = "v1.0.8"
// generated version string, used by -v contains lib.Version on // generated version string, used by -v contains lib.Version on
// main branch, and lib.Version-$branch-$lastcommit-$date on // main branch, and lib.Version-$branch-$lastcommit-$date on
// development branch // development branch
VERSION string VERSION string
// sorting
SortByColumn int
) )
// contains a whole parsed table
type Tabdata struct {
maxwidthHeader int // longest header
maxwidthPerCol []int // max width per column
columns int // count
headers []string // [ "ID", "NAME", ...]
entries [][]string
}

View File

@@ -27,15 +27,6 @@ import (
"strings" "strings"
) )
// contains a whole parsed table
type Tabdata struct {
maxwidthHeader int // longest header
maxwidthPerCol []int // max width per column
columns int // count
headers []string // [ "ID", "NAME", ...]
entries [][]string
}
/* /*
Parse tabular input. Parse tabular input.
*/ */

View File

@@ -26,11 +26,19 @@ import (
) )
func printData(data *Tabdata) { func printData(data *Tabdata) {
// some output preparations:
if OutputMode != "shell" { if OutputMode != "shell" {
// not needed in eval string
numberizeHeaders(data) numberizeHeaders(data)
} }
// remove unwanted columns, if any
reduceColumns(data) reduceColumns(data)
// sort the data
sortTable(data, SortByColumn)
switch OutputMode { switch OutputMode {
case "extended": case "extended":
printExtendedData(data) printExtendedData(data)

View File

@@ -25,6 +25,20 @@ import (
"testing" "testing"
) )
func stdout2pipe(t *testing.T) (*os.File, *os.File) {
reader, writer, err := os.Pipe()
if err != nil {
t.Fatal(err)
}
origStdout := os.Stdout
os.Stdout = writer
// we need to tell the color mode the io.Writer, even if we don't usw colorization
color.SetOutput(writer)
return origStdout, reader
}
func TestPrinter(t *testing.T) { func TestPrinter(t *testing.T) {
startdata := Tabdata{ startdata := Tabdata{
maxwidthHeader: 5, maxwidthHeader: 5,
@@ -51,18 +65,22 @@ func TestPrinter(t *testing.T) {
"ascii": `ONE(1) TWO(2) THREE(3) "ascii": `ONE(1) TWO(2) THREE(3)
asd igig cxxxncnc asd igig cxxxncnc
19191 EDD 1 X`, 19191 EDD 1 X`,
"orgtbl": `|--------+--------+----------| "orgtbl": `|--------+--------+----------|
| ONE(1) | TWO(2) | THREE(3) | | ONE(1) | TWO(2) | THREE(3) |
|--------+--------+----------| |--------+--------+----------|
| asd | igig | cxxxncnc | | asd | igig | cxxxncnc |
| 19191 | EDD 1 | X | | 19191 | EDD 1 | X |
|--------+--------+----------|`, |--------+--------+----------|`,
"markdown": `| ONE(1) | TWO(2) | THREE(3) | "markdown": `| ONE(1) | TWO(2) | THREE(3) |
|--------|--------|----------| |--------|--------|----------|
| asd | igig | cxxxncnc | | asd | igig | cxxxncnc |
| 19191 | EDD 1 | X |`, | 19191 | EDD 1 | X |`,
"shell": `ONE="asd" TWO="igig" THREE="cxxxncnc" "shell": `ONE="asd" TWO="igig" THREE="cxxxncnc"
ONE="19191" TWO="EDD 1" THREE="X"`, ONE="19191" TWO="EDD 1" THREE="X"`,
"extended": `ONE(1): asd "extended": `ONE(1): asd
TWO(2): igig TWO(2): igig
THREE(3): cxxxncnc THREE(3): cxxxncnc
@@ -73,16 +91,9 @@ THREE(3): X`,
} }
NoColor = true NoColor = true
SortByColumn = 0 // disable sorting
r, w, err := os.Pipe() origStdout, reader := stdout2pipe(t)
if err != nil {
t.Fatal(err)
}
origStdout := os.Stdout
os.Stdout = w
// we need to tell the color mode the io.Writer, even if we don't usw colorization
color.SetOutput(w)
for mode, expect := range expects { for mode, expect := range expects {
testname := fmt.Sprintf("print-%s", mode) testname := fmt.Sprintf("print-%s", mode)
@@ -93,7 +104,7 @@ THREE(3): X`,
printData(&data) printData(&data)
buf := make([]byte, 1024) buf := make([]byte, 1024)
n, err := r.Read(buf) n, err := reader.Read(buf)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -101,7 +112,8 @@ THREE(3): X`,
output := strings.TrimSpace(string(buf)) output := strings.TrimSpace(string(buf))
if output != expect { if output != expect {
t.Errorf("output mode: %s, got:\n%s\nwant:\n%s\n (%d <=> %d)", mode, output, expect, len(output), len(expect)) t.Errorf("output mode: %s, got:\n%s\nwant:\n%s\n (%d <=> %d)",
mode, output, expect, len(output), len(expect))
} }
}) })
} }
@@ -110,3 +122,91 @@ THREE(3): X`,
os.Stdout = origStdout os.Stdout = origStdout
} }
func TestSortPrinter(t *testing.T) {
startdata := Tabdata{
maxwidthHeader: 5,
maxwidthPerCol: []int{
3,
3,
2,
},
columns: 3,
headers: []string{
"ONE", "TWO", "THREE",
},
entries: [][]string{
[]string{
"abc", "345", "b1",
},
[]string{
"bcd", "234", "a2",
},
[]string{
"cde", "123", "c3",
},
},
}
var tests = []struct {
data Tabdata
sortby int
expect string
}{
{
data: startdata,
sortby: 1,
expect: `ONE(1) TWO(2) THREE(3)
abc 345 b1
bcd 234 a2
cde 123 c3`,
},
{
data: startdata,
sortby: 2,
expect: `ONE(1) TWO(2) THREE(3)
cde 123 c3
bcd 234 a2
abc 345 b1`,
},
{
data: startdata,
sortby: 3,
expect: `ONE(1) TWO(2) THREE(3)
bcd 234 a2
abc 345 b1
cde 123 c3`,
},
}
NoColor = true
OutputMode = "ascii"
origStdout, reader := stdout2pipe(t)
for _, tt := range tests {
testname := fmt.Sprintf("print-sorted-table-%d", tt.sortby)
t.Run(testname, func(t *testing.T) {
SortByColumn = tt.sortby
printData(&tt.data)
buf := make([]byte, 1024)
n, err := reader.Read(buf)
if err != nil {
t.Fatal(err)
}
buf = buf[:n]
output := strings.TrimSpace(string(buf))
if output != tt.expect {
t.Errorf("sort column: %d, got:\n%s\nwant:\n%s",
tt.sortby, output, tt.expect)
}
})
}
// Restore
os.Stdout = origStdout
}

46
lib/sort.go Normal file
View File

@@ -0,0 +1,46 @@
/*
Copyright © 2022 Thomas von Dein
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package lib
import (
"sort"
)
func sortTable(data *Tabdata, col int) {
if col <= 0 {
// no sorting wanted
return
}
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 data.entries[i][col] < data.entries[j][col]
})
}

View File

@@ -133,7 +133,7 @@
.\" ======================================================================== .\" ========================================================================
.\" .\"
.IX Title "TABLIZER 1" .IX Title "TABLIZER 1"
.TH TABLIZER 1 "2022-10-10" "1" "User Commands" .TH TABLIZER 1 "2022-10-13" "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
@@ -159,6 +159,7 @@ tablizer \- Manipulate tabular output of other programs
\& \-M, \-\-markdown Enable markdown table output \& \-M, \-\-markdown Enable markdown table output
\& \-O, \-\-orgtbl Enable org\-mode table output \& \-O, \-\-orgtbl Enable org\-mode table output
\& \-s, \-\-separator string Custom field separator \& \-s, \-\-separator string Custom field separator
\& \-k, \-\-sort\-by int Sort by column (default: 1)
\& \-v, \-\-version Print program version \& \-v, \-\-version Print program version
.Ve .Ve
.SH "DESCRIPTION" .SH "DESCRIPTION"
@@ -222,6 +223,10 @@ The numbering can be suppressed by using the \fB\-n\fR option.
By default, if a \fBpattern\fR has been speficied, matches will be 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
data (as in \s-1GNU\s0 \fBsort\fR\|(1)). The default sort column is the first one. To
disable sorting at all, supply 0 (Zero) to \-k.
.PP
Finally the \fB\-d\fR option enables debugging output which is mostly Finally the \fB\-d\fR option enables debugging output which is mostly
usefull for the developer. usefull for the developer.
.SS "\s-1PATTERNS\s0" .SS "\s-1PATTERNS\s0"

View File

@@ -20,6 +20,7 @@ tablizer - Manipulate tabular output of other programs
-M, --markdown Enable markdown table output -M, --markdown Enable markdown table output
-O, --orgtbl Enable org-mode table output -O, --orgtbl Enable org-mode table output
-s, --separator string Custom field separator -s, --separator string Custom field separator
-k, --sort-by int Sort by column (default: 1)
-v, --version Print program version -v, --version Print program version
@@ -78,6 +79,10 @@ The numbering can be suppressed by using the B<-n> option.
By default, if a B<pattern> has been speficied, matches will be 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
data (as in GNU sort(1)). The default sort column is the first one. To
disable sorting at all, supply 0 (Zero) to -k.
Finally the B<-d> option enables debugging output which is mostly Finally the B<-d> option enables debugging output which is mostly
usefull for the developer. usefull for the developer.