forked from kelseyhightower/envconfig
-
Notifications
You must be signed in to change notification settings - Fork 2
/
usage.go
167 lines (152 loc) · 4.86 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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
// Copyright (c) 2016 Kelsey Hightower and others. All rights reserved.
// Copyright (c) 2020 Oleg Zaytsev. All rights reserved.
//
// Use of this source code is governed by the MIT License that can be found in
// the LICENSE file.
package envconfig
import (
"encoding"
"fmt"
"io"
"os"
"reflect"
"strconv"
"strings"
"text/tabwriter"
"text/template"
)
const (
// DefaultListFormat constant to use to display usage in a list format
DefaultListFormat = `This application is configured via the environment. The following environment
variables can be used:
{{range .}}
{{usage_key .}}
[description] {{usage_description .}}
[type] {{usage_type .}}
[default] {{usage_default .}}
[required] {{usage_required .}}{{end}}
`
// DefaultTableFormat constant to use to display usage in a tabular format
DefaultTableFormat = `This application is configured via the environment. The following environment
variables can be used:
KEY TYPE DEFAULT REQUIRED DESCRIPTION
{{range .}}{{usage_key .}} {{usage_type .}} {{usage_default .}} {{usage_required .}} {{usage_description .}}
{{end}}`
)
var (
decoderType = reflect.TypeOf((*Decoder)(nil)).Elem()
setterType = reflect.TypeOf((*Setter)(nil)).Elem()
textUnmarshalerType = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem()
binaryUnmarshalerType = reflect.TypeOf((*encoding.BinaryUnmarshaler)(nil)).Elem()
)
func implementsInterface(t reflect.Type) bool {
return t.Implements(decoderType) ||
reflect.PtrTo(t).Implements(decoderType) ||
t.Implements(setterType) ||
reflect.PtrTo(t).Implements(setterType) ||
t.Implements(textUnmarshalerType) ||
reflect.PtrTo(t).Implements(textUnmarshalerType) ||
t.Implements(binaryUnmarshalerType) ||
reflect.PtrTo(t).Implements(binaryUnmarshalerType)
}
// toTypeDescription converts Go types into a human readable description
func toTypeDescription(t reflect.Type) string {
switch t.Kind() {
case reflect.Array, reflect.Slice:
if t.Elem().Kind() == reflect.Uint8 {
return "String"
}
return fmt.Sprintf("Comma-separated list of %s", toTypeDescription(t.Elem()))
case reflect.Map:
return fmt.Sprintf(
"Comma-separated list of %s:%s pairs",
toTypeDescription(t.Key()),
toTypeDescription(t.Elem()),
)
case reflect.Ptr:
return toTypeDescription(t.Elem())
case reflect.Struct:
if implementsInterface(t) && t.Name() != "" {
return t.Name()
}
return ""
case reflect.String:
name := t.Name()
if name != "" && name != "string" {
return name
}
return "String"
case reflect.Bool:
name := t.Name()
if name != "" && name != "bool" {
return name
}
return "True or False"
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
name := t.Name()
if name != "" && !strings.HasPrefix(name, "int") {
return name
}
return "Integer"
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
name := t.Name()
if name != "" && !strings.HasPrefix(name, "uint") {
return name
}
return "Unsigned Integer"
case reflect.Float32, reflect.Float64:
name := t.Name()
if name != "" && !strings.HasPrefix(name, "float") {
return name
}
return "Float"
}
return fmt.Sprintf("%+v", t)
}
// Usage writes usage information to stdout using the default header and table format
func Usage(prefix string, spec interface{}) error {
// The default is to output the usage information as a table
// Create tabwriter instance to support table output
tabs := tabwriter.NewWriter(os.Stdout, 1, 0, 4, ' ', 0)
err := Usagef(prefix, spec, tabs, DefaultTableFormat)
tabs.Flush()
return err
}
// Usagef writes usage information to the specified io.Writer using the specifed template specification
func Usagef(prefix string, spec interface{}, out io.Writer, format string) error {
// Specify the default usage template functions
functions := template.FuncMap{
"usage_key": func(v varInfo) string { return v.Key },
"usage_description": func(v varInfo) string { return v.Tags.Get("desc") },
"usage_type": func(v varInfo) string { return toTypeDescription(v.Field.Type()) },
"usage_default": func(v varInfo) string { return v.Tags.Get("default") },
"usage_required": func(v varInfo) (string, error) {
req := v.Tags.Get("required")
if req != "" {
reqB, err := strconv.ParseBool(req)
if err != nil {
return "", err
}
if reqB {
req = "true"
}
}
return req, nil
},
}
tmpl, err := template.New("envconfig").Funcs(functions).Parse(format)
if err != nil {
return err
}
return Usaget(prefix, spec, out, tmpl)
}
// Usaget writes usage information to the specified io.Writer using the specified template
func Usaget(prefix string, spec interface{}, out io.Writer, tmpl *template.Template) error {
spec = copySpec(spec)
// gather first
infos, err := gatherInfoForUsage(prefix, spec)
if err != nil {
return err
}
return tmpl.Execute(out, infos)
}