add type caster to table, convert cells automatically, users use any

This commit is contained in:
2026-07-07 06:29:40 +02:00
parent 0d81e5c3bc
commit 033dd0ccd1
3 changed files with 146 additions and 31 deletions

31
pkg/printer/bytes.go Normal file
View File

@@ -0,0 +1,31 @@
/*
Copyright © 2026 Thomas von Dein
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package printer
import "github.com/dustin/go-humanize"
type ByteSize struct {
size uint64
}
func Bytes(size int64) ByteSize {
return ByteSize{size: uint64(size)}
}
func (b *ByteSize) String() string {
return humanize.Bytes(b.size)
}

99
pkg/printer/cast.go Normal file
View File

@@ -0,0 +1,99 @@
/*
Copyright © 2026 Thomas von Dein
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package printer
import (
"fmt"
"strconv"
"strings"
"time"
"github.com/elastic/go-elasticsearch/v9/typedapi/types"
)
func any2string(in any) string {
switch val := in.(type) {
case string:
return val
case bool:
return strconv.FormatBool(val)
case int64:
return strconv.FormatInt(val, 10)
case int:
return strconv.Itoa(val)
case float64:
return fmt.Sprintf("%.2f", val)
case []string:
return strings.Join(val, ",")
case ByteSize:
return val.String()
case time.Time:
return val.Format("2006-01-02 15:04:05")
case []byte:
return string(val)
case types.Percentage:
return val.(string)
case types.DateTime:
return val.(string)
case nil:
return "null"
}
return ""
}
func (data *Table) preprocessRows() {
if data.processed {
// only do it once
return
}
// convert entries to strings
data.rows = make([][]string, len(data.Entries))
for rowidx, entries := range data.Entries {
data.rows[rowidx] = make([]string, len(entries))
for colidx, entry := range data.Entries[rowidx] {
data.rows[rowidx][colidx] = any2string(entry)
}
}
// determine header lenght's
for idx, head := range data.Headers {
data.lenHeaders[idx] = visibleLen(head)
}
// determine max width per column
for _, entries := range data.rows {
currentWidth := 0
for idx, entry := range entries {
length := visibleLen(entry)
if data.lenHeaders[idx] < length {
if length > currentWidth+data.maxwidth {
data.lenHeaders[idx] = data.maxwidth - currentWidth
} else {
data.lenHeaders[idx] = length
}
}
currentWidth += data.lenHeaders[idx]
}
}
data.processed = true
}

View File

@@ -32,8 +32,10 @@ import (
type Table struct { type Table struct {
Mode string // tsv, json, yaml Mode string // tsv, json, yaml
Headers []string Headers []string
Entries [][]string Entries [][]any
rows [][]string // representation used for printing
processed bool
lenHeaders []int lenHeaders []int
alignInts bool alignInts bool
maxwidth int maxwidth int
@@ -43,7 +45,7 @@ func NewTable(conf *cfg.Config, columns, rows int) *Table {
table := Table{Mode: conf.Output, maxwidth: cfg.GetTermWidth()} table := Table{Mode: conf.Output, maxwidth: cfg.GetTermWidth()}
table.Headers = make([]string, columns) table.Headers = make([]string, columns)
table.Entries = make([][]string, rows) table.Entries = make([][]any, rows)
table.lenHeaders = make([]int, columns) table.lenHeaders = make([]int, columns)
table.alignInts = conf.AlignInts table.alignInts = conf.AlignInts
@@ -59,7 +61,7 @@ func NewTableEmpty(conf *cfg.Config) *Table {
func (table *Table) WithHeaders(headers ...string) *Table { func (table *Table) WithHeaders(headers ...string) *Table {
count := len(headers) count := len(headers)
table.Entries = [][]string{} table.Entries = [][]any{}
table.lenHeaders = make([]int, count) table.lenHeaders = make([]int, count)
table.Headers = make([]string, count) table.Headers = make([]string, count)
@@ -84,11 +86,11 @@ var (
) )
// needed for json and yaml output // needed for json and yaml output
func (data *Table) toMap() []map[string]string { func (data *Table) toMap() []map[string]any {
raw := make([]map[string]string, len(data.Entries)) raw := make([]map[string]any, len(data.Entries))
for idx, entries := range data.Entries { for idx, entries := range data.Entries {
raw[idx] = make(map[string]string, len(data.Headers)) raw[idx] = make(map[string]any, len(data.Headers))
for eidx, entry := range entries { for eidx, entry := range entries {
raw[idx][data.Headers[eidx]] = entry raw[idx][data.Headers[eidx]] = entry
} }
@@ -124,27 +126,8 @@ func (data *Table) PrintJSON() error {
} }
func (data *Table) PrintTSV() error { func (data *Table) PrintTSV() error {
// determine lenght's // length's, convert cell types
for idx, head := range data.Headers { data.preprocessRows()
data.lenHeaders[idx] = visibleLen(head)
}
for _, entries := range data.Entries {
currentWidth := 0
for idx, entry := range entries {
length := visibleLen(entry)
if data.lenHeaders[idx] < length {
if length > currentWidth+data.maxwidth {
data.lenHeaders[idx] = data.maxwidth - currentWidth
} else {
data.lenHeaders[idx] = length
}
}
currentWidth += data.lenHeaders[idx]
}
}
// output headers // output headers
for idx, header := range data.Headers { for idx, header := range data.Headers {
@@ -161,7 +144,7 @@ func (data *Table) PrintTSV() error {
} }
fmt.Println() fmt.Println()
for _, entries := range data.Entries { for _, entries := range data.rows {
currentWidth := 0 currentWidth := 0
for idx, entry := range entries { for idx, entry := range entries {
@@ -211,8 +194,10 @@ func (data *Table) Sort() {
return return
} }
sort.Slice(data.Entries, func(i, j int) bool { data.preprocessRows()
return data.Entries[i][0] < data.Entries[j][0]
sort.Slice(data.rows, func(i, j int) bool {
return data.rows[i][0] < data.rows[j][0]
}) })
} }
@@ -227,7 +212,7 @@ func (data *Table) Addheaders(headers ...string) {
} }
} }
func (data *Table) AddRow(fields ...string) { func (data *Table) AddRow(fields ...any) {
data.Entries = append(data.Entries, fields) data.Entries = append(data.Entries, fields)
} }