2
0

Pre 1.0 version

This commit is contained in:
eschao 2017-12-11 17:14:53 +08:00
parent 50ac8e4572
commit fca9c22d48
14 changed files with 753 additions and 722 deletions

1
.gitignore vendored
View File

@ -3,6 +3,7 @@
*.a
*.so
*.swp
main
# Folders
_obj

View File

@ -137,32 +137,27 @@ func (this *Command) Init(i interface{}) error {
func (this *Command) parseValue(v reflect.Value) error {
typeOfStruct := v.Type()
var err error
for i := 0; i < v.NumField(); i++ {
for i := 0; i < v.NumField() && err == nil; i++ {
valueOfField := v.Field(i)
kindOfField := valueOfField.Kind()
structOfField := typeOfStruct.Field(i)
if kindOfField == reflect.Ptr {
if !valueOfField.IsNil() && valueOfField.CanSet() {
cmd := this.createCliFlagSet(structOfField.Tag)
if err := cmd.Init(valueOfField.Interface()); err != nil {
return err
}
cmd := this.createSubCommand(structOfField.Tag)
err = cmd.Init(valueOfField.Interface())
}
} else if kindOfField == reflect.Struct {
cmd := this.createCliFlagSet(structOfField.Tag)
if err := cmd.parseValue(valueOfField); err != nil {
return err
}
cmd := this.createSubCommand(structOfField.Tag)
err = cmd.parseValue(valueOfField)
} else {
if err := this.addFlag(valueOfField, structOfField); err != nil {
return err
}
err = this.addFlag(valueOfField, structOfField)
}
}
return nil
return err
}
func (this *Command) addFlag(v reflect.Value, f reflect.StructField) error {
@ -207,7 +202,7 @@ func (this *Command) addFlag(v reflect.Value, f reflect.StructField) error {
return nil
}
func (this *Command) createCliFlagSet(tag reflect.StructTag) *Command {
func (this *Command) createSubCommand(tag reflect.StructTag) *Command {
cmdTag, ok := tag.Lookup("cmd")
if !ok || cmdTag == "" {
return this

View File

@ -5,59 +5,13 @@ import (
"strconv"
"testing"
"github.com/eschao/config/test"
"github.com/stretchr/testify/assert"
)
type dbConfig struct {
Host string `cmd:"dbHost database server hostname"`
Port int `cmd:"dbPort database server port"`
User string `cmd:"dbUser database username"`
Password string `cmd:"dbPassword database user password"`
Log logConfig `cmd:"log database log configuration"`
}
type loginConfig struct {
User string `cmd:"user login username"`
Password string `cmd:"password login password"`
}
type logConfig struct {
Path string `cmd:"path log path"`
Level string `cmd:"level log level {debug|warning|error}"`
}
type serviceConfig struct {
Host string `cmd:"hostname service hostname"`
Port int `cmd:"port service port"`
DBConfig dbConfig `cmd:"database database configuration"`
Login *loginConfig `cmd:"login login user and password"`
Log logConfig `cmd:"log service log configuration"`
}
type typesConfig struct {
BoolValue bool `cmd:"bool boolean value"`
StrValue string `cmd:"str string value"`
Int8Value int8 `cmd:"int8 int8 value"`
Int16Value int16 `cmd:"int16 int16 value"`
IntValue int `cmd:"int int value"`
Int32Value int32 `cmd:"int32 int32 value"`
Int64Value int64 `cmd:"int64 int64 value"`
Uint8Value uint8 `cmd:"uint8 uint8 value"`
Uint16Value uint16 `cmd:"uint16 uint16 value"`
UintValue uint `cmd:"uint uint value"`
Uint32Value uint32 `cmd:"uint32 uint32 value"`
Uint64Value uint64 `cmd:"uint64 uint64 value"`
Float32Value float32 `cmd:"float32 float32 value"`
Float64Value float64 `cmd:"float64 float64 value"`
}
type defValueConfig struct {
BoolValue bool `cmd:"bool boolean value" default:"true"`
}
func TestServiceCommand(t *testing.T) {
assert := assert.New(t)
serviceConfig := serviceConfig{}
serviceConfig := test.ServiceConfig{}
cmd := New("Service")
err := cmd.Init(&serviceConfig)
if err != nil {
@ -66,80 +20,65 @@ func TestServiceCommand(t *testing.T) {
// assert service cmd
assert.NotNil(cmd.FlagSet)
assert.NotNil(cmd.FlagSet.Lookup("hostname"),
"service cmd should have {hostname} parameter")
assert.NotNil(cmd.FlagSet.Lookup("port"),
"service cmd should have {port} parameter")
assert.Equal(2, len(cmd.SubCommands),
"service cmd should have 2 sub cmds")
assert.Nil(cmd.SubCommands["login"],
"service cmd shouldn't have {login} sub cmd")
assert.NotNil(cmd.FlagSet.Lookup("hostname"))
assert.NotNil(cmd.FlagSet.Lookup("port"))
assert.Equal(2, len(cmd.SubCommands))
assert.Nil(cmd.SubCommands["login"])
// assert database sub cmd
dbCmd := cmd.SubCommands["database"]
assert.NotNil(dbCmd, "service cmd should have {database} sub cmd")
assert.NotNil(dbCmd.FlagSet.Lookup("dbHost"),
"database cmd should have {dbHost} parameter")
assert.NotNil(dbCmd.FlagSet.Lookup("dbPort"),
"database cmd should have {dbPort} parameter")
assert.NotNil(dbCmd.FlagSet.Lookup("dbUser"),
"database cmd should have {dbUser} parameter")
assert.NotNil(dbCmd.FlagSet.Lookup("dbPassword"),
"database cmd should have {dbPassword} parameter")
assert.NotNil(dbCmd.FlagSet.Lookup("dbHost"))
assert.NotNil(dbCmd.FlagSet.Lookup("dbPort"))
assert.NotNil(dbCmd.FlagSet.Lookup("dbUser"))
assert.NotNil(dbCmd.FlagSet.Lookup("dbPassword"))
// assert database log sub cmd
dbLogCmd := dbCmd.SubCommands["log"]
assert.NotNil(dbCmd, "database cmd should have {log} sub cmd")
assert.NotNil(dbLogCmd.FlagSet.Lookup("path"),
"database log cmd should have {path} parameter")
assert.NotNil(dbLogCmd.FlagSet.Lookup("level"),
"database log cmd should have {level} parameter")
assert.Equal(0, len(dbLogCmd.SubCommands),
"database log cmd shouldn't have sub cmd")
assert.NotNil(dbLogCmd.FlagSet.Lookup("path"))
assert.NotNil(dbLogCmd.FlagSet.Lookup("level"))
assert.Equal(0, len(dbLogCmd.SubCommands))
// assert log cmd
logCmd := cmd.SubCommands["log"]
assert.NotNil(logCmd, "service cmd should have {log} sub cmd")
assert.NotNil(logCmd.FlagSet.Lookup("path"),
"log cmd should have {path} parameter")
assert.NotNil(logCmd.FlagSet.Lookup("level"),
"log cmd should have {level} parameter")
assert.NotNil(logCmd.FlagSet.Lookup("path"))
assert.NotNil(logCmd.FlagSet.Lookup("level"))
}
func TestLoginSubCommand(t *testing.T) {
assert := assert.New(t)
serviceConfig := serviceConfig{Login: &loginConfig{}}
serviceConfig := test.ServiceConfig{Login: &test.LoginConfig{}}
cmd := New("Service")
assert.NoError(cmd.Init(&serviceConfig), "Can't init service command")
assert.NoError(cmd.Init(&serviceConfig))
// assert login sub command
loginCmd := cmd.SubCommands["login"]
assert.NotNil(loginCmd, "service cmd should have {login} sub cmd")
assert.NotNil(loginCmd.FlagSet.Lookup("user"),
"login cmd should have {user} parameter")
assert.NotNil(loginCmd.FlagSet.Lookup("password"),
"login cmd should have {password} parameter")
assert.NotNil(loginCmd.FlagSet.Lookup("user"))
assert.NotNil(loginCmd.FlagSet.Lookup("password"))
}
func TestLoginCommandWithValues(t *testing.T) {
assert := assert.New(t)
loginConfig := loginConfig{}
loginConfig := test.LoginConfig{}
cmd := New("Login")
assert.NoError(cmd.Init(&loginConfig), "Can't init login command")
username := "test-user"
password := "test-passwd"
args := []string{"-user", username, "--password", password}
assert.NoError(cmd.Parse(args), "Can't parse login command")
assert.Equal(username, loginConfig.User, "Failed to parse login command")
assert.Equal(password, loginConfig.Password, "Failed to parse login command")
assert.NoError(cmd.Parse(args))
assert.Equal(username, loginConfig.User)
assert.Equal(password, loginConfig.Password)
}
func TestServiceCommandWithValues(t *testing.T) {
assert := assert.New(t)
serviceConfig := serviceConfig{Login: &loginConfig{}}
serviceConfig := test.ServiceConfig{Login: &test.LoginConfig{}}
cmd := New("Service")
assert.NoError(cmd.Init(&serviceConfig), "Can't init service command")
assert.NoError(cmd.Init(&serviceConfig))
serviceHost := "service-hostname"
servicePort := 8080
@ -159,155 +98,112 @@ func TestServiceCommandWithValues(t *testing.T) {
serviceArgs := []string{"--hostname", serviceHost, "--port",
strconv.Itoa(servicePort), "log", "-path", serviceLogPath, "-level",
serviceLogLevel}
assert.NoError(cmd.Parse(serviceArgs), "Can't parse service command")
assert.Equal(serviceHost, serviceConfig.Host,
"Service hostname is not equal")
assert.Equal(servicePort, serviceConfig.Port,
"Service port is not equal")
assert.Equal(serviceLogPath, serviceConfig.Log.Path,
"Service log path is not equal")
assert.Equal(serviceLogLevel, serviceConfig.Log.Level,
"Service log level is not equal")
assert.NoError(cmd.Parse(serviceArgs))
assert.Equal(serviceHost, serviceConfig.Host)
assert.Equal(servicePort, serviceConfig.Port)
assert.Equal(serviceLogPath, serviceConfig.Log.Path)
assert.Equal(serviceLogLevel, serviceConfig.Log.Level)
dbCmdArgs := []string{"database", "-dbHost", dbHost, "-dbPort",
strconv.Itoa(dbPort), "-dbUser", dbUser, "-dbPassword", dbPassword}
assert.NoError(cmd.Parse(dbCmdArgs), "Can't parse service database command")
assert.Equal(dbHost, serviceConfig.DBConfig.Host,
"Database hostname is not equal")
assert.Equal(dbPort, serviceConfig.DBConfig.Port,
"Database port is not equal")
assert.Equal(dbUser, serviceConfig.DBConfig.User,
"Database username is not equal")
assert.Equal(dbPassword, serviceConfig.DBConfig.Password,
"Database password is not equal")
assert.NoError(cmd.Parse(dbCmdArgs))
assert.Equal(dbHost, serviceConfig.DBConfig.Host)
assert.Equal(dbPort, serviceConfig.DBConfig.Port)
assert.Equal(dbUser, serviceConfig.DBConfig.User)
assert.Equal(dbPassword, serviceConfig.DBConfig.Password)
loginCmdArgs := []string{"login", "--user", loginUser, "-password",
loginPassword}
assert.NoError(cmd.Parse(loginCmdArgs), "Can't parse service login command")
assert.Equal(loginUser, serviceConfig.Login.User,
"Login username is not equal")
assert.Equal(loginPassword, serviceConfig.Login.Password,
"Login password is not equal")
assert.NoError(cmd.Parse(loginCmdArgs))
assert.Equal(loginUser, serviceConfig.Login.User)
assert.Equal(loginPassword, serviceConfig.Login.Password)
dbLogCmdArgs := []string{"database", "log", "-path", dbLogPath, "-level",
dbLogLevel}
assert.NoError(cmd.Parse(dbLogCmdArgs), "Can't parse database log command")
assert.Equal(dbLogPath, serviceConfig.DBConfig.Log.Path,
"Database log path is not equal")
assert.Equal(dbLogLevel, serviceConfig.DBConfig.Log.Level,
"Database log level is not equal")
assert.NoError(cmd.Parse(dbLogCmdArgs))
assert.Equal(dbLogPath, serviceConfig.DBConfig.Log.Path)
assert.Equal(dbLogLevel, serviceConfig.DBConfig.Log.Level)
}
func TestVariousTypeCommand(t *testing.T) {
assert := assert.New(t)
typesConfig := typesConfig{}
cmd := NewWith("Types", flag.ContinueOnError, nil)
typesConfig := test.TypesConfig{}
cmd := NewWith("Types", flag.ContinueOnError, func(cmd *Command) func() {
return func() {
}
})
assert.NoError(cmd.Init(&typesConfig))
// bool value
assert.NoError(cmd.Parse([]string{"-bool=true"}),
"Can't parse bool value command")
assert.Equal(true, typesConfig.BoolValue, "Bool value is not true")
assert.NoError(cmd.Parse([]string{"-bool"}),
"Can't parse bool value command")
assert.Equal(true, typesConfig.BoolValue, "Bool value is not false")
assert.Error(cmd.Parse([]string{"-bool=xxx"}),
"Parsing string as bool should have an error")
assert.NoError(cmd.Parse([]string{"-bool=true"}))
assert.Equal(true, typesConfig.BoolValue)
assert.NoError(cmd.Parse([]string{"-bool"}))
assert.Equal(true, typesConfig.BoolValue)
assert.Error(cmd.Parse([]string{"-bool=xxx"}))
// string value
assert.NoError(cmd.Parse([]string{"-str=xxx"}),
"Can't parse string value command")
assert.Equal("xxx", typesConfig.StrValue, "String value it not equal")
assert.NoError(cmd.Parse([]string{"-str", "yyy"}),
"Can't parse string value command")
assert.Equal("yyy", typesConfig.StrValue, "String value is not equal")
assert.NoError(cmd.Parse([]string{"-str=xxx"}))
assert.Equal("xxx", typesConfig.StrValue)
assert.NoError(cmd.Parse([]string{"-str", "yyy"}))
assert.Equal("yyy", typesConfig.StrValue)
// int8 value
assert.NoError(cmd.Parse([]string{"-int8=100"}),
"Can't parse int8 value command")
assert.Equal(int8(100), typesConfig.Int8Value, "Int8 value is not equal")
assert.Error(cmd.Parse([]string{"-int8=xxx"}),
"Parsing string as int8 should have an error")
assert.NoError(cmd.Parse([]string{"-int8=100"}))
assert.Equal(int8(100), typesConfig.Int8Value)
assert.Error(cmd.Parse([]string{"-int8=xxx"}))
// int16 value
assert.NoError(cmd.Parse([]string{"-int16=200"}),
"Can't parse int16 value command")
assert.Equal(int16(200), typesConfig.Int16Value, "Int16 value is not equal")
assert.Error(cmd.Parse([]string{"-int16=xxx"}),
"Parsing string as int16 should have an error")
assert.NoError(cmd.Parse([]string{"-int16=200"}))
assert.Equal(int16(200), typesConfig.Int16Value)
assert.Error(cmd.Parse([]string{"-int16=xxx"}))
// int value
assert.NoError(cmd.Parse([]string{"-int=300"}),
"Can't parse int value command")
assert.Equal(int(300), typesConfig.IntValue, "Int value is not equal")
assert.Error(cmd.Parse([]string{"-int=xxx"}),
"Parsing string as int should have an error")
assert.NoError(cmd.Parse([]string{"-int=300"}))
assert.Equal(int(300), typesConfig.IntValue)
assert.Error(cmd.Parse([]string{"-int=xxx"}))
// int32 value
assert.NoError(cmd.Parse([]string{"-int32=400"}),
"Can't parse int32 value command")
assert.Equal(int32(400), typesConfig.Int32Value, "Int32 value is not equal")
assert.Error(cmd.Parse([]string{"-int32=xxx"}),
"Parsing string as int32 should have an error")
assert.NoError(cmd.Parse([]string{"-int32=400"}))
assert.Equal(int32(400), typesConfig.Int32Value)
assert.Error(cmd.Parse([]string{"-int32=xxx"}))
// int64 value
assert.NoError(cmd.Parse([]string{"-int64=500"}),
"Can't parse int64 value command")
assert.Equal(int64(500), typesConfig.Int64Value, "Int64 value is not equal")
assert.Error(cmd.Parse([]string{"-int64=xxx"}),
"Parsing string as int64 should have an error")
assert.NoError(cmd.Parse([]string{"-int64=500"}))
assert.Equal(int64(500), typesConfig.Int64Value)
assert.Error(cmd.Parse([]string{"-int64=xxx"}))
// uint8 value
assert.NoError(cmd.Parse([]string{"-uint8=10"}),
"Can't parse uint8 value command")
assert.Equal(uint8(10), typesConfig.Uint8Value, "Uint8 value is not equal")
assert.Error(cmd.Parse([]string{"-uint8=-10"}),
"Parsing string as uint8 should have an error")
assert.NoError(cmd.Parse([]string{"-uint8=10"}))
assert.Equal(uint8(10), typesConfig.Uint8Value)
assert.Error(cmd.Parse([]string{"-uint8=-10"}))
// uint16 value
assert.NoError(cmd.Parse([]string{"-uint16=1000"}),
"Can't parse uint16 value command")
assert.Equal(uint16(1000), typesConfig.Uint16Value,
"Uint16 value is not equal")
assert.Error(cmd.Parse([]string{"-uint16=xxx"}),
"Parsing string as uint16 should have an error")
assert.NoError(cmd.Parse([]string{"-uint16=1000"}))
assert.Equal(uint16(1000), typesConfig.Uint16Value)
assert.Error(cmd.Parse([]string{"-uint16=xxx"}))
// uint value
assert.NoError(cmd.Parse([]string{"-uint=2000"}),
"Can't parse uint value command")
assert.Equal(uint(2000), typesConfig.UintValue, "Uint value is not equal")
assert.Error(cmd.Parse([]string{"-uint=xxx"}),
"Parsing string as uint should have an error")
assert.NoError(cmd.Parse([]string{"-uint=2000"}))
assert.Equal(uint(2000), typesConfig.UintValue)
assert.Error(cmd.Parse([]string{"-uint=xxx"}))
// uint32 value
assert.NoError(cmd.Parse([]string{"-uint32=3000"}),
"Can't parse uint32 value command")
assert.Equal(uint32(3000), typesConfig.Uint32Value,
"Uint32 value is not equal")
assert.Error(cmd.Parse([]string{"-uint32=xxx"}),
"Parsing string as uint32 should have an error")
assert.NoError(cmd.Parse([]string{"-uint32=3000"}))
assert.Equal(uint32(3000), typesConfig.Uint32Value)
assert.Error(cmd.Parse([]string{"-uint32=xxx"}))
// uint64 value
assert.NoError(cmd.Parse([]string{"-uint64=4000"}),
"Can't parse uint64 value command")
assert.Equal(uint64(4000), typesConfig.Uint64Value,
"Uint64 value is not equal")
assert.Error(cmd.Parse([]string{"-uint64=xxx"}),
"Parsing string as uint64 should have an error")
assert.NoError(cmd.Parse([]string{"-uint64=4000"}))
assert.Equal(uint64(4000), typesConfig.Uint64Value)
assert.Error(cmd.Parse([]string{"-uint64=xxx"}))
// float32 value
assert.NoError(cmd.Parse([]string{"-float32=1.234"}),
"Can't parse float32 value command")
assert.Equal(float32(1.234), typesConfig.Float32Value,
"Float32 value is not equal")
assert.Error(cmd.Parse([]string{"-float32=xxx"}),
"Parsing string as float32 should have an error")
assert.NoError(cmd.Parse([]string{"-float32=1.234"}))
assert.Equal(float32(1.234), typesConfig.Float32Value)
assert.Error(cmd.Parse([]string{"-float32=xxx"}))
// float64 value
assert.NoError(cmd.Parse([]string{"-float64=2.345"}),
"Can't parse float64 value command")
assert.Equal(float64(2.345), typesConfig.Float64Value,
"Float64 value is not equal")
assert.Error(cmd.Parse([]string{"-float64=xxx"}),
"Parsing string as float64 should have an error")
assert.NoError(cmd.Parse([]string{"-float64=2.345"}))
assert.Equal(float64(2.345), typesConfig.Float64Value)
assert.Error(cmd.Parse([]string{"-float64=xxx"}))
}

226
config.go
View File

@ -2,46 +2,220 @@ package config
import (
"encoding/json"
"errors"
"flag"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"reflect"
"github.com/eschao/config/cli"
"github.com/eschao/config/env"
"github.com/eschao/config/utils"
"gopkg.in/yaml.v2"
)
type Field struct {
JsonName string
YamlName string
PropName string
EnvName string
CliName string
Value reflect.Value
DefaultValue string
Separator string
}
const (
DefaultJSONConfig = "config.json"
DefaultYamlConfig = "config.yaml"
DefaultPropConfig = "config.properties"
)
type Config struct {
Fields []Field
}
const (
JSONConfigType = "json"
YamlConfigType = "yaml"
PropConfigType = "properties"
)
func (this *Config) Init() *Config {
if this.Fields == nil {
this.Fields = []Field{}
func ParseDefault(i interface{}) error {
ptrRef := reflect.ValueOf(i)
if ptrRef.IsNil() || ptrRef.Kind() != reflect.Ptr {
return fmt.Errorf("Expect a structure pointer type instead of %s",
ptrRef.Kind().String())
}
return this
valueOfStruct := ptrRef.Elem()
if valueOfStruct.Kind() != reflect.Struct {
return fmt.Errorf("Expect a structure pointer type instead of %s",
valueOfStruct.Kind().String())
}
return parseValue(valueOfStruct)
}
func (this *Config) ParseJSON(jsonFile string, data interface{}) error {
raw, err := ioutil.ReadFile(jsonFile)
func parseValue(v reflect.Value) error {
typeOfStruct := v.Type()
var err error
for i := 0; i < v.NumField() && err == nil; i++ {
valueOfField := v.Field(i)
kindOfField := valueOfField.Kind()
structOfField := typeOfStruct.Field(i)
if kindOfField == reflect.Ptr {
if !valueOfField.IsNil() && valueOfField.CanSet() {
err = ParseDefault(valueOfField.Interface())
} else {
continue
}
} else if kindOfField == reflect.Struct {
err = parseValue(valueOfField)
}
defValue, ok := structOfField.Tag.Lookup("default")
if !ok {
continue
}
kind := valueOfField.Kind()
switch kind {
case reflect.Bool:
err = utils.SetValueWithBool(valueOfField, defValue)
case reflect.String:
valueOfField.SetString(defValue)
case reflect.Int8:
err = utils.SetValueWithIntX(valueOfField, defValue, 8)
case reflect.Int16:
err = utils.SetValueWithIntX(valueOfField, defValue, 16)
case reflect.Int, reflect.Int32:
err = utils.SetValueWithIntX(valueOfField, defValue, 32)
case reflect.Int64:
err = utils.SetValueWithIntX(valueOfField, defValue, 64)
case reflect.Uint8:
err = utils.SetValueWithUintX(valueOfField, defValue, 8)
case reflect.Uint16:
err = utils.SetValueWithUintX(valueOfField, defValue, 16)
case reflect.Uint, reflect.Uint32:
err = utils.SetValueWithUintX(valueOfField, defValue, 32)
case reflect.Uint64:
err = utils.SetValueWithUintX(valueOfField, defValue, 64)
case reflect.Float32:
err = utils.SetValueWithFloatX(valueOfField, defValue, 32)
case reflect.Float64:
err = utils.SetValueWithFloatX(valueOfField, defValue, 64)
case reflect.Slice:
sp, ok := structOfField.Tag.Lookup("separator")
if !ok {
sp = ":"
}
err = utils.SetValueWithSlice(valueOfField, defValue, sp)
default:
return fmt.Errorf("Can't support type: %s", kind.String())
}
}
return err
}
func ParseEnv(i interface{}) error {
return env.ParseWith(i, "")
}
func ParseCli(i interface{}) error {
cli := cli.New(os.Args[0])
if err := cli.Init(i); err != nil {
return err
}
if err := cli.Parse(os.Args[1:]); err != nil {
return err
}
return nil
}
func ParseConfig(i interface{}, configFlag string) error {
configFile := flag.String(configFlag, "", "Specifiy configuration file")
return ParseConfigFile(i, *configFile)
}
func ParseConfigFile(i interface{}, configFile string) error {
var err error
if configFile == "" {
configFile, err = getDefaultConfigFile()
if err != nil {
return err
}
}
configType, err := getConfigFileType(configFile)
if err != nil {
return errors.New("Can't open json file. Err: " + err.Error())
return err
}
err = json.Unmarshal(raw, data)
if err != nil {
return errors.New("Failed unmarshal json. Err: " + err.Error())
switch configType {
case JSONConfigType:
return parseJSON(i, configFile)
case YamlConfigType:
return parseYaml(i, configFile)
case PropConfigType:
return parseProp(i, configFile)
default:
return fmt.Errorf("Can't support config file: %s", configFile)
}
//fmt.Printf("Data: %v", *data.(*interface{}))
return nil
}
func parseJSON(i interface{}, jsonFile string) error {
raw, err := ioutil.ReadFile(jsonFile)
if err != nil {
return fmt.Errorf("Can't open json config file. %s", err.Error())
}
return json.Unmarshal(raw, i)
}
func parseYaml(i interface{}, yamlFile string) error {
raw, err := ioutil.ReadFile(yamlFile)
if err != nil {
return fmt.Errorf("Can't open yaml config file. %s", err.Error())
}
return yaml.Unmarshal(raw, i)
}
func parseProp(i interface{}, propFile string) error {
return fmt.Errorf("Properties config has not implemented!")
}
func getDefaultConfigFile() (string, error) {
exe, err := os.Executable()
if err != nil {
return "", fmt.Errorf("Can't find default config file. %s", err.Error())
}
path := filepath.Dir(exe) + string(filepath.Separator)
// check json config
jsonConfig := path + DefaultJSONConfig
if _, err := os.Stat(jsonConfig); err == nil {
return jsonConfig, nil
}
// check yaml config
yamlConfig := path + DefaultYamlConfig
if _, err := os.Stat(yamlConfig); err == nil {
return yamlConfig, nil
}
// check prop config
propConfig := path + DefaultPropConfig
if _, err := os.Stat(propConfig); err == nil {
return propConfig, nil
}
return "", fmt.Errorf("No default config file found in path: %s", path)
}
func getConfigFileType(configFile string) (string, error) {
ext := filepath.Ext(configFile)
if ext == ".json" {
return JSONConfigType, nil
} else if ext == ".yaml" || ext == ".yml" {
return YamlConfigType, nil
} else if ext == ".properties" || ext == ".prop" {
return PropConfigType, nil
}
return "", fmt.Errorf("Can't support file type: %s", configFile)
}

View File

@ -1,27 +1,101 @@
package config
import (
"os"
"path/filepath"
"runtime"
"strconv"
"testing"
"github.com/eschao/config/test"
"github.com/stretchr/testify/assert"
)
type TestConfig struct {
Name string `json:"name" default:"test-name"`
Path string `json:"path" default:"./"`
const (
LOGIN_USER = "test-login-user"
LOGIN_PASSWORD = "test-login-passwd"
SERVICE_HOST = "test-service-host"
SERVICE_PORT = 8080
SERVICE_LOG_PATH = "/var/log/service"
SERVICE_LOG_LEVEL = "debug"
DB_HOST = "test-db-host"
DB_PORT = 9090
DB_USER = "test-db-user"
DB_PASSWORD = "test-db-password"
DB_LOG_PATH = "/var/log/db"
DB_LOG_LEVEL = "error"
)
func TestDefaultValueConfig(t *testing.T) {
conf := test.DefValueConfig{}
assert := assert.New(t)
assert.NoError(ParseDefault(&conf))
assert.Equal(true, conf.BoolValue)
assert.Equal(123, conf.IntValue)
assert.Equal(float64(123.4567), conf.Float64Value)
assert.Equal("default-string", conf.StrValue)
assert.Equal(3, len(conf.SliceValue))
assert.Equal("xx", conf.SliceValue[0])
assert.Equal("yy", conf.SliceValue[1])
assert.Equal("zz", conf.SliceValue[2])
assert.Equal("", conf.NoDefValue)
}
func TestJSONConfig(t *testing.T) {
config := Config{}
myConfig := TestConfig{}
err := config.Init().ParseJSON("test.json", &myConfig)
if err != nil {
t.Errorf("JSON config test failed. ", err.Error())
}
func TestEnvConfig(t *testing.T) {
dbLogPrefix := "LOG_"
if myConfig.Name != "jsonconfig" {
t.Errorf("Name json value: %s != jsonconfig", myConfig.Name)
}
os.Setenv("HOST", DB_HOST)
os.Setenv("PORT", strconv.Itoa(DB_PORT))
os.Setenv("USER", DB_USER)
os.Setenv("PASSWORD", DB_PASSWORD)
os.Setenv(dbLogPrefix+"PATH", DB_LOG_PATH)
os.Setenv(dbLogPrefix+"LEVEL", DB_LOG_LEVEL)
if myConfig.Path != "/var" {
t.Errorf("Path json value: %s != /var", myConfig.Path)
}
defer os.Unsetenv("HOST")
defer os.Unsetenv("PORT")
defer os.Unsetenv("USER")
defer os.Unsetenv("PASSWORD")
defer os.Unsetenv(dbLogPrefix + "PATH")
defer os.Unsetenv(dbLogPrefix + "LEVEL")
conf := test.DBConfig{}
assert := assert.New(t)
assert.NoError(ParseEnv(&conf))
assert.Equal(DB_HOST, conf.Host)
assert.Equal(DB_PORT, conf.Port)
assert.Equal(DB_USER, conf.User)
assert.Equal(DB_PASSWORD, conf.Password)
assert.Equal(DB_LOG_PATH, conf.Log.Path)
assert.Equal(DB_LOG_LEVEL, conf.Log.Level)
}
func TestJSONConfigFile(t *testing.T) {
_, curTestFile, _, _ := runtime.Caller(0)
path := filepath.Dir(curTestFile)
conf := test.DBConfig{}
assert := assert.New(t)
assert.NoError(ParseConfigFile(&conf, path+"/test/config.json"))
assert.Equal(DB_HOST, conf.Host)
assert.Equal(DB_PORT, conf.Port)
assert.Equal(DB_USER, conf.User)
assert.Equal(DB_PASSWORD, conf.Password)
assert.Equal(DB_LOG_PATH, conf.Log.Path)
assert.Equal(DB_LOG_LEVEL, conf.Log.Level)
}
func TestYamlConfigFile(t *testing.T) {
_, curTestFile, _, _ := runtime.Caller(0)
path := filepath.Dir(curTestFile)
conf := test.DBConfig{}
assert := assert.New(t)
assert.NoError(ParseConfigFile(&conf, path+"/test/config.yaml"))
assert.Equal(DB_HOST, conf.Host)
assert.Equal(DB_PORT, conf.Port)
assert.Equal(DB_USER, conf.User)
assert.Equal(DB_PASSWORD, conf.Password)
assert.Equal(DB_LOG_PATH, conf.Log.Path)
assert.Equal(DB_LOG_LEVEL, conf.Log.Level)
}

183
env/env.go vendored
View File

@ -4,11 +4,15 @@ import (
"fmt"
"os"
"reflect"
"strconv"
"strings"
"github.com/eschao/config/utils"
)
func Unmarshal(i interface{}) error {
func Parse(i interface{}) error {
return ParseWith(i, "")
}
func ParseWith(i interface{}, prefix string) error {
ptrRef := reflect.ValueOf(i)
if ptrRef.IsNil() || ptrRef.Kind() != reflect.Ptr {
@ -22,37 +26,37 @@ func Unmarshal(i interface{}) error {
valueOfStruct.Kind().String())
}
return unmarshal(valueOfStruct)
return parseValue(valueOfStruct, prefix)
}
func unmarshal(v reflect.Value) error {
func parseValue(v reflect.Value, prefix string) error {
typeOfStruct := v.Type()
for i := 0; i < v.NumField(); i++ {
var err error
for i := 0; i < v.NumField() && err == nil; i++ {
valueOfField := v.Field(i)
kindOfField := valueOfField.Kind()
structOfField := typeOfStruct.Field(i)
//fmt.Printf("Name: %s, Type: %s\n", structOfField.Name, kindOfField.String())
// recursively unmarshal if value is ptr type
if kindOfField == reflect.Ptr {
if !valueOfField.IsNil() && valueOfField.CanSet() {
Unmarshal(valueOfField.Interface())
err = ParseWith(valueOfField.Interface(),
prefix+structOfField.Tag.Get("env"))
} else {
continue
}
} else if kindOfField == reflect.Struct {
unmarshal(valueOfField)
err = parseValue(valueOfField, prefix+structOfField.Tag.Get("env"))
}
if err := setFieldValue(valueOfField, structOfField); err != nil {
return err
}
err = setFieldValue(valueOfField, structOfField, prefix)
}
return nil
return err
}
func getEnvValue(envName string, f reflect.StructField) (string, bool) {
//fmt.Printf("Lookup ENV: %s\n", envName)
envValue, ok := os.LookupEnv(envName)
if !ok {
envValue, ok = f.Tag.Lookup("default")
@ -61,13 +65,13 @@ func getEnvValue(envName string, f reflect.StructField) (string, bool) {
return envValue, ok
}
func setFieldValue(v reflect.Value, f reflect.StructField) error {
func setFieldValue(v reflect.Value, f reflect.StructField, prefix string) error {
envName := f.Tag.Get("env")
if envName == "" {
return nil
}
envValue, ok := getEnvValue(envName, f)
envValue, ok := getEnvValue(prefix+envName, f)
if !ok {
return nil
}
@ -76,168 +80,47 @@ func setFieldValue(v reflect.Value, f reflect.StructField) error {
return fmt.Errorf("%s: can't be set", f.Name)
}
var err error
kind := v.Kind()
name := f.Name
switch kind {
case reflect.Bool:
return setFieldValueWithBool(name, v, envValue)
err = utils.SetValueWithBool(v, envValue)
case reflect.String:
v.SetString(envValue)
return nil
case reflect.Int8:
return setFieldValueWithIntX(name, v, envValue, 8)
err = utils.SetValueWithIntX(v, envValue, 8)
case reflect.Int16:
return setFieldValueWithIntX(name, v, envValue, 16)
err = utils.SetValueWithIntX(v, envValue, 16)
case reflect.Int, reflect.Int32:
return setFieldValueWithIntX(name, v, envValue, 32)
err = utils.SetValueWithIntX(v, envValue, 32)
case reflect.Int64:
return setFieldValueWithIntX(name, v, envValue, 64)
err = utils.SetValueWithIntX(v, envValue, 64)
case reflect.Uint8:
return setFieldValueWithUintX(name, v, envValue, 8)
err = utils.SetValueWithUintX(v, envValue, 8)
case reflect.Uint16:
return setFieldValueWithUintX(name, v, envValue, 16)
err = utils.SetValueWithUintX(v, envValue, 16)
case reflect.Uint, reflect.Uint32:
return setFieldValueWithUintX(name, v, envValue, 32)
err = utils.SetValueWithUintX(v, envValue, 32)
case reflect.Uint64:
return setFieldValueWithUintX(name, v, envValue, 64)
err = utils.SetValueWithUintX(v, envValue, 64)
case reflect.Float32:
return setFieldValueWithFloatX(name, v, envValue, 32)
err = utils.SetValueWithFloatX(v, envValue, 32)
case reflect.Float64:
return setFieldValueWithFloatX(name, v, envValue, 64)
err = utils.SetValueWithFloatX(v, envValue, 64)
case reflect.Slice:
sp, ok := f.Tag.Lookup("separator")
if !ok {
sp = ":"
}
return setFieldValueWithSlice(name, v, envValue, sp)
err = utils.SetValueWithSlice(v, envValue, sp)
default:
return fmt.Errorf("Can't support type: %s", kind.String())
}
}
func setFieldValueWithBool(name string, v reflect.Value,
envValue string) error {
value, err := strconv.ParseBool(envValue)
if err != nil {
return fmt.Errorf("%s: can't convert %s to bool value. %s", name, envValue,
err.Error())
return fmt.Errorf("%s: %s", f.Name, err.Error())
}
v.SetBool(value)
return nil
}
func setFieldValueWithFloatX(name string, v reflect.Value, envValue string,
bitSize int) error {
value, err := strconv.ParseFloat(envValue, bitSize)
if err != nil {
return fmt.Errorf("%s: can't convert %s to float%d value. %s", name,
envValue, bitSize, err.Error())
}
v.SetFloat(value)
return nil
}
func setFieldValueWithIntX(name string, v reflect.Value, envValue string,
bitSize int) error {
value, err := strconv.ParseInt(envValue, 10, bitSize)
if err != nil {
return fmt.Errorf("%s: can't convert %s to int%d value. %s", name,
envValue, bitSize, err.Error())
}
v.SetInt(value)
return nil
}
func setFieldValueWithUintX(name string, v reflect.Value, envValue string,
bitSize int) error {
value, err := strconv.ParseUint(envValue, 10, bitSize)
if err != nil {
return fmt.Errorf("%s: can't convert %s to uint%d value. %s", name,
envValue, bitSize, err.Error())
}
v.SetUint(value)
return nil
}
func setFieldValueWithSlice(name string, v reflect.Value, envValue string,
separator string) error {
data := strings.Split(envValue, separator)
size := len(data)
if size > 0 {
slice := reflect.MakeSlice(v.Type(), size, size)
for i := 0; i < size; i++ {
ele := slice.Index(i)
kind := ele.Kind()
switch kind {
case reflect.Bool:
if err := setFieldValueWithBool(name, ele, data[i]); err != nil {
return err
}
case reflect.String:
ele.SetString(data[i])
case reflect.Uint8:
if err := setFieldValueWithUintX(name, ele, data[i], 8); err != nil {
return err
}
case reflect.Uint16:
if err := setFieldValueWithUintX(name, ele, data[i], 16); err != nil {
return err
}
case reflect.Uint, reflect.Uint32:
if err := setFieldValueWithUintX(name, ele, data[i], 32); err != nil {
return err
}
case reflect.Uint64:
if err := setFieldValueWithUintX(name, ele, data[i], 64); err != nil {
return err
}
case reflect.Int8:
if err := setFieldValueWithIntX(name, ele, data[i], 8); err != nil {
return err
}
case reflect.Int16:
if err := setFieldValueWithIntX(name, ele, data[i], 16); err != nil {
return err
}
case reflect.Int, reflect.Int32:
if err := setFieldValueWithIntX(name, ele, data[i], 32); err != nil {
return err
}
case reflect.Int64:
if err := setFieldValueWithIntX(name, ele, data[i], 64); err != nil {
return err
}
case reflect.Float32:
if err := setFieldValueWithFloatX(name, ele, data[i], 32); err != nil {
return err
}
case reflect.Float64:
if err := setFieldValueWithFloatX(name, ele, data[i], 64); err != nil {
return err
}
default:
return fmt.Errorf("%s: can't support type: %s", name, kind.String())
}
}
v.Set(slice)
}
return nil
}

393
env/env_test.go vendored
View File

@ -1,194 +1,259 @@
package env
import (
"fmt"
"os"
"strconv"
"strings"
"testing"
)
type EnvConfig1 struct {
Hostname string `env:"CONFIG_TEST_HOSTNAME" default:"localhost"`
Port int `env:"CONFIG_TEST_PORT"`
User string `env:"CONFIG_TEST_USER"`
Password string `env:"CONFIG_TEST_PASSWORD"`
Path1 []string `env:"CONFIG_TEST_PATH1"`
Path2 []string `env:"CONFIG_TEST_PATH2" separator:";"`
Home string
}
"github.com/eschao/config/test"
"github.com/stretchr/testify/assert"
)
const (
TEST_HOSTNAME = "test-hostname"
TEST_PORT = 8080
TEST_USER = "test-user"
TEST_PASSWORD = "test-password"
TEST_PATH1 = "/usr:/var:/bin"
TEST_PATH2 = "/root;/home;/tmp"
LOGIN_USER = "test-login-user"
LOGIN_PASSWORD = "test-login-passwd"
SERVICE_HOST = "test-service-host"
SERVICE_PORT = 8080
SERVICE_LOG_PATH = "/var/log/service"
SERVICE_LOG_LEVEL = "debug"
DB_HOST = "test-db-host"
DB_PORT = 9090
DB_USER = "test-db-user"
DB_PASSWORD = "test-db-password"
DB_LOG_PATH = "/var/log/db"
DB_LOG_LEVEL = "error"
)
func setEnvConfig1() {
os.Setenv("CONFIG_TEST_HOSTNAME", TEST_HOSTNAME)
os.Setenv("CONFIG_TEST_PORT", strconv.Itoa(TEST_PORT))
os.Setenv("CONFIG_TEST_USER", TEST_USER)
os.Setenv("CONFIG_TEST_PASSWORD", TEST_PASSWORD)
os.Setenv("CONFIG_TEST_PATH1", TEST_PATH1)
os.Setenv("CONFIG_TEST_PATH2", TEST_PATH2)
func TestLoginConfigEnv(t *testing.T) {
os.Setenv("USER", LOGIN_USER)
os.Setenv("PASSWORD", LOGIN_PASSWORD)
defer os.Unsetenv("USER")
defer os.Unsetenv("PASSWORD")
assert := assert.New(t)
loginConfig := test.LoginConfig{}
assert.NoError(Parse(&loginConfig))
assert.Equal(LOGIN_USER, loginConfig.User)
assert.Equal(LOGIN_PASSWORD, loginConfig.Password)
}
func unsetEnvConfig1() {
os.Unsetenv("CONFIG_TEST_HOSTNAME")
os.Unsetenv("CONFIG_TEST_PORT")
os.Unsetenv("CONFIG_TEST_USER")
os.Unsetenv("CONFIG_TEST_PASSWORD")
func TestLoginConfigEnvWithPrefix(t *testing.T) {
os.Setenv("DB_USER", LOGIN_USER)
os.Setenv("DB_PASSWORD", LOGIN_PASSWORD)
defer os.Unsetenv("DB_USER")
defer os.Unsetenv("DB_PASSWORD")
assert := assert.New(t)
loginConfig := test.LoginConfig{}
assert.NoError(ParseWith(&loginConfig, "DB_"))
assert.Equal(LOGIN_USER, loginConfig.User)
assert.Equal(LOGIN_PASSWORD, loginConfig.Password)
}
func assertEqual(expected, actual []string) (bool, error) {
if len(expected) != len(actual) {
return false, fmt.Errorf("Expected length of array is %d, but actual is %d",
len(expected), len(actual))
}
func TestServiceConfigEnv(t *testing.T) {
servicePrefix := "CONFIG_TEST_SERVICE_"
serviceLogPrefix := servicePrefix + "LOG_"
dbPrefix := servicePrefix + "DB_"
dbLogPrefix := dbPrefix + "LOG_"
for i := 0; i < len(expected); i++ {
if expected[i] != actual[i] {
return false, fmt.Errorf("Expected array[%d]=%s, but acutal array[%d]=%s",
i, expected[i], i, actual[i])
}
}
os.Setenv(servicePrefix+"HOST", SERVICE_HOST)
os.Setenv(servicePrefix+"PORT", strconv.Itoa(SERVICE_PORT))
os.Setenv(serviceLogPrefix+"PATH", SERVICE_LOG_PATH)
os.Setenv(serviceLogPrefix+"LEVEL", SERVICE_LOG_LEVEL)
os.Setenv(dbPrefix+"HOST", DB_HOST)
os.Setenv(dbPrefix+"PORT", strconv.Itoa(DB_PORT))
os.Setenv(dbPrefix+"USER", DB_USER)
os.Setenv(dbPrefix+"PASSWORD", DB_PASSWORD)
os.Setenv(dbLogPrefix+"PATH", DB_LOG_PATH)
os.Setenv(dbLogPrefix+"LEVEL", DB_LOG_LEVEL)
return true, nil
defer os.Unsetenv(servicePrefix + "HOST")
defer os.Unsetenv(servicePrefix + "PORT")
defer os.Unsetenv(serviceLogPrefix + "PATH")
defer os.Unsetenv(serviceLogPrefix + "LEVEL")
defer os.Unsetenv(dbPrefix + "HOST")
defer os.Unsetenv(dbPrefix + "PORT")
defer os.Unsetenv(dbPrefix + "USER")
defer os.Unsetenv(dbPrefix + "PASSWORD")
defer os.Unsetenv(dbLogPrefix + "PATH")
defer os.Unsetenv(dbLogPrefix + "LEVEL")
assert := assert.New(t)
serviceConfig := test.ServiceConfig{}
assert.NoError(Parse(&serviceConfig))
assert.Equal(SERVICE_HOST, serviceConfig.Host)
assert.Equal(SERVICE_PORT, serviceConfig.Port)
assert.Equal(SERVICE_LOG_PATH, serviceConfig.Log.Path)
assert.Equal(SERVICE_LOG_LEVEL, serviceConfig.Log.Level)
assert.Equal(DB_HOST, serviceConfig.DBConfig.Host)
assert.Equal(DB_PORT, serviceConfig.DBConfig.Port)
assert.Equal(DB_USER, serviceConfig.DBConfig.User)
assert.Equal(DB_PASSWORD, serviceConfig.DBConfig.Password)
assert.Equal(DB_LOG_PATH, serviceConfig.DBConfig.Log.Path)
assert.Equal(DB_LOG_LEVEL, serviceConfig.DBConfig.Log.Level)
}
func TestEnvConfig1(t *testing.T) {
setEnvConfig1()
defer unsetEnvConfig1()
func TestServiceLoginConfigEnv(t *testing.T) {
serviceLoginPrefix := "CONFIG_TEST_SERVICE_LOGIN_"
os.Setenv(serviceLoginPrefix+"USER", LOGIN_USER)
os.Setenv(serviceLoginPrefix+"PASSWORD", LOGIN_PASSWORD)
defer os.Unsetenv(serviceLoginPrefix + "USER")
defer os.Unsetenv(serviceLoginPrefix + "PASSWORD")
conf := EnvConfig1{}
err := Unmarshal(&conf)
if err != nil {
t.Errorf("Can't unmarshal config1 from environemnt variables. %s",
err.Error())
return
}
if conf.Hostname != TEST_HOSTNAME {
t.Errorf("Expect Hostname: %s, but got: %s", TEST_HOSTNAME, conf.Hostname)
}
if conf.Port != TEST_PORT {
t.Errorf("Expect Port: %d, but got: %d", TEST_PORT, conf.Port)
}
if conf.User != TEST_USER {
t.Errorf("Expect User: %s, but got: %s", TEST_USER, conf.User)
}
if conf.Password != TEST_PASSWORD {
t.Errorf("Expect Password: %s, but got: %s", TEST_PASSWORD, conf.Password)
}
if conf.Home != "" {
t.Errorf("Expect Home is empty, but got: %s", conf.Home)
}
expectedPath1 := strings.Split(TEST_PATH1, ":")
if ok, err := assertEqual(expectedPath1, conf.Path1); !ok {
t.Error(err.Error())
}
expectedPath2 := strings.Split(TEST_PATH2, ";")
if ok, err := assertEqual(expectedPath2, conf.Path2); !ok {
t.Error(err.Error())
}
assert := assert.New(t)
serviceConfig := test.ServiceConfig{Login: &test.LoginConfig{}}
assert.NoError(Parse(&serviceConfig))
assert.Equal(LOGIN_USER, serviceConfig.Login.User)
assert.Equal(LOGIN_PASSWORD, serviceConfig.Login.Password)
}
func TestEnvConfig1WithDefaultValue(t *testing.T) {
os.Setenv("CONFIG_TEST_PORT", strconv.Itoa(TEST_PORT))
os.Setenv("CONFIG_TEST_USER", TEST_USER)
os.Setenv("CONFIG_TEST_PASSWORD", TEST_PASSWORD)
defer unsetEnvConfig1()
func TestTypesConfigEnv(t *testing.T) {
typesPrefix := "CONFIG_TEST_"
os.Setenv(typesPrefix+"BOOL", "true")
os.Setenv(typesPrefix+"STR", "test-string")
os.Setenv(typesPrefix+"INT8", "100")
os.Setenv(typesPrefix+"INT16", "1000")
os.Setenv(typesPrefix+"INT", "10000")
os.Setenv(typesPrefix+"INT32", "100000")
os.Setenv(typesPrefix+"INT64", "1000000")
os.Setenv(typesPrefix+"UINT8", "200")
os.Setenv(typesPrefix+"UINT16", "2000")
os.Setenv(typesPrefix+"UINT", "20000")
os.Setenv(typesPrefix+"UINT32", "200000")
os.Setenv(typesPrefix+"UINT64", "2000000")
os.Setenv(typesPrefix+"FLOAT32", "1.234")
os.Setenv(typesPrefix+"FLOAT64", "2222.33333")
conf := EnvConfig1{}
err := Unmarshal(&conf)
if err != nil {
t.Errorf("Can't unmarshal config1 from environemnt variables. %s",
err.Error())
return
}
defer os.Unsetenv(typesPrefix + "BOOL")
defer os.Unsetenv(typesPrefix + "STR")
defer os.Unsetenv(typesPrefix + "INT8")
defer os.Unsetenv(typesPrefix + "INT16")
defer os.Unsetenv(typesPrefix + "INT")
defer os.Unsetenv(typesPrefix + "INT32")
defer os.Unsetenv(typesPrefix + "INT64")
defer os.Unsetenv(typesPrefix + "UINT8")
defer os.Unsetenv(typesPrefix + "UINT16")
defer os.Unsetenv(typesPrefix + "UINT")
defer os.Unsetenv(typesPrefix + "UINT32")
defer os.Unsetenv(typesPrefix + "UINT64")
defer os.Unsetenv(typesPrefix + "FLOAT32")
defer os.Unsetenv(typesPrefix + "FLOAT64")
if conf.Hostname != "localhost" {
t.Errorf("Expect Hostname: localhost, bug got: %s", conf.Hostname)
}
if conf.Port != TEST_PORT {
t.Errorf("Expect Port: %d, but got: %d", TEST_PORT, conf.Port)
}
if conf.User != TEST_USER {
t.Errorf("Expect User: %s, but got: %s", TEST_USER, conf.User)
}
if conf.Password != TEST_PASSWORD {
t.Errorf("Expect Password: %s, but got: %s", TEST_PASSWORD, conf.Password)
}
if conf.Home != "" {
t.Errorf("Expect Home is empty, but got: %s", conf.Home)
}
assert := assert.New(t)
typesConfig := test.TypesConfig{}
assert.NoError(Parse(&typesConfig))
assert.Equal(true, typesConfig.BoolValue)
assert.Equal("test-string", typesConfig.StrValue)
assert.Equal(int8(100), typesConfig.Int8Value)
assert.Equal(int16(1000), typesConfig.Int16Value)
assert.Equal(10000, typesConfig.IntValue)
assert.Equal(int32(100000), typesConfig.Int32Value)
assert.Equal(int64(1000000), typesConfig.Int64Value)
assert.Equal(uint8(200), typesConfig.Uint8Value)
assert.Equal(uint16(2000), typesConfig.Uint16Value)
assert.Equal(uint(20000), typesConfig.UintValue)
assert.Equal(uint32(200000), typesConfig.Uint32Value)
assert.Equal(uint64(2000000), typesConfig.Uint64Value)
assert.Equal(float32(1.234), typesConfig.Float32Value)
assert.Equal(float64(2222.33333), typesConfig.Float64Value)
}
type EnvConfig2 struct {
Config1 EnvConfig1
Server string `env:"CONFIG_ENV_TEST_SERVER"`
func TestTypesConfigWithErrorEnv(t *testing.T) {
assert := assert.New(t)
typesConfig := test.TypesConfig{}
typesPrefix := "CONFIG_TEST_"
os.Setenv(typesPrefix+"BOOL", "xxx")
assert.Error(Parse(&typesConfig))
os.Unsetenv(typesPrefix + "BOOL")
os.Setenv(typesPrefix+"INT8", "xxx")
assert.Error(Parse(&typesConfig))
os.Unsetenv(typesPrefix + "INT8")
os.Setenv(typesPrefix+"INT16", "xxx")
assert.Error(Parse(&typesConfig))
os.Unsetenv(typesPrefix + "INT16")
os.Setenv(typesPrefix+"INT", "xxx")
assert.Error(Parse(&typesConfig))
os.Unsetenv(typesPrefix + "INT")
os.Setenv(typesPrefix+"INT32", "xxx")
assert.Error(Parse(&typesConfig))
os.Unsetenv(typesPrefix + "INT32")
os.Setenv(typesPrefix+"INT64", "xxx")
assert.Error(Parse(&typesConfig))
os.Unsetenv(typesPrefix + "INT64")
os.Setenv(typesPrefix+"UINT8", "xxx")
assert.Error(Parse(&typesConfig))
os.Unsetenv(typesPrefix + "UINT8")
os.Setenv(typesPrefix+"UINT16", "xxx")
assert.Error(Parse(&typesConfig))
os.Unsetenv(typesPrefix + "UINT16")
os.Setenv(typesPrefix+"UINT", "xxx")
assert.Error(Parse(&typesConfig))
os.Unsetenv(typesPrefix + "UINT")
os.Setenv(typesPrefix+"UINT32", "xxx")
assert.Error(Parse(&typesConfig))
os.Unsetenv(typesPrefix + "UINT32")
os.Setenv(typesPrefix+"UINT64", "xxx")
assert.Error(Parse(&typesConfig))
os.Unsetenv(typesPrefix + "UINT64")
os.Setenv(typesPrefix+"FLOAT32", "xxx")
assert.Error(Parse(&typesConfig))
os.Unsetenv(typesPrefix + "FLOAT32")
os.Setenv(typesPrefix+"FLOAT64", "xxx")
assert.Error(Parse(&typesConfig))
os.Unsetenv(typesPrefix + "FLOAT64")
defer os.Unsetenv(typesPrefix + "BOOL")
defer os.Unsetenv(typesPrefix + "INT8")
defer os.Unsetenv(typesPrefix + "INT16")
defer os.Unsetenv(typesPrefix + "INT")
defer os.Unsetenv(typesPrefix + "INT32")
defer os.Unsetenv(typesPrefix + "INT64")
defer os.Unsetenv(typesPrefix + "UINT8")
defer os.Unsetenv(typesPrefix + "UINT16")
defer os.Unsetenv(typesPrefix + "UINT")
defer os.Unsetenv(typesPrefix + "UINT32")
defer os.Unsetenv(typesPrefix + "UINT64")
defer os.Unsetenv(typesPrefix + "FLOAT32")
defer os.Unsetenv(typesPrefix + "FLOAT64")
}
const (
TEST_SERVER = "test-server"
)
func TestSlicesConfigEnv(t *testing.T) {
prefix := "CONFIG_TEST_SLICES_"
os.Setenv(prefix+"PATHS", "/var:/usr:/home")
os.Setenv(prefix+"DEBUG", "/root;/log;/opt")
os.Setenv(prefix+"VALUES", "1,2,4,5")
func setEnvConfig2() {
setEnvConfig1()
os.Setenv("CONFIG_ENV_TEST_SERVER", TEST_SERVER)
}
func unsetEnvConfig2() {
unsetEnvConfig1()
os.Unsetenv("CONFIG_ENV_TEST_SERVER")
}
func TestEnvConfig2(t *testing.T) {
setEnvConfig2()
defer unsetEnvConfig2()
conf := EnvConfig2{}
err := Unmarshal(&conf)
if err != nil {
t.Errorf("Can't unmarshal config2 from environemnt variables. %s",
err.Error())
return
}
if conf.Config1.Hostname != TEST_HOSTNAME {
t.Errorf("Expect Hostname: %s, but got: %s", TEST_HOSTNAME, conf.Config1.Hostname)
}
if conf.Config1.Port != TEST_PORT {
t.Errorf("Expect Port: %d, but got: %d", TEST_PORT, conf.Config1.Port)
}
if conf.Config1.User != TEST_USER {
t.Errorf("Expect User: %s, but got: %s", TEST_USER, conf.Config1.User)
}
if conf.Config1.Password != TEST_PASSWORD {
t.Errorf("Expect Password: %s, but got: %s", TEST_PASSWORD, conf.Config1.Password)
}
if conf.Config1.Home != "" {
t.Errorf("Expect Home is empty, but got: %s", conf.Config1.Home)
}
if conf.Server != TEST_SERVER {
t.Errorf("Expect Server: %s, but got: %s", TEST_SERVER, conf.Server)
}
defer os.Unsetenv(prefix + "PATHS")
defer os.Unsetenv(prefix + "DEBUG")
defer os.Unsetenv(prefix + "VALUES")
assert := assert.New(t)
conf := test.SlicesConfig{}
assert.NoError(Parse(&conf))
assert.Equal(3, len(conf.Paths))
assert.Equal("/var", conf.Paths[0])
assert.Equal("/usr", conf.Paths[1])
assert.Equal("/home", conf.Paths[2])
assert.Equal(3, len(conf.Debugs))
assert.Equal("/root", conf.Debugs[0])
assert.Equal("/log", conf.Debugs[1])
assert.Equal("/opt", conf.Debugs[2])
assert.Equal(4, len(conf.Values))
assert.Equal(1, conf.Values[0])
assert.Equal(2, conf.Values[1])
assert.Equal(4, conf.Values[2])
assert.Equal(5, conf.Values[3])
}

BIN
main

Binary file not shown.

114
main.go
View File

@ -1,114 +0,0 @@
package main
import (
"flag"
"fmt"
"os"
//"github.com/eschao/Config/cli"
)
type DBCli struct {
Host string `cli:"hostname database server hostname"`
Port string `cli:"port database server port"`
User string `cli:"user database username"`
Password string `cli:"password database user password"`
}
func main() {
countCommand := flag.NewFlagSet("count", flag.ExitOnError)
listCommand := flag.NewFlagSet("list", flag.ExitOnError)
// Count subcommand flag pointers
// Adding a new choice for --metric of 'substring' and a new --substring flag
countTextPtr := countCommand.String("text", "", "Text to parse. (Required)")
countMetricPtr := countCommand.String("metric", "chars", "Metric {chars|words|lines|substring}. (Required)")
countSubstringPtr := countCommand.String("substring", "", "The substring to be counted. Required for --metric=substring")
countUniquePtr := countCommand.Bool("unique", false, "Measure unique values of a metric.")
// List subcommand flag pointers
listTextPtr := listCommand.String("text", "", "Text to parse. (Required)")
listMetricPtr := listCommand.String("metric", "chars", "Metric <chars|words|lines>. (Required)")
listUniquePtr := listCommand.Bool("unique", false, "Measure unique values of a metric.")
// Verify that a subcommand has been provided
// os.Arg[0] is the main command
// os.Arg[1] will be the subcommand
if len(os.Args) < 2 {
//fmt.Println("list or count subcommand is required")
flag.PrintDefaults()
os.Exit(1)
}
// Switch on the subcommand
// Parse the flags for appropriate FlagSet
// FlagSet.Parse() requires a set of arguments to parse as input
// os.Args[2:] will be all arguments starting after the subcommand at os.Args[1]
switch os.Args[1] {
case "list":
listCommand.Parse(os.Args[2:])
case "count":
countCommand.Parse(os.Args[2:])
default:
flag.PrintDefaults()
os.Exit(1)
}
// Check which subcommand was Parsed using the FlagSet.Parsed() function. Handle each case accordingly.
// FlagSet.Parse() will evaluate to false if no flags were parsed (i.e. the user did not provide any flags)
if listCommand.Parsed() {
// Required Flags
if *listTextPtr == "" {
listCommand.PrintDefaults()
os.Exit(1)
}
//Choice flag
metricChoices := map[string]bool{"chars": true, "words": true, "lines": true}
if _, validChoice := metricChoices[*listMetricPtr]; !validChoice {
listCommand.PrintDefaults()
os.Exit(1)
}
// Print
fmt.Printf("textPtr: %s, metricPtr: %s, uniquePtr: %t\n", *listTextPtr, *listMetricPtr, *listUniquePtr)
}
if countCommand.Parsed() {
// Required Flags
if *countTextPtr == "" {
countCommand.PrintDefaults()
os.Exit(1)
}
// If the metric flag is substring, the substring flag is required
if *countMetricPtr == "substring" && *countSubstringPtr == "" {
countCommand.PrintDefaults()
os.Exit(1)
}
//If the metric flag is not substring, the substring flag must not be used
if *countMetricPtr != "substring" && *countSubstringPtr != "" {
fmt.Println("--substring may only be used with --metric=substring.")
countCommand.PrintDefaults()
os.Exit(1)
}
//Choice flag
metricChoices := map[string]bool{"chars": true, "words": true, "lines": true, "substring": true}
if _, validChoice := metricChoices[*listMetricPtr]; !validChoice {
countCommand.PrintDefaults()
os.Exit(1)
}
//Print
fmt.Printf("textPtr: %s, metricPtr: %s, substringPtr: %v, uniquePtr: %t\n", *countTextPtr, *countMetricPtr, *countSubstringPtr, *countUniquePtr)
}
/*
config := DBCli{}
root := cli.CliFlag{}
err := cli.Parse(&config, &root)
if err != nil {
fmt.Printf("Can't parse cli. %s", err.Error())
}
flag.Parse()
fmt.Printf("Host=%s\n", config.Host)
*/
}

View File

@ -1,4 +0,0 @@
{
"name": "jsonconfig",
"path": "/var"
}

10
test/config.json Normal file
View File

@ -0,0 +1,10 @@
{
"dbHost": "test-db-host",
"dbPort": 9090,
"dbUser": "test-db-user",
"dbPassword": "test-db-password",
"log": {
"path": "/var/log/db",
"level": "error"
}
}

8
test/config.yaml Normal file
View File

@ -0,0 +1,8 @@
dbHost: test-db-host
dbPort: 9090
dbUser: test-db-user
dbPassword: test-db-password
log:
path: /var/log/db
level: error

59
test/data.go Normal file
View File

@ -0,0 +1,59 @@
package test
type DBConfig struct {
Host string `json:"dbHost" yaml:"dbHost" env:"HOST" cmd:"dbHost database server hostname"`
Port int `json:"dbPort" yaml:"dbPort" env:"PORT" cmd:"dbPort database server port"`
User string `json:"dbUser" yaml:"dbUser" env:"USER" cmd:"dbUser database username"`
Password string `json:"dbPassword" yaml:"dbPassword" env:"PASSWORD" cmd:"dbPassword database user password"`
Log LogConfig `json:"log" yaml:"log" env:"LOG_" cmd:"log database log configuration"`
}
type LoginConfig struct {
User string `json:"user" yaml:"user" env:"USER" cmd:"user login username"`
Password string `json:"password" yaml:"password" env:"PASSWORD" cmd:"password login password"`
}
type LogConfig struct {
Path string `json:"path" yaml:"path" env:"PATH" cmd:"path log path"`
Level string `json:"level" yaml:"level" env:"LEVEL" cmd:"level log level {debug|warning|error}"`
}
type ServiceConfig struct {
Host string `env:"CONFIG_TEST_SERVICE_HOST" cmd:"hostname service hostname"`
Port int `env:"CONFIG_TEST_SERVICE_PORT" cmd:"port service port"`
DBConfig DBConfig `env:"CONFIG_TEST_SERVICE_DB_" cmd:"database database configuration"`
Login *LoginConfig `env:"CONFIG_TEST_SERVICE_LOGIN_" cmd:"login login user and password"`
Log LogConfig `env:"CONFIG_TEST_SERVICE_LOG_" cmd:"log service log configuration"`
}
type TypesConfig struct {
BoolValue bool `env:"CONFIG_TEST_BOOL" cmd:"bool boolean value"`
StrValue string `env:"CONFIG_TEST_STR" cmd:"str string value"`
Int8Value int8 `env:"CONFIG_TEST_INT8" cmd:"int8 int8 value"`
Int16Value int16 `env:"CONFIG_TEST_INT16" cmd:"int16 int16 value"`
IntValue int `env:"CONFIG_TEST_INT" cmd:"int int value"`
Int32Value int32 `env:"CONFIG_TEST_INT32" cmd:"int32 int32 value"`
Int64Value int64 `env:"CONFIG_TEST_INT64" cmd:"int64 int64 value"`
Uint8Value uint8 `env:"CONFIG_TEST_UINT8" cmd:"uint8 uint8 value"`
Uint16Value uint16 `env:"CONFIG_TEST_UINT16" cmd:"uint16 uint16 value"`
UintValue uint `env:"CONFIG_TEST_UINT" cmd:"uint uint value"`
Uint32Value uint32 `env:"CONFIG_TEST_UINT32" cmd:"uint32 uint32 value"`
Uint64Value uint64 `env:"CONFIG_TEST_UINT64" cmd:"uint64 uint64 value"`
Float32Value float32 `env:"CONFIG_TEST_FLOAT32" cmd:"float32 float32 value"`
Float64Value float64 `env:"CONFIG_TEST_FLOAT64" cmd:"float64 float64 value"`
}
type DefValueConfig struct {
BoolValue bool `env:"CONFIG_TEST_BOOL" cmd:"bool boolean value" default:"true"`
IntValue int `env:"CONFIG_TEST_INT" cmd:"int int value" default:"123"`
Float64Value float64 `env:"CONFIG_TEST_FLOAT64" cmd:"float64 float64 value" default:"123.4567"`
StrValue string `env:"CONFIG_TEST_STR" cmd:"str string value" default:"default-string"`
SliceValue []string `env:"CONFIG_TEST_SLICE" cmd:"slice slice values" default:"xx:yy:zz"`
NoDefValue string `env:"CONFIG_TEST_NO_DEFVALUE" cmd:"nodefvalue no default value"`
}
type SlicesConfig struct {
Paths []string `env:"CONFIG_TEST_SLICES_PATHS"`
Debugs []string `env:"CONFIG_TEST_SLICES_DEBUG" separator:";"`
Values []int `env:"CONFIG_TEST_SLICES_VALUES" separator:","`
}

View File

@ -1,8 +1,10 @@
package utils
import (
"fmt"
"reflect"
"strconv"
"strings"
)
func SetValueWithBool(v reflect.Value, boolValue string) error {
@ -45,69 +47,51 @@ func SetValueWithUintX(v reflect.Value, uintValue string, bitSize int) error {
return nil
}
/*
func setFieldValueWithSlice(name string, v reflect.Value, envValue string,
separator string) error {
data := strings.Split(envValue, separator)
func SetValueWithSlice(v reflect.Value, slice string, separator string) error {
data := strings.Split(slice, separator)
size := len(data)
if size > 0 {
slice := reflect.MakeSlice(v.Type(), size, size)
for i := 0; i < size; i++ {
ele := slice.Index(i)
kind := ele.Kind()
var err error
switch kind {
case reflect.Bool:
if err := setFieldValueWithBool(name, ele, data[i]); err != nil {
return err
}
err = SetValueWithBool(ele, data[i])
case reflect.String:
ele.SetString(data[i])
case reflect.Uint8:
if err := setFieldValueWithUintX(name, ele, data[i], 8); err != nil {
return err
}
err = SetValueWithUintX(ele, data[i], 8)
case reflect.Uint16:
if err := setFieldValueWithUintX(name, ele, data[i], 16); err != nil {
return err
}
err = SetValueWithUintX(ele, data[i], 16)
case reflect.Uint, reflect.Uint32:
if err := setFieldValueWithUintX(name, ele, data[i], 32); err != nil {
return err
}
err = SetValueWithUintX(ele, data[i], 32)
case reflect.Uint64:
if err := setFieldValueWithUintX(name, ele, data[i], 64); err != nil {
return err
}
err = SetValueWithUintX(ele, data[i], 64)
case reflect.Int8:
if err := setFieldValueWithIntX(name, ele, data[i], 8); err != nil {
return err
}
err = SetValueWithIntX(ele, data[i], 8)
case reflect.Int16:
if err := setFieldValueWithIntX(name, ele, data[i], 16); err != nil {
return err
}
err = SetValueWithIntX(ele, data[i], 16)
case reflect.Int, reflect.Int32:
if err := setFieldValueWithIntX(name, ele, data[i], 32); err != nil {
return err
}
err = SetValueWithIntX(ele, data[i], 32)
case reflect.Int64:
if err := setFieldValueWithIntX(name, ele, data[i], 64); err != nil {
return err
}
err = SetValueWithIntX(ele, data[i], 64)
case reflect.Float32:
if err := setFieldValueWithFloatX(name, ele, data[i], 32); err != nil {
return err
}
err = SetValueWithFloatX(ele, data[i], 32)
case reflect.Float64:
if err := setFieldValueWithFloatX(name, ele, data[i], 64); err != nil {
return err
}
err = SetValueWithFloatX(ele, data[i], 64)
default:
return fmt.Errorf("%s: can't support type: %s", name, kind.String())
return fmt.Errorf("Can't support type: %s", kind.String())
}
if err != nil {
return err
}
}
v.Set(slice)
}
return nil
}*/
}