feat: huge overhaul of messaging into FAIL, WARN, hint, ok, prompt, and progress types

This commit is contained in:
Lewis Wynne 2026-02-11 02:11:58 +00:00
parent 6ccd801c89
commit b52a5bfdb7
30 changed files with 192 additions and 96 deletions

View file

@ -45,29 +45,28 @@ func delStore(cmd *cobra.Command, args []string) error {
store := &Store{} store := &Store{}
dbName, err := store.parseDB(args[0], false) dbName, err := store.parseDB(args[0], false)
if err != nil { if err != nil {
return fmt.Errorf("cannot delete-store '%s': %v", args[0], err) return fmt.Errorf("cannot delete store '%s': %v", args[0], err)
} }
var notFound errNotFound var notFound errNotFound
path, err := store.FindStore(dbName) path, err := store.FindStore(dbName)
if errors.As(err, &notFound) { if errors.As(err, &notFound) {
return fmt.Errorf("cannot delete-store '%s': %v", dbName, err) return fmt.Errorf("cannot delete store '%s': %w", dbName, err)
} }
if err != nil { if err != nil {
return fmt.Errorf("cannot delete-store '%s': %v", dbName, err) return fmt.Errorf("cannot delete store '%s': %v", dbName, err)
} }
interactive, err := cmd.Flags().GetBool("interactive") interactive, err := cmd.Flags().GetBool("interactive")
if err != nil { if err != nil {
return fmt.Errorf("cannot delete-store '%s': %v", dbName, err) return fmt.Errorf("cannot delete store '%s': %v", dbName, err)
} }
if interactive || config.Store.AlwaysPromptDelete { if interactive || config.Store.AlwaysPromptDelete {
message := fmt.Sprintf("delete-store '%s': are you sure? (y/n)", args[0]) promptf("delete store '%s'? (y/n)", args[0])
fmt.Println(message)
var confirm string var confirm string
if _, err := fmt.Scanln(&confirm); err != nil { if err := scanln(&confirm); err != nil {
return fmt.Errorf("cannot delete-store '%s': %v", dbName, err) return fmt.Errorf("cannot delete store '%s': %v", dbName, err)
} }
if strings.ToLower(confirm) != "y" { if strings.ToLower(confirm) != "y" {
return nil return nil
@ -81,7 +80,7 @@ func delStore(cmd *cobra.Command, args []string) error {
func executeDeletion(path string) error { func executeDeletion(path string) error {
if err := os.Remove(path); err != nil { if err := os.Remove(path); err != nil {
return fmt.Errorf("cannot delete-store '%s': %v", path, err) return fmt.Errorf("cannot delete store '%s': %v", path, err)
} }
return nil return nil
} }

View file

@ -66,7 +66,7 @@ func del(cmd *cobra.Command, args []string) error {
} }
if len(targets) == 0 { if len(targets) == 0 {
return fmt.Errorf("cannot remove: No such key") return fmt.Errorf("cannot remove: no such key")
} }
// Group targets by store for batch deletes. // Group targets by store for batch deletes.
@ -78,9 +78,8 @@ func del(cmd *cobra.Command, args []string) error {
for _, target := range targets { for _, target := range targets {
if interactive || config.Key.AlwaysPromptDelete { if interactive || config.Key.AlwaysPromptDelete {
var confirm string var confirm string
message := fmt.Sprintf("remove %q: are you sure? (y/n)", target.display) promptf("remove '%s'? (y/n)", target.display)
fmt.Println(message) if err := scanln(&confirm); err != nil {
if _, err := fmt.Scanln(&confirm); err != nil {
return fmt.Errorf("cannot remove '%s': %v", target.full, err) return fmt.Errorf("cannot remove '%s': %v", target.full, err)
} }
if strings.ToLower(confirm) != "y" { if strings.ToLower(confirm) != "y" {
@ -111,7 +110,7 @@ func del(cmd *cobra.Command, args []string) error {
for _, t := range st.targets { for _, t := range st.targets {
idx := findEntry(entries, t.key) idx := findEntry(entries, t.key)
if idx < 0 { if idx < 0 {
return fmt.Errorf("cannot remove '%s': No such key", t.full) return fmt.Errorf("cannot remove '%s': no such key", t.full)
} }
entries = append(entries[:idx], entries[idx+1:]...) entries = append(entries[:idx], entries[idx+1:]...)
} }

View file

@ -90,7 +90,7 @@ func get(cmd *cobra.Command, args []string) error {
for i, e := range entries { for i, e := range entries {
keys[i] = e.Key keys[i] = e.Key
} }
return fmt.Errorf("cannot get '%s': %v", args[0], suggestKey(spec.Key, keys)) return fmt.Errorf("cannot get '%s': %w", args[0], suggestKey(spec.Key, keys))
} }
v := entries[idx].Value v := entries[idx].Value
@ -128,7 +128,7 @@ func applyTemplate(tplBytes []byte, substitutions []string) ([]byte, error) {
for _, s := range substitutions { for _, s := range substitutions {
parts := strings.SplitN(s, "=", 2) parts := strings.SplitN(s, "=", 2)
if len(parts) != 2 || parts[0] == "" { if len(parts) != 2 || parts[0] == "" {
fmt.Fprintf(os.Stderr, "invalid substitutions %q (expected KEY=VALUE)\n", s) warnf("invalid substitution '%s', expected KEY=VALUE", s)
continue continue
} }
key := parts[0] key := parts[0]
@ -159,13 +159,13 @@ func applyTemplate(tplBytes []byte, substitutions []string) ([]byte, error) {
if slices.Contains(allowed, s) { if slices.Contains(allowed, s) {
return s, nil return s, nil
} }
return "", fmt.Errorf("invalid value %q (allowed: %v)", s, allowed) return "", fmt.Errorf("invalid value '%s', allowed: %v", s, allowed)
}, },
"int": func(v any) (int, error) { "int": func(v any) (int, error) {
s := fmt.Sprint(v) s := fmt.Sprint(v)
i, err := strconv.Atoi(s) i, err := strconv.Atoi(s)
if err != nil { if err != nil {
return 0, fmt.Errorf("failed to convert to int: %w", err) return 0, fmt.Errorf("cannot convert to int: %w", err)
} }
return i, nil return i, nil
}, },

View file

@ -61,36 +61,36 @@ func vcsInit(cmd *cobra.Command, args []string) error {
if clean { if clean {
gitDir := filepath.Join(repoDir, ".git") gitDir := filepath.Join(repoDir, ".git")
if _, err := os.Stat(gitDir); err == nil { if _, err := os.Stat(gitDir); err == nil {
fmt.Printf("remove .git from '%s'? (y/n)\n", repoDir) promptf("remove .git from '%s'? (y/n)", repoDir)
var confirm string var confirm string
if _, err := fmt.Scanln(&confirm); err != nil { if err := scanln(&confirm); err != nil {
return fmt.Errorf("cannot clean git dir: %w", err) return fmt.Errorf("cannot init: %w", err)
} }
if strings.ToLower(confirm) != "y" { if strings.ToLower(confirm) != "y" {
return fmt.Errorf("aborted cleaning git dir") return fmt.Errorf("cannot init: aborted")
} }
if err := os.RemoveAll(gitDir); err != nil { if err := os.RemoveAll(gitDir); err != nil {
return fmt.Errorf("cannot clean git dir: %w", err) return fmt.Errorf("cannot init: %w", err)
} }
} }
if hasRemote { if hasRemote {
dbs, err := store.AllStores() dbs, err := store.AllStores()
if err == nil && len(dbs) > 0 { if err == nil && len(dbs) > 0 {
fmt.Printf("remove all existing stores and .gitignore? (required for clone) (y/n)\n") promptf("remove all existing stores and .gitignore, required for clone? (y/n)")
var confirm string var confirm string
if _, err := fmt.Scanln(&confirm); err != nil { if err := scanln(&confirm); err != nil {
return fmt.Errorf("cannot clean stores: %w", err) return fmt.Errorf("cannot init: %w", err)
} }
if strings.ToLower(confirm) != "y" { if strings.ToLower(confirm) != "y" {
return fmt.Errorf("aborted cleaning stores") return fmt.Errorf("cannot init: aborted")
} }
if err := wipeAllStores(store); err != nil { if err := wipeAllStores(store); err != nil {
return fmt.Errorf("cannot clean stores: %w", err) return fmt.Errorf("cannot init: %w", err)
} }
gi := filepath.Join(repoDir, ".gitignore") gi := filepath.Join(repoDir, ".gitignore")
if err := os.Remove(gi); err != nil && !os.IsNotExist(err) { if err := os.Remove(gi); err != nil && !os.IsNotExist(err) {
return fmt.Errorf("cannot remove .gitignore: %w", err) return fmt.Errorf("cannot init: %w", err)
} }
} }
} }
@ -98,7 +98,8 @@ func vcsInit(cmd *cobra.Command, args []string) error {
gitDir := filepath.Join(repoDir, ".git") gitDir := filepath.Join(repoDir, ".git")
if _, err := os.Stat(gitDir); err == nil { if _, err := os.Stat(gitDir); err == nil {
fmt.Println("vcs already initialised; use --clean to reinitialise") warnf("vcs already initialised")
printHint("use --clean to reinitialise")
return nil return nil
} }
@ -106,11 +107,11 @@ func vcsInit(cmd *cobra.Command, args []string) error {
// git clone requires the target directory to be empty // git clone requires the target directory to be empty
entries, err := os.ReadDir(repoDir) entries, err := os.ReadDir(repoDir)
if err == nil && len(entries) > 0 { if err == nil && len(entries) > 0 {
return fmt.Errorf("stores directory is not empty; use --clean with a remote to wipe and clone") return withHint(fmt.Errorf("cannot init: stores directory not empty"), "use --clean with a remote to wipe and clone")
} }
remote := args[0] remote := args[0]
fmt.Printf("running: git clone %s %s\n", remote, repoDir) progressf("git clone %s %s", remote, repoDir)
if err := runGit("", "clone", remote, repoDir); err != nil { if err := runGit("", "clone", remote, repoDir); err != nil {
return err return err
} }
@ -118,7 +119,7 @@ func vcsInit(cmd *cobra.Command, args []string) error {
if err := os.MkdirAll(repoDir, 0o750); err != nil { if err := os.MkdirAll(repoDir, 0o750); err != nil {
return err return err
} }
fmt.Printf("running: git init\n") progressf("git init")
if err := runGit(repoDir, "init"); err != nil { if err := runGit(repoDir, "init"); err != nil {
return err return err
} }

View file

@ -41,7 +41,7 @@ func listStores(cmd *cobra.Command, args []string) error {
store := &Store{} store := &Store{}
dbs, err := store.AllStores() dbs, err := store.AllStores()
if err != nil { if err != nil {
return fmt.Errorf("cannot list-stores: %v", err) return fmt.Errorf("cannot list stores: %v", err)
} }
for _, db := range dbs { for _, db := range dbs {
fmt.Println("@" + db) fmt.Println("@" + db)

View file

@ -47,7 +47,7 @@ func (e *formatEnum) Set(v string) error {
*e = formatEnum(v) *e = formatEnum(v)
return nil return nil
default: default:
return fmt.Errorf("must be one of \"table\", \"tsv\", \"csv\", \"html\", \"markdown\", or \"ndjson\"") return fmt.Errorf("must be one of 'table', 'tsv', 'csv', 'html', 'markdown', or 'ndjson'")
} }
} }
@ -91,7 +91,7 @@ func list(cmd *cobra.Command, args []string) error {
if _, err := store.FindStore(dbName); err != nil { if _, err := store.FindStore(dbName); err != nil {
var notFound errNotFound var notFound errNotFound
if errors.As(err, &notFound) { if errors.As(err, &notFound) {
return fmt.Errorf("cannot ls '%s': No such store", args[0]) return fmt.Errorf("cannot ls '%s': %w", args[0], err)
} }
return fmt.Errorf("cannot ls '%s': %v", args[0], err) return fmt.Errorf("cannot ls '%s': %v", args[0], err)
} }
@ -99,7 +99,7 @@ func list(cmd *cobra.Command, args []string) error {
} }
if listNoKeys && listNoValues && !listTTL { if listNoKeys && listNoValues && !listTTL {
return fmt.Errorf("cannot ls '%s': no columns selected; disable --no-keys/--no-values or pass --ttl", targetDB) return withHint(fmt.Errorf("cannot ls '%s': no columns selected", targetDB), "disable --no-keys/--no-values or pass --ttl")
} }
var columns []columnKind var columns []columnKind
@ -145,7 +145,7 @@ func list(cmd *cobra.Command, args []string) error {
} }
if len(matchers) > 0 && len(filtered) == 0 { if len(matchers) > 0 && len(filtered) == 0 {
return fmt.Errorf("cannot ls '%s': No matches for pattern %s", targetDB, formatGlobPatterns(globPatterns)) return fmt.Errorf("cannot ls '%s': no matches for pattern %s", targetDB, formatGlobPatterns(globPatterns))
} }
output := cmd.OutOrStdout() output := cmd.OutOrStdout()

95
cmd/msg.go Normal file
View file

@ -0,0 +1,95 @@
package cmd
import (
"errors"
"fmt"
"os"
"strings"
"golang.org/x/term"
)
// hinted wraps an error with an actionable hint shown on a separate line.
type hinted struct {
err error
hint string
}
func (h hinted) Error() string { return h.err.Error() }
func (h hinted) Unwrap() error { return h.err }
func withHint(err error, hint string) error {
return hinted{err: err, hint: hint}
}
func stderrIsTerminal() bool {
return term.IsTerminal(int(os.Stderr.Fd()))
}
func stdoutIsTerminal() bool {
return term.IsTerminal(int(os.Stdout.Fd()))
}
// keyword returns a right-aligned, colored keyword (color only on TTY).
//
// FAIL red (stderr)
// hint dim (stderr)
// WARN yellow (stderr)
// ok green (stderr)
// ? cyan (stdout)
// > dim (stdout)
func keyword(code, word string, tty bool) string {
padded := fmt.Sprintf("%4s", word)
if tty {
return fmt.Sprintf("\033[%sm%s\033[0m", code, padded)
}
return padded
}
func printError(err error) {
fmt.Fprintf(os.Stderr, "%s %s\n", keyword("31", "FAIL", stderrIsTerminal()), err)
}
func printHint(format string, args ...any) {
msg := fmt.Sprintf(format, args...)
fmt.Fprintf(os.Stderr, "%s %s\n", keyword("2", "hint", stderrIsTerminal()), msg)
}
func warnf(format string, args ...any) {
msg := fmt.Sprintf(format, args...)
fmt.Fprintf(os.Stderr, "%s %s\n", keyword("33", "WARN", stderrIsTerminal()), msg)
}
func okf(format string, args ...any) {
msg := fmt.Sprintf(format, args...)
fmt.Fprintf(os.Stderr, "%s %s\n", keyword("32", "ok", stderrIsTerminal()), msg)
}
func promptf(format string, args ...any) {
msg := fmt.Sprintf(format, args...)
fmt.Fprintf(os.Stdout, "%s %s\n", keyword("36", "???", stdoutIsTerminal()), msg)
}
func progressf(format string, args ...any) {
msg := fmt.Sprintf(format, args...)
fmt.Fprintf(os.Stdout, "%s %s\n", keyword("2", ">", stdoutIsTerminal()), msg)
}
func scanln(dest *string) error {
fmt.Fprintf(os.Stdout, "%s ", keyword("2", "==>", stdoutIsTerminal()))
_, err := fmt.Scanln(dest)
return err
}
// printErrorWithHints prints the error and any hints found in the error chain.
func printErrorWithHints(err error) {
printError(err)
var h hinted
if errors.As(err, &h) {
printHint("%s", h.hint)
}
var nf errNotFound
if errors.As(err, &nf) && len(nf.suggestions) > 0 {
printHint("did you mean '%s'?", strings.Join(nf.suggestions, "', '"))
}
}

View file

@ -84,7 +84,7 @@ func mvImpl(cmd *cobra.Command, args []string, keepSource bool) error {
} }
srcIdx := findEntry(srcEntries, fromSpec.Key) srcIdx := findEntry(srcEntries, fromSpec.Key)
if srcIdx < 0 { if srcIdx < 0 {
return fmt.Errorf("cannot move '%s': No such key", fromSpec.Key) return fmt.Errorf("cannot move '%s': no such key", fromSpec.Key)
} }
srcEntry := srcEntries[srcIdx] srcEntry := srcEntries[srcIdx]
@ -108,8 +108,8 @@ func mvImpl(cmd *cobra.Command, args []string, keepSource bool) error {
if promptOverwrite && dstIdx >= 0 { if promptOverwrite && dstIdx >= 0 {
var confirm string var confirm string
fmt.Printf("overwrite '%s'? (y/n)\n", toSpec.Display()) promptf("overwrite '%s'? (y/n)", toSpec.Display())
if _, err := fmt.Scanln(&confirm); err != nil { if err := scanln(&confirm); err != nil {
return fmt.Errorf("cannot move '%s': %v", fromSpec.Key, err) return fmt.Errorf("cannot move '%s': %v", fromSpec.Key, err)
} }
if strings.ToLower(confirm) != "y" { if strings.ToLower(confirm) != "y" {

View file

@ -116,7 +116,7 @@ func writeStoreFile(path string, entries []Entry) error {
je := encodeJsonEntry(e) je := encodeJsonEntry(e)
data, err := json.Marshal(je) data, err := json.Marshal(je)
if err != nil { if err != nil {
return fmt.Errorf("key %q: %w", e.Key, err) return fmt.Errorf("key '%s': %w", e.Key, err)
} }
w.Write(data) w.Write(data)
w.WriteByte('\n') w.WriteByte('\n')
@ -142,10 +142,10 @@ func decodeJsonEntry(je jsonEntry) (Entry, error) {
var err error var err error
value, err = base64.StdEncoding.DecodeString(je.Value) value, err = base64.StdEncoding.DecodeString(je.Value)
if err != nil { if err != nil {
return Entry{}, fmt.Errorf("decode base64 for %q: %w", je.Key, err) return Entry{}, fmt.Errorf("decode base64 for '%s': %w", je.Key, err)
} }
default: default:
return Entry{}, fmt.Errorf("unsupported encoding %q for %q", je.Encoding, je.Key) return Entry{}, fmt.Errorf("unsupported encoding '%s' for '%s'", je.Encoding, je.Key)
} }
var expiresAt uint64 var expiresAt uint64
if je.ExpiresAt != nil { if je.ExpiresAt != nil {

View file

@ -104,10 +104,10 @@ func restore(cmd *cobra.Command, args []string) error {
} }
if len(matchers) > 0 && restored == 0 { if len(matchers) > 0 && restored == 0 {
return fmt.Errorf("cannot restore '%s': No matches for pattern %s", displayTarget, formatGlobPatterns(globPatterns)) return fmt.Errorf("cannot restore '%s': no matches for pattern %s", displayTarget, formatGlobPatterns(globPatterns))
} }
fmt.Fprintf(cmd.ErrOrStderr(), "Restored %d entries into @%s\n", restored, dbName) okf("restored %d entries into @%s", restored, dbName)
return autoSync() return autoSync()
} }
@ -169,9 +169,9 @@ func restoreEntries(decoder *json.Decoder, storePath string, opts restoreOpts) (
idx := findEntry(existing, entry.Key) idx := findEntry(existing, entry.Key)
if opts.promptOverwrite && idx >= 0 { if opts.promptOverwrite && idx >= 0 {
fmt.Printf("overwrite '%s'? (y/n)\n", entry.Key) promptf("overwrite '%s'? (y/n)", entry.Key)
var confirm string var confirm string
if _, err := fmt.Scanln(&confirm); err != nil { if err := scanln(&confirm); err != nil {
return 0, fmt.Errorf("entry %d: %v", entryNo, err) return 0, fmt.Errorf("entry %d: %v", entryNo, err)
} }
if strings.ToLower(confirm) != "y" { if strings.ToLower(confirm) != "y" {

View file

@ -31,18 +31,20 @@ import (
// rootCmd represents the base command when called without any subcommands // rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{ var rootCmd = &cobra.Command{
Use: "pda", Use: "pda",
Short: "A key-value store tool", Short: "A key-value store tool",
Long: asciiArt, Long: asciiArt,
SilenceErrors: true, // we print errors ourselves
} }
func Execute() { func Execute() {
if configErr != nil { if configErr != nil {
fmt.Fprintln(os.Stderr, "failed to load config:", configErr) printError(fmt.Errorf("cannot load config: %v", configErr))
os.Exit(1) os.Exit(1)
} }
err := rootCmd.Execute() err := rootCmd.Execute()
if err != nil { if err != nil {
printErrorWithHints(err)
os.Exit(1) os.Exit(1)
} }
} }

View file

@ -93,9 +93,9 @@ func set(cmd *cobra.Command, args []string) error {
idx := findEntry(entries, spec.Key) idx := findEntry(entries, spec.Key)
if promptOverwrite && idx >= 0 { if promptOverwrite && idx >= 0 {
fmt.Printf("overwrite '%s'? (y/n)\n", spec.Display()) promptf("overwrite '%s'? (y/n)", spec.Display())
var confirm string var confirm string
if _, err := fmt.Scanln(&confirm); err != nil { if err := scanln(&confirm); err != nil {
return fmt.Errorf("cannot set '%s': %v", args[0], err) return fmt.Errorf("cannot set '%s': %v", args[0], err)
} }
if strings.ToLower(confirm) != "y" { if strings.ToLower(confirm) != "y" {

View file

@ -37,14 +37,12 @@ import (
) )
type errNotFound struct { type errNotFound struct {
what string // "key" or "store"
suggestions []string suggestions []string
} }
func (err errNotFound) Error() string { func (err errNotFound) Error() string {
if len(err.suggestions) == 0 { return fmt.Sprintf("no such %s", err.what)
return "No such key"
}
return fmt.Sprintf("No such key. Did you mean '%s'?", strings.Join(err.suggestions, ", "))
} }
type Store struct{} type Store struct{}
@ -129,7 +127,7 @@ func (s *Store) FindStore(k string) (string, error) {
if err != nil { if err != nil {
return "", err return "", err
} }
return "", errNotFound{suggestions} return "", errNotFound{what: "store", suggestions: suggestions}
} }
if statErr != nil { if statErr != nil {
return "", statErr return "", statErr
@ -205,7 +203,7 @@ func suggestKey(target string, keys []string) error {
suggestions = append(suggestions, k) suggestions = append(suggestions, k)
} }
} }
return errNotFound{suggestions} return errNotFound{what: "key", suggestions: suggestions}
} }
func ensureSubpath(base, target string) error { func ensureSubpath(base, target string) error {

View file

@ -67,12 +67,12 @@ func sync(manual bool) error {
return err return err
} }
} else if manual { } else if manual {
fmt.Println("no changes to commit") okf("no changes to commit")
} }
if remoteInfo.Ref == "" { if remoteInfo.Ref == "" {
if manual { if manual {
fmt.Println("no remote configured; skipping push") warnf("no remote configured, skipping push")
} }
return nil return nil
} }
@ -105,7 +105,7 @@ func sync(manual bool) error {
return pushRemote(repoDir, remoteInfo) return pushRemote(repoDir, remoteInfo)
} }
if manual { if manual {
fmt.Println("nothing to push") okf("nothing to push")
} }
} }

View file

@ -17,7 +17,7 @@ func ensureVCSInitialized() (string, error) {
} }
if _, err := os.Stat(filepath.Join(repoDir, ".git")); err != nil { if _, err := os.Stat(filepath.Join(repoDir, ".git")); err != nil {
if os.IsNotExist(err) { if os.IsNotExist(err) {
return "", fmt.Errorf("vcs repository not initialised; run 'pda init' first") return "", withHint(fmt.Errorf("vcs not initialised"), "run 'pda init' first")
} }
return "", err return "", err
} }
@ -43,7 +43,7 @@ func writeGitignore(repoDir string) error {
} }
return runGit(repoDir, "commit", "-m", "generated gitignore") return runGit(repoDir, "commit", "-m", "generated gitignore")
} }
fmt.Println("Existing .gitignore found.") okf("existing .gitignore found")
return nil return nil
} }
@ -195,7 +195,7 @@ func wipeAllStores(store *Store) error {
return err return err
} }
if err := os.Remove(p); err != nil && !os.IsNotExist(err) { if err := os.Remove(p); err != nil && !os.IsNotExist(err) {
return fmt.Errorf("remove store '%s': %w", db, err) return fmt.Errorf("cannot remove store '%s': %w", db, err)
} }
} }
return nil return nil

View file

@ -5,4 +5,4 @@ $ pda dump --glob a*
{"key":"a1","value":"1","encoding":"text"} {"key":"a1","value":"1","encoding":"text"}
{"key":"a2","value":"2","encoding":"text"} {"key":"a2","value":"2","encoding":"text"}
$ pda dump --glob c* --> FAIL $ pda dump --glob c* --> FAIL
Error: cannot ls '@default': No matches for pattern 'c*' FAIL cannot ls '@default': no matches for pattern 'c*'

View file

@ -1,2 +1,2 @@
$ pda get key@foo/bar --> FAIL $ pda get key@foo/bar --> FAIL
Error: cannot get 'key@foo/bar': bad store format, use STORE or @STORE FAIL cannot get 'key@foo/bar': bad store format, use STORE or @STORE

View file

@ -1,2 +1,2 @@
$ pda get foobar --> FAIL $ pda get foobar --> FAIL
Error: cannot get 'foobar': No such key FAIL cannot get 'foobar': no such key

View file

@ -5,10 +5,10 @@ $ pda get foobar --include-binary --run --secret --> FAIL
$ pda get foobar --run --> FAIL $ pda get foobar --run --> FAIL
$ pda get foobar --run --secret --> FAIL $ pda get foobar --run --secret --> FAIL
$ pda get foobar --secret --> FAIL $ pda get foobar --secret --> FAIL
Error: cannot get 'foobar': No such key FAIL cannot get 'foobar': no such key
Error: cannot get 'foobar': No such key FAIL cannot get 'foobar': no such key
Error: cannot get 'foobar': No such key FAIL cannot get 'foobar': no such key
Error: unknown flag: --secret FAIL unknown flag: --secret
Error: cannot get 'foobar': No such key FAIL cannot get 'foobar': no such key
Error: unknown flag: --secret FAIL unknown flag: --secret
Error: unknown flag: --secret FAIL unknown flag: --secret

View file

@ -1,3 +1,2 @@
$ pda invalidcmd --> FAIL $ pda invalidcmd --> FAIL
Error: unknown command "invalidcmd" for "pda" FAIL unknown command "invalidcmd" for "pda"
Run 'pda --help' for usage.

View file

@ -1,2 +1,2 @@
$ pda ls foo/bar --> FAIL $ pda ls foo/bar --> FAIL
Error: cannot ls 'foo/bar': cannot parse store: bad store format, use STORE or @STORE FAIL cannot ls 'foo/bar': cannot parse store: bad store format, use STORE or @STORE

View file

@ -7,4 +7,4 @@ a2 2
$ pda ls lg --glob b* --format tsv $ pda ls lg --glob b* --format tsv
b1 3 b1 3
$ pda ls lg --glob c* --> FAIL $ pda ls lg --glob c* --> FAIL
Error: cannot ls '@lg': No matches for pattern 'c*' FAIL cannot ls '@lg': no matches for pattern 'c*'

View file

@ -1,2 +1,2 @@
$ pda rms foo/bar --> FAIL $ pda rms foo/bar --> FAIL
Error: cannot delete-store 'foo/bar': cannot parse store: bad store format, use STORE or @STORE FAIL cannot delete store 'foo/bar': cannot parse store: bad store format, use STORE or @STORE

View file

@ -10,6 +10,6 @@ $ pda ls
foo 1 foo 1
$ pda rm foo --glob "*" $ pda rm foo --glob "*"
$ pda get bar --> FAIL $ pda get bar --> FAIL
Error: cannot get 'bar': No such key FAIL cannot get 'bar': no such key
$ pda get foo --> FAIL $ pda get foo --> FAIL
Error: cannot get 'foo': No such key FAIL cannot get 'foo': no such key

View file

@ -3,8 +3,8 @@ $ pda set bar1 2
$ pda set bar2 3 $ pda set bar2 3
$ pda rm foo --glob bar* $ pda rm foo --glob bar*
$ pda get foo --> FAIL $ pda get foo --> FAIL
Error: cannot get 'foo': No such key FAIL cannot get 'foo': no such key
$ pda get bar1 --> FAIL $ pda get bar1 --> FAIL
Error: cannot get 'bar1': No such key FAIL cannot get 'bar1': no such key
$ pda get bar2 --> FAIL $ pda get bar2 --> FAIL
Error: cannot get 'bar2': No such key FAIL cannot get 'bar2': no such key

View file

@ -3,8 +3,9 @@ $ pda set a2 2
$ pda set b1 3 $ pda set b1 3
$ pda rm --glob a* $ pda rm --glob a*
$ pda get a1 --> FAIL $ pda get a1 --> FAIL
Error: cannot get 'a1': No such key. Did you mean 'b1'? FAIL cannot get 'a1': no such key
hint did you mean 'b1'?
$ pda get a2 --> FAIL $ pda get a2 --> FAIL
Error: cannot get 'a2': No such key FAIL cannot get 'a2': no such key
$ pda get b1 $ pda get b1
3 3

View file

@ -2,6 +2,7 @@ $ pda set a 1
$ pda set b 2 $ pda set b 2
$ pda rm a b $ pda rm a b
$ pda get a --> FAIL $ pda get a --> FAIL
Error: cannot get 'a': No such key FAIL cannot get 'a': no such key
$ pda get b --> FAIL $ pda get b --> FAIL
Error: cannot get 'b': No such key. Did you mean 'b1'? FAIL cannot get 'b': no such key
hint did you mean 'b1'?

View file

@ -2,8 +2,8 @@ $ pda set existing keep-me
$ pda set other also-keep $ pda set other also-keep
$ fecho dumpfile {"key":"new","value":"hello","encoding":"text"} $ fecho dumpfile {"key":"new","value":"hello","encoding":"text"}
$ pda restore --drop --file dumpfile $ pda restore --drop --file dumpfile
Restored 1 entries into @default ok restored 1 entries into @default
$ pda get new $ pda get new
hello hello
$ pda get existing --> FAIL $ pda get existing --> FAIL
Error: cannot get 'existing': No such key FAIL cannot get 'existing': no such key

View file

@ -4,12 +4,13 @@ $ pda set b1 3
$ fecho dumpfile {"key":"a1","value":"1","encoding":"text"} {"key":"a2","value":"2","encoding":"text"} {"key":"b1","value":"3","encoding":"text"} $ fecho dumpfile {"key":"a1","value":"1","encoding":"text"} {"key":"a2","value":"2","encoding":"text"} {"key":"b1","value":"3","encoding":"text"}
$ pda rm a1 a2 b1 $ pda rm a1 a2 b1
$ pda restore --glob a* --file dumpfile $ pda restore --glob a* --file dumpfile
Restored 2 entries into @default ok restored 2 entries into @default
$ pda get a1 $ pda get a1
1 1
$ pda get a2 $ pda get a2
2 2
$ pda get b1 --> FAIL $ pda get b1 --> FAIL
Error: cannot get 'b1': No such key. Did you mean 'a1'? FAIL cannot get 'b1': no such key
hint did you mean 'a1'?
$ pda restore --glob c* --file dumpfile --> FAIL $ pda restore --glob c* --file dumpfile --> FAIL
Error: cannot restore '@default': No matches for pattern 'c*' FAIL cannot restore '@default': no matches for pattern 'c*'

View file

@ -1,2 +1,2 @@
$ pda set a b --ttl 3343r --> FAIL $ pda set a b --ttl 3343r --> FAIL
Error: invalid argument "3343r" for "-t, --ttl" flag: time: unknown unit "r" in duration "3343r" FAIL invalid argument "3343r" for "-t, --ttl" flag: time: unknown unit "r" in duration "3343r"