Compare commits

..

7 Commits

Author SHA1 Message Date
200f1f32f8 using patched tiagomeol/go-clipboard/clipboard, fixes #37 2025-01-28 14:40:17 +01:00
768a19b4d6 fine tuning, added test, which hangs, but yanking works anyway 2025-01-23 13:59:02 +01:00
Thomas von Dein
dc718392b6 fix-import 2025-01-22 23:15:12 +01:00
Thomas von Dein
e8f4fef41c fix #37: make yank portable 2025-01-22 23:12:42 +01:00
82f54c120d catch err 2025-01-21 18:42:04 +01:00
T.v.Dein
2d5799e2f2 Use primary clipboard on unix 2025-01-20 21:27:26 +01:00
8e33cadcaa add -y 2025-01-20 19:28:19 +01:00
21 changed files with 338 additions and 389 deletions

View File

@@ -69,7 +69,7 @@ test:
singletest: singletest:
@echo "Call like this: 'make singletest TEST=TestPrepareColumns MOD=lib'" @echo "Call like this: 'make singletest TEST=TestPrepareColumns MOD=lib'"
go test -run $(TEST) github.com/tlinden/tablizer/$(MOD) go test -run $(TEST) github.com/tlinden/tablizer/$(MOD) $(OPTS)
cover-report: cover-report:
go test ./... -cover -coverprofile=coverage.out go test ./... -cover -coverprofile=coverage.out

View File

@@ -22,7 +22,7 @@ Operational Flags:
-s, --separator string Custom field separator -s, --separator string Custom field separator
-k, --sort-by int Sort by column (default: 1) -k, --sort-by int Sort by column (default: 1)
-z, --fuzzy Use fuzzy search [experimental] -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 ,) -T, --transpose-columns string Transpose the speficied columns (separated by ,)
-R, --regex-transposer /from/to/ Apply /search/replace/ regexp to fields given in -T -R, --regex-transposer /from/to/ Apply /search/replace/ regexp to fields given in -T
@@ -35,6 +35,8 @@ Output Flags (mutually exclusive):
-C, --csv Enable CSV output -C, --csv Enable CSV output
-A, --ascii Default output mode, ascii tabular -A, --ascii Default output mode, ascii tabular
-L, --hightlight-lines Use alternating background colors for tables -L, --hightlight-lines Use alternating background colors for tables
-y, --yank-columns Yank specified columns (separated by ,) to clipboard,
space separated
Sort Mode Flags (mutually exclusive): Sort Mode Flags (mutually exclusive):
-a, --sort-age sort according to age (duration) string -a, --sort-age sort according to age (duration) string

View File

@@ -1,5 +1,5 @@
/* /*
Copyright © 2022-2025 Thomas von Dein Copyright © 2022-2024 Thomas von Dein
This program is free software: you can redistribute it and/or modify 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 it under the terms of the GNU General Public License as published by
@@ -28,7 +28,7 @@ import (
) )
const DefaultSeparator string = `(\s\s+|\t)` const DefaultSeparator string = `(\s\s+|\t)`
const Version string = "v1.3.2" const Version string = "v1.3.1"
const MAXPARTS = 2 const MAXPARTS = 2
var DefaultConfigfile = os.Getenv("HOME") + "/.config/tablizer/config" var DefaultConfigfile = os.Getenv("HOME") + "/.config/tablizer/config"
@@ -52,17 +52,6 @@ type Transposer struct {
Replace string Replace string
} }
type Pattern struct {
Pattern string
PatternRe *regexp.Regexp
Negate bool
}
type Filter struct {
Regex *regexp.Regexp
Negate bool
}
// internal config // internal config
type Config struct { type Config struct {
Debug bool Debug bool
@@ -70,10 +59,13 @@ type Config struct {
NoHeaders bool NoHeaders bool
Columns string Columns string
UseColumns []int UseColumns []int
YankColumns string
UseYankColumns []int
Separator string Separator string
OutputMode int OutputMode int
InvertMatch bool InvertMatch bool
Patterns []*Pattern Pattern string
PatternR *regexp.Regexp
UseFuzzySearch bool UseFuzzySearch bool
UseHighlight bool UseHighlight bool
@@ -105,7 +97,7 @@ type Config struct {
// used for field filtering // used for field filtering
Rawfilters []string Rawfilters []string
Filters map[string]Filter //map[string]*regexp.Regexp Filters map[string]*regexp.Regexp
// -r <file> // -r <file>
InputFile string InputFile string
@@ -275,20 +267,12 @@ func (conf *Config) PrepareModeFlags(flag Modeflag) {
} }
func (conf *Config) PrepareFilters() error { func (conf *Config) PrepareFilters() error {
conf.Filters = make(map[string]Filter, len(conf.Rawfilters)) conf.Filters = make(map[string]*regexp.Regexp, len(conf.Rawfilters))
for _, rawfilter := range conf.Rawfilters { for _, filter := range conf.Rawfilters {
filter := Filter{} parts := strings.Split(filter, "=")
parts := strings.Split(rawfilter, "!=")
if len(parts) != MAXPARTS { if len(parts) != MAXPARTS {
parts = strings.Split(rawfilter, "=") return errors.New("filter field and value must be separated by =")
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]) reg, err := regexp.Compile(parts[1])
@@ -297,8 +281,7 @@ func (conf *Config) PrepareFilters() error {
parts[0], err) parts[0], err)
} }
filter.Regex = reg conf.Filters[strings.ToLower(strings.ToLower(parts[0]))] = reg
conf.Filters[strings.ToLower(parts[0])] = filter
} }
return nil return nil
@@ -352,37 +335,15 @@ func (conf *Config) ApplyDefaults() {
} }
} }
func (conf *Config) PreparePattern(patterns []*Pattern) error { func (conf *Config) PreparePattern(pattern string) error {
// regex checks if a pattern looks like /$pattern/[i!] PatternR, err := regexp.Compile(pattern)
flagre := regexp.MustCompile(`^/(.*)/([i!]*)$`)
for _, pattern := range patterns { if err != nil {
matches := flagre.FindAllStringSubmatch(pattern.Pattern, -1) return fmt.Errorf("regexp pattern %s is invalid: %w", conf.Pattern, err)
// we have a regex with flags
for _, match := range matches {
pattern.Pattern = match[1] // the inner part is our actual pattern
flags := match[2] // the flags
for _, flag := range flags {
switch flag {
case 'i':
pattern.Pattern = `(?i)` + pattern.Pattern
case '!':
pattern.Negate = true
}
}
}
PatternRe, err := regexp.Compile(pattern.Pattern)
if err != nil {
return fmt.Errorf("regexp pattern %s is invalid: %w", pattern.Pattern, err)
}
pattern.PatternRe = PatternRe
} }
conf.Patterns = patterns conf.PatternR = PatternR
conf.Pattern = pattern
return nil return nil
} }

View File

@@ -79,55 +79,20 @@ func TestPrepareSortFlags(t *testing.T) {
func TestPreparePattern(t *testing.T) { func TestPreparePattern(t *testing.T) {
var tests = []struct { var tests = []struct {
patterns []*Pattern pattern string
name string wanterr bool
wanterr bool
wanticase bool
wantneg bool
}{ }{
{ {"[A-Z]+", false},
[]*Pattern{{Pattern: "[A-Z]+"}}, {"[a-z", true},
"simple",
false,
false,
false,
},
{
[]*Pattern{{Pattern: "[a-z"}},
"regfail",
true,
false,
false,
},
{
[]*Pattern{{Pattern: "/[A-Z]+/i"}},
"icase",
false,
true,
false,
},
{
[]*Pattern{{Pattern: "/[A-Z]+/!"}},
"negate",
false,
false,
true,
},
{
[]*Pattern{{Pattern: "/[A-Z]+/!i"}},
"negicase",
false,
true,
true,
},
} }
for _, testdata := range tests { for _, testdata := range tests {
testname := fmt.Sprintf("PreparePattern-pattern-%s-wanterr-%t", testdata.name, testdata.wanterr) testname := fmt.Sprintf("PreparePattern-pattern-%s-wanterr-%t",
testdata.pattern, testdata.wanterr)
t.Run(testname, func(t *testing.T) { t.Run(testname, func(t *testing.T) {
conf := Config{} conf := Config{}
err := conf.PreparePattern(testdata.patterns) err := conf.PreparePattern(testdata.pattern)
if err != nil { if err != nil {
if !testdata.wanterr { if !testdata.wanterr {

View File

@@ -1,5 +1,5 @@
/* /*
Copyright © 2022-2025 Thomas von Dein Copyright © 2022-2024 Thomas von Dein
This program is free software: you can redistribute it and/or modify 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 it under the terms of the GNU General Public License as published by
@@ -147,6 +147,8 @@ func Execute() {
"Custom field separator") "Custom field separator")
rootCmd.PersistentFlags().StringVarP(&conf.Columns, "columns", "c", "", rootCmd.PersistentFlags().StringVarP(&conf.Columns, "columns", "c", "",
"Only show the speficied columns (separated by ,)") "Only show the speficied columns (separated by ,)")
rootCmd.PersistentFlags().StringVarP(&conf.YankColumns, "yank-columns", "y", "",
"Yank the speficied columns (separated by ,) to the clipboard")
rootCmd.PersistentFlags().StringVarP(&conf.TransposeColumns, "transpose-columns", "T", "", rootCmd.PersistentFlags().StringVarP(&conf.TransposeColumns, "transpose-columns", "T", "",
"Transpose the speficied columns (separated by ,)") "Transpose the speficied columns (separated by ,)")
@@ -190,7 +192,7 @@ func Execute() {
// filters // filters
rootCmd.PersistentFlags().StringArrayVarP(&conf.Rawfilters, rootCmd.PersistentFlags().StringArrayVarP(&conf.Rawfilters,
"filter", "F", nil, "Filter by field (field=regexp || field!=regexp)") "filter", "F", nil, "Filter by field (field=regexp)")
rootCmd.PersistentFlags().StringArrayVarP(&conf.Transposers, rootCmd.PersistentFlags().StringArrayVarP(&conf.Transposers,
"regex-transposer", "R", nil, "apply /search/replace/ regexp to fields given in -T") "regex-transposer", "R", nil, "apply /search/replace/ regexp to fields given in -T")

View File

@@ -6,7 +6,7 @@ NAME
SYNOPSIS SYNOPSIS
Usage: Usage:
tablizer [regex,...] [file, ...] [flags] tablizer [regex] [file, ...] [flags]
Operational Flags: Operational Flags:
-c, --columns string Only show the speficied columns (separated by ,) -c, --columns string Only show the speficied columns (separated by ,)
@@ -17,7 +17,7 @@ SYNOPSIS
-s, --separator string Custom field separator -s, --separator string Custom field separator
-k, --sort-by int|name Sort by column (default: 1) -k, --sort-by int|name Sort by column (default: 1)
-z, --fuzzy Use fuzzy search [experimental] -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 ,) -T, --transpose-columns string Transpose the speficied columns (separated by ,)
-R, --regex-transposer /from/to/ Apply /search/replace/ regexp to fields given in -T -R, --regex-transposer /from/to/ Apply /search/replace/ regexp to fields given in -T
@@ -30,6 +30,8 @@ SYNOPSIS
-C, --csv Enable CSV output -C, --csv Enable CSV output
-A, --ascii Default output mode, ascii tabular -A, --ascii Default output mode, ascii tabular
-L, --hightlight-lines Use alternating background colors for tables -L, --hightlight-lines Use alternating background colors for tables
-y, --yank-columns Yank specified columns (separated by ,) to clipboard,
space separated
Sort Mode Flags (mutually exclusive): Sort Mode Flags (mutually exclusive):
-a, --sort-age sort according to age (duration) string -a, --sort-age sort according to age (duration) string
@@ -130,43 +132,30 @@ DESCRIPTION
for the developer. for the developer.
PATTERNS AND FILTERING PATTERNS AND FILTERING
You can reduce the rows being displayed by using one or more regular You can reduce the rows being displayed by using a regular expression
expression patterns. The regexp language being used is the one of pattern. The regexp is PCRE compatible, refer to the syntax cheat sheet
GOLANG, refer to the syntax cheat sheet here: here: <https://github.com/google/re2/wiki/Syntax>. If you want to read a
<https://pkg.go.dev/regexp/syntax>. more comprehensive documentation about the topic and have perl installed
you can read it with:
If you want to read a more comprehensive documentation about the topic
and have perl installed you can read it with:
perldoc perlre perldoc perlre
Or read it online: <https://perldoc.perl.org/perlre>. But please note Or read it online: <https://perldoc.perl.org/perlre>.
that the GO regexp engine does NOT support all perl regex terms,
especially look-ahead and look-behind.
If you want to supply flags to a regex, then surround it with slashes A note on modifiers: the regexp engine used in tablizer uses another
and append the flag. The following flags are supported: modifier syntax:
i => case insensitive (?MODIFIER)
! => negative match
The most important modifiers are:
"i" ignore case "m" multiline mode "s" single line mode
Example for a case insensitive search: Example for a case insensitive search:
kubectl get pods -A | tablizer "/account/i" kubectl get pods -A | tablizer "(?i)account"
If you use the "!" flag, then the regex match will be negated, that is, You can use the experimental fuzzy search feature by providing the
if a line in the input matches the given regex, but "!" is supplied,
tablizer will NOT include it in the output.
For example, here we want to get all lines matching "foo" but not "bar":
cat table | tablizer foo '/bar/!'
This would match a line "foo zorro" but not "foo bar".
The flags can also be combined.
You can also use the experimental fuzzy search feature by providing the
option -z, in which case the pattern is regarded as a fuzzy search term, option -z, in which case the pattern is regarded as a fuzzy search term,
not a regexp. not a regexp.
@@ -181,10 +170,6 @@ DESCRIPTION
If you specify more than one filter, both filters have to match (AND If you specify more than one filter, both filters have to match (AND
operation). operation).
These field filters can also be negated:
fieldname!=regexp
If the option -v is specified, the filtering is inverted. If the option -v is specified, the filtering is inverted.
COLUMNS COLUMNS
@@ -284,6 +269,18 @@ DESCRIPTION
markdown which prints a Markdown table, yaml, which prints yaml encoding markdown which prints a Markdown table, yaml, which prints yaml encoding
and CSV mode, which prints a comma separated value file. and CSV mode, which prints a comma separated value file.
PUT FIELDS TO CLIPBOARD
You can let tablizer put fields to the clipboard using the option "-y".
This best fits the use-case when the result of your filtering yields
just one row. For example:
cloudctl cluster ls | tablizer -yid matchbox
If "matchbox" matches one cluster, you can immediately use the id of
that cluster somewhere else and paste it. Of course, if there are
multiple matches, then all id's will be put into the clipboard separated
by one space.
ENVIRONMENT VARIABLES ENVIRONMENT VARIABLES
tablizer supports certain environment variables which use can use to tablizer supports certain environment variables which use can use to
influence program behavior. Commandline flags have always precedence influence program behavior. Commandline flags have always precedence
@@ -409,7 +406,7 @@ AUTHORS
var usage = ` var usage = `
Usage: Usage:
tablizer [regex,...] [file, ...] [flags] tablizer [regex] [file, ...] [flags]
Operational Flags: Operational Flags:
-c, --columns string Only show the speficied columns (separated by ,) -c, --columns string Only show the speficied columns (separated by ,)
@@ -420,7 +417,7 @@ Operational Flags:
-s, --separator string Custom field separator -s, --separator string Custom field separator
-k, --sort-by int|name Sort by column (default: 1) -k, --sort-by int|name Sort by column (default: 1)
-z, --fuzzy Use fuzzy search [experimental] -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 ,) -T, --transpose-columns string Transpose the speficied columns (separated by ,)
-R, --regex-transposer /from/to/ Apply /search/replace/ regexp to fields given in -T -R, --regex-transposer /from/to/ Apply /search/replace/ regexp to fields given in -T
@@ -433,6 +430,8 @@ Output Flags (mutually exclusive):
-C, --csv Enable CSV output -C, --csv Enable CSV output
-A, --ascii Default output mode, ascii tabular -A, --ascii Default output mode, ascii tabular
-L, --hightlight-lines Use alternating background colors for tables -L, --hightlight-lines Use alternating background colors for tables
-y, --yank-columns Yank specified columns (separated by ,) to clipboard,
space separated
Sort Mode Flags (mutually exclusive): Sort Mode Flags (mutually exclusive):
-a, --sort-age sort according to age (duration) string -a, --sort-age sort according to age (duration) string

3
go.mod
View File

@@ -5,6 +5,7 @@ go 1.22
require ( require (
github.com/alecthomas/repr v0.4.0 github.com/alecthomas/repr v0.4.0
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de
github.com/atotto/clipboard v0.1.4
github.com/gookit/color v1.5.4 github.com/gookit/color v1.5.4
github.com/hashicorp/hcl/v2 v2.23.0 github.com/hashicorp/hcl/v2 v2.23.0
github.com/lithammer/fuzzysearch v1.1.8 github.com/lithammer/fuzzysearch v1.1.8
@@ -22,8 +23,10 @@ require (
github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 // indirect github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/rivo/uniseg v0.2.0 // indirect github.com/rivo/uniseg v0.2.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/pflag v1.0.5 // indirect
github.com/tiagomelo/go-clipboard v0.1.2-0.20250126153310-fcc1f95408cf // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
github.com/zclconf/go-cty v1.13.3 // indirect github.com/zclconf/go-cty v1.13.3 // indirect
golang.org/x/mod v0.18.0 // indirect golang.org/x/mod v0.18.0 // indirect

8
go.sum
View File

@@ -8,6 +8,8 @@ github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew
github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4= github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4=
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de h1:FxWPpzIjnTlhPwqqXc4/vE0f7GvRjuAsbW+HOIe8KnA= github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de h1:FxWPpzIjnTlhPwqqXc4/vE0f7GvRjuAsbW+HOIe8KnA=
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de/go.mod h1:DCaWoUhZrYW9p1lxo/cm8EmUOOzAPSEZNGF2DK1dJgw= github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de/go.mod h1:DCaWoUhZrYW9p1lxo/cm8EmUOOzAPSEZNGF2DK1dJgw=
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@@ -32,6 +34,8 @@ github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 h1:DpOJ2HYzC
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
@@ -49,6 +53,10 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/tiagomelo/go-clipboard v0.1.1 h1:nddQ5DsEnKW0KdzTILhbLpSq3e9y2dkJXEOtsMs6H7A=
github.com/tiagomelo/go-clipboard v0.1.1/go.mod h1:kXtjJBIMimZaGbxmcKZ8+JqK+acSNf5tAJiChlZBOr8=
github.com/tiagomelo/go-clipboard v0.1.2-0.20250126153310-fcc1f95408cf h1:csb/+rmJBAtOP6OP+9soTnwJVuhlUpedjb5dPlNZasY=
github.com/tiagomelo/go-clipboard v0.1.2-0.20250126153310-fcc1f95408cf/go.mod h1:kXtjJBIMimZaGbxmcKZ8+JqK+acSNf5tAJiChlZBOr8=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=

View File

@@ -1,5 +1,5 @@
/* /*
Copyright © 2022-2025 Thomas von Dein Copyright © 2022-2024 Thomas von Dein
This program is free software: you can redistribute it and/or modify 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 it under the terms of the GNU General Public License as published by
@@ -27,46 +27,15 @@ import (
) )
/* /*
* [!]Match a line, use fuzzy search for normal pattern strings and * [!]Match a line, use fuzzy search for normal pattern strings and
* regexp otherwise. * 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 { func matchPattern(conf cfg.Config, line string) bool {
if len(conf.Patterns) == 0 {
// any line always matches ""
return true
}
if conf.UseFuzzySearch { if conf.UseFuzzySearch {
// fuzzy search only considers the 1st pattern return fuzzy.MatchFold(conf.Pattern, line)
return fuzzy.MatchFold(conf.Patterns[0].Pattern, line)
} }
var match int return conf.PatternR.MatchString(line)
//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)
} }
/* /*
@@ -86,19 +55,15 @@ func FilterByFields(conf cfg.Config, data *Tabdata) (*Tabdata, bool, error) {
keep := true keep := true
for idx, header := range data.headers { for idx, header := range data.headers {
lcheader := strings.ToLower(header) if !Exists(conf.Filters, strings.ToLower(header)) {
if !Exists(conf.Filters, lcheader) {
// do not filter by unspecified field // do not filter by unspecified field
continue continue
} }
match := conf.Filters[lcheader].Regex.MatchString(row[idx]) if !conf.Filters[strings.ToLower(header)].MatchString(row[idx]) {
if conf.Filters[lcheader].Negate { // there IS a filter, but it doesn't match
match = !match
}
if !match {
keep = false keep = false
break break
} }
} }
@@ -158,11 +123,8 @@ func Exists[K comparable, V any](m map[K]V, v K) bool {
return false return false
} }
/*
* Filters the whole input lines, returns filtered lines
*/
func FilterByPattern(conf cfg.Config, input io.Reader) (io.Reader, error) { func FilterByPattern(conf cfg.Config, input io.Reader) (io.Reader, error) {
if len(conf.Patterns) == 0 { if conf.Pattern == "" {
return input, nil return input, nil
} }
@@ -174,7 +136,7 @@ func FilterByPattern(conf cfg.Config, input io.Reader) (io.Reader, error) {
line := strings.TrimSpace(scanner.Text()) line := strings.TrimSpace(scanner.Text())
if hadFirst { if hadFirst {
// don't match 1st line, it's the header // don't match 1st line, it's the header
if matchPattern(conf, line) == conf.InvertMatch { if conf.Pattern != "" && matchPattern(conf, line) == conf.InvertMatch {
// by default -v is false, so if a line does NOT // by default -v is false, so if a line does NOT
// match the pattern, we will ignore it. However, // match the pattern, we will ignore it. However,
// if the user specified -v, the matching is inverted, // if the user specified -v, the matching is inverted,

View File

@@ -1,5 +1,5 @@
/* /*
Copyright © 2024-2025 Thomas von Dein Copyright © 2024 Thomas von Dein
This program is free software: you can redistribute it and/or modify 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 it under the terms of the GNU General Public License as published by
@@ -27,21 +27,21 @@ import (
func TestMatchPattern(t *testing.T) { func TestMatchPattern(t *testing.T) {
var input = []struct { var input = []struct {
name string name string
fuzzy bool fuzzy bool
patterns []*cfg.Pattern pattern string
line string line string
}{ }{
{ {
name: "normal", name: "normal",
patterns: []*cfg.Pattern{{Pattern: "haus"}}, pattern: "haus",
line: "hausparty", line: "hausparty",
}, },
{ {
name: "fuzzy", name: "fuzzy",
patterns: []*cfg.Pattern{{Pattern: "hpt"}}, pattern: "hpt",
line: "haus-party-termin", line: "haus-party-termin",
fuzzy: true, fuzzy: true,
}, },
} }
@@ -55,7 +55,7 @@ func TestMatchPattern(t *testing.T) {
conf.UseFuzzySearch = true conf.UseFuzzySearch = true
} }
err := conf.PreparePattern(inputdata.patterns) err := conf.PreparePattern(inputdata.pattern)
if err != nil { if err != nil {
t.Errorf("PreparePattern returned error: %s", err) t.Errorf("PreparePattern returned error: %s", err)
} }
@@ -98,20 +98,6 @@ 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", name: "one-field-inverted",
filter: []string{"one=19"}, filter: []string{"one=19"},

View File

@@ -77,6 +77,14 @@ func PrepareColumns(conf *cfg.Config, data *Tabdata) error {
conf.UseColumns = usecolumns conf.UseColumns = usecolumns
// -y columns
useyankcolumns, err := PrepareColumnVars(conf.YankColumns, data)
if err != nil {
return err
}
conf.UseYankColumns = useyankcolumns
return nil return nil
} }
@@ -293,20 +301,12 @@ func colorizeData(conf cfg.Config, output string) string {
return colorized return colorized
case len(conf.Patterns) > 0 && !conf.NoColor && color.IsConsole(os.Stdout): case len(conf.Pattern) > 0 && !conf.NoColor && color.IsConsole(os.Stdout):
out := output r := regexp.MustCompile("(" + conf.Pattern + ")")
for _, re := range conf.Patterns { return r.ReplaceAllStringFunc(output, func(in string) string {
if !re.Negate { return conf.ColorStyle.Sprint(in)
r := regexp.MustCompile("(" + re.Pattern + ")") })
out = r.ReplaceAllStringFunc(out, func(in string) string {
return conf.ColorStyle.Sprint(in)
})
}
}
return out
default: default:
return output return output

View File

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

View File

@@ -137,7 +137,7 @@ func parseTabular(conf cfg.Config, input io.Reader) (Tabdata, error) {
} }
} else { } else {
// data processing // data processing
if matchPattern(conf, line) == conf.InvertMatch { if conf.Pattern != "" && matchPattern(conf, line) == conf.InvertMatch {
// by default -v is false, so if a line does NOT // by default -v is false, so if a line does NOT
// match the pattern, we will ignore it. However, // match the pattern, we will ignore it. However,
// if the user specified -v, the matching is inverted, // if the user specified -v, the matching is inverted,

View File

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

View File

@@ -1,5 +1,5 @@
/* /*
Copyright © 2022 Thomas von Dein Copyright © 2022-2025 Thomas von Dein
This program is free software: you can redistribute it and/or modify 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 it under the terms of the GNU General Public License as published by
@@ -38,6 +38,9 @@ func printData(writer io.Writer, conf cfg.Config, data *Tabdata) {
// by, independently if it's being used for display or not. // by, independently if it's being used for display or not.
sortTable(conf, data) sortTable(conf, data)
// put one or more columns into clipboard
yankColumns(conf, data)
// add numbers to headers and remove those we're not interested in // add numbers to headers and remove those we're not interested in
numberizeAndReduceHeaders(conf, data) numberizeAndReduceHeaders(conf, data)

51
lib/yank.go Normal file
View File

@@ -0,0 +1,51 @@
/*
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
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 <http://www.gnu.org/licenses/>.
*/
package lib
import (
"log"
"strings"
"github.com/tiagomelo/go-clipboard/clipboard"
"github.com/tlinden/tablizer/cfg"
)
func yankColumns(conf cfg.Config, data *Tabdata) {
var yank []string
if len(data.entries) == 0 || len(conf.UseYankColumns) == 0 {
return
}
for _, row := range data.entries {
for i, field := range row {
for _, idx := range conf.UseYankColumns {
if i == idx-1 {
yank = append(yank, field)
}
}
}
}
if len(yank) > 0 {
cb := clipboard.New(clipboard.ClipboardOptions{Primary: true})
if err := cb.CopyText(strings.Join(yank, " ")); err != nil {
log.Fatalln("error writing string to clipboard:", err)
}
}
}

72
lib/yank_test.go Normal file
View File

@@ -0,0 +1,72 @@
/*
Copyright © 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
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 <http://www.gnu.org/licenses/>.
*/
package lib
import (
"bytes"
"fmt"
"testing"
"github.com/tiagomelo/go-clipboard/clipboard"
"github.com/tlinden/tablizer/cfg"
)
var yanktests = []struct {
name string
yank []int // -y$colum,$column... after processing
filter string
expect string
}{
{
name: "one",
yank: []int{1},
filter: "beta",
},
}
func DISABLED_TestYankColumns(t *testing.T) {
cb := clipboard.New()
for _, testdata := range yanktests {
testname := fmt.Sprintf("yank-%s-filter-%s",
testdata.name, testdata.filter)
t.Run(testname, func(t *testing.T) {
conf := cfg.Config{
OutputMode: cfg.ASCII,
UseYankColumns: testdata.yank,
NoColor: true,
}
conf.ApplyDefaults()
data := newData() // defined in printer_test.go, reused here
var writer bytes.Buffer
printData(&writer, conf, &data)
got, err := cb.PasteText()
if err != nil {
t.Errorf("failed to fetch yanked text from clipboard")
}
if got != testdata.expect {
t.Errorf("not yanked correctly:\n+++ got:\n%s\n+++ want:\n%s",
got, testdata.expect)
}
})
}
}

View File

@@ -1,46 +0,0 @@
# filtering
# a AND b
exec tablizer -r testtable.txt -H -cspecies invasive imperium
stdout 'namak'
! stdout human
# a AND !b
exec tablizer -r testtable.txt -H -cspecies invasive '/imperium/!'
stdout 'human'
! stdout namak
# a AND !b AND c
exec tablizer -r testtable.txt -H -cspecies peaceful '/imperium/!' planetary
stdout 'kenaha'
! stdout 'namak|heduu|riedl'
# case insensitive
exec tablizer -r testtable.txt -H -cspecies '/REGIONAL/i'
stdout namak
! stdout 'human|riedl|heduu|kenaa'
# case insensitive negated
exec tablizer -r testtable.txt -H -cspecies '/REGIONAL/!i'
stdout 'human|riedl|heduu|kenaa'
! stdout namak
# !a AND !b
exec tablizer -r testtable.txt -H -cspecies '/galactic/!' '/planetary/!'
stdout namak
! stdout 'human|riedl|heduu|kenaa'
# same case insensitive
exec tablizer -r testtable.txt -H -cspecies '/GALACTIC/i!' '/PLANETARY/!i'
stdout namak
! stdout 'human|riedl|heduu|kenaa'
# will be automatically created in work dir
-- testtable.txt --
SPECIES TYPE HOME STAGE SPREAD
human invasive earth brink planetary
riedl peaceful keauna civilized pangalactic
namak invasive namak imperium regional
heduu peaceful iu imperium galactic
kenaha peaceful kohi hunter-gatherer planetary

View File

@@ -1,6 +0,0 @@
SPECIES TYPE HOME STAGE
human invasive earth brink
riedl peaceful keauna civilized
namak invasive namak imperium
heduu peaceful iu imperium
kenaha peaceful kohi hunter-gatherer

View File

@@ -133,7 +133,7 @@
.\" ======================================================================== .\" ========================================================================
.\" .\"
.IX Title "TABLIZER 1" .IX Title "TABLIZER 1"
.TH TABLIZER 1 "2025-01-30" "1" "User Commands" .TH TABLIZER 1 "2025-01-23" "1" "User Commands"
.\" For nroff, turn off justification. Always turn off hyphenation; it makes .\" For nroff, turn off justification. Always turn off hyphenation; it makes
.\" way too many mistakes in technical documents. .\" way too many mistakes in technical documents.
.if n .ad l .if n .ad l
@@ -144,7 +144,7 @@ tablizer \- Manipulate tabular output of other programs
.IX Header "SYNOPSIS" .IX Header "SYNOPSIS"
.Vb 2 .Vb 2
\& Usage: \& Usage:
\& tablizer [regex,...] [file, ...] [flags] \& tablizer [regex] [file, ...] [flags]
\& \&
\& Operational Flags: \& Operational Flags:
\& \-c, \-\-columns string Only show the speficied columns (separated by ,) \& \-c, \-\-columns string Only show the speficied columns (separated by ,)
@@ -155,7 +155,7 @@ tablizer \- Manipulate tabular output of other programs
\& \-s, \-\-separator string Custom field separator \& \-s, \-\-separator string Custom field separator
\& \-k, \-\-sort\-by int|name Sort by column (default: 1) \& \-k, \-\-sort\-by int|name Sort by column (default: 1)
\& \-z, \-\-fuzzy Use fuzzy search [experimental] \& \-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 ,) \& \-T, \-\-transpose\-columns string Transpose the speficied columns (separated by ,)
\& \-R, \-\-regex\-transposer /from/to/ Apply /search/replace/ regexp to fields given in \-T \& \-R, \-\-regex\-transposer /from/to/ Apply /search/replace/ regexp to fields given in \-T
\& \&
@@ -168,6 +168,8 @@ tablizer \- Manipulate tabular output of other programs
\& \-C, \-\-csv Enable CSV output \& \-C, \-\-csv Enable CSV output
\& \-A, \-\-ascii Default output mode, ascii tabular \& \-A, \-\-ascii Default output mode, ascii tabular
\& \-L, \-\-hightlight\-lines Use alternating background colors for tables \& \-L, \-\-hightlight\-lines Use alternating background colors for tables
\& \-y, \-\-yank\-columns Yank specified columns (separated by ,) to clipboard,
\& space separated
\& \&
\& Sort Mode Flags (mutually exclusive): \& Sort Mode Flags (mutually exclusive):
\& \-a, \-\-sort\-age sort according to age (duration) string \& \-a, \-\-sort\-age sort according to age (duration) string
@@ -278,52 +280,38 @@ Finally the \fB\-d\fR option enables debugging output which is mostly
useful for the developer. useful for the developer.
.SS "\s-1PATTERNS AND FILTERING\s0" .SS "\s-1PATTERNS AND FILTERING\s0"
.IX Subsection "PATTERNS AND FILTERING" .IX Subsection "PATTERNS AND FILTERING"
You can reduce the rows being displayed by using one or more regular You can reduce the rows being displayed by using a regular expression
expression patterns. The regexp language being used is the one of pattern. The regexp is \s-1PCRE\s0 compatible, refer to the syntax cheat
\&\s-1GOLANG,\s0 refer to the syntax cheat sheet here: sheet here: <https://github.com/google/re2/wiki/Syntax>. If you want
<https://pkg.go.dev/regexp/syntax>. to read a more comprehensive documentation about the topic and have
.PP perl installed you can read it with:
If you want to read a more comprehensive documentation about the
topic and have perl installed you can read it with:
.PP .PP
.Vb 1 .Vb 1
\& perldoc perlre \& perldoc perlre
.Ve .Ve
.PP .PP
Or read it online: <https://perldoc.perl.org/perlre>. But please note Or read it online: <https://perldoc.perl.org/perlre>.
that the \s-1GO\s0 regexp engine does \s-1NOT\s0 support all perl regex terms,
especially look-ahead and look-behind.
.PP .PP
If you want to supply flags to a regex, then surround it with slashes A note on modifiers: the regexp engine used in tablizer uses another
and append the flag. The following flags are supported: modifier syntax:
.PP .PP
.Vb 2 .Vb 1
\& i => case insensitive \& (?MODIFIER)
\& ! => negative match
.Ve .Ve
.PP .PP
The most important modifiers are:
.PP
\&\f(CW\*(C`i\*(C'\fR ignore case
\&\f(CW\*(C`m\*(C'\fR multiline mode
\&\f(CW\*(C`s\*(C'\fR single line mode
.PP
Example for a case insensitive search: Example for a case insensitive search:
.PP .PP
.Vb 1 .Vb 1
\& kubectl get pods \-A | tablizer "/account/i" \& kubectl get pods \-A | tablizer "(?i)account"
.Ve .Ve
.PP .PP
If you use the \f(CW\*(C`!\*(C'\fR flag, then the regex match will be negated, that You can use the experimental fuzzy search feature by providing the
is, if a line in the input matches the given regex, but \f(CW\*(C`!\*(C'\fR is
supplied, tablizer will \s-1NOT\s0 include it in the output.
.PP
For example, here we want to get all lines matching \*(L"foo\*(R" but not
\&\*(L"bar\*(R":
.PP
.Vb 1
\& cat table | tablizer foo \*(Aq/bar/!\*(Aq
.Ve
.PP
This would match a line \*(L"foo zorro\*(R" but not \*(L"foo bar\*(R".
.PP
The flags can also be combined.
.PP
You can also use the experimental fuzzy search feature by providing the
option \fB\-z\fR, in which case the pattern is regarded as a fuzzy search option \fB\-z\fR, in which case the pattern is regarded as a fuzzy search
term, not a regexp. term, not a regexp.
.PP .PP
@@ -340,12 +328,6 @@ Fieldnames (== columns headers) are case insensitive.
If you specify more than one filter, both filters have to match (\s-1AND\s0 If you specify more than one filter, both filters have to match (\s-1AND\s0
operation). operation).
.PP .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. If the option \fB\-v\fR is specified, the filtering is inverted.
.SS "\s-1COLUMNS\s0" .SS "\s-1COLUMNS\s0"
.IX Subsection "COLUMNS" .IX Subsection "COLUMNS"
@@ -462,6 +444,20 @@ more output modes available: \fBorgtbl\fR which prints an Emacs org-mode
table and \fBmarkdown\fR which prints a Markdown table, \fByaml\fR, which table and \fBmarkdown\fR which prints a Markdown table, \fByaml\fR, which
prints yaml encoding and \s-1CSV\s0 mode, which prints a comma separated prints yaml encoding and \s-1CSV\s0 mode, which prints a comma separated
value file. value file.
.SS "\s-1PUT FIELDS TO CLIPBOARD\s0"
.IX Subsection "PUT FIELDS TO CLIPBOARD"
You can let tablizer put fields to the clipboard using the option
\&\f(CW\*(C`\-y\*(C'\fR. This best fits the use-case when the result of your filtering
yields just one row. For example:
.PP
.Vb 1
\& cloudctl cluster ls | tablizer \-yid matchbox
.Ve
.PP
If \*(L"matchbox\*(R" matches one cluster, you can immediately use the id of
that cluster somewhere else and paste it. Of course, if there are
multiple matches, then all id's will be put into the clipboard
separated by one space.
.SS "\s-1ENVIRONMENT VARIABLES\s0" .SS "\s-1ENVIRONMENT VARIABLES\s0"
.IX Subsection "ENVIRONMENT VARIABLES" .IX Subsection "ENVIRONMENT VARIABLES"
\&\fBtablizer\fR supports certain environment variables which use can use \&\fBtablizer\fR supports certain environment variables which use can use

View File

@@ -5,7 +5,7 @@ tablizer - Manipulate tabular output of other programs
=head1 SYNOPSIS =head1 SYNOPSIS
Usage: Usage:
tablizer [regex,...] [file, ...] [flags] tablizer [regex] [file, ...] [flags]
Operational Flags: Operational Flags:
-c, --columns string Only show the speficied columns (separated by ,) -c, --columns string Only show the speficied columns (separated by ,)
@@ -16,7 +16,7 @@ tablizer - Manipulate tabular output of other programs
-s, --separator string Custom field separator -s, --separator string Custom field separator
-k, --sort-by int|name Sort by column (default: 1) -k, --sort-by int|name Sort by column (default: 1)
-z, --fuzzy Use fuzzy search [experimental] -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 ,) -T, --transpose-columns string Transpose the speficied columns (separated by ,)
-R, --regex-transposer /from/to/ Apply /search/replace/ regexp to fields given in -T -R, --regex-transposer /from/to/ Apply /search/replace/ regexp to fields given in -T
@@ -29,6 +29,8 @@ tablizer - Manipulate tabular output of other programs
-C, --csv Enable CSV output -C, --csv Enable CSV output
-A, --ascii Default output mode, ascii tabular -A, --ascii Default output mode, ascii tabular
-L, --hightlight-lines Use alternating background colors for tables -L, --hightlight-lines Use alternating background colors for tables
-y, --yank-columns Yank specified columns (separated by ,) to clipboard,
space separated
Sort Mode Flags (mutually exclusive): Sort Mode Flags (mutually exclusive):
-a, --sort-age sort according to age (duration) string -a, --sort-age sort according to age (duration) string
@@ -142,44 +144,32 @@ useful for the developer.
=head2 PATTERNS AND FILTERING =head2 PATTERNS AND FILTERING
You can reduce the rows being displayed by using one or more regular You can reduce the rows being displayed by using a regular expression
expression patterns. The regexp language being used is the one of pattern. The regexp is PCRE compatible, refer to the syntax cheat
GOLANG, refer to the syntax cheat sheet here: sheet here: L<https://github.com/google/re2/wiki/Syntax>. If you want
L<https://pkg.go.dev/regexp/syntax>. to read a more comprehensive documentation about the topic and have
perl installed you can read it with:
If you want to read a more comprehensive documentation about the
topic and have perl installed you can read it with:
perldoc perlre perldoc perlre
Or read it online: L<https://perldoc.perl.org/perlre>. But please note Or read it online: L<https://perldoc.perl.org/perlre>.
that the GO regexp engine does NOT support all perl regex terms,
especially look-ahead and look-behind.
If you want to supply flags to a regex, then surround it with slashes A note on modifiers: the regexp engine used in tablizer uses another
and append the flag. The following flags are supported: modifier syntax:
i => case insensitive (?MODIFIER)
! => negative match
The most important modifiers are:
C<i> ignore case
C<m> multiline mode
C<s> single line mode
Example for a case insensitive search: Example for a case insensitive search:
kubectl get pods -A | tablizer "/account/i" kubectl get pods -A | tablizer "(?i)account"
If you use the C<!> flag, then the regex match will be negated, that You can use the experimental fuzzy search feature by providing the
is, if a line in the input matches the given regex, but C<!> is
supplied, tablizer will NOT include it in the output.
For example, here we want to get all lines matching "foo" but not
"bar":
cat table | tablizer foo '/bar/!'
This would match a line "foo zorro" but not "foo bar".
The flags can also be combined.
You can also use the experimental fuzzy search feature by providing the
option B<-z>, in which case the pattern is regarded as a fuzzy search option B<-z>, in which case the pattern is regarded as a fuzzy search
term, not a regexp. term, not a regexp.
@@ -194,10 +184,6 @@ Fieldnames (== columns headers) are case insensitive.
If you specify more than one filter, both filters have to match (AND If you specify more than one filter, both filters have to match (AND
operation). operation).
These field filters can also be negated:
fieldname!=regexp
If the option B<-v> is specified, the filtering is inverted. If the option B<-v> is specified, the filtering is inverted.
@@ -306,6 +292,19 @@ table and B<markdown> which prints a Markdown table, B<yaml>, which
prints yaml encoding and CSV mode, which prints a comma separated prints yaml encoding and CSV mode, which prints a comma separated
value file. value file.
=head2 PUT FIELDS TO CLIPBOARD
You can let tablizer put fields to the clipboard using the option
C<-y>. This best fits the use-case when the result of your filtering
yields just one row. For example:
cloudctl cluster ls | tablizer -yid matchbox
If "matchbox" matches one cluster, you can immediately use the id of
that cluster somewhere else and paste it. Of course, if there are
multiple matches, then all id's will be put into the clipboard
separated by one space.
=head2 ENVIRONMENT VARIABLES =head2 ENVIRONMENT VARIABLES
B<tablizer> supports certain environment variables which use can use B<tablizer> supports certain environment variables which use can use