diff --git a/os/gcfg/gcfg.go b/os/gcfg/gcfg.go index 76f5f7985..d9eac9f2b 100644 --- a/os/gcfg/gcfg.go +++ b/os/gcfg/gcfg.go @@ -8,32 +8,13 @@ package gcfg import ( - "bytes" - "errors" - "fmt" - "github.com/gogf/gf/errors/gerror" - "github.com/gogf/gf/internal/intlog" - "github.com/gogf/gf/os/gcmd" - "github.com/gogf/gf/text/gstr" - "github.com/gogf/gf/util/gmode" - - "github.com/gogf/gf/os/gres" - "github.com/gogf/gf/container/garray" "github.com/gogf/gf/container/gmap" - "github.com/gogf/gf/encoding/gjson" - "github.com/gogf/gf/os/gfile" - "github.com/gogf/gf/os/gfsnotify" - "github.com/gogf/gf/os/glog" - "github.com/gogf/gf/os/gspath" + "github.com/gogf/gf/internal/intlog" + "github.com/gogf/gf/os/gcmd" ) -const ( - DefaultConfigFile = "config.toml" // The default configuration file name. - cmdEnvKey = "gf.gcfg" // Configuration key for command argument or environment. -) - -// Configuration struct. +// Config is the configuration manager. type Config struct { defaultName string // Default configuration file name. searchPaths *garray.StrArray // Searching path array. @@ -41,374 +22,82 @@ type Config struct { violenceCheck bool // Whether do violence check in value index searching. It affects the performance when set true(false in default). } -var ( - supportedFileTypes = []string{"toml", "yaml", "yml", "json", "ini", "xml"} - resourceTryFiles = []string{"", "/", "config/", "config", "/config", "/config/"} +const ( + DefaultName = "config" // DefaultName is the default group name for instance usage. + DefaultConfigFile = "config.toml" // DefaultConfigFile is the default configuration file name. + cmdEnvKey = "gf.gcfg" // cmdEnvKey is the configuration key for command argument or environment. + errorPrintKey = "gf.gcfg.errorprint" // errorPrintKey is used to specify the key controlling error printing to stdout. ) -// New returns a new configuration management object. -// The parameter `file` specifies the default configuration file name for reading. -func New(file ...string) *Config { +var ( + supportedFileTypes = []string{"toml", "yaml", "yml", "json", "ini", "xml"} // All supported file types suffixes. + resourceTryFiles = []string{"", "/", "config/", "config", "/config", "/config/"} // Prefix array for trying searching in resource manager. + instances = gmap.NewStrAnyMap(true) // Instances map containing configuration instances. + customConfigContentMap = gmap.NewStrStrMap(true) // Customized configuration content. +) + +// SetContent sets customized configuration content for specified `file`. +// The `file` is unnecessary param, default is DefaultConfigFile. +func SetContent(content string, file ...string) { name := DefaultConfigFile if len(file) > 0 { name = file[0] - } else { - // Custom default configuration file name from command line or environment. - if customFile := gcmd.GetOptWithEnv(fmt.Sprintf("%s.file", cmdEnvKey)).String(); customFile != "" { - name = customFile - } } - c := &Config{ - defaultName: name, - searchPaths: garray.NewStrArray(true), - jsonMap: gmap.NewStrAnyMap(true), - } - // Customized dir path from env/cmd. - if customPath := gcmd.GetOptWithEnv(fmt.Sprintf("%s.path", cmdEnvKey)).String(); customPath != "" { - if gfile.Exists(customPath) { - _ = c.SetPath(customPath) - } else { - if errorPrint() { - glog.Errorf("[gcfg] Configuration directory path does not exist: %s", customPath) + // Clear file cache for instances which cached `name`. + instances.LockFunc(func(m map[string]interface{}) { + if customConfigContentMap.Contains(name) { + for _, v := range m { + v.(*Config).jsonMap.Remove(name) } } - } else { - // Dir path of working dir. - if err := c.AddPath(gfile.Pwd()); err != nil { - intlog.Error(err) - } - - // Dir path of main package. - if mainPath := gfile.MainPkgPath(); mainPath != "" && gfile.Exists(mainPath) { - if err := c.AddPath(mainPath); err != nil { - intlog.Error(err) - } - } - - // Dir path of binary. - if selfPath := gfile.SelfDir(); selfPath != "" && gfile.Exists(selfPath) { - if err := c.AddPath(selfPath); err != nil { - intlog.Error(err) - } - } - } - return c + customConfigContentMap.Set(name, content) + }) } -// SetPath sets the configuration directory path for file search. -// The parameter `path` can be absolute or relative path, -// but absolute path is strongly recommended. -func (c *Config) SetPath(path string) error { - var ( - isDir = false - realPath = "" - ) - if file := gres.Get(path); file != nil { - realPath = path - isDir = file.FileInfo().IsDir() - } else { - // Absolute path. - realPath = gfile.RealPath(path) - if realPath == "" { - // Relative path. - c.searchPaths.RLockFunc(func(array []string) { - for _, v := range array { - if path, _ := gspath.Search(v, path); path != "" { - realPath = path - break - } - } - }) - } - if realPath != "" { - isDir = gfile.IsDir(realPath) - } - } - // Path not exist. - if realPath == "" { - buffer := bytes.NewBuffer(nil) - if c.searchPaths.Len() > 0 { - buffer.WriteString(fmt.Sprintf("[gcfg] SetPath failed: cannot find directory \"%s\" in following paths:", path)) - c.searchPaths.RLockFunc(func(array []string) { - for k, v := range array { - buffer.WriteString(fmt.Sprintf("\n%d. %s", k+1, v)) - } - }) - } else { - buffer.WriteString(fmt.Sprintf(`[gcfg] SetPath failed: path "%s" does not exist`, path)) - } - err := errors.New(buffer.String()) - if errorPrint() { - glog.Error(err) - } - return err - } - // Should be a directory. - if !isDir { - err := fmt.Errorf(`[gcfg] SetPath failed: path "%s" should be directory type`, path) - if errorPrint() { - glog.Error(err) - } - return err - } - // Repeated path check. - if c.searchPaths.Search(realPath) != -1 { - return nil - } - c.jsonMap.Clear() - c.searchPaths.Clear() - c.searchPaths.Append(realPath) - intlog.Print("SetPath:", realPath) - return nil -} - -// SetViolenceCheck sets whether to perform hierarchical conflict checking. -// This feature needs to be enabled when there is a level symbol in the key name. -// It is off in default. -// -// Note that, turning on this feature is quite expensive, and it is not recommended -// to allow separators in the key names. It is best to avoid this on the application side. -func (c *Config) SetViolenceCheck(check bool) { - c.violenceCheck = check - c.Clear() -} - -// AddPath adds a absolute or relative path to the search paths. -func (c *Config) AddPath(path string) error { - var ( - isDir = false - realPath = "" - ) - // It firstly checks the resource manager, - // and then checks the filesystem for the path. - if file := gres.Get(path); file != nil { - realPath = path - isDir = file.FileInfo().IsDir() - } else { - // Absolute path. - realPath = gfile.RealPath(path) - if realPath == "" { - // Relative path. - c.searchPaths.RLockFunc(func(array []string) { - for _, v := range array { - if path, _ := gspath.Search(v, path); path != "" { - realPath = path - break - } - } - }) - } - if realPath != "" { - isDir = gfile.IsDir(realPath) - } - } - if realPath == "" { - buffer := bytes.NewBuffer(nil) - if c.searchPaths.Len() > 0 { - buffer.WriteString(fmt.Sprintf("[gcfg] AddPath failed: cannot find directory \"%s\" in following paths:", path)) - c.searchPaths.RLockFunc(func(array []string) { - for k, v := range array { - buffer.WriteString(fmt.Sprintf("\n%d. %s", k+1, v)) - } - }) - } else { - buffer.WriteString(fmt.Sprintf(`[gcfg] AddPath failed: path "%s" does not exist`, path)) - } - err := gerror.New(buffer.String()) - if errorPrint() { - glog.Error(err) - } - return err - } - if !isDir { - err := gerror.Newf(`[gcfg] AddPath failed: path "%s" should be directory type`, path) - if errorPrint() { - glog.Error(err) - } - return err - } - // Repeated path check. - if c.searchPaths.Search(realPath) != -1 { - return nil - } - c.searchPaths.Append(realPath) - intlog.Print("AddPath:", realPath) - return nil -} - -// SetFileName sets the default configuration file name. -func (c *Config) SetFileName(name string) *Config { - c.defaultName = name - return c -} - -// GetFileName returns the default configuration file name. -func (c *Config) GetFileName() string { - return c.defaultName -} - -// Available checks and returns whether configuration of given `file` is available. -func (c *Config) Available(file ...string) bool { - var name string - if len(file) > 0 && file[0] != "" { - name = file[0] - } else { - name = c.defaultName - } - if path, _ := c.GetFilePath(name); path != "" { - return true - } - if GetContent(name) != "" { - return true - } - return false -} - -// GetFilePath returns the absolute configuration file path for the given filename by `file`. -// If `file` is not passed, it returns the configuration file path of the default name. -// It returns an empty `path` string and an error if the given `file` does not exist. -func (c *Config) GetFilePath(file ...string) (path string, err error) { - name := c.defaultName +// GetContent returns customized configuration content for specified `file`. +// The `file` is unnecessary param, default is DefaultConfigFile. +func GetContent(file ...string) string { + name := DefaultConfigFile if len(file) > 0 { name = file[0] } - // Searching resource manager. - if !gres.IsEmpty() { - for _, v := range resourceTryFiles { - if file := gres.Get(v + name); file != nil { - path = file.Name() - return - } - } - c.searchPaths.RLockFunc(func(array []string) { - for _, prefix := range array { - for _, v := range resourceTryFiles { - if file := gres.Get(prefix + v + name); file != nil { - path = file.Name() - return - } - } - } - }) - } - c.autoCheckAndAddMainPkgPathToSearchPaths() - // Searching the file system. - c.searchPaths.RLockFunc(func(array []string) { - for _, prefix := range array { - prefix = gstr.TrimRight(prefix, `\/`) - if path, _ = gspath.Search(prefix, name); path != "" { - return - } - if path, _ = gspath.Search(prefix+gfile.Separator+"config", name); path != "" { - return - } - } - }) - // If it cannot find the path of `file`, it formats and returns a detailed error. - if path == "" { - var ( - buffer = bytes.NewBuffer(nil) - ) - if c.searchPaths.Len() > 0 { - buffer.WriteString(fmt.Sprintf(`[gcfg] cannot find config file "%s" in resource manager or the following paths:`, name)) - c.searchPaths.RLockFunc(func(array []string) { - index := 1 - for _, v := range array { - v = gstr.TrimRight(v, `\/`) - buffer.WriteString(fmt.Sprintf("\n%d. %s", index, v)) - index++ - buffer.WriteString(fmt.Sprintf("\n%d. %s", index, v+gfile.Separator+"config")) - index++ - } - }) - } else { - buffer.WriteString(fmt.Sprintf("[gcfg] cannot find config file \"%s\" with no path configured", name)) - } - err = gerror.New(buffer.String()) - } - return + return customConfigContentMap.Get(name) } -// autoCheckAndAddMainPkgPathToSearchPaths automatically checks and adds directory path of package main -// to the searching path list if it's currently in development environment. -func (c *Config) autoCheckAndAddMainPkgPathToSearchPaths() { - if gmode.IsDevelop() { - mainPkgPath := gfile.MainPkgPath() - if mainPkgPath != "" { - if !c.searchPaths.Contains(mainPkgPath) { - c.searchPaths.Append(mainPkgPath) - } - } - } -} - -// getJson returns a *gjson.Json object for the specified `file` content. -// It would print error if file reading fails. It return nil if any error occurs. -func (c *Config) getJson(file ...string) *gjson.Json { - var name string - if len(file) > 0 && file[0] != "" { +// RemoveContent removes the global configuration with specified `file`. +// If `name` is not passed, it removes configuration of the default group name. +func RemoveContent(file ...string) { + name := DefaultConfigFile + if len(file) > 0 { name = file[0] - } else { - name = c.defaultName } - r := c.jsonMap.GetOrSetFuncLock(name, func() interface{} { - var ( - err error - content string - filePath string - ) - // The configured content can be any kind of data type different from its file type. - isFromConfigContent := true - if content = GetContent(name); content == "" { - isFromConfigContent = false - filePath, err = c.GetFilePath(name) - if err != nil && errorPrint() { - glog.Error(err) - } - if filePath == "" { - return nil - } - if file := gres.Get(filePath); file != nil { - content = string(file.Content()) - } else { - content = gfile.GetContents(filePath) + // Clear file cache for instances which cached `name`. + instances.LockFunc(func(m map[string]interface{}) { + if customConfigContentMap.Contains(name) { + for _, v := range m { + v.(*Config).jsonMap.Remove(name) } + customConfigContentMap.Remove(name) } - // Note that the underlying configuration json object operations are concurrent safe. - var ( - j *gjson.Json - ) - dataType := gfile.ExtName(name) - if gjson.IsValidDataType(dataType) && !isFromConfigContent { - j, err = gjson.LoadContentType(dataType, content, true) - } else { - j, err = gjson.LoadContent(content, true) - } - if err == nil { - j.SetViolenceCheck(c.violenceCheck) - // Add monitor for this configuration file, - // any changes of this file will refresh its cache in Config object. - if filePath != "" && !gres.Contains(filePath) { - _, err = gfsnotify.Add(filePath, func(event *gfsnotify.Event) { - c.jsonMap.Remove(name) - }) - if err != nil && errorPrint() { - glog.Error(err) - } - } - return j - } - if errorPrint() { - if filePath != "" { - glog.Criticalf(`[gcfg] load config file "%s" failed: %s`, filePath, err.Error()) - } else { - glog.Criticalf(`[gcfg] load configuration failed: %s`, err.Error()) - } - } - return nil }) - if r != nil { - return r.(*gjson.Json) - } - return nil + + intlog.Printf(`RemoveContent: %s`, name) +} + +// ClearContent removes all global configuration contents. +func ClearContent() { + customConfigContentMap.Clear() + // Clear cache for all instances. + instances.LockFunc(func(m map[string]interface{}) { + for _, v := range m { + v.(*Config).jsonMap.Clear() + } + }) + + intlog.Print(`RemoveConfig`) +} + +// errorPrint checks whether printing error to stdout. +func errorPrint() bool { + return gcmd.GetOptWithEnv(errorPrintKey, true).Bool() } diff --git a/os/gcfg/gcfg_config.go b/os/gcfg/gcfg_config.go index 68adf275a..6034f1f88 100644 --- a/os/gcfg/gcfg_config.go +++ b/os/gcfg/gcfg_config.go @@ -7,72 +7,413 @@ package gcfg import ( + "bytes" + "errors" + "fmt" + "github.com/gogf/gf/container/garray" "github.com/gogf/gf/container/gmap" + "github.com/gogf/gf/encoding/gjson" + "github.com/gogf/gf/errors/gerror" "github.com/gogf/gf/internal/intlog" + "github.com/gogf/gf/os/gcmd" + "github.com/gogf/gf/os/gfile" + "github.com/gogf/gf/os/gfsnotify" + "github.com/gogf/gf/os/glog" + "github.com/gogf/gf/os/gres" + "github.com/gogf/gf/os/gspath" + "github.com/gogf/gf/text/gstr" + "github.com/gogf/gf/util/gmode" ) -var ( - // Customized configuration content. - configs = gmap.NewStrStrMap(true) -) - -// SetContent sets customized configuration content for specified `file`. -// The `file` is unnecessary param, default is DefaultConfigFile. -func SetContent(content string, file ...string) { +// New returns a new configuration management object. +// The parameter `file` specifies the default configuration file name for reading. +func New(file ...string) *Config { name := DefaultConfigFile if len(file) > 0 { name = file[0] + } else { + // Custom default configuration file name from command line or environment. + if customFile := gcmd.GetOptWithEnv(fmt.Sprintf("%s.file", cmdEnvKey)).String(); customFile != "" { + name = customFile + } } - // Clear file cache for instances which cached `name`. - instances.LockFunc(func(m map[string]interface{}) { - if configs.Contains(name) { - for _, v := range m { - v.(*Config).jsonMap.Remove(name) + c := &Config{ + defaultName: name, + searchPaths: garray.NewStrArray(true), + jsonMap: gmap.NewStrAnyMap(true), + } + // Customized dir path from env/cmd. + if customPath := gcmd.GetOptWithEnv(fmt.Sprintf("%s.path", cmdEnvKey)).String(); customPath != "" { + if gfile.Exists(customPath) { + _ = c.SetPath(customPath) + } else { + if errorPrint() { + glog.Errorf("[gcfg] Configuration directory path does not exist: %s", customPath) } } - configs.Set(name, content) - }) -} + } else { + // Dir path of working dir. + if err := c.AddPath(gfile.Pwd()); err != nil { + intlog.Error(err) + } -// GetContent returns customized configuration content for specified `file`. -// The `file` is unnecessary param, default is DefaultConfigFile. -func GetContent(file ...string) string { - name := DefaultConfigFile - if len(file) > 0 { - name = file[0] - } - return configs.Get(name) -} - -// RemoveContent removes the global configuration with specified `file`. -// If `name` is not passed, it removes configuration of the default group name. -func RemoveContent(file ...string) { - name := DefaultConfigFile - if len(file) > 0 { - name = file[0] - } - // Clear file cache for instances which cached `name`. - instances.LockFunc(func(m map[string]interface{}) { - if configs.Contains(name) { - for _, v := range m { - v.(*Config).jsonMap.Remove(name) + // Dir path of main package. + if mainPath := gfile.MainPkgPath(); mainPath != "" && gfile.Exists(mainPath) { + if err := c.AddPath(mainPath); err != nil { + intlog.Error(err) } - configs.Remove(name) } - }) - intlog.Printf(`RemoveContent: %s`, name) + // Dir path of binary. + if selfPath := gfile.SelfDir(); selfPath != "" && gfile.Exists(selfPath) { + if err := c.AddPath(selfPath); err != nil { + intlog.Error(err) + } + } + } + return c } -// ClearContent removes all global configuration contents. -func ClearContent() { - configs.Clear() - // Clear cache for all instances. - instances.LockFunc(func(m map[string]interface{}) { - for _, v := range m { - v.(*Config).jsonMap.Clear() +// Instance returns an instance of Config with default settings. +// The parameter `name` is the name for the instance. But very note that, if the file "name.toml" +// exists in the configuration directory, it then sets it as the default configuration file. The +// toml file type is the default configuration file type. +func Instance(name ...string) *Config { + key := DefaultName + if len(name) > 0 && name[0] != "" { + key = name[0] + } + return instances.GetOrSetFuncLock(key, func() interface{} { + c := New() + // If it's not using default configuration or its configuration file is not available, + // it searches the possible configuration file according to the name and all supported + // file types. + if key != DefaultName || !c.Available() { + for _, fileType := range supportedFileTypes { + if file := fmt.Sprintf(`%s.%s`, key, fileType); c.Available(file) { + c.SetFileName(file) + break + } + } + } + return c + }).(*Config) +} + +// SetPath sets the configuration directory path for file search. +// The parameter `path` can be absolute or relative path, +// but absolute path is strongly recommended. +func (c *Config) SetPath(path string) error { + var ( + isDir = false + realPath = "" + ) + if file := gres.Get(path); file != nil { + realPath = path + isDir = file.FileInfo().IsDir() + } else { + // Absolute path. + realPath = gfile.RealPath(path) + if realPath == "" { + // Relative path. + c.searchPaths.RLockFunc(func(array []string) { + for _, v := range array { + if path, _ := gspath.Search(v, path); path != "" { + realPath = path + break + } + } + }) + } + if realPath != "" { + isDir = gfile.IsDir(realPath) + } + } + // Path not exist. + if realPath == "" { + buffer := bytes.NewBuffer(nil) + if c.searchPaths.Len() > 0 { + buffer.WriteString(fmt.Sprintf("[gcfg] SetPath failed: cannot find directory \"%s\" in following paths:", path)) + c.searchPaths.RLockFunc(func(array []string) { + for k, v := range array { + buffer.WriteString(fmt.Sprintf("\n%d. %s", k+1, v)) + } + }) + } else { + buffer.WriteString(fmt.Sprintf(`[gcfg] SetPath failed: path "%s" does not exist`, path)) + } + err := errors.New(buffer.String()) + if errorPrint() { + glog.Error(err) + } + return err + } + // Should be a directory. + if !isDir { + err := fmt.Errorf(`[gcfg] SetPath failed: path "%s" should be directory type`, path) + if errorPrint() { + glog.Error(err) + } + return err + } + // Repeated path check. + if c.searchPaths.Search(realPath) != -1 { + return nil + } + c.jsonMap.Clear() + c.searchPaths.Clear() + c.searchPaths.Append(realPath) + intlog.Print("SetPath:", realPath) + return nil +} + +// SetViolenceCheck sets whether to perform hierarchical conflict checking. +// This feature needs to be enabled when there is a level symbol in the key name. +// It is off in default. +// +// Note that, turning on this feature is quite expensive, and it is not recommended +// to allow separators in the key names. It is best to avoid this on the application side. +func (c *Config) SetViolenceCheck(check bool) { + c.violenceCheck = check + c.Clear() +} + +// AddPath adds a absolute or relative path to the search paths. +func (c *Config) AddPath(path string) error { + var ( + isDir = false + realPath = "" + ) + // It firstly checks the resource manager, + // and then checks the filesystem for the path. + if file := gres.Get(path); file != nil { + realPath = path + isDir = file.FileInfo().IsDir() + } else { + // Absolute path. + realPath = gfile.RealPath(path) + if realPath == "" { + // Relative path. + c.searchPaths.RLockFunc(func(array []string) { + for _, v := range array { + if path, _ := gspath.Search(v, path); path != "" { + realPath = path + break + } + } + }) + } + if realPath != "" { + isDir = gfile.IsDir(realPath) + } + } + if realPath == "" { + buffer := bytes.NewBuffer(nil) + if c.searchPaths.Len() > 0 { + buffer.WriteString(fmt.Sprintf("[gcfg] AddPath failed: cannot find directory \"%s\" in following paths:", path)) + c.searchPaths.RLockFunc(func(array []string) { + for k, v := range array { + buffer.WriteString(fmt.Sprintf("\n%d. %s", k+1, v)) + } + }) + } else { + buffer.WriteString(fmt.Sprintf(`[gcfg] AddPath failed: path "%s" does not exist`, path)) + } + err := gerror.New(buffer.String()) + if errorPrint() { + glog.Error(err) + } + return err + } + if !isDir { + err := gerror.Newf(`[gcfg] AddPath failed: path "%s" should be directory type`, path) + if errorPrint() { + glog.Error(err) + } + return err + } + // Repeated path check. + if c.searchPaths.Search(realPath) != -1 { + return nil + } + c.searchPaths.Append(realPath) + intlog.Print("AddPath:", realPath) + return nil +} + +// SetFileName sets the default configuration file name. +func (c *Config) SetFileName(name string) *Config { + c.defaultName = name + return c +} + +// GetFileName returns the default configuration file name. +func (c *Config) GetFileName() string { + return c.defaultName +} + +// Available checks and returns whether configuration of given `file` is available. +func (c *Config) Available(file ...string) bool { + var name string + if len(file) > 0 && file[0] != "" { + name = file[0] + } else { + name = c.defaultName + } + if path, _ := c.GetFilePath(name); path != "" { + return true + } + if GetContent(name) != "" { + return true + } + return false +} + +// GetFilePath returns the absolute configuration file path for the given filename by `file`. +// If `file` is not passed, it returns the configuration file path of the default name. +// It returns an empty `path` string and an error if the given `file` does not exist. +func (c *Config) GetFilePath(file ...string) (path string, err error) { + name := c.defaultName + if len(file) > 0 { + name = file[0] + } + // Searching resource manager. + if !gres.IsEmpty() { + for _, v := range resourceTryFiles { + if file := gres.Get(v + name); file != nil { + path = file.Name() + return + } + } + c.searchPaths.RLockFunc(func(array []string) { + for _, prefix := range array { + for _, v := range resourceTryFiles { + if file := gres.Get(prefix + v + name); file != nil { + path = file.Name() + return + } + } + } + }) + } + c.autoCheckAndAddMainPkgPathToSearchPaths() + // Searching the file system. + c.searchPaths.RLockFunc(func(array []string) { + for _, prefix := range array { + prefix = gstr.TrimRight(prefix, `\/`) + if path, _ = gspath.Search(prefix, name); path != "" { + return + } + if path, _ = gspath.Search(prefix+gfile.Separator+"config", name); path != "" { + return + } } }) - - intlog.Print(`RemoveConfig`) + // If it cannot find the path of `file`, it formats and returns a detailed error. + if path == "" { + var ( + buffer = bytes.NewBuffer(nil) + ) + if c.searchPaths.Len() > 0 { + buffer.WriteString(fmt.Sprintf(`[gcfg] cannot find config file "%s" in resource manager or the following paths:`, name)) + c.searchPaths.RLockFunc(func(array []string) { + index := 1 + for _, v := range array { + v = gstr.TrimRight(v, `\/`) + buffer.WriteString(fmt.Sprintf("\n%d. %s", index, v)) + index++ + buffer.WriteString(fmt.Sprintf("\n%d. %s", index, v+gfile.Separator+"config")) + index++ + } + }) + } else { + buffer.WriteString(fmt.Sprintf("[gcfg] cannot find config file \"%s\" with no path configured", name)) + } + err = gerror.New(buffer.String()) + } + return +} + +// autoCheckAndAddMainPkgPathToSearchPaths automatically checks and adds directory path of package main +// to the searching path list if it's currently in development environment. +func (c *Config) autoCheckAndAddMainPkgPathToSearchPaths() { + if gmode.IsDevelop() { + mainPkgPath := gfile.MainPkgPath() + if mainPkgPath != "" { + if !c.searchPaths.Contains(mainPkgPath) { + c.searchPaths.Append(mainPkgPath) + } + } + } +} + +// getJson returns a *gjson.Json object for the specified `file` content. +// It would print error if file reading fails. It return nil if any error occurs. +func (c *Config) getJson(file ...string) *gjson.Json { + var name string + if len(file) > 0 && file[0] != "" { + name = file[0] + } else { + name = c.defaultName + } + r := c.jsonMap.GetOrSetFuncLock(name, func() interface{} { + var ( + err error + content string + filePath string + ) + // The configured content can be any kind of data type different from its file type. + isFromConfigContent := true + if content = GetContent(name); content == "" { + isFromConfigContent = false + filePath, err = c.GetFilePath(name) + if err != nil && errorPrint() { + glog.Error(err) + } + if filePath == "" { + return nil + } + if file := gres.Get(filePath); file != nil { + content = string(file.Content()) + } else { + content = gfile.GetContents(filePath) + } + } + // Note that the underlying configuration json object operations are concurrent safe. + var ( + j *gjson.Json + ) + dataType := gfile.ExtName(name) + if gjson.IsValidDataType(dataType) && !isFromConfigContent { + j, err = gjson.LoadContentType(dataType, content, true) + } else { + j, err = gjson.LoadContent(content, true) + } + if err == nil { + j.SetViolenceCheck(c.violenceCheck) + // Add monitor for this configuration file, + // any changes of this file will refresh its cache in Config object. + if filePath != "" && !gres.Contains(filePath) { + _, err = gfsnotify.Add(filePath, func(event *gfsnotify.Event) { + c.jsonMap.Remove(name) + }) + if err != nil && errorPrint() { + glog.Error(err) + } + } + return j + } + if errorPrint() { + if filePath != "" { + glog.Criticalf(`[gcfg] load config file "%s" failed: %s`, filePath, err.Error()) + } else { + glog.Criticalf(`[gcfg] load configuration failed: %s`, err.Error()) + } + } + return nil + }) + if r != nil { + return r.(*gjson.Json) + } + return nil } diff --git a/os/gcfg/gcfg_api.go b/os/gcfg/gcfg_config_api.go similarity index 100% rename from os/gcfg/gcfg_api.go rename to os/gcfg/gcfg_config_api.go diff --git a/os/gcfg/gcfg_error.go b/os/gcfg/gcfg_error.go deleted file mode 100644 index fd7df93b3..000000000 --- a/os/gcfg/gcfg_error.go +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. -// -// This Source Code Form is subject to the terms of the MIT License. -// If a copy of the MIT was not distributed with this file, -// You can obtain one at https://github.com/gogf/gf. - -package gcfg - -import ( - "github.com/gogf/gf/os/gcmd" -) - -const ( - // errorPrintKey is used to specify the key controlling error printing to stdout. - // This error is designed not to be returned by functions. - errorPrintKey = "gf.gcfg.errorprint" -) - -// errorPrint checks whether printing error to stdout. -func errorPrint() bool { - return gcmd.GetOptWithEnv(errorPrintKey, true).Bool() -} diff --git a/os/gcfg/gcfg_instance.go b/os/gcfg/gcfg_instance.go deleted file mode 100644 index 4ef396801..000000000 --- a/os/gcfg/gcfg_instance.go +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. -// -// This Source Code Form is subject to the terms of the MIT License. -// If a copy of the MIT was not distributed with this file, -// You can obtain one at https://github.com/gogf/gf. - -package gcfg - -import ( - "fmt" - "github.com/gogf/gf/container/gmap" -) - -const ( - // Default group name for instance usage. - DefaultName = "config" -) - -var ( - // Instances map containing configuration instances. - instances = gmap.NewStrAnyMap(true) -) - -// Instance returns an instance of Config with default settings. -// The parameter `name` is the name for the instance. But very note that, if the file "name.toml" -// exists in the configuration directory, it then sets it as the default configuration file. The -// toml file type is the default configuration file type. -func Instance(name ...string) *Config { - key := DefaultName - if len(name) > 0 && name[0] != "" { - key = name[0] - } - return instances.GetOrSetFuncLock(key, func() interface{} { - c := New() - // If it's not using default configuration or its configuration file is not available, - // it searches the possible configuration file according to the name and all supported - // file types. - if key != DefaultName || !c.Available() { - for _, fileType := range supportedFileTypes { - if file := fmt.Sprintf(`%s.%s`, key, fileType); c.Available(file) { - c.SetFileName(file) - break - } - } - } - return c - }).(*Config) -}