2
0

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

This commit is contained in:
Алексей Бадяев 2025-05-04 13:02:38 +07:00
parent 192724f3b8
commit ce2ec715be
Signed by: alexey
GPG Key ID: 686FBC1363E4AFAE
6 changed files with 85 additions and 33 deletions

View File

@ -19,15 +19,8 @@ jobs:
- name: set-up dependencies - name: set-up dependencies
run: | run: |
go install github.com/kisielk/errcheck@latest
go install honnef.co/go/tools/cmd/staticcheck@latest
go install github.com/sashamelentyev/usestdlibvars@latest
go install golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow@latest
make vendor make vendor
- name: lint
run: make lint
- name: golangci-lint - name: golangci-lint
uses: https://github.com/golangci/golangci-lint-action@v7 uses: https://github.com/golangci/golangci-lint-action@v7
with: with:

View File

@ -1,10 +1,24 @@
version: "0.2" version: "0.2"
ignorePaths: [] ignorePaths: []
dictionaryDefinitions: [] dictionaryDefinitions:
dictionaries: [] - name: custom-dictionary
path: ./.cspell/custom-dictionary.txt
addWords: true
dictionaries:
- custom-dictionary
words: [] words: []
ignoreWords: ignoreWords:
- GOCMD
- GOPATH
- GOTEST
- GOVET
- Txterm
- covermode
- coverprofile
- davecgh - davecgh
- difflib - difflib
- gocov
- pmezard - pmezard
- setaf
- tput
import: [] import: []

72
env.go
View File

@ -27,13 +27,16 @@ import (
// ParseValueEnvFunc func to parse env value // ParseValueEnvFunc func to parse env value
type ParseValueEnvFunc func(value reflect.Value, prefix string) error type ParseValueEnvFunc func(value reflect.Value, prefix string) error
// ParseFieldEnvFunc func to parse env field
type ParseFieldEnvFunc func(out io.Writer, field reflect.StructField, 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(out interface{}) error { func ParseEnv(out interface{}) error {
return ParseEnvWith(out, "", parseValueEnv) return ParseEnvWith(out, "", parseValueEnv)
} }
// UsageEnv prints usage description of config i to out // UsageEnv prints usage description of config in to out in text
func UsageEnv(out io.Writer, in interface{}) error { func UsageEnv(out io.Writer, in interface{}) error {
if _, err := out.Write([]byte("Environment Usage:\n")); err != nil { if _, err := out.Write([]byte("Environment Usage:\n")); err != nil {
return fmt.Errorf("write usage: %w", err) return fmt.Errorf("write usage: %w", err)
@ -42,7 +45,7 @@ func UsageEnv(out io.Writer, in interface{}) error {
buf := bytes.NewBufferString("") buf := bytes.NewBufferString("")
if err := ParseEnvWith(in, "", func(value reflect.Value, prefix string) error { if err := ParseEnvWith(in, "", func(value reflect.Value, prefix string) error {
return usageValueEnv(buf, value, prefix) return usageValueEnv(buf, value, prefix, usageFieldEnv)
}); err != nil { }); err != nil {
return err return err
} }
@ -72,6 +75,21 @@ func UsageEnv(out io.Writer, in interface{}) error {
return nil return nil
} }
// MarkdownEnv prints description of config in to out in markdown
func MarkdownEnv(out io.Writer, in interface{}) error {
msg := "# Environment variables\n\n"
msg += "| Name | Default | Required | Usage |\n"
msg += "| :--- | :------ | :------: | :---- |\n"
if _, err := out.Write([]byte(msg)); err != nil {
return fmt.Errorf("write markdown: %w", err)
}
return ParseEnvWith(in, "", func(value reflect.Value, prefix string) error {
return usageValueEnv(out, value, prefix, markdownFieldEnv)
})
}
// ParseWith parses with given structure interface and environment name prefix // ParseWith parses with given structure interface and environment name prefix
// It is normally used in nested structure. // It is normally used in nested structure.
// For example: we have such structure // For example: we have such structure
@ -103,7 +121,9 @@ func ParseEnvWith(out interface{}, prefix string, parser ParseValueEnvFunc) erro
return parser(valueOfStruct, prefix) return parser(valueOfStruct, prefix)
} }
func usageValueEnv(out io.Writer, value reflect.Value, prefix string) error { func usageValueEnv(
out io.Writer, value reflect.Value, prefix string, usageField ParseFieldEnvFunc,
) error {
var err error var err error
typeOfStruct := value.Type() typeOfStruct := value.Type()
@ -120,22 +140,18 @@ func usageValueEnv(out io.Writer, value reflect.Value, prefix string) error {
valueOfField.Interface(), valueOfField.Interface(),
prefix+structOfField.Tag.Get("env"), prefix+structOfField.Tag.Get("env"),
func(value reflect.Value, prefix string) error { func(value reflect.Value, prefix string) error {
return usageValueEnv(out, value, prefix) return usageValueEnv(out, value, prefix, usageField)
}, },
) )
} else { } else {
continue continue
} }
} else if kindOfField == reflect.Struct { } else if kindOfField == reflect.Struct {
err = usageValueEnv(out, valueOfField, prefix+structOfField.Tag.Get("env")) err = usageValueEnv(out, valueOfField, prefix+structOfField.Tag.Get("env"), usageField)
} }
if err != nil { if err == nil && kindOfField != reflect.Struct {
return err err = usageField(out, structOfField, prefix)
}
if kindOfField != reflect.Struct {
err = usageFieldValueEnv(out, structOfField, prefix)
} }
} }
@ -256,7 +272,7 @@ func setSimpleEnvValue(value reflect.Value, field reflect.StructField, str strin
return err return err
} }
func usageFieldValueEnv(out io.Writer, field reflect.StructField, prefix string) error { func usageFieldEnv(out io.Writer, field reflect.StructField, prefix string) error {
envName := field.Tag.Get("env") envName := field.Tag.Get("env")
if envName == "" { if envName == "" {
return nil return nil
@ -267,6 +283,8 @@ func usageFieldValueEnv(out io.Writer, field reflect.StructField, prefix string)
if def := field.Tag.Get("default"); def != "" { if def := field.Tag.Get("default"); def != "" {
msg += " (default: " + def + ")" msg += " (default: " + def + ")"
} else if req := field.Tag.Get("required"); req == "true" {
msg += " (required)"
} }
if _, err := out.Write([]byte(msg + "\n")); err != nil { if _, err := out.Write([]byte(msg + "\n")); err != nil {
@ -275,3 +293,33 @@ func usageFieldValueEnv(out io.Writer, field reflect.StructField, prefix string)
return nil return nil
} }
func markdownFieldEnv(out io.Writer, field reflect.StructField, prefix string) error {
const mark = "✅"
envName := field.Tag.Get("env")
if envName == "" {
return nil
}
envName = prefix + envName
msg := "| " + envName + " | "
if def := field.Tag.Get("default"); def != "" {
msg += def
}
msg += " | "
if req := field.Tag.Get("required"); req == "true" {
msg += mark
}
msg += " | " + strings.ReplaceAll(field.Tag.Get("usage"), "|", "|") + " |\n"
if _, err := out.Write([]byte(msg)); err != nil {
return fmt.Errorf("write markdown: %w", err)
}
return nil
}

View File

@ -147,6 +147,11 @@ func TestEnvUsage(t *testing.T) {
assert.NoError(t, UsageEnv(os.Stdout, &conf)) assert.NoError(t, UsageEnv(os.Stdout, &conf))
} }
func TestEnvMarkdown(t *testing.T) {
conf := test.ServiceConfig{}
assert.NoError(t, MarkdownEnv(os.Stdout, &conf))
}
func TestServiceLoginConfigEnv(t *testing.T) { func TestServiceLoginConfigEnv(t *testing.T) {
var err error var err error

View File

@ -82,11 +82,8 @@ endif
## Lint ## Lint
lint: ## Run all available linters. lint: ## Run all available linters.
go vet ./... @golangci-lint version
errcheck ./... @golangci-lint run
staticcheck ./...
# usestdlibvars ./...
shadow ./...
@$(ECHO_CMD) "Lint\t\t${GREEN}[OK]${RESET}" @$(ECHO_CMD) "Lint\t\t${GREEN}[OK]${RESET}"
.PHONY:lint .PHONY:lint
@ -97,11 +94,6 @@ golangci-lint-install: ## Install golangci-lint util
curl -sfL ${GOLANGCI_URL} | sh -s -- -b ${GOPATH}/bin ${GOLANGCI_VERSION} curl -sfL ${GOLANGCI_URL} | sh -s -- -b ${GOPATH}/bin ${GOLANGCI_VERSION}
.PHONY:lint-golangci-install .PHONY:lint-golangci-install
golangci-lint: ## Run golangci-lint linter
@golangci-lint run
@$(ECHO_CMD) "GolangCI Lint\t${GREEN}[OK]${RESET}"
.PHONY:golangci-lint
## Help ## Help
help: ## Show this help. help: ## Show this help.

View File

@ -39,8 +39,8 @@ type LogConfig struct {
} }
type ServiceConfig struct { type ServiceConfig struct {
Host string `cli:"hostname" env:"CONFIG_TEST_SERVICE_HOST" usage:"service hostname"` Host string `cli:"hostname" env:"CONFIG_TEST_SERVICE_HOST" required:"true" usage:"service hostname"`
Port int `cli:"port" env:"CONFIG_TEST_SERVICE_PORT" usage:"service port"` Port int `cli:"port" env:"CONFIG_TEST_SERVICE_PORT" required:"true" usage:"service port"`
DBConfig DBConfig `cli:"database" env:"CONFIG_TEST_SERVICE_DB_" usage:"database configuration"` DBConfig DBConfig `cli:"database" env:"CONFIG_TEST_SERVICE_DB_" usage:"database configuration"`
Login *LoginConfig `cli:"login" env:"CONFIG_TEST_SERVICE_LOGIN_" usage:"login user and password"` Login *LoginConfig `cli:"login" env:"CONFIG_TEST_SERVICE_LOGIN_" usage:"login user and password"`
Log LogConfig `cli:"log" env:"CONFIG_TEST_SERVICE_LOG_" usage:"service log configuration"` Log LogConfig `cli:"log" env:"CONFIG_TEST_SERVICE_LOG_" usage:"service log configuration"`