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

remove pkg directory;

remove build directory;
remove githook directory;
This commit is contained in:
Ming Deng
2020-10-08 17:17:15 +08:00
parent 034cb3222e
commit 14c1b76569
431 changed files with 372 additions and 545 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(context.Background(), "key1", false))
assert.True(t, bc.DefaultBool(context.Background(), "key2", true))
}
func TestBaseConfiger_DefaultFloat(t *testing.T) {
bc := newBaseConfier("12.3")
assert.Equal(t, 12.3, bc.DefaultFloat(context.Background(), "key1", 0.1))
assert.Equal(t, 0.1, bc.DefaultFloat(context.Background(), "key2", 0.1))
}
func TestBaseConfiger_DefaultInt(t *testing.T) {
bc := newBaseConfier("10")
assert.Equal(t, 10, bc.DefaultInt(context.Background(), "key1", 8))
assert.Equal(t, 8, bc.DefaultInt(context.Background(), "key2", 8))
}
func TestBaseConfiger_DefaultInt64(t *testing.T) {
bc := newBaseConfier("64")
assert.Equal(t, int64(64), bc.DefaultInt64(context.Background(), "key1", int64(8)))
assert.Equal(t, int64(8), bc.DefaultInt64(context.Background(), "key2", int64(8)))
}
func TestBaseConfiger_DefaultString(t *testing.T) {
bc := newBaseConfier("Hello")
assert.Equal(t, "Hello", bc.DefaultString(context.Background(), "key1", "world"))
assert.Equal(t, "world", bc.DefaultString(context.Background(), "key2", "world"))
}
func TestBaseConfiger_DefaultStrings(t *testing.T) {
bc := newBaseConfier("Hello;world")
assert.Equal(t, []string{"Hello", "world"}, bc.DefaultStrings(context.Background(), "key1", []string{"world"}))
assert.Equal(t, []string{"world"}, bc.DefaultStrings(context.Background(), "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")
}
},
}
}

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

@ -0,0 +1,379 @@
// 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(ctx context.Context, 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(ctx context.Context, key string) (string, error)
// get string slice
Strings(ctx context.Context, key string) ([]string, error)
Int(ctx context.Context, key string) (int, error)
Int64(ctx context.Context, key string) (int64, error)
Bool(ctx context.Context, key string) (bool, error)
Float(ctx context.Context, 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(ctx context.Context, key string, defaultVal string) string
// get string slice
DefaultStrings(ctx context.Context, key string, defaultVal []string) []string
DefaultInt(ctx context.Context, key string, defaultVal int) int
DefaultInt64(ctx context.Context, key string, defaultVal int64) int64
DefaultBool(ctx context.Context, key string, defaultVal bool) bool
DefaultFloat(ctx context.Context, key string, defaultVal float64) float64
DIY(ctx context.Context, key string) (interface{}, error)
GetSection(ctx context.Context, section string) (map[string]string, error)
Unmarshaler(ctx context.Context, prefix string, obj interface{}, opt ...DecodeOption) error
Sub(ctx context.Context, key string) (Configer, error)
OnChange(ctx context.Context, key string, fn func(value string))
SaveConfigFile(ctx context.Context, 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(ctx context.Context, 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(ctx context.Context, 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(ctx context.Context, key string) (bool, error) {
res, err := c.reader(context.TODO(), key)
if err != nil {
return false, err
}
return ParseBool(res)
}
func (c *BaseConfiger) Float(ctx context.Context, 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(ctx context.Context, key string, defaultVal string) string {
if res, err := c.String(ctx, 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(ctx context.Context, key string, defaultVal []string) []string {
if res, err := c.Strings(ctx, key); len(res) > 0 && err == nil {
return res
}
return defaultVal
}
func (c *BaseConfiger) DefaultInt(ctx context.Context, key string, defaultVal int) int {
if res, err := c.Int(ctx, key); err == nil {
return res
}
return defaultVal
}
func (c *BaseConfiger) DefaultInt64(ctx context.Context, key string, defaultVal int64) int64 {
if res, err := c.Int64(ctx, key); err == nil {
return res
}
return defaultVal
}
func (c *BaseConfiger) DefaultBool(ctx context.Context, key string, defaultVal bool) bool {
if res, err := c.Bool(ctx, key); err == nil {
return res
}
return defaultVal
}
func (c *BaseConfiger) DefaultFloat(ctx context.Context, key string, defaultVal float64) float64 {
if res, err := c.Float(ctx, key); err == nil {
return res
}
return defaultVal
}
func (c *BaseConfiger) String(ctx context.Context, 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(ctx context.Context, key string) ([]string, error) {
res, err := c.String(nil, key)
if err != nil || res == "" {
return nil, err
}
return strings.Split(res, ";"), nil
}
// TODO remove this before release v2.0.0
func (c *BaseConfiger) Unmarshaler(ctx context.Context, prefix string, obj interface{}, opt ...DecodeOption) error {
return errors.New("unsupported operation")
}
// TODO remove this before release v2.0.0
func (c *BaseConfiger) Sub(ctx context.Context, key string) (Configer, error) {
return nil, errors.New("unsupported operation")
}
// TODO remove this before release v2.0.0
func (c *BaseConfiger) OnChange(ctx context.Context, 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.")
}
}

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

@ -0,0 +1,214 @@
// 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"
)
const etcdOpts = "etcdOpts"
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, ctx, 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(ctx context.Context, 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(ctx context.Context, key string) (interface{}, error) {
return get(e.client, context.TODO(), key)
}
// GetSection in this implementation, we use section as prefix
func (e *EtcdConfiger) GetSection(ctx context.Context, section string) (map[string]string, error) {
var (
resp *clientv3.GetResponse
err error
)
if opts, ok := ctx.Value(etcdOpts).([]clientv3.OpOption); ok {
opts = append(opts, clientv3.WithPrefix())
resp, err = e.client.Get(context.TODO(), e.prefix+section, opts...)
} else {
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(ctx context.Context, 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(ctx context.Context, prefix string, obj interface{}, opt ...config.DecodeOption) error {
res, err := e.GetSection(ctx, 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(ctx context.Context, 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(ctx context.Context, key string, fn func(value string)) {
buildOptsFunc := func() []clientv3.OpOption {
if opts, ok := ctx.Value(etcdOpts).([]clientv3.OpOption); ok {
opts = append(opts, clientv3.WithCreatedNotify())
return opts
}
return []clientv3.OpOption{}
}
rch := e.client.Watch(ctx, 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(ctx, 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, ctx context.Context, key string) (*clientv3.GetResponse, error) {
var (
resp *clientv3.GetResponse
err error
)
if opts, ok := ctx.Value(etcdOpts).([]clientv3.OpOption); ok {
resp, err = client.Get(ctx, key, opts...)
} else {
resp, err = client.Get(ctx, key)
}
if err != nil {
return nil, errors.WithMessage(err, fmt.Sprintf("read config from etcd with key %s failed", key))
}
return resp, err
}
func WithEtcdOption(ctx context.Context, opts ...clientv3.OpOption) context.Context {
return context.WithValue(ctx, etcdOpts, opts)
}
func init() {
config.Register("json", &EtcdConfigerProvider{})
}

View File

@ -0,0 +1,123 @@
// 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"
"os"
"testing"
"time"
"github.com/coreos/etcd/clientv3"
"github.com/stretchr/testify/assert"
)
func TestWithEtcdOption(t *testing.T) {
ctx := WithEtcdOption(context.Background(), clientv3.WithPrefix())
assert.NotNil(t, ctx.Value(etcdOpts))
}
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(nil, "sub.")
assert.Nil(t, err)
assert.NotNil(t, subCfger)
subSubCfger, err := subCfger.Sub(nil, "sub.")
assert.NotNil(t, subSubCfger)
assert.Nil(t, err)
str, err := subSubCfger.String(nil, "key1")
assert.Nil(t, err)
assert.Equal(t, "sub.sub.key", str)
// we cannot test it
subSubCfger.OnChange(context.Background(), "watch", func(value string) {
// do nothing
})
defStr := cfger.DefaultString(nil, "not_exit", "default value")
assert.Equal(t, "default value", defStr)
defInt64 := cfger.DefaultInt64(nil, "not_exit", -1)
assert.Equal(t, int64(-1), defInt64)
defInt := cfger.DefaultInt(nil, "not_exit", -2)
assert.Equal(t, -2, defInt)
defFlt := cfger.DefaultFloat(nil, "not_exit", 12.3)
assert.Equal(t, 12.3, defFlt)
defBl := cfger.DefaultBool(nil, "not_exit", true)
assert.True(t, defBl)
defStrs := cfger.DefaultStrings(nil, "not_exit", []string{"hello"})
assert.Equal(t, []string{"hello"}, defStrs)
fl, err := cfger.Float(nil, "current.float")
assert.Nil(t, err)
assert.Equal(t, 1.23, fl)
bl, err := cfger.Bool(nil, "current.bool")
assert.Nil(t, err)
assert.True(t, bl)
it, err := cfger.Int(nil, "current.int")
assert.Nil(t, err)
assert.Equal(t, 11, it)
str, err = cfger.String(nil, "current.string")
assert.Nil(t, err)
assert.Equal(t, "hello", str)
tn := &TestEntity{}
err = cfger.Unmarshaler(context.Background(), "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)
}

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

@ -0,0 +1,112 @@
// 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(ctx context.Context, key, val string) error {
c.data[strings.ToLower(key)] = val
return nil
}
func (c *fakeConfigContainer) Int(ctx context.Context, key string) (int, error) {
return strconv.Atoi(c.getData(key))
}
func (c *fakeConfigContainer) DefaultInt(ctx context.Context, key string, defaultVal int) int {
v, err := c.Int(ctx, key)
if err != nil {
return defaultVal
}
return v
}
func (c *fakeConfigContainer) Int64(ctx context.Context, key string) (int64, error) {
return strconv.ParseInt(c.getData(key), 10, 64)
}
func (c *fakeConfigContainer) DefaultInt64(ctx context.Context, key string, defaultVal int64) int64 {
v, err := c.Int64(ctx, key)
if err != nil {
return defaultVal
}
return v
}
func (c *fakeConfigContainer) Bool(ctx context.Context, key string) (bool, error) {
return ParseBool(c.getData(key))
}
func (c *fakeConfigContainer) DefaultBool(ctx context.Context, key string, defaultVal bool) bool {
v, err := c.Bool(ctx, key)
if err != nil {
return defaultVal
}
return v
}
func (c *fakeConfigContainer) Float(ctx context.Context, key string) (float64, error) {
return strconv.ParseFloat(c.getData(key), 64)
}
func (c *fakeConfigContainer) DefaultFloat(ctx context.Context, key string, defaultVal float64) float64 {
v, err := c.Float(ctx, key)
if err != nil {
return defaultVal
}
return v
}
func (c *fakeConfigContainer) DIY(ctx context.Context, 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(ctx context.Context, section string) (map[string]string, error) {
return nil, errors.New("not implement in the fakeConfigContainer")
}
func (c *fakeConfigContainer) SaveConfigFile(ctx context.Context, filename string) error {
return errors.New("not implement in the fakeConfigContainer")
}
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
}

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

@ -0,0 +1,510 @@
// 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"
)
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(ctx context.Context, 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(ctx context.Context, key string, defaultVal bool) bool {
v, err := c.Bool(ctx, key)
if err != nil {
return defaultVal
}
return v
}
// Int returns the integer value for a given key.
func (c *IniConfigContainer) Int(ctx context.Context, 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(ctx context.Context, key string, defaultVal int) int {
v, err := c.Int(ctx, key)
if err != nil {
return defaultVal
}
return v
}
// Int64 returns the int64 value for a given key.
func (c *IniConfigContainer) Int64(ctx context.Context, 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(ctx context.Context, key string, defaultVal int64) int64 {
v, err := c.Int64(ctx, key)
if err != nil {
return defaultVal
}
return v
}
// Float returns the float value for a given key.
func (c *IniConfigContainer) Float(ctx context.Context, 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(ctx context.Context, key string, defaultVal float64) float64 {
v, err := c.Float(ctx, key)
if err != nil {
return defaultVal
}
return v
}
// String returns the string value for a given key.
func (c *IniConfigContainer) String(ctx context.Context, 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(ctx context.Context, key string, defaultVal string) string {
v, err := c.String(nil, 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(ctx context.Context, key string) ([]string, error) {
v, err := c.String(nil, 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(ctx context.Context, key string, defaultVal []string) []string {
v, err := c.Strings(ctx, key)
if v == nil || err != nil {
return defaultVal
}
return v
}
// GetSection returns map for the given section
func (c *IniConfigContainer) GetSection(ctx context.Context, 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(ctx context.Context, 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(ctx context.Context, 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(ctx context.Context, 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 init() {
Register("ini", &IniConfig{})
}

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(nil, k)
case int64:
value, err = iniconf.Int64(nil, k)
case float64:
value, err = iniconf.Float(nil, k)
case bool:
value, err = iniconf.Bool(nil, k)
case []string:
value, err = iniconf.Strings(nil, k)
case string:
value, err = iniconf.String(nil, k)
default:
value, err = iniconf.DIY(nil, 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(nil, "name", "astaxie"); err != nil {
t.Fatal(err)
}
res, _ := iniconf.String(nil, "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(nil, 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)
}
}
}
}

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

@ -0,0 +1,313 @@
// 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 (
"context"
"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(ctx context.Context, prefix string, obj interface{}, opt ...config.DecodeOption) error {
sub, err := c.sub(ctx, prefix)
if err != nil {
return err
}
return mapstructure.Decode(sub, obj)
}
func (c *JSONConfigContainer) Sub(ctx context.Context, key string) (config.Configer, error) {
sub, err := c.sub(ctx, key)
if err != nil {
return nil, err
}
return &JSONConfigContainer{
data: sub,
}, nil
}
func (c *JSONConfigContainer) sub(ctx context.Context, 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(ctx context.Context, key string, fn func(value string)) {
logs.Warn("unsupported operation")
}
// Bool returns the boolean value for a given key.
func (c *JSONConfigContainer) Bool(ctx context.Context, 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(ctx context.Context, key string, defaultVal bool) bool {
if v, err := c.Bool(ctx, key); err == nil {
return v
}
return defaultVal
}
// Int returns the integer value for a given key.
func (c *JSONConfigContainer) Int(ctx context.Context, 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(ctx context.Context, key string, defaultVal int) int {
if v, err := c.Int(ctx, key); err == nil {
return v
}
return defaultVal
}
// Int64 returns the int64 value for a given key.
func (c *JSONConfigContainer) Int64(ctx context.Context, 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(ctx context.Context, key string, defaultVal int64) int64 {
if v, err := c.Int64(ctx, key); err == nil {
return v
}
return defaultVal
}
// Float returns the float value for a given key.
func (c *JSONConfigContainer) Float(ctx context.Context, 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(ctx context.Context, key string, defaultVal float64) float64 {
if v, err := c.Float(ctx, key); err == nil {
return v
}
return defaultVal
}
// String returns the string value for a given key.
func (c *JSONConfigContainer) String(ctx context.Context, 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(ctx context.Context, key string, defaultVal string) string {
// TODO FIXME should not use "" to replace non existence
if v, err := c.String(ctx, key); v != "" && err == nil {
return v
}
return defaultVal
}
// Strings returns the []string value for a given key.
func (c *JSONConfigContainer) Strings(ctx context.Context, key string) ([]string, error) {
stringVal, err := c.String(nil, 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(ctx context.Context, key string, defaultVal []string) []string {
if v, err := c.Strings(ctx, key); v != nil && err == nil {
return v
}
return defaultVal
}
// GetSection returns map for the given section
func (c *JSONConfigContainer) GetSection(ctx context.Context, 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(ctx context.Context, 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(ctx context.Context, 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(ctx context.Context, 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,252 @@
// 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 (
"context"
"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(nil, "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(nil, k)
case int64:
value, err = jsonconf.Int64(nil, k)
case float64:
value, err = jsonconf.Float(nil, k)
case bool:
value, err = jsonconf.Bool(nil, k)
case []string:
value, err = jsonconf.Strings(nil, k)
case string:
value, err = jsonconf.String(nil, k)
default:
value, err = jsonconf.DIY(nil, 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(nil, "name", "astaxie"); err != nil {
t.Fatal(err)
}
res, _ := jsonconf.String(nil, "name")
if res != "astaxie" {
t.Fatal("get name error")
}
if db, err := jsonconf.DIY(nil, "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(nil, "unknown"); err == nil {
t.Error("unknown keys should return an error when expecting an Int")
}
if _, err := jsonconf.Int64(nil, "unknown"); err == nil {
t.Error("unknown keys should return an error when expecting an Int64")
}
if _, err := jsonconf.Float(nil, "unknown"); err == nil {
t.Error("unknown keys should return an error when expecting a Float")
}
if _, err := jsonconf.DIY(nil, "unknown"); err == nil {
t.Error("unknown keys should return an error when expecting an interface{}")
}
if val, _ := jsonconf.String(nil, "unknown"); val != "" {
t.Error("unknown keys should return an empty string when expecting a String")
}
if _, err := jsonconf.Bool(nil, "unknown"); err == nil {
t.Error("unknown keys should return an error when expecting a Bool")
}
if !jsonconf.DefaultBool(nil, "unknown", true) {
t.Error("unknown keys with default value wrong")
}
sub, err := jsonconf.Sub(context.Background(), "database")
assert.Nil(t, err)
assert.NotNil(t, sub)
sub, err = sub.Sub(context.Background(), "conns")
assert.Nil(t, err)
maxCon, _ := sub.Int(context.Background(), "maxconnection")
assert.Equal(t, 12, maxCon)
dbCfg := &DatabaseConfig{}
err = sub.Unmarshaler(context.Background(), "", 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"`
}

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

@ -0,0 +1,276 @@
// 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 (
"context"
"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(ctx context.Context, prefix string, obj interface{}, opt ...config.DecodeOption) error {
sub, err := c.sub(ctx, prefix)
if err != nil {
return err
}
return mapstructure.Decode(sub, obj)
}
func (c *ConfigContainer) Sub(ctx context.Context, key string) (config.Configer, error) {
sub, err := c.sub(ctx, key)
if err != nil {
return nil, err
}
return &ConfigContainer{
data: sub,
}, nil
}
func (c *ConfigContainer) sub(ctx context.Context, 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(ctx context.Context, key string, fn func(value string)) {
logs.Warn("Unsupported operation")
}
// Bool returns the boolean value for a given key.
func (c *ConfigContainer) Bool(ctx context.Context, 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(ctx context.Context, key string, defaultVal bool) bool {
v, err := c.Bool(ctx, key)
if err != nil {
return defaultVal
}
return v
}
// Int returns the integer value for a given key.
func (c *ConfigContainer) Int(ctx context.Context, 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(ctx context.Context, key string, defaultVal int) int {
v, err := c.Int(ctx, key)
if err != nil {
return defaultVal
}
return v
}
// Int64 returns the int64 value for a given key.
func (c *ConfigContainer) Int64(ctx context.Context, 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(ctx context.Context, key string, defaultVal int64) int64 {
v, err := c.Int64(ctx, key)
if err != nil {
return defaultVal
}
return v
}
// Float returns the float value for a given key.
func (c *ConfigContainer) Float(ctx context.Context, 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(ctx context.Context, key string, defaultVal float64) float64 {
v, err := c.Float(ctx, key)
if err != nil {
return defaultVal
}
return v
}
// String returns the string value for a given key.
func (c *ConfigContainer) String(ctx context.Context, 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(ctx context.Context, key string, defaultVal string) string {
v, err := c.String(ctx, key)
if v == "" || err != nil {
return defaultVal
}
return v
}
// Strings returns the []string value for a given key.
func (c *ConfigContainer) Strings(ctx context.Context, key string) ([]string, error) {
v, err := c.String(ctx, 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(ctx context.Context, key string, defaultVal []string) []string {
v, err := c.Strings(ctx, key)
if v == nil || err != nil {
return defaultVal
}
return v
}
// GetSection returns map for the given section
func (c *ConfigContainer) GetSection(ctx context.Context, 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(ctx context.Context, 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(ctx context.Context, 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(ctx context.Context, 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{})
}

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

@ -0,0 +1,158 @@
// 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 (
"context"
"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(nil, "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(nil, k)
case int64:
value, err = xmlconf.Int64(nil, k)
case float64:
value, err = xmlconf.Float(nil, k)
case bool:
value, err = xmlconf.Bool(nil, k)
case []string:
value, err = xmlconf.Strings(nil, k)
case string:
value, err = xmlconf.String(nil, k)
default:
value, err = xmlconf.DIY(nil, 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(nil, "name", "astaxie"); err != nil {
t.Fatal(err)
}
res, _ := xmlconf.String(context.Background(), "name")
if res != "astaxie" {
t.Fatal("get name error")
}
sub, err := xmlconf.Sub(context.Background(), "mysection")
assert.Nil(t, err)
assert.NotNil(t, sub)
name, err := sub.String(context.Background(), "name")
assert.Nil(t, err)
assert.Equal(t, "MySection", name)
id, err := sub.Int(context.Background(), "id")
assert.Nil(t, err)
assert.Equal(t, 1, id)
sec := &Section{}
err = sub.Unmarshaler(context.Background(), "", sec)
assert.Nil(t, err)
assert.Equal(t, "MySection", sec.Name)
sec = &Section{}
err = xmlconf.Unmarshaler(context.Background(), "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"
"context"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"log"
"os"
"strings"
"sync"
"github.com/astaxie/beego/core/config"
"github.com/astaxie/beego/core/logs"
"github.com/beego/goyaml2"
"gopkg.in/yaml.v2"
)
// 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(ctx context.Context, prefix string, obj interface{}, opt ...config.DecodeOption) error {
sub, err := c.sub(ctx, 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(ctx context.Context, key string) (config.Configer, error) {
sub, err := c.sub(ctx, key)
if err != nil {
return nil, err
}
return &ConfigContainer{
data: sub,
}, nil
}
func (c *ConfigContainer) sub(ctx context.Context, 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(ctx context.Context, 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(ctx context.Context, 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(ctx context.Context, key string, defaultVal bool) bool {
v, err := c.Bool(ctx, key)
if err != nil {
return defaultVal
}
return v
}
// Int returns the integer value for a given key.
func (c *ConfigContainer) Int(ctx context.Context, 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(ctx context.Context, key string, defaultVal int) int {
v, err := c.Int(ctx, key)
if err != nil {
return defaultVal
}
return v
}
// Int64 returns the int64 value for a given key.
func (c *ConfigContainer) Int64(ctx context.Context, 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(ctx context.Context, key string, defaultVal int64) int64 {
v, err := c.Int64(ctx, key)
if err != nil {
return defaultVal
}
return v
}
// Float returns the float value for a given key.
func (c *ConfigContainer) Float(ctx context.Context, 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(ctx context.Context, key string, defaultVal float64) float64 {
v, err := c.Float(ctx, key)
if err != nil {
return defaultVal
}
return v
}
// String returns the string value for a given key.
func (c *ConfigContainer) String(ctx context.Context, 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(ctx context.Context, key string, defaultVal string) string {
v, err := c.String(nil, key)
if v == "" || err != nil {
return defaultVal
}
return v
}
// Strings returns the []string value for a given key.
func (c *ConfigContainer) Strings(ctx context.Context, key string) ([]string, error) {
v, err := c.String(nil, 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(ctx context.Context, key string, defaultVal []string) []string {
v, err := c.Strings(ctx, key)
if v == nil || err != nil {
return defaultVal
}
return v
}
// GetSection returns map for the given section
func (c *ConfigContainer) GetSection(ctx context.Context, 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(ctx context.Context, 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(ctx context.Context, 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(ctx context.Context, 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,152 @@
// 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 (
"context"
"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(nil, "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(nil, k)
case int64:
value, err = yamlconf.Int64(nil, k)
case float64:
value, err = yamlconf.Float(nil, k)
case bool:
value, err = yamlconf.Bool(nil, k)
case []string:
value, err = yamlconf.Strings(nil, k)
case string:
value, err = yamlconf.String(nil, k)
default:
value, err = yamlconf.DIY(nil, 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(nil, "name", "astaxie"); err != nil {
t.Fatal(err)
}
res, _ = yamlconf.String(nil, "name")
if res != "astaxie" {
t.Fatal("get name error")
}
sub, err := yamlconf.Sub(context.Background(), "user")
assert.Nil(t, err)
assert.NotNil(t, sub)
name, err := sub.String(context.Background(), "name")
assert.Nil(t, err)
assert.Equal(t, "tom", name)
age, err := sub.Int(context.Background(), "age")
assert.Nil(t, err)
assert.Equal(t, 13, age)
user := &User{}
err = sub.Unmarshaler(context.Background(), "", user)
assert.Nil(t, err)
assert.Equal(t, "tom", user.Name)
assert.Equal(t, 13, user.Age)
user = &User{}
err = yamlconf.Unmarshaler(context.Background(), "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"`
}