Compare commits

..

16 Commits

Author SHA1 Message Date
726cc8b906 Merge pull request #22 from mmilata/dont-swallow-errors
Improve error handling
2016-12-17 10:05:37 +11:00
861984c215 Don't hide line parsing errors 2016-12-12 14:43:30 +01:00
0ff0c0fc7a Propagate errors encountered when reading file 2016-12-12 14:41:36 +01:00
4ed13390c0 Merge pull request #13 from jmervine/master
Add Overload methods.
2015-09-07 11:02:28 +10:00
008304c688 adding Overload method 2015-09-05 08:59:08 -07:00
443e926da0 Merge pull request #11 from buddhamagnet/master
Remove unecessary assignment in autoloader
2015-06-10 07:30:23 +10:00
2ed25fcb28 remove unecessary assignment in autoloader 2015-06-09 18:24:15 +01:00
f6581828bb outdent else because golint said so. 2015-03-23 12:17:14 +11:00
d29c003c20 Still trying to please golint with package comments. 2015-03-23 12:15:55 +11:00
19b5c2bf30 Some golint feedback from http://goreportcard.com/report/joho/godotenv 2015-03-23 12:15:01 +11:00
e1c92610d7 run gofmt -w -s ./.. 2015-03-23 12:06:31 +11:00
ead2e75027 Merge pull request #8 from calavera/add_values_to_envmap
Fix issue reading file.
2015-01-02 15:46:44 +11:00
dc9cc93c4e Add values to the envMap when reading the file.
But do not override values in the global environment.
2014-12-23 17:57:02 -08:00
a01a834e16 Update docs for the bin command. 2014-10-12 10:38:08 +11:00
d2ce5befea Move cmd.go so it installs as "godotenv" 2014-10-12 10:27:22 +11:00
a86c254d7d Merge pull request #6 from joho/add_bin_command
Add a bin command
2014-10-12 09:58:57 +11:00
6 changed files with 170 additions and 59 deletions

View File

@ -8,12 +8,23 @@ From the original Library:
>
> But it is not always practical to set environment variables on development machines or continuous integration servers where multiple projects are run. Dotenv load variables from a .env file into ENV when the environment is bootstrapped.
It can be used as a library (for loading in env for your own daemons etc) or as a bin command.
There is test coverage and CI for both linuxish and windows environments, but I make no guarantees about the bin version working on windows.
## Installation
As a library
```shell
go get github.com/joho/godotenv
```
or if you want to use it as a bin command
```shell
go get github.com/joho/godotenv/cmd/godotenv
```
## Usage
Add your application configuration to your `.env` file in the root of your project:
@ -85,7 +96,15 @@ myEnv, err := godotenv.Read()
s3Bucket := myEnv["S3_BUCKET"]
```
end
### Command Mode
Assuming you've installed the command as above and you've got `$GOPATH/bin` in your `$PATH`
```
godotenv -f /some/path/to/.env some_command with some args
```
If you don't specify `-f` it will fall back on the default of loading `.env` in `PWD`
## Contributing

View File

@ -11,5 +11,5 @@ package autoload
import "github.com/joho/godotenv"
func init() {
_ = godotenv.Load()
godotenv.Load()
}

View File

@ -45,7 +45,7 @@ example
// take rest of args and "exec" them
cmd := args[0]
cmdArgs := args[1:len(args)]
cmdArgs := args[1:]
err := godotenv.Exec(envFilenames, cmd, cmdArgs)
if err != nil {

2
fixtures/invalid1.env Normal file
View File

@ -0,0 +1,2 @@
INVALID LINE
foo=bar

View File

@ -1,18 +1,16 @@
/*
A go port of the ruby dotenv library (https://github.com/bkeepers/dotenv)
Examples/readme can be found on the github page at https://github.com/joho/godotenv
The TL;DR is that you make a .env file that looks something like
SOME_ENV_VAR=somevalue
and then in your go code you can call
godotenv.Load()
and all the env vars declared in .env will be avaiable through os.Getenv("SOME_ENV_VAR")
*/
// Package godotenv is a go port of the ruby dotenv library (https://github.com/bkeepers/dotenv)
//
// Examples/readme can be found on the github page at https://github.com/joho/godotenv
//
// The TL;DR is that you make a .env file that looks something like
//
// SOME_ENV_VAR=somevalue
//
// and then in your go code you can call
//
// godotenv.Load()
//
// and all the env vars declared in .env will be avaiable through os.Getenv("SOME_ENV_VAR")
package godotenv
import (
@ -23,22 +21,22 @@ import (
"strings"
)
/*
Call this function as close as possible to the start of your program (ideally in main)
If you call Load without any args it will default to loading .env in the current path
You can otherwise tell it which files to load (there can be more than one) like
godotenv.Load("fileone", "filetwo")
It's important to note that it WILL NOT OVERRIDE an env variable that already exists - consider the .env file to set dev vars or sensible defaults
*/
// Load will read your env file(s) and load them into ENV for this process.
//
// Call this function as close as possible to the start of your program (ideally in main)
//
// If you call Load without any args it will default to loading .env in the current path
//
// You can otherwise tell it which files to load (there can be more than one) like
//
// godotenv.Load("fileone", "filetwo")
//
// It's important to note that it WILL NOT OVERRIDE an env variable that already exists - consider the .env file to set dev vars or sensible defaults
func Load(filenames ...string) (err error) {
filenames = filenamesOrDefault(filenames)
for _, filename := range filenames {
err = loadFile(filename)
err = loadFile(filename, false)
if err != nil {
return // return early on a spazout
}
@ -46,10 +44,31 @@ func Load(filenames ...string) (err error) {
return
}
/*
Read all env (with same file loading semantics as Load) but return values as
a map rather than automatically writing values into env
*/
// Overload will read your env file(s) and load them into ENV for this process.
//
// Call this function as close as possible to the start of your program (ideally in main)
//
// If you call Overload without any args it will default to loading .env in the current path
//
// You can otherwise tell it which files to load (there can be more than one) like
//
// godotenv.Overload("fileone", "filetwo")
//
// It's important to note this WILL OVERRIDE an env variable that already exists - consider the .env file to forcefilly set all vars.
func Overload(filenames ...string) (err error) {
filenames = filenamesOrDefault(filenames)
for _, filename := range filenames {
err = loadFile(filename, true)
if err != nil {
return // return early on a spazout
}
}
return
}
// Read all env (with same file loading semantics as Load) but return values as
// a map rather than automatically writing values into env
func Read(filenames ...string) (envMap map[string]string, err error) {
filenames = filenamesOrDefault(filenames)
envMap = make(map[string]string)
@ -70,15 +89,13 @@ func Read(filenames ...string) (envMap map[string]string, err error) {
return
}
/*
Loads env vars from the specified filenames (empty map falls back to default)
then executes the cmd specified.
Simply hooks up os.Stdin/err/out to the command and calls Run()
If you want more fine grained control over your command it's recommended
that you use `Load()` or `Read()` and the `os/exec` package yourself.
*/
// Exec loads env vars from the specified filenames (empty map falls back to default)
// then executes the cmd specified.
//
// Simply hooks up os.Stdin/err/out to the command and calls Run()
//
// If you want more fine grained control over your command it's recommended
// that you use `Load()` or `Read()` and the `os/exec` package yourself.
func Exec(filenames []string, cmd string, cmdArgs []string) error {
Load(filenames...)
@ -92,22 +109,23 @@ func Exec(filenames []string, cmd string, cmdArgs []string) error {
func filenamesOrDefault(filenames []string) []string {
if len(filenames) == 0 {
return []string{".env"}
} else {
return filenames
}
return filenames
}
func loadFile(filename string) (err error) {
func loadFile(filename string, overload bool) error {
envMap, err := readFile(filename)
if err != nil {
return
return err
}
for key, value := range envMap {
os.Setenv(key, value)
if os.Getenv(key) == "" || overload {
os.Setenv(key, value)
}
}
return
return nil
}
func readFile(filename string) (envMap map[string]string, err error) {
@ -125,13 +143,19 @@ func readFile(filename string) (envMap map[string]string, err error) {
lines = append(lines, scanner.Text())
}
if err = scanner.Err(); err != nil {
return
}
for _, fullLine := range lines {
if !isIgnoredLine(fullLine) {
key, value, err := parseLine(fullLine)
var key, value string
key, value, err = parseLine(fullLine)
if err == nil && os.Getenv(key) == "" {
envMap[key] = value
if err != nil {
return
}
envMap[key] = value
}
}
return
@ -147,7 +171,7 @@ func parseLine(line string) (key string, value string, err error) {
if strings.Contains(line, "#") {
segmentsBetweenHashes := strings.Split(line, "#")
quotesAreOpen := false
segmentsToKeep := make([]string, 0)
var segmentsToKeep []string
for _, segment := range segmentsBetweenHashes {
if strings.Count(segment, "\"") == 1 || strings.Count(segment, "'") == 1 {
if quotesAreOpen {

View File

@ -5,6 +5,8 @@ import (
"testing"
)
var noopPresets = make(map[string]string)
func parseAndCompare(t *testing.T, rawEnvLine string, expectedKey string, expectedValue string) {
key, value, _ := parseLine(rawEnvLine)
if key != expectedKey || value != expectedValue {
@ -12,11 +14,15 @@ func parseAndCompare(t *testing.T, rawEnvLine string, expectedKey string, expect
}
}
func loadEnvAndCompareValues(t *testing.T, envFileName string, expectedValues map[string]string) {
func loadEnvAndCompareValues(t *testing.T, loader func(files ...string) error, envFileName string, expectedValues map[string]string, presets map[string]string) {
// first up, clear the env
os.Clearenv()
err := Load(envFileName)
for k, v := range presets {
os.Setenv(k, v)
}
err := loader(envFileName)
if err != nil {
t.Fatalf("Error loading %v", envFileName)
}
@ -38,6 +44,14 @@ func TestLoadWithNoArgsLoadsDotEnv(t *testing.T) {
}
}
func TestOverloadWithNoArgsOverloadsDotEnv(t *testing.T) {
err := Overload()
pathError := err.(*os.PathError)
if pathError == nil || pathError.Op != "open" || pathError.Path != ".env" {
t.Errorf("Didn't try and open .env by default")
}
}
func TestLoadFileNotFound(t *testing.T) {
err := Load("somefilethatwillneverexistever.env")
if err == nil {
@ -45,6 +59,13 @@ func TestLoadFileNotFound(t *testing.T) {
}
}
func TestOverloadFileNotFound(t *testing.T) {
err := Overload("somefilethatwillneverexistever.env")
if err == nil {
t.Error("File wasn't found but Overload didn't return an error")
}
}
func TestReadPlainEnv(t *testing.T) {
envFileName := "fixtures/plain.env"
expectedValues := map[string]string{
@ -71,6 +92,34 @@ func TestReadPlainEnv(t *testing.T) {
}
}
func TestLoadDoesNotOverride(t *testing.T) {
envFileName := "fixtures/plain.env"
// ensure NO overload
presets := map[string]string{
"OPTION_A": "do_not_override",
}
expectedValues := map[string]string{
"OPTION_A": "do_not_override",
}
loadEnvAndCompareValues(t, Load, envFileName, expectedValues, presets)
}
func TestOveroadDoesOverride(t *testing.T) {
envFileName := "fixtures/plain.env"
// ensure NO overload
presets := map[string]string{
"OPTION_A": "do_not_override",
}
expectedValues := map[string]string{
"OPTION_A": "1",
}
loadEnvAndCompareValues(t, Overload, envFileName, expectedValues, presets)
}
func TestLoadPlainEnv(t *testing.T) {
envFileName := "fixtures/plain.env"
expectedValues := map[string]string{
@ -81,7 +130,7 @@ func TestLoadPlainEnv(t *testing.T) {
"OPTION_E": "5",
}
loadEnvAndCompareValues(t, envFileName, expectedValues)
loadEnvAndCompareValues(t, Load, envFileName, expectedValues, noopPresets)
}
func TestLoadExportedEnv(t *testing.T) {
@ -91,7 +140,7 @@ func TestLoadExportedEnv(t *testing.T) {
"OPTION_B": "\n",
}
loadEnvAndCompareValues(t, envFileName, expectedValues)
loadEnvAndCompareValues(t, Load, envFileName, expectedValues, noopPresets)
}
func TestLoadEqualsEnv(t *testing.T) {
@ -100,7 +149,7 @@ func TestLoadEqualsEnv(t *testing.T) {
"OPTION_A": "postgres://localhost:5432/database?sslmode=disable",
}
loadEnvAndCompareValues(t, envFileName, expectedValues)
loadEnvAndCompareValues(t, Load, envFileName, expectedValues, noopPresets)
}
func TestLoadQuotedEnv(t *testing.T) {
@ -116,7 +165,7 @@ func TestLoadQuotedEnv(t *testing.T) {
"OPTION_H": "\n",
}
loadEnvAndCompareValues(t, envFileName, expectedValues)
loadEnvAndCompareValues(t, Load, envFileName, expectedValues, noopPresets)
}
func TestActualEnvVarsAreLeftAlone(t *testing.T) {
@ -220,3 +269,20 @@ func TestLinesToIgnore(t *testing.T) {
t.Error("ignoring a perfectly valid line to parse")
}
}
func TestErrorReadDirectory(t *testing.T) {
envFileName := "fixtures/"
envMap, err := Read(envFileName)
if err == nil {
t.Errorf("Expected error, got %v", envMap)
}
}
func TestErrorParsing(t *testing.T) {
envFileName := "fixtures/invalid1.env"
envMap, err := Read(envFileName)
if err == nil {
t.Errorf("Expected error, got %v", envMap)
}
}