commit de4e62f15af731ebe603b3924d567d71a88c5c2a
parent c1aae52bb7e1e929575bf049e0f9e5befd0cb69f
Author: Sean Enck <sean@ttypty.com>
Date: Sun, 2 Oct 2022 10:54:57 -0400
adding mv
Diffstat:
8 files changed, 104 insertions(+), 30 deletions(-)
diff --git a/cmd/main.go b/cmd/main.go
@@ -98,6 +98,28 @@ func run() *programError {
}
case "version":
fmt.Printf("version: %s\n", strings.TrimSpace(version))
+ case "mv":
+ if len(args) != 4 {
+ return newError("mv requires src and dst", errors.New("src/dst required"))
+ }
+ src := getEntry(args, 2)
+ dst := getEntry(args, 3)
+ srcExists, err := t.Get(src, backend.SecretValue)
+ if err != nil {
+ return newError("unable to get source object", errors.New("failed to get source"))
+ }
+ if srcExists == nil {
+ return newError("no source object found", errors.New("source object required"))
+ }
+ dstExists, err := t.Get(dst, backend.BlankValue)
+ if err != nil {
+ return newError("unable to get destination object", errors.New("failed to get destination"))
+ }
+ if dstExists != nil {
+ if !confirm("overwrite destination") {
+ return nil
+ }
+ }
case "insert":
options := cli.Arguments{}
idx := 2
@@ -132,7 +154,7 @@ func run() *programError {
return newError("invalid input", err)
}
p := strings.TrimSpace(string(password))
- if err := t.Insert(entry, p, len(strings.Split(p, "\n")) > 1); err != nil {
+ if err := t.Insert(entry, p); err != nil {
return newError("failed to insert", err)
}
fmt.Println("")
diff --git a/cmd/vers.txt b/cmd/vers.txt
@@ -1 +1 @@
-v22.10.02
+v22.10.03
diff --git a/contrib/completions.bash b/contrib/completions.bash
@@ -18,7 +18,7 @@ _lb() {
fi
cur=${COMP_WORDS[COMP_CWORD]}
if [ "$COMP_CWORD" -eq 1 ]; then
- opts="version ls show insert rm totp find$clip_enabled"
+ opts="version ls show insert rm totp mv find$clip_enabled"
# shellcheck disable=SC2207
COMPREPLY=( $(compgen -W "$opts" -- "$cur") )
else
@@ -27,6 +27,9 @@ _lb() {
"insert")
opts="-multi $(lb ls)"
;;
+ "mv")
+ opts=$(lb ls)
+ ;;
"totp")
opts="-once -short "$(lb totp -list)
if [ -n "$clip_enabled" ]; then
@@ -52,6 +55,9 @@ _lb() {
opts="-multi"
fi
;;
+ "mv")
+ opts=$(lb ls)
+ ;;
"totp")
needs=0
if [ "${COMP_WORDS[2]}" == "-once" ] || [ "${COMP_WORDS[2]}" == "-short" ]; then
diff --git a/internal/backend/actions.go b/internal/backend/actions.go
@@ -156,33 +156,47 @@ func splitComponents(path string) ([]string, string, error) {
return parts, title, nil
}
-// Insert handles inserting a new element
-func (t *Transaction) Insert(path, val string, multi bool) error {
- if strings.TrimSpace(path) == "" {
+// Move will move a src object to a dst location
+func (t *Transaction) Move(src QueryEntity, dst string) error {
+ if strings.TrimSpace(src.Path) == "" {
return errors.New("empty path not allowed")
}
- if strings.TrimSpace(val) == "" {
+ if strings.TrimSpace(src.Value) == "" {
return errors.New("empty secret not allowed")
}
- offset, title, err := splitComponents(path)
+ dOffset, dTitle, err := splitComponents(dst)
if err != nil {
return err
}
+ sOffset, sTitle, err := splitComponents(src.Path)
+ if err != nil {
+ return err
+ }
+ isMove := dst != src.Path
+ multi := len(strings.Split(strings.TrimSpace(src.Value), "\n")) > 1
return t.change(func(c Context) error {
- c.removeEntity(offset, title)
+ c.removeEntity(sOffset, sTitle)
+ if isMove {
+ c.removeEntity(dOffset, dTitle)
+ }
e := gokeepasslib.NewEntry()
- e.Values = append(e.Values, value(titleKey, title))
+ e.Values = append(e.Values, value(titleKey, dTitle))
field := passKey
if multi {
field = notesKey
}
- e.Values = append(e.Values, protectedValue(field, val))
- c.insertEntity(offset, title, e)
+ e.Values = append(e.Values, protectedValue(field, src.Value))
+ c.insertEntity(dOffset, dTitle, e)
return nil
})
}
+// Insert is a move to the same location
+func (t *Transaction) Insert(path, val string) error {
+ return t.Move(QueryEntity{Path: path, Value: val}, path)
+}
+
// Remove handles remove an element
func (t *Transaction) Remove(entity *QueryEntity) error {
if entity == nil {
diff --git a/internal/backend/actions_test.go b/internal/backend/actions_test.go
@@ -29,31 +29,57 @@ func setup(t *testing.T) *backend.Transaction {
func TestBadAction(t *testing.T) {
tr := &backend.Transaction{}
- if err := tr.Insert("a/a/a", "a", false); err.Error() != "invalid transaction" {
+ if err := tr.Insert("a/a/a", "a"); err.Error() != "invalid transaction" {
t.Errorf("wrong error: %v", err)
}
}
+func TestMove(t *testing.T) {
+ setup(t)
+ fullSetup(t, true).Insert(filepath.Join("test", "test2", "test1"), "pass")
+ fullSetup(t, true).Insert(filepath.Join("test", "test2", "test3"), "pass")
+ if err := fullSetup(t, true).Move(backend.QueryEntity{Path: filepath.Join("test", "test2", "test3"), Value: "abc"}, filepath.Join("test1", "test2", "test3")); err != nil {
+ t.Errorf("no error: %v", err)
+ }
+ q, err := fullSetup(t, true).Get(filepath.Join("test1", "test2", "test3"), backend.SecretValue)
+ if err != nil {
+ t.Errorf("no error: %v", err)
+ }
+ if q.Value != "abc" {
+ t.Errorf("invalid retrieval")
+ }
+ if err := fullSetup(t, true).Move(backend.QueryEntity{Path: filepath.Join("test", "test2", "test1"), Value: "test"}, filepath.Join("test1", "test2", "test3")); err != nil {
+ t.Errorf("no error: %v", err)
+ }
+ q, err = fullSetup(t, true).Get(filepath.Join("test1", "test2", "test3"), backend.SecretValue)
+ if err != nil {
+ t.Errorf("no error: %v", err)
+ }
+ if q.Value != "test" {
+ t.Errorf("invalid retrieval")
+ }
+}
+
func TestInserts(t *testing.T) {
- if err := setup(t).Insert("", "", false); err.Error() != "empty path not allowed" {
+ if err := setup(t).Insert("", ""); err.Error() != "empty path not allowed" {
t.Errorf("wrong error: %v", err)
}
- if err := setup(t).Insert(filepath.Join("test", "offset"), "test", false); err.Error() != "invalid component path" {
+ if err := setup(t).Insert(filepath.Join("test", "offset"), "test"); err.Error() != "invalid component path" {
t.Errorf("wrong error: %v", err)
}
- if err := setup(t).Insert("test", "test", false); err.Error() != "invalid component path" {
+ if err := setup(t).Insert("test", "test"); err.Error() != "invalid component path" {
t.Errorf("wrong error: %v", err)
}
- if err := setup(t).Insert("a", "", false); err.Error() != "empty secret not allowed" {
+ if err := setup(t).Insert("a", ""); err.Error() != "empty secret not allowed" {
t.Errorf("wrong error: %v", err)
}
- if err := setup(t).Insert(filepath.Join("test", "offset", "value"), "pass", false); err != nil {
+ if err := setup(t).Insert(filepath.Join("test", "offset", "value"), "pass"); err != nil {
t.Errorf("no error: %v", err)
}
- if err := fullSetup(t, true).Insert(filepath.Join("test", "offset", "value"), "pass2", false); err != nil {
+ if err := fullSetup(t, true).Insert(filepath.Join("test", "offset", "value"), "pass2"); err != nil {
t.Errorf("wrong error: %v", err)
}
- if err := fullSetup(t, true).Insert(filepath.Join("test", "offset", "value2"), "pass", true); err != nil {
+ if err := fullSetup(t, true).Insert(filepath.Join("test", "offset", "value2"), "pass\npass"); err != nil {
t.Errorf("no error: %v", err)
}
q, err := fullSetup(t, true).Get(filepath.Join("test", "offset", "value"), backend.SecretValue)
@@ -67,7 +93,7 @@ func TestInserts(t *testing.T) {
if err != nil {
t.Errorf("no error: %v", err)
}
- if q.Value != "pass" {
+ if q.Value != "pass\npass" {
t.Errorf("invalid retrieval")
}
}
@@ -84,7 +110,7 @@ func TestRemoves(t *testing.T) {
}
setup(t)
for _, i := range []string{"test1", "test2"} {
- fullSetup(t, true).Insert(filepath.Join(i, i, i), "pass", false)
+ fullSetup(t, true).Insert(filepath.Join(i, i, i), "pass")
}
if err := fullSetup(t, true).Remove(&backend.QueryEntity{Path: filepath.Join("test1", "test1", "test1")}); err != nil {
t.Errorf("wrong error: %v", err)
@@ -97,7 +123,7 @@ func TestRemoves(t *testing.T) {
}
setup(t)
for _, i := range []string{filepath.Join("test", "test", "test1"), filepath.Join("test", "test", "test2"), filepath.Join("test", "test", "test3"), filepath.Join("test", "test1", "test2"), filepath.Join("test", "test1", "test5")} {
- fullSetup(t, true).Insert(i, "pass", false)
+ fullSetup(t, true).Insert(i, "pass")
}
if err := fullSetup(t, true).Remove(&backend.QueryEntity{Path: "test/test/test3"}); err != nil {
t.Errorf("wrong error: %v", err)
diff --git a/internal/backend/query_test.go b/internal/backend/query_test.go
@@ -9,10 +9,10 @@ import (
func setupInserts(t *testing.T) {
setup(t)
- fullSetup(t, true).Insert("test/test/abc", "tedst", false)
- fullSetup(t, true).Insert("test/test/abcx", "tedst", false)
- fullSetup(t, true).Insert("test/test/ab11c", "tdest", true)
- fullSetup(t, true).Insert("test/test/abc1ak", "atest", false)
+ fullSetup(t, true).Insert("test/test/abc", "tedst")
+ fullSetup(t, true).Insert("test/test/abcx", "tedst")
+ fullSetup(t, true).Insert("test/test/ab11c", "tdest\ntest")
+ fullSetup(t, true).Insert("test/test/abc1ak", "atest")
}
func TestGet(t *testing.T) {
@@ -50,7 +50,7 @@ func TestValueModes(t *testing.T) {
if err != nil {
t.Errorf("no error: %v", err)
}
- if q.Value != "tdest" {
+ if q.Value != "tdest\ntest" {
t.Errorf("invalid result value: %s", q.Value)
}
}
diff --git a/tests/expected.log b/tests/expected.log
@@ -29,5 +29,8 @@ hash:132ab0244293c495a027cec12d0050598616daca888449920fc652719be0987830827d069ef
test/k/totp:
hash:7ef183065ba70aaa417b87ea0a96b7e550a938a52440c640a07537f7794d8a89e50078eca6a7cbcfacabd97a2db06d11e82ddf7556ca909c4df9fc0d006013b1
delete entry? (y/N)
-delete entry? (y/N) delete entry? (y/N) unable to remove entry (entity is empty/invalid)
-delete entry? (y/N) keys/k/one2
+delete entry? (y/N)
+delete entry? (y/N) unable to remove entry (entity is empty/invalid)
+
+delete entry? (y/N)
+keys/k/one2
diff --git a/tests/run.sh b/tests/run.sh
@@ -31,8 +31,11 @@ _run() {
yes 2>/dev/null | "$BIN/lb" rm keys2/k/three
echo
yes 2>/dev/null | "$BIN/lb" rm test/k/totp
+ echo
yes 2>/dev/null | "$BIN/lb" rm test/k/one
+ echo
yes 2>/dev/null | "$BIN/lb" rm key/a/one
+ echo
"$BIN/lb" ls
}