diff --git a/TODO b/TODO
index 5b120d5..73b8d16 100644
--- a/TODO
+++ b/TODO
@@ -3,4 +3,3 @@ Add a mode like FreeBSD stat(1):
stat -s dead.letter
st_dev=170671546954750497 st_ino=159667 st_mode=0100644 st_nlink=1 st_uid=1001 st_gid=1001 st_rdev=18446744073709551615 st_size=573 st_atime=1661994007 st_mtime=1661961878 st_ctime=1661961878 st_birthtime=1658394900 st_blksize=4096 st_blocks=3 st_flags=2048
-mv UseColumns processing out of process()
diff --git a/cmd/root.go b/cmd/root.go
index fd0446d..6d047eb 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -33,9 +33,12 @@ var rootCmd = &cobra.Command{
return nil
}
- lib.PrepareColumns()
+ err := lib.PrepareColumns()
+ if err != nil {
+ return err
+ }
- err := lib.PrepareModeFlags()
+ err = lib.PrepareModeFlags()
if err != nil {
return err
}
@@ -58,11 +61,14 @@ func init() {
rootCmd.PersistentFlags().StringVarP(&lib.Separator, "separator", "s", "", "Custom field separator")
rootCmd.PersistentFlags().StringVarP(&lib.Columns, "columns", "c", "", "Only show the speficied columns (separated by ,)")
- // output flags, only 1 allowed
+ // 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.OutflagMarkdown, "markdown", "M", false, "Enable markdown table output")
rootCmd.PersistentFlags().BoolVarP(&lib.OutflagOrgtable, "orgtbl", "O", false, "Enable org-mode table output")
rootCmd.MarkFlagsMutuallyExclusive("extended", "markdown", "orgtbl")
+ rootCmd.Flags().MarkHidden("extended")
+ rootCmd.Flags().MarkHidden("orgtbl")
+ rootCmd.Flags().MarkHidden("markdown")
// same thing but more common, takes precedence over above group
rootCmd.PersistentFlags().StringVarP(&lib.OutputMode, "output", "o", "", "Output mode - one of: orgtbl, markdown, extended, ascii(default)")
diff --git a/lib/helpers.go b/lib/helpers.go
index 5dab33f..ddda4ee 100644
--- a/lib/helpers.go
+++ b/lib/helpers.go
@@ -40,16 +40,18 @@ func contains(s []int, e int) bool {
return false
}
-func PrepareColumns() {
+func PrepareColumns() error {
if len(Columns) > 0 {
for _, use := range strings.Split(Columns, ",") {
usenum, err := strconv.Atoi(use)
if err != nil {
- die(err)
+ msg := fmt.Sprintf("Could not parse columns list %s: %v", Columns, err)
+ return errors.New(msg)
}
UseColumns = append(UseColumns, usenum)
}
}
+ return nil
}
func PrepareModeFlags() error {
diff --git a/lib/helpers_test.go b/lib/helpers_test.go
index 68fa2c3..b02bb9b 100644
--- a/lib/helpers_test.go
+++ b/lib/helpers_test.go
@@ -19,20 +19,22 @@ package lib
import (
"fmt"
+ "reflect"
"testing"
)
-func TestArrayContains(t *testing.T) {
+func Testcontains(t *testing.T) {
var tests = []struct {
list []int
search int
want bool
}{
{[]int{1, 2, 3}, 2, true},
+ {[]int{2, 3, 4}, 5, false},
}
for _, tt := range tests {
- testname := fmt.Sprintf("%d,%d,%t", tt.list, tt.search, tt.want)
+ testname := fmt.Sprintf("contains-%d,%d,%t", tt.list, tt.search, tt.want)
t.Run(testname, func(t *testing.T) {
answer := contains(tt.list, tt.search)
if answer != tt.want {
@@ -41,3 +43,31 @@ func TestArrayContains(t *testing.T) {
})
}
}
+
+func TestPrepareColumns(t *testing.T) {
+ var tests = []struct {
+ input string
+ exp []int
+ wanterror bool // expect error
+ }{
+ {"1,2,3", []int{1, 2, 3}, false},
+ {"1,2,", []int{}, true},
+ }
+
+ for _, tt := range tests {
+ testname := fmt.Sprintf("PrepareColumns-%s-%t", tt.input, tt.wanterror)
+ t.Run(testname, func(t *testing.T) {
+ Columns = tt.input
+ err := PrepareColumns()
+ if err != nil {
+ if !tt.wanterror {
+ t.Errorf("got error: %v", err)
+ }
+ } else {
+ if !reflect.DeepEqual(UseColumns, tt.exp) {
+ t.Errorf("got: %v, expected: %v", UseColumns, tt.exp)
+ }
+ }
+ })
+ }
+}
diff --git a/lib/io.go b/lib/io.go
index 3665dbf..4c6a726 100644
--- a/lib/io.go
+++ b/lib/io.go
@@ -19,34 +19,48 @@ package lib
import (
"errors"
- "github.com/alecthomas/repr"
+ "io"
"os"
)
func ProcessFiles(args []string) error {
- var pattern string
- havefiles := false
+ fds, pattern, err := determineIO(args)
- //prepareColumns()
+ if err != nil {
+ return err
+ }
+
+ for _, fd := range fds {
+ printData(parseFile(fd, pattern))
+ }
+
+ return nil
+}
+
+func determineIO(args []string) ([]io.Reader, string, error) {
+ var pattern string
+ var fds []io.Reader
+ var havefiles bool
if len(args) > 0 {
+ // threre were args left, take a look
if _, err := os.Stat(args[0]); err != nil {
+ // first one is not a file, consider it as regexp and
+ // shift arg list
pattern = args[0]
args = args[1:]
}
if len(args) > 0 {
+ // only files
for _, file := range args {
fd, err := os.OpenFile(file, os.O_RDONLY, 0755)
+
if err != nil {
- die(err)
+ return nil, "", err
}
- data := parseFile(fd, pattern)
- if Debug {
- repr.Print(data)
- }
- printData(data)
+ fds = append(fds, fd)
}
havefiles = true
}
@@ -55,15 +69,11 @@ func ProcessFiles(args []string) error {
if !havefiles {
stat, _ := os.Stdin.Stat()
if (stat.Mode() & os.ModeCharDevice) == 0 {
- data := parseFile(os.Stdin, pattern)
- if Debug {
- repr.Print(data)
- }
- printData(data)
+ fds = append(fds, os.Stdin)
} else {
- return errors.New("No file specified and nothing to read on stdin!")
+ return nil, "", errors.New("No file specified and nothing to read on stdin!")
}
}
- return nil
+ return fds, pattern, nil
}
diff --git a/lib/parser.go b/lib/parser.go
index eadac31..0b19e14 100644
--- a/lib/parser.go
+++ b/lib/parser.go
@@ -20,6 +20,7 @@ package lib
import (
"bufio"
"fmt"
+ "github.com/alecthomas/repr"
"io"
"regexp"
"strings"
@@ -59,7 +60,7 @@ func parseFile(input io.Reader, pattern string) Tabdata {
scanner = bufio.NewScanner(input)
for scanner.Scan() {
- line := scanner.Text()
+ line := strings.TrimSpace(scanner.Text())
values := []string{}
patternR, err := regexp.Compile(pattern)
@@ -109,22 +110,18 @@ func parseFile(input io.Reader, pattern string) Tabdata {
// done
hadFirst = true
}
- // if Debug {
- // fmt.Println(data.headerIndices)
- // }
} else {
// data processing
if len(pattern) > 0 {
- //fmt.Println(patternR.MatchString(line))
if !patternR.MatchString(line) {
continue
}
}
idx := 0 // we cannot use the header index, because we could exclude columns
-
for _, index := range data.headerIndices {
value := ""
+
if index["end"] == 0 {
value = string(line[index["beg"]:])
} else {
@@ -159,5 +156,9 @@ func parseFile(input io.Reader, pattern string) Tabdata {
die(scanner.Err())
}
+ if Debug {
+ repr.Print(data)
+ }
+
return data
}
diff --git a/lib/parser_test.go b/lib/parser_test.go
new file mode 100644
index 0000000..19218b3
--- /dev/null
+++ b/lib/parser_test.go
@@ -0,0 +1,77 @@
+/*
+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 (
+ "reflect"
+ "strings"
+ "testing"
+)
+
+func TestParser(t *testing.T) {
+ data := Tabdata{
+ maxwidthHeader: 5,
+ maxwidthPerCol: []int{
+ 5,
+ 5,
+ 8,
+ },
+ columns: 3,
+ headerIndices: []map[string]int{
+ map[string]int{
+ "beg": 0,
+ "end": 6,
+ },
+ map[string]int{
+ "end": 13,
+ "beg": 7,
+ },
+ map[string]int{
+ "beg": 14,
+ "end": 0,
+ },
+ },
+ headers: []string{
+ "ONE",
+ "TWO",
+ "THREE",
+ },
+ entries: [][]string{
+ []string{
+ "asd",
+ "igig",
+ "cxxxncnc",
+ },
+ []string{
+ "19191",
+ "EDD 1",
+ "X",
+ },
+ },
+ }
+
+ table := `ONE TWO THREE
+asd igig cxxxncnc
+19191 EDD 1 X`
+
+ readFd := strings.NewReader(table)
+ gotdata := parseFile(readFd, "")
+ if !reflect.DeepEqual(data, gotdata) {
+ t.Errorf("Parser returned invalid data\nExp: %+v\nGot: %+v\n", data, gotdata)
+ }
+}
diff --git a/lib/printer_test.go b/lib/printer_test.go
new file mode 100644
index 0000000..5abb75a
--- /dev/null
+++ b/lib/printer_test.go
@@ -0,0 +1,76 @@
+/*
+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 (
+ "os"
+ "strings"
+ "testing"
+)
+
+func TestPrinter(t *testing.T) {
+ table := `ONE TWO THREE
+asd igig cxxxncnc
+19191 EDD 1 X`
+
+ expects := map[string]string{
+ "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 |`,
+ }
+
+ r, w, err := os.Pipe()
+ if err != nil {
+ t.Fatal(err)
+ }
+ origStdout := os.Stdout
+ os.Stdout = w
+
+ for mode, expect := range expects {
+ OutputMode = mode
+ fd := strings.NewReader(table)
+ data := parseFile(fd, "")
+ printData(data)
+
+ buf := make([]byte, 1024)
+ n, err := r.Read(buf)
+ if err != nil {
+ t.Fatal(err)
+ }
+ buf = buf[:n]
+ 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))
+ }
+ }
+
+ // Restore
+ os.Stdout = origStdout
+
+}
diff --git a/tablizer.pod b/tablizer.pod
index bb11ee0..4808812 100644
--- a/tablizer.pod
+++ b/tablizer.pod
@@ -10,12 +10,12 @@ tablizer - Manipulate tabular output of other programs
Flags:
-c, --columns string Only show the speficied columns (separated by ,)
-d, --debug Enable debugging
- -X, --extended Enable extended output
-h, --help help for tablizer
- -M, --markdown Enable markdown table output
-n, --no-numbering Disable header numbering
- -O, --orgtbl Enable org-mode table output
-o, --output string Output mode - one of: orgtbl, markdown, extended, ascii(default)
+ -X, --extended Enable extended output
+ -M, --markdown Enable markdown table output
+ -O, --orgtbl Enable org-mode table output
-s, --separator string Custom field separator
-v, --version Print program version
@@ -71,9 +71,14 @@ the original order.
The numbering can be suppressed by using the B<-n> option.
+Finally the B<-d> option enables debugging output which is mostly
+usefull for the developer.
+
+?head2 OUTPUT MODES
+
There might be cases when the tabular output of a program is way too
large for your current terminal but you still need to see every
-column. In such cases the B<-X> option (or B<-o extended> can be
+column. In such cases the B<-o extended> or B<-X> option can be
usefull which enables I. In this mode, each row will be
printed vertically, header left, value right, aligned by the field
widths. Here's an example:
@@ -88,12 +93,9 @@ widths. Here's an example:
You can of course still use a regex to reduce the number of rows
displayed.
-Beside normal ascii mode (the default) and extended mode there more
-output modes available: B which prints an Emacs org-mode table
-and B which prints a Markdown table.
-
-Finally the B<-d> option enables debugging output which is mostly
-usefull for the developer.
+Beside normal ascii mode (the default) and extended mode there are
+more output modes available: B which prints an Emacs org-mode
+table and B which prints a Markdown table.
=head1 BUGS