diff --git a/watcher/exec.go b/watcher/exec.go index 83baeb6..7a08f08 100644 --- a/watcher/exec.go +++ b/watcher/exec.go @@ -15,6 +15,88 @@ import ( "time" ) +// GoBuild is an implementation of the "go build" +func (p *Project) goBuild() (string, error) { + var out bytes.Buffer + var stderr bytes.Buffer + args := []string{"build"} + args = arguments(args, p.Cmds.Build.Args) + build := exec.Command("go", args...) + build.Dir = p.base + build.Stdout = &out + build.Stderr = &stderr + if err := build.Run(); err != nil { + return stderr.String(), err + } + return "", nil +} + +// GoInstall is an implementation of the "go install" +func (p *Project) goInstall() (string, error) { + var out bytes.Buffer + var stderr bytes.Buffer + err := os.Setenv("GOBIN", filepath.Join(getEnvPath("GOPATH"), "bin")) + if err != nil { + return "", err + } + args := []string{"install"} + for _, arg := range p.Cmds.Install.Args { + arr := strings.Fields(arg) + args = append(args, arr...) + } + build := exec.Command("go", args...) + build.Dir = p.base + build.Stdout = &out + build.Stderr = &stderr + if err := build.Run(); err != nil { + return stderr.String(), err + } + return "", nil +} + +// Exec an additional command from a defined path if specified +func (p *Project) command(cmd Command) (errors string, logs string) { + var stdout bytes.Buffer + var stderr bytes.Buffer + command := strings.Replace(strings.Replace(cmd.Command, "'", "", -1), "\"", "", -1) + c := strings.Split(command, " ") + build := exec.Command(c[0], c[1:]...) + build.Dir = p.base + if cmd.Path != "" { + if strings.Contains(cmd.Path, p.base) { + build.Dir = cmd.Path + } else { + build.Dir = filepath.Join(p.base, cmd.Path) + } + } + build.Stdout = &stdout + build.Stderr = &stderr + err := build.Run() + // check if log + logs = stdout.String() + if err != nil { + errors = stderr.String() + return errors, logs + } + return "", logs +} + +// GoTool is used for run go methods such as fmt, test, generate... +func (p *Project) goTool(dir string, name string, cmd ...string) (string, error) { + if s := filepath.Ext(dir); s != "" && s != ".go" { + return "", nil + } + var out, stderr bytes.Buffer + build := exec.Command(name, cmd...) + build.Dir = dir + build.Stdout = &out + build.Stderr = &stderr + if err := build.Run(); err != nil { + return stderr.String() + out.String(), err + } + return "", nil +} + // GoRun is an implementation of the bin execution func (p *Project) goRun(channel chan bool, runner chan bool, wr *sync.WaitGroup) error { var build *exec.Cmd @@ -105,85 +187,3 @@ func (p *Project) goRun(channel chan bool, runner chan bool, wr *sync.WaitGroup) } } } - -// GoBuild is an implementation of the "go build" -func (p *Project) goBuild() (string, error) { - var out bytes.Buffer - var stderr bytes.Buffer - args := []string{"build"} - args = arguments(args, p.Cmds.Build.Args) - build := exec.Command("go", args...) - build.Dir = p.base - build.Stdout = &out - build.Stderr = &stderr - if err := build.Run(); err != nil { - return stderr.String(), err - } - return "", nil -} - -// GoInstall is an implementation of the "go install" -func (p *Project) goInstall() (string, error) { - var out bytes.Buffer - var stderr bytes.Buffer - err := os.Setenv("GOBIN", filepath.Join(getEnvPath("GOPATH"), "bin")) - if err != nil { - return "", err - } - args := []string{"install"} - for _, arg := range p.Cmds.Install.Args { - arr := strings.Fields(arg) - args = append(args, arr...) - } - build := exec.Command("go", args...) - build.Dir = p.base - build.Stdout = &out - build.Stderr = &stderr - if err := build.Run(); err != nil { - return stderr.String(), err - } - return "", nil -} - -// GoTool is used for run go methods such as fmt, test, generate... -func (p *Project) goTool(dir string, name string, cmd ...string) (string, error) { - if s := filepath.Ext(dir); s != "" && s != ".go" { - return "", nil - } - var out, stderr bytes.Buffer - build := exec.Command(name, cmd...) - build.Dir = dir - build.Stdout = &out - build.Stderr = &stderr - if err := build.Run(); err != nil { - return stderr.String() + out.String(), err - } - return "", nil -} - -// Exec an additional command from a defined path if specified -func (p *Project) command(cmd Command) (errors string, logs string) { - var stdout bytes.Buffer - var stderr bytes.Buffer - command := strings.Replace(strings.Replace(cmd.Command, "'", "", -1), "\"", "", -1) - c := strings.Split(command, " ") - build := exec.Command(c[0], c[1:]...) - build.Dir = p.base - if cmd.Path != "" { - if strings.Contains(cmd.Path, p.base) { - build.Dir = cmd.Path - } else { - build.Dir = filepath.Join(p.base, cmd.Path) - } - } - build.Stdout = &stdout - build.Stderr = &stderr - err := build.Run() - // check if log - logs = stdout.String() - if err != nil { - errors = stderr.String() - return errors, logs - } - return "", logs -} diff --git a/watcher/watcher.go b/watcher/watcher.go index 9bb78db..f30ca1b 100644 --- a/watcher/watcher.go +++ b/watcher/watcher.go @@ -4,13 +4,13 @@ import ( "errors" "fmt" "github.com/fsnotify/fsnotify" + "github.com/tockins/realize/settings" "github.com/tockins/realize/style" "log" "math/big" "os" "os/signal" "path/filepath" - "reflect" "strconv" "strings" "sync" @@ -21,26 +21,91 @@ import ( var msg string var out BufferOut -// Add a path to paths list -func (w *pollWatcher) Add(path string) error { - if w.paths == nil { - w.paths = map[string]bool{} - } - w.paths[path] = true - return nil +// Project defines the informations of a single project +type Project struct { + settings.Settings + parent *Blueprint + path string + tools tools + wr sync.WaitGroup + watcher *fsnotify.Watcher + channel chan bool + exit chan os.Signal + base string + LastChangedOn time.Time `yaml:"-" json:"-"` + Name string `yaml:"name" json:"name"` + Path string `yaml:"path" json:"path"` + Environment map[string]string `yaml:"environment,omitempty" json:"environment,omitempty"` + Cmds Cmds `yaml:"commands" json:"commands"` + Args []string `yaml:"args,omitempty" json:"args,omitempty"` + Watcher Watcher `yaml:"watcher" json:"watcher"` + Buffer Buffer `yaml:"-" json:"buffer"` + ErrorOutputPattern string `yaml:"errorOutputPattern,omitempty" json:"errorOutputPattern,omitempty"` } -// Check if is watching -func (w *pollWatcher) isWatching(path string) bool { - a, b := w.paths[path] - return a && b +// Watch the project by fsnotify +func (p *Project) watchByNotify() { + wr := sync.WaitGroup{} + channel := make(chan bool, 1) + watcher := &fsnotify.Watcher{} + exit := make(chan os.Signal, 2) + signal.Notify(exit, os.Interrupt, syscall.SIGTERM) + watcher, err := fsnotify.NewWatcher() + p.Fatal(err) + defer func() { + p.cmd("after", true) + wg.Done() + }() + p.cmd("before", true) + go p.routines(&wr, channel, watcher, "") + p.LastChangedOn = time.Now().Truncate(time.Second) +L: + for { + select { + case event := <-watcher.Events: + if time.Now().Truncate(time.Second).After(p.LastChangedOn) { + if event.Op&fsnotify.Chmod == fsnotify.Chmod { + continue + } + if index := strings.Index(filepath.Ext(event.Name), "__"); index != -1 { + continue + } + ext := filepath.Ext(event.Name) + if inArray(filepath.Ext(event.Name), p.Watcher.Exts) { + if p.Cmds.Run { + close(channel) + channel = make(chan bool) + } + // repeat the initial cycle + msg = fmt.Sprintln(p.pname(p.Name, 4), ":", style.Magenta.Bold(strings.ToUpper(ext[1:])+" changed"), style.Magenta.Bold(event.Name)) + out = BufferOut{Time: time.Now(), Text: strings.ToUpper(ext[1:]) + " changed " + event.Name} + p.stamp("log", out, msg, "") + // check if is deleted + if event.Op&fsnotify.Remove == fsnotify.Remove { + go p.routines(&wr, channel, watcher, "") + } else { + go p.routines(&wr, channel, watcher, event.Name) + } + p.LastChangedOn = time.Now().Truncate(time.Second) + } + } + case err := <-watcher.Errors: + msg = fmt.Sprintln(p.pname(p.Name, 2), ":", style.Red.Regular(err.Error())) + out = BufferOut{Time: time.Now(), Text: err.Error()} + p.stamp("error", out, msg, "") + case <-exit: + break L + } + } + return } // Watch the project by polling func (p *Project) watchByPolling() { - var wr sync.WaitGroup - var watcher = new(pollWatcher) - channel, exit := make(chan bool, 1), make(chan os.Signal, 2) + wr := sync.WaitGroup{} + watcher := new(pollWatcher) + channel := make(chan bool, 1) + exit := make(chan os.Signal, 2) signal.Notify(exit, os.Interrupt, syscall.SIGTERM) defer func() { p.cmd("after", true) @@ -50,7 +115,6 @@ func (p *Project) watchByPolling() { go p.routines(&wr, channel, watcher, "") p.LastChangedOn = time.Now().Truncate(time.Second) walk := func(changed string, info os.FileInfo, err error) error { - var ext string if err != nil { return err } else if !watcher.isWatching(changed) { @@ -58,24 +122,21 @@ func (p *Project) watchByPolling() { } else if !info.ModTime().Truncate(time.Second).After(p.LastChangedOn) { return nil } - if index := strings.Index(filepath.Ext(changed), "_"); index == -1 { - ext = filepath.Ext(changed) - } else { - ext = filepath.Ext(changed)[0:index] + if index := strings.Index(filepath.Ext(changed), "__"); index != -1 { + return nil } - i := strings.Index(changed, filepath.Ext(changed)) - file := changed[:i] + ext - if changed[:i] != "" && inArray(ext, p.Watcher.Exts) { + ext := filepath.Ext(changed) + if inArray(ext, p.Watcher.Exts) { if p.Cmds.Run { close(channel) channel = make(chan bool) } p.LastChangedOn = time.Now().Truncate(time.Second) // repeat the initial cycle - msg = fmt.Sprintln(p.pname(p.Name, 4), ":", style.Magenta.Bold(strings.ToUpper(ext[1:])+" changed"), style.Magenta.Bold(file)) - out = BufferOut{Time: time.Now(), Text: strings.ToUpper(ext[1:]) + " changed " + file} + msg = fmt.Sprintln(p.pname(p.Name, 4), ":", style.Magenta.Bold(strings.ToUpper(ext[1:])+" changed"), style.Magenta.Bold(changed)) + out = BufferOut{Time: time.Now(), Text: strings.ToUpper(ext[1:]) + " changed " + changed} p.stamp("log", out, msg, "") - go p.routines(&wr, channel, watcher, file) + go p.routines(&wr, channel, watcher, changed) } return nil } @@ -102,67 +163,58 @@ func (p *Project) watchByPolling() { } } -// Watch the project by fsnotify -func (p *Project) watchByNotify() { - var wr sync.WaitGroup - var watcher *fsnotify.Watcher - channel, exit := make(chan bool, 1), make(chan os.Signal, 2) - signal.Notify(exit, os.Interrupt, syscall.SIGTERM) - watcher, err := fsnotify.NewWatcher() - p.Fatal(err) - defer func() { - p.cmd("after", true) - wg.Done() - }() - p.cmd("before", true) - go p.routines(&wr, channel, watcher, "") - p.LastChangedOn = time.Now().Truncate(time.Second) - for { - select { - case event := <-watcher.Events: - if time.Now().Truncate(time.Second).After(p.LastChangedOn) { - if event.Op&fsnotify.Chmod == fsnotify.Chmod { - continue - } - var ext string - if index := strings.Index(filepath.Ext(event.Name), "_"); index == -1 { - ext = filepath.Ext(event.Name) - } else { - ext = filepath.Ext(event.Name)[0:index] - } - i := strings.Index(event.Name, filepath.Ext(event.Name)) - file := event.Name[:i] + ext - if event.Name[:i] != "" && inArray(ext, p.Watcher.Exts) { - if p.Cmds.Run { - close(channel) - channel = make(chan bool) - } - // repeat the initial cycle - msg = fmt.Sprintln(p.pname(p.Name, 4), ":", style.Magenta.Bold(strings.ToUpper(ext[1:])+" changed"), style.Magenta.Bold(file)) - out = BufferOut{Time: time.Now(), Text: strings.ToUpper(ext[1:]) + " changed " + file} - p.stamp("log", out, msg, "") +// Build calls an implementation of the "go build" +func (p *Project) build() error { + if p.Cmds.Build.Status { + start := time.Now() + log.Println(p.pname(p.Name, 1), ":", "Building..") + stream, err := p.goBuild() + if err != nil { + msg = fmt.Sprintln(p.pname(p.Name, 2), ":", style.Red.Bold("Go Build"), style.Red.Regular(err.Error())) + out = BufferOut{Time: time.Now(), Text: err.Error(), Type: "Go Build", Stream: stream} + p.stamp("error", out, msg, stream) + } else { + msg = fmt.Sprintln(p.pname(p.Name, 5), ":", style.Green.Regular("Built")+" after", style.Magenta.Regular(big.NewFloat(float64(time.Since(start).Seconds())).Text('f', 3), " s")) + out = BufferOut{Time: time.Now(), Text: "Built after " + big.NewFloat(float64(time.Since(start).Seconds())).Text('f', 3) + " s"} + p.stamp("log", out, msg, stream) + } + return err + } + return nil +} - // check if is deleted - if event.Op&fsnotify.Remove == fsnotify.Remove { - go p.routines(&wr, channel, watcher, "") - }else{ - go p.routines(&wr, channel, watcher, file) - } - p.LastChangedOn = time.Now().Truncate(time.Second) - } - } - case err := <-watcher.Errors: - msg = fmt.Sprintln(p.pname(p.Name, 2), ":", style.Red.Regular(err.Error())) - out = BufferOut{Time: time.Now(), Text: err.Error()} - p.stamp("error", out, msg, "") - case <-exit: - return +// Install calls an implementation of "go install" +func (p *Project) install() error { + if p.Cmds.Install.Status { + start := time.Now() + log.Println(p.pname(p.Name, 1), ":", "Installing..") + stream, err := p.goInstall() + if err != nil { + msg = fmt.Sprintln(p.pname(p.Name, 2), ":", style.Red.Bold("Go Install"), style.Red.Regular(err.Error())) + out = BufferOut{Time: time.Now(), Text: err.Error(), Type: "Go Install", Stream: stream} + p.stamp("error", out, msg, stream) + } else { + msg = fmt.Sprintln(p.pname(p.Name, 5), ":", style.Green.Regular("Installed")+" after", style.Magenta.Regular(big.NewFloat(float64(time.Since(start).Seconds())).Text('f', 3), " s")) + out = BufferOut{Time: time.Now(), Text: "Installed after " + big.NewFloat(float64(time.Since(start).Seconds())).Text('f', 3) + " s"} + p.stamp("log", out, msg, stream) + } + return err + } + return nil +} + +// Ignore and validate a path +func (p *Project) ignore(str string) bool { + for _, v := range p.Watcher.Ignore { + if strings.Contains(str, filepath.Join(p.base, v)) { + return true } } + return false } // Watch the files tree of a project -func (p *Project) watch(watcher watcher) error { +func (p *Project) walk(watcher watcher) error { var files, folders int64 walk := func(path string, info os.FileInfo, err error) error { if !p.ignore(path) { @@ -203,85 +255,6 @@ func (p *Project) watch(watcher watcher) error { return nil } -// Install calls an implementation of "go install" -func (p *Project) install() error { - if p.Cmds.Install.Status { - start := time.Now() - log.Println(p.pname(p.Name, 1), ":", "Installing..") - stream, err := p.goInstall() - if err != nil { - msg = fmt.Sprintln(p.pname(p.Name, 2), ":", style.Red.Bold("Go Install"), style.Red.Regular(err.Error())) - out = BufferOut{Time: time.Now(), Text: err.Error(), Type: "Go Install", Stream: stream} - p.stamp("error", out, msg, stream) - } else { - msg = fmt.Sprintln(p.pname(p.Name, 5), ":", style.Green.Regular("Installed")+" after", style.Magenta.Regular(big.NewFloat(float64(time.Since(start).Seconds())).Text('f', 3), " s")) - out = BufferOut{Time: time.Now(), Text: "Installed after " + big.NewFloat(float64(time.Since(start).Seconds())).Text('f', 3) + " s"} - p.stamp("log", out, msg, stream) - } - return err - } - return nil -} - -// Install calls an implementation of "go run" -func (p *Project) run(channel chan bool, wr *sync.WaitGroup) { - if p.Cmds.Run { - start := time.Now() - runner := make(chan bool, 1) - log.Println(p.pname(p.Name, 1), ":", "Running..") - go p.goRun(channel, runner, wr) - for { - select { - case <-runner: - msg = fmt.Sprintln(p.pname(p.Name, 5), ":", style.Green.Regular("Started")+" after", style.Magenta.Regular(big.NewFloat(float64(time.Since(start).Seconds())).Text('f', 3), " s")) - out = BufferOut{Time: time.Now(), Text: "Started after " + big.NewFloat(float64(time.Since(start).Seconds())).Text('f', 3) + " s"} - p.stamp("log", out, msg, "") - return - } - } - } -} - -// Build calls an implementation of the "go build" -func (p *Project) build() error { - if p.Cmds.Build.Status { - start := time.Now() - log.Println(p.pname(p.Name, 1), ":", "Building..") - stream, err := p.goBuild() - if err != nil { - msg = fmt.Sprintln(p.pname(p.Name, 2), ":", style.Red.Bold("Go Build"), style.Red.Regular(err.Error())) - out = BufferOut{Time: time.Now(), Text: err.Error(), Type: "Go Build", Stream: stream} - p.stamp("error", out, msg, stream) - } else { - msg = fmt.Sprintln(p.pname(p.Name, 5), ":", style.Green.Regular("Built")+" after", style.Magenta.Regular(big.NewFloat(float64(time.Since(start).Seconds())).Text('f', 3), " s")) - out = BufferOut{Time: time.Now(), Text: "Built after " + big.NewFloat(float64(time.Since(start).Seconds())).Text('f', 3) + " s"} - p.stamp("log", out, msg, stream) - } - return err - } - return nil -} - -// Tool logs the result of a go command -func (p *Project) tool(path string, tool tool) error { - if tool.status != nil { - v := reflect.ValueOf(tool.status).Elem() - if v.Interface().(bool) && (strings.HasSuffix(path, ".go") || strings.HasSuffix(path, "")) { - if strings.HasSuffix(path, ".go") { - tool.options = append(tool.options, path) - path = p.base - } - if stream, err := p.goTool(path, tool.cmd, tool.options...); err != nil { - msg = fmt.Sprintln(p.pname(p.Name, 2), ":", style.Red.Bold(tool.name), style.Red.Regular("there are some errors in"), ":", style.Magenta.Bold(path)) - out = BufferOut{Time: time.Now(), Text: "there are some errors in", Path: path, Type: tool.name, Stream: stream} - p.stamp("error", out, msg, stream) - return err - } - } - } - return nil -} - // Cmd calls an wrapper for execute the commands after/before func (p *Project) cmd(flag string, global bool) { for _, cmd := range p.Watcher.Scripts { @@ -308,38 +281,23 @@ func (p *Project) cmd(flag string, global bool) { } } -// Ignore and validate a path -func (p *Project) ignore(str string) bool { - for _, v := range p.Watcher.Ignore { - if strings.Contains(str, filepath.Join(p.base, v)) { - return true +// Tool logs the result of a go command +func (p *Project) tool(path string, tool tool) error { + if tool.status { + if strings.HasSuffix(path, ".go") || strings.HasSuffix(path, "") { + if strings.HasSuffix(path, ".go") { + tool.options = append(tool.options, path) + path = p.base + } + if stream, err := p.goTool(path, tool.cmd, tool.options...); err != nil { + msg = fmt.Sprintln(p.pname(p.Name, 2), ":", style.Red.Bold(tool.name), style.Red.Regular("there are some errors in"), ":", style.Magenta.Bold(path)) + out = BufferOut{Time: time.Now(), Text: "there are some errors in", Path: path, Type: tool.name, Stream: stream} + p.stamp("error", out, msg, stream) + return err + } } } - return false -} - -// Routines launches the toolchain run, build, install -func (p *Project) routines(wr *sync.WaitGroup, channel chan bool, watcher watcher, file string) { - p.cmd("before", false) - if len(file) > 0 { - path := filepath.Dir(file) - p.tool(file, p.tools.Fmt) - p.tool(path, p.tools.Vet) - p.tool(path, p.tools.Test) - p.tool(path, p.tools.Generate) - } else { - p.Fatal(p.watch(watcher)) - } - install := p.install() - build := p.build() - wr.Add(1) - if install == nil && build == nil { - go p.run(channel, wr) - } - wr.Wait() - if len(file) > 0 { - p.cmd("after", false) - } + return nil } // Defines the colors scheme for the project name @@ -364,6 +322,25 @@ func (p *Project) pname(name string, color int) string { return name } +// Install calls an implementation of "go run" +func (p *Project) run(channel chan bool, wr *sync.WaitGroup) { + if p.Cmds.Run { + start := time.Now() + runner := make(chan bool, 1) + log.Println(p.pname(p.Name, 1), ":", "Running..") + go p.goRun(channel, runner, wr) + for { + select { + case <-runner: + msg = fmt.Sprintln(p.pname(p.Name, 5), ":", style.Green.Regular("Started")+" after", style.Magenta.Regular(big.NewFloat(float64(time.Since(start).Seconds())).Text('f', 3), " s")) + out = BufferOut{Time: time.Now(), Text: "Started after " + big.NewFloat(float64(time.Since(start).Seconds())).Text('f', 3) + " s"} + p.stamp("log", out, msg, "") + return + } + } + } +} + // Print on files, cli, ws func (p *Project) stamp(t string, o BufferOut, msg string, stream string) { switch t { @@ -414,3 +391,42 @@ func (p *Project) stamp(t string, o BufferOut, msg string, stream string) { p.parent.Sync <- "sync" }() } + +// Routines launches the toolchain run, build, install +func (p *Project) routines(wr *sync.WaitGroup, channel chan bool, watcher watcher, file string) { + p.cmd("before", false) + if len(file) > 0 { + path := filepath.Dir(file) + p.tool(file, p.tools.Fmt) + p.tool(path, p.tools.Vet) + p.tool(path, p.tools.Test) + p.tool(path, p.tools.Generate) + } else { + p.Fatal(p.walk(watcher)) + } + install := p.install() + build := p.build() + wr.Add(1) + if install == nil && build == nil { + go p.run(channel, wr) + } + wr.Wait() + if len(file) > 0 { + p.cmd("after", false) + } +} + +// Add a path to paths list +func (w *pollWatcher) Add(path string) error { + if w.paths == nil { + w.paths = map[string]bool{} + } + w.paths[path] = true + return nil +} + +// Check if is watching +func (w *pollWatcher) isWatching(path string) bool { + a, b := w.paths[path] + return a && b +}