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
|
// delStoreCmd represents the set command
|
||||||
var delStoreCmd = &cobra.Command{
|
var delStoreCmd = &cobra.Command{
|
||||||
Use: "remove-store STORE",
|
Use: "remove-store STORE",
|
||||||
Short: "Delete a store",
|
Short: "Delete a store",
|
||||||
Aliases: []string{"rms"},
|
Aliases: []string{"rms"},
|
||||||
Args: cobra.ExactArgs(1),
|
Args: cobra.ExactArgs(1),
|
||||||
RunE: delStore,
|
ValidArgsFunction: completeStores,
|
||||||
SilenceUsage: true,
|
RunE: delStore,
|
||||||
|
SilenceUsage: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
func delStore(cmd *cobra.Command, args []string) error {
|
func delStore(cmd *cobra.Command, args []string) error {
|
||||||
|
|
|
||||||
|
|
@ -34,9 +34,10 @@ import (
|
||||||
var delCmd = &cobra.Command{
|
var delCmd = &cobra.Command{
|
||||||
Use: "remove KEY[@STORE] [KEY[@STORE] ...]",
|
Use: "remove KEY[@STORE] [KEY[@STORE] ...]",
|
||||||
Short: "Delete one or more keys",
|
Short: "Delete one or more keys",
|
||||||
Aliases: []string{"rm"},
|
Aliases: []string{"rm"},
|
||||||
Args: cobra.ArbitraryArgs,
|
Args: cobra.ArbitraryArgs,
|
||||||
RunE: del,
|
ValidArgsFunction: completeKeys,
|
||||||
|
RunE: del,
|
||||||
SilenceUsage: true,
|
SilenceUsage: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -145,6 +146,7 @@ func init() {
|
||||||
delCmd.Flags().Bool("force", false, "bypass read-only protection")
|
delCmd.Flags().Bool("force", false, "bypass read-only protection")
|
||||||
delCmd.Flags().StringSliceP("key", "k", nil, "delete keys matching glob pattern (repeatable)")
|
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.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)")
|
delCmd.Flags().StringSliceP("value", "v", nil, "delete entries matching value glob pattern (repeatable)")
|
||||||
rootCmd.AddCommand(delCmd)
|
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
|
Metadata flags (--ttl, --encrypt, --decrypt) can be passed alongside the edit
|
||||||
to modify metadata in the same operation.`,
|
to modify metadata in the same operation.`,
|
||||||
Aliases: []string{"e"},
|
Aliases: []string{"e"},
|
||||||
Args: cobra.ExactArgs(1),
|
Args: cobra.ExactArgs(1),
|
||||||
RunE: edit,
|
ValidArgsFunction: completeKeys,
|
||||||
|
RunE: edit,
|
||||||
SilenceUsage: true,
|
SilenceUsage: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ var exportCmd = &cobra.Command{
|
||||||
Short: "Export store as NDJSON (alias for list --format ndjson)",
|
Short: "Export store as NDJSON (alias for list --format ndjson)",
|
||||||
Aliases: []string{},
|
Aliases: []string{},
|
||||||
Args: cobra.MaximumNArgs(1),
|
Args: cobra.MaximumNArgs(1),
|
||||||
|
ValidArgsFunction: completeStores,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
listFormat = "ndjson"
|
listFormat = "ndjson"
|
||||||
return list(cmd, args)
|
return list(cmd, args)
|
||||||
|
|
@ -41,6 +42,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("store", "s", nil, "filter stores 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)")
|
exportCmd.Flags().StringSliceP("value", "v", nil, "filter values with glob pattern (repeatable)")
|
||||||
rootCmd.AddCommand(exportCmd)
|
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:
|
For example:
|
||||||
pda set greeting 'Hello, {{ .NAME }}!'
|
pda set greeting 'Hello, {{ .NAME }}!'
|
||||||
pda get greeting NAME=World`,
|
pda get greeting NAME=World`,
|
||||||
Aliases: []string{"g"},
|
Aliases: []string{"g"},
|
||||||
Args: cobra.MinimumNArgs(1),
|
Args: cobra.MinimumNArgs(1),
|
||||||
RunE: get,
|
ValidArgsFunction: completeKeys,
|
||||||
|
RunE: get,
|
||||||
SilenceUsage: true,
|
SilenceUsage: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -63,8 +64,9 @@ additional argument after the initial KEY being fetched.
|
||||||
For example:
|
For example:
|
||||||
pda set greeting 'Hello, {{ .NAME }}!'
|
pda set greeting 'Hello, {{ .NAME }}!'
|
||||||
pda run greeting NAME=World`,
|
pda run greeting NAME=World`,
|
||||||
Args: cobra.MinimumNArgs(1),
|
Args: cobra.MinimumNArgs(1),
|
||||||
RunE: run,
|
ValidArgsFunction: completeKeys,
|
||||||
|
RunE: run,
|
||||||
SilenceUsage: true,
|
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
|
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
|
to filter by store name. All filters are repeatable and OR'd within the
|
||||||
same flag.`,
|
same flag.`,
|
||||||
Aliases: []string{"ls"},
|
Aliases: []string{"ls"},
|
||||||
Args: cobra.MaximumNArgs(1),
|
Args: cobra.MaximumNArgs(1),
|
||||||
RunE: list,
|
ValidArgsFunction: completeStores,
|
||||||
SilenceUsage: true,
|
RunE: list,
|
||||||
|
SilenceUsage: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
func list(cmd *cobra.Command, args []string) error {
|
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().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("key", "k", nil, "filter keys with glob pattern (repeatable)")
|
||||||
listCmd.Flags().StringSliceP("store", "s", nil, "filter stores 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)")
|
listCmd.Flags().StringSliceP("value", "v", nil, "filter values with glob pattern (repeatable)")
|
||||||
rootCmd.AddCommand(listCmd)
|
rootCmd.AddCommand(listCmd)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,9 @@ var metaCmd = &cobra.Command{
|
||||||
without changing its value.
|
without changing its value.
|
||||||
|
|
||||||
With no flags, displays the key's current metadata. Pass flags to modify.`,
|
With no flags, displays the key's current metadata. Pass flags to modify.`,
|
||||||
Args: cobra.ExactArgs(1),
|
Args: cobra.ExactArgs(1),
|
||||||
RunE: meta,
|
ValidArgsFunction: completeKeys,
|
||||||
|
RunE: meta,
|
||||||
SilenceUsage: true,
|
SilenceUsage: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
13
cmd/mv-db.go
13
cmd/mv-db.go
|
|
@ -33,12 +33,13 @@ import (
|
||||||
|
|
||||||
// mvStoreCmd represents the move-store command
|
// mvStoreCmd represents the move-store command
|
||||||
var mvStoreCmd = &cobra.Command{
|
var mvStoreCmd = &cobra.Command{
|
||||||
Use: "move-store FROM TO",
|
Use: "move-store FROM TO",
|
||||||
Short: "Rename a store",
|
Short: "Rename a store",
|
||||||
Aliases: []string{"mvs"},
|
Aliases: []string{"mvs"},
|
||||||
Args: cobra.ExactArgs(2),
|
Args: cobra.ExactArgs(2),
|
||||||
RunE: mvStore,
|
ValidArgsFunction: completeStores,
|
||||||
SilenceUsage: true,
|
RunE: mvStore,
|
||||||
|
SilenceUsage: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
func mvStore(cmd *cobra.Command, args []string) error {
|
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{
|
var cpCmd = &cobra.Command{
|
||||||
Use: "copy FROM[@STORE] TO[@STORE]",
|
Use: "copy FROM[@STORE] TO[@STORE]",
|
||||||
Aliases: []string{"cp"},
|
Aliases: []string{"cp"},
|
||||||
Short: "Make a copy of a key",
|
Short: "Make a copy of a key",
|
||||||
Args: cobra.ExactArgs(2),
|
Args: cobra.ExactArgs(2),
|
||||||
RunE: cp,
|
ValidArgsFunction: completeKeys,
|
||||||
SilenceUsage: true,
|
RunE: cp,
|
||||||
|
SilenceUsage: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
var mvCmd = &cobra.Command{
|
var mvCmd = &cobra.Command{
|
||||||
Use: "move FROM[@STORE] TO[@STORE]",
|
Use: "move FROM[@STORE] TO[@STORE]",
|
||||||
Aliases: []string{"mv"},
|
Aliases: []string{"mv"},
|
||||||
Short: "Move a key",
|
Short: "Move a key",
|
||||||
Args: cobra.ExactArgs(2),
|
Args: cobra.ExactArgs(2),
|
||||||
RunE: mv,
|
ValidArgsFunction: completeKeys,
|
||||||
SilenceUsage: true,
|
RunE: mv,
|
||||||
|
SilenceUsage: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
func cp(cmd *cobra.Command, args []string) error {
|
func cp(cmd *cobra.Command, args []string) error {
|
||||||
|
|
|
||||||
|
|
@ -37,12 +37,13 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var restoreCmd = &cobra.Command{
|
var restoreCmd = &cobra.Command{
|
||||||
Use: "import [STORE]",
|
Use: "import [STORE]",
|
||||||
Short: "Restore key/value pairs from an NDJSON dump",
|
Short: "Restore key/value pairs from an NDJSON dump",
|
||||||
Aliases: []string{},
|
Aliases: []string{},
|
||||||
Args: cobra.MaximumNArgs(1),
|
Args: cobra.MaximumNArgs(1),
|
||||||
RunE: restore,
|
ValidArgsFunction: completeStores,
|
||||||
SilenceUsage: true,
|
RunE: restore,
|
||||||
|
SilenceUsage: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
func restore(cmd *cobra.Command, args []string) error {
|
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().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().StringSliceP("store", "s", nil, "restore entries from stores 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().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)
|
||||||
|
|
|
||||||
|
|
@ -50,9 +50,10 @@ For example:
|
||||||
'Hello, {{ default "World" .NAME }}' will default to World if NAME is blank.
|
'Hello, {{ default "World" .NAME }}' will default to World if NAME is blank.
|
||||||
'Hello, {{ require .NAME }}' will error if NAME is blank.
|
'Hello, {{ require .NAME }}' will error if NAME is blank.
|
||||||
'{{ enum .NAME "Alice" "Bob" }}' allows only NAME=Alice or NAME=Bob.`,
|
'{{ enum .NAME "Alice" "Bob" }}' allows only NAME=Alice or NAME=Bob.`,
|
||||||
Aliases: []string{"s"},
|
Aliases: []string{"s"},
|
||||||
Args: cobra.RangeArgs(1, 2),
|
Args: cobra.RangeArgs(1, 2),
|
||||||
RunE: set,
|
ValidArgsFunction: completeKeys,
|
||||||
|
RunE: set,
|
||||||
SilenceUsage: true,
|
SilenceUsage: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
version = "pda! 2025.52 Christmas release"
|
version = "pda! 2026.14"
|
||||||
)
|
)
|
||||||
|
|
||||||
// versionCmd represents the version command
|
// versionCmd represents the version command
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue