blob: 184635380f2b2f80d052ac9274a9d14dee37a943 [file] [log] [blame]
Jonathan Hartf86817b2018-08-17 10:35:54 -07001// Copyright (c) 2016 Kelsey Hightower and others. All rights reserved.
2// Use of this source code is governed by the MIT License that can be found in
3// the LICENSE file.
4
5package envconfig
6
7import (
8 "encoding"
9 "fmt"
10 "io"
11 "os"
12 "reflect"
13 "strconv"
14 "strings"
15 "text/tabwriter"
16 "text/template"
17)
18
19const (
20 // DefaultListFormat constant to use to display usage in a list format
21 DefaultListFormat = `This application is configured via the environment. The following environment
22variables 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 // DefaultTableFormat constant to use to display usage in a tabluar format
31 DefaultTableFormat = `This application is configured via the environment. The following environment
32variables can be used:
33
34KEY TYPE DEFAULT REQUIRED DESCRIPTION
35{{range .}}{{usage_key .}} {{usage_type .}} {{usage_default .}} {{usage_required .}} {{usage_description .}}
36{{end}}`
37)
38
39var (
40 decoderType = reflect.TypeOf((*Decoder)(nil)).Elem()
41 setterType = reflect.TypeOf((*Setter)(nil)).Elem()
42 unmarshalerType = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem()
43)
44
45func implementsInterface(t reflect.Type) bool {
46 return t.Implements(decoderType) ||
47 reflect.PtrTo(t).Implements(decoderType) ||
48 t.Implements(setterType) ||
49 reflect.PtrTo(t).Implements(setterType) ||
50 t.Implements(unmarshalerType) ||
51 reflect.PtrTo(t).Implements(unmarshalerType)
52}
53
54// toTypeDescription converts Go types into a human readable description
55func toTypeDescription(t reflect.Type) string {
56 switch t.Kind() {
57 case reflect.Array, reflect.Slice:
58 return fmt.Sprintf("Comma-separated list of %s", toTypeDescription(t.Elem()))
Matteo Scandolo1f49bf52018-11-20 13:56:45 -080059 case reflect.Map:
60 return fmt.Sprintf(
61 "Comma-separated list of %s:%s pairs",
62 toTypeDescription(t.Key()),
63 toTypeDescription(t.Elem()),
64 )
Jonathan Hartf86817b2018-08-17 10:35:54 -070065 case reflect.Ptr:
66 return toTypeDescription(t.Elem())
67 case reflect.Struct:
68 if implementsInterface(t) && t.Name() != "" {
69 return t.Name()
70 }
71 return ""
72 case reflect.String:
73 name := t.Name()
74 if name != "" && name != "string" {
75 return name
76 }
77 return "String"
78 case reflect.Bool:
79 name := t.Name()
80 if name != "" && name != "bool" {
81 return name
82 }
83 return "True or False"
84 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
85 name := t.Name()
86 if name != "" && !strings.HasPrefix(name, "int") {
87 return name
88 }
89 return "Integer"
90 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
91 name := t.Name()
92 if name != "" && !strings.HasPrefix(name, "uint") {
93 return name
94 }
95 return "Unsigned Integer"
96 case reflect.Float32, reflect.Float64:
97 name := t.Name()
98 if name != "" && !strings.HasPrefix(name, "float") {
99 return name
100 }
101 return "Float"
102 }
103 return fmt.Sprintf("%+v", t)
104}
105
106// Usage writes usage information to stderr using the default header and table format
107func Usage(prefix string, spec interface{}) error {
108 // The default is to output the usage information as a table
109 // Create tabwriter instance to support table output
110 tabs := tabwriter.NewWriter(os.Stdout, 1, 0, 4, ' ', 0)
111
112 err := Usagef(prefix, spec, tabs, DefaultTableFormat)
113 tabs.Flush()
114 return err
115}
116
117// Usagef writes usage information to the specified io.Writer using the specifed template specification
118func Usagef(prefix string, spec interface{}, out io.Writer, format string) error {
119
120 // Specify the default usage template functions
121 functions := template.FuncMap{
122 "usage_key": func(v varInfo) string { return v.Key },
123 "usage_description": func(v varInfo) string { return v.Tags.Get("desc") },
124 "usage_type": func(v varInfo) string { return toTypeDescription(v.Field.Type()) },
125 "usage_default": func(v varInfo) string { return v.Tags.Get("default") },
126 "usage_required": func(v varInfo) (string, error) {
127 req := v.Tags.Get("required")
128 if req != "" {
129 reqB, err := strconv.ParseBool(req)
130 if err != nil {
131 return "", err
132 }
133 if reqB {
134 req = "true"
135 }
136 }
137 return req, nil
138 },
139 }
140
141 tmpl, err := template.New("envconfig").Funcs(functions).Parse(format)
142 if err != nil {
143 return err
144 }
145
146 return Usaget(prefix, spec, out, tmpl)
147}
148
149// Usaget writes usage information to the specified io.Writer using the specified template
150func Usaget(prefix string, spec interface{}, out io.Writer, tmpl *template.Template) error {
151 // gather first
152 infos, err := gatherInfo(prefix, spec)
153 if err != nil {
154 return err
155 }
156
157 return tmpl.Execute(out, infos)
158}