From 8e2ba58ddba402f766a154dfa62519b938b73f07 Mon Sep 17 00:00:00 2001 From: Thomas von Dein Date: Thu, 13 Oct 2022 18:56:34 +0200 Subject: [PATCH] added -k parameter to sort by columns --- cmd/root.go | 1 + cmd/tablizer.go | 5 ++ lib/common.go | 14 ++++- lib/parser.go | 9 ---- lib/printer.go | 8 +++ lib/printer_test.go | 122 ++++++++++++++++++++++++++++++++++++++++---- lib/sort.go | 46 +++++++++++++++++ tablizer.1 | 7 ++- tablizer.pod | 5 ++ 9 files changed, 195 insertions(+), 22 deletions(-) create mode 100644 lib/sort.go diff --git a/cmd/root.go b/cmd/root.go index 32ce390..24815af 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -90,6 +90,7 @@ func init() { 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.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 rootCmd.PersistentFlags().BoolVarP(&lib.OutflagExtended, "extended", "X", false, "Enable extended output") diff --git a/cmd/tablizer.go b/cmd/tablizer.go index 69c11d8..54328eb 100644 --- a/cmd/tablizer.go +++ b/cmd/tablizer.go @@ -20,6 +20,7 @@ SYNOPSIS -M, --markdown Enable markdown table output -O, --orgtbl Enable org-mode table output -s, --separator string Custom field separator + -k, --sort-by int Sort by column (default: 1) -v, --version Print program version DESCRIPTION @@ -74,6 +75,10 @@ DESCRIPTION By default, if a pattern has been speficied, matches will be 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 for the developer. diff --git a/lib/common.go b/lib/common.go index e7fcdec..1279e7c 100644 --- a/lib/common.go +++ b/lib/common.go @@ -68,10 +68,22 @@ var ( validOutputmodes = "(orgtbl|markdown|extended|ascii)" // main program version - Version = "v1.0.7" + Version = "v1.0.8" // generated version string, used by -v contains lib.Version on // main branch, and lib.Version-$branch-$lastcommit-$date on // development branch 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 +} diff --git a/lib/parser.go b/lib/parser.go index 981ceda..04894f9 100644 --- a/lib/parser.go +++ b/lib/parser.go @@ -27,15 +27,6 @@ import ( "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. */ diff --git a/lib/printer.go b/lib/printer.go index 6f3f76e..6eb8178 100644 --- a/lib/printer.go +++ b/lib/printer.go @@ -26,11 +26,19 @@ import ( ) func printData(data *Tabdata) { + // some output preparations: + if OutputMode != "shell" { + // not needed in eval string numberizeHeaders(data) } + + // remove unwanted columns, if any reduceColumns(data) + // sort the data + sortTable(data, SortByColumn) + switch OutputMode { case "extended": printExtendedData(data) diff --git a/lib/printer_test.go b/lib/printer_test.go index a0b132f..a615e50 100644 --- a/lib/printer_test.go +++ b/lib/printer_test.go @@ -25,6 +25,20 @@ import ( "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) { startdata := Tabdata{ maxwidthHeader: 5, @@ -51,18 +65,22 @@ func TestPrinter(t *testing.T) { "ascii": `ONE(1) TWO(2) THREE(3) asd igig cxxxncnc 19191 EDD 1 X`, + "orgtbl": `|--------+--------+----------| | ONE(1) | TWO(2) | THREE(3) | |--------+--------+----------| | asd | igig | cxxxncnc | | 19191 | EDD 1 | X | |--------+--------+----------|`, + "markdown": `| ONE(1) | TWO(2) | THREE(3) | |--------|--------|----------| | asd | igig | cxxxncnc | | 19191 | EDD 1 | X |`, + "shell": `ONE="asd" TWO="igig" THREE="cxxxncnc" ONE="19191" TWO="EDD 1" THREE="X"`, + "extended": `ONE(1): asd TWO(2): igig THREE(3): cxxxncnc @@ -73,16 +91,9 @@ THREE(3): X`, } NoColor = true + SortByColumn = 0 // disable sorting - r, w, err := os.Pipe() - 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) + origStdout, reader := stdout2pipe(t) for mode, expect := range expects { testname := fmt.Sprintf("print-%s", mode) @@ -93,7 +104,7 @@ THREE(3): X`, printData(&data) buf := make([]byte, 1024) - n, err := r.Read(buf) + n, err := reader.Read(buf) if err != nil { t.Fatal(err) } @@ -101,7 +112,8 @@ THREE(3): X`, output := strings.TrimSpace(string(buf)) 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 } + +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 +} diff --git a/lib/sort.go b/lib/sort.go new file mode 100644 index 0000000..25b8b57 --- /dev/null +++ b/lib/sort.go @@ -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 . +*/ + +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] + }) +} diff --git a/tablizer.1 b/tablizer.1 index 05bf4d6..766dabd 100644 --- a/tablizer.1 +++ b/tablizer.1 @@ -133,7 +133,7 @@ .\" ======================================================================== .\" .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 .\" way too many mistakes in technical documents. .if n .ad l @@ -159,6 +159,7 @@ tablizer \- Manipulate tabular output of other programs \& \-M, \-\-markdown Enable markdown table output \& \-O, \-\-orgtbl Enable org\-mode table output \& \-s, \-\-separator string Custom field separator +\& \-k, \-\-sort\-by int Sort by column (default: 1) \& \-v, \-\-version Print program version .Ve .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 highlighted. You can disable this behavior with the \fB\-N\fR option. .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 usefull for the developer. .SS "\s-1PATTERNS\s0" diff --git a/tablizer.pod b/tablizer.pod index 266b667..59a88e8 100644 --- a/tablizer.pod +++ b/tablizer.pod @@ -20,6 +20,7 @@ tablizer - Manipulate tabular output of other programs -M, --markdown Enable markdown table output -O, --orgtbl Enable org-mode table output -s, --separator string Custom field separator + -k, --sort-by int Sort by column (default: 1) -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 has been speficied, matches will be 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 usefull for the developer.