diff --git a/.gitignore b/.gitignore index c633d30..74dc80e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,49 +1,40 @@ -# Compiled Object files, Static and Dynamic libs (Shared Objects) -*.o -*.a -*.so -*.swp -main - -# Folders -_obj -_test - -# Architecture specific extensions/prefixes -*.[568vq] -[568vq].out - -*.cgo1.go -*.cgo2.c -_cgo_defun.c -_cgo_gotypes.go -_cgo_export.* - -_testmain.go - +# ---> Go +# If you prefer the allow list template instead of the deny list, see community template: +# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore +# +# Binaries for programs and plugins *.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` *.test -*.prof -# OS generated files # -.DS_Store -.DS_Store? -._* -.Spotlight-V100 -.Trashes -ehthumbs.db -Thumbs.db +# Output of the go coverage tool, specifically when used with LiteIDE +*.out -# Eclipse generated files # -.project -.classpath -.settings -**/target/ -*/bin/ -.metadata/ -clientdb.xml -# jazz files # -.jazzignore -.factorypath -.gitignore +# Dependency directories (remove the comment below to include it) +vendor/ +# Go workspace file +go.work + +# ---> VisualStudioCode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets + +# Local History for Visual Studio Code +.history/ + +# Built Visual Studio Code Extensions +*.vsix + +# Build outputs +tmp/ +out/ diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..7e5bbad --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "cSpell.words": [ + "eschao", + "stretchr" + ] +} \ No newline at end of file diff --git a/README.md b/README.md index 879401d..0e56bc9 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ ## Installation 1. Install [Yaml](https://github.com/go-yaml/yaml) library first: ``` -go get gopkg.in/yaml.v2 +go get gopkg.in/yaml.v3 ``` 2. Install **config** library: @@ -16,14 +16,14 @@ go get github.com/eschao/config ### I. Define configuration name in structure tags Like JSON, Yaml, **config** uses tags to define configurations: -| Tag | Example | Function | -|-----|---------|------| -| json | Host string `json:"host"` | Maps `Host` to a JSON field: **host** | -| yaml | Host string `yaml:"host"` | Maps `Host` to a Yaml field: **host** | -| env | Host string `env:"HOST"` | Maps `Host` to a Environment variable: **HOST** | -| cli | Host string `cli:"host database host"` | Maps `Host` to a command line argument: **-host** or **--host** | -| default | Port int `default:"8080"` | Defines the port with default value: **8080** | -| separator | Path string `json:"path" separator:";"` | Separator is used to split string to a slice | +| Tag | Example | Function | +| --------- | --------------------------------------- | --------------------------------------------------------------- | +| json | Host string `json:"host"` | Maps `Host` to a JSON field: **host** | +| yaml | Host string `yaml:"host"` | Maps `Host` to a Yaml field: **host** | +| env | Host string `env:"HOST"` | Maps `Host` to a Environment variable: **HOST** | +| cli | Host string `cli:"host database host"` | Maps `Host` to a command line argument: **-host** or **--host** | +| default | Port int `default:"8080"` | Defines the port with default value: **8080** | +| separator | Path string `json:"path" separator:";"` | Separator is used to split string to a slice | #### 1. Data types diff --git a/cli/cli.go b/cli/cli.go index 1f6dcab..89cb14e 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -37,60 +37,60 @@ func newAnyValue(v reflect.Value) *anyValue { return &anyValue{any: v} } -func (this *anyValue) String() string { - kind := this.any.Kind() +func (v *anyValue) String() string { + kind := v.any.Kind() switch kind { case reflect.Bool: - return strconv.FormatBool(this.any.Bool()) + return strconv.FormatBool(v.any.Bool()) case reflect.String: - return this.any.String() + return v.any.String() case reflect.Int8, reflect.Int16, reflect.Int, reflect.Int32, reflect.Int64: - return strconv.FormatInt(this.any.Int(), 10) + return strconv.FormatInt(v.any.Int(), 10) case reflect.Uint8, reflect.Uint16, reflect.Uint, reflect.Uint32, reflect.Uint64: - return strconv.FormatUint(this.any.Uint(), 10) + return strconv.FormatUint(v.any.Uint(), 10) case reflect.Float32: - return strconv.FormatFloat(this.any.Float(), 'E', -1, 32) + return strconv.FormatFloat(v.any.Float(), 'E', -1, 32) case reflect.Float64: - return strconv.FormatFloat(this.any.Float(), 'E', -1, 64) + return strconv.FormatFloat(v.any.Float(), 'E', -1, 64) } - return fmt.Sprintf("unsupport type %s", kind.String()) + return fmt.Sprintf("unsupported type: %s", kind.String()) } -func (this *anyValue) Set(v string) error { - kind := this.any.Kind() +func (av *anyValue) Set(v string) error { + kind := av.any.Kind() switch kind { case reflect.String: - this.any.SetString(v) + av.any.SetString(v) case reflect.Float32: - return utils.SetValueWithFloatX(this.any, v, 32) + return utils.SetValueWithFloatX(av.any, v, 32) case reflect.Float64: - return utils.SetValueWithFloatX(this.any, v, 64) + return utils.SetValueWithFloatX(av.any, v, 64) case reflect.Int8: - return utils.SetValueWithIntX(this.any, v, 8) + return utils.SetValueWithIntX(av.any, v, 8) case reflect.Int16: - return utils.SetValueWithIntX(this.any, v, 16) + return utils.SetValueWithIntX(av.any, v, 16) case reflect.Int, reflect.Int32: - return utils.SetValueWithIntX(this.any, v, 32) + return utils.SetValueWithIntX(av.any, v, 32) case reflect.Int64: - return utils.SetValueWithIntX(this.any, v, 64) + return utils.SetValueWithIntX(av.any, v, 64) case reflect.Uint8: - return utils.SetValueWithUintX(this.any, v, 8) + return utils.SetValueWithUintX(av.any, v, 8) case reflect.Uint16: - return utils.SetValueWithUintX(this.any, v, 16) + return utils.SetValueWithUintX(av.any, v, 16) case reflect.Uint, reflect.Uint32: - return utils.SetValueWithUintX(this.any, v, 32) + return utils.SetValueWithUintX(av.any, v, 32) case reflect.Uint64: - return utils.SetValueWithUintX(this.any, v, 64) + return utils.SetValueWithUintX(av.any, v, 64) default: - return fmt.Errorf("Can't support type %s", kind.String()) + return fmt.Errorf("unsupported type: %s", kind.String()) } return nil @@ -107,19 +107,19 @@ func newSliceValue(v reflect.Value, separator string) *sliceValue { return &sliceValue{value: v, separator: separator} } -func (this *sliceValue) String() string { - return this.value.String() +func (sv *sliceValue) String() string { + return sv.value.String() } -func (this *sliceValue) Set(v string) error { - sp := this.separator +func (sv *sliceValue) Set(v string) error { + sp := sv.separator if sp == "" { sp = ":" } - return utils.SetValueWithSlice(this.value, v, sp) + return utils.SetValueWithSlice(sv.value, v, sp) } -// errorHanling is a global flag.ErrorHandling +// errorHandling is a global flag.ErrorHandling var errorHandling = flag.ExitOnError // UsageFunc defines a callback function for printing command usage @@ -171,25 +171,25 @@ func NewWith(name string, errHandling flag.ErrorHandling, // Init analyzes the given structure interface, extracts cli definitions from // its tag and installs command flagset by flag APIs. The interface must be a // structure pointer, otherwise will return an error -func (this *Command) Init(i interface{}) error { +func (c *Command) Init(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", + 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", + return fmt.Errorf("expect a structure type instead of %s", valueOfStruct.Kind().String()) } - return this.parseValue(valueOfStruct) + return c.parseValue(valueOfStruct) } // parseValue parses a reflect.Value object and extracts cli definitions -func (this *Command) parseValue(v reflect.Value) error { +func (c *Command) parseValue(v reflect.Value) error { typeOfStruct := v.Type() var err error @@ -200,14 +200,14 @@ func (this *Command) parseValue(v reflect.Value) error { if kindOfField == reflect.Ptr { if !valueOfField.IsNil() && valueOfField.CanSet() { - cmd := this.createSubCommand(structOfField.Tag) + cmd := c.createSubCommand(structOfField.Tag) err = cmd.Init(valueOfField.Interface()) } } else if kindOfField == reflect.Struct { - cmd := this.createSubCommand(structOfField.Tag) + cmd := c.createSubCommand(structOfField.Tag) err = cmd.parseValue(valueOfField) } else { - err = this.addFlag(valueOfField, structOfField) + err = c.addFlag(valueOfField, structOfField) } } @@ -215,7 +215,7 @@ func (this *Command) parseValue(v reflect.Value) error { } // addFlag installs a command flag variable by flag API -func (this *Command) addFlag(v reflect.Value, f reflect.StructField) error { +func (c *Command) addFlag(v reflect.Value, f reflect.StructField) error { cmdTag, ok := f.Tag.Lookup("cli") if !ok || cmdTag == "" { return nil @@ -232,7 +232,7 @@ func (this *Command) addFlag(v reflect.Value, f reflect.StructField) error { kind := v.Kind() switch kind { case reflect.Bool: - this.FlagSet.BoolVar((*bool)(unsafe.Pointer(v.UnsafeAddr())), name, + c.FlagSet.BoolVar((*bool)(unsafe.Pointer(v.UnsafeAddr())), name, false, usage) return nil case reflect.String, @@ -249,22 +249,22 @@ func (this *Command) addFlag(v reflect.Value, f reflect.StructField) error { reflect.Float32, reflect.Float64: anyValue := newAnyValue(v) - this.FlagSet.Var(anyValue, name, usage) + c.FlagSet.Var(anyValue, name, usage) case reflect.Slice: sliceValue := newSliceValue(v, f.Tag.Get("separator")) - this.FlagSet.Var(sliceValue, name, usage) + c.FlagSet.Var(sliceValue, name, usage) default: - return fmt.Errorf("Can't support type %s", kind.String()) + return fmt.Errorf("unsupported type: %s", kind.String()) } return nil } // createSubCommand creates sub-commands -func (this *Command) createSubCommand(tag reflect.StructTag) *Command { +func (c *Command) createSubCommand(tag reflect.StructTag) *Command { cmdTag, ok := tag.Lookup("cli") if !ok || cmdTag == "" { - return this + return c } cmd := Command{SubCommands: make(map[string]*Command)} @@ -284,29 +284,29 @@ func (this *Command) createSubCommand(tag reflect.StructTag) *Command { cmd.FlagSet.Usage = usageHandler(&cmd) } - this.SubCommands[name] = &cmd + c.SubCommands[name] = &cmd return &cmd } // Parse parses values from command line and save values into given structure. // The Init(interface{}) function must be called before parsing -func (this *Command) Parse(args []string) error { - if err := this.FlagSet.Parse(args); err != nil { +func (c *Command) Parse(args []string) error { + if err := c.FlagSet.Parse(args); err != nil { return err } - unprocessed := this.FlagSet.Args() + unprocessed := c.FlagSet.Args() if len(unprocessed) < 1 { return nil } - if this.SubCommands == nil { - return fmt.Errorf("Command: %s is unsupport", unprocessed[0]) + if c.SubCommands == nil { + return fmt.Errorf("unsupported command: %s", unprocessed[0]) } - cmd := this.SubCommands[unprocessed[0]] + cmd := c.SubCommands[unprocessed[0]] if cmd == nil { - return fmt.Errorf("Command: %s is unsupport", unprocessed[0]) + return fmt.Errorf("unsupported command: %s", unprocessed[0]) } return cmd.Parse(unprocessed[1:]) diff --git a/cli/cli_test.go b/cli/cli_test.go index a240b6c..55c2f55 100644 --- a/cli/cli_test.go +++ b/cli/cli_test.go @@ -145,6 +145,7 @@ func TestVariousTypeCommand(t *testing.T) { typesConfig := test.TypesConfig{} cmd := NewWith("Types", flag.ContinueOnError, func(cmd *Command) func() { return func() { + // Stub } }) assert.NoError(cmd.Init(&typesConfig)) diff --git a/config.go b/config.go index 61729a6..7f97283 100644 --- a/config.go +++ b/config.go @@ -27,7 +27,7 @@ import ( "github.com/eschao/config/cli" "github.com/eschao/config/env" "github.com/eschao/config/utils" - "gopkg.in/yaml.v2" + "gopkg.in/yaml.v3" ) // Default configuration file @@ -51,13 +51,13 @@ 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", + 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", + return fmt.Errorf("expect a structure pointer type instead of %s", valueOfStruct.Kind().String()) } @@ -121,7 +121,7 @@ func parseValue(v reflect.Value) error { err = utils.SetValueWithSlice(valueOfField, defValue, sp) default: - return fmt.Errorf("Can't support type: %s", kind.String()) + return fmt.Errorf("unsupported type: %s", kind.String()) } } @@ -153,7 +153,7 @@ func ParseCli(i interface{}) error { // under the same folder with the fixed order: config.json, config.yaml and // config.properties func ParseConfig(i interface{}, configFlag string) error { - configFile := flag.String(configFlag, "", "Specifiy configuration file") + configFile := flag.String(configFlag, "", "Specify configuration file") flag.Parse() return ParseConfigFile(i, *configFile) } @@ -176,23 +176,23 @@ func ParseConfigFile(i interface{}, configFile string) error { switch configType { case JSONConfigType: - return parseJSON(i, configFile) + err = parseJSON(i, configFile) case YamlConfigType: - return parseYaml(i, configFile) + err = parseYaml(i, configFile) case PropConfigType: - return parseProp(i, configFile) + err = parseProp(i, configFile) default: - return fmt.Errorf("Can't support config file: %s", configFile) + err = fmt.Errorf("unsupported config file: %s", configFile) } - return nil + return err } // parseJSON parses JSON file and set structure with its value 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 fmt.Errorf("open json config file: %s", err.Error()) } return json.Unmarshal(raw, i) @@ -202,15 +202,15 @@ func parseJSON(i interface{}, jsonFile string) error { 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 fmt.Errorf("open yaml config file: %s", err.Error()) } return yaml.Unmarshal(raw, i) } // parseProp parses Properties file and set structure with its value -func parseProp(i interface{}, propFile string) error { - return fmt.Errorf("Properties config has not implemented!") +func parseProp(_ interface{}, _ /*propFile*/ string) error { + return fmt.Errorf("properties config is not implemented") } // getDefaultConfigFile returns a existing default config file. The checking @@ -219,7 +219,7 @@ func parseProp(i interface{}, propFile string) error { func getDefaultConfigFile() (string, error) { exe, err := os.Executable() if err != nil { - return "", fmt.Errorf("Can't find default config file. %s", err.Error()) + return "", fmt.Errorf("find default config file: %s", err.Error()) } path := filepath.Dir(exe) + string(filepath.Separator) @@ -242,7 +242,7 @@ func getDefaultConfigFile() (string, error) { return propConfig, nil } - return "", fmt.Errorf("No default config file found in path: %s", path) + return "", fmt.Errorf("default config file not found in path: %s", path) } // getConfigFileType analyzes config file extension name and return @@ -257,5 +257,5 @@ func getConfigFileType(configFile string) (string, error) { return PropConfigType, nil } - return "", fmt.Errorf("Can't support file type: %s", configFile) + return "", fmt.Errorf("unsupported file type: %s", configFile) } diff --git a/env/env.go b/env/env.go index 8343d3d..d923aa8 100644 --- a/env/env.go +++ b/env/env.go @@ -24,7 +24,7 @@ import ( ) // Parse parses given structure interface, extracts environment definitions -// from its tag and sets structure with defined environement variables +// from its tag and sets structure with defined environment variables func Parse(i interface{}) error { return ParseWith(i, "") } @@ -36,23 +36,24 @@ func Parse(i interface{}) error { // Host string `env:"HOST"` // } -// type Server struct { -// Server string `env:"SERVER"` -// DB Database `env:"DB_"` -// } +// type Server struct { +// Server string `env:"SERVER"` +// DB Database `env:"DB_"` +// } +// // The Server.DB.Host will be mapped to environment variable: DB_HOST which is // concatenated from DB tag in Server struct and Host tag in Database struct func ParseWith(i interface{}, prefix string) error { ptrRef := reflect.ValueOf(i) if ptrRef.IsNil() || ptrRef.Kind() != reflect.Ptr { - return fmt.Errorf("Expect a structure pointer type instead of %s", + 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", + return fmt.Errorf("expect a structure pointer type instead of %s", valueOfStruct.Kind().String()) } @@ -80,23 +81,16 @@ func parseValue(v reflect.Value, prefix string) error { err = parseValue(valueOfField, prefix+structOfField.Tag.Get("env")) } + if err != nil { + return err + } + err = setFieldValue(valueOfField, structOfField, prefix) } return err } -// getEnvValue get environment value -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") - } - - return envValue, ok -} - // setFieldValue sets a reflect.Value with environment value func setFieldValue(v reflect.Value, f reflect.StructField, prefix string) error { envName := f.Tag.Get("env") @@ -149,7 +143,7 @@ func setFieldValue(v reflect.Value, f reflect.StructField, prefix string) error err = utils.SetValueWithSlice(v, envValue, sp) default: - return fmt.Errorf("Can't support type: %s", kind.String()) + return fmt.Errorf("unsupported type: %s", kind.String()) } if err != nil { diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..7e69bac --- /dev/null +++ b/go.mod @@ -0,0 +1,14 @@ +module git.mousesoft.ru/ms/config + +go 1.23 + +require ( + github.com/eschao/config v0.1.0 + github.com/stretchr/testify v1.9.0 + gopkg.in/yaml.v3 v3.0.1 +) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..c34e9a8 --- /dev/null +++ b/go.sum @@ -0,0 +1,17 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/eschao/config v0.1.0 h1:vtlNamzs6dC9pE0zyplqql16PFUUlst3VttQ+IT2/rk= +github.com/eschao/config v0.1.0/go.mod h1:XMilcx0dPfk+tlJowGZPZdmdCRnd7AZuFhYA93tYBgA= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=