summaryrefslogtreecommitdiff
path: root/internal/config/config.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/config/config.go')
-rw-r--r--internal/config/config.go101
1 files changed, 81 insertions, 20 deletions
diff --git a/internal/config/config.go b/internal/config/config.go
index b9ae6c2..b8d0532 100644
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -13,8 +13,12 @@ import (
"strings"
)
-// configPath is the location of the optional user config file.
-const configPath = "~/.config/foostore.json"
+const (
+ // configPath is the location of the optional user config file.
+ configPath = "~/.config/foostore.json"
+ // fallbackHomeDirName is used when we cannot resolve a valid home directory.
+ fallbackHomeDirName = "foostore-home"
+)
// Config holds all application-wide configuration values.
// JSON field names use snake_case to match the original geheim.rb Config::DEFAULTS keys.
@@ -31,14 +35,53 @@ type Config struct {
SyncRepos []string `json:"sync_repos"`
}
-// defaultConfig returns a Config populated with built-in defaults.
-// EditCmd honours the $EDITOR environment variable and falls back to "vi"
-// when the variable is unset or empty, so users get their preferred editor
-// automatically without touching the config file.
-// It calls os.UserHomeDir() so that path fields expand correctly at runtime.
-func defaultConfig() Config {
- home, _ := os.UserHomeDir()
+// resolveHomeDir resolves the current user's home directory from OS state.
+// If resolution succeeds with os.UserHomeDir, error is nil.
+// If resolution falls back to HOME or a temp-based directory, an explanatory
+// non-nil error is returned so callers can warn without failing hard.
+func resolveHomeDir() (string, error) {
+ return resolveHomeDirFrom(os.UserHomeDir, os.Getenv("HOME"), os.TempDir())
+}
+
+// resolveHomeDirFrom is a test seam for home directory resolution.
+func resolveHomeDirFrom(userHomeDir func() (string, error), envHome, tempDir string) (string, error) {
+ home, err := userHomeDir()
+ if err == nil && home != "" {
+ return home, nil
+ }
+
+ if envHome != "" && filepath.IsAbs(envHome) {
+ if err != nil {
+ return envHome, fmt.Errorf("os.UserHomeDir failed; using HOME=%q: %w", envHome, err)
+ }
+ return envHome, fmt.Errorf("os.UserHomeDir returned empty home; using HOME=%q", envHome)
+ }
+
+ fallbackHome := filepath.Join(tempDir, fallbackHomeDirName)
+ if envHome != "" && !filepath.IsAbs(envHome) {
+ if err != nil {
+ return fallbackHome, fmt.Errorf("os.UserHomeDir failed; HOME is not absolute (%q), using %q: %w", envHome, fallbackHome, err)
+ }
+ return fallbackHome, fmt.Errorf("os.UserHomeDir returned empty home; HOME is not absolute (%q), using %q", envHome, fallbackHome)
+ }
+
+ if err != nil {
+ return fallbackHome, fmt.Errorf("os.UserHomeDir failed and HOME is unavailable; using %q: %w", fallbackHome, err)
+ }
+ return fallbackHome, fmt.Errorf("os.UserHomeDir returned empty home and HOME is unavailable; using %q", fallbackHome)
+}
+
+// homeDirOrFallback resolves a usable home path and logs fallback reasons.
+func homeDirOrFallback() string {
+ home, err := resolveHomeDir()
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "Warning: %v\n", err)
+ }
+ return home
+}
+// defaultConfigWithHome returns built-in defaults using the supplied home path.
+func defaultConfigWithHome(home string) Config {
// Prefer $EDITOR; fall back to vi if not set.
editCmd := os.Getenv("EDITOR")
if editCmd == "" {
@@ -59,22 +102,39 @@ func defaultConfig() Config {
}
}
-// expandTilde replaces a leading "~" in path with the user's home directory.
-// Non-tilde paths and empty strings are returned unchanged.
-func expandTilde(path string) string {
+// defaultConfig returns a Config populated with built-in defaults.
+// EditCmd honours the $EDITOR environment variable and falls back to "vi"
+// when the variable is unset or empty, so users get their preferred editor
+// automatically without touching the config file.
+func defaultConfig() Config {
+ return defaultConfigWithHome(homeDirOrFallback())
+}
+
+// expandTildeWithHome replaces a leading "~" in path with the supplied home.
+func expandTildeWithHome(path, home string) string {
if path == "" || !strings.HasPrefix(path, "~") {
return path
}
- home, _ := os.UserHomeDir()
// Replace only the leading "~"; preserve any subdirectory suffix.
return home + path[1:]
}
+// expandTilde replaces a leading "~" in path with the user's home directory.
+// Non-tilde paths and empty strings are returned unchanged.
+func expandTilde(path string) string {
+ return expandTildeWithHome(path, homeDirOrFallback())
+}
+
+// expandPathFieldsWithHome tilde-expands every path-typed field in cfg in place.
+func expandPathFieldsWithHome(cfg *Config, home string) {
+ cfg.DataDir = expandTildeWithHome(cfg.DataDir, home)
+ cfg.ExportDir = expandTildeWithHome(cfg.ExportDir, home)
+ cfg.KeyFile = expandTildeWithHome(cfg.KeyFile, home)
+}
+
// expandPathFields tilde-expands every path-typed field in cfg in place.
func expandPathFields(cfg *Config) {
- cfg.DataDir = expandTilde(cfg.DataDir)
- cfg.ExportDir = expandTilde(cfg.ExportDir)
- cfg.KeyFile = expandTilde(cfg.KeyFile)
+ expandPathFieldsWithHome(cfg, homeDirOrFallback())
}
// Load reads ~/.config/foostore.json and merges it over the built-in defaults.
@@ -86,8 +146,9 @@ func expandPathFields(cfg *Config) {
// Note: the Ruby reference uses puts (stdout) for this warning; we use stderr
// intentionally because warnings belong on the error stream.
func Load() Config {
- cfg := defaultConfig()
- path := expandTilde(configPath)
+ home := homeDirOrFallback()
+ cfg := defaultConfigWithHome(home)
+ path := expandTildeWithHome(configPath, home)
data, err := os.ReadFile(path)
if err != nil {
@@ -103,12 +164,12 @@ func Load() Config {
// JSON document are overwritten; all others retain their default values.
if err := json.Unmarshal(data, &cfg); err != nil {
fmt.Fprintf(os.Stderr, "Unable to read %s, using defaults! %v\n", path, err)
- return defaultConfig()
+ return defaultConfigWithHome(home)
}
// Tilde-expand path fields that may have been supplied as "~/…" strings
// in the JSON file (defaultConfig() already returns absolute paths, but
// user-supplied values might use "~").
- expandPathFields(&cfg)
+ expandPathFieldsWithHome(&cfg, home)
return cfg
}