commit 1230f22142630dbebad63e5a0429873fd6c32853
parent 82d2b4542af6c2da9aa4ec0528ab574ad9d99642
Author: Sean Enck <sean@ttypty.com>
Date: Mon, 10 Oct 2022 20:28:03 -0400
allow glob based removal
Diffstat:
7 files changed, 86 insertions(+), 12 deletions(-)
diff --git a/cmd/main.go b/cmd/main.go
@@ -158,15 +158,19 @@ func run() *programError {
return newError("rm requires a single entry", errors.New("missing argument"))
}
deleting := args[2]
- existing, err := t.Get(deleting, backend.BlankValue)
+ postfixRemove := "y"
+ existings, err := t.MatchPath(deleting)
if err != nil {
return newError("unable to get entity to delete", err)
}
- if confirm("delete entry") {
- if err := t.Remove(existing); err != nil {
+
+ if len(existings) > 1 {
+ postfixRemove = "ies"
+ }
+ if confirm(fmt.Sprintf("delete entr%s", postfixRemove)) {
+ if err := t.RemoveAll(existings); err != nil {
return newError("unable to remove entry", err)
}
-
}
case "show", "clip":
if len(args) != 3 {
diff --git a/internal/backend/actions.go b/internal/backend/actions.go
@@ -217,18 +217,28 @@ func (t *Transaction) Insert(path, val string) error {
return t.Move(QueryEntity{Path: path, Value: val}, path)
}
-// Remove handles remove an element
+// Remove will remove a single entity
func (t *Transaction) Remove(entity *QueryEntity) error {
if entity == nil {
return errors.New("entity is empty/invalid")
}
- offset, title, err := splitComponents(entity.Path)
- if err != nil {
- return err
+ return t.RemoveAll([]QueryEntity{*entity})
+}
+
+// RemoveAll handles removing elements
+func (t *Transaction) RemoveAll(entities []QueryEntity) error {
+ if len(entities) == 0 {
+ return errors.New("no entities given")
}
return t.change(func(c Context) error {
- if ok := c.removeEntity(offset, title); !ok {
- return errors.New("failed to remove entity")
+ for _, entity := range entities {
+ offset, title, err := splitComponents(entity.Path)
+ if err != nil {
+ return err
+ }
+ if ok := c.removeEntity(offset, title); !ok {
+ return errors.New("failed to remove entity")
+ }
}
return nil
})
diff --git a/internal/backend/actions_test.go b/internal/backend/actions_test.go
@@ -184,6 +184,25 @@ func TestRemoves(t *testing.T) {
}
}
+func TestRemoveAlls(t *testing.T) {
+ if err := setup(t).RemoveAll(nil); err.Error() != "no entities given" {
+ t.Errorf("wrong error: %v", err)
+ }
+ if err := setup(t).RemoveAll([]backend.QueryEntity{}); err.Error() != "no entities given" {
+ t.Errorf("wrong error: %v", err)
+ }
+ setup(t)
+ for _, i := range []string{backend.NewPath("test", "test", "test1"), backend.NewPath("test", "test", "test2"), backend.NewPath("test", "test", "test3"), backend.NewPath("test", "test1", "test2"), backend.NewPath("test", "test1", "test5")} {
+ fullSetup(t, true).Insert(i, "pass")
+ }
+ if err := fullSetup(t, true).RemoveAll([]backend.QueryEntity{{Path: "test/test/test3"}, {Path: "test/test/test1"}}); err != nil {
+ t.Errorf("wrong error: %v", err)
+ }
+ if err := check(t, backend.NewPath("test", "test", "test2"), backend.NewPath("test", "test1", "test2"), backend.NewPath("test", "test1", "test5")); err != nil {
+ t.Errorf("invalid check: %v", err)
+ }
+}
+
func check(t *testing.T, checks ...string) error {
tr := fullSetup(t, true)
for _, c := range checks {
diff --git a/internal/backend/query.go b/internal/backend/query.go
@@ -11,6 +11,25 @@ import (
"github.com/tobischo/gokeepasslib/v3"
)
+// MatchPath will try to match 1 or more elements (more elements when globbing)
+func (t *Transaction) MatchPath(path string) ([]QueryEntity, error) {
+ if !strings.HasSuffix(path, isGlob) {
+ e, err := t.Get(path, BlankValue)
+ if err != nil {
+ return nil, err
+ }
+ if e == nil {
+ return nil, nil
+ }
+ return []QueryEntity{*e}, nil
+ }
+ prefix := strings.TrimSuffix(path, isGlob)
+ if strings.HasSuffix(prefix, pathSep) {
+ return nil, errors.New("invalid match criteria, too many path separators")
+ }
+ return t.QueryCallback(QueryOptions{Mode: PrefixMode, Criteria: prefix + pathSep, Values: BlankValue})
+}
+
// Get will request a singular entity
func (t *Transaction) Get(path string, mode ValueMode) (*QueryEntity, error) {
_, _, err := splitComponents(path)
@@ -71,6 +90,10 @@ func (t *Transaction) QueryCallback(args QueryOptions) ([]QueryEntity, error) {
if !strings.HasSuffix(path, args.Criteria) {
return
}
+ case PrefixMode:
+ if !strings.HasPrefix(path, args.Criteria) {
+ return
+ }
}
} else {
diff --git a/internal/backend/types.go b/internal/backend/types.go
@@ -49,6 +49,8 @@ const (
ExactMode
// SuffixMode will look for an entity ending in a specific value
SuffixMode
+ // PrefixMode allows for entities starting with a specific value
+ PrefixMode
)
const (
@@ -65,6 +67,7 @@ const (
titleKey = "Title"
passKey = "Password"
pathSep = "/"
+ isGlob = pathSep + "*"
)
var (
diff --git a/tests/expected.log b/tests/expected.log
@@ -33,9 +33,17 @@ test/k/totp:
hash:b6c44d5d8a75071d8e8a39df231b0b98584d1d42982b5cf230e44f94d9c48e2983e78955a54b70c0acb0428d6db7205101e332f950ffb6b6d643aa37287c6aa5
delete entry? (y/N)
delete entry? (y/N)
-delete entry? (y/N) unable to remove entry (entity is empty/invalid)
+delete entry? (y/N) unable to remove entry (no entities given)
keys/k/one2
keyx/d/e
delete entry? (y/N)
keys/k/one2
+
+
+
+keys/k/one2
+keys/k2/one
+keys/k2/one2
+delete entries? (y/N)
+keys/k/one2
diff --git a/tests/run.sh b/tests/run.sh
@@ -43,6 +43,13 @@ _run() {
yes 2>/dev/null | "$BIN/lb" rm keyx/d/e
echo
"$BIN/lb" ls
-}
+ echo "test2" | "$BIN/lb" insert keys/k2/one2
+ echo "test" | "$BIN/lb" insert keys/k2/one
+ echo
+ "$BIN/lb" ls
+ yes 2>/dev/null | "$BIN/lb" rm keys/k2/*
+ echo
+ "$BIN/lb" ls
+ }
_run 2>&1 | sed "s#$LOCKBOX_STORE##g" > $TESTS/actual.log