// 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) }