Skip to content

Commit

Permalink
handle the error cases, add some command lines, remove unnecessary
Browse files Browse the repository at this point in the history
fields
  • Loading branch information
canthefason committed Jun 26, 2016
1 parent 1b2bf01 commit 49ee12a
Show file tree
Hide file tree
Showing 7 changed files with 164 additions and 99 deletions.
30 changes: 14 additions & 16 deletions build.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@ import (
"github.com/fatih/color"
)

// Builder composes of both runner and watcher. Whenever watcher gets notified, builder starts a build process, and forces the runner to restart
type Builder struct {
runner *Runner
watcher *Watcher
}

// NewBuilder constructs the Builder instance
func NewBuilder(w *Watcher, r *Runner) *Builder {
return &Builder{watcher: w, runner: r}
}
Expand All @@ -24,14 +26,16 @@ func NewBuilder(w *Watcher, r *Runner) *Builder {
func (b *Builder) Build(p *Params) {
go b.registerSignalHandler()
go func() {
b.watcher.update <- true
// used for triggering the first build
b.watcher.update <- struct{}{}
}()

for <-b.watcher.Wait() {
fileName := p.createBinaryName()
for range b.watcher.Wait() {
fileName := p.generateBinaryName()

pkg := p.GetPackage()
pkg := p.packagePath()

log.Println("build started")
color.Cyan("Building %s...\n", pkg)

// build package
Expand All @@ -50,25 +54,19 @@ func (b *Builder) Build(p *Params) {

continue
}
log.Println("build completed")

// and start the new process
b.runner.restart(fileName)
}
}

func (b *Builder) registerSignalHandler() {
go func() {
signals := make(chan os.Signal, 1)
signal.Notify(signals)
for {
signal := <-signals
switch signal {
case syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGSTOP:
b.watcher.Close()
b.runner.Close()
}
}
}()
signals := make(chan os.Signal)
signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
<-signals
b.watcher.Close()
b.runner.Close()
}

// interpretError checks the error, and returns nil if it is
Expand Down
5 changes: 2 additions & 3 deletions cmd/watcher/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,13 @@ package main
import (
"os"

watcher "github.com/canthefason/go-watcher"
"github.com/canthefason/go-watcher"
)

func main() {
params := watcher.PrepareArgs(os.Args)
params := watcher.ParseArgs(os.Args)

w := watcher.MustRegisterWatcher(params)
defer w.Close()

r := watcher.NewRunner()

Expand Down
57 changes: 39 additions & 18 deletions common.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,35 +14,36 @@ import (
// Binary name used for built package
const binaryName = "watcher"

var watcherFlags = []string{"run", "watch", "watch-vendor"}

// Params is used for keeping go-watcher and application flag parameters
type Params struct {
// Package parameters
Package []string
// Go-Watcher parameters
Watcher map[string]string
}

// NewParams creates a new instance of Params and returns the pointer
// NewParams creates a new Params instance
func NewParams() *Params {
return &Params{
Package: make([]string, 0),
Watcher: make(map[string]string),
}
}

// Get returns the watcher parameter with given name
// Get returns the watcher parameter with the given name
func (p *Params) Get(name string) string {
return p.Watcher[name]
}

// CloneRun copies run parameter value to watch parameter in-case watch
// parameter does not exist
func (p *Params) CloneRun() {
func (p *Params) cloneRunFlag() {
if p.Watcher["watch"] == "" && p.Watcher["run"] != "" {
p.Watcher["watch"] = p.Watcher["run"]
}
}

func (p *Params) GetPackage() string {
func (p *Params) packagePath() string {
run := p.Get("run")
if run != "" {
return run
Expand All @@ -51,16 +52,16 @@ func (p *Params) GetPackage() string {
return "."
}

// GetBinaryName prepares binary name with GOPATH if it is set
func (p *Params) createBinaryName() string {
// generateBinaryName generates a new binary name for each rebuild, for preventing any sorts of conflicts
func (p *Params) generateBinaryName() string {
rand.Seed(time.Now().UnixNano())
randName := rand.Int31n(999999)
packageName := strings.Replace(p.GetPackage(), "/", "-", -1)
packageName := strings.Replace(p.packagePath(), "/", "-", -1)

return fmt.Sprintf("%s-%s-%d", getBinaryNameRoot(), packageName, randName)
return fmt.Sprintf("%s-%s-%d", generateBinaryPrefix(), packageName, randName)
}

func getBinaryNameRoot() string {
func generateBinaryPrefix() string {
path := os.Getenv("GOPATH")
if path != "" {
return fmt.Sprintf("%s/bin/%s", path, binaryName)
Expand Down Expand Up @@ -93,20 +94,21 @@ func runCommand(name string, args ...string) (*exec.Cmd, error) {
return cmd, nil
}

// PrepareArgs filters the system parameters from package parameters
// and returns Params instance
func PrepareArgs(args []string) *Params {
// ParseArgs extracts the application parameters from args and returns
// Params instance with separated watcher and application parameters
func ParseArgs(args []string) *Params {

params := NewParams()

// remove command
// remove the command argument
args = args[1:len(args)]

for i := 0; i < len(args); i++ {
arg := args[i]
arg = stripDash(arg)

if arg == "run" || arg == "watch" || arg == "watch-vendor" {
if existIn(arg, watcherFlags) {
// used for fetching the value of the given parameter
if len(args) <= i+1 {
log.Fatalf("missing parameter value: %s", arg)
}
Expand All @@ -123,12 +125,13 @@ func PrepareArgs(args []string) *Params {
params.Package = append(params.Package, args[i])
}

params.CloneRun()
params.cloneRunFlag()

return params
}

// stripDash removes the dash chars and returns parameter name
// stripDash removes the both single and double dash chars and returns
// the actual parameter name
func stripDash(arg string) string {
if len(arg) > 1 {
if arg[1] == '-' {
Expand All @@ -140,3 +143,21 @@ func stripDash(arg string) string {

return arg
}

func existIn(search string, in []string) bool {
for i := range in {
if search == in[i] {
return true
}
}

return false
}

func removeFile(fileName string) {
if fileName != "" {
cmd := exec.Command("rm", fileName)
cmd.Run()
cmd.Wait()
}
}
18 changes: 15 additions & 3 deletions common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,15 @@ func TestParamsClone(t *testing.T) {
params := NewParams()
params.Watcher["run"] = "statler"

params.CloneRun()
params.cloneRunFlag()
watch := params.Get("watch")
if watch != "statler" {
t.Error("Expected statler but got %s when watch param is not set", watch)
}

params.Watcher["watch"] = "waldorf"

params.CloneRun()
params.cloneRunFlag()

watch = params.Get("watch")
if watch != "waldorf" {
Expand All @@ -37,7 +37,7 @@ func TestParamsClone(t *testing.T) {
func TestPrepareArgs(t *testing.T) {
args := []string{"watcher", "-run", "balcony", "-p", "11880", "--watch", "show", "--host", "localhost"}

params := PrepareArgs(args)
params := ParseArgs(args)
if len(params.Package) != 4 {
t.Fatalf("Expected 2 parameters with their values in Package parameters but got %d", len(params.Package))
}
Expand Down Expand Up @@ -86,3 +86,15 @@ func TestStripDash(t *testing.T) {
}

}

func TestExistIn(t *testing.T) {
input := []string{"a", "b", "c"}

if !existIn("c", input) {
t.Errorf("expected true, got false")
}

if existIn("d", input) {
t.Errorf("expected false, got true")
}
}
69 changes: 32 additions & 37 deletions run.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Watcher is a command line tool inspired by fresh (https://github.com/pilu/fresh) and used
// Package watcher is a command line tool inspired by fresh (https://github.com/pilu/fresh) and used
// for watching .go file changes, and restarting the app in case of an update/delete/add operation.
// After you installed it, you can run your apps with their default parameters as:
// watcher -c config -p 7000 -h localhost
Expand All @@ -12,80 +12,75 @@ import (
"github.com/fatih/color"
)

// Runner listens change events and depending on that kills
// the obsolete process, and runs the new one
// Runner listens for the change events and depending on that kills
// the obsolete process, and runs a new one
type Runner struct {
running bool
start chan string
done chan struct{}
fileName string
cmd *exec.Cmd
start chan string
done chan struct{}
cmd *exec.Cmd

mu *sync.Mutex
}

// NewRunner creates a new Runner instance and returns its pointer
func NewRunner() *Runner {
return &Runner{
running: false,
start: make(chan string),
done: make(chan struct{}),
mu: &sync.Mutex{},
start: make(chan string),
done: make(chan struct{}),
mu: &sync.Mutex{},
}
}

// Init initializes runner with given parameters.
// Run initializes runner with given parameters.
func (r *Runner) Run(p *Params) {
for fileName := range r.start {

color.Green("Running %s...\n", p.Get("run"))

cmd, err := runCommand(fileName, p.Package...)
if err != nil {
log.Printf("Could not run the go binary: %s", err)
log.Printf("Could not run the go binary: %s \n", err)
r.kill()

continue
}

r.mu.Lock()
r.cmd = cmd
removeFile(fileName)
r.mu.Unlock()

go func(name string) {
r.mu.Lock()
r.running = true
r.fileName = name
r.mu.Unlock()
r.cmd.Wait()
}(fileName)
go func(cmd *exec.Cmd) {
if err := cmd.Wait(); err != nil {
log.Printf("process interrupted: %s \n", err)
r.kill()
}
}(r.cmd)
}
}

// Restart kills the process, removes the old binary and
// restarts the new process
func (r *Runner) restart(fileName string) {
if r.running {
r.kill()
r.removeFile()
}
r.kill()

r.start <- fileName
}

func (r *Runner) kill() {
pid := r.cmd.Process.Pid
log.Printf("Killing PID %d \n", pid)
r.cmd.Process.Kill()
}

func (r *Runner) removeFile() {
if r.fileName != "" {
cmd := exec.Command("rm", r.fileName)
cmd.Run()
cmd.Wait()
r.mu.Lock()
defer r.mu.Unlock()
if r.cmd != nil {
pid := r.cmd.Process.Pid
log.Printf("Killing PID %d \n", pid)
r.cmd.Process.Kill()
r.cmd = nil
}
}

func (r *Runner) Close() {
r.kill()
r.removeFile()
close(r.start)
r.kill()
close(r.done)
}

Expand Down
Loading

0 comments on commit 49ee12a

Please sign in to comment.