-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Thomas von Dein
committed
Sep 28, 2022
1 parent
02a64a5
commit 10f4a81
Showing
9 changed files
with
530 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
# | ||
# no need to modify anything below | ||
tool = tablizer | ||
version = $(shell egrep "^var version = " cmd/root.go | cut -d'=' -f2 | cut -d'"' -f 2) | ||
archs = android darwin freebsd linux netbsd openbsd windows | ||
|
||
all: | ||
@echo "Type 'make install' to install $(tool)" | ||
|
||
install: | ||
install -m 755 -d $(bindir) | ||
install -m 755 -d $(linkdir) | ||
install -m 755 $(tool) $(bindir)/$(tool)-$(version) | ||
ln -sf $(bindir)/$(tool)-$(version) $(linkdir)/$(tool) | ||
|
||
release: | ||
mkdir -p releases | ||
$(foreach arch,$(archs), GOOS=$(arch) GOARCH=amd64 go build -x -o releases/$(tool)-$(arch)-amd64-$(version); sha256sum releases/$(tool)-$(arch)-amd64-$(version) | cut -d' ' -f1 > releases/$(tool)-$(arch)-amd64-$(version).sha256sum;) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,93 @@ | ||
# tablizer | ||
Manipulate tabular output of other programs | ||
## tablizer - Manipulate tabular output of other programs | ||
|
||
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. | ||
|
||
Let's take this output: | ||
``` | ||
% kubectl get pods -o wide | ||
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 | ||
``` | ||
|
||
But you're only interested in the NAME and STATUS columns. Here's how | ||
to do this with tablizer: | ||
|
||
``` | ||
% kubectl get pods | ./tablizer | ||
NAME(1) READY(2) STATUS(3) RESTARTS(4) AGE(5) | ||
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 | ||
% kubectl get pods | ./tablizer -c 1,3 | ||
NAME(1) STATUS(3) | ||
repldepl-7bcd8d5b64-7zq4l Running | ||
repldepl-7bcd8d5b64-m48n8 Running | ||
repldepl-7bcd8d5b64-q2bf4 Running | ||
``` | ||
|
||
Another use case is when the tabular output is so wide that lines are | ||
being broken and the whole output is completely distorted. In such a | ||
case you can use the `-x` flag to get an output similar to `\x` in `psql`: | ||
|
||
``` | ||
% kubectl get pods | ./tablizer -x | ||
NAME: repldepl-7bcd8d5b64-7zq4l | ||
READY: 1/1 | ||
STATUS: Running | ||
RESTARTS: 1 (71m ago) | ||
AGE: 5h28m | ||
NAME: repldepl-7bcd8d5b64-m48n8 | ||
READY: 1/1 | ||
STATUS: Running | ||
RESTARTS: 1 (71m ago) | ||
AGE: 5h28m | ||
NAME: repldepl-7bcd8d5b64-q2bf4 | ||
READY: 1/1 | ||
STATUS: Running | ||
RESTARTS: 1 (71m ago) | ||
AGE: 5h28m | ||
``` | ||
|
||
Tablize can read one or more files or - if none specified - from STDIN. | ||
|
||
You can also specify a regex pattern to reduce the output: | ||
|
||
``` | ||
% kubectl get pods | ./tablizer q2bf4 | ||
NAME(1) READY(2) STATUS(3) RESTARTS(4) AGE(5) | ||
repldepl-7bcd8d5b64-q2bf4 1/1 Running 1 (69m ago) 5h26m | ||
``` | ||
|
||
|
||
## Installation | ||
|
||
Download the latest release file for your architecture and put it into | ||
a directory within your `$PATH`. | ||
|
||
## Getting help | ||
|
||
Although I'm happy to hear from udpxd users in private email, | ||
that's the best way for me to forget to do something. | ||
|
||
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. | ||
|
||
## Copyright and license | ||
|
||
This software is licensed under the GNU GENERAL PUBLIC LICENSE version 3. | ||
|
||
## Authors | ||
|
||
T.v.Dein <tom AT vondein DOT org> | ||
|
||
## Project homepage | ||
|
||
https://github.com/TLINDEN/tablizer |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
Add a mode like FreeBSD stat(1): | ||
|
||
stat -s dead.letter | ||
st_dev=170671546954750497 st_ino=159667 st_mode=0100644 st_nlink=1 st_uid=1001 st_gid=1001 st_rdev=18446744073709551615 st_size=573 st_atime=1661994007 st_mtime=1661961878 st_ctime=1661961878 st_birthtime=1658394900 st_blksize=4096 st_blocks=3 st_flags=2048 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
package cmd | ||
|
||
import ( | ||
"bufio" | ||
"fmt" | ||
"io" | ||
"os" | ||
"regexp" | ||
"strings" | ||
) | ||
|
||
// contains a whole parsed table | ||
type Tabdata struct { | ||
maxwidthHeader int // longest header | ||
maxwidthPerCol []int // max width per column | ||
columns int | ||
headerIndices []map[string]int // [ {beg=>0, end=>17}, ... ] | ||
headers []string // [ "ID", "NAME", ...] | ||
entries [][]string | ||
} | ||
|
||
func die(v ...interface{}) { | ||
fmt.Fprintln(os.Stderr, v...) | ||
os.Exit(1) | ||
} | ||
|
||
/* | ||
Parse tabular input. We split the header (first line) by 2 or more | ||
spaces, remember the positions of the header fields. We then split | ||
the data (everything after the first line) by those positions. That | ||
way we can turn "tabular data" (with fields containing whitespaces) | ||
into real tabular data. We re-tabulate our input if you will. | ||
*/ | ||
func parseFile(input io.Reader, pattern string) Tabdata { | ||
data := Tabdata{} | ||
|
||
var scanner *bufio.Scanner | ||
var spaces = `\s\s+|$` | ||
|
||
if len(Separator) > 0 { | ||
spaces = Separator | ||
} | ||
|
||
hadFirst := false | ||
spacefinder := regexp.MustCompile(spaces) | ||
beg := 0 | ||
|
||
scanner = bufio.NewScanner(input) | ||
|
||
for scanner.Scan() { | ||
line := scanner.Text() | ||
values := []string{} | ||
|
||
patternR, err := regexp.Compile(pattern) | ||
if err != nil { | ||
die(err) | ||
} | ||
|
||
if !hadFirst { | ||
// header processing | ||
parts := spacefinder.FindAllStringIndex(line, -1) | ||
data.columns = len(parts) | ||
// if Debug { | ||
// fmt.Println(parts) | ||
// } | ||
|
||
// process all header fields | ||
for _, part := range parts { | ||
// if Debug { | ||
// fmt.Printf("Part: <%s>\n", string(line[beg:part[0]])) | ||
//} | ||
|
||
// current field | ||
head := string(line[beg:part[0]]) | ||
|
||
// register begin and end of field within line | ||
indices := make(map[string]int) | ||
indices["beg"] = beg | ||
if part[0] == part[1] { | ||
indices["end"] = 0 | ||
} else { | ||
indices["end"] = part[1] - 1 | ||
} | ||
|
||
// register widest header field | ||
headerlen := len(head) | ||
if headerlen > data.maxwidthHeader { | ||
data.maxwidthHeader = headerlen | ||
} | ||
|
||
// register fields data | ||
data.headerIndices = append(data.headerIndices, indices) | ||
data.headers = append(data.headers, head) | ||
|
||
// end of current field == begin of next one | ||
beg = part[1] | ||
|
||
// done | ||
hadFirst = true | ||
} | ||
// if Debug { | ||
// fmt.Println(data.headerIndices) | ||
// } | ||
} else { | ||
// data processing | ||
if len(pattern) > 0 { | ||
//fmt.Println(patternR.MatchString(line)) | ||
if !patternR.MatchString(line) { | ||
continue | ||
} | ||
} | ||
|
||
idx := 0 // we cannot use the header index, because we could exclude columns | ||
|
||
for _, index := range data.headerIndices { | ||
value := "" | ||
if index["end"] == 0 { | ||
value = string(line[index["beg"]:]) | ||
} else { | ||
value = string(line[index["beg"]:index["end"]]) | ||
} | ||
|
||
width := len(strings.TrimSpace(value)) | ||
|
||
if len(data.maxwidthPerCol)-1 < idx { | ||
data.maxwidthPerCol = append(data.maxwidthPerCol, width) | ||
} else { | ||
if width > data.maxwidthPerCol[idx] { | ||
data.maxwidthPerCol[idx] = width | ||
} | ||
} | ||
|
||
// if Debug { | ||
// fmt.Printf("<%s> ", value) | ||
// } | ||
values = append(values, value) | ||
|
||
idx++ | ||
} | ||
if Debug { | ||
fmt.Println() | ||
} | ||
data.entries = append(data.entries, values) | ||
} | ||
} | ||
|
||
if scanner.Err() != nil { | ||
die(scanner.Err()) | ||
} | ||
|
||
return data | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
package cmd | ||
|
||
import ( | ||
"fmt" | ||
"strings" | ||
) | ||
|
||
func printTable(data Tabdata) { | ||
if XtendedOut { | ||
printExtended(data) | ||
return | ||
} | ||
|
||
// needed for data output | ||
var formats []string | ||
|
||
if len(data.entries) > 0 { | ||
// headers | ||
for i, head := range data.headers { | ||
if len(Columns) > 0 { | ||
if !contains(UseColumns, i+1) { | ||
continue | ||
} | ||
} | ||
|
||
// calculate column width | ||
var width int | ||
var iwidth int | ||
var format string | ||
|
||
// generate format string | ||
if len(head) > data.maxwidthPerCol[i] { | ||
width = len(head) | ||
} else { | ||
width = data.maxwidthPerCol[i] | ||
} | ||
|
||
if NoNumbering { | ||
iwidth = 0 | ||
} else { | ||
iwidth = len(fmt.Sprintf("%d", i)) // in case i > 9 | ||
} | ||
|
||
format = fmt.Sprintf("%%-%ds", 3+iwidth+width) | ||
|
||
if NoNumbering { | ||
fmt.Printf(format, fmt.Sprintf("%s ", head)) | ||
} else { | ||
fmt.Printf(format, fmt.Sprintf("%s(%d) ", head, i+1)) | ||
} | ||
|
||
// register | ||
formats = append(formats, format) | ||
} | ||
fmt.Println() | ||
|
||
// entries | ||
var idx int | ||
for _, entry := range data.entries { | ||
idx = 0 | ||
//fmt.Println(entry) | ||
for i, value := range entry { | ||
if len(Columns) > 0 { | ||
if !contains(UseColumns, i+1) { | ||
continue | ||
} | ||
} | ||
fmt.Printf(formats[idx], strings.TrimSpace(value)) | ||
idx++ | ||
} | ||
fmt.Println() | ||
} | ||
} | ||
} | ||
|
||
/* | ||
We simulate the \x command of psql (the PostgreSQL client) | ||
*/ | ||
func printExtended(data Tabdata) { | ||
// needed for data output | ||
format := fmt.Sprintf("%%%ds: %%s\n", data.maxwidthHeader) // FIXME: re-calculate if -c has been set | ||
|
||
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++ | ||
} | ||
fmt.Println() | ||
} | ||
} | ||
} | ||
|
||
func contains(s []int, e int) bool { | ||
for _, a := range s { | ||
if a == e { | ||
return true | ||
} | ||
} | ||
return false | ||
} |
Oops, something went wrong.