refactoring and gouncritic, 1st part

This commit is contained in:
2024-05-07 15:19:54 +02:00
parent ba2a2e8460
commit 39609425e5
18 changed files with 434 additions and 382 deletions

View File

@@ -91,3 +91,10 @@ show-versions: buildlocal
goupdate: goupdate:
go get -t -u=patch ./... go get -t -u=patch ./...
lint:
golangci-lint run
lint-full:
golangci-lint run --enable-all --exclude-use-default --disable exhaustivestruct,exhaustruct,depguard,interfacer,deadcode,golint,structcheck,scopelint,varcheck,ifshort,maligned,nosnakecase,godot,funlen,gofumpt,cyclop,noctx,gochecknoglobals,paralleltest,forbidigo,gci,godox,goimports,ireturn
gocritic check -enableAll *.go

View File

@@ -19,7 +19,6 @@ package cfg
import ( import (
"errors" "errors"
"fmt" "fmt"
"log"
"os" "os"
"regexp" "regexp"
"strings" "strings"
@@ -31,14 +30,15 @@ import (
const DefaultSeparator string = `(\s\s+|\t)` const DefaultSeparator string = `(\s\s+|\t)`
const Version string = "v1.2.0" const Version string = "v1.2.0"
const MAXPARTS = 2
var DefaultLoadPath string = os.Getenv("HOME") + "/.config/tablizer/lisp" var DefaultLoadPath = os.Getenv("HOME") + "/.config/tablizer/lisp"
var DefaultConfigfile string = os.Getenv("HOME") + "/.config/tablizer/config" var DefaultConfigfile = os.Getenv("HOME") + "/.config/tablizer/config"
var VERSION string // maintained by -x var VERSION string // maintained by -x
// public config, set via config file or using defaults // public config, set via config file or using defaults
type Configuration struct { type Settings struct {
FG string `hcl:"FG"` FG string `hcl:"FG"`
BG string `hcl:"BG"` BG string `hcl:"BG"`
HighlightFG string `hcl:"HighlightFG"` HighlightFG string `hcl:"HighlightFG"`
@@ -89,7 +89,7 @@ type Config struct {
// config file, optional // config file, optional
Configfile string Configfile string
Configuration Configuration Settings Settings
// used for field filtering // used for field filtering
Rawfilters []string Rawfilters []string
@@ -115,7 +115,7 @@ const (
Shell Shell
Yaml Yaml
CSV CSV
Ascii ASCII
) )
// various sort types // various sort types
@@ -129,7 +129,7 @@ type Sortmode struct {
var ValidHooks []string var ValidHooks []string
// default color schemes // default color schemes
func (c *Config) Colors() map[color.Level]map[string]color.Color { func (conf *Config) Colors() map[color.Level]map[string]color.Color {
colors := map[color.Level]map[string]color.Color{ colors := map[color.Level]map[string]color.Color{
color.Level16: { color.Level16: {
"bg": color.BgGreen, "fg": color.FgWhite, "bg": color.BgGreen, "fg": color.FgWhite,
@@ -147,89 +147,85 @@ func (c *Config) Colors() map[color.Level]map[string]color.Color {
}, },
} }
if len(c.Configuration.BG) > 0 { if len(conf.Settings.BG) > 0 {
colors[color.Level16]["bg"] = ColorStringToBGColor(c.Configuration.BG) colors[color.Level16]["bg"] = ColorStringToBGColor(conf.Settings.BG)
colors[color.Level256]["bg"] = ColorStringToBGColor(c.Configuration.BG) colors[color.Level256]["bg"] = ColorStringToBGColor(conf.Settings.BG)
colors[color.LevelRgb]["bg"] = ColorStringToBGColor(c.Configuration.BG) colors[color.LevelRgb]["bg"] = ColorStringToBGColor(conf.Settings.BG)
} }
if len(c.Configuration.FG) > 0 { if len(conf.Settings.FG) > 0 {
colors[color.Level16]["fg"] = ColorStringToColor(c.Configuration.FG) colors[color.Level16]["fg"] = ColorStringToColor(conf.Settings.FG)
colors[color.Level256]["fg"] = ColorStringToColor(c.Configuration.FG) colors[color.Level256]["fg"] = ColorStringToColor(conf.Settings.FG)
colors[color.LevelRgb]["fg"] = ColorStringToColor(c.Configuration.FG) colors[color.LevelRgb]["fg"] = ColorStringToColor(conf.Settings.FG)
} }
if len(c.Configuration.HighlightBG) > 0 { if len(conf.Settings.HighlightBG) > 0 {
colors[color.Level16]["hlbg"] = ColorStringToBGColor(c.Configuration.HighlightBG) colors[color.Level16]["hlbg"] = ColorStringToBGColor(conf.Settings.HighlightBG)
colors[color.Level256]["hlbg"] = ColorStringToBGColor(c.Configuration.HighlightBG) colors[color.Level256]["hlbg"] = ColorStringToBGColor(conf.Settings.HighlightBG)
colors[color.LevelRgb]["hlbg"] = ColorStringToBGColor(c.Configuration.HighlightBG) colors[color.LevelRgb]["hlbg"] = ColorStringToBGColor(conf.Settings.HighlightBG)
} }
if len(c.Configuration.HighlightFG) > 0 { if len(conf.Settings.HighlightFG) > 0 {
colors[color.Level16]["hlfg"] = ColorStringToColor(c.Configuration.HighlightFG) colors[color.Level16]["hlfg"] = ColorStringToColor(conf.Settings.HighlightFG)
colors[color.Level256]["hlfg"] = ColorStringToColor(c.Configuration.HighlightFG) colors[color.Level256]["hlfg"] = ColorStringToColor(conf.Settings.HighlightFG)
colors[color.LevelRgb]["hlfg"] = ColorStringToColor(c.Configuration.HighlightFG) colors[color.LevelRgb]["hlfg"] = ColorStringToColor(conf.Settings.HighlightFG)
} }
if len(c.Configuration.NoHighlightBG) > 0 { if len(conf.Settings.NoHighlightBG) > 0 {
colors[color.Level16]["nohlbg"] = ColorStringToBGColor(c.Configuration.NoHighlightBG) colors[color.Level16]["nohlbg"] = ColorStringToBGColor(conf.Settings.NoHighlightBG)
colors[color.Level256]["nohlbg"] = ColorStringToBGColor(c.Configuration.NoHighlightBG) colors[color.Level256]["nohlbg"] = ColorStringToBGColor(conf.Settings.NoHighlightBG)
colors[color.LevelRgb]["nohlbg"] = ColorStringToBGColor(c.Configuration.NoHighlightBG) colors[color.LevelRgb]["nohlbg"] = ColorStringToBGColor(conf.Settings.NoHighlightBG)
} }
if len(c.Configuration.NoHighlightFG) > 0 { if len(conf.Settings.NoHighlightFG) > 0 {
colors[color.Level16]["nohlfg"] = ColorStringToColor(c.Configuration.NoHighlightFG) colors[color.Level16]["nohlfg"] = ColorStringToColor(conf.Settings.NoHighlightFG)
colors[color.Level256]["nohlfg"] = ColorStringToColor(c.Configuration.NoHighlightFG) colors[color.Level256]["nohlfg"] = ColorStringToColor(conf.Settings.NoHighlightFG)
colors[color.LevelRgb]["nohlfg"] = ColorStringToColor(c.Configuration.NoHighlightFG) colors[color.LevelRgb]["nohlfg"] = ColorStringToColor(conf.Settings.NoHighlightFG)
} }
if len(c.Configuration.HighlightHdrBG) > 0 { if len(conf.Settings.HighlightHdrBG) > 0 {
colors[color.Level16]["hdrbg"] = ColorStringToBGColor(c.Configuration.HighlightHdrBG) colors[color.Level16]["hdrbg"] = ColorStringToBGColor(conf.Settings.HighlightHdrBG)
colors[color.Level256]["hdrbg"] = ColorStringToBGColor(c.Configuration.HighlightHdrBG) colors[color.Level256]["hdrbg"] = ColorStringToBGColor(conf.Settings.HighlightHdrBG)
colors[color.LevelRgb]["hdrbg"] = ColorStringToBGColor(c.Configuration.HighlightHdrBG) colors[color.LevelRgb]["hdrbg"] = ColorStringToBGColor(conf.Settings.HighlightHdrBG)
} }
if len(c.Configuration.HighlightHdrFG) > 0 { if len(conf.Settings.HighlightHdrFG) > 0 {
colors[color.Level16]["hdrfg"] = ColorStringToColor(c.Configuration.HighlightHdrFG) colors[color.Level16]["hdrfg"] = ColorStringToColor(conf.Settings.HighlightHdrFG)
colors[color.Level256]["hdrfg"] = ColorStringToColor(c.Configuration.HighlightHdrFG) colors[color.Level256]["hdrfg"] = ColorStringToColor(conf.Settings.HighlightHdrFG)
colors[color.LevelRgb]["hdrfg"] = ColorStringToColor(c.Configuration.HighlightHdrFG) colors[color.LevelRgb]["hdrfg"] = ColorStringToColor(conf.Settings.HighlightHdrFG)
} }
return colors return colors
} }
// find supported color mode, modifies config based on constants // find supported color mode, modifies config based on constants
func (c *Config) DetermineColormode() { func (conf *Config) DetermineColormode() {
if !isTerminal(os.Stdout) { if !isTerminal(os.Stdout) {
color.Disable() color.Disable()
} else { } else {
level := color.TermColorLevel() level := color.TermColorLevel()
colors := c.Colors() colors := conf.Colors()
c.ColorStyle = color.New(colors[level]["bg"], colors[level]["fg"]) conf.ColorStyle = color.New(colors[level]["bg"], colors[level]["fg"])
c.HighlightStyle = color.New(colors[level]["hlbg"], colors[level]["hlfg"]) conf.HighlightStyle = color.New(colors[level]["hlbg"], colors[level]["hlfg"])
c.NoHighlightStyle = color.New(colors[level]["nohlbg"], colors[level]["nohlfg"]) conf.NoHighlightStyle = color.New(colors[level]["nohlbg"], colors[level]["nohlfg"])
c.HighlightHdrStyle = color.New(colors[level]["hdrbg"], colors[level]["hdrfg"]) conf.HighlightHdrStyle = color.New(colors[level]["hdrbg"], colors[level]["hdrfg"])
} }
} }
// Return true if current terminal is interactive // Return true if current terminal is interactive
func isTerminal(f *os.File) bool { func isTerminal(f *os.File) bool {
o, _ := f.Stat() o, _ := f.Stat()
if (o.Mode() & os.ModeCharDevice) == os.ModeCharDevice { return (o.Mode() & os.ModeCharDevice) == os.ModeCharDevice
return true
} else {
return false
}
} }
// main program version
// generated version string, used by -v contains lib.Version on
//
// main branch, and lib.Version-$branch-$lastcommit-$date on
//
// development branch
func Getversion() string { func Getversion() string {
// main program version
// generated version string, used by -v contains lib.Version on
// main branch, and lib.Version-$branch-$lastcommit-$date on
// development branch
return fmt.Sprintf("This is tablizer version %s", VERSION) return fmt.Sprintf("This is tablizer version %s", VERSION)
} }
@@ -261,7 +257,7 @@ func (conf *Config) PrepareModeFlags(flag Modeflag) {
case flag.C: case flag.C:
conf.OutputMode = CSV conf.OutputMode = CSV
default: default:
conf.OutputMode = Ascii conf.OutputMode = ASCII
} }
} }
@@ -270,13 +266,14 @@ func (conf *Config) PrepareFilters() error {
for _, filter := range conf.Rawfilters { for _, filter := range conf.Rawfilters {
parts := strings.Split(filter, "=") parts := strings.Split(filter, "=")
if len(parts) != 2 { if len(parts) != MAXPARTS {
return errors.New("filter field and value must be separated by =") return errors.New("filter field and value must be separated by =")
} }
reg, err := regexp.Compile(parts[1]) reg, err := regexp.Compile(parts[1])
if err != nil { if err != nil {
return err return fmt.Errorf("failed to compile filter regex for field %s: %w",
parts[0], err)
} }
conf.Filters[strings.ToLower(parts[0])] = reg conf.Filters[strings.ToLower(parts[0])] = reg
@@ -285,62 +282,69 @@ func (conf *Config) PrepareFilters() error {
return nil return nil
} }
func (c *Config) CheckEnv() { func (conf *Config) CheckEnv() {
// check for environment vars, command line flags have precedence, // check for environment vars, command line flags have precedence,
// NO_COLOR is being checked by the color module itself. // NO_COLOR is being checked by the color module itself.
if !c.NoNumbering { if !conf.NoNumbering {
_, set := os.LookupEnv("T_NO_HEADER_NUMBERING") _, set := os.LookupEnv("T_NO_HEADER_NUMBERING")
if set { if set {
c.NoNumbering = true conf.NoNumbering = true
} }
} }
if len(c.Columns) == 0 { if len(conf.Columns) == 0 {
cols := os.Getenv("T_COLUMNS") cols := os.Getenv("T_COLUMNS")
if len(cols) > 1 { if len(cols) > 1 {
c.Columns = cols conf.Columns = cols
} }
} }
} }
func (c *Config) ApplyDefaults() { func (conf *Config) ApplyDefaults() {
// mode specific defaults // mode specific defaults
if c.OutputMode == Yaml || c.OutputMode == CSV { if conf.OutputMode == Yaml || conf.OutputMode == CSV {
c.NoNumbering = true conf.NoNumbering = true
} }
ValidHooks = []string{"filter", "process", "transpose", "append"} ValidHooks = []string{"filter", "process", "transpose", "append"}
} }
func (c *Config) PreparePattern(pattern string) error { func (conf *Config) PreparePattern(pattern string) error {
PatternR, err := regexp.Compile(pattern) PatternR, err := regexp.Compile(pattern)
if err != nil { if err != nil {
return errors.Unwrap(fmt.Errorf("Regexp pattern %s is invalid: %w", c.Pattern, err)) return fmt.Errorf("regexp pattern %s is invalid: %w", conf.Pattern, err)
} }
c.PatternR = PatternR conf.PatternR = PatternR
c.Pattern = pattern conf.Pattern = pattern
return nil return nil
} }
func (c *Config) ParseConfigfile() error { // Parse config file. Ignore if the file doesn't exist but return an
if path, err := os.Stat(c.Configfile); !os.IsNotExist(err) { // error if it exists but fails to read or parse
if !path.IsDir() { func (conf *Config) ParseConfigfile() error {
configstring, err := os.ReadFile(path.Name()) path, err := os.Stat(conf.Configfile)
if err != nil {
return err
}
err = hclsimple.Decode( if os.IsNotExist(err) || path.IsDir() {
path.Name(), []byte(configstring), // ignore non-existent or dirs
nil, &c.Configuration, return nil
) }
if err != nil {
log.Fatalf("Failed to load configuration: %s", err) configstring, err := os.ReadFile(path.Name())
} if err != nil {
} return fmt.Errorf("failed to read config file %s: %w", path.Name(), err)
}
err = hclsimple.Decode(
path.Name(),
configstring,
nil,
&conf.Settings)
if err != nil {
return fmt.Errorf("failed to load configuration file %s: %w",
path.Name(), err)
} }
return nil return nil

View File

@@ -34,7 +34,7 @@ func TestPrepareModeFlags(t *testing.T) {
{Modeflag{O: true}, Orgtbl}, {Modeflag{O: true}, Orgtbl},
{Modeflag{Y: true}, Yaml}, {Modeflag{Y: true}, Yaml},
{Modeflag{M: true}, Markdown}, {Modeflag{M: true}, Markdown},
{Modeflag{}, Ascii}, {Modeflag{}, ASCII},
} }
// FIXME: use a map for easier printing // FIXME: use a map for easier printing
@@ -63,15 +63,15 @@ func TestPrepareSortFlags(t *testing.T) {
{Sortmode{}, "string"}, {Sortmode{}, "string"},
} }
for _, tt := range tests { for _, testdata := range tests {
testname := fmt.Sprintf("PrepareSortFlags-expect-%s", tt.expect) testname := fmt.Sprintf("PrepareSortFlags-expect-%s", testdata.expect)
t.Run(testname, func(t *testing.T) { t.Run(testname, func(t *testing.T) {
c := Config{} conf := Config{}
c.PrepareSortFlags(tt.flag) conf.PrepareSortFlags(testdata.flag)
if c.SortMode != tt.expect { if conf.SortMode != testdata.expect {
t.Errorf("got: %s, expect: %s", c.SortMode, tt.expect) t.Errorf("got: %s, expect: %s", conf.SortMode, testdata.expect)
} }
}) })
} }
@@ -86,15 +86,16 @@ func TestPreparePattern(t *testing.T) {
{"[a-z", true}, {"[a-z", true},
} }
for _, tt := range tests { for _, testdata := range tests {
testname := fmt.Sprintf("PreparePattern-pattern-%s-wanterr-%t", tt.pattern, tt.wanterr) testname := fmt.Sprintf("PreparePattern-pattern-%s-wanterr-%t",
testdata.pattern, testdata.wanterr)
t.Run(testname, func(t *testing.T) { t.Run(testname, func(t *testing.T) {
c := Config{} conf := Config{}
err := c.PreparePattern(tt.pattern) err := conf.PreparePattern(testdata.pattern)
if err != nil { if err != nil {
if !tt.wanterr { if !testdata.wanterr {
t.Errorf("PreparePattern returned error: %s", err) t.Errorf("PreparePattern returned error: %s", err)
} }
} }

View File

@@ -33,11 +33,11 @@ import (
func man() { func man() {
man := exec.Command("less", "-") man := exec.Command("less", "-")
var b bytes.Buffer var buffer bytes.Buffer
b.Write([]byte(manpage)) buffer.Write([]byte(manpage))
man.Stdout = os.Stdout man.Stdout = os.Stdout
man.Stdin = &b man.Stdin = &buffer
man.Stderr = os.Stderr man.Stderr = os.Stderr
err := man.Run() err := man.Run()
@@ -58,7 +58,7 @@ func completion(cmd *cobra.Command, mode string) error {
case "powershell": case "powershell":
return cmd.Root().GenPowerShellCompletionWithDesc(os.Stdout) return cmd.Root().GenPowerShellCompletionWithDesc(os.Stdout)
default: default:
return errors.New("Invalid shell parameter! Valid ones: bash|zsh|fish|powershell") return errors.New("invalid shell parameter! Valid ones: bash|zsh|fish|powershell")
} }
} }
@@ -79,11 +79,13 @@ func Execute() {
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
if ShowVersion { if ShowVersion {
fmt.Println(cfg.Getversion()) fmt.Println(cfg.Getversion())
return nil return nil
} }
if ShowManual { if ShowManual {
man() man()
return nil return nil
} }
@@ -120,38 +122,64 @@ func Execute() {
} }
// options // options
rootCmd.PersistentFlags().BoolVarP(&conf.Debug, "debug", "d", false, "Enable debugging") rootCmd.PersistentFlags().BoolVarP(&conf.Debug, "debug", "d", false,
rootCmd.PersistentFlags().BoolVarP(&conf.NoNumbering, "no-numbering", "n", false, "Disable header numbering") "Enable debugging")
rootCmd.PersistentFlags().BoolVarP(&conf.NoHeaders, "no-headers", "H", false, "Disable header display") rootCmd.PersistentFlags().BoolVarP(&conf.NoNumbering, "no-numbering", "n", false,
rootCmd.PersistentFlags().BoolVarP(&conf.NoColor, "no-color", "N", false, "Disable pattern highlighting") "Disable header numbering")
rootCmd.PersistentFlags().BoolVarP(&ShowVersion, "version", "V", false, "Print program version") rootCmd.PersistentFlags().BoolVarP(&conf.NoHeaders, "no-headers", "H", false,
rootCmd.PersistentFlags().BoolVarP(&conf.InvertMatch, "invert-match", "v", false, "select non-matching rows") "Disable header display")
rootCmd.PersistentFlags().BoolVarP(&ShowManual, "man", "m", false, "Display manual page") rootCmd.PersistentFlags().BoolVarP(&conf.NoColor, "no-color", "N", false,
rootCmd.PersistentFlags().BoolVarP(&conf.UseFuzzySearch, "fuzzy", "z", false, "Use fuzzy searching") "Disable pattern highlighting")
rootCmd.PersistentFlags().BoolVarP(&conf.UseHighlight, "highlight-lines", "L", false, "Use alternating background colors") rootCmd.PersistentFlags().BoolVarP(&ShowVersion, "version", "V", false,
rootCmd.PersistentFlags().StringVarP(&ShowCompletion, "completion", "", "", "Display completion code") "Print program version")
rootCmd.PersistentFlags().StringVarP(&conf.Separator, "separator", "s", cfg.DefaultSeparator, "Custom field separator") rootCmd.PersistentFlags().BoolVarP(&conf.InvertMatch, "invert-match", "v", false,
rootCmd.PersistentFlags().StringVarP(&conf.Columns, "columns", "c", "", "Only show the speficied columns (separated by ,)") "select non-matching rows")
rootCmd.PersistentFlags().BoolVarP(&ShowManual, "man", "m", false,
"Display manual page")
rootCmd.PersistentFlags().BoolVarP(&conf.UseFuzzySearch, "fuzzy", "z", false,
"Use fuzzy searching")
rootCmd.PersistentFlags().BoolVarP(&conf.UseHighlight, "highlight-lines", "L", false,
"Use alternating background colors")
rootCmd.PersistentFlags().StringVarP(&ShowCompletion, "completion", "", "",
"Display completion code")
rootCmd.PersistentFlags().StringVarP(&conf.Separator, "separator", "s", cfg.DefaultSeparator,
"Custom field separator")
rootCmd.PersistentFlags().StringVarP(&conf.Columns, "columns", "c", "",
"Only show the speficied columns (separated by ,)")
// sort options // sort options
rootCmd.PersistentFlags().IntVarP(&conf.SortByColumn, "sort-by", "k", 0, "Sort by column (default: 1)") rootCmd.PersistentFlags().IntVarP(&conf.SortByColumn, "sort-by", "k", 0,
"Sort by column (default: 1)")
// sort mode, only 1 allowed // sort mode, only 1 allowed
rootCmd.PersistentFlags().BoolVarP(&conf.SortDescending, "sort-desc", "D", false, "Sort in descending order (default: ascending)") rootCmd.PersistentFlags().BoolVarP(&conf.SortDescending, "sort-desc", "D", false,
rootCmd.PersistentFlags().BoolVarP(&sortmode.Numeric, "sort-numeric", "i", false, "sort according to string numerical value") "Sort in descending order (default: ascending)")
rootCmd.PersistentFlags().BoolVarP(&sortmode.Time, "sort-time", "t", false, "sort according to time string") rootCmd.PersistentFlags().BoolVarP(&sortmode.Numeric, "sort-numeric", "i", false,
rootCmd.PersistentFlags().BoolVarP(&sortmode.Age, "sort-age", "a", false, "sort according to age (duration) string") "sort according to string numerical value")
rootCmd.MarkFlagsMutuallyExclusive("sort-numeric", "sort-time", "sort-age") rootCmd.PersistentFlags().BoolVarP(&sortmode.Time, "sort-time", "t", false,
"sort according to time string")
rootCmd.PersistentFlags().BoolVarP(&sortmode.Age, "sort-age", "a", false,
"sort according to age (duration) string")
rootCmd.MarkFlagsMutuallyExclusive("sort-numeric", "sort-time",
"sort-age")
// output flags, only 1 allowed // output flags, only 1 allowed
rootCmd.PersistentFlags().BoolVarP(&modeflag.X, "extended", "X", false, "Enable extended output") rootCmd.PersistentFlags().BoolVarP(&modeflag.X, "extended", "X", false,
rootCmd.PersistentFlags().BoolVarP(&modeflag.M, "markdown", "M", false, "Enable markdown table output") "Enable extended output")
rootCmd.PersistentFlags().BoolVarP(&modeflag.O, "orgtbl", "O", false, "Enable org-mode table output") rootCmd.PersistentFlags().BoolVarP(&modeflag.M, "markdown", "M", false,
rootCmd.PersistentFlags().BoolVarP(&modeflag.S, "shell", "S", false, "Enable shell mode output") "Enable markdown table output")
rootCmd.PersistentFlags().BoolVarP(&modeflag.Y, "yaml", "Y", false, "Enable yaml output") rootCmd.PersistentFlags().BoolVarP(&modeflag.O, "orgtbl", "O", false,
rootCmd.PersistentFlags().BoolVarP(&modeflag.C, "csv", "C", false, "Enable CSV output") "Enable org-mode table output")
rootCmd.PersistentFlags().BoolVarP(&modeflag.A, "ascii", "A", false, "Enable ASCII output (default)") rootCmd.PersistentFlags().BoolVarP(&modeflag.S, "shell", "S", false,
rootCmd.MarkFlagsMutuallyExclusive("extended", "markdown", "orgtbl", "shell", "yaml", "csv") "Enable shell mode output")
rootCmd.PersistentFlags().BoolVarP(&modeflag.Y, "yaml", "Y", false,
"Enable yaml output")
rootCmd.PersistentFlags().BoolVarP(&modeflag.C, "csv", "C", false,
"Enable CSV output")
rootCmd.PersistentFlags().BoolVarP(&modeflag.A, "ascii", "A", false,
"Enable ASCII output (default)")
rootCmd.MarkFlagsMutuallyExclusive("extended", "markdown", "orgtbl",
"shell", "yaml", "csv")
// lisp options // lisp options
rootCmd.PersistentFlags().StringVarP(&conf.LispLoadPath, "load-path", "l", cfg.DefaultLoadPath, rootCmd.PersistentFlags().StringVarP(&conf.LispLoadPath, "load-path", "l", cfg.DefaultLoadPath,

View File

@@ -16,7 +16,7 @@ SYNOPSIS
-H, --no-headers Disable headers display -H, --no-headers Disable headers display
-s, --separator string Custom field separator -s, --separator string Custom field separator
-k, --sort-by int Sort by column (default: 1) -k, --sort-by int Sort by column (default: 1)
-z, --fuzzy Use fuzzy seach [experimental] -z, --fuzzy Use fuzzy search [experimental]
-F, --filter field=reg Filter given field with regex, can be used multiple times -F, --filter field=reg Filter given field with regex, can be used multiple times
Output Flags (mutually exclusive): Output Flags (mutually exclusive):
@@ -142,9 +142,9 @@ DESCRIPTION
kubectl get pods -A | tablizer "(?i)account" kubectl get pods -A | tablizer "(?i)account"
You can use the experimental fuzzy seach feature by providing the option You can use the experimental fuzzy search feature by providing the
-z, in which case the pattern is regarded as a fuzzy search term, not a option -z, in which case the pattern is regarded as a fuzzy search term,
regexp. not a regexp.
Sometimes you want to filter by one or more columns. You can do that Sometimes you want to filter by one or more columns. You can do that
using the -F option. The option can be specified multiple times and has using the -F option. The option can be specified multiple times and has
@@ -353,7 +353,7 @@ Operational Flags:
-H, --no-headers Disable headers display -H, --no-headers Disable headers display
-s, --separator string Custom field separator -s, --separator string Custom field separator
-k, --sort-by int Sort by column (default: 1) -k, --sort-by int Sort by column (default: 1)
-z, --fuzzy Use fuzzy seach [experimental] -z, --fuzzy Use fuzzy search [experimental]
-F, --filter field=reg Filter given field with regex, can be used multiple times -F, --filter field=reg Filter given field with regex, can be used multiple times
Output Flags (mutually exclusive): Output Flags (mutually exclusive):

View File

@@ -28,12 +28,12 @@ import (
* [!]Match a line, use fuzzy search for normal pattern strings and * [!]Match a line, use fuzzy search for normal pattern strings and
* regexp otherwise. * regexp otherwise.
*/ */
func matchPattern(c cfg.Config, line string) bool { func matchPattern(conf cfg.Config, line string) bool {
if c.UseFuzzySearch { if conf.UseFuzzySearch {
return fuzzy.MatchFold(c.Pattern, line) return fuzzy.MatchFold(conf.Pattern, line)
} }
return c.PatternR.MatchString(line) return conf.PatternR.MatchString(line)
} }
/* /*

View File

@@ -45,22 +45,22 @@ func TestMatchPattern(t *testing.T) {
}, },
} }
for _, in := range input { for _, inputdata := range input {
testname := fmt.Sprintf("match-pattern-%s", in.name) testname := fmt.Sprintf("match-pattern-%s", inputdata.name)
t.Run(testname, func(t *testing.T) { t.Run(testname, func(t *testing.T) {
c := cfg.Config{} conf := cfg.Config{}
if in.fuzzy { if inputdata.fuzzy {
c.UseFuzzySearch = true conf.UseFuzzySearch = true
} }
err := c.PreparePattern(in.pattern) err := conf.PreparePattern(inputdata.pattern)
if err != nil { if err != nil {
t.Errorf("PreparePattern returned error: %s", err) t.Errorf("PreparePattern returned error: %s", err)
} }
if !matchPattern(c, in.line) { if !matchPattern(conf, inputdata.line) {
t.Errorf("matchPattern() did not match\nExp: true\nGot: false\n") t.Errorf("matchPattern() did not match\nExp: true\nGot: false\n")
} }
}) })
@@ -143,20 +143,20 @@ func TestFilterByFields(t *testing.T) {
}, },
} }
for _, in := range input { for _, inputdata := range input {
testname := fmt.Sprintf("filter-by-fields-%s", in.name) testname := fmt.Sprintf("filter-by-fields-%s", inputdata.name)
t.Run(testname, func(t *testing.T) { t.Run(testname, func(t *testing.T) {
c := cfg.Config{Rawfilters: in.filter, InvertMatch: in.invert} conf := cfg.Config{Rawfilters: inputdata.filter, InvertMatch: inputdata.invert}
err := c.PrepareFilters() err := conf.PrepareFilters()
if err != nil { if err != nil {
t.Errorf("PrepareFilters returned error: %s", err) t.Errorf("PrepareFilters returned error: %s", err)
} }
data, _, _ := FilterByFields(c, data) data, _, _ := FilterByFields(conf, data)
if !reflect.DeepEqual(data, in.expect) { if !reflect.DeepEqual(data, inputdata.expect) {
t.Errorf("Filtered data does not match expected data:\ngot: %+v\nexp: %+v", data, in.expect) t.Errorf("Filtered data does not match expected data:\ngot: %+v\nexp: %+v", data, inputdata.expect)
} }
}) })
} }

View File

@@ -41,11 +41,11 @@ func contains(s []int, e int) bool {
// parse columns list given with -c, modifies config.UseColumns based // parse columns list given with -c, modifies config.UseColumns based
// on eventually given regex // on eventually given regex
func PrepareColumns(c *cfg.Config, data *Tabdata) error { func PrepareColumns(conf *cfg.Config, data *Tabdata) error {
if len(c.Columns) > 0 { if len(conf.Columns) > 0 {
for _, use := range strings.Split(c.Columns, ",") { for _, use := range strings.Split(conf.Columns, ",") {
if len(use) == 0 { if len(use) == 0 {
msg := fmt.Sprintf("Could not parse columns list %s: empty column", c.Columns) msg := fmt.Sprintf("Could not parse columns list %s: empty column", conf.Columns)
return errors.New(msg) return errors.New(msg)
} }
@@ -54,14 +54,14 @@ func PrepareColumns(c *cfg.Config, data *Tabdata) error {
// might be a regexp // might be a regexp
colPattern, err := regexp.Compile(use) colPattern, err := regexp.Compile(use)
if err != nil { if err != nil {
msg := fmt.Sprintf("Could not parse columns list %s: %v", c.Columns, err) msg := fmt.Sprintf("Could not parse columns list %s: %v", conf.Columns, err)
return errors.New(msg) return errors.New(msg)
} }
// find matching header fields // find matching header fields
for i, head := range data.headers { for i, head := range data.headers {
if colPattern.MatchString(head) { if colPattern.MatchString(head) {
c.UseColumns = append(c.UseColumns, i+1) conf.UseColumns = append(conf.UseColumns, i+1)
} }
} }
@@ -70,45 +70,45 @@ func PrepareColumns(c *cfg.Config, data *Tabdata) error {
// a colum spec is not a number, we process them above // a colum spec is not a number, we process them above
// inside the err handler for atoi(). so only add the // inside the err handler for atoi(). so only add the
// number, if it's really just a number. // number, if it's really just a number.
c.UseColumns = append(c.UseColumns, usenum) conf.UseColumns = append(conf.UseColumns, usenum)
} }
} }
// deduplicate: put all values into a map (value gets map key) // deduplicate: put all values into a map (value gets map key)
// thereby removing duplicates, extract keys into new slice // thereby removing duplicates, extract keys into new slice
// and sort it // and sort it
imap := make(map[int]int, len(c.UseColumns)) imap := make(map[int]int, len(conf.UseColumns))
for _, i := range c.UseColumns { for _, i := range conf.UseColumns {
imap[i] = 0 imap[i] = 0
} }
c.UseColumns = nil conf.UseColumns = nil
for k := range imap { for k := range imap {
c.UseColumns = append(c.UseColumns, k) conf.UseColumns = append(conf.UseColumns, k)
} }
sort.Ints(c.UseColumns) sort.Ints(conf.UseColumns)
} }
return nil return nil
} }
// prepare headers: add numbers to headers // prepare headers: add numbers to headers
func numberizeAndReduceHeaders(c cfg.Config, data *Tabdata) { func numberizeAndReduceHeaders(conf cfg.Config, data *Tabdata) {
numberedHeaders := []string{} numberedHeaders := []string{}
maxwidth := 0 // start from scratch, so we only look at displayed column widths maxwidth := 0 // start from scratch, so we only look at displayed column widths
for i, head := range data.headers { for idx, head := range data.headers {
headlen := 0 headlen := 0
if len(c.Columns) > 0 { if len(conf.Columns) > 0 {
// -c specified // -c specified
if !contains(c.UseColumns, i+1) { if !contains(conf.UseColumns, idx+1) {
// ignore this one // ignore this one
continue continue
} }
} }
if c.NoNumbering { if conf.NoNumbering {
numberedHeaders = append(numberedHeaders, head) numberedHeaders = append(numberedHeaders, head)
headlen = len(head) headlen = len(head)
} else { } else {
numhead := fmt.Sprintf("%s(%d)", head, i+1) numhead := fmt.Sprintf("%s(%d)", head, idx+1)
headlen = len(numhead) headlen = len(numhead)
numberedHeaders = append(numberedHeaders, numhead) numberedHeaders = append(numberedHeaders, numhead)
} }
@@ -124,14 +124,14 @@ func numberizeAndReduceHeaders(c cfg.Config, data *Tabdata) {
} }
// exclude columns, if any // exclude columns, if any
func reduceColumns(c cfg.Config, data *Tabdata) { func reduceColumns(conf cfg.Config, data *Tabdata) {
if len(c.Columns) > 0 { if len(conf.Columns) > 0 {
reducedEntries := [][]string{} reducedEntries := [][]string{}
var reducedEntry []string var reducedEntry []string
for _, entry := range data.entries { for _, entry := range data.entries {
reducedEntry = nil reducedEntry = nil
for i, value := range entry { for i, value := range entry {
if !contains(c.UseColumns, i+1) { if !contains(conf.UseColumns, i+1) {
continue continue
} }
@@ -153,8 +153,9 @@ func trimRow(row []string) []string {
return fixedrow return fixedrow
} }
func colorizeData(c cfg.Config, output string) string { func colorizeData(conf cfg.Config, output string) string {
if c.UseHighlight && color.IsConsole(os.Stdout) { switch {
case conf.UseHighlight && color.IsConsole(os.Stdout):
highlight := true highlight := true
colorized := "" colorized := ""
first := true first := true
@@ -167,17 +168,17 @@ func colorizeData(c cfg.Config, output string) string {
// in pprint mode. This doesn't matter as long as // in pprint mode. This doesn't matter as long as
// we don't use colorization. But with colors the // we don't use colorization. But with colors the
// missing spaces can be seen. // missing spaces can be seen.
if c.OutputMode == cfg.Ascii { if conf.OutputMode == cfg.ASCII {
line = line + " " line += " "
} }
line = c.HighlightHdrStyle.Sprint(line) line = conf.HighlightHdrStyle.Sprint(line)
first = false first = false
} else { } else {
line = c.HighlightStyle.Sprint(line) line = conf.HighlightStyle.Sprint(line)
} }
} else { } else {
line = c.NoHighlightStyle.Sprint(line) line = conf.NoHighlightStyle.Sprint(line)
} }
highlight = !highlight highlight = !highlight
@@ -185,12 +186,12 @@ func colorizeData(c cfg.Config, output string) string {
} }
return colorized return colorized
} else if len(c.Pattern) > 0 && !c.NoColor && color.IsConsole(os.Stdout) { case len(conf.Pattern) > 0 && !conf.NoColor && color.IsConsole(os.Stdout):
r := regexp.MustCompile("(" + c.Pattern + ")") r := regexp.MustCompile("(" + conf.Pattern + ")")
return r.ReplaceAllStringFunc(output, func(in string) string { return r.ReplaceAllStringFunc(output, func(in string) string {
return c.ColorStyle.Sprint(in) return conf.ColorStyle.Sprint(in)
}) })
} else { default:
return output return output
} }
} }

View File

@@ -19,9 +19,10 @@ package lib
import ( import (
"fmt" "fmt"
"github.com/tlinden/tablizer/cfg"
"reflect" "reflect"
"testing" "testing"
"github.com/tlinden/tablizer/cfg"
) )
func TestContains(t *testing.T) { func TestContains(t *testing.T) {
@@ -74,15 +75,15 @@ func TestPrepareColumns(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
testname := fmt.Sprintf("PrepareColumns-%s-%t", tt.input, tt.wanterror) testname := fmt.Sprintf("PrepareColumns-%s-%t", tt.input, tt.wanterror)
t.Run(testname, func(t *testing.T) { t.Run(testname, func(t *testing.T) {
c := cfg.Config{Columns: tt.input} conf := cfg.Config{Columns: tt.input}
err := PrepareColumns(&c, &data) err := PrepareColumns(&conf, &data)
if err != nil { if err != nil {
if !tt.wanterror { if !tt.wanterror {
t.Errorf("got error: %v", err) t.Errorf("got error: %v", err)
} }
} else { } else {
if !reflect.DeepEqual(c.UseColumns, tt.exp) { if !reflect.DeepEqual(conf.UseColumns, tt.exp) {
t.Errorf("got: %v, expected: %v", c.UseColumns, tt.exp) t.Errorf("got: %v, expected: %v", conf.UseColumns, tt.exp)
} }
} }
}) })
@@ -114,14 +115,16 @@ func TestReduceColumns(t *testing.T) {
input := [][]string{{"a", "b", "c"}} input := [][]string{{"a", "b", "c"}}
for _, tt := range tests { for _, testdata := range tests {
testname := fmt.Sprintf("reduce-columns-by-%+v", tt.columns) testname := fmt.Sprintf("reduce-columns-by-%+v", testdata.columns)
t.Run(testname, func(t *testing.T) { t.Run(testname, func(t *testing.T) {
c := cfg.Config{Columns: "x", UseColumns: tt.columns} c := cfg.Config{Columns: "x", UseColumns: testdata.columns}
data := Tabdata{entries: input} data := Tabdata{entries: input}
reduceColumns(c, &data) reduceColumns(c, &data)
if !reflect.DeepEqual(data.entries, tt.expect) { if !reflect.DeepEqual(data.entries, testdata.expect) {
t.Errorf("reduceColumns returned invalid data:\ngot: %+v\nexp: %+v", data.entries, tt.expect) t.Errorf("reduceColumns returned invalid data:\ngot: %+v\nexp: %+v",
data.entries, testdata.expect)
} }
}) })
} }
@@ -142,15 +145,17 @@ func TestNumberizeHeaders(t *testing.T) {
{[]string{"ONE", "TWO"}, []int{1, 2}, true}, {[]string{"ONE", "TWO"}, []int{1, 2}, true},
} }
for _, tt := range tests { for _, testdata := range tests {
testname := fmt.Sprintf("numberize-headers-columns-%+v-nonum-%t", tt.columns, tt.nonum) testname := fmt.Sprintf("numberize-headers-columns-%+v-nonum-%t",
testdata.columns, testdata.nonum)
t.Run(testname, func(t *testing.T) { t.Run(testname, func(t *testing.T) {
c := cfg.Config{Columns: "x", UseColumns: tt.columns, NoNumbering: tt.nonum} conf := cfg.Config{Columns: "x", UseColumns: testdata.columns, NoNumbering: testdata.nonum}
usedata := data usedata := data
numberizeAndReduceHeaders(c, &usedata) numberizeAndReduceHeaders(conf, &usedata)
if !reflect.DeepEqual(usedata.headers, tt.expect) { if !reflect.DeepEqual(usedata.headers, testdata.expect) {
t.Errorf("numberizeAndReduceHeaders returned invalid data:\ngot: %+v\nexp: %+v", t.Errorf("numberizeAndReduceHeaders returned invalid data:\ngot: %+v\nexp: %+v",
usedata.headers, tt.expect) usedata.headers, testdata.expect)
} }
}) })
} }

View File

@@ -19,40 +19,41 @@ package lib
import ( import (
"errors" "errors"
"github.com/tlinden/tablizer/cfg"
"io" "io"
"os" "os"
"github.com/tlinden/tablizer/cfg"
) )
func ProcessFiles(c *cfg.Config, args []string) error { func ProcessFiles(conf *cfg.Config, args []string) error {
fds, pattern, err := determineIO(c, args) fds, pattern, err := determineIO(conf, args)
if err != nil { if err != nil {
return err return err
} }
if err := c.PreparePattern(pattern); err != nil { if err := conf.PreparePattern(pattern); err != nil {
return err return err
} }
for _, fd := range fds { for _, fd := range fds {
data, err := Parse(*c, fd) data, err := Parse(*conf, fd)
if err != nil { if err != nil {
return err return err
} }
err = PrepareColumns(c, &data) err = PrepareColumns(conf, &data)
if err != nil { if err != nil {
return err return err
} }
printData(os.Stdout, *c, &data) printData(os.Stdout, *conf, &data)
} }
return nil return nil
} }
func determineIO(c *cfg.Config, args []string) ([]io.Reader, string, error) { func determineIO(conf *cfg.Config, args []string) ([]io.Reader, string, error) {
var pattern string var pattern string
var fds []io.Reader var fds []io.Reader
var haveio bool var haveio bool
@@ -64,45 +65,43 @@ func determineIO(c *cfg.Config, args []string) ([]io.Reader, string, error) {
if len(args) > 0 { if len(args) > 0 {
// ignore any args > 1 // ignore any args > 1
pattern = args[0] pattern = args[0]
c.Pattern = args[0] // used for colorization by printData() conf.Pattern = args[0] // used for colorization by printData()
} }
haveio = true haveio = true
} else { } else if len(args) > 0 {
if len(args) > 0 { // threre were args left, take a look
// threre were args left, take a look if args[0] == "-" {
if args[0] == "-" { // in traditional unix programs a dash denotes STDIN (forced)
// in traditional unix programs a dash denotes STDIN (forced) fds = append(fds, os.Stdin)
fds = append(fds, os.Stdin) haveio = true
haveio = true } else {
} else { if _, err := os.Stat(args[0]); err != nil {
if _, err := os.Stat(args[0]); err != nil { // first one is not a file, consider it as regexp and
// first one is not a file, consider it as regexp and // shift arg list
// shift arg list pattern = args[0]
pattern = args[0] conf.Pattern = args[0] // used for colorization by printData()
c.Pattern = args[0] // used for colorization by printData() args = args[1:]
args = args[1:] }
}
if len(args) > 0 { if len(args) > 0 {
// consider any other args as files // consider any other args as files
for _, file := range args { for _, file := range args {
fd, err := os.OpenFile(file, os.O_RDONLY, 0755) fd, err := os.OpenFile(file, os.O_RDONLY, 0755)
if err != nil { if err != nil {
return nil, "", err return nil, "", err
}
fds = append(fds, fd)
haveio = true
} }
fds = append(fds, fd)
haveio = true
} }
} }
} }
} }
if !haveio { if !haveio {
return nil, "", errors.New("No file specified and nothing to read on stdin!") return nil, "", errors.New("no file specified and nothing to read on stdin")
} }
return fds, pattern, nil return fds, pattern, nil

View File

@@ -52,19 +52,19 @@ func AddHook(env *zygo.Zlisp, name string, args []zygo.Sexp) (zygo.Sexp, error)
} }
hookname = t.Name() hookname = t.Name()
default: default:
return zygo.SexpNull, errors.New("hook name must be a symbol!") return zygo.SexpNull, errors.New("hook name must be a symbol ")
} }
switch t := args[1].(type) { switch sexptype := args[1].(type) {
case *zygo.SexpSymbol: case *zygo.SexpSymbol:
_, exists := Hooks[hookname] _, exists := Hooks[hookname]
if !exists { if !exists {
Hooks[hookname] = []*zygo.SexpSymbol{t} Hooks[hookname] = []*zygo.SexpSymbol{sexptype}
} else { } else {
Hooks[hookname] = append(Hooks[hookname], t) Hooks[hookname] = append(Hooks[hookname], sexptype)
} }
default: default:
return zygo.SexpNull, errors.New("hook function must be a symbol!") return zygo.SexpNull, errors.New("hook function must be a symbol ")
} }
return zygo.SexpNull, nil return zygo.SexpNull, nil
@@ -86,7 +86,7 @@ func HookExists(key string) bool {
/* /*
* Basic sanity checks and load lisp file * Basic sanity checks and load lisp file
*/ */
func LoadFile(env *zygo.Zlisp, path string) error { func LoadAndEvalFile(env *zygo.Zlisp, path string) error {
if strings.HasSuffix(path, `.zy`) { if strings.HasSuffix(path, `.zy`) {
code, err := os.ReadFile(path) code, err := os.ReadFile(path)
if err != nil { if err != nil {
@@ -106,32 +106,39 @@ func LoadFile(env *zygo.Zlisp, path string) error {
/* /*
* Setup lisp interpreter environment * Setup lisp interpreter environment
*/ */
func SetupLisp(c *cfg.Config) error { 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) Hooks = make(map[string][]*zygo.SexpSymbol)
// init sandbox
env := zygo.NewZlispSandbox() env := zygo.NewZlispSandbox()
env.AddFunction("addhook", AddHook) env.AddFunction("addhook", AddHook)
// iterate over load-path and evaluate all *.zy files there, if any if !path.IsDir() {
// we ignore if load-path does not exist, which is the default anyway // load single lisp file
if path, err := os.Stat(c.LispLoadPath); !os.IsNotExist(err) { err = LoadAndEvalFile(env, conf.LispLoadPath)
if !path.IsDir() { if err != nil {
err := LoadFile(env, c.LispLoadPath) return err
if err != nil { }
return err } else {
} // load all lisp file in load dir
} else { dir, err := os.ReadDir(conf.LispLoadPath)
dir, err := os.ReadDir(c.LispLoadPath) if err != nil {
if err != nil { return err
return err }
}
for _, entry := range dir { for _, entry := range dir {
if !entry.IsDir() { if !entry.IsDir() {
err := LoadFile(env, c.LispLoadPath+"/"+entry.Name()) err := LoadAndEvalFile(env, conf.LispLoadPath+"/"+entry.Name())
if err != nil { if err != nil {
return err return err
}
} }
} }
} }
@@ -139,7 +146,7 @@ func SetupLisp(c *cfg.Config) error {
RegisterLib(env) RegisterLib(env)
c.Lisp = env conf.Lisp = env
return nil return nil
} }
@@ -155,11 +162,11 @@ 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 will be kept, if at least one of them returns false, it will be
skipped. skipped.
*/ */
func RunFilterHooks(c cfg.Config, line string) (bool, error) { func RunFilterHooks(conf cfg.Config, line string) (bool, error) {
for _, hook := range Hooks["filter"] { for _, hook := range Hooks["filter"] {
var result bool var result bool
c.Lisp.Clear() conf.Lisp.Clear()
res, err := c.Lisp.EvalString(fmt.Sprintf("(%s `%s`)", hook.Name(), line)) res, err := conf.Lisp.EvalString(fmt.Sprintf("(%s `%s`)", hook.Name(), line))
if err != nil { if err != nil {
return false, err return false, err
} }
@@ -197,7 +204,7 @@ The somewhat complicated code is being caused by the fact, that we
need to convert our internal structure to a lisp variable and vice need to convert our internal structure to a lisp variable and vice
versa afterwards. versa afterwards.
*/ */
func RunProcessHooks(c cfg.Config, data Tabdata) (Tabdata, bool, error) { func RunProcessHooks(conf cfg.Config, data Tabdata) (Tabdata, bool, error) {
var userdata Tabdata var userdata Tabdata
lisplist := []zygo.Sexp{} lisplist := []zygo.Sexp{}
@@ -224,14 +231,14 @@ func RunProcessHooks(c cfg.Config, data Tabdata) (Tabdata, bool, error) {
} }
// we need to add it to the env so that the function can use the struct directly // we need to add it to the env so that the function can use the struct directly
c.Lisp.AddGlobal("data", &zygo.SexpArray{Val: lisplist, Env: c.Lisp}) conf.Lisp.AddGlobal("data", &zygo.SexpArray{Val: lisplist, Env: conf.Lisp})
// execute the actual hook // execute the actual hook
hook := Hooks["process"][0] hook := Hooks["process"][0]
var result bool var result bool
c.Lisp.Clear() conf.Lisp.Clear()
res, err := c.Lisp.EvalString(fmt.Sprintf("(%s data)", hook.Name())) res, err := conf.Lisp.EvalString(fmt.Sprintf("(%s data)", hook.Name()))
if err != nil { if err != nil {
return userdata, false, err return userdata, false, err
} }
@@ -243,17 +250,17 @@ func RunProcessHooks(c cfg.Config, data Tabdata) (Tabdata, bool, error) {
case *zygo.SexpBool: case *zygo.SexpBool:
result = th.Val result = th.Val
default: default:
return userdata, false, errors.New("Expect (bool, array(hash)) as return value!") return userdata, false, errors.New("xpect (bool, array(hash)) as return value")
} }
switch tt := t.Tail.(type) { switch tt := t.Tail.(type) {
case *zygo.SexpArray: case *zygo.SexpArray:
lisplist = tt.Val lisplist = tt.Val
default: default:
return userdata, false, errors.New("Expect (bool, array(hash)) as return value!") return userdata, false, errors.New("expect (bool, array(hash)) as return value ")
} }
default: default:
return userdata, false, errors.New("filter hook shall return array of hashes!") return userdata, false, errors.New("filter hook shall return array of hashes ")
} }
if !result { if !result {
@@ -268,7 +275,7 @@ func RunProcessHooks(c cfg.Config, data Tabdata) (Tabdata, bool, error) {
switch hash := item.(type) { switch hash := item.(type) {
case *zygo.SexpHash: case *zygo.SexpHash:
for _, header := range data.headers { for _, header := range data.headers {
entry, err := hash.HashGetDefault(c.Lisp, &zygo.SexpStr{S: header}, &zygo.SexpStr{S: ""}) entry, err := hash.HashGetDefault(conf.Lisp, &zygo.SexpStr{S: header}, &zygo.SexpStr{S: ""})
if err != nil { if err != nil {
return userdata, false, err return userdata, false, err
} }
@@ -277,11 +284,11 @@ func RunProcessHooks(c cfg.Config, data Tabdata) (Tabdata, bool, error) {
case *zygo.SexpStr: case *zygo.SexpStr:
row = append(row, t.S) row = append(row, t.S)
default: default:
return userdata, false, errors.New("Hash values should be string!") return userdata, false, errors.New("hsh values should be string ")
} }
} }
default: default:
return userdata, false, errors.New("Returned array should contain hashes!") return userdata, false, errors.New("rturned array should contain hashes ")
} }
userdata.entries = append(userdata.entries, row) userdata.entries = append(userdata.entries, row)

View File

@@ -20,7 +20,6 @@ package lib
import ( import (
"bufio" "bufio"
"encoding/csv" "encoding/csv"
"errors"
"fmt" "fmt"
"io" "io"
"regexp" "regexp"
@@ -33,22 +32,22 @@ import (
/* /*
Parser switch Parser switch
*/ */
func Parse(c cfg.Config, input io.Reader) (Tabdata, error) { func Parse(conf cfg.Config, input io.Reader) (Tabdata, error) {
if len(c.Separator) == 1 { if len(conf.Separator) == 1 {
return parseCSV(c, input) return parseCSV(conf, input)
} }
return parseTabular(c, input) return parseTabular(conf, input)
} }
/* /*
Parse CSV input. Parse CSV input.
*/ */
func parseCSV(c cfg.Config, input io.Reader) (Tabdata, error) { func parseCSV(conf cfg.Config, input io.Reader) (Tabdata, error) {
var content io.Reader = input var content = input
data := Tabdata{} data := Tabdata{}
if len(c.Pattern) > 0 { if len(conf.Pattern) > 0 {
scanner := bufio.NewScanner(input) scanner := bufio.NewScanner(input)
lines := []string{} lines := []string{}
hadFirst := false hadFirst := false
@@ -56,7 +55,7 @@ func parseCSV(c cfg.Config, input io.Reader) (Tabdata, error) {
line := strings.TrimSpace(scanner.Text()) line := strings.TrimSpace(scanner.Text())
if hadFirst { if hadFirst {
// don't match 1st line, it's the header // don't match 1st line, it's the header
if c.Pattern != "" && matchPattern(c, line) == c.InvertMatch { if conf.Pattern != "" && matchPattern(conf, line) == conf.InvertMatch {
// by default -v is false, so if a line does NOT // by default -v is false, so if a line does NOT
// match the pattern, we will ignore it. However, // match the pattern, we will ignore it. However,
// if the user specified -v, the matching is inverted, // if the user specified -v, the matching is inverted,
@@ -65,9 +64,9 @@ func parseCSV(c cfg.Config, input io.Reader) (Tabdata, error) {
} }
// apply user defined lisp filters, if any // apply user defined lisp filters, if any
accept, err := RunFilterHooks(c, line) accept, err := RunFilterHooks(conf, line)
if err != nil { if err != nil {
return data, errors.Unwrap(fmt.Errorf("Failed to apply filter hook: %w", err)) return data, fmt.Errorf("failed to apply filter hook: %w", err)
} }
if !accept { if !accept {
@@ -83,11 +82,11 @@ func parseCSV(c cfg.Config, input io.Reader) (Tabdata, error) {
} }
csvreader := csv.NewReader(content) csvreader := csv.NewReader(content)
csvreader.Comma = rune(c.Separator[0]) csvreader.Comma = rune(conf.Separator[0])
records, err := csvreader.ReadAll() records, err := csvreader.ReadAll()
if err != nil { if err != nil {
return data, errors.Unwrap(fmt.Errorf("Could not parse CSV input: %w", err)) return data, fmt.Errorf("could not parse CSV input: %w", err)
} }
if len(records) >= 1 { if len(records) >= 1 {
@@ -108,9 +107,9 @@ func parseCSV(c cfg.Config, input io.Reader) (Tabdata, error) {
} }
// apply user defined lisp process hooks, if any // apply user defined lisp process hooks, if any
userdata, changed, err := RunProcessHooks(c, data) userdata, changed, err := RunProcessHooks(conf, data)
if err != nil { if err != nil {
return data, errors.Unwrap(fmt.Errorf("Failed to apply filter hook: %w", err)) return data, fmt.Errorf("failed to apply filter hook: %w", err)
} }
if changed { if changed {
data = userdata data = userdata
@@ -122,13 +121,13 @@ func parseCSV(c cfg.Config, input io.Reader) (Tabdata, error) {
/* /*
Parse tabular input. Parse tabular input.
*/ */
func parseTabular(c cfg.Config, input io.Reader) (Tabdata, error) { func parseTabular(conf cfg.Config, input io.Reader) (Tabdata, error) {
data := Tabdata{} data := Tabdata{}
var scanner *bufio.Scanner var scanner *bufio.Scanner
hadFirst := false hadFirst := false
separate := regexp.MustCompile(c.Separator) separate := regexp.MustCompile(conf.Separator)
scanner = bufio.NewScanner(input) scanner = bufio.NewScanner(input)
@@ -163,7 +162,7 @@ func parseTabular(c cfg.Config, input io.Reader) (Tabdata, error) {
} }
} else { } else {
// data processing // data processing
if c.Pattern != "" && matchPattern(c, line) == c.InvertMatch { if conf.Pattern != "" && matchPattern(conf, line) == conf.InvertMatch {
// by default -v is false, so if a line does NOT // by default -v is false, so if a line does NOT
// match the pattern, we will ignore it. However, // match the pattern, we will ignore it. However,
// if the user specified -v, the matching is inverted, // if the user specified -v, the matching is inverted,
@@ -172,9 +171,9 @@ func parseTabular(c cfg.Config, input io.Reader) (Tabdata, error) {
} }
// apply user defined lisp filters, if any // apply user defined lisp filters, if any
accept, err := RunFilterHooks(c, line) accept, err := RunFilterHooks(conf, line)
if err != nil { if err != nil {
return data, errors.Unwrap(fmt.Errorf("Failed to apply filter hook: %w", err)) return data, fmt.Errorf("failed to apply filter hook: %w", err)
} }
if !accept { if !accept {
@@ -204,28 +203,28 @@ func parseTabular(c cfg.Config, input io.Reader) (Tabdata, error) {
} }
if scanner.Err() != nil { if scanner.Err() != nil {
return data, errors.Unwrap(fmt.Errorf("Failed to read from io.Reader: %w", scanner.Err())) return data, fmt.Errorf("failed to read from io.Reader: %w", scanner.Err())
} }
// filter by field filters, if any // filter by field filters, if any
filtereddata, changed, err := FilterByFields(c, data) filtereddata, changed, err := FilterByFields(conf, data)
if err != nil { if err != nil {
return data, fmt.Errorf("Failed to filter fields: %w", err) return data, fmt.Errorf("failed to filter fields: %w", err)
} }
if changed { if changed {
data = filtereddata data = filtereddata
} }
// apply user defined lisp process hooks, if any // apply user defined lisp process hooks, if any
userdata, changed, err := RunProcessHooks(c, data) userdata, changed, err := RunProcessHooks(conf, data)
if err != nil { if err != nil {
return data, errors.Unwrap(fmt.Errorf("Failed to apply filter hook: %w", err)) return data, fmt.Errorf("failed to apply filter hook: %w", err)
} }
if changed { if changed {
data = userdata data = userdata
} }
if c.Debug { if conf.Debug {
repr.Print(data) repr.Print(data)
} }

View File

@@ -109,13 +109,13 @@ func TestParserPatternmatching(t *testing.T) {
testname := fmt.Sprintf("parse-%s-with-pattern-%s-inverted-%t", testname := fmt.Sprintf("parse-%s-with-pattern-%s-inverted-%t",
in.name, tt.pattern, tt.invert) in.name, tt.pattern, tt.invert)
t.Run(testname, func(t *testing.T) { t.Run(testname, func(t *testing.T) {
c := cfg.Config{InvertMatch: tt.invert, Pattern: tt.pattern, conf := cfg.Config{InvertMatch: tt.invert, Pattern: tt.pattern,
Separator: in.separator} Separator: in.separator}
_ = c.PreparePattern(tt.pattern) _ = conf.PreparePattern(tt.pattern)
readFd := strings.NewReader(strings.TrimSpace(in.text)) readFd := strings.NewReader(strings.TrimSpace(in.text))
gotdata, err := Parse(c, readFd) gotdata, err := Parse(conf, readFd)
if err != nil { if err != nil {
if !tt.want { if !tt.want {

View File

@@ -32,51 +32,51 @@ import (
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
func printData(w io.Writer, c cfg.Config, data *Tabdata) { func printData(writer io.Writer, conf cfg.Config, data *Tabdata) {
// some output preparations: // some output preparations:
// add numbers to headers and remove this we're not interested in // add numbers to headers and remove this we're not interested in
numberizeAndReduceHeaders(c, data) numberizeAndReduceHeaders(conf, data)
// remove unwanted columns, if any // remove unwanted columns, if any
reduceColumns(c, data) reduceColumns(conf, data)
// sort the data // sort the data
sortTable(c, data) sortTable(conf, data)
switch c.OutputMode { switch conf.OutputMode {
case cfg.Extended: case cfg.Extended:
printExtendedData(w, c, data) printExtendedData(writer, conf, data)
case cfg.Ascii: case cfg.ASCII:
printAsciiData(w, c, data) printAsciiData(writer, conf, data)
case cfg.Orgtbl: case cfg.Orgtbl:
printOrgmodeData(w, c, data) printOrgmodeData(writer, conf, data)
case cfg.Markdown: case cfg.Markdown:
printMarkdownData(w, c, data) printMarkdownData(writer, conf, data)
case cfg.Shell: case cfg.Shell:
printShellData(w, c, data) printShellData(writer, data)
case cfg.Yaml: case cfg.Yaml:
printYamlData(w, c, data) printYamlData(writer, data)
case cfg.CSV: case cfg.CSV:
printCSVData(w, c, data) printCSVData(writer, data)
default: default:
printAsciiData(w, c, data) printAsciiData(writer, conf, data)
} }
} }
func output(w io.Writer, str string) { func output(writer io.Writer, str string) {
fmt.Fprint(w, str) fmt.Fprint(writer, str)
} }
/* /*
Emacs org-mode compatible table (also orgtbl-mode) Emacs org-mode compatible table (also orgtbl-mode)
*/ */
func printOrgmodeData(w io.Writer, c cfg.Config, data *Tabdata) { func printOrgmodeData(writer io.Writer, conf cfg.Config, data *Tabdata) {
tableString := &strings.Builder{} tableString := &strings.Builder{}
table := tablewriter.NewWriter(tableString) table := tablewriter.NewWriter(tableString)
if !c.NoHeaders { if !conf.NoHeaders {
table.SetHeader(data.headers) table.SetHeader(data.headers)
} }
@@ -100,8 +100,8 @@ func printOrgmodeData(w io.Writer, c cfg.Config, data *Tabdata) {
leftR := regexp.MustCompile(`(?m)^\\+`) leftR := regexp.MustCompile(`(?m)^\\+`)
rightR := regexp.MustCompile(`\\+(?m)$`) rightR := regexp.MustCompile(`\\+(?m)$`)
output(w, color.Sprint( output(writer, color.Sprint(
colorizeData(c, colorizeData(conf,
rightR.ReplaceAllString( rightR.ReplaceAllString(
leftR.ReplaceAllString(tableString.String(), "|"), "|")))) leftR.ReplaceAllString(tableString.String(), "|"), "|"))))
} }
@@ -109,11 +109,11 @@ func printOrgmodeData(w io.Writer, c cfg.Config, data *Tabdata) {
/* /*
Markdown table Markdown table
*/ */
func printMarkdownData(w io.Writer, c cfg.Config, data *Tabdata) { func printMarkdownData(writer io.Writer, conf cfg.Config, data *Tabdata) {
tableString := &strings.Builder{} tableString := &strings.Builder{}
table := tablewriter.NewWriter(tableString) table := tablewriter.NewWriter(tableString)
if !c.NoHeaders { if !conf.NoHeaders {
table.SetHeader(data.headers) table.SetHeader(data.headers)
} }
@@ -125,17 +125,17 @@ func printMarkdownData(w io.Writer, c cfg.Config, data *Tabdata) {
table.SetCenterSeparator("|") table.SetCenterSeparator("|")
table.Render() table.Render()
output(w, color.Sprint(colorizeData(c, tableString.String()))) output(writer, color.Sprint(colorizeData(conf, tableString.String())))
} }
/* /*
Simple ASCII table without any borders etc, just like the input we expect Simple ASCII table without any borders etc, just like the input we expect
*/ */
func printAsciiData(w io.Writer, c cfg.Config, data *Tabdata) { func printAsciiData(writer io.Writer, conf cfg.Config, data *Tabdata) {
tableString := &strings.Builder{} tableString := &strings.Builder{}
table := tablewriter.NewWriter(tableString) table := tablewriter.NewWriter(tableString)
if !c.NoHeaders { if !conf.NoHeaders {
table.SetHeader(data.headers) table.SetHeader(data.headers)
} }
table.AppendBulk(data.entries) table.AppendBulk(data.entries)
@@ -151,7 +151,7 @@ func printAsciiData(w io.Writer, c cfg.Config, data *Tabdata) {
table.SetBorder(false) table.SetBorder(false)
table.SetNoWhiteSpace(true) table.SetNoWhiteSpace(true)
if !c.UseHighlight { if !conf.UseHighlight {
// the tabs destroy the highlighting // the tabs destroy the highlighting
table.SetTablePadding("\t") // pad with tabs table.SetTablePadding("\t") // pad with tabs
} else { } else {
@@ -159,13 +159,13 @@ func printAsciiData(w io.Writer, c cfg.Config, data *Tabdata) {
} }
table.Render() table.Render()
output(w, color.Sprint(colorizeData(c, tableString.String()))) output(writer, color.Sprint(colorizeData(conf, tableString.String())))
} }
/* /*
We simulate the \x command of psql (the PostgreSQL client) We simulate the \x command of psql (the PostgreSQL client)
*/ */
func printExtendedData(w io.Writer, c cfg.Config, data *Tabdata) { func printExtendedData(writer io.Writer, conf cfg.Config, data *Tabdata) {
// needed for data output // needed for data output
format := fmt.Sprintf("%%%ds: %%s\n", data.maxwidthHeader) format := fmt.Sprintf("%%%ds: %%s\n", data.maxwidthHeader)
out := "" out := ""
@@ -179,67 +179,67 @@ func printExtendedData(w io.Writer, c cfg.Config, data *Tabdata) {
} }
} }
output(w, colorizeData(c, out)) output(writer, colorizeData(conf, out))
} }
/* /*
Shell output, ready to be eval'd. Just like FreeBSD stat(1) Shell output, ready to be eval'd. Just like FreeBSD stat(1)
*/ */
func printShellData(w io.Writer, c cfg.Config, data *Tabdata) { func printShellData(writer io.Writer, data *Tabdata) {
out := "" out := ""
if len(data.entries) > 0 { if len(data.entries) > 0 {
for _, entry := range data.entries { for _, entry := range data.entries {
shentries := []string{} shentries := []string{}
for i, value := range entry { for idx, value := range entry {
shentries = append(shentries, fmt.Sprintf("%s=\"%s\"", shentries = append(shentries, fmt.Sprintf("%s=\"%s\"",
data.headers[i], value)) data.headers[idx], value))
} }
out += fmt.Sprint(strings.Join(shentries, " ")) + "\n" out += fmt.Sprint(strings.Join(shentries, " ")) + "\n"
} }
} }
// no colorization here // no colorization here
output(w, out) output(writer, out)
} }
func printYamlData(w io.Writer, c cfg.Config, data *Tabdata) { func printYamlData(writer io.Writer, data *Tabdata) {
type D struct { type Data struct {
Entries []map[string]interface{} `yaml:"entries"` Entries []map[string]interface{} `yaml:"entries"`
} }
d := D{} yamlout := Data{}
for _, entry := range data.entries { for _, entry := range data.entries {
ml := map[string]interface{}{} yamldata := map[string]interface{}{}
for i, entry := range entry { for idx, entry := range entry {
style := yaml.TaggedStyle style := yaml.TaggedStyle
_, err := strconv.Atoi(entry) _, err := strconv.Atoi(entry)
if err != nil { if err != nil {
style = yaml.DoubleQuotedStyle style = yaml.DoubleQuotedStyle
} }
ml[strings.ToLower(data.headers[i])] = yamldata[strings.ToLower(data.headers[idx])] =
&yaml.Node{ &yaml.Node{
Kind: yaml.ScalarNode, Kind: yaml.ScalarNode,
Style: style, Style: style,
Value: entry} Value: entry}
} }
d.Entries = append(d.Entries, ml) yamlout.Entries = append(yamlout.Entries, yamldata)
} }
yamlstr, err := yaml.Marshal(&d) yamlstr, err := yaml.Marshal(&yamlout)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
output(w, string(yamlstr)) output(writer, string(yamlstr))
} }
func printCSVData(w io.Writer, c cfg.Config, data *Tabdata) { func printCSVData(writer io.Writer, data *Tabdata) {
csvout := csv.NewWriter(w) csvout := csv.NewWriter(writer)
if err := csvout.Write(data.headers); err != nil { if err := csvout.Write(data.headers); err != nil {
log.Fatalln("error writing record to csv:", err) log.Fatalln("error writing record to csv:", err)

View File

@@ -20,10 +20,10 @@ package lib
import ( import (
"bytes" "bytes"
"fmt" "fmt"
//"github.com/alecthomas/repr"
"github.com/tlinden/tablizer/cfg"
"strings" "strings"
"testing" "testing"
"github.com/tlinden/tablizer/cfg"
) )
func newData() Tabdata { func newData() Tabdata {
@@ -73,7 +73,7 @@ var tests = []struct {
}{ }{
// --------------------- Default settings mode tests `` // --------------------- Default settings mode tests ``
{ {
mode: cfg.Ascii, mode: cfg.ASCII,
name: "default", name: "default",
expect: ` expect: `
NAME(1) DURATION(2) COUNT(3) WHEN(4) NAME(1) DURATION(2) COUNT(3) WHEN(4)

View File

@@ -18,21 +18,22 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
package lib package lib
import ( import (
"github.com/araddon/dateparse"
"github.com/tlinden/tablizer/cfg"
"regexp" "regexp"
"sort" "sort"
"strconv" "strconv"
"github.com/araddon/dateparse"
"github.com/tlinden/tablizer/cfg"
) )
func sortTable(c cfg.Config, data *Tabdata) { func sortTable(conf cfg.Config, data *Tabdata) {
if c.SortByColumn <= 0 { if conf.SortByColumn <= 0 {
// no sorting wanted // no sorting wanted
return return
} }
// slightly modified here to match internal array indicies // slightly modified here to match internal array indicies
col := c.SortByColumn col := conf.SortByColumn
col-- // ui starts counting by 1, but use 0 internally col-- // ui starts counting by 1, but use 0 internally
@@ -48,38 +49,38 @@ func sortTable(c cfg.Config, data *Tabdata) {
// actual sorting // actual sorting
sort.SliceStable(data.entries, func(i, j int) bool { sort.SliceStable(data.entries, func(i, j int) bool {
return compare(&c, data.entries[i][col], data.entries[j][col]) return compare(&conf, data.entries[i][col], data.entries[j][col])
}) })
} }
// config is not modified here, but it would be inefficient to copy it every loop // config is not modified here, but it would be inefficient to copy it every loop
func compare(c *cfg.Config, a string, b string) bool { func compare(conf *cfg.Config, left string, right string) bool {
var comp bool var comp bool
switch c.SortMode { switch conf.SortMode {
case "numeric": case "numeric":
left, err := strconv.Atoi(a) left, err := strconv.Atoi(left)
if err != nil { if err != nil {
left = 0 left = 0
} }
right, err := strconv.Atoi(b) right, err := strconv.Atoi(right)
if err != nil { if err != nil {
right = 0 right = 0
} }
comp = left < right comp = left < right
case "duration": case "duration":
left := duration2int(a) left := duration2int(left)
right := duration2int(b) right := duration2int(right)
comp = left < right comp = left < right
case "time": case "time":
left, _ := dateparse.ParseAny(a) left, _ := dateparse.ParseAny(left)
right, _ := dateparse.ParseAny(b) right, _ := dateparse.ParseAny(right)
comp = left.Unix() < right.Unix() comp = left.Unix() < right.Unix()
default: default:
comp = a < b comp = left < right
} }
if c.SortDescending { if conf.SortDescending {
comp = !comp comp = !comp
} }
@@ -87,15 +88,15 @@ func compare(c *cfg.Config, a string, b string) bool {
} }
/* /*
We could use time.ParseDuration(), but this doesn't support days. We could use time.ParseDuration(), but this doesn't support days.
We could also use github.com/xhit/go-str2duration/v2, which does We could also use github.com/xhit/go-str2duration/v2, which does
the job, but it's just another dependency, just for this little the job, but it's just another dependency, just for this little
gem. And we don't need a time.Time value. And int is good enough gem. And we don't need a time.Time value. And int is good enough
for duration comparision. for duration comparison.
Convert a durartion into an integer. Valid time units are "s", Convert a duration into an integer. Valid time units are "s",
"m", "h" and "d". "m", "h" and "d".
*/ */
func duration2int(duration string) int { func duration2int(duration string) int {
re := regexp.MustCompile(`(\d+)([dhms])`) re := regexp.MustCompile(`(\d+)([dhms])`)
@@ -103,16 +104,16 @@ func duration2int(duration string) int {
for _, match := range re.FindAllStringSubmatch(duration, -1) { for _, match := range re.FindAllStringSubmatch(duration, -1) {
if len(match) == 3 { if len(match) == 3 {
v, _ := strconv.Atoi(match[1]) durationvalue, _ := strconv.Atoi(match[1])
switch match[2][0] { switch match[2][0] {
case 'd': case 'd':
seconds += v * 86400 seconds += durationvalue * 86400
case 'h': case 'h':
seconds += v * 3600 seconds += durationvalue * 3600
case 'm': case 'm':
seconds += v * 60 seconds += durationvalue * 60
case 's': case 's':
seconds += v seconds += durationvalue
} }
} }
} }

View File

@@ -154,7 +154,7 @@ tablizer \- Manipulate tabular output of other programs
\& \-H, \-\-no\-headers Disable headers display \& \-H, \-\-no\-headers Disable headers display
\& \-s, \-\-separator string Custom field separator \& \-s, \-\-separator string Custom field separator
\& \-k, \-\-sort\-by int Sort by column (default: 1) \& \-k, \-\-sort\-by int Sort by column (default: 1)
\& \-z, \-\-fuzzy Use fuzzy seach [experimental] \& \-z, \-\-fuzzy Use fuzzy search [experimental]
\& \-F, \-\-filter field=reg Filter given field with regex, can be used multiple times \& \-F, \-\-filter field=reg Filter given field with regex, can be used multiple times
\& \&
\& Output Flags (mutually exclusive): \& Output Flags (mutually exclusive):
@@ -298,7 +298,7 @@ Example for a case insensitive search:
\& kubectl get pods \-A | tablizer "(?i)account" \& kubectl get pods \-A | tablizer "(?i)account"
.Ve .Ve
.PP .PP
You can use the experimental fuzzy seach feature by providing the You can use the experimental fuzzy search feature by providing the
option \fB\-z\fR, in which case the pattern is regarded as a fuzzy search option \fB\-z\fR, in which case the pattern is regarded as a fuzzy search
term, not a regexp. term, not a regexp.
.PP .PP

View File

@@ -15,7 +15,7 @@ tablizer - Manipulate tabular output of other programs
-H, --no-headers Disable headers display -H, --no-headers Disable headers display
-s, --separator string Custom field separator -s, --separator string Custom field separator
-k, --sort-by int Sort by column (default: 1) -k, --sort-by int Sort by column (default: 1)
-z, --fuzzy Use fuzzy seach [experimental] -z, --fuzzy Use fuzzy search [experimental]
-F, --filter field=reg Filter given field with regex, can be used multiple times -F, --filter field=reg Filter given field with regex, can be used multiple times
Output Flags (mutually exclusive): Output Flags (mutually exclusive):
@@ -156,7 +156,7 @@ Example for a case insensitive search:
kubectl get pods -A | tablizer "(?i)account" kubectl get pods -A | tablizer "(?i)account"
You can use the experimental fuzzy seach feature by providing the You can use the experimental fuzzy search feature by providing the
option B<-z>, in which case the pattern is regarded as a fuzzy search option B<-z>, in which case the pattern is regarded as a fuzzy search
term, not a regexp. term, not a regexp.