2
0

Добавлены функции вывода в поток описания параметров.
All checks were successful
test / test (push) Successful in 58s

This commit is contained in:
Алексей Бадяев 2024-10-22 20:08:12 +07:00
parent ce3402b295
commit 16ce42d26c
Signed by: alexey
GPG Key ID: 686FBC1363E4AFAE
5 changed files with 161 additions and 15 deletions

View File

@ -184,6 +184,29 @@ linters-settings:
- name: unexported-return
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:
# The formats used to render issues.

13
cli.go
View File

@ -18,6 +18,7 @@ package config
import (
"flag"
"fmt"
"io"
"reflect"
"strconv"
"unsafe"
@ -313,3 +314,15 @@ func (c *Command) Parse(args []string) error {
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
}

View File

@ -17,6 +17,7 @@ package config
import (
"flag"
"os"
"strconv"
"testing"
@ -29,9 +30,7 @@ func TestServiceCommand(t *testing.T) {
serviceConfig := test.ServiceConfig{}
cmd := NewCLI("Service")
err := cmd.Init(&serviceConfig)
if err != nil {
t.Errorf("Can't init service command. %s", err.Error())
}
assert.NoError(err, "init service command")
// assert service cmd
assert.NotNil(cmd.FlagSet)
@ -62,6 +61,15 @@ func TestServiceCommand(t *testing.T) {
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) {
assert := assert.New(t)
serviceConfig := test.ServiceConfig{Login: &test.LoginConfig{}}
@ -256,5 +264,3 @@ func TestCommandWithSlices(t *testing.T) {
assert.Equal(200, conf.Values[1])
assert.Equal(300, conf.Values[2])
}
func TestUsage(t *testing.T) {}

117
env.go
View File

@ -16,21 +16,60 @@
package config
import (
"bytes"
"fmt"
"io"
"os"
"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
// from its tag and sets structure with defined environment variables
func ParseEnv(i interface{}) error {
return ParseEnvWith(i, "")
func ParseEnv(out interface{}) error {
return ParseEnvWith(out, "", parseValueEnv)
}
// UsageEnv prints usage description of config i to out
func UsageEnv(out io.Writer, i interface{}) {
// STub
func UsageEnv(out io.Writer, in interface{}) error {
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
@ -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
// concatenated from DB tag in Server struct and Host tag in Database struct
func ParseEnvWith(i interface{}, prefix string) error {
ptrRef := reflect.ValueOf(i)
func ParseEnvWith(out interface{}, prefix string, parser ParseValueEnvFunc) error {
ptrRef := reflect.ValueOf(out)
if ptrRef.IsNil() || ptrRef.Kind() != reflect.Ptr {
return fmt.Errorf("%w: %s",
@ -61,7 +100,44 @@ func ParseEnvWith(i interface{}, prefix string) error {
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
@ -78,8 +154,11 @@ func parseValueEnv(value reflect.Value, prefix string) error {
// 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"))
err = ParseEnvWith(
valueOfField.Interface(),
prefix+structOfField.Tag.Get("env"),
parseValueEnv,
)
} else {
continue
}
@ -160,3 +239,23 @@ func setFieldValueEnv(value reflect.Value, field reflect.StructField, prefix str
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
}

View File

@ -75,7 +75,7 @@ func TestLoginConfigEnvWithPrefix(t *testing.T) {
defer func() { _ = os.Unsetenv("DB_PASSWORD") }()
loginConfig := test.LoginConfig{}
assert.NoError(ParseEnvWith(&loginConfig, "DB_"))
assert.NoError(ParseEnvWith(&loginConfig, "DB_", parseValueEnv))
assert.Equal(LOGIN_USER, loginConfig.User)
assert.Equal(LOGIN_PASSWORD, loginConfig.Password)
}
@ -142,6 +142,11 @@ func TestServiceConfigEnv(t *testing.T) {
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) {
var err error