159 lines
4.5 KiB
Go
159 lines
4.5 KiB
Go
|
// Copyright (c) 2016 Kelsey Hightower and others. 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 tabluar 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()
|
||
|
unmarshalerType = reflect.TypeOf((*encoding.TextUnmarshaler)(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(unmarshalerType) ||
|
||
|
reflect.PtrTo(t).Implements(unmarshalerType)
|
||
|
}
|
||
|
|
||
|
// toTypeDescription converts Go types into a human readable description
|
||
|
func toTypeDescription(t reflect.Type) string {
|
||
|
switch t.Kind() {
|
||
|
case reflect.Array, reflect.Slice:
|
||
|
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 stderr 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 {
|
||
|
// gather first
|
||
|
infos, err := gatherInfo(prefix, spec)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
return tmpl.Execute(out, infos)
|
||
|
}
|