1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
|
// Package config handles loading and storing foostore configuration.
// Defaults mirror the Ruby reference (geheim.rb Config::DEFAULTS).
// A JSON file at ~/.config/foostore.json overrides individual fields;
// missing fields keep their default values because Go's json.Unmarshal
// only touches fields that are present in the JSON document.
package config
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
)
// configPath is the location of the optional user config file.
const configPath = "~/.config/foostore.json"
// Config holds all application-wide configuration values.
// JSON field names use snake_case to match the original geheim.rb Config::DEFAULTS keys.
type Config struct {
DataDir string `json:"data_dir"`
ExportDir string `json:"export_dir"`
KeyFile string `json:"key_file"`
KeyLength int `json:"key_length"`
EncAlg string `json:"enc_alg"`
AddToIV string `json:"add_to_iv"`
EditCmd string `json:"edit_cmd"`
GnomeClipboardCmd string `json:"gnome_clipboard_cmd"`
MacOSClipboardCmd string `json:"macos_clipboard_cmd"`
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()
// Prefer $EDITOR; fall back to vi if not set.
editCmd := os.Getenv("EDITOR")
if editCmd == "" {
editCmd = "vi"
}
return Config{
DataDir: filepath.Join(home, "git", "foostoredb"),
ExportDir: filepath.Join(home, ".foostore-export"),
KeyFile: filepath.Join(home, ".foostore.key"),
KeyLength: 32,
EncAlg: "AES-256-CBC",
AddToIV: "Hello world",
EditCmd: editCmd,
GnomeClipboardCmd: "gpaste-client",
MacOSClipboardCmd: "pbcopy",
SyncRepos: []string{"git1", "git2"},
}
}
// 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 {
if path == "" || !strings.HasPrefix(path, "~") {
return path
}
home, _ := os.UserHomeDir()
// Replace only the leading "~"; preserve any subdirectory suffix.
return home + path[1:]
}
// 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)
}
// Load reads ~/.config/foostore.json and merges it over the built-in defaults.
// Any field present in the JSON file overrides the corresponding default
// (including edit_cmd, which defaults to $EDITOR or "vi" when unset);
// fields absent from the file keep their default values.
// If the file is missing or contains invalid JSON a warning is printed to
// stderr and the pure defaults are returned.
// 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)
data, err := os.ReadFile(path)
if err != nil {
// File missing or unreadable — use defaults silently only when the
// error is "not found"; otherwise warn the caller.
if !os.IsNotExist(err) {
fmt.Fprintf(os.Stderr, "Unable to read %s, using defaults! %v\n", path, err)
}
return cfg
}
// Unmarshal into the defaults struct so that only fields present in the
// 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()
}
// 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)
return cfg
}
|