commit 8f09028fc5a53273584a2b7589f63ed33005a6a2
parent 8aae004bca6eb90abda3bfad32c5b7159da59d0b
Author: Sean Enck <sean@ttypty.com>
Date: Tue, 10 Jun 2025 10:27:19 -0400
globbing and listing filters are better for ls/conv
Diffstat:
7 files changed, 67 insertions(+), 10 deletions(-)
diff --git a/cmd/lb/main_test.go b/cmd/lb/main_test.go
@@ -222,13 +222,13 @@ func test(profile string) error {
r.run("", "ls */multiline/*")
r.run("", "ls url")
r.run("", "json")
- r.run("", "json '*/multiline'")
+ r.run("", "json 'multiline'")
r.section("totp")
r.logAppend("echo")
r.run("echo 5ae472abqdekjqykoyxk7hvc2leklq5n |", "insert test6/multiline/otp")
r.run(`printf "otpauth://totp/lbissuer:lbaccount?algorithm=SHA1&digits=6&issuer=lbissuer&period=30&secret=5ae472abqdekjqykoyxk7hvc2leklq5n" |`, "insert test10/key1/otp")
r.run("", "totp ls")
- r.run("", "totp ls '**/**/rooted/*")
+ r.run("", "totp ls deeper/rooted")
const grepTOTP = "| sed 's/^[[:space:]]*//g' | grep '^[0-9][0-9][0-9][0-9][0-9][0-9]$'"
r.run("", fmt.Sprintf("totp show test6/multiline/otp %s", grepTOTP))
r.run("", fmt.Sprintf("totp show test10/key1/otp %s", grepTOTP))
diff --git a/cmd/lb/tests/expected.log b/cmd/lb/tests/expected.log
@@ -61,6 +61,7 @@ test4/multiline/notes
test5/multiline/notes
test6/multiline/notes
test6/multiline/password
+test7/deeper/root/url
{
"test1/key1": {
"modtime": "XXXX-XX-XX",
@@ -126,6 +127,7 @@ totp
test10/key1/otp
test6/multiline/otp
test7/deeper/rooted/otp
+test7/deeper/rooted/otp
XXXXXX
XXXXXX
XXXXXX
diff --git a/internal/app/json_test.go b/internal/app/json_test.go
@@ -24,6 +24,22 @@ func TestJSON(t *testing.T) {
t.Error("no data")
}
m.buf = bytes.Buffer{}
+ m.args = []string{"test"}
+ if err := app.JSON(m); err != nil {
+ t.Errorf("invalid error: %v", err)
+ }
+ if m.buf.String() == "" || m.buf.String() == "{}\n" {
+ t.Error("no data")
+ }
+ m.buf = bytes.Buffer{}
+ m.args = []string{"test/test2"}
+ if err := app.JSON(m); err != nil {
+ t.Errorf("invalid error: %v", err)
+ }
+ if m.buf.String() == "" || m.buf.String() == "{}\n" {
+ t.Error("no data")
+ }
+ m.buf = bytes.Buffer{}
m.args = []string{"test/test2/*"}
if err := app.JSON(m); err != nil {
t.Errorf("invalid error: %v", err)
diff --git a/internal/app/list.go b/internal/app/list.go
@@ -88,5 +88,13 @@ func createFilter(filter string) (bool, func(string, string) (bool, error)) {
if filter == "" {
return false, nil
}
- return true, kdbx.Glob
+ parts := kdbx.SplitPath(filter)
+ for _, p := range parts {
+ if strings.ContainsAny(p, "*?[^]\\-") {
+ return true, kdbx.Glob
+ }
+ }
+ return true, func(criteria, path string) (bool, error) {
+ return strings.Contains(path, criteria), nil
+ }
}
diff --git a/internal/app/list_test.go b/internal/app/list_test.go
@@ -56,7 +56,7 @@ func TestList(t *testing.T) {
func TestFind(t *testing.T) {
m := newMockCommand(t)
- m.args = []string{"["}
+ m.args = []string{"a/["}
if err := app.List(m, false); err == nil || !strings.Contains(err.Error(), "syntax error in pattern") {
t.Errorf("invalid error: %v", err)
}
@@ -72,7 +72,23 @@ func TestFind(t *testing.T) {
t.Error("something listed")
}
m.buf.Reset()
- m.args = []string{"test/test2/**/*"}
+ m.args = []string{"test"}
+ if err := app.List(m, false); err != nil {
+ t.Errorf("invalid error: %v", err)
+ }
+ if m.buf.String() == "" {
+ t.Error("nothing listed")
+ }
+ m.buf.Reset()
+ m.args = []string{"test"}
+ if err := app.List(m, true); err != nil {
+ t.Errorf("invalid error: %v", err)
+ }
+ if m.buf.String() == "" {
+ t.Error("nothing listed")
+ }
+ m.buf.Reset()
+ m.args = []string{"test/**/**/*"}
if err := app.List(m, false); err != nil {
t.Errorf("invalid error: %v", err)
}
diff --git a/internal/kdbx/core.go b/internal/kdbx/core.go
@@ -23,7 +23,6 @@ var (
const (
titleKey = "Title"
pathSep = "/"
- isGlob = "*"
modTimeKey = "ModTime"
// OTPField is the totp storage attribute
OTPField = "otp"
@@ -83,7 +82,7 @@ func NewTransaction() (*Transaction, error) {
}
func splitComponents(path string) ([]string, string, error) {
- if len(strings.Split(path, pathSep)) < 2 {
+ if len(SplitPath(path)) < 2 {
return nil, "", errPath
}
if strings.HasPrefix(path, pathSep) {
@@ -96,7 +95,7 @@ func splitComponents(path string) ([]string, string, error) {
return nil, "", errors.New("unwilling to operate on path with empty segment")
}
title := Base(path)
- parts := strings.Split(Directory(path), pathSep)
+ parts := SplitPath(Directory(path))
return parts, title, nil
}
@@ -182,7 +181,7 @@ func (e Entity) Value(key string) (string, bool) {
// Base will get the base name of input path
func Base(s string) string {
- parts := strings.Split(s, pathSep)
+ parts := SplitPath(s)
if len(parts) == 0 {
return s
}
@@ -191,7 +190,7 @@ func Base(s string) string {
// Directory will get the directory/group for the given path
func Directory(s string) string {
- parts := strings.Split(s, pathSep)
+ parts := SplitPath(s)
return NewPath(parts[0 : len(parts)-1]...)
}
@@ -213,6 +212,11 @@ func IsLeafAttribute(path, attr string) bool {
return strings.HasSuffix(path, pathSep+attr)
}
+// SplitPath will split a path based on the separator
+func SplitPath(path string) []string {
+ return strings.Split(path, pathSep)
+}
+
// Collect will create a slice from an iterable set of query sequence results
func (s QuerySeq2) Collect() ([]Entity, error) {
var entities []Entity
diff --git a/internal/kdbx/core_test.go b/internal/kdbx/core_test.go
@@ -167,3 +167,14 @@ func TestEntityValue(t *testing.T) {
t.Error("values are not set")
}
}
+
+func TestSplitPath(t *testing.T) {
+ parts := kdbx.SplitPath("a")
+ if len(parts) != 1 || parts[0] != "a" {
+ t.Errorf("invalid split: %v", parts)
+ }
+ parts = kdbx.SplitPath("a/b")
+ if len(parts) != 2 || parts[0] != "a" || parts[1] != "b" {
+ t.Errorf("invalid split: %v", parts)
+ }
+}