feat: default ttl and header visibility, and removed unnecessray padding from tab output
This commit is contained in:
parent
07330be10b
commit
24853bfce8
6 changed files with 232 additions and 69 deletions
35
README.md
35
README.md
|
|
@ -192,19 +192,34 @@ pda rm kitty -i
|
||||||
`pda ls` to see what you've got stored.
|
`pda ls` to see what you've got stored.
|
||||||
```bash
|
```bash
|
||||||
pda ls
|
pda ls
|
||||||
# name Alice
|
# KEY VALUE TTL
|
||||||
# dogs four legged mammals
|
# name Alice no expiry
|
||||||
|
# dogs four legged mammals no expiry
|
||||||
|
|
||||||
# Or as CSV.
|
# Or as CSV.
|
||||||
pda ls --format csv
|
pda ls --format csv
|
||||||
# name,Alice
|
# Key,Value,TTL
|
||||||
# dogs,four legged mammals
|
# name,Alice,no expiry
|
||||||
|
# dogs,four legged mammals,no expiry
|
||||||
|
|
||||||
# Or TSV, or Markdown, or HTML.
|
# Or TSV, or Markdown, or HTML.
|
||||||
```
|
```
|
||||||
|
|
||||||
<p align="center"></p><!-- spacer -->
|
<p align="center"></p><!-- spacer -->
|
||||||
|
|
||||||
|
Long values are truncated to fit the terminal. Use `--full`/`-f` to show the complete value.
|
||||||
|
```bash
|
||||||
|
pda ls
|
||||||
|
# KEY VALUE TTL
|
||||||
|
# note this is a very long (..30 more chars) no expiry
|
||||||
|
|
||||||
|
pda ls --full
|
||||||
|
# KEY VALUE TTL
|
||||||
|
# note this is a very long value that keeps on going and going no expiry
|
||||||
|
```
|
||||||
|
|
||||||
|
<p align="center"></p><!-- spacer -->
|
||||||
|
|
||||||
`pda export` to export everything as NDJSON.
|
`pda export` to export everything as NDJSON.
|
||||||
```bash
|
```bash
|
||||||
pda export > my_backup
|
pda export > my_backup
|
||||||
|
|
@ -536,11 +551,12 @@ pda set session2 "xyz" --ttl 54m10s
|
||||||
|
|
||||||
<p align="center"></p><!-- spacer -->
|
<p align="center"></p><!-- spacer -->
|
||||||
|
|
||||||
`list --ttl` shows expiration date in list output.
|
`list` shows expiration in the TTL column by default.
|
||||||
```bash
|
```bash
|
||||||
pda ls --ttl
|
pda ls
|
||||||
# session 123 2025-11-21T15:30:00Z (in 59m30s)
|
# KEY VALUE TTL
|
||||||
# session2 xyz 2025-11-21T15:21:40Z (in 51m40s)
|
# session 123 in 59m30s
|
||||||
|
# session2 xyz in 51m40s
|
||||||
```
|
```
|
||||||
|
|
||||||
`export` and `import` persist the expiry date. Expirations will continue ticking down regardless of if they're actively in a store or not - the expiry is just a timestamp, not a timer.
|
`export` and `import` persist the expiry date. Expirations will continue ticking down regardless of if they're actively in a store or not - the expiry is just a timestamp, not a timer.
|
||||||
|
|
@ -628,7 +644,8 @@ pda set api-key "oops"
|
||||||
If the identity file is missing, encrypted values are inaccessible but not lost. Keys are still visible, and the ciphertext is preserved through reads and writes.
|
If the identity file is missing, encrypted values are inaccessible but not lost. Keys are still visible, and the ciphertext is preserved through reads and writes.
|
||||||
```bash
|
```bash
|
||||||
pda ls
|
pda ls
|
||||||
# api-key locked (identity file missing)
|
# KEY VALUE TTL
|
||||||
|
# api-key locked (identity file missing) no expiry
|
||||||
|
|
||||||
pda get api-key
|
pda get api-key
|
||||||
# FAIL cannot get 'api-key': secret is locked (identity file missing)
|
# FAIL cannot get 'api-key': secret is locked (identity file missing)
|
||||||
|
|
|
||||||
218
cmd/list.go
218
cmd/list.go
|
|
@ -29,6 +29,8 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
"filippo.io/age"
|
"filippo.io/age"
|
||||||
"github.com/jedib0t/go-pretty/v6/table"
|
"github.com/jedib0t/go-pretty/v6/table"
|
||||||
|
|
@ -58,9 +60,12 @@ var (
|
||||||
listBase64 bool
|
listBase64 bool
|
||||||
listNoKeys bool
|
listNoKeys bool
|
||||||
listNoValues bool
|
listNoValues bool
|
||||||
listTTL bool
|
listNoTTL bool
|
||||||
listHeader bool
|
listFull bool
|
||||||
|
listNoHeader bool
|
||||||
listFormat formatEnum = "table"
|
listFormat formatEnum = "table"
|
||||||
|
|
||||||
|
dimStyle = text.Colors{text.Faint, text.Italic}
|
||||||
)
|
)
|
||||||
|
|
||||||
type columnKind int
|
type columnKind int
|
||||||
|
|
@ -99,8 +104,8 @@ func list(cmd *cobra.Command, args []string) error {
|
||||||
targetDB = "@" + dbName
|
targetDB = "@" + dbName
|
||||||
}
|
}
|
||||||
|
|
||||||
if listNoKeys && listNoValues && !listTTL {
|
if listNoKeys && listNoValues && listNoTTL {
|
||||||
return withHint(fmt.Errorf("cannot ls '%s': no columns selected", targetDB), "disable --no-keys/--no-values or pass --ttl")
|
return withHint(fmt.Errorf("cannot ls '%s': no columns selected", targetDB), "disable --no-keys, --no-values, or --no-ttl")
|
||||||
}
|
}
|
||||||
|
|
||||||
var columns []columnKind
|
var columns []columnKind
|
||||||
|
|
@ -110,7 +115,7 @@ func list(cmd *cobra.Command, args []string) error {
|
||||||
if !listNoValues {
|
if !listNoValues {
|
||||||
columns = append(columns, columnValue)
|
columns = append(columns, columnValue)
|
||||||
}
|
}
|
||||||
if listTTL {
|
if !listNoTTL {
|
||||||
columns = append(columns, columnTTL)
|
columns = append(columns, columnTTL)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -183,39 +188,131 @@ func list(cmd *cobra.Command, args []string) error {
|
||||||
tw.Style().Options.DrawBorder = false
|
tw.Style().Options.DrawBorder = false
|
||||||
tw.Style().Options.SeparateRows = false
|
tw.Style().Options.SeparateRows = false
|
||||||
tw.Style().Options.SeparateColumns = false
|
tw.Style().Options.SeparateColumns = false
|
||||||
|
tw.Style().Box.PaddingLeft = ""
|
||||||
|
tw.Style().Box.PaddingRight = " "
|
||||||
|
|
||||||
if listHeader {
|
tty := stdoutIsTerminal() && listFormat.String() == "table"
|
||||||
|
|
||||||
|
if !listNoHeader {
|
||||||
tw.AppendHeader(headerRow(columns))
|
tw.AppendHeader(headerRow(columns))
|
||||||
|
if tty {
|
||||||
|
tw.Style().Color.Header = text.Colors{text.Bold}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
lay := computeLayout(columns, output, filtered)
|
||||||
|
|
||||||
for _, e := range filtered {
|
for _, e := range filtered {
|
||||||
var valueStr string
|
var valueStr string
|
||||||
|
dimValue := false
|
||||||
if showValues {
|
if showValues {
|
||||||
if e.Locked {
|
if e.Locked {
|
||||||
valueStr = "locked (identity file missing)"
|
valueStr = "locked (identity file missing)"
|
||||||
|
dimValue = true
|
||||||
} else {
|
} else {
|
||||||
valueStr = store.FormatBytes(listBase64, e.Value)
|
valueStr = store.FormatBytes(listBase64, e.Value)
|
||||||
|
if !utf8.Valid(e.Value) && !listBase64 {
|
||||||
|
dimValue = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !listFull {
|
||||||
|
valueStr = summariseValue(valueStr, lay.value, tty)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
row := make(table.Row, 0, len(columns))
|
row := make(table.Row, 0, len(columns))
|
||||||
for _, col := range columns {
|
for _, col := range columns {
|
||||||
switch col {
|
switch col {
|
||||||
case columnKey:
|
case columnKey:
|
||||||
|
if tty {
|
||||||
|
row = append(row, text.Bold.Sprint(e.Key))
|
||||||
|
} else {
|
||||||
row = append(row, e.Key)
|
row = append(row, e.Key)
|
||||||
|
}
|
||||||
case columnValue:
|
case columnValue:
|
||||||
|
if tty && dimValue {
|
||||||
|
row = append(row, dimStyle.Sprint(valueStr))
|
||||||
|
} else {
|
||||||
row = append(row, valueStr)
|
row = append(row, valueStr)
|
||||||
|
}
|
||||||
case columnTTL:
|
case columnTTL:
|
||||||
row = append(row, formatExpiry(e.ExpiresAt))
|
ttlStr := formatExpiry(e.ExpiresAt)
|
||||||
|
if tty && e.ExpiresAt == 0 {
|
||||||
|
ttlStr = dimStyle.Sprint(ttlStr)
|
||||||
|
}
|
||||||
|
row = append(row, ttlStr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tw.AppendRow(row)
|
tw.AppendRow(row)
|
||||||
}
|
}
|
||||||
|
|
||||||
applyColumnWidths(tw, columns, output)
|
applyColumnWidths(tw, columns, output, lay, listFull)
|
||||||
renderTable(tw)
|
renderTable(tw)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// summariseValue flattens a value to its first line and, when maxWidth > 0,
|
||||||
|
// truncates to fit. In both cases it appends "(..N more chars)" showing the
|
||||||
|
// total number of omitted characters.
|
||||||
|
func summariseValue(s string, maxWidth int, tty bool) string {
|
||||||
|
first := s
|
||||||
|
if i := strings.IndexByte(s, '\n'); i >= 0 {
|
||||||
|
first = s[:i]
|
||||||
|
}
|
||||||
|
|
||||||
|
totalRunes := utf8.RuneCountInString(s)
|
||||||
|
firstRunes := utf8.RuneCountInString(first)
|
||||||
|
|
||||||
|
// Nothing omitted and fits (or no width constraint).
|
||||||
|
if firstRunes == totalRunes && (maxWidth <= 0 || firstRunes <= maxWidth) {
|
||||||
|
return first
|
||||||
|
}
|
||||||
|
|
||||||
|
// How many runes of first can we show?
|
||||||
|
showRunes := firstRunes
|
||||||
|
if maxWidth > 0 && showRunes > maxWidth {
|
||||||
|
showRunes = maxWidth
|
||||||
|
}
|
||||||
|
|
||||||
|
style := func(s string) string {
|
||||||
|
if tty {
|
||||||
|
return dimStyle.Sprint(s)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iteratively make room for the suffix (at most two passes since
|
||||||
|
// the digit count can change by one at a boundary like 9→10).
|
||||||
|
for range 2 {
|
||||||
|
omitted := totalRunes - showRunes
|
||||||
|
if omitted <= 0 {
|
||||||
|
return first
|
||||||
|
}
|
||||||
|
suffix := fmt.Sprintf(" (..%d more chars)", omitted)
|
||||||
|
suffixRunes := utf8.RuneCountInString(suffix)
|
||||||
|
if maxWidth <= 0 {
|
||||||
|
return first + style(suffix)
|
||||||
|
}
|
||||||
|
if showRunes+suffixRunes <= maxWidth {
|
||||||
|
runes := []rune(first)
|
||||||
|
if showRunes < len(runes) {
|
||||||
|
first = string(runes[:showRunes])
|
||||||
|
}
|
||||||
|
return first + style(suffix)
|
||||||
|
}
|
||||||
|
avail := maxWidth - suffixRunes
|
||||||
|
if avail <= 0 {
|
||||||
|
// Suffix alone exceeds maxWidth; fall through to hard trim.
|
||||||
|
break
|
||||||
|
}
|
||||||
|
showRunes = avail
|
||||||
|
}
|
||||||
|
|
||||||
|
// Column too narrow for the suffix — just truncate with an ellipsis.
|
||||||
|
if maxWidth >= 2 {
|
||||||
|
return text.Trim(first, maxWidth-1) + style("…")
|
||||||
|
}
|
||||||
|
return text.Trim(first, maxWidth)
|
||||||
|
}
|
||||||
|
|
||||||
func headerRow(columns []columnKind) table.Row {
|
func headerRow(columns []columnKind) table.Row {
|
||||||
row := make(table.Row, 0, len(columns))
|
row := make(table.Row, 0, len(columns))
|
||||||
for _, col := range columns {
|
for _, col := range columns {
|
||||||
|
|
@ -231,51 +328,95 @@ func headerRow(columns []columnKind) table.Row {
|
||||||
return row
|
return row
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyColumnWidths(tw table.Writer, columns []columnKind, out io.Writer) {
|
const (
|
||||||
|
keyColumnWidthCap = 30
|
||||||
|
ttlColumnWidthCap = 20
|
||||||
|
)
|
||||||
|
|
||||||
|
// columnLayout holds the resolved max widths for each column kind.
|
||||||
|
type columnLayout struct {
|
||||||
|
key, value, ttl int
|
||||||
|
}
|
||||||
|
|
||||||
|
// computeLayout derives column widths from the terminal size and actual
|
||||||
|
// content widths of the key/TTL columns (capped at fixed maximums). This
|
||||||
|
// avoids reserving 30+40 chars for key+TTL when the real content is narrower.
|
||||||
|
func computeLayout(columns []columnKind, out io.Writer, entries []Entry) columnLayout {
|
||||||
|
var lay columnLayout
|
||||||
|
termWidth := detectTerminalWidth(out)
|
||||||
|
|
||||||
|
// Scan entries for actual max key/TTL content widths.
|
||||||
|
for _, e := range entries {
|
||||||
|
if w := utf8.RuneCountInString(e.Key); w > lay.key {
|
||||||
|
lay.key = w
|
||||||
|
}
|
||||||
|
if w := utf8.RuneCountInString(formatExpiry(e.ExpiresAt)); w > lay.ttl {
|
||||||
|
lay.ttl = w
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if lay.key > keyColumnWidthCap {
|
||||||
|
lay.key = keyColumnWidthCap
|
||||||
|
}
|
||||||
|
if lay.ttl > ttlColumnWidthCap {
|
||||||
|
lay.ttl = ttlColumnWidthCap
|
||||||
|
}
|
||||||
|
|
||||||
|
if termWidth <= 0 {
|
||||||
|
return lay
|
||||||
|
}
|
||||||
|
|
||||||
|
padding := len(columns) * 2
|
||||||
|
available := termWidth - padding
|
||||||
|
if available < len(columns) {
|
||||||
|
return lay
|
||||||
|
}
|
||||||
|
|
||||||
|
// Give the value column whatever is left after key and TTL.
|
||||||
|
lay.value = available
|
||||||
|
for _, col := range columns {
|
||||||
|
switch col {
|
||||||
|
case columnKey:
|
||||||
|
lay.value -= lay.key
|
||||||
|
case columnTTL:
|
||||||
|
lay.value -= lay.ttl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if lay.value < 10 {
|
||||||
|
lay.value = 10
|
||||||
|
}
|
||||||
|
return lay
|
||||||
|
}
|
||||||
|
|
||||||
|
func applyColumnWidths(tw table.Writer, columns []columnKind, out io.Writer, lay columnLayout, full bool) {
|
||||||
termWidth := detectTerminalWidth(out)
|
termWidth := detectTerminalWidth(out)
|
||||||
if termWidth <= 0 {
|
if termWidth <= 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
tw.SetAllowedRowLength(termWidth)
|
tw.SetAllowedRowLength(termWidth)
|
||||||
|
|
||||||
// Padding per column: go-pretty's default is one space each side.
|
|
||||||
padding := len(columns) * 2
|
|
||||||
available := termWidth - padding
|
|
||||||
if available < len(columns) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Give key and TTL columns a fixed budget; value gets the rest.
|
|
||||||
const keyWidth = 30
|
|
||||||
const ttlWidth = 40
|
|
||||||
valueWidth := available
|
|
||||||
for _, col := range columns {
|
|
||||||
switch col {
|
|
||||||
case columnKey:
|
|
||||||
valueWidth -= keyWidth
|
|
||||||
case columnTTL:
|
|
||||||
valueWidth -= ttlWidth
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if valueWidth < 10 {
|
|
||||||
valueWidth = 10
|
|
||||||
}
|
|
||||||
|
|
||||||
var configs []table.ColumnConfig
|
var configs []table.ColumnConfig
|
||||||
for i, col := range columns {
|
for i, col := range columns {
|
||||||
var maxW int
|
var maxW int
|
||||||
|
var enforcer func(string, int) string
|
||||||
switch col {
|
switch col {
|
||||||
case columnKey:
|
case columnKey:
|
||||||
maxW = keyWidth
|
maxW = lay.key
|
||||||
|
enforcer = text.Trim
|
||||||
case columnValue:
|
case columnValue:
|
||||||
maxW = valueWidth
|
maxW = lay.value
|
||||||
|
if full {
|
||||||
|
enforcer = text.WrapText
|
||||||
|
}
|
||||||
|
// When !full, values are already pre-truncated by
|
||||||
|
// summariseValue — no enforcer needed.
|
||||||
case columnTTL:
|
case columnTTL:
|
||||||
maxW = ttlWidth
|
maxW = lay.ttl
|
||||||
|
enforcer = text.Trim
|
||||||
}
|
}
|
||||||
configs = append(configs, table.ColumnConfig{
|
configs = append(configs, table.ColumnConfig{
|
||||||
Number: i + 1,
|
Number: i + 1,
|
||||||
WidthMax: maxW,
|
WidthMax: maxW,
|
||||||
WidthMaxEnforcer: text.WrapText,
|
WidthMaxEnforcer: enforcer,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
tw.SetColumnConfigs(configs)
|
tw.SetColumnConfigs(configs)
|
||||||
|
|
@ -318,8 +459,9 @@ func init() {
|
||||||
listCmd.Flags().BoolVarP(&listBase64, "base64", "b", false, "view binary data as base64")
|
listCmd.Flags().BoolVarP(&listBase64, "base64", "b", false, "view binary data as base64")
|
||||||
listCmd.Flags().BoolVar(&listNoKeys, "no-keys", false, "suppress the key column")
|
listCmd.Flags().BoolVar(&listNoKeys, "no-keys", false, "suppress the key column")
|
||||||
listCmd.Flags().BoolVar(&listNoValues, "no-values", false, "suppress the value column")
|
listCmd.Flags().BoolVar(&listNoValues, "no-values", false, "suppress the value column")
|
||||||
listCmd.Flags().BoolVarP(&listTTL, "ttl", "t", false, "append a TTL column when entries expire")
|
listCmd.Flags().BoolVar(&listNoTTL, "no-ttl", false, "suppress the TTL column")
|
||||||
listCmd.Flags().BoolVar(&listHeader, "header", false, "include header row")
|
listCmd.Flags().BoolVarP(&listFull, "full", "f", false, "show full values without truncation")
|
||||||
|
listCmd.Flags().BoolVar(&listNoHeader, "no-header", false, "suppress the header row")
|
||||||
listCmd.Flags().VarP(&listFormat, "format", "o", "output format (table|tsv|csv|markdown|html|ndjson)")
|
listCmd.Flags().VarP(&listFormat, "format", "o", "output format (table|tsv|csv|markdown|html|ndjson)")
|
||||||
listCmd.Flags().StringSliceP("glob", "g", nil, "Filter keys with glob pattern (repeatable)")
|
listCmd.Flags().StringSliceP("glob", "g", nil, "Filter keys with glob pattern (repeatable)")
|
||||||
listCmd.Flags().String("glob-sep", "", fmt.Sprintf("Characters treated as separators for globbing (default '%s')", defaultGlobSeparatorsDisplay()))
|
listCmd.Flags().String("glob-sep", "", fmt.Sprintf("Characters treated as separators for globbing (default '%s')", defaultGlobSeparatorsDisplay()))
|
||||||
|
|
|
||||||
|
|
@ -262,14 +262,14 @@ func validateDBName(name string) error {
|
||||||
|
|
||||||
func formatExpiry(expiresAt uint64) string {
|
func formatExpiry(expiresAt uint64) string {
|
||||||
if expiresAt == 0 {
|
if expiresAt == 0 {
|
||||||
return "never"
|
return "no expiry"
|
||||||
}
|
}
|
||||||
expiry := time.Unix(int64(expiresAt), 0).UTC()
|
expiry := time.Unix(int64(expiresAt), 0).UTC()
|
||||||
remaining := time.Until(expiry)
|
remaining := time.Until(expiry)
|
||||||
if remaining <= 0 {
|
if remaining <= 0 {
|
||||||
return fmt.Sprintf("%s (expired)", expiry.Format(time.RFC3339))
|
return "expired"
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("%s (in %s)", expiry.Format(time.RFC3339), remaining.Round(time.Second))
|
return fmt.Sprintf("in %s", remaining.Round(time.Second))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keys returns all keys for the provided store name (or default if empty).
|
// Keys returns all keys for the provided store name (or default if empty).
|
||||||
|
|
|
||||||
10
testdata/help__list__ok.ct
vendored
10
testdata/help__list__ok.ct
vendored
|
|
@ -11,13 +11,14 @@ Aliases:
|
||||||
Flags:
|
Flags:
|
||||||
-b, --base64 view binary data as base64
|
-b, --base64 view binary data as base64
|
||||||
-o, --format format output format (table|tsv|csv|markdown|html|ndjson) (default table)
|
-o, --format format output format (table|tsv|csv|markdown|html|ndjson) (default table)
|
||||||
|
-f, --full show full values without truncation
|
||||||
-g, --glob strings Filter keys with glob pattern (repeatable)
|
-g, --glob strings Filter keys with glob pattern (repeatable)
|
||||||
--glob-sep string Characters treated as separators for globbing (default '/-_.@: ')
|
--glob-sep string Characters treated as separators for globbing (default '/-_.@: ')
|
||||||
--header include header row
|
|
||||||
-h, --help help for list
|
-h, --help help for list
|
||||||
|
--no-header suppress the header row
|
||||||
--no-keys suppress the key column
|
--no-keys suppress the key column
|
||||||
|
--no-ttl suppress the TTL column
|
||||||
--no-values suppress the value column
|
--no-values suppress the value column
|
||||||
-t, --ttl append a TTL column when entries expire
|
|
||||||
List the contents of a store
|
List the contents of a store
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
|
|
@ -29,10 +30,11 @@ Aliases:
|
||||||
Flags:
|
Flags:
|
||||||
-b, --base64 view binary data as base64
|
-b, --base64 view binary data as base64
|
||||||
-o, --format format output format (table|tsv|csv|markdown|html|ndjson) (default table)
|
-o, --format format output format (table|tsv|csv|markdown|html|ndjson) (default table)
|
||||||
|
-f, --full show full values without truncation
|
||||||
-g, --glob strings Filter keys with glob pattern (repeatable)
|
-g, --glob strings Filter keys with glob pattern (repeatable)
|
||||||
--glob-sep string Characters treated as separators for globbing (default '/-_.@: ')
|
--glob-sep string Characters treated as separators for globbing (default '/-_.@: ')
|
||||||
--header include header row
|
|
||||||
-h, --help help for list
|
-h, --help help for list
|
||||||
|
--no-header suppress the header row
|
||||||
--no-keys suppress the key column
|
--no-keys suppress the key column
|
||||||
|
--no-ttl suppress the TTL column
|
||||||
--no-values suppress the value column
|
--no-values suppress the value column
|
||||||
-t, --ttl append a TTL column when entries expire
|
|
||||||
|
|
|
||||||
8
testdata/list__glob__ok.ct
vendored
8
testdata/list__glob__ok.ct
vendored
|
|
@ -2,9 +2,11 @@ $ pda set a1@lg 1
|
||||||
$ pda set a2@lg 2
|
$ pda set a2@lg 2
|
||||||
$ pda set b1@lg 3
|
$ pda set b1@lg 3
|
||||||
$ pda ls lg --glob a* --format tsv
|
$ pda ls lg --glob a* --format tsv
|
||||||
a1 1
|
Key Value TTL
|
||||||
a2 2
|
a1 1 no expiry
|
||||||
|
a2 2 no expiry
|
||||||
$ pda ls lg --glob b* --format tsv
|
$ pda ls lg --glob b* --format tsv
|
||||||
b1 3
|
Key Value TTL
|
||||||
|
b1 3 no expiry
|
||||||
$ pda ls lg --glob c* --> FAIL
|
$ pda ls lg --glob c* --> FAIL
|
||||||
FAIL cannot ls '@lg': no matches for pattern 'c*'
|
FAIL cannot ls '@lg': no matches for pattern 'c*'
|
||||||
|
|
|
||||||
18
testdata/remove__dedupe__ok.ct
vendored
18
testdata/remove__dedupe__ok.ct
vendored
|
|
@ -1,15 +1,15 @@
|
||||||
$ pda set foo 1
|
$ pda set foo 1
|
||||||
$ pda set bar 2
|
$ pda set bar 2
|
||||||
$ pda ls
|
$ pda ls
|
||||||
a echo hello
|
KEY VALUE TTL
|
||||||
|
a echo hello (..1 more chars) no expiry
|
||||||
a1 1
|
a1 1 no expiry
|
||||||
a2 2
|
a2 2 no expiry
|
||||||
b1 3
|
b1 3 no expiry
|
||||||
bar 2
|
bar 2 no expiry
|
||||||
copied-key hidden-value
|
copied-key hidden-value no expiry
|
||||||
foo 1
|
foo 1 no expiry
|
||||||
moved-key hidden-value
|
moved-key hidden-value no expiry
|
||||||
$ pda rm foo --glob "*"
|
$ pda rm foo --glob "*"
|
||||||
$ pda get bar --> FAIL
|
$ pda get bar --> FAIL
|
||||||
FAIL cannot get 'bar': no such key
|
FAIL cannot get 'bar': no such key
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue