123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420 |
- // 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 (
- "bytes"
- "context"
- "fmt"
- "github.com/gogf/gf/container/garray"
- "github.com/gogf/gf/container/gmap"
- "github.com/gogf/gf/encoding/gjson"
- "github.com/gogf/gf/errors/gcode"
- "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"
- )
- // 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(commandEnvKeyForFile).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(commandEnvKeyForPath).String(); customPath != "" {
- if gfile.Exists(customPath) {
- _ = c.SetPath(customPath)
- } else {
- if errorPrint() {
- glog.Errorf("[gcfg] Configuration directory path does not exist: %s", customPath)
- }
- }
- } else {
- // Dir path of working dir.
- if err := c.AddPath(gfile.Pwd()); err != nil {
- intlog.Error(context.TODO(), err)
- }
- // Dir path of main package.
- if mainPath := gfile.MainPkgPath(); mainPath != "" && gfile.Exists(mainPath) {
- if err := c.AddPath(mainPath); err != nil {
- intlog.Error(context.TODO(), err)
- }
- }
- // Dir path of binary.
- if selfPath := gfile.SelfDir(); selfPath != "" && gfile.Exists(selfPath) {
- if err := c.AddPath(selfPath); err != nil {
- intlog.Error(context.TODO(), err)
- }
- }
- }
- return c
- }
- // 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 := gerror.NewCode(gcode.CodeOperationFailed, 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(context.TODO(), "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.NewCode(gcode.CodeOperationFailed, buffer.String())
- if errorPrint() {
- glog.Error(err)
- }
- return err
- }
- if !isDir {
- err := gerror.NewCodef(gcode.CodeInvalidParameter, `[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(context.TODO(), "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
- }
- }
- })
- // 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.NewCode(gcode.CodeOperationFailed, 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
- }
|