From 15c1d6733c9109a714e2b9e7f5ea44019aa79a53 Mon Sep 17 00:00:00 2001 From: lew Date: Wed, 11 Feb 2026 19:29:14 +0000 Subject: [PATCH] feat(lss): adds --no-header and --short flags, and lowercases all flag descriptions --- README.md | 8 +++- cmd/del-db.go | 4 +- cmd/del.go | 6 +-- cmd/export.go | 4 +- cmd/identity.go | 4 +- cmd/init.go | 2 +- cmd/list-dbs.go | 69 ++++++++++++++++++++++++++++++++++- cmd/list.go | 4 +- cmd/mv-db.go | 8 ++-- cmd/mv.go | 14 +++---- cmd/restore.go | 8 ++-- cmd/set.go | 10 ++--- cmd/shared.go | 20 +++++----- cmd/sync.go | 2 +- cmd/version.go | 2 +- testdata/help-export.ct | 8 ++-- testdata/help-import.ct | 16 ++++---- testdata/help-list-stores.ct | 8 +++- testdata/help-list.ct | 8 ++-- testdata/help-remove-store.ct | 8 ++-- testdata/help-remove.ct | 12 +++--- testdata/help-set.ct | 20 +++++----- 22 files changed, 161 insertions(+), 84 deletions(-) diff --git a/README.md b/README.md index cb10747..528cbd3 100644 --- a/README.md +++ b/README.md @@ -292,8 +292,14 @@ pda set alice@birthdays 11/11/1998 # See which stores have contents. pda list-stores -# @default +# Keys Size Store +# 2 1.8k @birthdays +# 12 4.2k @default + +# Just the names. +pda list-stores --short # @birthdays +# @default # Check out a specific store. pda ls @birthdays --no-header --no-ttl diff --git a/cmd/del-db.go b/cmd/del-db.go index de9040b..b1ccd2b 100644 --- a/cmd/del-db.go +++ b/cmd/del-db.go @@ -90,7 +90,7 @@ func executeDeletion(path string) error { } func init() { - 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("interactive", "i", false, "prompt yes/no for each deletion") + delStoreCmd.Flags().BoolP("yes", "y", false, "skip all confirmation prompts") rootCmd.AddCommand(delStoreCmd) } diff --git a/cmd/del.go b/cmd/del.go index b7ffefc..641e5e0 100644 --- a/cmd/del.go +++ b/cmd/del.go @@ -123,9 +123,9 @@ func del(cmd *cobra.Command, args []string) error { } func init() { - delCmd.Flags().BoolP("interactive", "i", false, "Prompt yes/no for each deletion") - delCmd.Flags().BoolP("yes", "y", false, "Skip all confirmation prompts") - delCmd.Flags().StringSliceP("key", "k", nil, "Delete keys matching glob pattern (repeatable)") + delCmd.Flags().BoolP("interactive", "i", false, "prompt yes/no for each deletion") + delCmd.Flags().BoolP("yes", "y", false, "skip all confirmation prompts") + delCmd.Flags().StringSliceP("key", "k", nil, "delete keys matching glob pattern (repeatable)") rootCmd.AddCommand(delCmd) } diff --git a/cmd/export.go b/cmd/export.go index 35f0a02..ce90a82 100644 --- a/cmd/export.go +++ b/cmd/export.go @@ -39,7 +39,7 @@ var exportCmd = &cobra.Command{ } func init() { - 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("key", "k", nil, "filter keys with glob pattern (repeatable)") + exportCmd.Flags().StringSliceP("value", "v", nil, "filter values with glob pattern (repeatable)") rootCmd.AddCommand(exportCmd) } diff --git a/cmd/identity.go b/cmd/identity.go index 4e13fcc..89e81a9 100644 --- a/cmd/identity.go +++ b/cmd/identity.go @@ -70,8 +70,8 @@ func identityRun(cmd *cobra.Command, args []string) error { } func init() { - 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("new", false, "generate a new identity (errors if one already exists)") + identityCmd.Flags().Bool("path", false, "print only the identity file path") identityCmd.MarkFlagsMutuallyExclusive("new", "path") rootCmd.AddCommand(identityCmd) } diff --git a/cmd/init.go b/cmd/init.go index bec7472..3b37815 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -40,7 +40,7 @@ var initCmd = &cobra.Command{ } 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) } diff --git a/cmd/list-dbs.go b/cmd/list-dbs.go index 9e897b8..15b48cd 100644 --- a/cmd/list-dbs.go +++ b/cmd/list-dbs.go @@ -24,6 +24,8 @@ package cmd import ( "fmt" + "os" + "github.com/spf13/cobra" ) @@ -43,12 +45,77 @@ func listStores(cmd *cobra.Command, args []string) error { if err != nil { 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 { + fmt.Println("@" + db) + } + 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 { - fmt.Println("@" + db) + 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() { + listStoresCmd.Flags().Bool("short", false, "only print store names") + listStoresCmd.Flags().Bool("no-header", false, "suppress the header row") rootCmd.AddCommand(listStoresCmd) } diff --git a/cmd/list.go b/cmd/list.go index 0cebe64..8c4539d 100644 --- a/cmd/list.go +++ b/cmd/list.go @@ -486,7 +486,7 @@ func init() { 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().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("key", "k", nil, "filter keys with glob pattern (repeatable)") + listCmd.Flags().StringSliceP("value", "v", nil, "filter values with glob pattern (repeatable)") rootCmd.AddCommand(listCmd) } diff --git a/cmd/mv-db.go b/cmd/mv-db.go index 76bc21c..22c3cdc 100644 --- a/cmd/mv-db.go +++ b/cmd/mv-db.go @@ -121,9 +121,9 @@ func mvStore(cmd *cobra.Command, args []string) error { } func init() { - mvStoreCmd.Flags().Bool("copy", false, "Copy instead of move (keeps source)") - mvStoreCmd.Flags().BoolP("interactive", "i", false, "Prompt before overwriting destination") - 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("copy", false, "copy instead of move (keeps source)") + mvStoreCmd.Flags().BoolP("interactive", "i", false, "prompt before overwriting destination") + mvStoreCmd.Flags().BoolP("yes", "y", false, "skip all confirmation prompts") + mvStoreCmd.Flags().Bool("safe", false, "do not overwrite if the destination store already exists") rootCmd.AddCommand(mvStoreCmd) } diff --git a/cmd/mv.go b/cmd/mv.go index 1900013..1d549db 100644 --- a/cmd/mv.go +++ b/cmd/mv.go @@ -191,13 +191,13 @@ func mvImpl(cmd *cobra.Command, args []string, keepSource bool) error { } func init() { - mvCmd.Flags().Bool("copy", false, "Copy instead of move (keeps source)") - mvCmd.Flags().BoolP("interactive", "i", false, "Prompt before overwriting destination") - 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("copy", false, "copy instead of move (keeps source)") + mvCmd.Flags().BoolP("interactive", "i", false, "prompt before overwriting destination") + mvCmd.Flags().BoolP("yes", "y", false, "skip all confirmation prompts") + mvCmd.Flags().Bool("safe", false, "do not overwrite if the destination already exists") rootCmd.AddCommand(mvCmd) - cpCmd.Flags().BoolP("interactive", "i", false, "Prompt before overwriting destination") - 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().BoolP("interactive", "i", false, "prompt before overwriting destination") + cpCmd.Flags().BoolP("yes", "y", false, "skip all confirmation prompts") + cpCmd.Flags().Bool("safe", false, "do not overwrite if the destination already exists") rootCmd.AddCommand(cpCmd) } diff --git a/cmd/restore.go b/cmd/restore.go index bc678de..aaec428 100644 --- a/cmd/restore.go +++ b/cmd/restore.go @@ -226,9 +226,9 @@ func restoreEntries(decoder *json.Decoder, storePath string, opts restoreOpts) ( } func init() { - 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().BoolP("interactive", "i", false, "Prompt before overwriting existing keys") - restoreCmd.Flags().Bool("drop", false, "Drop existing entries before restoring (full replace)") + 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().BoolP("interactive", "i", false, "prompt before overwriting existing keys") + restoreCmd.Flags().Bool("drop", false, "drop existing entries before restoring (full replace)") rootCmd.AddCommand(restoreCmd) } diff --git a/cmd/set.go b/cmd/set.go index cb7c0e4..2f1a7de 100644 --- a/cmd/set.go +++ b/cmd/set.go @@ -180,9 +180,9 @@ func set(cmd *cobra.Command, args []string) error { func init() { rootCmd.AddCommand(setCmd) - 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("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().StringP("file", "f", "", "Read value from a file") + 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("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().StringP("file", "f", "", "read value from a file") } diff --git a/cmd/shared.go b/cmd/shared.go index 517162b..4e986cc 100644 --- a/cmd/shared.go +++ b/cmd/shared.go @@ -86,19 +86,19 @@ func (s *Store) formatBytes(base64Flag bool, v []byte) string { func formatSize(n int) string { const ( - kb = 1024 - mb = 1024 * kb - gb = 1024 * mb + ki = 1024 + mi = 1024 * ki + gi = 1024 * mi ) switch { - case n < kb: - return fmt.Sprintf("%d B", n) - case n < mb: - return fmt.Sprintf("%.1f KB", float64(n)/float64(kb)) - case n < gb: - return fmt.Sprintf("%.1f MB", float64(n)/float64(mb)) + case n < ki: + return fmt.Sprintf("%d", n) + case n < mi: + return fmt.Sprintf("%.1fk", float64(n)/float64(ki)) + case n < gi: + return fmt.Sprintf("%.1fM", float64(n)/float64(mi)) default: - return fmt.Sprintf("%.1f GB", float64(n)/float64(gb)) + return fmt.Sprintf("%.1fG", float64(n)/float64(gi)) } } diff --git a/cmd/sync.go b/cmd/sync.go index de0c233..b775d4a 100644 --- a/cmd/sync.go +++ b/cmd/sync.go @@ -40,7 +40,7 @@ var syncCmd = &cobra.Command{ } 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) } diff --git a/cmd/version.go b/cmd/version.go index 710737a..8c46579 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -45,6 +45,6 @@ var versionCmd = &cobra.Command{ } 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) } diff --git a/testdata/help-export.ct b/testdata/help-export.ct index e22b81b..4bbbf8e 100644 --- a/testdata/help-export.ct +++ b/testdata/help-export.ct @@ -7,8 +7,8 @@ Usage: Flags: -h, --help help for export - -k, --key strings Filter keys with glob pattern (repeatable) - -v, --value strings Filter values with regex pattern (repeatable) + -k, --key strings filter keys with glob pattern (repeatable) + -v, --value strings filter values with glob pattern (repeatable) Export store as NDJSON (alias for list --format ndjson) Usage: @@ -16,5 +16,5 @@ Usage: Flags: -h, --help help for export - -k, --key strings Filter keys with glob pattern (repeatable) - -v, --value strings Filter values with regex pattern (repeatable) + -k, --key strings filter keys with glob pattern (repeatable) + -v, --value strings filter values with glob pattern (repeatable) diff --git a/testdata/help-import.ct b/testdata/help-import.ct index 9fb102a..2baf780 100644 --- a/testdata/help-import.ct +++ b/testdata/help-import.ct @@ -6,19 +6,19 @@ Usage: pda import [STORE] [flags] Flags: - --drop Drop existing entries before restoring (full replace) - -f, --file string Path to an NDJSON dump (defaults to stdin) + --drop drop existing entries before restoring (full replace) + -f, --file string path to an NDJSON dump (defaults to stdin) -h, --help help for import - -i, --interactive Prompt before overwriting existing keys - -k, --key strings Restore keys matching glob pattern (repeatable) + -i, --interactive prompt before overwriting existing keys + -k, --key strings restore keys matching glob pattern (repeatable) Restore key/value pairs from an NDJSON dump Usage: pda import [STORE] [flags] Flags: - --drop Drop existing entries before restoring (full replace) - -f, --file string Path to an NDJSON dump (defaults to stdin) + --drop drop existing entries before restoring (full replace) + -f, --file string path to an NDJSON dump (defaults to stdin) -h, --help help for import - -i, --interactive Prompt before overwriting existing keys - -k, --key strings Restore keys matching glob pattern (repeatable) + -i, --interactive prompt before overwriting existing keys + -k, --key strings restore keys matching glob pattern (repeatable) diff --git a/testdata/help-list-stores.ct b/testdata/help-list-stores.ct index 4781886..5e57786 100644 --- a/testdata/help-list-stores.ct +++ b/testdata/help-list-stores.ct @@ -9,7 +9,9 @@ Aliases: list-stores, lss 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 Usage: @@ -19,4 +21,6 @@ Aliases: list-stores, lss Flags: - -h, --help help for list-stores + -h, --help help for list-stores + --no-header suppress the header row + --short only print store names diff --git a/testdata/help-list.ct b/testdata/help-list.ct index ce888b0..80e64f5 100644 --- a/testdata/help-list.ct +++ b/testdata/help-list.ct @@ -14,12 +14,12 @@ Flags: -o, --format format output format (table|tsv|csv|markdown|html|ndjson) (default table) -f, --full show full values without truncation -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-keys suppress the key column --no-ttl suppress the TTL 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 Usage: @@ -34,9 +34,9 @@ Flags: -o, --format format output format (table|tsv|csv|markdown|html|ndjson) (default table) -f, --full show full values without truncation -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-keys suppress the key column --no-ttl suppress the TTL 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) diff --git a/testdata/help-remove-store.ct b/testdata/help-remove-store.ct index f60225c..d367770 100644 --- a/testdata/help-remove-store.ct +++ b/testdata/help-remove-store.ct @@ -10,8 +10,8 @@ Aliases: Flags: -h, --help help for remove-store - -i, --interactive Prompt yes/no for each deletion - -y, --yes Skip all confirmation prompts + -i, --interactive prompt yes/no for each deletion + -y, --yes skip all confirmation prompts Delete a store Usage: @@ -22,5 +22,5 @@ Aliases: Flags: -h, --help help for remove-store - -i, --interactive Prompt yes/no for each deletion - -y, --yes Skip all confirmation prompts + -i, --interactive prompt yes/no for each deletion + -y, --yes skip all confirmation prompts diff --git a/testdata/help-remove.ct b/testdata/help-remove.ct index 2a28005..124c491 100644 --- a/testdata/help-remove.ct +++ b/testdata/help-remove.ct @@ -10,9 +10,9 @@ Aliases: Flags: -h, --help help for remove - -i, --interactive Prompt yes/no for each deletion - -k, --key strings Delete keys matching glob pattern (repeatable) - -y, --yes Skip all confirmation prompts + -i, --interactive prompt yes/no for each deletion + -k, --key strings delete keys matching glob pattern (repeatable) + -y, --yes skip all confirmation prompts Delete one or more keys Usage: @@ -23,6 +23,6 @@ Aliases: Flags: -h, --help help for remove - -i, --interactive Prompt yes/no for each deletion - -k, --key strings Delete keys matching glob pattern (repeatable) - -y, --yes Skip all confirmation prompts + -i, --interactive prompt yes/no for each deletion + -k, --key strings delete keys matching glob pattern (repeatable) + -y, --yes skip all confirmation prompts diff --git a/testdata/help-set.ct b/testdata/help-set.ct index 330ea71..0d8ac57 100644 --- a/testdata/help-set.ct +++ b/testdata/help-set.ct @@ -21,12 +21,12 @@ Aliases: set, s Flags: - -e, --encrypt Encrypt the value at rest using age - -f, --file string Read value from a file + -e, --encrypt encrypt the value at rest using age + -f, --file string read value from a file -h, --help help for set - -i, --interactive Prompt before overwriting an existing key - --safe Do not overwrite if the key already exists - -t, --ttl duration Expire the key after the provided duration (e.g. 24h, 30m) + -i, --interactive prompt before overwriting an existing key + --safe do not overwrite if the key already exists + -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. Pass --encrypt to encrypt the value at rest using age. An identity file @@ -48,9 +48,9 @@ Aliases: set, s Flags: - -e, --encrypt Encrypt the value at rest using age - -f, --file string Read value from a file + -e, --encrypt encrypt the value at rest using age + -f, --file string read value from a file -h, --help help for set - -i, --interactive Prompt before overwriting an existing key - --safe Do not overwrite if the key already exists - -t, --ttl duration Expire the key after the provided duration (e.g. 24h, 30m) + -i, --interactive prompt before overwriting an existing key + --safe do not overwrite if the key already exists + -t, --ttl duration expire the key after the provided duration (e.g. 24h, 30m)