mirror of
https://codeberg.org/scip/tablizer.git
synced 2025-12-18 21:11:03 +01:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7cdd7444a5 | |||
| 128a2c22f7 |
22
.github/workflows/ci.yaml
vendored
22
.github/workflows/ci.yaml
vendored
@@ -4,21 +4,19 @@ jobs:
|
||||
build:
|
||||
strategy:
|
||||
matrix:
|
||||
version: ['1.22']
|
||||
# windows-latest removed, see:
|
||||
# https://github.com/rogpeppe/go-internal/issues/284
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
version: [1.18, 1.19]
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
name: Build
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Set up Go ${{ matrix.version }}
|
||||
uses: actions/setup-go@v5
|
||||
- name: Set up Go 1.18
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '${{ matrix.version }}'
|
||||
go-version: ${{ matrix.version }}
|
||||
id: go
|
||||
|
||||
- name: checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: build
|
||||
run: make
|
||||
@@ -30,11 +28,11 @@ jobs:
|
||||
name: lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/setup-go@v5
|
||||
- uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: 1.22
|
||||
- uses: actions/checkout@v4
|
||||
go-version: 1.18
|
||||
- uses: actions/checkout@v3
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v6
|
||||
uses: golangci/golangci-lint-action@v3
|
||||
with:
|
||||
skip-cache: true
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,3 +1,2 @@
|
||||
releases
|
||||
tablizer
|
||||
*.out
|
||||
|
||||
5
Makefile
5
Makefile
@@ -66,10 +66,11 @@ clean:
|
||||
rm -rf $(tool) releases coverage.out
|
||||
|
||||
test:
|
||||
go test ./... $(OPTS)
|
||||
go test -v ./...
|
||||
bash t/test.sh
|
||||
|
||||
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)
|
||||
|
||||
cover-report:
|
||||
|
||||
95
README.md
95
README.md
@@ -8,49 +8,6 @@ Tablizer can be used to re-format tabular output of other
|
||||
programs. While you could do this using standard unix tools, in some
|
||||
cases it's a hard job.
|
||||
|
||||
Usage:
|
||||
```default
|
||||
Usage:
|
||||
tablizer [regex] [file, ...] [flags]
|
||||
|
||||
Operational Flags:
|
||||
-c, --columns string Only show the speficied columns (separated by ,)
|
||||
-v, --invert-match select non-matching rows
|
||||
-n, --no-numbering Disable header numbering
|
||||
-N, --no-color Disable pattern highlighting
|
||||
-H, --no-headers Disable headers display
|
||||
-s, --separator string Custom field separator
|
||||
-k, --sort-by int Sort by column (default: 1)
|
||||
-z, --fuzzy Use fuzzy search [experimental]
|
||||
-F, --filter field=reg Filter given field with regex, can be used multiple times
|
||||
-T, --transpose-columns string Transpose the speficied columns (separated by ,)
|
||||
-R, --regex-transposer /from/to/ Apply /search/replace/ regexp to fields given in -T
|
||||
|
||||
Output Flags (mutually exclusive):
|
||||
-X, --extended Enable extended output
|
||||
-M, --markdown Enable markdown table output
|
||||
-O, --orgtbl Enable org-mode table output
|
||||
-S, --shell Enable shell evaluable output
|
||||
-Y, --yaml Enable yaml output
|
||||
-C, --csv Enable CSV output
|
||||
-A, --ascii Default output mode, ascii tabular
|
||||
-L, --hightlight-lines Use alternating background colors for tables
|
||||
|
||||
Sort Mode Flags (mutually exclusive):
|
||||
-a, --sort-age sort according to age (duration) string
|
||||
-D, --sort-desc Sort in descending order (default: ascending)
|
||||
-i, --sort-numeric sort according to string numerical value
|
||||
-t, --sort-time sort according to time string
|
||||
|
||||
Other Flags:
|
||||
--completion <shell> Generate the autocompletion script for <shell>
|
||||
-f, --config <file> Configuration file (default: ~/.config/tablizer/config)
|
||||
-d, --debug Enable debugging
|
||||
-h, --help help for tablizer
|
||||
-m, --man Display manual page
|
||||
-V, --version Print program version
|
||||
```
|
||||
|
||||
Let's take this output:
|
||||
```
|
||||
% kubectl get pods -o wide
|
||||
@@ -126,23 +83,6 @@ otherwise on all rows.
|
||||
|
||||
There are more output modes like org-mode (orgtbl) and markdown.
|
||||
|
||||
You can also use it to modify certain cells using regular expression
|
||||
matching. For example:
|
||||
|
||||
```shell
|
||||
kubectl get pods | tablizer -n -T4 -R '/ /-/'
|
||||
NAME READY STATUS RESTARTS AGE
|
||||
repldepl-7bcd8d5b64-7zq4l 1/1 Running 1-(69m-ago) 5h26m
|
||||
repldepl-7bcd8d5b64-m48n8 1/1 Running 1-(69m-ago) 5h26m
|
||||
repldepl-7bcd8d5b64-q2bf4 1/1 Running 1-(69m-ago) 5h26m
|
||||
```
|
||||
|
||||
Here, we modified the 4th column (`-T4`) by replacing every space with
|
||||
a dash. If you need to work with `/` characters, you can also use any
|
||||
other separator, for instance: `-R '| |-|'`.
|
||||
|
||||
|
||||
|
||||
## Demo
|
||||
|
||||
[](https://asciinema.org/a/9FKc3HPnlg8D2X8otheleEa9t)
|
||||
@@ -198,41 +138,6 @@ In order to report a bug, unexpected behavior, feature requests
|
||||
or to submit a patch, please open an issue on github:
|
||||
https://github.com/TLINDEN/tablizer/issues.
|
||||
|
||||
## Prior Art
|
||||
|
||||
When I started with tablizer I was not aware that other tools
|
||||
exist. Here is a non-exhausive list of the ones I find especially
|
||||
awesome:
|
||||
|
||||
### [miller](https://github.com/johnkerl/miller)
|
||||
|
||||
This is a really powerful tool to work with tabular data and it also
|
||||
allows other inputs as json, csv etc. You can filter, manipulate,
|
||||
create pipelines, there's even a programming language builtin to do
|
||||
even more amazing things.
|
||||
|
||||
### [csvq](https://github.com/mithrandie/csvq)
|
||||
|
||||
Csvq allows you to query CSV and TSV data using SQL queries. How nice
|
||||
is that? Highly recommended if you have to work with a large (and
|
||||
wide) dataset and need to apply a complicated set of rules.
|
||||
|
||||
### [goawk](https://github.com/benhoyt/goawk)
|
||||
|
||||
Goawk is a 100% POSIX compliant AWK implementation in GO, which also
|
||||
supports CSV and TSV data as input (using `-i csv` for example). You
|
||||
can apply any kind of awk code to your tabular data, there are no
|
||||
limit to your creativity!
|
||||
|
||||
### [teip](https://github.com/greymd/teip)
|
||||
|
||||
I particularly like teip, it's a real gem. You can use it to drill
|
||||
"holes" into your tabular data and modify these "holes" using small
|
||||
external unix commands such as grep or sed. The possibilities are
|
||||
endless, you can even use teip to modify data inside a hole created by
|
||||
teip. Highly recommended.
|
||||
|
||||
|
||||
## Copyright and license
|
||||
|
||||
This software is licensed under the GNU GENERAL PUBLIC LICENSE version 3.
|
||||
|
||||
10
TODO.md
10
TODO.md
@@ -6,3 +6,13 @@
|
||||
|
||||
- add --no-headers option
|
||||
|
||||
### Lisp Plugin Infrastructure using zygo
|
||||
|
||||
Hooks:
|
||||
|
||||
| Filter | Purpose | Args | Return |
|
||||
|-----------|-------------------------------------------------------------|---------------------|--------|
|
||||
| filter | include or exclude lines | row as hash | bool |
|
||||
| process | do calculations with data, store results in global lisp env | whole dataset | nil |
|
||||
| transpose | modify a cell | headername and cell | cell |
|
||||
| append | add one or more rows to the dataset (use this to add stats) | nil | rows |
|
||||
|
||||
@@ -23,14 +23,16 @@ import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/glycerine/zygomys/zygo"
|
||||
"github.com/gookit/color"
|
||||
"github.com/hashicorp/hcl/v2/hclsimple"
|
||||
)
|
||||
|
||||
const DefaultSeparator string = `(\s\s+|\t)`
|
||||
const Version string = "v1.3.0"
|
||||
const Version string = "v1.2.0"
|
||||
const MAXPARTS = 2
|
||||
|
||||
var DefaultLoadPath = os.Getenv("HOME") + "/.config/tablizer/lisp"
|
||||
var DefaultConfigfile = os.Getenv("HOME") + "/.config/tablizer/config"
|
||||
|
||||
var VERSION string // maintained by -x
|
||||
@@ -47,11 +49,6 @@ type Settings struct {
|
||||
HighlightHdrBG string `hcl:"HighlightHdrBG"`
|
||||
}
|
||||
|
||||
type Transposer struct {
|
||||
Search regexp.Regexp
|
||||
Replace string
|
||||
}
|
||||
|
||||
// internal config
|
||||
type Config struct {
|
||||
Debug bool
|
||||
@@ -71,11 +68,6 @@ type Config struct {
|
||||
SortDescending bool
|
||||
SortByColumn int
|
||||
|
||||
TransposeColumns string // 1,2
|
||||
UseTransposeColumns []int // []int{1,2}
|
||||
Transposers []string // []string{"/ /-/", "/foo/bar/"}
|
||||
UseTransposers []Transposer // {Search: re, Replace: string}
|
||||
|
||||
/*
|
||||
FIXME: make configurable somehow, config file or ENV
|
||||
see https://github.com/gookit/color.
|
||||
@@ -87,6 +79,13 @@ type Config struct {
|
||||
|
||||
NoColor bool
|
||||
|
||||
// special case: we use the config struct to transport the lisp
|
||||
// env trough the program
|
||||
Lisp *zygo.Zlisp
|
||||
|
||||
// a path containing lisp scripts to be loaded on startup
|
||||
LispLoadPath string
|
||||
|
||||
// config file, optional
|
||||
Configfile string
|
||||
|
||||
@@ -95,9 +94,6 @@ type Config struct {
|
||||
// used for field filtering
|
||||
Rawfilters []string
|
||||
Filters map[string]*regexp.Regexp
|
||||
|
||||
// -r <file>
|
||||
InputFile string
|
||||
}
|
||||
|
||||
// maps outputmode short flags to output mode, ie. -O => -o orgtbl
|
||||
@@ -129,6 +125,9 @@ type Sortmode struct {
|
||||
Age bool
|
||||
}
|
||||
|
||||
// valid lisp hooks
|
||||
var ValidHooks []string
|
||||
|
||||
// default color schemes
|
||||
func (conf *Config) Colors() map[color.Level]map[string]color.Color {
|
||||
colors := map[color.Level]map[string]color.Color{
|
||||
@@ -278,30 +277,7 @@ func (conf *Config) PrepareFilters() error {
|
||||
parts[0], err)
|
||||
}
|
||||
|
||||
conf.Filters[strings.ToLower(strings.ToLower(parts[0]))] = reg
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// check if transposers match transposer columns and prepare transposer structs
|
||||
func (conf *Config) PrepareTransposers() error {
|
||||
if len(conf.Transposers) != len(conf.UseTransposeColumns) {
|
||||
return fmt.Errorf("the number of transposers needs to correspond to the number of transpose columns: %d != %d",
|
||||
len(conf.Transposers), len(conf.UseTransposeColumns))
|
||||
}
|
||||
|
||||
for _, transposer := range conf.Transposers {
|
||||
parts := strings.Split(transposer, string(transposer[0]))
|
||||
if len(parts) != 4 {
|
||||
return fmt.Errorf("transposer function must have the format /regexp/replace-string/")
|
||||
}
|
||||
|
||||
conf.UseTransposers = append(conf.UseTransposers,
|
||||
Transposer{
|
||||
Search: *regexp.MustCompile(parts[1]),
|
||||
Replace: parts[2]},
|
||||
)
|
||||
conf.Filters[strings.ToLower(parts[0])] = reg
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -330,6 +306,8 @@ func (conf *Config) ApplyDefaults() {
|
||||
if conf.OutputMode == Yaml || conf.OutputMode == CSV {
|
||||
conf.NoNumbering = true
|
||||
}
|
||||
|
||||
ValidHooks = []string{"filter", "process", "transpose", "append"}
|
||||
}
|
||||
|
||||
func (conf *Config) PreparePattern(pattern string) error {
|
||||
@@ -350,16 +328,7 @@ func (conf *Config) PreparePattern(pattern string) error {
|
||||
func (conf *Config) ParseConfigfile() error {
|
||||
path, err := os.Stat(conf.Configfile)
|
||||
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
// ignore non-existent files
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("failed to stat config file: %w", err)
|
||||
}
|
||||
|
||||
if path.IsDir() {
|
||||
if os.IsNotExist(err) || path.IsDir() {
|
||||
// ignore non-existent or dirs
|
||||
return nil
|
||||
}
|
||||
|
||||
51
cmd/root.go
51
cmd/root.go
@@ -63,14 +63,6 @@ func completion(cmd *cobra.Command, mode string) error {
|
||||
}
|
||||
}
|
||||
|
||||
// we die with exit 1 if there's an error
|
||||
func wrapE(err error) {
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func Execute() {
|
||||
var (
|
||||
conf cfg.Config
|
||||
@@ -85,40 +77,48 @@ func Execute() {
|
||||
Use: "tablizer [regex] [file, ...]",
|
||||
Short: "[Re-]tabularize tabular data",
|
||||
Long: `Manipulate tabular output of other programs`,
|
||||
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if ShowVersion {
|
||||
fmt.Println(cfg.Getversion())
|
||||
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
if ShowManual {
|
||||
man()
|
||||
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(ShowCompletion) > 0 {
|
||||
wrapE(completion(cmd, ShowCompletion))
|
||||
|
||||
return
|
||||
return completion(cmd, ShowCompletion)
|
||||
}
|
||||
|
||||
// Setup
|
||||
wrapE(conf.ParseConfigfile())
|
||||
err := conf.ParseConfigfile()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
conf.CheckEnv()
|
||||
conf.PrepareModeFlags(modeflag)
|
||||
conf.PrepareSortFlags(sortmode)
|
||||
|
||||
wrapE(conf.PrepareFilters())
|
||||
if err = conf.PrepareFilters(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
conf.DetermineColormode()
|
||||
conf.ApplyDefaults()
|
||||
|
||||
// setup lisp env, load plugins etc
|
||||
err = lib.SetupLisp(&conf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// actual execution starts here
|
||||
wrapE(lib.ProcessFiles(&conf, args))
|
||||
return lib.ProcessFiles(&conf, args)
|
||||
},
|
||||
}
|
||||
|
||||
@@ -147,8 +147,6 @@ func Execute() {
|
||||
"Custom field separator")
|
||||
rootCmd.PersistentFlags().StringVarP(&conf.Columns, "columns", "c", "",
|
||||
"Only show the speficied columns (separated by ,)")
|
||||
rootCmd.PersistentFlags().StringVarP(&conf.TransposeColumns, "transpose-columns", "T", "",
|
||||
"Transpose the speficied columns (separated by ,)")
|
||||
|
||||
// sort options
|
||||
rootCmd.PersistentFlags().IntVarP(&conf.SortByColumn, "sort-by", "k", 0,
|
||||
@@ -184,19 +182,16 @@ func Execute() {
|
||||
rootCmd.MarkFlagsMutuallyExclusive("extended", "markdown", "orgtbl",
|
||||
"shell", "yaml", "csv")
|
||||
|
||||
// lisp options
|
||||
rootCmd.PersistentFlags().StringVarP(&conf.LispLoadPath, "load-path", "l", cfg.DefaultLoadPath,
|
||||
"Load path for lisp plugins (expects *.zy files)")
|
||||
|
||||
// config file
|
||||
rootCmd.PersistentFlags().StringVarP(&conf.Configfile, "config", "f", cfg.DefaultConfigfile,
|
||||
"config file (default: ~/.config/tablizer/config)")
|
||||
|
||||
// filters
|
||||
rootCmd.PersistentFlags().StringArrayVarP(&conf.Rawfilters,
|
||||
"filter", "F", nil, "Filter by field (field=regexp)")
|
||||
rootCmd.PersistentFlags().StringArrayVarP(&conf.Transposers,
|
||||
"regex-transposer", "R", nil, "apply /search/replace/ regexp to fields given in -T")
|
||||
|
||||
// input
|
||||
rootCmd.PersistentFlags().StringVarP(&conf.InputFile, "read-file", "r", "",
|
||||
"Read input data from file")
|
||||
rootCmd.PersistentFlags().StringArrayVarP(&conf.Rawfilters, "filter", "F", nil, "Filter by field (field=regexp)")
|
||||
|
||||
rootCmd.SetUsageTemplate(strings.TrimSpace(usage) + "\n")
|
||||
|
||||
|
||||
150
cmd/tablizer.go
150
cmd/tablizer.go
@@ -9,41 +9,39 @@ SYNOPSIS
|
||||
tablizer [regex] [file, ...] [flags]
|
||||
|
||||
Operational Flags:
|
||||
-c, --columns string Only show the speficied columns (separated by ,)
|
||||
-v, --invert-match select non-matching rows
|
||||
-n, --no-numbering Disable header numbering
|
||||
-N, --no-color Disable pattern highlighting
|
||||
-H, --no-headers Disable headers display
|
||||
-s, --separator string Custom field separator
|
||||
-k, --sort-by int Sort by column (default: 1)
|
||||
-z, --fuzzy Use fuzzy search [experimental]
|
||||
-F, --filter field=reg Filter given field with regex, can be used multiple times
|
||||
-T, --transpose-columns string Transpose the speficied columns (separated by ,)
|
||||
-R, --regex-transposer /from/to/ Apply /search/replace/ regexp to fields given in -T
|
||||
-c, --columns string Only show the speficied columns (separated by ,)
|
||||
-v, --invert-match select non-matching rows
|
||||
-n, --no-numbering Disable header numbering
|
||||
-N, --no-color Disable pattern highlighting
|
||||
-H, --no-headers Disable headers display
|
||||
-s, --separator string Custom field separator
|
||||
-k, --sort-by int Sort by column (default: 1)
|
||||
-z, --fuzzy Use fuzzy search [experimental]
|
||||
-F, --filter field=reg Filter given field with regex, can be used multiple times
|
||||
|
||||
Output Flags (mutually exclusive):
|
||||
-X, --extended Enable extended output
|
||||
-M, --markdown Enable markdown table output
|
||||
-O, --orgtbl Enable org-mode table output
|
||||
-S, --shell Enable shell evaluable output
|
||||
-Y, --yaml Enable yaml output
|
||||
-C, --csv Enable CSV output
|
||||
-A, --ascii Default output mode, ascii tabular
|
||||
-L, --hightlight-lines Use alternating background colors for tables
|
||||
-X, --extended Enable extended output
|
||||
-M, --markdown Enable markdown table output
|
||||
-O, --orgtbl Enable org-mode table output
|
||||
-S, --shell Enable shell evaluable output
|
||||
-Y, --yaml Enable yaml output
|
||||
-C, --csv Enable CSV output
|
||||
-A, --ascii Default output mode, ascii tabular
|
||||
-L, --hightlight-lines Use alternating background colors for tables
|
||||
|
||||
Sort Mode Flags (mutually exclusive):
|
||||
-a, --sort-age sort according to age (duration) string
|
||||
-D, --sort-desc Sort in descending order (default: ascending)
|
||||
-i, --sort-numeric sort according to string numerical value
|
||||
-t, --sort-time sort according to time string
|
||||
-a, --sort-age sort according to age (duration) string
|
||||
-D, --sort-desc Sort in descending order (default: ascending)
|
||||
-i, --sort-numeric sort according to string numerical value
|
||||
-t, --sort-time sort according to time string
|
||||
|
||||
Other Flags:
|
||||
--completion <shell> Generate the autocompletion script for <shell>
|
||||
-f, --config <file> Configuration file (default: ~/.config/tablizer/config)
|
||||
-d, --debug Enable debugging
|
||||
-h, --help help for tablizer
|
||||
-m, --man Display manual page
|
||||
-V, --version Print program version
|
||||
--completion <shell> Generate the autocompletion script for <shell>
|
||||
-f, --config <file> Configuration file (default: ~/.config/tablizer/config)
|
||||
-d, --debug Enable debugging
|
||||
-h, --help help for tablizer
|
||||
-m, --man Display manual page
|
||||
-V, --version Print program version
|
||||
|
||||
DESCRIPTION
|
||||
Many programs generate tabular output. But sometimes you need to
|
||||
@@ -187,44 +185,6 @@ DESCRIPTION
|
||||
|
||||
where "C" is our regexp which matches CMD.
|
||||
|
||||
If a column specifier doesn't look like a regular expression, matching
|
||||
against header fields will be case insensitive. So, if you have a field
|
||||
with the name "ID" then these will all match: "-c id", "-c Id". The same
|
||||
rule applies to the options "-T" and "-F".
|
||||
|
||||
TRANSPOSE FIELDS USING REGEXPS
|
||||
You can manipulate field contents using regular expressions. You have to
|
||||
tell tablizer which field[s] to operate on using the option "-T" and the
|
||||
search/replace pattern using "-R". The number of columns and patterns
|
||||
must match.
|
||||
|
||||
A search/replace pattern consists of the following elements:
|
||||
|
||||
/search-regexp/replace-string/
|
||||
|
||||
The separator can be any valid character. Especially if you want to use
|
||||
a regexp containing the "/" character, eg:
|
||||
|
||||
|search-regexp|replace-string|
|
||||
|
||||
Example:
|
||||
|
||||
cat t/testtable2
|
||||
NAME DURATION
|
||||
x 10
|
||||
a 100
|
||||
z 0
|
||||
u 4
|
||||
k 6
|
||||
|
||||
cat t/testtable2 | tablizer -T2 -R '/^\d/4/' -n
|
||||
NAME DURATION
|
||||
x 40
|
||||
a 400
|
||||
z 4
|
||||
u 4
|
||||
k 4
|
||||
|
||||
OUTPUT MODES
|
||||
There might be cases when the tabular output of a program is way too
|
||||
large for your current terminal but you still need to see every column.
|
||||
@@ -386,41 +346,39 @@ Usage:
|
||||
tablizer [regex] [file, ...] [flags]
|
||||
|
||||
Operational Flags:
|
||||
-c, --columns string Only show the speficied columns (separated by ,)
|
||||
-v, --invert-match select non-matching rows
|
||||
-n, --no-numbering Disable header numbering
|
||||
-N, --no-color Disable pattern highlighting
|
||||
-H, --no-headers Disable headers display
|
||||
-s, --separator string Custom field separator
|
||||
-k, --sort-by int Sort by column (default: 1)
|
||||
-z, --fuzzy Use fuzzy search [experimental]
|
||||
-F, --filter field=reg Filter given field with regex, can be used multiple times
|
||||
-T, --transpose-columns string Transpose the speficied columns (separated by ,)
|
||||
-R, --regex-transposer /from/to/ Apply /search/replace/ regexp to fields given in -T
|
||||
-c, --columns string Only show the speficied columns (separated by ,)
|
||||
-v, --invert-match select non-matching rows
|
||||
-n, --no-numbering Disable header numbering
|
||||
-N, --no-color Disable pattern highlighting
|
||||
-H, --no-headers Disable headers display
|
||||
-s, --separator string Custom field separator
|
||||
-k, --sort-by int Sort by column (default: 1)
|
||||
-z, --fuzzy Use fuzzy search [experimental]
|
||||
-F, --filter field=reg Filter given field with regex, can be used multiple times
|
||||
|
||||
Output Flags (mutually exclusive):
|
||||
-X, --extended Enable extended output
|
||||
-M, --markdown Enable markdown table output
|
||||
-O, --orgtbl Enable org-mode table output
|
||||
-S, --shell Enable shell evaluable output
|
||||
-Y, --yaml Enable yaml output
|
||||
-C, --csv Enable CSV output
|
||||
-A, --ascii Default output mode, ascii tabular
|
||||
-L, --hightlight-lines Use alternating background colors for tables
|
||||
-X, --extended Enable extended output
|
||||
-M, --markdown Enable markdown table output
|
||||
-O, --orgtbl Enable org-mode table output
|
||||
-S, --shell Enable shell evaluable output
|
||||
-Y, --yaml Enable yaml output
|
||||
-C, --csv Enable CSV output
|
||||
-A, --ascii Default output mode, ascii tabular
|
||||
-L, --hightlight-lines Use alternating background colors for tables
|
||||
|
||||
Sort Mode Flags (mutually exclusive):
|
||||
-a, --sort-age sort according to age (duration) string
|
||||
-D, --sort-desc Sort in descending order (default: ascending)
|
||||
-i, --sort-numeric sort according to string numerical value
|
||||
-t, --sort-time sort according to time string
|
||||
-a, --sort-age sort according to age (duration) string
|
||||
-D, --sort-desc Sort in descending order (default: ascending)
|
||||
-i, --sort-numeric sort according to string numerical value
|
||||
-t, --sort-time sort according to time string
|
||||
|
||||
Other Flags:
|
||||
--completion <shell> Generate the autocompletion script for <shell>
|
||||
-f, --config <file> Configuration file (default: ~/.config/tablizer/config)
|
||||
-d, --debug Enable debugging
|
||||
-h, --help help for tablizer
|
||||
-m, --man Display manual page
|
||||
-V, --version Print program version
|
||||
--completion <shell> Generate the autocompletion script for <shell>
|
||||
-f, --config <file> Configuration file (default: ~/.config/tablizer/config)
|
||||
-d, --debug Enable debugging
|
||||
-h, --help help for tablizer
|
||||
-m, --man Display manual page
|
||||
-V, --version Print program version
|
||||
|
||||
|
||||
`
|
||||
|
||||
36
go.mod
36
go.mod
@@ -1,45 +1,43 @@
|
||||
module github.com/tlinden/tablizer
|
||||
|
||||
go 1.22
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/alecthomas/repr v0.4.0
|
||||
github.com/alecthomas/repr v0.1.1
|
||||
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de
|
||||
github.com/glycerine/zygomys v5.1.2+incompatible
|
||||
github.com/gookit/color v1.5.4
|
||||
github.com/hashicorp/hcl/v2 v2.22.0
|
||||
github.com/lithammer/fuzzysearch v1.1.8
|
||||
github.com/gookit/color v1.5.2
|
||||
github.com/hashicorp/hcl/v2 v2.19.1
|
||||
github.com/lithammer/fuzzysearch v1.1.7
|
||||
github.com/olekukonko/tablewriter v0.0.5
|
||||
github.com/spf13/cobra v1.8.1
|
||||
github.com/spf13/cobra v1.6.1
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/agext/levenshtein v1.2.3 // indirect
|
||||
github.com/agext/levenshtein v1.2.1 // indirect
|
||||
github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect
|
||||
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
|
||||
github.com/glycerine/blake2b v0.0.0-20151022103502-3c8c640cd7be // indirect
|
||||
github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31 // indirect
|
||||
github.com/glycerine/greenpack v5.1.1+incompatible // indirect
|
||||
github.com/glycerine/liner v0.0.0-20160121172638-72909af234e0 // indirect
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/google/go-cmp v0.5.6 // indirect
|
||||
github.com/gopherjs/gopherjs v1.17.2 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.1 // indirect
|
||||
github.com/jtolds/gls v4.20.0+incompatible // indirect
|
||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.10 // indirect
|
||||
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 // indirect
|
||||
github.com/philhofer/fwd v1.1.2 // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/rogpeppe/go-internal v1.13.1 // indirect
|
||||
github.com/shurcooL/go v0.0.0-20230706063926-5fe729b41b3a // indirect
|
||||
github.com/rivo/uniseg v0.1.0 // indirect
|
||||
github.com/shurcooL/go v0.0.0-20200502201357-93f07166e636 // indirect
|
||||
github.com/shurcooL/go-goon v1.0.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/tinylib/msgp v1.1.9 // indirect
|
||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||
github.com/zclconf/go-cty v1.13.3 // indirect
|
||||
golang.org/x/mod v0.18.0 // indirect
|
||||
golang.org/x/sys v0.21.0 // indirect
|
||||
github.com/ugorji/go/codec v1.2.11 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect
|
||||
github.com/zclconf/go-cty v1.13.0 // indirect
|
||||
golang.org/x/sys v0.13.0 // indirect
|
||||
golang.org/x/text v0.11.0 // indirect
|
||||
golang.org/x/tools v0.22.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
)
|
||||
|
||||
74
go.sum
74
go.sum
@@ -1,18 +1,17 @@
|
||||
github.com/agext/levenshtein v1.2.1 h1:QmvMAjj2aEICytGiWzmxoE0x2KZvE0fvmqMOfy2tjT8=
|
||||
github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
|
||||
github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo=
|
||||
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.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
||||
github.com/alecthomas/repr v0.1.1 h1:87P60cSmareLAxMc4Hro0r2RBY4ROm0dYwkJNpS4pPs=
|
||||
github.com/alecthomas/repr v0.1.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/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/go.mod h1:DCaWoUhZrYW9p1lxo/cm8EmUOOzAPSEZNGF2DK1dJgw=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/glycerine/blake2b v0.0.0-20151022103502-3c8c640cd7be h1:XBJdPGgA3qqhW+p9CANCAVdF7ZIXdu3pZAkypMkKAjE=
|
||||
github.com/glycerine/blake2b v0.0.0-20151022103502-3c8c640cd7be/go.mod h1:OSCrScrFAjcBObrulk6BEQlytA462OkG1UGB5NYj9kE=
|
||||
github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31 h1:gclg6gY70GLy3PbkQ1AERPfmLMMagS60DKF78eWwLn8=
|
||||
@@ -24,25 +23,26 @@ github.com/glycerine/liner v0.0.0-20160121172638-72909af234e0/go.mod h1:AqJLs6Ue
|
||||
github.com/glycerine/zygomys v5.1.2+incompatible h1:jmcdmA3XPxgfOunAXFpipE9LQoUL6eX6d2mhYyjV4GE=
|
||||
github.com/glycerine/zygomys v5.1.2+incompatible/go.mod h1:i3SPKZpmy9dwF/3iWrXJ/ZLyzZucegwypwOmqRkUUaQ=
|
||||
github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68=
|
||||
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/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0=
|
||||
github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w=
|
||||
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/gookit/color v1.5.2 h1:uLnfXcaFjlrDnQDT+NCBcfhrXqYTx/rcCa6xn01Y8yI=
|
||||
github.com/gookit/color v1.5.2/go.mod h1:w8h4bGiHeeBpvQVePTutdbERIUf3oJE5lZ8HM0UgXyg=
|
||||
github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g=
|
||||
github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k=
|
||||
github.com/hashicorp/hcl/v2 v2.22.0 h1:hkZ3nCtqeJsDhPRFz5EA9iwcG1hNWGePOTw6oyul12M=
|
||||
github.com/hashicorp/hcl/v2 v2.22.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/hashicorp/hcl/v2 v2.19.1 h1://i05Jqznmb2EXqa39Nsvyan2o5XyMowW5fnCKW5RPI=
|
||||
github.com/hashicorp/hcl/v2 v2.19.1/go.mod h1:ThLC89FV4p9MPW804KVbe/cEXoQ8NZEh+JtMeeGErHE=
|
||||
github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc=
|
||||
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/lithammer/fuzzysearch v1.1.8 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8LFgLN4=
|
||||
github.com/lithammer/fuzzysearch v1.1.8/go.mod h1:IdqeyBClc3FFqSzYq/MXESsS4S0FsZ5ajtkr5xPLts4=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3vkb4ax0b5D2DHbNAUsen0Gx5wZoq3lV4=
|
||||
github.com/lithammer/fuzzysearch v1.1.7 h1:q8rZNmBIUkqxsxb/IlwsXVbCoPIH/0juxjFHY0UIwhU=
|
||||
github.com/lithammer/fuzzysearch v1.1.7/go.mod h1:ZhIlfRGxnD8qa9car/yplC6GmnM14CS07BYAKJJBK2I=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-runewidth v0.0.10 h1:CoZ3S2P7pvtP45xOtBw+/mDL2z0RKI576gSkzRRpdGg=
|
||||
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 h1:DpOJ2HYzCv8LZP15IdmG+YdwD2luVPHITV96TkirNBM=
|
||||
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
|
||||
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
||||
@@ -53,49 +53,36 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY=
|
||||
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/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||
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/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
|
||||
github.com/shurcooL/go v0.0.0-20200502201357-93f07166e636 h1:aSISeOcal5irEhJd1M+IrApc0PdcN7e7Aj4yuEnOrfQ=
|
||||
github.com/shurcooL/go v0.0.0-20200502201357-93f07166e636/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
|
||||
github.com/shurcooL/go v0.0.0-20230706063926-5fe729b41b3a h1:ZHfoO7ZJhws9NU1kzZhStUnnVQiPtDe1PzpUnc6HirU=
|
||||
github.com/shurcooL/go v0.0.0-20230706063926-5fe729b41b3a/go.mod h1:DNrlr0AR9NsHD/aoc2pPeu4uSBZ/71yCHkR42yrzW3M=
|
||||
github.com/shurcooL/go-goon v1.0.0 h1:BCQPvxGkHHJ4WpBO4m/9FXbITVIsvAm/T66cCcCGI7E=
|
||||
github.com/shurcooL/go-goon v1.0.0/go.mod h1:2wTHMsGo7qnpmqA8ADYZtP4I1DD94JpXGQ3Dxq2YQ5w=
|
||||
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
|
||||
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
|
||||
github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA=
|
||||
github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
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.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/tinylib/msgp v1.1.9 h1:SHf3yoO2sGA0veCJeCBYLHuttAVFHGm2RHgNodW7wQU=
|
||||
github.com/tinylib/msgp v1.1.9/go.mod h1:BCXGB54lDD8qUEPmiG0cQQUANC4IUQyB2ItS2UDlO/k=
|
||||
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
|
||||
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
||||
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 h1:QldyIu/L63oPpyvQmHgvgickp1Yw510KJOqX7H24mg8=
|
||||
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs=
|
||||
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/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/zclconf/go-cty v1.13.0 h1:It5dfKTTZHe9aeppbNOda3mN7Ag7sg6QkBNm6TkyFa0=
|
||||
github.com/zclconf/go-cty v1.13.0/go.mod h1:YKQzy/7pZ7iq2jNFzy5go57xdxdWoLLpaEp4u238AE0=
|
||||
github.com/zclconf/go-cty v1.13.3 h1:m+b9q3YDbg6Bec5rr+KGy1MzEVzY/jC2X+YX4yqKtHI=
|
||||
github.com/zclconf/go-cty v1.13.3/go.mod h1:YKQzy/7pZ7iq2jNFzy5go57xdxdWoLLpaEp4u238AE0=
|
||||
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo=
|
||||
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/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.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY=
|
||||
golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
|
||||
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
@@ -103,17 +90,15 @@ 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-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.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ=
|
||||
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-20210330210617-4fbd30eecc44/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-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.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
|
||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
|
||||
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
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.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
@@ -121,18 +106,17 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
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.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.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
|
||||
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
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.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.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc=
|
||||
golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg=
|
||||
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
|
||||
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/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/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
||||
@@ -19,6 +19,7 @@ package lib
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
@@ -43,10 +44,10 @@ func matchPattern(conf cfg.Config, line string) bool {
|
||||
* more filters match on a row, it will be kept, otherwise it will be
|
||||
* excluded.
|
||||
*/
|
||||
func FilterByFields(conf cfg.Config, data *Tabdata) (*Tabdata, bool, error) {
|
||||
func FilterByFields(conf cfg.Config, data Tabdata) (Tabdata, bool, error) {
|
||||
if len(conf.Filters) == 0 {
|
||||
// no filters, no checking
|
||||
return nil, false, nil
|
||||
return Tabdata{}, false, nil
|
||||
}
|
||||
|
||||
newdata := data.CloneEmpty()
|
||||
@@ -74,44 +75,7 @@ func FilterByFields(conf cfg.Config, data *Tabdata) (*Tabdata, bool, error) {
|
||||
}
|
||||
}
|
||||
|
||||
return &newdata, true, nil
|
||||
}
|
||||
|
||||
/*
|
||||
* Transpose fields using search/replace regexp.
|
||||
*/
|
||||
func TransposeFields(conf cfg.Config, data *Tabdata) (*Tabdata, bool, error) {
|
||||
if len(conf.UseTransposers) == 0 {
|
||||
// nothing to be done
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
newdata := data.CloneEmpty()
|
||||
transposed := false
|
||||
|
||||
for _, row := range data.entries {
|
||||
transposedrow := false
|
||||
|
||||
for idx := range data.headers {
|
||||
transposeidx, hasone := findindex(conf.UseTransposeColumns, idx+1)
|
||||
if hasone {
|
||||
row[idx] =
|
||||
conf.UseTransposers[transposeidx].Search.ReplaceAllString(
|
||||
row[idx],
|
||||
conf.UseTransposers[transposeidx].Replace,
|
||||
)
|
||||
transposedrow = true
|
||||
}
|
||||
}
|
||||
|
||||
if transposedrow {
|
||||
// also apply -v
|
||||
newdata.entries = append(newdata.entries, row)
|
||||
transposed = true
|
||||
}
|
||||
}
|
||||
|
||||
return &newdata, transposed, nil
|
||||
return newdata, true, nil
|
||||
}
|
||||
|
||||
/* generic map.Exists(key) */
|
||||
@@ -143,6 +107,18 @@ func FilterByPattern(conf cfg.Config, input io.Reader) (io.Reader, error) {
|
||||
// so we ignore all lines, which DO match.
|
||||
continue
|
||||
}
|
||||
|
||||
// apply user defined lisp filters, if any
|
||||
accept, err := RunFilterHooks(conf, line)
|
||||
if err != nil {
|
||||
return input, fmt.Errorf("failed to apply filter hook: %w", err)
|
||||
}
|
||||
|
||||
if !accept {
|
||||
// IF there are filter hook[s] and IF one of them
|
||||
// returns false on the current line, reject it
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
lines = append(lines, line)
|
||||
|
||||
@@ -153,8 +153,8 @@ func TestFilterByFields(t *testing.T) {
|
||||
t.Errorf("PrepareFilters returned error: %s", err)
|
||||
}
|
||||
|
||||
data, _, _ := FilterByFields(conf, &data)
|
||||
if !reflect.DeepEqual(*data, inputdata.expect) {
|
||||
data, _, _ := FilterByFields(conf, data)
|
||||
if !reflect.DeepEqual(data, inputdata.expect) {
|
||||
t.Errorf("Filtered data does not match expected data:\ngot: %+v\nexp: %+v", data, inputdata.expect)
|
||||
}
|
||||
})
|
||||
|
||||
123
lib/helpers.go
123
lib/helpers.go
@@ -40,106 +40,32 @@ func contains(s []int, e int) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func findindex(s []int, e int) (int, bool) {
|
||||
for i, a := range s {
|
||||
if a == e {
|
||||
return i, true
|
||||
}
|
||||
}
|
||||
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// validate the consitency of parsed data
|
||||
func ValidateConsistency(data *Tabdata) error {
|
||||
expectedfields := len(data.headers)
|
||||
|
||||
for idx, row := range data.entries {
|
||||
if len(row) != expectedfields {
|
||||
return fmt.Errorf("row %d does not contain expected %d elements, but %d",
|
||||
idx, expectedfields, len(row))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// parse columns list given with -c, modifies config.UseColumns based
|
||||
// on eventually given regex.
|
||||
// This is an output filter, because -cN,N,... is being applied AFTER
|
||||
// processing of the input data.
|
||||
// on eventually given regex
|
||||
func PrepareColumns(conf *cfg.Config, data *Tabdata) error {
|
||||
// -c columns
|
||||
usecolumns, err := PrepareColumnVars(conf.Columns, data)
|
||||
if err != nil {
|
||||
return err
|
||||
if conf.Columns == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
conf.UseColumns = usecolumns
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Same thing as above but for -T option, which is an input option,
|
||||
// because transposers are being applied before output.
|
||||
func PrepareTransposerColumns(conf *cfg.Config, data *Tabdata) error {
|
||||
// -T columns
|
||||
usetransposecolumns, err := PrepareColumnVars(conf.TransposeColumns, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
conf.UseTransposeColumns = usetransposecolumns
|
||||
|
||||
// verify that columns and transposers match and prepare transposer structs
|
||||
if err := conf.PrepareTransposers(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func PrepareColumnVars(columns string, data *Tabdata) ([]int, error) {
|
||||
if columns == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
usecolumns := []int{}
|
||||
|
||||
isregex := regexp.MustCompile(`\W`)
|
||||
|
||||
for _, columnpattern := range strings.Split(columns, ",") {
|
||||
if len(columnpattern) == 0 {
|
||||
return nil, fmt.Errorf("could not parse columns list %s: empty column", columns)
|
||||
for _, use := range strings.Split(conf.Columns, ",") {
|
||||
if len(use) == 0 {
|
||||
return fmt.Errorf("could not parse columns list %s: empty column", conf.Columns)
|
||||
}
|
||||
|
||||
usenum, err := strconv.Atoi(columnpattern)
|
||||
usenum, err := strconv.Atoi(use)
|
||||
if err != nil {
|
||||
// not a number
|
||||
// might be a regexp
|
||||
colPattern, err := regexp.Compile(use)
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("Could not parse columns list %s: %v", conf.Columns, err)
|
||||
|
||||
if !isregex.MatchString(columnpattern) {
|
||||
// is not a regexp (contains no non-word chars)
|
||||
// lc() it so that word searches are case insensitive
|
||||
columnpattern = strings.ToLower(columnpattern)
|
||||
return errors.New(msg)
|
||||
}
|
||||
|
||||
for i, head := range data.headers {
|
||||
if columnpattern == strings.ToLower(head) {
|
||||
usecolumns = append(usecolumns, i+1)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
colPattern, err := regexp.Compile("(?i)" + columnpattern)
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("Could not parse columns list %s: %v", columns, err)
|
||||
|
||||
return nil, errors.New(msg)
|
||||
}
|
||||
|
||||
// find matching header fields, ignoring case
|
||||
for i, head := range data.headers {
|
||||
if colPattern.MatchString(strings.ToLower(head)) {
|
||||
usecolumns = append(usecolumns, i+1)
|
||||
}
|
||||
// find matching header fields
|
||||
for i, head := range data.headers {
|
||||
if colPattern.MatchString(head) {
|
||||
conf.UseColumns = append(conf.UseColumns, i+1)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -147,28 +73,27 @@ func PrepareColumnVars(columns string, data *Tabdata) ([]int, error) {
|
||||
// a colum spec is not a number, we process them above
|
||||
// inside the err handler for atoi(). so only add the
|
||||
// number, if it's really just a number.
|
||||
usecolumns = append(usecolumns, usenum)
|
||||
conf.UseColumns = append(conf.UseColumns, usenum)
|
||||
}
|
||||
}
|
||||
|
||||
// deduplicate: put all values into a map (value gets map key)
|
||||
// thereby removing duplicates, extract keys into new slice
|
||||
// and sort it
|
||||
imap := make(map[int]int, len(usecolumns))
|
||||
for _, i := range usecolumns {
|
||||
imap := make(map[int]int, len(conf.UseColumns))
|
||||
for _, i := range conf.UseColumns {
|
||||
imap[i] = 0
|
||||
}
|
||||
|
||||
// fill with deduplicated columns
|
||||
usecolumns = nil
|
||||
conf.UseColumns = nil
|
||||
|
||||
for k := range imap {
|
||||
usecolumns = append(usecolumns, k)
|
||||
conf.UseColumns = append(conf.UseColumns, k)
|
||||
}
|
||||
|
||||
sort.Ints(usecolumns)
|
||||
sort.Ints(conf.UseColumns)
|
||||
|
||||
return usecolumns, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// prepare headers: add numbers to headers
|
||||
|
||||
@@ -67,8 +67,8 @@ func TestPrepareColumns(t *testing.T) {
|
||||
}{
|
||||
{"1,2,3", []int{1, 2, 3}, false},
|
||||
{"1,2,", []int{}, true},
|
||||
{"T.", []int{2, 3}, false},
|
||||
{"T.,2,3", []int{2, 3}, false},
|
||||
{"T", []int{2, 3}, false},
|
||||
{"T,2,3", []int{2, 3}, false},
|
||||
{"[a-z,4,5", []int{4, 5}, true}, // invalid regexp
|
||||
}
|
||||
|
||||
@@ -90,86 +90,6 @@ func TestPrepareColumns(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrepareTransposerColumns(t *testing.T) {
|
||||
data := Tabdata{
|
||||
maxwidthHeader: 5,
|
||||
columns: 3,
|
||||
headers: []string{
|
||||
"ONE", "TWO", "THREE",
|
||||
},
|
||||
entries: [][]string{
|
||||
{
|
||||
"2", "3", "4",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var tests = []struct {
|
||||
input string
|
||||
transp []string
|
||||
exp int
|
||||
wanterror bool // expect error
|
||||
}{
|
||||
{
|
||||
"1",
|
||||
[]string{`/\d/x/`},
|
||||
1,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"T.", // will match [T]WO and [T]HREE
|
||||
[]string{`/\d/x/`, `/.//`},
|
||||
2,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"TH.,2",
|
||||
[]string{`/\d/x/`, `/.//`},
|
||||
2,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"1",
|
||||
[]string{},
|
||||
1,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"",
|
||||
[]string{`|.|N|`},
|
||||
0,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"1",
|
||||
[]string{`|.|N|`},
|
||||
1,
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testdata := range tests {
|
||||
testname := fmt.Sprintf("PrepareTransposerColumns-%s-%t", testdata.input, testdata.wanterror)
|
||||
t.Run(testname, func(t *testing.T) {
|
||||
conf := cfg.Config{TransposeColumns: testdata.input, Transposers: testdata.transp}
|
||||
err := PrepareTransposerColumns(&conf, &data)
|
||||
if err != nil {
|
||||
if !testdata.wanterror {
|
||||
t.Errorf("got error: %v", err)
|
||||
}
|
||||
} else {
|
||||
if len(conf.UseTransposeColumns) != testdata.exp {
|
||||
t.Errorf("got %d, want %d", conf.UseTransposeColumns, testdata.exp)
|
||||
}
|
||||
|
||||
if len(conf.Transposers) != len(conf.UseTransposeColumns) {
|
||||
t.Errorf("got %d, want %d", conf.UseTransposeColumns, testdata.exp)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestReduceColumns(t *testing.T) {
|
||||
var tests = []struct {
|
||||
expect [][]string
|
||||
|
||||
88
lib/io.go
88
lib/io.go
@@ -29,7 +29,7 @@ import (
|
||||
const RWRR = 0755
|
||||
|
||||
func ProcessFiles(conf *cfg.Config, args []string) error {
|
||||
fd, pattern, err := determineIO(conf, args)
|
||||
fds, pattern, err := determineIO(conf, args)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -39,62 +39,76 @@ func ProcessFiles(conf *cfg.Config, args []string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
data, err := Parse(*conf, fd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, fd := range fds {
|
||||
data, err := Parse(*conf, fd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = ValidateConsistency(&data); err != nil {
|
||||
return err
|
||||
}
|
||||
err = PrepareColumns(conf, &data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = PrepareColumns(conf, &data)
|
||||
if err != nil {
|
||||
return err
|
||||
printData(os.Stdout, *conf, &data)
|
||||
}
|
||||
|
||||
printData(os.Stdout, *conf, &data)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func determineIO(conf *cfg.Config, args []string) (io.Reader, string, error) {
|
||||
var filehandle io.Reader
|
||||
func determineIO(conf *cfg.Config, args []string) ([]io.Reader, string, error) {
|
||||
var filehandles []io.Reader
|
||||
|
||||
var pattern string
|
||||
|
||||
var haveio bool
|
||||
|
||||
switch {
|
||||
case conf.InputFile == "-":
|
||||
filehandle = os.Stdin
|
||||
haveio = true
|
||||
case conf.InputFile != "":
|
||||
fd, err := os.OpenFile(conf.InputFile, os.O_RDONLY, RWRR)
|
||||
stat, _ := os.Stdin.Stat()
|
||||
if (stat.Mode() & os.ModeCharDevice) == 0 {
|
||||
// we're reading from STDIN, which takes precedence over file args
|
||||
filehandles = append(filehandles, os.Stdin)
|
||||
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("failed to read input file %s: %w", conf.InputFile, err)
|
||||
if len(args) > 0 {
|
||||
// ignore any args > 1
|
||||
pattern = args[0]
|
||||
conf.Pattern = args[0] // used for colorization by printData()
|
||||
}
|
||||
|
||||
filehandle = fd
|
||||
haveio = true
|
||||
}
|
||||
|
||||
if !haveio {
|
||||
stat, _ := os.Stdin.Stat()
|
||||
if (stat.Mode() & os.ModeCharDevice) == 0 {
|
||||
// we're reading from STDIN, which takes precedence over file args
|
||||
filehandle = os.Stdin
|
||||
} else if len(args) > 0 {
|
||||
// threre were args left, take a look
|
||||
if args[0] == "-" {
|
||||
// in traditional unix programs a dash denotes STDIN (forced)
|
||||
filehandles = append(filehandles, os.Stdin)
|
||||
haveio = true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if _, err := os.Stat(args[0]); err != nil {
|
||||
// first one is not a file, consider it as regexp and
|
||||
// shift arg list
|
||||
pattern = args[0]
|
||||
conf.Pattern = args[0] // used for colorization by printData()
|
||||
args = args[1:]
|
||||
}
|
||||
|
||||
if len(args) > 0 {
|
||||
pattern = args[0]
|
||||
conf.Pattern = args[0]
|
||||
if len(args) > 0 {
|
||||
// consider any other args as files
|
||||
for _, file := range args {
|
||||
filehandle, err := os.OpenFile(file, os.O_RDONLY, RWRR)
|
||||
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("failed to read input file %s: %w", file, err)
|
||||
}
|
||||
|
||||
filehandles = append(filehandles, filehandle)
|
||||
haveio = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !haveio {
|
||||
return nil, "", errors.New("no file specified and nothing to read on stdin")
|
||||
}
|
||||
|
||||
return filehandle, pattern, nil
|
||||
return filehandles, pattern, nil
|
||||
}
|
||||
|
||||
313
lib/lisp.go
Normal file
313
lib/lisp.go
Normal file
@@ -0,0 +1,313 @@
|
||||
/*
|
||||
Copyright © 2023 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 (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/glycerine/zygomys/zygo"
|
||||
"github.com/tlinden/tablizer/cfg"
|
||||
)
|
||||
|
||||
/*
|
||||
needs to be global because we can't feed an cfg object to AddHook()
|
||||
which is being called from user lisp code
|
||||
*/
|
||||
var Hooks map[string][]*zygo.SexpSymbol
|
||||
|
||||
/*
|
||||
AddHook() (called addhook from lisp code) can be used by the user to
|
||||
add a function to one of the available hooks provided by tablizer.
|
||||
*/
|
||||
func AddHook(env *zygo.Zlisp, name string, args []zygo.Sexp) (zygo.Sexp, error) {
|
||||
var hookname string
|
||||
|
||||
if len(args) < 2 {
|
||||
return zygo.SexpNull, errors.New("argument of %add-hook should be: %hook-name %your-function")
|
||||
}
|
||||
|
||||
switch sexptype := args[0].(type) {
|
||||
case *zygo.SexpSymbol:
|
||||
if !HookExists(sexptype.Name()) {
|
||||
return zygo.SexpNull, errors.New("Unknown hook " + sexptype.Name())
|
||||
}
|
||||
|
||||
hookname = sexptype.Name()
|
||||
|
||||
default:
|
||||
return zygo.SexpNull, errors.New("hook name must be a symbol ")
|
||||
}
|
||||
|
||||
switch sexptype := args[1].(type) {
|
||||
case *zygo.SexpSymbol:
|
||||
_, exists := Hooks[hookname]
|
||||
if !exists {
|
||||
Hooks[hookname] = []*zygo.SexpSymbol{sexptype}
|
||||
} else {
|
||||
Hooks[hookname] = append(Hooks[hookname], sexptype)
|
||||
}
|
||||
|
||||
default:
|
||||
return zygo.SexpNull, errors.New("hook function must be a symbol ")
|
||||
}
|
||||
|
||||
return zygo.SexpNull, nil
|
||||
}
|
||||
|
||||
/*
|
||||
Check if a hook exists
|
||||
*/
|
||||
func HookExists(key string) bool {
|
||||
for _, hook := range cfg.ValidHooks {
|
||||
if hook == key {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
/*
|
||||
* Basic sanity checks and load lisp file
|
||||
*/
|
||||
func LoadAndEvalFile(env *zygo.Zlisp, path string) error {
|
||||
if strings.HasSuffix(path, `.zy`) {
|
||||
code, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read lisp file %s: %w", path, err)
|
||||
}
|
||||
|
||||
// FIXME: check what res (_ here) could be and mean
|
||||
_, err = env.EvalString(string(code))
|
||||
if err != nil {
|
||||
log.Fatal(env.GetStackTrace(err))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
* Setup lisp interpreter environment
|
||||
*/
|
||||
func SetupLisp(conf *cfg.Config) error {
|
||||
// iterate over load-path and evaluate all *.zy files there, if any
|
||||
// we ignore if load-path does not exist, which is the default anyway
|
||||
path, err := os.Stat(conf.LispLoadPath)
|
||||
if os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// init global hooks
|
||||
Hooks = make(map[string][]*zygo.SexpSymbol)
|
||||
|
||||
// init sandbox
|
||||
env := zygo.NewZlispSandbox()
|
||||
env.AddFunction("addhook", AddHook)
|
||||
|
||||
if !path.IsDir() {
|
||||
// load single lisp file
|
||||
err = LoadAndEvalFile(env, conf.LispLoadPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// load all lisp file in load dir
|
||||
dir, err := os.ReadDir(conf.LispLoadPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read lisp dir %s: %w",
|
||||
conf.LispLoadPath, err)
|
||||
}
|
||||
|
||||
for _, entry := range dir {
|
||||
if !entry.IsDir() {
|
||||
err := LoadAndEvalFile(env, conf.LispLoadPath+"/"+entry.Name())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RegisterLib(env)
|
||||
|
||||
conf.Lisp = env
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
Execute every user lisp function registered as filter hook.
|
||||
|
||||
Each function is given the current line as argument and is expected to
|
||||
return a boolean. True indicates to keep the line, false to skip
|
||||
it.
|
||||
|
||||
If there are multiple such functions registered, then the first one
|
||||
returning false wins, that is if each function returns true the line
|
||||
will be kept, if at least one of them returns false, it will be
|
||||
skipped.
|
||||
*/
|
||||
func RunFilterHooks(conf cfg.Config, line string) (bool, error) {
|
||||
for _, hook := range Hooks["filter"] {
|
||||
var result bool
|
||||
|
||||
conf.Lisp.Clear()
|
||||
|
||||
res, err := conf.Lisp.EvalString(fmt.Sprintf("(%s `%s`)", hook.Name(), line))
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to evaluate hook loader: %w", err)
|
||||
}
|
||||
|
||||
switch sexptype := res.(type) {
|
||||
case *zygo.SexpBool:
|
||||
result = sexptype.Val
|
||||
default:
|
||||
return false, fmt.Errorf("filter hook shall return bool")
|
||||
}
|
||||
|
||||
if !result {
|
||||
// the first hook which returns false leads to complete false
|
||||
return result, nil
|
||||
}
|
||||
}
|
||||
|
||||
// if no hook returned false, we succeed and accept the given line
|
||||
return true, nil
|
||||
}
|
||||
|
||||
/*
|
||||
These hooks get the data (Tabdata) readily processed by tablizer as
|
||||
argument. They are expected to return a SexpPair containing a boolean
|
||||
denoting if the data has been modified and the actual modified
|
||||
data. Columns must be the same, rows may differ. Cells may also have
|
||||
been modified.
|
||||
|
||||
Replaces the internal data structure Tabdata with the user supplied
|
||||
version.
|
||||
|
||||
Only one process hook function is supported.
|
||||
|
||||
The somewhat complicated code is being caused by the fact, that we
|
||||
need to convert our internal structure to a lisp variable and vice
|
||||
versa afterwards.
|
||||
*/
|
||||
func RunProcessHooks(conf cfg.Config, data Tabdata) (Tabdata, bool, error) {
|
||||
var userdata Tabdata
|
||||
|
||||
lisplist := []zygo.Sexp{}
|
||||
|
||||
if len(Hooks["process"]) == 0 {
|
||||
return userdata, false, nil
|
||||
}
|
||||
|
||||
if len(Hooks["process"]) > 1 {
|
||||
fmt.Println("Warning: only one process hook is allowed!")
|
||||
}
|
||||
|
||||
// there are hook[s] installed, convert the go data structure 'data to lisp
|
||||
for _, row := range data.entries {
|
||||
var entry zygo.SexpHash
|
||||
|
||||
for idx, cell := range row {
|
||||
err := entry.HashSet(&zygo.SexpStr{S: data.headers[idx]}, &zygo.SexpStr{S: cell})
|
||||
if err != nil {
|
||||
return userdata, false, fmt.Errorf("failed to convert to lisp data: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
lisplist = append(lisplist, &entry)
|
||||
}
|
||||
|
||||
// we need to add it to the env so that the function can use the struct directly
|
||||
conf.Lisp.AddGlobal("data", &zygo.SexpArray{Val: lisplist, Env: conf.Lisp})
|
||||
|
||||
// execute the actual hook
|
||||
hook := Hooks["process"][0]
|
||||
|
||||
conf.Lisp.Clear()
|
||||
|
||||
var result bool
|
||||
|
||||
res, err := conf.Lisp.EvalString(fmt.Sprintf("(%s data)", hook.Name()))
|
||||
if err != nil {
|
||||
return userdata, false, fmt.Errorf("failed to eval lisp loader: %w", err)
|
||||
}
|
||||
|
||||
// we expect (bool, array(hash)) as return from the function
|
||||
switch sexptype := res.(type) {
|
||||
case *zygo.SexpPair:
|
||||
switch th := sexptype.Head.(type) {
|
||||
case *zygo.SexpBool:
|
||||
result = th.Val
|
||||
default:
|
||||
return userdata, false, errors.New("xpect (bool, array(hash)) as return value")
|
||||
}
|
||||
|
||||
switch sexptailtype := sexptype.Tail.(type) {
|
||||
case *zygo.SexpArray:
|
||||
lisplist = sexptailtype.Val
|
||||
default:
|
||||
return userdata, false, errors.New("expect (bool, array(hash)) as return value ")
|
||||
}
|
||||
default:
|
||||
return userdata, false, errors.New("filter hook shall return array of hashes ")
|
||||
}
|
||||
|
||||
if !result {
|
||||
// no further processing required
|
||||
return userdata, result, nil
|
||||
}
|
||||
|
||||
// finally convert lispdata back to Tabdata
|
||||
for _, item := range lisplist {
|
||||
row := []string{}
|
||||
|
||||
switch hash := item.(type) {
|
||||
case *zygo.SexpHash:
|
||||
for _, header := range data.headers {
|
||||
entry, err := hash.HashGetDefault(
|
||||
conf.Lisp,
|
||||
&zygo.SexpStr{S: header},
|
||||
&zygo.SexpStr{S: ""})
|
||||
if err != nil {
|
||||
return userdata, false, fmt.Errorf("failed to get lisp hash entry: %w", err)
|
||||
}
|
||||
|
||||
switch sexptype := entry.(type) {
|
||||
case *zygo.SexpStr:
|
||||
row = append(row, sexptype.S)
|
||||
default:
|
||||
return userdata, false, errors.New("hsh values should be string ")
|
||||
}
|
||||
}
|
||||
default:
|
||||
return userdata, false, errors.New("rturned array should contain hashes ")
|
||||
}
|
||||
|
||||
userdata.entries = append(userdata.entries, row)
|
||||
}
|
||||
|
||||
userdata.headers = data.headers
|
||||
|
||||
return userdata, result, nil
|
||||
}
|
||||
88
lib/lisplib.go
Normal file
88
lib/lisplib.go
Normal file
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
Copyright © 2023 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 (
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
|
||||
"github.com/glycerine/zygomys/zygo"
|
||||
)
|
||||
|
||||
func Splice2SexpList(list []string) zygo.Sexp {
|
||||
slist := []zygo.Sexp{}
|
||||
|
||||
for _, item := range list {
|
||||
slist = append(slist, &zygo.SexpStr{S: item})
|
||||
}
|
||||
|
||||
return zygo.MakeList(slist)
|
||||
}
|
||||
|
||||
func StringReSplit(env *zygo.Zlisp, name string, args []zygo.Sexp) (zygo.Sexp, error) {
|
||||
if len(args) < 2 {
|
||||
return zygo.SexpNull, errors.New("expecting 2 arguments")
|
||||
}
|
||||
|
||||
var separator, input string
|
||||
|
||||
switch t := args[0].(type) {
|
||||
case *zygo.SexpStr:
|
||||
input = t.S
|
||||
default:
|
||||
return zygo.SexpNull, errors.New("second argument must be a string")
|
||||
}
|
||||
|
||||
switch t := args[1].(type) {
|
||||
case *zygo.SexpStr:
|
||||
separator = t.S
|
||||
default:
|
||||
return zygo.SexpNull, errors.New("first argument must be a string")
|
||||
}
|
||||
|
||||
sep := regexp.MustCompile(separator)
|
||||
|
||||
return Splice2SexpList(sep.Split(input, -1)), nil
|
||||
}
|
||||
|
||||
func String2Int(env *zygo.Zlisp, name string, args []zygo.Sexp) (zygo.Sexp, error) {
|
||||
var number int
|
||||
|
||||
switch t := args[0].(type) {
|
||||
case *zygo.SexpStr:
|
||||
num, err := strconv.Atoi(t.S)
|
||||
|
||||
if err != nil {
|
||||
return zygo.SexpNull, fmt.Errorf("failed to convert string to number: %w", err)
|
||||
}
|
||||
|
||||
number = num
|
||||
|
||||
default:
|
||||
return zygo.SexpNull, errors.New("argument must be a string")
|
||||
}
|
||||
|
||||
return &zygo.SexpInt{Val: int64(number)}, nil
|
||||
}
|
||||
|
||||
func RegisterLib(env *zygo.Zlisp) {
|
||||
env.AddFunction("resplit", StringReSplit)
|
||||
env.AddFunction("atoi", String2Int)
|
||||
}
|
||||
@@ -33,31 +33,11 @@ import (
|
||||
Parser switch
|
||||
*/
|
||||
func Parse(conf cfg.Config, input io.Reader) (Tabdata, error) {
|
||||
var data Tabdata
|
||||
var err error
|
||||
|
||||
// first step, parse the data
|
||||
if len(conf.Separator) == 1 {
|
||||
data, err = parseCSV(conf, input)
|
||||
} else {
|
||||
data, err = parseTabular(conf, input)
|
||||
return parseCSV(conf, input)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return data, err
|
||||
}
|
||||
|
||||
// 2nd step, apply filters, code or transposers, if any
|
||||
postdata, changed, err := PostProcess(conf, &data)
|
||||
if err != nil {
|
||||
return data, err
|
||||
}
|
||||
|
||||
if changed {
|
||||
return *postdata, nil
|
||||
}
|
||||
|
||||
return data, err
|
||||
return parseTabular(conf, input)
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -97,6 +77,16 @@ func parseCSV(conf cfg.Config, input io.Reader) (Tabdata, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// apply user defined lisp process hooks, if any
|
||||
userdata, changed, err := RunProcessHooks(conf, data)
|
||||
if err != nil {
|
||||
return data, fmt.Errorf("failed to apply filter hook: %w", err)
|
||||
}
|
||||
|
||||
if changed {
|
||||
data = userdata
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
@@ -120,6 +110,9 @@ func parseTabular(conf cfg.Config, input io.Reader) (Tabdata, error) {
|
||||
if !hadFirst {
|
||||
// header processing
|
||||
data.columns = len(parts)
|
||||
// if Debug {
|
||||
// fmt.Println(parts)
|
||||
// }
|
||||
|
||||
// process all header fields
|
||||
for _, part := range parts {
|
||||
@@ -145,6 +138,18 @@ func parseTabular(conf cfg.Config, input io.Reader) (Tabdata, error) {
|
||||
continue
|
||||
}
|
||||
|
||||
// apply user defined lisp filters, if any
|
||||
accept, err := RunFilterHooks(conf, line)
|
||||
if err != nil {
|
||||
return data, fmt.Errorf("failed to apply filter hook: %w", err)
|
||||
}
|
||||
|
||||
if !accept {
|
||||
// IF there are filter hook[s] and IF one of them
|
||||
// returns false on the current line, reject it
|
||||
continue
|
||||
}
|
||||
|
||||
idx := 0 // we cannot use the header index, because we could exclude columns
|
||||
values := []string{}
|
||||
for _, part := range parts {
|
||||
@@ -169,42 +174,29 @@ func parseTabular(conf cfg.Config, input io.Reader) (Tabdata, error) {
|
||||
return data, fmt.Errorf("failed to read from io.Reader: %w", scanner.Err())
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func PostProcess(conf cfg.Config, data *Tabdata) (*Tabdata, bool, error) {
|
||||
var modified bool
|
||||
|
||||
// filter by field filters, if any
|
||||
filtereddata, changed, err := FilterByFields(conf, data)
|
||||
if err != nil {
|
||||
return data, false, fmt.Errorf("failed to filter fields: %w", err)
|
||||
return data, fmt.Errorf("failed to filter fields: %w", err)
|
||||
}
|
||||
|
||||
if changed {
|
||||
data = filtereddata
|
||||
modified = true
|
||||
}
|
||||
|
||||
// check if transposers are valid and turn into Transposer structs
|
||||
if err := PrepareTransposerColumns(&conf, data); err != nil {
|
||||
return data, false, err
|
||||
}
|
||||
|
||||
// transpose if demanded
|
||||
modifieddata, changed, err := TransposeFields(conf, data)
|
||||
// apply user defined lisp process hooks, if any
|
||||
userdata, changed, err := RunProcessHooks(conf, data)
|
||||
if err != nil {
|
||||
return data, false, fmt.Errorf("failed to transpose fields: %w", err)
|
||||
return data, fmt.Errorf("failed to apply filter hook: %w", err)
|
||||
}
|
||||
|
||||
if changed {
|
||||
data = modifieddata
|
||||
modified = true
|
||||
data = userdata
|
||||
}
|
||||
|
||||
if conf.Debug {
|
||||
repr.Print(data)
|
||||
}
|
||||
|
||||
return data, modified, nil
|
||||
return data, nil
|
||||
}
|
||||
|
||||
8
main.go
8
main.go
@@ -18,17 +18,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/tlinden/tablizer/cmd"
|
||||
)
|
||||
|
||||
func main() {
|
||||
os.Exit(Main())
|
||||
}
|
||||
|
||||
func Main() int {
|
||||
cmd.Execute()
|
||||
|
||||
return 0 // cmd takes care of exit 1 itself
|
||||
}
|
||||
|
||||
20
main_test.go
20
main_test.go
@@ -1,20 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/rogpeppe/go-internal/testscript"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
os.Exit(testscript.RunMain(m, map[string]func() int{
|
||||
"tablizer": Main,
|
||||
}))
|
||||
}
|
||||
|
||||
func TestTablizer(t *testing.T) {
|
||||
testscript.Run(t, testscript.Params{
|
||||
Dir: "t",
|
||||
})
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
# usage
|
||||
exec tablizer -h
|
||||
stdout Usage
|
||||
|
||||
# version
|
||||
exec tablizer -V
|
||||
stdout version
|
||||
|
||||
# manpage
|
||||
exec tablizer -m
|
||||
stdout SYNOPSIS
|
||||
|
||||
# completion
|
||||
exec tablizer --completion bash
|
||||
stdout __tablizer_init_completion
|
||||
|
||||
# use config (configures colors, but these are not being used, since
|
||||
# this env doesn't support it, but at least it should succeed.
|
||||
exec tablizer -f config.hcl -r testtable.txt Runn
|
||||
stdout Runn
|
||||
|
||||
|
||||
|
||||
# will be automatically created in work dir
|
||||
-- testtable.txt --
|
||||
NAME READY STATUS RESTARTS AGE
|
||||
alertmanager-kube-prometheus-alertmanager-0 2/2 Running 35 (45m ago) 11d
|
||||
grafana-fcc54cbc9-bk7s8 1/1 Running 17 (45m ago) 1d
|
||||
kube-prometheus-blackbox-exporter-5d85b5d8f4-tskh7 1/1 Running 17 (45m ago) 1h44m
|
||||
kube-prometheus-kube-state-metrics-b4cd9487-75p7f 1/1 Running 20 (45m ago) 45m
|
||||
kube-prometheus-node-exporter-bfzpl 1/1 Running 17 (45m ago) 54s
|
||||
|
||||
|
||||
-- config.hcl --
|
||||
BG = "lightGreen"
|
||||
FG = "white"
|
||||
HighlightBG = "lightGreen"
|
||||
HighlightFG = "white"
|
||||
NoHighlightBG = "white"
|
||||
NoHighlightFG = "lightGreen"
|
||||
HighlightHdrBG = "red"
|
||||
HighlightHdrFG = "white"
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
# reading from file and matching with lowercase words
|
||||
exec tablizer -c name,status -r testtable.csv -s,
|
||||
stdout grafana.*Runn
|
||||
|
||||
# matching mixed case
|
||||
exec tablizer -c NAME,staTUS -r testtable.csv -s,
|
||||
stdout grafana.*Runn
|
||||
|
||||
# matching using numbers
|
||||
exec tablizer -c 1,3 -r testtable.csv -s,
|
||||
stdout grafana.*Runn
|
||||
|
||||
# matching using regex
|
||||
exec tablizer -c 'na.*,stat.' -r testtable.csv -s,
|
||||
stdout grafana.*Runn
|
||||
|
||||
|
||||
# will be automatically created in work dir
|
||||
-- testtable.csv --
|
||||
NAME,READY,STATUS,RESTARTS,AGE
|
||||
alertmanager-kube-prometheus-alertmanager-0,2/2,Running,35 (45m ago),11d
|
||||
grafana-fcc54cbc9-bk7s8,1/1,Running,17 (45m ago),1d
|
||||
kube-prometheus-blackbox-exporter-5d85b5d8f4-tskh7,1/1,Running,17 (45m ago),1h44m
|
||||
kube-prometheus-kube-state-metrics-b4cd9487-75p7f,1/1,Running,20 (45m ago),45m
|
||||
kube-prometheus-node-exporter-bfzpl,1/1,Running,17 (45m ago),54s
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
# filtering
|
||||
exec tablizer -r testtable.txt -F name=grafana
|
||||
stdout grafana.*Runn
|
||||
|
||||
# filtering two columns
|
||||
exec tablizer -r testtable.txt -F name=prometh -F age=1h
|
||||
stdout blackbox.*Runn
|
||||
|
||||
# filtering two same columns
|
||||
exec tablizer -r testtable.txt -F name=prometh -F name=alert
|
||||
stdout prometheus-alertmanager.*Runn
|
||||
|
||||
|
||||
# will be automatically created in work dir
|
||||
-- testtable.txt --
|
||||
NAME READY STATUS RESTARTS AGE
|
||||
alertmanager-kube-prometheus-alertmanager-0 2/2 Running 35 (45m ago) 11d
|
||||
grafana-fcc54cbc9-bk7s8 1/1 Running 17 (45m ago) 1d
|
||||
kube-prometheus-blackbox-exporter-5d85b5d8f4-tskh7 1/1 Running 17 (45m ago) 1h44m
|
||||
kube-prometheus-kube-state-metrics-b4cd9487-75p7f 1/1 Running 20 (45m ago) 45m
|
||||
kube-prometheus-node-exporter-bfzpl 1/1 Running 17 (45m ago) 54s
|
||||
@@ -1,25 +0,0 @@
|
||||
# reading from file and matching with lowercase words
|
||||
exec tablizer -c name,status -r testtable.txt
|
||||
stdout grafana.*Runn
|
||||
|
||||
# matching mixed case
|
||||
exec tablizer -c NAME,staTUS -r testtable.txt
|
||||
stdout grafana.*Runn
|
||||
|
||||
# matching using numbers
|
||||
exec tablizer -c 1,3 -r testtable.txt
|
||||
stdout grafana.*Runn
|
||||
|
||||
# matching using regex
|
||||
exec tablizer -c 'na.*,stat.' -r testtable.txt
|
||||
stdout grafana.*Runn
|
||||
|
||||
|
||||
# will be automatically created in work dir
|
||||
-- testtable.txt --
|
||||
NAME READY STATUS RESTARTS AGE
|
||||
alertmanager-kube-prometheus-alertmanager-0 2/2 Running 35 (45m ago) 11d
|
||||
grafana-fcc54cbc9-bk7s8 1/1 Running 17 (45m ago) 1d
|
||||
kube-prometheus-blackbox-exporter-5d85b5d8f4-tskh7 1/1 Running 17 (45m ago) 1h44m
|
||||
kube-prometheus-kube-state-metrics-b4cd9487-75p7f 1/1 Running 20 (45m ago) 45m
|
||||
kube-prometheus-node-exporter-bfzpl 1/1 Running 17 (45m ago) 54s
|
||||
@@ -1,49 +0,0 @@
|
||||
# sort by name
|
||||
exec tablizer -r testtable.txt -k 1
|
||||
stdout '^alert.*\n^grafana.*\n^kube'
|
||||
|
||||
# sort by name reversed
|
||||
exec tablizer -r testtable.txt -k 1 -D
|
||||
stdout 'kube.*\n^grafana.*\n^alert'
|
||||
|
||||
# sort by starts numerically
|
||||
exec tablizer -r testtable.txt -k 4 -i -c4
|
||||
stdout '17\s*\n^20\s*\n^35'
|
||||
|
||||
# sort by starts numerically reversed
|
||||
exec tablizer -r testtable.txt -k 4 -i -c4 -D
|
||||
stdout '35\s*\n^20\s*\n^17'
|
||||
|
||||
# sort by age
|
||||
exec tablizer -r testtable.txt -k 5 -a
|
||||
stdout '45m\s*\n.*1h44m'
|
||||
|
||||
# sort by age reverse
|
||||
exec tablizer -r testtable.txt -k 5 -a -D
|
||||
stdout '1h44m\s*\n.*45m'
|
||||
|
||||
# sort by time
|
||||
exec tablizer -r timetable.txt -k 2 -t
|
||||
stdout '^sel.*\n^foo.*\nbar'
|
||||
|
||||
# sort by time reverse
|
||||
exec tablizer -r timetable.txt -k 2 -t -D
|
||||
stdout '^bar.*\n^foo.*\nsel'
|
||||
|
||||
|
||||
# will be automatically created in work dir
|
||||
-- testtable.txt --
|
||||
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
|
||||
|
||||
|
||||
|
||||
-- timetable.txt --
|
||||
NAME TIME
|
||||
foo 2024-11-18T12:00:00+01:00
|
||||
bar 2024-11-18T12:45:00+01:00
|
||||
sel 2024-07-18T12:00:00+01:00
|
||||
@@ -1,18 +0,0 @@
|
||||
# reading from stdin and matching with lowercase words
|
||||
stdin testtable.txt
|
||||
exec tablizer -c name,status
|
||||
stdout grafana.*Runn
|
||||
|
||||
# reading from -r stdin and matching with lowercase words
|
||||
stdin testtable.txt
|
||||
exec tablizer -c name,status -r -
|
||||
stdout grafana.*Runn
|
||||
|
||||
# will be automatically created in work dir
|
||||
-- testtable.txt --
|
||||
NAME READY STATUS RESTARTS AGE
|
||||
alertmanager-kube-prometheus-alertmanager-0 2/2 Running 35 (45m ago) 11d
|
||||
grafana-fcc54cbc9-bk7s8 1/1 Running 17 (45m ago) 1d
|
||||
kube-prometheus-blackbox-exporter-5d85b5d8f4-tskh7 1/1 Running 17 (45m ago) 1h44m
|
||||
kube-prometheus-kube-state-metrics-b4cd9487-75p7f 1/1 Running 20 (45m ago) 45m
|
||||
kube-prometheus-node-exporter-bfzpl 1/1 Running 17 (45m ago) 54s
|
||||
@@ -1,21 +0,0 @@
|
||||
# transpose one field
|
||||
exec tablizer -r testtable.txt -T status -R '/Running/OK/'
|
||||
stdout grafana.*OK
|
||||
|
||||
# transpose two fields
|
||||
exec tablizer -r testtable.txt -T name,status -R '/alertmanager-//' -R '/Running/OK/'
|
||||
stdout prometheus-0.*OK
|
||||
|
||||
# transpose one field and show one column
|
||||
exec tablizer -r testtable.txt -T status -R '/Running/OK/' -c name
|
||||
! stdout grafana.*OK
|
||||
|
||||
|
||||
# will be automatically created in work dir
|
||||
-- testtable.txt --
|
||||
NAME READY STATUS RESTARTS AGE
|
||||
alertmanager-kube-prometheus-alertmanager-0 2/2 Running 35 (45m ago) 11d
|
||||
grafana-fcc54cbc9-bk7s8 1/1 Running 17 (45m ago) 1d
|
||||
kube-prometheus-blackbox-exporter-5d85b5d8f4-tskh7 1/1 Running 17 (45m ago) 1h44m
|
||||
kube-prometheus-kube-state-metrics-b4cd9487-75p7f 1/1 Running 20 (45m ago) 45m
|
||||
kube-prometheus-node-exporter-bfzpl 1/1 Running 17 (45m ago) 54s
|
||||
45
t/test.sh
Executable file
45
t/test.sh
Executable file
@@ -0,0 +1,45 @@
|
||||
#!/bin/sh
|
||||
|
||||
# simple commandline unit test script
|
||||
|
||||
t="../tablizer"
|
||||
fail=0
|
||||
|
||||
ex() {
|
||||
# execute a test, report+exit on error, stay silent otherwise
|
||||
log="/tmp/test-tablizer.$$.log"
|
||||
name=$1
|
||||
shift
|
||||
|
||||
echo -n "TEST $name "
|
||||
|
||||
$* > $log 2>&1
|
||||
|
||||
if test $? -ne 0; then
|
||||
echo "failed, see $log"
|
||||
fail=1
|
||||
else
|
||||
echo "ok"
|
||||
rm -f $log
|
||||
fi
|
||||
}
|
||||
|
||||
# only use files in test dir
|
||||
cd $(dirname $0)
|
||||
|
||||
echo "Executing commandline tests ..."
|
||||
|
||||
# io pattern tests
|
||||
ex io-pattern-and-file $t bk7 testtable
|
||||
cat testtable | ex io-pattern-and-stdin $t bk7
|
||||
cat testtable | ex io-pattern-and-stdin-dash $t bk7 -
|
||||
|
||||
# same w/o pattern
|
||||
ex io-just-file $t testtable
|
||||
cat testtable | ex io-just-stdin $t
|
||||
cat testtable | ex io-just-stdin-dash $t -
|
||||
|
||||
if test $fail -ne 0; then
|
||||
echo "!!! Some tests failed !!!"
|
||||
exit 1
|
||||
fi
|
||||
@@ -1,6 +0,0 @@
|
||||
NAME,DURATION
|
||||
x,10
|
||||
a,100
|
||||
z,0
|
||||
u,4
|
||||
k,6
|
||||
|
102
tablizer.1
102
tablizer.1
@@ -133,7 +133,7 @@
|
||||
.\" ========================================================================
|
||||
.\"
|
||||
.IX Title "TABLIZER 1"
|
||||
.TH TABLIZER 1 "2025-01-14" "1" "User Commands"
|
||||
.TH TABLIZER 1 "2024-05-07" "1" "User Commands"
|
||||
.\" For nroff, turn off justification. Always turn off hyphenation; it makes
|
||||
.\" way too many mistakes in technical documents.
|
||||
.if n .ad l
|
||||
@@ -147,41 +147,39 @@ tablizer \- Manipulate tabular output of other programs
|
||||
\& tablizer [regex] [file, ...] [flags]
|
||||
\&
|
||||
\& Operational Flags:
|
||||
\& \-c, \-\-columns string Only show the speficied columns (separated by ,)
|
||||
\& \-v, \-\-invert\-match select non\-matching rows
|
||||
\& \-n, \-\-no\-numbering Disable header numbering
|
||||
\& \-N, \-\-no\-color Disable pattern highlighting
|
||||
\& \-H, \-\-no\-headers Disable headers display
|
||||
\& \-s, \-\-separator string Custom field separator
|
||||
\& \-k, \-\-sort\-by int Sort by column (default: 1)
|
||||
\& \-z, \-\-fuzzy Use fuzzy search [experimental]
|
||||
\& \-F, \-\-filter field=reg Filter given field with regex, can be used multiple times
|
||||
\& \-T, \-\-transpose\-columns string Transpose the speficied columns (separated by ,)
|
||||
\& \-R, \-\-regex\-transposer /from/to/ Apply /search/replace/ regexp to fields given in \-T
|
||||
\& \-c, \-\-columns string Only show the speficied columns (separated by ,)
|
||||
\& \-v, \-\-invert\-match select non\-matching rows
|
||||
\& \-n, \-\-no\-numbering Disable header numbering
|
||||
\& \-N, \-\-no\-color Disable pattern highlighting
|
||||
\& \-H, \-\-no\-headers Disable headers display
|
||||
\& \-s, \-\-separator string Custom field separator
|
||||
\& \-k, \-\-sort\-by int Sort by column (default: 1)
|
||||
\& \-z, \-\-fuzzy Use fuzzy search [experimental]
|
||||
\& \-F, \-\-filter field=reg Filter given field with regex, can be used multiple times
|
||||
\&
|
||||
\& Output Flags (mutually exclusive):
|
||||
\& \-X, \-\-extended Enable extended output
|
||||
\& \-M, \-\-markdown Enable markdown table output
|
||||
\& \-O, \-\-orgtbl Enable org\-mode table output
|
||||
\& \-S, \-\-shell Enable shell evaluable output
|
||||
\& \-Y, \-\-yaml Enable yaml output
|
||||
\& \-C, \-\-csv Enable CSV output
|
||||
\& \-A, \-\-ascii Default output mode, ascii tabular
|
||||
\& \-L, \-\-hightlight\-lines Use alternating background colors for tables
|
||||
\& \-X, \-\-extended Enable extended output
|
||||
\& \-M, \-\-markdown Enable markdown table output
|
||||
\& \-O, \-\-orgtbl Enable org\-mode table output
|
||||
\& \-S, \-\-shell Enable shell evaluable output
|
||||
\& \-Y, \-\-yaml Enable yaml output
|
||||
\& \-C, \-\-csv Enable CSV output
|
||||
\& \-A, \-\-ascii Default output mode, ascii tabular
|
||||
\& \-L, \-\-hightlight\-lines Use alternating background colors for tables
|
||||
\&
|
||||
\& Sort Mode Flags (mutually exclusive):
|
||||
\& \-a, \-\-sort\-age sort according to age (duration) string
|
||||
\& \-D, \-\-sort\-desc Sort in descending order (default: ascending)
|
||||
\& \-i, \-\-sort\-numeric sort according to string numerical value
|
||||
\& \-t, \-\-sort\-time sort according to time string
|
||||
\& \-a, \-\-sort\-age sort according to age (duration) string
|
||||
\& \-D, \-\-sort\-desc Sort in descending order (default: ascending)
|
||||
\& \-i, \-\-sort\-numeric sort according to string numerical value
|
||||
\& \-t, \-\-sort\-time sort according to time string
|
||||
\&
|
||||
\& Other Flags:
|
||||
\& \-\-completion <shell> Generate the autocompletion script for <shell>
|
||||
\& \-f, \-\-config <file> Configuration file (default: ~/.config/tablizer/config)
|
||||
\& \-d, \-\-debug Enable debugging
|
||||
\& \-h, \-\-help help for tablizer
|
||||
\& \-m, \-\-man Display manual page
|
||||
\& \-V, \-\-version Print program version
|
||||
\& \-\-completion <shell> Generate the autocompletion script for <shell>
|
||||
\& \-f, \-\-config <file> Configuration file (default: ~/.config/tablizer/config)
|
||||
\& \-d, \-\-debug Enable debugging
|
||||
\& \-h, \-\-help help for tablizer
|
||||
\& \-m, \-\-man Display manual page
|
||||
\& \-V, \-\-version Print program version
|
||||
.Ve
|
||||
.SH "DESCRIPTION"
|
||||
.IX Header "DESCRIPTION"
|
||||
@@ -350,50 +348,6 @@ We want to see only the \s-1CMD\s0 column and use a regex for this:
|
||||
.Ve
|
||||
.PP
|
||||
where \*(L"C\*(R" is our regexp which matches \s-1CMD.\s0
|
||||
.PP
|
||||
If a column specifier doesn't look like a regular expression, matching
|
||||
against header fields will be case insensitive. So, if you have a
|
||||
field with the name \f(CW\*(C`ID\*(C'\fR then these will all match: \f(CW\*(C`\-c id\*(C'\fR, \f(CW\*(C`\-c
|
||||
Id\*(C'\fR. The same rule applies to the options \f(CW\*(C`\-T\*(C'\fR and \f(CW\*(C`\-F\*(C'\fR.
|
||||
.SS "\s-1TRANSPOSE FIELDS USING REGEXPS\s0"
|
||||
.IX Subsection "TRANSPOSE FIELDS USING REGEXPS"
|
||||
You can manipulate field contents using regular expressions. You have
|
||||
to tell tablizer which field[s] to operate on using the option \f(CW\*(C`\-T\*(C'\fR
|
||||
and the search/replace pattern using \f(CW\*(C`\-R\*(C'\fR. The number of columns and
|
||||
patterns must match.
|
||||
.PP
|
||||
A search/replace pattern consists of the following elements:
|
||||
.PP
|
||||
.Vb 1
|
||||
\& /search\-regexp/replace\-string/
|
||||
.Ve
|
||||
.PP
|
||||
The separator can be any valid character. Especially if you want to
|
||||
use a regexp containing the \f(CW\*(C`/\*(C'\fR character, eg:
|
||||
.PP
|
||||
.Vb 1
|
||||
\& |search\-regexp|replace\-string|
|
||||
.Ve
|
||||
.PP
|
||||
Example:
|
||||
.PP
|
||||
.Vb 7
|
||||
\& cat t/testtable2
|
||||
\& NAME DURATION
|
||||
\& x 10
|
||||
\& a 100
|
||||
\& z 0
|
||||
\& u 4
|
||||
\& k 6
|
||||
\&
|
||||
\& cat t/testtable2 | tablizer \-T2 \-R \*(Aq/^\ed/4/\*(Aq \-n
|
||||
\& NAME DURATION
|
||||
\& x 40
|
||||
\& a 400
|
||||
\& z 4
|
||||
\& u 4
|
||||
\& k 4
|
||||
.Ve
|
||||
.SS "\s-1OUTPUT MODES\s0"
|
||||
.IX Subsection "OUTPUT MODES"
|
||||
There might be cases when the tabular output of a program is way too
|
||||
|
||||
98
tablizer.pod
98
tablizer.pod
@@ -8,41 +8,39 @@ tablizer - Manipulate tabular output of other programs
|
||||
tablizer [regex] [file, ...] [flags]
|
||||
|
||||
Operational Flags:
|
||||
-c, --columns string Only show the speficied columns (separated by ,)
|
||||
-v, --invert-match select non-matching rows
|
||||
-n, --no-numbering Disable header numbering
|
||||
-N, --no-color Disable pattern highlighting
|
||||
-H, --no-headers Disable headers display
|
||||
-s, --separator string Custom field separator
|
||||
-k, --sort-by int Sort by column (default: 1)
|
||||
-z, --fuzzy Use fuzzy search [experimental]
|
||||
-F, --filter field=reg Filter given field with regex, can be used multiple times
|
||||
-T, --transpose-columns string Transpose the speficied columns (separated by ,)
|
||||
-R, --regex-transposer /from/to/ Apply /search/replace/ regexp to fields given in -T
|
||||
-c, --columns string Only show the speficied columns (separated by ,)
|
||||
-v, --invert-match select non-matching rows
|
||||
-n, --no-numbering Disable header numbering
|
||||
-N, --no-color Disable pattern highlighting
|
||||
-H, --no-headers Disable headers display
|
||||
-s, --separator string Custom field separator
|
||||
-k, --sort-by int Sort by column (default: 1)
|
||||
-z, --fuzzy Use fuzzy search [experimental]
|
||||
-F, --filter field=reg Filter given field with regex, can be used multiple times
|
||||
|
||||
Output Flags (mutually exclusive):
|
||||
-X, --extended Enable extended output
|
||||
-M, --markdown Enable markdown table output
|
||||
-O, --orgtbl Enable org-mode table output
|
||||
-S, --shell Enable shell evaluable output
|
||||
-Y, --yaml Enable yaml output
|
||||
-C, --csv Enable CSV output
|
||||
-A, --ascii Default output mode, ascii tabular
|
||||
-L, --hightlight-lines Use alternating background colors for tables
|
||||
-X, --extended Enable extended output
|
||||
-M, --markdown Enable markdown table output
|
||||
-O, --orgtbl Enable org-mode table output
|
||||
-S, --shell Enable shell evaluable output
|
||||
-Y, --yaml Enable yaml output
|
||||
-C, --csv Enable CSV output
|
||||
-A, --ascii Default output mode, ascii tabular
|
||||
-L, --hightlight-lines Use alternating background colors for tables
|
||||
|
||||
Sort Mode Flags (mutually exclusive):
|
||||
-a, --sort-age sort according to age (duration) string
|
||||
-D, --sort-desc Sort in descending order (default: ascending)
|
||||
-i, --sort-numeric sort according to string numerical value
|
||||
-t, --sort-time sort according to time string
|
||||
-a, --sort-age sort according to age (duration) string
|
||||
-D, --sort-desc Sort in descending order (default: ascending)
|
||||
-i, --sort-numeric sort according to string numerical value
|
||||
-t, --sort-time sort according to time string
|
||||
|
||||
Other Flags:
|
||||
--completion <shell> Generate the autocompletion script for <shell>
|
||||
-f, --config <file> Configuration file (default: ~/.config/tablizer/config)
|
||||
-d, --debug Enable debugging
|
||||
-h, --help help for tablizer
|
||||
-m, --man Display manual page
|
||||
-V, --version Print program version
|
||||
--completion <shell> Generate the autocompletion script for <shell>
|
||||
-f, --config <file> Configuration file (default: ~/.config/tablizer/config)
|
||||
-d, --debug Enable debugging
|
||||
-h, --help help for tablizer
|
||||
-m, --man Display manual page
|
||||
-V, --version Print program version
|
||||
|
||||
|
||||
=head1 DESCRIPTION
|
||||
@@ -205,46 +203,6 @@ We want to see only the CMD column and use a regex for this:
|
||||
|
||||
where "C" is our regexp which matches CMD.
|
||||
|
||||
If a column specifier doesn't look like a regular expression, matching
|
||||
against header fields will be case insensitive. So, if you have a
|
||||
field with the name C<ID> then these will all match: C<-c id>, C<-c
|
||||
Id>. The same rule applies to the options C<-T> and C<-F>.
|
||||
|
||||
|
||||
=head2 TRANSPOSE FIELDS USING REGEXPS
|
||||
|
||||
You can manipulate field contents using regular expressions. You have
|
||||
to tell tablizer which field[s] to operate on using the option C<-T>
|
||||
and the search/replace pattern using C<-R>. The number of columns and
|
||||
patterns must match.
|
||||
|
||||
A search/replace pattern consists of the following elements:
|
||||
|
||||
/search-regexp/replace-string/
|
||||
|
||||
The separator can be any valid character. Especially if you want to
|
||||
use a regexp containing the C</> character, eg:
|
||||
|
||||
|search-regexp|replace-string|
|
||||
|
||||
Example:
|
||||
|
||||
cat t/testtable2
|
||||
NAME DURATION
|
||||
x 10
|
||||
a 100
|
||||
z 0
|
||||
u 4
|
||||
k 6
|
||||
|
||||
cat t/testtable2 | tablizer -T2 -R '/^\d/4/' -n
|
||||
NAME DURATION
|
||||
x 40
|
||||
a 400
|
||||
z 4
|
||||
u 4
|
||||
k 4
|
||||
|
||||
=head2 OUTPUT MODES
|
||||
|
||||
There might be cases when the tabular output of a program is way too
|
||||
@@ -383,8 +341,6 @@ the C<-L> parameter).
|
||||
Colorization can be turned off completely either by setting the
|
||||
parameter C<-N> or the environment variable B<NO_COLOR> to a true value.
|
||||
|
||||
|
||||
|
||||
=head1 BUGS
|
||||
|
||||
In order to report a bug, unexpected behavior, feature requests
|
||||
|
||||
Reference in New Issue
Block a user