1
2
3
4
5 package envconfig
6
7 import (
8 "encoding"
9 "fmt"
10 "io"
11 "os"
12 "reflect"
13 "strconv"
14 "strings"
15 "text/tabwriter"
16 "text/template"
17 )
18
19 const (
20
21 DefaultListFormat = `This application is configured via the environment. The following environment
22 variables can be used:
23 {{range .}}
24 {{usage_key .}}
25 [description] {{usage_description .}}
26 [type] {{usage_type .}}
27 [default] {{usage_default .}}
28 [required] {{usage_required .}}{{end}}
29 `
30
31 DefaultTableFormat = `This application is configured via the environment. The following environment
32 variables can be used:
33
34 KEY TYPE DEFAULT REQUIRED DESCRIPTION
35 {{range .}}{{usage_key .}} {{usage_type .}} {{usage_default .}} {{usage_required .}} {{usage_description .}}
36 {{end}}`
37 )
38
39 var (
40 decoderType = reflect.TypeOf((*Decoder)(nil)).Elem()
41 setterType = reflect.TypeOf((*Setter)(nil)).Elem()
42 textUnmarshalerType = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem()
43 binaryUnmarshalerType = reflect.TypeOf((*encoding.BinaryUnmarshaler)(nil)).Elem()
44 )
45
46 func implementsInterface(t reflect.Type) bool {
47 return t.Implements(decoderType) ||
48 reflect.PtrTo(t).Implements(decoderType) ||
49 t.Implements(setterType) ||
50 reflect.PtrTo(t).Implements(setterType) ||
51 t.Implements(textUnmarshalerType) ||
52 reflect.PtrTo(t).Implements(textUnmarshalerType) ||
53 t.Implements(binaryUnmarshalerType) ||
54 reflect.PtrTo(t).Implements(binaryUnmarshalerType)
55 }
56
57
58 func toTypeDescription(t reflect.Type) string {
59 switch t.Kind() {
60 case reflect.Array, reflect.Slice:
61 if t.Elem().Kind() == reflect.Uint8 {
62 return "String"
63 }
64 return fmt.Sprintf("Comma-separated list of %s", toTypeDescription(t.Elem()))
65 case reflect.Map:
66 return fmt.Sprintf(
67 "Comma-separated list of %s:%s pairs",
68 toTypeDescription(t.Key()),
69 toTypeDescription(t.Elem()),
70 )
71 case reflect.Ptr:
72 return toTypeDescription(t.Elem())
73 case reflect.Struct:
74 if implementsInterface(t) && t.Name() != "" {
75 return t.Name()
76 }
77 return ""
78 case reflect.String:
79 name := t.Name()
80 if name != "" && name != "string" {
81 return name
82 }
83 return "String"
84 case reflect.Bool:
85 name := t.Name()
86 if name != "" && name != "bool" {
87 return name
88 }
89 return "True or False"
90 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
91 name := t.Name()
92 if name != "" && !strings.HasPrefix(name, "int") {
93 return name
94 }
95 return "Integer"
96 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
97 name := t.Name()
98 if name != "" && !strings.HasPrefix(name, "uint") {
99 return name
100 }
101 return "Unsigned Integer"
102 case reflect.Float32, reflect.Float64:
103 name := t.Name()
104 if name != "" && !strings.HasPrefix(name, "float") {
105 return name
106 }
107 return "Float"
108 }
109 return fmt.Sprintf("%+v", t)
110 }
111
112
113 func Usage(prefix string, spec interface{}) error {
114
115
116 tabs := tabwriter.NewWriter(os.Stdout, 1, 0, 4, ' ', 0)
117
118 err := Usagef(prefix, spec, tabs, DefaultTableFormat)
119 tabs.Flush()
120 return err
121 }
122
123
124 func Usagef(prefix string, spec interface{}, out io.Writer, format string) error {
125
126
127 functions := template.FuncMap{
128 "usage_key": func(v varInfo) string { return v.Key },
129 "usage_description": func(v varInfo) string { return v.Tags.Get("desc") },
130 "usage_type": func(v varInfo) string { return toTypeDescription(v.Field.Type()) },
131 "usage_default": func(v varInfo) string { return v.Tags.Get("default") },
132 "usage_required": func(v varInfo) (string, error) {
133 req := v.Tags.Get("required")
134 if req != "" {
135 reqB, err := strconv.ParseBool(req)
136 if err != nil {
137 return "", err
138 }
139 if reqB {
140 req = "true"
141 }
142 }
143 return req, nil
144 },
145 }
146
147 tmpl, err := template.New("envconfig").Funcs(functions).Parse(format)
148 if err != nil {
149 return err
150 }
151
152 return Usaget(prefix, spec, out, tmpl)
153 }
154
155
156 func Usaget(prefix string, spec interface{}, out io.Writer, tmpl *template.Template) error {
157
158 infos, err := gatherInfo(prefix, spec)
159 if err != nil {
160 return err
161 }
162
163 return tmpl.Execute(out, infos)
164 }
165
View as plain text