-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add env package for accessing environment variables; new Map methods …
…in opt package
- Loading branch information
Showing
5 changed files
with
442 additions
and
47 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
package env | ||
|
||
import ( | ||
"github.com/jpfourny/papaya/pkg/constraint" | ||
"github.com/jpfourny/papaya/pkg/opt" | ||
"github.com/jpfourny/papaya/pkg/pair" | ||
"github.com/jpfourny/papaya/pkg/stream" | ||
"github.com/jpfourny/papaya/pkg/stream/mapper" | ||
"os" | ||
"strings" | ||
) | ||
|
||
// Set sets the environment variable with the given key to the given value. | ||
func Set(key, value string) { | ||
_ = os.Setenv(key, value) | ||
} | ||
|
||
// Unset unsets the environment variable with the given key. | ||
func Unset(key string) { | ||
_ = os.Unsetenv(key) | ||
} | ||
|
||
// SetAllPairs sets the environment variables to the given pairs. | ||
// Returns a function that can be called to revert the changes. | ||
func SetAllPairs(pairs ...pair.Pair[string, string]) (revert func()) { | ||
return setAll(stream.Of(pairs...)) | ||
} | ||
|
||
// SetAllMap sets the environment variables to the given map. | ||
// Returns a function that can be called to revert the changes. | ||
func SetAllMap(m map[string]string) (revert func()) { | ||
return setAll(stream.FromMap(m)) | ||
} | ||
|
||
func setAll(vars stream.Stream[pair.Pair[string, string]]) (revert func()) { | ||
backupVars := make(map[string]string) | ||
var addedKeys []string | ||
|
||
stream.ForEach(vars, func(p pair.Pair[string, string]) { | ||
if prev, ok := os.LookupEnv(p.First()); ok { | ||
backupVars[p.First()] = prev // Backup existing key before overwriting. | ||
} else { | ||
addedKeys = append(addedKeys, p.First()) // Keep track of added keys. | ||
} | ||
Set(p.First(), p.Second()) | ||
}) | ||
|
||
return func() { | ||
for k, v := range backupVars { | ||
Set(k, v) | ||
} | ||
for _, k := range addedKeys { | ||
Unset(k) | ||
} | ||
} | ||
} | ||
|
||
// Get returns the value of the environment variable with the given key, if it exists. | ||
func Get(key string) opt.Optional[string] { | ||
return opt.Maybe(os.LookupEnv(key)) | ||
} | ||
|
||
// GetBool returns the value of the environment variable with the given key, if it exists and can be parsed as a boolean. | ||
// An empty Optional is returned if the variable is unset or if value cannot be parsed as a boolean. | ||
func GetBool(key string) opt.Optional[bool] { | ||
return opt.OptionalMap( | ||
Get(key), | ||
mapper.TryParseBool[string](), | ||
) | ||
} | ||
|
||
func GetInt[I constraint.SignedInteger](key string) opt.Optional[I] { | ||
return opt.OptionalMap( | ||
Get(key), | ||
mapper.TryParseInt[string, I](10, 64), | ||
) | ||
} | ||
|
||
func GetUInt[I constraint.UnsignedInteger](key string) opt.Optional[I] { | ||
return opt.OptionalMap( | ||
Get(key), | ||
mapper.TryParseUint[string, I](10, 64), | ||
) | ||
} | ||
|
||
func GetFloat[F constraint.Float](key string) opt.Optional[F] { | ||
return opt.OptionalMap( | ||
Get(key), | ||
mapper.TryParseFloat[string, F](64), | ||
) | ||
} | ||
|
||
// ToStream returns a stream of pairs representing the environment variables. | ||
func ToStream() stream.Stream[pair.Pair[string, string]] { | ||
return stream.Map( | ||
stream.FromSlice(os.Environ()), | ||
func(s string) pair.Pair[string, string] { | ||
parts := strings.SplitN(s, "=", 2) | ||
return pair.Of(parts[0], parts[1]) | ||
}, | ||
) | ||
} | ||
|
||
// ToMap returns a map representing the environment variables. | ||
func ToMap() map[string]string { | ||
return stream.CollectMap(ToStream()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,209 @@ | ||
package env | ||
|
||
import ( | ||
"github.com/jpfourny/papaya/pkg/pair" | ||
"github.com/jpfourny/papaya/pkg/stream" | ||
"os" | ||
"testing" | ||
) | ||
|
||
func TestSet(t *testing.T) { | ||
Set("foo", "bar") | ||
|
||
if os.Getenv("foo") != "bar" { | ||
t.Errorf("expected os.Getenv(%q) to return %q; got %q", "foo", "bar", os.Getenv("foo")) | ||
} | ||
} | ||
|
||
func TestUnset(t *testing.T) { | ||
_ = os.Setenv("foo", "bar") | ||
Unset("foo") | ||
|
||
if os.Getenv("foo") != "" { | ||
t.Errorf("expected os.Getenv(%q) to return %q; got %q", "foo", "", os.Getenv("foo")) | ||
} | ||
} | ||
|
||
func TestSetAllPairs(t *testing.T) { | ||
_ = os.Setenv("foo", "bar") | ||
|
||
undo := SetAllPairs( | ||
pair.Of("foo", "bar2"), | ||
pair.Of("baz", "qux"), | ||
) | ||
|
||
if os.Getenv("foo") != "bar2" { | ||
t.Errorf("expected os.Getenv(%q) to return %q; got %q", "foo", "bar2", os.Getenv("foo")) | ||
} | ||
if os.Getenv("baz") != "qux" { | ||
t.Errorf("expected os.Getenv(%q) to return %q; got %q", "baz", "qux", os.Getenv("baz")) | ||
} | ||
|
||
undo() | ||
|
||
if os.Getenv("foo") != "bar" { | ||
t.Errorf("expected os.Getenv(%q) to return %q; got %q", "foo", "bar", os.Getenv("foo")) | ||
} | ||
if os.Getenv("baz") != "" { | ||
t.Errorf("expected os.Getenv(%q) to return %q; got %q", "baz", "", os.Getenv("baz")) | ||
} | ||
} | ||
|
||
func TestSetAllMap(t *testing.T) { | ||
_ = os.Setenv("foo", "bar") | ||
|
||
undo := SetAllMap(map[string]string{ | ||
"foo": "bar2", | ||
"baz": "qux", | ||
}) | ||
|
||
if os.Getenv("foo") != "bar2" { | ||
t.Errorf("expected os.Getenv(%q) to return %q; got %q", "foo", "bar2", os.Getenv("foo")) | ||
} | ||
if os.Getenv("baz") != "qux" { | ||
t.Errorf("expected os.Getenv(%q) to return %q; got %q", "baz", "qux", os.Getenv("baz")) | ||
} | ||
|
||
undo() | ||
|
||
if os.Getenv("foo") != "bar" { | ||
t.Errorf("expected os.Getenv(%q) to return %q; got %q", "foo", "bar", os.Getenv("foo")) | ||
} | ||
if os.Getenv("baz") != "" { | ||
t.Errorf("expected os.Getenv(%q) to return %q; got %q", "baz", "", os.Getenv("baz")) | ||
} | ||
} | ||
|
||
func TestGet(t *testing.T) { | ||
_ = os.Setenv("foo", "bar") | ||
|
||
got := Get("foo") | ||
if !got.Present() || got.GetOrZero() != "bar" { | ||
t.Errorf("expected Get(%q) to return %q; got %v", "foo", "bar", got) | ||
} | ||
|
||
got = Get("baz") | ||
if got.Present() { | ||
t.Errorf("expected Get(%q) to return empty opt; got %v", "baz", got) | ||
} | ||
} | ||
|
||
func TestGetBool(t *testing.T) { | ||
_ = os.Setenv("foo", "true") | ||
|
||
got := GetBool("foo") | ||
if !got.Present() || !got.GetOrZero() { | ||
t.Errorf("expected GetBool(%q) to return true; got %v", "foo", got) | ||
} | ||
|
||
_ = os.Setenv("foo", "false") | ||
|
||
got = GetBool("foo") | ||
if !got.Present() || got.GetOrZero() { | ||
t.Errorf("expected GetBool(%q) to return false; got %v", "foo", got) | ||
} | ||
|
||
got = GetBool("baz") | ||
if got.Present() { | ||
t.Errorf("expected GetBool(%q) to return empty opt; got %v", "baz", got) | ||
} | ||
|
||
_ = os.Setenv("foo", "not-a-bool") | ||
|
||
got = GetBool("foo") | ||
if got.Present() { | ||
t.Errorf("expected GetBool(%q) to return empty opt; got %v", "foo", got) | ||
} | ||
} | ||
|
||
func TestGetInt(t *testing.T) { | ||
_ = os.Setenv("foo", "42") | ||
|
||
got := GetInt[int]("foo") | ||
if !got.Present() || got.GetOrZero() != 42 { | ||
t.Errorf("expected GetInt(%q) to return 42; got %v", "foo", got) | ||
} | ||
|
||
got = GetInt[int]("baz") | ||
if got.Present() { | ||
t.Errorf("expected GetInt(%q) to return empty opt; got %v", "baz", got) | ||
} | ||
|
||
_ = os.Setenv("foo", "not-an-int") | ||
|
||
got = GetInt[int]("foo") | ||
if got.Present() { | ||
t.Errorf("expected GetInt(%q) to return empty opt; got %v", "foo", got) | ||
} | ||
} | ||
|
||
func TestGetUInt(t *testing.T) { | ||
_ = os.Setenv("foo", "42") | ||
|
||
got := GetUInt[uint]("foo") | ||
if !got.Present() || got.GetOrZero() != 42 { | ||
t.Errorf("expected GetUInt(%q) to return 42; got %v", "foo", got) | ||
} | ||
|
||
got = GetUInt[uint]("baz") | ||
if got.Present() { | ||
t.Errorf("expected GetUInt(%q) to return empty opt; got %v", "baz", got) | ||
} | ||
|
||
_ = os.Setenv("foo", "not-an-uint") | ||
|
||
got = GetUInt[uint]("foo") | ||
if got.Present() { | ||
t.Errorf("expected GetUInt(%q) to return empty opt; got %v", "foo", got) | ||
} | ||
} | ||
|
||
func TestGetFloat(t *testing.T) { | ||
_ = os.Setenv("foo", "42.42") | ||
|
||
got := GetFloat[float64]("foo") | ||
if !got.Present() || got.GetOrZero() != 42.42 { | ||
t.Errorf("expected GetFloat(%q) to return 42.42; got %v", "foo", got) | ||
} | ||
|
||
got = GetFloat[float64]("baz") | ||
if got.Present() { | ||
t.Errorf("expected GetFloat(%q) to return empty opt; got %v", "baz", got) | ||
} | ||
|
||
_ = os.Setenv("foo", "not-a-float") | ||
|
||
got = GetFloat[float64]("foo") | ||
if got.Present() { | ||
t.Errorf("expected GetFloat(%q) to return empty opt; got %v", "foo", got) | ||
} | ||
} | ||
|
||
func TestToStream(t *testing.T) { | ||
_ = os.Setenv("foo", "bar") | ||
_ = os.Setenv("baz", "qux") | ||
|
||
got := ToStream() | ||
m := stream.CollectMap(got) | ||
|
||
if m["foo"] != "bar" { | ||
t.Errorf("expected ToStream() to contain %q; got %v", "foo", m["foo"]) | ||
} | ||
if m["baz"] != "qux" { | ||
t.Errorf("expected ToStream() to contain %q; got %v", "baz", m["baz"]) | ||
} | ||
} | ||
|
||
func TestToMap(t *testing.T) { | ||
_ = os.Setenv("foo", "bar") | ||
_ = os.Setenv("baz", "qux") | ||
|
||
got := ToMap() | ||
|
||
if got["foo"] != "bar" { | ||
t.Errorf("expected ToMap() to contain %q; got %v", "foo", got["foo"]) | ||
} | ||
if got["baz"] != "qux" { | ||
t.Errorf("expected ToMap() to contain %q; got %v", "baz", got["baz"]) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.