feat(lss): adds --no-header and --short flags, and lowercases all flag descriptions

This commit is contained in:
Lewis Wynne 2026-02-11 19:29:14 +00:00
parent 4e5064d07a
commit 15c1d6733c
22 changed files with 161 additions and 84 deletions

View file

@ -292,8 +292,14 @@ pda set alice@birthdays 11/11/1998
# See which stores have contents. # See which stores have contents.
pda list-stores pda list-stores
# @default # Keys Size Store
# 2 1.8k @birthdays
# 12 4.2k @default
# Just the names.
pda list-stores --short
# @birthdays # @birthdays
# @default
# Check out a specific store. # Check out a specific store.
pda ls @birthdays --no-header --no-ttl pda ls @birthdays --no-header --no-ttl

View file

@ -90,7 +90,7 @@ func executeDeletion(path string) error {
} }
func init() { func init() {
delStoreCmd.Flags().BoolP("interactive", "i", false, "Prompt yes/no for each deletion") delStoreCmd.Flags().BoolP("interactive", "i", false, "prompt yes/no for each deletion")
delStoreCmd.Flags().BoolP("yes", "y", false, "Skip all confirmation prompts") delStoreCmd.Flags().BoolP("yes", "y", false, "skip all confirmation prompts")
rootCmd.AddCommand(delStoreCmd) rootCmd.AddCommand(delStoreCmd)
} }

View file

@ -123,9 +123,9 @@ func del(cmd *cobra.Command, args []string) error {
} }
func init() { func init() {
delCmd.Flags().BoolP("interactive", "i", false, "Prompt yes/no for each deletion") delCmd.Flags().BoolP("interactive", "i", false, "prompt yes/no for each deletion")
delCmd.Flags().BoolP("yes", "y", false, "Skip all confirmation prompts") delCmd.Flags().BoolP("yes", "y", false, "skip all confirmation prompts")
delCmd.Flags().StringSliceP("key", "k", nil, "Delete keys matching glob pattern (repeatable)") delCmd.Flags().StringSliceP("key", "k", nil, "delete keys matching glob pattern (repeatable)")
rootCmd.AddCommand(delCmd) rootCmd.AddCommand(delCmd)
} }

View file

@ -39,7 +39,7 @@ var exportCmd = &cobra.Command{
} }
func init() { func init() {
exportCmd.Flags().StringSliceP("key", "k", nil, "Filter keys with glob pattern (repeatable)") exportCmd.Flags().StringSliceP("key", "k", nil, "filter keys with glob pattern (repeatable)")
exportCmd.Flags().StringSliceP("value", "v", nil, "Filter values with regex pattern (repeatable)") exportCmd.Flags().StringSliceP("value", "v", nil, "filter values with glob pattern (repeatable)")
rootCmd.AddCommand(exportCmd) rootCmd.AddCommand(exportCmd)
} }

View file

@ -70,8 +70,8 @@ func identityRun(cmd *cobra.Command, args []string) error {
} }
func init() { func init() {
identityCmd.Flags().Bool("new", false, "Generate a new identity (errors if one already exists)") identityCmd.Flags().Bool("new", false, "generate a new identity (errors if one already exists)")
identityCmd.Flags().Bool("path", false, "Print only the identity file path") identityCmd.Flags().Bool("path", false, "print only the identity file path")
identityCmd.MarkFlagsMutuallyExclusive("new", "path") identityCmd.MarkFlagsMutuallyExclusive("new", "path")
rootCmd.AddCommand(identityCmd) rootCmd.AddCommand(identityCmd)
} }

View file

@ -40,7 +40,7 @@ var initCmd = &cobra.Command{
} }
func init() { func init() {
initCmd.Flags().Bool("clean", false, "Remove .git from stores directory before initialising") initCmd.Flags().Bool("clean", false, "remove .git from stores directory before initialising")
rootCmd.AddCommand(initCmd) rootCmd.AddCommand(initCmd)
} }

View file

@ -24,6 +24,8 @@ package cmd
import ( import (
"fmt" "fmt"
"os"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -43,12 +45,77 @@ func listStores(cmd *cobra.Command, args []string) error {
if err != nil { if err != nil {
return fmt.Errorf("cannot list stores: %v", err) return fmt.Errorf("cannot list stores: %v", err)
} }
short, err := cmd.Flags().GetBool("short")
if err != nil {
return fmt.Errorf("cannot list stores: %v", err)
}
if short {
for _, db := range dbs { for _, db := range dbs {
fmt.Println("@" + db) fmt.Println("@" + db)
} }
return nil return nil
}
type storeInfo struct {
name string
keys int
size string
}
rows := make([]storeInfo, 0, len(dbs))
nameW, keysW, sizeW := len("Store"), len("Keys"), len("Size")
for _, db := range dbs {
p, err := store.storePath(db)
if err != nil {
return fmt.Errorf("cannot list stores: %v", err)
}
fi, err := os.Stat(p)
if err != nil {
return fmt.Errorf("cannot list stores: %v", err)
}
entries, err := readStoreFile(p, nil)
if err != nil {
return fmt.Errorf("cannot list stores: %v", err)
}
name := "@" + db
keysStr := fmt.Sprintf("%d", len(entries))
sizeStr := formatSize(int(fi.Size()))
if len(name) > nameW {
nameW = len(name)
}
if len(keysStr) > keysW {
keysW = len(keysStr)
}
if len(sizeStr) > sizeW {
sizeW = len(sizeStr)
}
rows = append(rows, storeInfo{name: name, keys: len(entries), size: sizeStr})
}
underline := func(s string) string {
if stdoutIsTerminal() {
return "\033[4m" + s + "\033[0m"
}
return s
}
noHeader, _ := cmd.Flags().GetBool("no-header")
if !noHeader {
fmt.Printf("%*s%s %*s%s %s\n",
keysW-len("Keys"), "", underline("Keys"),
sizeW-len("Size"), "", underline("Size"),
underline("Store"))
}
for _, r := range rows {
fmt.Printf("%*d %*s %s\n", keysW, r.keys, sizeW, r.size, r.name)
}
return nil
} }
func init() { func init() {
listStoresCmd.Flags().Bool("short", false, "only print store names")
listStoresCmd.Flags().Bool("no-header", false, "suppress the header row")
rootCmd.AddCommand(listStoresCmd) rootCmd.AddCommand(listStoresCmd)
} }

View file

@ -486,7 +486,7 @@ func init() {
listCmd.Flags().BoolVarP(&listFull, "full", "f", false, "show full values without truncation") 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().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("key", "k", nil, "Filter keys with glob pattern (repeatable)") listCmd.Flags().StringSliceP("key", "k", nil, "filter keys with glob pattern (repeatable)")
listCmd.Flags().StringSliceP("value", "v", nil, "Filter values with regex pattern (repeatable)") listCmd.Flags().StringSliceP("value", "v", nil, "filter values with glob pattern (repeatable)")
rootCmd.AddCommand(listCmd) rootCmd.AddCommand(listCmd)
} }

View file

@ -121,9 +121,9 @@ func mvStore(cmd *cobra.Command, args []string) error {
} }
func init() { func init() {
mvStoreCmd.Flags().Bool("copy", false, "Copy instead of move (keeps source)") mvStoreCmd.Flags().Bool("copy", false, "copy instead of move (keeps source)")
mvStoreCmd.Flags().BoolP("interactive", "i", false, "Prompt before overwriting destination") mvStoreCmd.Flags().BoolP("interactive", "i", false, "prompt before overwriting destination")
mvStoreCmd.Flags().BoolP("yes", "y", false, "Skip all confirmation prompts") mvStoreCmd.Flags().BoolP("yes", "y", false, "skip all confirmation prompts")
mvStoreCmd.Flags().Bool("safe", false, "Do not overwrite if the destination store already exists") mvStoreCmd.Flags().Bool("safe", false, "do not overwrite if the destination store already exists")
rootCmd.AddCommand(mvStoreCmd) rootCmd.AddCommand(mvStoreCmd)
} }

View file

@ -191,13 +191,13 @@ func mvImpl(cmd *cobra.Command, args []string, keepSource bool) error {
} }
func init() { func init() {
mvCmd.Flags().Bool("copy", false, "Copy instead of move (keeps source)") mvCmd.Flags().Bool("copy", false, "copy instead of move (keeps source)")
mvCmd.Flags().BoolP("interactive", "i", false, "Prompt before overwriting destination") mvCmd.Flags().BoolP("interactive", "i", false, "prompt before overwriting destination")
mvCmd.Flags().BoolP("yes", "y", false, "Skip all confirmation prompts") mvCmd.Flags().BoolP("yes", "y", false, "skip all confirmation prompts")
mvCmd.Flags().Bool("safe", false, "Do not overwrite if the destination already exists") mvCmd.Flags().Bool("safe", false, "do not overwrite if the destination already exists")
rootCmd.AddCommand(mvCmd) rootCmd.AddCommand(mvCmd)
cpCmd.Flags().BoolP("interactive", "i", false, "Prompt before overwriting destination") cpCmd.Flags().BoolP("interactive", "i", false, "prompt before overwriting destination")
cpCmd.Flags().BoolP("yes", "y", false, "Skip all confirmation prompts") cpCmd.Flags().BoolP("yes", "y", false, "skip all confirmation prompts")
cpCmd.Flags().Bool("safe", false, "Do not overwrite if the destination already exists") cpCmd.Flags().Bool("safe", false, "do not overwrite if the destination already exists")
rootCmd.AddCommand(cpCmd) rootCmd.AddCommand(cpCmd)
} }

View file

@ -226,9 +226,9 @@ func restoreEntries(decoder *json.Decoder, storePath string, opts restoreOpts) (
} }
func init() { func init() {
restoreCmd.Flags().StringP("file", "f", "", "Path to an NDJSON dump (defaults to stdin)") restoreCmd.Flags().StringP("file", "f", "", "path to an NDJSON dump (defaults to stdin)")
restoreCmd.Flags().StringSliceP("key", "k", nil, "Restore keys matching glob pattern (repeatable)") restoreCmd.Flags().StringSliceP("key", "k", nil, "restore keys matching glob pattern (repeatable)")
restoreCmd.Flags().BoolP("interactive", "i", false, "Prompt before overwriting existing keys") restoreCmd.Flags().BoolP("interactive", "i", false, "prompt before overwriting existing keys")
restoreCmd.Flags().Bool("drop", false, "Drop existing entries before restoring (full replace)") restoreCmd.Flags().Bool("drop", false, "drop existing entries before restoring (full replace)")
rootCmd.AddCommand(restoreCmd) rootCmd.AddCommand(restoreCmd)
} }

View file

@ -180,9 +180,9 @@ func set(cmd *cobra.Command, args []string) error {
func init() { func init() {
rootCmd.AddCommand(setCmd) rootCmd.AddCommand(setCmd)
setCmd.Flags().DurationP("ttl", "t", 0, "Expire the key after the provided duration (e.g. 24h, 30m)") setCmd.Flags().DurationP("ttl", "t", 0, "expire the key after the provided duration (e.g. 24h, 30m)")
setCmd.Flags().BoolP("interactive", "i", false, "Prompt before overwriting an existing key") setCmd.Flags().BoolP("interactive", "i", false, "prompt before overwriting an existing key")
setCmd.Flags().BoolP("encrypt", "e", false, "Encrypt the value at rest using age") setCmd.Flags().BoolP("encrypt", "e", false, "encrypt the value at rest using age")
setCmd.Flags().Bool("safe", false, "Do not overwrite if the key already exists") setCmd.Flags().Bool("safe", false, "do not overwrite if the key already exists")
setCmd.Flags().StringP("file", "f", "", "Read value from a file") setCmd.Flags().StringP("file", "f", "", "read value from a file")
} }

View file

@ -86,19 +86,19 @@ func (s *Store) formatBytes(base64Flag bool, v []byte) string {
func formatSize(n int) string { func formatSize(n int) string {
const ( const (
kb = 1024 ki = 1024
mb = 1024 * kb mi = 1024 * ki
gb = 1024 * mb gi = 1024 * mi
) )
switch { switch {
case n < kb: case n < ki:
return fmt.Sprintf("%d B", n) return fmt.Sprintf("%d", n)
case n < mb: case n < mi:
return fmt.Sprintf("%.1f KB", float64(n)/float64(kb)) return fmt.Sprintf("%.1fk", float64(n)/float64(ki))
case n < gb: case n < gi:
return fmt.Sprintf("%.1f MB", float64(n)/float64(mb)) return fmt.Sprintf("%.1fM", float64(n)/float64(mi))
default: default:
return fmt.Sprintf("%.1f GB", float64(n)/float64(gb)) return fmt.Sprintf("%.1fG", float64(n)/float64(gi))
} }
} }

View file

@ -40,7 +40,7 @@ var syncCmd = &cobra.Command{
} }
func init() { func init() {
syncCmd.Flags().StringP("message", "m", "", "Custom commit message (defaults to timestamp)") syncCmd.Flags().StringP("message", "m", "", "custom commit message (defaults to timestamp)")
rootCmd.AddCommand(syncCmd) rootCmd.AddCommand(syncCmd)
} }

View file

@ -45,6 +45,6 @@ var versionCmd = &cobra.Command{
} }
func init() { func init() {
versionCmd.Flags().Bool("short", false, "Print only the version string") versionCmd.Flags().Bool("short", false, "print only the version string")
rootCmd.AddCommand(versionCmd) rootCmd.AddCommand(versionCmd)
} }

View file

@ -7,8 +7,8 @@ Usage:
Flags: Flags:
-h, --help help for export -h, --help help for export
-k, --key strings Filter keys with glob pattern (repeatable) -k, --key strings filter keys with glob pattern (repeatable)
-v, --value strings Filter values with regex pattern (repeatable) -v, --value strings filter values with glob pattern (repeatable)
Export store as NDJSON (alias for list --format ndjson) Export store as NDJSON (alias for list --format ndjson)
Usage: Usage:
@ -16,5 +16,5 @@ Usage:
Flags: Flags:
-h, --help help for export -h, --help help for export
-k, --key strings Filter keys with glob pattern (repeatable) -k, --key strings filter keys with glob pattern (repeatable)
-v, --value strings Filter values with regex pattern (repeatable) -v, --value strings filter values with glob pattern (repeatable)

View file

@ -6,19 +6,19 @@ Usage:
pda import [STORE] [flags] pda import [STORE] [flags]
Flags: Flags:
--drop Drop existing entries before restoring (full replace) --drop drop existing entries before restoring (full replace)
-f, --file string Path to an NDJSON dump (defaults to stdin) -f, --file string path to an NDJSON dump (defaults to stdin)
-h, --help help for import -h, --help help for import
-i, --interactive Prompt before overwriting existing keys -i, --interactive prompt before overwriting existing keys
-k, --key strings Restore keys matching glob pattern (repeatable) -k, --key strings restore keys matching glob pattern (repeatable)
Restore key/value pairs from an NDJSON dump Restore key/value pairs from an NDJSON dump
Usage: Usage:
pda import [STORE] [flags] pda import [STORE] [flags]
Flags: Flags:
--drop Drop existing entries before restoring (full replace) --drop drop existing entries before restoring (full replace)
-f, --file string Path to an NDJSON dump (defaults to stdin) -f, --file string path to an NDJSON dump (defaults to stdin)
-h, --help help for import -h, --help help for import
-i, --interactive Prompt before overwriting existing keys -i, --interactive prompt before overwriting existing keys
-k, --key strings Restore keys matching glob pattern (repeatable) -k, --key strings restore keys matching glob pattern (repeatable)

View file

@ -10,6 +10,8 @@ Aliases:
Flags: Flags:
-h, --help help for list-stores -h, --help help for list-stores
--no-header suppress the header row
--short only print store names
List all stores List all stores
Usage: Usage:
@ -20,3 +22,5 @@ Aliases:
Flags: Flags:
-h, --help help for list-stores -h, --help help for list-stores
--no-header suppress the header row
--short only print store names

View file

@ -14,12 +14,12 @@ Flags:
-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 -f, --full show full values without truncation
-h, --help help for list -h, --help help for list
-k, --key strings Filter keys with glob pattern (repeatable) -k, --key strings filter keys with glob pattern (repeatable)
--no-header suppress the header row --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-ttl suppress the TTL column
--no-values suppress the value column --no-values suppress the value column
-v, --value strings Filter values with regex pattern (repeatable) -v, --value strings filter values with glob pattern (repeatable)
List the contents of a store List the contents of a store
Usage: Usage:
@ -34,9 +34,9 @@ Flags:
-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 -f, --full show full values without truncation
-h, --help help for list -h, --help help for list
-k, --key strings Filter keys with glob pattern (repeatable) -k, --key strings filter keys with glob pattern (repeatable)
--no-header suppress the header row --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-ttl suppress the TTL column
--no-values suppress the value column --no-values suppress the value column
-v, --value strings Filter values with regex pattern (repeatable) -v, --value strings filter values with glob pattern (repeatable)

View file

@ -10,8 +10,8 @@ Aliases:
Flags: Flags:
-h, --help help for remove-store -h, --help help for remove-store
-i, --interactive Prompt yes/no for each deletion -i, --interactive prompt yes/no for each deletion
-y, --yes Skip all confirmation prompts -y, --yes skip all confirmation prompts
Delete a store Delete a store
Usage: Usage:
@ -22,5 +22,5 @@ Aliases:
Flags: Flags:
-h, --help help for remove-store -h, --help help for remove-store
-i, --interactive Prompt yes/no for each deletion -i, --interactive prompt yes/no for each deletion
-y, --yes Skip all confirmation prompts -y, --yes skip all confirmation prompts

View file

@ -10,9 +10,9 @@ Aliases:
Flags: Flags:
-h, --help help for remove -h, --help help for remove
-i, --interactive Prompt yes/no for each deletion -i, --interactive prompt yes/no for each deletion
-k, --key strings Delete keys matching glob pattern (repeatable) -k, --key strings delete keys matching glob pattern (repeatable)
-y, --yes Skip all confirmation prompts -y, --yes skip all confirmation prompts
Delete one or more keys Delete one or more keys
Usage: Usage:
@ -23,6 +23,6 @@ Aliases:
Flags: Flags:
-h, --help help for remove -h, --help help for remove
-i, --interactive Prompt yes/no for each deletion -i, --interactive prompt yes/no for each deletion
-k, --key strings Delete keys matching glob pattern (repeatable) -k, --key strings delete keys matching glob pattern (repeatable)
-y, --yes Skip all confirmation prompts -y, --yes skip all confirmation prompts

20
testdata/help-set.ct vendored
View file

@ -21,12 +21,12 @@ Aliases:
set, s set, s
Flags: Flags:
-e, --encrypt Encrypt the value at rest using age -e, --encrypt encrypt the value at rest using age
-f, --file string Read value from a file -f, --file string read value from a file
-h, --help help for set -h, --help help for set
-i, --interactive Prompt before overwriting an existing key -i, --interactive prompt before overwriting an existing key
--safe Do not overwrite if the key already exists --safe do not overwrite if the key already exists
-t, --ttl duration Expire the key after the provided duration (e.g. 24h, 30m) -t, --ttl duration expire the key after the provided duration (e.g. 24h, 30m)
Set a key to a given value or stdin. Optionally specify a store. Set a key to a given value or stdin. Optionally specify a store.
Pass --encrypt to encrypt the value at rest using age. An identity file Pass --encrypt to encrypt the value at rest using age. An identity file
@ -48,9 +48,9 @@ Aliases:
set, s set, s
Flags: Flags:
-e, --encrypt Encrypt the value at rest using age -e, --encrypt encrypt the value at rest using age
-f, --file string Read value from a file -f, --file string read value from a file
-h, --help help for set -h, --help help for set
-i, --interactive Prompt before overwriting an existing key -i, --interactive prompt before overwriting an existing key
--safe Do not overwrite if the key already exists --safe do not overwrite if the key already exists
-t, --ttl duration Expire the key after the provided duration (e.g. 24h, 30m) -t, --ttl duration expire the key after the provided duration (e.g. 24h, 30m)