This commit is contained in:
David Dollar
2013-08-27 16:51:24 -04:00
parent f85df22d56
commit f32684375a
9 changed files with 310 additions and 310 deletions
+21 -21
View File
@@ -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 != ""
}
+27 -27
View File
@@ -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
}
+6 -6
View File
@@ -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)
}
}
+30 -30
View File
@@ -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)
}
+22 -22
View File
@@ -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()
}
+46 -46
View File
@@ -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)
}
+40 -40
View File
@@ -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
}
+29 -29
View File
@@ -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()
}
+89 -89
View File
@@ -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)
}
}()
}