Skip to content

Commit b3e5818

Browse files
authored
dev: minor cleanups (#64)
1 parent d416254 commit b3e5818

File tree

6 files changed

+51
-47
lines changed

6 files changed

+51
-47
lines changed

.golangci.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ linters:
77
- govet
88
- ineffassign
99
- staticcheck
10-
- typecheck
1110
- unused
1211
# disabled by default:
1312
- gocritic

README.md

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,14 @@ which simplifies config management and improves code readability.
2121
## 🚀 Features
2222

2323
* Support for all common types and user-defined types
24-
* Options: [required](#required), [expand](#expand), [slice separator](#slice-separator)
2524
* Configurable [source](#source) of environment variables
25+
* Options: [required](#required), [expand](#expand), [slice separator](#slice-separator), [name separator](#name-separator)
2626
* Auto-generated [usage message](#usage-message)
2727

2828
## 📦 Install
2929

3030
Go 1.20+
31+
3132
```shell
3233
go get go-simpler.org/env
3334
```
@@ -38,7 +39,7 @@ go get go-simpler.org/env
3839
It loads environment variables into the given struct.
3940

4041
The struct fields must have the `env:"VAR"` struct tag,
41-
where VAR is the name of the corresponding environment variable.
42+
where `VAR` is the name of the corresponding environment variable.
4243
Unexported fields are ignored.
4344

4445
```go
@@ -74,33 +75,35 @@ Nested struct of any depth level are supported,
7475
allowing grouping of related environment variables.
7576

7677
```go
77-
os.Setenv("HTTP_PORT", "8080")
78+
os.Setenv("DB_HOST", "localhost")
79+
os.Setenv("DB_PORT", "5432")
7880

7981
var cfg struct {
80-
HTTP struct {
81-
Port int `env:"HTTP_PORT"`
82+
DB struct {
83+
Host string `env:"DB_HOST"`
84+
Port int `env:"DB_PORT"`
8285
}
8386
}
8487
if err := env.Load(&cfg, nil); err != nil {
8588
fmt.Println(err)
8689
}
8790

88-
fmt.Println(cfg.HTTP.Port) // 8080
91+
fmt.Println(cfg.DB.Host) // localhost
92+
fmt.Println(cfg.DB.Port) // 5432
8993
```
9094

91-
A nested struct can have the optional `env:"PREFIX"` tag.
92-
In this case, the environment variables declared by its fields are prefixed with PREFIX.
93-
This rule is applied recursively to all nested structs.
95+
If a nested struct has the optional `env:"PREFIX"` tag,
96+
the environment variables declared by its fields are prefixed with `PREFIX`.
9497

9598
```go
96-
os.Setenv("DBHOST", "localhost")
97-
os.Setenv("DBPORT", "5432")
99+
os.Setenv("DB_HOST", "localhost")
100+
os.Setenv("DB_PORT", "5432")
98101

99102
var cfg struct {
100103
DB struct {
101104
Host string `env:"HOST"`
102105
Port int `env:"PORT"`
103-
} `env:"DB"`
106+
} `env:"DB_"`
104107
}
105108
if err := env.Load(&cfg, nil); err != nil {
106109
fmt.Println(err)
@@ -112,7 +115,7 @@ fmt.Println(cfg.DB.Port) // 5432
112115

113116
### Default values
114117

115-
Default values can be specified using the `default:"VALUE"` struct tag:
118+
Default values can be specified using the `default:"VALUE"` struct tag.
116119

117120
```go
118121
os.Unsetenv("PORT")
@@ -167,7 +170,7 @@ fmt.Println(cfg.Addr) // localhost:8080
167170
### Slice separator
168171

169172
Space is the default separator used to parse slice values.
170-
It can be changed with `Options.SliceSep`:
173+
It can be changed with `Options.SliceSep`.
171174

172175
```go
173176
os.Setenv("PORTS", "8080,8081,8082")
@@ -185,7 +188,7 @@ fmt.Println(cfg.Ports) // [8080 8081 8082]
185188
### Name separator
186189

187190
By default, environment variable names are concatenated from nested struct tags as is.
188-
If `Options.NameSep` is not empty, it is used as the separator:
191+
If `Options.NameSep` is not empty, it is used as the separator.
189192

190193
```go
191194
os.Setenv("DB_HOST", "localhost")
@@ -216,7 +219,7 @@ type Source interface {
216219
}
217220
```
218221

219-
Here's an example of using `Map`, a `Source` implementation useful in tests:
222+
Here's an example of using `Map`, a `Source` implementation useful in tests.
220223

221224
```go
222225
m := env.Map{"PORT": "8080"}
@@ -234,7 +237,7 @@ fmt.Println(cfg.Port) // 8080
234237
### Usage message
235238

236239
The `Usage` function prints a usage message documenting all defined environment variables.
237-
An optional usage string can be added to environment variables using the `usage:"STRING"` struct tag:
240+
An optional usage string can be added to environment variables with the `usage:"STRING"` struct tag.
238241

239242
```go
240243
os.Unsetenv("DB_HOST")
@@ -261,7 +264,7 @@ Usage:
261264
HTTP_PORT int default 8080 http server port
262265
```
263266

264-
The format of the message can be customized by implementing the `Usage([]env.Var, io.Writer, *env.Options)` method:
267+
The format of the message can be customized by implementing the `Usage([]env.Var, io.Writer, *env.Options)` method.
265268

266269
```go
267270
type Config struct{ ... }

env.go

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ type Options struct {
1515
NameSep string // The separator used to concatenate environment variable names from nested struct tags. The default is an empty string.
1616
}
1717

18-
// NotSetError is returned when environment variables are marked as required but not set.
18+
// NotSetError is returned when required environment variables are not set.
1919
type NotSetError struct {
20-
Names []string // The names of the missing environment variables.
20+
Names []string
2121
}
2222

2323
// Error implements the error interface.
@@ -51,9 +51,8 @@ func (e *NotSetError) Error() string {
5151
//
5252
// Nested struct of any depth level are supported,
5353
// allowing grouping of related environment variables.
54-
// A nested struct can have the optional `env:"PREFIX"` tag.
55-
// In this case, the environment variables declared by its fields are prefixed with PREFIX.
56-
// This rule is applied recursively to all nested structs.
54+
// If a nested struct has the optional `env:"PREFIX"` tag,
55+
// the environment variables declared by its fields are prefixed with PREFIX.
5756
//
5857
// Default values can be specified using the `default:"VALUE"` struct tag.
5958
//
@@ -126,12 +125,11 @@ func parseVars(v reflect.Value, opts *Options) []Var {
126125
continue
127126
}
128127

129-
// special case: a nested struct, parse its fields recursively.
128+
tags := v.Type().Field(i).Tag
129+
130130
if kindOf(field, reflect.Struct) && !implements(field, unmarshalerIface) {
131131
var prefix string
132-
sf := v.Type().Field(i)
133-
value, ok := sf.Tag.Lookup("env")
134-
if ok {
132+
if value, ok := tags.Lookup("env"); ok {
135133
prefix = value + opts.NameSep
136134
}
137135
for _, v := range parseVars(field, opts) {
@@ -141,8 +139,7 @@ func parseVars(v reflect.Value, opts *Options) []Var {
141139
continue
142140
}
143141

144-
sf := v.Type().Field(i)
145-
value, ok := sf.Tag.Lookup("env")
142+
value, ok := tags.Lookup("env")
146143
if !ok {
147144
continue
148145
}
@@ -165,7 +162,7 @@ func parseVars(v reflect.Value, opts *Options) []Var {
165162
}
166163
}
167164

168-
defValue, defSet := sf.Tag.Lookup("default")
165+
defValue, defSet := tags.Lookup("default")
169166
switch {
170167
case defSet && required:
171168
panic("env: `required` and `default` can't be used simultaneously")
@@ -176,7 +173,7 @@ func parseVars(v reflect.Value, opts *Options) []Var {
176173
vars = append(vars, Var{
177174
Name: name,
178175
Type: field.Type(),
179-
Usage: sf.Tag.Get("usage"),
176+
Usage: tags.Get("usage"),
180177
Default: defValue,
181178
Required: required,
182179
Expand: expand,

env_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ func TestLoad(t *testing.T) {
2828
t.Run(name, func(t *testing.T) {
2929
const panicMsg = "env: cfg must be a non-nil struct pointer"
3030

31-
load := func() { _ = env.Load(cfg, &env.Options{Source: env.Map{}}) }
31+
load := func() { _ = env.Load(cfg, nil) }
3232
assert.Panics[E](t, load, panicMsg)
3333

3434
usage := func() { env.Usage(cfg, io.Discard, nil) }
@@ -41,23 +41,23 @@ func TestLoad(t *testing.T) {
4141
var cfg struct {
4242
Foo int `env:""`
4343
}
44-
load := func() { _ = env.Load(&cfg, &env.Options{Source: env.Map{}}) }
44+
load := func() { _ = env.Load(&cfg, nil) }
4545
assert.Panics[E](t, load, "env: empty tag name is not allowed")
4646
})
4747

4848
t.Run("invalid tag option", func(t *testing.T) {
4949
var cfg struct {
5050
Foo int `env:"FOO,?"`
5151
}
52-
load := func() { _ = env.Load(&cfg, &env.Options{Source: env.Map{}}) }
52+
load := func() { _ = env.Load(&cfg, nil) }
5353
assert.Panics[E](t, load, "env: invalid tag option `?`")
5454
})
5555

5656
t.Run("required with default", func(t *testing.T) {
5757
var cfg struct {
5858
Foo int `env:"FOO,required" default:"1"`
5959
}
60-
load := func() { _ = env.Load(&cfg, &env.Options{Source: env.Map{}}) }
60+
load := func() { _ = env.Load(&cfg, nil) }
6161
assert.Panics[E](t, load, "env: `required` and `default` can't be used simultaneously")
6262
})
6363

example_test.go

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -37,30 +37,35 @@ func ExampleLoad_defaultValue() {
3737
}
3838

3939
func ExampleLoad_nestedStruct() {
40-
os.Setenv("HTTP_PORT", "8080")
40+
os.Setenv("DB_HOST", "localhost")
41+
os.Setenv("DB_PORT", "5432")
4142

4243
var cfg struct {
43-
HTTP struct {
44-
Port int `env:"HTTP_PORT"`
44+
DB struct {
45+
Host string `env:"DB_HOST"`
46+
Port int `env:"DB_PORT"`
4547
}
4648
}
4749
if err := env.Load(&cfg, nil); err != nil {
4850
fmt.Println(err)
4951
}
5052

51-
fmt.Println(cfg.HTTP.Port)
52-
// Output: 8080
53+
fmt.Println(cfg.DB.Host)
54+
fmt.Println(cfg.DB.Port)
55+
// Output:
56+
// localhost
57+
// 5432
5358
}
5459

5560
func ExampleLoad_nestedStructWithPrefix() {
56-
os.Setenv("DBHOST", "localhost")
57-
os.Setenv("DBPORT", "5432")
61+
os.Setenv("DB_HOST", "localhost")
62+
os.Setenv("DB_PORT", "5432")
5863

5964
var cfg struct {
6065
DB struct {
6166
Host string `env:"HOST"`
6267
Port int `env:"PORT"`
63-
} `env:"DB"`
68+
} `env:"DB_"`
6469
}
6570
if err := env.Load(&cfg, nil); err != nil {
6671
fmt.Println(err)

usage.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,15 @@ import (
88
)
99

1010
// cache maps a struct type to the [Var] slice parsed from it.
11-
// It is primarily needed to fix the following bug:
11+
// It is needed to fix the following bug:
1212
//
1313
// var cfg struct {
1414
// Port int `env:"PORT"`
1515
// }
1616
// env.Load(&cfg, nil) // 1. sets cfg.Port to 8080
1717
// env.Usage(&cfg, os.Stdout, nil) // 2. prints cfg.Port's default == 8080 (instead of 0)
1818
//
19-
// It also speeds up [Usage], since there is no need to parse the struct again.
19+
// It also speeds up [Usage] a bit, since a struct is only parsed once.
2020
var cache = make(map[reflect.Type][]Var)
2121

2222
// Var holds the information about the environment variable parsed from a struct field.
@@ -34,7 +34,7 @@ type Var struct {
3434

3535
// Usage writes a usage message documenting all defined environment variables to the given [io.Writer].
3636
// The caller must pass the same [Options] to both [Load] and [Usage], or nil.
37-
// An optional usage string can be added to environment variables using the `usage:"STRING"` struct tag.
37+
// An optional usage string can be added to environment variables with the `usage:"STRING"` struct tag.
3838
// The format of the message can be customized by implementing the Usage([]env.Var, io.Writer, *env.Options) method on the cfg's type.
3939
func Usage(cfg any, w io.Writer, opts *Options) {
4040
pv := reflect.ValueOf(cfg)

0 commit comments

Comments
 (0)