|
|
|
package conf
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"log"
|
|
|
|
"os"
|
|
|
|
"path"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/zeromicro/go-zero/core/jsonx"
|
|
|
|
"github.com/zeromicro/go-zero/core/mapping"
|
|
|
|
"github.com/zeromicro/go-zero/internal/encoding"
|
|
|
|
)
|
|
|
|
|
|
|
|
const distanceBetweenUpperAndLower = 32
|
|
|
|
|
|
|
|
var loaders = map[string]func([]byte, interface{}) error{
|
|
|
|
".json": LoadFromJsonBytes,
|
|
|
|
".toml": LoadFromTomlBytes,
|
|
|
|
".yaml": LoadFromYamlBytes,
|
|
|
|
".yml": LoadFromYamlBytes,
|
|
|
|
}
|
|
|
|
|
|
|
|
// Load loads config into v from file, .json, .yaml and .yml are acceptable.
|
|
|
|
func Load(file string, v interface{}, opts ...Option) error {
|
|
|
|
content, err := os.ReadFile(file)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
loader, ok := loaders[strings.ToLower(path.Ext(file))]
|
|
|
|
if !ok {
|
|
|
|
return fmt.Errorf("unrecognized file type: %s", file)
|
|
|
|
}
|
|
|
|
|
|
|
|
var opt options
|
|
|
|
for _, o := range opts {
|
|
|
|
o(&opt)
|
|
|
|
}
|
|
|
|
|
|
|
|
if opt.env {
|
|
|
|
return loader([]byte(os.ExpandEnv(string(content))), v)
|
|
|
|
}
|
|
|
|
|
|
|
|
return loader(content, v)
|
|
|
|
}
|
|
|
|
|
|
|
|
// LoadConfig loads config into v from file, .json, .yaml and .yml are acceptable.
|
|
|
|
// Deprecated: use Load instead.
|
|
|
|
func LoadConfig(file string, v interface{}, opts ...Option) error {
|
|
|
|
return Load(file, v, opts...)
|
|
|
|
}
|
|
|
|
|
|
|
|
// LoadFromJsonBytes loads config into v from content json bytes.
|
|
|
|
func LoadFromJsonBytes(content []byte, v interface{}) error {
|
|
|
|
var m map[string]interface{}
|
|
|
|
if err := jsonx.Unmarshal(content, &m); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return mapping.UnmarshalJsonMap(toCamelCaseKeyMap(m), v, mapping.WithCanonicalKeyFunc(toCamelCase))
|
|
|
|
}
|
|
|
|
|
|
|
|
// LoadConfigFromJsonBytes loads config into v from content json bytes.
|
|
|
|
// Deprecated: use LoadFromJsonBytes instead.
|
|
|
|
func LoadConfigFromJsonBytes(content []byte, v interface{}) error {
|
|
|
|
return LoadFromJsonBytes(content, v)
|
|
|
|
}
|
|
|
|
|
|
|
|
// LoadFromTomlBytes loads config into v from content toml bytes.
|
|
|
|
func LoadFromTomlBytes(content []byte, v interface{}) error {
|
|
|
|
b, err := encoding.TomlToJson(content)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return LoadFromJsonBytes(b, v)
|
|
|
|
}
|
|
|
|
|
|
|
|
// LoadFromYamlBytes loads config into v from content yaml bytes.
|
|
|
|
func LoadFromYamlBytes(content []byte, v interface{}) error {
|
|
|
|
b, err := encoding.YamlToJson(content)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return LoadFromJsonBytes(b, v)
|
|
|
|
}
|
|
|
|
|
|
|
|
// LoadConfigFromYamlBytes loads config into v from content yaml bytes.
|
|
|
|
// Deprecated: use LoadFromYamlBytes instead.
|
|
|
|
func LoadConfigFromYamlBytes(content []byte, v interface{}) error {
|
|
|
|
return LoadFromYamlBytes(content, v)
|
|
|
|
}
|
|
|
|
|
|
|
|
// MustLoad loads config into v from path, exits on error.
|
|
|
|
func MustLoad(path string, v interface{}, opts ...Option) {
|
|
|
|
if err := Load(path, v, opts...); err != nil {
|
|
|
|
log.Fatalf("error: config file %s, %s", path, err.Error())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func toCamelCase(s string) string {
|
|
|
|
var buf strings.Builder
|
|
|
|
buf.Grow(len(s))
|
|
|
|
var capNext bool
|
|
|
|
boundary := true
|
|
|
|
for _, v := range s {
|
|
|
|
isCap := v >= 'A' && v <= 'Z'
|
|
|
|
isLow := v >= 'a' && v <= 'z'
|
|
|
|
if boundary && (isCap || isLow) {
|
|
|
|
if capNext {
|
|
|
|
if isLow {
|
|
|
|
v -= distanceBetweenUpperAndLower
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if isCap {
|
|
|
|
v += distanceBetweenUpperAndLower
|
|
|
|
}
|
|
|
|
}
|
|
|
|
boundary = false
|
|
|
|
}
|
|
|
|
if isCap || isLow {
|
|
|
|
buf.WriteRune(v)
|
|
|
|
capNext = false
|
|
|
|
} else if v == ' ' || v == '\t' {
|
|
|
|
buf.WriteRune(v)
|
|
|
|
capNext = false
|
|
|
|
boundary = true
|
|
|
|
} else if v == '_' {
|
|
|
|
capNext = true
|
|
|
|
boundary = true
|
|
|
|
} else {
|
|
|
|
buf.WriteRune(v)
|
|
|
|
capNext = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return buf.String()
|
|
|
|
}
|
|
|
|
|
|
|
|
func toCamelCaseInterface(v interface{}) interface{} {
|
|
|
|
switch vv := v.(type) {
|
|
|
|
case map[string]interface{}:
|
|
|
|
return toCamelCaseKeyMap(vv)
|
|
|
|
case []interface{}:
|
|
|
|
var arr []interface{}
|
|
|
|
for _, vvv := range vv {
|
|
|
|
arr = append(arr, toCamelCaseInterface(vvv))
|
|
|
|
}
|
|
|
|
return arr
|
|
|
|
default:
|
|
|
|
return v
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func toCamelCaseKeyMap(m map[string]interface{}) map[string]interface{} {
|
|
|
|
res := make(map[string]interface{})
|
|
|
|
for k, v := range m {
|
|
|
|
res[toCamelCase(k)] = toCamelCaseInterface(v)
|
|
|
|
}
|
|
|
|
|
|
|
|
return res
|
|
|
|
}
|