-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathconfig.go
218 lines (177 loc) · 5.61 KB
/
config.go
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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
package main
import (
"embed"
"encoding/json"
"errors"
"fmt"
"log"
"os"
"path"
lib "github.com/charles-m-knox/finance-planner-lib"
"github.com/charles-m-knox/go-uuid"
"github.com/adrg/xdg"
"gopkg.in/yaml.v3"
)
// Attempts to load from a specific location, if possible.
//
// The first return value is the populated config, if one was found and parsed.
// The second return value is a string that indicates the properly loaded path
// that successfully loaded the config (if it didn't succeed, it will be an
// empty string). The third return value is an error, if present.
//
// The "t" parameter is the map of translations.
func loadConfFrom(file string, t map[string]string) (Config, string, error) {
conf := Config{}
b, err := os.ReadFile(file)
if err != nil {
return conf, "", fmt.Errorf("%v %v: %w", t["ConfigFailedToLoadConfig"], file, err)
}
err = yaml.Unmarshal(b, &conf)
if err != nil {
return conf, "", fmt.Errorf("%v %v: %w", t["ConfigFailedToUnmarshalConfig"], file, err)
}
return conf, file, nil
}
func loadConfFromEmbed(file string, emb embed.FS, t map[string]string) (Config, string, error) {
conf := Config{}
b, err := emb.ReadFile(file)
if err != nil {
return conf, "", fmt.Errorf("%v %v: %w", t["ConfigFailedToLoadEmbeddedConfig"], file, err)
}
err = yaml.Unmarshal(b, &conf)
if err != nil {
return conf, "", fmt.Errorf("%v %v: %w", t["ConfigFailedToUnmarshalEmbeddedConfig"], file, err)
}
return conf, file, nil
}
func fileExists(name string) (bool, error) {
_, err := os.Stat(name)
if err == nil {
return true, nil
}
if errors.Is(err, os.ErrNotExist) {
return false, nil
}
return false, err
}
// Attempts to load from the "file" path provided - if not successful,
// attempts to load from xdg config, then xdg home.
//
// The first return value is the populated config, if one was found and parsed.
// The second return value is a string that indicates the properly loaded path
// that successfully loaded the config (if it didn't succeed, it will be an
// empty string). The third return value is an error, if present.
//
// You should set the global configFile variable to match the returned string
// value so that other logic can use it.
//
// The "t" parameter is the map of translations.
func loadConfig(file string, t map[string]string, exampleConf embed.FS) (Config, string, error) {
if file == "" {
file = DefaultConfig
}
var err error
var exists bool
var conf Config
// create the XDG config dir for this application once upon startup
xdgConfigDir := path.Join(xdg.ConfigHome, DefaultConfigParentDir)
err = os.MkdirAll(xdgConfigDir, 0o755)
if err != nil {
return conf, file, fmt.Errorf("failed to make all directories %v: %w ", xdgConfigDir, err)
}
exists, err = fileExists(file)
if err != nil {
return conf, file, fmt.Errorf("failed to check if file %v exists: %w ", file, err)
}
if exists {
conf, file, err = loadConfFrom(file, t)
if err != nil {
return conf, file, fmt.Errorf("failed to load config from existing config file %v: %w ", file, err)
}
return conf, file, nil
}
xdgConfig := path.Join(xdgConfigDir, DefaultConfig)
exists, err = fileExists(xdgConfig)
if err != nil {
return conf, file, fmt.Errorf("failed to check if file %v exists: %w ", file, err)
}
if exists {
conf, file, err = loadConfFrom(xdgConfig, t)
if err != nil {
return conf, file, fmt.Errorf("failed to load config from existing config file %v: %w ", file, err)
}
return conf, file, nil
}
xdgHome := path.Join(xdg.Home, DefaultConfigParentDir, DefaultConfig)
exists, err = fileExists(xdgHome)
if err != nil {
return conf, file, fmt.Errorf("failed to check if file %v exists: %w ", file, err)
}
if exists {
conf, file, err = loadConfFrom(xdgConfig, t)
if err != nil {
return conf, file, fmt.Errorf("failed to load config from existing config file %v: %w ", file, err)
}
return conf, file, nil
}
// if the config file doesn't exist, create it at xdgConfig with the
// example config (note: this doesn't *write* to the xdgConfig path,
// but instead sets the target config write path there so that it will
// be saved there)
conf, file, err = loadConfFromEmbed("example.yml", exampleConf, t)
if err != nil {
return conf, file, fmt.Errorf("failed to load config from template config %v: %w ", file, err)
}
return conf, xdgConfig, err
}
// processConfig applies any post-load configuration parameters/logic to ensure
// that data is valid & consistent. Use it after loadConfig.
func processConfig(conf *Config) {
if conf == nil {
log.Fatalf("config is nil")
}
// ensure that every transaction has its weekdays map properly populated
for i := 0; i < 7; i++ {
for j := range conf.Profiles {
for k := range conf.Profiles[j].TX {
_, ok := conf.Profiles[j].TX[k].Weekdays[i]
if !ok {
conf.Profiles[j].TX[k].Weekdays[i] = false
}
}
}
}
}
// converts a json file to yaml (one-off job for converting from legacy versions
// of this program).
func JSONtoYAML() {
b, err := os.ReadFile("conf.json")
if err != nil {
log.Fatalf("failed to load conf.json")
}
nc := Config{
Profiles: []Profile{
{
Name: "migrated",
TX: []lib.TX{},
},
},
}
err = json.Unmarshal(b, &nc.Profiles[0].TX)
if err != nil {
log.Fatalf("failed to unmarshal conf: %v", err.Error())
}
// update all uuids in the config
for i := range nc.Profiles[0].TX {
nc.Profiles[0].TX[i].ID = uuid.New()
}
out, err := yaml.Marshal(nc)
if err != nil {
log.Fatalf("failed to marshal nc: %v", err.Error())
}
//nolint:gosec
err = os.WriteFile("migrated.yml", out, 0o644)
if err != nil {
log.Fatalf("failed to write migrated.yml: %v", err.Error())
}
}