github.com/gogf/gf@v1.16.9/os/gcfg/gcfg_config.go (about) 1 // Copyright GoFrame Author(https://goframe.org). All Rights Reserved. 2 // 3 // This Source Code Form is subject to the terms of the MIT License. 4 // If a copy of the MIT was not distributed with this file, 5 // You can obtain one at https://github.com/gogf/gf. 6 7 package gcfg 8 9 import ( 10 "bytes" 11 "context" 12 "fmt" 13 "github.com/gogf/gf/container/garray" 14 "github.com/gogf/gf/container/gmap" 15 "github.com/gogf/gf/encoding/gjson" 16 "github.com/gogf/gf/errors/gcode" 17 "github.com/gogf/gf/errors/gerror" 18 "github.com/gogf/gf/internal/intlog" 19 "github.com/gogf/gf/os/gcmd" 20 "github.com/gogf/gf/os/gfile" 21 "github.com/gogf/gf/os/gfsnotify" 22 "github.com/gogf/gf/os/glog" 23 "github.com/gogf/gf/os/gres" 24 "github.com/gogf/gf/os/gspath" 25 "github.com/gogf/gf/text/gstr" 26 "github.com/gogf/gf/util/gmode" 27 ) 28 29 // New returns a new configuration management object. 30 // The parameter `file` specifies the default configuration file name for reading. 31 func New(file ...string) *Config { 32 name := DefaultConfigFile 33 if len(file) > 0 { 34 name = file[0] 35 } else { 36 // Custom default configuration file name from command line or environment. 37 if customFile := gcmd.GetOptWithEnv(commandEnvKeyForFile).String(); customFile != "" { 38 name = customFile 39 } 40 } 41 c := &Config{ 42 defaultName: name, 43 searchPaths: garray.NewStrArray(true), 44 jsonMap: gmap.NewStrAnyMap(true), 45 } 46 // Customized dir path from env/cmd. 47 if customPath := gcmd.GetOptWithEnv(commandEnvKeyForPath).String(); customPath != "" { 48 if gfile.Exists(customPath) { 49 _ = c.SetPath(customPath) 50 } else { 51 if errorPrint() { 52 glog.Errorf("[gcfg] Configuration directory path does not exist: %s", customPath) 53 } 54 } 55 } else { 56 // Dir path of working dir. 57 if err := c.AddPath(gfile.Pwd()); err != nil { 58 intlog.Error(context.TODO(), err) 59 } 60 61 // Dir path of main package. 62 if mainPath := gfile.MainPkgPath(); mainPath != "" && gfile.Exists(mainPath) { 63 if err := c.AddPath(mainPath); err != nil { 64 intlog.Error(context.TODO(), err) 65 } 66 } 67 68 // Dir path of binary. 69 if selfPath := gfile.SelfDir(); selfPath != "" && gfile.Exists(selfPath) { 70 if err := c.AddPath(selfPath); err != nil { 71 intlog.Error(context.TODO(), err) 72 } 73 } 74 } 75 return c 76 } 77 78 // Instance returns an instance of Config with default settings. 79 // The parameter `name` is the name for the instance. But very note that, if the file "name.toml" 80 // exists in the configuration directory, it then sets it as the default configuration file. The 81 // toml file type is the default configuration file type. 82 func Instance(name ...string) *Config { 83 key := DefaultName 84 if len(name) > 0 && name[0] != "" { 85 key = name[0] 86 } 87 return instances.GetOrSetFuncLock(key, func() interface{} { 88 c := New() 89 // If it's not using default configuration or its configuration file is not available, 90 // it searches the possible configuration file according to the name and all supported 91 // file types. 92 if key != DefaultName || !c.Available() { 93 for _, fileType := range supportedFileTypes { 94 if file := fmt.Sprintf(`%s.%s`, key, fileType); c.Available(file) { 95 c.SetFileName(file) 96 break 97 } 98 } 99 } 100 return c 101 }).(*Config) 102 } 103 104 // SetPath sets the configuration directory path for file search. 105 // The parameter `path` can be absolute or relative path, 106 // but absolute path is strongly recommended. 107 func (c *Config) SetPath(path string) error { 108 var ( 109 isDir = false 110 realPath = "" 111 ) 112 if file := gres.Get(path); file != nil { 113 realPath = path 114 isDir = file.FileInfo().IsDir() 115 } else { 116 // Absolute path. 117 realPath = gfile.RealPath(path) 118 if realPath == "" { 119 // Relative path. 120 c.searchPaths.RLockFunc(func(array []string) { 121 for _, v := range array { 122 if path, _ := gspath.Search(v, path); path != "" { 123 realPath = path 124 break 125 } 126 } 127 }) 128 } 129 if realPath != "" { 130 isDir = gfile.IsDir(realPath) 131 } 132 } 133 // Path not exist. 134 if realPath == "" { 135 buffer := bytes.NewBuffer(nil) 136 if c.searchPaths.Len() > 0 { 137 buffer.WriteString(fmt.Sprintf("[gcfg] SetPath failed: cannot find directory \"%s\" in following paths:", path)) 138 c.searchPaths.RLockFunc(func(array []string) { 139 for k, v := range array { 140 buffer.WriteString(fmt.Sprintf("\n%d. %s", k+1, v)) 141 } 142 }) 143 } else { 144 buffer.WriteString(fmt.Sprintf(`[gcfg] SetPath failed: path "%s" does not exist`, path)) 145 } 146 err := gerror.NewCode(gcode.CodeOperationFailed, buffer.String()) 147 if errorPrint() { 148 glog.Error(err) 149 } 150 return err 151 } 152 // Should be a directory. 153 if !isDir { 154 err := fmt.Errorf(`[gcfg] SetPath failed: path "%s" should be directory type`, path) 155 if errorPrint() { 156 glog.Error(err) 157 } 158 return err 159 } 160 // Repeated path check. 161 if c.searchPaths.Search(realPath) != -1 { 162 return nil 163 } 164 c.jsonMap.Clear() 165 c.searchPaths.Clear() 166 c.searchPaths.Append(realPath) 167 intlog.Print(context.TODO(), "SetPath:", realPath) 168 return nil 169 } 170 171 // SetViolenceCheck sets whether to perform hierarchical conflict checking. 172 // This feature needs to be enabled when there is a level symbol in the key name. 173 // It is off in default. 174 // 175 // Note that, turning on this feature is quite expensive, and it is not recommended 176 // to allow separators in the key names. It is best to avoid this on the application side. 177 func (c *Config) SetViolenceCheck(check bool) { 178 c.violenceCheck = check 179 c.Clear() 180 } 181 182 // AddPath adds a absolute or relative path to the search paths. 183 func (c *Config) AddPath(path string) error { 184 var ( 185 isDir = false 186 realPath = "" 187 ) 188 // It firstly checks the resource manager, 189 // and then checks the filesystem for the path. 190 if file := gres.Get(path); file != nil { 191 realPath = path 192 isDir = file.FileInfo().IsDir() 193 } else { 194 // Absolute path. 195 realPath = gfile.RealPath(path) 196 if realPath == "" { 197 // Relative path. 198 c.searchPaths.RLockFunc(func(array []string) { 199 for _, v := range array { 200 if path, _ := gspath.Search(v, path); path != "" { 201 realPath = path 202 break 203 } 204 } 205 }) 206 } 207 if realPath != "" { 208 isDir = gfile.IsDir(realPath) 209 } 210 } 211 if realPath == "" { 212 buffer := bytes.NewBuffer(nil) 213 if c.searchPaths.Len() > 0 { 214 buffer.WriteString(fmt.Sprintf("[gcfg] AddPath failed: cannot find directory \"%s\" in following paths:", path)) 215 c.searchPaths.RLockFunc(func(array []string) { 216 for k, v := range array { 217 buffer.WriteString(fmt.Sprintf("\n%d. %s", k+1, v)) 218 } 219 }) 220 } else { 221 buffer.WriteString(fmt.Sprintf(`[gcfg] AddPath failed: path "%s" does not exist`, path)) 222 } 223 err := gerror.NewCode(gcode.CodeOperationFailed, buffer.String()) 224 if errorPrint() { 225 glog.Error(err) 226 } 227 return err 228 } 229 if !isDir { 230 err := gerror.NewCodef(gcode.CodeInvalidParameter, `[gcfg] AddPath failed: path "%s" should be directory type`, path) 231 if errorPrint() { 232 glog.Error(err) 233 } 234 return err 235 } 236 // Repeated path check. 237 if c.searchPaths.Search(realPath) != -1 { 238 return nil 239 } 240 c.searchPaths.Append(realPath) 241 intlog.Print(context.TODO(), "AddPath:", realPath) 242 return nil 243 } 244 245 // SetFileName sets the default configuration file name. 246 func (c *Config) SetFileName(name string) *Config { 247 c.defaultName = name 248 return c 249 } 250 251 // GetFileName returns the default configuration file name. 252 func (c *Config) GetFileName() string { 253 return c.defaultName 254 } 255 256 // Available checks and returns whether configuration of given `file` is available. 257 func (c *Config) Available(file ...string) bool { 258 var name string 259 if len(file) > 0 && file[0] != "" { 260 name = file[0] 261 } else { 262 name = c.defaultName 263 } 264 if path, _ := c.GetFilePath(name); path != "" { 265 return true 266 } 267 if GetContent(name) != "" { 268 return true 269 } 270 return false 271 } 272 273 // GetFilePath returns the absolute configuration file path for the given filename by `file`. 274 // If `file` is not passed, it returns the configuration file path of the default name. 275 // It returns an empty `path` string and an error if the given `file` does not exist. 276 func (c *Config) GetFilePath(file ...string) (path string, err error) { 277 name := c.defaultName 278 if len(file) > 0 { 279 name = file[0] 280 } 281 // Searching resource manager. 282 if !gres.IsEmpty() { 283 for _, v := range resourceTryFiles { 284 if file := gres.Get(v + name); file != nil { 285 path = file.Name() 286 return 287 } 288 } 289 c.searchPaths.RLockFunc(func(array []string) { 290 for _, prefix := range array { 291 for _, v := range resourceTryFiles { 292 if file := gres.Get(prefix + v + name); file != nil { 293 path = file.Name() 294 return 295 } 296 } 297 } 298 }) 299 } 300 c.autoCheckAndAddMainPkgPathToSearchPaths() 301 // Searching the file system. 302 c.searchPaths.RLockFunc(func(array []string) { 303 for _, prefix := range array { 304 prefix = gstr.TrimRight(prefix, `\/`) 305 if path, _ = gspath.Search(prefix, name); path != "" { 306 return 307 } 308 if path, _ = gspath.Search(prefix+gfile.Separator+"config", name); path != "" { 309 return 310 } 311 } 312 }) 313 // If it cannot find the path of `file`, it formats and returns a detailed error. 314 if path == "" { 315 var ( 316 buffer = bytes.NewBuffer(nil) 317 ) 318 if c.searchPaths.Len() > 0 { 319 buffer.WriteString(fmt.Sprintf(`[gcfg] cannot find config file "%s" in resource manager or the following paths:`, name)) 320 c.searchPaths.RLockFunc(func(array []string) { 321 index := 1 322 for _, v := range array { 323 v = gstr.TrimRight(v, `\/`) 324 buffer.WriteString(fmt.Sprintf("\n%d. %s", index, v)) 325 index++ 326 buffer.WriteString(fmt.Sprintf("\n%d. %s", index, v+gfile.Separator+"config")) 327 index++ 328 } 329 }) 330 } else { 331 buffer.WriteString(fmt.Sprintf("[gcfg] cannot find config file \"%s\" with no path configured", name)) 332 } 333 err = gerror.NewCode(gcode.CodeOperationFailed, buffer.String()) 334 } 335 return 336 } 337 338 // autoCheckAndAddMainPkgPathToSearchPaths automatically checks and adds directory path of package main 339 // to the searching path list if it's currently in development environment. 340 func (c *Config) autoCheckAndAddMainPkgPathToSearchPaths() { 341 if gmode.IsDevelop() { 342 mainPkgPath := gfile.MainPkgPath() 343 if mainPkgPath != "" { 344 if !c.searchPaths.Contains(mainPkgPath) { 345 c.searchPaths.Append(mainPkgPath) 346 } 347 } 348 } 349 } 350 351 // getJson returns a *gjson.Json object for the specified `file` content. 352 // It would print error if file reading fails. It return nil if any error occurs. 353 func (c *Config) getJson(file ...string) *gjson.Json { 354 var name string 355 if len(file) > 0 && file[0] != "" { 356 name = file[0] 357 } else { 358 name = c.defaultName 359 } 360 r := c.jsonMap.GetOrSetFuncLock(name, func() interface{} { 361 var ( 362 err error 363 content string 364 filePath string 365 ) 366 // The configured content can be any kind of data type different from its file type. 367 isFromConfigContent := true 368 if content = GetContent(name); content == "" { 369 isFromConfigContent = false 370 filePath, err = c.GetFilePath(name) 371 if err != nil && errorPrint() { 372 glog.Error(err) 373 } 374 if filePath == "" { 375 return nil 376 } 377 if file := gres.Get(filePath); file != nil { 378 content = string(file.Content()) 379 } else { 380 content = gfile.GetContents(filePath) 381 } 382 } 383 // Note that the underlying configuration json object operations are concurrent safe. 384 var ( 385 j *gjson.Json 386 ) 387 dataType := gfile.ExtName(name) 388 if gjson.IsValidDataType(dataType) && !isFromConfigContent { 389 j, err = gjson.LoadContentType(dataType, content, true) 390 } else { 391 j, err = gjson.LoadContent(content, true) 392 } 393 if err == nil { 394 j.SetViolenceCheck(c.violenceCheck) 395 // Add monitor for this configuration file, 396 // any changes of this file will refresh its cache in Config object. 397 if filePath != "" && !gres.Contains(filePath) { 398 _, err = gfsnotify.Add(filePath, func(event *gfsnotify.Event) { 399 c.jsonMap.Remove(name) 400 }) 401 if err != nil && errorPrint() { 402 glog.Error(err) 403 } 404 } 405 return j 406 } 407 if errorPrint() { 408 if filePath != "" { 409 glog.Criticalf(`[gcfg] load config file "%s" failed: %s`, filePath, err.Error()) 410 } else { 411 glog.Criticalf(`[gcfg] load configuration failed: %s`, err.Error()) 412 } 413 } 414 return nil 415 }) 416 if r != nil { 417 return r.(*gjson.Json) 418 } 419 return nil 420 }