diff --git a/command.go b/command.go index 2a275d7..74c13f4 100644 --- a/command.go +++ b/command.go @@ -6,7 +6,6 @@ import ( "strings" ) -var flagEnv string var flagProcfile string type Command struct { @@ -37,7 +36,7 @@ func (c *Command) Name() string { } func (c *Command) Runnable() bool { - return c.Run != nil && c.Disabled != true + return c.Run != nil && !c.Disabled } func (c *Command) List() bool { diff --git a/env.go b/env.go index 61ec535..396cf3b 100644 --- a/env.go +++ b/env.go @@ -3,14 +3,35 @@ package main import ( "fmt" "os" - "regexp" + "sync" "github.com/subosito/gotenv" ) -var envEntryRegexp = regexp.MustCompile("^([A-Za-z_0-9]+)=(.*)$") +type Env struct { + m sync.Map +} -type Env map[string]string +func (e *Env) Get(key string) string { + value, ok := e.m.Load(key) + if !ok { + return "" + } + return value.(string) +} + +func (e *Env) Set(key, value string) { + e.m.Store(key, value) +} + +func (e *Env) Delete(key string) { + e.m.Delete(key) +} + +// NewEnv makes a new environment +func NewEnv() *Env { + return &Env{sync.Map{}} +} type envFiles []string @@ -23,13 +44,14 @@ func (e *envFiles) Set(value string) error { return nil } -func loadEnvs(files []string) (Env, error) { +func loadEnvs(files []string) (*Env, error) { if len(files) == 0 { files = []string{".env"} } - env := make(Env) + env := NewEnv() + // don't need to lock either environment for _, file := range files { tmpEnv, err := ReadEnv(file) @@ -38,35 +60,40 @@ func loadEnvs(files []string) (Env, error) { } // Merge the file I just read into the env. - for k, v := range tmpEnv { - env[k] = v - } + tmpEnv.m.Range(func(key, value interface{}) bool { + env.m.Store(key, value) + return true + }) } return env, nil } -func ReadEnv(filename string) (Env, error) { +func ReadEnv(filename string) (*Env, error) { + env := NewEnv() + if _, err := os.Stat(filename); os.IsNotExist(err) { - return make(Env), nil + return env, nil } + fd, err := os.Open(filename) if err != nil { return nil, err } defer fd.Close() - env := make(Env) + for key, val := range gotenv.Parse(fd) { - env[key] = val + env.Set(key, val) } return env, nil } func (e *Env) asArray() (env []string) { - for _, pair := range os.Environ() { - env = append(env, pair) - } - for name, val := range *e { + env = append(env, os.Environ()...) + + e.m.Range(func(name, val interface{}) bool { env = append(env, fmt.Sprintf("%s=%s", name, val)) - } + return true + }) + return } diff --git a/env_test.go b/env_test.go index 4eac3ae..7db1012 100644 --- a/env_test.go +++ b/env_test.go @@ -10,11 +10,11 @@ func TestMultipleEnvironmentFiles(t *testing.T) { t.Fatalf("Could not read environments: %s", err) } - if env["env1"] == "" { + if env.Get("env1") == "" { t.Fatal("$env1 should be present and is not") } - if env["env2"] == "" { + if env.Get("env2") == "" { t.Fatal("$env2 should be present and is not") } } diff --git a/process.go b/process.go index ae1bcdd..9725caf 100644 --- a/process.go +++ b/process.go @@ -8,13 +8,13 @@ import ( type Process struct { Command string - Env Env + Env *Env Interactive bool *exec.Cmd } -func NewProcess(workdir, command string, env Env, interactive bool) (p *Process) { +func NewProcess(workdir, command string, env *Env, interactive bool) (p *Process) { argv := ShellInvocationCommand(interactive, workdir, command) return &Process{ command, env, interactive, exec.Command(argv[0], argv[1:]...), diff --git a/procfile.go b/procfile.go index 07b9837..2ce90f9 100644 --- a/procfile.go +++ b/procfile.go @@ -9,7 +9,7 @@ import ( "regexp" ) -var procfileEntryRegexp = regexp.MustCompile("^([A-Za-z0-9_-]+):\\s*(.+)$") +var procfileEntryRegexp = regexp.MustCompile(`^([A-Za-z0-9_-]+):\s*(.+)$`) type ProcfileEntry struct { Name string @@ -68,7 +68,7 @@ func parseProcfile(r io.Reader) (*Procfile, error) { } } if err := scanner.Err(); err != nil { - return nil, fmt.Errorf("Reading Procfile: %s", err) + return nil, fmt.Errorf("reading Procfile: %s", err) } return pf, nil } diff --git a/start.go b/start.go index 59f5e47..70b77ce 100644 --- a/start.go +++ b/start.go @@ -121,13 +121,13 @@ func parseConcurrency(value string) (map[string]int, error) { parts := strings.Split(value, ",") for _, part := range parts { if !strings.Contains(part, "=") { - return concurrency, errors.New("Concurrency should be in the format: foo=1,bar=2") + return concurrency, errors.New("concurrency should be in the format: foo=1,bar=2") } nameValue := strings.Split(part, "=") n, v := strings.TrimSpace(nameValue[0]), strings.TrimSpace(nameValue[1]) if n == "" || v == "" { - return concurrency, errors.New("Concurrency should be in the format: foo=1,bar=2") + return concurrency, errors.New("concurrency should be in the format: foo=1,bar=2") } numProcs, err := strconv.ParseInt(v, 10, 16) @@ -169,18 +169,19 @@ func (f *Forego) monitorInterrupt() { } } -func basePort(env Env) (int, error) { +func basePort(env *Env) (int, error) { + if flagPort != defaultPort { return flagPort, nil - } else if env["PORT"] != "" { - return strconv.Atoi(env["PORT"]) + } else if port := env.Get("PORT"); port != "" { + return strconv.Atoi(port) } else if os.Getenv("PORT") != "" { return strconv.Atoi(os.Getenv("PORT")) } return defaultPort, nil } -func (f *Forego) startProcess(idx, procNum int, proc ProcfileEntry, env Env, of *OutletFactory) { +func (f *Forego) startProcess(idx, procNum int, proc ProcfileEntry, env *Env, of *OutletFactory) { port, err := basePort(env) if err != nil { panic(err) @@ -192,7 +193,8 @@ func (f *Forego) startProcess(idx, procNum int, proc ProcfileEntry, env Env, of workDir := filepath.Dir(flagProcfile) ps := NewProcess(workDir, proc.Command, env, interactive) procName := fmt.Sprint(proc.Name, ".", procNum+1) - ps.Env["PORT"] = strconv.Itoa(port) + + ps.Env.Set("PORT", strconv.Itoa(port)) ps.Stdin = nil diff --git a/start_test.go b/start_test.go index d196f2c..1b4cdd5 100644 --- a/start_test.go +++ b/start_test.go @@ -111,7 +111,7 @@ func TestParseConcurrencyFlagNoValue(t *testing.T) { } func TestPortFromEnv(t *testing.T) { - env := make(Env) + env := NewEnv() port, err := basePort(env) if err != nil { t.Fatalf("Can not get base port: %s", err) @@ -129,7 +129,7 @@ func TestPortFromEnv(t *testing.T) { t.Fatal("Base port should be 4000") } - env["PORT"] = "6000" + env.Set("PORT", "6000") port, err = basePort(env) if err != nil { t.Fatalf("Can not get base port: %s", err) @@ -138,8 +138,8 @@ func TestPortFromEnv(t *testing.T) { t.Fatal("Base port should be 6000") } - env["PORT"] = "forego" - port, err = basePort(env) + env.Set("PORT", "forego") + _, err = basePort(env) if err == nil { t.Fatalf("Port 'forego' should fail: %s", err) } diff --git a/unix.go b/unix.go index 84f158c..6137364 100644 --- a/unix.go +++ b/unix.go @@ -7,6 +7,8 @@ import ( "syscall" ) +var osShell string = "bash" + const osHaveSigTerm = true func ShellInvocationCommand(interactive bool, root, command string) []string { @@ -15,7 +17,7 @@ func ShellInvocationCommand(interactive bool, root, command string) []string { shellArgument = "-ic" } shellCommand := fmt.Sprintf("cd \"%s\"; source .profile 2>/dev/null; exec %s", root, command) - return []string{"sh", shellArgument, shellCommand} + return []string{osShell, shellArgument, shellCommand} } func (p *Process) PlatformSpecificInit() { @@ -23,7 +25,6 @@ func (p *Process) PlatformSpecificInit() { p.SysProcAttr = &syscall.SysProcAttr{} p.SysProcAttr.Setsid = true } - return } func (p *Process) SendSigTerm() {