1
0
mirror of https://github.com/astaxie/beego.git synced 2025-06-20 14:30:18 +00:00

Revert "Merge pull request #4325 from flycash/revert1"

This reverts commit fad897346f, reversing
changes made to e284b0ddae.
This commit is contained in:
Ming Deng
2020-12-12 21:28:58 +08:00
parent fad897346f
commit debd68cbe4
455 changed files with 30003 additions and 4667 deletions

View File

@ -0,0 +1,72 @@
// Copyright 2020
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package config
import (
"context"
"errors"
"testing"
"github.com/stretchr/testify/assert"
)
func TestBaseConfiger_DefaultBool(t *testing.T) {
bc := newBaseConfier("true")
assert.True(t, bc.DefaultBool("key1", false))
assert.True(t, bc.DefaultBool("key2", true))
}
func TestBaseConfiger_DefaultFloat(t *testing.T) {
bc := newBaseConfier("12.3")
assert.Equal(t, 12.3, bc.DefaultFloat("key1", 0.1))
assert.Equal(t, 0.1, bc.DefaultFloat("key2", 0.1))
}
func TestBaseConfiger_DefaultInt(t *testing.T) {
bc := newBaseConfier("10")
assert.Equal(t, 10, bc.DefaultInt("key1", 8))
assert.Equal(t, 8, bc.DefaultInt("key2", 8))
}
func TestBaseConfiger_DefaultInt64(t *testing.T) {
bc := newBaseConfier("64")
assert.Equal(t, int64(64), bc.DefaultInt64("key1", int64(8)))
assert.Equal(t, int64(8), bc.DefaultInt64("key2", int64(8)))
}
func TestBaseConfiger_DefaultString(t *testing.T) {
bc := newBaseConfier("Hello")
assert.Equal(t, "Hello", bc.DefaultString("key1", "world"))
assert.Equal(t, "world", bc.DefaultString("key2", "world"))
}
func TestBaseConfiger_DefaultStrings(t *testing.T) {
bc := newBaseConfier("Hello;world")
assert.Equal(t, []string{"Hello", "world"}, bc.DefaultStrings("key1", []string{"world"}))
assert.Equal(t, []string{"world"}, bc.DefaultStrings("key2", []string{"world"}))
}
func newBaseConfier(str1 string) *BaseConfiger {
return &BaseConfiger{
reader: func(ctx context.Context, key string) (string, error) {
if key == "key1" {
return str1, nil
} else {
return "", errors.New("mock error")
}
},
}
}

374
core/config/config.go Normal file
View File

@ -0,0 +1,374 @@
// Copyright 2014 beego Author. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package config is used to parse config.
// Usage:
// import "github.com/astaxie/beego/config"
// Examples.
//
// cnf, err := config.NewConfig("ini", "config.conf")
//
// cnf APIS:
//
// cnf.Set(key, val string) error
// cnf.String(key string) string
// cnf.Strings(key string) []string
// cnf.Int(key string) (int, error)
// cnf.Int64(key string) (int64, error)
// cnf.Bool(key string) (bool, error)
// cnf.Float(key string) (float64, error)
// cnf.DefaultString(key string, defaultVal string) string
// cnf.DefaultStrings(key string, defaultVal []string) []string
// cnf.DefaultInt(key string, defaultVal int) int
// cnf.DefaultInt64(key string, defaultVal int64) int64
// cnf.DefaultBool(key string, defaultVal bool) bool
// cnf.DefaultFloat(key string, defaultVal float64) float64
// cnf.DIY(key string) (interface{}, error)
// cnf.GetSection(section string) (map[string]string, error)
// cnf.SaveConfigFile(filename string) error
// More docs http://beego.me/docs/module/config.md
package config
import (
"context"
"errors"
"fmt"
"os"
"reflect"
"strconv"
"strings"
"time"
)
// Configer defines how to get and set value from configuration raw data.
type Configer interface {
// support section::key type in given key when using ini type.
Set(key, val string) error
// support section::key type in key string when using ini and json type; Int,Int64,Bool,Float,DIY are same.
String(key string) (string, error)
// get string slice
Strings(key string) ([]string, error)
Int(key string) (int, error)
Int64(key string) (int64, error)
Bool(key string) (bool, error)
Float(key string) (float64, error)
// support section::key type in key string when using ini and json type; Int,Int64,Bool,Float,DIY are same.
DefaultString(key string, defaultVal string) string
// get string slice
DefaultStrings(key string, defaultVal []string) []string
DefaultInt(key string, defaultVal int) int
DefaultInt64(key string, defaultVal int64) int64
DefaultBool(key string, defaultVal bool) bool
DefaultFloat(key string, defaultVal float64) float64
// DIY return the original value
DIY(key string) (interface{}, error)
GetSection(section string) (map[string]string, error)
Unmarshaler(prefix string, obj interface{}, opt ...DecodeOption) error
Sub(key string) (Configer, error)
OnChange(key string, fn func(value string))
SaveConfigFile(filename string) error
}
type BaseConfiger struct {
// The reader should support key like "a.b.c"
reader func(ctx context.Context, key string) (string, error)
}
func NewBaseConfiger(reader func(ctx context.Context, key string) (string, error)) BaseConfiger {
return BaseConfiger{
reader: reader,
}
}
func (c *BaseConfiger) Int(key string) (int, error) {
res, err := c.reader(context.TODO(), key)
if err != nil {
return 0, err
}
return strconv.Atoi(res)
}
func (c *BaseConfiger) Int64(key string) (int64, error) {
res, err := c.reader(context.TODO(), key)
if err != nil {
return 0, err
}
return strconv.ParseInt(res, 10, 64)
}
func (c *BaseConfiger) Bool(key string) (bool, error) {
res, err := c.reader(context.TODO(), key)
if err != nil {
return false, err
}
return ParseBool(res)
}
func (c *BaseConfiger) Float(key string) (float64, error) {
res, err := c.reader(context.TODO(), key)
if err != nil {
return 0, err
}
return strconv.ParseFloat(res, 64)
}
// DefaultString returns the string value for a given key.
// if err != nil or value is empty return defaultval
func (c *BaseConfiger) DefaultString(key string, defaultVal string) string {
if res, err := c.String(key); res != "" && err == nil {
return res
}
return defaultVal
}
// DefaultStrings returns the []string value for a given key.
// if err != nil return defaultval
func (c *BaseConfiger) DefaultStrings(key string, defaultVal []string) []string {
if res, err := c.Strings(key); len(res) > 0 && err == nil {
return res
}
return defaultVal
}
func (c *BaseConfiger) DefaultInt(key string, defaultVal int) int {
if res, err := c.Int(key); err == nil {
return res
}
return defaultVal
}
func (c *BaseConfiger) DefaultInt64(key string, defaultVal int64) int64 {
if res, err := c.Int64(key); err == nil {
return res
}
return defaultVal
}
func (c *BaseConfiger) DefaultBool(key string, defaultVal bool) bool {
if res, err := c.Bool(key); err == nil {
return res
}
return defaultVal
}
func (c *BaseConfiger) DefaultFloat(key string, defaultVal float64) float64 {
if res, err := c.Float(key); err == nil {
return res
}
return defaultVal
}
func (c *BaseConfiger) String(key string) (string, error) {
return c.reader(context.TODO(), key)
}
// Strings returns the []string value for a given key.
// Return nil if config value does not exist or is empty.
func (c *BaseConfiger) Strings(key string) ([]string, error) {
res, err := c.String(key)
if err != nil || res == "" {
return nil, err
}
return strings.Split(res, ";"), nil
}
func (c *BaseConfiger) Sub(key string) (Configer, error) {
return nil, errors.New("unsupported operation")
}
func (c *BaseConfiger) OnChange(key string, fn func(value string)) {
// do nothing
}
// Config is the adapter interface for parsing config file to get raw data to Configer.
type Config interface {
Parse(key string) (Configer, error)
ParseData(data []byte) (Configer, error)
}
var adapters = make(map[string]Config)
// Register makes a config adapter available by the adapter name.
// If Register is called twice with the same name or if driver is nil,
// it panics.
func Register(name string, adapter Config) {
if adapter == nil {
panic("config: Register adapter is nil")
}
if _, ok := adapters[name]; ok {
panic("config: Register called twice for adapter " + name)
}
adapters[name] = adapter
}
// NewConfig adapterName is ini/json/xml/yaml.
// filename is the config file path.
func NewConfig(adapterName, filename string) (Configer, error) {
adapter, ok := adapters[adapterName]
if !ok {
return nil, fmt.Errorf("config: unknown adaptername %q (forgotten import?)", adapterName)
}
return adapter.Parse(filename)
}
// NewConfigData adapterName is ini/json/xml/yaml.
// data is the config data.
func NewConfigData(adapterName string, data []byte) (Configer, error) {
adapter, ok := adapters[adapterName]
if !ok {
return nil, fmt.Errorf("config: unknown adaptername %q (forgotten import?)", adapterName)
}
return adapter.ParseData(data)
}
// ExpandValueEnvForMap convert all string value with environment variable.
func ExpandValueEnvForMap(m map[string]interface{}) map[string]interface{} {
for k, v := range m {
switch value := v.(type) {
case string:
m[k] = ExpandValueEnv(value)
case map[string]interface{}:
m[k] = ExpandValueEnvForMap(value)
case map[string]string:
for k2, v2 := range value {
value[k2] = ExpandValueEnv(v2)
}
m[k] = value
}
}
return m
}
// ExpandValueEnv returns value of convert with environment variable.
//
// Return environment variable if value start with "${" and end with "}".
// Return default value if environment variable is empty or not exist.
//
// It accept value formats "${env}" , "${env||}}" , "${env||defaultValue}" , "defaultvalue".
// Examples:
// v1 := config.ExpandValueEnv("${GOPATH}") // return the GOPATH environment variable.
// v2 := config.ExpandValueEnv("${GOAsta||/usr/local/go}") // return the default value "/usr/local/go/".
// v3 := config.ExpandValueEnv("Astaxie") // return the value "Astaxie".
func ExpandValueEnv(value string) (realValue string) {
realValue = value
vLen := len(value)
// 3 = ${}
if vLen < 3 {
return
}
// Need start with "${" and end with "}", then return.
if value[0] != '$' || value[1] != '{' || value[vLen-1] != '}' {
return
}
key := ""
defaultV := ""
// value start with "${"
for i := 2; i < vLen; i++ {
if value[i] == '|' && (i+1 < vLen && value[i+1] == '|') {
key = value[2:i]
defaultV = value[i+2 : vLen-1] // other string is default value.
break
} else if value[i] == '}' {
key = value[2:i]
break
}
}
realValue = os.Getenv(key)
if realValue == "" {
realValue = defaultV
}
return
}
// ParseBool returns the boolean value represented by the string.
//
// It accepts 1, 1.0, t, T, TRUE, true, True, YES, yes, Yes,Y, y, ON, on, On,
// 0, 0.0, f, F, FALSE, false, False, NO, no, No, N,n, OFF, off, Off.
// Any other value returns an error.
func ParseBool(val interface{}) (value bool, err error) {
if val != nil {
switch v := val.(type) {
case bool:
return v, nil
case string:
switch v {
case "1", "t", "T", "true", "TRUE", "True", "YES", "yes", "Yes", "Y", "y", "ON", "on", "On":
return true, nil
case "0", "f", "F", "false", "FALSE", "False", "NO", "no", "No", "N", "n", "OFF", "off", "Off":
return false, nil
}
case int8, int32, int64:
strV := fmt.Sprintf("%d", v)
if strV == "1" {
return true, nil
} else if strV == "0" {
return false, nil
}
case float64:
if v == 1.0 {
return true, nil
} else if v == 0.0 {
return false, nil
}
}
return false, fmt.Errorf("parsing %q: invalid syntax", val)
}
return false, fmt.Errorf("parsing <nil>: invalid syntax")
}
// ToString converts values of any type to string.
func ToString(x interface{}) string {
switch y := x.(type) {
// Handle dates with special logic
// This needs to come above the fmt.Stringer
// test since time.Time's have a .String()
// method
case time.Time:
return y.Format("A Monday")
// Handle type string
case string:
return y
// Handle type with .String() method
case fmt.Stringer:
return y.String()
// Handle type with .Error() method
case error:
return y.Error()
}
// Handle named string type
if v := reflect.ValueOf(x); v.Kind() == reflect.String {
return v.String()
}
// Fallback to fmt package for anything else like numeric types
return fmt.Sprint(x)
}
type DecodeOption func(options decodeOptions)
type decodeOptions struct {
}

View File

@ -0,0 +1,55 @@
// Copyright 2016 beego Author. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package config
import (
"os"
"testing"
)
func TestExpandValueEnv(t *testing.T) {
testCases := []struct {
item string
want string
}{
{"", ""},
{"$", "$"},
{"{", "{"},
{"{}", "{}"},
{"${}", ""},
{"${|}", ""},
{"${}", ""},
{"${{}}", ""},
{"${{||}}", "}"},
{"${pwd||}", ""},
{"${pwd||}", ""},
{"${pwd||}", ""},
{"${pwd||}}", "}"},
{"${pwd||{{||}}}", "{{||}}"},
{"${GOPATH}", os.Getenv("GOPATH")},
{"${GOPATH||}", os.Getenv("GOPATH")},
{"${GOPATH||root}", os.Getenv("GOPATH")},
{"${GOPATH_NOT||root}", "root"},
{"${GOPATH_NOT||||root}", "||root"},
}
for _, c := range testCases {
if got := ExpandValueEnv(c.item); got != c.want {
t.Errorf("expand value error, item %q want %q, got %q", c.item, c.want, got)
}
}
}

87
core/config/env/env.go vendored Normal file
View File

@ -0,0 +1,87 @@
// Copyright 2014 beego Author. All Rights Reserved.
// Copyright 2017 Faissal Elamraoui. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package env is used to parse environment.
package env
import (
"fmt"
"os"
"strings"
"github.com/astaxie/beego/core/utils"
)
var env *utils.BeeMap
func init() {
env = utils.NewBeeMap()
for _, e := range os.Environ() {
splits := strings.Split(e, "=")
env.Set(splits[0], os.Getenv(splits[0]))
}
}
// Get returns a value for a given key.
// If the key does not exist, the default value will be returned.
func Get(key string, defVal string) string {
if val := env.Get(key); val != nil {
return val.(string)
}
return defVal
}
// MustGet returns a value by key.
// If the key does not exist, it will return an error.
func MustGet(key string) (string, error) {
if val := env.Get(key); val != nil {
return val.(string), nil
}
return "", fmt.Errorf("no env variable with %s", key)
}
// Set sets a value in the ENV copy.
// This does not affect the child process environment.
func Set(key string, value string) {
env.Set(key, value)
}
// MustSet sets a value in the ENV copy and the child process environment.
// It returns an error in case the set operation failed.
func MustSet(key string, value string) error {
err := os.Setenv(key, value)
if err != nil {
return err
}
env.Set(key, value)
return nil
}
// GetAll returns all keys/values in the current child process environment.
func GetAll() map[string]string {
items := env.Items()
envs := make(map[string]string, env.Count())
for key, val := range items {
switch key := key.(type) {
case string:
switch val := val.(type) {
case string:
envs[key] = val
}
}
}
return envs
}

75
core/config/env/env_test.go vendored Normal file
View File

@ -0,0 +1,75 @@
// Copyright 2014 beego Author. All Rights Reserved.
// Copyright 2017 Faissal Elamraoui. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package env
import (
"os"
"testing"
)
func TestEnvGet(t *testing.T) {
gopath := Get("GOPATH", "")
if gopath != os.Getenv("GOPATH") {
t.Error("expected GOPATH not empty.")
}
noExistVar := Get("NOEXISTVAR", "foo")
if noExistVar != "foo" {
t.Errorf("expected NOEXISTVAR to equal foo, got %s.", noExistVar)
}
}
func TestEnvMustGet(t *testing.T) {
gopath, err := MustGet("GOPATH")
if err != nil {
t.Error(err)
}
if gopath != os.Getenv("GOPATH") {
t.Errorf("expected GOPATH to be the same, got %s.", gopath)
}
_, err = MustGet("NOEXISTVAR")
if err == nil {
t.Error("expected error to be non-nil")
}
}
func TestEnvSet(t *testing.T) {
Set("MYVAR", "foo")
myVar := Get("MYVAR", "bar")
if myVar != "foo" {
t.Errorf("expected MYVAR to equal foo, got %s.", myVar)
}
}
func TestEnvMustSet(t *testing.T) {
err := MustSet("FOO", "bar")
if err != nil {
t.Error(err)
}
fooVar := os.Getenv("FOO")
if fooVar != "bar" {
t.Errorf("expected FOO variable to equal bar, got %s.", fooVar)
}
}
func TestEnvGetAll(t *testing.T) {
envMap := GetAll()
if len(envMap) == 0 {
t.Error("expected environment not empty.")
}
}

25
core/config/error.go Normal file
View File

@ -0,0 +1,25 @@
// Copyright 2020
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package config
import (
"github.com/pkg/errors"
)
// now not all implementation return those error codes
var (
KeyNotFoundError = errors.New("the key is not found")
InvalidValueTypeError = errors.New("the value is not expected type")
)

195
core/config/etcd/config.go Normal file
View File

@ -0,0 +1,195 @@
// Copyright 2020
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package etcd
import (
"context"
"encoding/json"
"fmt"
"time"
"github.com/coreos/etcd/clientv3"
grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus"
"github.com/mitchellh/mapstructure"
"github.com/pkg/errors"
"google.golang.org/grpc"
"github.com/astaxie/beego/core/config"
"github.com/astaxie/beego/core/logs"
)
type EtcdConfiger struct {
prefix string
client *clientv3.Client
config.BaseConfiger
}
func newEtcdConfiger(client *clientv3.Client, prefix string) *EtcdConfiger {
res := &EtcdConfiger{
client: client,
prefix: prefix,
}
res.BaseConfiger = config.NewBaseConfiger(res.reader)
return res
}
// reader is an general implementation that read config from etcd.
func (e *EtcdConfiger) reader(ctx context.Context, key string) (string, error) {
resp, err := get(e.client, e.prefix+key)
if err != nil {
return "", err
}
if resp.Count > 0 {
return string(resp.Kvs[0].Value), nil
}
return "", nil
}
// Set do nothing and return an error
// I think write data to remote config center is not a good practice
func (e *EtcdConfiger) Set(key, val string) error {
return errors.New("Unsupported operation")
}
// DIY return the original response from etcd
// be careful when you decide to use this
func (e *EtcdConfiger) DIY(key string) (interface{}, error) {
return get(e.client, key)
}
// GetSection in this implementation, we use section as prefix
func (e *EtcdConfiger) GetSection(section string) (map[string]string, error) {
var (
resp *clientv3.GetResponse
err error
)
resp, err = e.client.Get(context.TODO(), e.prefix+section, clientv3.WithPrefix())
if err != nil {
return nil, errors.WithMessage(err, "GetSection failed")
}
res := make(map[string]string, len(resp.Kvs))
for _, kv := range resp.Kvs {
res[string(kv.Key)] = string(kv.Value)
}
return res, nil
}
func (e *EtcdConfiger) SaveConfigFile(filename string) error {
return errors.New("Unsupported operation")
}
// Unmarshaler is not very powerful because we lost the type information when we get configuration from etcd
// for example, when we got "5", we are not sure whether it's int 5, or it's string "5"
// TODO(support more complicated decoder)
func (e *EtcdConfiger) Unmarshaler(prefix string, obj interface{}, opt ...config.DecodeOption) error {
res, err := e.GetSection(prefix)
if err != nil {
return errors.WithMessage(err, fmt.Sprintf("could not read config with prefix: %s", prefix))
}
prefixLen := len(e.prefix + prefix)
m := make(map[string]string, len(res))
for k, v := range res {
m[k[prefixLen:]] = v
}
return mapstructure.Decode(m, obj)
}
// Sub return an sub configer.
func (e *EtcdConfiger) Sub(key string) (config.Configer, error) {
return newEtcdConfiger(e.client, e.prefix+key), nil
}
// TODO remove this before release v2.0.0
func (e *EtcdConfiger) OnChange(key string, fn func(value string)) {
buildOptsFunc := func() []clientv3.OpOption {
return []clientv3.OpOption{}
}
rch := e.client.Watch(context.Background(), e.prefix+key, buildOptsFunc()...)
go func() {
for {
for resp := range rch {
if err := resp.Err(); err != nil {
logs.Error("listen to key but got error callback", err)
break
}
for _, e := range resp.Events {
if e.Kv == nil {
continue
}
fn(string(e.Kv.Value))
}
}
time.Sleep(time.Second)
rch = e.client.Watch(context.Background(), e.prefix+key, buildOptsFunc()...)
}
}()
}
type EtcdConfigerProvider struct {
}
// Parse = ParseData([]byte(key))
// key must be json
func (provider *EtcdConfigerProvider) Parse(key string) (config.Configer, error) {
return provider.ParseData([]byte(key))
}
// ParseData try to parse key as clientv3.Config, using this to build etcdClient
func (provider *EtcdConfigerProvider) ParseData(data []byte) (config.Configer, error) {
cfg := &clientv3.Config{}
err := json.Unmarshal(data, cfg)
if err != nil {
return nil, errors.WithMessage(err, "parse data to etcd config failed, please check your input")
}
cfg.DialOptions = []grpc.DialOption{
grpc.WithBlock(),
grpc.WithUnaryInterceptor(grpc_prometheus.UnaryClientInterceptor),
grpc.WithStreamInterceptor(grpc_prometheus.StreamClientInterceptor),
}
client, err := clientv3.New(*cfg)
if err != nil {
return nil, errors.WithMessage(err, "create etcd client failed")
}
return newEtcdConfiger(client, ""), nil
}
func get(client *clientv3.Client, key string) (*clientv3.GetResponse, error) {
var (
resp *clientv3.GetResponse
err error
)
resp, err = client.Get(context.Background(), key)
if err != nil {
return nil, errors.WithMessage(err, fmt.Sprintf("read config from etcd with key %s failed", key))
}
return resp, err
}
func init() {
config.Register("json", &EtcdConfigerProvider{})
}

View File

@ -0,0 +1,117 @@
// Copyright 2020
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package etcd
import (
"encoding/json"
"os"
"testing"
"time"
"github.com/coreos/etcd/clientv3"
"github.com/stretchr/testify/assert"
)
func TestEtcdConfigerProvider_Parse(t *testing.T) {
provider := &EtcdConfigerProvider{}
cfger, err := provider.Parse(readEtcdConfig())
assert.Nil(t, err)
assert.NotNil(t, cfger)
}
func TestEtcdConfiger(t *testing.T) {
provider := &EtcdConfigerProvider{}
cfger, _ := provider.Parse(readEtcdConfig())
subCfger, err := cfger.Sub("sub.")
assert.Nil(t, err)
assert.NotNil(t, subCfger)
subSubCfger, err := subCfger.Sub("sub.")
assert.NotNil(t, subSubCfger)
assert.Nil(t, err)
str, err := subSubCfger.String("key1")
assert.Nil(t, err)
assert.Equal(t, "sub.sub.key", str)
// we cannot test it
subSubCfger.OnChange("watch", func(value string) {
// do nothing
})
defStr := cfger.DefaultString("not_exit", "default value")
assert.Equal(t, "default value", defStr)
defInt64 := cfger.DefaultInt64("not_exit", -1)
assert.Equal(t, int64(-1), defInt64)
defInt := cfger.DefaultInt("not_exit", -2)
assert.Equal(t, -2, defInt)
defFlt := cfger.DefaultFloat("not_exit", 12.3)
assert.Equal(t, 12.3, defFlt)
defBl := cfger.DefaultBool("not_exit", true)
assert.True(t, defBl)
defStrs := cfger.DefaultStrings("not_exit", []string{"hello"})
assert.Equal(t, []string{"hello"}, defStrs)
fl, err := cfger.Float("current.float")
assert.Nil(t, err)
assert.Equal(t, 1.23, fl)
bl, err := cfger.Bool("current.bool")
assert.Nil(t, err)
assert.True(t, bl)
it, err := cfger.Int("current.int")
assert.Nil(t, err)
assert.Equal(t, 11, it)
str, err = cfger.String("current.string")
assert.Nil(t, err)
assert.Equal(t, "hello", str)
tn := &TestEntity{}
err = cfger.Unmarshaler("current.serialize.", tn)
assert.Nil(t, err)
assert.Equal(t, "test", tn.Name)
}
type TestEntity struct {
Name string `yaml:"name"`
Sub SubEntity `yaml:"sub"`
}
type SubEntity struct {
SubName string `yaml:"subName"`
}
func readEtcdConfig() string {
addr := os.Getenv("ETCD_ADDR")
if addr == "" {
addr = "localhost:2379"
}
obj := clientv3.Config{
Endpoints: []string{addr},
DialTimeout: 3 * time.Second,
}
cfg, _ := json.Marshal(obj)
return string(cfg)
}

116
core/config/fake.go Normal file
View File

@ -0,0 +1,116 @@
// Copyright 2014 beego Author. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package config
import (
"context"
"errors"
"strconv"
"strings"
)
type fakeConfigContainer struct {
BaseConfiger
data map[string]string
}
func (c *fakeConfigContainer) getData(key string) string {
return c.data[strings.ToLower(key)]
}
func (c *fakeConfigContainer) Set(key, val string) error {
c.data[strings.ToLower(key)] = val
return nil
}
func (c *fakeConfigContainer) Int(key string) (int, error) {
return strconv.Atoi(c.getData(key))
}
func (c *fakeConfigContainer) DefaultInt(key string, defaultVal int) int {
v, err := c.Int(key)
if err != nil {
return defaultVal
}
return v
}
func (c *fakeConfigContainer) Int64(key string) (int64, error) {
return strconv.ParseInt(c.getData(key), 10, 64)
}
func (c *fakeConfigContainer) DefaultInt64(key string, defaultVal int64) int64 {
v, err := c.Int64(key)
if err != nil {
return defaultVal
}
return v
}
func (c *fakeConfigContainer) Bool(key string) (bool, error) {
return ParseBool(c.getData(key))
}
func (c *fakeConfigContainer) DefaultBool(key string, defaultVal bool) bool {
v, err := c.Bool(key)
if err != nil {
return defaultVal
}
return v
}
func (c *fakeConfigContainer) Float(key string) (float64, error) {
return strconv.ParseFloat(c.getData(key), 64)
}
func (c *fakeConfigContainer) DefaultFloat(key string, defaultVal float64) float64 {
v, err := c.Float(key)
if err != nil {
return defaultVal
}
return v
}
func (c *fakeConfigContainer) DIY(key string) (interface{}, error) {
if v, ok := c.data[strings.ToLower(key)]; ok {
return v, nil
}
return nil, errors.New("key not find")
}
func (c *fakeConfigContainer) GetSection(section string) (map[string]string, error) {
return nil, errors.New("not implement in the fakeConfigContainer")
}
func (c *fakeConfigContainer) SaveConfigFile(filename string) error {
return errors.New("not implement in the fakeConfigContainer")
}
func (c *fakeConfigContainer) Unmarshaler(prefix string, obj interface{}, opt ...DecodeOption) error {
return errors.New("unsupported operation")
}
var _ Configer = new(fakeConfigContainer)
// NewFakeConfig return a fake Configer
func NewFakeConfig() Configer {
res := &fakeConfigContainer{
data: make(map[string]string),
}
res.BaseConfiger = NewBaseConfiger(func(ctx context.Context, key string) (string, error) {
return res.getData(key), nil
})
return res
}

103
core/config/global.go Normal file
View File

@ -0,0 +1,103 @@
// Copyright 2020
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package config
// We use this to simply application's development
// for most users, they only need to use those methods
var globalInstance Configer
// InitGlobalInstance will ini the global instance
// If you want to use specific implementation, don't forget to import it.
// e.g. _ import "github.com/astaxie/beego/core/config/etcd"
// err := InitGlobalInstance("etcd", "someconfig")
func InitGlobalInstance(name string, cfg string) error {
var err error
globalInstance, err = NewConfig(name, cfg)
return err
}
// support section::key type in given key when using ini type.
func Set(key, val string) error {
return globalInstance.Set(key, val)
}
// support section::key type in key string when using ini and json type; Int,Int64,Bool,Float,DIY are same.
func String(key string) (string, error) {
return globalInstance.String(key)
}
// get string slice
func Strings(key string) ([]string, error) {
return globalInstance.Strings(key)
}
func Int(key string) (int, error) {
return globalInstance.Int(key)
}
func Int64(key string) (int64, error) {
return globalInstance.Int64(key)
}
func Bool(key string) (bool, error) {
return globalInstance.Bool(key)
}
func Float(key string) (float64, error) {
return globalInstance.Float(key)
}
// support section::key type in key string when using ini and json type; Int,Int64,Bool,Float,DIY are same.
func DefaultString(key string, defaultVal string) string {
return globalInstance.DefaultString(key, defaultVal)
}
// get string slice
func DefaultStrings(key string, defaultVal []string) []string {
return globalInstance.DefaultStrings(key, defaultVal)
}
func DefaultInt(key string, defaultVal int) int {
return globalInstance.DefaultInt(key, defaultVal)
}
func DefaultInt64(key string, defaultVal int64) int64 {
return globalInstance.DefaultInt64(key, defaultVal)
}
func DefaultBool(key string, defaultVal bool) bool {
return globalInstance.DefaultBool(key, defaultVal)
}
func DefaultFloat(key string, defaultVal float64) float64 {
return globalInstance.DefaultFloat(key, defaultVal)
}
// DIY return the original value
func DIY(key string) (interface{}, error) {
return globalInstance.DIY(key)
}
func GetSection(section string) (map[string]string, error) {
return globalInstance.GetSection(section)
}
func Unmarshaler(prefix string, obj interface{}, opt ...DecodeOption) error {
return globalInstance.Unmarshaler(prefix, obj, opt...)
}
func Sub(key string) (Configer, error) {
return globalInstance.Sub(key)
}
func OnChange(key string, fn func(value string)) {
globalInstance.OnChange(key, fn)
}
func SaveConfigFile(filename string) error {
return globalInstance.SaveConfigFile(filename)
}

104
core/config/global_test.go Normal file
View File

@ -0,0 +1,104 @@
// Copyright 2020
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package config
import (
"os"
"testing"
"github.com/stretchr/testify/assert"
)
func TestGlobalInstance(t *testing.T) {
cfgStr := `
appname = beeapi
httpport = 8080
mysqlport = 3600
PI = 3.1415926
runmode = "dev"
autorender = false
copyrequestbody = true
session= on
cookieon= off
newreg = OFF
needlogin = ON
enableSession = Y
enableCookie = N
developer="tom;jerry"
flag = 1
path1 = ${GOPATH}
path2 = ${GOPATH||/home/go}
[demo]
key1="asta"
key2 = "xie"
CaseInsensitive = true
peers = one;two;three
password = ${GOPATH}
`
path := os.TempDir() + string(os.PathSeparator) + "test_global_instance.ini"
f, err := os.Create(path)
if err != nil {
t.Fatal(err)
}
_, err = f.WriteString(cfgStr)
if err != nil {
f.Close()
t.Fatal(err)
}
f.Close()
defer os.Remove(path)
err = InitGlobalInstance("ini", path)
assert.Nil(t, err)
val, err := String("appname")
assert.Nil(t, err)
assert.Equal(t, "beeapi", val)
val = DefaultString("appname__", "404")
assert.Equal(t, "404", val)
vi, err := Int("httpport")
assert.Nil(t, err)
assert.Equal(t, 8080, vi)
vi = DefaultInt("httpport__", 404)
assert.Equal(t, 404, vi)
vi64, err := Int64("mysqlport")
assert.Nil(t, err)
assert.Equal(t, int64(3600), vi64)
vi64 = DefaultInt64("mysqlport__", 404)
assert.Equal(t, int64(404), vi64)
vf, err := Float("PI")
assert.Nil(t, err)
assert.Equal(t, 3.1415926, vf)
vf = DefaultFloat("PI__", 4.04)
assert.Equal(t, 4.04, vf)
vb, err := Bool("copyrequestbody")
assert.Nil(t, err)
assert.True(t, vb)
vb = DefaultBool("copyrequestbody__", false)
assert.False(t, vb)
vss := DefaultStrings("developer__", []string{"tom", ""})
assert.Equal(t, []string{"tom", ""}, vss)
vss, err = Strings("developer")
assert.Nil(t, err)
assert.Equal(t, []string{"tom", "jerry"}, vss)
}

528
core/config/ini.go Normal file
View File

@ -0,0 +1,528 @@
// Copyright 2014 beego Author. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package config
import (
"bufio"
"bytes"
"context"
"errors"
"io"
"io/ioutil"
"os"
"os/user"
"path/filepath"
"strconv"
"strings"
"sync"
"github.com/mitchellh/mapstructure"
"github.com/astaxie/beego/core/logs"
)
var (
defaultSection = "default" // default section means if some ini items not in a section, make them in default section,
bNumComment = []byte{'#'} // number signal
bSemComment = []byte{';'} // semicolon signal
bEmpty = []byte{}
bEqual = []byte{'='} // equal signal
bDQuote = []byte{'"'} // quote signal
sectionStart = []byte{'['} // section start signal
sectionEnd = []byte{']'} // section end signal
lineBreak = "\n"
)
// IniConfig implements Config to parse ini file.
type IniConfig struct {
}
// Parse creates a new Config and parses the file configuration from the named file.
func (ini *IniConfig) Parse(name string) (Configer, error) {
return ini.parseFile(name)
}
func (ini *IniConfig) parseFile(name string) (*IniConfigContainer, error) {
data, err := ioutil.ReadFile(name)
if err != nil {
return nil, err
}
return ini.parseData(filepath.Dir(name), data)
}
func (ini *IniConfig) parseData(dir string, data []byte) (*IniConfigContainer, error) {
cfg := &IniConfigContainer{
data: make(map[string]map[string]string),
sectionComment: make(map[string]string),
keyComment: make(map[string]string),
RWMutex: sync.RWMutex{},
}
cfg.BaseConfiger = NewBaseConfiger(func(ctx context.Context, key string) (string, error) {
return cfg.getdata(key), nil
})
cfg.Lock()
defer cfg.Unlock()
var comment bytes.Buffer
buf := bufio.NewReader(bytes.NewBuffer(data))
// check the BOM
head, err := buf.Peek(3)
if err == nil && head[0] == 239 && head[1] == 187 && head[2] == 191 {
for i := 1; i <= 3; i++ {
buf.ReadByte()
}
}
section := defaultSection
tmpBuf := bytes.NewBuffer(nil)
for {
tmpBuf.Reset()
shouldBreak := false
for {
tmp, isPrefix, err := buf.ReadLine()
if err == io.EOF {
shouldBreak = true
break
}
// It might be a good idea to throw a error on all unknonw errors?
if _, ok := err.(*os.PathError); ok {
return nil, err
}
tmpBuf.Write(tmp)
if isPrefix {
continue
}
if !isPrefix {
break
}
}
if shouldBreak {
break
}
line := tmpBuf.Bytes()
line = bytes.TrimSpace(line)
if bytes.Equal(line, bEmpty) {
continue
}
var bComment []byte
switch {
case bytes.HasPrefix(line, bNumComment):
bComment = bNumComment
case bytes.HasPrefix(line, bSemComment):
bComment = bSemComment
}
if bComment != nil {
line = bytes.TrimLeft(line, string(bComment))
// Need append to a new line if multi-line comments.
if comment.Len() > 0 {
comment.WriteByte('\n')
}
comment.Write(line)
continue
}
if bytes.HasPrefix(line, sectionStart) && bytes.HasSuffix(line, sectionEnd) {
section = strings.ToLower(string(line[1 : len(line)-1])) // section name case insensitive
if comment.Len() > 0 {
cfg.sectionComment[section] = comment.String()
comment.Reset()
}
if _, ok := cfg.data[section]; !ok {
cfg.data[section] = make(map[string]string)
}
continue
}
if _, ok := cfg.data[section]; !ok {
cfg.data[section] = make(map[string]string)
}
keyValue := bytes.SplitN(line, bEqual, 2)
key := string(bytes.TrimSpace(keyValue[0])) // key name case insensitive
key = strings.ToLower(key)
// handle include "other.conf"
if len(keyValue) == 1 && strings.HasPrefix(key, "include") {
includefiles := strings.Fields(key)
if includefiles[0] == "include" && len(includefiles) == 2 {
otherfile := strings.Trim(includefiles[1], "\"")
if !filepath.IsAbs(otherfile) {
otherfile = filepath.Join(dir, otherfile)
}
i, err := ini.parseFile(otherfile)
if err != nil {
return nil, err
}
for sec, dt := range i.data {
if _, ok := cfg.data[sec]; !ok {
cfg.data[sec] = make(map[string]string)
}
for k, v := range dt {
cfg.data[sec][k] = v
}
}
for sec, comm := range i.sectionComment {
cfg.sectionComment[sec] = comm
}
for k, comm := range i.keyComment {
cfg.keyComment[k] = comm
}
continue
}
}
if len(keyValue) != 2 {
return nil, errors.New("read the content error: \"" + string(line) + "\", should key = val")
}
val := bytes.TrimSpace(keyValue[1])
if bytes.HasPrefix(val, bDQuote) {
val = bytes.Trim(val, `"`)
}
cfg.data[section][key] = ExpandValueEnv(string(val))
if comment.Len() > 0 {
cfg.keyComment[section+"."+key] = comment.String()
comment.Reset()
}
}
return cfg, nil
}
// ParseData parse ini the data
// When include other.conf,other.conf is either absolute directory
// or under beego in default temporary directory(/tmp/beego[-username]).
func (ini *IniConfig) ParseData(data []byte) (Configer, error) {
dir := "beego"
currentUser, err := user.Current()
if err == nil {
dir = "beego-" + currentUser.Username
}
dir = filepath.Join(os.TempDir(), dir)
if err = os.MkdirAll(dir, os.ModePerm); err != nil {
return nil, err
}
return ini.parseData(dir, data)
}
// IniConfigContainer is a config which represents the ini configuration.
// When set and get value, support key as section:name type.
type IniConfigContainer struct {
BaseConfiger
data map[string]map[string]string // section=> key:val
sectionComment map[string]string // section : comment
keyComment map[string]string // id: []{comment, key...}; id 1 is for main comment.
sync.RWMutex
}
// Bool returns the boolean value for a given key.
func (c *IniConfigContainer) Bool(key string) (bool, error) {
return ParseBool(c.getdata(key))
}
// DefaultBool returns the boolean value for a given key.
// if err != nil return defaultVal
func (c *IniConfigContainer) DefaultBool(key string, defaultVal bool) bool {
v, err := c.Bool(key)
if err != nil {
return defaultVal
}
return v
}
// Int returns the integer value for a given key.
func (c *IniConfigContainer) Int(key string) (int, error) {
return strconv.Atoi(c.getdata(key))
}
// DefaultInt returns the integer value for a given key.
// if err != nil return defaultVal
func (c *IniConfigContainer) DefaultInt(key string, defaultVal int) int {
v, err := c.Int(key)
if err != nil {
return defaultVal
}
return v
}
// Int64 returns the int64 value for a given key.
func (c *IniConfigContainer) Int64(key string) (int64, error) {
return strconv.ParseInt(c.getdata(key), 10, 64)
}
// DefaultInt64 returns the int64 value for a given key.
// if err != nil return defaultVal
func (c *IniConfigContainer) DefaultInt64(key string, defaultVal int64) int64 {
v, err := c.Int64(key)
if err != nil {
return defaultVal
}
return v
}
// Float returns the float value for a given key.
func (c *IniConfigContainer) Float(key string) (float64, error) {
return strconv.ParseFloat(c.getdata(key), 64)
}
// DefaultFloat returns the float64 value for a given key.
// if err != nil return defaultVal
func (c *IniConfigContainer) DefaultFloat(key string, defaultVal float64) float64 {
v, err := c.Float(key)
if err != nil {
return defaultVal
}
return v
}
// String returns the string value for a given key.
func (c *IniConfigContainer) String(key string) (string, error) {
return c.getdata(key), nil
}
// DefaultString returns the string value for a given key.
// if err != nil return defaultVal
func (c *IniConfigContainer) DefaultString(key string, defaultVal string) string {
v, err := c.String(key)
if v == "" || err != nil {
return defaultVal
}
return v
}
// Strings returns the []string value for a given key.
// Return nil if config value does not exist or is empty.
func (c *IniConfigContainer) Strings(key string) ([]string, error) {
v, err := c.String(key)
if v == "" || err != nil {
return nil, err
}
return strings.Split(v, ";"), nil
}
// DefaultStrings returns the []string value for a given key.
// if err != nil return defaultVal
func (c *IniConfigContainer) DefaultStrings(key string, defaultVal []string) []string {
v, err := c.Strings(key)
if v == nil || err != nil {
return defaultVal
}
return v
}
// GetSection returns map for the given section
func (c *IniConfigContainer) GetSection(section string) (map[string]string, error) {
if v, ok := c.data[section]; ok {
return v, nil
}
return nil, errors.New("not exist section")
}
// SaveConfigFile save the config into file.
//
// BUG(env): The environment variable config item will be saved with real value in SaveConfigFile Function.
func (c *IniConfigContainer) SaveConfigFile(filename string) (err error) {
// Write configuration file by filename.
f, err := os.Create(filename)
if err != nil {
return err
}
defer f.Close()
// Get section or key comments. Fixed #1607
getCommentStr := func(section, key string) string {
var (
comment string
ok bool
)
if len(key) == 0 {
comment, ok = c.sectionComment[section]
} else {
comment, ok = c.keyComment[section+"."+key]
}
if ok {
// Empty comment
if len(comment) == 0 || len(strings.TrimSpace(comment)) == 0 {
return string(bNumComment)
}
prefix := string(bNumComment)
// Add the line head character "#"
return prefix + strings.Replace(comment, lineBreak, lineBreak+prefix, -1)
}
return ""
}
buf := bytes.NewBuffer(nil)
// Save default section at first place
if dt, ok := c.data[defaultSection]; ok {
for key, val := range dt {
if key != " " {
// Write key comments.
if v := getCommentStr(defaultSection, key); len(v) > 0 {
if _, err = buf.WriteString(v + lineBreak); err != nil {
return err
}
}
// Write key and value.
if _, err = buf.WriteString(key + string(bEqual) + val + lineBreak); err != nil {
return err
}
}
}
// Put a line between sections.
if _, err = buf.WriteString(lineBreak); err != nil {
return err
}
}
// Save named sections
for section, dt := range c.data {
if section != defaultSection {
// Write section comments.
if v := getCommentStr(section, ""); len(v) > 0 {
if _, err = buf.WriteString(v + lineBreak); err != nil {
return err
}
}
// Write section name.
if _, err = buf.WriteString(string(sectionStart) + section + string(sectionEnd) + lineBreak); err != nil {
return err
}
for key, val := range dt {
if key != " " {
// Write key comments.
if v := getCommentStr(section, key); len(v) > 0 {
if _, err = buf.WriteString(v + lineBreak); err != nil {
return err
}
}
// Write key and value.
if _, err = buf.WriteString(key + string(bEqual) + val + lineBreak); err != nil {
return err
}
}
}
// Put a line between sections.
if _, err = buf.WriteString(lineBreak); err != nil {
return err
}
}
}
_, err = buf.WriteTo(f)
return err
}
// Set writes a new value for key.
// if write to one section, the key need be "section::key".
// if the section is not existed, it panics.
func (c *IniConfigContainer) Set(key, val string) error {
c.Lock()
defer c.Unlock()
if len(key) == 0 {
return errors.New("key is empty")
}
var (
section, k string
sectionKey = strings.Split(strings.ToLower(key), "::")
)
if len(sectionKey) >= 2 {
section = sectionKey[0]
k = sectionKey[1]
} else {
section = defaultSection
k = sectionKey[0]
}
if _, ok := c.data[section]; !ok {
c.data[section] = make(map[string]string)
}
c.data[section][k] = val
return nil
}
// DIY returns the raw value by a given key.
func (c *IniConfigContainer) DIY(key string) (v interface{}, err error) {
if v, ok := c.data[strings.ToLower(key)]; ok {
return v, nil
}
return v, errors.New("key not find")
}
// section.key or key
func (c *IniConfigContainer) getdata(key string) string {
if len(key) == 0 {
return ""
}
c.RLock()
defer c.RUnlock()
var (
section, k string
sectionKey = strings.Split(strings.ToLower(key), "::")
)
if len(sectionKey) >= 2 {
section = sectionKey[0]
k = sectionKey[1]
} else {
section = defaultSection
k = sectionKey[0]
}
if v, ok := c.data[section]; ok {
if vv, ok := v[k]; ok {
return vv
}
}
return ""
}
func (c *IniConfigContainer) Unmarshaler(prefix string, obj interface{}, opt ...DecodeOption) error {
if len(prefix) > 0 {
return errors.New("unsupported prefix params")
}
return mapstructure.Decode(c.data, obj)
}
func init() {
Register("ini", &IniConfig{})
err := InitGlobalInstance("ini", "config/app.conf")
if err != nil {
logs.Warn("init global config instance failed. If you donot use this, just ignore it. ", err)
}
}
// Ignore this error

191
core/config/ini_test.go Normal file
View File

@ -0,0 +1,191 @@
// Copyright 2014 beego Author. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package config
import (
"fmt"
"io/ioutil"
"os"
"strings"
"testing"
)
func TestIni(t *testing.T) {
var (
inicontext = `
;comment one
#comment two
appname = beeapi
httpport = 8080
mysqlport = 3600
PI = 3.1415976
runmode = "dev"
autorender = false
copyrequestbody = true
session= on
cookieon= off
newreg = OFF
needlogin = ON
enableSession = Y
enableCookie = N
flag = 1
path1 = ${GOPATH}
path2 = ${GOPATH||/home/go}
[demo]
key1="asta"
key2 = "xie"
CaseInsensitive = true
peers = one;two;three
password = ${GOPATH}
`
keyValue = map[string]interface{}{
"appname": "beeapi",
"httpport": 8080,
"mysqlport": int64(3600),
"pi": 3.1415976,
"runmode": "dev",
"autorender": false,
"copyrequestbody": true,
"session": true,
"cookieon": false,
"newreg": false,
"needlogin": true,
"enableSession": true,
"enableCookie": false,
"flag": true,
"path1": os.Getenv("GOPATH"),
"path2": os.Getenv("GOPATH"),
"demo::key1": "asta",
"demo::key2": "xie",
"demo::CaseInsensitive": true,
"demo::peers": []string{"one", "two", "three"},
"demo::password": os.Getenv("GOPATH"),
"null": "",
"demo2::key1": "",
"error": "",
"emptystrings": []string{},
}
)
f, err := os.Create("testini.conf")
if err != nil {
t.Fatal(err)
}
_, err = f.WriteString(inicontext)
if err != nil {
f.Close()
t.Fatal(err)
}
f.Close()
defer os.Remove("testini.conf")
iniconf, err := NewConfig("ini", "testini.conf")
if err != nil {
t.Fatal(err)
}
for k, v := range keyValue {
var err error
var value interface{}
switch v.(type) {
case int:
value, err = iniconf.Int(k)
case int64:
value, err = iniconf.Int64(k)
case float64:
value, err = iniconf.Float(k)
case bool:
value, err = iniconf.Bool(k)
case []string:
value, err = iniconf.Strings(k)
case string:
value, err = iniconf.String(k)
default:
value, err = iniconf.DIY(k)
}
if err != nil {
t.Fatalf("get key %q value fail,err %s", k, err)
} else if fmt.Sprintf("%v", v) != fmt.Sprintf("%v", value) {
t.Fatalf("get key %q value, want %v got %v .", k, v, value)
}
}
if err = iniconf.Set("name", "astaxie"); err != nil {
t.Fatal(err)
}
res, _ := iniconf.String("name")
if res != "astaxie" {
t.Fatal("get name error")
}
}
func TestIniSave(t *testing.T) {
const (
inicontext = `
app = app
;comment one
#comment two
# comment three
appname = beeapi
httpport = 8080
# DB Info
# enable db
[dbinfo]
# db type name
# suport mysql,sqlserver
name = mysql
`
saveResult = `
app=app
#comment one
#comment two
# comment three
appname=beeapi
httpport=8080
# DB Info
# enable db
[dbinfo]
# db type name
# suport mysql,sqlserver
name=mysql
`
)
cfg, err := NewConfigData("ini", []byte(inicontext))
if err != nil {
t.Fatal(err)
}
name := "newIniConfig.ini"
if err := cfg.SaveConfigFile(name); err != nil {
t.Fatal(err)
}
defer os.Remove(name)
if data, err := ioutil.ReadFile(name); err != nil {
t.Fatal(err)
} else {
cfgData := string(data)
datas := strings.Split(saveResult, "\n")
for _, line := range datas {
if !strings.Contains(cfgData, line+"\n") {
t.Fatalf("different after save ini config file. need contains %q", line)
}
}
}
}

312
core/config/json/json.go Normal file
View File

@ -0,0 +1,312 @@
// Copyright 2014 beego Author. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package json
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"os"
"strconv"
"strings"
"sync"
"github.com/mitchellh/mapstructure"
"github.com/astaxie/beego/core/config"
"github.com/astaxie/beego/core/logs"
)
// JSONConfig is a json config parser and implements Config interface.
type JSONConfig struct {
}
// Parse returns a ConfigContainer with parsed json config map.
func (js *JSONConfig) Parse(filename string) (config.Configer, error) {
file, err := os.Open(filename)
if err != nil {
return nil, err
}
defer file.Close()
content, err := ioutil.ReadAll(file)
if err != nil {
return nil, err
}
return js.ParseData(content)
}
// ParseData returns a ConfigContainer with json string
func (js *JSONConfig) ParseData(data []byte) (config.Configer, error) {
x := &JSONConfigContainer{
data: make(map[string]interface{}),
}
err := json.Unmarshal(data, &x.data)
if err != nil {
var wrappingArray []interface{}
err2 := json.Unmarshal(data, &wrappingArray)
if err2 != nil {
return nil, err
}
x.data["rootArray"] = wrappingArray
}
x.data = config.ExpandValueEnvForMap(x.data)
return x, nil
}
// JSONConfigContainer is a config which represents the json configuration.
// Only when get value, support key as section:name type.
type JSONConfigContainer struct {
data map[string]interface{}
sync.RWMutex
}
func (c *JSONConfigContainer) Unmarshaler(prefix string, obj interface{}, opt ...config.DecodeOption) error {
sub, err := c.sub(prefix)
if err != nil {
return err
}
return mapstructure.Decode(sub, obj)
}
func (c *JSONConfigContainer) Sub(key string) (config.Configer, error) {
sub, err := c.sub(key)
if err != nil {
return nil, err
}
return &JSONConfigContainer{
data: sub,
}, nil
}
func (c *JSONConfigContainer) sub(key string) (map[string]interface{}, error) {
if key == "" {
return c.data, nil
}
value, ok := c.data[key]
if !ok {
return nil, errors.New(fmt.Sprintf("key is not found: %s", key))
}
res, ok := value.(map[string]interface{})
if !ok {
return nil, errors.New(fmt.Sprintf("the type of value is invalid, key: %s", key))
}
return res, nil
}
func (c *JSONConfigContainer) OnChange(key string, fn func(value string)) {
logs.Warn("unsupported operation")
}
// Bool returns the boolean value for a given key.
func (c *JSONConfigContainer) Bool(key string) (bool, error) {
val := c.getData(key)
if val != nil {
return config.ParseBool(val)
}
return false, fmt.Errorf("not exist key: %q", key)
}
// DefaultBool return the bool value if has no error
// otherwise return the defaultval
func (c *JSONConfigContainer) DefaultBool(key string, defaultVal bool) bool {
if v, err := c.Bool(key); err == nil {
return v
}
return defaultVal
}
// Int returns the integer value for a given key.
func (c *JSONConfigContainer) Int(key string) (int, error) {
val := c.getData(key)
if val != nil {
if v, ok := val.(float64); ok {
return int(v), nil
} else if v, ok := val.(string); ok {
return strconv.Atoi(v)
}
return 0, errors.New("not valid value")
}
return 0, errors.New("not exist key:" + key)
}
// DefaultInt returns the integer value for a given key.
// if err != nil return defaultval
func (c *JSONConfigContainer) DefaultInt(key string, defaultVal int) int {
if v, err := c.Int(key); err == nil {
return v
}
return defaultVal
}
// Int64 returns the int64 value for a given key.
func (c *JSONConfigContainer) Int64(key string) (int64, error) {
val := c.getData(key)
if val != nil {
if v, ok := val.(float64); ok {
return int64(v), nil
}
return 0, errors.New("not int64 value")
}
return 0, errors.New("not exist key:" + key)
}
// DefaultInt64 returns the int64 value for a given key.
// if err != nil return defaultval
func (c *JSONConfigContainer) DefaultInt64(key string, defaultVal int64) int64 {
if v, err := c.Int64(key); err == nil {
return v
}
return defaultVal
}
// Float returns the float value for a given key.
func (c *JSONConfigContainer) Float(key string) (float64, error) {
val := c.getData(key)
if val != nil {
if v, ok := val.(float64); ok {
return v, nil
}
return 0.0, errors.New("not float64 value")
}
return 0.0, errors.New("not exist key:" + key)
}
// DefaultFloat returns the float64 value for a given key.
// if err != nil return defaultval
func (c *JSONConfigContainer) DefaultFloat(key string, defaultVal float64) float64 {
if v, err := c.Float(key); err == nil {
return v
}
return defaultVal
}
// String returns the string value for a given key.
func (c *JSONConfigContainer) String(key string) (string, error) {
val := c.getData(key)
if val != nil {
if v, ok := val.(string); ok {
return v, nil
}
}
return "", nil
}
// DefaultString returns the string value for a given key.
// if err != nil return defaultval
func (c *JSONConfigContainer) DefaultString(key string, defaultVal string) string {
// TODO FIXME should not use "" to replace non existence
if v, err := c.String(key); v != "" && err == nil {
return v
}
return defaultVal
}
// Strings returns the []string value for a given key.
func (c *JSONConfigContainer) Strings(key string) ([]string, error) {
stringVal, err := c.String(key)
if stringVal == "" || err != nil {
return nil, err
}
return strings.Split(stringVal, ";"), nil
}
// DefaultStrings returns the []string value for a given key.
// if err != nil return defaultval
func (c *JSONConfigContainer) DefaultStrings(key string, defaultVal []string) []string {
if v, err := c.Strings(key); v != nil && err == nil {
return v
}
return defaultVal
}
// GetSection returns map for the given section
func (c *JSONConfigContainer) GetSection(section string) (map[string]string, error) {
if v, ok := c.data[section]; ok {
return v.(map[string]string), nil
}
return nil, errors.New("nonexist section " + section)
}
// SaveConfigFile save the config into file
func (c *JSONConfigContainer) SaveConfigFile(filename string) (err error) {
// Write configuration file by filename.
f, err := os.Create(filename)
if err != nil {
return err
}
defer f.Close()
b, err := json.MarshalIndent(c.data, "", " ")
if err != nil {
return err
}
_, err = f.Write(b)
return err
}
// Set writes a new value for key.
func (c *JSONConfigContainer) Set(key, val string) error {
c.Lock()
defer c.Unlock()
c.data[key] = val
return nil
}
// DIY returns the raw value by a given key.
func (c *JSONConfigContainer) DIY(key string) (v interface{}, err error) {
val := c.getData(key)
if val != nil {
return val, nil
}
return nil, errors.New("not exist key")
}
// section.key or key
func (c *JSONConfigContainer) getData(key string) interface{} {
if len(key) == 0 {
return nil
}
c.RLock()
defer c.RUnlock()
sectionKeys := strings.Split(key, "::")
if len(sectionKeys) >= 2 {
curValue, ok := c.data[sectionKeys[0]]
if !ok {
return nil
}
for _, key := range sectionKeys[1:] {
if v, ok := curValue.(map[string]interface{}); ok {
if curValue, ok = v[key]; !ok {
return nil
}
}
}
return curValue
}
if v, ok := c.data[key]; ok {
return v
}
return nil
}
func init() {
config.Register("json", &JSONConfig{})
}

View File

@ -0,0 +1,251 @@
// Copyright 2014 beego Author. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package json
import (
"fmt"
"os"
"testing"
"github.com/stretchr/testify/assert"
"github.com/astaxie/beego/core/config"
)
func TestJsonStartsWithArray(t *testing.T) {
const jsoncontextwitharray = `[
{
"url": "user",
"serviceAPI": "http://www.test.com/user"
},
{
"url": "employee",
"serviceAPI": "http://www.test.com/employee"
}
]`
f, err := os.Create("testjsonWithArray.conf")
if err != nil {
t.Fatal(err)
}
_, err = f.WriteString(jsoncontextwitharray)
if err != nil {
f.Close()
t.Fatal(err)
}
f.Close()
defer os.Remove("testjsonWithArray.conf")
jsonconf, err := config.NewConfig("json", "testjsonWithArray.conf")
if err != nil {
t.Fatal(err)
}
rootArray, err := jsonconf.DIY("rootArray")
if err != nil {
t.Error("array does not exist as element")
}
rootArrayCasted := rootArray.([]interface{})
if rootArrayCasted == nil {
t.Error("array from root is nil")
} else {
elem := rootArrayCasted[0].(map[string]interface{})
if elem["url"] != "user" || elem["serviceAPI"] != "http://www.test.com/user" {
t.Error("array[0] values are not valid")
}
elem2 := rootArrayCasted[1].(map[string]interface{})
if elem2["url"] != "employee" || elem2["serviceAPI"] != "http://www.test.com/employee" {
t.Error("array[1] values are not valid")
}
}
}
func TestJson(t *testing.T) {
var (
jsoncontext = `{
"appname": "beeapi",
"testnames": "foo;bar",
"httpport": 8080,
"mysqlport": 3600,
"PI": 3.1415976,
"runmode": "dev",
"autorender": false,
"copyrequestbody": true,
"session": "on",
"cookieon": "off",
"newreg": "OFF",
"needlogin": "ON",
"enableSession": "Y",
"enableCookie": "N",
"flag": 1,
"path1": "${GOPATH}",
"path2": "${GOPATH||/home/go}",
"database": {
"host": "host",
"port": "port",
"database": "database",
"username": "username",
"password": "${GOPATH}",
"conns":{
"maxconnection":12,
"autoconnect":true,
"connectioninfo":"info",
"root": "${GOPATH}"
}
}
}`
keyValue = map[string]interface{}{
"appname": "beeapi",
"testnames": []string{"foo", "bar"},
"httpport": 8080,
"mysqlport": int64(3600),
"PI": 3.1415976,
"runmode": "dev",
"autorender": false,
"copyrequestbody": true,
"session": true,
"cookieon": false,
"newreg": false,
"needlogin": true,
"enableSession": true,
"enableCookie": false,
"flag": true,
"path1": os.Getenv("GOPATH"),
"path2": os.Getenv("GOPATH"),
"database::host": "host",
"database::port": "port",
"database::database": "database",
"database::password": os.Getenv("GOPATH"),
"database::conns::maxconnection": 12,
"database::conns::autoconnect": true,
"database::conns::connectioninfo": "info",
"database::conns::root": os.Getenv("GOPATH"),
"unknown": "",
}
)
f, err := os.Create("testjson.conf")
if err != nil {
t.Fatal(err)
}
_, err = f.WriteString(jsoncontext)
if err != nil {
f.Close()
t.Fatal(err)
}
f.Close()
defer os.Remove("testjson.conf")
jsonconf, err := config.NewConfig("json", "testjson.conf")
if err != nil {
t.Fatal(err)
}
for k, v := range keyValue {
var err error
var value interface{}
switch v.(type) {
case int:
value, err = jsonconf.Int(k)
case int64:
value, err = jsonconf.Int64(k)
case float64:
value, err = jsonconf.Float(k)
case bool:
value, err = jsonconf.Bool(k)
case []string:
value, err = jsonconf.Strings(k)
case string:
value, err = jsonconf.String(k)
default:
value, err = jsonconf.DIY(k)
}
if err != nil {
t.Fatalf("get key %q value fatal,%v err %s", k, v, err)
} else if fmt.Sprintf("%v", v) != fmt.Sprintf("%v", value) {
t.Fatalf("get key %q value, want %v got %v .", k, v, value)
}
}
if err = jsonconf.Set("name", "astaxie"); err != nil {
t.Fatal(err)
}
res, _ := jsonconf.String("name")
if res != "astaxie" {
t.Fatal("get name error")
}
if db, err := jsonconf.DIY("database"); err != nil {
t.Fatal(err)
} else if m, ok := db.(map[string]interface{}); !ok {
t.Log(db)
t.Fatal("db not map[string]interface{}")
} else {
if m["host"].(string) != "host" {
t.Fatal("get host err")
}
}
if _, err := jsonconf.Int("unknown"); err == nil {
t.Error("unknown keys should return an error when expecting an Int")
}
if _, err := jsonconf.Int64("unknown"); err == nil {
t.Error("unknown keys should return an error when expecting an Int64")
}
if _, err := jsonconf.Float("unknown"); err == nil {
t.Error("unknown keys should return an error when expecting a Float")
}
if _, err := jsonconf.DIY("unknown"); err == nil {
t.Error("unknown keys should return an error when expecting an interface{}")
}
if val, _ := jsonconf.String("unknown"); val != "" {
t.Error("unknown keys should return an empty string when expecting a String")
}
if _, err := jsonconf.Bool("unknown"); err == nil {
t.Error("unknown keys should return an error when expecting a Bool")
}
if !jsonconf.DefaultBool("unknown", true) {
t.Error("unknown keys with default value wrong")
}
sub, err := jsonconf.Sub("database")
assert.Nil(t, err)
assert.NotNil(t, sub)
sub, err = sub.Sub("conns")
assert.Nil(t, err)
maxCon, _ := sub.Int("maxconnection")
assert.Equal(t, 12, maxCon)
dbCfg := &DatabaseConfig{}
err = sub.Unmarshaler("", dbCfg)
assert.Nil(t, err)
assert.Equal(t, 12, dbCfg.MaxConnection)
assert.True(t, dbCfg.Autoconnect)
assert.Equal(t, "info", dbCfg.Connectioninfo)
}
type DatabaseConfig struct {
MaxConnection int `json:"maxconnection"`
Autoconnect bool `json:"autoconnect"`
Connectioninfo string `json:"connectioninfo"`
}

357
core/config/toml/toml.go Normal file
View File

@ -0,0 +1,357 @@
// Copyright 2020
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package toml
import (
"io/ioutil"
"os"
"strings"
"github.com/pelletier/go-toml"
"github.com/astaxie/beego/core/config"
)
const keySeparator = "."
type Config struct {
tree *toml.Tree
}
// Parse accepts filename as the parameter
func (c *Config) Parse(filename string) (config.Configer, error) {
ctx, err := ioutil.ReadFile(filename)
if err != nil {
return nil, err
}
return c.ParseData(ctx)
}
func (c *Config) ParseData(data []byte) (config.Configer, error) {
t, err := toml.LoadBytes(data)
if err != nil {
return nil, err
}
return &configContainer{
t: t,
}, nil
}
// configContainer support key looks like "a.b.c"
type configContainer struct {
t *toml.Tree
}
// Set put key, val
func (c *configContainer) Set(key, val string) error {
path := strings.Split(key, keySeparator)
sub, err := subTree(c.t, path[0:len(path)-1])
if err != nil {
return err
}
sub.Set(path[len(path)-1], val)
return nil
}
// String return the value.
// return error if key not found or value is invalid type
func (c *configContainer) String(key string) (string, error) {
res, err := c.get(key)
if err != nil {
return "", err
}
if res == nil {
return "", config.KeyNotFoundError
}
if str, ok := res.(string); ok {
return str, nil
} else {
return "", config.InvalidValueTypeError
}
}
// Strings return []string
// return error if key not found or value is invalid type
func (c *configContainer) Strings(key string) ([]string, error) {
val, err := c.get(key)
if err != nil {
return []string{}, err
}
if val == nil {
return []string{}, config.KeyNotFoundError
}
if arr, ok := val.([]interface{}); ok {
res := make([]string, 0, len(arr))
for _, ele := range arr {
if str, ok := ele.(string); ok {
res = append(res, str)
} else {
return []string{}, config.InvalidValueTypeError
}
}
return res, nil
} else {
return []string{}, config.InvalidValueTypeError
}
}
// Int return int value
// return error if key not found or value is invalid type
func (c *configContainer) Int(key string) (int, error) {
val, err := c.Int64(key)
return int(val), err
}
// Int64 return int64 value
// return error if key not found or value is invalid type
func (c *configContainer) Int64(key string) (int64, error) {
res, err := c.get(key)
if err != nil {
return 0, err
}
if res == nil {
return 0, config.KeyNotFoundError
}
if i, ok := res.(int); ok {
return int64(i), nil
} else if i64, ok := res.(int64); ok {
return i64, nil
} else {
return 0, config.InvalidValueTypeError
}
}
// bool return bool value
// return error if key not found or value is invalid type
func (c *configContainer) Bool(key string) (bool, error) {
res, err := c.get(key)
if err != nil {
return false, err
}
if res == nil {
return false, config.KeyNotFoundError
}
if b, ok := res.(bool); ok {
return b, nil
} else {
return false, config.InvalidValueTypeError
}
}
// Float return float value
// return error if key not found or value is invalid type
func (c *configContainer) Float(key string) (float64, error) {
res, err := c.get(key)
if err != nil {
return 0, err
}
if res == nil {
return 0, config.KeyNotFoundError
}
if f, ok := res.(float64); ok {
return f, nil
} else {
return 0, config.InvalidValueTypeError
}
}
// DefaultString return string value
// return default value if key not found or value is invalid type
func (c *configContainer) DefaultString(key string, defaultVal string) string {
res, err := c.get(key)
if err != nil {
return defaultVal
}
if str, ok := res.(string); ok {
return str
} else {
return defaultVal
}
}
// DefaultStrings return []string
// return default value if key not found or value is invalid type
func (c *configContainer) DefaultStrings(key string, defaultVal []string) []string {
val, err := c.get(key)
if err != nil {
return defaultVal
}
if arr, ok := val.([]interface{}); ok {
res := make([]string, 0, len(arr))
for _, ele := range arr {
if str, ok := ele.(string); ok {
res = append(res, str)
} else {
return defaultVal
}
}
return res
} else {
return defaultVal
}
}
// DefaultInt return int value
// return default value if key not found or value is invalid type
func (c *configContainer) DefaultInt(key string, defaultVal int) int {
return int(c.DefaultInt64(key, int64(defaultVal)))
}
// DefaultInt64 return int64 value
// return default value if key not found or value is invalid type
func (c *configContainer) DefaultInt64(key string, defaultVal int64) int64 {
res, err := c.get(key)
if err != nil {
return defaultVal
}
if i, ok := res.(int); ok {
return int64(i)
} else if i64, ok := res.(int64); ok {
return i64
} else {
return defaultVal
}
}
// DefaultBool return bool value
// return default value if key not found or value is invalid type
func (c *configContainer) DefaultBool(key string, defaultVal bool) bool {
res, err := c.get(key)
if err != nil {
return defaultVal
}
if b, ok := res.(bool); ok {
return b
} else {
return defaultVal
}
}
// DefaultFloat return float value
// return default value if key not found or value is invalid type
func (c *configContainer) DefaultFloat(key string, defaultVal float64) float64 {
res, err := c.get(key)
if err != nil {
return defaultVal
}
if f, ok := res.(float64); ok {
return f
} else {
return defaultVal
}
}
// DIY returns the original value
func (c *configContainer) DIY(key string) (interface{}, error) {
return c.get(key)
}
// GetSection return error if the value is not valid toml doc
func (c *configContainer) GetSection(section string) (map[string]string, error) {
val, err := subTree(c.t, strings.Split(section, keySeparator))
if err != nil {
return map[string]string{}, err
}
m := val.ToMap()
res := make(map[string]string, len(m))
for k, v := range m {
res[k] = config.ToString(v)
}
return res, nil
}
func (c *configContainer) Unmarshaler(prefix string, obj interface{}, opt ...config.DecodeOption) error {
if len(prefix) > 0 {
t, err := subTree(c.t, strings.Split(prefix, keySeparator))
if err != nil {
return err
}
return t.Unmarshal(obj)
}
return c.t.Unmarshal(obj)
}
// Sub return sub configer
// return error if key not found or the value is not a sub doc
func (c *configContainer) Sub(key string) (config.Configer, error) {
val, err := subTree(c.t, strings.Split(key, keySeparator))
if err != nil {
return nil, err
}
return &configContainer{
t: val,
}, nil
}
// OnChange do nothing
func (c *configContainer) OnChange(key string, fn func(value string)) {
// do nothing
}
// SaveConfigFile create or override the file
func (c *configContainer) SaveConfigFile(filename string) error {
// Write configuration file by filename.
f, err := os.Create(filename)
if err != nil {
return err
}
defer f.Close()
_, err = c.t.WriteTo(f)
return err
}
func (c *configContainer) get(key string) (interface{}, error) {
if len(key) == 0 {
return nil, config.KeyNotFoundError
}
segs := strings.Split(key, keySeparator)
t, err := subTree(c.t, segs[0:len(segs)-1])
if err != nil {
return nil, err
}
return t.Get(segs[len(segs)-1]), nil
}
func subTree(t *toml.Tree, path []string) (*toml.Tree, error) {
res := t
for i := 0; i < len(path); i++ {
if subTree, ok := res.Get(path[i]).(*toml.Tree); ok {
res = subTree
} else {
return nil, config.InvalidValueTypeError
}
}
if res == nil {
return nil, config.KeyNotFoundError
}
return res, nil
}
func init() {
config.Register("toml", &Config{})
}

View File

@ -0,0 +1,379 @@
// Copyright 2020
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package toml
import (
"fmt"
"os"
"testing"
"github.com/stretchr/testify/assert"
"github.com/astaxie/beego/core/config"
)
func TestConfig_Parse(t *testing.T) {
// file not found
cfg := &Config{}
_, err := cfg.Parse("invalid_file_name.txt")
assert.NotNil(t, err)
}
func TestConfig_ParseData(t *testing.T) {
data := `
name="Tom"
`
cfg := &Config{}
c, err := cfg.ParseData([]byte(data))
assert.Nil(t, err)
assert.NotNil(t, c)
}
func TestConfigContainer_Bool(t *testing.T) {
data := `
Man=true
Woman="true"
`
cfg := &Config{}
c, err := cfg.ParseData([]byte(data))
assert.Nil(t, err)
assert.NotNil(t, c)
val, err := c.Bool("Man")
assert.Nil(t, err)
assert.True(t, val)
_, err = c.Bool("Woman")
assert.NotNil(t, err)
assert.Equal(t, config.InvalidValueTypeError, err)
}
func TestConfigContainer_DefaultBool(t *testing.T) {
data := `
Man=true
Woman="false"
`
cfg := &Config{}
c, err := cfg.ParseData([]byte(data))
assert.Nil(t, err)
assert.NotNil(t, c)
val := c.DefaultBool("Man11", true)
assert.True(t, val)
val = c.DefaultBool("Man", false)
assert.True(t, val)
val = c.DefaultBool("Woman", true)
assert.True(t, val)
}
func TestConfigContainer_DefaultFloat(t *testing.T) {
data := `
Price=12.3
PriceInvalid="12.3"
`
cfg := &Config{}
c, err := cfg.ParseData([]byte(data))
assert.Nil(t, err)
assert.NotNil(t, c)
val := c.DefaultFloat("Price", 11.2)
assert.Equal(t, 12.3, val)
val = c.DefaultFloat("Price11", 11.2)
assert.Equal(t, 11.2, val)
val = c.DefaultFloat("PriceInvalid", 11.2)
assert.Equal(t, 11.2, val)
}
func TestConfigContainer_DefaultInt(t *testing.T) {
data := `
Age=12
AgeInvalid="13"
`
cfg := &Config{}
c, err := cfg.ParseData([]byte(data))
assert.Nil(t, err)
assert.NotNil(t, c)
val := c.DefaultInt("Age", 11)
assert.Equal(t, 12, val)
val = c.DefaultInt("Price11", 11)
assert.Equal(t, 11, val)
val = c.DefaultInt("PriceInvalid", 11)
assert.Equal(t, 11, val)
}
func TestConfigContainer_DefaultString(t *testing.T) {
data := `
Name="Tom"
NameInvalid=13
`
cfg := &Config{}
c, err := cfg.ParseData([]byte(data))
assert.Nil(t, err)
assert.NotNil(t, c)
val := c.DefaultString("Name", "Jerry")
assert.Equal(t, "Tom", val)
val = c.DefaultString("Name11", "Jerry")
assert.Equal(t, "Jerry", val)
val = c.DefaultString("NameInvalid", "Jerry")
assert.Equal(t, "Jerry", val)
}
func TestConfigContainer_DefaultStrings(t *testing.T) {
data := `
Name=["Tom", "Jerry"]
NameInvalid="Tom"
`
cfg := &Config{}
c, err := cfg.ParseData([]byte(data))
assert.Nil(t, err)
assert.NotNil(t, c)
val := c.DefaultStrings("Name", []string{"Jerry"})
assert.Equal(t, []string{"Tom", "Jerry"}, val)
val = c.DefaultStrings("Name11", []string{"Jerry"})
assert.Equal(t, []string{"Jerry"}, val)
val = c.DefaultStrings("NameInvalid", []string{"Jerry"})
assert.Equal(t, []string{"Jerry"}, val)
}
func TestConfigContainer_DIY(t *testing.T) {
data := `
Name=["Tom", "Jerry"]
`
cfg := &Config{}
c, err := cfg.ParseData([]byte(data))
assert.Nil(t, err)
assert.NotNil(t, c)
_, err = c.DIY("Name")
assert.Nil(t, err)
}
func TestConfigContainer_Float(t *testing.T) {
data := `
Price=12.3
PriceInvalid="12.3"
`
cfg := &Config{}
c, err := cfg.ParseData([]byte(data))
assert.Nil(t, err)
assert.NotNil(t, c)
val, err := c.Float("Price")
assert.Nil(t, err)
assert.Equal(t, 12.3, val)
_, err = c.Float("Price11")
assert.Equal(t, config.KeyNotFoundError, err)
_, err = c.Float("PriceInvalid")
assert.Equal(t, config.InvalidValueTypeError, err)
}
func TestConfigContainer_Int(t *testing.T) {
data := `
Age=12
AgeInvalid="13"
`
cfg := &Config{}
c, err := cfg.ParseData([]byte(data))
assert.Nil(t, err)
assert.NotNil(t, c)
val, err := c.Int("Age")
assert.Nil(t, err)
assert.Equal(t, 12, val)
_, err = c.Int("Age11")
assert.Equal(t, config.KeyNotFoundError, err)
_, err = c.Int("AgeInvalid")
assert.Equal(t, config.InvalidValueTypeError, err)
}
func TestConfigContainer_GetSection(t *testing.T) {
data := `
[servers]
# You can indent as you please. Tabs or spaces. TOML don't care.
[servers.alpha]
ip = "10.0.0.1"
dc = "eqdc10"
[servers.beta]
ip = "10.0.0.2"
dc = "eqdc10"
`
cfg := &Config{}
c, err := cfg.ParseData([]byte(data))
assert.Nil(t, err)
assert.NotNil(t, c)
m, err := c.GetSection("servers")
assert.Nil(t, err)
assert.NotNil(t, m)
assert.Equal(t, 2, len(m))
}
func TestConfigContainer_String(t *testing.T) {
data := `
Name="Tom"
NameInvalid=13
[Person]
Name="Jerry"
`
cfg := &Config{}
c, err := cfg.ParseData([]byte(data))
assert.Nil(t, err)
assert.NotNil(t, c)
val, err := c.String("Name")
assert.Nil(t, err)
assert.Equal(t, "Tom", val)
_, err = c.String("Name11")
assert.Equal(t, config.KeyNotFoundError, err)
_, err = c.String("NameInvalid")
assert.Equal(t, config.InvalidValueTypeError, err)
val, err = c.String("Person.Name")
assert.Nil(t, err)
assert.Equal(t, "Jerry", val)
}
func TestConfigContainer_Strings(t *testing.T) {
data := `
Name=["Tom", "Jerry"]
NameInvalid="Tom"
`
cfg := &Config{}
c, err := cfg.ParseData([]byte(data))
assert.Nil(t, err)
assert.NotNil(t, c)
val, err := c.Strings("Name")
assert.Nil(t, err)
assert.Equal(t, []string{"Tom", "Jerry"}, val)
_, err = c.Strings("Name11")
assert.Equal(t, config.KeyNotFoundError, err)
_, err = c.Strings("NameInvalid")
assert.Equal(t, config.InvalidValueTypeError, err)
}
func TestConfigContainer_Set(t *testing.T) {
data := `
Name=["Tom", "Jerry"]
NameInvalid="Tom"
`
cfg := &Config{}
c, err := cfg.ParseData([]byte(data))
assert.Nil(t, err)
assert.NotNil(t, c)
err = c.Set("Age", "11")
assert.Nil(t, err)
age, err := c.String("Age")
assert.Nil(t, err)
assert.Equal(t, "11", age)
}
func TestConfigContainer_SubAndMushall(t *testing.T) {
data := `
[servers]
# You can indent as you please. Tabs or spaces. TOML don't care.
[servers.alpha]
ip = "10.0.0.1"
dc = "eqdc10"
[servers.beta]
ip = "10.0.0.2"
dc = "eqdc10"
`
cfg := &Config{}
c, err := cfg.ParseData([]byte(data))
assert.Nil(t, err)
assert.NotNil(t, c)
sub, err := c.Sub("servers")
assert.Nil(t, err)
assert.NotNil(t, sub)
sub, err = sub.Sub("alpha")
assert.Nil(t, err)
assert.NotNil(t, sub)
ip, err := sub.String("ip")
assert.Nil(t, err)
assert.Equal(t, "10.0.0.1", ip)
svr := &Server{}
err = sub.Unmarshaler("", svr)
assert.Nil(t, err)
assert.Equal(t, "10.0.0.1", svr.Ip)
svr = &Server{}
err = c.Unmarshaler("servers.alpha", svr)
assert.Nil(t, err)
assert.Equal(t, "10.0.0.1", svr.Ip)
}
func TestConfigContainer_SaveConfigFile(t *testing.T) {
filename := "test_config.toml"
path := os.TempDir() + string(os.PathSeparator) + filename
data := `
[servers]
# You can indent as you please. Tabs or spaces. TOML don't care.
[servers.alpha]
ip = "10.0.0.1"
dc = "eqdc10"
[servers.beta]
ip = "10.0.0.2"
dc = "eqdc10"
`
cfg := &Config{}
c, err := cfg.ParseData([]byte(data))
fmt.Println(path)
assert.Nil(t, err)
assert.NotNil(t, c)
sub, err := c.Sub("servers")
assert.Nil(t, err)
err = sub.SaveConfigFile(path)
assert.Nil(t, err)
}
type Server struct {
Ip string `toml:"ip"`
}

275
core/config/xml/xml.go Normal file
View File

@ -0,0 +1,275 @@
// Copyright 2014 beego Author. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package xml for config provider.
//
// depend on github.com/beego/x2j.
//
// go install github.com/beego/x2j.
//
// Usage:
// import(
// _ "github.com/astaxie/beego/config/xml"
// "github.com/astaxie/beego/config"
// )
//
// cnf, err := config.NewConfig("xml", "config.xml")
//
// More docs http://beego.me/docs/module/config.md
package xml
import (
"encoding/xml"
"errors"
"fmt"
"io/ioutil"
"os"
"strconv"
"strings"
"sync"
"github.com/mitchellh/mapstructure"
"github.com/astaxie/beego/core/config"
"github.com/astaxie/beego/core/logs"
"github.com/beego/x2j"
)
// Config is a xml config parser and implements Config interface.
// xml configurations should be included in <config></config> tag.
// only support key/value pair as <key>value</key> as each item.
type Config struct{}
// Parse returns a ConfigContainer with parsed xml config map.
func (xc *Config) Parse(filename string) (config.Configer, error) {
context, err := ioutil.ReadFile(filename)
if err != nil {
return nil, err
}
return xc.ParseData(context)
}
// ParseData xml data
func (xc *Config) ParseData(data []byte) (config.Configer, error) {
x := &ConfigContainer{data: make(map[string]interface{})}
d, err := x2j.DocToMap(string(data))
if err != nil {
return nil, err
}
x.data = config.ExpandValueEnvForMap(d["config"].(map[string]interface{}))
return x, nil
}
// ConfigContainer is a Config which represents the xml configuration.
type ConfigContainer struct {
data map[string]interface{}
sync.Mutex
}
// Unmarshaler is a little be inconvenient since the xml library doesn't know type.
// So when you use
// <id>1</id>
// The "1" is a string, not int
func (c *ConfigContainer) Unmarshaler(prefix string, obj interface{}, opt ...config.DecodeOption) error {
sub, err := c.sub(prefix)
if err != nil {
return err
}
return mapstructure.Decode(sub, obj)
}
func (c *ConfigContainer) Sub(key string) (config.Configer, error) {
sub, err := c.sub(key)
if err != nil {
return nil, err
}
return &ConfigContainer{
data: sub,
}, nil
}
func (c *ConfigContainer) sub(key string) (map[string]interface{}, error) {
if key == "" {
return c.data, nil
}
value, ok := c.data[key]
if !ok {
return nil, errors.New(fmt.Sprintf("the key is not found: %s", key))
}
res, ok := value.(map[string]interface{})
if !ok {
return nil, errors.New(fmt.Sprintf("the value of this key is not a structure: %s", key))
}
return res, nil
}
func (c *ConfigContainer) OnChange(key string, fn func(value string)) {
logs.Warn("Unsupported operation")
}
// Bool returns the boolean value for a given key.
func (c *ConfigContainer) Bool(key string) (bool, error) {
if v := c.data[key]; v != nil {
return config.ParseBool(v)
}
return false, fmt.Errorf("not exist key: %q", key)
}
// DefaultBool return the bool value if has no error
// otherwise return the defaultVal
func (c *ConfigContainer) DefaultBool(key string, defaultVal bool) bool {
v, err := c.Bool(key)
if err != nil {
return defaultVal
}
return v
}
// Int returns the integer value for a given key.
func (c *ConfigContainer) Int(key string) (int, error) {
return strconv.Atoi(c.data[key].(string))
}
// DefaultInt returns the integer value for a given key.
// if err != nil return defaultVal
func (c *ConfigContainer) DefaultInt(key string, defaultVal int) int {
v, err := c.Int(key)
if err != nil {
return defaultVal
}
return v
}
// Int64 returns the int64 value for a given key.
func (c *ConfigContainer) Int64(key string) (int64, error) {
return strconv.ParseInt(c.data[key].(string), 10, 64)
}
// DefaultInt64 returns the int64 value for a given key.
// if err != nil return defaultVal
func (c *ConfigContainer) DefaultInt64(key string, defaultVal int64) int64 {
v, err := c.Int64(key)
if err != nil {
return defaultVal
}
return v
}
// Float returns the float value for a given key.
func (c *ConfigContainer) Float(key string) (float64, error) {
return strconv.ParseFloat(c.data[key].(string), 64)
}
// DefaultFloat returns the float64 value for a given key.
// if err != nil return defaultVal
func (c *ConfigContainer) DefaultFloat(key string, defaultVal float64) float64 {
v, err := c.Float(key)
if err != nil {
return defaultVal
}
return v
}
// String returns the string value for a given key.
func (c *ConfigContainer) String(key string) (string, error) {
if v, ok := c.data[key].(string); ok {
return v, nil
}
return "", nil
}
// DefaultString returns the string value for a given key.
// if err != nil return defaultVal
func (c *ConfigContainer) DefaultString(key string, defaultVal string) string {
v, err := c.String(key)
if v == "" || err != nil {
return defaultVal
}
return v
}
// Strings returns the []string value for a given key.
func (c *ConfigContainer) Strings(key string) ([]string, error) {
v, err := c.String(key)
if v == "" || err != nil {
return nil, err
}
return strings.Split(v, ";"), nil
}
// DefaultStrings returns the []string value for a given key.
// if err != nil return defaultVal
func (c *ConfigContainer) DefaultStrings(key string, defaultVal []string) []string {
v, err := c.Strings(key)
if v == nil || err != nil {
return defaultVal
}
return v
}
// GetSection returns map for the given section
func (c *ConfigContainer) GetSection(section string) (map[string]string, error) {
if v, ok := c.data[section].(map[string]interface{}); ok {
mapstr := make(map[string]string)
for k, val := range v {
mapstr[k] = config.ToString(val)
}
return mapstr, nil
}
return nil, fmt.Errorf("section '%s' not found", section)
}
// SaveConfigFile save the config into file
func (c *ConfigContainer) SaveConfigFile(filename string) (err error) {
// Write configuration file by filename.
f, err := os.Create(filename)
if err != nil {
return err
}
defer f.Close()
b, err := xml.MarshalIndent(c.data, " ", " ")
if err != nil {
return err
}
_, err = f.Write(b)
return err
}
// Set writes a new value for key.
func (c *ConfigContainer) Set(key, val string) error {
c.Lock()
defer c.Unlock()
c.data[key] = val
return nil
}
// DIY returns the raw value by a given key.
func (c *ConfigContainer) DIY(key string) (v interface{}, err error) {
if v, ok := c.data[key]; ok {
return v, nil
}
return nil, errors.New("not exist key")
}
func init() {
config.Register("xml", &Config{})
}

157
core/config/xml/xml_test.go Normal file
View File

@ -0,0 +1,157 @@
// Copyright 2014 beego Author. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package xml
import (
"fmt"
"os"
"testing"
"github.com/stretchr/testify/assert"
"github.com/astaxie/beego/core/config"
)
func TestXML(t *testing.T) {
var (
// xml parse should incluce in <config></config> tags
xmlcontext = `<?xml version="1.0" encoding="UTF-8"?>
<config>
<appname>beeapi</appname>
<httpport>8080</httpport>
<mysqlport>3600</mysqlport>
<PI>3.1415976</PI>
<runmode>dev</runmode>
<autorender>false</autorender>
<copyrequestbody>true</copyrequestbody>
<path1>${GOPATH}</path1>
<path2>${GOPATH||/home/go}</path2>
<mysection>
<id>1</id>
<name>MySection</name>
</mysection>
</config>
`
keyValue = map[string]interface{}{
"appname": "beeapi",
"httpport": 8080,
"mysqlport": int64(3600),
"PI": 3.1415976,
"runmode": "dev",
"autorender": false,
"copyrequestbody": true,
"path1": os.Getenv("GOPATH"),
"path2": os.Getenv("GOPATH"),
"error": "",
"emptystrings": []string{},
}
)
f, err := os.Create("testxml.conf")
if err != nil {
t.Fatal(err)
}
_, err = f.WriteString(xmlcontext)
if err != nil {
f.Close()
t.Fatal(err)
}
f.Close()
defer os.Remove("testxml.conf")
xmlconf, err := config.NewConfig("xml", "testxml.conf")
if err != nil {
t.Fatal(err)
}
var xmlsection map[string]string
xmlsection, err = xmlconf.GetSection("mysection")
if err != nil {
t.Fatal(err)
}
if len(xmlsection) == 0 {
t.Error("section should not be empty")
}
for k, v := range keyValue {
var (
value interface{}
err error
)
switch v.(type) {
case int:
value, err = xmlconf.Int(k)
case int64:
value, err = xmlconf.Int64(k)
case float64:
value, err = xmlconf.Float(k)
case bool:
value, err = xmlconf.Bool(k)
case []string:
value, err = xmlconf.Strings(k)
case string:
value, err = xmlconf.String(k)
default:
value, err = xmlconf.DIY(k)
}
if err != nil {
t.Errorf("get key %q value fatal,%v err %s", k, v, err)
} else if fmt.Sprintf("%v", v) != fmt.Sprintf("%v", value) {
t.Errorf("get key %q value, want %v got %v .", k, v, value)
}
}
if err = xmlconf.Set("name", "astaxie"); err != nil {
t.Fatal(err)
}
res, _ := xmlconf.String("name")
if res != "astaxie" {
t.Fatal("get name error")
}
sub, err := xmlconf.Sub("mysection")
assert.Nil(t, err)
assert.NotNil(t, sub)
name, err := sub.String("name")
assert.Nil(t, err)
assert.Equal(t, "MySection", name)
id, err := sub.Int("id")
assert.Nil(t, err)
assert.Equal(t, 1, id)
sec := &Section{}
err = sub.Unmarshaler("", sec)
assert.Nil(t, err)
assert.Equal(t, "MySection", sec.Name)
sec = &Section{}
err = xmlconf.Unmarshaler("mysection", sec)
assert.Nil(t, err)
assert.Equal(t, "MySection", sec.Name)
}
type Section struct {
Name string `xml:"name"`
}

374
core/config/yaml/yaml.go Normal file
View File

@ -0,0 +1,374 @@
// Copyright 2014 beego Author. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package yaml for config provider
//
// depend on github.com/beego/goyaml2
//
// go install github.com/beego/goyaml2
//
// Usage:
// import(
// _ "github.com/astaxie/beego/config/yaml"
// "github.com/astaxie/beego/config"
// )
//
// cnf, err := config.NewConfig("yaml", "config.yaml")
//
// More docs http://beego.me/docs/module/config.md
package yaml
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"log"
"os"
"strings"
"sync"
"github.com/beego/goyaml2"
"gopkg.in/yaml.v2"
"github.com/astaxie/beego/core/config"
"github.com/astaxie/beego/core/logs"
)
// Config is a yaml config parser and implements Config interface.
type Config struct{}
// Parse returns a ConfigContainer with parsed yaml config map.
func (yaml *Config) Parse(filename string) (y config.Configer, err error) {
cnf, err := ReadYmlReader(filename)
if err != nil {
return
}
y = &ConfigContainer{
data: cnf,
}
return
}
// ParseData parse yaml data
func (yaml *Config) ParseData(data []byte) (config.Configer, error) {
cnf, err := parseYML(data)
if err != nil {
return nil, err
}
return &ConfigContainer{
data: cnf,
}, nil
}
// ReadYmlReader Read yaml file to map.
// if json like, use json package, unless goyaml2 package.
func ReadYmlReader(path string) (cnf map[string]interface{}, err error) {
buf, err := ioutil.ReadFile(path)
if err != nil {
return
}
return parseYML(buf)
}
// parseYML parse yaml formatted []byte to map.
func parseYML(buf []byte) (cnf map[string]interface{}, err error) {
if len(buf) < 3 {
return
}
if string(buf[0:1]) == "{" {
log.Println("Look like a Json, try json umarshal")
err = json.Unmarshal(buf, &cnf)
if err == nil {
log.Println("It is Json Map")
return
}
}
data, err := goyaml2.Read(bytes.NewReader(buf))
if err != nil {
log.Println("Goyaml2 ERR>", string(buf), err)
return
}
if data == nil {
log.Println("Goyaml2 output nil? Pls report bug\n" + string(buf))
return
}
cnf, ok := data.(map[string]interface{})
if !ok {
log.Println("Not a Map? >> ", string(buf), data)
cnf = nil
}
cnf = config.ExpandValueEnvForMap(cnf)
return
}
// ConfigContainer is a config which represents the yaml configuration.
type ConfigContainer struct {
data map[string]interface{}
sync.RWMutex
}
// Unmarshaler is similar to Sub
func (c *ConfigContainer) Unmarshaler(prefix string, obj interface{}, opt ...config.DecodeOption) error {
sub, err := c.sub(prefix)
if err != nil {
return err
}
bytes, err := yaml.Marshal(sub)
if err != nil {
return err
}
return yaml.Unmarshal(bytes, obj)
}
func (c *ConfigContainer) Sub(key string) (config.Configer, error) {
sub, err := c.sub(key)
if err != nil {
return nil, err
}
return &ConfigContainer{
data: sub,
}, nil
}
func (c *ConfigContainer) sub(key string) (map[string]interface{}, error) {
tmpData := c.data
keys := strings.Split(key, ".")
for idx, k := range keys {
if v, ok := tmpData[k]; ok {
switch v.(type) {
case map[string]interface{}:
{
tmpData = v.(map[string]interface{})
if idx == len(keys)-1 {
return tmpData, nil
}
}
default:
return nil, errors.New(fmt.Sprintf("the key is invalid: %s", key))
}
}
}
return tmpData, nil
}
func (c *ConfigContainer) OnChange(key string, fn func(value string)) {
// do nothing
logs.Warn("Unsupported operation: OnChange")
}
// Bool returns the boolean value for a given key.
func (c *ConfigContainer) Bool(key string) (bool, error) {
v, err := c.getData(key)
if err != nil {
return false, err
}
return config.ParseBool(v)
}
// DefaultBool return the bool value if has no error
// otherwise return the defaultVal
func (c *ConfigContainer) DefaultBool(key string, defaultVal bool) bool {
v, err := c.Bool(key)
if err != nil {
return defaultVal
}
return v
}
// Int returns the integer value for a given key.
func (c *ConfigContainer) Int(key string) (int, error) {
if v, err := c.getData(key); err != nil {
return 0, err
} else if vv, ok := v.(int); ok {
return vv, nil
} else if vv, ok := v.(int64); ok {
return int(vv), nil
}
return 0, errors.New("not int value")
}
// DefaultInt returns the integer value for a given key.
// if err != nil return defaultVal
func (c *ConfigContainer) DefaultInt(key string, defaultVal int) int {
v, err := c.Int(key)
if err != nil {
return defaultVal
}
return v
}
// Int64 returns the int64 value for a given key.
func (c *ConfigContainer) Int64(key string) (int64, error) {
if v, err := c.getData(key); err != nil {
return 0, err
} else if vv, ok := v.(int64); ok {
return vv, nil
}
return 0, errors.New("not bool value")
}
// DefaultInt64 returns the int64 value for a given key.
// if err != nil return defaultVal
func (c *ConfigContainer) DefaultInt64(key string, defaultVal int64) int64 {
v, err := c.Int64(key)
if err != nil {
return defaultVal
}
return v
}
// Float returns the float value for a given key.
func (c *ConfigContainer) Float(key string) (float64, error) {
if v, err := c.getData(key); err != nil {
return 0.0, err
} else if vv, ok := v.(float64); ok {
return vv, nil
} else if vv, ok := v.(int); ok {
return float64(vv), nil
} else if vv, ok := v.(int64); ok {
return float64(vv), nil
}
return 0.0, errors.New("not float64 value")
}
// DefaultFloat returns the float64 value for a given key.
// if err != nil return defaultVal
func (c *ConfigContainer) DefaultFloat(key string, defaultVal float64) float64 {
v, err := c.Float(key)
if err != nil {
return defaultVal
}
return v
}
// String returns the string value for a given key.
func (c *ConfigContainer) String(key string) (string, error) {
if v, err := c.getData(key); err == nil {
if vv, ok := v.(string); ok {
return vv, nil
}
}
return "", nil
}
// DefaultString returns the string value for a given key.
// if err != nil return defaultVal
func (c *ConfigContainer) DefaultString(key string, defaultVal string) string {
v, err := c.String(key)
if v == "" || err != nil {
return defaultVal
}
return v
}
// Strings returns the []string value for a given key.
func (c *ConfigContainer) Strings(key string) ([]string, error) {
v, err := c.String(key)
if v == "" || err != nil {
return nil, err
}
return strings.Split(v, ";"), nil
}
// DefaultStrings returns the []string value for a given key.
// if err != nil return defaultVal
func (c *ConfigContainer) DefaultStrings(key string, defaultVal []string) []string {
v, err := c.Strings(key)
if v == nil || err != nil {
return defaultVal
}
return v
}
// GetSection returns map for the given section
func (c *ConfigContainer) GetSection(section string) (map[string]string, error) {
if v, ok := c.data[section]; ok {
return v.(map[string]string), nil
}
return nil, errors.New("not exist section")
}
// SaveConfigFile save the config into file
func (c *ConfigContainer) SaveConfigFile(filename string) (err error) {
// Write configuration file by filename.
f, err := os.Create(filename)
if err != nil {
return err
}
defer f.Close()
err = goyaml2.Write(f, c.data)
return err
}
// Set writes a new value for key.
func (c *ConfigContainer) Set(key, val string) error {
c.Lock()
defer c.Unlock()
c.data[key] = val
return nil
}
// DIY returns the raw value by a given key.
func (c *ConfigContainer) DIY(key string) (v interface{}, err error) {
return c.getData(key)
}
func (c *ConfigContainer) getData(key string) (interface{}, error) {
if len(key) == 0 {
return nil, errors.New("key is empty")
}
c.RLock()
defer c.RUnlock()
keys := strings.Split(c.key(key), ".")
tmpData := c.data
for idx, k := range keys {
if v, ok := tmpData[k]; ok {
switch v.(type) {
case map[string]interface{}:
{
tmpData = v.(map[string]interface{})
if idx == len(keys)-1 {
return tmpData, nil
}
}
default:
{
return v, nil
}
}
}
}
return nil, fmt.Errorf("not exist key %q", key)
}
func (c *ConfigContainer) key(key string) string {
return key
}
func init() {
config.Register("yaml", &Config{})
}

View File

@ -0,0 +1,151 @@
// Copyright 2014 beego Author. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package yaml
import (
"fmt"
"os"
"testing"
"github.com/stretchr/testify/assert"
"github.com/astaxie/beego/core/config"
)
func TestYaml(t *testing.T) {
var (
yamlcontext = `
"appname": beeapi
"httpport": 8080
"mysqlport": 3600
"PI": 3.1415976
"runmode": dev
"autorender": false
"copyrequestbody": true
"PATH": GOPATH
"path1": ${GOPATH}
"path2": ${GOPATH||/home/go}
"empty": ""
"user":
"name": "tom"
"age": 13
`
keyValue = map[string]interface{}{
"appname": "beeapi",
"httpport": 8080,
"mysqlport": int64(3600),
"PI": 3.1415976,
"runmode": "dev",
"autorender": false,
"copyrequestbody": true,
"PATH": "GOPATH",
"path1": os.Getenv("GOPATH"),
"path2": os.Getenv("GOPATH"),
"error": "",
"emptystrings": []string{},
}
)
f, err := os.Create("testyaml.conf")
if err != nil {
t.Fatal(err)
}
_, err = f.WriteString(yamlcontext)
if err != nil {
f.Close()
t.Fatal(err)
}
f.Close()
defer os.Remove("testyaml.conf")
yamlconf, err := config.NewConfig("yaml", "testyaml.conf")
if err != nil {
t.Fatal(err)
}
res, _ := yamlconf.String("appname")
if res != "beeapi" {
t.Fatal("appname not equal to beeapi")
}
for k, v := range keyValue {
var (
value interface{}
err error
)
switch v.(type) {
case int:
value, err = yamlconf.Int(k)
case int64:
value, err = yamlconf.Int64(k)
case float64:
value, err = yamlconf.Float(k)
case bool:
value, err = yamlconf.Bool(k)
case []string:
value, err = yamlconf.Strings(k)
case string:
value, err = yamlconf.String(k)
default:
value, err = yamlconf.DIY(k)
}
if err != nil {
t.Errorf("get key %q value fatal,%v err %s", k, v, err)
} else if fmt.Sprintf("%v", v) != fmt.Sprintf("%v", value) {
t.Errorf("get key %q value, want %v got %v .", k, v, value)
}
}
if err = yamlconf.Set("name", "astaxie"); err != nil {
t.Fatal(err)
}
res, _ = yamlconf.String("name")
if res != "astaxie" {
t.Fatal("get name error")
}
sub, err := yamlconf.Sub("user")
assert.Nil(t, err)
assert.NotNil(t, sub)
name, err := sub.String("name")
assert.Nil(t, err)
assert.Equal(t, "tom", name)
age, err := sub.Int("age")
assert.Nil(t, err)
assert.Equal(t, 13, age)
user := &User{}
err = sub.Unmarshaler("", user)
assert.Nil(t, err)
assert.Equal(t, "tom", user.Name)
assert.Equal(t, 13, user.Age)
user = &User{}
err = yamlconf.Unmarshaler("user", user)
assert.Nil(t, err)
assert.Equal(t, "tom", user.Name)
assert.Equal(t, 13, user.Age)
}
type User struct {
Name string `yaml:"name"`
Age int `yaml:"age"`
}