diff --git a/command.go b/command.go index 677f3cb..26ba3b7 100644 --- a/command.go +++ b/command.go @@ -1,44 +1,44 @@ package main import ( - "flag" - "fmt" - "strings" + "flag" + "fmt" + "strings" ) var flagEnv string var flagProcfile string type Command struct { - // args does not include the command name - Run func(cmd *Command, args []string) - Flag flag.FlagSet + // args does not include the command name + Run func(cmd *Command, args []string) + Flag flag.FlagSet - Usage string // first word is the command name - Short string // `forego help` output - Long string // `forego help cmd` output + Usage string // first word is the command name + Short string // `forego help` output + Long string // `forego help cmd` output } func (c *Command) printUsage() { - if c.Runnable() { - fmt.Printf("Usage: forego %s\n\n", c.Usage) - } - fmt.Println(strings.Trim(c.Long, "\n")) + if c.Runnable() { + fmt.Printf("Usage: forego %s\n\n", c.Usage) + } + fmt.Println(strings.Trim(c.Long, "\n")) } func (c *Command) Name() string { - name := c.Usage - i := strings.Index(name, " ") - if i >= 0 { - name = name[:i] - } - return name + name := c.Usage + i := strings.Index(name, " ") + if i >= 0 { + name = name[:i] + } + return name } func (c *Command) Runnable() bool { - return c.Run != nil + return c.Run != nil } func (c *Command) List() bool { - return c.Short != "" + return c.Short != "" } diff --git a/env.go b/env.go index d1921da..695157a 100644 --- a/env.go +++ b/env.go @@ -1,19 +1,19 @@ package main import ( - "bufio" - "fmt" - "github.com/kr/pretty" - "io" - "os" - "regexp" + "bufio" + "fmt" + "github.com/kr/pretty" + "io" + "os" + "regexp" ) var envEntryRegexp = regexp.MustCompile("^([A-Za-z_0-9]+)=(.*)$") type EnvEntry struct { - name string - command string + name string + command string } type Env map[string]string @@ -21,26 +21,26 @@ type Env map[string]string var _ = pretty.Println // lol func ReadEnv(filename string) (Env, error) { - if _, err := os.Stat(filename); os.IsNotExist(err) { - return make(Env), nil - } - fd, err := os.Open(filename) - if err != nil { - return nil, err - } - defer fd.Close() - return parseEnv(fd) + if _, err := os.Stat(filename); os.IsNotExist(err) { + return make(Env), nil + } + fd, err := os.Open(filename) + if err != nil { + return nil, err + } + defer fd.Close() + return parseEnv(fd) } func parseEnv(r io.Reader) (Env, error) { - env := make(Env) - scanner := bufio.NewScanner(r) - for scanner.Scan() { - parts := envEntryRegexp.FindStringSubmatch(scanner.Text()) - env[parts[1]] = parts[2] - } - if err := scanner.Err(); err != nil { - return nil, fmt.Errorf("Reading Env: %s", err) - } - return env, nil + env := make(Env) + scanner := bufio.NewScanner(r) + for scanner.Scan() { + parts := envEntryRegexp.FindStringSubmatch(scanner.Text()) + env[parts[1]] = parts[2] + } + if err := scanner.Err(); err != nil { + return nil, fmt.Errorf("Reading Env: %s", err) + } + return env, nil } diff --git a/error.go b/error.go index 5f30269..da14cff 100644 --- a/error.go +++ b/error.go @@ -1,13 +1,13 @@ package main import ( - "fmt" - "os" + "fmt" + "os" ) func handleError(err error) { - if err != nil { - fmt.Println("ERROR:", err) - os.Exit(1) - } + if err != nil { + fmt.Println("ERROR:", err) + os.Exit(1) + } } diff --git a/help.go b/help.go index 1f85f41..db0f7ae 100644 --- a/help.go +++ b/help.go @@ -1,40 +1,40 @@ package main import ( - "fmt" - "log" - "os" - "text/template" + "fmt" + "log" + "os" + "text/template" ) var cmdHelp = &Command{ - Usage: "help [topic]", - Short: "Show this help", - Long: `Help shows usage for a command.`, + Usage: "help [topic]", + Short: "Show this help", + Long: `Help shows usage for a command.`, } func init() { - cmdHelp.Run = runHelp // break init loop + cmdHelp.Run = runHelp // break init loop } func runHelp(cmd *Command, args []string) { - if len(args) == 0 { - printUsage() - return - } - if len(args) != 1 { - log.Fatal("too many arguments") - } + if len(args) == 0 { + printUsage() + return + } + if len(args) != 1 { + log.Fatal("too many arguments") + } - for _, cmd := range commands { - if cmd.Name() == args[0] { - cmd.printUsage() - return - } - } + for _, cmd := range commands { + if cmd.Name() == args[0] { + cmd.printUsage() + return + } + } - fmt.Fprintf(os.Stderr, "Unknown help topic: %q. Run 'forego help'.\n", args[0]) - os.Exit(2) + fmt.Fprintf(os.Stderr, "Unknown help topic: %q. Run 'forego help'.\n", args[0]) + os.Exit(2) } var usageTemplate = template.Must(template.New("usage").Parse(` @@ -47,14 +47,14 @@ Run 'forego help [command]' for details. `[1:])) func printUsage() { - usageTemplate.Execute(os.Stdout, struct { - Commands []*Command - }{ - commands, - }) + usageTemplate.Execute(os.Stdout, struct { + Commands []*Command + }{ + commands, + }) } func usage() { - printUsage() - os.Exit(2) + printUsage() + os.Exit(2) } diff --git a/main.go b/main.go index 3b87dd3..2a81c0b 100644 --- a/main.go +++ b/main.go @@ -1,35 +1,35 @@ package main import ( - "github.com/kr/pretty" - "os" + "github.com/kr/pretty" + "os" ) var _ = pretty.Println // lol var commands = []*Command{ - cmdStart, - cmdRun, - cmdHelp, + cmdStart, + cmdRun, + cmdHelp, } func main() { - args := os.Args[1:] - if len(args) < 1 { - usage() - } + args := os.Args[1:] + if len(args) < 1 { + usage() + } - for _, cmd := range commands { - if cmd.Name() == args[0] && cmd.Run != nil { - cmd.Flag.Usage = func() { - cmd.printUsage() - } - if err := cmd.Flag.Parse(args[1:]); err != nil { - os.Exit(2) - } - cmd.Run(cmd, cmd.Flag.Args()) - return - } - } - usage() + for _, cmd := range commands { + if cmd.Name() == args[0] && cmd.Run != nil { + cmd.Flag.Usage = func() { + cmd.printUsage() + } + if err := cmd.Flag.Parse(args[1:]); err != nil { + os.Exit(2) + } + cmd.Run(cmd, cmd.Flag.Args()) + return + } + } + usage() } diff --git a/outlet.go b/outlet.go index 235b27b..370317c 100644 --- a/outlet.go +++ b/outlet.go @@ -1,20 +1,20 @@ package main import ( - "bufio" - "bytes" - "fmt" - "github.com/daviddengcn/go-colortext" - "github.com/kr/pretty" - "io" - "os" - "sync" + "bufio" + "bytes" + "fmt" + "github.com/daviddengcn/go-colortext" + "github.com/kr/pretty" + "io" + "os" + "sync" ) type Outlet struct { - Name string - Color ct.Color - IsError bool + Name string + Color ct.Color + IsError bool } var _ = pretty.Println // lol @@ -25,59 +25,59 @@ var longest int var mutex = new(sync.Mutex) var colors = []ct.Color{ - ct.Cyan, - ct.Yellow, - ct.Green, - ct.Magenta, - ct.Red, - ct.Blue, + ct.Cyan, + ct.Yellow, + ct.Green, + ct.Magenta, + ct.Red, + ct.Blue, } func (o *Outlet) Write(b []byte) (num int, err error) { - mutex.Lock() - defer mutex.Unlock() - scanner := bufio.NewScanner(bytes.NewReader(b)) - for scanner.Scan() { - formatter := fmt.Sprintf("%%-%ds | ", longest) - ct.ChangeColor(o.Color, true, ct.None, false) - fmt.Printf(formatter, o.Name) - if (o.IsError) { - ct.ChangeColor(ct.Red, true, ct.None, true) - } else { - ct.ResetColor() - } - fmt.Println(scanner.Text()) - ct.ResetColor() - } - num = len(b) - return + mutex.Lock() + defer mutex.Unlock() + scanner := bufio.NewScanner(bytes.NewReader(b)) + for scanner.Scan() { + formatter := fmt.Sprintf("%%-%ds | ", longest) + ct.ChangeColor(o.Color, true, ct.None, false) + fmt.Printf(formatter, o.Name) + if o.IsError { + ct.ChangeColor(ct.Red, true, ct.None, true) + } else { + ct.ResetColor() + } + fmt.Println(scanner.Text()) + ct.ResetColor() + } + num = len(b) + return } func ProcessOutput(w io.Writer, str string) { - w.Write([]byte(str)) + w.Write([]byte(str)) } var outlets = map[string]*Outlet{} func createOutlet(name string, index int, isError bool) *Outlet { - outlets[name] = &Outlet{name, colors[index%len(colors)], isError} - return outlets[name] + outlets[name] = &Outlet{name, colors[index%len(colors)], isError} + return outlets[name] } func SetLongestOutletName(l int) { - longest = l + longest = l } func SystemOutput(str string) { - ct.ChangeColor(ct.White, true, ct.None, false) - formatter := fmt.Sprintf("%%-%ds | ", longest) - fmt.Printf(formatter, "forego") - ct.ResetColor() - fmt.Println(str) - ct.ResetColor() + ct.ChangeColor(ct.White, true, ct.None, false) + formatter := fmt.Sprintf("%%-%ds | ", longest) + fmt.Printf(formatter, "forego") + ct.ResetColor() + fmt.Println(str) + ct.ResetColor() } func ErrorOutput(str string) { - fmt.Printf("ERROR: %s\n", str) - os.Exit(1) + fmt.Printf("ERROR: %s\n", str) + os.Exit(1) } diff --git a/procfile.go b/procfile.go index 81f814e..9cd7382 100644 --- a/procfile.go +++ b/procfile.go @@ -1,66 +1,66 @@ package main import ( - "bufio" - "fmt" - "github.com/kr/pretty" - "io" - "os" - "regexp" + "bufio" + "fmt" + "github.com/kr/pretty" + "io" + "os" + "regexp" ) var procfileEntryRegexp = regexp.MustCompile("^([A-Za-z0-9_]+):\\s*(.+)$") type ProcfileEntry struct { - Name string - Command string + Name string + Command string } type Procfile struct { - Entries []ProcfileEntry + Entries []ProcfileEntry } var _ = pretty.Println // lol func ReadProcfile(filename string) (*Procfile, error) { - fd, err := os.Open(filename) - if err != nil { - return nil, err - } - defer fd.Close() - return parseProcfile(fd) + fd, err := os.Open(filename) + if err != nil { + return nil, err + } + defer fd.Close() + return parseProcfile(fd) } func (pf *Procfile) HasProcess(name string) (exists bool) { - exists = false - for _, entry := range pf.Entries { - if name == entry.Name { - exists = true - break - } - } - return + exists = false + for _, entry := range pf.Entries { + if name == entry.Name { + exists = true + break + } + } + return } func (pf *Procfile) LongestProcessName() (longest int) { - longest = 6 // length of forego - for _, entry := range pf.Entries { - if len(entry.Name) > longest { - longest = len(entry.Name) - } - } - return + longest = 6 // length of forego + for _, entry := range pf.Entries { + if len(entry.Name) > longest { + longest = len(entry.Name) + } + } + return } func parseProcfile(r io.Reader) (*Procfile, error) { - pf := new(Procfile) - scanner := bufio.NewScanner(r) - for scanner.Scan() { - parts := procfileEntryRegexp.FindStringSubmatch(scanner.Text()) - pf.Entries = append(pf.Entries, ProcfileEntry{parts[1], parts[2]}) - } - if err := scanner.Err(); err != nil { - return nil, fmt.Errorf("Reading Procfile: %s", err) - } - return pf, nil + pf := new(Procfile) + scanner := bufio.NewScanner(r) + for scanner.Scan() { + parts := procfileEntryRegexp.FindStringSubmatch(scanner.Text()) + pf.Entries = append(pf.Entries, ProcfileEntry{parts[1], parts[2]}) + } + if err := scanner.Err(); err != nil { + return nil, fmt.Errorf("Reading Procfile: %s", err) + } + return pf, nil } diff --git a/run.go b/run.go index c8cca7d..9498c2c 100644 --- a/run.go +++ b/run.go @@ -1,22 +1,22 @@ package main import ( - "fmt" - "github.com/kr/pretty" - "os" - "os/exec" - "strings" - "syscall" + "fmt" + "github.com/kr/pretty" + "os" + "os/exec" + "strings" + "syscall" ) var _ = pretty.Println // lol var _ = os.Stdout var cmdRun = &Command{ - Run: runRun, - Usage: "run [-e env] [-c concurrency] [-p port]", - Short: "Run a one-off command", - Long: ` + Run: runRun, + Usage: "run [-e env] [-c concurrency] [-p port]", + Short: "Run a one-off command", + Long: ` Run a one-off command Examples: @@ -26,29 +26,29 @@ Examples: } func init() { - cmdRun.Flag.StringVar(&flagEnv, "e", ".env", "env") + cmdRun.Flag.StringVar(&flagEnv, "e", ".env", "env") } func runRun(cmd *Command, args []string) { - command := []string{"/bin/bash", "-c"} - command = append(command, fmt.Sprintf("source .profile 2>/dev/null; %s", strings.Join(args, " "))) + command := []string{"/bin/bash", "-c"} + command = append(command, fmt.Sprintf("source .profile 2>/dev/null; %s", strings.Join(args, " "))) - env, err := ReadEnv(flagEnv) - handleError(err) + env, err := ReadEnv(flagEnv) + handleError(err) - ps_env := os.Environ() - for name, val := range env { - ps_env = append(ps_env, fmt.Sprintf("%s=%s", name, val)) - } + ps_env := os.Environ() + for name, val := range env { + ps_env = append(ps_env, fmt.Sprintf("%s=%s", name, val)) + } - ps := exec.Command(command[0], command[1:]...) - ps.Dir, _ = os.Getwd() - ps.Env = ps_env - ps.Stdin = nil - ps.Stdout = os.Stdout - ps.Stderr = os.Stderr - ps.SysProcAttr = &syscall.SysProcAttr{} - ps.SysProcAttr.Setsid = true - ps.Start() - ps.Wait() + ps := exec.Command(command[0], command[1:]...) + ps.Dir, _ = os.Getwd() + ps.Env = ps_env + ps.Stdin = nil + ps.Stdout = os.Stdout + ps.Stderr = os.Stderr + ps.SysProcAttr = &syscall.SysProcAttr{} + ps.SysProcAttr.Setsid = true + ps.Start() + ps.Wait() } diff --git a/start.go b/start.go index 9421843..83a57ab 100644 --- a/start.go +++ b/start.go @@ -1,15 +1,15 @@ package main import ( - "fmt" - "github.com/kr/pretty" - "os" - "os/exec" - "os/signal" - "path/filepath" - "sync" - "syscall" - "time" + "fmt" + "github.com/kr/pretty" + "os" + "os/exec" + "os/signal" + "path/filepath" + "sync" + "syscall" + "time" ) const shutdownGraceTime = 3 * time.Second @@ -24,10 +24,10 @@ var shutdown_mutex = new(sync.Mutex) var wg sync.WaitGroup var cmdStart = &Command{ - Run: runStart, - Usage: "start [process name] [-f procfile] [-e env] [-c concurrency] [-p port]", - Short: "Start the application", - Long: ` + Run: runStart, + Usage: "start [process name] [-f procfile] [-e env] [-c concurrency] [-p port]", + Short: "Start the application", + Long: ` Start the application specified by a Procfile (defaults to ./Procfile) Examples: @@ -39,96 +39,96 @@ Examples: } func init() { - cmdStart.Flag.StringVar(&flagProcfile, "f", "Procfile", "procfile") - cmdStart.Flag.StringVar(&flagEnv, "e", "", "env") - cmdStart.Flag.IntVar(&flagPort, "p", 5000, "port") + cmdStart.Flag.StringVar(&flagProcfile, "f", "Procfile", "procfile") + cmdStart.Flag.StringVar(&flagEnv, "e", "", "env") + cmdStart.Flag.IntVar(&flagPort, "p", 5000, "port") } func runStart(cmd *Command, args []string) { - handler := make(chan os.Signal, 1) - signal.Notify(handler, os.Interrupt) + handler := make(chan os.Signal, 1) + signal.Notify(handler, os.Interrupt) - go func() { - for sig := range handler { - switch (sig) { - case os.Interrupt: - fmt.Println(" | ctrl-c detected") - go func() { ShutdownProcesses() }() - } - } - }() + go func() { + for sig := range handler { + switch sig { + case os.Interrupt: + fmt.Println(" | ctrl-c detected") + go func() { ShutdownProcesses() }() + } + } + }() - root := filepath.Dir(flagProcfile) + root := filepath.Dir(flagProcfile) - if (flagEnv == "") { - flagEnv = filepath.Join(root, ".env") - } + if flagEnv == "" { + flagEnv = filepath.Join(root, ".env") + } - var singleton string = "" - if (len(args) > 0) { - singleton = args[0] - } + var singleton string = "" + if len(args) > 0 { + singleton = args[0] + } - pf, err := ReadProcfile(flagProcfile) - handleError(err) - env, err := ReadEnv(flagEnv) - handleError(err) + pf, err := ReadProcfile(flagProcfile) + handleError(err) + env, err := ReadEnv(flagEnv) + handleError(err) - SetLongestOutletName(pf.LongestProcessName()) + SetLongestOutletName(pf.LongestProcessName()) - ps_env := os.Environ() - for name, val := range env { - ps_env = append(ps_env, fmt.Sprintf("%s=%s", name, val)) - } + ps_env := os.Environ() + for name, val := range env { + ps_env = append(ps_env, fmt.Sprintf("%s=%s", name, val)) + } - if singleton != "" { - if !pf.HasProcess(singleton) { - ErrorOutput(fmt.Sprintf("no such process: %s", singleton)) - } - } + if singleton != "" { + if !pf.HasProcess(singleton) { + ErrorOutput(fmt.Sprintf("no such process: %s", singleton)) + } + } - for idx, proc := range pf.Entries { - if (singleton == "") || (singleton == proc.Name) { - wg.Add(1) - command := []string{"/bin/bash", "-c", fmt.Sprintf("source \"%s\" 2>/dev/null; %s", filepath.Join(root, ".profile"), proc.Command)} - ps := exec.Command(command[0], command[1:]...) - port := flagPort + (idx * 100) - processes[proc.Name] = ps - ps.Dir = root - ps.Env = append(ps_env, fmt.Sprintf("PORT=%d", port)) - ps.Stdin = nil - ps.Stdout = createOutlet(proc.Name, idx, false) - ps.Stderr = createOutlet(proc.Name, idx, true) - ps.SysProcAttr = &syscall.SysProcAttr{} - ps.SysProcAttr.Setsid = true - ps.Start() - SystemOutput(fmt.Sprintf("starting %s on port %d", proc.Name, port)) - go func(proc ProcfileEntry, ps *exec.Cmd) { - ps.Wait() - wg.Done() - delete(processes, proc.Name) - }(proc, ps) - } - } + for idx, proc := range pf.Entries { + if (singleton == "") || (singleton == proc.Name) { + wg.Add(1) + command := []string{"/bin/bash", "-c", fmt.Sprintf("source \"%s\" 2>/dev/null; %s", filepath.Join(root, ".profile"), proc.Command)} + ps := exec.Command(command[0], command[1:]...) + port := flagPort + (idx * 100) + processes[proc.Name] = ps + ps.Dir = root + ps.Env = append(ps_env, fmt.Sprintf("PORT=%d", port)) + ps.Stdin = nil + ps.Stdout = createOutlet(proc.Name, idx, false) + ps.Stderr = createOutlet(proc.Name, idx, true) + ps.SysProcAttr = &syscall.SysProcAttr{} + ps.SysProcAttr.Setsid = true + ps.Start() + SystemOutput(fmt.Sprintf("starting %s on port %d", proc.Name, port)) + go func(proc ProcfileEntry, ps *exec.Cmd) { + ps.Wait() + wg.Done() + delete(processes, proc.Name) + }(proc, ps) + } + } - wg.Wait() - shutdown_mutex.Unlock() + wg.Wait() + shutdown_mutex.Unlock() } func ShutdownProcesses() { - shutdown_mutex.Lock() - SystemOutput("shutting down") - for name, ps := range processes { - SystemOutput(fmt.Sprintf("sending SIGTERM to %s", name)) - group, _ := os.FindProcess(-1 * ps.Process.Pid) - group.Signal(syscall.SIGTERM) - } - go func() { - time.Sleep(shutdownGraceTime) - for name, ps := range processes { - SystemOutput(fmt.Sprintf("sending SIGKILL to %s", name)) - group, _ := os.FindProcess(-1 * ps.Process.Pid) - group.Signal(syscall.SIGKILL) - } - }() + shutdown_mutex.Lock() + SystemOutput("shutting down") + for name, ps := range processes { + SystemOutput(fmt.Sprintf("sending SIGTERM to %s", name)) + group, _ := os.FindProcess(-1 * ps.Process.Pid) + group.Signal(syscall.SIGTERM) + } + go func() { + time.Sleep(shutdownGraceTime) + for name, ps := range processes { + SystemOutput(fmt.Sprintf("sending SIGKILL to %s", name)) + group, _ := os.FindProcess(-1 * ps.Process.Pid) + group.Signal(syscall.SIGKILL) + } + }() }