From 8f6edca65454782a349c28388bbefb8af1202e14 Mon Sep 17 00:00:00 2001 From: eschao Date: Fri, 8 Dec 2017 21:02:26 +0800 Subject: [PATCH] update --- .gitignore | 51 ++++++++-- cli/cli.go | 118 +++++++++++++++++++++++ cli/cli_test.go | 38 ++++++++ config.go | 47 ++++++++++ config_test.go | 27 ++++++ env/env.go | 243 ++++++++++++++++++++++++++++++++++++++++++++++++ env/env_test.go | 194 ++++++++++++++++++++++++++++++++++++++ main.go | 10 ++ test.json | 4 + 9 files changed, 723 insertions(+), 9 deletions(-) create mode 100644 cli/cli.go create mode 100644 cli/cli_test.go create mode 100644 config.go create mode 100644 config_test.go create mode 100644 env/env.go create mode 100644 env/env_test.go create mode 100644 main.go create mode 100644 test.json diff --git a/.gitignore b/.gitignore index a1338d6..778b7f5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,14 +1,47 @@ -# Binaries for programs and plugins -*.exe -*.dll +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a *.so -*.dylib -# Test binary, build with `go test -c` +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe *.test +*.prof -# Output of the go coverage tool, specifically when used with LiteIDE -*.out +# OS generated files # +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# Eclipse generated files # +.project +.classpath +.settings +**/target/ +*/bin/ +.metadata/ +clientdb.xml +# jazz files # +.jazzignore +.factorypath +.gitignore -# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 -.glide/ diff --git a/cli/cli.go b/cli/cli.go new file mode 100644 index 0000000..d5a9cde --- /dev/null +++ b/cli/cli.go @@ -0,0 +1,118 @@ +package cli + +import ( + "flag" + "fmt" + "reflect" + "strings" +) + +type CliFlag struct { + Name string + Usage string + DefValue *string + CliFlags *[]CliFlag +} + +type ValueFlag struct { + Value reflect.Value +} + +func (this *ValueFlag) String() string { + return this.Value.Kind().String() +} + +func (this *ValueFlag) Set(v string) error { + return nil +} + +func Parse(i interface{}, cliFlag *CliFlag) 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()) + } + + valueOfStruct := ptrRef.Elem() + if valueOfStruct.Kind() != reflect.Struct { + return fmt.Errorf("Expect a structure type instead of %s", + valueOfStruct.Kind().String()) + } + + return parseValue(valueOfStruct, cliFlag) +} + +func parseValue(v reflect.Value, cliParent *CliFlag) error { + cliFlags := []CliFlag{} + typeOfStruct := v.Type() + + for i := 0; i < v.NumField(); i++ { + valueOfField := v.Field(i) + kindOfField := valueOfField.Kind() + structOfField := typeOfStruct.Field(i) + + if kindOfField == reflect.Ptr { + if !valueOfField.IsNil() && valueOfField.CanSet() { + cliFlag := createCliFlagFromTag(structOfField.Tag) + if cliFlag != nil { + cliFlags = append(cliFlags, *cliFlag) + } else { + cliFlag = cliParent + } + Parse(valueOfField.Interface(), cliFlag) + } else { + continue + } + } else if kindOfField == reflect.Struct { + cliFlag := createCliFlagFromTag(structOfField.Tag) + if cliFlag != nil { + cliFlags = append(cliFlags, *cliFlag) + } else { + cliFlag = cliParent + } + parseValue(valueOfField, cliFlag) + } + + if cliFlag := installFlag(valueOfField, structOfField); cliFlag != nil { + cliFlags = append(cliFlags, *cliFlag) + } + } + + if len(cliFlags) > 0 { + cliParent.CliFlags = &cliFlags + } + return nil +} + +func installFlag(v reflect.Value, f reflect.StructField) *CliFlag { + cliFlag := createCliFlagFromTag(f.Tag) + if cliFlag != nil { + vFlag := ValueFlag{Value: v} + flag.Var(&vFlag, cliFlag.Name, cliFlag.Usage) + } + fmt.Printf("Installed flag: %d", cliFlag.Name) + return cliFlag +} + +func createCliFlagFromTag(tag reflect.StructTag) *CliFlag { + cliTag, ok := tag.Lookup("cli") + if !ok || cliTag == "" { + return nil + } + + cliFlag := CliFlag{} + firstSpace := strings.Index(cliTag, " ") + cliFlag.Name = cliTag + if firstSpace > 0 { + cliFlag.Name = cliTag[0:firstSpace] + cliFlag.Usage = cliTag[firstSpace+1:] + } + + defValue, ok := tag.Lookup("default") + if !ok { + cliFlag.DefValue = &defValue + } + + return &cliFlag +} diff --git a/cli/cli_test.go b/cli/cli_test.go new file mode 100644 index 0000000..3688c5a --- /dev/null +++ b/cli/cli_test.go @@ -0,0 +1,38 @@ +package cli + +import ( + "flag" + "fmt" + "testing" +) + +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"` +} + +type MyCli struct { + Server string `cli:"-server server URL"` + DBCli DBCli `cli:"database database information"` +} + +func TestMain(m *testing.M) { +} + +func TestCli(t *testing.T) { + + hostname := flag.String("hostanme", "127.0.0.1", "hostanme value") + fmt.Printf("Hostname: %s", hostname) + + cli := DBCli{} + root := CliFlag{} + err := Parse(&cli, &root) + + if err != nil { + t.Errorf("Can't parse cli. %s", err.Error()) + } + flag.Parse() + +} diff --git a/config.go b/config.go new file mode 100644 index 0000000..0b6455d --- /dev/null +++ b/config.go @@ -0,0 +1,47 @@ +package config + +import ( + "encoding/json" + "errors" + "io/ioutil" + "reflect" +) + +type Field struct { + JsonName string + YamlName string + PropName string + EnvName string + CliName string + Value reflect.Value + DefaultValue string + Separator string +} + +type Config struct { + Fields []Field +} + +func (this *Config) Init() *Config { + if this.Fields == nil { + this.Fields = []Field{} + } + + return this +} + +func (this *Config) ParseJSON(jsonFile string, data interface{}) error { + raw, err := ioutil.ReadFile(jsonFile) + if err != nil { + return errors.New("Can't open json file. Err: " + err.Error()) + } + + err = json.Unmarshal(raw, data) + if err != nil { + return errors.New("Failed unmarshal json. Err: " + err.Error()) + } + + //fmt.Printf("Data: %v", *data.(*interface{})) + + return nil +} diff --git a/config_test.go b/config_test.go new file mode 100644 index 0000000..63f3de5 --- /dev/null +++ b/config_test.go @@ -0,0 +1,27 @@ +package config + +import ( + "testing" +) + +type TestConfig struct { + Name string `json:"name" default:"test-name"` + Path string `json:"path" default:"./"` +} + +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()) + } + + if myConfig.Name != "jsonconfig" { + t.Errorf("Name json value: %s != jsonconfig", myConfig.Name) + } + + if myConfig.Path != "/var" { + t.Errorf("Path json value: %s != /var", myConfig.Path) + } +} diff --git a/env/env.go b/env/env.go new file mode 100644 index 0000000..80a6537 --- /dev/null +++ b/env/env.go @@ -0,0 +1,243 @@ +package env + +import ( + "fmt" + "os" + "reflect" + "strconv" + "strings" +) + +func Unmarshal(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()) + } + + valueOfStruct := ptrRef.Elem() + if valueOfStruct.Kind() != reflect.Struct { + return fmt.Errorf("Expect a structure pointer type instead of %s", + valueOfStruct.Kind().String()) + } + + return unmarshal(valueOfStruct) +} + +func unmarshal(v reflect.Value) error { + typeOfStruct := v.Type() + for i := 0; i < v.NumField(); 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()) + } else { + continue + } + } else if kindOfField == reflect.Struct { + unmarshal(valueOfField) + } + + if err := setFieldValue(valueOfField, structOfField); err != nil { + return err + } + } + + return nil +} + +func getEnvValue(envName string, f reflect.StructField) (string, bool) { + envValue, ok := os.LookupEnv(envName) + if !ok { + envValue, ok = f.Tag.Lookup("default") + } + + return envValue, ok +} + +func setFieldValue(v reflect.Value, f reflect.StructField) error { + envName := f.Tag.Get("env") + if envName == "" { + return nil + } + + envValue, ok := getEnvValue(envName, f) + if !ok { + return nil + } + + if !v.CanSet() { + return fmt.Errorf("%s: can't be set", f.Name) + } + + kind := v.Kind() + name := f.Name + switch kind { + case reflect.Bool: + return setFieldValueWithBool(name, v, envValue) + + case reflect.String: + v.SetString(envValue) + return nil + + case reflect.Int8: + return setFieldValueWithIntX(name, v, envValue, 8) + + case reflect.Int16: + return setFieldValueWithIntX(name, v, envValue, 16) + + case reflect.Int, reflect.Int32: + return setFieldValueWithIntX(name, v, envValue, 32) + + case reflect.Int64: + return setFieldValueWithIntX(name, v, envValue, 64) + + case reflect.Uint8: + return setFieldValueWithUintX(name, v, envValue, 8) + + case reflect.Uint16: + return setFieldValueWithUintX(name, v, envValue, 16) + + case reflect.Uint, reflect.Uint32: + return setFieldValueWithUintX(name, v, envValue, 32) + + case reflect.Uint64: + return setFieldValueWithUintX(name, v, envValue, 64) + + case reflect.Float32: + return setFieldValueWithFloatX(name, v, envValue, 32) + + case reflect.Float64: + return setFieldValueWithFloatX(name, v, envValue, 64) + + case reflect.Slice: + sp, ok := f.Tag.Lookup("separator") + if !ok { + sp = ":" + } + return setFieldValueWithSlice(name, 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()) + } + + 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 +} diff --git a/env/env_test.go b/env/env_test.go new file mode 100644 index 0000000..fd6fd9e --- /dev/null +++ b/env/env_test.go @@ -0,0 +1,194 @@ +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 +} + +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" +) + +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 unsetEnvConfig1() { + os.Unsetenv("CONFIG_TEST_HOSTNAME") + os.Unsetenv("CONFIG_TEST_PORT") + os.Unsetenv("CONFIG_TEST_USER") + os.Unsetenv("CONFIG_TEST_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)) + } + + 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]) + } + } + + return true, nil +} + +func TestEnvConfig1(t *testing.T) { + setEnvConfig1() + defer unsetEnvConfig1() + + 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()) + } +} + +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() + + conf := EnvConfig1{} + err := Unmarshal(&conf) + if err != nil { + t.Errorf("Can't unmarshal config1 from environemnt variables. %s", + err.Error()) + return + } + + 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) + } +} + +type EnvConfig2 struct { + Config1 EnvConfig1 + Server string `env:"CONFIG_ENV_TEST_SERVER"` +} + +const ( + TEST_SERVER = "test-server" +) + +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) + } +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..4bce14b --- /dev/null +++ b/main.go @@ -0,0 +1,10 @@ +package main + +import ( + "fmt" +) + +func main() { + cli := DBCli{} + +} diff --git a/test.json b/test.json new file mode 100644 index 0000000..fc286de --- /dev/null +++ b/test.json @@ -0,0 +1,4 @@ +{ + "name": "jsonconfig", + "path": "/var" +}