Initial QSfera import
This commit is contained in:
+664
@@ -0,0 +1,664 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gookit/goutil/envutil"
|
||||
"github.com/gookit/goutil/maputil"
|
||||
"github.com/gookit/goutil/mathutil"
|
||||
"github.com/gookit/goutil/strutil"
|
||||
)
|
||||
|
||||
// Exists key exists check
|
||||
func Exists(key string, findByPath ...bool) bool { return dc.Exists(key, findByPath...) }
|
||||
|
||||
// Exists key exists check
|
||||
func (c *Config) Exists(key string, findByPath ...bool) (ok bool) {
|
||||
sep := c.opts.Delimiter
|
||||
if key = formatKey(key, string(sep)); key == "" {
|
||||
return
|
||||
}
|
||||
|
||||
if _, ok = c.data[key]; ok {
|
||||
return
|
||||
}
|
||||
|
||||
// disable find by path.
|
||||
if len(findByPath) > 0 && !findByPath[0] {
|
||||
return
|
||||
}
|
||||
|
||||
// has sub key? eg. "lang.dir"
|
||||
if strings.IndexByte(key, sep) == -1 {
|
||||
return
|
||||
}
|
||||
|
||||
keys := strings.Split(key, string(sep))
|
||||
topK := keys[0]
|
||||
|
||||
// find top item data based on top key
|
||||
var item any
|
||||
if item, ok = c.data[topK]; !ok {
|
||||
return
|
||||
}
|
||||
for _, k := range keys[1:] {
|
||||
switch typeData := item.(type) {
|
||||
case map[string]int: // is map(from Set)
|
||||
if item, ok = typeData[k]; !ok {
|
||||
return
|
||||
}
|
||||
case map[string]string: // is map(from Set)
|
||||
if item, ok = typeData[k]; !ok {
|
||||
return
|
||||
}
|
||||
case map[string]any: // is map(decode from toml/json/yaml.v3)
|
||||
if item, ok = typeData[k]; !ok {
|
||||
return
|
||||
}
|
||||
case map[any]any: // is map(decode from yaml.v2)
|
||||
if item, ok = typeData[k]; !ok {
|
||||
return
|
||||
}
|
||||
case []int: // is array(is from Set)
|
||||
i, err := strconv.Atoi(k)
|
||||
|
||||
// check slice index
|
||||
if err != nil || len(typeData) < i {
|
||||
return false
|
||||
}
|
||||
case []string: // is array(is from Set)
|
||||
i, err := strconv.Atoi(k)
|
||||
if err != nil || len(typeData) < i {
|
||||
return false
|
||||
}
|
||||
case []any: // is array(load from file)
|
||||
i, err := strconv.Atoi(k)
|
||||
if err != nil || len(typeData) < i {
|
||||
return false
|
||||
}
|
||||
default: // error
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/*************************************************************
|
||||
* read config data
|
||||
*************************************************************/
|
||||
|
||||
// Data return all config data
|
||||
func Data() map[string]any { return dc.Data() }
|
||||
|
||||
// Data get all config data.
|
||||
//
|
||||
// Note: will don't apply any options, like ParseEnv
|
||||
func (c *Config) Data() map[string]any {
|
||||
return c.data
|
||||
}
|
||||
|
||||
// Sub return a map config data by key
|
||||
func Sub(key string) map[string]any { return dc.Sub(key) }
|
||||
|
||||
// Sub get a map config data by key
|
||||
//
|
||||
// Note: will don't apply any options, like ParseEnv
|
||||
func (c *Config) Sub(key string) map[string]any {
|
||||
if mp, ok := c.GetValue(key); ok {
|
||||
if mmp, ok := mp.(map[string]any); ok {
|
||||
return mmp
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Keys return all config data
|
||||
func Keys() []string { return dc.Keys() }
|
||||
|
||||
// Keys get all config data
|
||||
func (c *Config) Keys() []string {
|
||||
keys := make([]string, 0, len(c.data))
|
||||
for key := range c.data {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
return keys
|
||||
}
|
||||
|
||||
// Get config value by key string, support get sub-value by key path(eg. 'map.key'),
|
||||
func Get(key string, findByPath ...bool) any { return dc.Get(key, findByPath...) }
|
||||
|
||||
// Get config value by key, findByPath default is true.
|
||||
func (c *Config) Get(key string, findByPath ...bool) any {
|
||||
val, _ := c.GetValue(key, findByPath...)
|
||||
return val
|
||||
}
|
||||
|
||||
// GetValue get value by given key string. findByPath default is true.
|
||||
func GetValue(key string, findByPath ...bool) (any, bool) {
|
||||
return dc.GetValue(key, findByPath...)
|
||||
}
|
||||
|
||||
// GetValue get value by given key string. findByPath default is true.
|
||||
//
|
||||
// Return:
|
||||
// - ok is true, find value from config
|
||||
// - ok is false, not found or error
|
||||
func (c *Config) GetValue(key string, findByPath ...bool) (value any, ok bool) {
|
||||
sep := c.opts.Delimiter
|
||||
if key = formatKey(key, string(sep)); key == "" {
|
||||
c.addError(ErrKeyIsEmpty)
|
||||
return
|
||||
}
|
||||
|
||||
// if not is readonly
|
||||
if !c.opts.Readonly {
|
||||
c.lock.RLock()
|
||||
defer c.lock.RUnlock()
|
||||
}
|
||||
|
||||
// is top key
|
||||
if value, ok = c.data[key]; ok {
|
||||
return
|
||||
}
|
||||
|
||||
// disable find by path.
|
||||
if len(findByPath) > 0 && !findByPath[0] {
|
||||
// c.addError(ErrNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
// has sub key? eg. "lang.dir"
|
||||
if strings.IndexByte(key, sep) == -1 {
|
||||
// c.addError(ErrNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
keys := strings.Split(key, string(sep))
|
||||
topK := keys[0]
|
||||
|
||||
// find top item data based on top key
|
||||
var item any
|
||||
if item, ok = c.data[topK]; !ok {
|
||||
// c.addError(ErrNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
// find child
|
||||
// NOTICE: don't merge case, will result in an error.
|
||||
// e.g. case []int, []string
|
||||
// OR
|
||||
// case []int:
|
||||
// case []string:
|
||||
for _, k := range keys[1:] {
|
||||
switch typeData := item.(type) {
|
||||
case map[string]int: // is map(from Set)
|
||||
if item, ok = typeData[k]; !ok {
|
||||
return
|
||||
}
|
||||
case map[string]string: // is map(from Set)
|
||||
if item, ok = typeData[k]; !ok {
|
||||
return
|
||||
}
|
||||
case map[string]any: // is map(decode from toml/json)
|
||||
if item, ok = typeData[k]; !ok {
|
||||
return
|
||||
}
|
||||
case map[any]any: // is map(decode from yaml)
|
||||
if item, ok = typeData[k]; !ok {
|
||||
return
|
||||
}
|
||||
case []int: // is array(is from Set)
|
||||
i, err := strconv.Atoi(k)
|
||||
|
||||
// check slice index
|
||||
if err != nil || len(typeData) < i {
|
||||
ok = false
|
||||
c.addError(err)
|
||||
return
|
||||
}
|
||||
|
||||
item = typeData[i]
|
||||
case []string: // is array(is from Set)
|
||||
i, err := strconv.Atoi(k)
|
||||
if err != nil || len(typeData) < i {
|
||||
ok = false
|
||||
c.addError(err)
|
||||
return
|
||||
}
|
||||
|
||||
item = typeData[i]
|
||||
case []any: // is array(load from file)
|
||||
i, err := strconv.Atoi(k)
|
||||
if err != nil || len(typeData) < i {
|
||||
ok = false
|
||||
c.addError(err)
|
||||
return
|
||||
}
|
||||
|
||||
item = typeData[i]
|
||||
default: // error
|
||||
ok = false
|
||||
c.addErrorf("cannot get value of the key '%s'", key)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return item, true
|
||||
}
|
||||
|
||||
/*************************************************************
|
||||
* read config (basic data type)
|
||||
*************************************************************/
|
||||
|
||||
// String get a string by key
|
||||
func String(key string, defVal ...string) string { return dc.String(key, defVal...) }
|
||||
|
||||
// String get a string by key, if not found return default value
|
||||
func (c *Config) String(key string, defVal ...string) string {
|
||||
value, ok := c.getString(key)
|
||||
|
||||
if !ok && len(defVal) > 0 { // give default value
|
||||
value = defVal[0]
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// MustString get a string by key, will panic on empty or not exists
|
||||
func MustString(key string) string { return dc.MustString(key) }
|
||||
|
||||
// MustString get a string by key, will panic on empty or not exists
|
||||
func (c *Config) MustString(key string) string {
|
||||
value, ok := c.getString(key)
|
||||
if !ok {
|
||||
panic("config: string value not found, key: " + key)
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func (c *Config) getString(key string) (value string, ok bool) {
|
||||
// find from cache
|
||||
if c.opts.EnableCache && len(c.strCache) > 0 {
|
||||
value, ok = c.strCache[key]
|
||||
if ok {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
val, ok := c.GetValue(key)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
switch typVal := val.(type) {
|
||||
// from json `int` always is float64
|
||||
case string:
|
||||
value = typVal
|
||||
if c.opts.ParseEnv {
|
||||
value = envutil.ParseEnvValue(value)
|
||||
}
|
||||
default:
|
||||
var err error
|
||||
value, err = strutil.AnyToString(val, false)
|
||||
if err != nil {
|
||||
return "", false
|
||||
}
|
||||
}
|
||||
|
||||
// add cache
|
||||
if ok && c.opts.EnableCache {
|
||||
if c.strCache == nil {
|
||||
c.strCache = make(map[string]string)
|
||||
}
|
||||
c.strCache[key] = value
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Int get an int by key
|
||||
func Int(key string, defVal ...int) int { return dc.Int(key, defVal...) }
|
||||
|
||||
// Int get a int value, if not found return default value
|
||||
func (c *Config) Int(key string, defVal ...int) (value int) {
|
||||
i64, exist := c.tryInt64(key)
|
||||
|
||||
if exist {
|
||||
value = int(i64)
|
||||
} else if len(defVal) > 0 {
|
||||
value = defVal[0]
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Uint get a uint value, if not found return default value
|
||||
func Uint(key string, defVal ...uint) uint { return dc.Uint(key, defVal...) }
|
||||
|
||||
// Uint get a int value, if not found return default value
|
||||
func (c *Config) Uint(key string, defVal ...uint) (value uint) {
|
||||
i64, exist := c.tryInt64(key)
|
||||
|
||||
if exist {
|
||||
value = uint(i64)
|
||||
} else if len(defVal) > 0 {
|
||||
value = defVal[0]
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Int64 get a int value, if not found return default value
|
||||
func Int64(key string, defVal ...int64) int64 { return dc.Int64(key, defVal...) }
|
||||
|
||||
// Int64 get a int value, if not found return default value
|
||||
func (c *Config) Int64(key string, defVal ...int64) (value int64) {
|
||||
value, exist := c.tryInt64(key)
|
||||
|
||||
if !exist && len(defVal) > 0 {
|
||||
value = defVal[0]
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// try to get an int64 value by given key
|
||||
func (c *Config) tryInt64(key string) (value int64, ok bool) {
|
||||
strVal, ok := c.getString(key)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
value, err := strconv.ParseInt(strVal, 10, 0)
|
||||
if err != nil {
|
||||
c.addError(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Duration get a time.Duration type value. if not found return default value
|
||||
func Duration(key string, defVal ...time.Duration) time.Duration { return dc.Duration(key, defVal...) }
|
||||
|
||||
// Duration get a time.Duration type value. if not found return default value
|
||||
func (c *Config) Duration(key string, defVal ...time.Duration) time.Duration {
|
||||
value, exist := c.tryInt64(key)
|
||||
|
||||
if !exist && len(defVal) > 0 {
|
||||
return defVal[0]
|
||||
}
|
||||
return time.Duration(value)
|
||||
}
|
||||
|
||||
// Float get a float64 value, if not found return default value
|
||||
func Float(key string, defVal ...float64) float64 { return dc.Float(key, defVal...) }
|
||||
|
||||
// Float get a float64 by key
|
||||
func (c *Config) Float(key string, defVal ...float64) (value float64) {
|
||||
str, ok := c.getString(key)
|
||||
if !ok {
|
||||
if len(defVal) > 0 {
|
||||
value = defVal[0]
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
value, err := strconv.ParseFloat(str, 64)
|
||||
if err != nil {
|
||||
c.addError(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Bool get a bool value, if not found return default value
|
||||
func Bool(key string, defVal ...bool) bool { return dc.Bool(key, defVal...) }
|
||||
|
||||
// Bool looks up a value for a key in this section and attempts to parse that value as a boolean,
|
||||
// along with a boolean result similar to a map lookup.
|
||||
//
|
||||
// of following(case insensitive):
|
||||
// - true
|
||||
// - yes
|
||||
// - false
|
||||
// - no
|
||||
// - 1
|
||||
// - 0
|
||||
//
|
||||
// The `ok` boolean will be false in the event that the value could not be parsed as a bool
|
||||
func (c *Config) Bool(key string, defVal ...bool) (value bool) {
|
||||
rawVal, ok := c.getString(key)
|
||||
if !ok {
|
||||
if len(defVal) > 0 {
|
||||
return defVal[0]
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
lowerCase := strings.ToLower(rawVal)
|
||||
switch lowerCase {
|
||||
case "", "0", "false", "no":
|
||||
value = false
|
||||
case "1", "true", "yes":
|
||||
value = true
|
||||
default:
|
||||
c.addErrorf("the value '%s' cannot be convert to bool", lowerCase)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
/*************************************************************
|
||||
* read config (complex data type)
|
||||
*************************************************************/
|
||||
|
||||
// Ints get config data as an int slice/array
|
||||
func Ints(key string) []int { return dc.Ints(key) }
|
||||
|
||||
// Ints get config data as an int slice/array
|
||||
func (c *Config) Ints(key string) (arr []int) {
|
||||
rawVal, ok := c.GetValue(key)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
switch typeData := rawVal.(type) {
|
||||
case []int:
|
||||
arr = typeData
|
||||
case []any:
|
||||
for _, v := range typeData {
|
||||
iv, err := mathutil.ToInt(v)
|
||||
// iv, err := strconv.Atoi(fmt.Sprintf("%v", v))
|
||||
if err != nil {
|
||||
c.addError(err)
|
||||
arr = arr[0:0] // reset
|
||||
return
|
||||
}
|
||||
|
||||
arr = append(arr, iv)
|
||||
}
|
||||
default:
|
||||
c.addErrorf("value cannot be convert to []int, key is '%s'", key)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// IntMap get config data as a map[string]int
|
||||
func IntMap(key string) map[string]int { return dc.IntMap(key) }
|
||||
|
||||
// IntMap get config data as a map[string]int
|
||||
func (c *Config) IntMap(key string) (mp map[string]int) {
|
||||
rawVal, ok := c.GetValue(key)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
switch typeData := rawVal.(type) {
|
||||
case map[string]int: // from Set
|
||||
mp = typeData
|
||||
case map[string]any: // decode from json,toml
|
||||
mp = make(map[string]int)
|
||||
for k, v := range typeData {
|
||||
// iv, err := strconv.Atoi(fmt.Sprintf("%v", v))
|
||||
iv, err := mathutil.ToInt(v)
|
||||
if err != nil {
|
||||
c.addError(err)
|
||||
mp = map[string]int{} // reset
|
||||
return
|
||||
}
|
||||
mp[k] = iv
|
||||
}
|
||||
case map[any]any: // if decode from yaml
|
||||
mp = make(map[string]int)
|
||||
for k, v := range typeData {
|
||||
// iv, err := strconv.Atoi(fmt.Sprintf( "%v", v))
|
||||
iv, err := mathutil.ToInt(v)
|
||||
if err != nil {
|
||||
c.addError(err)
|
||||
mp = map[string]int{} // reset
|
||||
return
|
||||
}
|
||||
|
||||
// sk := fmt.Sprintf("%v", k)
|
||||
sk, _ := strutil.AnyToString(k, false)
|
||||
mp[sk] = iv
|
||||
}
|
||||
default:
|
||||
c.addErrorf("value cannot be convert to map[string]int, key is '%s'", key)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Strings get strings by key
|
||||
func Strings(key string) []string { return dc.Strings(key) }
|
||||
|
||||
// Strings get config data as a string slice/array
|
||||
func (c *Config) Strings(key string) (arr []string) {
|
||||
var ok bool
|
||||
// find from cache
|
||||
if c.opts.EnableCache && len(c.sArrCache) > 0 {
|
||||
arr, ok = c.sArrCache[key]
|
||||
if ok {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
rawVal, ok := c.GetValue(key)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
switch typeData := rawVal.(type) {
|
||||
case []string:
|
||||
arr = typeData
|
||||
case []any:
|
||||
for _, v := range typeData {
|
||||
// arr = append(arr, fmt.Sprintf("%v", v))
|
||||
arr = append(arr, strutil.MustString(v))
|
||||
}
|
||||
default:
|
||||
c.addErrorf("value cannot be convert to []string, key is '%s'", key)
|
||||
return
|
||||
}
|
||||
|
||||
// add cache
|
||||
if c.opts.EnableCache {
|
||||
if c.sArrCache == nil {
|
||||
c.sArrCache = make(map[string]strArr)
|
||||
}
|
||||
c.sArrCache[key] = arr
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// StringsBySplit get []string by split a string value.
|
||||
func StringsBySplit(key, sep string) []string { return dc.StringsBySplit(key, sep) }
|
||||
|
||||
// StringsBySplit get []string by split a string value.
|
||||
func (c *Config) StringsBySplit(key, sep string) (ss []string) {
|
||||
if str, ok := c.getString(key); ok {
|
||||
ss = strutil.Split(str, sep)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// StringMap get config data as a map[string]string
|
||||
func StringMap(key string) map[string]string { return dc.StringMap(key) }
|
||||
|
||||
// StringMap get config data as a map[string]string
|
||||
func (c *Config) StringMap(key string) (mp map[string]string) {
|
||||
var ok bool
|
||||
|
||||
// find from cache
|
||||
if c.opts.EnableCache && len(c.sMapCache) > 0 {
|
||||
mp, ok = c.sMapCache[key]
|
||||
if ok {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
rawVal, ok := c.GetValue(key)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
switch typeData := rawVal.(type) {
|
||||
case map[string]string: // from Set
|
||||
mp = typeData
|
||||
case map[string]any: // decode from json,toml,yaml.v3
|
||||
mp = make(map[string]string, len(typeData))
|
||||
|
||||
for k, v := range typeData {
|
||||
switch tv := v.(type) {
|
||||
case string:
|
||||
if c.opts.ParseEnv {
|
||||
mp[k] = envutil.ParseEnvValue(tv)
|
||||
} else {
|
||||
mp[k] = tv
|
||||
}
|
||||
default:
|
||||
mp[k] = strutil.QuietString(v)
|
||||
}
|
||||
}
|
||||
case map[any]any: // decode from yaml v2
|
||||
mp = make(map[string]string, len(typeData))
|
||||
|
||||
for k, v := range typeData {
|
||||
sk := strutil.QuietString(k)
|
||||
|
||||
switch typVal := v.(type) {
|
||||
case string:
|
||||
if c.opts.ParseEnv {
|
||||
mp[sk] = envutil.ParseEnvValue(typVal)
|
||||
} else {
|
||||
mp[sk] = typVal
|
||||
}
|
||||
default:
|
||||
mp[sk] = strutil.QuietString(v)
|
||||
}
|
||||
}
|
||||
default:
|
||||
c.addErrorf("value cannot be convert to map[string]string, key is %q", key)
|
||||
return
|
||||
}
|
||||
|
||||
// add cache
|
||||
if c.opts.EnableCache {
|
||||
if c.sMapCache == nil {
|
||||
c.sMapCache = make(map[string]strMap)
|
||||
}
|
||||
c.sMapCache[key] = mp
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// SubDataMap get sub config data as maputil.Map
|
||||
func SubDataMap(key string) maputil.Map { return dc.SubDataMap(key) }
|
||||
|
||||
// SubDataMap get sub config data as maputil.Map
|
||||
//
|
||||
// TIP: will not enable parse Env and more
|
||||
func (c *Config) SubDataMap(key string) maputil.Map {
|
||||
if mp, ok := c.GetValue(key); ok {
|
||||
if mmp, ok := mp.(map[string]any); ok {
|
||||
return mmp
|
||||
}
|
||||
}
|
||||
|
||||
// keep is not nil
|
||||
return maputil.Map{}
|
||||
}
|
||||
Reference in New Issue
Block a user