Compare commits

..

1 Commits
v1.4.2 ... sort

28 changed files with 334 additions and 1026 deletions

View File

@@ -4,8 +4,10 @@ jobs:
build: build:
strategy: strategy:
matrix: matrix:
version: ['1.23'] version: ['1.22']
os: [ubuntu-latest, macos-latest, windows-latest] # windows-latest removed, see:
# https://github.com/rogpeppe/go-internal/issues/284
os: [ubuntu-latest, macos-latest]
name: Build name: Build
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
@@ -30,7 +32,7 @@ jobs:
steps: steps:
- uses: actions/setup-go@v5 - uses: actions/setup-go@v5
with: with:
go-version: 1.23 go-version: 1.22
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: golangci-lint - name: golangci-lint
uses: golangci/golangci-lint-action@v6 uses: golangci/golangci-lint-action@v6

View File

@@ -1,87 +0,0 @@
name: build-release
on:
push:
tags:
- "v*.*.*"
jobs:
release:
name: Build Release Assets
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
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
- name: Build Changelog
id: github_release
uses: mikepenz/release-changelog-builder-action@v5
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
mode: "PR"
configurationJson: |
{
"template": "#{{CHANGELOG}}\n\n**Full Changelog**: #{{RELEASE_DIFF}}",
"pr_template": "- #{{TITLE}} (##{{NUMBER}}) by #{{AUTHOR}}\n#{{BODY}}",
"empty_template": "- no changes",
"categories": [
{
"title": "## New Features",
"labels": ["add", "feature"]
},
{
"title": "## Bug Fixes",
"labels": ["fix", "bug", "revert"]
},
{
"title": "## Documentation Enhancements",
"labels": ["doc"]
},
{
"title": "## Refactoring Efforts",
"labels": ["refactor"]
},
{
"title": "## Miscellaneus Changes",
"labels": []
}
],
"ignore_labels": [
"duplicate", "good first issue", "help wanted", "invalid", "question", "wontfix"
],
"label_extractor": [
{
"pattern": "(.) (.+)",
"target": "$1"
},
{
"pattern": "(.) (.+)",
"target": "$1",
"on_property": "title"
}
]
}
- name: Create Release
uses: softprops/action-gh-release@v2
with:
body: ${{steps.github_release.outputs.changelog}}

View File

@@ -53,7 +53,8 @@ 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:
gh release create $(version) --generate-notes ./mkrel.sh $(tool) $(version)
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
@@ -64,12 +65,12 @@ install: buildlocal
clean: clean:
rm -rf $(tool) releases coverage.out rm -rf $(tool) releases coverage.out
test: clean test:
go test ./... $(OPTS) go test ./... $(OPTS)
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) $(OPTS) go test -run $(TEST) github.com/tlinden/tablizer/$(MOD)
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,8 +35,6 @@ 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.4.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,30 +52,18 @@ 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
Numbering bool NoNumbering bool
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
@@ -107,7 +95,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
@@ -277,20 +265,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])
@@ -299,8 +279,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
@@ -332,10 +311,10 @@ func (conf *Config) PrepareTransposers() error {
func (conf *Config) CheckEnv() { func (conf *Config) CheckEnv() {
// check for environment vars, command line flags have precedence, // check for environment vars, command line flags have precedence,
// NO_COLOR is being checked by the color module itself. // NO_COLOR is being checked by the color module itself.
if !conf.Numbering { if !conf.NoNumbering {
_, set := os.LookupEnv("T_HEADER_NUMBERING") _, set := os.LookupEnv("T_NO_HEADER_NUMBERING")
if set { if set {
conf.Numbering = true conf.NoNumbering = true
} }
} }
@@ -350,41 +329,19 @@ func (conf *Config) CheckEnv() {
func (conf *Config) ApplyDefaults() { func (conf *Config) ApplyDefaults() {
// mode specific defaults // mode specific defaults
if conf.OutputMode == Yaml || conf.OutputMode == CSV { if conf.OutputMode == Yaml || conf.OutputMode == CSV {
conf.Numbering = false conf.NoNumbering = true
} }
} }
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
@@ -125,7 +125,7 @@ func Execute() {
// options // options
rootCmd.PersistentFlags().BoolVarP(&conf.Debug, "debug", "d", false, rootCmd.PersistentFlags().BoolVarP(&conf.Debug, "debug", "d", false,
"Enable debugging") "Enable debugging")
rootCmd.PersistentFlags().BoolVarP(&conf.Numbering, "numbering", "n", false, rootCmd.PersistentFlags().BoolVarP(&conf.NoNumbering, "no-numbering", "n", false,
"Disable header numbering") "Disable header numbering")
rootCmd.PersistentFlags().BoolVarP(&conf.NoHeaders, "no-headers", "H", false, rootCmd.PersistentFlags().BoolVarP(&conf.NoHeaders, "no-headers", "H", false,
"Disable header display") "Disable header display")
@@ -147,8 +147,6 @@ 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 ,)")
@@ -192,7 +190,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,18 +6,18 @@ 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 ,)
-v, --invert-match select non-matching rows -v, --invert-match select non-matching rows
-n, --numbering Enable header numbering -n, --no-numbering Disable header numbering
-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|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,8 +30,6 @@ 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
@@ -132,43 +130,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.
@@ -183,10 +168,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
@@ -286,24 +267,12 @@ 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
over environment variables. over environment variables.
<T_HEADER_NUMBERING> - enable numbering of header fields, like -n. <T_NO_HEADER_NUMBERING> - disable numbering of header fields, like -n.
<T_COLUMNS> - comma separated list of columns to output, like -c <T_COLUMNS> - comma separated list of columns to output, like -c
<NO_COLORS> - disable colorization of matches, like -N <NO_COLORS> - disable colorization of matches, like -N
@@ -423,18 +392,18 @@ 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 ,)
-v, --invert-match select non-matching rows -v, --invert-match select non-matching rows
-n, --numbering Enable header numbering -n, --no-numbering Disable header numbering
-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|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
@@ -447,8 +416,6 @@ 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

29
go.mod
View File

@@ -1,8 +1,6 @@
module github.com/tlinden/tablizer module github.com/tlinden/tablizer
go 1.23.0 go 1.22
toolchain go1.23.5
require ( require (
github.com/alecthomas/repr v0.4.0 github.com/alecthomas/repr v0.4.0
@@ -10,10 +8,9 @@ require (
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
github.com/olekukonko/tablewriter v1.0.6 github.com/olekukonko/tablewriter v0.0.5
github.com/rogpeppe/go-internal v1.14.1 github.com/rogpeppe/go-internal v1.13.1
github.com/spf13/cobra v1.9.1 github.com/spf13/cobra v1.8.1
github.com/tiagomelo/go-clipboard v0.1.2
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
) )
@@ -21,23 +18,17 @@ require (
github.com/agext/levenshtein v1.2.3 // indirect github.com/agext/levenshtein v1.2.3 // indirect
github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
github.com/fatih/color v1.18.0 // indirect
github.com/google/go-cmp v0.6.0 // indirect github.com/google/go-cmp v0.6.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // 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/olekukonko/errors v1.1.0 // indirect github.com/rivo/uniseg v0.2.0 // indirect
github.com/olekukonko/ll v0.0.8 // indirect github.com/spf13/pflag v1.0.5 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/spf13/pflag v1.0.6 // 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.21.0 // indirect golang.org/x/mod v0.18.0 // indirect
golang.org/x/sync v0.8.0 // indirect golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.33.0 // indirect golang.org/x/sys v0.21.0 // indirect
golang.org/x/text v0.11.0 // indirect golang.org/x/text v0.11.0 // indirect
golang.org/x/tools v0.26.0 // indirect golang.org/x/tools v0.22.0 // indirect
) )

67
go.sum
View File

@@ -8,14 +8,10 @@ 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/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= 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=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68=
github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
@@ -28,60 +24,31 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/lithammer/fuzzysearch v1.1.8 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8LFgLN4= github.com/lithammer/fuzzysearch v1.1.8 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8LFgLN4=
github.com/lithammer/fuzzysearch v1.1.8/go.mod h1:IdqeyBClc3FFqSzYq/MXESsS4S0FsZ5ajtkr5xPLts4= github.com/lithammer/fuzzysearch v1.1.8/go.mod h1:IdqeyBClc3FFqSzYq/MXESsS4S0FsZ5ajtkr5xPLts4=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 h1:DpOJ2HYzCv8LZP15IdmG+YdwD2luVPHITV96TkirNBM= github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 h1:DpOJ2HYzCv8LZP15IdmG+YdwD2luVPHITV96TkirNBM=
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/errors v0.0.0-20250405072817-4e6d85265da6 h1:r3FaAI0NZK3hSmtTDrBVREhKULp8oUeqLT5Eyl2mSPo=
github.com/olekukonko/errors v0.0.0-20250405072817-4e6d85265da6/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y=
github.com/olekukonko/errors v1.1.0 h1:RNuGIh15QdDenh+hNvKrJkmxxjV4hcS50Db478Ou5sM=
github.com/olekukonko/errors v1.1.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y=
github.com/olekukonko/ll v0.0.6-0.20250511102614-9564773e9d27 h1:LgDwLQDELPB6wMOx1x4DSXnH2pjQNDKFgqv2inJuiAU=
github.com/olekukonko/ll v0.0.6-0.20250511102614-9564773e9d27/go.mod h1:En+sEW0JNETl26+K8eZ6/W4UQ7CYSrrgg/EdIYT2H8g=
github.com/olekukonko/ll v0.0.8 h1:sbGZ1Fx4QxJXEqL/6IG8GEFnYojUSQ45dJVwN2FH2fc=
github.com/olekukonko/ll v0.0.8/go.mod h1:En+sEW0JNETl26+K8eZ6/W4UQ7CYSrrgg/EdIYT2H8g=
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/olekukonko/tablewriter v1.0.2 h1:nxz/j28kPYQUuc4veIv3Ymmef7gHKn8rhr42aauENnk=
github.com/olekukonko/tablewriter v1.0.2/go.mod h1:eUa4ArVhHJYomS27xrJB/GyLtnzKKVkZeLM6/MNO+pA=
github.com/olekukonko/tablewriter v1.0.4 h1:Lnz32TW+q/MQhA4qwhIyLA+j5hZ3dcNpZrcpPC+4iaM=
github.com/olekukonko/tablewriter v1.0.4/go.mod h1:eUa4ArVhHJYomS27xrJB/GyLtnzKKVkZeLM6/MNO+pA=
github.com/olekukonko/tablewriter v1.0.6 h1:/T45mIHc5hcEvibgzBzvMy7ruT+RjgoQRvkHbnl6OWA=
github.com/olekukonko/tablewriter v1.0.6/go.mod h1:SJ0MV1aHb/89fLcsBMXMp30Xg3g5eGoOUu0RptEk4AU=
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=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/scylladb/termtables v0.0.0-20191203121021-c4c0b6d42ff4/go.mod h1:C1a7PQSMz9NShzorzCiG2fk9+xuCgLkPeCvMHYR2OWg= github.com/scylladb/termtables v0.0.0-20191203121021-c4c0b6d42ff4/go.mod h1:C1a7PQSMz9NShzorzCiG2fk9+xuCgLkPeCvMHYR2OWg=
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
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.2 h1:Ph2icR0vZRIj3v5ExvsGweBwsbbDUTlS6HoF40MkQD8=
github.com/tiagomelo/go-clipboard v0.1.2/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=
@@ -95,8 +62,8 @@ golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZ
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
@@ -104,20 +71,16 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
@@ -132,8 +95,8 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View File

@@ -34,39 +34,3 @@ func (data *Tabdata) CloneEmpty() Tabdata {
return newdata return newdata
} }
// add a TAB (\t) in front of every cell, but not the first
func (data *Tabdata) TabEntries() [][]string {
newentries := make([][]string, len(data.entries))
for rowidx, row := range data.entries {
newentries[rowidx] = make([]string, len(row))
for colidx, cell := range row {
switch colidx {
case 0:
newentries[rowidx][colidx] = cell
default:
newentries[rowidx][colidx] = "\t" + cell
}
}
}
return newentries
}
// add a TAB (\t) in front of every header, but not the first
func (data *Tabdata) TabHeaders() []string {
newheaders := make([]string, len(data.headers))
for colidx, cell := range data.headers {
switch colidx {
case 0:
newheaders[colidx] = cell
default:
newheaders[colidx] = "\t" + cell
}
}
return newheaders
}

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,14 +77,6 @@ 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
} }
@@ -208,13 +200,13 @@ func numberizeAndReduceHeaders(conf cfg.Config, data *Tabdata) {
} }
} }
if conf.Numbering { if conf.NoNumbering {
numberedHeaders = append(numberedHeaders, head)
headlen = len(head)
} else {
numhead := fmt.Sprintf("%s(%d)", head, idx+1) numhead := fmt.Sprintf("%s(%d)", head, idx+1)
headlen = len(numhead) headlen = len(numhead)
numberedHeaders = append(numberedHeaders, numhead) numberedHeaders = append(numberedHeaders, numhead)
} else {
numberedHeaders = append(numberedHeaders, head)
headlen = len(head)
} }
if headlen > maxwidth { if headlen > maxwidth {
@@ -254,6 +246,17 @@ func reduceColumns(conf cfg.Config, data *Tabdata) {
} }
} }
// FIXME: remove this when we only use Tablewriter and strip in ParseFile()!
func trimRow(row []string) []string {
var fixedrow = make([]string, len(row))
for idx, cell := range row {
fixedrow[idx] = strings.TrimSpace(cell)
}
return fixedrow
}
// FIXME: refactor this beast! // FIXME: refactor this beast!
func colorizeData(conf cfg.Config, output string) string { func colorizeData(conf cfg.Config, output string) string {
switch { switch {
@@ -290,20 +293,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

@@ -216,21 +216,21 @@ func TestNumberizeHeaders(t *testing.T) {
} }
var tests = []struct { var tests = []struct {
expect []string expect []string
columns []int columns []int
numberize bool nonum bool
}{ }{
{[]string{"ONE(1)", "TWO(2)", "THREE(3)"}, []int{1, 2, 3}, true}, {[]string{"ONE(1)", "TWO(2)", "THREE(3)"}, []int{1, 2, 3}, false},
{[]string{"ONE(1)", "TWO(2)"}, []int{1, 2}, true}, {[]string{"ONE(1)", "TWO(2)"}, []int{1, 2}, false},
{[]string{"ONE", "TWO"}, []int{1, 2}, false}, {[]string{"ONE", "TWO"}, []int{1, 2}, true},
} }
for _, testdata := range tests { for _, testdata := range tests {
testname := fmt.Sprintf("numberize-headers-columns-%+v-nonum-%t", testname := fmt.Sprintf("numberize-headers-columns-%+v-nonum-%t",
testdata.columns, testdata.numberize) testdata.columns, testdata.nonum)
t.Run(testname, func(t *testing.T) { t.Run(testname, func(t *testing.T) {
conf := cfg.Config{Columns: "x", UseColumns: testdata.columns, Numbering: testdata.numberize} conf := cfg.Config{Columns: "x", UseColumns: testdata.columns, NoNumbering: testdata.nonum}
usedata := data usedata := data
numberizeAndReduceHeaders(conf, &usedata) numberizeAndReduceHeaders(conf, &usedata)
if !reflect.DeepEqual(usedata.headers, testdata.expect) { if !reflect.DeepEqual(usedata.headers, testdata.expect) {

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-2025 Thomas von Dein Copyright © 2022 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
@@ -22,13 +22,12 @@ import (
"fmt" "fmt"
"io" "io"
"log" "log"
"regexp"
"strconv" "strconv"
"strings" "strings"
"github.com/gookit/color" "github.com/gookit/color"
"github.com/olekukonko/tablewriter" "github.com/olekukonko/tablewriter"
"github.com/olekukonko/tablewriter/renderer"
"github.com/olekukonko/tablewriter/tw"
"github.com/tlinden/tablizer/cfg" "github.com/tlinden/tablizer/cfg"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
@@ -39,9 +38,6 @@ 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)
@@ -77,58 +73,36 @@ Emacs org-mode compatible table (also orgtbl-mode)
*/ */
func printOrgmodeData(writer io.Writer, conf cfg.Config, data *Tabdata) { func printOrgmodeData(writer io.Writer, conf cfg.Config, data *Tabdata) {
tableString := &strings.Builder{} tableString := &strings.Builder{}
table := tablewriter.NewWriter(tableString)
table := tablewriter.NewTable(tableString,
tablewriter.WithRenderer(
renderer.NewBlueprint(
tw.Rendition{
Borders: tw.Border{
Left: tw.On,
Right: tw.On,
Top: tw.On,
Bottom: tw.On,
},
Settings: tw.Settings{
Separators: tw.Separators{
ShowHeader: tw.On,
ShowFooter: tw.Off,
BetweenRows: tw.Off,
BetweenColumns: 0,
},
},
Symbols: tw.NewSymbols(tw.StyleASCII),
})),
tablewriter.WithConfig(
tablewriter.Config{
Header: tw.CellConfig{
Formatting: tw.CellFormatting{
Alignment: tw.AlignLeft,
AutoFormat: tw.Off,
},
},
Row: tw.CellConfig{
Formatting: tw.CellFormatting{
Alignment: tw.AlignLeft,
},
},
},
),
)
if !conf.NoHeaders { if !conf.NoHeaders {
table.Header(data.headers) table.SetHeader(data.headers)
} }
if err := table.Bulk(data.entries); err != nil { for _, row := range data.entries {
log.Fatalf("Failed to add data to table renderer: %s", err) table.Append(trimRow(row))
} }
if err := table.Render(); err != nil { table.Render()
log.Fatalf("Failed to render table: %s", err)
}
output(writer, color.Sprint(colorizeData(conf, tableString.String()))) /* fix output for org-mode (orgtbl)
tableWriter output:
+------+------+
| cell | cell |
+------+------+
Needed for org-mode compatibility:
|------+------|
| cell | cell |
|------+------|
*/
leftR := regexp.MustCompile(`(?m)^\\+`)
rightR := regexp.MustCompile(`\\+(?m)$`)
output(writer, color.Sprint(
colorizeData(conf,
rightR.ReplaceAllString(
leftR.ReplaceAllString(tableString.String(), "|"), "|"))))
} }
/* /*
@@ -136,57 +110,20 @@ Markdown table
*/ */
func printMarkdownData(writer io.Writer, conf cfg.Config, data *Tabdata) { func printMarkdownData(writer io.Writer, conf cfg.Config, data *Tabdata) {
tableString := &strings.Builder{} tableString := &strings.Builder{}
table := tablewriter.NewWriter(tableString)
table := tablewriter.NewTable(tableString,
tablewriter.WithRenderer(
renderer.NewBlueprint(
tw.Rendition{
Borders: tw.Border{
Left: tw.On,
Right: tw.On,
Top: tw.Off,
Bottom: tw.Off,
},
Settings: tw.Settings{
Separators: tw.Separators{
ShowHeader: tw.On,
ShowFooter: tw.Off,
BetweenRows: tw.Off,
BetweenColumns: 0,
},
},
Symbols: tw.NewSymbols(tw.StyleMarkdown),
})),
tablewriter.WithConfig(
tablewriter.Config{
Header: tw.CellConfig{
Formatting: tw.CellFormatting{
Alignment: tw.AlignLeft,
AutoFormat: tw.Off,
},
},
Row: tw.CellConfig{
Formatting: tw.CellFormatting{
Alignment: tw.AlignLeft,
},
},
},
),
)
if !conf.NoHeaders { if !conf.NoHeaders {
table.Header(data.headers) table.SetHeader(data.headers)
} }
if err := table.Bulk(data.entries); err != nil { for _, row := range data.entries {
log.Fatalf("Failed to add data to table renderer: %s", err) table.Append(trimRow(row))
} }
if err := table.Render(); err != nil { table.SetBorders(tablewriter.Border{Left: true, Top: false, Right: true, Bottom: false})
log.Fatalf("Failed to render table: %s", err) table.SetCenterSeparator("|")
}
table.Render()
output(writer, color.Sprint(colorizeData(conf, tableString.String()))) output(writer, color.Sprint(colorizeData(conf, tableString.String())))
} }
@@ -195,53 +132,33 @@ Simple ASCII table without any borders etc, just like the input we expect
*/ */
func printASCIIData(writer io.Writer, conf cfg.Config, data *Tabdata) { func printASCIIData(writer io.Writer, conf cfg.Config, data *Tabdata) {
tableString := &strings.Builder{} tableString := &strings.Builder{}
table := tablewriter.NewWriter(tableString)
table := tablewriter.NewTable(tableString,
tablewriter.WithRenderer(
renderer.NewBlueprint(tw.Rendition{
Borders: tw.BorderNone,
Symbols: tw.NewSymbols(tw.StyleASCII),
Settings: tw.Settings{
Separators: tw.Separators{BetweenRows: tw.Off, BetweenColumns: tw.Off},
Lines: tw.Lines{ShowFooterLine: tw.Off, ShowHeaderLine: tw.Off},
},
})),
tablewriter.WithConfig(tablewriter.Config{
Header: tw.CellConfig{
Formatting: tw.CellFormatting{
AutoFormat: tw.Off,
},
Padding: tw.CellPadding{
Global: tw.Padding{Left: "", Right: ""},
},
},
Row: tw.CellConfig{
Formatting: tw.CellFormatting{
AutoWrap: tw.WrapNone,
Alignment: tw.AlignLeft,
},
Padding: tw.CellPadding{
Global: tw.Padding{Left: "", Right: ""},
},
},
Debug: true,
}),
tablewriter.WithPadding(tw.PaddingNone),
)
if !conf.NoHeaders { if !conf.NoHeaders {
table.Header(data.TabHeaders()) table.SetHeader(data.headers)
} }
if err := table.Bulk(data.TabEntries()); err != nil { table.AppendBulk(data.entries)
log.Fatalf("Failed to add data to table renderer: %s", err)
} table.SetAutoWrapText(false)
table.SetAutoFormatHeaders(true)
if err := table.Render(); err != nil { table.SetHeaderAlignment(tablewriter.ALIGN_LEFT)
log.Fatalf("Failed to render table: %s", err) table.SetAlignment(tablewriter.ALIGN_LEFT)
table.SetCenterSeparator("")
table.SetColumnSeparator("")
table.SetRowSeparator("")
table.SetHeaderLine(false)
table.SetBorder(false)
table.SetNoWhiteSpace(true)
if !conf.UseHighlight {
// the tabs destroy the highlighting
table.SetTablePadding("\t") // pad with tabs
} else {
table.SetTablePadding(" ")
} }
table.Render()
output(writer, color.Sprint(colorizeData(conf, tableString.String()))) output(writer, color.Sprint(colorizeData(conf, tableString.String())))
} }

View File

@@ -65,7 +65,7 @@ var tests = []struct {
sortby string // empty == default sortby string // empty == default
column int // sort by this column (numbers start by 1) 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
numberize bool // add header numbering nonum bool // hide numbering
mode int // shell, orgtbl, etc. empty == default: ascii mode int // shell, orgtbl, etc. empty == default: ascii
usecol []int // columns to display, empty == display all usecol []int // columns to display, empty == display all
usecolstr string // for testname, must match usecol usecolstr string // for testname, must match usecol
@@ -73,9 +73,8 @@ var tests = []struct {
}{ }{
// --------------------- Default settings mode tests `` // --------------------- Default settings mode tests ``
{ {
mode: cfg.ASCII, mode: cfg.ASCII,
numberize: true, name: "default",
name: "default",
expect: ` expect: `
NAME(1) DURATION(2) COUNT(3) WHEN(4) NAME(1) DURATION(2) COUNT(3) WHEN(4)
beta 1d10h5m1s 33 3/1/2014 beta 1d10h5m1s 33 3/1/2014
@@ -83,9 +82,8 @@ alpha 4h35m 170 2013-Feb-03
ceta 33d12h 9 06/Jan/2008 15:04:05 -0700`, ceta 33d12h 9 06/Jan/2008 15:04:05 -0700`,
}, },
{ {
mode: cfg.CSV, mode: cfg.CSV,
numberize: false, name: "csv",
name: "csv",
expect: ` expect: `
NAME,DURATION,COUNT,WHEN NAME,DURATION,COUNT,WHEN
beta,1d10h5m1s,33,3/1/2014 beta,1d10h5m1s,33,3/1/2014
@@ -93,42 +91,40 @@ alpha,4h35m,170,2013-Feb-03
ceta,33d12h,9,06/Jan/2008 15:04:05 -0700`, ceta,33d12h,9,06/Jan/2008 15:04:05 -0700`,
}, },
{ {
name: "orgtbl", name: "default",
numberize: true, mode: cfg.Orgtbl,
mode: cfg.Orgtbl,
expect: ` expect: `
+---------+-------------+----------+----------------------------+ +---------+-------------+----------+----------------------------+
| NAME(1) | DURATION(2) | COUNT(3) | WHEN(4) | | NAME(1) | DURATION(2) | COUNT(3) | WHEN(4) |
+---------+-------------+----------+----------------------------+ +---------+-------------+----------+----------------------------+
| beta | 1d10h5m1s | 33 | 3/1/2014 | | beta | 1d10h5m1s | 33 | 3/1/2014 |
| alpha | 4h35m | 170 | 2013-Feb-03 | | alpha | 4h35m | 170 | 2013-Feb-03 |
| ceta | 33d12h | 9 | 06/Jan/2008 15:04:05 -0700 | | ceta | 33d12h | 9 | 06/Jan/2008 15:04:05 -0700 |
+---------+-------------+----------+----------------------------+`, +---------+-------------+----------+----------------------------+`,
}, },
{ {
name: "markdown", name: "default",
mode: cfg.Markdown, mode: cfg.Markdown,
numberize: true,
expect: ` expect: `
| NAME(1) | DURATION(2) | COUNT(3) | WHEN(4) | | NAME(1) | DURATION(2) | COUNT(3) | WHEN(4) |
|---------|-------------|----------|----------------------------| |---------|-------------|----------|----------------------------|
| beta | 1d10h5m1s | 33 | 3/1/2014 | | beta | 1d10h5m1s | 33 | 3/1/2014 |
| alpha | 4h35m | 170 | 2013-Feb-03 | | alpha | 4h35m | 170 | 2013-Feb-03 |
| ceta | 33d12h | 9 | 06/Jan/2008 15:04:05 -0700 |`, | ceta | 33d12h | 9 | 06/Jan/2008 15:04:05 -0700 |`,
}, },
{ {
name: "shell", name: "default",
mode: cfg.Shell, mode: cfg.Shell,
numberize: false, nonum: true,
expect: ` expect: `
NAME="beta" DURATION="1d10h5m1s" COUNT="33" WHEN="3/1/2014" NAME="beta" DURATION="1d10h5m1s" COUNT="33" WHEN="3/1/2014"
NAME="alpha" DURATION="4h35m" COUNT="170" WHEN="2013-Feb-03" NAME="alpha" DURATION="4h35m" COUNT="170" WHEN="2013-Feb-03"
NAME="ceta" DURATION="33d12h" COUNT="9" WHEN="06/Jan/2008 15:04:05 -0700"`, NAME="ceta" DURATION="33d12h" COUNT="9" WHEN="06/Jan/2008 15:04:05 -0700"`,
}, },
{ {
name: "yaml", name: "default",
mode: cfg.Yaml, mode: cfg.Yaml,
numberize: false, nonum: true,
expect: ` expect: `
entries: entries:
- count: 33 - count: 33
@@ -145,9 +141,8 @@ entries:
when: "06/Jan/2008 15:04:05 -0700"`, when: "06/Jan/2008 15:04:05 -0700"`,
}, },
{ {
name: "extended", name: "default",
mode: cfg.Extended, mode: cfg.Extended,
numberize: true,
expect: ` expect: `
NAME(1): beta NAME(1): beta
DURATION(2): 1d10h5m1s DURATION(2): 1d10h5m1s
@@ -167,11 +162,10 @@ DURATION(2): 33d12h
//------------------------ SORT TESTS //------------------------ SORT TESTS
{ {
name: "sortbycolumn3", name: "sortbycolumn3",
column: 3, column: 3,
sortby: "numeric", sortby: "numeric",
numberize: true, desc: false,
desc: false,
expect: ` expect: `
NAME(1) DURATION(2) COUNT(3) WHEN(4) NAME(1) DURATION(2) COUNT(3) WHEN(4)
ceta 33d12h 9 06/Jan/2008 15:04:05 -0700 ceta 33d12h 9 06/Jan/2008 15:04:05 -0700
@@ -179,11 +173,10 @@ beta 1d10h5m1s 33 3/1/2014
alpha 4h35m 170 2013-Feb-03`, alpha 4h35m 170 2013-Feb-03`,
}, },
{ {
name: "sortbycolumn4", name: "sortbycolumn4",
column: 4, column: 4,
sortby: "time", sortby: "time",
desc: false, desc: false,
numberize: true,
expect: ` expect: `
NAME(1) DURATION(2) COUNT(3) WHEN(4) NAME(1) DURATION(2) COUNT(3) WHEN(4)
ceta 33d12h 9 06/Jan/2008 15:04:05 -0700 ceta 33d12h 9 06/Jan/2008 15:04:05 -0700
@@ -191,11 +184,10 @@ alpha 4h35m 170 2013-Feb-03
beta 1d10h5m1s 33 3/1/2014`, beta 1d10h5m1s 33 3/1/2014`,
}, },
{ {
name: "sortbycolumn2", name: "sortbycolumn2",
column: 2, column: 2,
sortby: "duration", sortby: "duration",
numberize: true, desc: false,
desc: false,
expect: ` expect: `
NAME(1) DURATION(2) COUNT(3) WHEN(4) NAME(1) DURATION(2) COUNT(3) WHEN(4)
alpha 4h35m 170 2013-Feb-03 alpha 4h35m 170 2013-Feb-03
@@ -207,7 +199,6 @@ ceta 33d12h 9 06/Jan/2008 15:04:05 -0700`,
{ {
name: "usecolumns", name: "usecolumns",
usecol: []int{1, 4}, usecol: []int{1, 4},
numberize: true,
usecolstr: "1,4", usecolstr: "1,4",
expect: ` expect: `
NAME(1) WHEN(4) NAME(1) WHEN(4)
@@ -218,7 +209,6 @@ ceta 06/Jan/2008 15:04:05 -0700`,
{ {
name: "usecolumns", name: "usecolumns",
usecol: []int{2}, usecol: []int{2},
numberize: true,
usecolstr: "2", usecolstr: "2",
expect: ` expect: `
DURATION(2) DURATION(2)
@@ -229,7 +219,6 @@ DURATION(2)
{ {
name: "usecolumns", name: "usecolumns",
usecol: []int{3}, usecol: []int{3},
numberize: true,
usecolstr: "3", usecolstr: "3",
expect: ` expect: `
COUNT(3) COUNT(3)
@@ -241,7 +230,6 @@ COUNT(3)
name: "usecolumns", name: "usecolumns",
column: 0, column: 0,
usecol: []int{1, 3}, usecol: []int{1, 3},
numberize: true,
usecolstr: "1,3", usecolstr: "1,3",
expect: ` expect: `
NAME(1) COUNT(3) NAME(1) COUNT(3)
@@ -252,7 +240,6 @@ ceta 9`,
{ {
name: "usecolumns", name: "usecolumns",
usecol: []int{2, 4}, usecol: []int{2, 4},
numberize: true,
usecolstr: "2,4", usecolstr: "2,4",
expect: ` expect: `
DURATION(2) WHEN(4) DURATION(2) WHEN(4)
@@ -264,10 +251,8 @@ 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-%s-%d-desc-%t-sortby-%s-mode-%d-usecolumns-%s-numberize-%t", testname := fmt.Sprintf("print-%s-%d-desc-%t-sortby-%s-mode-%d-usecolumns-%s",
testdata.name, testdata.column, testdata.desc, testdata.sortby, testdata.name, testdata.column, testdata.desc, testdata.sortby, testdata.mode, testdata.usecolstr)
testdata.mode, testdata.usecolstr, testdata.numberize)
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
@@ -277,7 +262,7 @@ func TestPrinter(t *testing.T) {
SortDescending: testdata.desc, SortDescending: testdata.desc,
SortMode: testdata.sortby, SortMode: testdata.sortby,
OutputMode: testdata.mode, OutputMode: testdata.mode,
Numbering: testdata.numberize, NoNumbering: testdata.nonum,
UseColumns: testdata.usecol, UseColumns: testdata.usecol,
NoColor: true, NoColor: true,
} }

View File

@@ -1,51 +0,0 @@
/*
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)
}
}
}

View File

@@ -1,72 +0,0 @@
/*
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,15 +1,16 @@
package main package main
import ( import (
"os"
"testing" "testing"
"github.com/rogpeppe/go-internal/testscript" "github.com/rogpeppe/go-internal/testscript"
) )
func TestMain(m *testing.M) { func TestMain(m *testing.M) {
testscript.Main(m, map[string]func(){ os.Exit(testscript.RunMain(m, map[string]func() int{
"tablizer": main, "tablizer": Main,
}) }))
} }
func TestTablizer(t *testing.T) { func TestTablizer(t *testing.T) {

View File

@@ -42,15 +42,8 @@ for D in $DIST; do
binfile="releases/${tool}-${os}-${arch}-${version}" binfile="releases/${tool}-${os}-${arch}-${version}"
tardir="${tool}-${os}-${arch}-${version}" tardir="${tool}-${os}-${arch}-${version}"
tarfile="releases/${tool}-${os}-${arch}-${version}.tar.gz" tarfile="releases/${tool}-${os}-${arch}-${version}.tar.gz"
pie=""
if test "$D" = "linux/amd64"; then
pie="-buildmode=pie"
fi
set -x set -x
GOOS=${os} GOARCH=${arch} go build -tags osusergo,netgo -ldflags "-extldflags=-static -w -X 'github.com/tlinden/tablizer/cfg.VERSION=${version}'" --trimpath $pie -o ${binfile} GOOS=${os} GOARCH=${arch} go build -o ${binfile} -ldflags "-X 'github.com/tlinden/tablizer/cfg.VERSION=${version}'"
strip --strip-all ${binfile}
mkdir -p ${tardir} mkdir -p ${tardir}
cp ${binfile} README.md LICENSE ${tardir}/ cp ${binfile} README.md LICENSE ${tardir}/
echo 'tool = tablizer echo 'tool = tablizer

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-03-06" "1" "User Commands" .TH TABLIZER 1 "2025-01-15" "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,18 +144,18 @@ 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 ,)
\& \-v, \-\-invert\-match select non\-matching rows \& \-v, \-\-invert\-match select non\-matching rows
\& \-n, \-\-numbering Enable header numbering \& \-n, \-\-no\-numbering Disable header numbering
\& \-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|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,8 +168,6 @@ 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
@@ -280,52 +278,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
@@ -342,12 +326,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"
@@ -464,27 +442,13 @@ 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
to influence program behavior. Commandline flags have always to influence program behavior. Commandline flags have always
precedence over environment variables. precedence over environment variables.
.IP "<T_HEADER_NUMBERING> \- enable numbering of header fields, like \fB\-n\fR." 4 .IP "<T_NO_HEADER_NUMBERING> \- disable numbering of header fields, like \fB\-n\fR." 4
.IX Item "<T_HEADER_NUMBERING> - enable numbering of header fields, like -n." .IX Item "<T_NO_HEADER_NUMBERING> - disable numbering of header fields, like -n."
.PD 0 .PD 0
.IP "<T_COLUMNS> \- comma separated list of columns to output, like \fB\-c\fR" 4 .IP "<T_COLUMNS> \- comma separated list of columns to output, like \fB\-c\fR" 4
.IX Item "<T_COLUMNS> - comma separated list of columns to output, like -c" .IX Item "<T_COLUMNS> - comma separated list of columns to output, like -c"

View File

@@ -5,18 +5,18 @@ 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 ,)
-v, --invert-match select non-matching rows -v, --invert-match select non-matching rows
-n, --numbering Enable header numbering -n, --no-numbering Disable header numbering
-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|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,8 +29,6 @@ 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
@@ -144,44 +142,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.
@@ -196,10 +182,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.
@@ -308,19 +290,6 @@ 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
@@ -329,7 +298,7 @@ precedence over environment variables.
=over =over
=item <T_HEADER_NUMBERING> - enable numbering of header fields, like B<-n>. =item <T_NO_HEADER_NUMBERING> - disable numbering of header fields, like B<-n>.
=item <T_COLUMNS> - comma separated list of columns to output, like B<-c> =item <T_COLUMNS> - comma separated list of columns to output, like B<-c>