github.com/zhongdalu/gf@v1.0.0/g/os/gcfg/gcfg.go (about)

     1  // Copyright 2017 gf Author(https://github.com/zhongdalu/gf). 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/zhongdalu/gf.
     6  
     7  // Package gcfg provides reading, caching and managing for configuration.
     8  package gcfg
     9  
    10  import (
    11  	"bytes"
    12  	"errors"
    13  	"fmt"
    14  	"time"
    15  
    16  	"github.com/zhongdalu/gf/g/container/garray"
    17  	"github.com/zhongdalu/gf/g/container/gmap"
    18  	"github.com/zhongdalu/gf/g/container/gtype"
    19  	"github.com/zhongdalu/gf/g/container/gvar"
    20  	"github.com/zhongdalu/gf/g/encoding/gjson"
    21  	"github.com/zhongdalu/gf/g/internal/cmdenv"
    22  	"github.com/zhongdalu/gf/g/os/gfile"
    23  	"github.com/zhongdalu/gf/g/os/gfsnotify"
    24  	"github.com/zhongdalu/gf/g/os/glog"
    25  	"github.com/zhongdalu/gf/g/os/gspath"
    26  	"github.com/zhongdalu/gf/g/os/gtime"
    27  )
    28  
    29  const (
    30  	// DEFAULT_CONFIG_FILE is the default configuration file name.
    31  	DEFAULT_CONFIG_FILE = "config.toml"
    32  )
    33  
    34  // Configuration struct.
    35  type Config struct {
    36  	name  *gtype.String       // Default configuration file name.
    37  	paths *garray.StringArray // Searching path array.
    38  	jsons *gmap.StrAnyMap     // The pared JSON objects for configuration files.
    39  	vc    *gtype.Bool         // Whether do violence check in value index searching. It affects the performance when set true(false in default).
    40  }
    41  
    42  // New returns a new configuration management object.
    43  // The parameter <file> specifies the default configuration file name for reading.
    44  func New(file ...string) *Config {
    45  	name := DEFAULT_CONFIG_FILE
    46  	if len(file) > 0 {
    47  		name = file[0]
    48  	}
    49  	c := &Config{
    50  		name:  gtype.NewString(name),
    51  		paths: garray.NewStringArray(),
    52  		jsons: gmap.NewStrAnyMap(),
    53  		vc:    gtype.NewBool(),
    54  	}
    55  	// Customized dir path from env/cmd.
    56  	if envPath := cmdenv.Get("gf.gcfg.path").String(); envPath != "" {
    57  		if gfile.Exists(envPath) {
    58  			_ = c.SetPath(envPath)
    59  		} else {
    60  			if errorPrint() {
    61  				glog.Errorf("Configuration directory path does not exist: %s", envPath)
    62  			}
    63  		}
    64  	} else {
    65  		// Dir path of working dir.
    66  		_ = c.SetPath(gfile.Pwd())
    67  		// Dir path of binary.
    68  		if selfPath := gfile.SelfDir(); selfPath != "" && gfile.Exists(selfPath) {
    69  			_ = c.AddPath(selfPath)
    70  		}
    71  		// Dir path of main package.
    72  		if mainPath := gfile.MainPkgPath(); mainPath != "" && gfile.Exists(mainPath) {
    73  			_ = c.AddPath(mainPath)
    74  		}
    75  	}
    76  	return c
    77  }
    78  
    79  // filePath returns the absolute configuration file path for the given filename by <file>.
    80  func (c *Config) filePath(file ...string) (path string) {
    81  	name := c.name.Val()
    82  	if len(file) > 0 {
    83  		name = file[0]
    84  	}
    85  	path = c.FilePath(name)
    86  	if path == "" {
    87  		buffer := bytes.NewBuffer(nil)
    88  		if c.paths.Len() > 0 {
    89  			buffer.WriteString(fmt.Sprintf("[gcfg] cannot find config file \"%s\" in following paths:", name))
    90  			c.paths.RLockFunc(func(array []string) {
    91  				index := 1
    92  				for _, v := range array {
    93  					buffer.WriteString(fmt.Sprintf("\n%d. %s", index, v))
    94  					index++
    95  					buffer.WriteString(fmt.Sprintf("\n%d. %s", index, v+gfile.Separator+"config"))
    96  					index++
    97  				}
    98  			})
    99  		} else {
   100  			buffer.WriteString(fmt.Sprintf("[gcfg] cannot find config file \"%s\" with no path set/add", name))
   101  		}
   102  		if errorPrint() {
   103  			glog.Error(buffer.String())
   104  		}
   105  	}
   106  	return path
   107  }
   108  
   109  // SetPath sets the configuration directory path for file search.
   110  // The parameter <path> can be absolute or relative path,
   111  // but absolute path is strongly recommended.
   112  func (c *Config) SetPath(path string) error {
   113  	// Absolute path.
   114  	realPath := gfile.RealPath(path)
   115  	if realPath == "" {
   116  		// Relative path.
   117  		c.paths.RLockFunc(func(array []string) {
   118  			for _, v := range array {
   119  				if path, _ := gspath.Search(v, path); path != "" {
   120  					realPath = path
   121  					break
   122  				}
   123  			}
   124  		})
   125  	}
   126  	// Path not exist.
   127  	if realPath == "" {
   128  		buffer := bytes.NewBuffer(nil)
   129  		if c.paths.Len() > 0 {
   130  			buffer.WriteString(fmt.Sprintf("[gcfg] SetPath failed: cannot find directory \"%s\" in following paths:", path))
   131  			c.paths.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 := errors.New(buffer.String())
   140  		if errorPrint() {
   141  			glog.Error(err)
   142  		}
   143  		return err
   144  	}
   145  	// Should be a directory.
   146  	if !gfile.IsDir(realPath) {
   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.paths.Search(realPath) != -1 {
   155  		return nil
   156  	}
   157  	c.jsons.Clear()
   158  	c.paths.Clear()
   159  	c.paths.Append(realPath)
   160  	return nil
   161  }
   162  
   163  // SetViolenceCheck sets whether to perform hierarchical conflict check.
   164  // This feature needs to be enabled when there is a level symbol in the key name.
   165  // The default is off.
   166  // Turning on this feature is quite expensive,
   167  // and it is not recommended to allow separators in the key names.
   168  // It is best to avoid this on the application side.
   169  func (c *Config) SetViolenceCheck(check bool) {
   170  	c.vc.Set(check)
   171  	c.Clear()
   172  }
   173  
   174  // AddPath adds a absolute or relative path to the search paths.
   175  func (c *Config) AddPath(path string) error {
   176  	// Absolute path.
   177  	realPath := gfile.RealPath(path)
   178  	if realPath == "" {
   179  		// Relative path.
   180  		c.paths.RLockFunc(func(array []string) {
   181  			for _, v := range array {
   182  				if path, _ := gspath.Search(v, path); path != "" {
   183  					realPath = path
   184  					break
   185  				}
   186  			}
   187  		})
   188  	}
   189  	if realPath == "" {
   190  		buffer := bytes.NewBuffer(nil)
   191  		if c.paths.Len() > 0 {
   192  			buffer.WriteString(fmt.Sprintf("[gcfg] AddPath failed: cannot find directory \"%s\" in following paths:", path))
   193  			c.paths.RLockFunc(func(array []string) {
   194  				for k, v := range array {
   195  					buffer.WriteString(fmt.Sprintf("\n%d. %s", k+1, v))
   196  				}
   197  			})
   198  		} else {
   199  			buffer.WriteString(fmt.Sprintf(`[gcfg] AddPath failed: path "%s" does not exist`, path))
   200  		}
   201  		err := errors.New(buffer.String())
   202  		if errorPrint() {
   203  			glog.Error(err)
   204  		}
   205  		return err
   206  	}
   207  	if !gfile.IsDir(realPath) {
   208  		err := fmt.Errorf(`[gcfg] AddPath failed: path "%s" should be directory type`, path)
   209  		if errorPrint() {
   210  			glog.Error(err)
   211  		}
   212  		return err
   213  	}
   214  	// Repeated path check.
   215  	if c.paths.Search(realPath) != -1 {
   216  		return nil
   217  	}
   218  	c.paths.Append(realPath)
   219  	//glog.Debug("[gcfg] AddPath:", realPath)
   220  	return nil
   221  }
   222  
   223  // GetFilePath is alias of FilePath.
   224  // Deprecated.
   225  func (c *Config) GetFilePath(file ...string) (path string) {
   226  	return c.FilePath(file...)
   227  }
   228  
   229  // GetFilePath returns the absolute path of the specified configuration file.
   230  // If <file> is not passed, it returns the configuration file path of the default name.
   231  // If the specified configuration file does not exist,
   232  // an empty string is returned.
   233  func (c *Config) FilePath(file ...string) (path string) {
   234  	name := c.name.Val()
   235  	if len(file) > 0 {
   236  		name = file[0]
   237  	}
   238  	c.paths.RLockFunc(func(array []string) {
   239  		for _, v := range array {
   240  			if path, _ = gspath.Search(v, name); path != "" {
   241  				break
   242  			}
   243  			if path, _ = gspath.Search(v+gfile.Separator+"config", name); path != "" {
   244  				break
   245  			}
   246  		}
   247  	})
   248  	return
   249  }
   250  
   251  // SetFileName sets the default configuration file name.
   252  func (c *Config) SetFileName(name string) {
   253  	c.name.Set(name)
   254  }
   255  
   256  // GetFileName returns the default configuration file name.
   257  func (c *Config) GetFileName() string {
   258  	return c.name.Val()
   259  }
   260  
   261  // getJson returns a *gjson.Json object for the specified <file> content.
   262  // It would print error if file reading fails.
   263  // If any error occurs, it return nil.
   264  func (c *Config) getJson(file ...string) *gjson.Json {
   265  	name := c.name.Val()
   266  	if len(file) > 0 {
   267  		name = file[0]
   268  	}
   269  	r := c.jsons.GetOrSetFuncLock(name, func() interface{} {
   270  		content := ""
   271  		filePath := ""
   272  		if content = GetContent(name); content == "" {
   273  			filePath = c.filePath(name)
   274  			if filePath == "" {
   275  				return nil
   276  			}
   277  			content = gfile.GetContents(filePath)
   278  		}
   279  		if j, err := gjson.LoadContent(content); err == nil {
   280  			j.SetViolenceCheck(c.vc.Val())
   281  			// Add monitor for this configuration file,
   282  			// any changes of this file will refresh its cache in Config object.
   283  			if filePath != "" {
   284  				_, err = gfsnotify.Add(filePath, func(event *gfsnotify.Event) {
   285  					c.jsons.Remove(name)
   286  				})
   287  				if err != nil && errorPrint() {
   288  					glog.Error(err)
   289  				}
   290  			}
   291  			return j
   292  		} else {
   293  			if errorPrint() {
   294  				if filePath != "" {
   295  					glog.Criticalf(`[gcfg] Load config file "%s" failed: %s`, filePath, err.Error())
   296  				} else {
   297  					glog.Criticalf(`[gcfg] Load configuration failed: %s`, err.Error())
   298  				}
   299  			}
   300  		}
   301  		return nil
   302  	})
   303  	if r != nil {
   304  		return r.(*gjson.Json)
   305  	}
   306  	return nil
   307  }
   308  
   309  func (c *Config) Get(pattern string, def ...interface{}) interface{} {
   310  	if j := c.getJson(); j != nil {
   311  		return j.Get(pattern, def...)
   312  	}
   313  	return nil
   314  }
   315  
   316  func (c *Config) GetVar(pattern string, def ...interface{}) *gvar.Var {
   317  	if j := c.getJson(); j != nil {
   318  		return gvar.New(j.Get(pattern, def...), true)
   319  	}
   320  	return gvar.New(nil, true)
   321  }
   322  
   323  func (c *Config) Contains(pattern string) bool {
   324  	if j := c.getJson(); j != nil {
   325  		return j.Contains(pattern)
   326  	}
   327  	return false
   328  }
   329  
   330  func (c *Config) GetMap(pattern string, def ...interface{}) map[string]interface{} {
   331  	if j := c.getJson(); j != nil {
   332  		return j.GetMap(pattern, def...)
   333  	}
   334  	return nil
   335  }
   336  
   337  func (c *Config) GetArray(pattern string, def ...interface{}) []interface{} {
   338  	if j := c.getJson(); j != nil {
   339  		return j.GetArray(pattern, def...)
   340  	}
   341  	return nil
   342  }
   343  
   344  func (c *Config) GetString(pattern string, def ...interface{}) string {
   345  	if j := c.getJson(); j != nil {
   346  		return j.GetString(pattern, def...)
   347  	}
   348  	return ""
   349  }
   350  
   351  func (c *Config) GetStrings(pattern string, def ...interface{}) []string {
   352  	if j := c.getJson(); j != nil {
   353  		return j.GetStrings(pattern, def...)
   354  	}
   355  	return nil
   356  }
   357  
   358  func (c *Config) GetInterfaces(pattern string, def ...interface{}) []interface{} {
   359  	if j := c.getJson(); j != nil {
   360  		return j.GetInterfaces(pattern, def...)
   361  	}
   362  	return nil
   363  }
   364  
   365  func (c *Config) GetBool(pattern string, def ...interface{}) bool {
   366  	if j := c.getJson(); j != nil {
   367  		return j.GetBool(pattern, def...)
   368  	}
   369  	return false
   370  }
   371  
   372  func (c *Config) GetFloat32(pattern string, def ...interface{}) float32 {
   373  	if j := c.getJson(); j != nil {
   374  		return j.GetFloat32(pattern, def...)
   375  	}
   376  	return 0
   377  }
   378  
   379  func (c *Config) GetFloat64(pattern string, def ...interface{}) float64 {
   380  	if j := c.getJson(); j != nil {
   381  		return j.GetFloat64(pattern, def...)
   382  	}
   383  	return 0
   384  }
   385  
   386  func (c *Config) GetFloats(pattern string, def ...interface{}) []float64 {
   387  	if j := c.getJson(); j != nil {
   388  		return j.GetFloats(pattern, def...)
   389  	}
   390  	return nil
   391  }
   392  
   393  func (c *Config) GetInt(pattern string, def ...interface{}) int {
   394  	if j := c.getJson(); j != nil {
   395  		return j.GetInt(pattern, def...)
   396  	}
   397  	return 0
   398  }
   399  
   400  func (c *Config) GetInt8(pattern string, def ...interface{}) int8 {
   401  	if j := c.getJson(); j != nil {
   402  		return j.GetInt8(pattern, def...)
   403  	}
   404  	return 0
   405  }
   406  
   407  func (c *Config) GetInt16(pattern string, def ...interface{}) int16 {
   408  	if j := c.getJson(); j != nil {
   409  		return j.GetInt16(pattern, def...)
   410  	}
   411  	return 0
   412  }
   413  
   414  func (c *Config) GetInt32(pattern string, def ...interface{}) int32 {
   415  	if j := c.getJson(); j != nil {
   416  		return j.GetInt32(pattern, def...)
   417  	}
   418  	return 0
   419  }
   420  
   421  func (c *Config) GetInt64(pattern string, def ...interface{}) int64 {
   422  	if j := c.getJson(); j != nil {
   423  		return j.GetInt64(pattern, def...)
   424  	}
   425  	return 0
   426  }
   427  
   428  func (c *Config) GetInts(pattern string, def ...interface{}) []int {
   429  	if j := c.getJson(); j != nil {
   430  		return j.GetInts(pattern, def...)
   431  	}
   432  	return nil
   433  }
   434  
   435  func (c *Config) GetUint(pattern string, def ...interface{}) uint {
   436  	if j := c.getJson(); j != nil {
   437  		return j.GetUint(pattern, def...)
   438  	}
   439  	return 0
   440  }
   441  
   442  func (c *Config) GetUint8(pattern string, def ...interface{}) uint8 {
   443  	if j := c.getJson(); j != nil {
   444  		return j.GetUint8(pattern, def...)
   445  	}
   446  	return 0
   447  }
   448  
   449  func (c *Config) GetUint16(pattern string, def ...interface{}) uint16 {
   450  	if j := c.getJson(); j != nil {
   451  		return j.GetUint16(pattern, def...)
   452  	}
   453  	return 0
   454  }
   455  
   456  func (c *Config) GetUint32(pattern string, def ...interface{}) uint32 {
   457  	if j := c.getJson(); j != nil {
   458  		return j.GetUint32(pattern, def...)
   459  	}
   460  	return 0
   461  }
   462  
   463  func (c *Config) GetUint64(pattern string, def ...interface{}) uint64 {
   464  	if j := c.getJson(); j != nil {
   465  		return j.GetUint64(pattern, def...)
   466  	}
   467  	return 0
   468  }
   469  
   470  func (c *Config) GetTime(pattern string, format ...string) time.Time {
   471  	if j := c.getJson(); j != nil {
   472  		return j.GetTime(pattern, format...)
   473  	}
   474  	return time.Time{}
   475  }
   476  
   477  func (c *Config) GetDuration(pattern string, def ...interface{}) time.Duration {
   478  	if j := c.getJson(); j != nil {
   479  		return j.GetDuration(pattern, def...)
   480  	}
   481  	return 0
   482  }
   483  
   484  func (c *Config) GetGTime(pattern string, format ...string) *gtime.Time {
   485  	if j := c.getJson(); j != nil {
   486  		return j.GetGTime(pattern, format...)
   487  	}
   488  	return nil
   489  }
   490  
   491  func (c *Config) GetStruct(pattern string, pointer interface{}, mapping ...map[string]string) error {
   492  	if j := c.getJson(); j != nil {
   493  		return j.GetStruct(pattern, pointer, mapping...)
   494  	}
   495  	return errors.New("config file not found")
   496  }
   497  
   498  func (c *Config) GetStructDeep(pattern string, pointer interface{}, mapping ...map[string]string) error {
   499  	if j := c.getJson(); j != nil {
   500  		return j.GetStructDeep(pattern, pointer, mapping...)
   501  	}
   502  	return errors.New("config file not found")
   503  }
   504  
   505  func (c *Config) GetStructs(pattern string, pointer interface{}, mapping ...map[string]string) error {
   506  	if j := c.getJson(); j != nil {
   507  		return j.GetStructs(pattern, pointer, mapping...)
   508  	}
   509  	return errors.New("config file not found")
   510  }
   511  
   512  func (c *Config) GetStructsDeep(pattern string, pointer interface{}, mapping ...map[string]string) error {
   513  	if j := c.getJson(); j != nil {
   514  		return j.GetStructsDeep(pattern, pointer, mapping...)
   515  	}
   516  	return errors.New("config file not found")
   517  }
   518  
   519  // Deprecated.
   520  func (c *Config) GetToStruct(pattern string, pointer interface{}) error {
   521  	return c.GetStruct(pattern, pointer)
   522  }
   523  
   524  // Reload is alias of Clear.
   525  // Deprecated.
   526  func (c *Config) Reload() {
   527  	c.jsons.Clear()
   528  }
   529  
   530  // Clear removes all parsed configuration files content cache,
   531  // which will force reload configuration content from file.
   532  func (c *Config) Clear() {
   533  	c.jsons.Clear()
   534  }