Compare commits
10 commits
940c3d694d
...
b614c97f42
| Author | SHA1 | Date | |
|---|---|---|---|
| b614c97f42 | |||
| 2f87f0fd98 | |||
| d71f00357e | |||
| d6e71cde12 | |||
| bc0b98c7f9 | |||
| 84b1c67c72 | |||
| cb135b7caa | |||
| c8f91e8d02 | |||
| 16b07df33e | |||
| 3923d20ae9 |
14 changed files with 391 additions and 498 deletions
83
cmd/completions.go
Normal file
83
cmd/completions.go
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// completeKeys returns key[@store] completions for the current toComplete prefix.
|
||||
// It handles three cases:
|
||||
// - No "@" typed yet: return all keys from all stores (as "key@store")
|
||||
// - "@" typed with partial store: return store-scoped completions
|
||||
// - "key@store" with known store: return keys from that store
|
||||
func completeKeys(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
store := &Store{}
|
||||
stores, err := store.AllStores()
|
||||
if err != nil || len(stores) == 0 {
|
||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
|
||||
var completions []string
|
||||
parts := strings.SplitN(toComplete, "@", 2)
|
||||
|
||||
if len(parts) == 2 {
|
||||
// User typed "something@" — complete keys within matching stores.
|
||||
prefix := parts[0]
|
||||
dbFilter := strings.ToLower(parts[1])
|
||||
for _, db := range stores {
|
||||
if !strings.HasPrefix(db, dbFilter) {
|
||||
continue
|
||||
}
|
||||
keys, err := store.Keys(db)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
for _, k := range keys {
|
||||
if prefix == "" || strings.HasPrefix(k, strings.ToLower(prefix)) {
|
||||
completions = append(completions, k+"@"+db)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// No "@" yet — offer key@store for every key in every store.
|
||||
lowerPrefix := strings.ToLower(toComplete)
|
||||
for _, db := range stores {
|
||||
keys, err := store.Keys(db)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
for _, k := range keys {
|
||||
full := k + "@" + db
|
||||
if strings.HasPrefix(full, lowerPrefix) || strings.HasPrefix(k, lowerPrefix) {
|
||||
completions = append(completions, full)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return completions, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
|
||||
// completeStores returns store name completions.
|
||||
func completeStores(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
store := &Store{}
|
||||
stores, err := store.AllStores()
|
||||
if err != nil {
|
||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
|
||||
var completions []string
|
||||
lowerPrefix := strings.ToLower(toComplete)
|
||||
for _, db := range stores {
|
||||
if strings.HasPrefix(db, lowerPrefix) {
|
||||
completions = append(completions, db)
|
||||
}
|
||||
}
|
||||
return completions, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
|
||||
// completeStoreFlag is a completion function for --store / -s string slice flags.
|
||||
func completeStoreFlag(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
return completeStores(cmd, args, toComplete)
|
||||
}
|
||||
|
|
@ -33,12 +33,13 @@ import (
|
|||
|
||||
// delStoreCmd represents the set command
|
||||
var delStoreCmd = &cobra.Command{
|
||||
Use: "remove-store STORE",
|
||||
Short: "Delete a store",
|
||||
Aliases: []string{"rms"},
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: delStore,
|
||||
SilenceUsage: true,
|
||||
Use: "remove-store STORE",
|
||||
Short: "Delete a store",
|
||||
Aliases: []string{"rms"},
|
||||
Args: cobra.ExactArgs(1),
|
||||
ValidArgsFunction: completeStores,
|
||||
RunE: delStore,
|
||||
SilenceUsage: true,
|
||||
}
|
||||
|
||||
func delStore(cmd *cobra.Command, args []string) error {
|
||||
|
|
|
|||
|
|
@ -34,9 +34,10 @@ import (
|
|||
var delCmd = &cobra.Command{
|
||||
Use: "remove KEY[@STORE] [KEY[@STORE] ...]",
|
||||
Short: "Delete one or more keys",
|
||||
Aliases: []string{"rm"},
|
||||
Args: cobra.ArbitraryArgs,
|
||||
RunE: del,
|
||||
Aliases: []string{"rm"},
|
||||
Args: cobra.ArbitraryArgs,
|
||||
ValidArgsFunction: completeKeys,
|
||||
RunE: del,
|
||||
SilenceUsage: true,
|
||||
}
|
||||
|
||||
|
|
@ -145,6 +146,7 @@ func init() {
|
|||
delCmd.Flags().Bool("force", false, "bypass read-only protection")
|
||||
delCmd.Flags().StringSliceP("key", "k", nil, "delete keys matching glob pattern (repeatable)")
|
||||
delCmd.Flags().StringSliceP("store", "s", nil, "target stores matching glob pattern (repeatable)")
|
||||
delCmd.RegisterFlagCompletionFunc("store", completeStoreFlag)
|
||||
delCmd.Flags().StringSliceP("value", "v", nil, "delete entries matching value glob pattern (repeatable)")
|
||||
rootCmd.AddCommand(delCmd)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,9 +22,10 @@ Binary values are presented as base64 for editing and decoded back on save.
|
|||
|
||||
Metadata flags (--ttl, --encrypt, --decrypt) can be passed alongside the edit
|
||||
to modify metadata in the same operation.`,
|
||||
Aliases: []string{"e"},
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: edit,
|
||||
Aliases: []string{"e"},
|
||||
Args: cobra.ExactArgs(1),
|
||||
ValidArgsFunction: completeKeys,
|
||||
RunE: edit,
|
||||
SilenceUsage: true,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ var exportCmd = &cobra.Command{
|
|||
Short: "Export store as NDJSON (alias for list --format ndjson)",
|
||||
Aliases: []string{},
|
||||
Args: cobra.MaximumNArgs(1),
|
||||
ValidArgsFunction: completeStores,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
listFormat = "ndjson"
|
||||
return list(cmd, args)
|
||||
|
|
@ -41,6 +42,7 @@ var exportCmd = &cobra.Command{
|
|||
func init() {
|
||||
exportCmd.Flags().StringSliceP("key", "k", nil, "filter keys with glob pattern (repeatable)")
|
||||
exportCmd.Flags().StringSliceP("store", "s", nil, "filter stores with glob pattern (repeatable)")
|
||||
exportCmd.RegisterFlagCompletionFunc("store", completeStoreFlag)
|
||||
exportCmd.Flags().StringSliceP("value", "v", nil, "filter values with glob pattern (repeatable)")
|
||||
rootCmd.AddCommand(exportCmd)
|
||||
}
|
||||
|
|
|
|||
12
cmd/get.go
12
cmd/get.go
|
|
@ -46,9 +46,10 @@ additional argument after the initial KEY being fetched.
|
|||
For example:
|
||||
pda set greeting 'Hello, {{ .NAME }}!'
|
||||
pda get greeting NAME=World`,
|
||||
Aliases: []string{"g"},
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
RunE: get,
|
||||
Aliases: []string{"g"},
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
ValidArgsFunction: completeKeys,
|
||||
RunE: get,
|
||||
SilenceUsage: true,
|
||||
}
|
||||
|
||||
|
|
@ -63,8 +64,9 @@ additional argument after the initial KEY being fetched.
|
|||
For example:
|
||||
pda set greeting 'Hello, {{ .NAME }}!'
|
||||
pda run greeting NAME=World`,
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
RunE: run,
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
ValidArgsFunction: completeKeys,
|
||||
RunE: run,
|
||||
SilenceUsage: true,
|
||||
}
|
||||
|
||||
|
|
|
|||
10
cmd/list.go
10
cmd/list.go
|
|
@ -141,10 +141,11 @@ glob pattern to filter by store name.
|
|||
Use --key/-k and --value/-v to filter by key or value glob, and --store/-s
|
||||
to filter by store name. All filters are repeatable and OR'd within the
|
||||
same flag.`,
|
||||
Aliases: []string{"ls"},
|
||||
Args: cobra.MaximumNArgs(1),
|
||||
RunE: list,
|
||||
SilenceUsage: true,
|
||||
Aliases: []string{"ls"},
|
||||
Args: cobra.MaximumNArgs(1),
|
||||
ValidArgsFunction: completeStores,
|
||||
RunE: list,
|
||||
SilenceUsage: true,
|
||||
}
|
||||
|
||||
func list(cmd *cobra.Command, args []string) error {
|
||||
|
|
@ -785,6 +786,7 @@ func init() {
|
|||
listCmd.Flags().VarP(&listFormat, "format", "o", "output format (table|tsv|csv|markdown|html|ndjson|json)")
|
||||
listCmd.Flags().StringSliceP("key", "k", nil, "filter keys with glob pattern (repeatable)")
|
||||
listCmd.Flags().StringSliceP("store", "s", nil, "filter stores with glob pattern (repeatable)")
|
||||
listCmd.RegisterFlagCompletionFunc("store", completeStoreFlag)
|
||||
listCmd.Flags().StringSliceP("value", "v", nil, "filter values with glob pattern (repeatable)")
|
||||
rootCmd.AddCommand(listCmd)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,8 +14,9 @@ var metaCmd = &cobra.Command{
|
|||
without changing its value.
|
||||
|
||||
With no flags, displays the key's current metadata. Pass flags to modify.`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: meta,
|
||||
Args: cobra.ExactArgs(1),
|
||||
ValidArgsFunction: completeKeys,
|
||||
RunE: meta,
|
||||
SilenceUsage: true,
|
||||
}
|
||||
|
||||
|
|
|
|||
13
cmd/mv-db.go
13
cmd/mv-db.go
|
|
@ -33,12 +33,13 @@ import (
|
|||
|
||||
// mvStoreCmd represents the move-store command
|
||||
var mvStoreCmd = &cobra.Command{
|
||||
Use: "move-store FROM TO",
|
||||
Short: "Rename a store",
|
||||
Aliases: []string{"mvs"},
|
||||
Args: cobra.ExactArgs(2),
|
||||
RunE: mvStore,
|
||||
SilenceUsage: true,
|
||||
Use: "move-store FROM TO",
|
||||
Short: "Rename a store",
|
||||
Aliases: []string{"mvs"},
|
||||
Args: cobra.ExactArgs(2),
|
||||
ValidArgsFunction: completeStores,
|
||||
RunE: mvStore,
|
||||
SilenceUsage: true,
|
||||
}
|
||||
|
||||
func mvStore(cmd *cobra.Command, args []string) error {
|
||||
|
|
|
|||
26
cmd/mv.go
26
cmd/mv.go
|
|
@ -30,21 +30,23 @@ import (
|
|||
)
|
||||
|
||||
var cpCmd = &cobra.Command{
|
||||
Use: "copy FROM[@STORE] TO[@STORE]",
|
||||
Aliases: []string{"cp"},
|
||||
Short: "Make a copy of a key",
|
||||
Args: cobra.ExactArgs(2),
|
||||
RunE: cp,
|
||||
SilenceUsage: true,
|
||||
Use: "copy FROM[@STORE] TO[@STORE]",
|
||||
Aliases: []string{"cp"},
|
||||
Short: "Make a copy of a key",
|
||||
Args: cobra.ExactArgs(2),
|
||||
ValidArgsFunction: completeKeys,
|
||||
RunE: cp,
|
||||
SilenceUsage: true,
|
||||
}
|
||||
|
||||
var mvCmd = &cobra.Command{
|
||||
Use: "move FROM[@STORE] TO[@STORE]",
|
||||
Aliases: []string{"mv"},
|
||||
Short: "Move a key",
|
||||
Args: cobra.ExactArgs(2),
|
||||
RunE: mv,
|
||||
SilenceUsage: true,
|
||||
Use: "move FROM[@STORE] TO[@STORE]",
|
||||
Aliases: []string{"mv"},
|
||||
Short: "Move a key",
|
||||
Args: cobra.ExactArgs(2),
|
||||
ValidArgsFunction: completeKeys,
|
||||
RunE: mv,
|
||||
SilenceUsage: true,
|
||||
}
|
||||
|
||||
func cp(cmd *cobra.Command, args []string) error {
|
||||
|
|
|
|||
|
|
@ -37,12 +37,13 @@ import (
|
|||
)
|
||||
|
||||
var restoreCmd = &cobra.Command{
|
||||
Use: "import [STORE]",
|
||||
Short: "Restore key/value pairs from an NDJSON dump",
|
||||
Aliases: []string{},
|
||||
Args: cobra.MaximumNArgs(1),
|
||||
RunE: restore,
|
||||
SilenceUsage: true,
|
||||
Use: "import [STORE]",
|
||||
Short: "Restore key/value pairs from an NDJSON dump",
|
||||
Aliases: []string{},
|
||||
Args: cobra.MaximumNArgs(1),
|
||||
ValidArgsFunction: completeStores,
|
||||
RunE: restore,
|
||||
SilenceUsage: true,
|
||||
}
|
||||
|
||||
func restore(cmd *cobra.Command, args []string) error {
|
||||
|
|
@ -323,6 +324,7 @@ 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().StringSliceP("store", "s", nil, "restore entries from stores matching glob pattern (repeatable)")
|
||||
restoreCmd.RegisterFlagCompletionFunc("store", completeStoreFlag)
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -50,9 +50,10 @@ For example:
|
|||
'Hello, {{ default "World" .NAME }}' will default to World if NAME is blank.
|
||||
'Hello, {{ require .NAME }}' will error if NAME is blank.
|
||||
'{{ enum .NAME "Alice" "Bob" }}' allows only NAME=Alice or NAME=Bob.`,
|
||||
Aliases: []string{"s"},
|
||||
Args: cobra.RangeArgs(1, 2),
|
||||
RunE: set,
|
||||
Aliases: []string{"s"},
|
||||
Args: cobra.RangeArgs(1, 2),
|
||||
ValidArgsFunction: completeKeys,
|
||||
RunE: set,
|
||||
SilenceUsage: true,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
version = "pda! 2025.52 Christmas release"
|
||||
version = "pda! 2026.14"
|
||||
)
|
||||
|
||||
// versionCmd represents the version command
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue