diff --git a/README.md b/README.md index 8710627..98c266d 100644 --- a/README.md +++ b/README.md @@ -91,6 +91,7 @@ fmt.Println(cfg.HTTP.Port) // 8080 A nested struct can have the optional `env:"PREFIX"` tag. In this case, the environment variables declared by its fields are prefixed with PREFIX. This rule is applied recursively to all nested structs. +Nested field names are separated by a string defined in `NameSep` option, empty string by default. ```go os.Setenv("DB_HOST", "localhost") @@ -100,14 +101,16 @@ var cfg struct { DB struct { Host string `env:"HOST"` Port int `env:"PORT"` - } `env:"DB_"` + } `env:"DB"` } -if err := env.Load(&cfg, nil); err != nil { +if err := env.Load(&cfg, &env.Options{ + NameSep: "_", +}); err != nil { fmt.Println(err) } -fmt.Println(cfg.DB.Host) // localhost -fmt.Println(cfg.DB.Port) // 5432 +fmt.Println(cfg.DB.Host) +fmt.Println(cfg.DB.Port) ``` ### Default values @@ -182,6 +185,32 @@ if err := env.Load(&cfg, &env.Options{SliceSep: ","}); err != nil { fmt.Println(cfg.Ports) // [8080 8081 8082] ``` +### Names separator +By defaul variable names of nested structs are built with just simple concatenation. + +So given config like the following: + +```go +var cfg struct { + DB struct { + Host string `env:"HOST"` + Port int `env:"PORT"` + } `env:"DB"` +} +``` + +would parse variables `DBHOST` and `DBPORT`. + +This can be changed with `Options.NameSep`, so parsing it like + +```go +env.Load(&cfg, &env.Options{NameSep: "_"}) +``` + +Will parse `DB_HOST` and `DB_PORT` variable names. +Values like `"-"`, or any other string that builds valid environment variables +are also possible + ### Source By default, `Load` retrieves environment variables directly from OS. diff --git a/env.go b/env.go index e08a741..a997f53 100644 --- a/env.go +++ b/env.go @@ -12,6 +12,7 @@ import ( type Options struct { Source Source // The source of environment variables. The default is [OS]. SliceSep string // The separator used to parse slice values. The default is space. + NameSep string // The separator used to join nested struct names. The default is empty string. } // NotSetError is returned when environment variables are marked as required but not set. @@ -76,7 +77,7 @@ func Load(cfg any, opts *Options) error { } v := pv.Elem() - vars := parseVars(v) + vars := parseVars(v, opts) cache[v.Type()] = vars var notset []string @@ -111,7 +112,7 @@ func Load(cfg any, opts *Options) error { return nil } -func parseVars(v reflect.Value) []Var { +func parseVars(v reflect.Value, opts *Options) []Var { var vars []Var for i := 0; i < v.NumField(); i++ { @@ -128,7 +129,10 @@ func parseVars(v reflect.Value) []Var { if ok { prefix = value } - for _, v := range parseVars(field) { + if opts != nil { + prefix += opts.NameSep + } + for _, v := range parseVars(field, opts) { v.Name = prefix + v.Name vars = append(vars, v) } diff --git a/example_test.go b/example_test.go index d0db336..c230319 100644 --- a/example_test.go +++ b/example_test.go @@ -53,6 +53,27 @@ func ExampleLoad_nestedStruct() { } func ExampleLoad_nestedStructPrefixed() { + os.Setenv("DBHOST", "localhost") + os.Setenv("DBPORT", "5432") + + var cfg struct { + DB struct { + Host string `env:"HOST"` + Port int `env:"PORT"` + } `env:"DB"` + } + if err := env.Load(&cfg, nil); err != nil { + fmt.Println(err) + } + + fmt.Println(cfg.DB.Host) + fmt.Println(cfg.DB.Port) + // Output: + // localhost + // 5432 +} + +func ExampleLoad_nestedStructPrefixedWithSeparator() { os.Setenv("DB_HOST", "localhost") os.Setenv("DB_PORT", "5432") @@ -60,9 +81,9 @@ func ExampleLoad_nestedStructPrefixed() { DB struct { Host string `env:"HOST"` Port int `env:"PORT"` - } `env:"DB_"` + } `env:"DB"` } - if err := env.Load(&cfg, nil); err != nil { + if err := env.Load(&cfg, &env.Options{NameSep: "_"}); err != nil { fmt.Println(err) } diff --git a/usage.go b/usage.go index ef4a610..b248557 100644 --- a/usage.go +++ b/usage.go @@ -45,7 +45,7 @@ func Usage(cfg any, w io.Writer, opts *Options) { v := pv.Elem() vars, ok := cache[v.Type()] if !ok { - vars = parseVars(v) + vars = parseVars(v, opts) } if u, ok := cfg.(interface {