chore(refactor): refactors listcmd

This commit is contained in:
Lewis Wynne 2025-11-07 17:19:39 +00:00
parent d96b0f7a2c
commit 7600434a05

View file

@ -35,7 +35,6 @@ import (
"golang.org/x/term" "golang.org/x/term"
) )
// listCmd represents the set command
var listCmd = &cobra.Command{ var listCmd = &cobra.Command{
Use: "list [DB]", Use: "list [DB]",
Short: "List the contents of a db.", Short: "List the contents of a db.",
@ -43,6 +42,16 @@ var listCmd = &cobra.Command{
RunE: list, RunE: list,
} }
type ListArgs struct {
header bool
key bool
value bool
ttl bool
binary bool
secrets bool
format string
}
func list(cmd *cobra.Command, args []string) error { func list(cmd *cobra.Command, args []string) error {
store := &Store{} store := &Store{}
targetDB := "@default" targetDB := "@default"
@ -62,66 +71,36 @@ func list(cmd *cobra.Command, args []string) error {
targetDB = "@" + dbName targetDB = "@" + dbName
} }
showSecrets, err := cmd.Flags().GetBool("secret") flags, err := parseFlags(cmd)
if err != nil { if err != nil {
return err return err
} }
noKeys, err := cmd.Flags().GetBool("no-keys")
if err != nil {
return err
}
noValues, err := cmd.Flags().GetBool("no-values")
if err != nil {
return err
}
showTTL, err := cmd.Flags().GetBool("ttl")
if err != nil {
return err
}
noHeader, err := cmd.Flags().GetBool("no-header")
if err != nil {
return err
}
includeBinary, err := cmd.Flags().GetBool("binary")
if err != nil {
return err
}
format, err := cmd.Flags().GetString("format")
if err != nil {
return err
}
switch format {
case "auto", "table", "tabular", "csv", "html", "markdown", "md":
default:
return fmt.Errorf("unsupported format %q", format)
}
includeKey := !noKeys columnKinds, err := requireColumns(flags)
includeValue := !noValues if err != nil {
if !includeKey && !includeValue && !showTTL { return err
return fmt.Errorf("no columns selected; disable --no-keys/--no-values or pass --ttl")
}
columnKinds := selectColumns(includeKey, includeValue, showTTL)
if len(columnKinds) == 0 {
return fmt.Errorf("no columns selected; enable key, value, or ttl output")
} }
output := cmd.OutOrStdout() output := cmd.OutOrStdout()
tw := table.NewWriter() tw := table.NewWriter()
tw.SetOutputMirror(output) tw.SetOutputMirror(output)
configureListTable(tw) tw.SetStyle(table.StyleColoredBlackOnGreenWhite)
if shouldLimitColumns(format) {
applyColumnConstraints(tw, columnKinds, output) limitColumns := shouldLimitColumns(flags.format)
var maxContentWidths []int
if limitColumns {
maxContentWidths = make([]int, len(columnKinds))
} }
if !noHeader { if flags.header {
header := buildHeaderCells(columnKinds) header := buildHeaderCells(columnKinds)
if limitColumns {
updateMaxContentWidths(maxContentWidths, header)
}
tw.AppendHeader(stringSliceToRow(header)) tw.AppendHeader(stringSliceToRow(header))
} }
placeholder := "[secret: pass --secret to view]" placeholder := "**********"
trans := TransactionArgs{ trans := TransactionArgs{
key: targetDB, key: targetDB,
readonly: true, readonly: true,
@ -129,7 +108,7 @@ func list(cmd *cobra.Command, args []string) error {
transact: func(tx *badger.Txn, k []byte) error { transact: func(tx *badger.Txn, k []byte) error {
opts := badger.DefaultIteratorOptions opts := badger.DefaultIteratorOptions
opts.PrefetchSize = 10 opts.PrefetchSize = 10
opts.PrefetchValues = includeValue opts.PrefetchValues = flags.value
it := tx.NewIterator(opts) it := tx.NewIterator(opts)
defer it.Close() defer it.Close()
var valueBuf []byte var valueBuf []byte
@ -140,14 +119,14 @@ func list(cmd *cobra.Command, args []string) error {
isSecret := meta&metaSecret != 0 isSecret := meta&metaSecret != 0
var valueStr string var valueStr string
if includeValue && (!isSecret || showSecrets) { if flags.value && (!isSecret || flags.secrets) {
if err := item.Value(func(v []byte) error { if err := item.Value(func(v []byte) error {
valueBuf = append(valueBuf[:0], v...) valueBuf = append(valueBuf[:0], v...)
return nil return nil
}); err != nil { }); err != nil {
return err return err
} }
valueStr = store.FormatBytes(includeBinary, valueBuf) valueStr = store.FormatBytes(flags.binary, valueBuf)
} }
columns := make([]string, 0, len(columnKinds)) columns := make([]string, 0, len(columnKinds))
@ -156,7 +135,7 @@ func list(cmd *cobra.Command, args []string) error {
case columnKey: case columnKey:
columns = append(columns, key) columns = append(columns, key)
case columnValue: case columnValue:
if isSecret && !showSecrets { if isSecret && !flags.secrets {
columns = append(columns, placeholder) columns = append(columns, placeholder)
} else { } else {
columns = append(columns, valueStr) columns = append(columns, valueStr)
@ -165,7 +144,9 @@ func list(cmd *cobra.Command, args []string) error {
columns = append(columns, formatExpiry(item.ExpiresAt())) columns = append(columns, formatExpiry(item.ExpiresAt()))
} }
} }
if limitColumns {
updateMaxContentWidths(maxContentWidths, columns)
}
tw.AppendRow(stringSliceToRow(columns)) tw.AppendRow(stringSliceToRow(columns))
} }
return nil return nil
@ -176,7 +157,11 @@ func list(cmd *cobra.Command, args []string) error {
return err return err
} }
switch format { if limitColumns {
applyColumnConstraints(tw, columnKinds, output, maxContentWidths)
}
switch flags.format {
case "auto", "table", "tabular": case "auto", "table", "tabular":
tw.Render() tw.Render()
case "csv": case "csv":
@ -200,6 +185,56 @@ func init() {
rootCmd.AddCommand(listCmd) rootCmd.AddCommand(listCmd)
} }
func parseFlags(cmd *cobra.Command) (ListArgs, error) {
secrets, err := cmd.Flags().GetBool("secret")
if err != nil {
return ListArgs{}, err
}
noKeys, err := cmd.Flags().GetBool("no-keys")
if err != nil {
return ListArgs{}, err
}
noValues, err := cmd.Flags().GetBool("no-values")
if err != nil {
return ListArgs{}, err
}
ttl, err := cmd.Flags().GetBool("ttl")
if err != nil {
return ListArgs{}, err
}
noHeader, err := cmd.Flags().GetBool("no-header")
if err != nil {
return ListArgs{}, err
}
binary, err := cmd.Flags().GetBool("binary")
if err != nil {
return ListArgs{}, err
}
format, err := cmd.Flags().GetString("format")
if err != nil {
return ListArgs{}, err
}
switch format {
case "auto", "table", "tabular", "csv", "html", "markdown", "md":
default:
return ListArgs{}, fmt.Errorf("unsupported format %q", format)
}
if noKeys && noValues && !ttl {
return ListArgs{}, fmt.Errorf("no columns selected; disable --no-keys/--no-values or pass --ttl")
}
return ListArgs{
header: !noHeader,
key: !noKeys,
value: !noValues,
ttl: ttl,
binary: binary,
format: format,
secrets: secrets,
}, nil
}
type columnKind int type columnKind int
const ( const (
@ -208,18 +243,21 @@ const (
columnTTL columnTTL
) )
func selectColumns(includeKey, includeValue, showTTL bool) []columnKind { func requireColumns(args ListArgs) ([]columnKind, error) {
var columns []columnKind var columns []columnKind
if includeKey { if args.key {
columns = append(columns, columnKey) columns = append(columns, columnKey)
} }
if includeValue { if args.value {
columns = append(columns, columnValue) columns = append(columns, columnValue)
} }
if showTTL { if args.ttl {
columns = append(columns, columnTTL) columns = append(columns, columnTTL)
} }
return columns 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 { func buildHeaderCells(columnKinds []columnKind) []string {
@ -245,8 +283,17 @@ func stringSliceToRow(values []string) table.Row {
return row return row
} }
func configureListTable(tw table.Writer) { func updateMaxContentWidths(maxWidths []int, values []string) {
tw.SetStyle(table.StyleColoredBlackOnGreenWhite) 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 shouldLimitColumns(format string) bool { func shouldLimitColumns(format string) bool {
@ -258,12 +305,51 @@ func shouldLimitColumns(format string) bool {
} }
} }
func applyColumnConstraints(tw table.Writer, columns []columnKind, out io.Writer) { func applyColumnConstraints(tw table.Writer, columns []columnKind, out io.Writer, maxContentWidths []int) {
totalWidth := detectTerminalWidth(out) totalWidth := detectTerminalWidth(out)
if totalWidth <= 0 { if totalWidth <= 0 {
totalWidth = 100 totalWidth = 100
} }
widths := distributeWidths(totalWidth, columns) 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)) configs := make([]table.ColumnConfig, 0, len(columns))
for idx, width := range widths { for idx, width := range widths {
configs = append(configs, table.ColumnConfig{ configs = append(configs, table.ColumnConfig{
@ -276,6 +362,55 @@ func applyColumnConstraints(tw table.Writer, columns []columnKind, out io.Writer
tw.SetAllowedRowLength(totalWidth) 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 { type fdWriter interface {
Fd() uintptr Fd() uintptr
} }