tools workflow improved
This commit is contained in:
parent
4d58cf52bf
commit
ef7e2eb716
@ -3,6 +3,7 @@ package realize
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"go/build"
|
"go/build"
|
||||||
|
"gopkg.in/fsnotify.v1"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
@ -22,14 +23,30 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
|
LogWriter struct{}
|
||||||
|
|
||||||
Realize struct {
|
Realize struct {
|
||||||
Settings Settings `yaml:"settings" json:"settings"`
|
Settings Settings `yaml:"settings" json:"settings"`
|
||||||
Server Server `yaml:"server" json:"server"`
|
Server Server `yaml:"server" json:"server"`
|
||||||
Schema `yaml:",inline"`
|
Schema `yaml:",inline"`
|
||||||
sync chan string
|
sync chan string
|
||||||
exit chan os.Signal
|
exit chan os.Signal
|
||||||
|
Err Func `yaml:"-"`
|
||||||
|
After Func `yaml:"-"`
|
||||||
|
Before Func `yaml:"-"`
|
||||||
|
Change Func `yaml:"-"`
|
||||||
|
Reload Func `yaml:"-"`
|
||||||
}
|
}
|
||||||
LogWriter struct{}
|
|
||||||
|
Context struct {
|
||||||
|
Path string
|
||||||
|
Project Project
|
||||||
|
Stop <-chan bool
|
||||||
|
Watcher FileWatcher
|
||||||
|
Event fsnotify.Event
|
||||||
|
}
|
||||||
|
|
||||||
|
Func func(Context)
|
||||||
)
|
)
|
||||||
|
|
||||||
// init check
|
// init check
|
||||||
@ -45,6 +62,10 @@ func init() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Realize) New() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// Stop realize workflow
|
// Stop realize workflow
|
||||||
func (r *Realize) Stop() {
|
func (r *Realize) Stop() {
|
||||||
close(r.exit)
|
close(r.exit)
|
||||||
@ -56,7 +77,6 @@ func (r *Realize) Start() {
|
|||||||
signal.Notify(r.exit, os.Interrupt, syscall.SIGTERM)
|
signal.Notify(r.exit, os.Interrupt, syscall.SIGTERM)
|
||||||
for k := range r.Schema.Projects {
|
for k := range r.Schema.Projects {
|
||||||
r.Schema.Projects[k].parent = r
|
r.Schema.Projects[k].parent = r
|
||||||
r.Schema.Projects[k].Setup()
|
|
||||||
go r.Schema.Projects[k].Watch(r.exit)
|
go r.Schema.Projects[k].Watch(r.exit)
|
||||||
}
|
}
|
||||||
for {
|
for {
|
||||||
|
@ -66,8 +66,8 @@ func PollingWatcher(interval time.Duration) FileWatcher {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Watcher tries to use an fs-event watcher, and falls back to the poller if there is an error
|
// NewWatcher tries to use an fs-event watcher, and falls back to the poller if there is an error
|
||||||
func Watcher(force bool, interval time.Duration) (FileWatcher, error) {
|
func NewFileWatcher(force bool, interval time.Duration) (FileWatcher, error) {
|
||||||
if !force {
|
if !force {
|
||||||
if w, err := EventWatcher(); err == nil {
|
if w, err := EventWatcher(); err == nil {
|
||||||
return w, nil
|
return w, nil
|
||||||
|
@ -2,6 +2,7 @@ package realize
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/fsnotify/fsnotify"
|
"github.com/fsnotify/fsnotify"
|
||||||
@ -14,7 +15,6 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"bytes"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -29,6 +29,7 @@ type Watch struct {
|
|||||||
Exts []string `yaml:"extensions" json:"extensions"`
|
Exts []string `yaml:"extensions" json:"extensions"`
|
||||||
Ignore []string `yaml:"ignored_paths,omitempty" json:"ignored_paths,omitempty"`
|
Ignore []string `yaml:"ignored_paths,omitempty" json:"ignored_paths,omitempty"`
|
||||||
Scripts []Command `yaml:"scripts,omitempty" json:"scripts,omitempty"`
|
Scripts []Command `yaml:"scripts,omitempty" json:"scripts,omitempty"`
|
||||||
|
Hidden bool `yaml:"skip_hidden,omitempty" json:"skip_hidden,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Command fields
|
// Command fields
|
||||||
@ -45,6 +46,7 @@ type Project struct {
|
|||||||
parent *Realize
|
parent *Realize
|
||||||
watcher FileWatcher
|
watcher FileWatcher
|
||||||
init bool
|
init bool
|
||||||
|
stop chan bool
|
||||||
files int64
|
files int64
|
||||||
folders int64
|
folders int64
|
||||||
lastFile string
|
lastFile string
|
||||||
@ -84,155 +86,61 @@ type BufferOut struct {
|
|||||||
Errors []string `json:"errors"`
|
Errors []string `json:"errors"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Project interface
|
// After stop watcher
|
||||||
type ProjectI interface {
|
func (p *Project) After() {
|
||||||
Setup()
|
p.cmd(nil, "after", true)
|
||||||
Watch(chan os.Signal)
|
|
||||||
Run(string, chan Response, <-chan bool)
|
|
||||||
Restart(FileWatcher, string, <-chan bool)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup a project
|
// Before start watcher
|
||||||
func (p *Project) Setup() {
|
func (p *Project) Before() {
|
||||||
// get base path
|
// setup go tools
|
||||||
p.Name = filepath.Base(p.Path)
|
p.Tools.Setup()
|
||||||
// set env const
|
// set env const
|
||||||
for key, item := range p.Environment {
|
for key, item := range p.Environment {
|
||||||
if err := os.Setenv(key, item); err != nil {
|
if err := os.Setenv(key, item); err != nil {
|
||||||
p.Buffer.StdErr = append(p.Buffer.StdErr, BufferOut{Time: time.Now(), Text: err.Error(), Type: "Env error", Stream: ""})
|
p.Buffer.StdErr = append(p.Buffer.StdErr, BufferOut{Time: time.Now(), Text: err.Error(), Type: "Env error", Stream: ""})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// setup go tools
|
|
||||||
p.Tools.Setup()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exec an additional command from a defined path if specified
|
|
||||||
func (c *Command) Exec(base string, stop <-chan bool) (response Response) {
|
|
||||||
var stdout bytes.Buffer
|
|
||||||
var stderr bytes.Buffer
|
|
||||||
done := make(chan error)
|
|
||||||
args := strings.Split(strings.Replace(strings.Replace(c.Cmd, "'", "", -1), "\"", "", -1), " ")
|
|
||||||
ex := exec.Command(args[0], args[1:]...)
|
|
||||||
ex.Dir = base
|
|
||||||
// make cmd path
|
|
||||||
if c.Path != "" {
|
|
||||||
if strings.Contains(c.Path, base) {
|
|
||||||
ex.Dir = c.Path
|
|
||||||
} else {
|
|
||||||
ex.Dir = filepath.Join(base, c.Path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ex.Stdout = &stdout
|
|
||||||
ex.Stderr = &stderr
|
|
||||||
// Start command
|
|
||||||
ex.Start()
|
|
||||||
go func() { done <- ex.Wait() }()
|
|
||||||
// Wait a result
|
|
||||||
select {
|
|
||||||
case <-stop:
|
|
||||||
// Stop running command
|
|
||||||
ex.Process.Kill()
|
|
||||||
case err := <-done:
|
|
||||||
// Command completed
|
|
||||||
response.Name = c.Cmd
|
|
||||||
response.Out = stdout.String()
|
|
||||||
if err != nil {
|
|
||||||
response.Err = errors.New(stderr.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Watch a project
|
|
||||||
func (p *Project) Watch(exit chan os.Signal) {
|
|
||||||
var err error
|
|
||||||
stop := make(chan bool)
|
|
||||||
// init a new watcher
|
|
||||||
p.watcher, err = Watcher(p.parent.Settings.Legacy.Force, p.parent.Settings.Legacy.Interval)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
// global commands before
|
// global commands before
|
||||||
p.cmd(stop, "before", true)
|
p.cmd(p.stop, "before", true)
|
||||||
// indexing files and dirs
|
// indexing files and dirs
|
||||||
for _, dir := range p.Watcher.Paths {
|
for _, dir := range p.Watcher.Paths {
|
||||||
base, _ := filepath.Abs(p.Path)
|
base, _ := filepath.Abs(p.Path)
|
||||||
base = filepath.Join(base, dir)
|
base = filepath.Join(base, dir)
|
||||||
if _, err := os.Stat(base); err == nil {
|
if _, err := os.Stat(base); err == nil {
|
||||||
if err := filepath.Walk(base, p.walk); err == nil {
|
if err := filepath.Walk(base, p.walk); err != nil {
|
||||||
p.tools(stop, base)
|
p.Err(err)
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
p.err(err)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// start message
|
// start message
|
||||||
msg = fmt.Sprintln(p.pname(p.Name, 1), ":", Blue.Bold("Watching"), Magenta.Bold(p.files), "file/s", Magenta.Bold(p.folders), "folder/s")
|
msg = fmt.Sprintln(p.pname(p.Name, 1), ":", Blue.Bold("Watching"), Magenta.Bold(p.files), "file/s", Magenta.Bold(p.folders), "folder/s")
|
||||||
out = BufferOut{Time: time.Now(), Text: "Watching " + strconv.FormatInt(p.files, 10) + " files/s " + strconv.FormatInt(p.folders, 10) + " folder/s"}
|
out = BufferOut{Time: time.Now(), Text: "Watching " + strconv.FormatInt(p.files, 10) + " files/s " + strconv.FormatInt(p.folders, 10) + " folder/s"}
|
||||||
p.stamp("log", out, msg, "")
|
p.stamp("log", out, msg, "")
|
||||||
// start watcher
|
}
|
||||||
go p.Restart(p.watcher, "", stop)
|
|
||||||
L:
|
// Error occurred
|
||||||
for {
|
func (p *Project) Err(err error) {
|
||||||
select {
|
msg = fmt.Sprintln(p.pname(p.Name, 2), ":", Red.Regular(err.Error()))
|
||||||
case event := <-p.watcher.Events():
|
out = BufferOut{Time: time.Now(), Text: err.Error()}
|
||||||
if time.Now().Truncate(time.Second).After(p.lastTime) || event.Name != p.lastFile {
|
p.stamp("error", out, msg, "")
|
||||||
// event time
|
}
|
||||||
eventTime := time.Now()
|
|
||||||
// file extension
|
// Change event message
|
||||||
ext := ext(event.Name)
|
func (p *Project) Change(event fsnotify.Event) {
|
||||||
if ext == "" {
|
// file extension
|
||||||
ext = "DIR"
|
ext := ext(event.Name)
|
||||||
}
|
if ext == "" {
|
||||||
// change message
|
ext = "DIR"
|
||||||
msg = fmt.Sprintln(p.pname(p.Name, 4), ":", Magenta.Bold(strings.ToUpper(ext)), "changed", Magenta.Bold(event.Name))
|
|
||||||
out = BufferOut{Time: time.Now(), Text: ext + " changed " + event.Name}
|
|
||||||
// switch event type
|
|
||||||
switch event.Op {
|
|
||||||
case fsnotify.Chmod:
|
|
||||||
case fsnotify.Remove:
|
|
||||||
p.watcher.Remove(event.Name)
|
|
||||||
if !strings.Contains(ext, "_") && !strings.Contains(ext, ".") && array(ext, p.Watcher.Exts) {
|
|
||||||
close(stop)
|
|
||||||
stop = make(chan bool)
|
|
||||||
p.stamp("log", out, msg, "")
|
|
||||||
go p.Restart(p.watcher, "", stop)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
file, err := os.Stat(event.Name)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if file.IsDir() {
|
|
||||||
filepath.Walk(event.Name, p.walk)
|
|
||||||
} else if file.Size() > 0 {
|
|
||||||
if !strings.Contains(ext, "_") && !strings.Contains(ext, ".") && array(ext, p.Watcher.Exts) {
|
|
||||||
// change watched
|
|
||||||
// check if a file is still writing #119
|
|
||||||
if event.Op != fsnotify.Write || (eventTime.Truncate(time.Millisecond).After(file.ModTime().Truncate(time.Millisecond)) || event.Name != p.lastFile) {
|
|
||||||
close(stop)
|
|
||||||
stop = make(chan bool)
|
|
||||||
// stop and start again
|
|
||||||
p.stamp("log", out, msg, "")
|
|
||||||
go p.Restart(p.watcher, event.Name, stop)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
p.lastTime = time.Now().Truncate(time.Second)
|
|
||||||
p.lastFile = event.Name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case err := <-p.watcher.Errors():
|
|
||||||
p.err(err)
|
|
||||||
case <-exit:
|
|
||||||
p.cmd(nil, "after", true)
|
|
||||||
break L
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
// change message
|
||||||
|
msg = fmt.Sprintln(p.pname(p.Name, 4), ":", Magenta.Bold(strings.ToUpper(ext)), "changed", Magenta.Bold(event.Name))
|
||||||
|
out = BufferOut{Time: time.Now(), Text: ext + " changed " + event.Name}
|
||||||
|
p.stamp("log", out, msg, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reload launches the toolchain run, build, install
|
// Reload launches the toolchain run, build, install
|
||||||
func (p *Project) Restart(watcher FileWatcher, path string, stop <-chan bool) {
|
func (p *Project) Reload(watcher FileWatcher, path string, stop <-chan bool) {
|
||||||
var done bool
|
var done bool
|
||||||
var install, build Response
|
var install, build Response
|
||||||
go func() {
|
go func() {
|
||||||
@ -253,7 +161,22 @@ func (p *Project) Restart(watcher FileWatcher, path string, stop <-chan bool) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Go supported tools
|
// Go supported tools
|
||||||
p.tools(stop, path)
|
if len(path) > 0{
|
||||||
|
fi, err := os.Stat(path)
|
||||||
|
if err != nil{
|
||||||
|
p.Err(err)
|
||||||
|
}
|
||||||
|
p.tools(stop, path, fi)
|
||||||
|
// path dir
|
||||||
|
if !fi.IsDir() {
|
||||||
|
path := filepath.Dir(path)
|
||||||
|
fi, err = os.Stat(path)
|
||||||
|
if err != nil {
|
||||||
|
p.Err(err)
|
||||||
|
}
|
||||||
|
p.tools(stop, path, fi)
|
||||||
|
}
|
||||||
|
}
|
||||||
// Prevent fake events on polling startup
|
// Prevent fake events on polling startup
|
||||||
p.init = true
|
p.init = true
|
||||||
// prevent errors using realize without config with only run flag
|
// prevent errors using realize without config with only run flag
|
||||||
@ -310,7 +233,7 @@ func (p *Project) Restart(watcher FileWatcher, path string, stop <-chan bool) {
|
|||||||
go func() {
|
go func() {
|
||||||
log.Println(p.pname(p.Name, 1), ":", "Running..")
|
log.Println(p.pname(p.Name, 1), ":", "Running..")
|
||||||
start = time.Now()
|
start = time.Now()
|
||||||
err := p.Run(p.Path, result, stop)
|
err := p.run(p.Path, result, stop)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
msg := fmt.Sprintln(p.pname(p.Name, 2), ":", Red.Regular(err))
|
msg := fmt.Sprintln(p.pname(p.Name, 2), ":", Red.Regular(err))
|
||||||
out := BufferOut{Time: time.Now(), Text: err.Error(), Type: "Go Run"}
|
out := BufferOut{Time: time.Now(), Text: err.Error(), Type: "Go Run"}
|
||||||
@ -324,8 +247,273 @@ func (p *Project) Restart(watcher FileWatcher, path string, stop <-chan bool) {
|
|||||||
p.cmd(stop, "after", false)
|
p.cmd(stop, "after", false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Watch a project
|
||||||
|
func (p *Project) Watch(exit chan os.Signal) {
|
||||||
|
var err error
|
||||||
|
// change channel
|
||||||
|
p.stop = make(chan bool)
|
||||||
|
// init a new watcher
|
||||||
|
p.watcher, err = NewFileWatcher(p.parent.Settings.Legacy.Force, p.parent.Settings.Legacy.Interval)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
close(p.stop)
|
||||||
|
p.watcher.Close()
|
||||||
|
}()
|
||||||
|
// before start checks
|
||||||
|
p.Before()
|
||||||
|
// start watcher
|
||||||
|
go p.Reload(p.watcher, "", p.stop)
|
||||||
|
L:
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case event := <-p.watcher.Events():
|
||||||
|
if time.Now().Truncate(time.Second).After(p.lastTime) || event.Name != p.lastFile {
|
||||||
|
// event time
|
||||||
|
eventTime := time.Now()
|
||||||
|
// switch event type
|
||||||
|
switch event.Op {
|
||||||
|
case fsnotify.Chmod:
|
||||||
|
case fsnotify.Remove:
|
||||||
|
p.watcher.Remove(event.Name)
|
||||||
|
if p.Validate(event.Name, false) {
|
||||||
|
// stop and restart
|
||||||
|
close(p.stop)
|
||||||
|
p.stop = make(chan bool)
|
||||||
|
p.Change(event)
|
||||||
|
go p.Reload(p.watcher, "", p.stop)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if p.Validate(event.Name, true) {
|
||||||
|
fi, err := os.Stat(event.Name)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if fi.IsDir() {
|
||||||
|
filepath.Walk(event.Name, p.walk)
|
||||||
|
} else {
|
||||||
|
if event.Op != fsnotify.Write || (eventTime.Truncate(time.Millisecond).After(fi.ModTime().Truncate(time.Millisecond)) || event.Name != p.lastFile) {
|
||||||
|
// stop and restart
|
||||||
|
close(p.stop)
|
||||||
|
p.stop = make(chan bool)
|
||||||
|
p.Change(event)
|
||||||
|
go p.Reload(p.watcher, event.Name, p.stop)
|
||||||
|
}
|
||||||
|
p.lastTime = time.Now().Truncate(time.Second)
|
||||||
|
p.lastFile = event.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case err := <-p.watcher.Errors():
|
||||||
|
p.Err(err)
|
||||||
|
case <-exit:
|
||||||
|
p.After()
|
||||||
|
break L
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate a file path
|
||||||
|
func (p *Project) Validate(path string, fiche bool) bool {
|
||||||
|
if len(path) <= 0{
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// check if skip hidden
|
||||||
|
if p.Watcher.Hidden && isHidden(path) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// check for a valid ext or path
|
||||||
|
if e := ext(path); e != "" {
|
||||||
|
// supported exts
|
||||||
|
if !array(e, p.Watcher.Exts) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
separator := string(os.PathSeparator)
|
||||||
|
// supported paths
|
||||||
|
for _, v := range p.Watcher.Ignore {
|
||||||
|
s := append([]string{p.Path}, strings.Split(v, separator)...)
|
||||||
|
abs, _ := filepath.Abs(filepath.Join(s...))
|
||||||
|
if path == abs || strings.HasPrefix(path, abs+separator) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// file check
|
||||||
|
if fiche {
|
||||||
|
fi, err := os.Stat(path)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if fi.IsDir() || (!fi.IsDir() && fi.Size() > 0) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Defines the colors scheme for the project name
|
||||||
|
func (p *Project) pname(name string, color int) string {
|
||||||
|
switch color {
|
||||||
|
case 1:
|
||||||
|
name = Yellow.Regular("[") + strings.ToUpper(name) + Yellow.Regular("]")
|
||||||
|
break
|
||||||
|
case 2:
|
||||||
|
name = Yellow.Regular("[") + Red.Bold(strings.ToUpper(name)) + Yellow.Regular("]")
|
||||||
|
break
|
||||||
|
case 3:
|
||||||
|
name = Yellow.Regular("[") + Blue.Bold(strings.ToUpper(name)) + Yellow.Regular("]")
|
||||||
|
break
|
||||||
|
case 4:
|
||||||
|
name = Yellow.Regular("[") + Magenta.Bold(strings.ToUpper(name)) + Yellow.Regular("]")
|
||||||
|
break
|
||||||
|
case 5:
|
||||||
|
name = Yellow.Regular("[") + Green.Bold(strings.ToUpper(name)) + Yellow.Regular("]")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tool logs the result of a go command
|
||||||
|
func (p *Project) tools(stop <-chan bool, path string, fi os.FileInfo) {
|
||||||
|
done := make(chan bool)
|
||||||
|
result := make(chan Response)
|
||||||
|
v := reflect.ValueOf(p.Tools)
|
||||||
|
go func() {
|
||||||
|
for i := 0; i < v.NumField()-1; i++ {
|
||||||
|
tool := v.Field(i).Interface().(Tool)
|
||||||
|
if tool.Status && tool.isTool {
|
||||||
|
if fi.IsDir() {
|
||||||
|
if tool.dir {
|
||||||
|
result <- tool.Exec(path, stop)
|
||||||
|
}
|
||||||
|
} else if !tool.dir{
|
||||||
|
result <- tool.Exec(path, stop)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
close(done)
|
||||||
|
}()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-done:
|
||||||
|
return
|
||||||
|
case <-stop:
|
||||||
|
return
|
||||||
|
case r := <-result:
|
||||||
|
if r.Err != nil {
|
||||||
|
msg = fmt.Sprintln(p.pname(p.Name, 2), ":", Red.Bold(r.Name), Red.Regular("there are some errors in"), ":", Magenta.Bold(path))
|
||||||
|
buff := BufferOut{Time: time.Now(), Text: "there are some errors in", Path: path, Type: r.Name, Stream: r.Err.Error()}
|
||||||
|
p.stamp("error", buff, msg, r.Err.Error())
|
||||||
|
} else if r.Out != "" {
|
||||||
|
msg = fmt.Sprintln(p.pname(p.Name, 3), ":", Red.Bold(r.Name), Red.Regular("outputs"), ":", Blue.Bold(path))
|
||||||
|
buff := BufferOut{Time: time.Now(), Text: "outputs", Path: path, Type: r.Name, Stream: r.Out}
|
||||||
|
p.stamp("out", buff, msg, r.Out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cmd after/before
|
||||||
|
func (p *Project) cmd(stop <-chan bool, flag string, global bool) {
|
||||||
|
done := make(chan bool)
|
||||||
|
result := make(chan Response)
|
||||||
|
// commands sequence
|
||||||
|
go func() {
|
||||||
|
for _, cmd := range p.Watcher.Scripts {
|
||||||
|
if strings.ToLower(cmd.Type) == flag && cmd.Global == global {
|
||||||
|
result <- cmd.exec(p.Path, stop)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
close(done)
|
||||||
|
}()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-stop:
|
||||||
|
return
|
||||||
|
case <-done:
|
||||||
|
return
|
||||||
|
case r := <-result:
|
||||||
|
msg = fmt.Sprintln(p.pname(p.Name, 5), ":", Green.Bold("Command"), Green.Bold("\"")+r.Name+Green.Bold("\""))
|
||||||
|
out = BufferOut{Time: time.Now(), Text: r.Name, Type: flag}
|
||||||
|
if r.Err != nil {
|
||||||
|
p.stamp("error", out, msg, "")
|
||||||
|
out = BufferOut{Time: time.Now(), Text: r.Err.Error(), Type: flag}
|
||||||
|
p.stamp("error", out, "", fmt.Sprintln(Red.Regular(r.Err.Error())))
|
||||||
|
}
|
||||||
|
if r.Out != "" {
|
||||||
|
out = BufferOut{Time: time.Now(), Text: r.Out, Type: flag}
|
||||||
|
p.stamp("log", out, "", fmt.Sprintln(r.Out))
|
||||||
|
} else {
|
||||||
|
p.stamp("log", out, msg, "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Watch the files tree of a project
|
||||||
|
func (p *Project) walk(path string, info os.FileInfo, err error) error {
|
||||||
|
if p.Validate(path, true) {
|
||||||
|
result := p.watcher.Walk(path, p.init)
|
||||||
|
if result != "" {
|
||||||
|
p.tools(p.stop, path,info)
|
||||||
|
if info.IsDir() {
|
||||||
|
// tools dir
|
||||||
|
p.folders++
|
||||||
|
} else {
|
||||||
|
// tools files
|
||||||
|
p.files++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print on files, cli, ws
|
||||||
|
func (p *Project) stamp(t string, o BufferOut, msg string, stream string) {
|
||||||
|
ctime := time.Now()
|
||||||
|
content := []string{ctime.Format("2006-01-02 15:04:05"), strings.ToUpper(p.Name), ":", o.Text, "\r\n", stream}
|
||||||
|
switch t {
|
||||||
|
case "out":
|
||||||
|
p.Buffer.StdOut = append(p.Buffer.StdOut, o)
|
||||||
|
if p.parent.Settings.Files.Outputs.Status {
|
||||||
|
f := p.parent.Settings.Create(p.Path, p.parent.Settings.Files.Outputs.Name)
|
||||||
|
if _, err := f.WriteString(strings.Join(content, " ")); err != nil {
|
||||||
|
p.parent.Settings.Fatal(err, "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "log":
|
||||||
|
p.Buffer.StdLog = append(p.Buffer.StdLog, o)
|
||||||
|
if p.parent.Settings.Files.Logs.Status {
|
||||||
|
f := p.parent.Settings.Create(p.Path, p.parent.Settings.Files.Logs.Name)
|
||||||
|
if _, err := f.WriteString(strings.Join(content, " ")); err != nil {
|
||||||
|
p.parent.Settings.Fatal(err, "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "error":
|
||||||
|
p.Buffer.StdErr = append(p.Buffer.StdErr, o)
|
||||||
|
if p.parent.Settings.Files.Errors.Status {
|
||||||
|
f := p.parent.Settings.Create(p.Path, p.parent.Settings.Files.Errors.Name)
|
||||||
|
if _, err := f.WriteString(strings.Join(content, " ")); err != nil {
|
||||||
|
p.parent.Settings.Fatal(err, "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if msg != "" {
|
||||||
|
log.Print(msg)
|
||||||
|
}
|
||||||
|
if stream != "" {
|
||||||
|
fmt.Fprint(Output, stream)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Run a project
|
// Run a project
|
||||||
func (p *Project) Run(path string, stream chan Response, stop <-chan bool) (err error) {
|
func (p *Project) run(path string, stream chan Response, stop <-chan bool) (err error) {
|
||||||
var args []string
|
var args []string
|
||||||
var build *exec.Cmd
|
var build *exec.Cmd
|
||||||
var r Response
|
var r Response
|
||||||
@ -418,167 +606,6 @@ func (p *Project) Run(path string, stream chan Response, stop <-chan bool) (err
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Error occurred
|
|
||||||
func (p *Project) err(err error) {
|
|
||||||
msg = fmt.Sprintln(p.pname(p.Name, 2), ":", Red.Regular(err.Error()))
|
|
||||||
out = BufferOut{Time: time.Now(), Text: err.Error()}
|
|
||||||
p.stamp("error", out, msg, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Defines the colors scheme for the project name
|
|
||||||
func (p *Project) pname(name string, color int) string {
|
|
||||||
switch color {
|
|
||||||
case 1:
|
|
||||||
name = Yellow.Regular("[") + strings.ToUpper(name) + Yellow.Regular("]")
|
|
||||||
break
|
|
||||||
case 2:
|
|
||||||
name = Yellow.Regular("[") + Red.Bold(strings.ToUpper(name)) + Yellow.Regular("]")
|
|
||||||
break
|
|
||||||
case 3:
|
|
||||||
name = Yellow.Regular("[") + Blue.Bold(strings.ToUpper(name)) + Yellow.Regular("]")
|
|
||||||
break
|
|
||||||
case 4:
|
|
||||||
name = Yellow.Regular("[") + Magenta.Bold(strings.ToUpper(name)) + Yellow.Regular("]")
|
|
||||||
break
|
|
||||||
case 5:
|
|
||||||
name = Yellow.Regular("[") + Green.Bold(strings.ToUpper(name)) + Yellow.Regular("]")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
return name
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tool logs the result of a go command
|
|
||||||
func (p *Project) tools(stop <-chan bool, path string) {
|
|
||||||
if len(path) > 0 {
|
|
||||||
done := make(chan bool)
|
|
||||||
result := make(chan Response)
|
|
||||||
v := reflect.ValueOf(p.Tools)
|
|
||||||
go func() {
|
|
||||||
for i := 0; i < v.NumField()-1; i++ {
|
|
||||||
tool := v.Field(i).Interface().(Tool)
|
|
||||||
if tool.Status && tool.isTool {
|
|
||||||
result <- tool.Exec(path, stop)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
close(done)
|
|
||||||
}()
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-done:
|
|
||||||
return
|
|
||||||
case <-stop:
|
|
||||||
return
|
|
||||||
case r := <-result:
|
|
||||||
if r.Err != nil {
|
|
||||||
msg = fmt.Sprintln(p.pname(p.Name, 2), ":", Red.Bold(r.Name), Red.Regular("there are some errors in"), ":", Magenta.Bold(path))
|
|
||||||
buff := BufferOut{Time: time.Now(), Text: "there are some errors in", Path: path, Type: r.Name, Stream: r.Err.Error()}
|
|
||||||
p.stamp("error", buff, msg, r.Err.Error())
|
|
||||||
} else if r.Out != "" {
|
|
||||||
msg = fmt.Sprintln(p.pname(p.Name, 3), ":", Red.Bold(r.Name), Red.Regular("outputs"), ":", Blue.Bold(path))
|
|
||||||
buff := BufferOut{Time: time.Now(), Text: "outputs", Path: path, Type: r.Name, Stream: r.Out}
|
|
||||||
p.stamp("out", buff, msg, r.Out)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cmd after/before
|
|
||||||
func (p *Project) cmd(stop <-chan bool, flag string, global bool) {
|
|
||||||
done := make(chan bool)
|
|
||||||
result := make(chan Response)
|
|
||||||
// commands sequence
|
|
||||||
go func() {
|
|
||||||
for _, cmd := range p.Watcher.Scripts {
|
|
||||||
if strings.ToLower(cmd.Type) == flag && cmd.Global == global {
|
|
||||||
result <- cmd.Exec(p.Path, stop)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
close(done)
|
|
||||||
}()
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-stop:
|
|
||||||
return
|
|
||||||
case <-done:
|
|
||||||
return
|
|
||||||
case r := <-result:
|
|
||||||
msg = fmt.Sprintln(p.pname(p.Name, 5), ":", Green.Bold("Command"), Green.Bold("\"")+r.Name+Green.Bold("\""))
|
|
||||||
out = BufferOut{Time: time.Now(), Text: r.Name, Type: flag}
|
|
||||||
if r.Err != nil {
|
|
||||||
p.stamp("error", out, msg, "")
|
|
||||||
out = BufferOut{Time: time.Now(), Text: r.Err.Error(), Type: flag}
|
|
||||||
p.stamp("error", out, "", fmt.Sprintln(Red.Regular(r.Err.Error())))
|
|
||||||
}
|
|
||||||
if r.Out != "" {
|
|
||||||
out = BufferOut{Time: time.Now(), Text: r.Out, Type: flag}
|
|
||||||
p.stamp("log", out, "", fmt.Sprintln(r.Out))
|
|
||||||
} else {
|
|
||||||
p.stamp("log", out, msg, "")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Watch the files tree of a project
|
|
||||||
func (p *Project) walk(path string, info os.FileInfo, err error) error {
|
|
||||||
for _, v := range p.Watcher.Ignore {
|
|
||||||
s := append([]string{p.Path}, strings.Split(v, string(os.PathSeparator))...)
|
|
||||||
if strings.Contains(path, filepath.Join(s...)) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !strings.HasPrefix(path, ".") && (info.IsDir() || array(ext(path), p.Watcher.Exts)) {
|
|
||||||
result := p.watcher.Walk(path, p.init)
|
|
||||||
if result != "" {
|
|
||||||
if info.IsDir() {
|
|
||||||
p.folders++
|
|
||||||
} else {
|
|
||||||
p.files++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print on files, cli, ws
|
|
||||||
func (p *Project) stamp(t string, o BufferOut, msg string, stream string) {
|
|
||||||
ctime := time.Now()
|
|
||||||
content := []string{ctime.Format("2006-01-02 15:04:05"), strings.ToUpper(p.Name), ":", o.Text, "\r\n", stream}
|
|
||||||
switch t {
|
|
||||||
case "out":
|
|
||||||
p.Buffer.StdOut = append(p.Buffer.StdOut, o)
|
|
||||||
if p.parent.Settings.Files.Outputs.Status {
|
|
||||||
f := p.parent.Settings.Create(p.Path, p.parent.Settings.Files.Outputs.Name)
|
|
||||||
if _, err := f.WriteString(strings.Join(content, " ")); err != nil {
|
|
||||||
p.parent.Settings.Fatal(err, "")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case "log":
|
|
||||||
p.Buffer.StdLog = append(p.Buffer.StdLog, o)
|
|
||||||
if p.parent.Settings.Files.Logs.Status {
|
|
||||||
f := p.parent.Settings.Create(p.Path, p.parent.Settings.Files.Logs.Name)
|
|
||||||
if _, err := f.WriteString(strings.Join(content, " ")); err != nil {
|
|
||||||
p.parent.Settings.Fatal(err, "")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case "error":
|
|
||||||
p.Buffer.StdErr = append(p.Buffer.StdErr, o)
|
|
||||||
if p.parent.Settings.Files.Errors.Status {
|
|
||||||
f := p.parent.Settings.Create(p.Path, p.parent.Settings.Files.Errors.Name)
|
|
||||||
if _, err := f.WriteString(strings.Join(content, " ")); err != nil {
|
|
||||||
p.parent.Settings.Fatal(err, "")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if msg != "" {
|
|
||||||
log.Print(msg)
|
|
||||||
}
|
|
||||||
if stream != "" {
|
|
||||||
fmt.Fprint(Output, stream)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print with time after
|
// Print with time after
|
||||||
func (r *Response) print(start time.Time, p *Project) {
|
func (r *Response) print(start time.Time, p *Project) {
|
||||||
if r.Err != nil {
|
if r.Err != nil {
|
||||||
@ -591,3 +618,40 @@ func (r *Response) print(start time.Time, p *Project) {
|
|||||||
p.stamp("log", out, msg, r.Out)
|
p.stamp("log", out, msg, r.Out)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Exec an additional command from a defined path if specified
|
||||||
|
func (c *Command) exec(base string, stop <-chan bool) (response Response) {
|
||||||
|
var stdout bytes.Buffer
|
||||||
|
var stderr bytes.Buffer
|
||||||
|
done := make(chan error)
|
||||||
|
args := strings.Split(strings.Replace(strings.Replace(c.Cmd, "'", "", -1), "\"", "", -1), " ")
|
||||||
|
ex := exec.Command(args[0], args[1:]...)
|
||||||
|
ex.Dir = base
|
||||||
|
// make cmd path
|
||||||
|
if c.Path != "" {
|
||||||
|
if strings.Contains(c.Path, base) {
|
||||||
|
ex.Dir = c.Path
|
||||||
|
} else {
|
||||||
|
ex.Dir = filepath.Join(base, c.Path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ex.Stdout = &stdout
|
||||||
|
ex.Stderr = &stderr
|
||||||
|
// Start command
|
||||||
|
ex.Start()
|
||||||
|
go func() { done <- ex.Wait() }()
|
||||||
|
// Wait a result
|
||||||
|
select {
|
||||||
|
case <-stop:
|
||||||
|
// Stop running command
|
||||||
|
ex.Process.Kill()
|
||||||
|
case err := <-done:
|
||||||
|
// Command completed
|
||||||
|
response.Name = c.Cmd
|
||||||
|
response.Out = stdout.String()
|
||||||
|
if err != nil {
|
||||||
|
response.Err = errors.New(stderr.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
@ -2,22 +2,20 @@ package realize
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
"os"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestProject_Setup(t *testing.T) {
|
func TestProject_Watch(t *testing.T) {
|
||||||
input := "Rtest"
|
|
||||||
p := Project{
|
// test force polling watcher
|
||||||
Path: "/test/prova/"+input,
|
|
||||||
Environment: map[string]string{
|
// test indexing
|
||||||
input: input,
|
|
||||||
},
|
// test event
|
||||||
}
|
|
||||||
p.Setup()
|
// test event types
|
||||||
if p.Name != input{
|
|
||||||
t.Error("Unexpected error", p.Name,"instead",input)
|
// test error
|
||||||
}
|
|
||||||
if os.Getenv(input) != input{
|
// test exit
|
||||||
t.Error("Unexpected error", os.Getenv(input),"instead",input)
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -20,11 +20,11 @@ const (
|
|||||||
|
|
||||||
// Server settings
|
// Server settings
|
||||||
type Server struct {
|
type Server struct {
|
||||||
Parent *Realize
|
Parent *Realize `yaml:"-"`
|
||||||
Status bool `yaml:"status" json:"status"`
|
Status bool `yaml:"status" json:"status"`
|
||||||
Open bool `yaml:"open" json:"open"`
|
Open bool `yaml:"open" json:"open"`
|
||||||
Port int `yaml:"port" json:"port"`
|
Port int `yaml:"port" json:"port"`
|
||||||
Host string `yaml:"host" json:"host"`
|
Host string `yaml:"host" json:"host"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Websocket projects
|
// Websocket projects
|
||||||
|
@ -14,6 +14,7 @@ type Tool struct {
|
|||||||
Method string `yaml:"method,omitempty" json:"method,omitempty"`
|
Method string `yaml:"method,omitempty" json:"method,omitempty"`
|
||||||
Dir string `yaml:"dir,omitempty" json:"dir,omitempty"` //wdir of the command
|
Dir string `yaml:"dir,omitempty" json:"dir,omitempty"` //wdir of the command
|
||||||
Status bool `yaml:"status,omitempty" json:"status,omitempty"`
|
Status bool `yaml:"status,omitempty" json:"status,omitempty"`
|
||||||
|
Output bool `yaml:"output,omitempty" json:"output,omitempty"`
|
||||||
dir bool
|
dir bool
|
||||||
isTool bool
|
isTool bool
|
||||||
method []string
|
method []string
|
||||||
@ -133,7 +134,9 @@ func (t *Tool) Exec(path string, stop <-chan bool) (response Response) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
response.Err = errors.New(stderr.String() + out.String())
|
response.Err = errors.New(stderr.String() + out.String())
|
||||||
} else {
|
} else {
|
||||||
response.Out = out.String()
|
if t.Output {
|
||||||
|
response.Out = out.String()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -80,3 +80,14 @@ func Wdir() string {
|
|||||||
}
|
}
|
||||||
return dir
|
return dir
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isHidden check if a file or a path is hidden
|
||||||
|
func isHidden(path string) bool {
|
||||||
|
arr := strings.Split(path[len(Wdir()):], "/")
|
||||||
|
for _, elm := range arr {
|
||||||
|
if strings.HasPrefix(elm, ".") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
14
realize/utils_windows.go
Normal file
14
realize/utils_windows.go
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
package realize
|
||||||
|
|
||||||
|
// isHidden check if a file or a path is hidden
|
||||||
|
func isHidden(path string) bool {
|
||||||
|
p, e := syscall.UTF16PtrFromString(path)
|
||||||
|
if e != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
attrs, e := syscall.GetFileAttributes(p)
|
||||||
|
if e != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return attrs&syscall.FILE_ATTRIBUTE_HIDDEN != 0
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user