pkg.re/essentialkaos/ek.10@v12.41.0+incompatible/knf/knf.go (about)

     1  // Package knf provides methods for working with configuration files in KNF format
     2  package knf
     3  
     4  // ////////////////////////////////////////////////////////////////////////////////// //
     5  //                                                                                    //
     6  //                         Copyright (c) 2022 ESSENTIAL KAOS                          //
     7  //      Apache License, Version 2.0 <https://www.apache.org/licenses/LICENSE-2.0>     //
     8  //                                                                                    //
     9  // ////////////////////////////////////////////////////////////////////////////////// //
    10  
    11  import (
    12  	"errors"
    13  	"os"
    14  	"strconv"
    15  	"strings"
    16  	"sync"
    17  	"time"
    18  )
    19  
    20  // ////////////////////////////////////////////////////////////////////////////////// //
    21  
    22  // Config is basic config struct
    23  type Config struct {
    24  	sections []string
    25  	props    []string
    26  	data     map[string]string
    27  	file     string
    28  
    29  	mx *sync.RWMutex
    30  }
    31  
    32  // Validator is config property validator struct
    33  type Validator struct {
    34  	Property string            // Property name
    35  	Func     PropertyValidator // Validation function
    36  	Value    interface{}       // Expected value
    37  }
    38  
    39  // PropertyValidator is default type of property validation function
    40  type PropertyValidator func(config *Config, prop string, value interface{}) error
    41  
    42  // ////////////////////////////////////////////////////////////////////////////////// //
    43  
    44  var (
    45  	ErrConfigIsNil = errors.New("Config struct is nil")
    46  	ErrFileNotSet  = errors.New("Path to config file is empty (non initialized struct?)")
    47  )
    48  
    49  // ////////////////////////////////////////////////////////////////////////////////// //
    50  
    51  // global is global configuration file
    52  var global *Config
    53  
    54  // ////////////////////////////////////////////////////////////////////////////////// //
    55  
    56  // Global reads and parses configuration file
    57  // Global instance is accessible from any part of the code
    58  func Global(file string) error {
    59  	config, err := Read(file)
    60  
    61  	if err != nil {
    62  		return err
    63  	}
    64  
    65  	global = config
    66  
    67  	return nil
    68  }
    69  
    70  // Read reads and parses configuration file
    71  func Read(file string) (*Config, error) {
    72  	return readKNFFile(file)
    73  }
    74  
    75  // Reload reloads global configuration file
    76  func Reload() (map[string]bool, error) {
    77  	if global == nil {
    78  		return nil, ErrConfigIsNil
    79  	}
    80  
    81  	return global.Reload()
    82  }
    83  
    84  // GetS returns configuration value as string
    85  func GetS(name string, defvals ...string) string {
    86  	if global == nil {
    87  		if len(defvals) == 0 {
    88  			return ""
    89  		}
    90  
    91  		return defvals[0]
    92  	}
    93  
    94  	return global.GetS(name, defvals...)
    95  }
    96  
    97  // GetI returns configuration value as int
    98  func GetI(name string, defvals ...int) int {
    99  	if global == nil {
   100  		if len(defvals) == 0 {
   101  			return 0
   102  		}
   103  
   104  		return defvals[0]
   105  	}
   106  
   107  	return global.GetI(name, defvals...)
   108  }
   109  
   110  // GetI64 returns configuration value as int64
   111  func GetI64(name string, defvals ...int64) int64 {
   112  	if global == nil {
   113  		if len(defvals) == 0 {
   114  			return 0
   115  		}
   116  
   117  		return defvals[0]
   118  	}
   119  
   120  	return global.GetI64(name, defvals...)
   121  }
   122  
   123  // GetU returns configuration value as uint
   124  func GetU(name string, defvals ...uint) uint {
   125  	if global == nil {
   126  		if len(defvals) == 0 {
   127  			return 0
   128  		}
   129  
   130  		return defvals[0]
   131  	}
   132  
   133  	return global.GetU(name, defvals...)
   134  }
   135  
   136  // GetU64 returns configuration value as uint64
   137  func GetU64(name string, defvals ...uint64) uint64 {
   138  	if global == nil {
   139  		if len(defvals) == 0 {
   140  			return 0
   141  		}
   142  
   143  		return defvals[0]
   144  	}
   145  
   146  	return global.GetU64(name, defvals...)
   147  }
   148  
   149  // GetF returns configuration value as floating number
   150  func GetF(name string, defvals ...float64) float64 {
   151  	if global == nil {
   152  		if len(defvals) == 0 {
   153  			return 0.0
   154  		}
   155  
   156  		return defvals[0]
   157  	}
   158  
   159  	return global.GetF(name, defvals...)
   160  }
   161  
   162  // GetB returns configuration value as boolean
   163  func GetB(name string, defvals ...bool) bool {
   164  	if global == nil {
   165  		if len(defvals) == 0 {
   166  			return false
   167  		}
   168  
   169  		return defvals[0]
   170  	}
   171  
   172  	return global.GetB(name, defvals...)
   173  }
   174  
   175  // GetM returns configuration value as file mode
   176  func GetM(name string, defvals ...os.FileMode) os.FileMode {
   177  	if global == nil {
   178  		if len(defvals) == 0 {
   179  			return os.FileMode(0)
   180  		}
   181  
   182  		return defvals[0]
   183  	}
   184  
   185  	return global.GetM(name, defvals...)
   186  }
   187  
   188  // GetD returns configuration values as duration
   189  func GetD(name string, defvals ...time.Duration) time.Duration {
   190  	if global == nil {
   191  		if len(defvals) == 0 {
   192  			return time.Duration(0)
   193  		}
   194  
   195  		return defvals[0]
   196  	}
   197  
   198  	return global.GetD(name, defvals...)
   199  }
   200  
   201  // HasSection checks if section exist
   202  func HasSection(section string) bool {
   203  	if global == nil {
   204  		return false
   205  	}
   206  
   207  	return global.HasSection(section)
   208  }
   209  
   210  // HasProp checks if property exist
   211  func HasProp(name string) bool {
   212  	if global == nil {
   213  		return false
   214  	}
   215  
   216  	return global.HasProp(name)
   217  }
   218  
   219  // Sections returns slice with section names
   220  func Sections() []string {
   221  	if global == nil {
   222  		return nil
   223  	}
   224  
   225  	return global.Sections()
   226  }
   227  
   228  // Props returns slice with properties names in some section
   229  func Props(section string) []string {
   230  	if global == nil {
   231  		return nil
   232  	}
   233  
   234  	return global.Props(section)
   235  }
   236  
   237  // Validate executes all given validators and
   238  // returns slice with validation errors
   239  func Validate(validators []*Validator) []error {
   240  	if global == nil {
   241  		return []error{ErrConfigIsNil}
   242  	}
   243  
   244  	return global.Validate(validators)
   245  }
   246  
   247  // ////////////////////////////////////////////////////////////////////////////////// //
   248  
   249  // Reload reloads configuration file
   250  func (c *Config) Reload() (map[string]bool, error) {
   251  	if c == nil || c.mx == nil {
   252  		return nil, ErrConfigIsNil
   253  	}
   254  
   255  	if c.file == "" {
   256  		return nil, ErrFileNotSet
   257  	}
   258  
   259  	nc, err := Read(c.file)
   260  
   261  	if err != nil {
   262  		return nil, err
   263  	}
   264  
   265  	changes := make(map[string]bool)
   266  
   267  	c.mx.RLock()
   268  
   269  	for _, prop := range c.props {
   270  		changes[prop] = c.GetS(prop) != nc.GetS(prop)
   271  	}
   272  
   273  	c.mx.RUnlock()
   274  	c.mx.Lock()
   275  
   276  	// Update current config data
   277  	c.data, c.sections, c.props = nc.data, nc.sections, nc.props
   278  
   279  	c.mx.Unlock()
   280  	return changes, nil
   281  }
   282  
   283  // GetS returns configuration value as string
   284  func (c *Config) GetS(name string, defvals ...string) string {
   285  	if c == nil || c.mx == nil {
   286  		if len(defvals) == 0 {
   287  			return ""
   288  		}
   289  
   290  		return defvals[0]
   291  	}
   292  
   293  	c.mx.RLock()
   294  	val := c.data[strings.ToLower(name)]
   295  	c.mx.RUnlock()
   296  
   297  	if val == "" {
   298  		if len(defvals) == 0 {
   299  			return ""
   300  		}
   301  
   302  		return defvals[0]
   303  	}
   304  
   305  	return val
   306  }
   307  
   308  // GetI64 returns configuration value as int64
   309  func (c *Config) GetI64(name string, defvals ...int64) int64 {
   310  	if c == nil || c.mx == nil {
   311  		if len(defvals) == 0 {
   312  			return 0
   313  		}
   314  
   315  		return defvals[0]
   316  	}
   317  
   318  	c.mx.RLock()
   319  	val := c.data[strings.ToLower(name)]
   320  	c.mx.RUnlock()
   321  
   322  	if val == "" {
   323  		if len(defvals) == 0 {
   324  			return 0
   325  		}
   326  
   327  		return defvals[0]
   328  	}
   329  
   330  	// HEX Parsing
   331  	if len(val) >= 3 && val[0:2] == "0x" {
   332  		valHex, err := strconv.ParseInt(val[2:], 16, 0)
   333  
   334  		if err != nil {
   335  			return 0
   336  		}
   337  
   338  		return valHex
   339  	}
   340  
   341  	valInt, err := strconv.ParseInt(val, 10, 0)
   342  
   343  	if err != nil {
   344  		return 0
   345  	}
   346  
   347  	return valInt
   348  }
   349  
   350  // GetI returns configuration value as int
   351  func (c *Config) GetI(name string, defvals ...int) int {
   352  	if len(defvals) != 0 {
   353  		return int(c.GetI64(name, int64(defvals[0])))
   354  	}
   355  
   356  	return int(c.GetI64(name))
   357  }
   358  
   359  // GetU returns configuration value as uint
   360  func (c *Config) GetU(name string, defvals ...uint) uint {
   361  	if len(defvals) != 0 {
   362  		return uint(c.GetI64(name, int64(defvals[0])))
   363  	}
   364  
   365  	return uint(c.GetI64(name))
   366  }
   367  
   368  // GetU64 returns configuration value as uint64
   369  func (c *Config) GetU64(name string, defvals ...uint64) uint64 {
   370  	if len(defvals) != 0 {
   371  		return uint64(c.GetI64(name, int64(defvals[0])))
   372  	}
   373  
   374  	return uint64(c.GetI64(name))
   375  }
   376  
   377  // GetF returns configuration value as floating number
   378  func (c *Config) GetF(name string, defvals ...float64) float64 {
   379  	if c == nil || c.mx == nil {
   380  		if len(defvals) == 0 {
   381  			return 0.0
   382  		}
   383  
   384  		return defvals[0]
   385  	}
   386  
   387  	c.mx.RLock()
   388  	val := c.data[strings.ToLower(name)]
   389  	c.mx.RUnlock()
   390  
   391  	if val == "" {
   392  		if len(defvals) == 0 {
   393  			return 0.0
   394  		}
   395  
   396  		return defvals[0]
   397  	}
   398  
   399  	valFl, err := strconv.ParseFloat(val, 64)
   400  
   401  	if err != nil {
   402  		return 0.0
   403  	}
   404  
   405  	return valFl
   406  }
   407  
   408  // GetB returns configuration value as boolean
   409  func (c *Config) GetB(name string, defvals ...bool) bool {
   410  	if c == nil || c.mx == nil {
   411  		if len(defvals) == 0 {
   412  			return false
   413  		}
   414  
   415  		return defvals[0]
   416  	}
   417  
   418  	c.mx.RLock()
   419  	val := c.data[strings.ToLower(name)]
   420  	c.mx.RUnlock()
   421  
   422  	if val == "" {
   423  		if len(defvals) == 0 {
   424  			return false
   425  		}
   426  
   427  		return defvals[0]
   428  	}
   429  
   430  	switch val {
   431  	case "", "0", "false", "no":
   432  		return false
   433  	default:
   434  		return true
   435  	}
   436  }
   437  
   438  // GetM returns configuration value as file mode
   439  func (c *Config) GetM(name string, defvals ...os.FileMode) os.FileMode {
   440  	if c == nil || c.mx == nil {
   441  		if len(defvals) == 0 {
   442  			return os.FileMode(0)
   443  		}
   444  
   445  		return defvals[0]
   446  	}
   447  
   448  	c.mx.RLock()
   449  	val := c.data[strings.ToLower(name)]
   450  	c.mx.RUnlock()
   451  
   452  	if val == "" {
   453  		if len(defvals) == 0 {
   454  			return os.FileMode(0)
   455  		}
   456  
   457  		return defvals[0]
   458  	}
   459  
   460  	valM, err := strconv.ParseUint(val, 8, 32)
   461  
   462  	if err != nil {
   463  		return 0
   464  	}
   465  
   466  	return os.FileMode(valM)
   467  }
   468  
   469  // GetD returns configuration value as duration
   470  func (c *Config) GetD(name string, defvals ...time.Duration) time.Duration {
   471  	if c == nil || c.mx == nil {
   472  		if len(defvals) == 0 {
   473  			return time.Duration(0)
   474  		}
   475  
   476  		return defvals[0]
   477  	}
   478  
   479  	c.mx.RLock()
   480  	val := c.data[strings.ToLower(name)]
   481  	c.mx.RUnlock()
   482  
   483  	if val == "" {
   484  		if len(defvals) == 0 {
   485  			return time.Duration(0)
   486  		}
   487  
   488  		return defvals[0]
   489  	}
   490  
   491  	return time.Duration(c.GetI64(name)) * time.Second
   492  }
   493  
   494  // HasSection checks if section exist
   495  func (c *Config) HasSection(section string) bool {
   496  	if c == nil || c.mx == nil {
   497  		return false
   498  	}
   499  
   500  	c.mx.RLock()
   501  	defer c.mx.RUnlock()
   502  
   503  	return c.data[strings.ToLower(section)] == "!"
   504  }
   505  
   506  // HasProp checks if property exist
   507  func (c *Config) HasProp(name string) bool {
   508  	if c == nil || c.mx == nil {
   509  		return false
   510  	}
   511  
   512  	c.mx.RLock()
   513  	defer c.mx.RUnlock()
   514  
   515  	return c.data[strings.ToLower(name)] != ""
   516  }
   517  
   518  // Sections returns slice with section names
   519  func (c *Config) Sections() []string {
   520  	if c == nil || c.mx == nil {
   521  		return nil
   522  	}
   523  
   524  	c.mx.RLock()
   525  	defer c.mx.RUnlock()
   526  
   527  	return c.sections
   528  }
   529  
   530  // Props returns slice with properties names in some section
   531  func (c *Config) Props(section string) []string {
   532  	var result []string
   533  
   534  	if c == nil || !c.HasSection(section) {
   535  		return result
   536  	}
   537  
   538  	// Section name + delimiter
   539  	snLength := len(section) + 1
   540  
   541  	c.mx.RLock()
   542  
   543  	for _, prop := range c.props {
   544  		if len(prop) <= snLength {
   545  			continue
   546  		}
   547  
   548  		if prop[:snLength] == section+_PROP_DELIMITER {
   549  			result = append(result, prop[snLength:])
   550  		}
   551  	}
   552  
   553  	defer c.mx.RUnlock()
   554  
   555  	return result
   556  }
   557  
   558  // Validate executes all given validators and
   559  // returns slice with validation errors
   560  func (c *Config) Validate(validators []*Validator) []error {
   561  	if c == nil || c.mx == nil {
   562  		return []error{ErrConfigIsNil}
   563  	}
   564  
   565  	var result []error
   566  
   567  	c.mx.RLock()
   568  
   569  	for _, v := range validators {
   570  		err := v.Func(c, v.Property, v.Value)
   571  
   572  		if err != nil {
   573  			result = append(result, err)
   574  		}
   575  	}
   576  
   577  	defer c.mx.RUnlock()
   578  
   579  	return result
   580  }
   581  
   582  // ////////////////////////////////////////////////////////////////////////////////// //