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