diff options
Diffstat (limited to 'internal/config/config.go')
| -rw-r--r-- | internal/config/config.go | 101 |
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 } |
