Добавлены функции вывода в поток описания параметров.
All checks were successful
test / test (push) Successful in 58s
All checks were successful
test / test (push) Successful in 58s
This commit is contained in:
parent
ce3402b295
commit
16ce42d26c
@ -184,6 +184,29 @@ linters-settings:
|
|||||||
- name: unexported-return
|
- name: unexported-return
|
||||||
disabled: true
|
disabled: true
|
||||||
|
|
||||||
|
varnamelen:
|
||||||
|
# The longest distance, in source lines, that is being considered a "small scope".
|
||||||
|
# Variables used in at most this many lines will be ignored.
|
||||||
|
# Default: 5
|
||||||
|
max-distance: 6
|
||||||
|
# The minimum length of a variable's name that is considered "long".
|
||||||
|
# Variable names that are at least this long will be ignored.
|
||||||
|
# Default: 3
|
||||||
|
min-name-length: 2
|
||||||
|
# Ignore "ok" variables that hold the bool return value of a type assertion.
|
||||||
|
# Default: false
|
||||||
|
ignore-type-assert-ok: true
|
||||||
|
# Ignore "ok" variables that hold the bool return value of a map index.
|
||||||
|
# Default: false
|
||||||
|
ignore-map-index-ok: true
|
||||||
|
# Ignore "ok" variables that hold the bool return value of a channel receive.
|
||||||
|
# Default: false
|
||||||
|
ignore-chan-recv-ok: true
|
||||||
|
# Optional list of variable names that should be ignored completely.
|
||||||
|
# Default: []
|
||||||
|
ignore-names:
|
||||||
|
- err
|
||||||
|
|
||||||
# output configuration options
|
# output configuration options
|
||||||
output:
|
output:
|
||||||
# The formats used to render issues.
|
# The formats used to render issues.
|
||||||
|
13
cli.go
13
cli.go
@ -18,6 +18,7 @@ package config
|
|||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
@ -313,3 +314,15 @@ func (c *Command) Parse(args []string) error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PrintUsage prints command description
|
||||||
|
func (c *Command) PrintUsage(w io.Writer) error {
|
||||||
|
if _, err := w.Write([]byte(c.Usage + "\n")); err != nil {
|
||||||
|
return fmt.Errorf("write usage: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.FlagSet.SetOutput(w)
|
||||||
|
c.FlagSet.Usage()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
16
cli_test.go
16
cli_test.go
@ -17,6 +17,7 @@ package config
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@ -29,9 +30,7 @@ func TestServiceCommand(t *testing.T) {
|
|||||||
serviceConfig := test.ServiceConfig{}
|
serviceConfig := test.ServiceConfig{}
|
||||||
cmd := NewCLI("Service")
|
cmd := NewCLI("Service")
|
||||||
err := cmd.Init(&serviceConfig)
|
err := cmd.Init(&serviceConfig)
|
||||||
if err != nil {
|
assert.NoError(err, "init service command")
|
||||||
t.Errorf("Can't init service command. %s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
// assert service cmd
|
// assert service cmd
|
||||||
assert.NotNil(cmd.FlagSet)
|
assert.NotNil(cmd.FlagSet)
|
||||||
@ -62,6 +61,15 @@ func TestServiceCommand(t *testing.T) {
|
|||||||
assert.NotNil(cmd.FlagSet.Lookup("log-level"))
|
assert.NotNil(cmd.FlagSet.Lookup("log-level"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPrintUsage(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
serviceConfig := test.ServiceConfig{}
|
||||||
|
cmd := NewCLI("Service")
|
||||||
|
err := cmd.Init(&serviceConfig)
|
||||||
|
assert.NoError(err, "init service command")
|
||||||
|
assert.NoError(cmd.PrintUsage(os.Stdout))
|
||||||
|
}
|
||||||
|
|
||||||
func TestLoginSubCommand(t *testing.T) {
|
func TestLoginSubCommand(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
serviceConfig := test.ServiceConfig{Login: &test.LoginConfig{}}
|
serviceConfig := test.ServiceConfig{Login: &test.LoginConfig{}}
|
||||||
@ -256,5 +264,3 @@ func TestCommandWithSlices(t *testing.T) {
|
|||||||
assert.Equal(200, conf.Values[1])
|
assert.Equal(200, conf.Values[1])
|
||||||
assert.Equal(300, conf.Values[2])
|
assert.Equal(300, conf.Values[2])
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUsage(t *testing.T) {}
|
|
||||||
|
117
env.go
117
env.go
@ -16,21 +16,60 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ParseValueEnvFunc func to parse env value
|
||||||
|
type ParseValueEnvFunc func(value reflect.Value, prefix string) error
|
||||||
|
|
||||||
// Parse parses given structure interface, extracts environment definitions
|
// Parse parses given structure interface, extracts environment definitions
|
||||||
// from its tag and sets structure with defined environment variables
|
// from its tag and sets structure with defined environment variables
|
||||||
func ParseEnv(i interface{}) error {
|
func ParseEnv(out interface{}) error {
|
||||||
return ParseEnvWith(i, "")
|
return ParseEnvWith(out, "", parseValueEnv)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UsageEnv prints usage description of config i to out
|
// UsageEnv prints usage description of config i to out
|
||||||
func UsageEnv(out io.Writer, i interface{}) {
|
func UsageEnv(out io.Writer, in interface{}) error {
|
||||||
// STub
|
if _, err := out.Write([]byte("Environment Usage:\n")); err != nil {
|
||||||
|
return fmt.Errorf("write usage: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := bytes.NewBufferString("")
|
||||||
|
|
||||||
|
if err := ParseEnvWith(in, "", func(value reflect.Value, prefix string) error {
|
||||||
|
return usageValueEnv(buf, value, prefix)
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var tabPos int
|
||||||
|
|
||||||
|
for _, line := range strings.Split(strings.TrimRight(buf.String(), "\n"), "\n") {
|
||||||
|
if pos := strings.Index(line, "\t"); pos > tabPos {
|
||||||
|
tabPos = pos
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const TabSize = 8
|
||||||
|
|
||||||
|
for _, line := range strings.Split(strings.TrimRight(buf.String(), "\n"), "\n") {
|
||||||
|
if pos := strings.Index(line, "\t"); pos >= 0 {
|
||||||
|
if count := tabPos/TabSize - pos/TabSize; count > 0 {
|
||||||
|
line = line[:pos] + strings.Repeat("\t", count) + line[pos:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := out.Write([]byte(line + "\n")); err != nil {
|
||||||
|
return fmt.Errorf("write usage: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseWith parses with given structure interface and environment name prefix
|
// ParseWith parses with given structure interface and environment name prefix
|
||||||
@ -47,8 +86,8 @@ func UsageEnv(out io.Writer, i interface{}) {
|
|||||||
//
|
//
|
||||||
// The Server.DB.Host will be mapped to environment variable: DB_HOST which is
|
// 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
|
// concatenated from DB tag in Server struct and Host tag in Database struct
|
||||||
func ParseEnvWith(i interface{}, prefix string) error {
|
func ParseEnvWith(out interface{}, prefix string, parser ParseValueEnvFunc) error {
|
||||||
ptrRef := reflect.ValueOf(i)
|
ptrRef := reflect.ValueOf(out)
|
||||||
|
|
||||||
if ptrRef.IsNil() || ptrRef.Kind() != reflect.Ptr {
|
if ptrRef.IsNil() || ptrRef.Kind() != reflect.Ptr {
|
||||||
return fmt.Errorf("%w: %s",
|
return fmt.Errorf("%w: %s",
|
||||||
@ -61,7 +100,44 @@ func ParseEnvWith(i interface{}, prefix string) error {
|
|||||||
errExpectStructPointerInsteadOf, valueOfStruct.Kind().String())
|
errExpectStructPointerInsteadOf, valueOfStruct.Kind().String())
|
||||||
}
|
}
|
||||||
|
|
||||||
return parseValueEnv(valueOfStruct, prefix)
|
return parser(valueOfStruct, prefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
func usageValueEnv(out io.Writer, value reflect.Value, prefix string) error {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
typeOfStruct := value.Type()
|
||||||
|
|
||||||
|
for i := 0; i < value.NumField() && err == nil; i++ {
|
||||||
|
valueOfField := value.Field(i)
|
||||||
|
kindOfField := valueOfField.Kind()
|
||||||
|
structOfField := typeOfStruct.Field(i)
|
||||||
|
|
||||||
|
// Recursively unmarshal if value is ptr type
|
||||||
|
if kindOfField == reflect.Ptr {
|
||||||
|
if !valueOfField.IsNil() && valueOfField.CanSet() {
|
||||||
|
err = ParseEnvWith(
|
||||||
|
valueOfField.Interface(),
|
||||||
|
prefix+structOfField.Tag.Get("env"),
|
||||||
|
func(value reflect.Value, prefix string) error {
|
||||||
|
return usageValueEnv(out, value, prefix)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
} else if kindOfField == reflect.Struct {
|
||||||
|
err = usageValueEnv(out, valueOfField, prefix+structOfField.Tag.Get("env"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = usageFieldValueEnv(out, structOfField, prefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseValue parses a reflect.Value object
|
// parseValue parses a reflect.Value object
|
||||||
@ -78,8 +154,11 @@ func parseValueEnv(value reflect.Value, prefix string) error {
|
|||||||
// Recursively unmarshal if value is ptr type
|
// Recursively unmarshal if value is ptr type
|
||||||
if kindOfField == reflect.Ptr {
|
if kindOfField == reflect.Ptr {
|
||||||
if !valueOfField.IsNil() && valueOfField.CanSet() {
|
if !valueOfField.IsNil() && valueOfField.CanSet() {
|
||||||
err = ParseEnvWith(valueOfField.Interface(),
|
err = ParseEnvWith(
|
||||||
prefix+structOfField.Tag.Get("env"))
|
valueOfField.Interface(),
|
||||||
|
prefix+structOfField.Tag.Get("env"),
|
||||||
|
parseValueEnv,
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -160,3 +239,23 @@ func setFieldValueEnv(value reflect.Value, field reflect.StructField, prefix str
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func usageFieldValueEnv(out io.Writer, field reflect.StructField, prefix string) error {
|
||||||
|
envName := field.Tag.Get("env")
|
||||||
|
if envName == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
envName = prefix + envName
|
||||||
|
msg := " " + envName + "\t" + field.Tag.Get("usage")
|
||||||
|
|
||||||
|
if def := field.Tag.Get("default"); def != "" {
|
||||||
|
msg += " (default: " + def + ")"
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := out.Write([]byte(msg + "\n")); err != nil {
|
||||||
|
return fmt.Errorf("write usage: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -75,7 +75,7 @@ func TestLoginConfigEnvWithPrefix(t *testing.T) {
|
|||||||
defer func() { _ = os.Unsetenv("DB_PASSWORD") }()
|
defer func() { _ = os.Unsetenv("DB_PASSWORD") }()
|
||||||
|
|
||||||
loginConfig := test.LoginConfig{}
|
loginConfig := test.LoginConfig{}
|
||||||
assert.NoError(ParseEnvWith(&loginConfig, "DB_"))
|
assert.NoError(ParseEnvWith(&loginConfig, "DB_", parseValueEnv))
|
||||||
assert.Equal(LOGIN_USER, loginConfig.User)
|
assert.Equal(LOGIN_USER, loginConfig.User)
|
||||||
assert.Equal(LOGIN_PASSWORD, loginConfig.Password)
|
assert.Equal(LOGIN_PASSWORD, loginConfig.Password)
|
||||||
}
|
}
|
||||||
@ -142,6 +142,11 @@ func TestServiceConfigEnv(t *testing.T) {
|
|||||||
assert.Equal(DB_LOG_LEVEL, serviceConfig.DBConfig.Log.Level)
|
assert.Equal(DB_LOG_LEVEL, serviceConfig.DBConfig.Log.Level)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestEnvUsage(t *testing.T) {
|
||||||
|
conf := test.ServiceConfig{}
|
||||||
|
assert.NoError(t, UsageEnv(os.Stdout, &conf))
|
||||||
|
}
|
||||||
|
|
||||||
func TestServiceLoginConfigEnv(t *testing.T) {
|
func TestServiceLoginConfigEnv(t *testing.T) {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user