github.com/wfusion/gofusion@v1.1.14/internal/configor/configor.go (about) 1 // Fork from github.com/jinzhu/configor@v1.2.2-0.20230118083828-f7a0fc7c9fc6 2 // Here is the license: 3 // 4 // The MIT License (MIT) 5 // 6 // Copyright (c) 2013-NOW Jinzhu <wosmvp@gmail.com> 7 // 8 // Permission is hereby granted, free of charge, to any person obtaining a copy 9 // of this software and associated documentation files (the "Software"), to deal 10 // in the Software without restriction, including without limitation the rights 11 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 // copies of the Software, and to permit persons to whom the Software is 13 // furnished to do so, subject to the following conditions: 14 // 15 // The above copyright notice and this permission notice shall be included in all 16 // copies or substantial portions of the Software. 17 // 18 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 // SOFTWARE. 25 26 package configor 27 28 import ( 29 "crypto/sha256" 30 "fmt" 31 "io" 32 "io/fs" 33 "os" 34 "reflect" 35 "regexp" 36 "time" 37 38 "github.com/wfusion/gofusion/common/utils" 39 ) 40 41 type Configor struct { 42 *Config 43 configHash map[string]string 44 configModTimes map[string]time.Time 45 46 statFunc func(name string) (fs.FileInfo, error) 47 hashFunc func(name string) (string, error) 48 } 49 50 type Config struct { 51 Environment string 52 ENVPrefix string 53 Debug bool 54 Verbose bool 55 Silent bool 56 AutoReload bool 57 AutoReloadInterval time.Duration 58 AutoReloadCallback func(config any) 59 60 // In case of json files, this field will be used only when compiled with 61 // go 1.10 or later. 62 // This field will be ignored when compiled with go versions lower than 1.10. 63 ErrorOnUnmatchedKeys bool 64 65 // You can use embed.FS or any other fs.FS to load configs from. Default - use "os" package 66 FS fs.FS 67 } 68 69 // New initialize a Configor 70 func New(config *Config) *Configor { 71 if config == nil { 72 config = &Config{} 73 } 74 75 if os.Getenv("CONFIGOR_DEBUG_MODE") != "" { 76 config.Debug = true 77 } 78 79 if os.Getenv("CONFIGOR_VERBOSE_MODE") != "" { 80 config.Verbose = true 81 } 82 83 if os.Getenv("CONFIGOR_SILENT_MODE") != "" { 84 config.Silent = true 85 } 86 87 if config.AutoReload && config.AutoReloadInterval == 0 { 88 config.AutoReloadInterval = time.Second 89 } 90 91 cfg := &Configor{ 92 Config: config, 93 configHash: make(map[string]string), 94 configModTimes: make(map[string]time.Time), 95 statFunc: os.Stat, 96 hashFunc: func(name string) (h string, err error) { 97 var file fs.File 98 if file, err = os.Open(name); err != nil { 99 return 100 } 101 defer utils.CloseAnyway(file) 102 sha256Hash := sha256.New() 103 if _, err = io.Copy(sha256Hash, file); err != nil { 104 return 105 } 106 h = string(sha256Hash.Sum(nil)) 107 return 108 }, 109 } 110 if cfg.FS != nil { 111 cfg.statFunc = func(name string) (os.FileInfo, error) { 112 return fs.Stat(cfg.FS, name) 113 } 114 cfg.hashFunc = func(name string) (h string, err error) { 115 var file fs.File 116 if file, err = cfg.FS.Open(name); err != nil { 117 return 118 } 119 defer utils.CloseAnyway(file) 120 sha256Hash := sha256.New() 121 if _, err = io.Copy(sha256Hash, file); err != nil { 122 return 123 } 124 h = string(sha256Hash.Sum(nil)) 125 return 126 } 127 } 128 129 return cfg 130 } 131 132 var testRegexp = regexp.MustCompile(`_test|(\.test$)`) 133 134 // GetEnvironment get environment 135 func (c *Configor) GetEnvironment() string { 136 if c.Environment == "" { 137 if env := os.Getenv("CONFIGOR_ENV"); env != "" { 138 return env 139 } 140 141 if testRegexp.MatchString(os.Args[0]) { 142 return "test" 143 } 144 145 return "development" 146 } 147 return c.Environment 148 } 149 150 // GetErrorOnUnmatchedKeys returns a boolean indicating if an error should be 151 // thrown if there are keys in the config file that do not correspond to the 152 // config struct 153 func (c *Configor) GetErrorOnUnmatchedKeys() bool { 154 return c.ErrorOnUnmatchedKeys 155 } 156 157 // Load will unmarshal configurations to struct from files that you provide 158 func (c *Configor) Load(config any, files ...string) (err error) { 159 defaultValue := reflect.Indirect(reflect.ValueOf(config)) 160 if !defaultValue.CanAddr() { 161 return fmt.Errorf("config %v should be addressable", config) 162 } 163 err, _ = c.load(config, false, files...) 164 165 if c.Config.AutoReload { 166 go func() { 167 timer := time.NewTimer(c.Config.AutoReloadInterval) 168 for range timer.C { 169 reflectPtr := reflect.New(reflect.ValueOf(config).Elem().Type()) 170 reflectPtr.Elem().Set(defaultValue) 171 172 var changed bool 173 if err, changed = c.load(reflectPtr.Interface(), true, files...); err == nil && changed { 174 reflect.ValueOf(config).Elem().Set(reflectPtr.Elem()) 175 if c.Config.AutoReloadCallback != nil { 176 c.Config.AutoReloadCallback(config) 177 } 178 } else if err != nil { 179 if !c.Silent { 180 fmt.Printf("Failed to reload configuration from %v, got error %v\n", files, err) 181 } 182 } 183 timer.Reset(c.Config.AutoReloadInterval) 184 } 185 }() 186 } 187 return 188 } 189 190 // ENV return environment 191 func ENV() string { 192 return New(nil).GetEnvironment() 193 } 194 195 // Load will unmarshal configurations to struct from files that you provide 196 func Load(config any, files ...string) error { 197 return New(nil).Load(config, files...) 198 }