blob: 4870237522866b20e4f7b55f9103b5714097a341 [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()))
59 case reflect.Ptr:
60 return toTypeDescription(t.Elem())
61 case reflect.Struct:
62 if implementsInterface(t) && t.Name() != "" {
63 return t.Name()
64 }
65 return ""
66 case reflect.String:
67 name := t.Name()
68 if name != "" && name != "string" {
69 return name
70 }
71 return "String"
72 case reflect.Bool:
73 name := t.Name()
74 if name != "" && name != "bool" {
75 return name
76 }
77 return "True or False"
78 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
79 name := t.Name()
80 if name != "" && !strings.HasPrefix(name, "int") {
81 return name
82 }
83 return "Integer"
84 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
85 name := t.Name()
86 if name != "" && !strings.HasPrefix(name, "uint") {
87 return name
88 }
89 return "Unsigned Integer"
90 case reflect.Float32, reflect.Float64:
91 name := t.Name()
92 if name != "" && !strings.HasPrefix(name, "float") {
93 return name
94 }
95 return "Float"
96 }
97 return fmt.Sprintf("%+v", t)
98}
99
100// Usage writes usage information to stderr using the default header and table format
101func Usage(prefix string, spec interface{}) error {
102 // The default is to output the usage information as a table
103 // Create tabwriter instance to support table output
104 tabs := tabwriter.NewWriter(os.Stdout, 1, 0, 4, ' ', 0)
105
106 err := Usagef(prefix, spec, tabs, DefaultTableFormat)
107 tabs.Flush()
108 return err
109}
110
111// Usagef writes usage information to the specified io.Writer using the specifed template specification
112func Usagef(prefix string, spec interface{}, out io.Writer, format string) error {
113
114 // Specify the default usage template functions
115 functions := template.FuncMap{
116 "usage_key": func(v varInfo) string { return v.Key },
117 "usage_description": func(v varInfo) string { return v.Tags.Get("desc") },
118 "usage_type": func(v varInfo) string { return toTypeDescription(v.Field.Type()) },
119 "usage_default": func(v varInfo) string { return v.Tags.Get("default") },
120 "usage_required": func(v varInfo) (string, error) {
121 req := v.Tags.Get("required")
122 if req != "" {
123 reqB, err := strconv.ParseBool(req)
124 if err != nil {
125 return "", err
126 }
127 if reqB {
128 req = "true"
129 }
130 }
131 return req, nil
132 },
133 }
134
135 tmpl, err := template.New("envconfig").Funcs(functions).Parse(format)
136 if err != nil {
137 return err
138 }
139
140 return Usaget(prefix, spec, out, tmpl)
141}
142
143// Usaget writes usage information to the specified io.Writer using the specified template
144func Usaget(prefix string, spec interface{}, out io.Writer, tmpl *template.Template) error {
145 // gather first
146 infos, err := gatherInfo(prefix, spec)
147 if err != nil {
148 return err
149 }
150
151 return tmpl.Execute(out, infos)
152}