gcfg_config.go 12 KB

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