Merge branch 'main' into feature/yank

This commit is contained in:
T.v.Dein
2025-02-23 18:09:04 +01:00
committed by GitHub
18 changed files with 454 additions and 144 deletions

View File

@@ -1,5 +1,5 @@
/*
Copyright © 2022-2024 Thomas von Dein
Copyright © 2022-2025 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
@@ -27,15 +27,46 @@ import (
)
/*
* [!]Match a line, use fuzzy search for normal pattern strings and
* regexp otherwise.
*/
* [!]Match a line, use fuzzy search for normal pattern strings and
* regexp otherwise.
'foo bar' foo, /bar/! => false => line contains foo and not (not bar)
'foo nix' foo, /bar/! => ture => line contains foo and (not bar)
'foo bar' foo, /bar/ => true => line contains both foo and bar
'foo nix' foo, /bar/ => false => line does not contain bar
'foo bar' foo, /nix/ => false => line does not contain nix
*/
func matchPattern(conf cfg.Config, line string) bool {
if conf.UseFuzzySearch {
return fuzzy.MatchFold(conf.Pattern, line)
if len(conf.Patterns) == 0 {
// any line always matches ""
return true
}
return conf.PatternR.MatchString(line)
if conf.UseFuzzySearch {
// fuzzy search only considers the 1st pattern
return fuzzy.MatchFold(conf.Patterns[0].Pattern, line)
}
var match int
//fmt.Printf("<%s>\n", line)
for _, re := range conf.Patterns {
patmatch := re.PatternRe.MatchString(line)
if re.Negate {
// toggle the meaning of match
patmatch = !patmatch
}
if patmatch {
match++
}
//fmt.Printf("patmatch: %t, match: %d, pattern: %s, negate: %t\n", patmatch, match, re.Pattern, re.Negate)
}
// fmt.Printf("result: %t\n", match == len(conf.Patterns))
//fmt.Println()
return match == len(conf.Patterns)
}
/*
@@ -55,15 +86,19 @@ func FilterByFields(conf cfg.Config, data *Tabdata) (*Tabdata, bool, error) {
keep := true
for idx, header := range data.headers {
if !Exists(conf.Filters, strings.ToLower(header)) {
lcheader := strings.ToLower(header)
if !Exists(conf.Filters, lcheader) {
// do not filter by unspecified field
continue
}
if !conf.Filters[strings.ToLower(header)].MatchString(row[idx]) {
// there IS a filter, but it doesn't match
keep = false
match := conf.Filters[lcheader].Regex.MatchString(row[idx])
if conf.Filters[lcheader].Negate {
match = !match
}
if !match {
keep = false
break
}
}
@@ -123,8 +158,11 @@ func Exists[K comparable, V any](m map[K]V, v K) bool {
return false
}
/*
* Filters the whole input lines, returns filtered lines
*/
func FilterByPattern(conf cfg.Config, input io.Reader) (io.Reader, error) {
if conf.Pattern == "" {
if len(conf.Patterns) == 0 {
return input, nil
}
@@ -136,7 +174,7 @@ func FilterByPattern(conf cfg.Config, input io.Reader) (io.Reader, error) {
line := strings.TrimSpace(scanner.Text())
if hadFirst {
// don't match 1st line, it's the header
if conf.Pattern != "" && matchPattern(conf, line) == conf.InvertMatch {
if 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,

View File

@@ -1,5 +1,5 @@
/*
Copyright © 2024 Thomas von Dein
Copyright © 2024-2025 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
@@ -27,21 +27,21 @@ import (
func TestMatchPattern(t *testing.T) {
var input = []struct {
name string
fuzzy bool
pattern string
line string
name string
fuzzy bool
patterns []*cfg.Pattern
line string
}{
{
name: "normal",
pattern: "haus",
line: "hausparty",
name: "normal",
patterns: []*cfg.Pattern{{Pattern: "haus"}},
line: "hausparty",
},
{
name: "fuzzy",
pattern: "hpt",
line: "haus-party-termin",
fuzzy: true,
name: "fuzzy",
patterns: []*cfg.Pattern{{Pattern: "hpt"}},
line: "haus-party-termin",
fuzzy: true,
},
}
@@ -55,7 +55,7 @@ func TestMatchPattern(t *testing.T) {
conf.UseFuzzySearch = true
}
err := conf.PreparePattern(inputdata.pattern)
err := conf.PreparePattern(inputdata.patterns)
if err != nil {
t.Errorf("PreparePattern returned error: %s", err)
}
@@ -98,6 +98,20 @@ func TestFilterByFields(t *testing.T) {
},
},
{
name: "one-field-negative",
filter: []string{"one!=asd"},
expect: Tabdata{
headers: []string{
"ONE", "TWO", "THREE",
},
entries: [][]string{
{"19191", "EDD 1", "x"},
{"8d8", "AN 1", "y"},
},
},
},
{
name: "one-field-inverted",
filter: []string{"one=19"},

View File

@@ -301,12 +301,20 @@ func colorizeData(conf cfg.Config, output string) string {
return colorized
case len(conf.Pattern) > 0 && !conf.NoColor && color.IsConsole(os.Stdout):
r := regexp.MustCompile("(" + conf.Pattern + ")")
case len(conf.Patterns) > 0 && !conf.NoColor && color.IsConsole(os.Stdout):
out := output
return r.ReplaceAllStringFunc(output, func(in string) string {
return conf.ColorStyle.Sprint(in)
})
for _, re := range conf.Patterns {
if !re.Negate {
r := regexp.MustCompile("(" + re.Pattern + ")")
out = r.ReplaceAllStringFunc(out, func(in string) string {
return conf.ColorStyle.Sprint(in)
})
}
}
return out
default:
return output

View File

@@ -29,13 +29,13 @@ import (
const RWRR = 0755
func ProcessFiles(conf *cfg.Config, args []string) error {
fd, pattern, err := determineIO(conf, args)
fd, patterns, err := determineIO(conf, args)
if err != nil {
return err
}
if err := conf.PreparePattern(pattern); err != nil {
if err := conf.PreparePattern(patterns); err != nil {
return err
}
@@ -63,9 +63,9 @@ func ProcessFiles(conf *cfg.Config, args []string) error {
return nil
}
func determineIO(conf *cfg.Config, args []string) (io.Reader, string, error) {
func determineIO(conf *cfg.Config, args []string) (io.Reader, []*cfg.Pattern, error) {
var filehandle io.Reader
var pattern string
var patterns []*cfg.Pattern
var haveio bool
switch {
@@ -76,7 +76,7 @@ func determineIO(conf *cfg.Config, args []string) (io.Reader, string, error) {
fd, err := os.OpenFile(conf.InputFile, os.O_RDONLY, RWRR)
if err != nil {
return nil, "", fmt.Errorf("failed to read input file %s: %w", conf.InputFile, err)
return nil, nil, fmt.Errorf("failed to read input file %s: %w", conf.InputFile, err)
}
filehandle = fd
@@ -93,13 +93,15 @@ func determineIO(conf *cfg.Config, args []string) (io.Reader, string, error) {
}
if len(args) > 0 {
pattern = args[0]
conf.Pattern = args[0]
patterns = make([]*cfg.Pattern, len(args))
for i, arg := range args {
patterns[i] = &cfg.Pattern{Pattern: arg}
}
}
if !haveio {
return nil, "", errors.New("no file specified and nothing to read on stdin")
return nil, nil, errors.New("no file specified and nothing to read on stdin")
}
return filehandle, pattern, nil
return filehandle, patterns, nil
}

View File

@@ -137,7 +137,7 @@ func parseTabular(conf cfg.Config, input io.Reader) (Tabdata, error) {
}
} else {
// data processing
if conf.Pattern != "" && matchPattern(conf, line) == conf.InvertMatch {
if 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,

View File

@@ -83,36 +83,42 @@ func TestParser(t *testing.T) {
func TestParserPatternmatching(t *testing.T) {
var tests = []struct {
entries [][]string
pattern string
invert bool
want bool
name string
entries [][]string
patterns []*cfg.Pattern
invert bool
want bool
}{
{
name: "match",
entries: [][]string{
{"asd", "igig", "cxxxncnc"},
},
pattern: "ig",
invert: false,
patterns: []*cfg.Pattern{{Pattern: "ig"}},
invert: false,
},
{
name: "invert",
entries: [][]string{
{"19191", "EDD 1", "X"},
},
pattern: "ig",
invert: true,
patterns: []*cfg.Pattern{{Pattern: "ig"}},
invert: true,
},
}
for _, inputdata := range input {
for _, testdata := range tests {
testname := fmt.Sprintf("parse-%s-with-pattern-%s-inverted-%t",
inputdata.name, testdata.pattern, testdata.invert)
inputdata.name, testdata.name, testdata.invert)
t.Run(testname, func(t *testing.T) {
conf := cfg.Config{InvertMatch: testdata.invert, Pattern: testdata.pattern,
Separator: inputdata.separator}
conf := cfg.Config{
InvertMatch: testdata.invert,
Patterns: testdata.patterns,
Separator: inputdata.separator,
}
_ = conf.PreparePattern(testdata.pattern)
_ = conf.PreparePattern(testdata.patterns)
readFd := strings.NewReader(strings.TrimSpace(inputdata.text))
gotdata, err := Parse(conf, readFd)
@@ -125,7 +131,7 @@ func TestParserPatternmatching(t *testing.T) {
} else {
if !reflect.DeepEqual(testdata.entries, gotdata.entries) {
t.Errorf("Parser returned invalid data (pattern: %s, invert: %t)\nExp: %+v\nGot: %+v\n",
testdata.pattern, testdata.invert, testdata.entries, gotdata.entries)
testdata.name, testdata.invert, testdata.entries, gotdata.entries)
}
}
})