From 473feff4515259783ec89e6e6956f2d2f30cfafe Mon Sep 17 00:00:00 2001 From: Thomas von Dein Date: Tue, 7 May 2024 18:01:12 +0200 Subject: [PATCH] refactored and un-go-criticed --- Makefile | 3 +- cfg/config.go | 1 + cfg/config_test.go | 12 ++--- cmd/root.go | 1 + lib/common.go | 4 +- lib/filter.go | 48 ++++++++++++++++++++ lib/filter_test.go | 2 - lib/helpers.go | 107 ++++++++++++++++++++++++++------------------ lib/helpers_test.go | 12 ++--- lib/io.go | 22 +++++---- lib/lisp.go | 55 ++++++++++++++--------- lib/lisplib.go | 18 +++++--- lib/parser.go | 44 +++--------------- lib/parser_test.go | 36 +++++++-------- lib/printer.go | 17 ++++--- lib/printer_test.go | 36 +++++++-------- lib/sort.go | 5 +++ lib/sort_test.go | 27 ++++++----- 18 files changed, 262 insertions(+), 188 deletions(-) diff --git a/Makefile b/Makefile index a59b7da..95535c0 100644 --- a/Makefile +++ b/Makefile @@ -95,6 +95,7 @@ goupdate: lint: golangci-lint run +# keep til ireturn lint-full: - golangci-lint run --enable-all --exclude-use-default --disable exhaustivestruct,exhaustruct,depguard,interfacer,deadcode,golint,structcheck,scopelint,varcheck,ifshort,maligned,nosnakecase,godot,funlen,gofumpt,cyclop,noctx,gochecknoglobals,paralleltest,forbidigo,gci,godox,goimports,ireturn + golangci-lint run --enable-all --exclude-use-default --disable exhaustivestruct,exhaustruct,depguard,interfacer,deadcode,golint,structcheck,scopelint,varcheck,ifshort,maligned,nosnakecase,godot,funlen,gofumpt,cyclop,noctx,gochecknoglobals,paralleltest,forbidigo,gci,godox,goimports,ireturn,stylecheck,testpackage,mirror,nestif,revive,goerr113,gomnd gocritic check -enableAll *.go diff --git a/cfg/config.go b/cfg/config.go index 51d8347..f6e891e 100644 --- a/cfg/config.go +++ b/cfg/config.go @@ -216,6 +216,7 @@ func (conf *Config) DetermineColormode() { // Return true if current terminal is interactive func isTerminal(f *os.File) bool { o, _ := f.Stat() + return (o.Mode() & os.ModeCharDevice) == os.ModeCharDevice } diff --git a/cfg/config_test.go b/cfg/config_test.go index 3bf2732..84a477f 100644 --- a/cfg/config_test.go +++ b/cfg/config_test.go @@ -38,14 +38,14 @@ func TestPrepareModeFlags(t *testing.T) { } // FIXME: use a map for easier printing - for _, tt := range tests { - testname := fmt.Sprintf("PrepareModeFlags-expect-%d", tt.expect) + for _, testdata := range tests { + testname := fmt.Sprintf("PrepareModeFlags-expect-%d", testdata.expect) t.Run(testname, func(t *testing.T) { - c := Config{} + conf := Config{} - c.PrepareModeFlags(tt.flag) - if c.OutputMode != tt.expect { - t.Errorf("got: %d, expect: %d", c.OutputMode, tt.expect) + conf.PrepareModeFlags(testdata.flag) + if conf.OutputMode != testdata.expect { + t.Errorf("got: %d, expect: %d", conf.OutputMode, testdata.expect) } }) } diff --git a/cmd/root.go b/cmd/root.go index 3bf3c8d..38cf2ed 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -34,6 +34,7 @@ func man() { man := exec.Command("less", "-") var buffer bytes.Buffer + buffer.Write([]byte(manpage)) man.Stdout = os.Stdout diff --git a/lib/common.go b/lib/common.go index 0c9b915..6a6c929 100644 --- a/lib/common.go +++ b/lib/common.go @@ -26,11 +26,11 @@ type Tabdata struct { } func (data *Tabdata) CloneEmpty() Tabdata { - new := Tabdata{ + newdata := Tabdata{ maxwidthHeader: data.maxwidthHeader, columns: data.columns, headers: data.headers, } - return new + return newdata } diff --git a/lib/filter.go b/lib/filter.go index 1d6f7a5..2d35113 100644 --- a/lib/filter.go +++ b/lib/filter.go @@ -18,6 +18,9 @@ along with this program. If not, see . package lib import ( + "bufio" + "fmt" + "io" "strings" "github.com/lithammer/fuzzysearch/fuzzy" @@ -61,6 +64,7 @@ func FilterByFields(conf cfg.Config, data Tabdata) (Tabdata, bool, error) { if !conf.Filters[strings.ToLower(header)].MatchString(row[idx]) { // there IS a filter, but it doesn't match keep = false + break } } @@ -74,9 +78,53 @@ func FilterByFields(conf cfg.Config, data Tabdata) (Tabdata, bool, error) { return newdata, true, nil } +/* generic map.Exists(key) */ func Exists[K comparable, V any](m map[K]V, v K) bool { if _, ok := m[v]; ok { return true } + return false } + +func FilterByPattern(conf cfg.Config, input io.Reader) (io.Reader, error) { + if conf.Pattern == "" { + return input, nil + } + + scanner := bufio.NewScanner(input) + lines := []string{} + hadFirst := false + + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + if hadFirst { + // don't match 1st line, it's the header + if conf.Pattern != "" && matchPattern(conf, line) == conf.InvertMatch { + // by default -v is false, so if a line does NOT + // match the pattern, we will ignore it. However, + // if the user specified -v, the matching is inverted, + // so we ignore all lines, which DO match. + continue + } + + // apply user defined lisp filters, if any + accept, err := RunFilterHooks(conf, line) + if err != nil { + return input, fmt.Errorf("failed to apply filter hook: %w", err) + } + + if !accept { + // IF there are filter hook[s] and IF one of them + // returns false on the current line, reject it + continue + } + } + + lines = append(lines, line) + + hadFirst = true + } + + return strings.NewReader(strings.Join(lines, "\n")), nil +} diff --git a/lib/filter_test.go b/lib/filter_test.go index 227a714..fc646cb 100644 --- a/lib/filter_test.go +++ b/lib/filter_test.go @@ -65,7 +65,6 @@ func TestMatchPattern(t *testing.T) { } }) } - } func TestFilterByFields(t *testing.T) { @@ -160,5 +159,4 @@ func TestFilterByFields(t *testing.T) { } }) } - } diff --git a/lib/helpers.go b/lib/helpers.go index ad3d841..2f04ad2 100644 --- a/lib/helpers.go +++ b/lib/helpers.go @@ -36,57 +36,63 @@ func contains(s []int, e int) bool { return true } } + return false } // parse columns list given with -c, modifies config.UseColumns based // on eventually given regex func PrepareColumns(conf *cfg.Config, data *Tabdata) error { - if len(conf.Columns) > 0 { - for _, use := range strings.Split(conf.Columns, ",") { - if len(use) == 0 { - msg := fmt.Sprintf("Could not parse columns list %s: empty column", conf.Columns) + if conf.Columns == "" { + return nil + } + + for _, use := range strings.Split(conf.Columns, ",") { + if len(use) == 0 { + return fmt.Errorf("could not parse columns list %s: empty column", conf.Columns) + } + + usenum, err := strconv.Atoi(use) + if err != nil { + // might be a regexp + colPattern, err := regexp.Compile(use) + if err != nil { + msg := fmt.Sprintf("Could not parse columns list %s: %v", conf.Columns, err) + return errors.New(msg) } - usenum, err := strconv.Atoi(use) - if err != nil { - // might be a regexp - colPattern, err := regexp.Compile(use) - if err != nil { - msg := fmt.Sprintf("Could not parse columns list %s: %v", conf.Columns, err) - return errors.New(msg) + // find matching header fields + for i, head := range data.headers { + if colPattern.MatchString(head) { + conf.UseColumns = append(conf.UseColumns, i+1) } - - // find matching header fields - for i, head := range data.headers { - if colPattern.MatchString(head) { - conf.UseColumns = append(conf.UseColumns, i+1) - } - - } - } else { - // we digress from go best practises here, because if - // a colum spec is not a number, we process them above - // inside the err handler for atoi(). so only add the - // number, if it's really just a number. - conf.UseColumns = append(conf.UseColumns, usenum) } + } else { + // we digress from go best practises here, because if + // a colum spec is not a number, we process them above + // inside the err handler for atoi(). so only add the + // number, if it's really just a number. + conf.UseColumns = append(conf.UseColumns, usenum) } - - // deduplicate: put all values into a map (value gets map key) - // thereby removing duplicates, extract keys into new slice - // and sort it - imap := make(map[int]int, len(conf.UseColumns)) - for _, i := range conf.UseColumns { - imap[i] = 0 - } - conf.UseColumns = nil - for k := range imap { - conf.UseColumns = append(conf.UseColumns, k) - } - sort.Ints(conf.UseColumns) } + + // deduplicate: put all values into a map (value gets map key) + // thereby removing duplicates, extract keys into new slice + // and sort it + imap := make(map[int]int, len(conf.UseColumns)) + for _, i := range conf.UseColumns { + imap[i] = 0 + } + + conf.UseColumns = nil + + for k := range imap { + conf.UseColumns = append(conf.UseColumns, k) + } + + sort.Ints(conf.UseColumns) + return nil } @@ -96,7 +102,8 @@ func numberizeAndReduceHeaders(conf cfg.Config, data *Tabdata) { maxwidth := 0 // start from scratch, so we only look at displayed column widths for idx, head := range data.headers { - headlen := 0 + var headlen int + if len(conf.Columns) > 0 { // -c specified if !contains(conf.UseColumns, idx+1) { @@ -104,6 +111,7 @@ func numberizeAndReduceHeaders(conf cfg.Config, data *Tabdata) { continue } } + if conf.NoNumbering { numberedHeaders = append(numberedHeaders, head) headlen = len(head) @@ -117,7 +125,9 @@ func numberizeAndReduceHeaders(conf cfg.Config, data *Tabdata) { maxwidth = headlen } } + data.headers = numberedHeaders + if data.maxwidthHeader != maxwidth && maxwidth > 0 { data.maxwidthHeader = maxwidth } @@ -127,9 +137,12 @@ func numberizeAndReduceHeaders(conf cfg.Config, data *Tabdata) { func reduceColumns(conf cfg.Config, data *Tabdata) { if len(conf.Columns) > 0 { reducedEntries := [][]string{} + var reducedEntry []string + for _, entry := range data.entries { reducedEntry = nil + for i, value := range entry { if !contains(conf.UseColumns, i+1) { continue @@ -137,22 +150,26 @@ func reduceColumns(conf cfg.Config, data *Tabdata) { reducedEntry = append(reducedEntry, value) } + reducedEntries = append(reducedEntries, reducedEntry) } + data.entries = reducedEntries } } +// FIXME: remove this when we only use Tablewriter and strip in ParseFile()! func trimRow(row []string) []string { - // FIXME: remove this when we only use Tablewriter and strip in ParseFile()! - var fixedrow []string - for _, cell := range row { - fixedrow = append(fixedrow, strings.TrimSpace(cell)) + var fixedrow = make([]string, len(row)) + + for idx, cell := range row { + fixedrow[idx] = strings.TrimSpace(cell) } return fixedrow } +// FIXME: refactor this beast! func colorizeData(conf cfg.Config, output string) string { switch { case conf.UseHighlight && color.IsConsole(os.Stdout): @@ -180,17 +197,21 @@ func colorizeData(conf cfg.Config, output string) string { } else { line = conf.NoHighlightStyle.Sprint(line) } + highlight = !highlight colorized += line + "\n" } return colorized + case len(conf.Pattern) > 0 && !conf.NoColor && color.IsConsole(os.Stdout): r := regexp.MustCompile("(" + conf.Pattern + ")") + return r.ReplaceAllStringFunc(output, func(in string) string { return conf.ColorStyle.Sprint(in) }) + default: return output } diff --git a/lib/helpers_test.go b/lib/helpers_test.go index f079d8f..8013b44 100644 --- a/lib/helpers_test.go +++ b/lib/helpers_test.go @@ -72,18 +72,18 @@ func TestPrepareColumns(t *testing.T) { {"[a-z,4,5", []int{4, 5}, true}, // invalid regexp } - for _, tt := range tests { - testname := fmt.Sprintf("PrepareColumns-%s-%t", tt.input, tt.wanterror) + for _, testdata := range tests { + testname := fmt.Sprintf("PrepareColumns-%s-%t", testdata.input, testdata.wanterror) t.Run(testname, func(t *testing.T) { - conf := cfg.Config{Columns: tt.input} + conf := cfg.Config{Columns: testdata.input} err := PrepareColumns(&conf, &data) if err != nil { - if !tt.wanterror { + if !testdata.wanterror { t.Errorf("got error: %v", err) } } else { - if !reflect.DeepEqual(conf.UseColumns, tt.exp) { - t.Errorf("got: %v, expected: %v", conf.UseColumns, tt.exp) + if !reflect.DeepEqual(conf.UseColumns, testdata.exp) { + t.Errorf("got: %v, expected: %v", conf.UseColumns, testdata.exp) } } }) diff --git a/lib/io.go b/lib/io.go index 5e184ca..90e98a5 100644 --- a/lib/io.go +++ b/lib/io.go @@ -19,12 +19,15 @@ package lib import ( "errors" + "fmt" "io" "os" "github.com/tlinden/tablizer/cfg" ) +const RWRR = 0755 + func ProcessFiles(conf *cfg.Config, args []string) error { fds, pattern, err := determineIO(conf, args) @@ -54,25 +57,29 @@ func ProcessFiles(conf *cfg.Config, args []string) error { } func determineIO(conf *cfg.Config, args []string) ([]io.Reader, string, error) { + var filehandles []io.Reader + var pattern string - var fds []io.Reader + var haveio bool stat, _ := os.Stdin.Stat() if (stat.Mode() & os.ModeCharDevice) == 0 { // we're reading from STDIN, which takes precedence over file args - fds = append(fds, os.Stdin) + filehandles = append(filehandles, os.Stdin) + if len(args) > 0 { // ignore any args > 1 pattern = args[0] conf.Pattern = args[0] // used for colorization by printData() } + haveio = true } else if len(args) > 0 { // threre were args left, take a look if args[0] == "-" { // in traditional unix programs a dash denotes STDIN (forced) - fds = append(fds, os.Stdin) + filehandles = append(filehandles, os.Stdin) haveio = true } else { if _, err := os.Stat(args[0]); err != nil { @@ -86,14 +93,13 @@ func determineIO(conf *cfg.Config, args []string) ([]io.Reader, string, error) { if len(args) > 0 { // consider any other args as files for _, file := range args { - - fd, err := os.OpenFile(file, os.O_RDONLY, 0755) + filehandle, err := os.OpenFile(file, os.O_RDONLY, RWRR) if err != nil { - return nil, "", err + return nil, "", fmt.Errorf("failed to read input file %s: %w", file, err) } - fds = append(fds, fd) + filehandles = append(filehandles, filehandle) haveio = true } } @@ -104,5 +110,5 @@ func determineIO(conf *cfg.Config, args []string) ([]io.Reader, string, error) { return nil, "", errors.New("no file specified and nothing to read on stdin") } - return fds, pattern, nil + return filehandles, pattern, nil } diff --git a/lib/lisp.go b/lib/lisp.go index e13c292..f576a3b 100644 --- a/lib/lisp.go +++ b/lib/lisp.go @@ -45,12 +45,14 @@ func AddHook(env *zygo.Zlisp, name string, args []zygo.Sexp) (zygo.Sexp, error) return zygo.SexpNull, errors.New("argument of %add-hook should be: %hook-name %your-function") } - switch t := args[0].(type) { + switch sexptype := args[0].(type) { case *zygo.SexpSymbol: - if !HookExists(t.Name()) { - return zygo.SexpNull, errors.New("Unknown hook " + t.Name()) + if !HookExists(sexptype.Name()) { + return zygo.SexpNull, errors.New("Unknown hook " + sexptype.Name()) } - hookname = t.Name() + + hookname = sexptype.Name() + default: return zygo.SexpNull, errors.New("hook name must be a symbol ") } @@ -63,6 +65,7 @@ func AddHook(env *zygo.Zlisp, name string, args []zygo.Sexp) (zygo.Sexp, error) } else { Hooks[hookname] = append(Hooks[hookname], sexptype) } + default: return zygo.SexpNull, errors.New("hook function must be a symbol ") } @@ -90,7 +93,7 @@ func LoadAndEvalFile(env *zygo.Zlisp, path string) error { if strings.HasSuffix(path, `.zy`) { code, err := os.ReadFile(path) if err != nil { - return err + return fmt.Errorf("failed to read lisp file %s: %w", path, err) } // FIXME: check what res (_ here) could be and mean @@ -131,7 +134,8 @@ func SetupLisp(conf *cfg.Config) error { // load all lisp file in load dir dir, err := os.ReadDir(conf.LispLoadPath) if err != nil { - return err + return fmt.Errorf("failed to read lisp dir %s: %w", + conf.LispLoadPath, err) } for _, entry := range dir { @@ -147,6 +151,7 @@ func SetupLisp(conf *cfg.Config) error { RegisterLib(env) conf.Lisp = env + return nil } @@ -165,17 +170,19 @@ skipped. func RunFilterHooks(conf cfg.Config, line string) (bool, error) { for _, hook := range Hooks["filter"] { var result bool + conf.Lisp.Clear() + res, err := conf.Lisp.EvalString(fmt.Sprintf("(%s `%s`)", hook.Name(), line)) if err != nil { - return false, err + return false, fmt.Errorf("failed to evaluate hook loader: %w", err) } - switch t := res.(type) { + switch sexptype := res.(type) { case *zygo.SexpBool: - result = t.Val + result = sexptype.Val default: - return false, errors.New("filter hook shall return BOOL!") + return false, fmt.Errorf("filter hook shall return bool") } if !result { @@ -206,6 +213,7 @@ versa afterwards. */ func RunProcessHooks(conf cfg.Config, data Tabdata) (Tabdata, bool, error) { var userdata Tabdata + lisplist := []zygo.Sexp{} if len(Hooks["process"]) == 0 { @@ -223,7 +231,7 @@ func RunProcessHooks(conf cfg.Config, data Tabdata) (Tabdata, bool, error) { for idx, cell := range row { err := entry.HashSet(&zygo.SexpStr{S: data.headers[idx]}, &zygo.SexpStr{S: cell}) if err != nil { - return userdata, false, err + return userdata, false, fmt.Errorf("failed to convert to lisp data: %w", err) } } @@ -235,27 +243,29 @@ func RunProcessHooks(conf cfg.Config, data Tabdata) (Tabdata, bool, error) { // execute the actual hook hook := Hooks["process"][0] - var result bool + conf.Lisp.Clear() + var result bool + res, err := conf.Lisp.EvalString(fmt.Sprintf("(%s data)", hook.Name())) if err != nil { - return userdata, false, err + return userdata, false, fmt.Errorf("failed to eval lisp loader: %w", err) } // we expect (bool, array(hash)) as return from the function - switch t := res.(type) { + switch sexptype := res.(type) { case *zygo.SexpPair: - switch th := t.Head.(type) { + switch th := sexptype.Head.(type) { case *zygo.SexpBool: result = th.Val default: return userdata, false, errors.New("xpect (bool, array(hash)) as return value") } - switch tt := t.Tail.(type) { + switch sexptailtype := sexptype.Tail.(type) { case *zygo.SexpArray: - lisplist = tt.Val + lisplist = sexptailtype.Val default: return userdata, false, errors.New("expect (bool, array(hash)) as return value ") } @@ -275,14 +285,17 @@ func RunProcessHooks(conf cfg.Config, data Tabdata) (Tabdata, bool, error) { switch hash := item.(type) { case *zygo.SexpHash: for _, header := range data.headers { - entry, err := hash.HashGetDefault(conf.Lisp, &zygo.SexpStr{S: header}, &zygo.SexpStr{S: ""}) + entry, err := hash.HashGetDefault( + conf.Lisp, + &zygo.SexpStr{S: header}, + &zygo.SexpStr{S: ""}) if err != nil { - return userdata, false, err + return userdata, false, fmt.Errorf("failed to get lisp hash entry: %w", err) } - switch t := entry.(type) { + switch sexptype := entry.(type) { case *zygo.SexpStr: - row = append(row, t.S) + row = append(row, sexptype.S) default: return userdata, false, errors.New("hsh values should be string ") } diff --git a/lib/lisplib.go b/lib/lisplib.go index 21c5ee2..11b763e 100644 --- a/lib/lisplib.go +++ b/lib/lisplib.go @@ -19,6 +19,7 @@ package lib import ( "errors" + "fmt" "regexp" "strconv" @@ -31,29 +32,29 @@ func Splice2SexpList(list []string) zygo.Sexp { for _, item := range list { slist = append(slist, &zygo.SexpStr{S: item}) } + return zygo.MakeList(slist) } func StringReSplit(env *zygo.Zlisp, name string, args []zygo.Sexp) (zygo.Sexp, error) { if len(args) < 2 { - return zygo.SexpNull, errors.New("expecting 2 arguments!") + return zygo.SexpNull, errors.New("expecting 2 arguments") } - var separator string - var input string + var separator, input string switch t := args[0].(type) { case *zygo.SexpStr: input = t.S default: - return zygo.SexpNull, errors.New("second argument must be a string!") + return zygo.SexpNull, errors.New("second argument must be a string") } switch t := args[1].(type) { case *zygo.SexpStr: separator = t.S default: - return zygo.SexpNull, errors.New("first argument must be a string!") + return zygo.SexpNull, errors.New("first argument must be a string") } sep := regexp.MustCompile(separator) @@ -67,12 +68,15 @@ func String2Int(env *zygo.Zlisp, name string, args []zygo.Sexp) (zygo.Sexp, erro switch t := args[0].(type) { case *zygo.SexpStr: num, err := strconv.Atoi(t.S) + if err != nil { - return zygo.SexpNull, err + return zygo.SexpNull, fmt.Errorf("failed to convert string to number: %w", err) } + number = num + default: - return zygo.SexpNull, errors.New("argument must be a string!") + return zygo.SexpNull, errors.New("argument must be a string") } return &zygo.SexpInt{Val: int64(number)}, nil diff --git a/lib/parser.go b/lib/parser.go index eec0a59..dfaf62b 100644 --- a/lib/parser.go +++ b/lib/parser.go @@ -44,41 +44,12 @@ func Parse(conf cfg.Config, input io.Reader) (Tabdata, error) { Parse CSV input. */ func parseCSV(conf cfg.Config, input io.Reader) (Tabdata, error) { - var content = input data := Tabdata{} - if len(conf.Pattern) > 0 { - scanner := bufio.NewScanner(input) - lines := []string{} - hadFirst := false - for scanner.Scan() { - line := strings.TrimSpace(scanner.Text()) - if hadFirst { - // don't match 1st line, it's the header - if conf.Pattern != "" && matchPattern(conf, line) == conf.InvertMatch { - // by default -v is false, so if a line does NOT - // match the pattern, we will ignore it. However, - // if the user specified -v, the matching is inverted, - // so we ignore all lines, which DO match. - continue - } - - // apply user defined lisp filters, if any - accept, err := RunFilterHooks(conf, line) - if err != nil { - return data, fmt.Errorf("failed to apply filter hook: %w", err) - } - - if !accept { - // IF there are filter hook[s] and IF one of them - // returns false on the current line, reject it - continue - } - } - lines = append(lines, line) - hadFirst = true - } - content = strings.NewReader(strings.Join(lines, "\n")) + // apply pattern, if any + content, err := FilterByPattern(conf, input) + if err != nil { + return data, err } csvreader := csv.NewReader(content) @@ -111,6 +82,7 @@ func parseCSV(conf cfg.Config, input io.Reader) (Tabdata, error) { if err != nil { return data, fmt.Errorf("failed to apply filter hook: %w", err) } + if changed { data = userdata } @@ -144,10 +116,6 @@ func parseTabular(conf cfg.Config, input io.Reader) (Tabdata, error) { // process all header fields for _, part := range parts { - // if Debug { - // fmt.Printf("Part: <%s>\n", string(line[beg:part[0]])) - //} - // register widest header field headerlen := len(part) if headerlen > data.maxwidthHeader { @@ -211,6 +179,7 @@ func parseTabular(conf cfg.Config, input io.Reader) (Tabdata, error) { if err != nil { return data, fmt.Errorf("failed to filter fields: %w", err) } + if changed { data = filtereddata } @@ -220,6 +189,7 @@ func parseTabular(conf cfg.Config, input io.Reader) (Tabdata, error) { if err != nil { return data, fmt.Errorf("failed to apply filter hook: %w", err) } + if changed { data = userdata } diff --git a/lib/parser_test.go b/lib/parser_test.go index 23aba28..e382f59 100644 --- a/lib/parser_test.go +++ b/lib/parser_test.go @@ -62,12 +62,12 @@ func TestParser(t *testing.T) { }, } - for _, in := range input { - testname := fmt.Sprintf("parse-%s", in.name) + for _, testdata := range input { + testname := fmt.Sprintf("parse-%s", testdata.name) t.Run(testname, func(t *testing.T) { - readFd := strings.NewReader(strings.TrimSpace(in.text)) - c := cfg.Config{Separator: in.separator} - gotdata, err := Parse(c, readFd) + readFd := strings.NewReader(strings.TrimSpace(testdata.text)) + conf := cfg.Config{Separator: testdata.separator} + gotdata, err := Parse(conf, readFd) if err != nil { t.Errorf("Parser returned error: %s\nData processed so far: %+v", err, gotdata) @@ -104,28 +104,28 @@ func TestParserPatternmatching(t *testing.T) { }, } - for _, in := range input { - for _, tt := range tests { + for _, inputdata := range input { + for _, testdata := range tests { testname := fmt.Sprintf("parse-%s-with-pattern-%s-inverted-%t", - in.name, tt.pattern, tt.invert) + inputdata.name, testdata.pattern, testdata.invert) t.Run(testname, func(t *testing.T) { - conf := cfg.Config{InvertMatch: tt.invert, Pattern: tt.pattern, - Separator: in.separator} + conf := cfg.Config{InvertMatch: testdata.invert, Pattern: testdata.pattern, + Separator: inputdata.separator} - _ = conf.PreparePattern(tt.pattern) + _ = conf.PreparePattern(testdata.pattern) - readFd := strings.NewReader(strings.TrimSpace(in.text)) + readFd := strings.NewReader(strings.TrimSpace(inputdata.text)) gotdata, err := Parse(conf, readFd) if err != nil { - if !tt.want { + if !testdata.want { t.Errorf("Parser returned error: %s\nData processed so far: %+v", err, gotdata) } } else { - if !reflect.DeepEqual(tt.entries, gotdata.entries) { + if !reflect.DeepEqual(testdata.entries, gotdata.entries) { t.Errorf("Parser returned invalid data (pattern: %s, invert: %t)\nExp: %+v\nGot: %+v\n", - tt.pattern, tt.invert, tt.entries, gotdata.entries) + testdata.pattern, testdata.invert, testdata.entries, gotdata.entries) } } }) @@ -152,8 +152,8 @@ asd igig 19191 EDD 1 X` readFd := strings.NewReader(strings.TrimSpace(table)) - c := cfg.Config{Separator: cfg.DefaultSeparator} - gotdata, err := Parse(c, readFd) + conf := cfg.Config{Separator: cfg.DefaultSeparator} + gotdata, err := Parse(conf, readFd) if err != nil { t.Errorf("Parser returned error: %s\nData processed so far: %+v", err, gotdata) @@ -161,6 +161,6 @@ asd igig if !reflect.DeepEqual(data, gotdata) { t.Errorf("Parser returned invalid data, Regex: %s\nExp: %+v\nGot: %+v\n", - c.Separator, data, gotdata) + conf.Separator, data, gotdata) } } diff --git a/lib/printer.go b/lib/printer.go index 418aaf6..7adcd00 100644 --- a/lib/printer.go +++ b/lib/printer.go @@ -33,8 +33,6 @@ import ( ) func printData(writer io.Writer, conf cfg.Config, data *Tabdata) { - // some output preparations: - // add numbers to headers and remove this we're not interested in numberizeAndReduceHeaders(conf, data) @@ -48,7 +46,7 @@ func printData(writer io.Writer, conf cfg.Config, data *Tabdata) { case cfg.Extended: printExtendedData(writer, conf, data) case cfg.ASCII: - printAsciiData(writer, conf, data) + printASCIIData(writer, conf, data) case cfg.Orgtbl: printOrgmodeData(writer, conf, data) case cfg.Markdown: @@ -60,9 +58,8 @@ func printData(writer io.Writer, conf cfg.Config, data *Tabdata) { case cfg.CSV: printCSVData(writer, data) default: - printAsciiData(writer, conf, data) + printASCIIData(writer, conf, data) } - } func output(writer io.Writer, str string) { @@ -131,13 +128,14 @@ func printMarkdownData(writer io.Writer, conf cfg.Config, data *Tabdata) { /* Simple ASCII table without any borders etc, just like the input we expect */ -func printAsciiData(writer io.Writer, conf cfg.Config, data *Tabdata) { +func printASCIIData(writer io.Writer, conf cfg.Config, data *Tabdata) { tableString := &strings.Builder{} table := tablewriter.NewWriter(tableString) if !conf.NoHeaders { table.SetHeader(data.headers) } + table.AppendBulk(data.entries) table.SetAutoWrapText(false) @@ -169,6 +167,7 @@ func printExtendedData(writer io.Writer, conf cfg.Config, data *Tabdata) { // needed for data output format := fmt.Sprintf("%%%ds: %%s\n", data.maxwidthHeader) out := "" + if len(data.entries) > 0 { for _, entry := range data.entries { for i, value := range entry { @@ -187,14 +186,17 @@ Shell output, ready to be eval'd. Just like FreeBSD stat(1) */ func printShellData(writer io.Writer, data *Tabdata) { out := "" + if len(data.entries) > 0 { for _, entry := range data.entries { shentries := []string{} + for idx, value := range entry { shentries = append(shentries, fmt.Sprintf("%s=\"%s\"", data.headers[idx], value)) } - out += fmt.Sprint(strings.Join(shentries, " ")) + "\n" + + out += strings.Join(shentries, " ") + "\n" } } @@ -214,6 +216,7 @@ func printYamlData(writer io.Writer, data *Tabdata) { for idx, entry := range entry { style := yaml.TaggedStyle + _, err := strconv.Atoi(entry) if err != nil { style = yaml.DoubleQuotedStyle diff --git a/lib/printer_test.go b/lib/printer_test.go index 008543a..73ced90 100644 --- a/lib/printer_test.go +++ b/lib/printer_test.go @@ -250,39 +250,39 @@ DURATION(2) WHEN(4) } func TestPrinter(t *testing.T) { - for _, tt := range tests { + for _, testdata := range tests { testname := fmt.Sprintf("print-sortcol-%d-desc-%t-sortby-%s-mode-%d-usecolumns-%s", - tt.column, tt.desc, tt.sortby, tt.mode, tt.usecolstr) + 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 w bytes.Buffer + var writer bytes.Buffer // cmd flags - c := cfg.Config{ - SortByColumn: tt.column, - SortDescending: tt.desc, - SortMode: tt.sortby, - OutputMode: tt.mode, - NoNumbering: tt.nonum, - UseColumns: tt.usecol, + conf := cfg.Config{ + SortByColumn: testdata.column, + SortDescending: testdata.desc, + SortMode: testdata.sortby, + OutputMode: testdata.mode, + NoNumbering: testdata.nonum, + UseColumns: testdata.usecol, NoColor: true, } - c.ApplyDefaults() + conf.ApplyDefaults() // the test checks the len! - if len(tt.usecol) > 0 { - c.Columns = "yes" + if len(testdata.usecol) > 0 { + conf.Columns = "yes" } else { - c.Columns = "" + conf.Columns = "" } - testdata := newData() - exp := strings.TrimSpace(tt.expect) + data := newData() + exp := strings.TrimSpace(testdata.expect) - printData(&w, c, &testdata) + printData(&writer, conf, &data) - got := strings.TrimSpace(w.String()) + got := strings.TrimSpace(writer.String()) if got != exp { t.Errorf("not rendered correctly:\n+++ got:\n%s\n+++ want:\n%s", diff --git a/lib/sort.go b/lib/sort.go index 1a55d80..2d41bf9 100644 --- a/lib/sort.go +++ b/lib/sort.go @@ -63,18 +63,22 @@ func compare(conf *cfg.Config, left string, right string) bool { if err != nil { left = 0 } + right, err := strconv.Atoi(right) if err != nil { right = 0 } + comp = left < right case "duration": left := duration2int(left) right := duration2int(right) + comp = left < right case "time": left, _ := dateparse.ParseAny(left) right, _ := dateparse.ParseAny(right) + comp = left.Unix() < right.Unix() default: comp = left < right @@ -105,6 +109,7 @@ func duration2int(duration string) int { for _, match := range re.FindAllStringSubmatch(duration, -1) { if len(match) == 3 { durationvalue, _ := strconv.Atoi(match[1]) + switch match[2][0] { case 'd': seconds += durationvalue * 86400 diff --git a/lib/sort_test.go b/lib/sort_test.go index b46cce2..4d19cfa 100644 --- a/lib/sort_test.go +++ b/lib/sort_test.go @@ -19,8 +19,9 @@ package lib import ( "fmt" - "github.com/tlinden/tablizer/cfg" "testing" + + "github.com/tlinden/tablizer/cfg" ) func TestDuration2Seconds(t *testing.T) { @@ -36,12 +37,12 @@ func TestDuration2Seconds(t *testing.T) { {"19t77X what?4s", 4}, } - for _, tt := range tests { - testname := fmt.Sprintf("duration-%s", tt.dur) + for _, testdata := range tests { + testname := fmt.Sprintf("duration-%s", testdata.dur) t.Run(testname, func(t *testing.T) { - seconds := duration2int(tt.dur) - if seconds != tt.expect { - t.Errorf("got %d, want %d", seconds, tt.expect) + seconds := duration2int(testdata.dur) + if seconds != testdata.expect { + t.Errorf("got %d, want %d", seconds, testdata.expect) } }) } @@ -66,13 +67,15 @@ func TestCompare(t *testing.T) { {"time", "12/24/2022", "1/1/1970", true, true}, } - for _, tt := range tests { - testname := fmt.Sprintf("compare-mode-%s-a-%s-b-%s-desc-%t", tt.mode, tt.a, tt.b, tt.desc) + for _, testdata := range tests { + testname := fmt.Sprintf("compare-mode-%s-a-%s-b-%s-desc-%t", + testdata.mode, testdata.a, testdata.b, testdata.desc) + t.Run(testname, func(t *testing.T) { - c := cfg.Config{SortMode: tt.mode, SortDescending: tt.desc} - got := compare(&c, tt.a, tt.b) - if got != tt.want { - t.Errorf("got %t, want %t", got, tt.want) + 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) } }) }