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)
|
||||
}
|
||||
|
|
@ -37,6 +37,7 @@ var delStoreCmd = &cobra.Command{
|
|||
Short: "Delete a store",
|
||||
Aliases: []string{"rms"},
|
||||
Args: cobra.ExactArgs(1),
|
||||
ValidArgsFunction: completeStores,
|
||||
RunE: delStore,
|
||||
SilenceUsage: true,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ var delCmd = &cobra.Command{
|
|||
Short: "Delete one or more keys",
|
||||
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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ 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),
|
||||
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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ For example:
|
|||
pda get greeting NAME=World`,
|
||||
Aliases: []string{"g"},
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
ValidArgsFunction: completeKeys,
|
||||
RunE: get,
|
||||
SilenceUsage: true,
|
||||
}
|
||||
|
|
@ -64,6 +65,7 @@ For example:
|
|||
pda set greeting 'Hello, {{ .NAME }}!'
|
||||
pda run greeting NAME=World`,
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
ValidArgsFunction: completeKeys,
|
||||
RunE: run,
|
||||
SilenceUsage: true,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -143,6 +143,7 @@ to filter by store name. All filters are repeatable and OR'd within the
|
|||
same flag.`,
|
||||
Aliases: []string{"ls"},
|
||||
Args: cobra.MaximumNArgs(1),
|
||||
ValidArgsFunction: completeStores,
|
||||
RunE: list,
|
||||
SilenceUsage: true,
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ without changing its value.
|
|||
|
||||
With no flags, displays the key's current metadata. Pass flags to modify.`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
ValidArgsFunction: completeKeys,
|
||||
RunE: meta,
|
||||
SilenceUsage: true,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ var mvStoreCmd = &cobra.Command{
|
|||
Short: "Rename a store",
|
||||
Aliases: []string{"mvs"},
|
||||
Args: cobra.ExactArgs(2),
|
||||
ValidArgsFunction: completeStores,
|
||||
RunE: mvStore,
|
||||
SilenceUsage: true,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ var cpCmd = &cobra.Command{
|
|||
Aliases: []string{"cp"},
|
||||
Short: "Make a copy of a key",
|
||||
Args: cobra.ExactArgs(2),
|
||||
ValidArgsFunction: completeKeys,
|
||||
RunE: cp,
|
||||
SilenceUsage: true,
|
||||
}
|
||||
|
|
@ -43,6 +44,7 @@ var mvCmd = &cobra.Command{
|
|||
Aliases: []string{"mv"},
|
||||
Short: "Move a key",
|
||||
Args: cobra.ExactArgs(2),
|
||||
ValidArgsFunction: completeKeys,
|
||||
RunE: mv,
|
||||
SilenceUsage: true,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ var restoreCmd = &cobra.Command{
|
|||
Short: "Restore key/value pairs from an NDJSON dump",
|
||||
Aliases: []string{},
|
||||
Args: cobra.MaximumNArgs(1),
|
||||
ValidArgsFunction: completeStores,
|
||||
RunE: restore,
|
||||
SilenceUsage: true,
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ For example:
|
|||
'{{ enum .NAME "Alice" "Bob" }}' allows only NAME=Alice or NAME=Bob.`,
|
||||
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