New Upstream Release - golang-github-kelseyhightower-envconfig-dev
Ready changes
Summary
Merged new upstream version: 1.4.0 (was: 1.3.0).
Resulting package
Built on 2022-09-29T23:55 (took 4m16s)
The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:
apt install -t fresh-releases golang-github-kelseyhightower-envconfig-dev
Lintian Result
Diff
diff --git a/.travis.yml b/.travis.yml
index e15301a..04b97ae 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,7 +1,13 @@
language: go
go:
- - 1.4
- - 1.5
- - 1.6
+ - 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
diff --git a/README.md b/README.md
index b6c65a8..33408d6 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
# envconfig
-[![Build Status](https://travis-ci.org/kelseyhightower/envconfig.png)](https://travis-ci.org/kelseyhightower/envconfig)
+[![Build Status](https://travis-ci.org/kelseyhightower/envconfig.svg)](https://travis-ci.org/kelseyhightower/envconfig)
```Go
import "github.com/kelseyhightower/envconfig"
@@ -54,7 +54,7 @@ func main() {
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)
+ _, err = fmt.Printf(format, s.Debug, s.Port, s.User, s.Rate, s.Timeout)
if err != nil {
log.Fatal(err.Error())
}
@@ -103,6 +103,7 @@ type Specification struct {
RequiredVar string `required:"true"`
IgnoredVar string `ignored:"true"`
AutoSplitVar string `split_words:"true"`
+ RequiredAndAutoSplitVar string `required:"true" split_words:"true"`
}
```
@@ -128,7 +129,8 @@ 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.
+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
@@ -150,7 +152,7 @@ environment variable is set.
## Supported Struct Field Types
-envconfig supports supports these struct field types:
+envconfig supports these struct field types:
* string
* int8, int16, int32, int64
@@ -159,6 +161,8 @@ envconfig supports supports these struct field types:
* 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.
diff --git a/debian/changelog b/debian/changelog
index f853c9d..538b891 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+golang-github-kelseyhightower-envconfig-dev (1.4.0-1) UNRELEASED; urgency=low
+
+ * New upstream release.
+
+ -- Debian Janitor <janitor@jelmer.uk> Thu, 29 Sep 2022 23:51:00 -0000
+
golang-github-kelseyhightower-envconfig-dev (1.3.0-2) unstable; urgency=medium
* Build with golang-any and drop dependency on golang-go
diff --git a/env_os.go b/env_os.go
index a6a014a..eba07a6 100644
--- a/env_os.go
+++ b/env_os.go
@@ -1,4 +1,4 @@
-// +build appengine
+// +build appengine go1.5
package envconfig
diff --git a/env_syscall.go b/env_syscall.go
index 9d98085..4254540 100644
--- a/env_syscall.go
+++ b/env_syscall.go
@@ -1,4 +1,4 @@
-// +build !appengine
+// +build !appengine,!go1.5
package envconfig
diff --git a/envconfig.go b/envconfig.go
index 892d746..3f16108 100644
--- a/envconfig.go
+++ b/envconfig.go
@@ -8,6 +8,7 @@ import (
"encoding"
"errors"
"fmt"
+ "os"
"reflect"
"regexp"
"strconv"
@@ -18,6 +19,9 @@ import (
// 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 {
@@ -55,7 +59,6 @@ type varInfo struct {
// 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 {
@@ -72,7 +75,7 @@ func gatherInfo(prefix string, spec interface{}) ([]varInfo, error) {
for i := 0; i < s.NumField(); i++ {
f := s.Field(i)
ftype := typeOfSpec.Field(i)
- if !f.CanSet() || ftype.Tag.Get("ignored") == "true" {
+ if !f.CanSet() || isTrue(ftype.Tag.Get("ignored")) {
continue
}
@@ -100,12 +103,16 @@ func gatherInfo(prefix string, spec interface{}) ([]varInfo, error) {
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 isTrue(ftype.Tag.Get("split_words")) {
+ words := gatherRegexp.FindAllStringSubmatch(ftype.Name, -1)
if len(words) > 0 {
var name []string
for _, words := range words {
- name = append(name, words[0])
+ 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, "_")
@@ -122,7 +129,7 @@ func gatherInfo(prefix string, spec interface{}) ([]varInfo, error) {
if f.Kind() == reflect.Struct {
// honor Decode if present
- if decoderFrom(f) == nil && setterFrom(f) == nil && textUnmarshaler(f) == nil {
+ if decoderFrom(f) == nil && setterFrom(f) == nil && textUnmarshaler(f) == nil && binaryUnmarshaler(f) == nil {
innerPrefix := prefix
if !ftype.Anonymous {
innerPrefix = info.Key
@@ -142,6 +149,37 @@ func gatherInfo(prefix string, spec interface{}) ([]varInfo, error) {
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)
@@ -164,13 +202,17 @@ func Process(prefix string, spec interface{}) error {
req := info.Tags.Get("required")
if !ok && def == "" {
- if req == "true" {
- return fmt.Errorf("required key %s missing value", info.Key)
+ 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)
+ err = processField(value, info.Field)
if err != nil {
return &ParseError{
KeyName: info.Key,
@@ -209,6 +251,10 @@ func processField(value string, field reflect.Value) error {
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() {
@@ -256,34 +302,41 @@ func processField(value string, field reflect.Value) error {
}
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
+ 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:
- 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
+ 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)
}
- mp.SetMapIndex(k, v)
}
field.Set(mp)
}
@@ -317,3 +370,13 @@ 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
+}
diff --git a/envconfig_1.8_test.go b/envconfig_1.8_test.go
new file mode 100644
index 0000000..8dfcc6c
--- /dev/null
+++ b/envconfig_1.8_test.go
@@ -0,0 +1,68 @@
+// +build go1.8
+
+package envconfig
+
+import (
+ "errors"
+ "net/url"
+ "os"
+ "testing"
+)
+
+type SpecWithURL struct {
+ UrlValue url.URL
+ UrlPointer *url.URL
+}
+
+func TestParseURL(t *testing.T) {
+ var s SpecWithURL
+
+ os.Clearenv()
+ os.Setenv("ENV_CONFIG_URLVALUE", "https://github.com/kelseyhightower/envconfig")
+ os.Setenv("ENV_CONFIG_URLPOINTER", "https://github.com/kelseyhightower/envconfig")
+
+ err := Process("env_config", &s)
+ if err != nil {
+ t.Fatal("unexpected error:", err)
+ }
+
+ u, err := url.Parse("https://github.com/kelseyhightower/envconfig")
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+
+ if s.UrlValue != *u {
+ t.Errorf("expected %q, got %q", u, s.UrlValue.String())
+ }
+
+ if *s.UrlPointer != *u {
+ t.Errorf("expected %q, got %q", u, s.UrlPointer)
+ }
+}
+
+func TestParseURLError(t *testing.T) {
+ var s SpecWithURL
+
+ os.Clearenv()
+ os.Setenv("ENV_CONFIG_URLPOINTER", "http_://foo")
+
+ err := Process("env_config", &s)
+
+ v, ok := err.(*ParseError)
+ if !ok {
+ t.Fatalf("expected ParseError, got %T %v", err, err)
+ }
+ if v.FieldName != "UrlPointer" {
+ t.Errorf("expected %s, got %v", "UrlPointer", v.FieldName)
+ }
+
+ expectedUnerlyingError := url.Error{
+ Op: "parse",
+ URL: "http_://foo",
+ Err: errors.New("first path segment in URL cannot contain colon"),
+ }
+
+ if v.Err.Error() != expectedUnerlyingError.Error() {
+ t.Errorf("expected %q, got %q", expectedUnerlyingError, v.Err)
+ }
+}
diff --git a/envconfig_test.go b/envconfig_test.go
index e754058..2ca765d 100644
--- a/envconfig_test.go
+++ b/envconfig_test.go
@@ -7,7 +7,9 @@ package envconfig
import (
"flag"
"fmt"
+ "net/url"
"os"
+ "strings"
"testing"
"time"
)
@@ -21,6 +23,16 @@ func (h *HonorDecodeInStruct) Decode(env string) error {
return nil
}
+type CustomURL struct {
+ Value *url.URL
+}
+
+func (cu *CustomURL) UnmarshalBinary(data []byte) error {
+ u, err := url.Parse(string(data))
+ cu.Value = u
+ return err
+}
+
type Specification struct {
Embedded `desc:"can we document a struct"`
EmbeddedButIgnored `ignored:"true"`
@@ -32,16 +44,19 @@ type Specification struct {
Timeout time.Duration
AdminUsers []string
MagicNumbers []int
+ EmptyNumbers []int
+ ByteSlice []byte
ColorCodes map[string]int
MultiWordVar string
MultiWordVarWithAutoSplit uint32 `split_words:"true"`
+ MultiWordACRWithAutoSplit 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"`
+ RequiredVar string `required:"True"`
NoPrefixDefault string `envconfig:"BROKER" default:"127.0.0.1"`
RequiredDefault string `required:"true" default:"foo2bar"`
Ignored string `ignored:"true"`
@@ -52,6 +67,9 @@ type Specification struct {
AfterNested string
DecodeStruct HonorDecodeInStruct `envconfig:"honor"`
Datetime time.Time
+ MapField map[string]string `default:"one:two,three:four"`
+ UrlValue CustomURL
+ UrlPointer *CustomURL
}
type Embedded struct {
@@ -78,6 +96,8 @@ func TestProcess(t *testing.T) {
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_EMPTYNUMBERS", "")
+ os.Setenv("ENV_CONFIG_BYTESLICE", "this is a test value")
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")
@@ -88,6 +108,9 @@ func TestProcess(t *testing.T) {
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")
+ os.Setenv("ENV_CONFIG_MULTI_WORD_ACR_WITH_AUTO_SPLIT", "25")
+ os.Setenv("ENV_CONFIG_URLVALUE", "https://github.com/kelseyhightower/envconfig")
+ os.Setenv("ENV_CONFIG_URLPOINTER", "https://github.com/kelseyhightower/envconfig")
err := Process("env_config", &s)
if err != nil {
t.Error(err.Error())
@@ -128,6 +151,13 @@ func TestProcess(t *testing.T) {
s.MagicNumbers[2] != 20 {
t.Errorf("expected %#v, got %#v", []int{5, 10, 20}, s.MagicNumbers)
}
+ if len(s.EmptyNumbers) != 0 {
+ t.Errorf("expected %#v, got %#v", []int{}, s.EmptyNumbers)
+ }
+ expected := "this is a test value"
+ if string(s.ByteSlice) != expected {
+ t.Errorf("expected %v, got %v", expected, string(s.ByteSlice))
+ }
if s.Ignored != "" {
t.Errorf("expected empty string, got %#v", s.Ignored)
}
@@ -170,6 +200,23 @@ func TestProcess(t *testing.T) {
if s.MultiWordVarWithAutoSplit != 24 {
t.Errorf("expected %q, got %q", 24, s.MultiWordVarWithAutoSplit)
}
+
+ if s.MultiWordACRWithAutoSplit != 25 {
+ t.Errorf("expected %d, got %d", 25, s.MultiWordACRWithAutoSplit)
+ }
+
+ u, err := url.Parse("https://github.com/kelseyhightower/envconfig")
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+
+ if *s.UrlValue.Value != *u {
+ t.Errorf("expected %q, got %q", u, s.UrlValue.Value.String())
+ }
+
+ if *s.UrlPointer.Value != *u {
+ t.Errorf("expected %q, got %q", u, s.UrlPointer.Value.String())
+ }
}
func TestParseErrorBool(t *testing.T) {
@@ -326,6 +373,16 @@ func TestRequiredVar(t *testing.T) {
}
}
+func TestRequiredMissing(t *testing.T) {
+ var s Specification
+ os.Clearenv()
+
+ err := Process("env_config", &s)
+ if err == nil {
+ t.Error("no failure when missing required variable")
+ }
+}
+
func TestBlankDefaultVar(t *testing.T) {
var s Specification
os.Clearenv()
@@ -422,6 +479,24 @@ func TestPointerFieldBlank(t *testing.T) {
}
}
+func TestEmptyMapFieldOverride(t *testing.T) {
+ var s Specification
+ os.Clearenv()
+ os.Setenv("ENV_CONFIG_REQUIREDVAR", "foo")
+ os.Setenv("ENV_CONFIG_MAPFIELD", "")
+ if err := Process("env_config", &s); err != nil {
+ t.Error(err.Error())
+ }
+
+ if s.MapField == nil {
+ t.Error("expected empty map, got <nil>")
+ }
+
+ if len(s.MapField) != 0 {
+ t.Errorf("expected empty map, got map of size %d", len(s.MapField))
+ }
+}
+
func TestMustProcess(t *testing.T) {
var s Specification
os.Clearenv()
@@ -640,7 +715,7 @@ func TestTextUnmarshalerError(t *testing.T) {
t.Errorf("expected ParseError, got %v", v)
}
if v.FieldName != "Datetime" {
- t.Errorf("expected %s, got %v", "Debug", v.FieldName)
+ t.Errorf("expected %s, got %v", "Datetime", v.FieldName)
}
expectedLowLevelError := time.ParseError{
@@ -653,8 +728,84 @@ func TestTextUnmarshalerError(t *testing.T) {
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)
+}
+
+func TestBinaryUnmarshalerError(t *testing.T) {
+ var s Specification
+ os.Clearenv()
+ os.Setenv("ENV_CONFIG_REQUIREDVAR", "foo")
+ os.Setenv("ENV_CONFIG_URLPOINTER", "http://%41:8080/")
+
+ err := Process("env_config", &s)
+
+ v, ok := err.(*ParseError)
+ if !ok {
+ t.Fatalf("expected ParseError, got %T %v", err, err)
+ }
+ if v.FieldName != "UrlPointer" {
+ t.Errorf("expected %s, got %v", "UrlPointer", v.FieldName)
+ }
+
+ // To be compatible with go 1.5 and lower we should do a very basic check,
+ // because underlying error message varies in go 1.5 and go 1.6+.
+
+ ue, ok := v.Err.(*url.Error)
+ if !ok {
+ t.Errorf("expected error type to be \"*url.Error\", got %T", v.Err)
+ }
+
+ if ue.Op != "parse" {
+ t.Errorf("expected error op to be \"parse\", got %q", ue.Op)
+ }
+}
+
+func TestCheckDisallowedOnlyAllowed(t *testing.T) {
+ var s Specification
+ os.Clearenv()
+ os.Setenv("ENV_CONFIG_DEBUG", "true")
+ os.Setenv("UNRELATED_ENV_VAR", "true")
+ err := CheckDisallowed("env_config", &s)
+ if err != nil {
+ t.Errorf("expected no error, got %s", err)
+ }
+}
+
+func TestCheckDisallowedMispelled(t *testing.T) {
+ var s Specification
+ os.Clearenv()
+ os.Setenv("ENV_CONFIG_DEBUG", "true")
+ os.Setenv("ENV_CONFIG_ZEBUG", "false")
+ err := CheckDisallowed("env_config", &s)
+ if experr := "unknown environment variable ENV_CONFIG_ZEBUG"; err.Error() != experr {
+ t.Errorf("expected %s, got %s", experr, err)
+ }
+}
+
+func TestCheckDisallowedIgnored(t *testing.T) {
+ var s Specification
+ os.Clearenv()
+ os.Setenv("ENV_CONFIG_DEBUG", "true")
+ os.Setenv("ENV_CONFIG_IGNORED", "false")
+ err := CheckDisallowed("env_config", &s)
+ if experr := "unknown environment variable ENV_CONFIG_IGNORED"; err.Error() != experr {
+ t.Errorf("expected %s, got %s", experr, err)
+ }
+}
+
+func TestErrorMessageForRequiredAltVar(t *testing.T) {
+ var s struct {
+ Foo string `envconfig:"BAR" required:"true"`
+ }
+
+ os.Clearenv()
+ err := Process("env_config", &s)
+
+ if err == nil {
+ t.Error("no failure when missing required variable")
+ }
+
+ if !strings.Contains(err.Error(), " BAR ") {
+ t.Errorf("expected error message to contain BAR, got \"%v\"", err)
}
}
@@ -686,3 +837,28 @@ func (ss *setterStruct) Set(value string) error {
ss.Inner = fmt.Sprintf("setterstruct{%q}", value)
return nil
}
+
+func BenchmarkGatherInfo(b *testing.B) {
+ 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")
+ for i := 0; i < b.N; i++ {
+ var s Specification
+ gatherInfo("env_config", &s)
+ }
+}
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..1561d1e
--- /dev/null
+++ b/go.mod
@@ -0,0 +1 @@
+module github.com/kelseyhightower/envconfig
diff --git a/testdata/custom.txt b/testdata/custom.txt
index 243e82c..04d2f5d 100644
--- a/testdata/custom.txt
+++ b/testdata/custom.txt
@@ -11,9 +11,12 @@ ENV_CONFIG_TTL=
ENV_CONFIG_TIMEOUT=
ENV_CONFIG_ADMINUSERS=
ENV_CONFIG_MAGICNUMBERS=
+ENV_CONFIG_EMPTYNUMBERS=
+ENV_CONFIG_BYTESLICE=
ENV_CONFIG_COLORCODES=
ENV_CONFIG_MULTIWORDVAR=
ENV_CONFIG_MULTI_WORD_VAR_WITH_AUTO_SPLIT=
+ENV_CONFIG_MULTI_WORD_ACR_WITH_AUTO_SPLIT=
ENV_CONFIG_SOMEPOINTER=
ENV_CONFIG_SOMEPOINTERWITHDEFAULT=foorbar.is.the.word
ENV_CONFIG_MULTI_WORD_VAR_WITH_ALT=what.alt
@@ -28,3 +31,6 @@ ENV_CONFIG_OUTER_PROPERTYWITHDEFAULT=
ENV_CONFIG_AFTERNESTED=
ENV_CONFIG_HONOR=
ENV_CONFIG_DATETIME=
+ENV_CONFIG_MAPFIELD=
+ENV_CONFIG_URLVALUE=
+ENV_CONFIG_URLPOINTER=
diff --git a/testdata/default_list.txt b/testdata/default_list.txt
index bc29211..fb0eced 100644
--- a/testdata/default_list.txt
+++ b/testdata/default_list.txt
@@ -66,6 +66,16 @@ ENV_CONFIG_MAGICNUMBERS
..[type]........Comma-separated.list.of.Integer
..[default].....
..[required]....
+ENV_CONFIG_EMPTYNUMBERS
+..[description].
+..[type]........Comma-separated.list.of.Integer
+..[default].....
+..[required]....
+ENV_CONFIG_BYTESLICE
+..[description].
+..[type]........String
+..[default].....
+..[required]....
ENV_CONFIG_COLORCODES
..[description].
..[type]........Comma-separated.list.of.String:Integer.pairs
@@ -81,6 +91,11 @@ ENV_CONFIG_MULTI_WORD_VAR_WITH_AUTO_SPLIT
..[type]........Unsigned.Integer
..[default].....
..[required]....
+ENV_CONFIG_MULTI_WORD_ACR_WITH_AUTO_SPLIT
+..[description].
+..[type]........Unsigned.Integer
+..[default].....
+..[required]....
ENV_CONFIG_SOMEPOINTER
..[description].
..[type]........String
@@ -151,3 +166,18 @@ ENV_CONFIG_DATETIME
..[type]........Time
..[default].....
..[required]....
+ENV_CONFIG_MAPFIELD
+..[description].
+..[type]........Comma-separated.list.of.String:String.pairs
+..[default].....one:two,three:four
+..[required]....
+ENV_CONFIG_URLVALUE
+..[description].
+..[type]........CustomURL
+..[default].....
+..[required]....
+ENV_CONFIG_URLPOINTER
+..[description].
+..[type]........CustomURL
+..[default].....
+..[required]....
diff --git a/testdata/default_table.txt b/testdata/default_table.txt
index f3cf945..65c9b44 100644
--- a/testdata/default_table.txt
+++ b/testdata/default_table.txt
@@ -1,34 +1,40 @@
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..........................................................................
+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_EMPTYNUMBERS..........................Comma-separated.list.of.Integer...................................................
+ENV_CONFIG_BYTESLICE.............................String............................................................................
+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_MULTI_WORD_ACR_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..............................................................................
+ENV_CONFIG_MAPFIELD..............................Comma-separated.list.of.String:String.pairs.....one:two,three:four................
+ENV_CONFIG_URLVALUE..............................CustomURL.........................................................................
+ENV_CONFIG_URLPOINTER............................CustomURL.........................................................................
diff --git a/testdata/fault.txt b/testdata/fault.txt
index 30e28ce..b525ff1 100644
--- a/testdata/fault.txt
+++ b/testdata/fault.txt
@@ -28,3 +28,9 @@
{.Key}
{.Key}
{.Key}
+{.Key}
+{.Key}
+{.Key}
+{.Key}
+{.Key}
+{.Key}
diff --git a/usage.go b/usage.go
index 1846353..1e6d0a8 100644
--- a/usage.go
+++ b/usage.go
@@ -27,7 +27,7 @@ variables can be used:
[default] {{usage_default .}}
[required] {{usage_required .}}{{end}}
`
- // DefaultTableFormat constant to use to display usage in a tabluar format
+ // 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:
@@ -37,9 +37,10 @@ KEY TYPE DEFAULT REQUIRED DESCRIPTION
)
var (
- decoderType = reflect.TypeOf((*Decoder)(nil)).Elem()
- setterType = reflect.TypeOf((*Setter)(nil)).Elem()
- unmarshalerType = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem()
+ 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 {
@@ -47,14 +48,19 @@ func implementsInterface(t reflect.Type) bool {
reflect.PtrTo(t).Implements(decoderType) ||
t.Implements(setterType) ||
reflect.PtrTo(t).Implements(setterType) ||
- t.Implements(unmarshalerType) ||
- reflect.PtrTo(t).Implements(unmarshalerType)
+ 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(
@@ -103,7 +109,7 @@ func toTypeDescription(t reflect.Type) string {
return fmt.Sprintf("%+v", t)
}
-// Usage writes usage information to stderr using the default header and table format
+// 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
Debdiff
[The following lists of changes regard files as different if they have different names, permissions or owners.]
Files in second set of .debs but not in first
-rw-r--r-- root/root /usr/share/gocode/src/github.com/kelseyhightower/envconfig/envconfig_1.8_test.go -rw-r--r-- root/root /usr/share/gocode/src/github.com/kelseyhightower/envconfig/go.mod
No differences were encountered in the control files