Skip to content

Commit

Permalink
Simplify environment handling
Browse files Browse the repository at this point in the history
  • Loading branch information
roeldev committed Feb 27, 2024
1 parent de144b0 commit 31d06ee
Show file tree
Hide file tree
Showing 13 changed files with 140 additions and 151 deletions.
16 changes: 4 additions & 12 deletions dotenv/load.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@ const panicNilFsys = "dotenv: fs.FS must not be nil"
// Load sets the environment variables from the active environment using
// env.Load.
func Load(dir string, ae ActiveEnvironment) error {
return readAndLoad(ReadFS(nil, dir, ae), env.Load)
return env.Load(ReadFS(nil, dir, ae))
}

// Overload sets and overwrites the environment variables from the active
// environment using env.Overload.
func Overload(dir string, ae ActiveEnvironment) error {
return readAndLoad(ReadFS(nil, dir, ae), env.Overload)
return env.Overload(ReadFS(nil, dir, ae))
}

// LoadFS sets the environment variables from the active environment using
Expand All @@ -30,7 +30,7 @@ func LoadFS(fsys fs.FS, dir string, ae ActiveEnvironment) error {
panic(panicNilFsys)
}

return readAndLoad(ReadFS(fsys, dir, ae), env.Load)
return env.Load(ReadFS(fsys, dir, ae))
}

// OverloadFS sets and overwrites the environment variables from the active
Expand All @@ -40,13 +40,5 @@ func OverloadFS(fsys fs.FS, dir string, ae ActiveEnvironment) error {
panic(panicNilFsys)
}

return readAndLoad(ReadFS(fsys, dir, ae), env.Overload)
}

func readAndLoad(r *Reader, loadFn func(env.Map) error) error {
environ, err := r.Environ()
if err != nil {
return err
}
return loadFn(environ)
return env.Overload(ReadFS(fsys, dir, ae))
}
61 changes: 61 additions & 0 deletions dotenv/load_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Copyright (c) 2024, Roel Schut. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package dotenv

import (
"github.com/go-pogo/env"
"github.com/go-pogo/env/envtest"
"github.com/stretchr/testify/assert"
"testing"
"testing/fstest"
)

func TestLoadFS(t *testing.T) {
t.Run("nil fsys", func(t *testing.T) {
assert.PanicsWithValue(t, panicNilFsys, func() {
_ = LoadFS(nil, "", "")
})
})

t.Run("load", func(t *testing.T) {
envs := envtest.Prepare(env.Map{"FOO": "baz"})
defer envs.Restore()

fsys := fstest.MapFS{
".env": {Data: []byte("FOO=bar\nQUX=x00")},
".env.test": {Data: []byte("QUX=XOO")},
".env.dev": {Data: []byte("QUX=")},
}
assert.NoError(t, LoadFS(fsys, "", Development))
assert.Equal(t, env.Map{
"FOO": "baz",
"QUX": "",
}, env.Environ())
})
}

func TestOverloadFS(t *testing.T) {
t.Run("nil fsys", func(t *testing.T) {
assert.PanicsWithValue(t, panicNilFsys, func() {
_ = OverloadFS(nil, "", "")
})
})

t.Run("overload", func(t *testing.T) {
envs := envtest.Prepare(env.Map{"FOO": "baz"})
defer envs.Restore()

fsys := fstest.MapFS{
".env": {Data: []byte("FOO=bar\nQUX=x00")},
".env.test": {Data: []byte("QUX=XOO")},
".env.dev": {Data: []byte("QUX=devverdedevdev")},
}
assert.NoError(t, OverloadFS(fsys, "", Testing))
assert.Equal(t, env.Map{
"FOO": "bar",
"QUX": "XOO",
}, env.Environ())
})
}
4 changes: 2 additions & 2 deletions dotenv/read.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ import (
const ErrNoFilesLoaded errors.Msg = "no files loaded"

var (
_ env.EnvironLookupper = (*Reader)(nil)
_ io.Closer = (*Reader)(nil)
_ env.EnvironmentLookupper = (*Reader)(nil)
_ io.Closer = (*Reader)(nil)
)

type Reader struct {
Expand Down
78 changes: 19 additions & 59 deletions env.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,83 +10,41 @@ import (
"strings"
)

type Environment interface {
Lookupper
Set(key string, val Value) error
Get(key string) Value
Has(key string) bool
Environ() Map
}

type osEnv struct{}

// System returns an Environment which wraps the operating system's env related
// System returns an EnvironmentLookupper which wraps the operating system's env related
// functions.
//
// dec := NewDecoder(System())
func System() Environment { return new(osEnv) }

var environ = System()

// Use another Environment with this package. This is especially useful when
// testing.
//
// myEnv := make(env.Map)
// env.Use(myEnv)
// env.Setenv("foo", "bar")
//
// To reset env to use the system's environment:
//
// Use(System())
func Use(e Environment) { environ = e }
func System() EnvironmentLookupper { return new(osEnv) }

// Setenv sets the Value of the environment variable named by the key on the
// current Environment.
func Setenv(key string, val Value) error { return environ.Set(key, val) }

// Set sets the Value of the environment variable named by the key using
// os.Setenv.
func (osEnv) Set(key string, val Value) error {
// Setenv sets the Value of the environment variable named by the key using
// // os.Setenv.
func Setenv(key string, val Value) error {
return errors.WithStack(os.Setenv(key, val.String()))
}

// Getenv retrieves the Value of the environment variable named by the key from
// the current Environment.
func Getenv(key string) Value { return environ.Get(key) }

// Get retrieves the Value of the environment variable named by the key using
// Getenv retrieves the Value of the environment variable named by the key using
// os.Getenv.
func (osEnv) Get(key string) Value { return Value(os.Getenv(key)) }
func Getenv(key string) Value { return Value(os.Getenv(key)) }

// LookupEnv retrieves the Value of the environment variable named by the key
// from the current Environment.
// using os.LookupEnv.
func LookupEnv(key string) (Value, bool) {
v, err := environ.Lookup(key)
return v, err != nil
v, ok := os.LookupEnv(key)
return Value(v), ok
}

// Lookup retrieves the Value of the environment variable named by the key using
// os.LookupEnv.
func (osEnv) Lookup(key string) (Value, error) {
if v, ok := os.LookupEnv(key); ok {
return Value(v), nil
func (o osEnv) Lookup(key string) (Value, error) {
v, ok := LookupEnv(key)
if !ok {
return "", errors.New(ErrNotFound)
}
return "", errors.New(ErrNotFound)
}

// Has indicates if the environment variable named by the key is present in the
// system's environment using os.LookupEnv.
func (osEnv) Has(key string) bool {
_, ok := os.LookupEnv(key)
return ok
return v, nil
}

// Environ returns a Map with the environment variables from the current
// Environment.
func Environ() Map { return environ.Environ() }

// Environ returns a Map with the environment variables of os.Environ.
func (osEnv) Environ() Map {
// Environ returns a Map with the environment variables using os.Environ.
func Environ() Map {
env := os.Environ()
res := make(Map, len(env))

Expand All @@ -100,3 +58,5 @@ func (osEnv) Environ() Map {
}
return res
}

func (o osEnv) Environ() (Map, error) { return Environ(), nil }
8 changes: 2 additions & 6 deletions envfile/load.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,10 @@ func OverloadFS(fsys fs.FS, filename string) error {
return readAndLoad(fsys, filename, env.Overload)
}

func readAndLoad(fsys fs.FS, filename string, loadFn func(env.Map) error) error {
func readAndLoad(fsys fs.FS, filename string, loadFn func(env.Environment) error) error {
r, err := OpenFS(fsys, filename)
if err != nil {
return err
}
environ, err := r.Environ()
if err != nil {
return err
}
return loadFn(environ)
return loadFn(r)
}
15 changes: 7 additions & 8 deletions envfile/load_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package envfile

import (
"github.com/go-pogo/env"
"github.com/go-pogo/env/envtest"
"github.com/stretchr/testify/assert"
"testing"
"testing/fstest"
Expand All @@ -22,13 +23,12 @@ func TestLoadFS(t *testing.T) {
})

t.Run("load", func(t *testing.T) {
have := env.Map{"QUX": "x00"}
env.Use(have)
defer env.Use(env.System())
envs := envtest.Prepare(env.Map{"QUX": "x00"})
defer envs.Restore()

fsys := fstest.MapFS{"x": {Data: []byte("FOO=bar\nQUX=nop")}}
assert.NoError(t, LoadFS(fsys, "x"))
assert.Equal(t, env.Map{"FOO": "bar", "QUX": "x00"}, have)
assert.Equal(t, env.Map{"FOO": "bar", "QUX": "x00"}, env.Environ())
})
}

Expand All @@ -43,12 +43,11 @@ func TestOverloadFS(t *testing.T) {
})

t.Run("overload", func(t *testing.T) {
have := env.Map{"QUX": "x00"}
env.Use(have)
defer env.Use(env.System())
envs := envtest.Prepare(env.Map{"QUX": "x00"})
defer envs.Restore()

fsys := fstest.MapFS{"y": {Data: []byte("FOO=bar\nQUX=overload")}}
assert.NoError(t, OverloadFS(fsys, "y"))
assert.Equal(t, env.Map{"FOO": "bar", "QUX": "overload"}, have)
assert.Equal(t, env.Map{"FOO": "bar", "QUX": "overload"}, env.Environ())
})
}
4 changes: 2 additions & 2 deletions envfile/read.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ import (
)

var (
_ env.EnvironLookupper = (*Reader)(nil)
_ io.Closer = (*Reader)(nil)
_ env.EnvironmentLookupper = (*Reader)(nil)
_ io.Closer = (*Reader)(nil)
)

// reader prevents Reader from needing to have a public *Reader
Expand Down
35 changes: 22 additions & 13 deletions load.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,44 +4,53 @@

package env

import "strings"
import (
"github.com/go-pogo/errors"
"strings"
)

// Load sets the system's environment variables with those from the Map when
// they do not exist.
func Load(envs Map) error {
return load(envs, environ, false)
func Load(envs Environment) error {
return load(envs, false)
}

// Overload sets and overwrites the system's environment variables with those
// from the Map.
func Overload(envs Map) error {
return load(envs, environ, true)
func Overload(envs Environment) error {
return load(envs, true)
}

func load(envs Map, environ Environment, overload bool) error {
if len(envs) == 0 {
func load(envs Environment, overload bool) (err error) {
var m Map
if em, ok := envs.(Map); ok {
m = em
} else if m, err = envs.Environ(); err != nil {
return errors.WithStack(err)
}

if len(m) == 0 {
return nil
}

var r *Replacer
if predictReplacerNeed(envs) {
r = NewReplacer(Chain(envs, environ))
if predictReplacerNeed(m) {
r = NewReplacer(Chain(m, System()))
}

for k, v := range envs {
if !overload && environ.Has(k) {
for k, v := range m {
if _, has := LookupEnv(k); has && !overload {
continue
}

if r != nil {
var err error
v, err = r.Replace(v)
if err != nil {
return err
}
}

if err := environ.Set(k, v); err != nil {
if err = Setenv(k, v); err != nil {
return err
}
}
Expand Down
9 changes: 7 additions & 2 deletions load_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,18 @@ package env
import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"os"
"testing"
)

func TestLoad(t *testing.T) {
restore := Environ()
os.Clearenv()
defer Load(restore)

want := Value("this value is not overwritten")
key := randKey()
require.NoError(t, environ.Set(key, want))
require.NoError(t, Setenv(key, want))

assert.Nil(t, Load(Map{key: "foobar"}))
assert.Equal(t, want, Getenv(key))
Expand All @@ -22,7 +27,7 @@ func TestLoad(t *testing.T) {
func TestMap_Overload(t *testing.T) {
want := Value("foobar")
key := randKey()
require.NoError(t, environ.Set(key, "overwrite me!"))
require.NoError(t, Setenv(key, "overwrite me!"))

assert.Nil(t, Overload(Map{key: want}))
assert.Equal(t, want, Getenv(key))
Expand Down
Loading

0 comments on commit 31d06ee

Please sign in to comment.