Compare commits

..

17 Commits

Author SHA1 Message Date
253ef8262e fix builder go version 2025-10-08 10:36:09 +02:00
da48994744 fix comment 2025-10-06 23:27:48 +02:00
39f06fddc8 md fix 2025-10-06 23:02:28 +02:00
T.v.Dein
50a9378d92 use column order of -c when specified (#81) 2025-10-06 22:55:04 +02:00
T.v.Dein
35b726fee4 Fix json parser (#80)
* fix #77: parse floats and nils as well and convert them to string
2025-10-06 22:54:31 +02:00
T.v.Dein
8c87da34f2 show short help with -h (#76) 2025-10-02 21:34:38 +02:00
dependabot[bot]
6f0f5afb27 Bump actions/setup-go from 5 to 6 (#68) 2025-10-01 21:16:48 +02:00
T.v.Dein
62b606e7da use 1.24 for CI (#75) 2025-10-01 21:14:18 +02:00
dependabot[bot]
567d23b175 Bump github.com/alecthomas/repr from 0.5.1 to 0.5.2 (#69) 2025-10-01 21:08:36 +02:00
dependabot[bot]
14f24533f0 Bump github.com/spf13/cobra from 1.9.1 to 1.10.1 (#70) 2025-10-01 21:04:25 +02:00
dependabot[bot]
4e413c02b5 Bump github.com/charmbracelet/bubbletea from 1.3.6 to 1.3.10 (#71) 2025-10-01 21:01:04 +02:00
dependabot[bot]
6d8c0c0936 Bump github.com/evertras/bubble-table from 0.19.0 to 0.19.2 (#72) 2025-10-01 20:57:58 +02:00
dependabot[bot]
21b607af7c Bump github.com/olekukonko/tablewriter from 1.0.9 to 1.1.0 (#73) 2025-10-01 20:54:31 +02:00
T.v.Dein
06a5d74fb6 Add JSON input support (#74)
* added basic json input support
* add coverage to make test
* enhanced unit tests, switch to testify/assert
* reduce ci runs
2025-10-01 20:48:49 +02:00
T.v.Dein
5f3f7c417c Improve ascii table, add --ofs flag, enhance documentation (#67)
* enhanced documentation
* added --ofs parameter
  use 2 spaces for ascii output but it is customizable with --ofs,
  which is also being used by CSV mode (whose defaults remains unchanged)
* improve ascii table output, use 2 spaces as OFS by default
2025-09-30 11:21:49 +02:00
T.v.Dein
687f4b7bb2 Fix excess spaces on normal ascii table output (#66) 2025-09-29 20:46:33 +02:00
24b66b8a6b mv demo to top 2025-09-11 19:12:49 +02:00
23 changed files with 780 additions and 436 deletions

View File

@@ -1,16 +1,16 @@
name: build-and-test-tablizer name: build-and-test-tablizer
on: [push, pull_request] on: [push]
jobs: jobs:
build: build:
strategy: strategy:
matrix: matrix:
version: ['1.23'] version: ['1.24']
os: [ubuntu-latest, macos-latest, windows-latest] os: [ubuntu-latest, macos-latest, windows-latest]
name: Build name: Build
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- name: Set up Go ${{ matrix.version }} - name: Set up Go ${{ matrix.version }}
uses: actions/setup-go@v5 uses: actions/setup-go@v6
with: with:
go-version: '${{ matrix.version }}' go-version: '${{ matrix.version }}'
id: go id: go
@@ -28,9 +28,9 @@ jobs:
name: lint name: lint
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/setup-go@v5 - uses: actions/setup-go@v6
with: with:
go-version: 1.23 go-version: 1.24
- uses: actions/checkout@v5 - uses: actions/checkout@v5
- name: golangci-lint - name: golangci-lint
uses: golangci/golangci-lint-action@v6 uses: golangci/golangci-lint-action@v6

View File

@@ -13,9 +13,9 @@ jobs:
uses: actions/checkout@v5 uses: actions/checkout@v5
- name: Set up Go - name: Set up Go
uses: actions/setup-go@v5 uses: actions/setup-go@v6
with: with:
go-version: 1.22.11 go-version: 1.24.0
- name: Build the executables - name: Build the executables
run: ./mkrel.sh tablizer ${{ github.ref_name}} run: ./mkrel.sh tablizer ${{ github.ref_name}}

View File

@@ -65,7 +65,7 @@ clean:
rm -rf $(tool) releases coverage.out rm -rf $(tool) releases coverage.out
test: clean test: clean
go test ./... $(OPTS) go test -count=1 -cover ./... $(OPTS)
singletest: singletest:
@echo "Call like this: 'make singletest TEST=TestPrepareColumns MOD=lib'" @echo "Call like this: 'make singletest TEST=TestPrepareColumns MOD=lib'"

View File

@@ -11,7 +11,13 @@ ignore certain column[s] by regex, name or number. It can output the
tabular data in a range of formats (see below). There's even an tabular data in a range of formats (see below). There's even an
interactive filter/selection tool available. interactive filter/selection tool available.
Usage: ## Demo
![demo cast](vhsdemo/demo.gif)
## Usage
```default ```default
Usage: Usage:
tablizer [regex,...] [file, ...] [flags] tablizer [regex,...] [file, ...] [flags]
@@ -22,12 +28,13 @@ Operational Flags:
-n, --numbering Enable header numbering -n, --numbering Enable 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
-j, --json Read JSON input (must be array of hashes)
-I, --interactive Interactively filter and select rows -I, --interactive Interactively filter and select rows
Output Flags (mutually exclusive): Output Flags (mutually exclusive):
@@ -41,6 +48,7 @@ Output Flags (mutually exclusive):
-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, -y, --yank-columns Yank specified columns (separated by ,) to clipboard,
space separated space separated
--ofs <char> Output field separator, used by -A and -C.
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
@@ -49,6 +57,7 @@ Sort Mode Flags (mutually exclusive):
-t, --sort-time sort according to time string -t, --sort-time sort according to time string
Other Flags: Other Flags:
-r --read-file <file> Use <file> as input instead of STDIN
--completion <shell> Generate the autocompletion script for <shell> --completion <shell> Generate the autocompletion script for <shell>
-f, --config <file> Configuration file (default: ~/.config/tablizer/config) -f, --config <file> Configuration file (default: ~/.config/tablizer/config)
-d, --debug Enable debugging -d, --debug Enable debugging
@@ -71,13 +80,13 @@ to do this with tablizer:
``` ```
% kubectl get pods | tablizer % kubectl get pods | tablizer
NAME(1) READY(2) STATUS(3) RESTARTS(4) AGE(5) NAME READY STATUS RESTARTS AGE
repldepl-7bcd8d5b64-7zq4l 1/1 Running 1 (69m ago) 5h26m repldepl-7bcd8d5b64-7zq4l 1/1 Running 1 (69m ago) 5h26m
repldepl-7bcd8d5b64-m48n8 1/1 Running 1 (69m ago) 5h26m repldepl-7bcd8d5b64-m48n8 1/1 Running 1 (69m ago) 5h26m
repldepl-7bcd8d5b64-q2bf4 1/1 Running 1 (69m ago) 5h26m repldepl-7bcd8d5b64-q2bf4 1/1 Running 1 (69m ago) 5h26m
% kubectl get pods | tablizer -c 1,3 % kubectl get pods | tablizer -c 1,3
NAME(1) STATUS(3) NAME STATUS
repldepl-7bcd8d5b64-7zq4l Running repldepl-7bcd8d5b64-7zq4l Running
repldepl-7bcd8d5b64-m48n8 Running repldepl-7bcd8d5b64-m48n8 Running
repldepl-7bcd8d5b64-q2bf4 Running repldepl-7bcd8d5b64-q2bf4 Running
@@ -115,14 +124,14 @@ You can also specify a regex pattern to reduce the output:
``` ```
% kubectl get pods | tablizer q2bf4 % kubectl get pods | tablizer q2bf4
NAME(1) READY(2) STATUS(3) RESTARTS(4) AGE(5) NAME READY STATUS RESTARTS AGE
repldepl-7bcd8d5b64-q2bf4 1/1 Running 1 (69m ago) 5h26m repldepl-7bcd8d5b64-q2bf4 1/1 Running 1 (69m ago) 5h26m
``` ```
Sometimes a filter regex is to broad and you wish to filter only on a Sometimes a filter regex is to broad and you wish to filter only on a
particular column. This is possible using `-F`: particular column. This is possible using `-F`:
``` ```
% kubectl get pods | tablizer -n -Fname=2 % kubectl get pods | tablizer -Fname=2
NAME READY STATUS RESTARTS AGE NAME READY STATUS RESTARTS AGE
repldepl-7bcd8d5b64-q2bf4 1/1 Running 1 (69m ago) 5h26m repldepl-7bcd8d5b64-q2bf4 1/1 Running 1 (69m ago) 5h26m
``` ```
@@ -136,7 +145,7 @@ You can also use it to modify certain cells using regular expression
matching. For example: matching. For example:
```shell ```shell
kubectl get pods | tablizer -n -T4 -R '/ /-/' kubectl get pods | tablizer -T4 -R '/ /-/'
NAME READY STATUS RESTARTS AGE NAME READY STATUS RESTARTS AGE
repldepl-7bcd8d5b64-7zq4l 1/1 Running 1-(69m-ago) 5h26m repldepl-7bcd8d5b64-7zq4l 1/1 Running 1-(69m-ago) 5h26m
repldepl-7bcd8d5b64-m48n8 1/1 Running 1-(69m-ago) 5h26m repldepl-7bcd8d5b64-m48n8 1/1 Running 1-(69m-ago) 5h26m
@@ -147,11 +156,12 @@ Here, we modified the 4th column (`-T4`) by replacing every space with
a dash. If you need to work with `/` characters, you can also use any a dash. If you need to work with `/` characters, you can also use any
other separator, for instance: `-R '| |-|'`. other separator, for instance: `-R '| |-|'`.
There's also an interactive mode, invoked with the option B<-I>, where
you can interactively filter and select rows:
<img width="937" height="293" alt="interactive" src="https://github.com/user-attachments/assets/0d4d65e2-d156-43ed-8021-39047c7939ed" />
## Demo
![demo cast](vhsdemo/demo.gif)
## Installation ## Installation
@@ -182,10 +192,9 @@ hesitate to ask me about it, I'll add it.
## Documentation ## Documentation
The documentation is provided as a unix man-page. It will be The documentation is provided as a unix man-page. It will be
automatically installed if you install from source. However, you can automatically installed if you install from source.
read the man-page online:
https://github.com/TLINDEN/tablizer/blob/main/tablizer.pod [However, you can read the man-page online](https://github.com/TLINDEN/tablizer/blob/main/tablizer.pod).
Or if you cloned the repository you can read it this way (perl needs Or if you cloned the repository you can read it this way (perl needs
to be installed though): `perldoc tablizer.pod`. to be installed though): `perldoc tablizer.pod`.

View File

@@ -28,7 +28,7 @@ import (
) )
const DefaultSeparator string = `(\s\s+|\t)` const DefaultSeparator string = `(\s\s+|\t)`
const Version string = "v1.5.3" const Version string = "v1.5.8"
const MAXPARTS = 2 const MAXPARTS = 2
var DefaultConfigfile = os.Getenv("HOME") + "/.config/tablizer/config" var DefaultConfigfile = os.Getenv("HOME") + "/.config/tablizer/config"
@@ -79,6 +79,7 @@ type Config struct {
UseFuzzySearch bool UseFuzzySearch bool
UseHighlight bool UseHighlight bool
Interactive bool Interactive bool
InputJSON bool
SortMode string SortMode string
SortDescending bool SortDescending bool
@@ -112,6 +113,8 @@ type Config struct {
// -r <file> // -r <file>
InputFile string InputFile string
OFS string
} }
// maps outputmode short flags to output mode, ie. -O => -o orgtbl // maps outputmode short flags to output mode, ie. -O => -o orgtbl

View File

@@ -21,6 +21,8 @@ import (
"fmt" "fmt"
// "reflect" // "reflect"
"testing" "testing"
"github.com/stretchr/testify/assert"
) )
func TestPrepareModeFlags(t *testing.T) { func TestPrepareModeFlags(t *testing.T) {
@@ -44,9 +46,8 @@ func TestPrepareModeFlags(t *testing.T) {
conf := Config{} conf := Config{}
conf.PrepareModeFlags(testdata.flag) conf.PrepareModeFlags(testdata.flag)
if conf.OutputMode != testdata.expect {
t.Errorf("got: %d, expect: %d", conf.OutputMode, testdata.expect) assert.EqualValues(t, testdata.expect, conf.OutputMode)
}
}) })
} }
} }
@@ -70,9 +71,7 @@ func TestPrepareSortFlags(t *testing.T) {
conf.PrepareSortFlags(testdata.flag) conf.PrepareSortFlags(testdata.flag)
if conf.SortMode != testdata.expect { assert.EqualValues(t, testdata.expect, conf.SortMode)
t.Errorf("got: %s, expect: %s", conf.SortMode, testdata.expect)
}
}) })
} }
} }
@@ -81,7 +80,7 @@ func TestPreparePattern(t *testing.T) {
var tests = []struct { var tests = []struct {
patterns []*Pattern patterns []*Pattern
name string name string
wanterr bool wanterror bool
wanticase bool wanticase bool
wantneg bool wantneg bool
}{ }{
@@ -123,16 +122,16 @@ func TestPreparePattern(t *testing.T) {
} }
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.name, testdata.wanterror)
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.patterns)
if err != nil { if testdata.wanterror {
if !testdata.wanterr { assert.Error(t, err)
t.Errorf("PreparePattern returned error: %s", err) } else {
} assert.NoError(t, err)
} }
}) })
} }

View File

@@ -20,6 +20,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"os" "os"
"slices"
"strings" "strings"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@@ -131,7 +132,11 @@ func Execute() {
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 ,)")
rootCmd.PersistentFlags().BoolVarP(&conf.Interactive, "interactive", "I", false, rootCmd.PersistentFlags().BoolVarP(&conf.Interactive, "interactive", "I", false,
"interactive mode (experimental)") "interactive mode")
rootCmd.PersistentFlags().StringVarP(&conf.OFS, "ofs", "", "",
"Output field separator (' ' for ascii table, ',' for CSV)")
rootCmd.PersistentFlags().BoolVarP(&conf.InputJSON, "json", "j", false,
"JSON input mode")
// sort options // sort options
rootCmd.PersistentFlags().StringVarP(&conf.SortByColumn, "sort-by", "k", "", rootCmd.PersistentFlags().StringVarP(&conf.SortByColumn, "sort-by", "k", "",
@@ -183,6 +188,11 @@ func Execute() {
rootCmd.SetUsageTemplate(strings.TrimSpace(usage) + "\n") rootCmd.SetUsageTemplate(strings.TrimSpace(usage) + "\n")
if slices.Contains(os.Args, "-h") {
fmt.Println(shortusage)
os.Exit(0)
}
err := rootCmd.Execute() err := rootCmd.Execute()
if err != nil { if err != nil {
os.Exit(1) os.Exit(1)

16
cmd/shortusage.go Normal file
View File

@@ -0,0 +1,16 @@
package cmd
const shortusage = `tablizer [regex,...] [-r file] [flags]
-c col,... show specified columns -L highlight matching lines
-k col,... sort by specified columns -j read JSON input
-F col=reg filter field with regexp -v invert match
-T col,... transpose specified columns -n numberize columns
-R /from/to/ apply replacement to columns in -T -N do not use colors
-y col,... yank columns to clipboard -H do not show headers
--ofs char output field separator -s specify field separator
-r file read input from file -z use fuzzy search
-f file read config from file -I interactive filter mode
-d debug
-O org -C CSV -M md -X ext -S shell -Y yaml -D sort descending order
-m show manual --help show detailed help -v show version
-a sort by age -i sort numerically -t sort by time`

View File

@@ -14,12 +14,13 @@ SYNOPSIS
-n, --numbering Enable header numbering -n, --numbering Enable 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
-j, --json Read JSON input (must be array of hashes)
-I, --interactive Interactively filter and select rows -I, --interactive Interactively filter and select rows
Output Flags (mutually exclusive): Output Flags (mutually exclusive):
@@ -33,6 +34,7 @@ SYNOPSIS
-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, -y, --yank-columns Yank specified columns (separated by ,) to clipboard,
space separated space separated
--ofs <char> Output field separator, used by -A and -C.
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
@@ -75,16 +77,16 @@ DESCRIPTION
kubectl get pods | tablizer kubectl get pods | tablizer
# read a file # read a file
tablizer filename tablizer -r filename
# search for pattern in a file (works like grep) # search for pattern in a file (works like grep)
tablizer regex filename tablizer regex -r filename
# search for pattern in STDIN # search for pattern in STDIN
kubectl get pods | tablizer regex kubectl get pods | tablizer regex
The output looks like the original one but every header field will have The output looks like the original one. You can add the option -n, then
a numer associated with it, e.g.: every header field will have a numer associated with it, e.g.:
NAME(1) READY(2) STATUS(3) RESTARTS(4) AGE(5) NAME(1) READY(2) STATUS(3) RESTARTS(4) AGE(5)
@@ -96,7 +98,13 @@ DESCRIPTION
You can specify the numbers in any order but output will always follow You can specify the numbers in any order but output will always follow
the original order. the original order.
The numbering can be suppressed by using the -n option. However, you may also just use the header names instead of numbers, eg:
kubectl get pods | tablizer -cname,status
You can also use regular expressions with -c, eg:
kubectl get pods | tablizer -c '[ae]'
By default tablizer shows a header containing the names of each column. By default tablizer shows a header containing the names of each column.
This can be disabled using the -H option. Be aware that this only This can be disabled using the -H option. Be aware that this only
@@ -450,12 +458,13 @@ Operational Flags:
-n, --numbering Enable header numbering -n, --numbering Enable 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
-j, --json Read JSON input (must be array of hashes)
-I, --interactive Interactively filter and select rows -I, --interactive Interactively filter and select rows
Output Flags (mutually exclusive): Output Flags (mutually exclusive):
@@ -469,6 +478,7 @@ Output Flags (mutually exclusive):
-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, -y, --yank-columns Yank specified columns (separated by ,) to clipboard,
space separated space separated
--ofs <char> Output field separator, used by -A and -C.
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

25
go.mod
View File

@@ -1,22 +1,22 @@
module github.com/tlinden/tablizer module github.com/tlinden/tablizer
go 1.23.0 go 1.24.0
toolchain go1.23.5
require ( require (
github.com/alecthomas/repr v0.5.1 github.com/alecthomas/repr v0.5.2
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de
github.com/charmbracelet/bubbles v0.21.0 github.com/charmbracelet/bubbles v0.21.0
github.com/charmbracelet/bubbletea v1.3.6 github.com/charmbracelet/bubbletea v1.3.10
github.com/charmbracelet/lipgloss v1.1.0 github.com/charmbracelet/lipgloss v1.1.0
github.com/evertras/bubble-table v0.19.0 github.com/evertras/bubble-table v0.19.2
github.com/gookit/color v1.6.0 github.com/gookit/color v1.6.0
github.com/hashicorp/hcl/v2 v2.24.0 github.com/hashicorp/hcl/v2 v2.24.0
github.com/lithammer/fuzzysearch v1.1.8 github.com/lithammer/fuzzysearch v1.1.8
github.com/olekukonko/tablewriter v1.0.9 github.com/mattn/go-isatty v0.0.20
github.com/olekukonko/tablewriter v1.1.0
github.com/rogpeppe/go-internal v1.14.1 github.com/rogpeppe/go-internal v1.14.1
github.com/spf13/cobra v1.9.1 github.com/spf13/cobra v1.10.1
github.com/stretchr/testify v1.11.1
github.com/tiagomelo/go-clipboard v0.1.2 github.com/tiagomelo/go-clipboard v0.1.2
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
) )
@@ -27,16 +27,16 @@ require (
github.com/atotto/clipboard v0.1.4 // indirect github.com/atotto/clipboard v0.1.4 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/charmbracelet/colorprofile v0.3.1 // indirect github.com/charmbracelet/colorprofile v0.3.1 // indirect
github.com/charmbracelet/x/ansi v0.9.3 // indirect github.com/charmbracelet/x/ansi v0.10.1 // indirect
github.com/charmbracelet/x/cellbuf v0.0.13 // indirect github.com/charmbracelet/x/cellbuf v0.0.13 // indirect
github.com/charmbracelet/x/term v0.2.1 // indirect github.com/charmbracelet/x/term v0.2.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
github.com/fatih/color v1.18.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/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect github.com/mattn/go-localereader v0.0.1 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect
@@ -47,14 +47,15 @@ require (
github.com/olekukonko/errors v1.1.0 // indirect github.com/olekukonko/errors v1.1.0 // indirect
github.com/olekukonko/ll v0.0.9 // indirect github.com/olekukonko/ll v0.0.9 // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rivo/uniseg v0.4.7 // indirect github.com/rivo/uniseg v0.4.7 // indirect
github.com/spf13/pflag v1.0.6 // indirect github.com/spf13/pflag v1.0.9 // 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.16.3 // indirect github.com/zclconf/go-cty v1.16.3 // indirect
golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect
golang.org/x/mod v0.21.0 // indirect golang.org/x/mod v0.21.0 // indirect
golang.org/x/sync v0.15.0 // indirect golang.org/x/sync v0.15.0 // indirect
golang.org/x/sys v0.33.0 // indirect golang.org/x/sys v0.36.0 // indirect
golang.org/x/text v0.25.0 // indirect golang.org/x/text v0.25.0 // indirect
golang.org/x/tools v0.26.0 // indirect golang.org/x/tools v0.26.0 // indirect
) )

36
go.sum
View File

@@ -1,7 +1,7 @@
github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo=
github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
github.com/alecthomas/repr v0.5.1 h1:E3G4t2QbHTSNpPKBgMTln5KLkZHLOcU7r37J4pXBuIg= github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs=
github.com/alecthomas/repr v0.5.1/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY= github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY=
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=
@@ -12,14 +12,14 @@ github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiE
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/charmbracelet/bubbles v0.21.0 h1:9TdC97SdRVg/1aaXNVWfFH3nnLAwOXr8Fn6u6mfQdFs= github.com/charmbracelet/bubbles v0.21.0 h1:9TdC97SdRVg/1aaXNVWfFH3nnLAwOXr8Fn6u6mfQdFs=
github.com/charmbracelet/bubbles v0.21.0/go.mod h1:HF+v6QUR4HkEpz62dx7ym2xc71/KBHg+zKwJtMw+qtg= github.com/charmbracelet/bubbles v0.21.0/go.mod h1:HF+v6QUR4HkEpz62dx7ym2xc71/KBHg+zKwJtMw+qtg=
github.com/charmbracelet/bubbletea v1.3.6 h1:VkHIxPJQeDt0aFJIsVxw8BQdh/F/L2KKZGsK6et5taU= github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw=
github.com/charmbracelet/bubbletea v1.3.6/go.mod h1:oQD9VCRQFF8KplacJLo28/jofOI2ToOfGYeFgBBxHOc= github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4=
github.com/charmbracelet/colorprofile v0.3.1 h1:k8dTHMd7fgw4bnFd7jXTLZrSU/CQrKnL3m+AxCzDz40= github.com/charmbracelet/colorprofile v0.3.1 h1:k8dTHMd7fgw4bnFd7jXTLZrSU/CQrKnL3m+AxCzDz40=
github.com/charmbracelet/colorprofile v0.3.1/go.mod h1:/GkGusxNs8VB/RSOh3fu0TJmQ4ICMMPApIIVn0KszZ0= github.com/charmbracelet/colorprofile v0.3.1/go.mod h1:/GkGusxNs8VB/RSOh3fu0TJmQ4ICMMPApIIVn0KszZ0=
github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY= github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30= github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
github.com/charmbracelet/x/ansi v0.9.3 h1:BXt5DHS/MKF+LjuK4huWrC6NCvHtexww7dMayh6GXd0= github.com/charmbracelet/x/ansi v0.10.1 h1:rL3Koar5XvX0pHGfovN03f5cxLbCF2YvLeyz7D2jVDQ=
github.com/charmbracelet/x/ansi v0.9.3/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE= github.com/charmbracelet/x/ansi v0.10.1/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE=
github.com/charmbracelet/x/cellbuf v0.0.13 h1:/KBBKHuVRbq1lYx5BzEHBAFBP8VcQzJejZ/IA3iR28k= github.com/charmbracelet/x/cellbuf v0.0.13 h1:/KBBKHuVRbq1lYx5BzEHBAFBP8VcQzJejZ/IA3iR28k=
github.com/charmbracelet/x/cellbuf v0.0.13/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs= github.com/charmbracelet/x/cellbuf v0.0.13/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
@@ -30,8 +30,8 @@ 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/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
github.com/evertras/bubble-table v0.19.0 h1:+JlXRUjNuBN1JI7XU1PapmW1wglbcqZUKkiPnVKPgrc= github.com/evertras/bubble-table v0.19.2 h1:u77oiM6JlRR+CvS5FZc3Hz+J6iEsvEDcR5kO8OFb1Yw=
github.com/evertras/bubble-table v0.19.0/go.mod h1:ifHujS1YxwnYSOgcR2+m3GnJ84f7CVU/4kUOxUCjEbQ= github.com/evertras/bubble-table v0.19.2/go.mod h1:ifHujS1YxwnYSOgcR2+m3GnJ84f7CVU/4kUOxUCjEbQ=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= 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/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=
@@ -74,8 +74,8 @@ github.com/olekukonko/errors v1.1.0 h1:RNuGIh15QdDenh+hNvKrJkmxxjV4hcS50Db478Ou5
github.com/olekukonko/errors v1.1.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y= github.com/olekukonko/errors v1.1.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y=
github.com/olekukonko/ll v0.0.9 h1:Y+1YqDfVkqMWuEQMclsF9HUR5+a82+dxJuL1HHSRpxI= github.com/olekukonko/ll v0.0.9 h1:Y+1YqDfVkqMWuEQMclsF9HUR5+a82+dxJuL1HHSRpxI=
github.com/olekukonko/ll v0.0.9/go.mod h1:En+sEW0JNETl26+K8eZ6/W4UQ7CYSrrgg/EdIYT2H8g= github.com/olekukonko/ll v0.0.9/go.mod h1:En+sEW0JNETl26+K8eZ6/W4UQ7CYSrrgg/EdIYT2H8g=
github.com/olekukonko/tablewriter v1.0.9 h1:XGwRsYLC2bY7bNd93Dk51bcPZksWZmLYuaTHR0FqfL8= github.com/olekukonko/tablewriter v1.1.0 h1:N0LHrshF4T39KvI96fn6GT8HEjXRXYNDrDjKFDB7RIY=
github.com/olekukonko/tablewriter v1.0.9/go.mod h1:5c+EBPeSqvXnLLgkm9isDdzR3wjfBkHR9Nhfp3NWrzo= github.com/olekukonko/tablewriter v1.1.0/go.mod h1:5c+EBPeSqvXnLLgkm9isDdzR3wjfBkHR9Nhfp3NWrzo=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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=
@@ -88,14 +88,14 @@ github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0t
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= 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.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s=
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0=
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.9/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.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/tiagomelo/go-clipboard v0.1.2 h1:Ph2icR0vZRIj3v5ExvsGweBwsbbDUTlS6HoF40MkQD8= 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/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=
@@ -130,8 +130,8 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
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.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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
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=

View File

@@ -19,9 +19,9 @@ package lib
import ( import (
"fmt" "fmt"
"reflect"
"testing" "testing"
"github.com/stretchr/testify/assert"
"github.com/tlinden/tablizer/cfg" "github.com/tlinden/tablizer/cfg"
) )
@@ -56,13 +56,11 @@ func TestMatchPattern(t *testing.T) {
} }
err := conf.PreparePattern(inputdata.patterns) err := conf.PreparePattern(inputdata.patterns)
if err != nil {
t.Errorf("PreparePattern returned error: %s", err)
}
if !matchPattern(conf, inputdata.line) { assert.NoError(t, err)
t.Errorf("matchPattern() did not match\nExp: true\nGot: false\n")
} res := matchPattern(conf, inputdata.line)
assert.EqualValues(t, true, res)
}) })
} }
} }
@@ -163,14 +161,12 @@ func TestFilterByFields(t *testing.T) {
conf := cfg.Config{Rawfilters: inputdata.filter, InvertMatch: inputdata.invert} conf := cfg.Config{Rawfilters: inputdata.filter, InvertMatch: inputdata.invert}
err := conf.PrepareFilters() err := conf.PrepareFilters()
if err != nil {
t.Errorf("PrepareFilters returned error: %s", err) assert.NoError(t, err)
}
data, _, _ := FilterByFields(conf, &data) data, _, _ := FilterByFields(conf, &data)
if !reflect.DeepEqual(*data, inputdata.expect) {
t.Errorf("Filtered data does not match expected data:\ngot: %+v\nexp: %+v", data, inputdata.expect) assert.EqualValues(t, inputdata.expect, *data)
}
}) })
} }
} }

View File

@@ -22,7 +22,7 @@ import (
"fmt" "fmt"
"os" "os"
"regexp" "regexp"
"sort" "slices"
"strconv" "strconv"
"strings" "strings"
@@ -30,16 +30,6 @@ import (
"github.com/tlinden/tablizer/cfg" "github.com/tlinden/tablizer/cfg"
) )
func contains(s []int, e int) bool {
for _, a := range s {
if a == e {
return true
}
}
return false
}
func findindex(s []int, e int) (int, bool) { func findindex(s []int, e int) (int, bool) {
for i, a := range s { for i, a := range s {
if a == e { if a == e {
@@ -172,48 +162,32 @@ func PrepareColumnVars(columns string, data *Tabdata) ([]int, error) {
} }
} }
// deduplicate: put all values into a map (value gets map key) // deduplicate columns, preserve order
// thereby removing duplicates, extract keys into new slice deduped := []int{}
// and sort it
imap := make(map[int]int, len(usecolumns))
for _, i := range usecolumns { for _, i := range usecolumns {
imap[i] = 0 if !slices.Contains(deduped, i) {
deduped = append(deduped, i)
}
} }
// fill with deduplicated columns return deduped, nil
usecolumns = nil
for k := range imap {
usecolumns = append(usecolumns, k)
}
sort.Ints(usecolumns)
return usecolumns, nil
} }
// prepare headers: add numbers to headers // prepare headers: add numbers to headers
func numberizeAndReduceHeaders(conf cfg.Config, data *Tabdata) { func numberizeAndReduceHeaders(conf cfg.Config, data *Tabdata) {
numberedHeaders := []string{} numberedHeaders := make([]string, len(data.headers))
maxwidth := 0 // start from scratch, so we only look at displayed column widths maxwidth := 0 // start from scratch, so we only look at displayed column widths
// add numbers to headers if needed, get widest cell width
for idx, head := range data.headers { for idx, head := range data.headers {
var headlen int var headlen int
if len(conf.Columns) > 0 {
// -c specified
if !contains(conf.UseColumns, idx+1) {
// ignore this one
continue
}
}
if conf.Numbering { if conf.Numbering {
numhead := fmt.Sprintf("%s(%d)", head, idx+1) newhead := fmt.Sprintf("%s(%d)", head, idx+1)
headlen = len(numhead) numberedHeaders[idx] = newhead
numberedHeaders = append(numberedHeaders, numhead) headlen = len(newhead)
} else { } else {
numberedHeaders = append(numberedHeaders, head)
headlen = len(head) headlen = len(head)
} }
@@ -222,7 +196,24 @@ func numberizeAndReduceHeaders(conf cfg.Config, data *Tabdata) {
} }
} }
if conf.Numbering {
data.headers = numberedHeaders data.headers = numberedHeaders
}
if len(conf.UseColumns) > 0 {
// re-align headers based on user requested column list
headers := make([]string, len(conf.UseColumns))
for i, col := range conf.UseColumns {
for idx := range data.headers {
if col-1 == idx {
headers[i] = data.headers[col-1]
}
}
}
data.headers = headers
}
if data.maxwidthHeader != maxwidth && maxwidth > 0 { if data.maxwidthHeader != maxwidth && maxwidth > 0 {
data.maxwidthHeader = maxwidth data.maxwidthHeader = maxwidth
@@ -234,18 +225,18 @@ func reduceColumns(conf cfg.Config, data *Tabdata) {
if len(conf.Columns) > 0 { if len(conf.Columns) > 0 {
reducedEntries := [][]string{} reducedEntries := [][]string{}
for _, entry := range data.entries {
var reducedEntry []string var reducedEntry []string
for _, entry := range data.entries { for _, col := range conf.UseColumns {
reducedEntry = nil col--
for i, value := range entry {
if !contains(conf.UseColumns, i+1) {
continue
}
for idx, value := range entry {
if idx == col {
reducedEntry = append(reducedEntry, value) reducedEntry = append(reducedEntry, value)
} }
}
}
reducedEntries = append(reducedEntries, reducedEntry) reducedEntries = append(reducedEntries, reducedEntry)
} }

View File

@@ -19,9 +19,10 @@ package lib
import ( import (
"fmt" "fmt"
"reflect" "slices"
"testing" "testing"
"github.com/stretchr/testify/assert"
"github.com/tlinden/tablizer/cfg" "github.com/tlinden/tablizer/cfg"
) )
@@ -38,10 +39,9 @@ func TestContains(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
testname := fmt.Sprintf("contains-%d,%d,%t", tt.list, tt.search, tt.want) testname := fmt.Sprintf("contains-%d,%d,%t", tt.list, tt.search, tt.want)
t.Run(testname, func(t *testing.T) { t.Run(testname, func(t *testing.T) {
answer := contains(tt.list, tt.search) answer := slices.Contains(tt.list, tt.search)
if answer != tt.want {
t.Errorf("got %t, want %t", answer, tt.want) assert.EqualValues(t, tt.want, answer)
}
}) })
} }
} }
@@ -73,18 +73,17 @@ func TestPrepareColumns(t *testing.T) {
} }
for _, testdata := range tests { for _, testdata := range tests {
testname := fmt.Sprintf("PrepareColumns-%s-%t", testdata.input, testdata.wanterror) testname := fmt.Sprintf("PrepareColumns-%s-%t",
testdata.input, testdata.wanterror)
t.Run(testname, func(t *testing.T) { t.Run(testname, func(t *testing.T) {
conf := cfg.Config{Columns: testdata.input} conf := cfg.Config{Columns: testdata.input}
err := PrepareColumns(&conf, &data) err := PrepareColumns(&conf, &data)
if err != nil {
if !testdata.wanterror { if testdata.wanterror {
t.Errorf("got error: %v", err) assert.Error(t, err)
}
} else { } else {
if !reflect.DeepEqual(conf.UseColumns, testdata.exp) { assert.NoError(t, err)
t.Errorf("got: %v, expected: %v", conf.UseColumns, testdata.exp) assert.EqualValues(t, testdata.exp, conf.UseColumns)
}
} }
}) })
} }
@@ -153,18 +152,13 @@ func TestPrepareTransposerColumns(t *testing.T) {
t.Run(testname, func(t *testing.T) { t.Run(testname, func(t *testing.T) {
conf := cfg.Config{TransposeColumns: testdata.input, Transposers: testdata.transp} conf := cfg.Config{TransposeColumns: testdata.input, Transposers: testdata.transp}
err := PrepareTransposerColumns(&conf, &data) err := PrepareTransposerColumns(&conf, &data)
if err != nil {
if !testdata.wanterror {
t.Errorf("got error: %v", err)
}
} else {
if len(conf.UseTransposeColumns) != testdata.exp {
t.Errorf("got %d, want %d", conf.UseTransposeColumns, testdata.exp)
}
if len(conf.Transposers) != len(conf.UseTransposeColumns) { if testdata.wanterror {
t.Errorf("got %d, want %d", conf.UseTransposeColumns, testdata.exp) assert.Error(t, err)
} } else {
assert.NoError(t, err)
assert.EqualValues(t, testdata.exp, len(conf.UseTransposeColumns))
assert.EqualValues(t, len(conf.UseTransposeColumns), len(conf.Transposers))
} }
}) })
} }
@@ -202,10 +196,8 @@ func TestReduceColumns(t *testing.T) {
c := cfg.Config{Columns: "x", UseColumns: testdata.columns} c := cfg.Config{Columns: "x", UseColumns: testdata.columns}
data := Tabdata{entries: input} data := Tabdata{entries: input}
reduceColumns(c, &data) reduceColumns(c, &data)
if !reflect.DeepEqual(data.entries, testdata.expect) {
t.Errorf("reduceColumns returned invalid data:\ngot: %+v\nexp: %+v", assert.EqualValues(t, testdata.expect, data.entries)
data.entries, testdata.expect)
}
}) })
} }
} }
@@ -233,10 +225,8 @@ func TestNumberizeHeaders(t *testing.T) {
conf := cfg.Config{Columns: "x", UseColumns: testdata.columns, Numbering: testdata.numberize} conf := cfg.Config{Columns: "x", UseColumns: testdata.columns, Numbering: testdata.numberize}
usedata := data usedata := data
numberizeAndReduceHeaders(conf, &usedata) numberizeAndReduceHeaders(conf, &usedata)
if !reflect.DeepEqual(usedata.headers, testdata.expect) {
t.Errorf("numberizeAndReduceHeaders returned invalid data:\ngot: %+v\nexp: %+v", assert.EqualValues(t, testdata.expect, usedata.headers)
usedata.headers, testdata.expect)
}
}) })
} }
} }

View File

@@ -1,5 +1,5 @@
/* /*
Copyright © 2022-2024 Thomas von Dein Copyright © 2022-2025 Thomas von Dein
This program is free software: you can redistribute it and/or modify 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
@@ -20,8 +20,12 @@ package lib
import ( import (
"bufio" "bufio"
"encoding/csv" "encoding/csv"
"encoding/json"
"errors"
"fmt" "fmt"
"io" "io"
"log"
"math"
"regexp" "regexp"
"strings" "strings"
@@ -39,6 +43,8 @@ func Parse(conf cfg.Config, input io.Reader) (Tabdata, error) {
// first step, parse the data // first step, parse the data
if len(conf.Separator) == 1 { if len(conf.Separator) == 1 {
data, err = parseCSV(conf, input) data, err = parseCSV(conf, input)
} else if conf.InputJSON {
data, err = parseJSON(conf, input)
} else { } else {
data, err = parseTabular(conf, input) data, err = parseTabular(conf, input)
} }
@@ -172,6 +178,137 @@ func parseTabular(conf cfg.Config, input io.Reader) (Tabdata, error) {
return data, nil return data, nil
} }
/*
Parse JSON input. We only support an array of maps.
*/
func parseRawJSON(conf cfg.Config, input io.Reader) (Tabdata, error) {
dec := json.NewDecoder(input)
headers := []string{}
idxmap := map[string]int{}
data := [][]string{}
row := []string{}
iskey := true
haveheaders := false
var currentfield string
var idx int
var isjson bool
for {
t, err := dec.Token()
if err == io.EOF {
break
}
if err != nil {
log.Fatal(err)
}
switch val := t.(type) {
case string:
if iskey {
if !haveheaders {
// consider only the keys of the first item as headers
headers = append(headers, val)
}
currentfield = val
} else {
if !haveheaders {
// the first row uses the order as it comes in
row = append(row, val)
} else {
// use the pre-determined order, that way items
// can be in any order as long as they contain all
// neccessary fields. They may also contain less
// fields than the first item, these will contain
// the empty string
row[idxmap[currentfield]] = val
}
}
case float64:
var value string
// we set precision to 0 if the float is a whole number
if val == math.Trunc(val) {
value = fmt.Sprintf("%.f", val)
} else {
value = fmt.Sprintf("%f", val)
}
if !haveheaders {
row = append(row, value)
} else {
row[idxmap[currentfield]] = value
}
case nil:
// we ignore here if a value shall be an int or a string,
// because tablizer only works with strings anyway
if !haveheaders {
row = append(row, "")
} else {
row[idxmap[currentfield]] = ""
}
case json.Delim:
if val.String() == "}" {
data = append(data, row)
row = make([]string, len(headers))
idx++
if !haveheaders {
// remember the array position of header fields,
// which we use to assign elements to the correct
// row index
for i, header := range headers {
idxmap[header] = i
}
}
haveheaders = true
}
isjson = true
default:
fmt.Printf("unknown token: %v type: %T\n", t, t)
}
iskey = !iskey
}
if isjson && (len(headers) == 0 || len(data) == 0) {
return Tabdata{}, errors.New("failed to parse JSON, input did not contain array of hashes")
}
return Tabdata{headers: headers, entries: data, columns: len(headers)}, nil
}
func parseJSON(conf cfg.Config, input io.Reader) (Tabdata, error) {
// parse raw json
data, err := parseRawJSON(conf, input)
if err != nil {
return data, err
}
// apply filter, if any
filtered := [][]string{}
var line string
for _, row := range data.entries {
line = strings.Join(row, " ")
if matchPattern(conf, line) == conf.InvertMatch {
continue
}
filtered = append(filtered, row)
}
if len(filtered) != len(data.entries) {
data.entries = filtered
}
return data, nil
}
func PostProcess(conf cfg.Config, data *Tabdata) (*Tabdata, bool, error) { func PostProcess(conf cfg.Config, data *Tabdata) (*Tabdata, bool, error) {
var modified bool var modified bool

View File

@@ -1,5 +1,5 @@
/* /*
Copyright © 2022 Thomas von Dein Copyright © 2022-2025 Thomas von Dein
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@@ -19,10 +19,11 @@ package lib
import ( import (
"fmt" "fmt"
"reflect" "io"
"strings" "strings"
"testing" "testing"
"github.com/stretchr/testify/assert"
"github.com/tlinden/tablizer/cfg" "github.com/tlinden/tablizer/cfg"
) )
@@ -67,16 +68,10 @@ func TestParser(t *testing.T) {
t.Run(testname, func(t *testing.T) { t.Run(testname, func(t *testing.T) {
readFd := strings.NewReader(strings.TrimSpace(testdata.text)) readFd := strings.NewReader(strings.TrimSpace(testdata.text))
conf := cfg.Config{Separator: testdata.separator} conf := cfg.Config{Separator: testdata.separator}
gotdata, err := Parse(conf, readFd) gotdata, err := wrapValidateParser(conf, readFd)
if err != nil { assert.NoError(t, err)
t.Errorf("Parser returned error: %s\nData processed so far: %+v", err, gotdata) assert.EqualValues(t, data, gotdata)
}
if !reflect.DeepEqual(data, gotdata) {
t.Errorf("Parser returned invalid data\nExp: %+v\nGot: %+v\n",
data, gotdata)
}
}) })
} }
} }
@@ -87,7 +82,7 @@ func TestParserPatternmatching(t *testing.T) {
entries [][]string entries [][]string
patterns []*cfg.Pattern patterns []*cfg.Pattern
invert bool invert bool
want bool wanterror bool
}{ }{
{ {
name: "match", name: "match",
@@ -121,18 +116,13 @@ func TestParserPatternmatching(t *testing.T) {
_ = conf.PreparePattern(testdata.patterns) _ = conf.PreparePattern(testdata.patterns)
readFd := strings.NewReader(strings.TrimSpace(inputdata.text)) readFd := strings.NewReader(strings.TrimSpace(inputdata.text))
gotdata, err := Parse(conf, readFd) data, err := wrapValidateParser(conf, readFd)
if err != nil { if testdata.wanterror {
if !testdata.want { assert.Error(t, err)
t.Errorf("Parser returned error: %s\nData processed so far: %+v",
err, gotdata)
}
} else { } else {
if !reflect.DeepEqual(testdata.entries, gotdata.entries) { assert.NoError(t, err)
t.Errorf("Parser returned invalid data (pattern: %s, invert: %t)\nExp: %+v\nGot: %+v\n", assert.EqualValues(t, testdata.entries, data.entries)
testdata.name, testdata.invert, testdata.entries, gotdata.entries)
}
} }
}) })
} }
@@ -159,14 +149,179 @@ asd igig
readFd := strings.NewReader(strings.TrimSpace(table)) readFd := strings.NewReader(strings.TrimSpace(table))
conf := cfg.Config{Separator: cfg.DefaultSeparator} conf := cfg.Config{Separator: cfg.DefaultSeparator}
gotdata, err := Parse(conf, readFd) gotdata, err := wrapValidateParser(conf, readFd)
if err != nil { assert.NoError(t, err)
t.Errorf("Parser returned error: %s\nData processed so far: %+v", err, gotdata) assert.EqualValues(t, data, gotdata)
}
func TestParserJSONInput(t *testing.T) {
var tests = []struct {
name string
input string
expect Tabdata
wanterror bool // true: expect fail, false: expect success
}{
{
// too deep nesting
name: "invalidjson",
wanterror: true,
input: `[
{
"item": {
"NAME": "postgres-operator-7f4c7c8485-ntlns",
"READY": "1/1",
"STATUS": "Running",
"RESTARTS": "0",
"AGE": "24h"
}
}
`,
expect: Tabdata{},
},
{
// contains nil, int and float values
name: "niljson",
wanterror: false,
input: `[
{
"NAME": "postgres-operator-7f4c7c8485-ntlns",
"READY": "1/1",
"STATUS": "Running",
"RESTARTS": 0,
"AGE": null,
"X": 12,
"Y": 34.222
}
]`,
expect: Tabdata{
columns: 7,
headers: []string{"NAME", "READY", "STATUS", "RESTARTS", "AGE", "X", "Y"},
entries: [][]string{
[]string{
"postgres-operator-7f4c7c8485-ntlns",
"1/1",
"Running",
"0",
"",
"12",
"34.222000",
},
},
},
},
{
// one field missing + different order
// but shall not fail
name: "kgpfail",
wanterror: false,
input: `[
{
"NAME": "postgres-operator-7f4c7c8485-ntlns",
"READY": "1/1",
"STATUS": "Running",
"RESTARTS": "0",
"AGE": "24h"
},
{
"NAME": "wal-g-exporter-778dcd95f5-wcjzn",
"RESTARTS": "0",
"READY": "1/1",
"AGE": "24h"
}
]`,
expect: Tabdata{
columns: 5,
headers: []string{"NAME", "READY", "STATUS", "RESTARTS", "AGE"},
entries: [][]string{
[]string{
"postgres-operator-7f4c7c8485-ntlns",
"1/1",
"Running",
"0",
"24h",
},
[]string{
"wal-g-exporter-778dcd95f5-wcjzn",
"1/1",
"",
"0",
"24h",
},
},
},
},
{
name: "kgp",
wanterror: false,
input: `[
{
"NAME": "postgres-operator-7f4c7c8485-ntlns",
"READY": "1/1",
"STATUS": "Running",
"RESTARTS": "0",
"AGE": "24h"
},
{
"NAME": "wal-g-exporter-778dcd95f5-wcjzn",
"STATUS": "Running",
"READY": "1/1",
"RESTARTS": "0",
"AGE": "24h"
}
]`,
expect: Tabdata{
columns: 5,
headers: []string{"NAME", "READY", "STATUS", "RESTARTS", "AGE"},
entries: [][]string{
[]string{
"postgres-operator-7f4c7c8485-ntlns",
"1/1",
"Running",
"0",
"24h",
},
[]string{
"wal-g-exporter-778dcd95f5-wcjzn",
"1/1",
"Running",
"0",
"24h",
},
},
},
},
} }
if !reflect.DeepEqual(data, gotdata) { for _, testdata := range tests {
t.Errorf("Parser returned invalid data, Regex: %s\nExp: %+v\nGot: %+v\n", testname := fmt.Sprintf("parse-json-%s", testdata.name)
conf.Separator, data, gotdata) t.Run(testname, func(t *testing.T) {
conf := cfg.Config{InputJSON: true}
readFd := strings.NewReader(strings.TrimSpace(testdata.input))
data, err := wrapValidateParser(conf, readFd)
if testdata.wanterror {
assert.Error(t, err)
} else {
assert.NoError(t, err)
assert.EqualValues(t, testdata.expect, data)
}
})
} }
} }
func wrapValidateParser(conf cfg.Config, input io.Reader) (Tabdata, error) {
data, err := Parse(conf, input)
if err != nil {
return data, err
}
err = ValidateConsistency(&data)
return data, err
}

View File

@@ -62,7 +62,7 @@ func printData(writer io.Writer, conf cfg.Config, data *Tabdata) {
case cfg.Yaml: case cfg.Yaml:
printYamlData(writer, data) printYamlData(writer, data)
case cfg.CSV: case cfg.CSV:
printCSVData(writer, data) printCSVData(writer, conf, data)
default: default:
printASCIIData(writer, conf, data) printASCIIData(writer, conf, data)
} }
@@ -194,6 +194,11 @@ func printMarkdownData(writer io.Writer, conf cfg.Config, data *Tabdata) {
Simple ASCII table without any borders etc, just like the input we expect 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) {
OFS := " "
if conf.OFS != "" {
OFS = conf.OFS
}
tableString := &strings.Builder{} tableString := &strings.Builder{}
styleTSV := tw.NewSymbolCustom("space").WithColumn("\t") styleTSV := tw.NewSymbolCustom("space").WithColumn("\t")
@@ -204,8 +209,8 @@ func printASCIIData(writer io.Writer, conf cfg.Config, data *Tabdata) {
Borders: tw.BorderNone, Borders: tw.BorderNone,
Symbols: styleTSV, Symbols: styleTSV,
Settings: tw.Settings{ Settings: tw.Settings{
Separators: tw.Separators{BetweenRows: tw.Off, BetweenColumns: tw.On}, Separators: tw.SeparatorsNone,
Lines: tw.Lines{ShowFooterLine: tw.Off, ShowHeaderLine: tw.Off}, Lines: tw.LinesNone,
}, },
})), })),
tablewriter.WithConfig(tablewriter.Config{ tablewriter.WithConfig(tablewriter.Config{
@@ -213,23 +218,18 @@ func printASCIIData(writer io.Writer, conf cfg.Config, data *Tabdata) {
Formatting: tw.CellFormatting{ Formatting: tw.CellFormatting{
AutoFormat: tw.Off, AutoFormat: tw.Off,
}, },
Padding: tw.CellPadding{ Padding: tw.CellPadding{Global: tw.Padding{Left: "", Right: OFS}},
Global: tw.Padding{Left: "", Right: ""},
},
}, },
Row: tw.CellConfig{ Row: tw.CellConfig{
Formatting: tw.CellFormatting{ Formatting: tw.CellFormatting{
AutoWrap: tw.WrapNone, AutoWrap: tw.WrapNone,
Alignment: tw.AlignLeft, Alignment: tw.AlignLeft,
}, },
Padding: tw.CellPadding{ Padding: tw.CellPadding{Global: tw.Padding{Right: OFS}},
Global: tw.Padding{Left: "", Right: ""},
},
}, },
Debug: true, Debug: true,
}), }),
tablewriter.WithPadding(tw.PaddingNone),
) )
if !conf.NoHeaders { if !conf.NoHeaders {
@@ -328,8 +328,14 @@ func printYamlData(writer io.Writer, data *Tabdata) {
output(writer, string(yamlstr)) output(writer, string(yamlstr))
} }
func printCSVData(writer io.Writer, data *Tabdata) { func printCSVData(writer io.Writer, conf cfg.Config, data *Tabdata) {
OFS := ","
if conf.OFS != "" {
OFS = conf.OFS
}
csvout := csv.NewWriter(writer) csvout := csv.NewWriter(writer)
csvout.Comma = []rune(OFS)[0]
if err := csvout.Write(data.headers); err != nil { if err := csvout.Write(data.headers); err != nil {
log.Fatalln("error writing record to csv:", err) log.Fatalln("error writing record to csv:", err)

View File

@@ -23,6 +23,7 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/stretchr/testify/assert"
"github.com/tlinden/tablizer/cfg" "github.com/tlinden/tablizer/cfg"
) )
@@ -216,7 +217,7 @@ alpha 2013-Feb-03
ceta 06/Jan/2008 15:04:05 -0700`, ceta 06/Jan/2008 15:04:05 -0700`,
}, },
{ {
name: "usecolumns", name: "usecolumns2",
usecol: []int{2}, usecol: []int{2},
numberize: true, numberize: true,
usecolstr: "2", usecolstr: "2",
@@ -227,7 +228,7 @@ DURATION(2)
33d12h`, 33d12h`,
}, },
{ {
name: "usecolumns", name: "usecolumns3",
usecol: []int{3}, usecol: []int{3},
numberize: true, numberize: true,
usecolstr: "3", usecolstr: "3",
@@ -238,7 +239,7 @@ COUNT(3)
9`, 9`,
}, },
{ {
name: "usecolumns", name: "usecolumns4",
column: 0, column: 0,
usecol: []int{1, 3}, usecol: []int{1, 3},
numberize: true, numberize: true,
@@ -280,6 +281,11 @@ func TestPrinter(t *testing.T) {
Numbering: testdata.numberize, Numbering: testdata.numberize,
UseColumns: testdata.usecol, UseColumns: testdata.usecol,
NoColor: true, NoColor: true,
OFS: " ",
}
if conf.OutputMode == cfg.CSV {
conf.OFS = ","
} }
if testdata.column > 0 { if testdata.column > 0 {
@@ -302,10 +308,7 @@ func TestPrinter(t *testing.T) {
got := strings.TrimSpace(writer.String()) got := strings.TrimSpace(writer.String())
if got != exp { assert.EqualValues(t, exp, got)
t.Errorf("not rendered correctly:\n+++ got:\n%s\n+++ want:\n%s",
got, exp)
}
}) })
} }
} }

View File

@@ -21,6 +21,7 @@ import (
"fmt" "fmt"
"testing" "testing"
"github.com/stretchr/testify/assert"
"github.com/tlinden/tablizer/cfg" "github.com/tlinden/tablizer/cfg"
) )
@@ -41,9 +42,7 @@ func TestDuration2Seconds(t *testing.T) {
testname := fmt.Sprintf("duration-%s", testdata.dur) testname := fmt.Sprintf("duration-%s", testdata.dur)
t.Run(testname, func(t *testing.T) { t.Run(testname, func(t *testing.T) {
seconds := duration2int(testdata.dur) seconds := duration2int(testdata.dur)
if seconds != testdata.expect { assert.EqualValues(t, testdata.expect, seconds)
t.Errorf("got %d, want %d", seconds, testdata.expect)
}
}) })
} }
} }
@@ -74,9 +73,7 @@ func TestCompare(t *testing.T) {
t.Run(testname, func(t *testing.T) { t.Run(testname, func(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 { assert.EqualValues(t, testdata.want, got)
t.Errorf("got %d, want %d", got, testdata.want)
}
}) })
} }
} }

View File

@@ -22,6 +22,7 @@ import (
"fmt" "fmt"
"testing" "testing"
"github.com/stretchr/testify/assert"
"github.com/tiagomelo/go-clipboard/clipboard" "github.com/tiagomelo/go-clipboard/clipboard"
"github.com/tlinden/tablizer/cfg" "github.com/tlinden/tablizer/cfg"
) )
@@ -59,14 +60,9 @@ func DISABLED_TestYankColumns(t *testing.T) {
printData(&writer, conf, &data) printData(&writer, conf, &data)
got, err := cb.PasteText() got, err := cb.PasteText()
if err != nil {
t.Errorf("failed to fetch yanked text from clipboard")
}
if got != testdata.expect { assert.NoError(t, err)
t.Errorf("not yanked correctly:\n+++ got:\n%s\n+++ want:\n%s", assert.EqualValues(t, testdata.expect, got)
got, testdata.expect)
}
}) })
} }
} }

View File

@@ -1,7 +1,10 @@
# usage # usage
exec tablizer -h exec tablizer --help
stdout Usage stdout Usage
exec tablizer -h
stdout show
# version # version
exec tablizer -V exec tablizer -V
stdout version stdout version

View File

@@ -133,7 +133,7 @@
.\" ======================================================================== .\" ========================================================================
.\" .\"
.IX Title "TABLIZER 1" .IX Title "TABLIZER 1"
.TH TABLIZER 1 "2025-09-11" "1" "User Commands" .TH TABLIZER 1 "2025-10-01" "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
@@ -152,12 +152,13 @@ tablizer \- Manipulate tabular output of other programs
\& \-n, \-\-numbering Enable header numbering \& \-n, \-\-numbering Enable 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
\& \-j, \-\-json Read JSON input (must be array of hashes)
\& \-I, \-\-interactive Interactively filter and select rows \& \-I, \-\-interactive Interactively filter and select rows
\& \&
\& Output Flags (mutually exclusive): \& Output Flags (mutually exclusive):
@@ -171,6 +172,7 @@ tablizer \- Manipulate tabular output of other programs
\& \-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, \& \-y, \-\-yank\-columns Yank specified columns (separated by ,) to clipboard,
\& space separated \& space separated
\& \-\-ofs <char> Output field separator, used by \-A and \-C.
\& \&
\& 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
@@ -217,17 +219,17 @@ pattern. Hence:
\& kubectl get pods | tablizer \& kubectl get pods | tablizer
\& \&
\& # read a file \& # read a file
\& tablizer filename \& tablizer \-r filename
\& \&
\& # search for pattern in a file (works like grep) \& # search for pattern in a file (works like grep)
\& tablizer regex filename \& tablizer regex \-r filename
\& \&
\& # search for pattern in STDIN \& # search for pattern in STDIN
\& kubectl get pods | tablizer regex \& kubectl get pods | tablizer regex
.Ve .Ve
.PP .PP
The output looks like the original one but every header field will The output looks like the original one. You can add the option \fB\-n\fR,
have a numer associated with it, e.g.: then every header field will have a numer associated with it, e.g.:
.PP .PP
.Vb 1 .Vb 1
\& NAME(1) READY(2) STATUS(3) RESTARTS(4) AGE(5) \& NAME(1) READY(2) STATUS(3) RESTARTS(4) AGE(5)
@@ -243,7 +245,18 @@ columns you want to have in your output (see \s-1COLUMNS\s0:
You can specify the numbers in any order but output will always follow You can specify the numbers in any order but output will always follow
the original order. the original order.
.PP .PP
The numbering can be suppressed by using the \fB\-n\fR option. However, you may also just use the header names instead of numbers,
eg:
.PP
.Vb 1
\& kubectl get pods | tablizer \-cname,status
.Ve
.PP
You can also use regular expressions with \fB\-c\fR, eg:
.PP
.Vb 1
\& kubectl get pods | tablizer \-c \*(Aq[ae]\*(Aq
.Ve
.PP .PP
By default tablizer shows a header containing the names of each By default tablizer shows a header containing the names of each
column. This can be disabled using the \fB\-H\fR option. Be aware that column. This can be disabled using the \fB\-H\fR option. Be aware that

View File

@@ -13,12 +13,13 @@ tablizer - Manipulate tabular output of other programs
-n, --numbering Enable header numbering -n, --numbering Enable 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
-j, --json Read JSON input (must be array of hashes)
-I, --interactive Interactively filter and select rows -I, --interactive Interactively filter and select rows
Output Flags (mutually exclusive): Output Flags (mutually exclusive):
@@ -32,6 +33,7 @@ tablizer - Manipulate tabular output of other programs
-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, -y, --yank-columns Yank specified columns (separated by ,) to clipboard,
space separated space separated
--ofs <char> Output field separator, used by -A and -C.
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
@@ -78,16 +80,16 @@ pattern. Hence:
kubectl get pods | tablizer kubectl get pods | tablizer
# read a file # read a file
tablizer filename tablizer -r filename
# search for pattern in a file (works like grep) # search for pattern in a file (works like grep)
tablizer regex filename tablizer regex -r filename
# search for pattern in STDIN # search for pattern in STDIN
kubectl get pods | tablizer regex kubectl get pods | tablizer regex
The output looks like the original one but every header field will The output looks like the original one. You can add the option B<-n>,
have a numer associated with it, e.g.: then every header field will have a numer associated with it, e.g.:
NAME(1) READY(2) STATUS(3) RESTARTS(4) AGE(5) NAME(1) READY(2) STATUS(3) RESTARTS(4) AGE(5)
@@ -99,7 +101,14 @@ columns you want to have in your output (see L<COLUMNS>:
You can specify the numbers in any order but output will always follow You can specify the numbers in any order but output will always follow
the original order. the original order.
The numbering can be suppressed by using the B<-n> option. However, you may also just use the header names instead of numbers,
eg:
kubectl get pods | tablizer -cname,status
You can also use regular expressions with B<-c>, eg:
kubectl get pods | tablizer -c '[ae]'
By default tablizer shows a header containing the names of each By default tablizer shows a header containing the names of each
column. This can be disabled using the B<-H> option. Be aware that column. This can be disabled using the B<-H> option. Be aware that