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
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
- name: lint
run: make lint
- name: golangci-lint
uses: https://github.com/golangci/golangci-lint-action@v7
with:

View File

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

72
env.go
View File

@ -27,13 +27,16 @@ import (
// ParseValueEnvFunc func to parse env value
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
// from its tag and sets structure with defined environment variables
func ParseEnv(out interface{}) error {
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 {
if _, err := out.Write([]byte("Environment Usage:\n")); err != nil {
return fmt.Errorf("write usage: %w", err)
@ -42,7 +45,7 @@ func UsageEnv(out io.Writer, in interface{}) error {
buf := bytes.NewBufferString("")
if err := ParseEnvWith(in, "", func(value reflect.Value, prefix string) error {
return usageValueEnv(buf, value, prefix)
return usageValueEnv(buf, value, prefix, usageFieldEnv)
}); err != nil {
return err
}
@ -72,6 +75,21 @@ func UsageEnv(out io.Writer, in interface{}) error {
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
// It is normally used in nested structure.
// For example: we have such structure
@ -103,7 +121,9 @@ func ParseEnvWith(out interface{}, prefix string, parser ParseValueEnvFunc) erro
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
typeOfStruct := value.Type()
@ -120,22 +140,18 @@ func usageValueEnv(out io.Writer, value reflect.Value, prefix string) error {
valueOfField.Interface(),
prefix+structOfField.Tag.Get("env"),
func(value reflect.Value, prefix string) error {
return usageValueEnv(out, value, prefix)
return usageValueEnv(out, value, prefix, usageField)
},
)
} else {
continue
}
} 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 {
return err
}
if kindOfField != reflect.Struct {
err = usageFieldValueEnv(out, structOfField, prefix)
if err == nil && kindOfField != reflect.Struct {
err = usageField(out, structOfField, prefix)
}
}
@ -256,7 +272,7 @@ func setSimpleEnvValue(value reflect.Value, field reflect.StructField, str strin
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")
if envName == "" {
return nil
@ -267,6 +283,8 @@ func usageFieldValueEnv(out io.Writer, field reflect.StructField, prefix string)
if def := field.Tag.Get("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 {
@ -275,3 +293,33 @@ func usageFieldValueEnv(out io.Writer, field reflect.StructField, prefix string)
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))
}
func TestEnvMarkdown(t *testing.T) {
conf := test.ServiceConfig{}
assert.NoError(t, MarkdownEnv(os.Stdout, &conf))
}
func TestServiceLoginConfigEnv(t *testing.T) {
var err error

View File

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

View File

@ -39,8 +39,8 @@ type LogConfig struct {
}
type ServiceConfig struct {
Host string `cli:"hostname" env:"CONFIG_TEST_SERVICE_HOST" usage:"service hostname"`
Port int `cli:"port" env:"CONFIG_TEST_SERVICE_PORT" usage:"service port"`
Host string `cli:"hostname" env:"CONFIG_TEST_SERVICE_HOST" required:"true" usage:"service hostname"`
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"`
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"`