diff --git a/config/config.go b/config/config.go
index c0afec05..9f41fb79 100644
--- a/config/config.go
+++ b/config/config.go
@@ -12,11 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-// Package config is used to parse config
+// Package config is used to parse config.
// Usage:
-// import(
-// "github.com/astaxie/beego/config"
-// )
+// import "github.com/astaxie/beego/config"
+//Examples.
//
// cnf, err := config.NewConfig("ini", "config.conf")
//
@@ -38,12 +37,12 @@
// 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
+//More docs http://beego.me/docs/module/config.md
package config
import (
"fmt"
+ "os"
)
// Configer defines how to get and set value from configuration raw data.
@@ -107,6 +106,69 @@ func NewConfigData(adapterName string, data []byte) (Configer, error) {
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 := ""
+ defalutV := ""
+ // value start with "${"
+ for i := 2; i < vLen; i++ {
+ if value[i] == '|' && (i+1 < vLen && value[i+1] == '|') {
+ key = value[2:i]
+ defalutV = 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 = defalutV
+ }
+
+ 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,
diff --git a/config/config_test.go b/config/config_test.go
new file mode 100644
index 00000000..15d6ffa6
--- /dev/null
+++ b/config/config_test.go
@@ -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)
+ }
+ }
+
+}
diff --git a/config/fake.go b/config/fake.go
index 7e362608..f5144598 100644
--- a/config/fake.go
+++ b/config/fake.go
@@ -38,7 +38,7 @@ func (c *fakeConfigContainer) String(key string) string {
}
func (c *fakeConfigContainer) DefaultString(key string, defaultval string) string {
- v := c.getData(key)
+ v := c.String(key)
if v == "" {
return defaultval
}
@@ -46,7 +46,7 @@ func (c *fakeConfigContainer) DefaultString(key string, defaultval string) strin
}
func (c *fakeConfigContainer) Strings(key string) []string {
- v := c.getData(key)
+ v := c.String(key)
if v == "" {
return nil
}
diff --git a/config/ini.go b/config/ini.go
index c3934c53..53bd992d 100644
--- a/config/ini.go
+++ b/config/ini.go
@@ -166,7 +166,7 @@ func (ini *IniConfig) parseFile(name string) (*IniConfigContainer, error) {
val = bytes.Trim(val, `"`)
}
- cfg.data[section][key] = string(val)
+ cfg.data[section][key] = ExpandValueEnv(string(val))
if comment.Len() > 0 {
cfg.keyComment[section+"."+key] = comment.String()
comment.Reset()
@@ -300,7 +300,9 @@ func (c *IniConfigContainer) GetSection(section string) (map[string]string, erro
return nil, errors.New("not exist setction")
}
-// SaveConfigFile save the config into file
+// SaveConfigFile save the config into file.
+//
+// BUG(env): The environment variable config item will be saved with real value in SaveConfigFile Funcation.
func (c *IniConfigContainer) SaveConfigFile(filename string) (err error) {
// Write configuration file by filename.
f, err := os.Create(filename)
diff --git a/config/ini_test.go b/config/ini_test.go
index 93fce61f..83ff3668 100644
--- a/config/ini_test.go
+++ b/config/ini_test.go
@@ -42,11 +42,14 @@ 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{}{
@@ -64,10 +67,13 @@ peers = one;two;three
"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": "",
diff --git a/config/json.go b/config/json.go
index fce517eb..a0d93210 100644
--- a/config/json.go
+++ b/config/json.go
@@ -57,6 +57,9 @@ func (js *JSONConfig) ParseData(data []byte) (Configer, error) {
}
x.data["rootArray"] = wrappingArray
}
+
+ x.data = ExpandValueEnvForMap(x.data)
+
return x, nil
}
diff --git a/config/json_test.go b/config/json_test.go
index df663461..24ff9644 100644
--- a/config/json_test.go
+++ b/config/json_test.go
@@ -86,16 +86,19 @@ func TestJson(t *testing.T) {
"enableSession": "Y",
"enableCookie": "N",
"flag": 1,
+"path1": "${GOPATH}",
+"path2": "${GOPATH||/home/go}",
"database": {
"host": "host",
"port": "port",
"database": "database",
"username": "username",
- "password": "password",
+ "password": "${GOPATH}",
"conns":{
"maxconnection":12,
"autoconnect":true,
- "connectioninfo":"info"
+ "connectioninfo":"info",
+ "root": "${GOPATH}"
}
}
}`
@@ -115,13 +118,16 @@ func TestJson(t *testing.T) {
"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": "password",
+ "database::password": os.Getenv("GOPATH"),
"database::conns::maxconnection": 12,
"database::conns::autoconnect": true,
"database::conns::connectioninfo": "info",
+ "database::conns::root": os.Getenv("GOPATH"),
"unknown": "",
}
)
diff --git a/config/xml/xml.go b/config/xml/xml.go
index b5291bf4..0c4e4d27 100644
--- a/config/xml/xml.go
+++ b/config/xml/xml.go
@@ -12,21 +12,21 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-// Package xml for config provider
+// Package xml for config provider.
//
-// depend on github.com/beego/x2j
+// depend on github.com/beego/x2j.
//
-// go install github.com/beego/x2j
+// go install github.com/beego/x2j.
//
// Usage:
-// import(
-// _ "github.com/astaxie/beego/config/xml"
-// "github.com/astaxie/beego/config"
-// )
+// 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
+//More docs http://beego.me/docs/module/config.md
package xml
import (
@@ -69,7 +69,7 @@ func (xc *Config) Parse(filename string) (config.Configer, error) {
return nil, err
}
- x.data = d["config"].(map[string]interface{})
+ x.data = config.ExpandValueEnvForMap(d["config"].(map[string]interface{}))
return x, nil
}
@@ -92,7 +92,7 @@ type ConfigContainer struct {
// Bool returns the boolean value for a given key.
func (c *ConfigContainer) Bool(key string) (bool, error) {
- if v, ok := c.data[key]; ok {
+ if v := c.data[key]; v != nil {
return config.ParseBool(v)
}
return false, fmt.Errorf("not exist key: %q", key)
diff --git a/config/xml/xml_test.go b/config/xml/xml_test.go
index 60dcba54..d8a09a59 100644
--- a/config/xml/xml_test.go
+++ b/config/xml/xml_test.go
@@ -15,14 +15,18 @@
package xml
import (
+ "fmt"
"os"
"testing"
"github.com/astaxie/beego/config"
)
-//xml parse should incluce in tags
-var xmlcontext = `
+func TestXML(t *testing.T) {
+
+ var (
+ //xml parse should incluce in tags
+ xmlcontext = `
beeapi
8080
@@ -31,10 +35,25 @@ var xmlcontext = `
dev
false
true
+${GOPATH}
+${GOPATH||/home/go}
`
+ 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{},
+ }
+ )
-func TestXML(t *testing.T) {
f, err := os.Create("testxml.conf")
if err != nil {
t.Fatal(err)
@@ -50,39 +69,42 @@ func TestXML(t *testing.T) {
if err != nil {
t.Fatal(err)
}
- if xmlconf.String("appname") != "beeapi" {
- t.Fatal("appname not equal to beeapi")
- }
- if port, err := xmlconf.Int("httpport"); err != nil || port != 8080 {
- t.Error(port)
- t.Fatal(err)
- }
- if port, err := xmlconf.Int64("mysqlport"); err != nil || port != 3600 {
- t.Error(port)
- t.Fatal(err)
- }
- if pi, err := xmlconf.Float("PI"); err != nil || pi != 3.1415976 {
- t.Error(pi)
- t.Fatal(err)
- }
- if xmlconf.String("runmode") != "dev" {
- t.Fatal("runmode not equal to dev")
- }
- if v, err := xmlconf.Bool("autorender"); err != nil || v != false {
- t.Error(v)
- t.Fatal(err)
- }
- if v, err := xmlconf.Bool("copyrequestbody"); err != nil || v != true {
- t.Error(v)
- t.Fatal(err)
+
+ 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 = xmlconf.Strings(k)
+ case string:
+ value = 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)
}
if xmlconf.String("name") != "astaxie" {
t.Fatal("get name error")
}
- if xmlconf.Strings("emptystrings") != nil {
- t.Fatal("get emtpy strings error")
- }
}
diff --git a/config/yaml/yaml.go b/config/yaml/yaml.go
index 7e1d0426..64e25cb3 100644
--- a/config/yaml/yaml.go
+++ b/config/yaml/yaml.go
@@ -19,14 +19,14 @@
// go install github.com/beego/goyaml2
//
// Usage:
-// import(
+// import(
// _ "github.com/astaxie/beego/config/yaml"
-// "github.com/astaxie/beego/config"
-// )
+// "github.com/astaxie/beego/config"
+// )
//
// cnf, err := config.NewConfig("yaml", "config.yaml")
//
-// more docs http://beego.me/docs/module/config.md
+//More docs http://beego.me/docs/module/config.md
package yaml
import (
@@ -110,6 +110,7 @@ func ReadYmlReader(path string) (cnf map[string]interface{}, err error) {
log.Println("Not a Map? >> ", string(buf), data)
cnf = nil
}
+ cnf = config.ExpandValueEnvForMap(cnf)
return
}
@@ -121,10 +122,11 @@ type ConfigContainer struct {
// Bool returns the boolean value for a given key.
func (c *ConfigContainer) Bool(key string) (bool, error) {
- if v, ok := c.data[key]; ok {
- return config.ParseBool(v)
+ v, err := c.getData(key)
+ if err != nil {
+ return false, err
}
- return false, fmt.Errorf("not exist key: %q", key)
+ return config.ParseBool(v)
}
// DefaultBool return the bool value if has no error
@@ -139,8 +141,12 @@ func (c *ConfigContainer) DefaultBool(key string, defaultval bool) bool {
// Int returns the integer value for a given key.
func (c *ConfigContainer) Int(key string) (int, error) {
- if v, ok := c.data[key].(int64); ok {
- return int(v), nil
+ 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")
}
@@ -157,8 +163,10 @@ func (c *ConfigContainer) DefaultInt(key string, defaultval int) int {
// Int64 returns the int64 value for a given key.
func (c *ConfigContainer) Int64(key string) (int64, error) {
- if v, ok := c.data[key].(int64); ok {
- return v, nil
+ 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")
}
@@ -175,8 +183,14 @@ func (c *ConfigContainer) DefaultInt64(key string, defaultval int64) int64 {
// Float returns the float value for a given key.
func (c *ConfigContainer) Float(key string) (float64, error) {
- if v, ok := c.data[key].(float64); ok {
- return v, nil
+ 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")
}
@@ -193,8 +207,10 @@ func (c *ConfigContainer) DefaultFloat(key string, defaultval float64) float64 {
// String returns the string value for a given key.
func (c *ConfigContainer) String(key string) string {
- if v, ok := c.data[key].(string); ok {
- return v
+ if v, err := c.getData(key); err == nil {
+ if vv, ok := v.(string); ok {
+ return vv
+ }
}
return ""
}
@@ -230,8 +246,8 @@ func (c *ConfigContainer) DefaultStrings(key string, defaultval []string) []stri
// GetSection returns map for the given section
func (c *ConfigContainer) GetSection(section string) (map[string]string, error) {
- v, ok := c.data[section]
- if ok {
+
+ if v, ok := c.data[section]; ok {
return v.(map[string]string), nil
}
return nil, errors.New("not exist setction")
@@ -259,10 +275,19 @@ func (c *ConfigContainer) Set(key, val string) error {
// 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 emtpy")
+ }
+
if v, ok := c.data[key]; ok {
return v, nil
}
- return nil, errors.New("not exist key")
+ return nil, fmt.Errorf("not exist key %q", key)
}
func init() {
diff --git a/config/yaml/yaml_test.go b/config/yaml/yaml_test.go
index 80cbb8fe..49cc1d1e 100644
--- a/config/yaml/yaml_test.go
+++ b/config/yaml/yaml_test.go
@@ -15,13 +15,17 @@
package yaml
import (
+ "fmt"
"os"
"testing"
"github.com/astaxie/beego/config"
)
-var yamlcontext = `
+func TestYaml(t *testing.T) {
+
+ var (
+ yamlcontext = `
"appname": beeapi
"httpport": 8080
"mysqlport": 3600
@@ -29,9 +33,27 @@ var yamlcontext = `
"runmode": dev
"autorender": false
"copyrequestbody": true
+"PATH": GOPATH
+"path1": ${GOPATH}
+"path2": ${GOPATH||/home/go}
+"empty": ""
`
-func TestYaml(t *testing.T) {
+ 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)
@@ -47,32 +69,42 @@ func TestYaml(t *testing.T) {
if err != nil {
t.Fatal(err)
}
+
if yamlconf.String("appname") != "beeapi" {
t.Fatal("appname not equal to beeapi")
}
- if port, err := yamlconf.Int("httpport"); err != nil || port != 8080 {
- t.Error(port)
- t.Fatal(err)
- }
- if port, err := yamlconf.Int64("mysqlport"); err != nil || port != 3600 {
- t.Error(port)
- t.Fatal(err)
- }
- if pi, err := yamlconf.Float("PI"); err != nil || pi != 3.1415976 {
- t.Error(pi)
- t.Fatal(err)
- }
- if yamlconf.String("runmode") != "dev" {
- t.Fatal("runmode not equal to dev")
- }
- if v, err := yamlconf.Bool("autorender"); err != nil || v != false {
- t.Error(v)
- t.Fatal(err)
- }
- if v, err := yamlconf.Bool("copyrequestbody"); err != nil || v != true {
- t.Error(v)
- t.Fatal(err)
+
+ 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 = yamlconf.Strings(k)
+ case string:
+ value = 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)
}
@@ -80,7 +112,4 @@ func TestYaml(t *testing.T) {
t.Fatal("get name error")
}
- if yamlconf.Strings("emptystrings") != nil {
- t.Fatal("get emtpy strings error")
- }
}