Merge pull request #82 from tockins/dev

1.4.1 remastered
This commit is contained in:
Alessio Pracchia 2017-08-24 16:33:46 +02:00 committed by GitHub
commit 084aa2d5ac
19 changed files with 430 additions and 83 deletions

View File

@ -12,4 +12,5 @@ install:
- go get ./...
script:
- go install .
- go install .
- go test -v ./...

View File

@ -15,52 +15,59 @@
#### Realize is the Go tool that is focused to speed up and improve developers workflow.
Automate your work pipeline, integrate additional tools of third party, define custom cli commands and reload projects at each changed without stop to write code.
Automate the most recurring operations needed for development, define what you need only one time, integrate additional tools of third party, define custom cli commands and reload projects at each file change without stop to write code.
Various operations can be programmed for each project, which can be executed at startup, at stop, and at each file change.
<p align="center">
<img src="http://i.imgur.com/KpMSLnE.png">
</p>
#### Wiki
- [Features list](#features)
- [Getting Started](#installation)
- [Config sample](#config-sample) - Sample config file
- [Run cmd](#run) - Run a project
- [Add cmd](#add) - Add a new project
- [Init cmd](#init) - Make a custom config step by step
- [Remove cmd](#remove) - Remove a project
- [List cmd](#list) - List the projects
- [Support](#support-us-and-suggest-an-improvement)
- [Backers and Sponsors](#backers)
#### Features
- Highly customizable
- Setup step by step
- Live reload
- Support for multiple projects
- Save logs on files
- Web panel for a smart view
- Build, install, run, test, fmt, generate, vet and much more
- Watch custom paths and specific file extensions
- Multiple watching methods (Polling, File watcher)
- Docker support
- Two watcher types: file system and polling
- Logs and errors files
- Projects setup step by step
- After/Before custom commands
- Custom environment variables
- Multiple projects at the same time
- Custom arguments to pass at each project
- Docker support (only with polling watcher)
- Live reload on file change (extensions and paths customizable)
- Support for most go commands (install, build, run, vet, test, fmt and much more)
- Web panel for a smart control of the workflow
v 1.5
- [ ] Use cases
- [ ] Tests
- [ ] Watch gopath dependencies
- [ ] Web panel, download logs
- [ ] Multiple configurations (dev, production)
- [ ] Support to ignore paths and files in gititnore
- [ ] Input redirection (wait for an input and redirect)
#### Wiki
- [Getting Started](#installation)
- [Run cmd](#run) - Run a project
- [Add cmd](#add) - Add a new project
- [Init cmd](#init) - Make a custom config step by step
- [Remove cmd](#remove) - Remove a project
- [List cmd](#list) - List the projects
- [Config sample](#config-sample) - Sample config file
- [Support](#support-us-and-suggest-an-improvement)
- [Backers and Sponsors](#backers)
#### Installation
Run this to get/install:
```
$ go get github.com/tockins/realize
```
#### Commands
#### Commands available
- ##### Run
From project/projects root execute:
@ -148,10 +155,10 @@ $ go get github.com/tockins/realize
```
settings:
legacy:
status: true // legacy watch status
legacy:
status: true // enable polling watcher instead fsnotifiy
interval: 10s // polling interval
resources: // files names related to streams
resources: // files names
outputs: outputs.log
logs: logs.log
errors: errors.log
@ -163,10 +170,10 @@ $ go get github.com/tockins/realize
projects:
- name: coin
path: coin // project path
environment: // env variables
environment: // env variables available at startup
test: test
myvar: value
commands:
commands: // go commands supported
vet: true
fmt: true
test: false
@ -175,10 +182,10 @@ $ go get github.com/tockins/realize
status: true
build:
status: false
args:
args: // additional params for the command
- -race
run: true
args:
args: // arguments to pass at the project
- --myarg
watcher:
preview: false // watched files preview
@ -188,16 +195,15 @@ $ go get github.com/tockins/realize
- vendor
exts: // watched extensions
- .go
scripts:
- type: before // type
scripts: // custom scripts
- type: before // type (after/before)
command: ./ls -l // command
changed: true // relaunch when a file changes
changed: true // relaunch when a file change
startup: true // launch at start
- type: after
command: ./ls
changed: true
streams: // enable/disable streams
cli_out: true
streams: // save logs/errors/outputs on files
file_out: false
file_log: false
file_err: false

View File

@ -36,7 +36,6 @@ func main() {
Server server.Server `yaml:"-"`
Projects *[]watcher.Project `yaml:"projects" json:"projects"`
}
var r realize
// Before of every exec of a cli method
before := func(*cli.Context) error {
@ -76,9 +75,8 @@ func main() {
r.Projects = &r.Blueprint.Projects
// read if exist
if err := r.Read(&r); err != nil {
return err
}
r.Read(&r)
// increase the file limit
if r.Config.Flimit != 0 {
if err := r.Flimit(); err != nil {
@ -87,7 +85,6 @@ func main() {
}
return nil
}
app := &cli.App{
Name: "Realize",
Version: appVersion,
@ -136,6 +133,7 @@ func main() {
return err
}
}
if err := r.Server.Start(p); err != nil {
return err
}
@ -196,7 +194,7 @@ func main() {
Questions: []*interact.Question{
{
Before: func(d interact.Context) error {
if _, err := os.Stat(settings.Dir + config); err != nil {
if _, err := os.Stat(settings.Directory + config); err != nil {
d.Skip()
}
d.SetDef(false, style.Green.Regular("(n)"))
@ -894,7 +892,7 @@ func main() {
},
After: func(d interact.Context) error {
if val, _ := d.Qns().Get(0).Ans().Bool(); val {
actErr = r.Settings.Remove()
actErr = r.Settings.Remove(settings.Directory)
if actErr != nil {
return actErr
}
@ -946,7 +944,7 @@ func main() {
Aliases: []string{"c"},
Description: "Remove realize folder.",
Action: func(p *cli.Context) error {
if err := r.Settings.Remove(); err != nil {
if err := r.Settings.Remove(settings.Directory); err != nil {
return err
}
fmt.Println(style.Yellow.Bold("[")+"REALIZE"+style.Yellow.Bold("]"), style.Green.Bold("Realize folder successfully removed."))
@ -956,7 +954,6 @@ func main() {
},
},
}
if err := app.Run(os.Args); err != nil {
fmt.Println(style.Red.Bold(err))
os.Exit(1)

View File

@ -117,16 +117,15 @@ func (s *Server) Start(p *cli.Context) (err error) {
e.GET("/ws", s.projects)
go e.Start(string(s.Settings.Server.Host) + ":" + strconv.Itoa(s.Settings.Server.Port))
if s.Open {
_, err = Open("http://" + string(s.Settings.Server.Host) + ":" + strconv.Itoa(s.Settings.Server.Port))
if err != nil {
return err
}
_, err = s.OpenURL("http://" + string(s.Settings.Server.Host) + ":" + strconv.Itoa(s.Settings.Server.Port))
if err != nil {
return err
}
}
return nil
}
// Websocket projects
func (s *Server) projects(c echo.Context) error {
websocket.Handler(func(ws *websocket.Conn) {
defer ws.Close()

50
server/main_test.go Normal file
View File

@ -0,0 +1,50 @@
package server
import (
"github.com/tockins/realize/settings"
"net/http"
"testing"
)
func TestServer_Start(t *testing.T) {
s := settings.Settings{
Server: settings.Server{
Status: true,
Open: false,
Host: "localhost",
Port: 5000,
},
}
server := Server{
Settings: &s,
}
err := server.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)
}
}
}

View File

@ -21,17 +21,17 @@ func init() {
}
// Open a url in the default browser
func Open(url string) (io.Writer, error) {
goos := runtime.GOOS
open, err := cmd[goos]
if !err {
return nil, fmt.Errorf("operating system %q is not supported", goos)
func (s *Server) OpenURL(url string) (io.Writer, error) {
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
}
}
cmd := exec.Command(open, url)
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
return cmd.Stderr, err
}
return nil, nil
}

28
server/open_test.go Normal file
View File

@ -0,0 +1,28 @@
package server
//import (
// "testing"
// //"fmt"
// "github.com/tockins/realize/settings"
// "fmt"
//)
//
//func TestOpen(t *testing.T) {
// config := settings.Settings{
// Server: settings.Server{
// Open: true,
// },
// }
// s := Server{
// Settings: &config,
// }
// url := "open_test"
// out, err := s.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)
// }
//}

13
settings/flimit_test.go Normal file
View File

@ -0,0 +1,13 @@
package settings
import (
"testing"
)
func TestSettings_Flimit(t *testing.T) {
s := Settings{}
s.Config.Flimit = 100
if err := s.Flimit(); err != nil {
t.Fatal("Unable to increase limit", err)
}
}

View File

@ -17,21 +17,21 @@ func (s Settings) Stream(file string) ([]byte, error) {
return content, err
}
// Write a file given a name and a byte stream
// Write a file
func (s Settings) Write(name string, data []byte) error {
err := ioutil.WriteFile(name, data, 0655)
err := ioutil.WriteFile(name, data, Permission)
return s.Validate(err)
}
// Create a new file and return its pointer
func (s Settings) Create(path string, name string) *os.File {
var file string
if _, err := os.Stat(Dir); err == nil {
file = filepath.Join(path, Dir, name)
if _, err := os.Stat(Directory); err == nil {
file = filepath.Join(path, Directory, name)
} else {
file = filepath.Join(path, name)
}
out, err := os.OpenFile(file, os.O_APPEND|os.O_WRONLY|os.O_CREATE|os.O_SYNC, 0655)
out, err := os.OpenFile(file, os.O_APPEND|os.O_WRONLY|os.O_CREATE|os.O_SYNC, Permission)
s.Validate(err)
return out
}

43
settings/io_test.go Executable file
View File

@ -0,0 +1,43 @@
package settings
import (
"github.com/labstack/gommon/random"
"io/ioutil"
"os"
"testing"
)
func TestSettings_Stream(t *testing.T) {
s := Settings{}
filename := random.String(4)
if _, err := s.Stream(filename); err == nil {
t.Fatal("Error expected, none found", filename, err)
}
filename = "io.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())
}

View File

@ -6,7 +6,11 @@ import (
"time"
)
var Dir = ".realize/"
// settings const
const (
Permission = 0775
Directory = ".realize/"
)
// Settings defines a group of general settings
type Settings struct {
@ -47,15 +51,16 @@ type Resources struct {
// Read from config file
func (s *Settings) Read(out interface{}) error {
localConfigPath := s.Resources.Config
if _, err := os.Stat(Dir + s.Resources.Config); err == nil {
localConfigPath = Dir + s.Resources.Config
// backward compatibility
if _, err := os.Stat(Directory + s.Resources.Config); err == nil {
localConfigPath = Directory + s.Resources.Config
}
content, err := s.Stream(localConfigPath)
if err == nil {
err = yaml.Unmarshal(content, out)
return err
}
return nil
return err
}
// Record create and unmarshal the yaml config file
@ -65,20 +70,21 @@ func (s *Settings) Record(out interface{}) error {
if err != nil {
return err
}
if _, err := os.Stat(Dir); os.IsNotExist(err) {
if err = os.Mkdir(Dir, 0770); err != nil {
if _, err := os.Stat(Directory); os.IsNotExist(err) {
if err = os.Mkdir(Directory, Permission); err != nil {
return s.Write(s.Resources.Config, y)
}
}
return s.Write(Dir+s.Resources.Config, y)
return s.Write(Directory+s.Resources.Config, y)
}
return nil
}
// Remove realize folder
func (s *Settings) Remove() error {
if _, err := os.Stat(Dir); !os.IsNotExist(err) {
return os.RemoveAll(Dir)
func (s *Settings) Remove(d string) error {
_, err := os.Stat(d)
if !os.IsNotExist(err) {
return os.RemoveAll(d)
}
return nil
return err
}

51
settings/settings_test.go Normal file
View File

@ -0,0 +1,51 @@
package settings
import (
"io/ioutil"
"path/filepath"
"testing"
)
func TestSettings_Read(t *testing.T) {
s := Settings{}
var a interface{}
s.Resources.Config = "settings_b"
if err := s.Read(a); err == nil {
t.Fatal("Error unexpected", err)
}
s.Resources.Config = "settings_test.yaml"
d, err := ioutil.TempFile("", "settings_test.yaml")
if err != nil {
t.Fatal(err)
}
s.Resources.Config = d.Name()
if err := s.Read(a); err != nil {
t.Fatal("Error unexpected", 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_Record(t *testing.T) {
s := Settings{}
s.Resources.Config = "settings_test.yaml"
var a interface{}
if err := s.Record(a); err != nil {
t.Fatal(err)
}
s.Remove(filepath.Join(Directory, s.Resources.Config))
}

View File

@ -9,7 +9,7 @@ import (
"github.com/tockins/realize/style"
)
// Wdir return the current working directory
// Wdir return the current working Directory
func (s Settings) Wdir() string {
dir, err := os.Getwd()
s.Validate(err)

57
settings/utils_test.go Normal file
View File

@ -0,0 +1,57 @@
package settings
import (
"errors"
"github.com/labstack/gommon/random"
"os"
"path/filepath"
"strings"
"testing"
)
func TestSettings_Wdir(t *testing.T) {
s := Settings{}
expected, err := os.Getwd()
if err != nil {
t.Error(err)
}
result := s.Wdir()
if result != filepath.Base(expected) {
t.Error("Expected", filepath.Base(expected), "instead", result)
}
}
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_Name(t *testing.T) {
s := Settings{}
name := random.String(8)
path := random.String(5)
dir, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
result := s.Name(name, path)
if result != dir && result != filepath.Base(path) {
t.Fatal("Expected", dir, "or", filepath.Base(path), "instead", result)
}
}
func TestSettings_Path(t *testing.T) {
s := Settings{}
path := random.String(5)
expected := strings.Replace(filepath.Clean(path), "\\", "/", -1)
result := s.Path(path)
if result != expected {
t.Fatal("Expected", expected, "instead", result)
}
}

35
style/style_test.go Normal file
View File

@ -0,0 +1,35 @@
package style
import (
"bytes"
"fmt"
"testing"
)
func TestColorBase_Regular(t *testing.T) {
c := new(colorBase)
strs := []string{"a", "b", "c"}
input := make([]interface{}, len(strs))
for i, s := range strs {
input[i] = s
}
result := c.Regular(input)
expected := fmt.Sprint(input)
if !bytes.Equal([]byte(result), []byte(expected)) {
t.Error("Expected:", expected, "instead", result)
}
}
func TestColorBase_Bold(t *testing.T) {
c := new(colorBase)
strs := []string{"a", "b", "c"}
input := make([]interface{}, len(strs))
for i, s := range strs {
input[i] = s
}
result := c.Bold(input)
expected := fmt.Sprint(input)
if !bytes.Equal([]byte(result), []byte(expected)) {
t.Error("Expected:", expected, "instead", result)
}
}

View File

@ -17,6 +17,9 @@ func (h *Blueprint) Run(p *cli.Context) error {
// loop projects
wg.Add(len(h.Projects))
for k, element := range h.Projects {
if p.String("name") != "" && h.Projects[k].Name != p.String("name") {
continue
}
tools := tools{}
if element.Cmds.Fmt {
tools.Fmt = tool{
@ -60,7 +63,6 @@ func (h *Blueprint) Run(p *cli.Context) error {
h.Projects[k].Buffer.StdErr = append(h.Projects[k].Buffer.StdErr, BufferOut{Time: time.Now(), Text: err.Error(), Type: "Env error", Stream: ""})
}
}
if h.Legacy.Status {
go h.Projects[k].watchByPolling()
} else {

View File

@ -59,6 +59,7 @@ type tool struct {
name string
}
// Cmds go supported
type Cmds struct {
Vet bool `yaml:"vet" json:"vet"`
Fmt bool `yaml:"fmt" json:"fmt"`
@ -69,7 +70,7 @@ type Cmds struct {
Run bool `yaml:"run" json:"run"`
}
// Buildmode options
// Cmd buildmode options
type Cmd struct {
Status bool `yaml:"status" json:"status"`
Args []string `yaml:"args,omitempty" json:"args,omitempty"`

View File

@ -54,7 +54,6 @@ func getEnvPath(env string) string {
path := filepath.SplitList(os.Getenv(env))
if len(path) == 0 {
return ""
} else {
return path[0]
}
return path[0]
}

59
watcher/utils_test.go Normal file
View File

@ -0,0 +1,59 @@
package watcher
import (
"flag"
"gopkg.in/urfave/cli.v2"
"os"
"path/filepath"
"testing"
)
func TestArgsParam(t *testing.T) {
set := flag.NewFlagSet("test", 0)
set.Bool("myflag", false, "doc")
params := cli.NewContext(nil, set, nil)
set.Parse([]string{"--myflag", "bat", "baz"})
result := argsParam(params)
if len(result) != 2 {
t.Fatal("Expected 2 instead", len(result))
}
}
func TestDuplicates(t *testing.T) {
projects := []Project{
{
Name: "a",
}, {
Name: "b",
}, {
Name: "c",
},
}
_, err := duplicates(projects[0], projects)
if err == nil {
t.Fatal("Error unexpected", err)
}
_, err = duplicates(Project{}, projects)
if err != nil {
t.Fatal("Error unexpected", err)
}
}
func TestInArray(t *testing.T) {
arr := []string{"a", "b", "c"}
if !inArray(arr[0], arr) {
t.Fatal("Unexpected", arr[0], "should be in", arr)
}
if inArray("d", arr) {
t.Fatal("Unexpected", "d", "shouldn't be in", arr)
}
}
func TestGetEnvPath(t *testing.T) {
expected := filepath.SplitList(os.Getenv("GOPATH"))[0]
result := getEnvPath("GOPATH")
if expected != result {
t.Fatal("Expected", expected, "instead", result)
}
}