Merge branch '2.0'
# Conflicts: # README.md # watcher.go # watcher_test.go
This commit is contained in:
commit
b1a65dbf9e
|
@ -28,7 +28,7 @@ _testmain.go
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.realize
|
.realize
|
||||||
|
|
||||||
server/assets
|
realize/assets/*
|
||||||
docker
|
docker
|
||||||
vendor
|
vendor
|
||||||
bin
|
bin
|
||||||
|
|
34
README.md
34
README.md
|
@ -20,7 +20,7 @@
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="http://i.imgur.com/KpMSLnE.png">
|
<img src="https://gorealize.io/img/realize-ui-2.png">
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
@ -58,7 +58,7 @@ From **project/projects** root execute:
|
||||||
$ realize start
|
$ realize start
|
||||||
|
|
||||||
|
|
||||||
It will create a **realize.yaml** file if doesn't already exist, add the working directory as project and run your workflow.
|
It will create a **.realize.yaml** file if doesn't already exist, add the working directory as project and run your workflow.
|
||||||
|
|
||||||
***start*** command supports the following custom parameters:
|
***start*** command supports the following custom parameters:
|
||||||
|
|
||||||
|
@ -72,6 +72,7 @@ It will create a **realize.yaml** file if doesn't already exist, add the working
|
||||||
--build -> Enable go build
|
--build -> Enable go build
|
||||||
--run -> Enable go run
|
--run -> Enable go run
|
||||||
--server -> Enable the web server
|
--server -> Enable the web server
|
||||||
|
--open -> Open web ui in default browser
|
||||||
--no-config -> Ignore an existing config / skip the creation of a new one
|
--no-config -> Ignore an existing config / skip the creation of a new one
|
||||||
|
|
||||||
Some examples:
|
Some examples:
|
||||||
|
@ -118,6 +119,8 @@ Remove a project by its name
|
||||||
|
|
||||||
## Config sample
|
## Config sample
|
||||||
|
|
||||||
|
*** there is no more a .realize dir, but only a .realize.yaml file ***
|
||||||
|
|
||||||
For more examples check: [Realize Examples](https://github.com/tockins/realize-examples)
|
For more examples check: [Realize Examples](https://github.com/tockins/realize-examples)
|
||||||
|
|
||||||
settings:
|
settings:
|
||||||
|
@ -159,11 +162,11 @@ For more examples check: [Realize Examples](https://github.com/tockins/realize-e
|
||||||
method: gb build // support differents build tool
|
method: gb build // support differents build tool
|
||||||
args: // additional params for the command
|
args: // additional params for the command
|
||||||
- -race
|
- -race
|
||||||
run: true
|
run:
|
||||||
|
status: true
|
||||||
args: // arguments to pass at the project
|
args: // arguments to pass at the project
|
||||||
- --myarg
|
- --myarg
|
||||||
watcher:
|
watcher:
|
||||||
preview: false // watched files preview
|
|
||||||
paths: // watched paths
|
paths: // watched paths
|
||||||
- /
|
- /
|
||||||
ignore_paths: // ignored paths
|
ignore_paths: // ignored paths
|
||||||
|
@ -171,14 +174,21 @@ For more examples check: [Realize Examples](https://github.com/tockins/realize-e
|
||||||
extensions: // watched extensions
|
extensions: // watched extensions
|
||||||
- go
|
- go
|
||||||
- html
|
- html
|
||||||
scripts: // custom scripts
|
scripts:
|
||||||
- type: before // type (after/before)
|
- type: before
|
||||||
command: ./ls -l // command
|
command: echo before global
|
||||||
changed: true // relaunch when a file change
|
global: true
|
||||||
startup: true // launch at start
|
output: true
|
||||||
|
- type: before
|
||||||
|
command: echo before change
|
||||||
|
output: true
|
||||||
- type: after
|
- type: after
|
||||||
command: ./ls
|
command: echo after change
|
||||||
changed: true
|
output: true
|
||||||
|
- type: after
|
||||||
|
command: echo after global
|
||||||
|
global: true
|
||||||
|
output: true
|
||||||
errorOutputPattern: mypattern //custom error pattern
|
errorOutputPattern: mypattern //custom error pattern
|
||||||
|
|
||||||
## Support and Suggestions
|
## Support and Suggestions
|
||||||
|
@ -197,5 +207,3 @@ Support us with a monthly donation and help us continue our activities. [[Become
|
||||||
## Sponsors
|
## Sponsors
|
||||||
|
|
||||||
Become a sponsor and get your logo here! [[Become a sponsor](https://opencollective.com/realize#sponsor)]
|
Become a sponsor and get your logo here! [[Become a sponsor](https://opencollective.com/realize#sponsor)]
|
||||||
|
|
||||||
|
|
||||||
|
|
157
cmd.go
157
cmd.go
|
@ -1,157 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"gopkg.in/urfave/cli.v2"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Tool options customizable, should be moved in Cmd
|
|
||||||
type tool struct {
|
|
||||||
name, err, out string
|
|
||||||
cmd, options []string
|
|
||||||
dir, status bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cmds list of go commands
|
|
||||||
type Cmds struct {
|
|
||||||
Fix Cmd `yaml:"fix,omitempty" json:"fix,omitempty"`
|
|
||||||
Clean Cmd `yaml:"clean,omitempty" json:"clean,omitempty"`
|
|
||||||
Vet Cmd `yaml:"vet,omitempty" json:"vet,omitempty"`
|
|
||||||
Fmt Cmd `yaml:"fmt,omitempty" json:"fmt,omitempty"`
|
|
||||||
Test Cmd `yaml:"test,omitempty" json:"test,omitempty"`
|
|
||||||
Generate Cmd `yaml:"generate,omitempty" json:"generate,omitempty"`
|
|
||||||
Install Cmd `yaml:"install,omitempty" json:"install,omitempty"`
|
|
||||||
Build Cmd `yaml:"build,omitempty" json:"build,omitempty"`
|
|
||||||
Run bool `yaml:"run,omitempty" json:"run,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cmd single command fields and options
|
|
||||||
type Cmd struct {
|
|
||||||
Method string `yaml:"method,omitempty" json:"method,omitempty"`
|
|
||||||
Args []string `yaml:"args,omitempty" json:"args,omitempty"`
|
|
||||||
Status bool `yaml:"status,omitempty" json:"status,omitempty"`
|
|
||||||
tool bool
|
|
||||||
method []string
|
|
||||||
name, startTxt, endTxt string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clean duplicate projects
|
|
||||||
func (r *realize) clean() error {
|
|
||||||
if len(r.Schema) > 0 {
|
|
||||||
arr := r.Schema
|
|
||||||
for key, val := range arr {
|
|
||||||
if _, err := duplicates(val, arr[key+1:]); err != nil {
|
|
||||||
// path validation
|
|
||||||
|
|
||||||
r.Schema = append(arr[:key], arr[key+1:]...)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return errors.New("there are no projects")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add a new project
|
|
||||||
func (r *realize) add(p *cli.Context) (err error) {
|
|
||||||
// project init
|
|
||||||
name := filepath.Base(p.String("path"))
|
|
||||||
if name == "." {
|
|
||||||
name = filepath.Base(wdir())
|
|
||||||
}
|
|
||||||
project := Project{
|
|
||||||
Name: name,
|
|
||||||
Path: p.String("path"),
|
|
||||||
Cmds: Cmds{
|
|
||||||
Vet: Cmd{
|
|
||||||
Status: p.Bool("vet"),
|
|
||||||
},
|
|
||||||
Fmt: Cmd{
|
|
||||||
Status: p.Bool("fmt"),
|
|
||||||
},
|
|
||||||
Test: Cmd{
|
|
||||||
Status: p.Bool("test"),
|
|
||||||
},
|
|
||||||
Generate: Cmd{
|
|
||||||
Status: p.Bool("generate"),
|
|
||||||
},
|
|
||||||
Build: Cmd{
|
|
||||||
Status: p.Bool("build"),
|
|
||||||
},
|
|
||||||
Install: Cmd{
|
|
||||||
Status: p.Bool("install"),
|
|
||||||
},
|
|
||||||
Run: p.Bool("run"),
|
|
||||||
},
|
|
||||||
Args: params(p),
|
|
||||||
Watcher: Watch{
|
|
||||||
Paths: []string{"/"},
|
|
||||||
Ignore: []string{".git", ".realize", "vendor"},
|
|
||||||
Exts: []string{"go"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
if _, err := duplicates(project, r.Schema); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
r.Schema = append(r.Schema, project)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run launches the toolchain for each project
|
|
||||||
func (r *realize) run(p *cli.Context) error {
|
|
||||||
var match bool
|
|
||||||
// check projects and remove duplicates
|
|
||||||
if err := r.clean(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// set gobin
|
|
||||||
if err := os.Setenv("GOBIN", filepath.Join(os.Getenv("GOPATH"), "bin")); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// loop projects
|
|
||||||
if p.String("name") != "" {
|
|
||||||
wg.Add(1)
|
|
||||||
} else {
|
|
||||||
wg.Add(len(r.Schema))
|
|
||||||
}
|
|
||||||
for k, elm := range r.Schema {
|
|
||||||
// command start using name flag
|
|
||||||
if p.String("name") != "" && elm.Name != p.String("name") {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
match = true
|
|
||||||
r.Schema[k].config(r)
|
|
||||||
go r.Schema[k].watch()
|
|
||||||
}
|
|
||||||
if !match {
|
|
||||||
return errors.New("there is no project with the given name")
|
|
||||||
}
|
|
||||||
wg.Wait()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove a project
|
|
||||||
func (r *realize) remove(p *cli.Context) error {
|
|
||||||
for key, val := range r.Schema {
|
|
||||||
if p.String("name") == val.Name {
|
|
||||||
r.Schema = append(r.Schema[:key], r.Schema[key+1:]...)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return errors.New("no project found")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Insert current project if there isn't already one
|
|
||||||
func (r *realize) insert(c *cli.Context) error {
|
|
||||||
if c.Bool("no-config") {
|
|
||||||
r.Schema = []Project{}
|
|
||||||
}
|
|
||||||
if len(r.Schema) <= 0 {
|
|
||||||
if err := r.add(c); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
156
cmd_test.go
156
cmd_test.go
|
@ -1,156 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"gopkg.in/urfave/cli.v2"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type loggerT struct{}
|
|
||||||
|
|
||||||
func (loggerT) Write(bytes []byte) (int, error) {
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
|
||||||
log.SetFlags(0)
|
|
||||||
log.SetOutput(loggerT{})
|
|
||||||
os.Exit(m.Run())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRealize_Clean(t *testing.T) {
|
|
||||||
r := realize{}
|
|
||||||
r.Schema = append(r.Schema, Project{Name: "test0"})
|
|
||||||
r.Schema = append(r.Schema, Project{Name: "test0"})
|
|
||||||
r.clean()
|
|
||||||
if len(r.Schema) > 1 {
|
|
||||||
t.Error("Expected only one project")
|
|
||||||
}
|
|
||||||
r.Schema = append(r.Schema, Project{Path: "test1"})
|
|
||||||
r.Schema = append(r.Schema, Project{Path: "test1"})
|
|
||||||
r.clean()
|
|
||||||
if len(r.Schema) != 2 {
|
|
||||||
t.Error("Expected two projects")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRealize_Add(t *testing.T) {
|
|
||||||
r := realize{}
|
|
||||||
// add all flags, test with expected
|
|
||||||
set := flag.NewFlagSet("test", 0)
|
|
||||||
set.Bool("fmt", false, "")
|
|
||||||
set.Bool("vet", false, "")
|
|
||||||
set.Bool("test", false, "")
|
|
||||||
set.Bool("install", false, "")
|
|
||||||
set.Bool("run", false, "")
|
|
||||||
set.Bool("build", false, "")
|
|
||||||
set.Bool("generate", false, "")
|
|
||||||
set.String("path", wdir(), "")
|
|
||||||
c := cli.NewContext(nil, set, nil)
|
|
||||||
set.Parse([]string{"--fmt", "--install", "--run", "--build", "--generate", "--test", "--vet"})
|
|
||||||
r.add(c)
|
|
||||||
expected := Project{
|
|
||||||
Name: filepath.Base(wdir()),
|
|
||||||
Path: wdir(),
|
|
||||||
Cmds: Cmds{
|
|
||||||
Fmt: Cmd{
|
|
||||||
Status: true,
|
|
||||||
},
|
|
||||||
Install: Cmd{
|
|
||||||
Status: true,
|
|
||||||
},
|
|
||||||
Generate: Cmd{
|
|
||||||
Status: true,
|
|
||||||
},
|
|
||||||
Test: Cmd{
|
|
||||||
Status: true,
|
|
||||||
},
|
|
||||||
Build: Cmd{
|
|
||||||
Status: true,
|
|
||||||
},
|
|
||||||
Vet: Cmd{
|
|
||||||
Status: true,
|
|
||||||
},
|
|
||||||
Run: true,
|
|
||||||
},
|
|
||||||
Watcher: Watch{
|
|
||||||
Paths: []string{"/"},
|
|
||||||
Ignore: []string{".git", ".realize", "vendor"},
|
|
||||||
Exts: []string{"go"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(r.Schema[0], expected) {
|
|
||||||
t.Error("Expected equal struct")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRealize_Run(t *testing.T) {
|
|
||||||
set := flag.NewFlagSet("test", 0)
|
|
||||||
params := cli.NewContext(nil, set, nil)
|
|
||||||
m := make(map[string]string)
|
|
||||||
m["test"] = "test"
|
|
||||||
r := realize{}
|
|
||||||
r.Schema = []Project{
|
|
||||||
{
|
|
||||||
Name: "test0",
|
|
||||||
Path: ".",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "test1",
|
|
||||||
Path: "/test",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "test2",
|
|
||||||
Path: "/test",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
go r.run(params)
|
|
||||||
time.Sleep(1 * time.Second)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRealize_Remove(t *testing.T) {
|
|
||||||
r := realize{}
|
|
||||||
set := flag.NewFlagSet("name", 0)
|
|
||||||
set.String("name", "", "")
|
|
||||||
c := cli.NewContext(nil, set, nil)
|
|
||||||
set.Parse([]string{"--name=test0"})
|
|
||||||
err := r.remove(c)
|
|
||||||
if err == nil {
|
|
||||||
t.Error("Expected an error, there are no projects")
|
|
||||||
}
|
|
||||||
// Append a new project
|
|
||||||
r.Schema = append(r.Schema, Project{Name: "test0"})
|
|
||||||
err = r.remove(c)
|
|
||||||
if err != nil {
|
|
||||||
t.Error("Error unexpected, the project should be remove", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRealize_Insert(t *testing.T) {
|
|
||||||
r := realize{}
|
|
||||||
// add all flags, test with expected
|
|
||||||
set := flag.NewFlagSet("test", 0)
|
|
||||||
set.Bool("no-config", false, "")
|
|
||||||
c := cli.NewContext(nil, set, nil)
|
|
||||||
set.Parse([]string{"--no-config"})
|
|
||||||
|
|
||||||
r.insert(c)
|
|
||||||
if len(r.Schema) != 1 {
|
|
||||||
t.Error("Expected one project instead", len(r.Schema))
|
|
||||||
}
|
|
||||||
|
|
||||||
r.Schema = []Project{}
|
|
||||||
r.Schema = append(r.Schema, Project{})
|
|
||||||
r.Schema = append(r.Schema, Project{})
|
|
||||||
c = cli.NewContext(nil, set, nil)
|
|
||||||
r.insert(c)
|
|
||||||
if len(r.Schema) != 1 {
|
|
||||||
t.Error("Expected one project instead", len(r.Schema))
|
|
||||||
}
|
|
||||||
}
|
|
228
exec.go
228
exec.go
|
@ -1,228 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// GoCompile is used for compile a project
|
|
||||||
func (p *Project) goCompile(stop <-chan bool, method []string, args []string) (string, error) {
|
|
||||||
var out bytes.Buffer
|
|
||||||
var stderr bytes.Buffer
|
|
||||||
done := make(chan error)
|
|
||||||
args = append(method, args...)
|
|
||||||
cmd := exec.Command(args[0], args[1:]...)
|
|
||||||
cmd.Dir = p.Path
|
|
||||||
cmd.Stdout = &out
|
|
||||||
cmd.Stderr = &stderr
|
|
||||||
// Start command
|
|
||||||
cmd.Start()
|
|
||||||
go func() { done <- cmd.Wait() }()
|
|
||||||
// Wait a result
|
|
||||||
select {
|
|
||||||
case <-stop:
|
|
||||||
// Stop running command
|
|
||||||
cmd.Process.Kill()
|
|
||||||
return msgStop, nil
|
|
||||||
case err := <-done:
|
|
||||||
// Command completed
|
|
||||||
if err != nil {
|
|
||||||
return stderr.String(), err
|
|
||||||
}
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GoRun is an implementation of the bin execution
|
|
||||||
func (p *Project) goRun(stop <-chan bool, runner chan bool) {
|
|
||||||
var build *exec.Cmd
|
|
||||||
var args []string
|
|
||||||
// custom error pattern
|
|
||||||
isErrorText := func(string) bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
errRegexp, err := regexp.Compile(p.ErrorOutputPattern)
|
|
||||||
if err != nil {
|
|
||||||
msg := fmt.Sprintln(p.pname(p.Name, 3), ":", blue.regular(err.Error()))
|
|
||||||
out := BufferOut{Time: time.Now(), Text: err.Error(), Type: "Go Run"}
|
|
||||||
p.stamp("error", out, msg, "")
|
|
||||||
} else {
|
|
||||||
isErrorText = func(t string) bool {
|
|
||||||
return errRegexp.MatchString(t)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// add additional arguments
|
|
||||||
for _, arg := range p.Args {
|
|
||||||
a := strings.FieldsFunc(arg, func(i rune) bool {
|
|
||||||
return i == '"' || i == '=' || i == '\''
|
|
||||||
})
|
|
||||||
args = append(args, a...)
|
|
||||||
}
|
|
||||||
|
|
||||||
gobin := os.Getenv("GOBIN")
|
|
||||||
dirPath := filepath.Base(p.Path)
|
|
||||||
if p.Path == "." {
|
|
||||||
dirPath = filepath.Base(wdir())
|
|
||||||
}
|
|
||||||
path := filepath.Join(gobin, dirPath)
|
|
||||||
if _, err := os.Stat(path); err == nil {
|
|
||||||
build = exec.Command(path, args...)
|
|
||||||
} else if _, err := os.Stat(path + extWindows); err == nil {
|
|
||||||
build = exec.Command(path+extWindows, args...)
|
|
||||||
} else {
|
|
||||||
if _, err = os.Stat(path); err == nil {
|
|
||||||
build = exec.Command(path, args...)
|
|
||||||
} else if _, err = os.Stat(path + extWindows); err == nil {
|
|
||||||
build = exec.Command(path+extWindows, args...)
|
|
||||||
} else {
|
|
||||||
p.err(errors.New("Build not found"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
if err := build.Process.Kill(); err != nil {
|
|
||||||
p.Buffer.StdLog = append(p.Buffer.StdLog, BufferOut{Time: time.Now(), Text: "Failed to stop: " + err.Error()})
|
|
||||||
p.fatal(err, "Failed to stop", ":")
|
|
||||||
}
|
|
||||||
msg := fmt.Sprintln(p.pname(p.Name, 2), ":", red.regular("Ended"))
|
|
||||||
out := BufferOut{Time: time.Now(), Text: "Ended", Type: "Go Run"}
|
|
||||||
p.stamp("log", out, msg, "")
|
|
||||||
}()
|
|
||||||
|
|
||||||
// scan project stream
|
|
||||||
stdout, err := build.StdoutPipe()
|
|
||||||
stderr, err := build.StderrPipe()
|
|
||||||
if err != nil {
|
|
||||||
log.Println(red.bold(err.Error()))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := build.Start(); err != nil {
|
|
||||||
log.Println(red.bold(err.Error()))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
close(runner)
|
|
||||||
|
|
||||||
execOutput, execError := bufio.NewScanner(stdout), bufio.NewScanner(stderr)
|
|
||||||
stopOutput, stopError := make(chan bool, 1), make(chan bool, 1)
|
|
||||||
scanner := func(stop chan bool, output *bufio.Scanner, isError bool) {
|
|
||||||
for output.Scan() {
|
|
||||||
text := output.Text()
|
|
||||||
msg := fmt.Sprintln(p.pname(p.Name, 3), ":", blue.regular(text))
|
|
||||||
if isError && !isErrorText(text) {
|
|
||||||
out := BufferOut{Time: time.Now(), Text: text, Type: "Go Run"}
|
|
||||||
p.stamp("error", out, msg, "")
|
|
||||||
} else {
|
|
||||||
out := BufferOut{Time: time.Now(), Text: text, Type: "Go Run"}
|
|
||||||
p.stamp("out", out, msg, "")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
close(stop)
|
|
||||||
}
|
|
||||||
go scanner(stopOutput, execOutput, false)
|
|
||||||
go scanner(stopError, execError, true)
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-stop:
|
|
||||||
return
|
|
||||||
case <-stopOutput:
|
|
||||||
return
|
|
||||||
case <-stopError:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exec an additional command from a defined path if specified
|
|
||||||
func (p *Project) command(stop <-chan bool, cmd Command) (string, string) {
|
|
||||||
var stdout bytes.Buffer
|
|
||||||
var stderr bytes.Buffer
|
|
||||||
done := make(chan error)
|
|
||||||
args := strings.Split(strings.Replace(strings.Replace(cmd.Command, "'", "", -1), "\"", "", -1), " ")
|
|
||||||
ex := exec.Command(args[0], args[1:]...)
|
|
||||||
ex.Dir = p.Path
|
|
||||||
// make cmd path
|
|
||||||
if cmd.Path != "" {
|
|
||||||
if strings.Contains(cmd.Path, p.Path) {
|
|
||||||
ex.Dir = cmd.Path
|
|
||||||
} else {
|
|
||||||
ex.Dir = filepath.Join(p.Path, cmd.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()
|
|
||||||
return "", ""
|
|
||||||
case err := <-done:
|
|
||||||
// Command completed
|
|
||||||
if err != nil {
|
|
||||||
return stderr.String(), stdout.String()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "", stdout.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// GoTool is used for run go tools methods such as fmt, test, generate and so on
|
|
||||||
func (p *Project) goTool(wg *sync.WaitGroup, stop <-chan bool, result chan<- tool, path string, tool tool) {
|
|
||||||
defer wg.Done()
|
|
||||||
if tool.status {
|
|
||||||
if tool.dir && filepath.Ext(path) != "" {
|
|
||||||
path = filepath.Dir(path)
|
|
||||||
}
|
|
||||||
if strings.HasSuffix(path, ".go") || strings.HasSuffix(path, "") {
|
|
||||||
if strings.HasSuffix(path, ".go") {
|
|
||||||
tool.options = append(tool.options, path)
|
|
||||||
path = p.Path
|
|
||||||
}
|
|
||||||
if s := ext(path); s == "" || s == "go" {
|
|
||||||
var out, stderr bytes.Buffer
|
|
||||||
done := make(chan error)
|
|
||||||
tool.cmd = append(tool.cmd, tool.options...)
|
|
||||||
cmd := exec.Command(tool.cmd[0], tool.cmd[1:]...)
|
|
||||||
cmd.Dir = path
|
|
||||||
cmd.Stdout = &out
|
|
||||||
cmd.Stderr = &stderr
|
|
||||||
// Start command
|
|
||||||
cmd.Start()
|
|
||||||
go func() { done <- cmd.Wait() }()
|
|
||||||
// Wait a result
|
|
||||||
select {
|
|
||||||
case <-stop:
|
|
||||||
// Stop running command
|
|
||||||
cmd.Process.Kill()
|
|
||||||
return
|
|
||||||
case err := <-done:
|
|
||||||
// Command completed
|
|
||||||
if err != nil {
|
|
||||||
tool.err = stderr.String() + out.String()
|
|
||||||
// send command result
|
|
||||||
result <- tool
|
|
||||||
} else {
|
|
||||||
tool.out = out.String()
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
29
exec_test.go
29
exec_test.go
|
@ -1,29 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestProject_GoCompile(t *testing.T) {
|
|
||||||
p := Project{}
|
|
||||||
stop := make(chan bool)
|
|
||||||
response := make(chan string)
|
|
||||||
result, err := p.goCompile(stop, []string{"echo"}, []string{"test"})
|
|
||||||
if err != nil {
|
|
||||||
t.Error("Unexpected", err)
|
|
||||||
}
|
|
||||||
go func() {
|
|
||||||
result, _ = p.goCompile(stop, []string{"sleep"}, []string{"20s"})
|
|
||||||
response <- result
|
|
||||||
}()
|
|
||||||
close(stop)
|
|
||||||
select {
|
|
||||||
case v := <-response:
|
|
||||||
if v != msgStop {
|
|
||||||
t.Error("Unexpected result", response)
|
|
||||||
}
|
|
||||||
case <-time.After(2 * time.Second):
|
|
||||||
t.Error("Channel doesn't works")
|
|
||||||
}
|
|
||||||
}
|
|
650
realize.go
650
realize.go
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,115 @@
|
||||||
|
package realize
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/fsnotify/fsnotify"
|
||||||
|
"github.com/go-siris/siris/core/errors"
|
||||||
|
"go/build"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// RPrefix tool name
|
||||||
|
RPrefix = "realize"
|
||||||
|
// RVersion current version
|
||||||
|
RVersion = "2.0"
|
||||||
|
// RExt file extension
|
||||||
|
RExt = ".yaml"
|
||||||
|
// RFile config file name
|
||||||
|
RFile = "." + RPrefix + RExt
|
||||||
|
//RExtWin windows extension
|
||||||
|
RExtWin = ".exe"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
// LogWriter used for all log
|
||||||
|
LogWriter struct{}
|
||||||
|
|
||||||
|
// Realize main struct
|
||||||
|
Realize struct {
|
||||||
|
Settings Settings `yaml:"settings" json:"settings"`
|
||||||
|
Server Server `yaml:"server" json:"server"`
|
||||||
|
Schema `yaml:",inline" json:",inline"`
|
||||||
|
Sync chan string `yaml:"-" json:"-"`
|
||||||
|
Err Func `yaml:"-" json:"-"`
|
||||||
|
After Func `yaml:"-" json:"-"`
|
||||||
|
Before Func `yaml:"-" json:"-"`
|
||||||
|
Change Func `yaml:"-" json:"-"`
|
||||||
|
Reload Func `yaml:"-" json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Context is used as argument for func
|
||||||
|
Context struct {
|
||||||
|
Path string
|
||||||
|
Project *Project
|
||||||
|
Stop <-chan bool
|
||||||
|
Watcher FileWatcher
|
||||||
|
Event fsnotify.Event
|
||||||
|
}
|
||||||
|
|
||||||
|
// Func is used instead realize func
|
||||||
|
Func func(Context)
|
||||||
|
)
|
||||||
|
|
||||||
|
// init check
|
||||||
|
func init() {
|
||||||
|
// custom log
|
||||||
|
log.SetFlags(0)
|
||||||
|
log.SetOutput(LogWriter{})
|
||||||
|
if build.Default.GOPATH == "" {
|
||||||
|
log.Fatal("$GOPATH isn't set properly")
|
||||||
|
}
|
||||||
|
if err := os.Setenv("GOBIN", filepath.Join(build.Default.GOPATH, "bin")); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop realize workflow
|
||||||
|
func (r *Realize) Stop() error {
|
||||||
|
for k := range r.Schema.Projects {
|
||||||
|
if r.Schema.Projects[k].exit != nil {
|
||||||
|
close(r.Schema.Projects[k].exit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start realize workflow
|
||||||
|
func (r *Realize) Start() error {
|
||||||
|
if len(r.Schema.Projects) > 0 {
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(len(r.Schema.Projects))
|
||||||
|
for k := range r.Schema.Projects {
|
||||||
|
r.Schema.Projects[k].exit = make(chan os.Signal, 1)
|
||||||
|
signal.Notify(r.Schema.Projects[k].exit, os.Interrupt)
|
||||||
|
r.Schema.Projects[k].parent = r
|
||||||
|
go r.Schema.Projects[k].Watch(&wg)
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
} else {
|
||||||
|
return errors.New("there are no projects")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prefix a given string with tool name
|
||||||
|
func (r *Realize) Prefix(input string) string {
|
||||||
|
if len(input) > 0 {
|
||||||
|
return fmt.Sprint(Yellow.Bold("["), strings.ToUpper(RPrefix), Yellow.Bold("]"), " : ", input)
|
||||||
|
}
|
||||||
|
return input
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rewrite the layout of the log timestamp
|
||||||
|
func (w LogWriter) Write(bytes []byte) (int, error) {
|
||||||
|
if len(bytes) > 0 {
|
||||||
|
return fmt.Fprint(Output, Yellow.Regular("["), time.Now().Format("15:04:05"), Yellow.Regular("]"), string(bytes))
|
||||||
|
}
|
||||||
|
return 0, nil
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
package realize
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRealize_Stop(t *testing.T) {
|
||||||
|
r := Realize{}
|
||||||
|
r.Projects = append(r.Schema.Projects, Project{exit: make(chan os.Signal, 1)})
|
||||||
|
r.Stop()
|
||||||
|
_, ok := <-r.Projects[0].exit
|
||||||
|
if ok != false {
|
||||||
|
t.Error("Unexpected error", "channel should be closed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRealize_Start(t *testing.T) {
|
||||||
|
r := Realize{}
|
||||||
|
err := r.Start()
|
||||||
|
if err == nil {
|
||||||
|
t.Error("Error expected")
|
||||||
|
}
|
||||||
|
r.Projects = append(r.Projects, Project{Name: "test", exit: make(chan os.Signal, 1)})
|
||||||
|
go func() {
|
||||||
|
time.Sleep(100)
|
||||||
|
close(r.Projects[0].exit)
|
||||||
|
_, ok := <-r.Projects[0].exit
|
||||||
|
if ok != false {
|
||||||
|
t.Error("Unexpected error", "channel should be closed")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
err = r.Start()
|
||||||
|
if err != nil {
|
||||||
|
t.Error("Unexpected error", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRealize_Prefix(t *testing.T) {
|
||||||
|
r := Realize{}
|
||||||
|
input := "test"
|
||||||
|
result := r.Prefix(input)
|
||||||
|
if len(result) <= 0 && !strings.Contains(result, input) {
|
||||||
|
t.Error("Unexpected error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLogWriter_Write(t *testing.T) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
log.SetOutput(&buf)
|
||||||
|
w := LogWriter{}
|
||||||
|
input := ""
|
||||||
|
int, err := w.Write([]byte(input))
|
||||||
|
if err != nil || int > 0 {
|
||||||
|
t.Error("Unexpected error", err, "string lenght should be 0 instead", int)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package main
|
package realize
|
||||||
|
|
||||||
// this code is imported from moby, unfortunately i can't import it directly as dependencies from its repo,
|
// this code is imported from moby, unfortunately i can't import it directly as dependencies from its repo,
|
||||||
// cause there was a problem between moby vendor and fsnotify
|
// cause there was a problem between moby vendor and fsnotify
|
||||||
|
@ -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
|
// NewFileWatcher 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
|
|
@ -1,4 +1,4 @@
|
||||||
package main
|
package realize
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
|
@ -0,0 +1,680 @@
|
||||||
|
package realize
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/fsnotify/fsnotify"
|
||||||
|
"log"
|
||||||
|
"math/big"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
msg string
|
||||||
|
out BufferOut
|
||||||
|
)
|
||||||
|
|
||||||
|
// Watch info
|
||||||
|
type Watch struct {
|
||||||
|
Paths []string `yaml:"paths" json:"paths"`
|
||||||
|
Exts []string `yaml:"extensions" json:"extensions"`
|
||||||
|
Ignore []string `yaml:"ignored_paths,omitempty" json:"ignored_paths,omitempty"`
|
||||||
|
Scripts []Command `yaml:"scripts,omitempty" json:"scripts,omitempty"`
|
||||||
|
Hidden bool `yaml:"skip_hidden,omitempty" json:"skip_hidden,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command fields
|
||||||
|
type Command struct {
|
||||||
|
Type string `yaml:"type" json:"type"`
|
||||||
|
Cmd string `yaml:"command" json:"command"`
|
||||||
|
Path string `yaml:"path,omitempty" json:"path,omitempty"`
|
||||||
|
Global bool `yaml:"global,omitempty" json:"global,omitempty"`
|
||||||
|
Output bool `yaml:"output,omitempty" json:"output,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Project info
|
||||||
|
type Project struct {
|
||||||
|
parent *Realize
|
||||||
|
watcher FileWatcher
|
||||||
|
init bool
|
||||||
|
exit chan os.Signal
|
||||||
|
stop chan bool
|
||||||
|
files int64
|
||||||
|
folders int64
|
||||||
|
lastFile string
|
||||||
|
paths []string
|
||||||
|
lastTime time.Time
|
||||||
|
Name string `yaml:"name" json:"name"`
|
||||||
|
Path string `yaml:"path" json:"path"`
|
||||||
|
Environment map[string]string `yaml:"environment,omitempty" json:"environment,omitempty"`
|
||||||
|
Tools Tools `yaml:"commands" json:"commands"`
|
||||||
|
Args []string `yaml:"args,omitempty" json:"args,omitempty"`
|
||||||
|
Watcher Watch `yaml:"watcher" json:"watcher"`
|
||||||
|
Buffer Buffer `yaml:"-" json:"buffer"`
|
||||||
|
ErrorOutputPattern string `yaml:"errorOutputPattern,omitempty" json:"errorOutputPattern,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response exec
|
||||||
|
type Response struct {
|
||||||
|
Name string
|
||||||
|
Out string
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Buffer define an array buffer for each log files
|
||||||
|
type Buffer struct {
|
||||||
|
StdOut []BufferOut `json:"stdOut"`
|
||||||
|
StdLog []BufferOut `json:"stdLog"`
|
||||||
|
StdErr []BufferOut `json:"stdErr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// BufferOut is used for exchange information between "realize cli" and "web realize"
|
||||||
|
type BufferOut struct {
|
||||||
|
Time time.Time `json:"time"`
|
||||||
|
Text string `json:"text"`
|
||||||
|
Path string `json:"path"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Stream string `json:"stream"`
|
||||||
|
Errors []string `json:"errors"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// After stop watcher
|
||||||
|
func (p *Project) After() {
|
||||||
|
if p.parent.After != nil {
|
||||||
|
p.parent.After(Context{Project: p})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.cmd(nil, "after", true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Before start watcher
|
||||||
|
func (p *Project) Before() {
|
||||||
|
if p.parent.Before != nil {
|
||||||
|
p.parent.Before(Context{Project: p})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// setup go tools
|
||||||
|
p.Tools.Setup()
|
||||||
|
// set env const
|
||||||
|
for key, item := range p.Environment {
|
||||||
|
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: ""})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// global commands before
|
||||||
|
p.cmd(p.stop, "before", true)
|
||||||
|
// indexing files and dirs
|
||||||
|
for _, dir := range p.Watcher.Paths {
|
||||||
|
base, _ := filepath.Abs(p.Path)
|
||||||
|
base = filepath.Join(base, dir)
|
||||||
|
if _, err := os.Stat(base); err == nil {
|
||||||
|
if err := filepath.Walk(base, p.walk); err != nil {
|
||||||
|
p.Err(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 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")
|
||||||
|
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, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Err occurred
|
||||||
|
func (p *Project) Err(err error) {
|
||||||
|
if p.parent.Err != nil {
|
||||||
|
p.parent.Err(Context{Project: p})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
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, "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Change event message
|
||||||
|
func (p *Project) Change(event fsnotify.Event) {
|
||||||
|
if p.parent.Change != nil {
|
||||||
|
p.parent.Change(Context{Project: p, Event: event})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// file extension
|
||||||
|
ext := ext(event.Name)
|
||||||
|
if ext == "" {
|
||||||
|
ext = "DIR"
|
||||||
|
}
|
||||||
|
// 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
|
||||||
|
func (p *Project) Reload(path string, stop <-chan bool) {
|
||||||
|
if p.parent.Reload != nil {
|
||||||
|
p.parent.Reload(Context{Project: p, Watcher: p.watcher, Path: path, Stop: stop})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var done bool
|
||||||
|
var install, build Response
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-stop:
|
||||||
|
done = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
if done {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// before command
|
||||||
|
p.cmd(stop, "before", false)
|
||||||
|
if done {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Go supported tools
|
||||||
|
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
|
||||||
|
p.init = true
|
||||||
|
// prevent errors using realize without config with only run flag
|
||||||
|
if p.Tools.Run.Status && !p.Tools.Install.Status && !p.Tools.Build.Status {
|
||||||
|
p.Tools.Install.Status = true
|
||||||
|
}
|
||||||
|
if done {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if p.Tools.Install.Status {
|
||||||
|
msg = fmt.Sprintln(p.pname(p.Name, 1), ":", Green.Regular(p.Tools.Install.name), "started")
|
||||||
|
out = BufferOut{Time: time.Now(), Text: p.Tools.Install.name + " started"}
|
||||||
|
p.stamp("log", out, msg, "")
|
||||||
|
start := time.Now()
|
||||||
|
install = p.Tools.Install.Compile(p.Path, stop)
|
||||||
|
install.print(start, p)
|
||||||
|
}
|
||||||
|
if done {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if p.Tools.Build.Status {
|
||||||
|
msg = fmt.Sprintln(p.pname(p.Name, 1), ":", Green.Regular(p.Tools.Build.name), "started")
|
||||||
|
out = BufferOut{Time: time.Now(), Text: p.Tools.Build.name + " started"}
|
||||||
|
p.stamp("log", out, msg, "")
|
||||||
|
start := time.Now()
|
||||||
|
build = p.Tools.Build.Compile(p.Path, stop)
|
||||||
|
build.print(start, p)
|
||||||
|
}
|
||||||
|
if done {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if install.Err == nil && build.Err == nil && p.Tools.Run.Status {
|
||||||
|
var start time.Time
|
||||||
|
result := make(chan Response)
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-stop:
|
||||||
|
return
|
||||||
|
case r := <-result:
|
||||||
|
if r.Err != nil {
|
||||||
|
msg := fmt.Sprintln(p.pname(p.Name, 2), ":", Red.Regular(r.Err))
|
||||||
|
out := BufferOut{Time: time.Now(), Text: r.Err.Error(), Type: "Go Run"}
|
||||||
|
p.stamp("error", out, msg, "")
|
||||||
|
}
|
||||||
|
if r.Out != "" {
|
||||||
|
msg := fmt.Sprintln(p.pname(p.Name, 3), ":", Blue.Regular(r.Out))
|
||||||
|
out := BufferOut{Time: time.Now(), Text: r.Out, Type: "Go Run"}
|
||||||
|
p.stamp("out", out, msg, "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
go func() {
|
||||||
|
log.Println(p.pname(p.Name, 1), ":", "Running..")
|
||||||
|
start = time.Now()
|
||||||
|
err := p.run(p.Path, result, stop)
|
||||||
|
if err != nil {
|
||||||
|
msg := fmt.Sprintln(p.pname(p.Name, 2), ":", Red.Regular(err))
|
||||||
|
out := BufferOut{Time: time.Now(), Text: err.Error(), Type: "Go Run"}
|
||||||
|
p.stamp("error", out, msg, "")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
if done {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.cmd(stop, "after", false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Watch a project
|
||||||
|
func (p *Project) Watch(wg *sync.WaitGroup) {
|
||||||
|
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.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.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(event.Name, p.stop)
|
||||||
|
}
|
||||||
|
p.lastTime = time.Now().Truncate(time.Second)
|
||||||
|
p.lastFile = event.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case err := <-p.watcher.Errors():
|
||||||
|
p.Err(err)
|
||||||
|
case <-p.exit:
|
||||||
|
p.After()
|
||||||
|
break L
|
||||||
|
}
|
||||||
|
}
|
||||||
|
wg.Done()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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("\""))
|
||||||
|
if r.Err != nil {
|
||||||
|
out = BufferOut{Time: time.Now(), Text: r.Err.Error(), Type: flag}
|
||||||
|
p.stamp("error", out, msg, fmt.Sprint(Red.Regular(r.Err.Error())))
|
||||||
|
} else {
|
||||||
|
out = BufferOut{Time: time.Now(), Text: r.Out, Type: flag}
|
||||||
|
p.stamp("log", out, msg, fmt.Sprint(r.Out))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
p.parent.Sync <- "sync"
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run a project
|
||||||
|
func (p *Project) run(path string, stream chan Response, stop <-chan bool) (err error) {
|
||||||
|
var args []string
|
||||||
|
var build *exec.Cmd
|
||||||
|
var r Response
|
||||||
|
defer func() {
|
||||||
|
// https://github.com/golang/go/issues/5615
|
||||||
|
// https://github.com/golang/go/issues/6720
|
||||||
|
build.Process.Signal(os.Interrupt)
|
||||||
|
}()
|
||||||
|
|
||||||
|
// custom error pattern
|
||||||
|
isErrorText := func(string) bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
errRegexp, err := regexp.Compile(p.ErrorOutputPattern)
|
||||||
|
if err != nil {
|
||||||
|
r.Err = err
|
||||||
|
stream <- r
|
||||||
|
} else {
|
||||||
|
isErrorText = func(t string) bool {
|
||||||
|
return errRegexp.MatchString(t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// add additional arguments
|
||||||
|
for _, arg := range p.Args {
|
||||||
|
a := strings.FieldsFunc(arg, func(i rune) bool {
|
||||||
|
return i == '"' || i == '=' || i == '\''
|
||||||
|
})
|
||||||
|
args = append(args, a...)
|
||||||
|
}
|
||||||
|
dirPath := os.Getenv("GOBIN")
|
||||||
|
if p.Tools.Run.Dir != "" {
|
||||||
|
dirPath, _ = filepath.Abs(p.Tools.Run.Dir)
|
||||||
|
}
|
||||||
|
name := filepath.Base(path)
|
||||||
|
if path == "." {
|
||||||
|
name = filepath.Base(Wdir())
|
||||||
|
}
|
||||||
|
path = filepath.Join(dirPath, name)
|
||||||
|
if _, err := os.Stat(path); err == nil {
|
||||||
|
build = exec.Command(path, args...)
|
||||||
|
} else if _, err := os.Stat(path + RExtWin); err == nil {
|
||||||
|
build = exec.Command(path+RExtWin, args...)
|
||||||
|
} else {
|
||||||
|
if _, err = os.Stat(path); err == nil {
|
||||||
|
build = exec.Command(path, args...)
|
||||||
|
} else if _, err = os.Stat(path + RExtWin); err == nil {
|
||||||
|
build = exec.Command(path+RExtWin, args...)
|
||||||
|
} else {
|
||||||
|
return errors.New("project not found")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// scan project stream
|
||||||
|
stdout, err := build.StdoutPipe()
|
||||||
|
stderr, err := build.StderrPipe()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := build.Start(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
execOutput, execError := bufio.NewScanner(stdout), bufio.NewScanner(stderr)
|
||||||
|
stopOutput, stopError := make(chan bool, 1), make(chan bool, 1)
|
||||||
|
scanner := func(stop chan bool, output *bufio.Scanner, isError bool) {
|
||||||
|
for output.Scan() {
|
||||||
|
text := output.Text()
|
||||||
|
if isError && !isErrorText(text) {
|
||||||
|
r.Err = errors.New(text)
|
||||||
|
stream <- r
|
||||||
|
r.Err = nil
|
||||||
|
} else {
|
||||||
|
r.Out = text
|
||||||
|
stream <- r
|
||||||
|
r.Out = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
close(stop)
|
||||||
|
}
|
||||||
|
go scanner(stopOutput, execOutput, false)
|
||||||
|
go scanner(stopError, execError, true)
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-stop:
|
||||||
|
return
|
||||||
|
case <-stopOutput:
|
||||||
|
return
|
||||||
|
case <-stopError:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print with time after
|
||||||
|
func (r *Response) print(start time.Time, p *Project) {
|
||||||
|
if r.Err != nil {
|
||||||
|
msg = fmt.Sprintln(p.pname(p.Name, 2), ":", Red.Bold(r.Name), "\n", r.Err.Error())
|
||||||
|
out = BufferOut{Time: time.Now(), Text: r.Err.Error(), Type: r.Name, Stream: r.Out}
|
||||||
|
p.stamp("error", out, msg, r.Out)
|
||||||
|
} else {
|
||||||
|
msg = fmt.Sprintln(p.pname(p.Name, 5), ":", Green.Bold(r.Name), "completed in", Magenta.Regular(big.NewFloat(float64(time.Since(start).Seconds())).Text('f', 3), " s"))
|
||||||
|
out = BufferOut{Time: time.Now(), Text: r.Name + " in " + big.NewFloat(float64(time.Since(start).Seconds())).Text('f', 3) + " s"}
|
||||||
|
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
|
||||||
|
}
|
|
@ -0,0 +1,154 @@
|
||||||
|
package realize
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"github.com/fsnotify/fsnotify"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestProject_After(t *testing.T) /**/ {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
log.SetOutput(&buf)
|
||||||
|
r := Realize{}
|
||||||
|
input := "text"
|
||||||
|
r.After = func(context Context) {
|
||||||
|
log.Println(input)
|
||||||
|
}
|
||||||
|
r.Projects = append(r.Projects, Project{
|
||||||
|
parent: &r,
|
||||||
|
})
|
||||||
|
r.Projects[0].After()
|
||||||
|
if !strings.Contains(buf.String(), input) {
|
||||||
|
t.Error("Unexpected error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProject_Before(t *testing.T) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
log.SetOutput(&buf)
|
||||||
|
r := Realize{}
|
||||||
|
r.Projects = append(r.Projects, Project{
|
||||||
|
parent: &r,
|
||||||
|
})
|
||||||
|
input := "text"
|
||||||
|
r.Before = func(context Context) {
|
||||||
|
log.Println(input)
|
||||||
|
}
|
||||||
|
r.Projects[0].Before()
|
||||||
|
if !strings.Contains(buf.String(), input) {
|
||||||
|
t.Error("Unexpected error")
|
||||||
|
}
|
||||||
|
|
||||||
|
r = Realize{}
|
||||||
|
r.Projects = append(r.Projects, Project{
|
||||||
|
parent: &r,
|
||||||
|
Environment: map[string]string{
|
||||||
|
input: input,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
r.Projects[0].Before()
|
||||||
|
if os.Getenv(input) != input {
|
||||||
|
t.Error("Unexpected error expected", input, "instead", os.Getenv(input))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProject_Err(t *testing.T) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
log.SetOutput(&buf)
|
||||||
|
r := Realize{}
|
||||||
|
r.Projects = append(r.Projects, Project{
|
||||||
|
parent: &r,
|
||||||
|
})
|
||||||
|
input := "text"
|
||||||
|
r.Err = func(context Context) {
|
||||||
|
log.Println(input)
|
||||||
|
}
|
||||||
|
r.Projects[0].Err(errors.New(input))
|
||||||
|
if !strings.Contains(buf.String(), input) {
|
||||||
|
t.Error("Unexpected error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProject_Change(t *testing.T) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
log.SetOutput(&buf)
|
||||||
|
r := Realize{}
|
||||||
|
r.Projects = append(r.Projects, Project{
|
||||||
|
parent: &r,
|
||||||
|
})
|
||||||
|
r.Change = func(context Context) {
|
||||||
|
log.Println(context.Event.Name)
|
||||||
|
}
|
||||||
|
event := fsnotify.Event{Name: "test", Op: fsnotify.Write}
|
||||||
|
r.Projects[0].Change(event)
|
||||||
|
if !strings.Contains(buf.String(), event.Name) {
|
||||||
|
t.Error("Unexpected error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProject_Reload(t *testing.T) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
log.SetOutput(&buf)
|
||||||
|
r := Realize{}
|
||||||
|
r.Projects = append(r.Projects, Project{
|
||||||
|
parent: &r,
|
||||||
|
})
|
||||||
|
input := "test/path"
|
||||||
|
r.Projects[0].watcher, _ = NewFileWatcher(false, 0)
|
||||||
|
r.Reload = func(context Context) {
|
||||||
|
log.Println(context.Path)
|
||||||
|
}
|
||||||
|
stop := make(chan bool)
|
||||||
|
r.Projects[0].Reload(input, stop)
|
||||||
|
if !strings.Contains(buf.String(), input) {
|
||||||
|
t.Error("Unexpected error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProject_Validate(t *testing.T) {
|
||||||
|
data := map[string]bool{
|
||||||
|
"": false,
|
||||||
|
"/test/.path/": false,
|
||||||
|
"./test/path/": false,
|
||||||
|
"/test/path/test.html": false,
|
||||||
|
"/test/path/test.go": false,
|
||||||
|
"/test/ignore/test.go": false,
|
||||||
|
"/test/check/notexist.go": false,
|
||||||
|
"/test/check/exist.go": false,
|
||||||
|
}
|
||||||
|
r := Realize{}
|
||||||
|
r.Projects = append(r.Projects, Project{
|
||||||
|
parent: &r,
|
||||||
|
Watcher: Watch{
|
||||||
|
Ignore: []string{"/test/ignore"},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
for i, v := range data {
|
||||||
|
if r.Projects[0].Validate(i, true) != v {
|
||||||
|
t.Error("Unexpected error", i, "expected", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProject_Watch(t *testing.T) {
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
r := Realize{}
|
||||||
|
r.Projects = append(r.Projects, Project{
|
||||||
|
parent: &r,
|
||||||
|
exit: make(chan os.Signal, 1),
|
||||||
|
})
|
||||||
|
go func() {
|
||||||
|
time.Sleep(100)
|
||||||
|
close(r.Projects[0].exit)
|
||||||
|
}()
|
||||||
|
wg.Add(1)
|
||||||
|
// test before after and file change
|
||||||
|
r.Projects[0].Watch(&wg)
|
||||||
|
wg.Wait()
|
||||||
|
}
|
|
@ -0,0 +1,92 @@
|
||||||
|
package realize
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"gopkg.in/urfave/cli.v2"
|
||||||
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Schema projects list
|
||||||
|
type Schema struct {
|
||||||
|
Projects []Project `yaml:"schema" json:"schema"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a project if unique
|
||||||
|
func (s *Schema) Add(p Project) {
|
||||||
|
for _, val := range s.Projects {
|
||||||
|
if reflect.DeepEqual(val, p) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.Projects = append(s.Projects, p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove a project
|
||||||
|
func (s *Schema) Remove(name string) error {
|
||||||
|
for key, val := range s.Projects {
|
||||||
|
if name == val.Name {
|
||||||
|
s.Projects = append(s.Projects[:key], s.Projects[key+1:]...)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return errors.New("project not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
// New create a project using cli fields
|
||||||
|
func (s *Schema) New(c *cli.Context) Project {
|
||||||
|
name := filepath.Base(c.String("path"))
|
||||||
|
if len(name) == 0 || name == "." {
|
||||||
|
name = filepath.Base(Wdir())
|
||||||
|
}
|
||||||
|
project := Project{
|
||||||
|
Name: name,
|
||||||
|
Path: c.String("path"),
|
||||||
|
Tools: Tools{
|
||||||
|
Vet: Tool{
|
||||||
|
Status: c.Bool("vet"),
|
||||||
|
},
|
||||||
|
Fmt: Tool{
|
||||||
|
Status: c.Bool("fmt"),
|
||||||
|
},
|
||||||
|
Test: Tool{
|
||||||
|
Status: c.Bool("test"),
|
||||||
|
},
|
||||||
|
Generate: Tool{
|
||||||
|
Status: c.Bool("generate"),
|
||||||
|
},
|
||||||
|
Build: Tool{
|
||||||
|
Status: c.Bool("build"),
|
||||||
|
},
|
||||||
|
Install: Tool{
|
||||||
|
Status: c.Bool("install"),
|
||||||
|
},
|
||||||
|
Run: Tool{
|
||||||
|
Status: c.Bool("run"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Args: params(c),
|
||||||
|
Watcher: Watch{
|
||||||
|
Paths: []string{"/"},
|
||||||
|
Ignore: []string{".git", ".realize", "vendor"},
|
||||||
|
Exts: []string{"go"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return project
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter project list by field
|
||||||
|
func (s *Schema) Filter(field string, value interface{}) []Project {
|
||||||
|
result := []Project{}
|
||||||
|
for _, item := range s.Projects {
|
||||||
|
v := reflect.ValueOf(item)
|
||||||
|
for i := 0; i < v.NumField(); i++ {
|
||||||
|
if v.Type().Field(i).Name == field {
|
||||||
|
if reflect.DeepEqual(v.Field(i).Interface(), value) {
|
||||||
|
result = append(result, item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
|
@ -0,0 +1,104 @@
|
||||||
|
package realize
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"gopkg.in/urfave/cli.v2"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSchema_Add(t *testing.T) {
|
||||||
|
r := Realize{}
|
||||||
|
p := Project{Name: "test"}
|
||||||
|
r.Add(p)
|
||||||
|
if len(r.Schema.Projects) != 1 {
|
||||||
|
t.Error("Unexpected error there are", len(r.Schema.Projects), "instead one")
|
||||||
|
}
|
||||||
|
r.Add(p)
|
||||||
|
if len(r.Schema.Projects) != 1 {
|
||||||
|
t.Error("Unexpected error there are", len(r.Schema.Projects), "instead one")
|
||||||
|
}
|
||||||
|
r.Add(Project{Name: "testing"})
|
||||||
|
if len(r.Schema.Projects) != 2 {
|
||||||
|
t.Error("Unexpected error there are", len(r.Schema.Projects), "instead two")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSchema_Remove(t *testing.T) {
|
||||||
|
r := Realize{}
|
||||||
|
r.Schema.Projects = []Project{
|
||||||
|
{
|
||||||
|
Name: "test",
|
||||||
|
}, {
|
||||||
|
Name: "testing",
|
||||||
|
}, {
|
||||||
|
Name: "testing",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
r.Remove("testing")
|
||||||
|
if len(r.Schema.Projects) != 2 {
|
||||||
|
t.Error("Unexpected errore there are", len(r.Schema.Projects), "instead one")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSchema_New(t *testing.T) {
|
||||||
|
r := Realize{}
|
||||||
|
set := flag.NewFlagSet("test", 0)
|
||||||
|
set.Bool("fmt", false, "")
|
||||||
|
set.Bool("vet", false, "")
|
||||||
|
set.Bool("test", false, "")
|
||||||
|
set.Bool("install", false, "")
|
||||||
|
set.Bool("run", false, "")
|
||||||
|
set.Bool("build", false, "")
|
||||||
|
set.Bool("generate", false, "")
|
||||||
|
set.String("path", "", "")
|
||||||
|
c := cli.NewContext(nil, set, nil)
|
||||||
|
set.Parse([]string{"--fmt", "--install", "--run", "--build", "--generate", "--test", "--vet"})
|
||||||
|
p := r.New(c)
|
||||||
|
if p.Name != filepath.Base(Wdir()) {
|
||||||
|
t.Error("Unexpected error", p.Name, "instead", filepath.Base(Wdir()))
|
||||||
|
}
|
||||||
|
if !p.Tools.Install.Status {
|
||||||
|
t.Error("Install should be enabled")
|
||||||
|
}
|
||||||
|
if !p.Tools.Fmt.Status {
|
||||||
|
t.Error("Fmt should be enabled")
|
||||||
|
}
|
||||||
|
if !p.Tools.Run.Status {
|
||||||
|
t.Error("Run should be enabled")
|
||||||
|
}
|
||||||
|
if !p.Tools.Build.Status {
|
||||||
|
t.Error("Build should be enabled")
|
||||||
|
}
|
||||||
|
if !p.Tools.Generate.Status {
|
||||||
|
t.Error("Generate should be enabled")
|
||||||
|
}
|
||||||
|
if !p.Tools.Test.Status {
|
||||||
|
t.Error("Test should be enabled")
|
||||||
|
}
|
||||||
|
if !p.Tools.Vet.Status {
|
||||||
|
t.Error("Vet should be enabled")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSchema_Filter(t *testing.T) {
|
||||||
|
r := Realize{}
|
||||||
|
r.Schema.Projects = []Project{
|
||||||
|
{
|
||||||
|
Name: "test",
|
||||||
|
}, {
|
||||||
|
Name: "test",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "example",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
result := r.Filter("Name", "test")
|
||||||
|
if len(result) != 2 {
|
||||||
|
t.Error("Expected two project")
|
||||||
|
}
|
||||||
|
result = r.Filter("Name", "example")
|
||||||
|
if len(result) != 1 {
|
||||||
|
t.Error("Expected one project")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,177 @@
|
||||||
|
package realize
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"github.com/labstack/echo"
|
||||||
|
"github.com/labstack/echo/middleware"
|
||||||
|
"golang.org/x/net/websocket"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os/exec"
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Dafault host and port
|
||||||
|
const (
|
||||||
|
Host = "localhost"
|
||||||
|
Port = 5002
|
||||||
|
)
|
||||||
|
|
||||||
|
// Server settings
|
||||||
|
type Server struct {
|
||||||
|
Parent *Realize `yaml:"-" json:"-"`
|
||||||
|
Status bool `yaml:"status" json:"status"`
|
||||||
|
Open bool `yaml:"open" json:"open"`
|
||||||
|
Port int `yaml:"port" json:"port"`
|
||||||
|
Host string `yaml:"host" json:"host"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Websocket projects
|
||||||
|
func (s *Server) projects(c echo.Context) (err error) {
|
||||||
|
websocket.Handler(func(ws *websocket.Conn) {
|
||||||
|
msg, _ := json.Marshal(s.Parent)
|
||||||
|
err = websocket.Message.Send(ws, string(msg))
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-s.Parent.Sync:
|
||||||
|
msg, _ := json.Marshal(s.Parent)
|
||||||
|
err = websocket.Message.Send(ws, string(msg))
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
for {
|
||||||
|
// Read
|
||||||
|
text := ""
|
||||||
|
err = websocket.Message.Receive(ws, &text)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
err := json.Unmarshal([]byte(text), &s.Parent)
|
||||||
|
if err == nil {
|
||||||
|
s.Parent.Settings.Write(s.Parent)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ws.Close()
|
||||||
|
}).ServeHTTP(c.Response(), c.Request())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render return a web pages defined in bindata
|
||||||
|
func (s *Server) render(c echo.Context, path string, mime int) error {
|
||||||
|
data, err := Asset(path)
|
||||||
|
if err != nil {
|
||||||
|
return echo.NewHTTPError(http.StatusNotFound)
|
||||||
|
}
|
||||||
|
rs := c.Response()
|
||||||
|
// check content type by extensions
|
||||||
|
switch mime {
|
||||||
|
case 1:
|
||||||
|
rs.Header().Set(echo.HeaderContentType, echo.MIMETextHTMLCharsetUTF8)
|
||||||
|
break
|
||||||
|
case 2:
|
||||||
|
rs.Header().Set(echo.HeaderContentType, echo.MIMEApplicationJavaScriptCharsetUTF8)
|
||||||
|
break
|
||||||
|
case 3:
|
||||||
|
rs.Header().Set(echo.HeaderContentType, "text/css")
|
||||||
|
break
|
||||||
|
case 4:
|
||||||
|
rs.Header().Set(echo.HeaderContentType, "image/svg+xml")
|
||||||
|
break
|
||||||
|
case 5:
|
||||||
|
rs.Header().Set(echo.HeaderContentType, "image/png")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
rs.WriteHeader(http.StatusOK)
|
||||||
|
rs.Write(data)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start the web server
|
||||||
|
func (s *Server) Start() (err error) {
|
||||||
|
e := echo.New()
|
||||||
|
e.Use(middleware.GzipWithConfig(middleware.GzipConfig{
|
||||||
|
Level: 2,
|
||||||
|
}))
|
||||||
|
e.Use(middleware.Recover())
|
||||||
|
|
||||||
|
// web panel
|
||||||
|
e.GET("/", func(c echo.Context) error {
|
||||||
|
return s.render(c, "assets/index.html", 1)
|
||||||
|
})
|
||||||
|
e.GET("/assets/js/all.min.js", func(c echo.Context) error {
|
||||||
|
return s.render(c, "assets/assets/js/all.min.js", 2)
|
||||||
|
})
|
||||||
|
e.GET("/assets/css/app.css", func(c echo.Context) error {
|
||||||
|
return s.render(c, "assets/assets/css/app.css", 3)
|
||||||
|
})
|
||||||
|
e.GET("/app/components/settings/index.html", func(c echo.Context) error {
|
||||||
|
return s.render(c, "assets/app/components/settings/index.html", 1)
|
||||||
|
})
|
||||||
|
e.GET("/app/components/project/index.html", func(c echo.Context) error {
|
||||||
|
return s.render(c, "assets/app/components/project/index.html", 1)
|
||||||
|
})
|
||||||
|
e.GET("/app/components/index.html", func(c echo.Context) error {
|
||||||
|
return s.render(c, "assets/app/components/index.html", 1)
|
||||||
|
})
|
||||||
|
e.GET("/assets/img/logo.png", func(c echo.Context) error {
|
||||||
|
return s.render(c, "assets/assets/img/logo.png", 5)
|
||||||
|
})
|
||||||
|
e.GET("/assets/img/svg/github-logo.svg", func(c echo.Context) error {
|
||||||
|
return s.render(c, "assets/assets/img/svg/github-logo.svg", 4)
|
||||||
|
})
|
||||||
|
e.GET("/assets/img/svg/ic_arrow_back_black_48px.svg", func(c echo.Context) error {
|
||||||
|
return s.render(c, "assets/assets/img/svg/ic_arrow_back_black_48px.svg", 4)
|
||||||
|
})
|
||||||
|
e.GET("/assets/img/svg/ic_clear_white_48px.svg", func(c echo.Context) error {
|
||||||
|
return s.render(c, "assets/assets/img/svg/ic_clear_white_48px.svg", 4)
|
||||||
|
})
|
||||||
|
e.GET("/assets/img/svg/ic_menu_white_48px.svg", func(c echo.Context) error {
|
||||||
|
return s.render(c, "assets/assets/img/svg/ic_menu_white_48px.svg", 4)
|
||||||
|
})
|
||||||
|
e.GET("/assets/img/svg/ic_settings_black_48px.svg", func(c echo.Context) error {
|
||||||
|
return s.render(c, "assets/assets/img/svg/ic_settings_black_48px.svg", 4)
|
||||||
|
})
|
||||||
|
|
||||||
|
//websocket
|
||||||
|
e.GET("/ws", s.projects)
|
||||||
|
e.HideBanner = true
|
||||||
|
e.Debug = false
|
||||||
|
go func() {
|
||||||
|
log.Println(s.Parent.Prefix("Started on " + string(s.Host) + ":" + strconv.Itoa(s.Port)))
|
||||||
|
e.Start(string(s.Host) + ":" + strconv.Itoa(s.Port))
|
||||||
|
}()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenURL in a new tab of default browser
|
||||||
|
func (s *Server) OpenURL() (io.Writer, error) {
|
||||||
|
url := "http://" + string(s.Parent.Server.Host) + ":" + strconv.Itoa(s.Parent.Server.Port)
|
||||||
|
stderr := bytes.Buffer{}
|
||||||
|
cmd := map[string]string{
|
||||||
|
"windows": "start",
|
||||||
|
"darwin": "open",
|
||||||
|
"linux": "xdg-open",
|
||||||
|
}
|
||||||
|
if s.Open {
|
||||||
|
open, err := cmd[runtime.GOOS]
|
||||||
|
if !err {
|
||||||
|
return nil, fmt.Errorf("operating system %q is not supported", runtime.GOOS)
|
||||||
|
}
|
||||||
|
cmd := exec.Command(open, url)
|
||||||
|
cmd.Stderr = &stderr
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
return cmd.Stderr, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
package realize
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"runtime"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestServer_Start(t *testing.T) {
|
||||||
|
s := Server{
|
||||||
|
Host: "localhost",
|
||||||
|
Port: 5000,
|
||||||
|
}
|
||||||
|
host := "http://localhost:5000/"
|
||||||
|
urls := []string{
|
||||||
|
host,
|
||||||
|
host + "assets/js/all.min.js",
|
||||||
|
host + "assets/css/app.css",
|
||||||
|
host + "app/components/settings/index.html",
|
||||||
|
host + "app/components/project/index.html",
|
||||||
|
host + "app/components/project/index.html",
|
||||||
|
host + "app/components/index.html",
|
||||||
|
}
|
||||||
|
err := s.Start()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
for _, elm := range urls {
|
||||||
|
resp, err := http.Get(elm)
|
||||||
|
if err != nil || resp.StatusCode != 200 {
|
||||||
|
t.Fatal(err, resp.StatusCode, elm)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServer_Open(t *testing.T) {
|
||||||
|
cmd := map[string]string{
|
||||||
|
"windows": "start",
|
||||||
|
"darwin": "open",
|
||||||
|
"linux": "xdg-open",
|
||||||
|
}
|
||||||
|
key := runtime.GOOS
|
||||||
|
if _, ok := cmd[key]; !ok {
|
||||||
|
t.Error("System not supported")
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,10 +1,9 @@
|
||||||
package main
|
package realize
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"math/rand"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
@ -12,12 +11,11 @@ import (
|
||||||
|
|
||||||
// settings const
|
// settings const
|
||||||
const (
|
const (
|
||||||
permission = 0775
|
Permission = 0775
|
||||||
directory = ".realize"
|
File = ".realize.yaml"
|
||||||
file = "realize.yaml"
|
FileOut = ".r.outputs.log"
|
||||||
fileOut = "outputs.log"
|
FileErr = ".r.errors.log"
|
||||||
fileErr = "errors.log"
|
FileLog = ".r.logs.log"
|
||||||
fileLog = "logs.log"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// random string preference
|
// random string preference
|
||||||
|
@ -30,7 +28,6 @@ const (
|
||||||
|
|
||||||
// Settings defines a group of general settings and options
|
// Settings defines a group of general settings and options
|
||||||
type Settings struct {
|
type Settings struct {
|
||||||
file string
|
|
||||||
Files `yaml:"files,omitempty" json:"files,omitempty"`
|
Files `yaml:"files,omitempty" json:"files,omitempty"`
|
||||||
Legacy Legacy `yaml:"legacy" json:"legacy"`
|
Legacy Legacy `yaml:"legacy" json:"legacy"`
|
||||||
FileLimit int32 `yaml:"flimit,omitempty" json:"flimit,omitempty"`
|
FileLimit int32 `yaml:"flimit,omitempty" json:"flimit,omitempty"`
|
||||||
|
@ -54,29 +51,12 @@ type Files struct {
|
||||||
// Resource status and file name
|
// Resource status and file name
|
||||||
type Resource struct {
|
type Resource struct {
|
||||||
Status bool
|
Status bool
|
||||||
|
Path string
|
||||||
Name string
|
Name string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rand is used for generate a random string
|
// Remove realize folder
|
||||||
func random(n int) string {
|
func (s *Settings) Remove(d string) error {
|
||||||
src := rand.NewSource(time.Now().UnixNano())
|
|
||||||
b := make([]byte, n)
|
|
||||||
for i, cache, remain := n-1, src.Int63(), letterIdxMax; i >= 0; {
|
|
||||||
if remain == 0 {
|
|
||||||
cache, remain = src.Int63(), letterIdxMax
|
|
||||||
}
|
|
||||||
if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
|
|
||||||
b[i] = letterBytes[idx]
|
|
||||||
i--
|
|
||||||
}
|
|
||||||
cache >>= letterIdxBits
|
|
||||||
remain--
|
|
||||||
}
|
|
||||||
return string(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete realize folder
|
|
||||||
func (s *Settings) del(d string) error {
|
|
||||||
_, err := os.Stat(d)
|
_, err := os.Stat(d)
|
||||||
if !os.IsNotExist(err) {
|
if !os.IsNotExist(err) {
|
||||||
return os.RemoveAll(d)
|
return os.RemoveAll(d)
|
||||||
|
@ -84,23 +64,13 @@ func (s *Settings) del(d string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate checks a fatal error
|
// Read config file
|
||||||
func (s Settings) validate(err error) error {
|
func (s *Settings) Read(out interface{}) error {
|
||||||
if err != nil {
|
|
||||||
s.fatal(err, "")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read from config file
|
|
||||||
func (s *Settings) read(out interface{}) error {
|
|
||||||
localConfigPath := s.file
|
|
||||||
// backward compatibility
|
// backward compatibility
|
||||||
path := filepath.Join(directory, s.file)
|
if _, err := os.Stat(RFile); err != nil {
|
||||||
if _, err := os.Stat(path); err == nil {
|
return err
|
||||||
localConfigPath = path
|
|
||||||
}
|
}
|
||||||
content, err := s.stream(localConfigPath)
|
content, err := s.Stream(RFile)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
err = yaml.Unmarshal(content, out)
|
err = yaml.Unmarshal(content, out)
|
||||||
return err
|
return err
|
||||||
|
@ -108,55 +78,42 @@ func (s *Settings) read(out interface{}) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Record create and unmarshal the yaml config file
|
// Write config file
|
||||||
func (s *Settings) record(out interface{}) error {
|
func (s *Settings) Write(out interface{}) error {
|
||||||
y, err := yaml.Marshal(out)
|
y, err := yaml.Marshal(out)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if _, err := os.Stat(directory); os.IsNotExist(err) {
|
s.Fatal(ioutil.WriteFile(RFile, y, Permission))
|
||||||
if err = os.Mkdir(directory, permission); err != nil {
|
return nil
|
||||||
return s.write(s.file, y)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return s.write(filepath.Join(directory, s.file), y)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stream return a byte stream of a given file
|
// Stream return a byte stream of a given file
|
||||||
func (s Settings) stream(file string) ([]byte, error) {
|
func (s Settings) Stream(file string) ([]byte, error) {
|
||||||
_, err := os.Stat(file)
|
_, err := os.Stat(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
content, err := ioutil.ReadFile(file)
|
content, err := ioutil.ReadFile(file)
|
||||||
s.validate(err)
|
s.Fatal(err)
|
||||||
return content, err
|
return content, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fatal prints a fatal error with its additional messages
|
// Fatal prints a fatal error with its additional messages
|
||||||
func (s Settings) fatal(err error, msg ...interface{}) {
|
func (s Settings) Fatal(err error, msg ...interface{}) {
|
||||||
if len(msg) > 0 && err != nil {
|
if err != nil {
|
||||||
log.Fatalln(red.regular(msg...), err.Error())
|
if len(msg) > 0 {
|
||||||
} else if err != nil {
|
log.Fatalln(Red.Regular(msg...), err.Error())
|
||||||
|
} else {
|
||||||
log.Fatalln(err.Error())
|
log.Fatalln(err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write a file
|
|
||||||
func (s Settings) write(name string, data []byte) error {
|
|
||||||
err := ioutil.WriteFile(name, data, permission)
|
|
||||||
return s.validate(err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a new file and return its pointer
|
// Create a new file and return its pointer
|
||||||
func (s Settings) create(path string, name string) *os.File {
|
func (s Settings) Create(path string, name string) *os.File {
|
||||||
var file string
|
file := filepath.Join(path, name)
|
||||||
if _, err := os.Stat(directory); err == nil {
|
out, err := os.OpenFile(file, os.O_APPEND|os.O_WRONLY|os.O_CREATE|os.O_SYNC, Permission)
|
||||||
file = filepath.Join(path, directory, name)
|
s.Fatal(err)
|
||||||
} else {
|
|
||||||
file = filepath.Join(path, name)
|
|
||||||
}
|
|
||||||
out, err := os.OpenFile(file, os.O_APPEND|os.O_WRONLY|os.O_CREATE|os.O_SYNC, permission)
|
|
||||||
s.validate(err)
|
|
||||||
return out
|
return out
|
||||||
}
|
}
|
|
@ -0,0 +1,101 @@
|
||||||
|
package realize
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"math/rand"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Rand is used for generate a random string
|
||||||
|
func random(n int) string {
|
||||||
|
src := rand.NewSource(time.Now().UnixNano())
|
||||||
|
b := make([]byte, n)
|
||||||
|
for i, cache, remain := n-1, src.Int63(), letterIdxMax; i >= 0; {
|
||||||
|
if remain == 0 {
|
||||||
|
cache, remain = src.Int63(), letterIdxMax
|
||||||
|
}
|
||||||
|
if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
|
||||||
|
b[i] = letterBytes[idx]
|
||||||
|
i--
|
||||||
|
}
|
||||||
|
cache >>= letterIdxBits
|
||||||
|
remain--
|
||||||
|
}
|
||||||
|
return string(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSettings_Stream(t *testing.T) {
|
||||||
|
s := Settings{}
|
||||||
|
filename := random(4)
|
||||||
|
if _, err := s.Stream(filename); err == nil {
|
||||||
|
t.Fatal("Error expected, none found", filename, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
filename = "settings.go"
|
||||||
|
if _, err := s.Stream(filename); err != nil {
|
||||||
|
t.Fatal("Error unexpected", filename, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSettings_Remove(t *testing.T) {
|
||||||
|
s := Settings{}
|
||||||
|
if err := s.Remove("abcd"); err == nil {
|
||||||
|
t.Fatal("Error unexpected, dir dosn't exist", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
d, err := ioutil.TempDir("", "settings_test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := s.Remove(d); err != nil {
|
||||||
|
t.Fatal("Error unexpected, dir exist", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSettings_Write(t *testing.T) {
|
||||||
|
s := Settings{}
|
||||||
|
data := "abcdefgh"
|
||||||
|
d, err := ioutil.TempFile("", "io_test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
RFile = d.Name()
|
||||||
|
if err := s.Write([]byte(data)); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSettings_Read(t *testing.T) {
|
||||||
|
s := Settings{}
|
||||||
|
var a interface{}
|
||||||
|
RFile = "settings_b"
|
||||||
|
if err := s.Read(a); err == nil {
|
||||||
|
t.Fatal("Error unexpected", err)
|
||||||
|
}
|
||||||
|
RFile = "settings_test.yaml"
|
||||||
|
d, err := ioutil.TempFile("", "settings_test.yaml")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
RFile = d.Name()
|
||||||
|
if err := s.Read(a); err != nil {
|
||||||
|
t.Fatal("Error unexpected", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSettings_Fatal(t *testing.T) {
|
||||||
|
s := Settings{}
|
||||||
|
s.Fatal(nil, "test")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSettings_Create(t *testing.T) {
|
||||||
|
s := Settings{}
|
||||||
|
p, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
f := s.Create(p, "io_test")
|
||||||
|
os.Remove(f.Name())
|
||||||
|
}
|
|
@ -1,11 +1,11 @@
|
||||||
// +build !windows
|
// +build !windows
|
||||||
|
|
||||||
package main
|
package realize
|
||||||
|
|
||||||
import "syscall"
|
import "syscall"
|
||||||
|
|
||||||
// Flimit defines the max number of watched files
|
// Flimit defines the max number of watched files
|
||||||
func (s *Settings) flimit() error {
|
func (s *Settings) Flimit() error {
|
||||||
var rLimit syscall.Rlimit
|
var rLimit syscall.Rlimit
|
||||||
rLimit.Max = uint64(s.FileLimit)
|
rLimit.Max = uint64(s.FileLimit)
|
||||||
rLimit.Cur = uint64(s.FileLimit)
|
rLimit.Cur = uint64(s.FileLimit)
|
|
@ -0,0 +1,15 @@
|
||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package realize
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSettings_Flimit(t *testing.T) {
|
||||||
|
s := Settings{}
|
||||||
|
s.FileLimit = 100
|
||||||
|
if err := s.Flimit(); err != nil {
|
||||||
|
t.Error("Unable to increase limit", err)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,8 +1,8 @@
|
||||||
// +build windows
|
// +build windows
|
||||||
|
|
||||||
package main
|
package realize
|
||||||
|
|
||||||
// Flimit defines the max number of watched files
|
// Flimit defines the max number of watched files
|
||||||
func (s *Settings) flimit() error {
|
func (s *Settings) Flimit() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
package realize
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/fatih/color"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
//Output writer
|
||||||
|
Output = color.Output
|
||||||
|
// Red color
|
||||||
|
Red = colorBase(color.FgHiRed)
|
||||||
|
// Blue color
|
||||||
|
Blue = colorBase(color.FgHiBlue)
|
||||||
|
// Green color
|
||||||
|
Green = colorBase(color.FgHiGreen)
|
||||||
|
// Yellow color
|
||||||
|
Yellow = colorBase(color.FgHiYellow)
|
||||||
|
// Magenta color
|
||||||
|
Magenta = colorBase(color.FgHiMagenta)
|
||||||
|
)
|
||||||
|
|
||||||
|
// ColorBase type
|
||||||
|
type colorBase color.Attribute
|
||||||
|
|
||||||
|
// Regular font with a color
|
||||||
|
func (c colorBase) Regular(a ...interface{}) string {
|
||||||
|
return color.New(color.Attribute(c)).Sprint(a...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bold font with a color
|
||||||
|
func (c colorBase) Bold(a ...interface{}) string {
|
||||||
|
return color.New(color.Attribute(c), color.Bold).Sprint(a...)
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package main
|
package realize
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
@ -13,7 +13,7 @@ func TestStyle_Regular(t *testing.T) {
|
||||||
for i, s := range strs {
|
for i, s := range strs {
|
||||||
input[i] = s
|
input[i] = s
|
||||||
}
|
}
|
||||||
result := red.regular(input)
|
result := Red.Regular(input)
|
||||||
c := color.New(color.FgHiRed).SprintFunc()
|
c := color.New(color.FgHiRed).SprintFunc()
|
||||||
expected := fmt.Sprint(c(input))
|
expected := fmt.Sprint(c(input))
|
||||||
if !bytes.Equal([]byte(result), []byte(expected)) {
|
if !bytes.Equal([]byte(result), []byte(expected)) {
|
||||||
|
@ -27,7 +27,7 @@ func TestStyle_Bold(t *testing.T) {
|
||||||
for i, s := range strs {
|
for i, s := range strs {
|
||||||
input[i] = s
|
input[i] = s
|
||||||
}
|
}
|
||||||
result := red.bold(input)
|
result := Red.Bold(input)
|
||||||
c := color.New(color.FgHiRed, color.Bold).SprintFunc()
|
c := color.New(color.FgHiRed, color.Bold).SprintFunc()
|
||||||
expected := fmt.Sprint(c(input))
|
expected := fmt.Sprint(c(input))
|
||||||
if !bytes.Equal([]byte(result), []byte(expected)) {
|
if !bytes.Equal([]byte(result), []byte(expected)) {
|
|
@ -0,0 +1,171 @@
|
||||||
|
package realize
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Tool info
|
||||||
|
type Tool struct {
|
||||||
|
Args []string `yaml:"args,omitempty" json:"args,omitempty"`
|
||||||
|
Method string `yaml:"method,omitempty" json:"method,omitempty"`
|
||||||
|
Dir string `yaml:"dir,omitempty" json:"dir,omitempty"` //wdir of the command
|
||||||
|
Status bool `yaml:"status,omitempty" json:"status,omitempty"`
|
||||||
|
Output bool `yaml:"output,omitempty" json:"output,omitempty"`
|
||||||
|
dir bool
|
||||||
|
isTool bool
|
||||||
|
method []string
|
||||||
|
cmd []string
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tools go
|
||||||
|
type Tools struct {
|
||||||
|
Clean Tool `yaml:"clean,omitempty" json:"clean,omitempty"`
|
||||||
|
Vet Tool `yaml:"vet,omitempty" json:"vet,omitempty"`
|
||||||
|
Fmt Tool `yaml:"fmt,omitempty" json:"fmt,omitempty"`
|
||||||
|
Test Tool `yaml:"test,omitempty" json:"test,omitempty"`
|
||||||
|
Generate Tool `yaml:"generate,omitempty" json:"generate,omitempty"`
|
||||||
|
Install Tool `yaml:"install,omitempty" json:"install,omitempty"`
|
||||||
|
Build Tool `yaml:"build,omitempty" json:"build,omitempty"`
|
||||||
|
Run Tool `yaml:"run,omitempty" json:"run,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup go tools
|
||||||
|
func (t *Tools) Setup() {
|
||||||
|
// go clean
|
||||||
|
if t.Clean.Status {
|
||||||
|
t.Clean.name = "Clean"
|
||||||
|
t.Clean.isTool = true
|
||||||
|
t.Clean.cmd = replace([]string{"go clean"}, t.Clean.Method)
|
||||||
|
t.Clean.Args = split([]string{}, t.Clean.Args)
|
||||||
|
}
|
||||||
|
// go generate
|
||||||
|
if t.Generate.Status {
|
||||||
|
t.Generate.dir = true
|
||||||
|
t.Generate.isTool = true
|
||||||
|
t.Generate.name = "Generate"
|
||||||
|
t.Generate.cmd = replace([]string{"go", "generate"}, t.Generate.Method)
|
||||||
|
t.Generate.Args = split([]string{}, t.Generate.Args)
|
||||||
|
}
|
||||||
|
// go fmt
|
||||||
|
if t.Fmt.Status {
|
||||||
|
if len(t.Fmt.Args) == 0 {
|
||||||
|
t.Fmt.Args = []string{"-s", "-w", "-e"}
|
||||||
|
}
|
||||||
|
t.Fmt.name = "Fmt"
|
||||||
|
t.Fmt.isTool = true
|
||||||
|
t.Fmt.cmd = replace([]string{"gofmt"}, t.Fmt.Method)
|
||||||
|
t.Fmt.Args = split([]string{}, t.Fmt.Args)
|
||||||
|
}
|
||||||
|
// go vet
|
||||||
|
if t.Vet.Status {
|
||||||
|
t.Vet.dir = true
|
||||||
|
t.Vet.name = "Vet"
|
||||||
|
t.Vet.isTool = true
|
||||||
|
t.Vet.cmd = replace([]string{"go", "vet"}, t.Vet.Method)
|
||||||
|
t.Vet.Args = split([]string{}, t.Vet.Args)
|
||||||
|
}
|
||||||
|
// go test
|
||||||
|
if t.Test.Status {
|
||||||
|
t.Test.dir = true
|
||||||
|
t.Test.isTool = true
|
||||||
|
t.Test.name = "Test"
|
||||||
|
t.Test.cmd = replace([]string{"go", "test"}, t.Test.Method)
|
||||||
|
t.Test.Args = split([]string{}, t.Test.Args)
|
||||||
|
}
|
||||||
|
// go install
|
||||||
|
t.Install.name = "Install"
|
||||||
|
t.Install.cmd = replace([]string{"go", "install"}, t.Install.Method)
|
||||||
|
t.Install.Args = split([]string{}, t.Install.Args)
|
||||||
|
// go build
|
||||||
|
if t.Build.Status {
|
||||||
|
t.Build.name = "Build"
|
||||||
|
t.Build.cmd = replace([]string{"go", "build"}, t.Build.Method)
|
||||||
|
t.Build.Args = split([]string{}, t.Build.Args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exec a go tool
|
||||||
|
func (t *Tool) Exec(path string, stop <-chan bool) (response Response) {
|
||||||
|
if t.dir {
|
||||||
|
if filepath.Ext(path) != "" {
|
||||||
|
path = filepath.Dir(path)
|
||||||
|
}
|
||||||
|
} else if !strings.HasSuffix(path, ".go") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
args := []string{}
|
||||||
|
if strings.HasSuffix(path, ".go") {
|
||||||
|
args = append(t.Args, path)
|
||||||
|
path = filepath.Dir(path)
|
||||||
|
}
|
||||||
|
if s := ext(path); s == "" || s == "go" {
|
||||||
|
var out, stderr bytes.Buffer
|
||||||
|
done := make(chan error)
|
||||||
|
args = append(t.cmd, args...)
|
||||||
|
cmd := exec.Command(args[0], args[1:]...)
|
||||||
|
if t.Dir != "" {
|
||||||
|
cmd.Dir, _ = filepath.Abs(t.Dir)
|
||||||
|
} else {
|
||||||
|
cmd.Dir = path
|
||||||
|
}
|
||||||
|
cmd.Stdout = &out
|
||||||
|
cmd.Stderr = &stderr
|
||||||
|
// Start command
|
||||||
|
cmd.Start()
|
||||||
|
go func() { done <- cmd.Wait() }()
|
||||||
|
// Wait a result
|
||||||
|
select {
|
||||||
|
case <-stop:
|
||||||
|
// Stop running command
|
||||||
|
cmd.Process.Kill()
|
||||||
|
case err := <-done:
|
||||||
|
// Command completed
|
||||||
|
response.Name = t.name
|
||||||
|
if err != nil {
|
||||||
|
response.Err = errors.New(stderr.String() + out.String())
|
||||||
|
} else {
|
||||||
|
if t.Output {
|
||||||
|
response.Out = out.String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compile is used for build and install
|
||||||
|
func (t *Tool) Compile(path string, stop <-chan bool) (response Response) {
|
||||||
|
var out bytes.Buffer
|
||||||
|
var stderr bytes.Buffer
|
||||||
|
done := make(chan error)
|
||||||
|
args := append(t.cmd, t.Args...)
|
||||||
|
cmd := exec.Command(args[0], args[1:]...)
|
||||||
|
if t.Dir != "" {
|
||||||
|
cmd.Dir, _ = filepath.Abs(t.Dir)
|
||||||
|
} else {
|
||||||
|
cmd.Dir = path
|
||||||
|
}
|
||||||
|
cmd.Stdout = &out
|
||||||
|
cmd.Stderr = &stderr
|
||||||
|
// Start command
|
||||||
|
cmd.Start()
|
||||||
|
go func() { done <- cmd.Wait() }()
|
||||||
|
// Wait a result
|
||||||
|
select {
|
||||||
|
case <-stop:
|
||||||
|
// Stop running command
|
||||||
|
cmd.Process.Kill()
|
||||||
|
case err := <-done:
|
||||||
|
// Command completed
|
||||||
|
response.Name = t.name
|
||||||
|
if err != nil {
|
||||||
|
response.Err = errors.New(stderr.String() + err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
package realize
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestTools_Setup(t *testing.T) {
|
||||||
|
tools := Tools{
|
||||||
|
Clean: Tool{
|
||||||
|
Status: true,
|
||||||
|
name: "test",
|
||||||
|
isTool: false,
|
||||||
|
Method: "test",
|
||||||
|
Args: []string{"arg"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
tools.Setup()
|
||||||
|
if tools.Clean.name == "test" {
|
||||||
|
t.Error("Unexpected value")
|
||||||
|
}
|
||||||
|
if tools.Clean.Method != "test" {
|
||||||
|
t.Error("Unexpected value")
|
||||||
|
}
|
||||||
|
if !tools.Clean.isTool {
|
||||||
|
t.Error("Unexpected value")
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package main
|
package realize
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
@ -56,6 +56,9 @@ func ext(path string) string {
|
||||||
for i := len(path) - 1; i >= 0 && !os.IsPathSeparator(path[i]); i-- {
|
for i := len(path) - 1; i >= 0 && !os.IsPathSeparator(path[i]); i-- {
|
||||||
if path[i] == '.' {
|
if path[i] == '.' {
|
||||||
ext = path[i:]
|
ext = path[i:]
|
||||||
|
if index := strings.LastIndex(ext, "."); index > 0 {
|
||||||
|
ext = ext[index:]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ext != "" {
|
if ext != "" {
|
||||||
|
@ -73,10 +76,10 @@ func replace(a []string, b string) []string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wdir return current working directory
|
// Wdir return current working directory
|
||||||
func wdir() string {
|
func Wdir() string {
|
||||||
dir, err := os.Getwd()
|
dir, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(prefix(err.Error()))
|
log.Fatal(err.Error())
|
||||||
}
|
}
|
||||||
return dir
|
return dir
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package main
|
package realize
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
|
@ -8,7 +8,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestArgsParam(t *testing.T) {
|
func TestParams(t *testing.T) {
|
||||||
set := flag.NewFlagSet("test", 0)
|
set := flag.NewFlagSet("test", 0)
|
||||||
set.Bool("myflag", false, "doc")
|
set.Bool("myflag", false, "doc")
|
||||||
p := cli.NewContext(nil, set, nil)
|
p := cli.NewContext(nil, set, nil)
|
||||||
|
@ -43,7 +43,7 @@ func TestDuplicates(t *testing.T) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInArray(t *testing.T) {
|
func TestArray(t *testing.T) {
|
||||||
arr := []string{"a", "b", "c"}
|
arr := []string{"a", "b", "c"}
|
||||||
if !array(arr[0], arr) {
|
if !array(arr[0], arr) {
|
||||||
t.Fatal("Unexpected", arr[0], "should be in", arr)
|
t.Fatal("Unexpected", arr[0], "should be in", arr)
|
||||||
|
@ -58,8 +58,26 @@ func TestWdir(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
result := wdir()
|
result := Wdir()
|
||||||
if result != expected {
|
if result != expected {
|
||||||
t.Error("Expected", filepath.Base(expected), "instead", result)
|
t.Error("Expected", filepath.Base(expected), "instead", result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestExt(t *testing.T) {
|
||||||
|
paths := map[string]string{
|
||||||
|
"/test/a/b/c": "",
|
||||||
|
"/test/a/ac.go": "go",
|
||||||
|
"/test/a/ac.test.go": "go",
|
||||||
|
"/test/a/ac_test.go": "go",
|
||||||
|
"/test/./ac_test.go": "go",
|
||||||
|
"/test/a/.test": "test",
|
||||||
|
"/test/a/.": "",
|
||||||
|
}
|
||||||
|
for i, v := range paths {
|
||||||
|
if ext(i) != v {
|
||||||
|
t.Error("Wrong extension", ext(i), v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package realize
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package realize
|
||||||
|
|
||||||
|
import "syscall"
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
145
realize_test.go
145
realize_test.go
|
@ -1,34 +1,141 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"bytes"
|
||||||
"gopkg.in/urfave/cli.v2"
|
"errors"
|
||||||
"reflect"
|
"github.com/tockins/realize/realize"
|
||||||
|
"log"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestPrefix(t *testing.T) {
|
var mockResponse interface{}
|
||||||
input := random(10)
|
|
||||||
value := fmt.Sprint(yellow.bold("["), "REALIZE", yellow.bold("]"), " : ", input)
|
type mockRealize realize.Realize
|
||||||
result := prefix(input)
|
|
||||||
if result == "" {
|
func (m *mockRealize) add() error {
|
||||||
t.Fatal("Expected a string")
|
if mockResponse != nil {
|
||||||
|
return mockResponse.(error)
|
||||||
}
|
}
|
||||||
if result != value {
|
m.Projects = append(m.Projects, realize.Project{Name: "One"})
|
||||||
t.Fatal("Expected", value, "Instead", result)
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockRealize) setup() error {
|
||||||
|
if mockResponse != nil {
|
||||||
|
return mockResponse.(error)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockRealize) start() error {
|
||||||
|
if mockResponse != nil {
|
||||||
|
return mockResponse.(error)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockRealize) clean() error {
|
||||||
|
if mockResponse != nil {
|
||||||
|
return mockResponse.(error)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockRealize) remove() error {
|
||||||
|
if mockResponse != nil {
|
||||||
|
return mockResponse.(error)
|
||||||
|
}
|
||||||
|
m.Projects = []realize.Project{}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRealize_add(t *testing.T) {
|
||||||
|
m := mockRealize{}
|
||||||
|
mockResponse = nil
|
||||||
|
if err := m.add(); err != nil {
|
||||||
|
t.Error("Unexpected error")
|
||||||
|
}
|
||||||
|
if len(m.Projects) <= 0 {
|
||||||
|
t.Error("Unexpected error")
|
||||||
|
}
|
||||||
|
|
||||||
|
m = mockRealize{}
|
||||||
|
m.Projects = []realize.Project{{Name: "Default"}}
|
||||||
|
mockResponse = nil
|
||||||
|
if err := m.add(); err != nil {
|
||||||
|
t.Error("Unexpected error")
|
||||||
|
}
|
||||||
|
if len(m.Projects) != 2 {
|
||||||
|
t.Error("Unexpected error")
|
||||||
|
}
|
||||||
|
|
||||||
|
m = mockRealize{}
|
||||||
|
mockResponse = errors.New("error")
|
||||||
|
if err := m.clean(); err == nil {
|
||||||
|
t.Error("Expected error")
|
||||||
|
}
|
||||||
|
if len(m.Projects) != 0 {
|
||||||
|
t.Error("Unexpected error")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBefore(t *testing.T) {
|
func TestRealize_start(t *testing.T) {
|
||||||
context := cli.Context{}
|
m := mockRealize{}
|
||||||
if err := before(&context); err != nil {
|
mockResponse = nil
|
||||||
t.Fatal(err)
|
if err := m.add(); err != nil {
|
||||||
|
t.Error("Unexpected error")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNew(t *testing.T) {
|
func TestRealize_setup(t *testing.T) {
|
||||||
r := new()
|
m := mockRealize{}
|
||||||
if reflect.TypeOf(r).String() != "main.realize" {
|
mockResponse = nil
|
||||||
t.Error("Expected a realize struct")
|
if err := m.setup(); err != nil {
|
||||||
|
t.Error("Unexpected error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRealize_clean(t *testing.T) {
|
||||||
|
m := mockRealize{}
|
||||||
|
mockResponse = nil
|
||||||
|
if err := m.clean(); err != nil {
|
||||||
|
t.Error("Unexpected error")
|
||||||
|
}
|
||||||
|
mockResponse = errors.New("error")
|
||||||
|
if err := m.clean(); err == nil {
|
||||||
|
t.Error("Expected error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRealize_remove(t *testing.T) {
|
||||||
|
m := mockRealize{}
|
||||||
|
mockResponse = nil
|
||||||
|
if err := m.remove(); err != nil {
|
||||||
|
t.Error("Unexpected error")
|
||||||
|
}
|
||||||
|
|
||||||
|
m = mockRealize{}
|
||||||
|
mockResponse = nil
|
||||||
|
m.Projects = []realize.Project{{Name: "Default"}, {Name: "Default"}}
|
||||||
|
if err := m.remove(); err != nil {
|
||||||
|
t.Error("Unexpected error")
|
||||||
|
}
|
||||||
|
if len(m.Projects) != 0 {
|
||||||
|
t.Error("Unexpected error")
|
||||||
|
}
|
||||||
|
|
||||||
|
mockResponse = errors.New("error")
|
||||||
|
if err := m.clean(); err == nil {
|
||||||
|
t.Error("Expected error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRealize_version(t *testing.T) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
log.SetOutput(&buf)
|
||||||
|
version()
|
||||||
|
if !strings.Contains(buf.String(), realize.RVersion) {
|
||||||
|
t.Error("Version expted", realize.RVersion)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
195
server.go
195
server.go
|
@ -1,195 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"github.com/labstack/echo"
|
|
||||||
"github.com/labstack/echo/middleware"
|
|
||||||
"golang.org/x/net/websocket"
|
|
||||||
"gopkg.in/urfave/cli.v2"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"os/exec"
|
|
||||||
"runtime"
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Dafault host and port
|
|
||||||
const (
|
|
||||||
host = "localhost"
|
|
||||||
port = 5001
|
|
||||||
)
|
|
||||||
|
|
||||||
// Server settings
|
|
||||||
type Server struct {
|
|
||||||
parent *realize
|
|
||||||
Status bool `yaml:"status" json:"status"`
|
|
||||||
Open bool `yaml:"open" json:"open"`
|
|
||||||
Port int `yaml:"port" json:"port"`
|
|
||||||
Host string `yaml:"host" json:"host"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Websocket projects
|
|
||||||
func (s *Server) projects(c echo.Context) (err error) {
|
|
||||||
websocket.Handler(func(ws *websocket.Conn) {
|
|
||||||
msg, _ := json.Marshal(s.parent)
|
|
||||||
err = websocket.Message.Send(ws, string(msg))
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-s.parent.sync:
|
|
||||||
msg, _ := json.Marshal(s.parent)
|
|
||||||
err = websocket.Message.Send(ws, string(msg))
|
|
||||||
if err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
for {
|
|
||||||
// Read
|
|
||||||
text := ""
|
|
||||||
err = websocket.Message.Receive(ws, &text)
|
|
||||||
if err != nil {
|
|
||||||
break
|
|
||||||
} else {
|
|
||||||
err := json.Unmarshal([]byte(text), &s.parent)
|
|
||||||
if err == nil {
|
|
||||||
s.parent.Settings.record(s.parent)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ws.Close()
|
|
||||||
}).ServeHTTP(c.Response(), c.Request())
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start the web server
|
|
||||||
func (s *Server) start(p *cli.Context) (err error) {
|
|
||||||
if p.Bool("server") {
|
|
||||||
s.parent.Server.Status = true
|
|
||||||
}
|
|
||||||
if p.Bool("open") {
|
|
||||||
s.parent.Server.Open = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.parent.Server.Status {
|
|
||||||
e := echo.New()
|
|
||||||
e.Use(middleware.GzipWithConfig(middleware.GzipConfig{
|
|
||||||
Level: 2,
|
|
||||||
}))
|
|
||||||
e.Use(middleware.Recover())
|
|
||||||
|
|
||||||
// web panel
|
|
||||||
e.GET("/", func(c echo.Context) error {
|
|
||||||
return s.render(c, "assets/index.html", 1)
|
|
||||||
})
|
|
||||||
e.GET("/assets/js/all.min.js", func(c echo.Context) error {
|
|
||||||
return s.render(c, "assets/assets/js/all.min.js", 2)
|
|
||||||
})
|
|
||||||
e.GET("/assets/css/app.css", func(c echo.Context) error {
|
|
||||||
return s.render(c, "assets/assets/css/app.css", 3)
|
|
||||||
})
|
|
||||||
e.GET("/app/components/settings/index.html", func(c echo.Context) error {
|
|
||||||
return s.render(c, "assets/app/components/settings/index.html", 1)
|
|
||||||
})
|
|
||||||
e.GET("/app/components/project/index.html", func(c echo.Context) error {
|
|
||||||
return s.render(c, "assets/app/components/project/index.html", 1)
|
|
||||||
})
|
|
||||||
e.GET("/app/components/index.html", func(c echo.Context) error {
|
|
||||||
return s.render(c, "assets/app/components/index.html", 1)
|
|
||||||
})
|
|
||||||
e.GET("/assets/img/svg/settings.svg", func(c echo.Context) error {
|
|
||||||
return s.render(c, "assets/assets/img/svg/settings.svg", 4)
|
|
||||||
})
|
|
||||||
e.GET("/assets/img/svg/fullscreen.svg", func(c echo.Context) error {
|
|
||||||
return s.render(c, "assets/assets/img/svg/fullscreen.svg", 4)
|
|
||||||
})
|
|
||||||
e.GET("/assets/img/svg/add.svg", func(c echo.Context) error {
|
|
||||||
return s.render(c, "assets/assets/img/svg/add.svg", 4)
|
|
||||||
})
|
|
||||||
e.GET("/assets/img/svg/backspace.svg", func(c echo.Context) error {
|
|
||||||
return s.render(c, "assets/assets/img/svg/backspace.svg", 4)
|
|
||||||
})
|
|
||||||
e.GET("/assets/img/svg/error.svg", func(c echo.Context) error {
|
|
||||||
return s.render(c, "assets/assets/img/svg/error.svg", 4)
|
|
||||||
})
|
|
||||||
e.GET("/assets/img/svg/remove.svg", func(c echo.Context) error {
|
|
||||||
return s.render(c, "assets/assets/img/remove.svg", 4)
|
|
||||||
})
|
|
||||||
e.GET("/assets/img/svg/logo.svg", func(c echo.Context) error {
|
|
||||||
return s.render(c, "assets/assets/img/svg/logo.svg", 4)
|
|
||||||
})
|
|
||||||
e.GET("/assets/img/fav.png", func(c echo.Context) error {
|
|
||||||
return s.render(c, "assets/assets/img/fav.png", 5)
|
|
||||||
})
|
|
||||||
e.GET("/assets/img/svg/circle.svg", func(c echo.Context) error {
|
|
||||||
return s.render(c, "assets/assets/img/svg/circle.svg", 4)
|
|
||||||
})
|
|
||||||
|
|
||||||
//websocket
|
|
||||||
e.GET("/ws", s.projects)
|
|
||||||
e.HideBanner = true
|
|
||||||
e.Debug = false
|
|
||||||
go e.Start(string(s.parent.Server.Host) + ":" + strconv.Itoa(s.parent.Server.Port))
|
|
||||||
_, err = s.openURL("http://" + string(s.parent.Server.Host) + ":" + strconv.Itoa(s.parent.Server.Port))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// OpenURL in a new tab of default browser
|
|
||||||
func (s *Server) openURL(url string) (io.Writer, error) {
|
|
||||||
stderr := bytes.Buffer{}
|
|
||||||
cmd := map[string]string{
|
|
||||||
"windows": "start",
|
|
||||||
"darwin": "open",
|
|
||||||
"linux": "xdg-open",
|
|
||||||
}
|
|
||||||
if s.Open {
|
|
||||||
open, err := cmd[runtime.GOOS]
|
|
||||||
if !err {
|
|
||||||
return nil, fmt.Errorf("operating system %q is not supported", runtime.GOOS)
|
|
||||||
}
|
|
||||||
cmd := exec.Command(open, url)
|
|
||||||
cmd.Stderr = &stderr
|
|
||||||
if err := cmd.Run(); err != nil {
|
|
||||||
return cmd.Stderr, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render return a web pages defined in bindata
|
|
||||||
func (s *Server) render(c echo.Context, path string, mime int) error {
|
|
||||||
data, err := Asset(path)
|
|
||||||
if err != nil {
|
|
||||||
return echo.NewHTTPError(http.StatusNotFound)
|
|
||||||
}
|
|
||||||
rs := c.Response()
|
|
||||||
// check content type by extensions
|
|
||||||
switch mime {
|
|
||||||
case 1:
|
|
||||||
rs.Header().Set(echo.HeaderContentType, echo.MIMETextHTMLCharsetUTF8)
|
|
||||||
break
|
|
||||||
case 2:
|
|
||||||
rs.Header().Set(echo.HeaderContentType, echo.MIMEApplicationJavaScriptCharsetUTF8)
|
|
||||||
break
|
|
||||||
case 3:
|
|
||||||
rs.Header().Set(echo.HeaderContentType, "text/css")
|
|
||||||
break
|
|
||||||
case 4:
|
|
||||||
rs.Header().Set(echo.HeaderContentType, "image/svg+xml")
|
|
||||||
break
|
|
||||||
case 5:
|
|
||||||
rs.Header().Set(echo.HeaderContentType, "image/png")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
rs.WriteHeader(http.StatusOK)
|
|
||||||
rs.Write(data)
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,60 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
//import (
|
|
||||||
// "fmt"
|
|
||||||
// "net/http"
|
|
||||||
// "testing"
|
|
||||||
//)
|
|
||||||
//
|
|
||||||
//func TestServer_Start(t *testing.T) {
|
|
||||||
// s := Server{
|
|
||||||
// Status: true,
|
|
||||||
// Open: false,
|
|
||||||
// Host: "localhost",
|
|
||||||
// Port: 5000,
|
|
||||||
// }
|
|
||||||
// err := s.start(nil)
|
|
||||||
// if err != nil {
|
|
||||||
// t.Fatal(err)
|
|
||||||
// }
|
|
||||||
// host := "http://localhost:5000/"
|
|
||||||
// urls := []string{
|
|
||||||
// host,
|
|
||||||
// host + "assets/js/all.min.js",
|
|
||||||
// host + "assets/css/app.css",
|
|
||||||
// host + "app/components/settings/index.html",
|
|
||||||
// host + "app/components/project/index.html",
|
|
||||||
// host + "app/components/project/index.html",
|
|
||||||
// host + "app/components/index.html",
|
|
||||||
// host + "assets/img/svg/ic_settings_black_24px.svg",
|
|
||||||
// host + "assets/img/svg/ic_fullscreen_black_24px.svg",
|
|
||||||
// host + "assets/img/svg/ic_add_black_24px.svg",
|
|
||||||
// host + "assets/img/svg/ic_keyboard_backspace_black_24px.svg",
|
|
||||||
// host + "assets/img/svg/ic_error_black_48px.svg",
|
|
||||||
// host + "assets/img/svg/ic_remove_black_24px.svg",
|
|
||||||
// host + "assets/img/svg/logo.svg",
|
|
||||||
// host + "assets/img/favicon-32x32.png",
|
|
||||||
// host + "assets/img/svg/ic_swap_vertical_circle_black_48px.svg",
|
|
||||||
// }
|
|
||||||
// for _, elm := range urls {
|
|
||||||
// resp, err := http.Get(elm)
|
|
||||||
// if err != nil || resp.StatusCode != 200 {
|
|
||||||
// t.Fatal(err, resp.StatusCode, elm)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//func TestServer_Open(t *testing.T) {
|
|
||||||
// c := Server{
|
|
||||||
// Open: true,
|
|
||||||
// }
|
|
||||||
// url := "open_test"
|
|
||||||
// out, err := c.openURL(url)
|
|
||||||
// if err == nil {
|
|
||||||
// t.Fatal("Unexpected, invalid url", url, err)
|
|
||||||
// }
|
|
||||||
// output := fmt.Sprint(out)
|
|
||||||
// if output == "" {
|
|
||||||
// t.Fatal("Unexpected, invalid url", url, output)
|
|
||||||
// }
|
|
||||||
//}
|
|
114
settings_test.go
114
settings_test.go
|
@ -1,114 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestSettings_Flimit(t *testing.T) {
|
|
||||||
s := Settings{}
|
|
||||||
s.FileLimit = 100
|
|
||||||
if err := s.flimit(); err != nil {
|
|
||||||
t.Fatal("Unable to increase limit", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSettings_Stream(t *testing.T) {
|
|
||||||
s := Settings{}
|
|
||||||
filename := random(4)
|
|
||||||
if _, err := s.stream(filename); err == nil {
|
|
||||||
t.Fatal("Error expected, none found", filename, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
filename = "settings.go"
|
|
||||||
if _, err := s.stream(filename); err != nil {
|
|
||||||
t.Fatal("Error unexpected", filename, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSettings_Write(t *testing.T) {
|
|
||||||
s := Settings{}
|
|
||||||
data := "abcdefgh"
|
|
||||||
d, err := ioutil.TempFile("", "io_test")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if err := s.write(d.Name(), []byte(data)); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSettings_Create(t *testing.T) {
|
|
||||||
s := Settings{}
|
|
||||||
p, err := os.Getwd()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
f := s.create(p, "io_test")
|
|
||||||
os.Remove(f.Name())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSettings_Read(t *testing.T) {
|
|
||||||
s := Settings{}
|
|
||||||
var a interface{}
|
|
||||||
s.file = "settings_b"
|
|
||||||
if err := s.read(a); err == nil {
|
|
||||||
t.Fatal("Error unexpected", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
s.file = "settings_test.yaml"
|
|
||||||
dir, err := ioutil.TempDir("", directory)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
d, err := ioutil.TempFile(dir, "settings_test.yaml")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
s.file = d.Name()
|
|
||||||
if err := s.read(a); err != nil {
|
|
||||||
t.Fatal("Error unexpected", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSettings_Del(t *testing.T) {
|
|
||||||
s := Settings{}
|
|
||||||
if err := s.del("abcd"); err == nil {
|
|
||||||
t.Fatal("Error unexpected, dir dosn't exist", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
d, err := ioutil.TempDir("", "settings_test")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if err := s.del(d); err != nil {
|
|
||||||
t.Fatal("Error unexpected, dir exist", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSettings_Record(t *testing.T) {
|
|
||||||
s := Settings{}
|
|
||||||
s.file = "settings_test.yaml"
|
|
||||||
var a interface{}
|
|
||||||
if err := s.record(a); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
s.del(filepath.Join(directory, s.file))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSettings_Validate(t *testing.T) {
|
|
||||||
s := Settings{}
|
|
||||||
input := errors.New("")
|
|
||||||
input = nil
|
|
||||||
if err := s.validate(input); err != nil {
|
|
||||||
t.Error("Expected", input, "instead", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSettings_Fatal(t *testing.T) {
|
|
||||||
s := Settings{}
|
|
||||||
s.fatal(nil, "test")
|
|
||||||
}
|
|
24
style.go
24
style.go
|
@ -1,24 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/fatih/color"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
output = color.Output
|
|
||||||
red = colorBase(color.FgHiRed)
|
|
||||||
blue = colorBase(color.FgHiBlue)
|
|
||||||
green = colorBase(color.FgHiGreen)
|
|
||||||
yellow = colorBase(color.FgHiYellow)
|
|
||||||
magenta = colorBase(color.FgHiMagenta)
|
|
||||||
)
|
|
||||||
|
|
||||||
type colorBase color.Attribute
|
|
||||||
|
|
||||||
func (c colorBase) regular(a ...interface{}) string {
|
|
||||||
return color.New(color.Attribute(c)).Sprint(a...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c colorBase) bold(a ...interface{}) string {
|
|
||||||
return color.New(color.Attribute(c), color.Bold).Sprint(a...)
|
|
||||||
}
|
|
533
watcher.go
533
watcher.go
|
@ -1,533 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/fsnotify/fsnotify"
|
|
||||||
"log"
|
|
||||||
"math/big"
|
|
||||||
"os"
|
|
||||||
"os/signal"
|
|
||||||
"path/filepath"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
msg string
|
|
||||||
out BufferOut
|
|
||||||
wg sync.WaitGroup
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
msgStop = "killed"
|
|
||||||
extWindows = ".exe"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Watch struct defines options for livereload
|
|
||||||
type Watch struct {
|
|
||||||
Preview bool `yaml:"preview,omitempty" json:"preview,omitempty"`
|
|
||||||
Paths []string `yaml:"paths" json:"paths"`
|
|
||||||
Exts []string `yaml:"extensions" json:"extensions"`
|
|
||||||
Ignore []string `yaml:"ignored_paths,omitempty" json:"ignored_paths,omitempty"`
|
|
||||||
Scripts []Command `yaml:"scripts,omitempty" json:"scripts,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Result channel with data stream and errors
|
|
||||||
type Result struct {
|
|
||||||
stream string
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
// Buffer define an array buffer for each log files
|
|
||||||
type Buffer struct {
|
|
||||||
StdOut []BufferOut `json:"stdOut"`
|
|
||||||
StdLog []BufferOut `json:"stdLog"`
|
|
||||||
StdErr []BufferOut `json:"stdErr"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Project defines the informations of a single project
|
|
||||||
type Project struct {
|
|
||||||
parent *realize
|
|
||||||
watcher FileWatcher
|
|
||||||
init bool
|
|
||||||
Settings `yaml:"-" json:"-"`
|
|
||||||
files, folders int64
|
|
||||||
name, lastFile string
|
|
||||||
tools []tool
|
|
||||||
paths []string
|
|
||||||
lastTime time.Time
|
|
||||||
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 Watch `yaml:"watcher" json:"watcher"`
|
|
||||||
Buffer Buffer `yaml:"-" json:"buffer"`
|
|
||||||
ErrorOutputPattern string `yaml:"errorOutputPattern,omitempty" json:"errorOutputPattern,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Command options
|
|
||||||
type Command struct {
|
|
||||||
Type string `yaml:"type" json:"type"`
|
|
||||||
Command string `yaml:"command" json:"command"`
|
|
||||||
Path string `yaml:"path,omitempty" json:"path,omitempty"`
|
|
||||||
Global bool `yaml:"global,omitempty" json:"global,omitempty"`
|
|
||||||
Output bool `yaml:"output,omitempty" json:"output,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// BufferOut is used for exchange information between "realize cli" and "web realize"
|
|
||||||
type BufferOut struct {
|
|
||||||
Time time.Time `json:"time"`
|
|
||||||
Text string `json:"text"`
|
|
||||||
Path string `json:"path"`
|
|
||||||
Type string `json:"type"`
|
|
||||||
Stream string `json:"stream"`
|
|
||||||
Errors []string `json:"errors"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Watch the project
|
|
||||||
func (p *Project) watch() {
|
|
||||||
p.watcher, _ = Watcher(r.Settings.Legacy.Force, r.Settings.Legacy.Interval)
|
|
||||||
stop, exit := make(chan bool), make(chan os.Signal, 2)
|
|
||||||
signal.Notify(exit, os.Interrupt, syscall.SIGTERM)
|
|
||||||
// before global commands
|
|
||||||
p.cmd(stop, "before", true)
|
|
||||||
// indexing files and dirs
|
|
||||||
for _, dir := range p.Watcher.Paths {
|
|
||||||
base, _ := filepath.Abs(p.Path)
|
|
||||||
base = filepath.Join(base, dir)
|
|
||||||
if _, err := os.Stat(base); err == nil {
|
|
||||||
if err := filepath.Walk(base, p.walk); err == nil {
|
|
||||||
p.tool(stop, base)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
p.err(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// indexing done, files and folders
|
|
||||||
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"}
|
|
||||||
p.stamp("log", out, msg, "")
|
|
||||||
// start
|
|
||||||
go p.routines(stop, p.watcher, "")
|
|
||||||
//is watching
|
|
||||||
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()
|
|
||||||
// file extension
|
|
||||||
ext := ext(event.Name)
|
|
||||||
if ext == "" {
|
|
||||||
ext = "DIR"
|
|
||||||
}
|
|
||||||
// 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}
|
|
||||||
// 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.routines(stop, p.watcher, "")
|
|
||||||
}
|
|
||||||
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 {
|
|
||||||
// used only for test and debug
|
|
||||||
if p.parent.Settings.Recovery {
|
|
||||||
log.Println(event)
|
|
||||||
}
|
|
||||||
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.routines(stop, p.watcher, event.Name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
wg.Done()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Config project init
|
|
||||||
func (p *Project) config(r *realize) {
|
|
||||||
// get basepath name
|
|
||||||
p.name = filepath.Base(p.Path)
|
|
||||||
// env variables
|
|
||||||
for key, item := range p.Environment {
|
|
||||||
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: ""})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// init commands
|
|
||||||
if len(p.Cmds.Fmt.Args) == 0 {
|
|
||||||
p.Cmds.Fmt.Args = []string{"-s", "-w", "-e", "./"}
|
|
||||||
}
|
|
||||||
p.tools = append(p.tools, tool{
|
|
||||||
status: p.Cmds.Clean.Status,
|
|
||||||
cmd: replace([]string{"go clean"}, p.Cmds.Clean.Method),
|
|
||||||
options: split([]string{}, p.Cmds.Clean.Args),
|
|
||||||
name: "Clean",
|
|
||||||
})
|
|
||||||
p.tools = append(p.tools, tool{
|
|
||||||
status: p.Cmds.Generate.Status,
|
|
||||||
cmd: replace([]string{"go", "generate"}, p.Cmds.Generate.Method),
|
|
||||||
options: split([]string{}, p.Cmds.Generate.Args),
|
|
||||||
name: "Generate",
|
|
||||||
dir: true,
|
|
||||||
})
|
|
||||||
p.tools = append(p.tools, tool{
|
|
||||||
status: p.Cmds.Fix.Status,
|
|
||||||
cmd: replace([]string{"go fix"}, p.Cmds.Fix.Method),
|
|
||||||
options: split([]string{}, p.Cmds.Fix.Args),
|
|
||||||
name: "Fix",
|
|
||||||
})
|
|
||||||
p.tools = append(p.tools, tool{
|
|
||||||
status: p.Cmds.Fmt.Status,
|
|
||||||
cmd: replace([]string{"gofmt"}, p.Cmds.Fmt.Method),
|
|
||||||
options: split([]string{}, p.Cmds.Fmt.Args),
|
|
||||||
name: "Fmt",
|
|
||||||
})
|
|
||||||
p.tools = append(p.tools, tool{
|
|
||||||
status: p.Cmds.Vet.Status,
|
|
||||||
cmd: replace([]string{"go", "vet"}, p.Cmds.Vet.Method),
|
|
||||||
options: split([]string{}, p.Cmds.Vet.Args),
|
|
||||||
name: "Vet",
|
|
||||||
dir: true,
|
|
||||||
})
|
|
||||||
p.tools = append(p.tools, tool{
|
|
||||||
status: p.Cmds.Test.Status,
|
|
||||||
cmd: replace([]string{"go", "test"}, p.Cmds.Test.Method),
|
|
||||||
options: split([]string{}, p.Cmds.Test.Args),
|
|
||||||
name: "Test",
|
|
||||||
dir: true,
|
|
||||||
})
|
|
||||||
p.Cmds.Install = Cmd{
|
|
||||||
Status: p.Cmds.Install.Status,
|
|
||||||
Args: append([]string{}, p.Cmds.Install.Args...),
|
|
||||||
method: replace([]string{"go", "install"}, p.Cmds.Install.Method),
|
|
||||||
name: "Install",
|
|
||||||
startTxt: "Installing...",
|
|
||||||
endTxt: "Installed",
|
|
||||||
}
|
|
||||||
p.Cmds.Build = Cmd{
|
|
||||||
Status: p.Cmds.Build.Status,
|
|
||||||
Args: append([]string{}, p.Cmds.Build.Args...),
|
|
||||||
method: replace([]string{"go", "build"}, p.Cmds.Build.Method),
|
|
||||||
name: "Build",
|
|
||||||
startTxt: "Building...",
|
|
||||||
endTxt: "Built",
|
|
||||||
}
|
|
||||||
p.parent = r
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cmd calls the method that execute commands after/before and display the results
|
|
||||||
func (p *Project) cmd(stop <-chan bool, flag string, global bool) {
|
|
||||||
done := make(chan bool)
|
|
||||||
// cmds are scheduled in sequence
|
|
||||||
go func() {
|
|
||||||
for _, cmd := range p.Watcher.Scripts {
|
|
||||||
if strings.ToLower(cmd.Type) == flag && cmd.Global == global {
|
|
||||||
err, logs := p.command(stop, cmd)
|
|
||||||
if err == "" && logs == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
msg = fmt.Sprintln(p.pname(p.Name, 5), ":", green.bold("Command"), green.bold("\"")+cmd.Command+green.bold("\""))
|
|
||||||
out = BufferOut{Time: time.Now(), Text: cmd.Command, Type: flag}
|
|
||||||
if err != "" {
|
|
||||||
p.stamp("error", out, msg, "")
|
|
||||||
|
|
||||||
msg = fmt.Sprintln(red.regular(err))
|
|
||||||
out = BufferOut{Time: time.Now(), Text: err, Type: flag}
|
|
||||||
p.stamp("error", out, "", msg)
|
|
||||||
|
|
||||||
} else if logs != "" && cmd.Output {
|
|
||||||
msg = fmt.Sprintln(logs)
|
|
||||||
out = BufferOut{Time: time.Now(), Text: logs, Type: flag}
|
|
||||||
p.stamp("log", out, "", msg)
|
|
||||||
} else {
|
|
||||||
p.stamp("log", out, msg, "")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
close(done)
|
|
||||||
}()
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-stop:
|
|
||||||
return
|
|
||||||
case <-done:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compile is used for run and display the result of a compiling
|
|
||||||
func (p *Project) compile(stop <-chan bool, cmd Cmd) error {
|
|
||||||
if cmd.Status {
|
|
||||||
var start time.Time
|
|
||||||
channel := make(chan Result)
|
|
||||||
go func() {
|
|
||||||
log.Println(p.pname(p.Name, 1), ":", cmd.startTxt)
|
|
||||||
start = time.Now()
|
|
||||||
stream, err := p.goCompile(stop, cmd.method, cmd.Args)
|
|
||||||
if stream != msgStop {
|
|
||||||
channel <- Result{stream, err}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
select {
|
|
||||||
case r := <-channel:
|
|
||||||
if r.err != nil {
|
|
||||||
msg = fmt.Sprintln(p.pname(p.Name, 2), ":", red.bold(cmd.name), red.regular(r.err.Error()))
|
|
||||||
out = BufferOut{Time: time.Now(), Text: r.err.Error(), Type: cmd.name, Stream: r.stream}
|
|
||||||
p.stamp("error", out, msg, r.stream)
|
|
||||||
} else {
|
|
||||||
msg = fmt.Sprintln(p.pname(p.Name, 5), ":", green.regular(cmd.endTxt), "in", magenta.regular(big.NewFloat(float64(time.Since(start).Seconds())).Text('f', 3), " s"))
|
|
||||||
out = BufferOut{Time: time.Now(), Text: cmd.name + " in " + big.NewFloat(float64(time.Since(start).Seconds())).Text('f', 3) + " s"}
|
|
||||||
p.stamp("log", out, msg, r.stream)
|
|
||||||
}
|
|
||||||
return r.err
|
|
||||||
case <-stop:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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) tool(stop <-chan bool, path string) error {
|
|
||||||
if len(path) > 0 {
|
|
||||||
done := make(chan bool)
|
|
||||||
result := make(chan tool)
|
|
||||||
go func() {
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
for _, element := range p.tools {
|
|
||||||
// no need a sequence, these commands can be asynchronous
|
|
||||||
if element.status {
|
|
||||||
wg.Add(1)
|
|
||||||
p.goTool(&wg, stop, result, path, element)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
wg.Wait()
|
|
||||||
close(done)
|
|
||||||
}()
|
|
||||||
loop:
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case tool := <-result:
|
|
||||||
if tool.err != "" {
|
|
||||||
msg = fmt.Sprintln(p.pname(p.Name, 2), ":", red.bold(tool.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: tool.name, Stream: tool.err}
|
|
||||||
p.stamp("error", buff, msg, tool.err)
|
|
||||||
} else if tool.out != "" {
|
|
||||||
msg = fmt.Sprintln(p.pname(p.Name, 3), ":", red.bold(tool.name), red.regular("outputs"), ":", blue.bold(path))
|
|
||||||
buff := BufferOut{Time: time.Now(), Text: "outputs", Path: path, Type: tool.name, Stream: tool.out}
|
|
||||||
p.stamp("out", buff, msg, tool.out)
|
|
||||||
}
|
|
||||||
case <-done:
|
|
||||||
break loop
|
|
||||||
case <-stop:
|
|
||||||
break loop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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))...)
|
|
||||||
absolutePath, _ := filepath.Abs(filepath.Join(s...))
|
|
||||||
if path == absolutePath || strings.HasPrefix(path, absolutePath+string(os.PathSeparator)) {
|
|
||||||
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++
|
|
||||||
}
|
|
||||||
if p.Watcher.Preview {
|
|
||||||
log.Println(p.pname(p.Name, 1), ":", path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print on files, cli, ws
|
|
||||||
func (p *Project) stamp(t string, o BufferOut, msg string, stream string) {
|
|
||||||
switch t {
|
|
||||||
case "out":
|
|
||||||
p.Buffer.StdOut = append(p.Buffer.StdOut, o)
|
|
||||||
if p.Files.Outputs.Status {
|
|
||||||
f := p.create(p.Path, p.Files.Outputs.Name)
|
|
||||||
t := time.Now()
|
|
||||||
s := []string{t.Format("2006-01-02 15:04:05"), strings.ToUpper(p.Name), ":", o.Text, "\r\n"}
|
|
||||||
if _, err := f.WriteString(strings.Join(s, " ")); err != nil {
|
|
||||||
p.fatal(err, "")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case "log":
|
|
||||||
p.Buffer.StdLog = append(p.Buffer.StdLog, o)
|
|
||||||
if p.Files.Logs.Status {
|
|
||||||
f := p.create(p.Path, p.Files.Logs.Name)
|
|
||||||
t := time.Now()
|
|
||||||
s := []string{t.Format("2006-01-02 15:04:05"), strings.ToUpper(p.Name), ":", o.Text, "\r\n"}
|
|
||||||
if stream != "" {
|
|
||||||
s = []string{t.Format("2006-01-02 15:04:05"), strings.ToUpper(p.Name), ":", o.Text, "\r\n", stream}
|
|
||||||
}
|
|
||||||
if _, err := f.WriteString(strings.Join(s, " ")); err != nil {
|
|
||||||
p.fatal(err, "")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case "error":
|
|
||||||
p.Buffer.StdErr = append(p.Buffer.StdErr, o)
|
|
||||||
if p.Files.Errors.Status {
|
|
||||||
f := p.create(p.Path, p.Files.Errors.Name)
|
|
||||||
t := time.Now()
|
|
||||||
s := []string{t.Format("2006-01-02 15:04:05"), strings.ToUpper(p.Name), ":", o.Type, o.Text, o.Path, "\r\n"}
|
|
||||||
if stream != "" {
|
|
||||||
s = []string{t.Format("2006-01-02 15:04:05"), strings.ToUpper(p.Name), ":", o.Type, o.Text, o.Path, "\r\n", stream}
|
|
||||||
}
|
|
||||||
if _, err := f.WriteString(strings.Join(s, " ")); err != nil {
|
|
||||||
p.fatal(err, "")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if msg != "" {
|
|
||||||
log.Print(msg)
|
|
||||||
}
|
|
||||||
if stream != "" {
|
|
||||||
fmt.Fprint(output, stream)
|
|
||||||
}
|
|
||||||
go func() {
|
|
||||||
p.parent.sync <- "sync"
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Routines launches the toolchain run, build, install
|
|
||||||
func (p *Project) routines(stop <-chan bool, watcher FileWatcher, path string) {
|
|
||||||
var done bool
|
|
||||||
var install, build error
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-stop:
|
|
||||||
done = true
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
if done {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// before command
|
|
||||||
p.cmd(stop, "before", false)
|
|
||||||
if done {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Go supported tools
|
|
||||||
p.tool(stop, path)
|
|
||||||
// Prevent fake events on polling startup
|
|
||||||
p.init = true
|
|
||||||
// prevent errors using realize without config with only run flag
|
|
||||||
if p.Cmds.Run && !p.Cmds.Install.Status && !p.Cmds.Build.Status {
|
|
||||||
p.Cmds.Install.Status = true
|
|
||||||
}
|
|
||||||
if done {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
install = p.compile(stop, p.Cmds.Install)
|
|
||||||
if done {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
build = p.compile(stop, p.Cmds.Build)
|
|
||||||
if done {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if install == nil && build == nil && p.Cmds.Run {
|
|
||||||
var start time.Time
|
|
||||||
runner := make(chan bool, 1)
|
|
||||||
go func() {
|
|
||||||
log.Println(p.pname(p.Name, 1), ":", "Running..")
|
|
||||||
start = time.Now()
|
|
||||||
p.goRun(stop, runner)
|
|
||||||
}()
|
|
||||||
select {
|
|
||||||
case <-runner:
|
|
||||||
msg = fmt.Sprintln(p.pname(p.Name, 5), ":", green.regular("Started"), "in", magenta.regular(big.NewFloat(float64(time.Since(start).Seconds())).Text('f', 3), " s"))
|
|
||||||
out = BufferOut{Time: time.Now(), Text: "Started in " + big.NewFloat(float64(time.Since(start).Seconds())).Text('f', 3) + " s"}
|
|
||||||
p.stamp("log", out, msg, "")
|
|
||||||
case <-stop:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if done {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
p.cmd(stop, "after", false)
|
|
||||||
}
|
|
|
@ -1,73 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
type fileWatcherMock struct {
|
|
||||||
FileWatcher
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *fileWatcherMock) Walk(path string, _ bool) string {
|
|
||||||
return path
|
|
||||||
}
|
|
||||||
|
|
||||||
type fileInfoMock struct {
|
|
||||||
os.FileInfo
|
|
||||||
FileIsDir bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *fileInfoMock) IsDir() bool { return m.FileIsDir }
|
|
||||||
|
|
||||||
func TestWalk(t *testing.T) {
|
|
||||||
p := Project{
|
|
||||||
Name: "Test Project",
|
|
||||||
Watcher: Watch{
|
|
||||||
Paths: []string{"/"},
|
|
||||||
Ignore: []string{"vendor"},
|
|
||||||
Exts: []string{"go"},
|
|
||||||
},
|
|
||||||
Path: "/go/project",
|
|
||||||
watcher: &fileWatcherMock{},
|
|
||||||
init: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
files := []struct {
|
|
||||||
Path string
|
|
||||||
IsDir bool
|
|
||||||
}{
|
|
||||||
// valid files
|
|
||||||
{"/go/project", true},
|
|
||||||
{"/go/project/main.go", false},
|
|
||||||
{"/go/project/main_test.go", false},
|
|
||||||
{"/go/project/vendorish/foo", true},
|
|
||||||
// invalid relative path
|
|
||||||
{"./relative/path", true},
|
|
||||||
{"./relative/path/file.go", false},
|
|
||||||
// invalid extension
|
|
||||||
{"/go/project/settings.yaml", false},
|
|
||||||
// invalid vendor files
|
|
||||||
{"/go/project/vendor/foo", true},
|
|
||||||
{"/go/project/vendor/foo/main.go", false},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, file := range files {
|
|
||||||
fileInfo := fileInfoMock{
|
|
||||||
FileIsDir: file.IsDir,
|
|
||||||
}
|
|
||||||
err := p.walk(file.Path, &fileInfo, nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Error not expected: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if p.files != 2 {
|
|
||||||
t.Errorf("Exepeted %d files, but was %d", 2, p.files)
|
|
||||||
}
|
|
||||||
|
|
||||||
if p.folders != 2 {
|
|
||||||
t.Errorf("Exepeted %d folders, but was %d", 2, p.folders)
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue