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