+7
@@ -0,0 +1,7 @@
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.4
|
||||
- 1.5
|
||||
- 1.6
|
||||
- tip
|
||||
+19
@@ -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.
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
Kelsey Hightower kelsey.hightower@gmail.com github.com/kelseyhightower
|
||||
Travis Parker travis.parker@gmail.com github.com/teepark
|
||||
+188
@@ -0,0 +1,188 @@
|
||||
# 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)
|
||||
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"`
|
||||
}
|
||||
```
|
||||
|
||||
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 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 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)
|
||||
|
||||
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.
|
||||
+8
@@ -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
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
// +build appengine
|
||||
|
||||
package envconfig
|
||||
|
||||
import "os"
|
||||
|
||||
var lookupEnv = os.LookupEnv
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
// +build !appengine
|
||||
|
||||
package envconfig
|
||||
|
||||
import "syscall"
|
||||
|
||||
var lookupEnv = syscall.Getenv
|
||||
+319
@@ -0,0 +1,319 @@
|
||||
// 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"
|
||||
"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")
|
||||
|
||||
// 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) {
|
||||
expr := regexp.MustCompile("([^A-Z]+|[A-Z][^A-Z]+|[A-Z]+)")
|
||||
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() || ftype.Tag.Get("ignored") == "true" {
|
||||
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 ftype.Tag.Get("split_words") == "true" {
|
||||
words := expr.FindAllStringSubmatch(ftype.Name, -1)
|
||||
if len(words) > 0 {
|
||||
var name []string
|
||||
for _, words := range words {
|
||||
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 {
|
||||
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
|
||||
}
|
||||
|
||||
// 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 req == "true" {
|
||||
return fmt.Errorf("required key %s missing value", info.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 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:
|
||||
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:
|
||||
pairs := strings.Split(value, ",")
|
||||
mp := reflect.MakeMap(typ)
|
||||
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
|
||||
}
|
||||
+688
@@ -0,0 +1,688 @@
|
||||
// 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 (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
type HonorDecodeInStruct struct {
|
||||
Value string
|
||||
}
|
||||
|
||||
func (h *HonorDecodeInStruct) Decode(env string) error {
|
||||
h.Value = "decoded"
|
||||
return nil
|
||||
}
|
||||
|
||||
type Specification struct {
|
||||
Embedded `desc:"can we document a struct"`
|
||||
EmbeddedButIgnored `ignored:"true"`
|
||||
Debug bool
|
||||
Port int
|
||||
Rate float32
|
||||
User string
|
||||
TTL uint32
|
||||
Timeout time.Duration
|
||||
AdminUsers []string
|
||||
MagicNumbers []int
|
||||
ColorCodes map[string]int
|
||||
MultiWordVar string
|
||||
MultiWordVarWithAutoSplit uint32 `split_words:"true"`
|
||||
SomePointer *string
|
||||
SomePointerWithDefault *string `default:"foo2baz" desc:"foorbar is the word"`
|
||||
MultiWordVarWithAlt string `envconfig:"MULTI_WORD_VAR_WITH_ALT" desc:"what alt"`
|
||||
MultiWordVarWithLowerCaseAlt string `envconfig:"multi_word_var_with_lower_case_alt"`
|
||||
NoPrefixWithAlt string `envconfig:"SERVICE_HOST"`
|
||||
DefaultVar string `default:"foobar"`
|
||||
RequiredVar string `required:"true"`
|
||||
NoPrefixDefault string `envconfig:"BROKER" default:"127.0.0.1"`
|
||||
RequiredDefault string `required:"true" default:"foo2bar"`
|
||||
Ignored string `ignored:"true"`
|
||||
NestedSpecification struct {
|
||||
Property string `envconfig:"inner"`
|
||||
PropertyWithDefault string `default:"fuzzybydefault"`
|
||||
} `envconfig:"outer"`
|
||||
AfterNested string
|
||||
DecodeStruct HonorDecodeInStruct `envconfig:"honor"`
|
||||
Datetime time.Time
|
||||
}
|
||||
|
||||
type Embedded struct {
|
||||
Enabled bool `desc:"some embedded value"`
|
||||
EmbeddedPort int
|
||||
MultiWordVar string
|
||||
MultiWordVarWithAlt string `envconfig:"MULTI_WITH_DIFFERENT_ALT"`
|
||||
EmbeddedAlt string `envconfig:"EMBEDDED_WITH_ALT"`
|
||||
EmbeddedIgnored string `ignored:"true"`
|
||||
}
|
||||
|
||||
type EmbeddedButIgnored struct {
|
||||
FirstEmbeddedButIgnored string
|
||||
SecondEmbeddedButIgnored string
|
||||
}
|
||||
|
||||
func TestProcess(t *testing.T) {
|
||||
var s Specification
|
||||
os.Clearenv()
|
||||
os.Setenv("ENV_CONFIG_DEBUG", "true")
|
||||
os.Setenv("ENV_CONFIG_PORT", "8080")
|
||||
os.Setenv("ENV_CONFIG_RATE", "0.5")
|
||||
os.Setenv("ENV_CONFIG_USER", "Kelsey")
|
||||
os.Setenv("ENV_CONFIG_TIMEOUT", "2m")
|
||||
os.Setenv("ENV_CONFIG_ADMINUSERS", "John,Adam,Will")
|
||||
os.Setenv("ENV_CONFIG_MAGICNUMBERS", "5,10,20")
|
||||
os.Setenv("ENV_CONFIG_COLORCODES", "red:1,green:2,blue:3")
|
||||
os.Setenv("SERVICE_HOST", "127.0.0.1")
|
||||
os.Setenv("ENV_CONFIG_TTL", "30")
|
||||
os.Setenv("ENV_CONFIG_REQUIREDVAR", "foo")
|
||||
os.Setenv("ENV_CONFIG_IGNORED", "was-not-ignored")
|
||||
os.Setenv("ENV_CONFIG_OUTER_INNER", "iamnested")
|
||||
os.Setenv("ENV_CONFIG_AFTERNESTED", "after")
|
||||
os.Setenv("ENV_CONFIG_HONOR", "honor")
|
||||
os.Setenv("ENV_CONFIG_DATETIME", "2016-08-16T18:57:05Z")
|
||||
os.Setenv("ENV_CONFIG_MULTI_WORD_VAR_WITH_AUTO_SPLIT", "24")
|
||||
err := Process("env_config", &s)
|
||||
if err != nil {
|
||||
t.Error(err.Error())
|
||||
}
|
||||
if s.NoPrefixWithAlt != "127.0.0.1" {
|
||||
t.Errorf("expected %v, got %v", "127.0.0.1", s.NoPrefixWithAlt)
|
||||
}
|
||||
if !s.Debug {
|
||||
t.Errorf("expected %v, got %v", true, s.Debug)
|
||||
}
|
||||
if s.Port != 8080 {
|
||||
t.Errorf("expected %d, got %v", 8080, s.Port)
|
||||
}
|
||||
if s.Rate != 0.5 {
|
||||
t.Errorf("expected %f, got %v", 0.5, s.Rate)
|
||||
}
|
||||
if s.TTL != 30 {
|
||||
t.Errorf("expected %d, got %v", 30, s.TTL)
|
||||
}
|
||||
if s.User != "Kelsey" {
|
||||
t.Errorf("expected %s, got %s", "Kelsey", s.User)
|
||||
}
|
||||
if s.Timeout != 2*time.Minute {
|
||||
t.Errorf("expected %s, got %s", 2*time.Minute, s.Timeout)
|
||||
}
|
||||
if s.RequiredVar != "foo" {
|
||||
t.Errorf("expected %s, got %s", "foo", s.RequiredVar)
|
||||
}
|
||||
if len(s.AdminUsers) != 3 ||
|
||||
s.AdminUsers[0] != "John" ||
|
||||
s.AdminUsers[1] != "Adam" ||
|
||||
s.AdminUsers[2] != "Will" {
|
||||
t.Errorf("expected %#v, got %#v", []string{"John", "Adam", "Will"}, s.AdminUsers)
|
||||
}
|
||||
if len(s.MagicNumbers) != 3 ||
|
||||
s.MagicNumbers[0] != 5 ||
|
||||
s.MagicNumbers[1] != 10 ||
|
||||
s.MagicNumbers[2] != 20 {
|
||||
t.Errorf("expected %#v, got %#v", []int{5, 10, 20}, s.MagicNumbers)
|
||||
}
|
||||
if s.Ignored != "" {
|
||||
t.Errorf("expected empty string, got %#v", s.Ignored)
|
||||
}
|
||||
|
||||
if len(s.ColorCodes) != 3 ||
|
||||
s.ColorCodes["red"] != 1 ||
|
||||
s.ColorCodes["green"] != 2 ||
|
||||
s.ColorCodes["blue"] != 3 {
|
||||
t.Errorf(
|
||||
"expected %#v, got %#v",
|
||||
map[string]int{
|
||||
"red": 1,
|
||||
"green": 2,
|
||||
"blue": 3,
|
||||
},
|
||||
s.ColorCodes,
|
||||
)
|
||||
}
|
||||
|
||||
if s.NestedSpecification.Property != "iamnested" {
|
||||
t.Errorf("expected '%s' string, got %#v", "iamnested", s.NestedSpecification.Property)
|
||||
}
|
||||
|
||||
if s.NestedSpecification.PropertyWithDefault != "fuzzybydefault" {
|
||||
t.Errorf("expected default '%s' string, got %#v", "fuzzybydefault", s.NestedSpecification.PropertyWithDefault)
|
||||
}
|
||||
|
||||
if s.AfterNested != "after" {
|
||||
t.Errorf("expected default '%s' string, got %#v", "after", s.AfterNested)
|
||||
}
|
||||
|
||||
if s.DecodeStruct.Value != "decoded" {
|
||||
t.Errorf("expected default '%s' string, got %#v", "decoded", s.DecodeStruct.Value)
|
||||
}
|
||||
|
||||
if expected := time.Date(2016, 8, 16, 18, 57, 05, 0, time.UTC); !s.Datetime.Equal(expected) {
|
||||
t.Errorf("expected %s, got %s", expected.Format(time.RFC3339), s.Datetime.Format(time.RFC3339))
|
||||
}
|
||||
|
||||
if s.MultiWordVarWithAutoSplit != 24 {
|
||||
t.Errorf("expected %q, got %q", 24, s.MultiWordVarWithAutoSplit)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseErrorBool(t *testing.T) {
|
||||
var s Specification
|
||||
os.Clearenv()
|
||||
os.Setenv("ENV_CONFIG_DEBUG", "string")
|
||||
os.Setenv("ENV_CONFIG_REQUIREDVAR", "foo")
|
||||
err := Process("env_config", &s)
|
||||
v, ok := err.(*ParseError)
|
||||
if !ok {
|
||||
t.Errorf("expected ParseError, got %v", v)
|
||||
}
|
||||
if v.FieldName != "Debug" {
|
||||
t.Errorf("expected %s, got %v", "Debug", v.FieldName)
|
||||
}
|
||||
if s.Debug != false {
|
||||
t.Errorf("expected %v, got %v", false, s.Debug)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseErrorFloat32(t *testing.T) {
|
||||
var s Specification
|
||||
os.Clearenv()
|
||||
os.Setenv("ENV_CONFIG_RATE", "string")
|
||||
os.Setenv("ENV_CONFIG_REQUIREDVAR", "foo")
|
||||
err := Process("env_config", &s)
|
||||
v, ok := err.(*ParseError)
|
||||
if !ok {
|
||||
t.Errorf("expected ParseError, got %v", v)
|
||||
}
|
||||
if v.FieldName != "Rate" {
|
||||
t.Errorf("expected %s, got %v", "Rate", v.FieldName)
|
||||
}
|
||||
if s.Rate != 0 {
|
||||
t.Errorf("expected %v, got %v", 0, s.Rate)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseErrorInt(t *testing.T) {
|
||||
var s Specification
|
||||
os.Clearenv()
|
||||
os.Setenv("ENV_CONFIG_PORT", "string")
|
||||
os.Setenv("ENV_CONFIG_REQUIREDVAR", "foo")
|
||||
err := Process("env_config", &s)
|
||||
v, ok := err.(*ParseError)
|
||||
if !ok {
|
||||
t.Errorf("expected ParseError, got %v", v)
|
||||
}
|
||||
if v.FieldName != "Port" {
|
||||
t.Errorf("expected %s, got %v", "Port", v.FieldName)
|
||||
}
|
||||
if s.Port != 0 {
|
||||
t.Errorf("expected %v, got %v", 0, s.Port)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseErrorUint(t *testing.T) {
|
||||
var s Specification
|
||||
os.Clearenv()
|
||||
os.Setenv("ENV_CONFIG_TTL", "-30")
|
||||
err := Process("env_config", &s)
|
||||
v, ok := err.(*ParseError)
|
||||
if !ok {
|
||||
t.Errorf("expected ParseError, got %v", v)
|
||||
}
|
||||
if v.FieldName != "TTL" {
|
||||
t.Errorf("expected %s, got %v", "TTL", v.FieldName)
|
||||
}
|
||||
if s.TTL != 0 {
|
||||
t.Errorf("expected %v, got %v", 0, s.TTL)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseErrorSplitWords(t *testing.T) {
|
||||
var s Specification
|
||||
os.Clearenv()
|
||||
os.Setenv("ENV_CONFIG_MULTI_WORD_VAR_WITH_AUTO_SPLIT", "shakespeare")
|
||||
err := Process("env_config", &s)
|
||||
v, ok := err.(*ParseError)
|
||||
if !ok {
|
||||
t.Errorf("expected ParseError, got %v", v)
|
||||
}
|
||||
if v.FieldName != "MultiWordVarWithAutoSplit" {
|
||||
t.Errorf("expected %s, got %v", "", v.FieldName)
|
||||
}
|
||||
if s.MultiWordVarWithAutoSplit != 0 {
|
||||
t.Errorf("expected %v, got %v", 0, s.MultiWordVarWithAutoSplit)
|
||||
}
|
||||
}
|
||||
|
||||
func TestErrInvalidSpecification(t *testing.T) {
|
||||
m := make(map[string]string)
|
||||
err := Process("env_config", &m)
|
||||
if err != ErrInvalidSpecification {
|
||||
t.Errorf("expected %v, got %v", ErrInvalidSpecification, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnsetVars(t *testing.T) {
|
||||
var s Specification
|
||||
os.Clearenv()
|
||||
os.Setenv("USER", "foo")
|
||||
os.Setenv("ENV_CONFIG_REQUIREDVAR", "foo")
|
||||
if err := Process("env_config", &s); err != nil {
|
||||
t.Error(err.Error())
|
||||
}
|
||||
|
||||
// If the var is not defined the non-prefixed version should not be used
|
||||
// unless the struct tag says so
|
||||
if s.User != "" {
|
||||
t.Errorf("expected %q, got %q", "", s.User)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAlternateVarNames(t *testing.T) {
|
||||
var s Specification
|
||||
os.Clearenv()
|
||||
os.Setenv("ENV_CONFIG_MULTI_WORD_VAR", "foo")
|
||||
os.Setenv("ENV_CONFIG_MULTI_WORD_VAR_WITH_ALT", "bar")
|
||||
os.Setenv("ENV_CONFIG_MULTI_WORD_VAR_WITH_LOWER_CASE_ALT", "baz")
|
||||
os.Setenv("ENV_CONFIG_REQUIREDVAR", "foo")
|
||||
if err := Process("env_config", &s); err != nil {
|
||||
t.Error(err.Error())
|
||||
}
|
||||
|
||||
// Setting the alt version of the var in the environment has no effect if
|
||||
// the struct tag is not supplied
|
||||
if s.MultiWordVar != "" {
|
||||
t.Errorf("expected %q, got %q", "", s.MultiWordVar)
|
||||
}
|
||||
|
||||
// Setting the alt version of the var in the environment correctly sets
|
||||
// the value if the struct tag IS supplied
|
||||
if s.MultiWordVarWithAlt != "bar" {
|
||||
t.Errorf("expected %q, got %q", "bar", s.MultiWordVarWithAlt)
|
||||
}
|
||||
|
||||
// Alt value is not case sensitive and is treated as all uppercase
|
||||
if s.MultiWordVarWithLowerCaseAlt != "baz" {
|
||||
t.Errorf("expected %q, got %q", "baz", s.MultiWordVarWithLowerCaseAlt)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequiredVar(t *testing.T) {
|
||||
var s Specification
|
||||
os.Clearenv()
|
||||
os.Setenv("ENV_CONFIG_REQUIREDVAR", "foobar")
|
||||
if err := Process("env_config", &s); err != nil {
|
||||
t.Error(err.Error())
|
||||
}
|
||||
|
||||
if s.RequiredVar != "foobar" {
|
||||
t.Errorf("expected %s, got %s", "foobar", s.RequiredVar)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBlankDefaultVar(t *testing.T) {
|
||||
var s Specification
|
||||
os.Clearenv()
|
||||
os.Setenv("ENV_CONFIG_REQUIREDVAR", "requiredvalue")
|
||||
if err := Process("env_config", &s); err != nil {
|
||||
t.Error(err.Error())
|
||||
}
|
||||
|
||||
if s.DefaultVar != "foobar" {
|
||||
t.Errorf("expected %s, got %s", "foobar", s.DefaultVar)
|
||||
}
|
||||
|
||||
if *s.SomePointerWithDefault != "foo2baz" {
|
||||
t.Errorf("expected %s, got %s", "foo2baz", *s.SomePointerWithDefault)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNonBlankDefaultVar(t *testing.T) {
|
||||
var s Specification
|
||||
os.Clearenv()
|
||||
os.Setenv("ENV_CONFIG_DEFAULTVAR", "nondefaultval")
|
||||
os.Setenv("ENV_CONFIG_REQUIREDVAR", "requiredvalue")
|
||||
if err := Process("env_config", &s); err != nil {
|
||||
t.Error(err.Error())
|
||||
}
|
||||
|
||||
if s.DefaultVar != "nondefaultval" {
|
||||
t.Errorf("expected %s, got %s", "nondefaultval", s.DefaultVar)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExplicitBlankDefaultVar(t *testing.T) {
|
||||
var s Specification
|
||||
os.Clearenv()
|
||||
os.Setenv("ENV_CONFIG_DEFAULTVAR", "")
|
||||
os.Setenv("ENV_CONFIG_REQUIREDVAR", "")
|
||||
|
||||
if err := Process("env_config", &s); err != nil {
|
||||
t.Error(err.Error())
|
||||
}
|
||||
|
||||
if s.DefaultVar != "" {
|
||||
t.Errorf("expected %s, got %s", "\"\"", s.DefaultVar)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAlternateNameDefaultVar(t *testing.T) {
|
||||
var s Specification
|
||||
os.Clearenv()
|
||||
os.Setenv("BROKER", "betterbroker")
|
||||
os.Setenv("ENV_CONFIG_REQUIREDVAR", "foo")
|
||||
if err := Process("env_config", &s); err != nil {
|
||||
t.Error(err.Error())
|
||||
}
|
||||
|
||||
if s.NoPrefixDefault != "betterbroker" {
|
||||
t.Errorf("expected %q, got %q", "betterbroker", s.NoPrefixDefault)
|
||||
}
|
||||
|
||||
os.Clearenv()
|
||||
os.Setenv("ENV_CONFIG_REQUIREDVAR", "foo")
|
||||
if err := Process("env_config", &s); err != nil {
|
||||
t.Error(err.Error())
|
||||
}
|
||||
|
||||
if s.NoPrefixDefault != "127.0.0.1" {
|
||||
t.Errorf("expected %q, got %q", "127.0.0.1", s.NoPrefixDefault)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequiredDefault(t *testing.T) {
|
||||
var s Specification
|
||||
os.Clearenv()
|
||||
os.Setenv("ENV_CONFIG_REQUIREDVAR", "foo")
|
||||
if err := Process("env_config", &s); err != nil {
|
||||
t.Error(err.Error())
|
||||
}
|
||||
|
||||
if s.RequiredDefault != "foo2bar" {
|
||||
t.Errorf("expected %q, got %q", "foo2bar", s.RequiredDefault)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPointerFieldBlank(t *testing.T) {
|
||||
var s Specification
|
||||
os.Clearenv()
|
||||
os.Setenv("ENV_CONFIG_REQUIREDVAR", "foo")
|
||||
if err := Process("env_config", &s); err != nil {
|
||||
t.Error(err.Error())
|
||||
}
|
||||
|
||||
if s.SomePointer != nil {
|
||||
t.Errorf("expected <nil>, got %q", *s.SomePointer)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMustProcess(t *testing.T) {
|
||||
var s Specification
|
||||
os.Clearenv()
|
||||
os.Setenv("ENV_CONFIG_DEBUG", "true")
|
||||
os.Setenv("ENV_CONFIG_PORT", "8080")
|
||||
os.Setenv("ENV_CONFIG_RATE", "0.5")
|
||||
os.Setenv("ENV_CONFIG_USER", "Kelsey")
|
||||
os.Setenv("SERVICE_HOST", "127.0.0.1")
|
||||
os.Setenv("ENV_CONFIG_REQUIREDVAR", "foo")
|
||||
MustProcess("env_config", &s)
|
||||
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
t.Error("expected panic")
|
||||
}()
|
||||
m := make(map[string]string)
|
||||
MustProcess("env_config", &m)
|
||||
}
|
||||
|
||||
func TestEmbeddedStruct(t *testing.T) {
|
||||
var s Specification
|
||||
os.Clearenv()
|
||||
os.Setenv("ENV_CONFIG_REQUIREDVAR", "required")
|
||||
os.Setenv("ENV_CONFIG_ENABLED", "true")
|
||||
os.Setenv("ENV_CONFIG_EMBEDDEDPORT", "1234")
|
||||
os.Setenv("ENV_CONFIG_MULTIWORDVAR", "foo")
|
||||
os.Setenv("ENV_CONFIG_MULTI_WORD_VAR_WITH_ALT", "bar")
|
||||
os.Setenv("ENV_CONFIG_MULTI_WITH_DIFFERENT_ALT", "baz")
|
||||
os.Setenv("ENV_CONFIG_EMBEDDED_WITH_ALT", "foobar")
|
||||
os.Setenv("ENV_CONFIG_SOMEPOINTER", "foobaz")
|
||||
os.Setenv("ENV_CONFIG_EMBEDDED_IGNORED", "was-not-ignored")
|
||||
if err := Process("env_config", &s); err != nil {
|
||||
t.Error(err.Error())
|
||||
}
|
||||
if !s.Enabled {
|
||||
t.Errorf("expected %v, got %v", true, s.Enabled)
|
||||
}
|
||||
if s.EmbeddedPort != 1234 {
|
||||
t.Errorf("expected %d, got %v", 1234, s.EmbeddedPort)
|
||||
}
|
||||
if s.MultiWordVar != "foo" {
|
||||
t.Errorf("expected %s, got %s", "foo", s.MultiWordVar)
|
||||
}
|
||||
if s.Embedded.MultiWordVar != "foo" {
|
||||
t.Errorf("expected %s, got %s", "foo", s.Embedded.MultiWordVar)
|
||||
}
|
||||
if s.MultiWordVarWithAlt != "bar" {
|
||||
t.Errorf("expected %s, got %s", "bar", s.MultiWordVarWithAlt)
|
||||
}
|
||||
if s.Embedded.MultiWordVarWithAlt != "baz" {
|
||||
t.Errorf("expected %s, got %s", "baz", s.Embedded.MultiWordVarWithAlt)
|
||||
}
|
||||
if s.EmbeddedAlt != "foobar" {
|
||||
t.Errorf("expected %s, got %s", "foobar", s.EmbeddedAlt)
|
||||
}
|
||||
if *s.SomePointer != "foobaz" {
|
||||
t.Errorf("expected %s, got %s", "foobaz", *s.SomePointer)
|
||||
}
|
||||
if s.EmbeddedIgnored != "" {
|
||||
t.Errorf("expected empty string, got %#v", s.Ignored)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmbeddedButIgnoredStruct(t *testing.T) {
|
||||
var s Specification
|
||||
os.Clearenv()
|
||||
os.Setenv("ENV_CONFIG_REQUIREDVAR", "required")
|
||||
os.Setenv("ENV_CONFIG_FIRSTEMBEDDEDBUTIGNORED", "was-not-ignored")
|
||||
os.Setenv("ENV_CONFIG_SECONDEMBEDDEDBUTIGNORED", "was-not-ignored")
|
||||
if err := Process("env_config", &s); err != nil {
|
||||
t.Error(err.Error())
|
||||
}
|
||||
if s.FirstEmbeddedButIgnored != "" {
|
||||
t.Errorf("expected empty string, got %#v", s.Ignored)
|
||||
}
|
||||
if s.SecondEmbeddedButIgnored != "" {
|
||||
t.Errorf("expected empty string, got %#v", s.Ignored)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNonPointerFailsProperly(t *testing.T) {
|
||||
var s Specification
|
||||
os.Clearenv()
|
||||
os.Setenv("ENV_CONFIG_REQUIREDVAR", "snap")
|
||||
|
||||
err := Process("env_config", s)
|
||||
if err != ErrInvalidSpecification {
|
||||
t.Errorf("non-pointer should fail with ErrInvalidSpecification, was instead %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCustomValueFields(t *testing.T) {
|
||||
var s struct {
|
||||
Foo string
|
||||
Bar bracketed
|
||||
Baz quoted
|
||||
Struct setterStruct
|
||||
}
|
||||
|
||||
// Set would panic when the receiver is nil,
|
||||
// so make sure it has an initial value to replace.
|
||||
s.Baz = quoted{new(bracketed)}
|
||||
|
||||
os.Clearenv()
|
||||
os.Setenv("ENV_CONFIG_FOO", "foo")
|
||||
os.Setenv("ENV_CONFIG_BAR", "bar")
|
||||
os.Setenv("ENV_CONFIG_BAZ", "baz")
|
||||
os.Setenv("ENV_CONFIG_STRUCT", "inner")
|
||||
|
||||
if err := Process("env_config", &s); err != nil {
|
||||
t.Error(err.Error())
|
||||
}
|
||||
|
||||
if want := "foo"; s.Foo != want {
|
||||
t.Errorf("foo: got %#q, want %#q", s.Foo, want)
|
||||
}
|
||||
|
||||
if want := "[bar]"; s.Bar.String() != want {
|
||||
t.Errorf("bar: got %#q, want %#q", s.Bar, want)
|
||||
}
|
||||
|
||||
if want := `["baz"]`; s.Baz.String() != want {
|
||||
t.Errorf(`baz: got %#q, want %#q`, s.Baz, want)
|
||||
}
|
||||
|
||||
if want := `setterstruct{"inner"}`; s.Struct.Inner != want {
|
||||
t.Errorf(`Struct.Inner: got %#q, want %#q`, s.Struct.Inner, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCustomPointerFields(t *testing.T) {
|
||||
var s struct {
|
||||
Foo string
|
||||
Bar *bracketed
|
||||
Baz *quoted
|
||||
Struct *setterStruct
|
||||
}
|
||||
|
||||
// Set would panic when the receiver is nil,
|
||||
// so make sure they have initial values to replace.
|
||||
s.Bar = new(bracketed)
|
||||
s.Baz = "ed{new(bracketed)}
|
||||
|
||||
os.Clearenv()
|
||||
os.Setenv("ENV_CONFIG_FOO", "foo")
|
||||
os.Setenv("ENV_CONFIG_BAR", "bar")
|
||||
os.Setenv("ENV_CONFIG_BAZ", "baz")
|
||||
os.Setenv("ENV_CONFIG_STRUCT", "inner")
|
||||
|
||||
if err := Process("env_config", &s); err != nil {
|
||||
t.Error(err.Error())
|
||||
}
|
||||
|
||||
if want := "foo"; s.Foo != want {
|
||||
t.Errorf("foo: got %#q, want %#q", s.Foo, want)
|
||||
}
|
||||
|
||||
if want := "[bar]"; s.Bar.String() != want {
|
||||
t.Errorf("bar: got %#q, want %#q", s.Bar, want)
|
||||
}
|
||||
|
||||
if want := `["baz"]`; s.Baz.String() != want {
|
||||
t.Errorf(`baz: got %#q, want %#q`, s.Baz, want)
|
||||
}
|
||||
|
||||
if want := `setterstruct{"inner"}`; s.Struct.Inner != want {
|
||||
t.Errorf(`Struct.Inner: got %#q, want %#q`, s.Struct.Inner, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmptyPrefixUsesFieldNames(t *testing.T) {
|
||||
var s Specification
|
||||
os.Clearenv()
|
||||
os.Setenv("REQUIREDVAR", "foo")
|
||||
|
||||
err := Process("", &s)
|
||||
if err != nil {
|
||||
t.Errorf("Process failed: %s", err)
|
||||
}
|
||||
|
||||
if s.RequiredVar != "foo" {
|
||||
t.Errorf(
|
||||
`RequiredVar not populated correctly: expected "foo", got %q`,
|
||||
s.RequiredVar,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNestedStructVarName(t *testing.T) {
|
||||
var s Specification
|
||||
os.Clearenv()
|
||||
os.Setenv("ENV_CONFIG_REQUIREDVAR", "required")
|
||||
val := "found with only short name"
|
||||
os.Setenv("INNER", val)
|
||||
if err := Process("env_config", &s); err != nil {
|
||||
t.Error(err.Error())
|
||||
}
|
||||
if s.NestedSpecification.Property != val {
|
||||
t.Errorf("expected %s, got %s", val, s.NestedSpecification.Property)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTextUnmarshalerError(t *testing.T) {
|
||||
var s Specification
|
||||
os.Clearenv()
|
||||
os.Setenv("ENV_CONFIG_REQUIREDVAR", "foo")
|
||||
os.Setenv("ENV_CONFIG_DATETIME", "I'M NOT A DATE")
|
||||
|
||||
err := Process("env_config", &s)
|
||||
|
||||
v, ok := err.(*ParseError)
|
||||
if !ok {
|
||||
t.Errorf("expected ParseError, got %v", v)
|
||||
}
|
||||
if v.FieldName != "Datetime" {
|
||||
t.Errorf("expected %s, got %v", "Debug", v.FieldName)
|
||||
}
|
||||
|
||||
expectedLowLevelError := time.ParseError{
|
||||
Layout: time.RFC3339,
|
||||
Value: "I'M NOT A DATE",
|
||||
LayoutElem: "2006",
|
||||
ValueElem: "I'M NOT A DATE",
|
||||
}
|
||||
|
||||
if v.Err.Error() != expectedLowLevelError.Error() {
|
||||
t.Errorf("expected %s, got %s", expectedLowLevelError, v.Err)
|
||||
}
|
||||
if s.Debug != false {
|
||||
t.Errorf("expected %v, got %v", false, s.Debug)
|
||||
}
|
||||
}
|
||||
|
||||
type bracketed string
|
||||
|
||||
func (b *bracketed) Set(value string) error {
|
||||
*b = bracketed("[" + value + "]")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b bracketed) String() string {
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// quoted is used to test the precedence of Decode over Set.
|
||||
// The sole field is a flag.Value rather than a setter to validate that
|
||||
// all flag.Value implementations are also Setter implementations.
|
||||
type quoted struct{ flag.Value }
|
||||
|
||||
func (d quoted) Decode(value string) error {
|
||||
return d.Set(`"` + value + `"`)
|
||||
}
|
||||
|
||||
type setterStruct struct {
|
||||
Inner string
|
||||
}
|
||||
|
||||
func (ss *setterStruct) Set(value string) error {
|
||||
ss.Inner = fmt.Sprintf("setterstruct{%q}", value)
|
||||
return nil
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
ENV_CONFIG_ENABLED=some.embedded.value
|
||||
ENV_CONFIG_EMBEDDEDPORT=
|
||||
ENV_CONFIG_MULTIWORDVAR=
|
||||
ENV_CONFIG_MULTI_WITH_DIFFERENT_ALT=
|
||||
ENV_CONFIG_EMBEDDED_WITH_ALT=
|
||||
ENV_CONFIG_DEBUG=
|
||||
ENV_CONFIG_PORT=
|
||||
ENV_CONFIG_RATE=
|
||||
ENV_CONFIG_USER=
|
||||
ENV_CONFIG_TTL=
|
||||
ENV_CONFIG_TIMEOUT=
|
||||
ENV_CONFIG_ADMINUSERS=
|
||||
ENV_CONFIG_MAGICNUMBERS=
|
||||
ENV_CONFIG_COLORCODES=
|
||||
ENV_CONFIG_MULTIWORDVAR=
|
||||
ENV_CONFIG_MULTI_WORD_VAR_WITH_AUTO_SPLIT=
|
||||
ENV_CONFIG_SOMEPOINTER=
|
||||
ENV_CONFIG_SOMEPOINTERWITHDEFAULT=foorbar.is.the.word
|
||||
ENV_CONFIG_MULTI_WORD_VAR_WITH_ALT=what.alt
|
||||
ENV_CONFIG_MULTI_WORD_VAR_WITH_LOWER_CASE_ALT=
|
||||
ENV_CONFIG_SERVICE_HOST=
|
||||
ENV_CONFIG_DEFAULTVAR=
|
||||
ENV_CONFIG_REQUIREDVAR=
|
||||
ENV_CONFIG_BROKER=
|
||||
ENV_CONFIG_REQUIREDDEFAULT=
|
||||
ENV_CONFIG_OUTER_INNER=
|
||||
ENV_CONFIG_OUTER_PROPERTYWITHDEFAULT=
|
||||
ENV_CONFIG_AFTERNESTED=
|
||||
ENV_CONFIG_HONOR=
|
||||
ENV_CONFIG_DATETIME=
|
||||
+153
@@ -0,0 +1,153 @@
|
||||
This.application.is.configured.via.the.environment..The.following.environment
|
||||
variables.can.be.used:
|
||||
|
||||
ENV_CONFIG_ENABLED
|
||||
..[description].some.embedded.value
|
||||
..[type]........True.or.False
|
||||
..[default].....
|
||||
..[required]....
|
||||
ENV_CONFIG_EMBEDDEDPORT
|
||||
..[description].
|
||||
..[type]........Integer
|
||||
..[default].....
|
||||
..[required]....
|
||||
ENV_CONFIG_MULTIWORDVAR
|
||||
..[description].
|
||||
..[type]........String
|
||||
..[default].....
|
||||
..[required]....
|
||||
ENV_CONFIG_MULTI_WITH_DIFFERENT_ALT
|
||||
..[description].
|
||||
..[type]........String
|
||||
..[default].....
|
||||
..[required]....
|
||||
ENV_CONFIG_EMBEDDED_WITH_ALT
|
||||
..[description].
|
||||
..[type]........String
|
||||
..[default].....
|
||||
..[required]....
|
||||
ENV_CONFIG_DEBUG
|
||||
..[description].
|
||||
..[type]........True.or.False
|
||||
..[default].....
|
||||
..[required]....
|
||||
ENV_CONFIG_PORT
|
||||
..[description].
|
||||
..[type]........Integer
|
||||
..[default].....
|
||||
..[required]....
|
||||
ENV_CONFIG_RATE
|
||||
..[description].
|
||||
..[type]........Float
|
||||
..[default].....
|
||||
..[required]....
|
||||
ENV_CONFIG_USER
|
||||
..[description].
|
||||
..[type]........String
|
||||
..[default].....
|
||||
..[required]....
|
||||
ENV_CONFIG_TTL
|
||||
..[description].
|
||||
..[type]........Unsigned.Integer
|
||||
..[default].....
|
||||
..[required]....
|
||||
ENV_CONFIG_TIMEOUT
|
||||
..[description].
|
||||
..[type]........Duration
|
||||
..[default].....
|
||||
..[required]....
|
||||
ENV_CONFIG_ADMINUSERS
|
||||
..[description].
|
||||
..[type]........Comma-separated.list.of.String
|
||||
..[default].....
|
||||
..[required]....
|
||||
ENV_CONFIG_MAGICNUMBERS
|
||||
..[description].
|
||||
..[type]........Comma-separated.list.of.Integer
|
||||
..[default].....
|
||||
..[required]....
|
||||
ENV_CONFIG_COLORCODES
|
||||
..[description].
|
||||
..[type]........Comma-separated.list.of.String:Integer.pairs
|
||||
..[default].....
|
||||
..[required]....
|
||||
ENV_CONFIG_MULTIWORDVAR
|
||||
..[description].
|
||||
..[type]........String
|
||||
..[default].....
|
||||
..[required]....
|
||||
ENV_CONFIG_MULTI_WORD_VAR_WITH_AUTO_SPLIT
|
||||
..[description].
|
||||
..[type]........Unsigned.Integer
|
||||
..[default].....
|
||||
..[required]....
|
||||
ENV_CONFIG_SOMEPOINTER
|
||||
..[description].
|
||||
..[type]........String
|
||||
..[default].....
|
||||
..[required]....
|
||||
ENV_CONFIG_SOMEPOINTERWITHDEFAULT
|
||||
..[description].foorbar.is.the.word
|
||||
..[type]........String
|
||||
..[default].....foo2baz
|
||||
..[required]....
|
||||
ENV_CONFIG_MULTI_WORD_VAR_WITH_ALT
|
||||
..[description].what.alt
|
||||
..[type]........String
|
||||
..[default].....
|
||||
..[required]....
|
||||
ENV_CONFIG_MULTI_WORD_VAR_WITH_LOWER_CASE_ALT
|
||||
..[description].
|
||||
..[type]........String
|
||||
..[default].....
|
||||
..[required]....
|
||||
ENV_CONFIG_SERVICE_HOST
|
||||
..[description].
|
||||
..[type]........String
|
||||
..[default].....
|
||||
..[required]....
|
||||
ENV_CONFIG_DEFAULTVAR
|
||||
..[description].
|
||||
..[type]........String
|
||||
..[default].....foobar
|
||||
..[required]....
|
||||
ENV_CONFIG_REQUIREDVAR
|
||||
..[description].
|
||||
..[type]........String
|
||||
..[default].....
|
||||
..[required]....true
|
||||
ENV_CONFIG_BROKER
|
||||
..[description].
|
||||
..[type]........String
|
||||
..[default].....127.0.0.1
|
||||
..[required]....
|
||||
ENV_CONFIG_REQUIREDDEFAULT
|
||||
..[description].
|
||||
..[type]........String
|
||||
..[default].....foo2bar
|
||||
..[required]....true
|
||||
ENV_CONFIG_OUTER_INNER
|
||||
..[description].
|
||||
..[type]........String
|
||||
..[default].....
|
||||
..[required]....
|
||||
ENV_CONFIG_OUTER_PROPERTYWITHDEFAULT
|
||||
..[description].
|
||||
..[type]........String
|
||||
..[default].....fuzzybydefault
|
||||
..[required]....
|
||||
ENV_CONFIG_AFTERNESTED
|
||||
..[description].
|
||||
..[type]........String
|
||||
..[default].....
|
||||
..[required]....
|
||||
ENV_CONFIG_HONOR
|
||||
..[description].
|
||||
..[type]........HonorDecodeInStruct
|
||||
..[default].....
|
||||
..[required]....
|
||||
ENV_CONFIG_DATETIME
|
||||
..[description].
|
||||
..[type]........Time
|
||||
..[default].....
|
||||
..[required]....
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
This.application.is.configured.via.the.environment..The.following.environment
|
||||
variables.can.be.used:
|
||||
|
||||
KEY..............................................TYPE............................................DEFAULT...........REQUIRED....DESCRIPTION
|
||||
ENV_CONFIG_ENABLED...............................True.or.False.................................................................some.embedded.value
|
||||
ENV_CONFIG_EMBEDDEDPORT..........................Integer.......................................................................
|
||||
ENV_CONFIG_MULTIWORDVAR..........................String........................................................................
|
||||
ENV_CONFIG_MULTI_WITH_DIFFERENT_ALT..............String........................................................................
|
||||
ENV_CONFIG_EMBEDDED_WITH_ALT.....................String........................................................................
|
||||
ENV_CONFIG_DEBUG.................................True.or.False.................................................................
|
||||
ENV_CONFIG_PORT..................................Integer.......................................................................
|
||||
ENV_CONFIG_RATE..................................Float.........................................................................
|
||||
ENV_CONFIG_USER..................................String........................................................................
|
||||
ENV_CONFIG_TTL...................................Unsigned.Integer..............................................................
|
||||
ENV_CONFIG_TIMEOUT...............................Duration......................................................................
|
||||
ENV_CONFIG_ADMINUSERS............................Comma-separated.list.of.String................................................
|
||||
ENV_CONFIG_MAGICNUMBERS..........................Comma-separated.list.of.Integer...............................................
|
||||
ENV_CONFIG_COLORCODES............................Comma-separated.list.of.String:Integer.pairs..................................
|
||||
ENV_CONFIG_MULTIWORDVAR..........................String........................................................................
|
||||
ENV_CONFIG_MULTI_WORD_VAR_WITH_AUTO_SPLIT........Unsigned.Integer..............................................................
|
||||
ENV_CONFIG_SOMEPOINTER...........................String........................................................................
|
||||
ENV_CONFIG_SOMEPOINTERWITHDEFAULT................String..........................................foo2baz.......................foorbar.is.the.word
|
||||
ENV_CONFIG_MULTI_WORD_VAR_WITH_ALT...............String........................................................................what.alt
|
||||
ENV_CONFIG_MULTI_WORD_VAR_WITH_LOWER_CASE_ALT....String........................................................................
|
||||
ENV_CONFIG_SERVICE_HOST..........................String........................................................................
|
||||
ENV_CONFIG_DEFAULTVAR............................String..........................................foobar........................
|
||||
ENV_CONFIG_REQUIREDVAR...........................String............................................................true........
|
||||
ENV_CONFIG_BROKER................................String..........................................127.0.0.1.....................
|
||||
ENV_CONFIG_REQUIREDDEFAULT.......................String..........................................foo2bar...........true........
|
||||
ENV_CONFIG_OUTER_INNER...........................String........................................................................
|
||||
ENV_CONFIG_OUTER_PROPERTYWITHDEFAULT.............String..........................................fuzzybydefault................
|
||||
ENV_CONFIG_AFTERNESTED...........................String........................................................................
|
||||
ENV_CONFIG_HONOR.................................HonorDecodeInStruct...........................................................
|
||||
ENV_CONFIG_DATETIME..............................Time..........................................................................
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
{.Key}
|
||||
{.Key}
|
||||
{.Key}
|
||||
{.Key}
|
||||
{.Key}
|
||||
{.Key}
|
||||
{.Key}
|
||||
{.Key}
|
||||
{.Key}
|
||||
{.Key}
|
||||
{.Key}
|
||||
{.Key}
|
||||
{.Key}
|
||||
{.Key}
|
||||
{.Key}
|
||||
{.Key}
|
||||
{.Key}
|
||||
{.Key}
|
||||
{.Key}
|
||||
{.Key}
|
||||
{.Key}
|
||||
{.Key}
|
||||
{.Key}
|
||||
{.Key}
|
||||
{.Key}
|
||||
{.Key}
|
||||
{.Key}
|
||||
{.Key}
|
||||
{.Key}
|
||||
{.Key}
|
||||
+158
@@ -0,0 +1,158 @@
|
||||
// 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)
|
||||
}
|
||||
+155
@@ -0,0 +1,155 @@
|
||||
// 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 (
|
||||
"bytes"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"text/tabwriter"
|
||||
)
|
||||
|
||||
var testUsageTableResult, testUsageListResult, testUsageCustomResult, testUsageBadFormatResult string
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
|
||||
// Load the expected test results from a text file
|
||||
data, err := ioutil.ReadFile("testdata/default_table.txt")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
testUsageTableResult = string(data)
|
||||
|
||||
data, err = ioutil.ReadFile("testdata/default_list.txt")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
testUsageListResult = string(data)
|
||||
|
||||
data, err = ioutil.ReadFile("testdata/custom.txt")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
testUsageCustomResult = string(data)
|
||||
|
||||
data, err = ioutil.ReadFile("testdata/fault.txt")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
testUsageBadFormatResult = string(data)
|
||||
|
||||
retCode := m.Run()
|
||||
os.Exit(retCode)
|
||||
}
|
||||
|
||||
func compareUsage(want, got string, t *testing.T) {
|
||||
got = strings.Replace(got, " ", ".", -1)
|
||||
if want != got {
|
||||
shortest := len(want)
|
||||
if len(got) < shortest {
|
||||
shortest = len(got)
|
||||
}
|
||||
if len(want) != len(got) {
|
||||
t.Errorf("expected result length of %d, found %d", len(want), len(got))
|
||||
}
|
||||
for i := 0; i < shortest; i++ {
|
||||
if want[i] != got[i] {
|
||||
t.Errorf("difference at index %d, expected '%c' (%v), found '%c' (%v)\n",
|
||||
i, want[i], want[i], got[i], got[i])
|
||||
break
|
||||
}
|
||||
}
|
||||
t.Errorf("Complete Expected:\n'%s'\nComplete Found:\n'%s'\n", want, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUsageDefault(t *testing.T) {
|
||||
var s Specification
|
||||
os.Clearenv()
|
||||
save := os.Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
err := Usage("env_config", &s)
|
||||
outC := make(chan string)
|
||||
// copy the output in a separate goroutine so printing can't block indefinitely
|
||||
go func() {
|
||||
var buf bytes.Buffer
|
||||
io.Copy(&buf, r)
|
||||
outC <- buf.String()
|
||||
}()
|
||||
w.Close()
|
||||
os.Stdout = save // restoring the real stdout
|
||||
out := <-outC
|
||||
|
||||
if err != nil {
|
||||
t.Error(err.Error())
|
||||
}
|
||||
compareUsage(testUsageTableResult, out, t)
|
||||
}
|
||||
|
||||
func TestUsageTable(t *testing.T) {
|
||||
var s Specification
|
||||
os.Clearenv()
|
||||
buf := new(bytes.Buffer)
|
||||
tabs := tabwriter.NewWriter(buf, 1, 0, 4, ' ', 0)
|
||||
err := Usagef("env_config", &s, tabs, DefaultTableFormat)
|
||||
tabs.Flush()
|
||||
if err != nil {
|
||||
t.Error(err.Error())
|
||||
}
|
||||
compareUsage(testUsageTableResult, buf.String(), t)
|
||||
}
|
||||
|
||||
func TestUsageList(t *testing.T) {
|
||||
var s Specification
|
||||
os.Clearenv()
|
||||
buf := new(bytes.Buffer)
|
||||
err := Usagef("env_config", &s, buf, DefaultListFormat)
|
||||
if err != nil {
|
||||
t.Error(err.Error())
|
||||
}
|
||||
compareUsage(testUsageListResult, buf.String(), t)
|
||||
}
|
||||
|
||||
func TestUsageCustomFormat(t *testing.T) {
|
||||
var s Specification
|
||||
os.Clearenv()
|
||||
buf := new(bytes.Buffer)
|
||||
err := Usagef("env_config", &s, buf, "{{range .}}{{usage_key .}}={{usage_description .}}\n{{end}}")
|
||||
if err != nil {
|
||||
t.Error(err.Error())
|
||||
}
|
||||
compareUsage(testUsageCustomResult, buf.String(), t)
|
||||
}
|
||||
|
||||
func TestUsageUnknownKeyFormat(t *testing.T) {
|
||||
var s Specification
|
||||
unknownError := "template: envconfig:1:2: executing \"envconfig\" at <.UnknownKey>"
|
||||
os.Clearenv()
|
||||
buf := new(bytes.Buffer)
|
||||
err := Usagef("env_config", &s, buf, "{{.UnknownKey}}")
|
||||
if err == nil {
|
||||
t.Errorf("expected 'unknown key' error, but got no error")
|
||||
}
|
||||
if strings.Index(err.Error(), unknownError) == -1 {
|
||||
t.Errorf("expected '%s', but got '%s'", unknownError, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestUsageBadFormat(t *testing.T) {
|
||||
var s Specification
|
||||
os.Clearenv()
|
||||
// If you don't use two {{}} then you get a lieteral
|
||||
buf := new(bytes.Buffer)
|
||||
err := Usagef("env_config", &s, buf, "{{range .}}{.Key}\n{{end}}")
|
||||
if err != nil {
|
||||
t.Error(err.Error())
|
||||
}
|
||||
compareUsage(testUsageBadFormatResult, buf.String(), t)
|
||||
}
|
||||
Reference in New Issue
Block a user