diff --git a/dotnev/README.md b/dotnev/README.md new file mode 100644 index 0000000..d5cc9e1 --- /dev/null +++ b/dotnev/README.md @@ -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") +``` diff --git a/dotnev/dotenv.go b/dotnev/dotenv.go new file mode 100644 index 0000000..693c0bc --- /dev/null +++ b/dotnev/dotenv.go @@ -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 +} diff --git a/dotnev/dotenv_test.go b/dotnev/dotenv_test.go new file mode 100644 index 0000000..970dcb7 --- /dev/null +++ b/dotnev/dotenv_test.go @@ -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() +} diff --git a/dotnev/testdata/.env b/dotnev/testdata/.env new file mode 100644 index 0000000..f09f7d3 --- /dev/null +++ b/dotnev/testdata/.env @@ -0,0 +1,2 @@ +# comments +DONT_ENV_TEST = "blog" \ No newline at end of file diff --git a/dotnev/testdata/error.ini b/dotnev/testdata/error.ini new file mode 100644 index 0000000..f412958 --- /dev/null +++ b/dotnev/testdata/error.ini @@ -0,0 +1,2 @@ +DONT_ENV_TEST = +df \ No newline at end of file diff --git a/ini_test.go b/ini_test.go index 8fb414a..a84fa97 100644 --- a/ini_test.go +++ b/ini_test.go @@ -58,7 +58,7 @@ some = change val // panic(err) // } - // Out: + // Output: // get int // - val: 100 // get bool