mirror of
https://codeberg.org/scip/tablizer.git
synced 2025-12-18 21:11:03 +01:00
Compare commits
54 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
687f4b7bb2 | ||
| 24b66b8a6b | |||
| d87c6878a4 | |||
| 4cdc4c8e18 | |||
| 9cb9a66332 | |||
| 24277cd716 | |||
| e51b141032 | |||
| 7af7304529 | |||
| b4c833a0ba | |||
| 1c36d93d65 | |||
|
|
ec864f42d6 | ||
|
|
4eaa676510 | ||
|
|
c600fb1136 | ||
|
|
abf9fac5c7 | ||
|
|
80dd6849ae | ||
| e2b82515f5 | |||
|
|
1976b4046e | ||
|
|
b1a2b3059e | ||
|
|
e3d6ef130c | ||
|
|
92fffaae9a | ||
|
|
f1c5ee5797 | ||
|
|
5168b04339 | ||
|
|
787178b17e | ||
|
|
eae39bbff1 | ||
| 40fbf17779 | |||
| 832841c1ff | |||
| 5726ed3f7f | |||
|
|
5e52cd9ce0 | ||
|
|
8c7c89c9ea | ||
| 25aa172c41 | |||
|
|
c436a92bcb | ||
|
|
65732a58d0 | ||
|
|
ace7f76210 | ||
| fda365bd8b | |||
| c1cfc08c23 | |||
| 150fdddd2a | |||
| 6b659773f1 | |||
| 74d82fa356 | |||
| 3949411c57 | |||
| a455f6b79a | |||
| 2c08687c29 | |||
| 200f1f32f8 | |||
| 768a19b4d6 | |||
|
|
dc718392b6 | ||
|
|
e8f4fef41c | ||
| 6566dd66f0 | |||
| 1593799c03 | |||
| ea3dd75fec | |||
| a306f2c601 | |||
| 82f54c120d | |||
|
|
2d5799e2f2 | ||
| 8e33cadcaa | |||
| 03f3225f24 | |||
| 63c7ef26b6 |
96
.gh-dash.yml
Normal file
96
.gh-dash.yml
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
prSections:
|
||||||
|
- title: Responsible PRs
|
||||||
|
filters: repo:tlinden/tablizer is:open NOT dependabot
|
||||||
|
layout:
|
||||||
|
repoName:
|
||||||
|
hidden: true
|
||||||
|
|
||||||
|
- title: Responsible Dependabot PRs
|
||||||
|
filters: repo:tlinden/tablizer is:open dependabot
|
||||||
|
layout:
|
||||||
|
repoName:
|
||||||
|
hidden: true
|
||||||
|
|
||||||
|
issuesSections:
|
||||||
|
- title: Responsible Issues
|
||||||
|
filters: is:open repo:tlinden/tablizer -author:@me
|
||||||
|
layout:
|
||||||
|
repoName:
|
||||||
|
hidden: true
|
||||||
|
|
||||||
|
- title: Note-to-Self Issues
|
||||||
|
filters: is:open repo:tlinden/tablizer author:@me
|
||||||
|
layout:
|
||||||
|
creator:
|
||||||
|
hidden: true
|
||||||
|
repoName:
|
||||||
|
hidden: true
|
||||||
|
|
||||||
|
defaults:
|
||||||
|
preview:
|
||||||
|
open: false
|
||||||
|
width: 100
|
||||||
|
|
||||||
|
keybindings:
|
||||||
|
universal:
|
||||||
|
- key: "shift+down"
|
||||||
|
builtin: pageDown
|
||||||
|
- key: "shift+up"
|
||||||
|
builtin: pageUp
|
||||||
|
prs:
|
||||||
|
- key: g
|
||||||
|
name: gitu
|
||||||
|
command: >
|
||||||
|
cd {{.RepoPath}} && /home/scip/bin/gitu
|
||||||
|
- key: M
|
||||||
|
name: squash-merge
|
||||||
|
command: gh pr merge --rebase --squash --admin --repo {{.RepoName}} {{.PrNumber}}
|
||||||
|
- key: i
|
||||||
|
name: show ci checks
|
||||||
|
command: gh pr checks --repo {{.RepoName}} {{.PrNumber}} | glow -p
|
||||||
|
- key: e
|
||||||
|
name: edit pr
|
||||||
|
command: ~/.config/gh-dash/edit-gh-pr {{.RepoName}} {{.PrNumber}}
|
||||||
|
- key: E
|
||||||
|
name: open repo in emacs
|
||||||
|
command: emacsclient {{.RepoPath}} &
|
||||||
|
issues:
|
||||||
|
- key: v
|
||||||
|
name: view
|
||||||
|
command: gh issue view --repo {{.RepoName}} {{.IssueNumber}} | glow -p
|
||||||
|
- key: l
|
||||||
|
name: add label
|
||||||
|
command: gh issue --repo {{.RepoName}} edit {{.IssueNumber}} --add-label $(gum choose bug enhancement question dependencies wontfix)
|
||||||
|
- key: L
|
||||||
|
name: remove label
|
||||||
|
command: gh issue --repo {{.RepoName}} edit {{.IssueNumber}} --remove-label $(gum choose bug enhancement question dependencies wontfix)
|
||||||
|
- key: E
|
||||||
|
name: open repo in emacs
|
||||||
|
command: emacsclient {{.RepoPath}} &
|
||||||
|
|
||||||
|
theme:
|
||||||
|
ui:
|
||||||
|
sectionsShowCount: true
|
||||||
|
table:
|
||||||
|
compact: false
|
||||||
|
showSeparator: true
|
||||||
|
colors:
|
||||||
|
text:
|
||||||
|
primary: "#E2E1ED"
|
||||||
|
secondary: "#6770cb"
|
||||||
|
inverted: "#242347"
|
||||||
|
faint: "#b0793b"
|
||||||
|
warning: "#E0AF68"
|
||||||
|
success: "#3DF294"
|
||||||
|
background:
|
||||||
|
selected: "#1B1B33"
|
||||||
|
border:
|
||||||
|
primary: "#383B5B"
|
||||||
|
secondary: "#39386B"
|
||||||
|
faint: "#8d3e0b"
|
||||||
|
|
||||||
|
repoPaths:
|
||||||
|
:owner/:repo: ~/dev/:repo
|
||||||
|
|
||||||
|
pager:
|
||||||
|
diff: delta
|
||||||
12
.github/workflows/ci.yaml
vendored
12
.github/workflows/ci.yaml
vendored
@@ -4,10 +4,8 @@ jobs:
|
|||||||
build:
|
build:
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
version: ['1.22']
|
version: ['1.23']
|
||||||
# windows-latest removed, see:
|
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||||
# 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:
|
||||||
@@ -18,7 +16,7 @@ jobs:
|
|||||||
id: go
|
id: go
|
||||||
|
|
||||||
- name: checkout
|
- name: checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: build
|
- name: build
|
||||||
run: make
|
run: make
|
||||||
@@ -32,8 +30,8 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/setup-go@v5
|
- uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: 1.22
|
go-version: 1.23
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
- name: golangci-lint
|
- name: golangci-lint
|
||||||
uses: golangci/golangci-lint-action@v6
|
uses: golangci/golangci-lint-action@v6
|
||||||
with:
|
with:
|
||||||
|
|||||||
87
.github/workflows/release.yaml
vendored
Normal file
87
.github/workflows/release.yaml
vendored
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
name: build-release
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- "v*.*.*"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release:
|
||||||
|
name: Build Release Assets
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
|
- 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}}
|
||||||
7
Makefile
7
Makefile
@@ -53,8 +53,7 @@ buildlocal:
|
|||||||
go build -ldflags "-X 'github.com/tlinden/tablizer/cfg.VERSION=$(VERSION)'"
|
go build -ldflags "-X 'github.com/tlinden/tablizer/cfg.VERSION=$(VERSION)'"
|
||||||
|
|
||||||
release:
|
release:
|
||||||
./mkrel.sh $(tool) $(version)
|
gh release create $(version) --generate-notes
|
||||||
gh release create $(version) --generate-notes releases/*
|
|
||||||
|
|
||||||
install: buildlocal
|
install: buildlocal
|
||||||
install -d -o $(UID) -g $(GID) $(PREFIX)/bin
|
install -d -o $(UID) -g $(GID) $(PREFIX)/bin
|
||||||
@@ -65,12 +64,12 @@ install: buildlocal
|
|||||||
clean:
|
clean:
|
||||||
rm -rf $(tool) releases coverage.out
|
rm -rf $(tool) releases coverage.out
|
||||||
|
|
||||||
test:
|
test: clean
|
||||||
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)
|
go test -run $(TEST) github.com/tlinden/tablizer/$(MOD) $(OPTS)
|
||||||
|
|
||||||
cover-report:
|
cover-report:
|
||||||
go test ./... -cover -coverprofile=coverage.out
|
go test ./... -cover -coverprofile=coverage.out
|
||||||
|
|||||||
28
README.md
28
README.md
@@ -6,25 +6,35 @@
|
|||||||
|
|
||||||
Tablizer can be used to re-format tabular output of other
|
Tablizer can be used to re-format tabular output of other
|
||||||
programs. While you could do this using standard unix tools, in some
|
programs. While you could do this using standard unix tools, in some
|
||||||
cases it's a hard job.
|
cases it's a hard job. With tablizer you can filter by column[s],
|
||||||
|
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
|
||||||
|
interactive filter/selection tool available.
|
||||||
|
|
||||||
|
## Demo
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
Usage:
|
|
||||||
```default
|
```default
|
||||||
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, --no-numbering Disable 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 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
|
||||||
|
-I, --interactive Interactively filter and select rows
|
||||||
|
|
||||||
Output Flags (mutually exclusive):
|
Output Flags (mutually exclusive):
|
||||||
-X, --extended Enable extended output
|
-X, --extended Enable extended output
|
||||||
@@ -35,6 +45,8 @@ Output Flags (mutually exclusive):
|
|||||||
-C, --csv Enable CSV output
|
-C, --csv Enable CSV output
|
||||||
-A, --ascii Default output mode, ascii tabular
|
-A, --ascii Default output mode, ascii tabular
|
||||||
-L, --hightlight-lines Use alternating background colors for tables
|
-L, --hightlight-lines Use alternating background colors for tables
|
||||||
|
-y, --yank-columns Yank specified columns (separated by ,) to clipboard,
|
||||||
|
space separated
|
||||||
|
|
||||||
Sort Mode Flags (mutually exclusive):
|
Sort Mode Flags (mutually exclusive):
|
||||||
-a, --sort-age sort according to age (duration) string
|
-a, --sort-age sort according to age (duration) string
|
||||||
@@ -143,10 +155,6 @@ other separator, for instance: `-R '| |-|'`.
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Demo
|
|
||||||
|
|
||||||
[](https://asciinema.org/a/9FKc3HPnlg8D2X8otheleEa9t)
|
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
There are multiple ways to install **tablizer**:
|
There are multiple ways to install **tablizer**:
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -28,7 +28,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const DefaultSeparator string = `(\s\s+|\t)`
|
const DefaultSeparator string = `(\s\s+|\t)`
|
||||||
const Version string = "v1.3.0"
|
const Version string = "v1.5.4"
|
||||||
const MAXPARTS = 2
|
const MAXPARTS = 2
|
||||||
|
|
||||||
var DefaultConfigfile = os.Getenv("HOME") + "/.config/tablizer/config"
|
var DefaultConfigfile = os.Getenv("HOME") + "/.config/tablizer/config"
|
||||||
@@ -52,24 +52,38 @@ 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
|
||||||
NoNumbering bool
|
Numbering 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
|
||||||
Pattern string
|
Patterns []*Pattern
|
||||||
PatternR *regexp.Regexp
|
|
||||||
UseFuzzySearch bool
|
UseFuzzySearch bool
|
||||||
UseHighlight bool
|
UseHighlight bool
|
||||||
|
Interactive bool
|
||||||
|
|
||||||
SortMode string
|
SortMode string
|
||||||
SortDescending bool
|
SortDescending bool
|
||||||
SortByColumn int
|
SortByColumn string // 1,2
|
||||||
|
UseSortByColumn []int // []int{1,2}
|
||||||
|
|
||||||
TransposeColumns string // 1,2
|
TransposeColumns string // 1,2
|
||||||
UseTransposeColumns []int // []int{1,2}
|
UseTransposeColumns []int // []int{1,2}
|
||||||
@@ -94,7 +108,7 @@ type Config struct {
|
|||||||
|
|
||||||
// used for field filtering
|
// used for field filtering
|
||||||
Rawfilters []string
|
Rawfilters []string
|
||||||
Filters map[string]*regexp.Regexp
|
Filters map[string]Filter //map[string]*regexp.Regexp
|
||||||
|
|
||||||
// -r <file>
|
// -r <file>
|
||||||
InputFile string
|
InputFile string
|
||||||
@@ -264,12 +278,20 @@ func (conf *Config) PrepareModeFlags(flag Modeflag) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (conf *Config) PrepareFilters() error {
|
func (conf *Config) PrepareFilters() error {
|
||||||
conf.Filters = make(map[string]*regexp.Regexp, len(conf.Rawfilters))
|
conf.Filters = make(map[string]Filter, len(conf.Rawfilters))
|
||||||
|
|
||||||
for _, filter := range conf.Rawfilters {
|
for _, rawfilter := range conf.Rawfilters {
|
||||||
parts := strings.Split(filter, "=")
|
filter := Filter{}
|
||||||
|
|
||||||
|
parts := strings.Split(rawfilter, "!=")
|
||||||
if len(parts) != MAXPARTS {
|
if len(parts) != MAXPARTS {
|
||||||
return errors.New("filter field and value must be separated by =")
|
parts = strings.Split(rawfilter, "=")
|
||||||
|
|
||||||
|
if len(parts) != MAXPARTS {
|
||||||
|
return errors.New("filter field and value must be separated by '=' or '!='")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
filter.Negate = true
|
||||||
}
|
}
|
||||||
|
|
||||||
reg, err := regexp.Compile(parts[1])
|
reg, err := regexp.Compile(parts[1])
|
||||||
@@ -278,7 +300,8 @@ func (conf *Config) PrepareFilters() error {
|
|||||||
parts[0], err)
|
parts[0], err)
|
||||||
}
|
}
|
||||||
|
|
||||||
conf.Filters[strings.ToLower(strings.ToLower(parts[0]))] = reg
|
filter.Regex = reg
|
||||||
|
conf.Filters[strings.ToLower(parts[0])] = filter
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -310,10 +333,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.NoNumbering {
|
if !conf.Numbering {
|
||||||
_, set := os.LookupEnv("T_NO_HEADER_NUMBERING")
|
_, set := os.LookupEnv("T_HEADER_NUMBERING")
|
||||||
if set {
|
if set {
|
||||||
conf.NoNumbering = true
|
conf.Numbering = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -328,19 +351,41 @@ 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.NoNumbering = true
|
conf.Numbering = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (conf *Config) PreparePattern(pattern string) error {
|
func (conf *Config) PreparePattern(patterns []*Pattern) error {
|
||||||
PatternR, err := regexp.Compile(pattern)
|
// regex checks if a pattern looks like /$pattern/[i!]
|
||||||
|
flagre := regexp.MustCompile(`^/(.*)/([i!]*)$`)
|
||||||
|
|
||||||
if err != nil {
|
for _, pattern := range patterns {
|
||||||
return fmt.Errorf("regexp pattern %s is invalid: %w", conf.Pattern, err)
|
matches := flagre.FindAllStringSubmatch(pattern.Pattern, -1)
|
||||||
|
|
||||||
|
// we have a regex with flags
|
||||||
|
for _, match := range matches {
|
||||||
|
pattern.Pattern = match[1] // the inner part is our actual pattern
|
||||||
|
flags := match[2] // the flags
|
||||||
|
|
||||||
|
for _, flag := range flags {
|
||||||
|
switch flag {
|
||||||
|
case 'i':
|
||||||
|
pattern.Pattern = `(?i)` + pattern.Pattern
|
||||||
|
case '!':
|
||||||
|
pattern.Negate = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PatternRe, err := regexp.Compile(pattern.Pattern)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("regexp pattern %s is invalid: %w", pattern.Pattern, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pattern.PatternRe = PatternRe
|
||||||
}
|
}
|
||||||
|
|
||||||
conf.PatternR = PatternR
|
conf.Patterns = patterns
|
||||||
conf.Pattern = pattern
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,20 +79,55 @@ func TestPrepareSortFlags(t *testing.T) {
|
|||||||
|
|
||||||
func TestPreparePattern(t *testing.T) {
|
func TestPreparePattern(t *testing.T) {
|
||||||
var tests = []struct {
|
var tests = []struct {
|
||||||
pattern string
|
patterns []*Pattern
|
||||||
wanterr bool
|
name string
|
||||||
|
wanterr bool
|
||||||
|
wanticase bool
|
||||||
|
wantneg bool
|
||||||
}{
|
}{
|
||||||
{"[A-Z]+", false},
|
{
|
||||||
{"[a-z", true},
|
[]*Pattern{{Pattern: "[A-Z]+"}},
|
||||||
|
"simple",
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]*Pattern{{Pattern: "[a-z"}},
|
||||||
|
"regfail",
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]*Pattern{{Pattern: "/[A-Z]+/i"}},
|
||||||
|
"icase",
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]*Pattern{{Pattern: "/[A-Z]+/!"}},
|
||||||
|
"negate",
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]*Pattern{{Pattern: "/[A-Z]+/!i"}},
|
||||||
|
"negicase",
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, testdata := range tests {
|
for _, testdata := range tests {
|
||||||
testname := fmt.Sprintf("PreparePattern-pattern-%s-wanterr-%t",
|
testname := fmt.Sprintf("PreparePattern-pattern-%s-wanterr-%t", testdata.name, testdata.wanterr)
|
||||||
testdata.pattern, testdata.wanterr)
|
|
||||||
t.Run(testname, func(t *testing.T) {
|
t.Run(testname, func(t *testing.T) {
|
||||||
conf := Config{}
|
conf := Config{}
|
||||||
|
|
||||||
err := conf.PreparePattern(testdata.pattern)
|
err := conf.PreparePattern(testdata.patterns)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !testdata.wanterr {
|
if !testdata.wanterr {
|
||||||
|
|||||||
35
cmd/root.go
35
cmd/root.go
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright © 2022-2024 Thomas von Dein
|
Copyright © 2022-2025 Thomas von Dein
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
||||||
@@ -17,12 +17,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@@ -30,24 +27,6 @@ import (
|
|||||||
"github.com/tlinden/tablizer/lib"
|
"github.com/tlinden/tablizer/lib"
|
||||||
)
|
)
|
||||||
|
|
||||||
func man() {
|
|
||||||
man := exec.Command("less", "-")
|
|
||||||
|
|
||||||
var buffer bytes.Buffer
|
|
||||||
|
|
||||||
buffer.Write([]byte(manpage))
|
|
||||||
|
|
||||||
man.Stdout = os.Stdout
|
|
||||||
man.Stdin = &buffer
|
|
||||||
man.Stderr = os.Stderr
|
|
||||||
|
|
||||||
err := man.Run()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func completion(cmd *cobra.Command, mode string) error {
|
func completion(cmd *cobra.Command, mode string) error {
|
||||||
switch mode {
|
switch mode {
|
||||||
case "bash":
|
case "bash":
|
||||||
@@ -94,7 +73,7 @@ func Execute() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ShowManual {
|
if ShowManual {
|
||||||
man()
|
lib.Pager("tablizer manual page", manpage)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -125,7 +104,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.NoNumbering, "no-numbering", "n", false,
|
rootCmd.PersistentFlags().BoolVarP(&conf.Numbering, "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,11 +126,15 @@ 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 ,)")
|
||||||
|
rootCmd.PersistentFlags().BoolVarP(&conf.Interactive, "interactive", "I", false,
|
||||||
|
"interactive mode (experimental)")
|
||||||
|
|
||||||
// sort options
|
// sort options
|
||||||
rootCmd.PersistentFlags().IntVarP(&conf.SortByColumn, "sort-by", "k", 0,
|
rootCmd.PersistentFlags().StringVarP(&conf.SortByColumn, "sort-by", "k", "",
|
||||||
"Sort by column (default: 1)")
|
"Sort by column (default: 1)")
|
||||||
|
|
||||||
// sort mode, only 1 allowed
|
// sort mode, only 1 allowed
|
||||||
@@ -190,7 +173,7 @@ func Execute() {
|
|||||||
|
|
||||||
// filters
|
// filters
|
||||||
rootCmd.PersistentFlags().StringArrayVarP(&conf.Rawfilters,
|
rootCmd.PersistentFlags().StringArrayVarP(&conf.Rawfilters,
|
||||||
"filter", "F", nil, "Filter by field (field=regexp)")
|
"filter", "F", nil, "Filter by field (field=regexp || field!=regexp)")
|
||||||
rootCmd.PersistentFlags().StringArrayVarP(&conf.Transposers,
|
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")
|
||||||
|
|
||||||
|
|||||||
143
cmd/tablizer.go
143
cmd/tablizer.go
@@ -6,20 +6,21 @@ NAME
|
|||||||
|
|
||||||
SYNOPSIS
|
SYNOPSIS
|
||||||
Usage:
|
Usage:
|
||||||
tablizer [regex] [file, ...] [flags]
|
tablizer [regex,...] [-r 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, --no-numbering Disable 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 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
|
||||||
|
-I, --interactive Interactively filter and select rows
|
||||||
|
|
||||||
Output Flags (mutually exclusive):
|
Output Flags (mutually exclusive):
|
||||||
-X, --extended Enable extended output
|
-X, --extended Enable extended output
|
||||||
@@ -30,6 +31,8 @@ SYNOPSIS
|
|||||||
-C, --csv Enable CSV output
|
-C, --csv Enable CSV output
|
||||||
-A, --ascii Default output mode, ascii tabular
|
-A, --ascii Default output mode, ascii tabular
|
||||||
-L, --hightlight-lines Use alternating background colors for tables
|
-L, --hightlight-lines Use alternating background colors for tables
|
||||||
|
-y, --yank-columns Yank specified columns (separated by ,) to clipboard,
|
||||||
|
space separated
|
||||||
|
|
||||||
Sort Mode Flags (mutually exclusive):
|
Sort Mode Flags (mutually exclusive):
|
||||||
-a, --sort-age sort according to age (duration) string
|
-a, --sort-age sort according to age (duration) string
|
||||||
@@ -38,12 +41,13 @@ SYNOPSIS
|
|||||||
-t, --sort-time sort according to time string
|
-t, --sort-time sort according to time string
|
||||||
|
|
||||||
Other Flags:
|
Other Flags:
|
||||||
--completion <shell> Generate the autocompletion script for <shell>
|
-r --read-file <file> Use <file> as input instead of STDIN
|
||||||
-f, --config <file> Configuration file (default: ~/.config/tablizer/config)
|
--completion <shell> Generate the autocompletion script for <shell>
|
||||||
-d, --debug Enable debugging
|
-f, --config <file> Configuration file (default: ~/.config/tablizer/config)
|
||||||
-h, --help help for tablizer
|
-d, --debug Enable debugging
|
||||||
-m, --man Display manual page
|
-h, --help help for tablizer
|
||||||
-V, --version Print program version
|
-m, --man Display manual page
|
||||||
|
-V, --version Print program version
|
||||||
|
|
||||||
DESCRIPTION
|
DESCRIPTION
|
||||||
Many programs generate tabular output. But sometimes you need to
|
Many programs generate tabular output. But sometimes you need to
|
||||||
@@ -103,10 +107,19 @@ DESCRIPTION
|
|||||||
highlighted. You can disable this behavior with the -N option.
|
highlighted. You can disable this behavior with the -N option.
|
||||||
|
|
||||||
Use the -k option to specify by which column to sort the tabular data
|
Use the -k option to specify by which column to sort the tabular data
|
||||||
(as in GNU sort(1)). The default sort column is the first one. To
|
(as in GNU sort(1)). The default sort column is the first one. You can
|
||||||
disable sorting at all, supply 0 (Zero) to -k. The default sort order is
|
specify column numbers or names. Column numbers start with 1, names are
|
||||||
ascending. You can change this to descending order using the option -D.
|
case insensitive. You can specify multiple columns separated by comma to
|
||||||
The default sort order is by string, but there are other sort modes:
|
sort, but the type must be the same. For example if you want to sort
|
||||||
|
numerically, all columns must be numbers. If you use column numbers,
|
||||||
|
then be aware, that these are the numbers before column extraction. For
|
||||||
|
example if you have a table with 4 columns and specify "-c4", then only
|
||||||
|
1 column (the fourth) will be printed, however if you want to sort by
|
||||||
|
this column, you'll have to specify "-k4".
|
||||||
|
|
||||||
|
The default sort order is ascending. You can change this to descending
|
||||||
|
order using the option -D. The default sort order is by alphanumeric
|
||||||
|
string, but there are other sort modes:
|
||||||
|
|
||||||
-a --sort-age
|
-a --sort-age
|
||||||
Sorts duration strings like "1d4h32m51s".
|
Sorts duration strings like "1d4h32m51s".
|
||||||
@@ -121,30 +134,43 @@ DESCRIPTION
|
|||||||
for the developer.
|
for the developer.
|
||||||
|
|
||||||
PATTERNS AND FILTERING
|
PATTERNS AND FILTERING
|
||||||
You can reduce the rows being displayed by using a regular expression
|
You can reduce the rows being displayed by using one or more regular
|
||||||
pattern. The regexp is PCRE compatible, refer to the syntax cheat sheet
|
expression patterns. The regexp language being used is the one of
|
||||||
here: <https://github.com/google/re2/wiki/Syntax>. If you want to read a
|
GOLANG, refer to the syntax cheat sheet here:
|
||||||
more comprehensive documentation about the topic and have perl installed
|
<https://pkg.go.dev/regexp/syntax>.
|
||||||
you can read it with:
|
|
||||||
|
If you want to read a more comprehensive documentation about the topic
|
||||||
|
and have perl installed you can read it with:
|
||||||
|
|
||||||
perldoc perlre
|
perldoc perlre
|
||||||
|
|
||||||
Or read it online: <https://perldoc.perl.org/perlre>.
|
Or read it online: <https://perldoc.perl.org/perlre>. But please note
|
||||||
|
that the GO regexp engine does NOT support all perl regex terms,
|
||||||
|
especially look-ahead and look-behind.
|
||||||
|
|
||||||
A note on modifiers: the regexp engine used in tablizer uses another
|
If you want to supply flags to a regex, then surround it with slashes
|
||||||
modifier syntax:
|
and append the flag. The following flags are supported:
|
||||||
|
|
||||||
(?MODIFIER)
|
i => case insensitive
|
||||||
|
! => negative match
|
||||||
The most important modifiers are:
|
|
||||||
|
|
||||||
"i" ignore case "m" multiline mode "s" single line mode
|
|
||||||
|
|
||||||
Example for a case insensitive search:
|
Example for a case insensitive search:
|
||||||
|
|
||||||
kubectl get pods -A | tablizer "(?i)account"
|
kubectl get pods -A | tablizer "/account/i"
|
||||||
|
|
||||||
You can use the experimental fuzzy search feature by providing the
|
If you use the "!" flag, then the regex match will be negated, that is,
|
||||||
|
if a line in the input matches the given regex, but "!" is supplied,
|
||||||
|
tablizer will NOT include it in the output.
|
||||||
|
|
||||||
|
For example, here we want to get all lines matching "foo" but not "bar":
|
||||||
|
|
||||||
|
cat table | tablizer foo '/bar/!'
|
||||||
|
|
||||||
|
This would match a line "foo zorro" but not "foo bar".
|
||||||
|
|
||||||
|
The flags can also be combined.
|
||||||
|
|
||||||
|
You can also use the experimental fuzzy search feature by providing the
|
||||||
option -z, in which case the pattern is regarded as a fuzzy search term,
|
option -z, in which case the pattern is regarded as a fuzzy search term,
|
||||||
not a regexp.
|
not a regexp.
|
||||||
|
|
||||||
@@ -159,8 +185,26 @@ 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.
|
||||||
|
|
||||||
|
INTERACTIVE FILTERING
|
||||||
|
You can also use the interactive mode, enabled with "-I" to filter and
|
||||||
|
select rows. This mode is complementary, that is, other filter options
|
||||||
|
are still being respected.
|
||||||
|
|
||||||
|
To enter e filter, hit "/", enter a filter string and finish with
|
||||||
|
"ENTER". Use "SPACE" to select/deselect rows, use "a" to select all
|
||||||
|
(visible) rows.
|
||||||
|
|
||||||
|
Commit your selection with "q". The selected rows are being fed to the
|
||||||
|
requested output mode as usual. Abort with "CTRL-c", in which case the
|
||||||
|
results of the interactive mode are being ignored and all rows are being
|
||||||
|
fed to output.
|
||||||
|
|
||||||
COLUMNS
|
COLUMNS
|
||||||
The parameter -c can be used to specify, which columns to display. By
|
The parameter -c can be used to specify, which columns to display. By
|
||||||
default tablizer numerizes the header names and these numbers can be
|
default tablizer numerizes the header names and these numbers can be
|
||||||
@@ -258,12 +302,24 @@ 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_NO_HEADER_NUMBERING> - disable numbering of header fields, like -n.
|
<T_HEADER_NUMBERING> - enable 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
|
||||||
|
|
||||||
@@ -376,6 +432,9 @@ LICENSE
|
|||||||
Released under the MIT License, Copyright (c) 2006-2011 Kirill
|
Released under the MIT License, Copyright (c) 2006-2011 Kirill
|
||||||
Simonov
|
Simonov
|
||||||
|
|
||||||
|
bubble-table (https://github.com/Evertras/bubble-table)
|
||||||
|
Released under the MIT License, Copyright (c) 2022 Brandon Fulljames
|
||||||
|
|
||||||
AUTHORS
|
AUTHORS
|
||||||
Thomas von Dein tom AT vondein DOT org
|
Thomas von Dein tom AT vondein DOT org
|
||||||
|
|
||||||
@@ -383,20 +442,21 @@ AUTHORS
|
|||||||
var usage = `
|
var usage = `
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
tablizer [regex] [file, ...] [flags]
|
tablizer [regex,...] [-r 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, --no-numbering Disable 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 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
|
||||||
|
-I, --interactive Interactively filter and select rows
|
||||||
|
|
||||||
Output Flags (mutually exclusive):
|
Output Flags (mutually exclusive):
|
||||||
-X, --extended Enable extended output
|
-X, --extended Enable extended output
|
||||||
@@ -407,6 +467,8 @@ Output Flags (mutually exclusive):
|
|||||||
-C, --csv Enable CSV output
|
-C, --csv Enable CSV output
|
||||||
-A, --ascii Default output mode, ascii tabular
|
-A, --ascii Default output mode, ascii tabular
|
||||||
-L, --hightlight-lines Use alternating background colors for tables
|
-L, --hightlight-lines Use alternating background colors for tables
|
||||||
|
-y, --yank-columns Yank specified columns (separated by ,) to clipboard,
|
||||||
|
space separated
|
||||||
|
|
||||||
Sort Mode Flags (mutually exclusive):
|
Sort Mode Flags (mutually exclusive):
|
||||||
-a, --sort-age sort according to age (duration) string
|
-a, --sort-age sort according to age (duration) string
|
||||||
@@ -415,12 +477,13 @@ 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:
|
||||||
--completion <shell> Generate the autocompletion script for <shell>
|
-r --read-file <file> Use <file> as input instead of STDIN
|
||||||
-f, --config <file> Configuration file (default: ~/.config/tablizer/config)
|
--completion <shell> Generate the autocompletion script for <shell>
|
||||||
-d, --debug Enable debugging
|
-f, --config <file> Configuration file (default: ~/.config/tablizer/config)
|
||||||
-h, --help help for tablizer
|
-d, --debug Enable debugging
|
||||||
-m, --man Display manual page
|
-h, --help help for tablizer
|
||||||
-V, --version Print program version
|
-m, --man Display manual page
|
||||||
|
-V, --version Print program version
|
||||||
|
|
||||||
|
|
||||||
`
|
`
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
all:
|
|
||||||
LC_ALL=en_US.UTF-8 asciinema rec --cols 50 --row 30 -c ./demo.sh --overwrite tmp.cast
|
|
||||||
agg tmp.cast tmp.gif
|
|
||||||
31
demo/demo.sh
31
demo/demo.sh
@@ -1,31 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
prompt() {
|
|
||||||
if test -n "$1"; then
|
|
||||||
echo
|
|
||||||
echo -n "% $*"
|
|
||||||
sleep 1
|
|
||||||
echo
|
|
||||||
$*
|
|
||||||
echo
|
|
||||||
echo -n "% "
|
|
||||||
else
|
|
||||||
echo -n "% "
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
PATH=..:$PATH
|
|
||||||
clear
|
|
||||||
while IFS=$'\t' read -r flags table msg source _; do
|
|
||||||
echo "#"
|
|
||||||
echo "# source tabular data:"
|
|
||||||
cat $table
|
|
||||||
echo
|
|
||||||
echo "#"
|
|
||||||
echo "# $msg:"
|
|
||||||
prompt "tablizer $flags $table"
|
|
||||||
|
|
||||||
sleep 4
|
|
||||||
clear
|
|
||||||
done < <(yq -r tables.yaml \
|
|
||||||
| yq -r '.tables[] | [.flags, .table, .msg, .source] | @tsv')
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
NAME DURATION COUNT WHEN
|
|
||||||
beta 1d10h5m1s 33 3/1/2014
|
|
||||||
alpha 4h35m 170 2013-Feb-03
|
|
||||||
ceta 33d12h 9 06/Jan/2008 15:04:05 -0700
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
PID TTY TIME CMD
|
|
||||||
30912 pts/0 00:00:00 bash
|
|
||||||
49526 pts/0 00:00:00 ps
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
tables:
|
|
||||||
# OUTPUTS
|
|
||||||
- flags: -A
|
|
||||||
table: table.demo1
|
|
||||||
msg: default output mode
|
|
||||||
- flags: -O
|
|
||||||
table: table.demo1
|
|
||||||
msg: orgmode output mode
|
|
||||||
- flags: -M
|
|
||||||
table: table.demo1
|
|
||||||
msg: markdown output mode
|
|
||||||
- flags: -S
|
|
||||||
table: table.demo1
|
|
||||||
msg: shell output mode
|
|
||||||
- flags: -X
|
|
||||||
table: table.demo1
|
|
||||||
msg: extended output mode
|
|
||||||
- flags: -Y
|
|
||||||
table: table.demo1
|
|
||||||
msg: yaml output mode
|
|
||||||
- flags: -C
|
|
||||||
table: table.demo1
|
|
||||||
msg: CSV output mode
|
|
||||||
|
|
||||||
# SORTS
|
|
||||||
- flags: -A -k 3
|
|
||||||
table: table.demo1
|
|
||||||
msg: sort by column 3
|
|
||||||
- flags: -A -k 4 -t
|
|
||||||
table: table.demo1
|
|
||||||
msg: sort by column 4 and sort type time
|
|
||||||
- flags: -A -k 2 -a
|
|
||||||
table: table.demo1
|
|
||||||
msg: sort by column 2 and sort type duration
|
|
||||||
|
|
||||||
# REDUCE
|
|
||||||
- flags: -A -c 1,3
|
|
||||||
table: table.demo1
|
|
||||||
msg: only display column 1 and 3
|
|
||||||
- flags: -A -c AM,RA
|
|
||||||
table: table.demo1
|
|
||||||
msg: only display columns matching /(RA|AM)/
|
|
||||||
- flags: -X -c 1,3
|
|
||||||
table: table.demo1
|
|
||||||
msg: only display column 1 and 3 in extended mode
|
|
||||||
|
|
||||||
# SEARCH
|
|
||||||
- flags: /20 -A
|
|
||||||
table: table.demo1
|
|
||||||
msg: only show rows matching /20
|
|
||||||
- flags: /20 -A -v
|
|
||||||
table: table.demo1
|
|
||||||
msg: only show rows NOT matching /20
|
|
||||||
|
|
||||||
@@ -1,119 +0,0 @@
|
|||||||
{"version": 2, "width": 80, "height": 25, "timestamp": 1666890777, "env": {"SHELL": "/bin/bash", "TERM": "xterm-256color"}}
|
|
||||||
[0.004618, "o", "\u001b[H\u001b[2J\u001b[3J"]
|
|
||||||
[0.010297, "o", "#\r\n# source tabular data:\r\n"]
|
|
||||||
[0.010898, "o", "NAME DURATION COUNT WHEN\r\nbeta 1d10h5m1s 33 3/1/2014\r\nalpha 4h35m 170 2013-Feb-03\r\nceta 33d12h 9 06/Jan/2008 15:04:05 -0700\r\n"]
|
|
||||||
[0.011125, "o", "\r\n#\r\n"]
|
|
||||||
[0.011177, "o", "# default output mode:\r\n"]
|
|
||||||
[0.011219, "o", "\r\n% tablizer -A table.demo1"]
|
|
||||||
[1.011851, "o", "\r\n"]
|
|
||||||
[1.013635, "o", "NAME(1)\tDURATION(2)\tCOUNT(3)\tWHEN(4) \r\nbeta \t1d10h5m1s \t33 \t3/1/2014 \t\r\nalpha \t4h35m \t170 \t2013-Feb-03 \t\r\nceta \t33d12h \t9 \t06/Jan/2008 15:04:05 -0700\t\r\n"]
|
|
||||||
[1.014021, "o", "\r\n% "]
|
|
||||||
[5.015241, "o", "\u001b[H\u001b[2J\u001b[3J"]
|
|
||||||
[5.015339, "o", "#\r\n# source tabular data:\r\n"]
|
|
||||||
[5.015688, "o", "NAME DURATION COUNT WHEN\r\nbeta 1d10h5m1s 33 3/1/2014\r\nalpha 4h35m 170 2013-Feb-03\r\nceta 33d12h 9 06/Jan/2008 15:04:05 -0700\r\n"]
|
|
||||||
[5.015776, "o", "\r\n#\r\n# orgmode output mode:\r\n\r\n% tablizer -O table.demo1"]
|
|
||||||
[6.016322, "o", "\r\n"]
|
|
||||||
[6.01823, "o", "+---------+-------------+----------+----------------------------+\r\n| NAME(1) | DURATION(2) | COUNT(3) | WHEN(4) |\r\n+---------+-------------+----------+----------------------------+\r\n| beta | 1d10h5m1s | 33 | 3/1/2014 |\r\n| alpha | 4h35m | 170 | 2013-Feb-03 |\r\n| ceta | 33d12h | 9 | 06/Jan/2008 15:04:05 -0700 |\r\n+---------+-------------+----------+----------------------------+\r\n"]
|
|
||||||
[6.018497, "o", "\r\n% "]
|
|
||||||
[10.020014, "o", "\u001b[H\u001b[2J\u001b[3J"]
|
|
||||||
[10.020112, "o", "#\r\n# source tabular data:\r\n"]
|
|
||||||
[10.020573, "o", "NAME DURATION COUNT WHEN\r\nbeta 1d10h5m1s 33 3/1/2014\r\nalpha 4h35m 170 2013-Feb-03\r\nceta 33d12h 9 06/Jan/2008 15:04:05 -0700\r\n"]
|
|
||||||
[10.020643, "o", "\r\n#\r\n"]
|
|
||||||
[10.02068, "o", "# markdown output mode:\r\n\r\n% tablizer -M table.demo1"]
|
|
||||||
[11.021559, "o", "\r\n"]
|
|
||||||
[11.023551, "o", "| NAME(1) | DURATION(2) | COUNT(3) | WHEN(4) |\r\n|---------|-------------|----------|----------------------------|\r\n| beta | 1d10h5m1s | 33 | 3/1/2014 |\r\n| alpha | 4h35m | 170 | 2013-Feb-03 |\r\n| ceta | 33d12h | 9 | 06/Jan/2008 15:04:05 -0700 |\r\n"]
|
|
||||||
[11.023838, "o", "\r\n% "]
|
|
||||||
[15.025244, "o", "\u001b[H\u001b[2J\u001b[3J"]
|
|
||||||
[15.025345, "o", "#\r\n# source tabular data:\r\n"]
|
|
||||||
[15.025829, "o", "NAME DURATION COUNT WHEN\r\nbeta 1d10h5m1s 33 3/1/2014\r\nalpha 4h35m 170 2013-Feb-03\r\nceta 33d12h 9 06/Jan/2008 15:04:05 -0700\r\n"]
|
|
||||||
[15.025915, "o", "\r\n#\r\n# shell output mode:\r\n"]
|
|
||||||
[15.025931, "o", "\r\n"]
|
|
||||||
[15.025948, "o", "% tablizer -S table.demo1"]
|
|
||||||
[16.026714, "o", "\r\n"]
|
|
||||||
[16.028606, "o", "NAME(1)=\"beta\" DURATION(2)=\"1d10h5m1s\" COUNT(3)=\"33\" WHEN(4)=\"3/1/2014\"\r\nNAME(1)=\"alpha\" DURATION(2)=\"4h35m\" COUNT(3)=\"170\" WHEN(4)=\"2013-Feb-03\"\r\nNAME(1)=\"ceta\" DURATION(2)=\"33d12h\" COUNT(3)=\"9\" WHEN(4)=\"06/Jan/2008 15:04:05 -0700\"\r\n"]
|
|
||||||
[16.029144, "o", "\r\n% "]
|
|
||||||
[20.030593, "o", "\u001b[H\u001b[2J\u001b[3J"]
|
|
||||||
[20.030706, "o", "#\r\n# source tabular data:\r\n"]
|
|
||||||
[20.03121, "o", "NAME DURATION COUNT WHEN\r\nbeta 1d10h5m1s 33 3/1/2014\r\nalpha 4h35m 170 2013-Feb-03\r\nceta 33d12h 9 06/Jan/2008 15:04:05 -0700\r\n"]
|
|
||||||
[20.031277, "o", "\r\n#\r\n# extended output mode:\r\n"]
|
|
||||||
[20.031327, "o", "\r\n% tablizer -X table.demo1"]
|
|
||||||
[21.032053, "o", "\r\n"]
|
|
||||||
[21.033787, "o", " NAME(1): beta\r\nDURATION(2): 1d10h5m1s\r\n COUNT(3): 33\r\n WHEN(4): 3/1/2014\r\n\r\n NAME(1): alpha\r\nDURATION(2): 4h35m\r\n COUNT(3): 170\r\n WHEN(4): 2013-Feb-03\r\n\r\n NAME(1): ceta\r\nDURATION(2): 33d12h\r\n COUNT(3): 9\r\n WHEN(4): 06/Jan/2008 15:04:05 -0700\r\n\r\n"]
|
|
||||||
[21.034132, "o", "\r\n% "]
|
|
||||||
[25.035531, "o", "\u001b[H\u001b[2J\u001b[3J"]
|
|
||||||
[25.035585, "o", "#\r\n"]
|
|
||||||
[25.035681, "o", "# source tabular data:\r\n"]
|
|
||||||
[25.036179, "o", "NAME DURATION COUNT WHEN\r\nbeta 1d10h5m1s 33 3/1/2014\r\nalpha 4h35m 170 2013-Feb-03\r\nceta 33d12h 9 06/Jan/2008 15:04:05 -0700\r\n"]
|
|
||||||
[25.036232, "o", "\r\n#\r\n"]
|
|
||||||
[25.036274, "o", "# yaml output mode:\r\n\r\n% tablizer -Y table.demo1"]
|
|
||||||
[26.036928, "o", "\r\n"]
|
|
||||||
[26.038674, "o", "entries:\r\n - count: 33\r\n duration: \"1d10h5m1s\"\r\n name: \"beta\"\r\n when: \"3/1/2014\"\r\n - count: 170\r\n duration: \"4h35m\"\r\n name: \"alpha\"\r\n when: \"2013-Feb-03\"\r\n - count: 9\r\n duration: \"33d12h\"\r\n name: \"ceta\"\r\n when: \"06/Jan/2008 15:04:05 -0700\"\r\n"]
|
|
||||||
[26.038975, "o", "\r\n% "]
|
|
||||||
[30.040539, "o", "\u001b[H\u001b[2J\u001b[3J"]
|
|
||||||
[30.040659, "o", "#\r\n# source tabular data:\r\n"]
|
|
||||||
[30.041167, "o", "NAME DURATION COUNT WHEN\r\nbeta 1d10h5m1s 33 3/1/2014\r\nalpha 4h35m 170 2013-Feb-03\r\nceta 33d12h 9 06/Jan/2008 15:04:05 -0700\r\n"]
|
|
||||||
[30.041246, "o", "\r\n#\r\n# CSV output mode:\r\n\r\n% tablizer -C table.demo1"]
|
|
||||||
[31.042088, "o", "\r\n"]
|
|
||||||
[31.043721, "o", "NAME,DURATION,COUNT,WHEN\r\nbeta,1d10h5m1s,33,3/1/2014\r\nalpha,4h35m,170,2013-Feb-03\r\nceta,33d12h,9,06/Jan/2008 15:04:05 -0700\r\n"]
|
|
||||||
[31.043997, "o", "\r\n% "]
|
|
||||||
[35.045523, "o", "\u001b[H\u001b[2J\u001b[3J"]
|
|
||||||
[35.04563, "o", "#\r\n# source tabular data:\r\n"]
|
|
||||||
[35.046209, "o", "NAME DURATION COUNT WHEN\r\nbeta 1d10h5m1s 33 3/1/2014\r\nalpha 4h35m 170 2013-Feb-03\r\nceta 33d12h 9 06/Jan/2008 15:04:05 -0700\r\n"]
|
|
||||||
[35.046275, "o", "\r\n#\r\n# sort by column 3:\r\n\r\n% tablizer -A -k 3 table.demo1"]
|
|
||||||
[36.047083, "o", "\r\n"]
|
|
||||||
[36.048793, "o", "NAME(1)\tDURATION(2)\tCOUNT(3)\tWHEN(4) \r\nalpha \t4h35m \t170 \t2013-Feb-03 \t\r\nbeta \t1d10h5m1s \t33 \t3/1/2014 \t\r\nceta \t33d12h \t9 \t06/Jan/2008 15:04:05 -0700\t\r\n"]
|
|
||||||
[36.049077, "o", "\r\n% "]
|
|
||||||
[40.050739, "o", "\u001b[H\u001b[2J\u001b[3J"]
|
|
||||||
[40.050925, "o", "#\r\n# source tabular data:\r\n"]
|
|
||||||
[40.051481, "o", "NAME DURATION COUNT WHEN\r\nbeta 1d10h5m1s 33 3/1/2014\r\nalpha 4h35m 170 2013-Feb-03\r\nceta 33d12h 9 06/Jan/2008 15:04:05 -0700\r\n"]
|
|
||||||
[40.051671, "o", "\r\n#\r\n# sort by column 4 and sort type time:\r\n\r\n% tablizer -A -k 4 -t table.demo1"]
|
|
||||||
[41.052486, "o", "\r\n"]
|
|
||||||
[41.05454, "o", "NAME(1)\tDURATION(2)\tCOUNT(3)\tWHEN(4) \r\nceta \t33d12h \t9 \t06/Jan/2008 15:04:05 -0700\t\r\nalpha \t4h35m \t170 \t2013-Feb-03 \t\r\nbeta \t1d10h5m1s \t33 \t3/1/2014 \t\r\n"]
|
|
||||||
[41.054864, "o", "\r\n% "]
|
|
||||||
[45.056297, "o", "\u001b[H\u001b[2J\u001b[3J"]
|
|
||||||
[45.056405, "o", "#\r\n# source tabular data:\r\n"]
|
|
||||||
[45.056895, "o", "NAME DURATION COUNT WHEN\r\nbeta 1d10h5m1s 33 3/1/2014\r\nalpha 4h35m 170 2013-Feb-03\r\nceta 33d12h 9 06/Jan/2008 15:04:05 -0700\r\n"]
|
|
||||||
[45.056978, "o", "\r\n#\r\n"]
|
|
||||||
[45.057023, "o", "# sort by column 2 and sort type duration:\r\n"]
|
|
||||||
[45.057073, "o", "\r\n% tablizer -A -k 2 -a table.demo1"]
|
|
||||||
[46.057895, "o", "\r\n"]
|
|
||||||
[46.059684, "o", "NAME(1)\tDURATION(2)\tCOUNT(3)\tWHEN(4) \r\nalpha \t4h35m \t170 \t2013-Feb-03 \t\r\nbeta \t1d10h5m1s \t33 \t3/1/2014 \t\r\nceta \t33d12h \t9 \t06/Jan/2008 15:04:05 -0700\t\r\n"]
|
|
||||||
[46.059988, "o", "\r\n% "]
|
|
||||||
[50.061514, "o", "\u001b[H\u001b[2J\u001b[3J"]
|
|
||||||
[50.061622, "o", "#\r\n# source tabular data:\r\n"]
|
|
||||||
[50.062091, "o", "NAME DURATION COUNT WHEN\r\nbeta 1d10h5m1s 33 3/1/2014\r\nalpha 4h35m 170 2013-Feb-03\r\nceta 33d12h 9 06/Jan/2008 15:04:05 -0700\r\n"]
|
|
||||||
[50.062188, "o", "\r\n#\r\n# only display column 1 and 3:\r\n\r\n% tablizer -A -c 1,3 table.demo1"]
|
|
||||||
[51.062985, "o", "\r\n"]
|
|
||||||
[51.066293, "o", "NAME(1)\tCOUNT(3) \r\nbeta \t33 \t\r\nalpha \t170 \t\r\nceta \t9 \t\r\n"]
|
|
||||||
[51.066843, "o", "\r\n% "]
|
|
||||||
[55.070781, "o", "\u001b[H\u001b[2J\u001b[3J"]
|
|
||||||
[55.071327, "o", "#\r\n# source tabular data:\r\n"]
|
|
||||||
[55.073499, "o", "NAME DURATION COUNT WHEN\r\nbeta 1d10h5m1s 33 3/1/2014\r\nalpha 4h35m 170 2013-Feb-03\r\nceta 33d12h 9 06/Jan/2008 15:04:05 -0700\r\n"]
|
|
||||||
[55.073822, "o", "\r\n#\r\n# only display columns matching /(RA|AM)/:\r\n"]
|
|
||||||
[55.074188, "o", "\r\n% tablizer -A -c AM,RA table.demo1"]
|
|
||||||
[56.07636, "o", "\r\n"]
|
|
||||||
[56.078603, "o", "NAME(1)\tDURATION(2) \r\nbeta \t1d10h5m1s \t\r\nalpha \t4h35m \t\r\nceta \t33d12h \t\r\n"]
|
|
||||||
[56.078957, "o", "\r\n% "]
|
|
||||||
[60.080574, "o", "\u001b[H\u001b[2J\u001b[3J"]
|
|
||||||
[60.080734, "o", "#\r\n# source tabular data:\r\n"]
|
|
||||||
[60.081286, "o", "NAME DURATION COUNT WHEN\r\nbeta 1d10h5m1s 33 3/1/2014\r\nalpha 4h35m 170 2013-Feb-03\r\nceta 33d12h 9 06/Jan/2008 15:04:05 -0700\r\n"]
|
|
||||||
[60.081418, "o", "\r\n#\r\n# only display column 1 and 3 in extended mode:\r\n\r\n% tablizer -X -c 1,3 table.demo1"]
|
|
||||||
[61.082844, "o", "\r\n"]
|
|
||||||
[61.089822, "o", " NAME(1): beta\r\nCOUNT(3): 33\r\n\r\n NAME(1): alpha\r\nCOUNT(3): 170\r\n\r\n NAME(1): ceta\r\nCOUNT(3): 9\r\n\r\n"]
|
|
||||||
[61.090969, "o", "\r\n% "]
|
|
||||||
[65.096092, "o", "\u001b[H\u001b[2J\u001b[3J"]
|
|
||||||
[65.096571, "o", "#\r\n# source tabular data:\r\n"]
|
|
||||||
[65.098736, "o", "NAME DURATION COUNT WHEN\r\nbeta 1d10h5m1s 33 3/1/2014\r\nalpha 4h35m 170 2013-Feb-03\r\nceta 33d12h 9 06/Jan/2008 15:04:05 -0700\r\n"]
|
|
||||||
[65.099085, "o", "\r\n#\r\n# only show rows matching /20:\r\n"]
|
|
||||||
[65.099283, "o", "\r\n% tablizer /20 -A table.demo1"]
|
|
||||||
[66.101537, "o", "\r\n"]
|
|
||||||
[66.109112, "o", "NAME(1)\tDURATION(2)\tCOUNT(3)\tWHEN(4) \r\nbeta \t1d10h5m1s \t33 \t3/1\u001b[102;30m/20\u001b[0m14 \t\r\nceta \t33d12h \t9 \t06/Jan\u001b[102;30m/20\u001b[0m08 15:04:05 -0700\t\r\n"]
|
|
||||||
[66.109405, "o", "\r\n% "]
|
|
||||||
[70.11076, "o", "\u001b[H\u001b[2J\u001b[3J"]
|
|
||||||
[70.110873, "o", "#\r\n# source tabular data:\r\n"]
|
|
||||||
[70.111365, "o", "NAME DURATION COUNT WHEN\r\nbeta 1d10h5m1s 33 3/1/2014\r\nalpha 4h35m 170 2013-Feb-03\r\nceta 33d12h 9 06/Jan/2008 15:04:05 -0700\r\n"]
|
|
||||||
[70.111469, "o", "\r\n#\r\n# only show rows NOT matching /20:\r\n\r\n% tablizer /20 -A -v table.demo1"]
|
|
||||||
[71.112738, "o", "\r\n"]
|
|
||||||
[71.120032, "o", "NAME(1)\tDURATION(2)\tCOUNT(3)\tWHEN(4) \r\nalpha \t4h35m \t170 \t2013-Feb-03\t\r\n"]
|
|
||||||
[71.121127, "o", "\r\n% "]
|
|
||||||
[75.126199, "o", "\u001b[H\u001b[2J\u001b[3J"]
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 243 KiB |
60
go.mod
60
go.mod
@@ -1,34 +1,60 @@
|
|||||||
module github.com/tlinden/tablizer
|
module github.com/tlinden/tablizer
|
||||||
|
|
||||||
go 1.22
|
go 1.23.0
|
||||||
|
|
||||||
|
toolchain go1.23.5
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/alecthomas/repr v0.4.0
|
github.com/alecthomas/repr v0.5.1
|
||||||
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de
|
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de
|
||||||
github.com/gookit/color v1.5.4
|
github.com/charmbracelet/bubbles v0.21.0
|
||||||
github.com/hashicorp/hcl/v2 v2.23.0
|
github.com/charmbracelet/bubbletea v1.3.6
|
||||||
|
github.com/charmbracelet/lipgloss v1.1.0
|
||||||
|
github.com/evertras/bubble-table v0.19.0
|
||||||
|
github.com/gookit/color v1.6.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 v0.0.5
|
github.com/olekukonko/tablewriter v1.0.9
|
||||||
github.com/rogpeppe/go-internal v1.13.1
|
github.com/rogpeppe/go-internal v1.14.1
|
||||||
github.com/spf13/cobra v1.8.1
|
github.com/spf13/cobra v1.9.1
|
||||||
|
github.com/tiagomelo/go-clipboard v0.1.2
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
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/v15 v15.0.0 // indirect
|
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
|
||||||
|
github.com/atotto/clipboard v0.1.4 // indirect
|
||||||
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||||
|
github.com/charmbracelet/colorprofile v0.3.1 // indirect
|
||||||
|
github.com/charmbracelet/x/ansi v0.9.3 // indirect
|
||||||
|
github.com/charmbracelet/x/cellbuf v0.0.13 // indirect
|
||||||
|
github.com/charmbracelet/x/term v0.2.1 // indirect
|
||||||
|
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // 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/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-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 v1.0.1 // indirect
|
||||||
github.com/rivo/uniseg v0.2.0 // indirect
|
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
|
||||||
github.com/spf13/pflag v1.0.5 // indirect
|
github.com/muesli/cancelreader v0.2.2 // indirect
|
||||||
|
github.com/muesli/reflow v0.3.0 // indirect
|
||||||
|
github.com/muesli/termenv v0.16.0 // indirect
|
||||||
|
github.com/olekukonko/errors v1.1.0 // indirect
|
||||||
|
github.com/olekukonko/ll v0.0.9 // 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.16.3 // indirect
|
||||||
golang.org/x/mod v0.18.0 // indirect
|
golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect
|
||||||
golang.org/x/sync v0.7.0 // indirect
|
golang.org/x/mod v0.21.0 // indirect
|
||||||
golang.org/x/sys v0.21.0 // indirect
|
golang.org/x/sync v0.15.0 // indirect
|
||||||
golang.org/x/text v0.11.0 // indirect
|
golang.org/x/sys v0.33.0 // indirect
|
||||||
golang.org/x/tools v0.22.0 // indirect
|
golang.org/x/text v0.25.0 // indirect
|
||||||
|
golang.org/x/tools v0.26.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
121
go.sum
121
go.sum
@@ -1,69 +1,118 @@
|
|||||||
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.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
|
github.com/alecthomas/repr v0.5.1 h1:E3G4t2QbHTSNpPKBgMTln5KLkZHLOcU7r37J4pXBuIg=
|
||||||
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
github.com/alecthomas/repr v0.5.1/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
||||||
github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw=
|
|
||||||
github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo=
|
|
||||||
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=
|
||||||
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.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
|
||||||
|
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
|
||||||
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||||
|
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/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.6/go.mod h1:oQD9VCRQFF8KplacJLo28/jofOI2ToOfGYeFgBBxHOc=
|
||||||
|
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/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
|
||||||
|
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.9.3/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE=
|
||||||
|
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/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
|
||||||
|
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
|
||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||||
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/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/evertras/bubble-table v0.19.0 h1:+JlXRUjNuBN1JI7XU1PapmW1wglbcqZUKkiPnVKPgrc=
|
||||||
|
github.com/evertras/bubble-table v0.19.0/go.mod h1:ifHujS1YxwnYSOgcR2+m3GnJ84f7CVU/4kUOxUCjEbQ=
|
||||||
|
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=
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0=
|
github.com/gookit/assert v0.1.1 h1:lh3GcawXe/p+cU7ESTZ5Ui3Sm/x8JWpIis4/1aF0mY0=
|
||||||
github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w=
|
github.com/gookit/assert v0.1.1/go.mod h1:jS5bmIVQZTIwk42uXl4lyj4iaaxx32tqH16CFj0VX2E=
|
||||||
github.com/hashicorp/hcl/v2 v2.23.0 h1:Fphj1/gCylPxHutVSEOf2fBOh1VE4AuLV7+kbJf3qos=
|
github.com/gookit/color v1.6.0 h1:JjJXBTk1ETNyqyilJhkTXJYYigHG24TM9Xa2M1xAhRA=
|
||||||
github.com/hashicorp/hcl/v2 v2.23.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA=
|
github.com/gookit/color v1.6.0/go.mod h1:9ACFc7/1IpHGBW8RwuDm/0YEnhg3dwwXpoMsmtyHfjs=
|
||||||
|
github.com/hashicorp/hcl/v2 v2.24.0 h1:2QJdZ454DSsYGoaE6QheQZjtKZSUs9Nh2izTWiwQxvE=
|
||||||
|
github.com/hashicorp/hcl/v2 v2.24.0/go.mod h1:oGoO1FIQYfn/AgyOhlg9qLC6/nOJPX3qGbkZpYAcqfM=
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||||
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-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||||
|
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||||
|
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.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
|
||||||
|
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
|
||||||
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.12/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 v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
|
||||||
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
|
github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
|
||||||
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
|
||||||
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
|
||||||
|
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
|
||||||
|
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
|
||||||
|
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
|
||||||
|
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
|
||||||
|
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
|
||||||
|
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
|
||||||
|
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.9 h1:Y+1YqDfVkqMWuEQMclsF9HUR5+a82+dxJuL1HHSRpxI=
|
||||||
|
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.0.9/go.mod h1:5c+EBPeSqvXnLLgkm9isDdzR3wjfBkHR9Nhfp3NWrzo=
|
||||||
|
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/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||||
|
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.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
|
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
|
||||||
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
|
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
|
||||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
|
||||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
github.com/spf13/pflag v1.0.6/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=
|
||||||
github.com/zclconf/go-cty v1.13.3 h1:m+b9q3YDbg6Bec5rr+KGy1MzEVzY/jC2X+YX4yqKtHI=
|
github.com/zclconf/go-cty v1.16.3 h1:osr++gw2T61A8KVYHoQiFbFd1Lh3JOCXc/jFLJXKTxk=
|
||||||
github.com/zclconf/go-cty v1.13.3/go.mod h1:YKQzy/7pZ7iq2jNFzy5go57xdxdWoLLpaEp4u238AE0=
|
github.com/zclconf/go-cty v1.16.3/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE=
|
||||||
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo=
|
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo=
|
||||||
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM=
|
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E=
|
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
|
||||||
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
|
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
|
||||||
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.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
|
golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
|
||||||
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||||
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=
|
||||||
@@ -71,16 +120,18 @@ 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.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
|
||||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||||
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-20210809222454-d867a43fc93e/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.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.21.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=
|
||||||
@@ -89,14 +140,14 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
|
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
|
||||||
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
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.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
|
golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ=
|
||||||
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
|
golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
|
||||||
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=
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -27,15 +27,46 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* [!]Match a line, use fuzzy search for normal pattern strings and
|
* [!]Match a line, use fuzzy search for normal pattern strings and
|
||||||
* regexp otherwise.
|
* regexp otherwise.
|
||||||
*/
|
|
||||||
|
'foo bar' foo, /bar/! => false => line contains foo and not (not bar)
|
||||||
|
'foo nix' foo, /bar/! => ture => line contains foo and (not bar)
|
||||||
|
'foo bar' foo, /bar/ => true => line contains both foo and bar
|
||||||
|
'foo nix' foo, /bar/ => false => line does not contain bar
|
||||||
|
'foo bar' foo, /nix/ => false => line does not contain nix
|
||||||
|
*/
|
||||||
func matchPattern(conf cfg.Config, line string) bool {
|
func matchPattern(conf cfg.Config, line string) bool {
|
||||||
if conf.UseFuzzySearch {
|
if len(conf.Patterns) == 0 {
|
||||||
return fuzzy.MatchFold(conf.Pattern, line)
|
// any line always matches ""
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
return conf.PatternR.MatchString(line)
|
if conf.UseFuzzySearch {
|
||||||
|
// fuzzy search only considers the 1st pattern
|
||||||
|
return fuzzy.MatchFold(conf.Patterns[0].Pattern, line)
|
||||||
|
}
|
||||||
|
|
||||||
|
var match int
|
||||||
|
|
||||||
|
//fmt.Printf("<%s>\n", line)
|
||||||
|
for _, re := range conf.Patterns {
|
||||||
|
patmatch := re.PatternRe.MatchString(line)
|
||||||
|
if re.Negate {
|
||||||
|
// toggle the meaning of match
|
||||||
|
patmatch = !patmatch
|
||||||
|
}
|
||||||
|
|
||||||
|
if patmatch {
|
||||||
|
match++
|
||||||
|
}
|
||||||
|
|
||||||
|
//fmt.Printf("patmatch: %t, match: %d, pattern: %s, negate: %t\n", patmatch, match, re.Pattern, re.Negate)
|
||||||
|
}
|
||||||
|
|
||||||
|
// fmt.Printf("result: %t\n", match == len(conf.Patterns))
|
||||||
|
//fmt.Println()
|
||||||
|
return match == len(conf.Patterns)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -55,15 +86,19 @@ 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 {
|
||||||
if !Exists(conf.Filters, strings.ToLower(header)) {
|
lcheader := strings.ToLower(header)
|
||||||
|
if !Exists(conf.Filters, lcheader) {
|
||||||
// do not filter by unspecified field
|
// do not filter by unspecified field
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if !conf.Filters[strings.ToLower(header)].MatchString(row[idx]) {
|
match := conf.Filters[lcheader].Regex.MatchString(row[idx])
|
||||||
// there IS a filter, but it doesn't match
|
if conf.Filters[lcheader].Negate {
|
||||||
keep = false
|
match = !match
|
||||||
|
}
|
||||||
|
|
||||||
|
if !match {
|
||||||
|
keep = false
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -123,8 +158,11 @@ func Exists[K comparable, V any](m map[K]V, v K) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Filters the whole input lines, returns filtered lines
|
||||||
|
*/
|
||||||
func FilterByPattern(conf cfg.Config, input io.Reader) (io.Reader, error) {
|
func FilterByPattern(conf cfg.Config, input io.Reader) (io.Reader, error) {
|
||||||
if conf.Pattern == "" {
|
if len(conf.Patterns) == 0 {
|
||||||
return input, nil
|
return input, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,7 +174,7 @@ func FilterByPattern(conf cfg.Config, input io.Reader) (io.Reader, error) {
|
|||||||
line := strings.TrimSpace(scanner.Text())
|
line := strings.TrimSpace(scanner.Text())
|
||||||
if hadFirst {
|
if hadFirst {
|
||||||
// don't match 1st line, it's the header
|
// don't match 1st line, it's the header
|
||||||
if conf.Pattern != "" && matchPattern(conf, line) == conf.InvertMatch {
|
if matchPattern(conf, line) == conf.InvertMatch {
|
||||||
// by default -v is false, so if a line does NOT
|
// by default -v is false, so if a line does NOT
|
||||||
// match the pattern, we will ignore it. However,
|
// match the pattern, we will ignore it. However,
|
||||||
// if the user specified -v, the matching is inverted,
|
// if the user specified -v, the matching is inverted,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright © 2024 Thomas von Dein
|
Copyright © 2024-2025 Thomas von Dein
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
||||||
pattern string
|
patterns []*cfg.Pattern
|
||||||
line string
|
line string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "normal",
|
name: "normal",
|
||||||
pattern: "haus",
|
patterns: []*cfg.Pattern{{Pattern: "haus"}},
|
||||||
line: "hausparty",
|
line: "hausparty",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "fuzzy",
|
name: "fuzzy",
|
||||||
pattern: "hpt",
|
patterns: []*cfg.Pattern{{Pattern: "hpt"}},
|
||||||
line: "haus-party-termin",
|
line: "haus-party-termin",
|
||||||
fuzzy: true,
|
fuzzy: true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,7 +55,7 @@ func TestMatchPattern(t *testing.T) {
|
|||||||
conf.UseFuzzySearch = true
|
conf.UseFuzzySearch = true
|
||||||
}
|
}
|
||||||
|
|
||||||
err := conf.PreparePattern(inputdata.pattern)
|
err := conf.PreparePattern(inputdata.patterns)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("PreparePattern returned error: %s", err)
|
t.Errorf("PreparePattern returned error: %s", err)
|
||||||
}
|
}
|
||||||
@@ -98,6 +98,20 @@ func TestFilterByFields(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "one-field-negative",
|
||||||
|
filter: []string{"one!=asd"},
|
||||||
|
expect: Tabdata{
|
||||||
|
headers: []string{
|
||||||
|
"ONE", "TWO", "THREE",
|
||||||
|
},
|
||||||
|
entries: [][]string{
|
||||||
|
{"19191", "EDD 1", "x"},
|
||||||
|
{"8d8", "AN 1", "y"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
name: "one-field-inverted",
|
name: "one-field-inverted",
|
||||||
filter: []string{"one=19"},
|
filter: []string{"one=19"},
|
||||||
|
|||||||
@@ -77,6 +77,14 @@ func PrepareColumns(conf *cfg.Config, data *Tabdata) error {
|
|||||||
|
|
||||||
conf.UseColumns = usecolumns
|
conf.UseColumns = usecolumns
|
||||||
|
|
||||||
|
// -y columns
|
||||||
|
useyankcolumns, err := PrepareColumnVars(conf.YankColumns, data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
conf.UseYankColumns = useyankcolumns
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,6 +107,19 @@ func PrepareTransposerColumns(conf *cfg.Config, data *Tabdata) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// output option, prepare -k1,2 sort fields
|
||||||
|
func PrepareSortColumns(conf *cfg.Config, data *Tabdata) error {
|
||||||
|
// -c columns
|
||||||
|
usecolumns, err := PrepareColumnVars(conf.SortByColumn, data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
conf.UseSortByColumn = usecolumns
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func PrepareColumnVars(columns string, data *Tabdata) ([]int, error) {
|
func PrepareColumnVars(columns string, data *Tabdata) ([]int, error) {
|
||||||
if columns == "" {
|
if columns == "" {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
@@ -187,13 +208,13 @@ func numberizeAndReduceHeaders(conf cfg.Config, data *Tabdata) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if conf.NoNumbering {
|
if conf.Numbering {
|
||||||
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 {
|
||||||
@@ -233,17 +254,6 @@ 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 {
|
||||||
@@ -280,12 +290,20 @@ func colorizeData(conf cfg.Config, output string) string {
|
|||||||
|
|
||||||
return colorized
|
return colorized
|
||||||
|
|
||||||
case len(conf.Pattern) > 0 && !conf.NoColor && color.IsConsole(os.Stdout):
|
case len(conf.Patterns) > 0 && !conf.NoColor && color.IsConsole(os.Stdout):
|
||||||
r := regexp.MustCompile("(" + conf.Pattern + ")")
|
out := output
|
||||||
|
|
||||||
return r.ReplaceAllStringFunc(output, func(in string) string {
|
for _, re := range conf.Patterns {
|
||||||
return conf.ColorStyle.Sprint(in)
|
if !re.Negate {
|
||||||
})
|
r := regexp.MustCompile("(" + re.Pattern + ")")
|
||||||
|
|
||||||
|
out = r.ReplaceAllStringFunc(out, func(in string) string {
|
||||||
|
return conf.ColorStyle.Sprint(in)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return out
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return output
|
return output
|
||||||
|
|||||||
@@ -216,21 +216,21 @@ func TestNumberizeHeaders(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var tests = []struct {
|
var tests = []struct {
|
||||||
expect []string
|
expect []string
|
||||||
columns []int
|
columns []int
|
||||||
nonum bool
|
numberize bool
|
||||||
}{
|
}{
|
||||||
{[]string{"ONE(1)", "TWO(2)", "THREE(3)"}, []int{1, 2, 3}, false},
|
{[]string{"ONE(1)", "TWO(2)", "THREE(3)"}, []int{1, 2, 3}, true},
|
||||||
{[]string{"ONE(1)", "TWO(2)"}, []int{1, 2}, false},
|
{[]string{"ONE(1)", "TWO(2)"}, []int{1, 2}, true},
|
||||||
{[]string{"ONE", "TWO"}, []int{1, 2}, true},
|
{[]string{"ONE", "TWO"}, []int{1, 2}, false},
|
||||||
}
|
}
|
||||||
|
|
||||||
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.nonum)
|
testdata.columns, testdata.numberize)
|
||||||
|
|
||||||
t.Run(testname, func(t *testing.T) {
|
t.Run(testname, func(t *testing.T) {
|
||||||
conf := cfg.Config{Columns: "x", UseColumns: testdata.columns, NoNumbering: testdata.nonum}
|
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) {
|
if !reflect.DeepEqual(usedata.headers, testdata.expect) {
|
||||||
|
|||||||
34
lib/io.go
34
lib/io.go
@@ -29,13 +29,13 @@ import (
|
|||||||
const RWRR = 0755
|
const RWRR = 0755
|
||||||
|
|
||||||
func ProcessFiles(conf *cfg.Config, args []string) error {
|
func ProcessFiles(conf *cfg.Config, args []string) error {
|
||||||
fd, pattern, err := determineIO(conf, args)
|
fd, patterns, err := determineIO(conf, args)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := conf.PreparePattern(pattern); err != nil {
|
if err := conf.PreparePattern(patterns); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,19 +48,33 @@ func ProcessFiles(conf *cfg.Config, args []string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = PrepareSortColumns(conf, &data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
err = PrepareColumns(conf, &data)
|
err = PrepareColumns(conf, &data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if conf.Interactive {
|
||||||
|
newdata, err := tableEditor(conf, &data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
data = *newdata
|
||||||
|
}
|
||||||
|
|
||||||
printData(os.Stdout, *conf, &data)
|
printData(os.Stdout, *conf, &data)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func determineIO(conf *cfg.Config, args []string) (io.Reader, string, error) {
|
func determineIO(conf *cfg.Config, args []string) (io.Reader, []*cfg.Pattern, error) {
|
||||||
var filehandle io.Reader
|
var filehandle io.Reader
|
||||||
var pattern string
|
var patterns []*cfg.Pattern
|
||||||
var haveio bool
|
var haveio bool
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
@@ -71,7 +85,7 @@ func determineIO(conf *cfg.Config, args []string) (io.Reader, string, error) {
|
|||||||
fd, err := os.OpenFile(conf.InputFile, os.O_RDONLY, RWRR)
|
fd, err := os.OpenFile(conf.InputFile, os.O_RDONLY, RWRR)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", fmt.Errorf("failed to read input file %s: %w", conf.InputFile, err)
|
return nil, nil, fmt.Errorf("failed to read input file %s: %w", conf.InputFile, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
filehandle = fd
|
filehandle = fd
|
||||||
@@ -88,13 +102,15 @@ func determineIO(conf *cfg.Config, args []string) (io.Reader, string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(args) > 0 {
|
if len(args) > 0 {
|
||||||
pattern = args[0]
|
patterns = make([]*cfg.Pattern, len(args))
|
||||||
conf.Pattern = args[0]
|
for i, arg := range args {
|
||||||
|
patterns[i] = &cfg.Pattern{Pattern: arg}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !haveio {
|
if !haveio {
|
||||||
return nil, "", errors.New("no file specified and nothing to read on stdin")
|
return nil, nil, errors.New("no file specified and nothing to read on stdin")
|
||||||
}
|
}
|
||||||
|
|
||||||
return filehandle, pattern, nil
|
return filehandle, patterns, nil
|
||||||
}
|
}
|
||||||
|
|||||||
120
lib/pager.go
Normal file
120
lib/pager.go
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
package lib
|
||||||
|
|
||||||
|
// pager setup using bubbletea
|
||||||
|
// file shamlelessly copied from:
|
||||||
|
// https://github.com/charmbracelet/bubbletea/tree/main/examples/pager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/charmbracelet/bubbles/viewport"
|
||||||
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
|
"github.com/charmbracelet/lipgloss"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
titleStyle = func() lipgloss.Style {
|
||||||
|
b := lipgloss.RoundedBorder()
|
||||||
|
b.Right = "├"
|
||||||
|
return lipgloss.NewStyle().BorderStyle(b).Padding(0, 1)
|
||||||
|
}()
|
||||||
|
|
||||||
|
infoStyle = func() lipgloss.Style {
|
||||||
|
b := lipgloss.RoundedBorder()
|
||||||
|
b.Left = "┤"
|
||||||
|
return titleStyle.BorderStyle(b)
|
||||||
|
}()
|
||||||
|
)
|
||||||
|
|
||||||
|
type Doc struct {
|
||||||
|
content string
|
||||||
|
title string
|
||||||
|
ready bool
|
||||||
|
viewport viewport.Model
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m Doc) Init() tea.Cmd {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m Doc) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
|
var (
|
||||||
|
cmd tea.Cmd
|
||||||
|
cmds []tea.Cmd
|
||||||
|
)
|
||||||
|
|
||||||
|
switch msg := msg.(type) {
|
||||||
|
case tea.KeyMsg:
|
||||||
|
if k := msg.String(); k == "ctrl+c" || k == "q" || k == "esc" {
|
||||||
|
return m, tea.Quit
|
||||||
|
}
|
||||||
|
|
||||||
|
case tea.WindowSizeMsg:
|
||||||
|
headerHeight := lipgloss.Height(m.headerView())
|
||||||
|
footerHeight := lipgloss.Height(m.footerView())
|
||||||
|
verticalMarginHeight := headerHeight + footerHeight
|
||||||
|
|
||||||
|
if !m.ready {
|
||||||
|
// Since this program is using the full size of the viewport we
|
||||||
|
// need to wait until we've received the window dimensions before
|
||||||
|
// we can initialize the viewport. The initial dimensions come in
|
||||||
|
// quickly, though asynchronously, which is why we wait for them
|
||||||
|
// here.
|
||||||
|
m.viewport = viewport.New(msg.Width, msg.Height-verticalMarginHeight)
|
||||||
|
m.viewport.YPosition = headerHeight
|
||||||
|
m.viewport.SetContent(m.content)
|
||||||
|
m.ready = true
|
||||||
|
} else {
|
||||||
|
m.viewport.Width = msg.Width
|
||||||
|
m.viewport.Height = msg.Height - verticalMarginHeight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle keyboard and mouse events in the viewport
|
||||||
|
m.viewport, cmd = m.viewport.Update(msg)
|
||||||
|
cmds = append(cmds, cmd)
|
||||||
|
|
||||||
|
return m, tea.Batch(cmds...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m Doc) View() string {
|
||||||
|
if !m.ready {
|
||||||
|
return "\n Initializing..."
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s\n%s\n%s", m.headerView(), m.viewport.View(), m.footerView())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m Doc) headerView() string {
|
||||||
|
// title := titleStyle.Render("RPN Help Overview")
|
||||||
|
title := titleStyle.Render(m.title)
|
||||||
|
line := strings.Repeat("─", max(0, m.viewport.Width-lipgloss.Width(title)))
|
||||||
|
return lipgloss.JoinHorizontal(lipgloss.Center, title, line)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m Doc) footerView() string {
|
||||||
|
info := infoStyle.Render(fmt.Sprintf("%3.f%%", m.viewport.ScrollPercent()*100))
|
||||||
|
line := strings.Repeat("─", max(0, m.viewport.Width-lipgloss.Width(info)))
|
||||||
|
return lipgloss.JoinHorizontal(lipgloss.Center, line, info)
|
||||||
|
}
|
||||||
|
|
||||||
|
func max(a, b int) int {
|
||||||
|
if a > b {
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func Pager(title, message string) {
|
||||||
|
p := tea.NewProgram(
|
||||||
|
Doc{content: message, title: title},
|
||||||
|
tea.WithAltScreen(), // use the full size of the terminal in its "alternate screen buffer"
|
||||||
|
tea.WithMouseCellMotion(), // turn on mouse support so we can track the mouse wheel
|
||||||
|
)
|
||||||
|
|
||||||
|
if _, err := p.Run(); err != nil {
|
||||||
|
fmt.Println("could not run pager:", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -137,7 +137,7 @@ func parseTabular(conf cfg.Config, input io.Reader) (Tabdata, error) {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// data processing
|
// data processing
|
||||||
if conf.Pattern != "" && matchPattern(conf, line) == conf.InvertMatch {
|
if matchPattern(conf, line) == conf.InvertMatch {
|
||||||
// by default -v is false, so if a line does NOT
|
// by default -v is false, so if a line does NOT
|
||||||
// match the pattern, we will ignore it. However,
|
// match the pattern, we will ignore it. However,
|
||||||
// if the user specified -v, the matching is inverted,
|
// if the user specified -v, the matching is inverted,
|
||||||
|
|||||||
@@ -83,36 +83,42 @@ func TestParser(t *testing.T) {
|
|||||||
|
|
||||||
func TestParserPatternmatching(t *testing.T) {
|
func TestParserPatternmatching(t *testing.T) {
|
||||||
var tests = []struct {
|
var tests = []struct {
|
||||||
entries [][]string
|
name string
|
||||||
pattern string
|
entries [][]string
|
||||||
invert bool
|
patterns []*cfg.Pattern
|
||||||
want bool
|
invert bool
|
||||||
|
want bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
|
name: "match",
|
||||||
entries: [][]string{
|
entries: [][]string{
|
||||||
{"asd", "igig", "cxxxncnc"},
|
{"asd", "igig", "cxxxncnc"},
|
||||||
},
|
},
|
||||||
pattern: "ig",
|
patterns: []*cfg.Pattern{{Pattern: "ig"}},
|
||||||
invert: false,
|
invert: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
name: "invert",
|
||||||
entries: [][]string{
|
entries: [][]string{
|
||||||
{"19191", "EDD 1", "X"},
|
{"19191", "EDD 1", "X"},
|
||||||
},
|
},
|
||||||
pattern: "ig",
|
patterns: []*cfg.Pattern{{Pattern: "ig"}},
|
||||||
invert: true,
|
invert: true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, inputdata := range input {
|
for _, inputdata := range input {
|
||||||
for _, testdata := range tests {
|
for _, testdata := range tests {
|
||||||
testname := fmt.Sprintf("parse-%s-with-pattern-%s-inverted-%t",
|
testname := fmt.Sprintf("parse-%s-with-pattern-%s-inverted-%t",
|
||||||
inputdata.name, testdata.pattern, testdata.invert)
|
inputdata.name, testdata.name, testdata.invert)
|
||||||
t.Run(testname, func(t *testing.T) {
|
t.Run(testname, func(t *testing.T) {
|
||||||
conf := cfg.Config{InvertMatch: testdata.invert, Pattern: testdata.pattern,
|
conf := cfg.Config{
|
||||||
Separator: inputdata.separator}
|
InvertMatch: testdata.invert,
|
||||||
|
Patterns: testdata.patterns,
|
||||||
|
Separator: inputdata.separator,
|
||||||
|
}
|
||||||
|
|
||||||
_ = conf.PreparePattern(testdata.pattern)
|
_ = conf.PreparePattern(testdata.patterns)
|
||||||
|
|
||||||
readFd := strings.NewReader(strings.TrimSpace(inputdata.text))
|
readFd := strings.NewReader(strings.TrimSpace(inputdata.text))
|
||||||
gotdata, err := Parse(conf, readFd)
|
gotdata, err := Parse(conf, readFd)
|
||||||
@@ -125,7 +131,7 @@ func TestParserPatternmatching(t *testing.T) {
|
|||||||
} else {
|
} else {
|
||||||
if !reflect.DeepEqual(testdata.entries, gotdata.entries) {
|
if !reflect.DeepEqual(testdata.entries, gotdata.entries) {
|
||||||
t.Errorf("Parser returned invalid data (pattern: %s, invert: %t)\nExp: %+v\nGot: %+v\n",
|
t.Errorf("Parser returned invalid data (pattern: %s, invert: %t)\nExp: %+v\nGot: %+v\n",
|
||||||
testdata.pattern, testdata.invert, testdata.entries, gotdata.entries)
|
testdata.name, testdata.invert, testdata.entries, gotdata.entries)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
196
lib/printer.go
196
lib/printer.go
@@ -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
|
||||||
@@ -22,26 +22,32 @@ 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"
|
||||||
)
|
)
|
||||||
|
|
||||||
func printData(writer io.Writer, conf cfg.Config, data *Tabdata) {
|
func printData(writer io.Writer, conf cfg.Config, data *Tabdata) {
|
||||||
// add numbers to headers and remove this we're not interested in
|
// Sort the data first, before headers+entries are being
|
||||||
|
// reduced. That way the user can specify any valid column to sort
|
||||||
|
// by, independently if it's being used for display or not.
|
||||||
|
sortTable(conf, data)
|
||||||
|
|
||||||
|
// put one or more columns into clipboard
|
||||||
|
yankColumns(conf, data)
|
||||||
|
|
||||||
|
// add numbers to headers and remove those we're not interested in
|
||||||
numberizeAndReduceHeaders(conf, data)
|
numberizeAndReduceHeaders(conf, data)
|
||||||
|
|
||||||
// remove unwanted columns, if any
|
// remove unwanted columns, if any
|
||||||
reduceColumns(conf, data)
|
reduceColumns(conf, data)
|
||||||
|
|
||||||
// sort the data
|
|
||||||
sortTable(conf, data)
|
|
||||||
|
|
||||||
switch conf.OutputMode {
|
switch conf.OutputMode {
|
||||||
case cfg.Extended:
|
case cfg.Extended:
|
||||||
printExtendedData(writer, conf, data)
|
printExtendedData(writer, conf, data)
|
||||||
@@ -71,36 +77,58 @@ 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.SetHeader(data.headers)
|
table.Header(data.headers)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, row := range data.entries {
|
if err := table.Bulk(data.entries); err != nil {
|
||||||
table.Append(trimRow(row))
|
log.Fatalf("Failed to add data to table renderer: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
table.Render()
|
if err := table.Render(); err != nil {
|
||||||
|
log.Fatalf("Failed to render table: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
/* fix output for org-mode (orgtbl)
|
output(writer, color.Sprint(colorizeData(conf, tableString.String())))
|
||||||
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(), "|"), "|"))))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -108,20 +136,57 @@ 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.SetHeader(data.headers)
|
table.Header(data.headers)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, row := range data.entries {
|
if err := table.Bulk(data.entries); err != nil {
|
||||||
table.Append(trimRow(row))
|
log.Fatalf("Failed to add data to table renderer: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
table.SetBorders(tablewriter.Border{Left: true, Top: false, Right: true, Bottom: false})
|
if err := table.Render(); err != nil {
|
||||||
table.SetCenterSeparator("|")
|
log.Fatalf("Failed to render table: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
table.Render()
|
|
||||||
output(writer, color.Sprint(colorizeData(conf, tableString.String())))
|
output(writer, color.Sprint(colorizeData(conf, tableString.String())))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,33 +195,50 @@ 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)
|
|
||||||
|
styleTSV := tw.NewSymbolCustom("space").WithColumn("\t")
|
||||||
|
|
||||||
|
table := tablewriter.NewTable(tableString,
|
||||||
|
tablewriter.WithRenderer(
|
||||||
|
renderer.NewBlueprint(tw.Rendition{
|
||||||
|
Borders: tw.BorderNone,
|
||||||
|
Symbols: styleTSV,
|
||||||
|
Settings: tw.Settings{
|
||||||
|
Separators: tw.SeparatorsNone,
|
||||||
|
Lines: tw.LinesNone,
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
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{Right: " "}},
|
||||||
|
},
|
||||||
|
|
||||||
|
Debug: true,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
if !conf.NoHeaders {
|
if !conf.NoHeaders {
|
||||||
table.SetHeader(data.headers)
|
table.Header(data.headers)
|
||||||
}
|
}
|
||||||
|
|
||||||
table.AppendBulk(data.entries)
|
if err := table.Bulk(data.entries); err != nil {
|
||||||
|
log.Fatalf("Failed to add data to table renderer: %s", err)
|
||||||
table.SetAutoWrapText(false)
|
}
|
||||||
table.SetAutoFormatHeaders(true)
|
|
||||||
table.SetHeaderAlignment(tablewriter.ALIGN_LEFT)
|
if err := table.Render(); err != nil {
|
||||||
table.SetAlignment(tablewriter.ALIGN_LEFT)
|
log.Fatalf("Failed to render table: %s", err)
|
||||||
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())))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -63,9 +63,9 @@ var tests = []struct {
|
|||||||
name string // so we can identify which one fails, can be the same
|
name string // so we can identify which one fails, can be the same
|
||||||
// for multiple tests, because flags will be appended to the name
|
// for multiple tests, because flags will be appended to the name
|
||||||
sortby string // empty == default
|
sortby string // empty == default
|
||||||
column int // sort by this column, 0 == default first or NO Sort
|
column int // sort by this column (numbers start by 1)
|
||||||
desc bool // sort in descending order, default == ascending
|
desc bool // sort in descending order, default == ascending
|
||||||
nonum bool // hide numbering
|
numberize bool // add header 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,17 +73,19 @@ var tests = []struct {
|
|||||||
}{
|
}{
|
||||||
// --------------------- Default settings mode tests ``
|
// --------------------- Default settings mode tests ``
|
||||||
{
|
{
|
||||||
mode: cfg.ASCII,
|
mode: cfg.ASCII,
|
||||||
name: "default",
|
numberize: true,
|
||||||
|
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
|
||||||
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`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
mode: cfg.CSV,
|
mode: cfg.CSV,
|
||||||
name: "csv",
|
numberize: false,
|
||||||
|
name: "csv",
|
||||||
expect: `
|
expect: `
|
||||||
NAME,DURATION,COUNT,WHEN
|
NAME,DURATION,COUNT,WHEN
|
||||||
beta,1d10h5m1s,33,3/1/2014
|
beta,1d10h5m1s,33,3/1/2014
|
||||||
@@ -91,40 +93,42 @@ 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: "default",
|
name: "orgtbl",
|
||||||
mode: cfg.Orgtbl,
|
numberize: true,
|
||||||
|
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: "default",
|
name: "markdown",
|
||||||
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: "default",
|
name: "shell",
|
||||||
mode: cfg.Shell,
|
mode: cfg.Shell,
|
||||||
nonum: true,
|
numberize: false,
|
||||||
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: "default",
|
name: "yaml",
|
||||||
mode: cfg.Yaml,
|
mode: cfg.Yaml,
|
||||||
nonum: true,
|
numberize: false,
|
||||||
expect: `
|
expect: `
|
||||||
entries:
|
entries:
|
||||||
- count: 33
|
- count: 33
|
||||||
@@ -141,8 +145,9 @@ entries:
|
|||||||
when: "06/Jan/2008 15:04:05 -0700"`,
|
when: "06/Jan/2008 15:04:05 -0700"`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "default",
|
name: "extended",
|
||||||
mode: cfg.Extended,
|
mode: cfg.Extended,
|
||||||
|
numberize: true,
|
||||||
expect: `
|
expect: `
|
||||||
NAME(1): beta
|
NAME(1): beta
|
||||||
DURATION(2): 1d10h5m1s
|
DURATION(2): 1d10h5m1s
|
||||||
@@ -162,112 +167,125 @@ DURATION(2): 33d12h
|
|||||||
|
|
||||||
//------------------------ SORT TESTS
|
//------------------------ SORT TESTS
|
||||||
{
|
{
|
||||||
name: "sortbycolumn",
|
name: "sortbycolumn3",
|
||||||
column: 3,
|
column: 3,
|
||||||
sortby: "numeric",
|
sortby: "numeric",
|
||||||
desc: false,
|
numberize: true,
|
||||||
|
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
|
||||||
beta 1d10h5m1s 33 3/1/2014
|
beta 1d10h5m1s 33 3/1/2014
|
||||||
alpha 4h35m 170 2013-Feb-03`,
|
alpha 4h35m 170 2013-Feb-03`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "sortbycolumn",
|
name: "sortbycolumn4",
|
||||||
column: 4,
|
column: 4,
|
||||||
sortby: "time",
|
sortby: "time",
|
||||||
desc: false,
|
desc: false,
|
||||||
|
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
|
||||||
alpha 4h35m 170 2013-Feb-03
|
alpha 4h35m 170 2013-Feb-03
|
||||||
beta 1d10h5m1s 33 3/1/2014`,
|
beta 1d10h5m1s 33 3/1/2014`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "sortbycolumn",
|
name: "sortbycolumn2",
|
||||||
column: 2,
|
column: 2,
|
||||||
sortby: "duration",
|
sortby: "duration",
|
||||||
desc: false,
|
numberize: true,
|
||||||
|
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
|
||||||
beta 1d10h5m1s 33 3/1/2014
|
beta 1d10h5m1s 33 3/1/2014
|
||||||
ceta 33d12h 9 06/Jan/2008 15:04:05 -0700`,
|
ceta 33d12h 9 06/Jan/2008 15:04:05 -0700`,
|
||||||
},
|
},
|
||||||
|
|
||||||
// ----------------------- UseColumns Tests
|
// ----------------------- UseColumns Tests
|
||||||
{
|
{
|
||||||
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)
|
||||||
beta 3/1/2014
|
beta 3/1/2014
|
||||||
alpha 2013-Feb-03
|
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,
|
||||||
usecolstr: "2",
|
usecolstr: "2",
|
||||||
expect: `
|
expect: `
|
||||||
DURATION(2)
|
DURATION(2)
|
||||||
1d10h5m1s
|
1d10h5m1s
|
||||||
4h35m
|
4h35m
|
||||||
33d12h`,
|
33d12h`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "usecolumns",
|
name: "usecolumns3",
|
||||||
usecol: []int{3},
|
usecol: []int{3},
|
||||||
|
numberize: true,
|
||||||
usecolstr: "3",
|
usecolstr: "3",
|
||||||
expect: `
|
expect: `
|
||||||
COUNT(3)
|
COUNT(3)
|
||||||
33
|
33
|
||||||
170
|
170
|
||||||
9`,
|
9`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "usecolumns",
|
name: "usecolumns4",
|
||||||
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)
|
||||||
beta 33
|
beta 33
|
||||||
alpha 170
|
alpha 170
|
||||||
ceta 9`,
|
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)
|
||||||
1d10h5m1s 3/1/2014
|
1d10h5m1s 3/1/2014
|
||||||
4h35m 2013-Feb-03
|
4h35m 2013-Feb-03
|
||||||
33d12h 06/Jan/2008 15:04:05 -0700`,
|
33d12h 06/Jan/2008 15:04:05 -0700`,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPrinter(t *testing.T) {
|
func TestPrinter(t *testing.T) {
|
||||||
for _, testdata := range tests {
|
for _, testdata := range tests {
|
||||||
testname := fmt.Sprintf("print-sortcol-%d-desc-%t-sortby-%s-mode-%d-usecolumns-%s",
|
testname := fmt.Sprintf("print-%s-%d-desc-%t-sortby-%s-mode-%d-usecolumns-%s-numberize-%t",
|
||||||
testdata.column, testdata.desc, testdata.sortby, testdata.mode, testdata.usecolstr)
|
testdata.name, testdata.column, testdata.desc, testdata.sortby,
|
||||||
|
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
|
||||||
|
|
||||||
// cmd flags
|
// cmd flags
|
||||||
conf := cfg.Config{
|
conf := cfg.Config{
|
||||||
SortByColumn: testdata.column,
|
|
||||||
SortDescending: testdata.desc,
|
SortDescending: testdata.desc,
|
||||||
SortMode: testdata.sortby,
|
SortMode: testdata.sortby,
|
||||||
OutputMode: testdata.mode,
|
OutputMode: testdata.mode,
|
||||||
NoNumbering: testdata.nonum,
|
Numbering: testdata.numberize,
|
||||||
UseColumns: testdata.usecol,
|
UseColumns: testdata.usecol,
|
||||||
NoColor: true,
|
NoColor: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if testdata.column > 0 {
|
||||||
|
conf.UseSortByColumn = []int{testdata.column}
|
||||||
|
}
|
||||||
|
|
||||||
conf.ApplyDefaults()
|
conf.ApplyDefaults()
|
||||||
|
|
||||||
// the test checks the len!
|
// the test checks the len!
|
||||||
@@ -286,7 +304,8 @@ func TestPrinter(t *testing.T) {
|
|||||||
|
|
||||||
if got != exp {
|
if got != exp {
|
||||||
t.Errorf("not rendered correctly:\n+++ got:\n%s\n+++ want:\n%s",
|
t.Errorf("not rendered correctly:\n+++ got:\n%s\n+++ want:\n%s",
|
||||||
got, exp)
|
strings.ReplaceAll(got, " ", "_"),
|
||||||
|
strings.ReplaceAll(exp, " ", "_"))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
41
lib/sort.go
41
lib/sort.go
@@ -18,6 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
package lib
|
package lib
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"cmp"
|
||||||
"regexp"
|
"regexp"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -27,34 +28,41 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func sortTable(conf cfg.Config, data *Tabdata) {
|
func sortTable(conf cfg.Config, data *Tabdata) {
|
||||||
if conf.SortByColumn <= 0 {
|
if len(conf.UseSortByColumn) == 0 {
|
||||||
// no sorting wanted
|
// no sorting wanted
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// slightly modified here to match internal array indicies
|
|
||||||
col := conf.SortByColumn
|
|
||||||
|
|
||||||
col-- // ui starts counting by 1, but use 0 internally
|
|
||||||
|
|
||||||
// sanity checks
|
// sanity checks
|
||||||
if len(data.entries) == 0 {
|
if len(data.entries) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if col >= len(data.headers) {
|
|
||||||
// fall back to default column
|
|
||||||
col = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// actual sorting
|
// actual sorting
|
||||||
sort.SliceStable(data.entries, func(i, j int) bool {
|
sort.SliceStable(data.entries, func(i, j int) bool {
|
||||||
return compare(&conf, data.entries[i][col], data.entries[j][col])
|
// holds the result of a sort of one column
|
||||||
|
comparators := []int{}
|
||||||
|
|
||||||
|
// iterate over all columns to be sorted, conf.SortMode must be identical!
|
||||||
|
for _, column := range conf.UseSortByColumn {
|
||||||
|
comparators = append(comparators, compare(&conf, data.entries[i][column-1], data.entries[j][column-1]))
|
||||||
|
}
|
||||||
|
|
||||||
|
// return the combined result
|
||||||
|
res := cmp.Or(comparators...)
|
||||||
|
|
||||||
|
switch res {
|
||||||
|
case 0:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// config is not modified here, but it would be inefficient to copy it every loop
|
// config is not modified here, but it would be inefficient to copy it every loop
|
||||||
func compare(conf *cfg.Config, left string, right string) bool {
|
func compare(conf *cfg.Config, left string, right string) int {
|
||||||
var comp bool
|
var comp bool
|
||||||
|
|
||||||
switch conf.SortMode {
|
switch conf.SortMode {
|
||||||
@@ -88,7 +96,12 @@ func compare(conf *cfg.Config, left string, right string) bool {
|
|||||||
comp = !comp
|
comp = !comp
|
||||||
}
|
}
|
||||||
|
|
||||||
return comp
|
switch comp {
|
||||||
|
case true:
|
||||||
|
return 0
|
||||||
|
default:
|
||||||
|
return 1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -53,18 +53,18 @@ func TestCompare(t *testing.T) {
|
|||||||
mode string
|
mode string
|
||||||
a string
|
a string
|
||||||
b string
|
b string
|
||||||
want bool
|
want int
|
||||||
desc bool
|
desc bool
|
||||||
}{
|
}{
|
||||||
// ascending
|
// ascending
|
||||||
{"numeric", "10", "20", true, false},
|
{"numeric", "10", "20", 0, false},
|
||||||
{"duration", "2d4h5m", "45m", false, false},
|
{"duration", "2d4h5m", "45m", 1, false},
|
||||||
{"time", "12/24/2022", "1/1/1970", false, false},
|
{"time", "12/24/2022", "1/1/1970", 1, false},
|
||||||
|
|
||||||
// descending
|
// descending
|
||||||
{"numeric", "10", "20", false, true},
|
{"numeric", "10", "20", 1, true},
|
||||||
{"duration", "2d4h5m", "45m", true, true},
|
{"duration", "2d4h5m", "45m", 0, true},
|
||||||
{"time", "12/24/2022", "1/1/1970", true, true},
|
{"time", "12/24/2022", "1/1/1970", 0, true},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, testdata := range tests {
|
for _, testdata := range tests {
|
||||||
@@ -75,7 +75,7 @@ func TestCompare(t *testing.T) {
|
|||||||
c := cfg.Config{SortMode: testdata.mode, SortDescending: testdata.desc}
|
c := cfg.Config{SortMode: testdata.mode, SortDescending: testdata.desc}
|
||||||
got := compare(&c, testdata.a, testdata.b)
|
got := compare(&c, testdata.a, testdata.b)
|
||||||
if got != testdata.want {
|
if got != testdata.want {
|
||||||
t.Errorf("got %t, want %t", got, testdata.want)
|
t.Errorf("got %d, want %d", got, testdata.want)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
524
lib/tableeditor.go
Normal file
524
lib/tableeditor.go
Normal file
@@ -0,0 +1,524 @@
|
|||||||
|
/*
|
||||||
|
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 (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
|
"github.com/charmbracelet/lipgloss"
|
||||||
|
"github.com/evertras/bubble-table/table"
|
||||||
|
"github.com/mattn/go-isatty"
|
||||||
|
"github.com/tlinden/tablizer/cfg"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The context exists outside of the bubble loop, and is being used as
|
||||||
|
// pointer reciever. That way we can use it as our primary storage
|
||||||
|
// container.
|
||||||
|
type Context struct {
|
||||||
|
selectedColumn int
|
||||||
|
showHelp bool
|
||||||
|
descending bool
|
||||||
|
data *Tabdata
|
||||||
|
|
||||||
|
// Window dimensions
|
||||||
|
totalWidth int
|
||||||
|
totalHeight int
|
||||||
|
|
||||||
|
// Table dimensions
|
||||||
|
horizontalMargin int
|
||||||
|
verticalMargin int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute tablizer sort function, feed it with fresh config, we do
|
||||||
|
// NOT use the existing runtime config, because sorting is
|
||||||
|
// configurable in the UI separately.
|
||||||
|
func (ctx *Context) Sort(mode string) {
|
||||||
|
conf := cfg.Config{
|
||||||
|
SortMode: mode,
|
||||||
|
SortDescending: ctx.descending,
|
||||||
|
UseSortByColumn: []int{ctx.selectedColumn + 1},
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.descending = !ctx.descending
|
||||||
|
|
||||||
|
sortTable(conf, ctx.data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The actual table model, holds the context pointer, a copy of the
|
||||||
|
// pre-processed data and some flags
|
||||||
|
type FilterTable struct {
|
||||||
|
Table table.Model
|
||||||
|
|
||||||
|
Rows int
|
||||||
|
|
||||||
|
quitting bool
|
||||||
|
unchanged bool
|
||||||
|
|
||||||
|
maxColumns int
|
||||||
|
headerIdx map[string]int
|
||||||
|
|
||||||
|
ctx *Context
|
||||||
|
|
||||||
|
columns []table.Column
|
||||||
|
}
|
||||||
|
|
||||||
|
type HelpLine []string
|
||||||
|
type HelpColumn []HelpLine
|
||||||
|
|
||||||
|
const (
|
||||||
|
// header+footer
|
||||||
|
ExtraRows = 5
|
||||||
|
|
||||||
|
HelpFooter = "?:help | "
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// we use our own custom border style
|
||||||
|
customBorder = table.Border{
|
||||||
|
Top: "─",
|
||||||
|
Left: "│",
|
||||||
|
Right: "│",
|
||||||
|
Bottom: "─",
|
||||||
|
|
||||||
|
TopRight: "╮",
|
||||||
|
TopLeft: "╭",
|
||||||
|
BottomRight: "╯",
|
||||||
|
BottomLeft: "╰",
|
||||||
|
|
||||||
|
TopJunction: "┬",
|
||||||
|
LeftJunction: "├",
|
||||||
|
RightJunction: "┤",
|
||||||
|
BottomJunction: "┴",
|
||||||
|
InnerJunction: "┼",
|
||||||
|
|
||||||
|
InnerDivider: "│",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cells in selected columns will be highlighted
|
||||||
|
StyleSelected = lipgloss.NewStyle().
|
||||||
|
Background(lipgloss.Color("#696969")).
|
||||||
|
Foreground(lipgloss.Color("#ffffff")).
|
||||||
|
Align(lipgloss.Left)
|
||||||
|
|
||||||
|
StyleHeader = lipgloss.NewStyle().
|
||||||
|
Foreground(lipgloss.Color("#ff4500")).
|
||||||
|
Align(lipgloss.Left).Bold(true)
|
||||||
|
|
||||||
|
// help buffer styles
|
||||||
|
StyleKey = lipgloss.NewStyle().Bold(true)
|
||||||
|
StyleHelp = lipgloss.NewStyle().Foreground(lipgloss.Color("#ff4500"))
|
||||||
|
|
||||||
|
// the default style
|
||||||
|
NoStyle = lipgloss.NewStyle().Align(lipgloss.Left)
|
||||||
|
|
||||||
|
HelpData = []HelpColumn{
|
||||||
|
{
|
||||||
|
HelpLine{"up", "navigate up"},
|
||||||
|
HelpLine{"down", "navigate down"},
|
||||||
|
HelpLine{"tab", "navigate columns"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
HelpLine{"s", "sort alpha-numerically"},
|
||||||
|
HelpLine{"n", "sort numerically"},
|
||||||
|
HelpLine{"t", "sort by time"},
|
||||||
|
HelpLine{"d", "sort by duration"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
HelpLine{"spc", "[de]select a row"},
|
||||||
|
HelpLine{"a", "[de]select all visible rows"},
|
||||||
|
HelpLine{"f", "enter fuzzy filter"},
|
||||||
|
HelpLine{"esc", "finish filter input"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
HelpLine{"?", "show help buffer"},
|
||||||
|
HelpLine{"q", "commit and quit"},
|
||||||
|
HelpLine{"c-c", "discard and quit"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// rendered from Help above
|
||||||
|
Help = ""
|
||||||
|
|
||||||
|
// number of lines taken by help below, adjust accordingly!
|
||||||
|
HelpRows = 0
|
||||||
|
)
|
||||||
|
|
||||||
|
// generate a lipgloss styled help buffer consisting of various
|
||||||
|
// columns
|
||||||
|
func generateHelp() {
|
||||||
|
help := strings.Builder{}
|
||||||
|
helpcols := []string{}
|
||||||
|
maxrows := 0
|
||||||
|
|
||||||
|
for _, col := range HelpData {
|
||||||
|
help.Reset()
|
||||||
|
|
||||||
|
// determine max key width to avoid excess spaces between keys and help
|
||||||
|
keylen := 0
|
||||||
|
for _, line := range col {
|
||||||
|
if len(line[0]) > keylen {
|
||||||
|
keylen = len(line[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
keylenstr := fmt.Sprintf("%d", keylen)
|
||||||
|
|
||||||
|
for _, line := range col {
|
||||||
|
// 0: key, 1: help text
|
||||||
|
help.WriteString(StyleKey.Render(fmt.Sprintf("%-"+keylenstr+"s", line[0])))
|
||||||
|
help.WriteString(" " + StyleHelp.Render(line[1]) + " \n")
|
||||||
|
}
|
||||||
|
|
||||||
|
helpcols = append(helpcols, help.String())
|
||||||
|
|
||||||
|
if len(col) > maxrows {
|
||||||
|
maxrows = len(col)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HelpRows = maxrows + 1
|
||||||
|
Help = "\n" + lipgloss.JoinHorizontal(lipgloss.Top, helpcols...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// initializes the table model
|
||||||
|
func NewModel(data *Tabdata, ctx *Context) FilterTable {
|
||||||
|
columns := make([]table.Column, len(data.headers))
|
||||||
|
lengths := make([]int, len(data.headers))
|
||||||
|
hidx := make(map[string]int, len(data.headers))
|
||||||
|
|
||||||
|
// give columns at least the header width
|
||||||
|
for idx, header := range data.headers {
|
||||||
|
lengths[idx] = len(header)
|
||||||
|
hidx[strings.ToLower(header)] = idx
|
||||||
|
}
|
||||||
|
|
||||||
|
// determine max width per column
|
||||||
|
for _, entry := range data.entries {
|
||||||
|
for i, cell := range entry {
|
||||||
|
if len(cell) > lengths[i] {
|
||||||
|
lengths[i] = len(cell)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// determine flexFactor with base 10, used by flexColumns
|
||||||
|
for i, len := range lengths {
|
||||||
|
if len <= 10 {
|
||||||
|
lengths[i] = 1
|
||||||
|
} else {
|
||||||
|
lengths[i] = len / 10
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// setup column data with flexColumns
|
||||||
|
for idx, header := range data.headers {
|
||||||
|
columns[idx] = table.NewFlexColumn(
|
||||||
|
strings.ToLower(header),
|
||||||
|
StyleHeader.Render(header),
|
||||||
|
lengths[idx]).WithFiltered(true).WithStyle(NoStyle)
|
||||||
|
}
|
||||||
|
|
||||||
|
// separate variable so we can share the row filling code
|
||||||
|
filtertbl := FilterTable{
|
||||||
|
maxColumns: len(data.headers),
|
||||||
|
Rows: len(data.entries),
|
||||||
|
headerIdx: hidx,
|
||||||
|
ctx: ctx,
|
||||||
|
columns: columns,
|
||||||
|
}
|
||||||
|
|
||||||
|
filtertbl.Table = table.New(columns)
|
||||||
|
filtertbl.fillRows()
|
||||||
|
|
||||||
|
// finally construct help buffer
|
||||||
|
generateHelp()
|
||||||
|
|
||||||
|
return filtertbl
|
||||||
|
}
|
||||||
|
|
||||||
|
// Applied to every cell on every change (TAB,up,down key, resize
|
||||||
|
// event etc)
|
||||||
|
func CellController(input table.StyledCellFuncInput, m FilterTable) lipgloss.Style {
|
||||||
|
if m.headerIdx[input.Column.Key()] == m.ctx.selectedColumn {
|
||||||
|
return StyleSelected
|
||||||
|
}
|
||||||
|
|
||||||
|
return NoStyle
|
||||||
|
}
|
||||||
|
|
||||||
|
// Selects or deselects ALL rows
|
||||||
|
func (m *FilterTable) ToggleAllSelected() {
|
||||||
|
rows := m.Table.GetVisibleRows()
|
||||||
|
selected := m.Table.SelectedRows()
|
||||||
|
|
||||||
|
if len(selected) > 0 {
|
||||||
|
for i, row := range selected {
|
||||||
|
rows[i] = row.Selected(false)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for i, row := range rows {
|
||||||
|
rows[i] = row.Selected(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m.Table.WithRows(rows)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ? pressed, display help message
|
||||||
|
func (m FilterTable) ToggleHelp() {
|
||||||
|
m.ctx.showHelp = !m.ctx.showHelp
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m FilterTable) Init() tea.Cmd {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Forward call to context sort
|
||||||
|
func (m *FilterTable) Sort(mode string) {
|
||||||
|
m.ctx.Sort(mode)
|
||||||
|
m.fillRows()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fills the table rows with our data. Called once on startup and
|
||||||
|
// repeatedly if the user changes the sort order in some way
|
||||||
|
func (m *FilterTable) fillRows() {
|
||||||
|
// required to be able to feed the model to the controller
|
||||||
|
controllerWrapper := func(input table.StyledCellFuncInput) lipgloss.Style {
|
||||||
|
return CellController(input, *m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// fill the rows with style
|
||||||
|
rows := make([]table.Row, len(m.ctx.data.entries))
|
||||||
|
for idx, entry := range m.ctx.data.entries {
|
||||||
|
rowdata := make(table.RowData, len(entry))
|
||||||
|
|
||||||
|
for i, cell := range entry {
|
||||||
|
rowdata[strings.ToLower(m.ctx.data.headers[i])] =
|
||||||
|
table.NewStyledCellWithStyleFunc(cell+" ", controllerWrapper)
|
||||||
|
}
|
||||||
|
|
||||||
|
rows[idx] = table.NewRow(rowdata)
|
||||||
|
}
|
||||||
|
|
||||||
|
m.Table = m.Table.
|
||||||
|
WithRows(rows).
|
||||||
|
Filtered(true).
|
||||||
|
WithFuzzyFilter().
|
||||||
|
Focused(true).
|
||||||
|
SelectableRows(true).
|
||||||
|
WithSelectedText(" ", "✓").
|
||||||
|
WithFooterVisibility(true).
|
||||||
|
WithHeaderVisibility(true).
|
||||||
|
HighlightStyle(StyleSelected).
|
||||||
|
Border(customBorder)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Part of the bubbletea event loop, called every tick
|
||||||
|
func (m FilterTable) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
|
var (
|
||||||
|
cmd tea.Cmd
|
||||||
|
cmds []tea.Cmd
|
||||||
|
)
|
||||||
|
|
||||||
|
m.Table, cmd = m.Table.Update(msg)
|
||||||
|
cmds = append(cmds, cmd)
|
||||||
|
|
||||||
|
// If the user is about to enter filter text, do NOT respond to
|
||||||
|
// key bindings, as they might be part of the filter!
|
||||||
|
if !m.Table.GetIsFilterInputFocused() {
|
||||||
|
switch msg := msg.(type) {
|
||||||
|
case tea.KeyMsg:
|
||||||
|
switch msg.String() {
|
||||||
|
case "q":
|
||||||
|
m.quitting = true
|
||||||
|
m.unchanged = false
|
||||||
|
cmds = append(cmds, tea.Quit)
|
||||||
|
|
||||||
|
case "ctrl+c":
|
||||||
|
m.quitting = true
|
||||||
|
m.unchanged = true
|
||||||
|
cmds = append(cmds, tea.Quit)
|
||||||
|
|
||||||
|
case "a":
|
||||||
|
m.ToggleAllSelected()
|
||||||
|
|
||||||
|
case "tab":
|
||||||
|
m.SelectNextColumn()
|
||||||
|
|
||||||
|
case "?":
|
||||||
|
m.ToggleHelp()
|
||||||
|
m.recalculateTable()
|
||||||
|
|
||||||
|
case "s":
|
||||||
|
m.Sort("alphanumeric")
|
||||||
|
|
||||||
|
case "n":
|
||||||
|
m.Sort("numeric")
|
||||||
|
|
||||||
|
case "d":
|
||||||
|
m.Sort("duration")
|
||||||
|
|
||||||
|
case "t":
|
||||||
|
m.Sort("time")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Happens when the terminal window has been resized
|
||||||
|
switch msg := msg.(type) {
|
||||||
|
case tea.WindowSizeMsg:
|
||||||
|
m.ctx.totalWidth = msg.Width
|
||||||
|
m.ctx.totalHeight = msg.Height
|
||||||
|
|
||||||
|
m.recalculateTable()
|
||||||
|
}
|
||||||
|
|
||||||
|
m.updateFooter()
|
||||||
|
|
||||||
|
return m, tea.Batch(cmds...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add some info to the footer
|
||||||
|
func (m *FilterTable) updateFooter() {
|
||||||
|
selected := m.Table.SelectedRows()
|
||||||
|
footer := fmt.Sprintf("selected: %d ", len(selected))
|
||||||
|
|
||||||
|
if m.Table.GetIsFilterInputFocused() {
|
||||||
|
footer = fmt.Sprintf("/%s %s", m.Table.GetCurrentFilter(), footer)
|
||||||
|
} else if m.Table.GetIsFilterActive() {
|
||||||
|
footer = fmt.Sprintf("Filter: %s %s", m.Table.GetCurrentFilter(), footer)
|
||||||
|
}
|
||||||
|
|
||||||
|
m.Table = m.Table.WithStaticFooter(HelpFooter + footer)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called on resize event (or if help has been toggled)
|
||||||
|
func (m *FilterTable) recalculateTable() {
|
||||||
|
m.Table = m.Table.
|
||||||
|
WithTargetWidth(m.calculateWidth()).
|
||||||
|
WithMinimumHeight(m.calculateHeight()).
|
||||||
|
WithPageSize(m.calculateHeight() - ExtraRows)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *FilterTable) calculateWidth() int {
|
||||||
|
return m.ctx.totalWidth - m.ctx.horizontalMargin
|
||||||
|
}
|
||||||
|
|
||||||
|
// Take help height into account, if enabled
|
||||||
|
func (m *FilterTable) calculateHeight() int {
|
||||||
|
height := m.Rows + ExtraRows
|
||||||
|
|
||||||
|
if height >= m.ctx.totalHeight {
|
||||||
|
height = m.ctx.totalHeight - m.ctx.verticalMargin
|
||||||
|
} else {
|
||||||
|
height = m.ctx.totalHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.ctx.showHelp {
|
||||||
|
height = height - HelpRows
|
||||||
|
}
|
||||||
|
|
||||||
|
return height
|
||||||
|
}
|
||||||
|
|
||||||
|
// Part of the bubbletable event view, called every tick
|
||||||
|
func (m FilterTable) View() string {
|
||||||
|
body := strings.Builder{}
|
||||||
|
|
||||||
|
if !m.quitting {
|
||||||
|
body.WriteString(m.Table.View())
|
||||||
|
|
||||||
|
if m.ctx.showHelp {
|
||||||
|
body.WriteString(Help)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return body.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// User hit the TAB key
|
||||||
|
func (m *FilterTable) SelectNextColumn() {
|
||||||
|
if m.ctx.selectedColumn == m.maxColumns-1 {
|
||||||
|
m.ctx.selectedColumn = 0
|
||||||
|
} else {
|
||||||
|
m.ctx.selectedColumn++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// entry point from outside tablizer into table editor
|
||||||
|
func tableEditor(conf *cfg.Config, data *Tabdata) (*Tabdata, error) {
|
||||||
|
// we render to STDERR to avoid dead lock when the user redirects STDOUT
|
||||||
|
// see https://github.com/charmbracelet/bubbletea/issues/860
|
||||||
|
//
|
||||||
|
// TODO: doesn't work with libgloss v2 anymore!
|
||||||
|
|
||||||
|
out := os.Stderr
|
||||||
|
|
||||||
|
if isatty.IsTerminal(os.Stdout.Fd()) {
|
||||||
|
out = os.Stdout
|
||||||
|
}
|
||||||
|
|
||||||
|
lipgloss.SetDefaultRenderer(lipgloss.NewRenderer(out))
|
||||||
|
|
||||||
|
ctx := &Context{data: data}
|
||||||
|
|
||||||
|
// Output to STDERR because there's a known bubbletea/lipgloss
|
||||||
|
// issue: if a program with a tui is expected to write something
|
||||||
|
// to STDOUT when the tui is finished, then the styles do not
|
||||||
|
// work. So we write to STDERR (which works) and tablizer can
|
||||||
|
// still be used inside pipes.
|
||||||
|
program := tea.NewProgram(
|
||||||
|
NewModel(data, ctx),
|
||||||
|
tea.WithOutput(out),
|
||||||
|
tea.WithAltScreen())
|
||||||
|
|
||||||
|
m, err := program.Run()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.(FilterTable).unchanged {
|
||||||
|
return data, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Data has been modified. Extract it, put it back into our own
|
||||||
|
// structure and give control back to cmdline tablizer.
|
||||||
|
filteredtable := m.(FilterTable)
|
||||||
|
|
||||||
|
data.entries = make([][]string, len(filteredtable.Table.SelectedRows()))
|
||||||
|
for pos, row := range m.(FilterTable).Table.SelectedRows() {
|
||||||
|
entry := make([]string, len(data.headers))
|
||||||
|
for idx, field := range data.headers {
|
||||||
|
cell := row.Data[strings.ToLower(field)]
|
||||||
|
switch value := cell.(type) {
|
||||||
|
case string:
|
||||||
|
entry[idx] = value
|
||||||
|
case table.StyledCell:
|
||||||
|
entry[idx] = value.Data.(string)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data.entries[pos] = entry
|
||||||
|
}
|
||||||
|
|
||||||
|
return data, err
|
||||||
|
}
|
||||||
51
lib/yank.go
Normal file
51
lib/yank.go
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
Copyright © 2022-2025 Thomas von Dein
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package lib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/tiagomelo/go-clipboard/clipboard"
|
||||||
|
"github.com/tlinden/tablizer/cfg"
|
||||||
|
)
|
||||||
|
|
||||||
|
func yankColumns(conf cfg.Config, data *Tabdata) {
|
||||||
|
var yank []string
|
||||||
|
|
||||||
|
if len(data.entries) == 0 || len(conf.UseYankColumns) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, row := range data.entries {
|
||||||
|
for i, field := range row {
|
||||||
|
for _, idx := range conf.UseYankColumns {
|
||||||
|
if i == idx-1 {
|
||||||
|
yank = append(yank, field)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(yank) > 0 {
|
||||||
|
cb := clipboard.New(clipboard.ClipboardOptions{Primary: true})
|
||||||
|
if err := cb.CopyText(strings.Join(yank, " ")); err != nil {
|
||||||
|
log.Fatalln("error writing string to clipboard:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
72
lib/yank_test.go
Normal file
72
lib/yank_test.go
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
/*
|
||||||
|
Copyright © 2025 Thomas von Dein
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package lib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/tiagomelo/go-clipboard/clipboard"
|
||||||
|
"github.com/tlinden/tablizer/cfg"
|
||||||
|
)
|
||||||
|
|
||||||
|
var yanktests = []struct {
|
||||||
|
name string
|
||||||
|
yank []int // -y$colum,$column... after processing
|
||||||
|
filter string
|
||||||
|
expect string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "one",
|
||||||
|
yank: []int{1},
|
||||||
|
filter: "beta",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func DISABLED_TestYankColumns(t *testing.T) {
|
||||||
|
cb := clipboard.New()
|
||||||
|
|
||||||
|
for _, testdata := range yanktests {
|
||||||
|
testname := fmt.Sprintf("yank-%s-filter-%s",
|
||||||
|
testdata.name, testdata.filter)
|
||||||
|
t.Run(testname, func(t *testing.T) {
|
||||||
|
conf := cfg.Config{
|
||||||
|
OutputMode: cfg.ASCII,
|
||||||
|
UseYankColumns: testdata.yank,
|
||||||
|
NoColor: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
conf.ApplyDefaults()
|
||||||
|
data := newData() // defined in printer_test.go, reused here
|
||||||
|
|
||||||
|
var writer bytes.Buffer
|
||||||
|
printData(&writer, conf, &data)
|
||||||
|
|
||||||
|
got, err := cb.PasteText()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to fetch yanked text from clipboard")
|
||||||
|
}
|
||||||
|
|
||||||
|
if got != testdata.expect {
|
||||||
|
t.Errorf("not yanked correctly:\n+++ got:\n%s\n+++ want:\n%s",
|
||||||
|
got, testdata.expect)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,16 +1,15 @@
|
|||||||
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) {
|
||||||
os.Exit(testscript.RunMain(m, map[string]func() int{
|
testscript.Main(m, map[string]func(){
|
||||||
"tablizer": Main,
|
"tablizer": main,
|
||||||
}))
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTablizer(t *testing.T) {
|
func TestTablizer(t *testing.T) {
|
||||||
|
|||||||
9
mkrel.sh
9
mkrel.sh
@@ -42,8 +42,15 @@ 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 -o ${binfile} -ldflags "-X 'github.com/tlinden/tablizer/cfg.VERSION=${version}'"
|
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}
|
||||||
|
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
|
||||||
|
|||||||
@@ -6,10 +6,6 @@ stdout Usage
|
|||||||
exec tablizer -V
|
exec tablizer -V
|
||||||
stdout version
|
stdout version
|
||||||
|
|
||||||
# manpage
|
|
||||||
exec tablizer -m
|
|
||||||
stdout SYNOPSIS
|
|
||||||
|
|
||||||
# completion
|
# completion
|
||||||
exec tablizer --completion bash
|
exec tablizer --completion bash
|
||||||
stdout __tablizer_init_completion
|
stdout __tablizer_init_completion
|
||||||
|
|||||||
46
t/test-multipatterns.txtar
Normal file
46
t/test-multipatterns.txtar
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
# filtering
|
||||||
|
|
||||||
|
# a AND b
|
||||||
|
exec tablizer -r testtable.txt -H -cspecies invasive imperium
|
||||||
|
stdout 'namak'
|
||||||
|
! stdout human
|
||||||
|
|
||||||
|
# a AND !b
|
||||||
|
exec tablizer -r testtable.txt -H -cspecies invasive '/imperium/!'
|
||||||
|
stdout 'human'
|
||||||
|
! stdout namak
|
||||||
|
|
||||||
|
# a AND !b AND c
|
||||||
|
exec tablizer -r testtable.txt -H -cspecies peaceful '/imperium/!' planetary
|
||||||
|
stdout 'kenaha'
|
||||||
|
! stdout 'namak|heduu|riedl'
|
||||||
|
|
||||||
|
# case insensitive
|
||||||
|
exec tablizer -r testtable.txt -H -cspecies '/REGIONAL/i'
|
||||||
|
stdout namak
|
||||||
|
! stdout 'human|riedl|heduu|kenaa'
|
||||||
|
|
||||||
|
# case insensitive negated
|
||||||
|
exec tablizer -r testtable.txt -H -cspecies '/REGIONAL/!i'
|
||||||
|
stdout 'human|riedl|heduu|kenaa'
|
||||||
|
! stdout namak
|
||||||
|
|
||||||
|
# !a AND !b
|
||||||
|
exec tablizer -r testtable.txt -H -cspecies '/galactic/!' '/planetary/!'
|
||||||
|
stdout namak
|
||||||
|
! stdout 'human|riedl|heduu|kenaa'
|
||||||
|
|
||||||
|
# same case insensitive
|
||||||
|
exec tablizer -r testtable.txt -H -cspecies '/GALACTIC/i!' '/PLANETARY/!i'
|
||||||
|
stdout namak
|
||||||
|
! stdout 'human|riedl|heduu|kenaa'
|
||||||
|
|
||||||
|
# will be automatically created in work dir
|
||||||
|
-- testtable.txt --
|
||||||
|
SPECIES TYPE HOME STAGE SPREAD
|
||||||
|
human invasive earth brink planetary
|
||||||
|
riedl peaceful keauna civilized pangalactic
|
||||||
|
namak invasive namak imperium regional
|
||||||
|
heduu peaceful iu imperium galactic
|
||||||
|
kenaha peaceful kohi hunter-gatherer planetary
|
||||||
|
|
||||||
6
t/testtable3
Normal file
6
t/testtable3
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
NAME READY STATUS STARTS AGE
|
||||||
|
alertmanager-kube-prometheus-alertmanager-0 2/2 Running 35 11d
|
||||||
|
kube-prometheus-blackbox-exporter-5d85b5d8f4-tskh7 1/1 Running 17 1h44m
|
||||||
|
grafana-fcc54cbc9-bk7s8 1/1 Running 17 1d
|
||||||
|
kube-prometheus-kube-state-metrics-b4cd9487-75p7f 1/1 Running 20 45m
|
||||||
|
kube-prometheus-node-exporter-bfzpl 1/1 Running 17 54s
|
||||||
4
t/testtable4
Normal file
4
t/testtable4
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
ONE TWO
|
||||||
|
1 4
|
||||||
|
3 1
|
||||||
|
5 2
|
||||||
6
t/testtable5
Normal file
6
t/testtable5
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
SPECIES TYPE HOME STAGE
|
||||||
|
human invasive earth brink
|
||||||
|
riedl peaceful keauna civilized
|
||||||
|
namak invasive namak imperium
|
||||||
|
heduu peaceful iu imperium
|
||||||
|
kenaha peaceful kohi hunter-gatherer
|
||||||
136
tablizer.1
136
tablizer.1
@@ -133,7 +133,7 @@
|
|||||||
.\" ========================================================================
|
.\" ========================================================================
|
||||||
.\"
|
.\"
|
||||||
.IX Title "TABLIZER 1"
|
.IX Title "TABLIZER 1"
|
||||||
.TH TABLIZER 1 "2025-01-14" "1" "User Commands"
|
.TH TABLIZER 1 "2025-09-11" "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,20 +144,21 @@ 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,...] [\-r 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, \-\-no\-numbering Disable 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 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
|
||||||
|
\& \-I, \-\-interactive Interactively filter and select rows
|
||||||
\&
|
\&
|
||||||
\& Output Flags (mutually exclusive):
|
\& Output Flags (mutually exclusive):
|
||||||
\& \-X, \-\-extended Enable extended output
|
\& \-X, \-\-extended Enable extended output
|
||||||
@@ -168,6 +169,8 @@ tablizer \- Manipulate tabular output of other programs
|
|||||||
\& \-C, \-\-csv Enable CSV output
|
\& \-C, \-\-csv Enable CSV output
|
||||||
\& \-A, \-\-ascii Default output mode, ascii tabular
|
\& \-A, \-\-ascii Default output mode, ascii tabular
|
||||||
\& \-L, \-\-hightlight\-lines Use alternating background colors for tables
|
\& \-L, \-\-hightlight\-lines Use alternating background colors for tables
|
||||||
|
\& \-y, \-\-yank\-columns Yank specified columns (separated by ,) to clipboard,
|
||||||
|
\& space separated
|
||||||
\&
|
\&
|
||||||
\& Sort Mode Flags (mutually exclusive):
|
\& Sort Mode Flags (mutually exclusive):
|
||||||
\& \-a, \-\-sort\-age sort according to age (duration) string
|
\& \-a, \-\-sort\-age sort according to age (duration) string
|
||||||
@@ -176,12 +179,13 @@ tablizer \- Manipulate tabular output of other programs
|
|||||||
\& \-t, \-\-sort\-time sort according to time string
|
\& \-t, \-\-sort\-time sort according to time string
|
||||||
\&
|
\&
|
||||||
\& Other Flags:
|
\& Other Flags:
|
||||||
\& \-\-completion <shell> Generate the autocompletion script for <shell>
|
\& \-r \-\-read\-file <file> Use <file> as input instead of STDIN
|
||||||
\& \-f, \-\-config <file> Configuration file (default: ~/.config/tablizer/config)
|
\& \-\-completion <shell> Generate the autocompletion script for <shell>
|
||||||
\& \-d, \-\-debug Enable debugging
|
\& \-f, \-\-config <file> Configuration file (default: ~/.config/tablizer/config)
|
||||||
\& \-h, \-\-help help for tablizer
|
\& \-d, \-\-debug Enable debugging
|
||||||
\& \-m, \-\-man Display manual page
|
\& \-h, \-\-help help for tablizer
|
||||||
\& \-V, \-\-version Print program version
|
\& \-m, \-\-man Display manual page
|
||||||
|
\& \-V, \-\-version Print program version
|
||||||
.Ve
|
.Ve
|
||||||
.SH "DESCRIPTION"
|
.SH "DESCRIPTION"
|
||||||
.IX Header "DESCRIPTION"
|
.IX Header "DESCRIPTION"
|
||||||
@@ -250,11 +254,20 @@ By default, if a \fBpattern\fR has been speficied, matches will be
|
|||||||
highlighted. You can disable this behavior with the \fB\-N\fR option.
|
highlighted. You can disable this behavior with the \fB\-N\fR option.
|
||||||
.PP
|
.PP
|
||||||
Use the \fB\-k\fR option to specify by which column to sort the tabular
|
Use the \fB\-k\fR option to specify by which column to sort the tabular
|
||||||
data (as in \s-1GNU\s0 \fBsort\fR\|(1)). The default sort column is the first one. To
|
data (as in \s-1GNU\s0 \fBsort\fR\|(1)). The default sort column is the first
|
||||||
disable sorting at all, supply 0 (Zero) to \-k. The default sort order
|
one. You can specify column numbers or names. Column numbers start
|
||||||
is ascending. You can change this to descending order using the option
|
with 1, names are case insensitive. You can specify multiple columns
|
||||||
\&\fB\-D\fR. The default sort order is by string, but there are other sort
|
separated by comma to sort, but the type must be the same. For example
|
||||||
modes:
|
if you want to sort numerically, all columns must be numbers. If you
|
||||||
|
use column numbers, then be aware, that these are the numbers before
|
||||||
|
column extraction. For example if you have a table with 4 columns and
|
||||||
|
specify \f(CW\*(C`\-c4\*(C'\fR, then only 1 column (the fourth) will be printed,
|
||||||
|
however if you want to sort by this column, you'll have to specify
|
||||||
|
\&\f(CW\*(C`\-k4\*(C'\fR.
|
||||||
|
.PP
|
||||||
|
The default sort order is ascending. You can change this to
|
||||||
|
descending order using the option \fB\-D\fR. The default sort order is by
|
||||||
|
alphanumeric string, but there are other sort modes:
|
||||||
.IP "\fB\-a \-\-sort\-age\fR" 4
|
.IP "\fB\-a \-\-sort\-age\fR" 4
|
||||||
.IX Item "-a --sort-age"
|
.IX Item "-a --sort-age"
|
||||||
Sorts duration strings like \*(L"1d4h32m51s\*(R".
|
Sorts duration strings like \*(L"1d4h32m51s\*(R".
|
||||||
@@ -269,38 +282,52 @@ Finally the \fB\-d\fR option enables debugging output which is mostly
|
|||||||
useful for the developer.
|
useful for the developer.
|
||||||
.SS "\s-1PATTERNS AND FILTERING\s0"
|
.SS "\s-1PATTERNS AND FILTERING\s0"
|
||||||
.IX Subsection "PATTERNS AND FILTERING"
|
.IX Subsection "PATTERNS AND FILTERING"
|
||||||
You can reduce the rows being displayed by using a regular expression
|
You can reduce the rows being displayed by using one or more regular
|
||||||
pattern. The regexp is \s-1PCRE\s0 compatible, refer to the syntax cheat
|
expression patterns. The regexp language being used is the one of
|
||||||
sheet here: <https://github.com/google/re2/wiki/Syntax>. If you want
|
\&\s-1GOLANG,\s0 refer to the syntax cheat sheet here:
|
||||||
to read a more comprehensive documentation about the topic and have
|
<https://pkg.go.dev/regexp/syntax>.
|
||||||
perl installed you can read it with:
|
.PP
|
||||||
|
If you want to read a more comprehensive documentation about the
|
||||||
|
topic and have perl installed you can read it with:
|
||||||
.PP
|
.PP
|
||||||
.Vb 1
|
.Vb 1
|
||||||
\& perldoc perlre
|
\& perldoc perlre
|
||||||
.Ve
|
.Ve
|
||||||
.PP
|
.PP
|
||||||
Or read it online: <https://perldoc.perl.org/perlre>.
|
Or read it online: <https://perldoc.perl.org/perlre>. But please note
|
||||||
|
that the \s-1GO\s0 regexp engine does \s-1NOT\s0 support all perl regex terms,
|
||||||
|
especially look-ahead and look-behind.
|
||||||
.PP
|
.PP
|
||||||
A note on modifiers: the regexp engine used in tablizer uses another
|
If you want to supply flags to a regex, then surround it with slashes
|
||||||
modifier syntax:
|
and append the flag. The following flags are supported:
|
||||||
.PP
|
.PP
|
||||||
.Vb 1
|
.Vb 2
|
||||||
\& (?MODIFIER)
|
\& i => case insensitive
|
||||||
|
\& ! => negative match
|
||||||
.Ve
|
.Ve
|
||||||
.PP
|
.PP
|
||||||
The most important modifiers are:
|
|
||||||
.PP
|
|
||||||
\&\f(CW\*(C`i\*(C'\fR ignore case
|
|
||||||
\&\f(CW\*(C`m\*(C'\fR multiline mode
|
|
||||||
\&\f(CW\*(C`s\*(C'\fR single line mode
|
|
||||||
.PP
|
|
||||||
Example for a case insensitive search:
|
Example for a case insensitive search:
|
||||||
.PP
|
.PP
|
||||||
.Vb 1
|
.Vb 1
|
||||||
\& kubectl get pods \-A | tablizer "(?i)account"
|
\& kubectl get pods \-A | tablizer "/account/i"
|
||||||
.Ve
|
.Ve
|
||||||
.PP
|
.PP
|
||||||
You can use the experimental fuzzy search feature by providing the
|
If you use the \f(CW\*(C`!\*(C'\fR flag, then the regex match will be negated, that
|
||||||
|
is, if a line in the input matches the given regex, but \f(CW\*(C`!\*(C'\fR is
|
||||||
|
supplied, tablizer will \s-1NOT\s0 include it in the output.
|
||||||
|
.PP
|
||||||
|
For example, here we want to get all lines matching \*(L"foo\*(R" but not
|
||||||
|
\&\*(L"bar\*(R":
|
||||||
|
.PP
|
||||||
|
.Vb 1
|
||||||
|
\& cat table | tablizer foo \*(Aq/bar/!\*(Aq
|
||||||
|
.Ve
|
||||||
|
.PP
|
||||||
|
This would match a line \*(L"foo zorro\*(R" but not \*(L"foo bar\*(R".
|
||||||
|
.PP
|
||||||
|
The flags can also be combined.
|
||||||
|
.PP
|
||||||
|
You can also use the experimental fuzzy search feature by providing the
|
||||||
option \fB\-z\fR, in which case the pattern is regarded as a fuzzy search
|
option \fB\-z\fR, in which case the pattern is regarded as a fuzzy search
|
||||||
term, not a regexp.
|
term, not a regexp.
|
||||||
.PP
|
.PP
|
||||||
@@ -317,7 +344,27 @@ 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-1INTERACTIVE FILTERING\s0"
|
||||||
|
.IX Subsection "INTERACTIVE FILTERING"
|
||||||
|
You can also use the interactive mode, enabled with \f(CW\*(C`\-I\*(C'\fR to filter
|
||||||
|
and select rows. This mode is complementary, that is, other filter
|
||||||
|
options are still being respected.
|
||||||
|
.PP
|
||||||
|
To enter e filter, hit \f(CW\*(C`/\*(C'\fR, enter a filter string and finish with
|
||||||
|
\&\f(CW\*(C`ENTER\*(C'\fR. Use \f(CW\*(C`SPACE\*(C'\fR to select/deselect rows, use \f(CW\*(C`a\*(C'\fR to select all
|
||||||
|
(visible) rows.
|
||||||
|
.PP
|
||||||
|
Commit your selection with \f(CW\*(C`q\*(C'\fR. The selected rows are being fed to
|
||||||
|
the requested output mode as usual. Abort with \f(CW\*(C`CTRL\-c\*(C'\fR, in which
|
||||||
|
case the results of the interactive mode are being ignored and all
|
||||||
|
rows are being fed to output.
|
||||||
.SS "\s-1COLUMNS\s0"
|
.SS "\s-1COLUMNS\s0"
|
||||||
.IX Subsection "COLUMNS"
|
.IX Subsection "COLUMNS"
|
||||||
The parameter \fB\-c\fR can be used to specify, which columns to
|
The parameter \fB\-c\fR can be used to specify, which columns to
|
||||||
@@ -433,13 +480,27 @@ 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_NO_HEADER_NUMBERING> \- disable numbering of header fields, like \fB\-n\fR." 4
|
.IP "<T_HEADER_NUMBERING> \- enable numbering of header fields, like \fB\-n\fR." 4
|
||||||
.IX Item "<T_NO_HEADER_NUMBERING> - disable numbering of header fields, like -n."
|
.IX Item "<T_HEADER_NUMBERING> - enable 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"
|
||||||
@@ -570,6 +631,9 @@ Released under the \s-1MIT\s0 License, Copyright (c) 201 by Oleku Konko
|
|||||||
.IP "yaml (gopkg.in/yaml.v3)" 4
|
.IP "yaml (gopkg.in/yaml.v3)" 4
|
||||||
.IX Item "yaml (gopkg.in/yaml.v3)"
|
.IX Item "yaml (gopkg.in/yaml.v3)"
|
||||||
Released under the \s-1MIT\s0 License, Copyright (c) 2006\-2011 Kirill Simonov
|
Released under the \s-1MIT\s0 License, Copyright (c) 2006\-2011 Kirill Simonov
|
||||||
|
.IP "bubble-table (https://github.com/Evertras/bubble\-table)" 4
|
||||||
|
.IX Item "bubble-table (https://github.com/Evertras/bubble-table)"
|
||||||
|
Released under the \s-1MIT\s0 License, Copyright (c) 2022 Brandon Fulljames
|
||||||
.SH "AUTHORS"
|
.SH "AUTHORS"
|
||||||
.IX Header "AUTHORS"
|
.IX Header "AUTHORS"
|
||||||
Thomas von Dein \fBtom \s-1AT\s0 vondein \s-1DOT\s0 org\fR
|
Thomas von Dein \fBtom \s-1AT\s0 vondein \s-1DOT\s0 org\fR
|
||||||
|
|||||||
126
tablizer.pod
126
tablizer.pod
@@ -5,20 +5,21 @@ tablizer - Manipulate tabular output of other programs
|
|||||||
=head1 SYNOPSIS
|
=head1 SYNOPSIS
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
tablizer [regex] [file, ...] [flags]
|
tablizer [regex,...] [-r 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, --no-numbering Disable 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 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
|
||||||
|
-I, --interactive Interactively filter and select rows
|
||||||
|
|
||||||
Output Flags (mutually exclusive):
|
Output Flags (mutually exclusive):
|
||||||
-X, --extended Enable extended output
|
-X, --extended Enable extended output
|
||||||
@@ -29,6 +30,8 @@ tablizer - Manipulate tabular output of other programs
|
|||||||
-C, --csv Enable CSV output
|
-C, --csv Enable CSV output
|
||||||
-A, --ascii Default output mode, ascii tabular
|
-A, --ascii Default output mode, ascii tabular
|
||||||
-L, --hightlight-lines Use alternating background colors for tables
|
-L, --hightlight-lines Use alternating background colors for tables
|
||||||
|
-y, --yank-columns Yank specified columns (separated by ,) to clipboard,
|
||||||
|
space separated
|
||||||
|
|
||||||
Sort Mode Flags (mutually exclusive):
|
Sort Mode Flags (mutually exclusive):
|
||||||
-a, --sort-age sort according to age (duration) string
|
-a, --sort-age sort according to age (duration) string
|
||||||
@@ -37,12 +40,13 @@ tablizer - Manipulate tabular output of other programs
|
|||||||
-t, --sort-time sort according to time string
|
-t, --sort-time sort according to time string
|
||||||
|
|
||||||
Other Flags:
|
Other Flags:
|
||||||
--completion <shell> Generate the autocompletion script for <shell>
|
-r --read-file <file> Use <file> as input instead of STDIN
|
||||||
-f, --config <file> Configuration file (default: ~/.config/tablizer/config)
|
--completion <shell> Generate the autocompletion script for <shell>
|
||||||
-d, --debug Enable debugging
|
-f, --config <file> Configuration file (default: ~/.config/tablizer/config)
|
||||||
-h, --help help for tablizer
|
-d, --debug Enable debugging
|
||||||
-m, --man Display manual page
|
-h, --help help for tablizer
|
||||||
-V, --version Print program version
|
-m, --man Display manual page
|
||||||
|
-V, --version Print program version
|
||||||
|
|
||||||
|
|
||||||
=head1 DESCRIPTION
|
=head1 DESCRIPTION
|
||||||
@@ -106,11 +110,20 @@ By default, if a B<pattern> has been speficied, matches will be
|
|||||||
highlighted. You can disable this behavior with the B<-N> option.
|
highlighted. You can disable this behavior with the B<-N> option.
|
||||||
|
|
||||||
Use the B<-k> option to specify by which column to sort the tabular
|
Use the B<-k> option to specify by which column to sort the tabular
|
||||||
data (as in GNU sort(1)). The default sort column is the first one. To
|
data (as in GNU sort(1)). The default sort column is the first
|
||||||
disable sorting at all, supply 0 (Zero) to -k. The default sort order
|
one. You can specify column numbers or names. Column numbers start
|
||||||
is ascending. You can change this to descending order using the option
|
with 1, names are case insensitive. You can specify multiple columns
|
||||||
B<-D>. The default sort order is by string, but there are other sort
|
separated by comma to sort, but the type must be the same. For example
|
||||||
modes:
|
if you want to sort numerically, all columns must be numbers. If you
|
||||||
|
use column numbers, then be aware, that these are the numbers before
|
||||||
|
column extraction. For example if you have a table with 4 columns and
|
||||||
|
specify C<-c4>, then only 1 column (the fourth) will be printed,
|
||||||
|
however if you want to sort by this column, you'll have to specify
|
||||||
|
C<-k4>.
|
||||||
|
|
||||||
|
The default sort order is ascending. You can change this to
|
||||||
|
descending order using the option B<-D>. The default sort order is by
|
||||||
|
alphanumeric string, but there are other sort modes:
|
||||||
|
|
||||||
=over
|
=over
|
||||||
|
|
||||||
@@ -133,32 +146,44 @@ useful for the developer.
|
|||||||
|
|
||||||
=head2 PATTERNS AND FILTERING
|
=head2 PATTERNS AND FILTERING
|
||||||
|
|
||||||
You can reduce the rows being displayed by using a regular expression
|
You can reduce the rows being displayed by using one or more regular
|
||||||
pattern. The regexp is PCRE compatible, refer to the syntax cheat
|
expression patterns. The regexp language being used is the one of
|
||||||
sheet here: L<https://github.com/google/re2/wiki/Syntax>. If you want
|
GOLANG, refer to the syntax cheat sheet here:
|
||||||
to read a more comprehensive documentation about the topic and have
|
L<https://pkg.go.dev/regexp/syntax>.
|
||||||
perl installed you can read it with:
|
|
||||||
|
If you want to read a more comprehensive documentation about the
|
||||||
|
topic and have perl installed you can read it with:
|
||||||
|
|
||||||
perldoc perlre
|
perldoc perlre
|
||||||
|
|
||||||
Or read it online: L<https://perldoc.perl.org/perlre>.
|
Or read it online: L<https://perldoc.perl.org/perlre>. But please note
|
||||||
|
that the GO regexp engine does NOT support all perl regex terms,
|
||||||
|
especially look-ahead and look-behind.
|
||||||
|
|
||||||
A note on modifiers: the regexp engine used in tablizer uses another
|
If you want to supply flags to a regex, then surround it with slashes
|
||||||
modifier syntax:
|
and append the flag. The following flags are supported:
|
||||||
|
|
||||||
(?MODIFIER)
|
i => case insensitive
|
||||||
|
! => negative match
|
||||||
The most important modifiers are:
|
|
||||||
|
|
||||||
C<i> ignore case
|
|
||||||
C<m> multiline mode
|
|
||||||
C<s> single line mode
|
|
||||||
|
|
||||||
Example for a case insensitive search:
|
Example for a case insensitive search:
|
||||||
|
|
||||||
kubectl get pods -A | tablizer "(?i)account"
|
kubectl get pods -A | tablizer "/account/i"
|
||||||
|
|
||||||
You can use the experimental fuzzy search feature by providing the
|
If you use the C<!> flag, then the regex match will be negated, that
|
||||||
|
is, if a line in the input matches the given regex, but C<!> is
|
||||||
|
supplied, tablizer will NOT include it in the output.
|
||||||
|
|
||||||
|
For example, here we want to get all lines matching "foo" but not
|
||||||
|
"bar":
|
||||||
|
|
||||||
|
cat table | tablizer foo '/bar/!'
|
||||||
|
|
||||||
|
This would match a line "foo zorro" but not "foo bar".
|
||||||
|
|
||||||
|
The flags can also be combined.
|
||||||
|
|
||||||
|
You can also use the experimental fuzzy search feature by providing the
|
||||||
option B<-z>, in which case the pattern is regarded as a fuzzy search
|
option B<-z>, in which case the pattern is regarded as a fuzzy search
|
||||||
term, not a regexp.
|
term, not a regexp.
|
||||||
|
|
||||||
@@ -173,8 +198,26 @@ 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.
|
||||||
|
|
||||||
|
=head2 INTERACTIVE FILTERING
|
||||||
|
|
||||||
|
You can also use the interactive mode, enabled with C<-I> to filter
|
||||||
|
and select rows. This mode is complementary, that is, other filter
|
||||||
|
options are still being respected.
|
||||||
|
|
||||||
|
To enter e filter, hit C</>, enter a filter string and finish with
|
||||||
|
C<ENTER>. Use C<SPACE> to select/deselect rows, use C<a> to select all
|
||||||
|
(visible) rows.
|
||||||
|
|
||||||
|
Commit your selection with C<q>. The selected rows are being fed to
|
||||||
|
the requested output mode as usual. Abort with C<CTRL-c>, in which
|
||||||
|
case the results of the interactive mode are being ignored and all
|
||||||
|
rows are being fed to output.
|
||||||
|
|
||||||
=head2 COLUMNS
|
=head2 COLUMNS
|
||||||
|
|
||||||
@@ -281,6 +324,19 @@ table and B<markdown> which prints a Markdown table, B<yaml>, which
|
|||||||
prints yaml encoding and CSV mode, which prints a comma separated
|
prints yaml encoding and CSV mode, which prints a comma separated
|
||||||
value file.
|
value file.
|
||||||
|
|
||||||
|
=head2 PUT FIELDS TO CLIPBOARD
|
||||||
|
|
||||||
|
You can let tablizer put fields to the clipboard using the option
|
||||||
|
C<-y>. This best fits the use-case when the result of your filtering
|
||||||
|
yields just one row. For example:
|
||||||
|
|
||||||
|
cloudctl cluster ls | tablizer -yid matchbox
|
||||||
|
|
||||||
|
If "matchbox" matches one cluster, you can immediately use the id of
|
||||||
|
that cluster somewhere else and paste it. Of course, if there are
|
||||||
|
multiple matches, then all id's will be put into the clipboard
|
||||||
|
separated by one space.
|
||||||
|
|
||||||
=head2 ENVIRONMENT VARIABLES
|
=head2 ENVIRONMENT VARIABLES
|
||||||
|
|
||||||
B<tablizer> supports certain environment variables which use can use
|
B<tablizer> supports certain environment variables which use can use
|
||||||
@@ -289,7 +345,7 @@ precedence over environment variables.
|
|||||||
|
|
||||||
=over
|
=over
|
||||||
|
|
||||||
=item <T_NO_HEADER_NUMBERING> - disable numbering of header fields, like B<-n>.
|
=item <T_HEADER_NUMBERING> - enable 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>
|
||||||
|
|
||||||
@@ -425,6 +481,10 @@ Released under the MIT License, Copyright (c) 201 by Oleku Konko
|
|||||||
|
|
||||||
Released under the MIT License, Copyright (c) 2006-2011 Kirill Simonov
|
Released under the MIT License, Copyright (c) 2006-2011 Kirill Simonov
|
||||||
|
|
||||||
|
=item bubble-table (https://github.com/Evertras/bubble-table)
|
||||||
|
|
||||||
|
Released under the MIT License, Copyright (c) 2022 Brandon Fulljames
|
||||||
|
|
||||||
=back
|
=back
|
||||||
|
|
||||||
=head1 AUTHORS
|
=head1 AUTHORS
|
||||||
|
|||||||
15
vhsdemo/Makefile
Normal file
15
vhsdemo/Makefile
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
.PHONY: demo check clean-demo
|
||||||
|
|
||||||
|
VHS = vhs
|
||||||
|
|
||||||
|
clean-demo:
|
||||||
|
|
||||||
|
%.gif: %.tape
|
||||||
|
@echo "vhs $<"
|
||||||
|
env PATH=..:$(PATH) vhs $<
|
||||||
|
|
||||||
|
check:
|
||||||
|
ls -l ../tablizer
|
||||||
|
|
||||||
|
demo: check clean-demo demo.gif
|
||||||
|
|
||||||
BIN
vhsdemo/demo.gif
Normal file
BIN
vhsdemo/demo.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.1 MiB |
157
vhsdemo/demo.tape
Normal file
157
vhsdemo/demo.tape
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
# -*-sh-*-
|
||||||
|
|
||||||
|
Output demo.gif
|
||||||
|
Set FontSize 20
|
||||||
|
Set Width 1200
|
||||||
|
Set Height 1000
|
||||||
|
Set Theme { "name": "Whimsy", "black": "#535178", "red": "#ef6487", "green": "#5eca89", "yellow": "#fdd877", "blue": "#65aef7", "magenta": "#aa7ff0", "cyan": "#43c1be", "white": "#ffffff", "brightBlack": "#535178", "brightRed": "#ef6487", "brightGreen": "#5eca89", "brightYellow": "#fdd877", "brightBlue": "#65aef7", "brightMagenta": "#aa7ff0", "brightCyan": "#43c1be", "brightWhite": "#ffffff", "background": "#29283b", "foreground": "#b3b0d6", "selection": "#3d3c58", "cursor": "#b3b0d6" }
|
||||||
|
Set WindowBar Colorful
|
||||||
|
Set BorderRadius 10
|
||||||
|
Set Shell zsh
|
||||||
|
Set FontFamily "IBM Plex Mono"
|
||||||
|
Set CursorBlink false
|
||||||
|
Set PlaybackSpeed 1
|
||||||
|
Set TypingSpeed .05
|
||||||
|
|
||||||
|
# initialize
|
||||||
|
Hide
|
||||||
|
Type `PROMPT=''`
|
||||||
|
Enter
|
||||||
|
Type "setopt interactivecomments"
|
||||||
|
Enter
|
||||||
|
Type "autoload -U colors && colors"
|
||||||
|
Enter
|
||||||
|
Type `PS1="%{$fg[magenta]%}demo> %{$reset_color%}"`
|
||||||
|
Enter
|
||||||
|
Type "clear"
|
||||||
|
Enter
|
||||||
|
Show
|
||||||
|
|
||||||
|
Type "# Our input data"
|
||||||
|
Enter
|
||||||
|
Sleep 1s
|
||||||
|
Type "cat input | head -10"
|
||||||
|
Enter
|
||||||
|
Sleep 2s
|
||||||
|
|
||||||
|
Enter
|
||||||
|
Type "# Filter over all rows"
|
||||||
|
Enter
|
||||||
|
Sleep 1s
|
||||||
|
Type "tablizer Central < input"
|
||||||
|
Enter
|
||||||
|
Sleep 2s
|
||||||
|
|
||||||
|
Enter
|
||||||
|
Type "# Filter over all rows case insensitive"
|
||||||
|
Enter
|
||||||
|
Sleep 1s
|
||||||
|
Type "tablizer '/penc/i' < input"
|
||||||
|
Enter
|
||||||
|
Sleep 2s
|
||||||
|
|
||||||
|
Enter
|
||||||
|
Type "# Filter over specific column"
|
||||||
|
Enter
|
||||||
|
Sleep 1s
|
||||||
|
Type "tablizer -Fcost=4.99 < input"
|
||||||
|
Enter
|
||||||
|
Sleep 2s
|
||||||
|
|
||||||
|
Enter
|
||||||
|
Type "# Filter by regex on specific column"
|
||||||
|
Enter
|
||||||
|
Sleep 1s
|
||||||
|
Type "tablizer -Funits=Pen. < input"
|
||||||
|
Enter
|
||||||
|
Sleep 2s
|
||||||
|
|
||||||
|
Enter
|
||||||
|
Type "# Output as markdown"
|
||||||
|
Enter
|
||||||
|
Sleep 1s
|
||||||
|
Type "tablizer -Funits=Pen. -M < input"
|
||||||
|
Enter
|
||||||
|
Sleep 2s
|
||||||
|
|
||||||
|
Enter
|
||||||
|
Type "# Output as CSV"
|
||||||
|
Enter
|
||||||
|
Sleep 1s
|
||||||
|
Type "tablizer -Funits=Pen. -C < input"
|
||||||
|
Enter
|
||||||
|
Sleep 2s
|
||||||
|
|
||||||
|
Enter
|
||||||
|
Type "# Output as shell evaluable"
|
||||||
|
Enter
|
||||||
|
Sleep 1s
|
||||||
|
Type "tablizer -Funits=Pen. -S < input"
|
||||||
|
Enter
|
||||||
|
Sleep 2s
|
||||||
|
Type "bat eval.sh"
|
||||||
|
Enter
|
||||||
|
Sleep 2s
|
||||||
|
Type "tablizer -Funits=Pen. -S < input | ./eval.sh"
|
||||||
|
Enter
|
||||||
|
Sleep 2s
|
||||||
|
|
||||||
|
Enter
|
||||||
|
Type "# Reduce columns"
|
||||||
|
Enter
|
||||||
|
Sleep 1s
|
||||||
|
Type "tablizer -Funits=Pen. -c region,customer,units,count < input"
|
||||||
|
Enter
|
||||||
|
Sleep 2s
|
||||||
|
|
||||||
|
Enter
|
||||||
|
Type "# Sort by COUNT column numerically "
|
||||||
|
Enter
|
||||||
|
Sleep 1s
|
||||||
|
Type "tablizer -Funits=Pen. -c region,customer,units,count -kcount -i < input"
|
||||||
|
Enter
|
||||||
|
Sleep 2s
|
||||||
|
|
||||||
|
Enter
|
||||||
|
Type "# Do further filtering interactively"
|
||||||
|
Enter
|
||||||
|
Sleep 1s
|
||||||
|
Type "tablizer -Funits=Pen. -c region,customer,units,count -I -O < input"
|
||||||
|
Enter
|
||||||
|
Sleep 2s
|
||||||
|
Type "?"
|
||||||
|
Sleep 2s
|
||||||
|
Type "/"
|
||||||
|
Sleep 2s
|
||||||
|
Type "J"
|
||||||
|
Sleep 1s
|
||||||
|
Type "o"
|
||||||
|
Sleep 1s
|
||||||
|
Type "n"
|
||||||
|
Sleep 1s
|
||||||
|
Type "e"
|
||||||
|
Sleep 1s
|
||||||
|
Type "s"
|
||||||
|
Sleep 1s
|
||||||
|
Enter
|
||||||
|
Sleep 2s
|
||||||
|
Tab
|
||||||
|
Sleep 1s
|
||||||
|
Tab
|
||||||
|
Sleep 1s
|
||||||
|
Tab
|
||||||
|
Sleep 1s
|
||||||
|
Tab
|
||||||
|
Type "n"
|
||||||
|
Sleep 2s
|
||||||
|
Space
|
||||||
|
Sleep 1s
|
||||||
|
Down
|
||||||
|
Sleep 1s
|
||||||
|
Down
|
||||||
|
Sleep 1s
|
||||||
|
Space
|
||||||
|
Sleep 2s
|
||||||
|
Type "q"
|
||||||
|
|
||||||
|
Sleep 10s
|
||||||
5
vhsdemo/eval.sh
Executable file
5
vhsdemo/eval.sh
Executable file
@@ -0,0 +1,5 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
while read LINE; do
|
||||||
|
eval "$LINE"; echo "$Customer ordered $Count ${Units}s"
|
||||||
|
done
|
||||||
44
vhsdemo/input
Normal file
44
vhsdemo/input
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
Date Region Customer Units Count Cost Total
|
||||||
|
2016-01-06 East Jones Pencil 95 1.99 189.05
|
||||||
|
2016-01-23 Central Kivell Binder 50 19.99 999.50
|
||||||
|
2016-02-09 Central Jardine Pencil 36 4.99 179.64
|
||||||
|
2016-02-26 Central Gill Pen 27 19.99 539.73
|
||||||
|
2016-03-15 West Sorvino Pencil 56 2.99 167.44
|
||||||
|
2016-04-01 East Jones Binder 60 4.99 299.40
|
||||||
|
2016-04-18 Central Andrews Pencil 75 1.99 149.25
|
||||||
|
2016-05-05 Central Jardine Pencil 90 4.99 449.10
|
||||||
|
2016-05-22 West Thompson Pencil 32 1.99 63.68
|
||||||
|
2016-06-08 East Jones Binder 60 8.99 539.40
|
||||||
|
2016-06-25 Central Morgan Pencil 90 4.99 449.10
|
||||||
|
2016-07-12 East Howard Binder 29 1.99 57.71
|
||||||
|
2016-07-29 East Parent Binder 81 19.99 1619.19
|
||||||
|
2016-08-15 East Jones Pencil 35 4.99 174.65
|
||||||
|
2016-09-01 Central Smith Desk 2 125.00 250.00
|
||||||
|
2016-09-18 East Jones Pen Set 16 15.99 255.84
|
||||||
|
2016-10-05 Central Morgan Binder 28 8.99 251.72
|
||||||
|
2016-10-22 East Jones Pen 64 8.99 575.36
|
||||||
|
2016-11-08 East Parent Pen 15 19.99 299.85
|
||||||
|
2016-11-25 Central Kivell Pen Set 96 4.99 479.04
|
||||||
|
2016-12-12 Central Smith Pencil 67 1.29 86.43
|
||||||
|
2016-12-29 East Parent Pen Set 74 15.99 1183.26
|
||||||
|
2017-01-15 Central Gill Binder 46 8.99 413.54
|
||||||
|
2017-02-01 Central Smith Binder 87 15.00 1305.00
|
||||||
|
2017-02-18 East Jones Binder 4 4.99 19.96
|
||||||
|
2017-03-07 West Sorvino Binder 7 19.99 139.93
|
||||||
|
2017-03-24 Central Jardine Pen Set 50 4.99 249.50
|
||||||
|
2017-04-10 Central Andrews Pencil 66 1.99 131.34
|
||||||
|
2017-04-27 East Howard Pen 96 4.99 479.04
|
||||||
|
2017-05-14 Central Gill Pencil 53 1.29 68.37
|
||||||
|
2017-05-31 Central Gill Binder 80 8.99 719.20
|
||||||
|
2017-06-17 Central Kivell Desk 5 125.00 625.00
|
||||||
|
2017-07-04 East Jones Pen Set 62 4.99 309.38
|
||||||
|
2017-07-21 Central Morgan Pen Set 55 12.49 686.95
|
||||||
|
2017-08-07 Central Kivell Pen Set 42 23.95 1005.90
|
||||||
|
2017-08-24 West Sorvino Desk 3 275.00 825.00
|
||||||
|
2017-09-10 Central Gill Pencil 7 1.29 9.03
|
||||||
|
2017-09-27 West Sorvino Pen 76 1.99 151.24
|
||||||
|
2017-10-14 West Thompson Binder 57 19.99 1139.43
|
||||||
|
2017-10-31 Central Andrews Pencil 14 1.29 18.06
|
||||||
|
2017-11-17 Central Jardine Binder 11 4.99 54.89
|
||||||
|
2017-12-04 Central Jardine Binder 94 19.99 1879.06
|
||||||
|
2017-12-21 Central Andrews Binder 28 4.99 139.72
|
||||||
Reference in New Issue
Block a user