2 package envy makes working with ENV variables in Go trivial.
4 * Get ENV variables with default values.
5 * Set ENV variables safely without affecting the underlying system.
6 * Temporarily change ENV vars; useful for testing.
7 * Map all of the key/values in the ENV.
8 * Loads .env files (by using [godotenv](https://github.com/joho/godotenv/))
25 "github.com/joho/godotenv"
26 "github.com/rogpeppe/go-internal/modfile"
29 var gil = &sync.RWMutex{}
30 var env = map[string]string{}
32 // GO111MODULE is ENV for turning mods on/off
33 const GO111MODULE = "GO111MODULE"
40 // Load the ENV variables to the env map
45 if os.Getenv("GO_ENV") == "" {
46 // if the flag "test.v" is *defined*, we're running as a unit test. Note that we don't care
47 // about v.Value (verbose test mode); we just want to know if the test environment has defined
48 // it. It's also possible that the flags are not yet fully parsed (i.e. flag.Parsed() == false),
49 // so we could not depend on v.Value anyway.
51 if v := flag.Lookup("test.v"); v != nil {
52 env["GO_ENV"] = "test"
56 // set the GOPATH if using >= 1.8 and the GOPATH isn't set
57 if os.Getenv("GOPATH") == "" {
58 out, err := exec.Command("go", "env", "GOPATH").Output()
60 gp := strings.TrimSpace(string(out))
61 os.Setenv("GOPATH", gp)
65 for _, e := range os.Environ() {
66 pair := strings.Split(e, "=")
67 env[pair[0]] = os.Getenv(pair[0])
72 return Get(GO111MODULE, "off") == "on"
75 // Reload the ENV variables. Useful if
76 // an external ENV manager has been used
78 env = map[string]string{}
82 // Load .env files. Files will be loaded in the same order that are received.
83 // Redefined vars will override previously existing values.
84 // IE: envy.Load(".env", "test_env/.env") will result in DIR=test_env
85 // If no arg passed, it will try to load a .env file.
86 func Load(files ...string) error {
88 // If no files received, load the default one
90 err := godotenv.Overload()
97 // We received a list of files
98 for _, file := range files {
100 // Check if it exists or we can access
101 if _, err := os.Stat(file); err != nil {
102 // It does not exist or we can not access.
103 // Return and stop loading
107 // It exists and we have permission. Load it
108 if err := godotenv.Overload(file); err != nil {
112 // Reload the env so all new changes are noticed
119 // Get a value from the ENV. If it doesn't exist the
120 // default value will be returned.
121 func Get(key string, value string) string {
124 if v, ok := env[key]; ok {
130 // Get a value from the ENV. If it doesn't exist
131 // an error will be returned
132 func MustGet(key string) (string, error) {
135 if v, ok := env[key]; ok {
138 return "", fmt.Errorf("could not find ENV var with %s", key)
141 // Set a value into the ENV. This is NOT permanent. It will
142 // only affect values accessed through envy.
143 func Set(key string, value string) {
149 // MustSet the value into the underlying ENV, as well as envy.
150 // This may return an error if there is a problem setting the
151 // underlying ENV value.
152 func MustSet(key string, value string) error {
155 err := os.Setenv(key, value)
163 // Map all of the keys/values set in envy.
164 func Map() map[string]string {
167 cp := map[string]string{}
168 for k, v := range env {
174 // Temp makes a copy of the values and allows operation on
175 // those values temporarily during the run of the function.
176 // At the end of the function run the copy is discarded and
177 // the original values are replaced. This is useful for testing.
178 // Warning: This function is NOT safe to use from a goroutine or
179 // from code which may access any Get or Set function from a goroutine
180 func Temp(f func()) {
182 env = map[string]string{}
183 for k, v := range oenv {
186 defer func() { env = oenv }()
190 func GoPath() string {
191 return Get("GOPATH", "")
194 func GoBin() string {
195 return Get("GO_BIN", "go")
198 func InGoPath() bool {
200 for _, p := range GoPaths() {
201 if strings.HasPrefix(pwd, p) {
208 // GoPaths returns all possible GOPATHS that are set.
209 func GoPaths() []string {
210 gp := Get("GOPATH", "")
211 if runtime.GOOS == "windows" {
212 return strings.Split(gp, ";") // Windows uses a different separator
214 return strings.Split(gp, ":")
217 func importPath(path string) string {
218 path = strings.TrimPrefix(path, "/private")
219 for _, gopath := range GoPaths() {
220 srcpath := filepath.Join(gopath, "src")
221 rel, err := filepath.Rel(srcpath, path)
223 return filepath.ToSlash(rel)
228 rel := strings.TrimPrefix(path, filepath.Join(GoPath(), "src"))
229 rel = strings.TrimPrefix(rel, string(filepath.Separator))
230 return filepath.ToSlash(rel)
233 // CurrentModule will attempt to return the module name from `go.mod` if
234 // modules are enabled.
235 // If modules are not enabled it will fallback to using CurrentPackage instead.
236 func CurrentModule() (string, error) {
238 return CurrentPackage(), nil
240 moddata, err := ioutil.ReadFile("go.mod")
242 return "", errors.New("go.mod cannot be read or does not exist while go module is enabled")
244 packagePath := modfile.ModulePath(moddata)
245 if packagePath == "" {
246 return "", errors.New("go.mod is malformed")
248 return packagePath, nil
251 // CurrentPackage attempts to figure out the current package name from the PWD
252 // Use CurrentModule for a more accurate package name.
253 func CurrentPackage() string {
257 return importPath(pwd)
260 func Environ() []string {
264 for k, v := range env {
265 e = append(e, fmt.Sprintf("%s=%s", k, v))