Compare commits

...

6 Commits

12 changed files with 191 additions and 29 deletions

View File

@@ -26,7 +26,7 @@ GID = 0
BRANCH = $(shell git describe --all | cut -d/ -f2)
COMMIT = $(shell git rev-parse --short=8 HEAD)
BUILD = $(shell date +%Y.%m.%d.%H%M%S)
VERSION:= $(if $(filter $(BRANCH), development),$(version)-$(BRANCH)-$(COMMIT)-$(BUILD))
VERSION:= $(if $(filter $(BRANCH), development),$(version)-$(BRANCH)-$(COMMIT)-$(BUILD),$(version))
all: $(tool).1 cmd/$(tool).go buildlocal

View File

@@ -84,6 +84,7 @@ func Execute() {
func init() {
rootCmd.PersistentFlags().BoolVarP(&lib.Debug, "debug", "d", false, "Enable debugging")
rootCmd.PersistentFlags().BoolVarP(&lib.NoNumbering, "no-numbering", "n", false, "Disable header numbering")
rootCmd.PersistentFlags().BoolVarP(&lib.NoColor, "no-color", "N", false, "Disable pattern highlighting")
rootCmd.PersistentFlags().BoolVarP(&lib.ShowVersion, "version", "V", false, "Print program version")
rootCmd.PersistentFlags().BoolVarP(&lib.InvertMatch, "invert-match", "v", false, "select non-matching rows")
rootCmd.PersistentFlags().BoolVarP(&ShowManual, "man", "m", false, "Display manual page")

View File

@@ -14,6 +14,7 @@ SYNOPSIS
-v, --invert-match select non-matching rows
-m, --man Display manual page
-n, --no-numbering Disable header numbering
-N, --no-color Disable pattern highlighting
-o, --output string Output mode - one of: orgtbl, markdown, extended, ascii(default)
-X, --extended Enable extended output
-M, --markdown Enable markdown table output
@@ -24,10 +25,10 @@ SYNOPSIS
DESCRIPTION
Many programs generate tabular output. But sometimes you need to
post-process these tables, you may need to remove one or more columns or
you may want to filter for some pattern or you may need the output in
another program and need to parse it somehow. Standard unix tools such
as awk(1), grep(1) or column(1) may help, but sometimes it's a tedious
business.
you may want to filter for some pattern (See PATTERNS) or you may need
the output in another program and need to parse it somehow. Standard
unix tools such as awk(1), grep(1) or column(1) may help, but sometimes
it's a tedious business.
Let's take the output of the tool kubectl. It contains cells with
withespace and they do not separate columns by TAB characters. This is
@@ -70,9 +71,36 @@ DESCRIPTION
The numbering can be suppressed by using the -n option.
By default, if a pattern has been speficied, matches will be
highlighted. You can disable this behavior with the -N option.
Finally the -d option enables debugging output which is mostly usefull
for the developer.
PATTERNS
You can reduce the rows being displayed by using a regular expression
pattern. The regexp is PCRE compatible, refer to the syntax cheat sheet
here: <https://github.com/google/re2/wiki/Syntax>. If you want to read a
more comprehensive documentation about the topic and have perl installed
you can read it with:
perldoc perlre
Or read it online: <https://perldoc.perl.org/perlre>.
A note on modifiers: the regexp engine used in tablizer uses another
modifier syntax:
(?MODIFIER)
The most important modifiers are:
"i" ignore case "m" multiline mode "s" single line mode
Example for a case insensitve search:
kubectl get pods -A | tablizer "(?i)account"
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.

4
go.mod
View File

@@ -4,13 +4,15 @@ go 1.18
require (
github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897
github.com/gookit/color v1.5.2
github.com/olekukonko/tablewriter v0.0.5
github.com/spf13/cobra v1.5.0
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778
)
require (
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/mattn/go-runewidth v0.0.9 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/stretchr/testify v1.8.0 // indirect
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44 // indirect
)

6
go.sum
View File

@@ -4,6 +4,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t
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/gookit/color v1.5.2 h1:uLnfXcaFjlrDnQDT+NCBcfhrXqYTx/rcCa6xn01Y8yI=
github.com/gookit/color v1.5.2/go.mod h1:w8h4bGiHeeBpvQVePTutdbERIUf3oJE5lZ8HM0UgXyg=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
@@ -22,6 +24,10 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS
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/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=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44 h1:Bli41pIlzTzf3KEY06n+xnzK/BESIg2ze4Pgfh/aI8c=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -17,6 +17,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
package lib
import (
"github.com/gookit/color"
//"github.com/xo/terminfo"
)
var (
// command line flags
Debug bool
@@ -33,12 +38,37 @@ var (
OutflagShell bool
OutputMode string
InvertMatch bool
Pattern string
/*
FIXME: make configurable somehow, config file or ENV
see https://github.com/gookit/color will be set by
io.ProcessFiles() according to currently supported
color mode.
*/
MatchFG string
MatchBG string
NoColor bool
// colors to be used per supported color mode
Colors = map[color.Level]map[string]string{
color.Level16: map[string]string{
"bg": "green", "fg": "black",
},
color.Level256: map[string]string{
"bg": "lightGreen", "fg": "black",
},
color.LevelRgb: map[string]string{
// FIXME: maybe use something nicer
"bg": "lightGreen", "fg": "black",
},
}
// used for validation
validOutputmodes = "(orgtbl|markdown|extended|ascii)"
// main program version
Version = "v1.0.6"
Version = "v1.0.7"
// generated version string, used by -v contains lib.Version on
// main branch, and lib.Version-$branch-$lastcommit-$date on

View File

@@ -20,6 +20,8 @@ package lib
import (
"errors"
"fmt"
"github.com/gookit/color"
"os"
"regexp"
"strconv"
"strings"
@@ -51,7 +53,10 @@ func PrepareColumns() error {
func numberizeHeaders(data *Tabdata) {
// prepare headers: add numbers to headers
numberedHeaders := []string{}
maxwidth := 0 // start from scratch, so we only look at displayed column widths
for i, head := range data.headers {
headlen := 0
if len(Columns) > 0 {
// -c specified
if !contains(UseColumns, i+1) {
@@ -61,11 +66,21 @@ func numberizeHeaders(data *Tabdata) {
}
if NoNumbering {
numberedHeaders = append(numberedHeaders, head)
headlen = len(head)
} else {
numberedHeaders = append(numberedHeaders, fmt.Sprintf("%s(%d)", head, i+1))
numhead := fmt.Sprintf("%s(%d)", head, i+1)
headlen = len(numhead)
numberedHeaders = append(numberedHeaders, numhead)
}
if headlen > maxwidth {
maxwidth = headlen
}
}
data.headers = numberedHeaders
if data.maxwidthHeader != maxwidth && maxwidth > 0 {
data.maxwidthHeader = maxwidth
}
}
func reduceColumns(data *Tabdata) {
@@ -130,3 +145,21 @@ func trimRow(row []string) []string {
return fixedrow
}
func colorizeData(output string) string {
if len(Pattern) > 0 && !NoColor && color.IsConsole(os.Stdout) {
r := regexp.MustCompile("(" + Pattern + ")")
return r.ReplaceAllString(output, "<bg="+MatchBG+";fg="+MatchFG+">$1</>")
} else {
return output
}
}
func isTerminal(f *os.File) bool {
o, _ := f.Stat()
if (o.Mode() & os.ModeCharDevice) == os.ModeCharDevice {
return true
} else {
return false
}
}

View File

@@ -19,6 +19,7 @@ package lib
import (
"errors"
"github.com/gookit/color"
"io"
"os"
)
@@ -26,6 +27,14 @@ import (
func ProcessFiles(args []string) error {
fds, pattern, err := determineIO(args)
if !isTerminal(os.Stdout) {
color.Disable()
} else {
level := color.TermColorLevel()
MatchFG = Colors[level]["fg"]
MatchBG = Colors[level]["bg"]
}
if err != nil {
return err
}
@@ -52,6 +61,7 @@ func determineIO(args []string) ([]io.Reader, string, error) {
// first one is not a file, consider it as regexp and
// shift arg list
pattern = args[0]
Pattern = args[0] // FIXME
args = args[1:]
}

View File

@@ -19,8 +19,8 @@ package lib
import (
"fmt"
"github.com/gookit/color"
"github.com/olekukonko/tablewriter"
"os"
"regexp"
"strings"
)
@@ -76,14 +76,18 @@ func printOrgmodeData(data *Tabdata) {
leftR := regexp.MustCompile("(?m)^\\+")
rightR := regexp.MustCompile("\\+(?m)$")
fmt.Print(rightR.ReplaceAllString(leftR.ReplaceAllString(tableString.String(), "|"), "|"))
color.Print(
colorizeData(
rightR.ReplaceAllString(
leftR.ReplaceAllString(tableString.String(), "|"), "|")))
}
/*
Markdown table
*/
func printMarkdownData(data *Tabdata) {
table := tablewriter.NewWriter(os.Stdout)
tableString := &strings.Builder{}
table := tablewriter.NewWriter(tableString)
table.SetHeader(data.headers)
@@ -95,13 +99,15 @@ func printMarkdownData(data *Tabdata) {
table.SetCenterSeparator("|")
table.Render()
color.Print(colorizeData(tableString.String()))
}
/*
Simple ASCII table without any borders etc, just like the input we expect
*/
func printAsciiData(data *Tabdata) {
table := tablewriter.NewWriter(os.Stdout)
tableString := &strings.Builder{}
table := tablewriter.NewWriter(tableString)
table.SetHeader(data.headers)
table.AppendBulk(data.entries)
@@ -123,6 +129,7 @@ func printAsciiData(data *Tabdata) {
table.SetNoWhiteSpace(true)
table.Render()
color.Print(colorizeData(tableString.String()))
}
/*
@@ -130,22 +137,14 @@ func printAsciiData(data *Tabdata) {
*/
func printExtendedData(data *Tabdata) {
// needed for data output
format := fmt.Sprintf("%%%ds: %%s\n", data.maxwidthHeader) // FIXME: re-calculate if -c has been set
format := fmt.Sprintf("%%%ds: %%s\n", data.maxwidthHeader)
if len(data.entries) > 0 {
var idx int
for _, entry := range data.entries {
idx = 0
for i, value := range entry {
if len(Columns) > 0 {
if !contains(UseColumns, i+1) {
continue
}
}
fmt.Printf(format, data.headers[idx], value)
idx++
color.Printf(format, data.headers[i], value)
}
fmt.Println()
}
}

View File

@@ -19,6 +19,7 @@ package lib
import (
"fmt"
"github.com/gookit/color"
"os"
"strings"
"testing"
@@ -62,8 +63,17 @@ asd igig cxxxncnc
| 19191 | EDD 1 | X |`,
"shell": `ONE="asd" TWO="igig" THREE="cxxxncnc"
ONE="19191" TWO="EDD 1" THREE="X"`,
"extended": `ONE(1): asd
TWO(2): igig
THREE(3): cxxxncnc
ONE(1): 19191
TWO(2): EDD 1
THREE(3): X`,
}
NoColor = true
r, w, err := os.Pipe()
if err != nil {
t.Fatal(err)
@@ -71,6 +81,9 @@ ONE="19191" TWO="EDD 1" THREE="X"`,
origStdout := os.Stdout
os.Stdout = w
// we need to tell the color mode the io.Writer, even if we don't usw colorization
color.SetOutput(w)
for mode, expect := range expects {
testname := fmt.Sprintf("print-%s", mode)
t.Run(testname, func(t *testing.T) {

View File

@@ -133,7 +133,7 @@
.\" ========================================================================
.\"
.IX Title "TABLIZER 1"
.TH TABLIZER 1 "2022-10-05" "1" "User Commands"
.TH TABLIZER 1 "2022-10-10" "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
@@ -153,6 +153,7 @@ tablizer \- Manipulate tabular output of other programs
\& \-v, \-\-invert\-match select non\-matching rows
\& \-m, \-\-man Display manual page
\& \-n, \-\-no\-numbering Disable header numbering
\& \-N, \-\-no\-color Disable pattern highlighting
\& \-o, \-\-output string Output mode \- one of: orgtbl, markdown, extended, ascii(default)
\& \-X, \-\-extended Enable extended output
\& \-M, \-\-markdown Enable markdown table output
@@ -162,12 +163,12 @@ tablizer \- Manipulate tabular output of other programs
.Ve
.SH "DESCRIPTION"
.IX Header "DESCRIPTION"
Many programs generate tabular output. But sometimes you need to
Many programs generate tabular output. But sometimes you need to
post-process these tables, you may need to remove one or more columns
or you may want to filter for some pattern or you may need the output
in another program and need to parse it somehow. Standard unix tools
such as \fBawk\fR\|(1), \fBgrep\fR\|(1) or \fBcolumn\fR\|(1) may help, but sometimes it's a
tedious business.
or you may want to filter for some pattern (See \s-1PATTERNS\s0) or you
may need the output in another program and need to parse it somehow.
Standard unix tools such as \fBawk\fR\|(1), \fBgrep\fR\|(1) or \fBcolumn\fR\|(1) may help, but
sometimes it's a tedious business.
.PP
Let's take the output of the tool kubectl. It contains cells with
withespace and they do not separate columns by \s-1TAB\s0 characters. This is
@@ -218,8 +219,43 @@ the original order.
.PP
The numbering can be suppressed by using the \fB\-n\fR option.
.PP
By default, if a \fBpattern\fR has been speficied, matches will be
highlighted. You can disable this behavior with the \fB\-N\fR option.
.PP
Finally the \fB\-d\fR option enables debugging output which is mostly
usefull for the developer.
.SS "\s-1PATTERNS\s0"
.IX Subsection "PATTERNS"
You can reduce the rows being displayed by using a regular expression
pattern. The regexp is \s-1PCRE\s0 compatible, refer to the syntax cheat
sheet here: <https://github.com/google/re2/wiki/Syntax>. If you want
to read a more comprehensive documentation about the topic and have
perl installed you can read it with:
.PP
.Vb 1
\& perldoc perlre
.Ve
.PP
Or read it online: <https://perldoc.perl.org/perlre>.
.PP
A note on modifiers: the regexp engine used in tablizer uses another
modifier syntax:
.PP
.Vb 1
\& (?MODIFIER)
.Ve
.PP
The most important modifiers are:
.PP
\&\f(CW\*(C`i\*(C'\fR ignore case
\&\f(CW\*(C`m\*(C'\fR multiline mode
\&\f(CW\*(C`s\*(C'\fR single line mode
.PP
Example for a case insensitve search:
.PP
.Vb 1
\& kubectl get pods \-A | tablizer "(?i)account"
.Ve
.SS "\s-1OUTPUT MODES\s0"
.IX Subsection "OUTPUT MODES"
There might be cases when the tabular output of a program is way too

View File

@@ -14,6 +14,7 @@ tablizer - Manipulate tabular output of other programs
-v, --invert-match select non-matching rows
-m, --man Display manual page
-n, --no-numbering Disable header numbering
-N, --no-color Disable pattern highlighting
-o, --output string Output mode - one of: orgtbl, markdown, extended, ascii(default)
-X, --extended Enable extended output
-M, --markdown Enable markdown table output
@@ -74,6 +75,9 @@ the original order.
The numbering can be suppressed by using the B<-n> option.
By default, if a B<pattern> has been speficied, matches will be
highlighted. You can disable this behavior with the B<-N> option.
Finally the B<-d> option enables debugging output which is mostly
usefull for the developer.