Skip to content

Commit

Permalink
add subpkg dotnev
Browse files Browse the repository at this point in the history
  • Loading branch information
inhere committed Sep 17, 2020
1 parent ec1ecf7 commit 13ffc6a
Show file tree
Hide file tree
Showing 6 changed files with 297 additions and 1 deletion.
17 changes: 17 additions & 0 deletions dotnev/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Dotenv

Package `dotenv` that supports importing data from files (eg `.env`) to ENV

## Usage

```go
err := dotenv.Load("./", ".env")
// err := dotenv.LoadExists("./", ".env")

val := dotenv.Get("ENV_KEY")
// Or use
// val := os.Getenv("ENV_KEY")

// with default value
val := dotenv.Get("ENV_KEY", "default value")
```
161 changes: 161 additions & 0 deletions dotnev/dotenv.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
// Package dotnev provide load .env data to os ENV
package dotnev

import (
"bufio"
"os"
"path/filepath"
"strconv"
"strings"

"github.com/gookit/ini/v2/parser"
)

var (
// UpperEnvKey change key to upper on set ENV
UpperEnvKey = true

// DefaultName default file name
DefaultName = ".env"

// OnlyLoadExists load on file exists
OnlyLoadExists bool

// save original Env data
// originalEnv []string

// cache all loaded ENV data
loadedData = map[string]string{}
)

// LoadedData get all loaded data by dontenv
func LoadedData() map[string]string {
return loadedData
}

// ClearLoaded clear the previously set ENV value
func ClearLoaded() {
for key := range loadedData {
_ = os.Unsetenv(key)
}

// reset
loadedData = map[string]string{}
}

// DontUpperEnvKey dont change key to upper on set ENV
func DontUpperEnvKey() {
UpperEnvKey = false
}

// Load parse .env file data to os ENV.
// Usage:
// dotenv.Load("./", ".env")
func Load(dir string, filenames ...string) (err error) {
if len(filenames) == 0 {
filenames = []string{DefaultName}
}

for _, filename := range filenames {
file := filepath.Join(dir, filename)
if err = loadFile(file); err != nil {
break
}
}
return
}

// LoadExists only load on file exists
func LoadExists(dir string, filenames ...string) error {
oldVal := OnlyLoadExists

OnlyLoadExists = true
err := Load(dir, filenames...)
OnlyLoadExists = oldVal

return err
}

// LoadFromMap load data from given string map
func LoadFromMap(kv map[string]string) (err error) {
for key, val := range kv {
if UpperEnvKey {
key = strings.ToUpper(key)
}

err = os.Setenv(key, val)
if err != nil {
break
}

// cache it
loadedData[key] = val
}
return
}

// Get get os ENV value by name
//
// NOTICE: if is windows OS, os.Getenv() Key is not case sensitive
func Get(name string, defVal ...string) (val string) {
if UpperEnvKey {
name = strings.ToUpper(name)
}
if val = loadedData[name]; val != "" {
return
}

if val = os.Getenv(name); val != "" {
return
}

if len(defVal) > 0 {
val = defVal[0]
}
return
}

// Int get a int value by key
func Int(name string, defVal ...int) (val int) {
if str := os.Getenv(name); str != "" {
val, err := strconv.ParseInt(str, 10, 0)
if err == nil {
return int(val)
}
}

if len(defVal) > 0 {
val = defVal[0]
}
return
}

// load and parse .env file data to os ENV
func loadFile(file string) (err error) {
// open file
fd, err := os.Open(file)
if err != nil {
// skip not exist file
if os.IsNotExist(err) && OnlyLoadExists {
return nil
}
return err
}

//noinspection GoUnhandledErrorResult
defer fd.Close()

// parse file content
s := bufio.NewScanner(fd)
p := parser.NewSimpled(parser.NoDefSection)

if _, err = p.ParseFrom(s); err != nil {
return
}

// set data to os ENV
if mp, ok := p.SimpleData()[p.DefSection]; ok {
err = LoadFromMap(mp)
}
return
}
114 changes: 114 additions & 0 deletions dotnev/dotenv_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package dotnev

import (
"fmt"
"os"
"runtime"
"testing"

"github.com/stretchr/testify/assert"
)

func TestLoad(t *testing.T) {
err := Load("./testdata", "not-exist", ".env")
assert.Error(t, err)

assert.Equal(t, "", os.Getenv("DONT_ENV_TEST"))

err = Load("./testdata")
assert.NoError(t, err)
assert.Equal(t, "blog", os.Getenv("DONT_ENV_TEST"))
assert.Equal(t, "blog", Get("DONT_ENV_TEST"))
_ = os.Unsetenv("DONT_ENV_TEST") // clear

err = Load("./testdata", "error.ini")
assert.Error(t, err)

err = Load("./testdata", "invalid_key.ini")
assert.Error(t, err)

assert.Equal(t, "def-val", Get("NOT-EXIST", "def-val"))

ClearLoaded()
}

func TestLoadExists(t *testing.T) {
assert.Equal(t, "", os.Getenv("DONT_ENV_TEST"))

err := LoadExists("./testdata", "not-exist", ".env")

assert.NoError(t, err)
assert.Equal(t, "blog", os.Getenv("DONT_ENV_TEST"))
assert.Equal(t, "blog", Get("DONT_ENV_TEST"))
ClearLoaded()
}

func TestLoadFromMap(t *testing.T) {
assert.Equal(t, "", os.Getenv("DONT_ENV_TEST"))

err := LoadFromMap(map[string]string{
"DONT_ENV_TEST": "blog",
"dont_env_test1": "val1",
"dont_env_test2": "23",
})

assert.NoError(t, err)

envStr := fmt.Sprint(os.Environ())
assert.Contains(t, envStr, "DONT_ENV_TEST=blog")
assert.Contains(t, envStr, "DONT_ENV_TEST1=val1")

assert.Equal(t, "blog", Get("DONT_ENV_TEST"))
assert.Equal(t, "blog", os.Getenv("DONT_ENV_TEST"))
assert.Equal(t, "val1", Get("DONT_ENV_TEST1"))
assert.Equal(t, 23, Int("DONT_ENV_TEST2"))

// on windows, os.Getenv() not case sensitive
if runtime.GOOS == "windows" {
assert.Equal(t, "val1", Get("dont_env_test1"))
assert.Equal(t, 23, Int("dont_env_test2"))
} else {
assert.Equal(t, "", Get("dont_env_test1"))
assert.Equal(t, 0, Int("dont_env_test2"))
}

assert.Equal(t, 20, Int("dont_env_test1", 20))
assert.Equal(t, 20, Int("dont_env_not_exist", 20))

// check cache
assert.Contains(t, LoadedData(), "DONT_ENV_TEST2")

// clear
ClearLoaded()
assert.Equal(t, "", os.Getenv("DONT_ENV_TEST"))
assert.Equal(t, "", Get("DONT_ENV_TEST1"))

err = LoadFromMap(map[string]string{
"": "val",
})
assert.Error(t, err)
}

func TestDontUpperEnvKey(t *testing.T) {
assert.Equal(t, "", os.Getenv("DONT_ENV_TEST"))

DontUpperEnvKey()

err := LoadFromMap(map[string]string{
"dont_env_test": "val",
})

assert.Contains(t, fmt.Sprint(os.Environ()), "dont_env_test=val")
assert.NoError(t, err)
assert.Equal(t, "val", Get("dont_env_test"))

// on windows, os.Getenv() not case sensitive
if runtime.GOOS == "windows" {
assert.Equal(t, "val", Get("DONT_ENV_TEST"))
} else {
assert.Equal(t, "", Get("DONT_ENV_TEST"))
}

UpperEnvKey = true // revert
ClearLoaded()
}
2 changes: 2 additions & 0 deletions dotnev/testdata/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# comments
DONT_ENV_TEST = "blog"
2 changes: 2 additions & 0 deletions dotnev/testdata/error.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
DONT_ENV_TEST =
df
2 changes: 1 addition & 1 deletion ini_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ some = change val
// panic(err)
// }

// Out:
// Output:
// get int
// - val: 100
// get bool
Expand Down

0 comments on commit 13ffc6a

Please sign in to comment.