-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathusage.go
83 lines (72 loc) · 2.43 KB
/
usage.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
package env
import (
"fmt"
"io"
"reflect"
"text/tabwriter"
)
// cache maps a struct type to the [Var] slice parsed from it.
// It is needed to fix the following bug:
//
// var cfg struct {
// Port int `env:"PORT"`
// }
// env.Load(&cfg, nil) // 1. sets cfg.Port to 8080
// env.Usage(&cfg, os.Stdout, nil) // 2. prints cfg.Port's default == 8080 (instead of 0)
//
// It also speeds up [Usage] a bit, since a struct is only parsed once.
var cache = make(map[reflect.Type][]Var)
// Var holds the information about the environment variable parsed from a struct field.
type Var struct {
Name string // The name of the variable.
Type reflect.Type // The type of the variable.
Usage string // The usage string parsed from the `usage` tag (if exists).
Default string // The default value of the variable. Empty, if the variable is required.
Required bool // True, if the variable is marked as required.
Expand bool // True, if the variable is marked to be expanded with [os.Expand].
structField reflect.Value
hasDefaultTag bool
}
// Usage writes a usage message documenting all defined environment variables to the given [io.Writer].
// The caller must pass the same [Options] to both [Load] and [Usage], or nil.
// An optional usage string can be added to environment variables with the `usage:"STRING"` struct tag.
// The format of the message can be customized by implementing the Usage([]env.Var, io.Writer, *env.Options) method on the cfg's type.
func Usage(cfg any, w io.Writer, opts *Options) {
pv := reflect.ValueOf(cfg)
if !structPtr(pv) {
panic("env: cfg must be a non-nil struct pointer")
}
opts = setDefaultOptions(opts)
v := pv.Elem()
vars, ok := cache[v.Type()]
if !ok {
vars = parseVars(v, opts)
}
if u, ok := cfg.(interface {
Usage([]Var, io.Writer, *Options)
}); ok {
u.Usage(vars, w, opts)
} else {
defaultUsage(vars, w, opts)
}
}
func defaultUsage(vars []Var, w io.Writer, _ *Options) {
// TODO: use opts.SliceSep to parse slice values.
tw := tabwriter.NewWriter(w, 0, 0, 2, ' ', 0)
defer tw.Flush()
for _, v := range vars {
fmt.Fprintf(tw, "\t%s\t%s", v.Name, v.Type)
if v.Required {
fmt.Fprintf(tw, "\trequired")
} else {
if v.Type.Kind() == reflect.String && v.Default == "" {
v.Default = "<empty>"
}
fmt.Fprintf(tw, "\tdefault %s", v.Default)
}
if v.Usage != "" {
fmt.Fprintf(tw, "\t%s", v.Usage)
}
fmt.Fprintf(tw, "\n")
}
}