lockbox

password manager
Log | Files | Refs | README | LICENSE

commit 8dc7af5e3a108742a2ae781d13e1127692704b4d
parent ae288011354112aa746043c79e5c6c70154ef1e0
Author: Sean Enck <sean@ttypty.com>
Date:   Sun, 28 Sep 2025 23:21:53 -0400

config loading and file handling can be a single function

Diffstat:
Mcmd/lb/main.go | 14++------------
Minternal/app/core.go | 5+++++
Minternal/config/core.go | 47++++++++++++++++++++++++++++++++++++-----------
Minternal/config/core_test.go | 80+++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------
Minternal/config/toml.go | 5-----
Minternal/config/toml_test.go | 8++++++++
6 files changed, 109 insertions(+), 50 deletions(-)

diff --git a/cmd/lb/main.go b/cmd/lb/main.go @@ -47,18 +47,8 @@ func handleEarly(command string, args []string) (bool, error) { } func run() error { - cfg := app.ConfigLoader{} - for _, p := range config.NewConfigFiles() { - if cfg.Check(p) { - r, err := cfg.Read(p) - if err != nil { - return err - } - if err := config.Load(r, cfg); err != nil { - return err - } - break - } + if err := config.Parse(app.ConfigLoader{}); err != nil { + return err } args := os.Args if len(args) < 2 { diff --git a/internal/app/core.go b/internal/app/core.go @@ -99,3 +99,8 @@ func (c ConfigLoader) Read(file string) (io.Reader, error) { func (c ConfigLoader) Check(file string) bool { return platform.PathExists(file) } + +// Home will read the user's home directory +func (c ConfigLoader) Home() (string, error) { + return os.UserHomeDir() +} diff --git a/internal/config/core.go b/internal/config/core.go @@ -3,6 +3,7 @@ package config import ( "fmt" + "io" "net/url" "os" "path/filepath" @@ -91,24 +92,48 @@ type ( Text string Position Position } + // Loader indicates how config files should be read + Loader interface { + Read(string) (io.Reader, error) + Check(string) bool + Home() (string, error) + } ) -// NewConfigFiles will get the list of candidate config files -func NewConfigFiles() []string { +// Parse will parse the config based on the loader +func Parse(loader Loader) error { + process := func(path string) (bool, error) { + if loader.Check(path) { + r, err := loader.Read(path) + if err != nil { + return false, err + } + return true, Load(r, loader) + } + return false, nil + } v := os.Expand(os.Getenv(ConfigEnv), os.Getenv) if v != "" { - return []string{v} + // NOTE: when this environment variable is set - either load the config or exit, the user does NOT want the default config options regardless + _, err := process(v) + return err } - var options []string - pathAdder := func(root, sub string, err error) { - if err == nil && root != "" { - options = append(options, filepath.Join(root, sub)) + pathAdder := func(root, sub string) (bool, error) { + if root != "" { + return process(filepath.Join(root, sub)) } + return false, nil + } + ok, err := pathAdder(os.Getenv("XDG_CONFIG_HOME"), ConfigXDG) + if ok || err != nil { + return err + } + h, err := loader.Home() + if err != nil { + return err } - pathAdder(os.Getenv("XDG_CONFIG_HOME"), ConfigXDG, nil) - h, err := os.UserHomeDir() - pathAdder(h, ConfigHome, err) - return options + _, err = pathAdder(h, ConfigHome) + return err } func environmentRegister[T printer](obj T) T { diff --git a/internal/config/core_test.go b/internal/config/core_test.go @@ -1,37 +1,73 @@ package config_test import ( + "errors" + "io" "os" + "strings" "testing" "github.com/enckse/lockbox/internal/config" "github.com/enckse/lockbox/internal/config/store" ) -func TestNewEnvFiles(t *testing.T) { +func TestParse(t *testing.T) { + store.Clear() + defer store.Clear() os.Clearenv() + defer os.Clearenv() + mock := mockReader{} + mock.home = func() (string, error) { + return "", errors.New("invalid home") + } + mock.check = func(string) bool { + return false + } + if err := config.Parse(mock); err == nil || err.Error() != "invalid home" { + t.Errorf("invalid error: %v", err) + } + mock.home = func() (string, error) { + return "hometest", nil + } + mock.read = func(p string) (io.Reader, error) { + if p == "hometest/.config/lockbox/config.toml" { + return strings.NewReader("[feature]\ncolor = true"), nil + } + return nil, nil + } + mock.check = func(string) bool { + return true + } + store.SetBool("LOCKBOX_FEATURE_COLOR", false) + if err := config.Parse(mock); err != nil { + t.Errorf("invalid error: %v", err) + } + if ok, _ := store.GetBool("LOCKBOX_FEATURE_COLOR"); !ok { + t.Error("should have set") + } + t.Setenv("XDG_CONFIG_HOME", "xdghome") + mock.read = func(p string) (io.Reader, error) { + if p == "xdghome/lockbox/config.toml" { + return strings.NewReader("[feature]\ncolor = false"), nil + } + return nil, nil + } + if err := config.Parse(mock); err != nil { + t.Errorf("invalid error: %v", err) + } + if ok, _ := store.GetBool("LOCKBOX_FEATURE_COLOR"); ok { + t.Error("should have unset") + } + store.SetBool("LOCKBOX_FEATURE_COLOR", true) t.Setenv("LOCKBOX_CONFIG_TOML", "test") - f := config.NewConfigFiles() - if len(f) != 1 || f[0] != "test" { - t.Errorf("invalid files: %v", f) - } - t.Setenv("HOME", "test") - t.Setenv("LOCKBOX_CONFIG_TOML", "") - f = config.NewConfigFiles() - if len(f) != 1 { - t.Errorf("invalid files: %v", f) - } - t.Setenv("LOCKBOX_CONFIG_TOML", "") - t.Setenv("XDG_CONFIG_HOME", "test") - f = config.NewConfigFiles() - if len(f) != 2 { - t.Errorf("invalid files: %v", f) - } - t.Setenv("LOCKBOX_CONFIG_TOML", "") - os.Unsetenv("HOME") - f = config.NewConfigFiles() - if len(f) != 1 { - t.Errorf("invalid files: %v", f) + mock.check = func(p string) bool { + return p != "test" + } + if err := config.Parse(mock); err != nil { + t.Errorf("invalid error: %v", err) + } + if ok, _ := store.GetBool("LOCKBOX_FEATURE_COLOR"); !ok { + t.Error("should have set") } } diff --git a/internal/config/toml.go b/internal/config/toml.go @@ -27,11 +27,6 @@ const ( type ( tomlType string - // Loader indicates how included files should be sourced - Loader interface { - Read(string) (io.Reader, error) - Check(string) bool - } included struct { value string required bool diff --git a/internal/config/toml_test.go b/internal/config/toml_test.go @@ -15,6 +15,7 @@ import ( type mockReader struct { read func(string) (io.Reader, error) check func(string) bool + home func() (string, error) } func (m mockReader) Read(path string) (io.Reader, error) { @@ -31,6 +32,13 @@ func (m mockReader) Check(path string) bool { return m.check(path) } +func (m mockReader) Home() (string, error) { + if m.home == nil { + return "", nil + } + return m.home() +} + func TestLoadIncludes(t *testing.T) { store.Clear() defer os.Clearenv()