Skip to content

Commit

Permalink
feat: environment loading, manipulation, value parsing and state mana…
Browse files Browse the repository at this point in the history
…gement
  • Loading branch information
deltics committed Jul 4, 2024
1 parent e374732 commit e003e0b
Show file tree
Hide file tree
Showing 13 changed files with 1,615 additions and 0 deletions.
131 changes: 131 additions & 0 deletions env.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package env

import (
"os"
"strings"
)

// Clear removes all environment variables.
func Clear() {
os.Clearenv()
}

// Get returns the value of the environment variable with the given name. If the
// variable is not set an empty string is returned.
//
// To differentiate between a variable that is not set and a variable that is set to
// an empty string, use the `Lookup` function.
//
// # parameters
//
// name string // the name of the environment variable
//
// # returns
//
// string // the value of the environment variable
func Get(name string) string {
return os.Getenv(name)
}

// GetVars returns a map of environment variables. If no variable names are provided
// all environment variables are returned. If variable names are provided, only
// those variables are returned (if set).
//
// # parameters
//
// names ...string // (optional) names of environment variables to return;
// // if no names are provided the returned map contains all
// // environment variables.
//
// If a name is provided that is not set in the environment it is not included in
// the returned map.
//
// # returns
//
// Vars // a map of environment variables
//
// The returned map is a `map[string]string` where the key is the name of the
// environment variable and the value is the value of the environment variable.
//
// If no environment variables are set or all specified variables names are not set,
// the returned map is empty.
func GetVars(names ...string) Vars {
var result Vars

switch len(names) {
case 0: // all environment variables
env := os.Environ()
result = make(Vars, len(env))
for _, s := range env {
k, v, _ := strings.Cut(s, "=")
result[k] = v
}
default: // only the named variables (if set)
result = make(Vars, len(names))
for _, k := range names {
if v, ok := os.LookupEnv(k); ok {
result[k] = v
}
}
}

return result
}

// Lookup returns the value of the environment variable with the given name and a
// boolean indicating whether the variable is set. If the variable is not set the
// returned value is an empty string and the boolean is `false`.
//
// If you do not need to differentiate between a variable that is not set and a
// variable that is set to an empty string, use the `Get` function.
//
// # parameters
//
// name string // the name of the environment variable
//
// # returns
//
// string // the value of the environment variable
//
// bool // true if the variable is set, false otherwise
func Lookup(name string) (string, bool) {
return os.LookupEnv(name)
}

// Set sets the value of the environment variable with the given name. If the variable
// does not exist it is created.
//
// # parameters
//
// name string // the name of the environment variable
//
// value string // the value to set
//
// # returns
//
// error // any error that occurs while setting the environment variable
func Set(name, value string) error {
return os.Setenv(name, value)
}

// Unset removes the environment variables with the given names. If a variable does
// not exist (already not set) it is ignored.
//
// # parameters
//
// names ...string // the names of the environment variables to remove
//
// # returns
//
// error // any error that occurs while unsetting the environment variables
//
// On Unix systems (including Linux and macOS) the error is always `nil`, but
// on Windows systems the error may be non-nil.
func Unset(name ...string) error {
for _, k := range name {
if err := osUnsetenv(k); err != nil {
return err
}
}
return nil
}
184 changes: 184 additions & 0 deletions env_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
package env

import (
"errors"
"os"
"testing"

"github.com/blugnu/test"
)

func TestClear(t *testing.T) {
// ARRANGE
defer State().Reset()
os.Setenv("VAR", "value")

// ACT
Clear()

// ASSERT
_, isSet := os.LookupEnv("VAR")
test.IsFalse(t, isSet)
}

func TestGet(t *testing.T) {
// ARRANGE
defer State().Reset()
os.Clearenv()
os.Setenv("VAR", "value")

// ACT
result := Get("VAR")

// ASSERT
test.That(t, result, "variable present").Equals("value")

// ACT
result = Get("NOTSET")

// ASSERT
test.That(t, result, "variable not present").Equals("")
}

func TestGetVars(t *testing.T) {
// ARRANGE
testcases := []struct {
scenario string
exec func(t *testing.T)
}{
{scenario: "all variables",
exec: func(t *testing.T) {
// ARRANGE
defer State().Reset()
os.Clearenv()
os.Setenv("VAR1", "value1")
os.Setenv("VAR2", "value2")

// ACT
result := GetVars()

// ASSERT
test.Map(t, result).Equals(Vars{"VAR1": "value1", "VAR2": "value2"})
},
},
{scenario: "specified variables (including ones not set)",
exec: func(t *testing.T) {
// ARRANGE
defer State().Reset()
os.Clearenv()
os.Setenv("VAR1", "value1")
os.Setenv("VAR2", "value2")

// ACT
result := GetVars("VAR1", "VAR3")

// ASSERT
test.Map(t, result).Equals(Vars{"VAR1": "value1"})
},
},
}
for _, tc := range testcases {
t.Run(tc.scenario, func(t *testing.T) {
tc.exec(t)
})
}
}

func TestLookup(t *testing.T) {
// ARRANGE
defer State().Reset()
os.Clearenv()
os.Setenv("VAR", "value")

// ACT
result, ok := Lookup("VAR")

// ASSERT
test.That(t, result, "variable present").Equals("value")
test.IsTrue(t, ok, "variable present")

// ACT
result, ok = Lookup("NOTSET")

// ASSERT
test.That(t, result, "variable not present").Equals("")
test.IsFalse(t, ok, "variable not present")
}

func TestSet(t *testing.T) {
// ARRANGE
defer State().Reset()
os.Clearenv()

// ACT
err := Set("VAR1", "value1")

// ASSERT
test.That(t, err).IsNil()
test.That(t, os.Getenv("VAR1")).Equals("value1")
}

func TestUnset(t *testing.T) {
// ARRANGE
testEnv := map[string]string{
"VAR1": "value1",
"VAR2": "value2",
}
testcases := []struct {
scenario string
exec func(t *testing.T)
}{
{scenario: "no names specified",
exec: func(t *testing.T) {
// ACT
err := Unset()

// ASSERT
test.That(t, err).IsNil()
test.That(t, os.Getenv("VAR1")).Equals("value1")
test.That(t, os.Getenv("VAR2")).Equals("value2")
},
},
{scenario: "name specified",
exec: func(t *testing.T) {
// ACT
err := Unset("VAR1")

// ASSERT
test.That(t, err).IsNil()
_, isSet := os.LookupEnv("VAR1")
test.IsFalse(t, isSet)
test.That(t, os.Getenv("VAR2")).Equals("value2")
},
},
{
scenario: "error when unsetting",
exec: func(t *testing.T) {
// ARRANGE
unseterr := errors.New("unset error")
defer test.Using(&osUnsetenv, func(string) error { return unseterr })()

// ACT
err := Unset("VAR1", "VAR2")

// ASSERT
test.Error(t, err).Is(unseterr)
test.That(t, os.Getenv("VAR1")).Equals("value1")
test.That(t, os.Getenv("VAR2")).Equals("value2")
},
},
}
for _, tc := range testcases {
t.Run(tc.scenario, func(t *testing.T) {
// ARRANGE
defer State().Reset()
os.Clearenv()
for k, v := range testEnv {
os.Setenv(k, v)
}

// ACT & ASSERT
tc.exec(t)
})
}
}
Loading

0 comments on commit e003e0b

Please sign in to comment.