main
parent
bf1a47102d
commit
24fa5f5b31
13 changed files with 798 additions and 5 deletions
@ -1,2 +1,2 @@ |
|||||||
authPostmanService |
authPostmanService |
||||||
vendor |
# vendor |
@ -1,2 +0,0 @@ |
|||||||
github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= |
|
||||||
github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= |
|
@ -0,0 +1,13 @@ |
|||||||
|
language: go |
||||||
|
|
||||||
|
go: |
||||||
|
- 1.4.x |
||||||
|
- 1.5.x |
||||||
|
- 1.6.x |
||||||
|
- 1.7.x |
||||||
|
- 1.8.x |
||||||
|
- 1.9.x |
||||||
|
- 1.10.x |
||||||
|
- 1.11.x |
||||||
|
- 1.12.x |
||||||
|
- tip |
@ -0,0 +1,19 @@ |
|||||||
|
Copyright (c) 2013 Kelsey Hightower |
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of |
||||||
|
this software and associated documentation files (the "Software"), to deal in |
||||||
|
the Software without restriction, including without limitation the rights to |
||||||
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies |
||||||
|
of the Software, and to permit persons to whom the Software is furnished to do |
||||||
|
so, subject to the following conditions: |
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all |
||||||
|
copies or substantial portions of the Software. |
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
||||||
|
SOFTWARE. |
@ -0,0 +1,2 @@ |
|||||||
|
Kelsey Hightower kelsey.hightower@gmail.com github.com/kelseyhightower |
||||||
|
Travis Parker travis.parker@gmail.com github.com/teepark |
@ -0,0 +1,192 @@ |
|||||||
|
# envconfig |
||||||
|
|
||||||
|
[](https://travis-ci.org/kelseyhightower/envconfig) |
||||||
|
|
||||||
|
```Go |
||||||
|
import "github.com/kelseyhightower/envconfig" |
||||||
|
``` |
||||||
|
|
||||||
|
## Documentation |
||||||
|
|
||||||
|
See [godoc](http://godoc.org/github.com/kelseyhightower/envconfig) |
||||||
|
|
||||||
|
## Usage |
||||||
|
|
||||||
|
Set some environment variables: |
||||||
|
|
||||||
|
```Bash |
||||||
|
export MYAPP_DEBUG=false |
||||||
|
export MYAPP_PORT=8080 |
||||||
|
export MYAPP_USER=Kelsey |
||||||
|
export MYAPP_RATE="0.5" |
||||||
|
export MYAPP_TIMEOUT="3m" |
||||||
|
export MYAPP_USERS="rob,ken,robert" |
||||||
|
export MYAPP_COLORCODES="red:1,green:2,blue:3" |
||||||
|
``` |
||||||
|
|
||||||
|
Write some code: |
||||||
|
|
||||||
|
```Go |
||||||
|
package main |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
"log" |
||||||
|
"time" |
||||||
|
|
||||||
|
"github.com/kelseyhightower/envconfig" |
||||||
|
) |
||||||
|
|
||||||
|
type Specification struct { |
||||||
|
Debug bool |
||||||
|
Port int |
||||||
|
User string |
||||||
|
Users []string |
||||||
|
Rate float32 |
||||||
|
Timeout time.Duration |
||||||
|
ColorCodes map[string]int |
||||||
|
} |
||||||
|
|
||||||
|
func main() { |
||||||
|
var s Specification |
||||||
|
err := envconfig.Process("myapp", &s) |
||||||
|
if err != nil { |
||||||
|
log.Fatal(err.Error()) |
||||||
|
} |
||||||
|
format := "Debug: %v\nPort: %d\nUser: %s\nRate: %f\nTimeout: %s\n" |
||||||
|
_, err = fmt.Printf(format, s.Debug, s.Port, s.User, s.Rate, s.Timeout) |
||||||
|
if err != nil { |
||||||
|
log.Fatal(err.Error()) |
||||||
|
} |
||||||
|
|
||||||
|
fmt.Println("Users:") |
||||||
|
for _, u := range s.Users { |
||||||
|
fmt.Printf(" %s\n", u) |
||||||
|
} |
||||||
|
|
||||||
|
fmt.Println("Color codes:") |
||||||
|
for k, v := range s.ColorCodes { |
||||||
|
fmt.Printf(" %s: %d\n", k, v) |
||||||
|
} |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
Results: |
||||||
|
|
||||||
|
```Bash |
||||||
|
Debug: false |
||||||
|
Port: 8080 |
||||||
|
User: Kelsey |
||||||
|
Rate: 0.500000 |
||||||
|
Timeout: 3m0s |
||||||
|
Users: |
||||||
|
rob |
||||||
|
ken |
||||||
|
robert |
||||||
|
Color codes: |
||||||
|
red: 1 |
||||||
|
green: 2 |
||||||
|
blue: 3 |
||||||
|
``` |
||||||
|
|
||||||
|
## Struct Tag Support |
||||||
|
|
||||||
|
Envconfig supports the use of struct tags to specify alternate, default, and required |
||||||
|
environment variables. |
||||||
|
|
||||||
|
For example, consider the following struct: |
||||||
|
|
||||||
|
```Go |
||||||
|
type Specification struct { |
||||||
|
ManualOverride1 string `envconfig:"manual_override_1"` |
||||||
|
DefaultVar string `default:"foobar"` |
||||||
|
RequiredVar string `required:"true"` |
||||||
|
IgnoredVar string `ignored:"true"` |
||||||
|
AutoSplitVar string `split_words:"true"` |
||||||
|
RequiredAndAutoSplitVar string `required:"true" split_words:"true"` |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
Envconfig has automatic support for CamelCased struct elements when the |
||||||
|
`split_words:"true"` tag is supplied. Without this tag, `AutoSplitVar` above |
||||||
|
would look for an environment variable called `MYAPP_AUTOSPLITVAR`. With the |
||||||
|
setting applied it will look for `MYAPP_AUTO_SPLIT_VAR`. Note that numbers |
||||||
|
will get globbed into the previous word. If the setting does not do the |
||||||
|
right thing, you may use a manual override. |
||||||
|
|
||||||
|
Envconfig will process value for `ManualOverride1` by populating it with the |
||||||
|
value for `MYAPP_MANUAL_OVERRIDE_1`. Without this struct tag, it would have |
||||||
|
instead looked up `MYAPP_MANUALOVERRIDE1`. With the `split_words:"true"` tag |
||||||
|
it would have looked up `MYAPP_MANUAL_OVERRIDE1`. |
||||||
|
|
||||||
|
```Bash |
||||||
|
export MYAPP_MANUAL_OVERRIDE_1="this will be the value" |
||||||
|
|
||||||
|
# export MYAPP_MANUALOVERRIDE1="and this will not" |
||||||
|
``` |
||||||
|
|
||||||
|
If envconfig can't find an environment variable value for `MYAPP_DEFAULTVAR`, |
||||||
|
it will populate it with "foobar" as a default value. |
||||||
|
|
||||||
|
If envconfig can't find an environment variable value for `MYAPP_REQUIREDVAR`, |
||||||
|
it will return an error when asked to process the struct. If |
||||||
|
`MYAPP_REQUIREDVAR` is present but empty, envconfig will not return an error. |
||||||
|
|
||||||
|
If envconfig can't find an environment variable in the form `PREFIX_MYVAR`, and there |
||||||
|
is a struct tag defined, it will try to populate your variable with an environment |
||||||
|
variable that directly matches the envconfig tag in your struct definition: |
||||||
|
|
||||||
|
```shell |
||||||
|
export SERVICE_HOST=127.0.0.1 |
||||||
|
export MYAPP_DEBUG=true |
||||||
|
``` |
||||||
|
```Go |
||||||
|
type Specification struct { |
||||||
|
ServiceHost string `envconfig:"SERVICE_HOST"` |
||||||
|
Debug bool |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
Envconfig won't process a field with the "ignored" tag set to "true", even if a corresponding |
||||||
|
environment variable is set. |
||||||
|
|
||||||
|
## Supported Struct Field Types |
||||||
|
|
||||||
|
envconfig supports these struct field types: |
||||||
|
|
||||||
|
* string |
||||||
|
* int8, int16, int32, int64 |
||||||
|
* bool |
||||||
|
* float32, float64 |
||||||
|
* slices of any supported type |
||||||
|
* maps (keys and values of any supported type) |
||||||
|
* [encoding.TextUnmarshaler](https://golang.org/pkg/encoding/#TextUnmarshaler) |
||||||
|
* [encoding.BinaryUnmarshaler](https://golang.org/pkg/encoding/#BinaryUnmarshaler) |
||||||
|
* [time.Duration](https://golang.org/pkg/time/#Duration) |
||||||
|
|
||||||
|
Embedded structs using these fields are also supported. |
||||||
|
|
||||||
|
## Custom Decoders |
||||||
|
|
||||||
|
Any field whose type (or pointer-to-type) implements `envconfig.Decoder` can |
||||||
|
control its own deserialization: |
||||||
|
|
||||||
|
```Bash |
||||||
|
export DNS_SERVER=8.8.8.8 |
||||||
|
``` |
||||||
|
|
||||||
|
```Go |
||||||
|
type IPDecoder net.IP |
||||||
|
|
||||||
|
func (ipd *IPDecoder) Decode(value string) error { |
||||||
|
*ipd = IPDecoder(net.ParseIP(value)) |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
type DNSConfig struct { |
||||||
|
Address IPDecoder `envconfig:"DNS_SERVER"` |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
Also, envconfig will use a `Set(string) error` method like from the |
||||||
|
[flag.Value](https://godoc.org/flag#Value) interface if implemented. |
@ -0,0 +1,8 @@ |
|||||||
|
// Copyright (c) 2013 Kelsey Hightower. All rights reserved.
|
||||||
|
// Use of this source code is governed by the MIT License that can be found in
|
||||||
|
// the LICENSE file.
|
||||||
|
|
||||||
|
// Package envconfig implements decoding of environment variables based on a user
|
||||||
|
// defined specification. A typical use is using environment variables for
|
||||||
|
// configuration settings.
|
||||||
|
package envconfig |
@ -0,0 +1,7 @@ |
|||||||
|
// +build appengine go1.5
|
||||||
|
|
||||||
|
package envconfig |
||||||
|
|
||||||
|
import "os" |
||||||
|
|
||||||
|
var lookupEnv = os.LookupEnv |
@ -0,0 +1,7 @@ |
|||||||
|
// +build !appengine,!go1.5
|
||||||
|
|
||||||
|
package envconfig |
||||||
|
|
||||||
|
import "syscall" |
||||||
|
|
||||||
|
var lookupEnv = syscall.Getenv |
@ -0,0 +1,382 @@ |
|||||||
|
// Copyright (c) 2013 Kelsey Hightower. 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" |
||||||
|
"errors" |
||||||
|
"fmt" |
||||||
|
"os" |
||||||
|
"reflect" |
||||||
|
"regexp" |
||||||
|
"strconv" |
||||||
|
"strings" |
||||||
|
"time" |
||||||
|
) |
||||||
|
|
||||||
|
// ErrInvalidSpecification indicates that a specification is of the wrong type.
|
||||||
|
var ErrInvalidSpecification = errors.New("specification must be a struct pointer") |
||||||
|
|
||||||
|
var gatherRegexp = regexp.MustCompile("([^A-Z]+|[A-Z]+[^A-Z]+|[A-Z]+)") |
||||||
|
var acronymRegexp = regexp.MustCompile("([A-Z]+)([A-Z][^A-Z]+)") |
||||||
|
|
||||||
|
// A ParseError occurs when an environment variable cannot be converted to
|
||||||
|
// the type required by a struct field during assignment.
|
||||||
|
type ParseError struct { |
||||||
|
KeyName string |
||||||
|
FieldName string |
||||||
|
TypeName string |
||||||
|
Value string |
||||||
|
Err error |
||||||
|
} |
||||||
|
|
||||||
|
// Decoder has the same semantics as Setter, but takes higher precedence.
|
||||||
|
// It is provided for historical compatibility.
|
||||||
|
type Decoder interface { |
||||||
|
Decode(value string) error |
||||||
|
} |
||||||
|
|
||||||
|
// Setter is implemented by types can self-deserialize values.
|
||||||
|
// Any type that implements flag.Value also implements Setter.
|
||||||
|
type Setter interface { |
||||||
|
Set(value string) error |
||||||
|
} |
||||||
|
|
||||||
|
func (e *ParseError) Error() string { |
||||||
|
return fmt.Sprintf("envconfig.Process: assigning %[1]s to %[2]s: converting '%[3]s' to type %[4]s. details: %[5]s", e.KeyName, e.FieldName, e.Value, e.TypeName, e.Err) |
||||||
|
} |
||||||
|
|
||||||
|
// varInfo maintains information about the configuration variable
|
||||||
|
type varInfo struct { |
||||||
|
Name string |
||||||
|
Alt string |
||||||
|
Key string |
||||||
|
Field reflect.Value |
||||||
|
Tags reflect.StructTag |
||||||
|
} |
||||||
|
|
||||||
|
// GatherInfo gathers information about the specified struct
|
||||||
|
func gatherInfo(prefix string, spec interface{}) ([]varInfo, error) { |
||||||
|
s := reflect.ValueOf(spec) |
||||||
|
|
||||||
|
if s.Kind() != reflect.Ptr { |
||||||
|
return nil, ErrInvalidSpecification |
||||||
|
} |
||||||
|
s = s.Elem() |
||||||
|
if s.Kind() != reflect.Struct { |
||||||
|
return nil, ErrInvalidSpecification |
||||||
|
} |
||||||
|
typeOfSpec := s.Type() |
||||||
|
|
||||||
|
// over allocate an info array, we will extend if needed later
|
||||||
|
infos := make([]varInfo, 0, s.NumField()) |
||||||
|
for i := 0; i < s.NumField(); i++ { |
||||||
|
f := s.Field(i) |
||||||
|
ftype := typeOfSpec.Field(i) |
||||||
|
if !f.CanSet() || isTrue(ftype.Tag.Get("ignored")) { |
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
for f.Kind() == reflect.Ptr { |
||||||
|
if f.IsNil() { |
||||||
|
if f.Type().Elem().Kind() != reflect.Struct { |
||||||
|
// nil pointer to a non-struct: leave it alone
|
||||||
|
break |
||||||
|
} |
||||||
|
// nil pointer to struct: create a zero instance
|
||||||
|
f.Set(reflect.New(f.Type().Elem())) |
||||||
|
} |
||||||
|
f = f.Elem() |
||||||
|
} |
||||||
|
|
||||||
|
// Capture information about the config variable
|
||||||
|
info := varInfo{ |
||||||
|
Name: ftype.Name, |
||||||
|
Field: f, |
||||||
|
Tags: ftype.Tag, |
||||||
|
Alt: strings.ToUpper(ftype.Tag.Get("envconfig")), |
||||||
|
} |
||||||
|
|
||||||
|
// Default to the field name as the env var name (will be upcased)
|
||||||
|
info.Key = info.Name |
||||||
|
|
||||||
|
// Best effort to un-pick camel casing as separate words
|
||||||
|
if isTrue(ftype.Tag.Get("split_words")) { |
||||||
|
words := gatherRegexp.FindAllStringSubmatch(ftype.Name, -1) |
||||||
|
if len(words) > 0 { |
||||||
|
var name []string |
||||||
|
for _, words := range words { |
||||||
|
if m := acronymRegexp.FindStringSubmatch(words[0]); len(m) == 3 { |
||||||
|
name = append(name, m[1], m[2]) |
||||||
|
} else { |
||||||
|
name = append(name, words[0]) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
info.Key = strings.Join(name, "_") |
||||||
|
} |
||||||
|
} |
||||||
|
if info.Alt != "" { |
||||||
|
info.Key = info.Alt |
||||||
|
} |
||||||
|
if prefix != "" { |
||||||
|
info.Key = fmt.Sprintf("%s_%s", prefix, info.Key) |
||||||
|
} |
||||||
|
info.Key = strings.ToUpper(info.Key) |
||||||
|
infos = append(infos, info) |
||||||
|
|
||||||
|
if f.Kind() == reflect.Struct { |
||||||
|
// honor Decode if present
|
||||||
|
if decoderFrom(f) == nil && setterFrom(f) == nil && textUnmarshaler(f) == nil && binaryUnmarshaler(f) == nil { |
||||||
|
innerPrefix := prefix |
||||||
|
if !ftype.Anonymous { |
||||||
|
innerPrefix = info.Key |
||||||
|
} |
||||||
|
|
||||||
|
embeddedPtr := f.Addr().Interface() |
||||||
|
embeddedInfos, err := gatherInfo(innerPrefix, embeddedPtr) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
infos = append(infos[:len(infos)-1], embeddedInfos...) |
||||||
|
|
||||||
|
continue |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return infos, nil |
||||||
|
} |
||||||
|
|
||||||
|
// CheckDisallowed checks that no environment variables with the prefix are set
|
||||||
|
// that we don't know how or want to parse. This is likely only meaningful with
|
||||||
|
// a non-empty prefix.
|
||||||
|
func CheckDisallowed(prefix string, spec interface{}) error { |
||||||
|
infos, err := gatherInfo(prefix, spec) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
vars := make(map[string]struct{}) |
||||||
|
for _, info := range infos { |
||||||
|
vars[info.Key] = struct{}{} |
||||||
|
} |
||||||
|
|
||||||
|
if prefix != "" { |
||||||
|
prefix = strings.ToUpper(prefix) + "_" |
||||||
|
} |
||||||
|
|
||||||
|
for _, env := range os.Environ() { |
||||||
|
if !strings.HasPrefix(env, prefix) { |
||||||
|
continue |
||||||
|
} |
||||||
|
v := strings.SplitN(env, "=", 2)[0] |
||||||
|
if _, found := vars[v]; !found { |
||||||
|
return fmt.Errorf("unknown environment variable %s", v) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// Process populates the specified struct based on environment variables
|
||||||
|
func Process(prefix string, spec interface{}) error { |
||||||
|
infos, err := gatherInfo(prefix, spec) |
||||||
|
|
||||||
|
for _, info := range infos { |
||||||
|
|
||||||
|
// `os.Getenv` cannot differentiate between an explicitly set empty value
|
||||||
|
// and an unset value. `os.LookupEnv` is preferred to `syscall.Getenv`,
|
||||||
|
// but it is only available in go1.5 or newer. We're using Go build tags
|
||||||
|
// here to use os.LookupEnv for >=go1.5
|
||||||
|
value, ok := lookupEnv(info.Key) |
||||||
|
if !ok && info.Alt != "" { |
||||||
|
value, ok = lookupEnv(info.Alt) |
||||||
|
} |
||||||
|
|
||||||
|
def := info.Tags.Get("default") |
||||||
|
if def != "" && !ok { |
||||||
|
value = def |
||||||
|
} |
||||||
|
|
||||||
|
req := info.Tags.Get("required") |
||||||
|
if !ok && def == "" { |
||||||
|
if isTrue(req) { |
||||||
|
key := info.Key |
||||||
|
if info.Alt != "" { |
||||||
|
key = info.Alt |
||||||
|
} |
||||||
|
return fmt.Errorf("required key %s missing value", key) |
||||||
|
} |
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
err = processField(value, info.Field) |
||||||
|
if err != nil { |
||||||
|
return &ParseError{ |
||||||
|
KeyName: info.Key, |
||||||
|
FieldName: info.Name, |
||||||
|
TypeName: info.Field.Type().String(), |
||||||
|
Value: value, |
||||||
|
Err: err, |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
// MustProcess is the same as Process but panics if an error occurs
|
||||||
|
func MustProcess(prefix string, spec interface{}) { |
||||||
|
if err := Process(prefix, spec); err != nil { |
||||||
|
panic(err) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func processField(value string, field reflect.Value) error { |
||||||
|
typ := field.Type() |
||||||
|
|
||||||
|
decoder := decoderFrom(field) |
||||||
|
if decoder != nil { |
||||||
|
return decoder.Decode(value) |
||||||
|
} |
||||||
|
// look for Set method if Decode not defined
|
||||||
|
setter := setterFrom(field) |
||||||
|
if setter != nil { |
||||||
|
return setter.Set(value) |
||||||
|
} |
||||||
|
|
||||||
|
if t := textUnmarshaler(field); t != nil { |
||||||
|
return t.UnmarshalText([]byte(value)) |
||||||
|
} |
||||||
|
|
||||||
|
if b := binaryUnmarshaler(field); b != nil { |
||||||
|
return b.UnmarshalBinary([]byte(value)) |
||||||
|
} |
||||||
|
|
||||||
|
if typ.Kind() == reflect.Ptr { |
||||||
|
typ = typ.Elem() |
||||||
|
if field.IsNil() { |
||||||
|
field.Set(reflect.New(typ)) |
||||||
|
} |
||||||
|
field = field.Elem() |
||||||
|
} |
||||||
|
|
||||||
|
switch typ.Kind() { |
||||||
|
case reflect.String: |
||||||
|
field.SetString(value) |
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: |
||||||
|
var ( |
||||||
|
val int64 |
||||||
|
err error |
||||||
|
) |
||||||
|
if field.Kind() == reflect.Int64 && typ.PkgPath() == "time" && typ.Name() == "Duration" { |
||||||
|
var d time.Duration |
||||||
|
d, err = time.ParseDuration(value) |
||||||
|
val = int64(d) |
||||||
|
} else { |
||||||
|
val, err = strconv.ParseInt(value, 0, typ.Bits()) |
||||||
|
} |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
field.SetInt(val) |
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: |
||||||
|
val, err := strconv.ParseUint(value, 0, typ.Bits()) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
field.SetUint(val) |
||||||
|
case reflect.Bool: |
||||||
|
val, err := strconv.ParseBool(value) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
field.SetBool(val) |
||||||
|
case reflect.Float32, reflect.Float64: |
||||||
|
val, err := strconv.ParseFloat(value, typ.Bits()) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
field.SetFloat(val) |
||||||
|
case reflect.Slice: |
||||||
|
sl := reflect.MakeSlice(typ, 0, 0) |
||||||
|
if typ.Elem().Kind() == reflect.Uint8 { |
||||||
|
sl = reflect.ValueOf([]byte(value)) |
||||||
|
} else if len(strings.TrimSpace(value)) != 0 { |
||||||
|
vals := strings.Split(value, ",") |
||||||
|
sl = reflect.MakeSlice(typ, len(vals), len(vals)) |
||||||
|
for i, val := range vals { |
||||||
|
err := processField(val, sl.Index(i)) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
field.Set(sl) |
||||||
|
case reflect.Map: |
||||||
|
mp := reflect.MakeMap(typ) |
||||||
|
if len(strings.TrimSpace(value)) != 0 { |
||||||
|
pairs := strings.Split(value, ",") |
||||||
|
for _, pair := range pairs { |
||||||
|
kvpair := strings.Split(pair, ":") |
||||||
|
if len(kvpair) != 2 { |
||||||
|
return fmt.Errorf("invalid map item: %q", pair) |
||||||
|
} |
||||||
|
k := reflect.New(typ.Key()).Elem() |
||||||
|
err := processField(kvpair[0], k) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
v := reflect.New(typ.Elem()).Elem() |
||||||
|
err = processField(kvpair[1], v) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
mp.SetMapIndex(k, v) |
||||||
|
} |
||||||
|
} |
||||||
|
field.Set(mp) |
||||||
|
} |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func interfaceFrom(field reflect.Value, fn func(interface{}, *bool)) { |
||||||
|
// it may be impossible for a struct field to fail this check
|
||||||
|
if !field.CanInterface() { |
||||||
|
return |
||||||
|
} |
||||||
|
var ok bool |
||||||
|
fn(field.Interface(), &ok) |
||||||
|
if !ok && field.CanAddr() { |
||||||
|
fn(field.Addr().Interface(), &ok) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func decoderFrom(field reflect.Value) (d Decoder) { |
||||||
|
interfaceFrom(field, func(v interface{}, ok *bool) { d, *ok = v.(Decoder) }) |
||||||
|
return d |
||||||
|
} |
||||||
|
|
||||||
|
func setterFrom(field reflect.Value) (s Setter) { |
||||||
|
interfaceFrom(field, func(v interface{}, ok *bool) { s, *ok = v.(Setter) }) |
||||||
|
return s |
||||||
|
} |
||||||
|
|
||||||
|
func textUnmarshaler(field reflect.Value) (t encoding.TextUnmarshaler) { |
||||||
|
interfaceFrom(field, func(v interface{}, ok *bool) { t, *ok = v.(encoding.TextUnmarshaler) }) |
||||||
|
return t |
||||||
|
} |
||||||
|
|
||||||
|
func binaryUnmarshaler(field reflect.Value) (b encoding.BinaryUnmarshaler) { |
||||||
|
interfaceFrom(field, func(v interface{}, ok *bool) { b, *ok = v.(encoding.BinaryUnmarshaler) }) |
||||||
|
return b |
||||||
|
} |
||||||
|
|
||||||
|
func isTrue(s string) bool { |
||||||
|
b, _ := strconv.ParseBool(s) |
||||||
|
return b |
||||||
|
} |
@ -0,0 +1,164 @@ |
|||||||
|
// 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 tabular 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() |
||||||
|
textUnmarshalerType = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem() |
||||||
|
binaryUnmarshalerType = reflect.TypeOf((*encoding.BinaryUnmarshaler)(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(textUnmarshalerType) || |
||||||
|
reflect.PtrTo(t).Implements(textUnmarshalerType) || |
||||||
|
t.Implements(binaryUnmarshalerType) || |
||||||
|
reflect.PtrTo(t).Implements(binaryUnmarshalerType) |
||||||
|
} |
||||||
|
|
||||||
|
// toTypeDescription converts Go types into a human readable description
|
||||||
|
func toTypeDescription(t reflect.Type) string { |
||||||
|
switch t.Kind() { |
||||||
|
case reflect.Array, reflect.Slice: |
||||||
|
if t.Elem().Kind() == reflect.Uint8 { |
||||||
|
return "String" |
||||||
|
} |
||||||
|
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 stdout 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) |
||||||
|
} |
@ -0,0 +1,3 @@ |
|||||||
|
# github.com/kelseyhightower/envconfig v1.4.0 |
||||||
|
## explicit |
||||||
|
github.com/kelseyhightower/envconfig |
Loading…
Reference in new issue