refactor(list): extracts the table column construction into list_table.go -- this needs a massive clean-up
This commit is contained in:
parent
983c07c829
commit
0dabf155be
2 changed files with 248 additions and 249 deletions
249
cmd/list.go
249
cmd/list.go
|
|
@ -24,15 +24,10 @@ package cmd
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/dgraph-io/badger/v4"
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
"github.com/jedib0t/go-pretty/v6/text"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
var listCmd = &cobra.Command{
|
||||
|
|
@ -156,247 +151,3 @@ func init() {
|
|||
listCmd.Flags().VarP(&format, "format", "o", "render output format (table|csv|markdown|html)")
|
||||
rootCmd.AddCommand(listCmd)
|
||||
}
|
||||
|
||||
type columnKind int
|
||||
|
||||
const (
|
||||
columnKey columnKind = iota
|
||||
columnValue
|
||||
columnTTL
|
||||
)
|
||||
|
||||
func requireColumns(args ListArgs) ([]columnKind, error) {
|
||||
var columns []columnKind
|
||||
if args.key {
|
||||
columns = append(columns, columnKey)
|
||||
}
|
||||
if args.value {
|
||||
columns = append(columns, columnValue)
|
||||
}
|
||||
if args.ttl {
|
||||
columns = append(columns, columnTTL)
|
||||
}
|
||||
if len(columns) == 0 {
|
||||
return nil, fmt.Errorf("no columns selected; enable key, value, or ttl output")
|
||||
}
|
||||
return columns, nil
|
||||
}
|
||||
|
||||
func buildHeaderCells(columnKinds []columnKind) []string {
|
||||
labels := make([]string, 0, len(columnKinds))
|
||||
for _, column := range columnKinds {
|
||||
switch column {
|
||||
case columnKey:
|
||||
labels = append(labels, "Key")
|
||||
case columnValue:
|
||||
labels = append(labels, "Value")
|
||||
case columnTTL:
|
||||
labels = append(labels, "TTL")
|
||||
}
|
||||
}
|
||||
return labels
|
||||
}
|
||||
|
||||
func stringSliceToRow(values []string) table.Row {
|
||||
row := make(table.Row, len(values))
|
||||
for i, val := range values {
|
||||
row[i] = val
|
||||
}
|
||||
return row
|
||||
}
|
||||
|
||||
func updateMaxContentWidths(maxWidths []int, values []string) {
|
||||
if len(maxWidths) == 0 {
|
||||
return
|
||||
}
|
||||
limit := min(len(values), len(maxWidths))
|
||||
for i := range limit {
|
||||
width := text.LongestLineLen(values[i])
|
||||
if width > maxWidths[i] {
|
||||
maxWidths[i] = width
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func applyColumnConstraints(tw table.Writer, columns []columnKind, out io.Writer, maxContentWidths []int) {
|
||||
totalWidth := detectTerminalWidth(out)
|
||||
if totalWidth <= 0 {
|
||||
totalWidth = 100
|
||||
}
|
||||
contentWidth := contentWidthForStyle(totalWidth, tw, len(columns))
|
||||
widths := distributeWidths(contentWidth, columns)
|
||||
|
||||
used := 0
|
||||
for idx, width := range widths {
|
||||
if width <= 0 {
|
||||
width = 1
|
||||
}
|
||||
if idx < len(maxContentWidths) {
|
||||
if actual := maxContentWidths[idx]; actual > 0 && width > actual {
|
||||
width = actual
|
||||
}
|
||||
}
|
||||
widths[idx] = width
|
||||
used += width
|
||||
}
|
||||
|
||||
remaining := contentWidth - used
|
||||
for remaining > 0 {
|
||||
progressed := false
|
||||
for idx := range widths {
|
||||
actual := 0
|
||||
if idx < len(maxContentWidths) {
|
||||
actual = maxContentWidths[idx]
|
||||
}
|
||||
if actual > 0 && widths[idx] >= actual {
|
||||
continue
|
||||
}
|
||||
widths[idx]++
|
||||
remaining--
|
||||
progressed = true
|
||||
if remaining == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if !progressed {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
configs := make([]table.ColumnConfig, 0, len(columns))
|
||||
for idx, width := range widths {
|
||||
configs = append(configs, table.ColumnConfig{
|
||||
Number: idx + 1,
|
||||
WidthMax: width,
|
||||
WidthMaxEnforcer: text.WrapText,
|
||||
})
|
||||
}
|
||||
tw.SetColumnConfigs(configs)
|
||||
tw.SetAllowedRowLength(totalWidth)
|
||||
}
|
||||
|
||||
func contentWidthForStyle(totalWidth int, tw table.Writer, columnCount int) int {
|
||||
if columnCount == 0 {
|
||||
return totalWidth
|
||||
}
|
||||
style := tw.Style()
|
||||
if style != nil {
|
||||
totalWidth -= tableRowOverhead(style, columnCount)
|
||||
}
|
||||
if totalWidth < columnCount {
|
||||
totalWidth = columnCount
|
||||
}
|
||||
return totalWidth
|
||||
}
|
||||
|
||||
func tableRowOverhead(style *table.Style, columnCount int) int {
|
||||
if style == nil || columnCount == 0 {
|
||||
return 0
|
||||
}
|
||||
paddingWidth := text.StringWidthWithoutEscSequences(style.Box.PaddingLeft + style.Box.PaddingRight)
|
||||
overhead := paddingWidth * columnCount
|
||||
if style.Options.SeparateColumns && columnCount > 1 {
|
||||
overhead += (columnCount - 1) * maxSeparatorWidth(style)
|
||||
}
|
||||
if style.Options.DrawBorder {
|
||||
overhead += text.StringWidthWithoutEscSequences(style.Box.Left + style.Box.Right)
|
||||
}
|
||||
return overhead
|
||||
}
|
||||
|
||||
func maxSeparatorWidth(style *table.Style) int {
|
||||
widest := 0
|
||||
separators := []string{
|
||||
style.Box.MiddleSeparator,
|
||||
style.Box.EmptySeparator,
|
||||
style.Box.MiddleHorizontal,
|
||||
style.Box.TopSeparator,
|
||||
style.Box.BottomSeparator,
|
||||
style.Box.MiddleVertical,
|
||||
style.Box.LeftSeparator,
|
||||
style.Box.RightSeparator,
|
||||
}
|
||||
for _, sep := range separators {
|
||||
if width := text.StringWidthWithoutEscSequences(sep); width > widest {
|
||||
widest = width
|
||||
}
|
||||
}
|
||||
return widest
|
||||
}
|
||||
|
||||
type fdWriter interface {
|
||||
Fd() uintptr
|
||||
}
|
||||
|
||||
func detectTerminalWidth(out io.Writer) int {
|
||||
if f, ok := out.(fdWriter); ok {
|
||||
if w, _, err := term.GetSize(int(f.Fd())); err == nil && w > 0 {
|
||||
return w
|
||||
}
|
||||
}
|
||||
if w, _, err := term.GetSize(int(os.Stdout.Fd())); err == nil && w > 0 {
|
||||
return w
|
||||
}
|
||||
if cols := os.Getenv("COLUMNS"); cols != "" {
|
||||
if parsed, err := strconv.Atoi(cols); err == nil && parsed > 0 {
|
||||
return parsed
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func distributeWidths(total int, columns []columnKind) []int {
|
||||
if total <= 0 {
|
||||
total = 100
|
||||
}
|
||||
hasTTL := false
|
||||
for _, c := range columns {
|
||||
if c == columnTTL {
|
||||
hasTTL = true
|
||||
break
|
||||
}
|
||||
}
|
||||
base := make([]float64, len(columns))
|
||||
sum := 0.0
|
||||
for i, c := range columns {
|
||||
pct := basePercentageForColumn(c, hasTTL)
|
||||
base[i] = pct
|
||||
sum += pct
|
||||
}
|
||||
if sum == 0 {
|
||||
sum = 1
|
||||
}
|
||||
widths := make([]int, len(columns))
|
||||
remaining := total
|
||||
const minColWidth = 10
|
||||
for i := range columns {
|
||||
width := int((base[i] / sum) * float64(total))
|
||||
if width < minColWidth {
|
||||
width = minColWidth
|
||||
}
|
||||
widths[i] = width
|
||||
remaining -= width
|
||||
}
|
||||
for i := 0; remaining > 0 && len(columns) > 0; i++ {
|
||||
idx := i % len(columns)
|
||||
widths[idx]++
|
||||
remaining--
|
||||
}
|
||||
return widths
|
||||
}
|
||||
|
||||
func basePercentageForColumn(c columnKind, hasTTL bool) float64 {
|
||||
switch c {
|
||||
case columnKey:
|
||||
return 0.25
|
||||
case columnValue:
|
||||
if hasTTL {
|
||||
return 0.5
|
||||
}
|
||||
return 0.75
|
||||
case columnTTL:
|
||||
return 0.25
|
||||
default:
|
||||
return 0.25
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue