-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsrc.go
177 lines (145 loc) · 4.19 KB
/
src.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
package confdir
import (
"errors"
"os"
"path"
"gopkg.in/yaml.v3"
"io/ioutil"
)
type logFunc func(format string, arguments ...any)
type YamlLoader struct {
path string
logFunc logFunc
configFiles map[string]*cfgLoader
}
type OptionFunc func(dir *YamlLoader)
func WithLogFunc(logger logFunc) OptionFunc {
return func(c *YamlLoader) {
c.logFunc = logger
}
}
// NewYamlLoader creates YamlLoader with configFolder relative path to $Home.
// If configFolder is empty, the config file would be put under $HOME directly
func NewYamlLoader(configFolder string, options ...OptionFunc) *YamlLoader {
c := &YamlLoader{
path: configFolder,
configFiles: map[string]*cfgLoader{},
}
for _, option := range options {
option(c)
}
return c
}
// LoadAll loads all configs registered
func (loader *YamlLoader) LoadAll() error {
configFolder := pathFromHome(loader.path)
if loader.path != "" {
err := loader.prepareFolder(configFolder)
if err != nil {
loader.logIfNeeded("prepareFolder error: ", err)
return err
}
}
for fileName, l := range loader.configFiles {
configFile := path.Join(configFolder, fileName)
loader.logIfNeeded("loading file: %s", configFile)
_, err := os.Stat(configFile)
if errors.Is(err, os.ErrNotExist) {
if l.exampleContent != "" {
loader.logIfNeeded("creating example config: %s", configFile)
err = saveExample(configFile, l.exampleContent)
if err != nil {
return err
}
} else if l.errorOnNoFile {
loader.logIfNeeded("returnning error because expected")
return os.ErrNotExist
} else {
// we just skip loading
continue
}
}
err = loadYaml(configFile, l.config)
if err != nil {
loader.logIfNeeded("unexpected error when loading %s: %s", fileName, err)
return err
}
}
return nil
}
// PrepareFolder prepares folderPath, to make sure folder exist
// it set permission to 700 (i.e., only owner have full access to folder)
func (loader *YamlLoader) prepareFolder(folder string) error {
return os.MkdirAll(folder, 0700)
}
func (loader *YamlLoader) logIfNeeded(format string, args ...interface{}) {
if loader.logFunc == nil {
return
}
loader.logFunc(format, args...)
}
type cfgLoader struct {
config interface{}
exampleContent string
errorOnNoFile bool
}
type RegisterOptionFunc func(loader *cfgLoader)
func RegWithExampleContent(content string) RegisterOptionFunc {
return func(loader *cfgLoader) {
loader.exampleContent = content
}
}
// RegErrorOnNoFile if registered file not exist, and no exampleContent provided, return error (i.e., loading failed)
func RegErrorOnNoFile() RegisterOptionFunc {
return func(loader *cfgLoader) {
loader.errorOnNoFile = true
}
}
func (loader *YamlLoader) RegisterFile(filename string, cfg interface{}, options ...RegisterOptionFunc) {
c := &cfgLoader{config: cfg}
for _, o := range options {
o(c)
}
loader.configFiles[filename] = c
}
// loadYaml loads `config` from given filepath, in yaml format
// expect `filepath` either absolute path, or relative to the directory executing
// to be relative to HomeDir, wrap with `pathFromHome(filepath)`
func loadYaml(filepath string, config interface{}) error {
f, err := os.Open(filepath)
if err != nil {
return err
}
content, err := ioutil.ReadAll(f)
if err != nil {
return err
}
return yaml.Unmarshal(content, config)
}
// pathFromHome appends the home dir, in front of the given relativePath
func pathFromHome(relativePath string) string {
home := os.Getenv("HOME")
return path.Join(home, relativePath)
}
// saveExample persist example data
func saveExample(path string, data string) error {
return os.WriteFile(path, []byte(data), 0700)
}
// SaveConfig saves the config back to yaml file
func (loader *YamlLoader) SaveConfig(filename string) error {
configFolder := pathFromHome(loader.path)
l, exists := loader.configFiles[filename]
if !exists {
return errors.New("config file not registered")
}
configFile := path.Join(configFolder, filename)
return saveYaml(configFile, l.config)
}
// saveYaml saves config to yaml file
func saveYaml(filepath string, config interface{}) error {
content, err := yaml.Marshal(config)
if err != nil {
return err
}
return os.WriteFile(filepath, content, 0600)
}