commit f0da2e7d9f80a0111e880ab09c1c2ab715ffdab2
parent 840604dc78a10f6e6db85385633113af37785258
Author: Sean Enck <sean@ttypty.com>
Date: Sat, 7 Jun 2025 16:23:31 -0400
groups/ls should do find on their own
Diffstat:
13 files changed, 58 insertions(+), 77 deletions(-)
diff --git a/cmd/lb/main.go b/cmd/lb/main.go
@@ -82,8 +82,8 @@ func run() error {
switch command {
case commands.ReKey:
return app.ReKey(p)
- case commands.List, commands.Find, commands.Groups:
- return app.List(p, command == commands.Find, command == commands.Groups)
+ case commands.List, commands.Groups:
+ return app.List(p, command == commands.Groups)
case commands.Unset:
return app.Unset(p)
case commands.Move:
diff --git a/cmd/lb/main_test.go b/cmd/lb/main_test.go
@@ -207,7 +207,7 @@ func test(profile string) error {
for _, k := range []string{"insert test4/multiline/notes", "insert test5/multiline/notes", "insert test5/multiline/otp", "insert test5/multiline/password"} {
r.run(`printf "testing3\ntesting4\n" |`, k)
}
- for _, k := range []string{"insert test6/multiline/password", "insert test6/multiline/notes", "insert test7/deeper/rooted/notes", "insert test8/unset/password", "insert test8/unset/notes", "insert test9/key1/sub1/password", "insert test9/key1/sub2/password", "insert test9/key2/sub1/password"} {
+ for _, k := range []string{"insert test6/multiline/password", "insert test6/multiline/notes", "insert test7/deeper/rooted/notes", "insert test7/deeper/rooted/otp", "insert test8/unset/password", "insert test8/unset/notes", "insert test9/key1/sub1/password", "insert test9/key1/sub2/password", "insert test9/key2/sub1/password"} {
r.run(`printf "testing5" |`, k)
r.run("", fmt.Sprintf("show %s", strings.ReplaceAll(k, "insert ", "")))
}
@@ -216,12 +216,13 @@ func test(profile string) error {
r.run("echo y |", "rm test2/key1")
r.logAppend("echo")
r.run("", "ls")
+ r.run("", "ls multiline")
r.run("", "json")
r.run("", "json 'multiline'")
r.logAppend("echo")
r.run("echo 5ae472abqdekjqykoyxk7hvc2leklq5n |", "insert test6/multiline/otp")
r.run("", "totp ls")
- r.run("", "totp find multiline")
+ r.run("", "totp ls rooted")
r.run("", "totp show test6/multiline/otp")
r.run("", "totp once test6/multiline/otp")
r.run("", "totp minimal test6/multiline/otp")
@@ -235,6 +236,7 @@ func test(profile string) error {
r.logAppend("echo")
r.run("", "ls")
r.run("", "groups")
+ r.run("", "groups test9")
r.run("echo y |", "unset test8/unset/password")
r.logAppend("echo")
r.run("", "ls")
diff --git a/cmd/lb/tests/expected.log b/cmd/lb/tests/expected.log
@@ -7,6 +7,7 @@ password can NOT be multi-line
testing5
testing5
testing5
+otpauth://totp/lbissuer:lbaccount?algorithm=SHA1&digits=6&issuer=lbissuer&period=30&secret=testing5
testing5
testing5
testing5
@@ -20,6 +21,7 @@ test5/multiline/notes
test6/multiline/notes
test6/multiline/password
test7/deeper/rooted/notes
+test7/deeper/rooted/otp
test8/unset/notes
test8/unset/password
test9/key1/sub1/password
@@ -42,11 +44,16 @@ test5/multiline/notes
test6/multiline/notes
test6/multiline/password
test7/deeper/rooted/notes
+test7/deeper/rooted/otp
test8/unset/notes
test8/unset/password
test9/key1/sub1/password
test9/key1/sub2/password
test9/key2/sub1/password
+test4/multiline/notes
+test5/multiline/notes
+test6/multiline/notes
+test6/multiline/password
{
"test1/key1": {
"modtime": "XXXX-XX-XX",
@@ -67,7 +74,8 @@ test9/key2/sub1/password
},
"test7/deeper/rooted": {
"modtime": "XXXX-XX-XX",
- "notes": "cbf67e6da88d43f048050e36d7010080536372397b6ed0a0446cd2640340bf47cb616ddbc5d35ec3500e295f875f806fc20120a2d5497b3e729e904091424633"
+ "notes": "cbf67e6da88d43f048050e36d7010080536372397b6ed0a0446cd2640340bf47cb616ddbc5d35ec3500e295f875f806fc20120a2d5497b3e729e904091424633",
+ "otp": "3d126ef25692664fd102e72f4ebaebef79c0cf6a8ea442af099dd47d0a89fab9c9b2cb9b77e605b1d00d91c666c1f7d651a9bb2e3a06406fdd1f6155861eae55"
},
"test8/unset": {
"modtime": "XXXX-XX-XX",
@@ -104,7 +112,8 @@ test9/key2/sub1/password
}
test6/multiline/otp
-test6/multiline/otp
+test7/deeper/rooted/otp
+test7/deeper/rooted/otp
XXXXXX
XXXXXX
XXXXXX
@@ -128,7 +137,8 @@ XXXXXX
}
"test7/deeper/rooted": {
"modtime": "XXXX-XX-XX",
- "notes": "cbf67e6da88d43f048050e36d7010080536372397b6ed0a0446cd2640340bf47cb616ddbc5d35ec3500e295f875f806fc20120a2d5497b3e729e904091424633"
+ "notes": "cbf67e6da88d43f048050e36d7010080536372397b6ed0a0446cd2640340bf47cb616ddbc5d35ec3500e295f875f806fc20120a2d5497b3e729e904091424633",
+ "otp": "3d126ef25692664fd102e72f4ebaebef79c0cf6a8ea442af099dd47d0a89fab9c9b2cb9b77e605b1d00d91c666c1f7d651a9bb2e3a06406fdd1f6155861eae55"
}
"test8/unset": {
"modtime": "XXXX-XX-XX",
@@ -169,6 +179,9 @@ test8/unset
test9/key1/sub1
test9/key1/sub2
test9/key2/sub1
+test9/key1/sub1
+test9/key1/sub2
+test9/key2/sub1
unset: test8/unset/password? (y/N) clearing value from: test8/unset/password
test4/multiline/notes
diff --git a/internal/app/commands/core.go b/internal/app/commands/core.go
@@ -10,8 +10,6 @@ const (
Clear = "clear"
// Clip will copy values to the clipboard
Clip = "clip"
- // Find is for simplistic searching of entries
- Find = "find"
// Insert adds a value
Insert = "insert"
// List lists all entries
@@ -38,8 +36,6 @@ const (
TOTPMinimal = "minimal"
// TOTPList will list the totp-enabled entries
TOTPList = List
- // TOTPFind allows a filter search of TOTP listed entries
- TOTPFind = Find
// TOTPOnce will perform like a normal totp request but not refresh
TOTPOnce = "once"
// CompletionsBash is the command to generate bash completions
diff --git a/internal/app/completions/core.go b/internal/app/completions/core.go
@@ -19,7 +19,6 @@ type (
Template struct {
InsertCommand string
TOTPListCommand string
- TOTPFindCommand string
RemoveCommand string
UnsetCommand string
ClipCommand string
@@ -108,7 +107,6 @@ func Generate(completionType, exe string) ([]string, error) {
UnsetCommand: commands.Unset,
RemoveCommand: commands.Remove,
TOTPListCommand: commands.TOTPList,
- TOTPFindCommand: commands.TOTPFind,
ClipCommand: commands.Clip,
ShowCommand: commands.Show,
JSONCommand: commands.JSON,
@@ -124,7 +122,7 @@ func Generate(completionType, exe string) ([]string, error) {
}
c.Conditionals = NewConditionals()
- c.Options = c.newGenOptions([]string{commands.Help, commands.List, commands.Show, commands.Version, commands.JSON, commands.Find, commands.Groups},
+ c.Options = c.newGenOptions([]string{commands.Help, commands.List, commands.Show, commands.Version, commands.JSON, commands.Groups},
map[string]string{
commands.Clip: c.Conditionals.Not.CanClip,
commands.TOTP: c.Conditionals.Not.CanTOTP,
diff --git a/internal/app/completions/shell/bash.sh b/internal/app/completions/shell/bash.sh
@@ -45,7 +45,6 @@ _{{ $.Executable }}() {
;;
"{{ $.TOTPCommand }}")
opts="{{ $.TOTPListCommand }} "
- opts="{{ $.TOTPFindCommand }} "
{{- range $key, $value := .TOTPSubCommands }}
if {{ $value.Conditional }}; then
opts="$opts {{ $value.Key }}"
diff --git a/internal/app/completions/shell/zsh.sh b/internal/app/completions/shell/zsh.sh
@@ -72,7 +72,6 @@ _{{ $.Executable }}() {
case "$len" in
3)
compadd "$@" {{ $.TOTPListCommand }}
- compadd "$@" {{ $.TOTPFindCommand }}
{{- range $key, $value := .TOTPSubCommands }}
if {{ $value.Conditional }}; then
compadd "$@" {{ $value.Key }}
diff --git a/internal/app/help/core.go b/internal/app/help/core.go
@@ -81,9 +81,8 @@ func Usage(verbose bool, exe string) ([]string, error) {
results = append(results, command(commands.Insert, isEntry, "insert a new entry into the store"))
results = append(results, command(commands.Unset, isEntry, "clear an entry value"))
results = append(results, command(commands.JSON, isFilter, "display detailed information"))
- results = append(results, command(commands.List, "", "list entries"))
- results = append(results, command(commands.Groups, "", "list groups"))
- results = append(results, command(commands.Find, isFilter, "find matching entries"))
+ results = append(results, command(commands.List, isFilter, "list entries"))
+ results = append(results, command(commands.Groups, isFilter, "list groups"))
results = append(results, command(commands.Move, "src dst", "move a group from source to destination"))
results = append(results, command(commands.PasswordGenerate, "", "generate a password"))
results = append(results, command(commands.ReKey, "", "rekey/reinitialize the database credentials"))
@@ -91,11 +90,10 @@ func Usage(verbose bool, exe string) ([]string, error) {
results = append(results, command(commands.Show, isEntry, "show the entry's value"))
results = append(results, command(commands.TOTP, isEntry, "display an updating totp generated code"))
results = append(results, subCommand(commands.TOTP, commands.TOTPClip, isEntry, "copy totp code to clipboard"))
- results = append(results, subCommand(commands.TOTP, commands.TOTPList, "", "list entries with totp settings"))
+ results = append(results, subCommand(commands.TOTP, commands.TOTPList, isFilter, "list entries with totp settings"))
results = append(results, subCommand(commands.TOTP, commands.TOTPOnce, isEntry, "display the first generated code"))
results = append(results, subCommand(commands.TOTP, commands.TOTPMinimal, isEntry, "display one generated code (no details)"))
results = append(results, subCommand(commands.TOTP, commands.TOTPShow, isEntry, "show the totp entry"))
- results = append(results, subCommand(commands.TOTP, commands.TOTPFind, isFilter, "find matching entries with totp settings"))
results = append(results, command(commands.Version, "", "display version information"))
sort.Strings(results)
usage := []string{fmt.Sprintf("%s usage:", exe)}
diff --git a/internal/app/help/core_test.go b/internal/app/help/core_test.go
@@ -9,11 +9,11 @@ import (
func TestUsage(t *testing.T) {
u, _ := help.Usage(false, "lb")
- if len(u) != 28 {
+ if len(u) != 26 {
t.Errorf("invalid usage, out of date? %d", len(u))
}
u, _ = help.Usage(true, "lb")
- if len(u) != 92 {
+ if len(u) != 90 {
t.Errorf("invalid verbose usage, out of date? %d", len(u))
}
for _, usage := range u {
diff --git a/internal/app/list.go b/internal/app/list.go
@@ -11,21 +11,16 @@ import (
)
// List will list/find entries
-func List(cmd CommandOptions, isFind, groups bool) error {
- if isFind && groups {
- return errors.New("groups+find not supported")
- }
+func List(cmd CommandOptions, groups bool) error {
args := cmd.Args()
filter := ""
- if isFind {
- if len(args) != 1 {
- return errors.New("find requires one argument")
- }
+ switch len(args) {
+ case 0:
+ break
+ case 1:
filter = args[0]
- } else {
- if len(args) != 0 {
- return errors.New("arguments not supported")
- }
+ default:
+ return errors.New("too many arguments (none or filter)")
}
return doList("", filter, cmd, groups)
diff --git a/internal/app/list_test.go b/internal/app/list_test.go
@@ -43,37 +43,22 @@ func setup(t *testing.T) *backend.Transaction {
func TestList(t *testing.T) {
m := newMockCommand(t)
- if err := app.List(m, false, false); err != nil {
+ if err := app.List(m, false); err != nil {
t.Errorf("invalid error: %v", err)
}
if m.buf.String() == "" {
t.Error("nothing listed")
}
- m.args = []string{"test"}
- if err := app.List(m, false, false); err == nil || err.Error() != "arguments not supported" {
+ m.args = []string{"test", "test2"}
+ if err := app.List(m, false); err == nil || err.Error() != "too many arguments (none or filter)" {
t.Errorf("invalid error: %v", err)
}
}
func TestFind(t *testing.T) {
m := newMockCommand(t)
- if err := app.List(m, true, false); err == nil || err.Error() != "find requires one argument" {
- t.Errorf("invalid error: %v", err)
- }
- if m.buf.String() != "" {
- t.Error("something listed")
- }
- m.buf.Reset()
m.args = []string{"["}
- if err := app.List(m, true, false); err == nil || !strings.Contains(err.Error(), "missing closing") {
- t.Errorf("invalid error: %v", err)
- }
- if m.buf.String() != "" {
- t.Error("something listed")
- }
- m.buf.Reset()
- m.args = []string{"test", "1"}
- if err := app.List(m, true, false); err == nil || err.Error() != "find requires one argument" {
+ if err := app.List(m, false); err == nil || !strings.Contains(err.Error(), "missing closing") {
t.Errorf("invalid error: %v", err)
}
if m.buf.String() != "" {
@@ -81,7 +66,7 @@ func TestFind(t *testing.T) {
}
m.buf.Reset()
m.args = []string{"[zzzzzz]"}
- if err := app.List(m, true, false); err != nil {
+ if err := app.List(m, false); err != nil {
t.Errorf("invalid error: %v", err)
}
if m.buf.String() != "" {
@@ -89,7 +74,7 @@ func TestFind(t *testing.T) {
}
m.buf.Reset()
m.args = []string{"test"}
- if err := app.List(m, true, false); err != nil {
+ if err := app.List(m, false); err != nil {
t.Errorf("invalid error: %v", err)
}
if m.buf.String() == "" {
@@ -99,18 +84,14 @@ func TestFind(t *testing.T) {
func TestGroups(t *testing.T) {
m := newMockCommand(t)
- if err := app.List(m, false, true); err != nil {
+ if err := app.List(m, true); err != nil {
t.Errorf("invalid error: %v", err)
}
if m.buf.String() == "" {
t.Errorf("nothing listed: %s", m.buf.String())
}
- m.args = []string{"test"}
- if err := app.List(m, false, true); err == nil || err.Error() != "arguments not supported" {
- t.Errorf("invalid error: %v", err)
- }
- m.args = []string{}
- if err := app.List(m, true, true); err == nil || err.Error() != "groups+find not supported" {
+ m.args = []string{"test", "test2"}
+ if err := app.List(m, true); err == nil || err.Error() != "too many arguments (none or filter)" {
t.Errorf("invalid error: %v", err)
}
}
diff --git a/internal/app/totp.go b/internal/app/totp.go
@@ -57,8 +57,6 @@ const (
MinimalTOTPMode
// ListTOTPMode lists the available tokens
ListTOTPMode
- // FindTOTPMode is list but with a regexp filter
- FindTOTPMode
// OnceTOTPMode will only show the token once and exit
OnceTOTPMode
)
@@ -220,7 +218,7 @@ func (args *TOTPArguments) Do(opts TOTPOptions) error {
if !opts.CanTOTP() {
return ErrNoTOTP
}
- if args.Mode == ListTOTPMode || args.Mode == FindTOTPMode {
+ if args.Mode == ListTOTPMode {
return doList(backend.OTP, args.Entry, opts.app, false)
}
return args.display(opts)
@@ -234,15 +232,17 @@ func NewTOTPArguments(args []string) (*TOTPArguments, error) {
opts := &TOTPArguments{Mode: UnknownTOTPMode}
sub := args[0]
needs := true
+ length := len(args)
switch sub {
case commands.TOTPList:
needs = false
- if len(args) != 1 {
- return nil, errors.New("list takes no arguments")
+ if length != 1 {
+ needs = true
+ if length != 2 {
+ return nil, errors.New("list takes only a filter (if any)")
+ }
}
opts.Mode = ListTOTPMode
- case commands.TOTPFind:
- opts.Mode = FindTOTPMode
case commands.TOTPShow:
opts.Mode = ShowTOTPMode
case commands.TOTPClip:
@@ -255,7 +255,7 @@ func NewTOTPArguments(args []string) (*TOTPArguments, error) {
return nil, ErrUnknownTOTPMode
}
if needs {
- if len(args) != 2 {
+ if length != 2 {
return nil, errors.New("invalid arguments")
}
opts.Entry = args[1]
diff --git a/internal/app/totp_test.go b/internal/app/totp_test.go
@@ -83,7 +83,7 @@ func TestNewTOTPArgumentsErrors(t *testing.T) {
if _, err := app.NewTOTPArguments([]string{"test"}); err == nil || err.Error() != "unknown totp mode" {
t.Errorf("invalid error: %v", err)
}
- if _, err := app.NewTOTPArguments([]string{"ls", "test"}); err == nil || err.Error() != "list takes no arguments" {
+ if _, err := app.NewTOTPArguments([]string{"ls", "test", "xxx"}); err == nil || err.Error() != "list takes only a filter (if any)" {
t.Errorf("invalid error: %v", err)
}
if _, err := app.NewTOTPArguments([]string{"show"}); err == nil || err.Error() != "invalid arguments" {
@@ -96,8 +96,8 @@ func TestNewTOTPArguments(t *testing.T) {
if args.Mode != app.ListTOTPMode || args.Entry != "" {
t.Error("invalid args")
}
- args, _ = app.NewTOTPArguments([]string{"find", "tesst"})
- if args.Mode != app.FindTOTPMode || args.Entry == "" {
+ args, _ = app.NewTOTPArguments([]string{"ls", "xyz"})
+ if args.Mode != app.ListTOTPMode || args.Entry != "xyz" {
t.Error("invalid args")
}
args, _ = app.NewTOTPArguments([]string{"show", "test"})
@@ -269,9 +269,9 @@ func TestParseWindows(t *testing.T) {
}
}
-func TestTOTPFind(t *testing.T) {
+func TestTOTPListFilter(t *testing.T) {
setupTOTP(t)
- args, _ := app.NewTOTPArguments([]string{"find", "test"})
+ args, _ := app.NewTOTPArguments([]string{"ls", "test"})
m, opts := newMock(t)
if err := args.Do(opts); err != nil {
t.Errorf("invalid error: %v", err)
@@ -280,7 +280,7 @@ func TestTOTPFind(t *testing.T) {
t.Errorf("invalid list: %s", m.buf.String())
}
m.buf.Reset()
- args, _ = app.NewTOTPArguments([]string{"find", "[zzzz]"})
+ args, _ = app.NewTOTPArguments([]string{"ls", "[zzzz]"})
if err := args.Do(opts); err != nil {
t.Errorf("invalid error: %v", err)
}