Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add functions for prefixed environment variables #65

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,30 @@ if err != nil {
}
```

### Read Environment Variables with Common Prefix

Some applications are configured with environment variables that share the same prefix. Prefixed
environment variables such as `APPNAME_PORT, APPNAME_HOST, ...` can be read without repeating the
prefix in struct tags:
```go
import github.com/ilyakaznacheev/cleanenv

type ConfigDatabase struct {
Port string `env:"PORT" env-default:"5432"`
Host string `env:"HOST" env-default:"localhost"`
Name string `env:"NAME" env-default:"postgres"`
User string `env:"USER" env-default:"user"`
Password string `env:"PASSWORD"`
}

var cfg ConfigDatabase

err := cleanenv.ReadEnvWithPrefix("appname", &cfg)
if err != nil {
...
}
```

### Update Environment Variables

Some environment variables may change during the application run. To get the new values you need to mark these variables as updatable with the tag `env-upd` and then run the update function:
Expand Down
66 changes: 57 additions & 9 deletions cleanenv.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,17 +89,40 @@ func ReadConfig(path string, cfg interface{}) error {
return err
}

return readEnvVars(cfg, false)
return readEnvVars(cfg, "", false)
}

// ReadEnv reads environment variables into the structure.
func ReadEnv(cfg interface{}) error {
return readEnvVars(cfg, false)
return readEnvVars(cfg, "", false)
}

// UpdateEnv rereads (updates) environment variables in the structure.
func UpdateEnv(cfg interface{}) error {
return readEnvVars(cfg, true)
return readEnvVars(cfg, "", true)
}

// ReadConfigWithPrefix reads configuration file and parses it depending on tags in structure provided.
// All environment variables are expected to begin with the provided prefix.
func ReadConfigWithPrefix(path string, prefix string, cfg interface{}) error {
err := parseFile(path, cfg)
if err != nil {
return err
}

return readEnvVars(cfg, prefix, false)
}

// ReadEnvWithPrefix reads environment variables into the structure.
// All environment variables are expected to begin with the provided prefix.
func ReadEnvWithPrefix(prefix string, cfg interface{}) error {
return readEnvVars(cfg, prefix, false)
}

// UpdateEnvWithPrefix rereads (updates) environment variables in the structure.
// All environment variables are expected to begin with the provided prefix.
func UpdateEnvWithPrefix(prefix string, cfg interface{}) error {
return readEnvVars(cfg, prefix, true)
}

// parseFile parses configuration file according to it's extension
Expand Down Expand Up @@ -199,7 +222,7 @@ func (sm *structMeta) isFieldValueZero() bool {
}

// readStructMetadata reads structure metadata (types, tags, etc.)
func readStructMetadata(cfgRoot interface{}) ([]structMeta, error) {
func readStructMetadata(cfgRoot interface{}, prefix string) ([]structMeta, error) {
cfgStack := []interface{}{cfgRoot}
metas := make([]structMeta, 0)

Expand Down Expand Up @@ -266,6 +289,13 @@ func readStructMetadata(cfgRoot interface{}) ([]structMeta, error) {
envList = strings.Split(envs, DefaultSeparator)
}

if prefix != "" {
prefixUpper := strings.ToUpper(prefix)
for i := 0; i < len(envList); i++ {
envList[i] = fmt.Sprintf("%s_%s", prefixUpper, envList[i])
}
}

metas = append(metas, structMeta{
envList: envList,
fieldName: s.Type().Field(idx).Name,
Expand All @@ -285,8 +315,8 @@ func readStructMetadata(cfgRoot interface{}) ([]structMeta, error) {
}

// readEnvVars reads environment variables to the provided configuration structure
func readEnvVars(cfg interface{}, update bool) error {
metaInfo, err := readStructMetadata(cfg)
func readEnvVars(cfg interface{}, prefix string, update bool) error {
metaInfo, err := readStructMetadata(cfg, prefix)
if err != nil {
return err
}
Expand Down Expand Up @@ -486,11 +516,29 @@ func parseMap(valueType reflect.Type, value string, sep string, layout *string)
// GetDescription returns a description of environment variables.
// You can provide a custom header text.
func GetDescription(cfg interface{}, headerText *string) (string, error) {
meta, err := readStructMetadata(cfg)
meta, err := readStructMetadata(cfg, "")
if err != nil {
return "", err
}

description := buildDescription(meta, headerText)
return description, nil
}

// GetDescriptionWithPrefix returns a description of environment variables.
// You can provide a custom header text and a custom environment variable prefix.
func GetDescriptionWithPrefix(cfg interface{}, prefix string, headerText *string) (string, error) {
meta, err := readStructMetadata(cfg, prefix)
if err != nil {
return "", err
}

description := buildDescription(meta, headerText)
return description, nil
}

// buildDescription returns a description of environment variables from struct metadata.
func buildDescription(meta []structMeta, headerText *string) string {
var header, description string

if headerText != nil {
Expand Down Expand Up @@ -519,9 +567,9 @@ func GetDescription(cfg interface{}, headerText *string) (string, error) {
}

if description != "" {
return header + description, nil
return header + description
}
return "", nil
return ""
}

// Usage returns a configuration usage help.
Expand Down