Skip to content

Commit 323191f

Browse files
committed
First take
1 parent ed38945 commit 323191f

File tree

7 files changed

+187
-38
lines changed

7 files changed

+187
-38
lines changed

‎.github/workflows/test.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ jobs:
99
build:
1010
strategy:
1111
matrix:
12-
go-version: [1.16.x, 1.17.x]
12+
go-version: [1.18.x, 1.19.x]
1313
os: [ubuntu-latest, windows-latest]
1414
runs-on: ${{ matrix.os }}
1515
steps:

‎README.md‎

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,37 @@
1-
# gomaintemplate
2-
Just a simple main() Go program template.
1+
2+
3+
4+
This is a simple Go program that finds the first `firstup.env` file starting from the current directory walking upwareds, excluding the root folder.
5+
6+
It will write the env vars as export statements. It will also store the list of keys set in a separate environment variable and unset those next time `firstupdotenv` is run.
7+
8+
The output may then look like this:
9+
10+
```bash
11+
unset FOO
12+
unset BAR
13+
export FIRSTUPDOTENV_CURRENT_SET_ENV=FOO,BAR
14+
export FOO=value1
15+
export BAR=value2
16+
```
17+
18+
The `.env` format is a file on the form `key=value`. It ignores empty lines and lines starting with # and lines without an equals sign. If the same key is defined more than once, the last will win.
19+
20+
To install:
21+
22+
```bash
23+
go install github.com/bep/firstupdotenv@latest
24+
```
25+
26+
This tool is meant to be used in combination with some shell extension that triggers when you `cd` into a directory. If you use the [Z shell](https://en.wikipedia.org/wiki/Z_shell), putting this in your `.zshrc` will work:
27+
28+
```
29+
autoload -U add-zsh-hook
30+
31+
firstupdotenv_after_cd() {
32+
source <(firstupdotenv)
33+
}
34+
35+
add-zsh-hook chpwd firstupdotenv_after_cd
36+
```
37+

‎firstup.env‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Test variables.
2+
FOO=value1
3+
BAR=value2

‎go.mod‎

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,3 @@
1-
module github.com/bep/gomaintemplate
1+
module github.com/bep/firstupdotenv
22

33
go 1.17
4-
5-
require github.com/frankban/quicktest v1.14.2
6-
7-
require (
8-
github.com/google/go-cmp v0.5.7 // indirect
9-
github.com/kr/pretty v0.3.0 // indirect
10-
github.com/kr/text v0.2.0 // indirect
11-
github.com/rogpeppe/go-internal v1.6.1 // indirect
12-
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 // indirect
13-
)

‎go.sum‎

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +0,0 @@
1-
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
2-
github.com/frankban/quicktest v1.14.2 h1:SPb1KFFmM+ybpEjPUhCCkZOM5xlovT5UbrMvWnXyBns=
3-
github.com/frankban/quicktest v1.14.2/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps=
4-
github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
5-
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
6-
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
7-
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
8-
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
9-
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
10-
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
11-
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
12-
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
13-
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
14-
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
15-
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
16-
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
17-
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
18-
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=

‎main.go‎

Lines changed: 127 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,135 @@
11
package main
22

33
import (
4+
"bufio"
45
"fmt"
6+
"log"
7+
"os"
8+
"path/filepath"
9+
"strings"
10+
)
11+
12+
const (
13+
name = "firstupdotenv"
14+
nameDotEnv = "firstup.env"
15+
currentSetEnvVar = "FIRSTUPDOTENV_CURRENT_SET_ENV"
516
)
617

718
func main() {
8-
fmt.Println("Do something useful here...")
19+
log.SetFlags(0)
20+
log.SetPrefix(name + ": ")
21+
22+
env, err := createEnvSourceFromCurrentDir()
23+
if err != nil {
24+
log.Fatal(err)
25+
}
26+
fmt.Println(env)
27+
}
28+
29+
func createEnvSourceFromCurrentDir() (string, error) {
30+
directory, err := os.Getwd()
31+
if err != nil {
32+
return "", err
33+
}
34+
35+
var envSetScript strings.Builder
36+
37+
// Always remove the old settings, even if we don't find a new one.
38+
old := os.Getenv(currentSetEnvVar)
39+
if old != "" {
40+
oldKeys := strings.Split(old, ",")
41+
for _, key := range oldKeys {
42+
envSetScript.WriteString(fmt.Sprintf("unset %s\n", key))
43+
}
44+
}
45+
46+
var envFromFile string
47+
48+
for {
49+
if strings.Count(directory, string(os.PathSeparator)) < 2 {
50+
// Stop before the root directory.
51+
break
52+
}
53+
54+
envFromFile, err = loadEnvFile(directory)
55+
if err != nil {
56+
return "", err
57+
}
58+
59+
if envFromFile != "" {
60+
break
61+
}
62+
63+
// Walk up one directory.
64+
directory = filepath.Dir(directory)
65+
}
66+
67+
if envFromFile != "" {
68+
envSetScript.WriteString(envFromFile)
69+
} else {
70+
envSetScript.WriteString(fmt.Sprintf("unset %s\n", currentSetEnvVar))
71+
}
72+
73+
return envSetScript.String(), nil
74+
}
75+
76+
func loadEnvFile(directory string) (string, error) {
77+
filename := filepath.Join(directory, nameDotEnv)
78+
if _, err := os.Stat(filename); err != nil {
79+
return "", nil
80+
}
81+
82+
envm, err := parseEnvFile(filename)
83+
if err != nil {
84+
return "", err
85+
}
86+
if len(envm) == 0 {
87+
return "", nil
88+
}
89+
90+
var envSetScript strings.Builder
91+
92+
var keys []string
93+
for k, v := range envm {
94+
os.Setenv(k, v)
95+
keys = append(keys, k)
96+
}
97+
envSetScript.WriteString(fmt.Sprintf("export %s=%s\n", currentSetEnvVar, strings.Join(keys, ",")))
98+
99+
for k, v := range envm {
100+
envSetScript.WriteString(fmt.Sprintf("export %s=%s\n", k, v))
101+
}
102+
103+
return envSetScript.String(), nil
104+
105+
}
106+
107+
// parseEnvFile loads environment variables from text file on the form key=value.
108+
// It ignores empty lines and lines starting with # and lines without an equals sign.
109+
func parseEnvFile(filename string) (map[string]string, error) {
110+
fi, err := os.Stat(filename)
111+
if err != nil || fi.IsDir() {
112+
return nil, nil
113+
}
114+
115+
f, err := os.Open(filename)
116+
if err != nil {
117+
return nil, err
118+
}
119+
defer f.Close()
120+
121+
env := make(map[string]string)
122+
scanner := bufio.NewScanner(f)
123+
for scanner.Scan() {
124+
line := strings.TrimSpace(scanner.Text())
125+
if line == "" || strings.HasPrefix(line, "#") {
126+
continue
127+
}
128+
key, value, found := strings.Cut(line, "=")
129+
if !found {
130+
continue
131+
}
132+
env[strings.TrimSpace(key)] = strings.TrimSpace(value)
133+
}
134+
return env, scanner.Err()
9135
}

‎main_test.go‎

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,28 @@
11
package main
22

33
import (
4+
"strings"
45
"testing"
5-
6-
qt "github.com/frankban/quicktest"
76
)
87

9-
func TestFoo(t *testing.T) {
8+
func TestFindEnvInCurrentDir(t *testing.T) {
109
t.Parallel()
1110

12-
c := qt.New(t)
11+
env, err := createEnvSourceFromCurrentDir()
12+
if err != nil {
13+
t.Fatal(err)
14+
}
15+
16+
check := func(s ...string) {
17+
for _, want := range s {
18+
if !strings.Contains(env, want) {
19+
t.Errorf("env %q does not contain %q", env, want)
20+
}
21+
}
22+
}
23+
24+
check("export FIRSTUPDOTENV_CURRENT_SET_ENV=FOO,BAR")
25+
check("export FOO=value1")
26+
check("export BAR=value2")
1327

14-
c.Assert(true, qt.IsTrue)
1528
}

0 commit comments

Comments
 (0)