mirror of
https://codeberg.org/scip/tablizer.git
synced 2025-12-18 21:11:03 +01:00
Compare commits
6 Commits
v1.3.0
...
feature/mu
| Author | SHA1 | Date | |
|---|---|---|---|
| 479d8c274a | |||
| a49e28518b | |||
| e29e72b7d2 | |||
| f6e3075ea8 | |||
| 03f3225f24 | |||
| 63c7ef26b6 |
32
.github/workflows/release.yaml
vendored
Normal file
32
.github/workflows/release.yaml
vendored
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
name: build-and-test
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- "*"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release:
|
||||||
|
name: Build Release Assets
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v1
|
||||||
|
with:
|
||||||
|
go-version: 1.22.11
|
||||||
|
|
||||||
|
- name: Build the executables
|
||||||
|
run: ./mkrel.sh tablizer ${{ github.ref_name}}
|
||||||
|
|
||||||
|
- name: List the executables
|
||||||
|
run: ls -l ./releases
|
||||||
|
|
||||||
|
- name: Upload the binaries
|
||||||
|
uses: svenstaro/upload-release-action@v2
|
||||||
|
with:
|
||||||
|
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
tag: ${{ github.ref_name }}
|
||||||
|
file: ./releases/*
|
||||||
|
file_glob: true
|
||||||
3
Makefile
3
Makefile
@@ -53,8 +53,7 @@ buildlocal:
|
|||||||
go build -ldflags "-X 'github.com/tlinden/tablizer/cfg.VERSION=$(VERSION)'"
|
go build -ldflags "-X 'github.com/tlinden/tablizer/cfg.VERSION=$(VERSION)'"
|
||||||
|
|
||||||
release:
|
release:
|
||||||
./mkrel.sh $(tool) $(version)
|
gh release create $(version) --generate-notes
|
||||||
gh release create $(version) --generate-notes releases/*
|
|
||||||
|
|
||||||
install: buildlocal
|
install: buildlocal
|
||||||
install -d -o $(UID) -g $(GID) $(PREFIX)/bin
|
install -d -o $(UID) -g $(GID) $(PREFIX)/bin
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const DefaultSeparator string = `(\s\s+|\t)`
|
const DefaultSeparator string = `(\s\s+|\t)`
|
||||||
const Version string = "v1.3.0"
|
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,6 +52,12 @@ type Transposer struct {
|
|||||||
Replace string
|
Replace string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Pattern struct {
|
||||||
|
Pattern string
|
||||||
|
PatternRe *regexp.Regexp
|
||||||
|
Negate bool
|
||||||
|
}
|
||||||
|
|
||||||
// internal config
|
// internal config
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Debug bool
|
Debug bool
|
||||||
@@ -62,14 +68,14 @@ type Config struct {
|
|||||||
Separator string
|
Separator string
|
||||||
OutputMode int
|
OutputMode int
|
||||||
InvertMatch bool
|
InvertMatch bool
|
||||||
Pattern string
|
Patterns []*Pattern
|
||||||
PatternR *regexp.Regexp
|
|
||||||
UseFuzzySearch bool
|
UseFuzzySearch bool
|
||||||
UseHighlight bool
|
UseHighlight bool
|
||||||
|
|
||||||
SortMode string
|
SortMode string
|
||||||
SortDescending bool
|
SortDescending bool
|
||||||
SortByColumn int
|
SortByColumn string // 1,2
|
||||||
|
UseSortByColumn []int // []int{1,2}
|
||||||
|
|
||||||
TransposeColumns string // 1,2
|
TransposeColumns string // 1,2
|
||||||
UseTransposeColumns []int // []int{1,2}
|
UseTransposeColumns []int // []int{1,2}
|
||||||
@@ -332,15 +338,37 @@ func (conf *Config) ApplyDefaults() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (conf *Config) PreparePattern(pattern string) error {
|
func (conf *Config) PreparePattern(patterns []*Pattern) error {
|
||||||
PatternR, err := regexp.Compile(pattern)
|
// regex checks if a pattern looks like /$pattern/[i!]
|
||||||
|
flagre := regexp.MustCompile(`^/(.*)/([i!]*)$`)
|
||||||
|
|
||||||
if err != nil {
|
for _, pattern := range patterns {
|
||||||
return fmt.Errorf("regexp pattern %s is invalid: %w", conf.Pattern, err)
|
matches := flagre.FindAllStringSubmatch(pattern.Pattern, -1)
|
||||||
|
|
||||||
|
// 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.PatternR = PatternR
|
conf.Patterns = patterns
|
||||||
conf.Pattern = pattern
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,20 +79,55 @@ func TestPrepareSortFlags(t *testing.T) {
|
|||||||
|
|
||||||
func TestPreparePattern(t *testing.T) {
|
func TestPreparePattern(t *testing.T) {
|
||||||
var tests = []struct {
|
var tests = []struct {
|
||||||
pattern string
|
patterns []*Pattern
|
||||||
wanterr bool
|
name string
|
||||||
|
wanterr bool
|
||||||
|
wanticase bool
|
||||||
|
wantneg bool
|
||||||
}{
|
}{
|
||||||
{"[A-Z]+", false},
|
{
|
||||||
{"[a-z", true},
|
[]*Pattern{{Pattern: "[A-Z]+"}},
|
||||||
|
"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",
|
testname := fmt.Sprintf("PreparePattern-pattern-%s-wanterr-%t", testdata.name, testdata.wanterr)
|
||||||
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.pattern)
|
err := conf.PreparePattern(testdata.patterns)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !testdata.wanterr {
|
if !testdata.wanterr {
|
||||||
|
|||||||
@@ -151,7 +151,7 @@ func Execute() {
|
|||||||
"Transpose the speficied columns (separated by ,)")
|
"Transpose the speficied columns (separated by ,)")
|
||||||
|
|
||||||
// sort options
|
// sort options
|
||||||
rootCmd.PersistentFlags().IntVarP(&conf.SortByColumn, "sort-by", "k", 0,
|
rootCmd.PersistentFlags().StringVarP(&conf.SortByColumn, "sort-by", "k", "",
|
||||||
"Sort by column (default: 1)")
|
"Sort by column (default: 1)")
|
||||||
|
|
||||||
// sort mode, only 1 allowed
|
// sort mode, only 1 allowed
|
||||||
|
|||||||
@@ -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 ,)
|
||||||
@@ -15,7 +15,7 @@ SYNOPSIS
|
|||||||
-N, --no-color Disable pattern highlighting
|
-N, --no-color Disable pattern highlighting
|
||||||
-H, --no-headers Disable headers display
|
-H, --no-headers Disable headers display
|
||||||
-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|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 ,)
|
||||||
@@ -103,10 +103,19 @@ DESCRIPTION
|
|||||||
highlighted. You can disable this behavior with the -N option.
|
highlighted. You can disable this behavior with the -N option.
|
||||||
|
|
||||||
Use the -k option to specify by which column to sort the tabular data
|
Use the -k option to specify by which column to sort the tabular data
|
||||||
(as in GNU sort(1)). The default sort column is the first one. To
|
(as in GNU sort(1)). The default sort column is the first one. You can
|
||||||
disable sorting at all, supply 0 (Zero) to -k. The default sort order is
|
specify column numbers or names. Column numbers start with 1, names are
|
||||||
ascending. You can change this to descending order using the option -D.
|
case insensitive. You can specify multiple columns separated by comma to
|
||||||
The default sort order is by string, but there are other sort modes:
|
sort, but the type must be the same. For example if you want to sort
|
||||||
|
numerically, all columns must be numbers. If you use column numbers,
|
||||||
|
then be aware, that these are the numbers before column extraction. For
|
||||||
|
example if you have a table with 4 columns and specify "-c4", then only
|
||||||
|
1 column (the fourth) will be printed, however if you want to sort by
|
||||||
|
this column, you'll have to specify "-k4".
|
||||||
|
|
||||||
|
The default sort order is ascending. You can change this to descending
|
||||||
|
order using the option -D. The default sort order is by alphanumeric
|
||||||
|
string, but there are other sort modes:
|
||||||
|
|
||||||
-a --sort-age
|
-a --sort-age
|
||||||
Sorts duration strings like "1d4h32m51s".
|
Sorts duration strings like "1d4h32m51s".
|
||||||
@@ -121,30 +130,43 @@ DESCRIPTION
|
|||||||
for the developer.
|
for the developer.
|
||||||
|
|
||||||
PATTERNS AND FILTERING
|
PATTERNS AND FILTERING
|
||||||
You can reduce the rows being displayed by using a regular expression
|
You can reduce the rows being displayed by using one or more regular
|
||||||
pattern. The regexp is PCRE compatible, refer to the syntax cheat sheet
|
expression patterns. The regexp language being used is the one of
|
||||||
here: <https://github.com/google/re2/wiki/Syntax>. If you want to read a
|
GOLANG, refer to the syntax cheat sheet here:
|
||||||
more comprehensive documentation about the topic and have perl installed
|
<https://pkg.go.dev/regexp/syntax>.
|
||||||
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>.
|
Or read it online: <https://perldoc.perl.org/perlre>. But please note
|
||||||
|
that the GO regexp engine does NOT support all perl regex terms,
|
||||||
|
especially look-ahead and look-behind.
|
||||||
|
|
||||||
A note on modifiers: the regexp engine used in tablizer uses another
|
If you want to supply flags to a regex, then surround it with slashes
|
||||||
modifier syntax:
|
and append the flag. The following flags are supported:
|
||||||
|
|
||||||
(?MODIFIER)
|
i => case insensitive
|
||||||
|
! => 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 "(?i)account"
|
kubectl get pods -A | tablizer "/account/i"
|
||||||
|
|
||||||
You can use the experimental fuzzy search feature by providing the
|
If you use the "!" flag, then the regex match will be negated, that is,
|
||||||
|
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.
|
||||||
|
|
||||||
@@ -383,7 +405,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 ,)
|
||||||
@@ -392,7 +414,7 @@ Operational Flags:
|
|||||||
-N, --no-color Disable pattern highlighting
|
-N, --no-color Disable pattern highlighting
|
||||||
-H, --no-headers Disable headers display
|
-H, --no-headers Disable headers display
|
||||||
-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|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 ,)
|
||||||
|
|||||||
@@ -27,15 +27,46 @@ 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 conf.UseFuzzySearch {
|
if len(conf.Patterns) == 0 {
|
||||||
return fuzzy.MatchFold(conf.Pattern, line)
|
// 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -123,8 +154,11 @@ 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 conf.Pattern == "" {
|
if len(conf.Patterns) == 0 {
|
||||||
return input, nil
|
return input, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,7 +170,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 conf.Pattern != "" && matchPattern(conf, line) == conf.InvertMatch {
|
if 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,
|
||||||
|
|||||||
@@ -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
|
||||||
pattern string
|
patterns []*cfg.Pattern
|
||||||
line string
|
line string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "normal",
|
name: "normal",
|
||||||
pattern: "haus",
|
patterns: []*cfg.Pattern{{Pattern: "haus"}},
|
||||||
line: "hausparty",
|
line: "hausparty",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "fuzzy",
|
name: "fuzzy",
|
||||||
pattern: "hpt",
|
patterns: []*cfg.Pattern{{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.pattern)
|
err := conf.PreparePattern(inputdata.patterns)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("PreparePattern returned error: %s", err)
|
t.Errorf("PreparePattern returned error: %s", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,6 +99,19 @@ func PrepareTransposerColumns(conf *cfg.Config, data *Tabdata) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// output option, prepare -k1,2 sort fields
|
||||||
|
func PrepareSortColumns(conf *cfg.Config, data *Tabdata) error {
|
||||||
|
// -c columns
|
||||||
|
usecolumns, err := PrepareColumnVars(conf.SortByColumn, data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
conf.UseSortByColumn = usecolumns
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func PrepareColumnVars(columns string, data *Tabdata) ([]int, error) {
|
func PrepareColumnVars(columns string, data *Tabdata) ([]int, error) {
|
||||||
if columns == "" {
|
if columns == "" {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
@@ -280,12 +293,20 @@ func colorizeData(conf cfg.Config, output string) string {
|
|||||||
|
|
||||||
return colorized
|
return colorized
|
||||||
|
|
||||||
case len(conf.Pattern) > 0 && !conf.NoColor && color.IsConsole(os.Stdout):
|
case len(conf.Patterns) > 0 && !conf.NoColor && color.IsConsole(os.Stdout):
|
||||||
r := regexp.MustCompile("(" + conf.Pattern + ")")
|
out := output
|
||||||
|
|
||||||
return r.ReplaceAllStringFunc(output, func(in string) string {
|
for _, re := range conf.Patterns {
|
||||||
return conf.ColorStyle.Sprint(in)
|
if !re.Negate {
|
||||||
})
|
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
|
||||||
|
|||||||
25
lib/io.go
25
lib/io.go
@@ -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, pattern, err := determineIO(conf, args)
|
fd, patterns, err := determineIO(conf, args)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := conf.PreparePattern(pattern); err != nil {
|
if err := conf.PreparePattern(patterns); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,6 +48,11 @@ func ProcessFiles(conf *cfg.Config, args []string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = PrepareSortColumns(conf, &data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
err = PrepareColumns(conf, &data)
|
err = PrepareColumns(conf, &data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -58,9 +63,9 @@ func ProcessFiles(conf *cfg.Config, args []string) error {
|
|||||||
return nil
|
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 filehandle io.Reader
|
||||||
var pattern string
|
var patterns []*cfg.Pattern
|
||||||
var haveio bool
|
var haveio bool
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
@@ -71,7 +76,7 @@ func determineIO(conf *cfg.Config, args []string) (io.Reader, string, error) {
|
|||||||
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, "", 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
|
filehandle = fd
|
||||||
@@ -88,13 +93,15 @@ func determineIO(conf *cfg.Config, args []string) (io.Reader, string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(args) > 0 {
|
if len(args) > 0 {
|
||||||
pattern = args[0]
|
patterns = make([]*cfg.Pattern, len(args))
|
||||||
conf.Pattern = args[0]
|
for i, arg := range args {
|
||||||
|
patterns[i] = &cfg.Pattern{Pattern: arg}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !haveio {
|
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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -137,7 +137,7 @@ func parseTabular(conf cfg.Config, input io.Reader) (Tabdata, error) {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// data processing
|
// 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
|
// 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,
|
||||||
|
|||||||
@@ -83,36 +83,42 @@ func TestParser(t *testing.T) {
|
|||||||
|
|
||||||
func TestParserPatternmatching(t *testing.T) {
|
func TestParserPatternmatching(t *testing.T) {
|
||||||
var tests = []struct {
|
var tests = []struct {
|
||||||
entries [][]string
|
name string
|
||||||
pattern string
|
entries [][]string
|
||||||
invert bool
|
patterns []*cfg.Pattern
|
||||||
want bool
|
invert bool
|
||||||
|
want bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
|
name: "match",
|
||||||
entries: [][]string{
|
entries: [][]string{
|
||||||
{"asd", "igig", "cxxxncnc"},
|
{"asd", "igig", "cxxxncnc"},
|
||||||
},
|
},
|
||||||
pattern: "ig",
|
patterns: []*cfg.Pattern{{Pattern: "ig"}},
|
||||||
invert: false,
|
invert: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
name: "invert",
|
||||||
entries: [][]string{
|
entries: [][]string{
|
||||||
{"19191", "EDD 1", "X"},
|
{"19191", "EDD 1", "X"},
|
||||||
},
|
},
|
||||||
pattern: "ig",
|
patterns: []*cfg.Pattern{{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.pattern, testdata.invert)
|
inputdata.name, testdata.name, testdata.invert)
|
||||||
t.Run(testname, func(t *testing.T) {
|
t.Run(testname, func(t *testing.T) {
|
||||||
conf := cfg.Config{InvertMatch: testdata.invert, Pattern: testdata.pattern,
|
conf := cfg.Config{
|
||||||
Separator: inputdata.separator}
|
InvertMatch: testdata.invert,
|
||||||
|
Patterns: testdata.patterns,
|
||||||
|
Separator: inputdata.separator,
|
||||||
|
}
|
||||||
|
|
||||||
_ = conf.PreparePattern(testdata.pattern)
|
_ = conf.PreparePattern(testdata.patterns)
|
||||||
|
|
||||||
readFd := strings.NewReader(strings.TrimSpace(inputdata.text))
|
readFd := strings.NewReader(strings.TrimSpace(inputdata.text))
|
||||||
gotdata, err := Parse(conf, readFd)
|
gotdata, err := Parse(conf, readFd)
|
||||||
@@ -125,7 +131,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.pattern, testdata.invert, testdata.entries, gotdata.entries)
|
testdata.name, testdata.invert, testdata.entries, gotdata.entries)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -33,15 +33,17 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func printData(writer io.Writer, conf cfg.Config, data *Tabdata) {
|
func printData(writer io.Writer, conf cfg.Config, data *Tabdata) {
|
||||||
// add numbers to headers and remove this we're not interested in
|
// Sort the data first, before headers+entries are being
|
||||||
|
// reduced. That way the user can specify any valid column to sort
|
||||||
|
// by, independently if it's being used for display or not.
|
||||||
|
sortTable(conf, data)
|
||||||
|
|
||||||
|
// add numbers to headers and remove those we're not interested in
|
||||||
numberizeAndReduceHeaders(conf, data)
|
numberizeAndReduceHeaders(conf, data)
|
||||||
|
|
||||||
// remove unwanted columns, if any
|
// remove unwanted columns, if any
|
||||||
reduceColumns(conf, data)
|
reduceColumns(conf, data)
|
||||||
|
|
||||||
// sort the data
|
|
||||||
sortTable(conf, data)
|
|
||||||
|
|
||||||
switch conf.OutputMode {
|
switch conf.OutputMode {
|
||||||
case cfg.Extended:
|
case cfg.Extended:
|
||||||
printExtendedData(writer, conf, data)
|
printExtendedData(writer, conf, data)
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ var tests = []struct {
|
|||||||
name string // so we can identify which one fails, can be the same
|
name string // so we can identify which one fails, can be the same
|
||||||
// for multiple tests, because flags will be appended to the name
|
// for multiple tests, because flags will be appended to the name
|
||||||
sortby string // empty == default
|
sortby string // empty == default
|
||||||
column int // sort by this column, 0 == default first or NO Sort
|
column int // sort by this column (numbers start by 1)
|
||||||
desc bool // sort in descending order, default == ascending
|
desc bool // sort in descending order, default == ascending
|
||||||
nonum bool // hide numbering
|
nonum bool // hide numbering
|
||||||
mode int // shell, orgtbl, etc. empty == default: ascii
|
mode int // shell, orgtbl, etc. empty == default: ascii
|
||||||
@@ -162,7 +162,7 @@ DURATION(2): 33d12h
|
|||||||
|
|
||||||
//------------------------ SORT TESTS
|
//------------------------ SORT TESTS
|
||||||
{
|
{
|
||||||
name: "sortbycolumn",
|
name: "sortbycolumn3",
|
||||||
column: 3,
|
column: 3,
|
||||||
sortby: "numeric",
|
sortby: "numeric",
|
||||||
desc: false,
|
desc: false,
|
||||||
@@ -173,7 +173,7 @@ beta 1d10h5m1s 33 3/1/2014
|
|||||||
alpha 4h35m 170 2013-Feb-03`,
|
alpha 4h35m 170 2013-Feb-03`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "sortbycolumn",
|
name: "sortbycolumn4",
|
||||||
column: 4,
|
column: 4,
|
||||||
sortby: "time",
|
sortby: "time",
|
||||||
desc: false,
|
desc: false,
|
||||||
@@ -184,7 +184,7 @@ alpha 4h35m 170 2013-Feb-03
|
|||||||
beta 1d10h5m1s 33 3/1/2014`,
|
beta 1d10h5m1s 33 3/1/2014`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "sortbycolumn",
|
name: "sortbycolumn2",
|
||||||
column: 2,
|
column: 2,
|
||||||
sortby: "duration",
|
sortby: "duration",
|
||||||
desc: false,
|
desc: false,
|
||||||
@@ -251,15 +251,14 @@ DURATION(2) WHEN(4)
|
|||||||
|
|
||||||
func TestPrinter(t *testing.T) {
|
func TestPrinter(t *testing.T) {
|
||||||
for _, testdata := range tests {
|
for _, testdata := range tests {
|
||||||
testname := fmt.Sprintf("print-sortcol-%d-desc-%t-sortby-%s-mode-%d-usecolumns-%s",
|
testname := fmt.Sprintf("print-%s-%d-desc-%t-sortby-%s-mode-%d-usecolumns-%s",
|
||||||
testdata.column, testdata.desc, testdata.sortby, testdata.mode, testdata.usecolstr)
|
testdata.name, testdata.column, testdata.desc, testdata.sortby, testdata.mode, testdata.usecolstr)
|
||||||
t.Run(testname, func(t *testing.T) {
|
t.Run(testname, func(t *testing.T) {
|
||||||
// replaces os.Stdout, but we ignore it
|
// replaces os.Stdout, but we ignore it
|
||||||
var writer bytes.Buffer
|
var writer bytes.Buffer
|
||||||
|
|
||||||
// cmd flags
|
// cmd flags
|
||||||
conf := cfg.Config{
|
conf := cfg.Config{
|
||||||
SortByColumn: testdata.column,
|
|
||||||
SortDescending: testdata.desc,
|
SortDescending: testdata.desc,
|
||||||
SortMode: testdata.sortby,
|
SortMode: testdata.sortby,
|
||||||
OutputMode: testdata.mode,
|
OutputMode: testdata.mode,
|
||||||
@@ -268,6 +267,10 @@ func TestPrinter(t *testing.T) {
|
|||||||
NoColor: true,
|
NoColor: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if testdata.column > 0 {
|
||||||
|
conf.UseSortByColumn = []int{testdata.column}
|
||||||
|
}
|
||||||
|
|
||||||
conf.ApplyDefaults()
|
conf.ApplyDefaults()
|
||||||
|
|
||||||
// the test checks the len!
|
// the test checks the len!
|
||||||
|
|||||||
41
lib/sort.go
41
lib/sort.go
@@ -18,6 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
package lib
|
package lib
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"cmp"
|
||||||
"regexp"
|
"regexp"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -27,34 +28,41 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func sortTable(conf cfg.Config, data *Tabdata) {
|
func sortTable(conf cfg.Config, data *Tabdata) {
|
||||||
if conf.SortByColumn <= 0 {
|
if len(conf.UseSortByColumn) == 0 {
|
||||||
// no sorting wanted
|
// no sorting wanted
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// slightly modified here to match internal array indicies
|
|
||||||
col := conf.SortByColumn
|
|
||||||
|
|
||||||
col-- // ui starts counting by 1, but use 0 internally
|
|
||||||
|
|
||||||
// sanity checks
|
// sanity checks
|
||||||
if len(data.entries) == 0 {
|
if len(data.entries) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if col >= len(data.headers) {
|
|
||||||
// fall back to default column
|
|
||||||
col = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// actual sorting
|
// actual sorting
|
||||||
sort.SliceStable(data.entries, func(i, j int) bool {
|
sort.SliceStable(data.entries, func(i, j int) bool {
|
||||||
return compare(&conf, data.entries[i][col], data.entries[j][col])
|
// holds the result of a sort of one column
|
||||||
|
comparators := []int{}
|
||||||
|
|
||||||
|
// iterate over all columns to be sorted, conf.SortMode must be identical!
|
||||||
|
for _, column := range conf.UseSortByColumn {
|
||||||
|
comparators = append(comparators, compare(&conf, data.entries[i][column-1], data.entries[j][column-1]))
|
||||||
|
}
|
||||||
|
|
||||||
|
// return the combined result
|
||||||
|
res := cmp.Or(comparators...)
|
||||||
|
|
||||||
|
switch res {
|
||||||
|
case 0:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// config is not modified here, but it would be inefficient to copy it every loop
|
// config is not modified here, but it would be inefficient to copy it every loop
|
||||||
func compare(conf *cfg.Config, left string, right string) bool {
|
func compare(conf *cfg.Config, left string, right string) int {
|
||||||
var comp bool
|
var comp bool
|
||||||
|
|
||||||
switch conf.SortMode {
|
switch conf.SortMode {
|
||||||
@@ -88,7 +96,12 @@ func compare(conf *cfg.Config, left string, right string) bool {
|
|||||||
comp = !comp
|
comp = !comp
|
||||||
}
|
}
|
||||||
|
|
||||||
return comp
|
switch comp {
|
||||||
|
case true:
|
||||||
|
return 0
|
||||||
|
default:
|
||||||
|
return 1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -53,18 +53,18 @@ func TestCompare(t *testing.T) {
|
|||||||
mode string
|
mode string
|
||||||
a string
|
a string
|
||||||
b string
|
b string
|
||||||
want bool
|
want int
|
||||||
desc bool
|
desc bool
|
||||||
}{
|
}{
|
||||||
// ascending
|
// ascending
|
||||||
{"numeric", "10", "20", true, false},
|
{"numeric", "10", "20", 0, false},
|
||||||
{"duration", "2d4h5m", "45m", false, false},
|
{"duration", "2d4h5m", "45m", 1, false},
|
||||||
{"time", "12/24/2022", "1/1/1970", false, false},
|
{"time", "12/24/2022", "1/1/1970", 1, false},
|
||||||
|
|
||||||
// descending
|
// descending
|
||||||
{"numeric", "10", "20", false, true},
|
{"numeric", "10", "20", 1, true},
|
||||||
{"duration", "2d4h5m", "45m", true, true},
|
{"duration", "2d4h5m", "45m", 0, true},
|
||||||
{"time", "12/24/2022", "1/1/1970", true, true},
|
{"time", "12/24/2022", "1/1/1970", 0, true},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, testdata := range tests {
|
for _, testdata := range tests {
|
||||||
@@ -75,7 +75,7 @@ func TestCompare(t *testing.T) {
|
|||||||
c := cfg.Config{SortMode: testdata.mode, SortDescending: testdata.desc}
|
c := cfg.Config{SortMode: testdata.mode, SortDescending: testdata.desc}
|
||||||
got := compare(&c, testdata.a, testdata.b)
|
got := compare(&c, testdata.a, testdata.b)
|
||||||
if got != testdata.want {
|
if got != testdata.want {
|
||||||
t.Errorf("got %t, want %t", got, testdata.want)
|
t.Errorf("got %d, want %d", got, testdata.want)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
46
t/test-multipatterns.txtar
Normal file
46
t/test-multipatterns.txtar
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
# 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
|
||||||
|
|
||||||
6
t/testtable3
Normal file
6
t/testtable3
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
NAME READY STATUS STARTS AGE
|
||||||
|
alertmanager-kube-prometheus-alertmanager-0 2/2 Running 35 11d
|
||||||
|
kube-prometheus-blackbox-exporter-5d85b5d8f4-tskh7 1/1 Running 17 1h44m
|
||||||
|
grafana-fcc54cbc9-bk7s8 1/1 Running 17 1d
|
||||||
|
kube-prometheus-kube-state-metrics-b4cd9487-75p7f 1/1 Running 20 45m
|
||||||
|
kube-prometheus-node-exporter-bfzpl 1/1 Running 17 54s
|
||||||
4
t/testtable4
Normal file
4
t/testtable4
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
ONE TWO
|
||||||
|
1 4
|
||||||
|
3 1
|
||||||
|
5 2
|
||||||
6
t/testtable5
Normal file
6
t/testtable5
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
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
|
||||||
75
tablizer.1
75
tablizer.1
@@ -133,7 +133,7 @@
|
|||||||
.\" ========================================================================
|
.\" ========================================================================
|
||||||
.\"
|
.\"
|
||||||
.IX Title "TABLIZER 1"
|
.IX Title "TABLIZER 1"
|
||||||
.TH TABLIZER 1 "2025-01-14" "1" "User Commands"
|
.TH TABLIZER 1 "2025-01-22" "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 ,)
|
||||||
@@ -153,7 +153,7 @@ tablizer \- Manipulate tabular output of other programs
|
|||||||
\& \-N, \-\-no\-color Disable pattern highlighting
|
\& \-N, \-\-no\-color Disable pattern highlighting
|
||||||
\& \-H, \-\-no\-headers Disable headers display
|
\& \-H, \-\-no\-headers Disable headers display
|
||||||
\& \-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|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 ,)
|
||||||
@@ -250,11 +250,20 @@ By default, if a \fBpattern\fR has been speficied, matches will be
|
|||||||
highlighted. You can disable this behavior with the \fB\-N\fR option.
|
highlighted. You can disable this behavior with the \fB\-N\fR option.
|
||||||
.PP
|
.PP
|
||||||
Use the \fB\-k\fR option to specify by which column to sort the tabular
|
Use the \fB\-k\fR option to specify by which column to sort the tabular
|
||||||
data (as in \s-1GNU\s0 \fBsort\fR\|(1)). The default sort column is the first one. To
|
data (as in \s-1GNU\s0 \fBsort\fR\|(1)). The default sort column is the first
|
||||||
disable sorting at all, supply 0 (Zero) to \-k. The default sort order
|
one. You can specify column numbers or names. Column numbers start
|
||||||
is ascending. You can change this to descending order using the option
|
with 1, names are case insensitive. You can specify multiple columns
|
||||||
\&\fB\-D\fR. The default sort order is by string, but there are other sort
|
separated by comma to sort, but the type must be the same. For example
|
||||||
modes:
|
if you want to sort numerically, all columns must be numbers. If you
|
||||||
|
use column numbers, then be aware, that these are the numbers before
|
||||||
|
column extraction. For example if you have a table with 4 columns and
|
||||||
|
specify \f(CW\*(C`\-c4\*(C'\fR, then only 1 column (the fourth) will be printed,
|
||||||
|
however if you want to sort by this column, you'll have to specify
|
||||||
|
\&\f(CW\*(C`\-k4\*(C'\fR.
|
||||||
|
.PP
|
||||||
|
The default sort order is ascending. You can change this to
|
||||||
|
descending order using the option \fB\-D\fR. The default sort order is by
|
||||||
|
alphanumeric string, but there are other sort modes:
|
||||||
.IP "\fB\-a \-\-sort\-age\fR" 4
|
.IP "\fB\-a \-\-sort\-age\fR" 4
|
||||||
.IX Item "-a --sort-age"
|
.IX Item "-a --sort-age"
|
||||||
Sorts duration strings like \*(L"1d4h32m51s\*(R".
|
Sorts duration strings like \*(L"1d4h32m51s\*(R".
|
||||||
@@ -269,38 +278,52 @@ 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 a regular expression
|
You can reduce the rows being displayed by using one or more regular
|
||||||
pattern. The regexp is \s-1PCRE\s0 compatible, refer to the syntax cheat
|
expression patterns. The regexp language being used is the one of
|
||||||
sheet here: <https://github.com/google/re2/wiki/Syntax>. If you want
|
\&\s-1GOLANG,\s0 refer to the syntax cheat sheet here:
|
||||||
to read a more comprehensive documentation about the topic and have
|
<https://pkg.go.dev/regexp/syntax>.
|
||||||
perl installed you can read it with:
|
.PP
|
||||||
|
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>.
|
Or read it online: <https://perldoc.perl.org/perlre>. But please note
|
||||||
|
that the \s-1GO\s0 regexp engine does \s-1NOT\s0 support all perl regex terms,
|
||||||
|
especially look-ahead and look-behind.
|
||||||
.PP
|
.PP
|
||||||
A note on modifiers: the regexp engine used in tablizer uses another
|
If you want to supply flags to a regex, then surround it with slashes
|
||||||
modifier syntax:
|
and append the flag. The following flags are supported:
|
||||||
.PP
|
.PP
|
||||||
.Vb 1
|
.Vb 2
|
||||||
\& (?MODIFIER)
|
\& i => case insensitive
|
||||||
|
\& ! => 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 "(?i)account"
|
\& kubectl get pods \-A | tablizer "/account/i"
|
||||||
.Ve
|
.Ve
|
||||||
.PP
|
.PP
|
||||||
You can use the experimental fuzzy search feature by providing the
|
If you use the \f(CW\*(C`!\*(C'\fR flag, then the regex match will be negated, that
|
||||||
|
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
|
||||||
|
|||||||
69
tablizer.pod
69
tablizer.pod
@@ -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 ,)
|
||||||
@@ -14,7 +14,7 @@ tablizer - Manipulate tabular output of other programs
|
|||||||
-N, --no-color Disable pattern highlighting
|
-N, --no-color Disable pattern highlighting
|
||||||
-H, --no-headers Disable headers display
|
-H, --no-headers Disable headers display
|
||||||
-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|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 ,)
|
||||||
@@ -106,11 +106,20 @@ By default, if a B<pattern> has been speficied, matches will be
|
|||||||
highlighted. You can disable this behavior with the B<-N> option.
|
highlighted. You can disable this behavior with the B<-N> option.
|
||||||
|
|
||||||
Use the B<-k> option to specify by which column to sort the tabular
|
Use the B<-k> option to specify by which column to sort the tabular
|
||||||
data (as in GNU sort(1)). The default sort column is the first one. To
|
data (as in GNU sort(1)). The default sort column is the first
|
||||||
disable sorting at all, supply 0 (Zero) to -k. The default sort order
|
one. You can specify column numbers or names. Column numbers start
|
||||||
is ascending. You can change this to descending order using the option
|
with 1, names are case insensitive. You can specify multiple columns
|
||||||
B<-D>. The default sort order is by string, but there are other sort
|
separated by comma to sort, but the type must be the same. For example
|
||||||
modes:
|
if you want to sort numerically, all columns must be numbers. If you
|
||||||
|
use column numbers, then be aware, that these are the numbers before
|
||||||
|
column extraction. For example if you have a table with 4 columns and
|
||||||
|
specify C<-c4>, then only 1 column (the fourth) will be printed,
|
||||||
|
however if you want to sort by this column, you'll have to specify
|
||||||
|
C<-k4>.
|
||||||
|
|
||||||
|
The default sort order is ascending. You can change this to
|
||||||
|
descending order using the option B<-D>. The default sort order is by
|
||||||
|
alphanumeric string, but there are other sort modes:
|
||||||
|
|
||||||
=over
|
=over
|
||||||
|
|
||||||
@@ -133,32 +142,44 @@ useful for the developer.
|
|||||||
|
|
||||||
=head2 PATTERNS AND FILTERING
|
=head2 PATTERNS AND FILTERING
|
||||||
|
|
||||||
You can reduce the rows being displayed by using a regular expression
|
You can reduce the rows being displayed by using one or more regular
|
||||||
pattern. The regexp is PCRE compatible, refer to the syntax cheat
|
expression patterns. The regexp language being used is the one of
|
||||||
sheet here: L<https://github.com/google/re2/wiki/Syntax>. If you want
|
GOLANG, refer to the syntax cheat sheet here:
|
||||||
to read a more comprehensive documentation about the topic and have
|
L<https://pkg.go.dev/regexp/syntax>.
|
||||||
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>.
|
Or read it online: L<https://perldoc.perl.org/perlre>. But please note
|
||||||
|
that the GO regexp engine does NOT support all perl regex terms,
|
||||||
|
especially look-ahead and look-behind.
|
||||||
|
|
||||||
A note on modifiers: the regexp engine used in tablizer uses another
|
If you want to supply flags to a regex, then surround it with slashes
|
||||||
modifier syntax:
|
and append the flag. The following flags are supported:
|
||||||
|
|
||||||
(?MODIFIER)
|
i => case insensitive
|
||||||
|
! => 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 "(?i)account"
|
kubectl get pods -A | tablizer "/account/i"
|
||||||
|
|
||||||
You can use the experimental fuzzy search feature by providing the
|
If you use the C<!> flag, then the regex match will be negated, that
|
||||||
|
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.
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user