github.com/wfusion/gofusion@v1.1.14/internal/configor/configor.go (about)

     1  // Fork from github.com/jinzhu/configor@v1.2.2-0.20230118083828-f7a0fc7c9fc6
     2  // Here is the license:
     3  //
     4  // The MIT License (MIT)
     5  //
     6  // Copyright (c) 2013-NOW Jinzhu <wosmvp@gmail.com>
     7  //
     8  // Permission is hereby granted, free of charge, to any person obtaining a copy
     9  // of this software and associated documentation files (the "Software"), to deal
    10  // in the Software without restriction, including without limitation the rights
    11  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    12  // copies of the Software, and to permit persons to whom the Software is
    13  // furnished to do so, subject to the following conditions:
    14  //
    15  // The above copyright notice and this permission notice shall be included in all
    16  // copies or substantial portions of the Software.
    17  //
    18  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    19  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    20  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    21  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    22  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    23  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    24  // SOFTWARE.
    25  
    26  package configor
    27  
    28  import (
    29  	"crypto/sha256"
    30  	"fmt"
    31  	"io"
    32  	"io/fs"
    33  	"os"
    34  	"reflect"
    35  	"regexp"
    36  	"time"
    37  
    38  	"github.com/wfusion/gofusion/common/utils"
    39  )
    40  
    41  type Configor struct {
    42  	*Config
    43  	configHash     map[string]string
    44  	configModTimes map[string]time.Time
    45  
    46  	statFunc func(name string) (fs.FileInfo, error)
    47  	hashFunc func(name string) (string, error)
    48  }
    49  
    50  type Config struct {
    51  	Environment        string
    52  	ENVPrefix          string
    53  	Debug              bool
    54  	Verbose            bool
    55  	Silent             bool
    56  	AutoReload         bool
    57  	AutoReloadInterval time.Duration
    58  	AutoReloadCallback func(config any)
    59  
    60  	// In case of json files, this field will be used only when compiled with
    61  	// go 1.10 or later.
    62  	// This field will be ignored when compiled with go versions lower than 1.10.
    63  	ErrorOnUnmatchedKeys bool
    64  
    65  	// You can use embed.FS or any other fs.FS to load configs from. Default - use "os" package
    66  	FS fs.FS
    67  }
    68  
    69  // New initialize a Configor
    70  func New(config *Config) *Configor {
    71  	if config == nil {
    72  		config = &Config{}
    73  	}
    74  
    75  	if os.Getenv("CONFIGOR_DEBUG_MODE") != "" {
    76  		config.Debug = true
    77  	}
    78  
    79  	if os.Getenv("CONFIGOR_VERBOSE_MODE") != "" {
    80  		config.Verbose = true
    81  	}
    82  
    83  	if os.Getenv("CONFIGOR_SILENT_MODE") != "" {
    84  		config.Silent = true
    85  	}
    86  
    87  	if config.AutoReload && config.AutoReloadInterval == 0 {
    88  		config.AutoReloadInterval = time.Second
    89  	}
    90  
    91  	cfg := &Configor{
    92  		Config:         config,
    93  		configHash:     make(map[string]string),
    94  		configModTimes: make(map[string]time.Time),
    95  		statFunc:       os.Stat,
    96  		hashFunc: func(name string) (h string, err error) {
    97  			var file fs.File
    98  			if file, err = os.Open(name); err != nil {
    99  				return
   100  			}
   101  			defer utils.CloseAnyway(file)
   102  			sha256Hash := sha256.New()
   103  			if _, err = io.Copy(sha256Hash, file); err != nil {
   104  				return
   105  			}
   106  			h = string(sha256Hash.Sum(nil))
   107  			return
   108  		},
   109  	}
   110  	if cfg.FS != nil {
   111  		cfg.statFunc = func(name string) (os.FileInfo, error) {
   112  			return fs.Stat(cfg.FS, name)
   113  		}
   114  		cfg.hashFunc = func(name string) (h string, err error) {
   115  			var file fs.File
   116  			if file, err = cfg.FS.Open(name); err != nil {
   117  				return
   118  			}
   119  			defer utils.CloseAnyway(file)
   120  			sha256Hash := sha256.New()
   121  			if _, err = io.Copy(sha256Hash, file); err != nil {
   122  				return
   123  			}
   124  			h = string(sha256Hash.Sum(nil))
   125  			return
   126  		}
   127  	}
   128  
   129  	return cfg
   130  }
   131  
   132  var testRegexp = regexp.MustCompile(`_test|(\.test$)`)
   133  
   134  // GetEnvironment get environment
   135  func (c *Configor) GetEnvironment() string {
   136  	if c.Environment == "" {
   137  		if env := os.Getenv("CONFIGOR_ENV"); env != "" {
   138  			return env
   139  		}
   140  
   141  		if testRegexp.MatchString(os.Args[0]) {
   142  			return "test"
   143  		}
   144  
   145  		return "development"
   146  	}
   147  	return c.Environment
   148  }
   149  
   150  // GetErrorOnUnmatchedKeys returns a boolean indicating if an error should be
   151  // thrown if there are keys in the config file that do not correspond to the
   152  // config struct
   153  func (c *Configor) GetErrorOnUnmatchedKeys() bool {
   154  	return c.ErrorOnUnmatchedKeys
   155  }
   156  
   157  // Load will unmarshal configurations to struct from files that you provide
   158  func (c *Configor) Load(config any, files ...string) (err error) {
   159  	defaultValue := reflect.Indirect(reflect.ValueOf(config))
   160  	if !defaultValue.CanAddr() {
   161  		return fmt.Errorf("config %v should be addressable", config)
   162  	}
   163  	err, _ = c.load(config, false, files...)
   164  
   165  	if c.Config.AutoReload {
   166  		go func() {
   167  			timer := time.NewTimer(c.Config.AutoReloadInterval)
   168  			for range timer.C {
   169  				reflectPtr := reflect.New(reflect.ValueOf(config).Elem().Type())
   170  				reflectPtr.Elem().Set(defaultValue)
   171  
   172  				var changed bool
   173  				if err, changed = c.load(reflectPtr.Interface(), true, files...); err == nil && changed {
   174  					reflect.ValueOf(config).Elem().Set(reflectPtr.Elem())
   175  					if c.Config.AutoReloadCallback != nil {
   176  						c.Config.AutoReloadCallback(config)
   177  					}
   178  				} else if err != nil {
   179  					if !c.Silent {
   180  						fmt.Printf("Failed to reload configuration from %v, got error %v\n", files, err)
   181  					}
   182  				}
   183  				timer.Reset(c.Config.AutoReloadInterval)
   184  			}
   185  		}()
   186  	}
   187  	return
   188  }
   189  
   190  // ENV return environment
   191  func ENV() string {
   192  	return New(nil).GetEnvironment()
   193  }
   194  
   195  // Load will unmarshal configurations to struct from files that you provide
   196  func Load(config any, files ...string) error {
   197  	return New(nil).Load(config, files...)
   198  }