mirror of
https://codeberg.org/scip/tablizer.git
synced 2025-12-18 21:11:03 +01:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 253ef8262e | |||
| da48994744 | |||
| 39f06fddc8 | |||
|
|
50a9378d92 | ||
|
|
35b726fee4 |
2
.github/workflows/release.yaml
vendored
2
.github/workflows/release.yaml
vendored
@@ -15,7 +15,7 @@ jobs:
|
|||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v6
|
uses: actions/setup-go@v6
|
||||||
with:
|
with:
|
||||||
go-version: 1.22.11
|
go-version: 1.24.0
|
||||||
|
|
||||||
- name: Build the executables
|
- name: Build the executables
|
||||||
run: ./mkrel.sh tablizer ${{ github.ref_name}}
|
run: ./mkrel.sh tablizer ${{ github.ref_name}}
|
||||||
|
|||||||
2
Makefile
2
Makefile
@@ -65,7 +65,7 @@ clean:
|
|||||||
rm -rf $(tool) releases coverage.out
|
rm -rf $(tool) releases coverage.out
|
||||||
|
|
||||||
test: clean
|
test: clean
|
||||||
go test -cover ./... $(OPTS)
|
go test -count=1 -cover ./... $(OPTS)
|
||||||
|
|
||||||
singletest:
|
singletest:
|
||||||
@echo "Call like this: 'make singletest TEST=TestPrepareColumns MOD=lib'"
|
@echo "Call like this: 'make singletest TEST=TestPrepareColumns MOD=lib'"
|
||||||
|
|||||||
@@ -192,10 +192,9 @@ hesitate to ask me about it, I'll add it.
|
|||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
The documentation is provided as a unix man-page. It will be
|
The documentation is provided as a unix man-page. It will be
|
||||||
automatically installed if you install from source. However, you can
|
automatically installed if you install from source.
|
||||||
read the man-page online:
|
|
||||||
|
|
||||||
https://github.com/TLINDEN/tablizer/blob/main/tablizer.pod
|
[However, you can read the man-page online](https://github.com/TLINDEN/tablizer/blob/main/tablizer.pod).
|
||||||
|
|
||||||
Or if you cloned the repository you can read it this way (perl needs
|
Or if you cloned the repository you can read it this way (perl needs
|
||||||
to be installed though): `perldoc tablizer.pod`.
|
to be installed though): `perldoc tablizer.pod`.
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const DefaultSeparator string = `(\s\s+|\t)`
|
const DefaultSeparator string = `(\s\s+|\t)`
|
||||||
const Version string = "v1.5.7"
|
const Version string = "v1.5.8"
|
||||||
const MAXPARTS = 2
|
const MAXPARTS = 2
|
||||||
|
|
||||||
var DefaultConfigfile = os.Getenv("HOME") + "/.config/tablizer/config"
|
var DefaultConfigfile = os.Getenv("HOME") + "/.config/tablizer/config"
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
"sort"
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -30,16 +30,6 @@ import (
|
|||||||
"github.com/tlinden/tablizer/cfg"
|
"github.com/tlinden/tablizer/cfg"
|
||||||
)
|
)
|
||||||
|
|
||||||
func contains(s []int, e int) bool {
|
|
||||||
for _, a := range s {
|
|
||||||
if a == e {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func findindex(s []int, e int) (int, bool) {
|
func findindex(s []int, e int) (int, bool) {
|
||||||
for i, a := range s {
|
for i, a := range s {
|
||||||
if a == e {
|
if a == e {
|
||||||
@@ -172,48 +162,32 @@ func PrepareColumnVars(columns string, data *Tabdata) ([]int, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// deduplicate: put all values into a map (value gets map key)
|
// deduplicate columns, preserve order
|
||||||
// thereby removing duplicates, extract keys into new slice
|
deduped := []int{}
|
||||||
// and sort it
|
|
||||||
imap := make(map[int]int, len(usecolumns))
|
|
||||||
for _, i := range usecolumns {
|
for _, i := range usecolumns {
|
||||||
imap[i] = 0
|
if !slices.Contains(deduped, i) {
|
||||||
|
deduped = append(deduped, i)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// fill with deduplicated columns
|
return deduped, nil
|
||||||
usecolumns = nil
|
|
||||||
|
|
||||||
for k := range imap {
|
|
||||||
usecolumns = append(usecolumns, k)
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Ints(usecolumns)
|
|
||||||
|
|
||||||
return usecolumns, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// prepare headers: add numbers to headers
|
// prepare headers: add numbers to headers
|
||||||
func numberizeAndReduceHeaders(conf cfg.Config, data *Tabdata) {
|
func numberizeAndReduceHeaders(conf cfg.Config, data *Tabdata) {
|
||||||
numberedHeaders := []string{}
|
numberedHeaders := make([]string, len(data.headers))
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
|
// add numbers to headers if needed, get widest cell width
|
||||||
for idx, head := range data.headers {
|
for idx, head := range data.headers {
|
||||||
var headlen int
|
var headlen int
|
||||||
|
|
||||||
if len(conf.Columns) > 0 {
|
|
||||||
// -c specified
|
|
||||||
if !contains(conf.UseColumns, idx+1) {
|
|
||||||
// ignore this one
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if conf.Numbering {
|
if conf.Numbering {
|
||||||
numhead := fmt.Sprintf("%s(%d)", head, idx+1)
|
newhead := fmt.Sprintf("%s(%d)", head, idx+1)
|
||||||
headlen = len(numhead)
|
numberedHeaders[idx] = newhead
|
||||||
numberedHeaders = append(numberedHeaders, numhead)
|
headlen = len(newhead)
|
||||||
} else {
|
} else {
|
||||||
numberedHeaders = append(numberedHeaders, head)
|
|
||||||
headlen = len(head)
|
headlen = len(head)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,7 +196,24 @@ func numberizeAndReduceHeaders(conf cfg.Config, data *Tabdata) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data.headers = numberedHeaders
|
if conf.Numbering {
|
||||||
|
data.headers = numberedHeaders
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(conf.UseColumns) > 0 {
|
||||||
|
// re-align headers based on user requested column list
|
||||||
|
headers := make([]string, len(conf.UseColumns))
|
||||||
|
|
||||||
|
for i, col := range conf.UseColumns {
|
||||||
|
for idx := range data.headers {
|
||||||
|
if col-1 == idx {
|
||||||
|
headers[i] = data.headers[col-1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data.headers = headers
|
||||||
|
}
|
||||||
|
|
||||||
if data.maxwidthHeader != maxwidth && maxwidth > 0 {
|
if data.maxwidthHeader != maxwidth && maxwidth > 0 {
|
||||||
data.maxwidthHeader = maxwidth
|
data.maxwidthHeader = maxwidth
|
||||||
@@ -234,17 +225,17 @@ func reduceColumns(conf cfg.Config, data *Tabdata) {
|
|||||||
if len(conf.Columns) > 0 {
|
if len(conf.Columns) > 0 {
|
||||||
reducedEntries := [][]string{}
|
reducedEntries := [][]string{}
|
||||||
|
|
||||||
var reducedEntry []string
|
|
||||||
|
|
||||||
for _, entry := range data.entries {
|
for _, entry := range data.entries {
|
||||||
reducedEntry = nil
|
var reducedEntry []string
|
||||||
|
|
||||||
for i, value := range entry {
|
for _, col := range conf.UseColumns {
|
||||||
if !contains(conf.UseColumns, i+1) {
|
col--
|
||||||
continue
|
|
||||||
|
for idx, value := range entry {
|
||||||
|
if idx == col {
|
||||||
|
reducedEntry = append(reducedEntry, value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
reducedEntry = append(reducedEntry, value)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
reducedEntries = append(reducedEntries, reducedEntry)
|
reducedEntries = append(reducedEntries, reducedEntry)
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ package lib
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"slices"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@@ -38,7 +39,7 @@ func TestContains(t *testing.T) {
|
|||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
testname := fmt.Sprintf("contains-%d,%d,%t", tt.list, tt.search, tt.want)
|
testname := fmt.Sprintf("contains-%d,%d,%t", tt.list, tt.search, tt.want)
|
||||||
t.Run(testname, func(t *testing.T) {
|
t.Run(testname, func(t *testing.T) {
|
||||||
answer := contains(tt.list, tt.search)
|
answer := slices.Contains(tt.list, tt.search)
|
||||||
|
|
||||||
assert.EqualValues(t, tt.want, answer)
|
assert.EqualValues(t, tt.want, answer)
|
||||||
})
|
})
|
||||||
@@ -72,7 +73,8 @@ func TestPrepareColumns(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, testdata := range tests {
|
for _, testdata := range tests {
|
||||||
testname := fmt.Sprintf("PrepareColumns-%s-%t", testdata.input, testdata.wanterror)
|
testname := fmt.Sprintf("PrepareColumns-%s-%t",
|
||||||
|
testdata.input, testdata.wanterror)
|
||||||
t.Run(testname, func(t *testing.T) {
|
t.Run(testname, func(t *testing.T) {
|
||||||
conf := cfg.Config{Columns: testdata.input}
|
conf := cfg.Config{Columns: testdata.input}
|
||||||
err := PrepareColumns(&conf, &data)
|
err := PrepareColumns(&conf, &data)
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
|
"math"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -222,6 +223,32 @@ func parseRawJSON(conf cfg.Config, input io.Reader) (Tabdata, error) {
|
|||||||
row[idxmap[currentfield]] = val
|
row[idxmap[currentfield]] = val
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case float64:
|
||||||
|
var value string
|
||||||
|
|
||||||
|
// we set precision to 0 if the float is a whole number
|
||||||
|
if val == math.Trunc(val) {
|
||||||
|
value = fmt.Sprintf("%.f", val)
|
||||||
|
} else {
|
||||||
|
value = fmt.Sprintf("%f", val)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !haveheaders {
|
||||||
|
row = append(row, value)
|
||||||
|
} else {
|
||||||
|
row[idxmap[currentfield]] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
case nil:
|
||||||
|
// we ignore here if a value shall be an int or a string,
|
||||||
|
// because tablizer only works with strings anyway
|
||||||
|
if !haveheaders {
|
||||||
|
row = append(row, "")
|
||||||
|
} else {
|
||||||
|
row[idxmap[currentfield]] = ""
|
||||||
|
}
|
||||||
|
|
||||||
case json.Delim:
|
case json.Delim:
|
||||||
if val.String() == "}" {
|
if val.String() == "}" {
|
||||||
data = append(data, row)
|
data = append(data, row)
|
||||||
@@ -240,6 +267,8 @@ func parseRawJSON(conf cfg.Config, input io.Reader) (Tabdata, error) {
|
|||||||
haveheaders = true
|
haveheaders = true
|
||||||
}
|
}
|
||||||
isjson = true
|
isjson = true
|
||||||
|
default:
|
||||||
|
fmt.Printf("unknown token: %v type: %T\n", t, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
iskey = !iskey
|
iskey = !iskey
|
||||||
|
|||||||
@@ -180,6 +180,38 @@ func TestParserJSONInput(t *testing.T) {
|
|||||||
expect: Tabdata{},
|
expect: Tabdata{},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
// contains nil, int and float values
|
||||||
|
name: "niljson",
|
||||||
|
wanterror: false,
|
||||||
|
input: `[
|
||||||
|
{
|
||||||
|
"NAME": "postgres-operator-7f4c7c8485-ntlns",
|
||||||
|
"READY": "1/1",
|
||||||
|
"STATUS": "Running",
|
||||||
|
"RESTARTS": 0,
|
||||||
|
"AGE": null,
|
||||||
|
"X": 12,
|
||||||
|
"Y": 34.222
|
||||||
|
}
|
||||||
|
]`,
|
||||||
|
expect: Tabdata{
|
||||||
|
columns: 7,
|
||||||
|
headers: []string{"NAME", "READY", "STATUS", "RESTARTS", "AGE", "X", "Y"},
|
||||||
|
entries: [][]string{
|
||||||
|
[]string{
|
||||||
|
"postgres-operator-7f4c7c8485-ntlns",
|
||||||
|
"1/1",
|
||||||
|
"Running",
|
||||||
|
"0",
|
||||||
|
"",
|
||||||
|
"12",
|
||||||
|
"34.222000",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
// one field missing + different order
|
// one field missing + different order
|
||||||
// but shall not fail
|
// but shall not fail
|
||||||
|
|||||||
Reference in New Issue
Block a user