diff --git a/README.md b/README.md index a66a97d..31888b7 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ Operational Flags: -s, --separator string Custom field separator -k, --sort-by int Sort by column (default: 1) -z, --fuzzy Use fuzzy search [experimental] - -F, --filter field=reg Filter given field with regex, can be used multiple times + -F, --filter field[!]=reg Filter given field with regex, can be used multiple times -T, --transpose-columns string Transpose the speficied columns (separated by ,) -R, --regex-transposer /from/to/ Apply /search/replace/ regexp to fields given in -T diff --git a/cfg/config.go b/cfg/config.go index fbb9917..172012b 100644 --- a/cfg/config.go +++ b/cfg/config.go @@ -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 @@ -58,6 +58,11 @@ type Pattern struct { Negate bool } +type Filter struct { + Regex *regexp.Regexp + Negate bool +} + // internal config type Config struct { Debug bool @@ -100,7 +105,7 @@ type Config struct { // used for field filtering Rawfilters []string - Filters map[string]*regexp.Regexp + Filters map[string]Filter //map[string]*regexp.Regexp // -r InputFile string @@ -270,12 +275,20 @@ func (conf *Config) PrepareModeFlags(flag Modeflag) { } func (conf *Config) PrepareFilters() error { - conf.Filters = make(map[string]*regexp.Regexp, len(conf.Rawfilters)) + conf.Filters = make(map[string]Filter, len(conf.Rawfilters)) - for _, filter := range conf.Rawfilters { - parts := strings.Split(filter, "=") + for _, rawfilter := range conf.Rawfilters { + filter := Filter{} + + parts := strings.Split(rawfilter, "!=") if len(parts) != MAXPARTS { - return errors.New("filter field and value must be separated by =") + parts = strings.Split(rawfilter, "=") + + if len(parts) != MAXPARTS { + return errors.New("filter field and value must be separated by '=' or '!='") + } + } else { + filter.Negate = true } reg, err := regexp.Compile(parts[1]) @@ -284,7 +297,8 @@ func (conf *Config) PrepareFilters() error { parts[0], err) } - conf.Filters[strings.ToLower(strings.ToLower(parts[0]))] = reg + filter.Regex = reg + conf.Filters[strings.ToLower(parts[0])] = filter } return nil diff --git a/cmd/root.go b/cmd/root.go index afbf0df..1d347db 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -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 @@ -190,7 +190,7 @@ func Execute() { // filters rootCmd.PersistentFlags().StringArrayVarP(&conf.Rawfilters, - "filter", "F", nil, "Filter by field (field=regexp)") + "filter", "F", nil, "Filter by field (field=regexp || field!=regexp)") rootCmd.PersistentFlags().StringArrayVarP(&conf.Transposers, "regex-transposer", "R", nil, "apply /search/replace/ regexp to fields given in -T") diff --git a/cmd/tablizer.go b/cmd/tablizer.go index d4768d2..7d6504b 100644 --- a/cmd/tablizer.go +++ b/cmd/tablizer.go @@ -17,7 +17,7 @@ SYNOPSIS -s, --separator string Custom field separator -k, --sort-by int|name Sort by column (default: 1) -z, --fuzzy Use fuzzy search [experimental] - -F, --filter field=reg Filter given field with regex, can be used multiple times + -F, --filter field[!]=reg Filter given field with regex, can be used multiple times -T, --transpose-columns string Transpose the speficied columns (separated by ,) -R, --regex-transposer /from/to/ Apply /search/replace/ regexp to fields given in -T @@ -181,6 +181,10 @@ DESCRIPTION If you specify more than one filter, both filters have to match (AND operation). + These field filters can also be negated: + + fieldname!=regexp + If the option -v is specified, the filtering is inverted. COLUMNS @@ -416,7 +420,7 @@ Operational Flags: -s, --separator string Custom field separator -k, --sort-by int|name Sort by column (default: 1) -z, --fuzzy Use fuzzy search [experimental] - -F, --filter field=reg Filter given field with regex, can be used multiple times + -F, --filter field[!]=reg Filter given field with regex, can be used multiple times -T, --transpose-columns string Transpose the speficied columns (separated by ,) -R, --regex-transposer /from/to/ Apply /search/replace/ regexp to fields given in -T diff --git a/lib/filter.go b/lib/filter.go index 8227e23..8b6271e 100644 --- a/lib/filter.go +++ b/lib/filter.go @@ -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 @@ -86,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 } } diff --git a/lib/filter_test.go b/lib/filter_test.go index 5562fc0..7e5381d 100644 --- a/lib/filter_test.go +++ b/lib/filter_test.go @@ -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 @@ -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"}, diff --git a/tablizer.1 b/tablizer.1 index d4d21ec..4bc9cb1 100644 --- a/tablizer.1 +++ b/tablizer.1 @@ -133,7 +133,7 @@ .\" ======================================================================== .\" .IX Title "TABLIZER 1" -.TH TABLIZER 1 "2025-01-22" "1" "User Commands" +.TH TABLIZER 1 "2025-01-30" "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 @@ -155,7 +155,7 @@ tablizer \- Manipulate tabular output of other programs \& \-s, \-\-separator string Custom field separator \& \-k, \-\-sort\-by int|name Sort by column (default: 1) \& \-z, \-\-fuzzy Use fuzzy search [experimental] -\& \-F, \-\-filter field=reg Filter given field with regex, can be used multiple times +\& \-F, \-\-filter field[!]=reg Filter given field with regex, can be used multiple times \& \-T, \-\-transpose\-columns string Transpose the speficied columns (separated by ,) \& \-R, \-\-regex\-transposer /from/to/ Apply /search/replace/ regexp to fields given in \-T \& @@ -340,6 +340,12 @@ Fieldnames (== columns headers) are case insensitive. If you specify more than one filter, both filters have to match (\s-1AND\s0 operation). .PP +These field filters can also be negated: +.PP +.Vb 1 +\& fieldname!=regexp +.Ve +.PP If the option \fB\-v\fR is specified, the filtering is inverted. .SS "\s-1COLUMNS\s0" .IX Subsection "COLUMNS" diff --git a/tablizer.pod b/tablizer.pod index 11846ea..5ebfc3d 100644 --- a/tablizer.pod +++ b/tablizer.pod @@ -16,7 +16,7 @@ tablizer - Manipulate tabular output of other programs -s, --separator string Custom field separator -k, --sort-by int|name Sort by column (default: 1) -z, --fuzzy Use fuzzy search [experimental] - -F, --filter field=reg Filter given field with regex, can be used multiple times + -F, --filter field[!]=reg Filter given field with regex, can be used multiple times -T, --transpose-columns string Transpose the speficied columns (separated by ,) -R, --regex-transposer /from/to/ Apply /search/replace/ regexp to fields given in -T @@ -194,6 +194,10 @@ Fieldnames (== columns headers) are case insensitive. If you specify more than one filter, both filters have to match (AND operation). +These field filters can also be negated: + + fieldname!=regexp + If the option B<-v> is specified, the filtering is inverted.