/
configureator.go
161 lines (136 loc) · 3.87 KB
/
configureator.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
package zconfig
import (
"encoding/json"
"fmt"
"github.com/BurntSushi/toml"
"github.com/cockroachdb/errors"
"github.com/gocarina/gocsv"
"github.com/spf13/afero"
"github.com/zonewave/pkgs/standutil/cputil"
"github.com/zonewave/pkgs/standutil/fileutil"
"github.com/zonewave/pkgs/standutil/reflectutil"
"github.com/zonewave/pkgs/standutil/sliceutil"
"gopkg.in/yaml.v3"
"strings"
)
// _defaultLookupPaths find config in those paths
var _defaultLookupPaths = []string{
"./", "../", "../../", "../../../", "../../../../",
"./conf", "/conf", "../conf", "../../conf", "../../../conf", "../../../../conf",
"./config", "/config", "../config", "../../config", "../../../config", "../../../../config",
}
// _defaultSupportedExts are universally supported extensions.
var _defaultSupportedExts = []string{"json", "yml", "yaml", "toml"}
// Configurator config manager
type Configurator struct {
container interface{}
configPaths []string
supportExts []string
mainFileType string
mainFile string
// The filesystem to read config from.
fs fileutil.Afero
unmarshalMgr map[string]func([]byte, interface{}) error
}
// New return config manager
func New() *Configurator {
return new(Configurator).Reset()
}
func defaultUnmarshalFunc() map[string]func([]byte, interface{}) error {
return map[string]func([]byte, interface{}) error{
"json": json.Unmarshal,
"yml": yaml.Unmarshal,
"yaml": yaml.Unmarshal,
"toml": toml.Unmarshal,
"csv": gocsv.UnmarshalBytes,
}
}
// Reset reset config manager
func (c *Configurator) Reset() *Configurator {
c.container = nil
c.configPaths = _defaultLookupPaths
c.supportExts = _defaultSupportedExts
c.mainFileType = "json"
c.mainFile = ""
c.fs = &afero.Afero{Fs: afero.NewOsFs()}
c.unmarshalMgr = defaultUnmarshalFunc()
return c
}
// Initialize your config
func (c *Configurator) Initialize(configFile string, cfgStructPtr interface{}) error {
if err := c.set(configFile, cfgStructPtr); err != nil {
return err
}
if err := c.loadConfig(); err != nil {
return err
}
if err := cputil.DeepCopy(cfgStructPtr, c.container); err != nil {
return err
}
return nil
}
func (c *Configurator) checkExt(ext string) error {
if !sliceutil.Contain(ext, c.supportExts) {
return errors.WithStack(NewErrInvalidCfgExt(ext))
}
return nil
}
func (c *Configurator) checkObject(obj interface{}) error {
if reflectutil.IsStructPtr(obj) {
return nil
}
return errors.WithStack(NewErrUnsupportedCfgType(obj))
}
func (c *Configurator) set(configFile string, cfgStructPtr interface{}) error {
var (
configType string
mainFile string
err error
)
// check type
configType = fileutil.FileExtNoDot(configFile)
if err = c.checkExt(configType); err != nil {
return errors.WithStack(err)
}
// check object
if err = c.checkObject(cfgStructPtr); err != nil {
return errors.WithStack(err)
}
// check file
if mainFile, err = c.searchFile(configFile); err != nil {
return errors.WithStack(err)
}
c.mainFile = mainFile
c.mainFileType = strings.ToLower(configType)
c.container = cfgStructPtr
return nil
}
// searchFile search file in configPaths
func (c *Configurator) searchFile(file string) (string, error) {
f, err := fileutil.SearchInPaths(c.fs, c.configPaths, file)
if err != nil {
return "", errors.WithStack(NewErrFileNotFound(file, fmt.Sprintf("%s", c.configPaths), err))
}
return f, nil
}
func (c *Configurator) loadConfig() error {
file, err := c.fs.ReadFile(c.mainFile)
if err != nil {
return err
}
err = c.unmarshal(c.mainFileType, file, c.container)
if err != nil {
return err
}
return nil
}
func (c *Configurator) unmarshal(fileType string, bs []byte, cfg interface{}) error {
unmarshal, ok := c.unmarshalMgr[fileType]
if !ok {
return errors.WithStack(NewErrUnsupportedUnmarshal(fileType))
}
if err := unmarshal(bs, cfg); err != nil {
return errors.WithStack(NewErrUnmarshal(err, bs, fileType, cfg))
}
return nil
}