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

@ -13,3 +13,4 @@ install:
script: 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. #### 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"> <p align="center">
<img src="http://i.imgur.com/KpMSLnE.png"> <img src="http://i.imgur.com/KpMSLnE.png">
</p> </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 #### Features
- Highly customizable - Two watcher types: file system and polling
- Setup step by step - Logs and errors files
- Live reload - Projects setup step by step
- Support for multiple projects - After/Before custom commands
- Save logs on files - Custom environment variables
- Web panel for a smart view - Multiple projects at the same time
- Build, install, run, test, fmt, generate, vet and much more - Custom arguments to pass at each project
- Watch custom paths and specific file extensions - Docker support (only with polling watcher)
- Multiple watching methods (Polling, File watcher) - Live reload on file change (extensions and paths customizable)
- Docker support - 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 v 1.5
- [ ] Use cases
- [ ] Tests
- [ ] Watch gopath dependencies - [ ] Watch gopath dependencies
- [ ] Web panel, download logs - [ ] Web panel, download logs
- [ ] Multiple configurations (dev, production) - [ ] Multiple configurations (dev, production)
- [ ] Support to ignore paths and files in gititnore - [ ] Support to ignore paths and files in gititnore
- [ ] Input redirection (wait for an input and redirect) - [ ] 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 #### Installation
Run this to get/install: Run this to get/install:
``` ```
$ go get github.com/tockins/realize $ go get github.com/tockins/realize
``` ```
#### Commands #### Commands available
- ##### Run - ##### Run
From project/projects root execute: From project/projects root execute:
@ -149,9 +156,9 @@ $ go get github.com/tockins/realize
``` ```
settings: settings:
legacy: legacy:
status: true // legacy watch status status: true // enable polling watcher instead fsnotifiy
interval: 10s // polling interval interval: 10s // polling interval
resources: // files names related to streams resources: // files names
outputs: outputs.log outputs: outputs.log
logs: logs.log logs: logs.log
errors: errors.log errors: errors.log
@ -163,10 +170,10 @@ $ go get github.com/tockins/realize
projects: projects:
- name: coin - name: coin
path: coin // project path path: coin // project path
environment: // env variables environment: // env variables available at startup
test: test test: test
myvar: value myvar: value
commands: commands: // go commands supported
vet: true vet: true
fmt: true fmt: true
test: false test: false
@ -175,10 +182,10 @@ $ go get github.com/tockins/realize
status: true status: true
build: build:
status: false status: false
args: args: // additional params for the command
- -race - -race
run: true run: true
args: args: // arguments to pass at the project
- --myarg - --myarg
watcher: watcher:
preview: false // watched files preview preview: false // watched files preview
@ -188,16 +195,15 @@ $ go get github.com/tockins/realize
- vendor - vendor
exts: // watched extensions exts: // watched extensions
- .go - .go
scripts: scripts: // custom scripts
- type: before // type - type: before // type (after/before)
command: ./ls -l // command command: ./ls -l // command
changed: true // relaunch when a file changes changed: true // relaunch when a file change
startup: true // launch at start startup: true // launch at start
- type: after - type: after
command: ./ls command: ./ls
changed: true changed: true
streams: // enable/disable streams streams: // save logs/errors/outputs on files
cli_out: true
file_out: false file_out: false
file_log: false file_log: false
file_err: false file_err: false

View File

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

View File

@ -117,16 +117,15 @@ func (s *Server) Start(p *cli.Context) (err error) {
e.GET("/ws", s.projects) e.GET("/ws", s.projects)
go e.Start(string(s.Settings.Server.Host) + ":" + strconv.Itoa(s.Settings.Server.Port)) go e.Start(string(s.Settings.Server.Host) + ":" + strconv.Itoa(s.Settings.Server.Port))
if s.Open { _, err = s.OpenURL("http://" + string(s.Settings.Server.Host) + ":" + strconv.Itoa(s.Settings.Server.Port))
_, err = Open("http://" + string(s.Settings.Server.Host) + ":" + strconv.Itoa(s.Settings.Server.Port)) if err != nil {
if err != nil { return err
return err
}
} }
} }
return nil return nil
} }
// Websocket projects
func (s *Server) projects(c echo.Context) error { func (s *Server) projects(c echo.Context) error {
websocket.Handler(func(ws *websocket.Conn) { websocket.Handler(func(ws *websocket.Conn) {
defer ws.Close() 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 // Open a url in the default browser
func Open(url string) (io.Writer, error) { func (s *Server) OpenURL(url string) (io.Writer, error) {
goos := runtime.GOOS if s.Open {
open, err := cmd[goos] open, err := cmd[runtime.GOOS]
if !err { if !err {
return nil, fmt.Errorf("operating system %q is not supported", goos) 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 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 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 { 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) 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 var file string
if _, err := os.Stat(Dir); err == nil { if _, err := os.Stat(Directory); err == nil {
file = filepath.Join(path, Dir, name) file = filepath.Join(path, Directory, name)
} else { } else {
file = filepath.Join(path, name) 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) s.Validate(err)
return out 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" "time"
) )
var Dir = ".realize/" // settings const
const (
Permission = 0775
Directory = ".realize/"
)
// Settings defines a group of general settings // Settings defines a group of general settings
type Settings struct { type Settings struct {
@ -47,15 +51,16 @@ type Resources struct {
// Read from config file // Read from config file
func (s *Settings) Read(out interface{}) error { func (s *Settings) Read(out interface{}) error {
localConfigPath := s.Resources.Config localConfigPath := s.Resources.Config
if _, err := os.Stat(Dir + s.Resources.Config); err == nil { // backward compatibility
localConfigPath = Dir + s.Resources.Config if _, err := os.Stat(Directory + s.Resources.Config); err == nil {
localConfigPath = Directory + s.Resources.Config
} }
content, err := s.Stream(localConfigPath) content, err := s.Stream(localConfigPath)
if err == nil { if err == nil {
err = yaml.Unmarshal(content, out) err = yaml.Unmarshal(content, out)
return err return err
} }
return nil return err
} }
// Record create and unmarshal the yaml config file // Record create and unmarshal the yaml config file
@ -65,20 +70,21 @@ func (s *Settings) Record(out interface{}) error {
if err != nil { if err != nil {
return err return err
} }
if _, err := os.Stat(Dir); os.IsNotExist(err) { if _, err := os.Stat(Directory); os.IsNotExist(err) {
if err = os.Mkdir(Dir, 0770); err != nil { if err = os.Mkdir(Directory, Permission); err != nil {
return s.Write(s.Resources.Config, y) 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 return nil
} }
// Remove realize folder // Remove realize folder
func (s *Settings) Remove() error { func (s *Settings) Remove(d string) error {
if _, err := os.Stat(Dir); !os.IsNotExist(err) { _, err := os.Stat(d)
return os.RemoveAll(Dir) 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" "github.com/tockins/realize/style"
) )
// Wdir return the current working directory // Wdir return the current working Directory
func (s Settings) Wdir() string { func (s Settings) Wdir() string {
dir, err := os.Getwd() dir, err := os.Getwd()
s.Validate(err) 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 // loop projects
wg.Add(len(h.Projects)) wg.Add(len(h.Projects))
for k, element := range h.Projects { for k, element := range h.Projects {
if p.String("name") != "" && h.Projects[k].Name != p.String("name") {
continue
}
tools := tools{} tools := tools{}
if element.Cmds.Fmt { if element.Cmds.Fmt {
tools.Fmt = tool{ 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: ""}) 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 { if h.Legacy.Status {
go h.Projects[k].watchByPolling() go h.Projects[k].watchByPolling()
} else { } else {

View File

@ -59,6 +59,7 @@ type tool struct {
name string name string
} }
// Cmds go supported
type Cmds struct { type Cmds struct {
Vet bool `yaml:"vet" json:"vet"` Vet bool `yaml:"vet" json:"vet"`
Fmt bool `yaml:"fmt" json:"fmt"` Fmt bool `yaml:"fmt" json:"fmt"`
@ -69,7 +70,7 @@ type Cmds struct {
Run bool `yaml:"run" json:"run"` Run bool `yaml:"run" json:"run"`
} }
// Buildmode options // Cmd buildmode options
type Cmd struct { type Cmd struct {
Status bool `yaml:"status" json:"status"` Status bool `yaml:"status" json:"status"`
Args []string `yaml:"args,omitempty" json:"args,omitempty"` 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)) path := filepath.SplitList(os.Getenv(env))
if len(path) == 0 { if len(path) == 0 {
return "" 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)
}
}