diff --git a/.travis.yml b/.travis.yml index 56bfc5c..4fc1614 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,4 +12,5 @@ install: - go get ./... script: - - go install . \ No newline at end of file + - go install . + - go test -v ./... \ No newline at end of file diff --git a/README.md b/README.md index 1f8b4cd..44bcfbf 100644 --- a/README.md +++ b/README.md @@ -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. +

+#### 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 diff --git a/realize.go b/realize.go index 5f9cb92..8353be0 100644 --- a/realize.go +++ b/realize.go @@ -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) diff --git a/server/main.go b/server/main.go index 0e3c726..ca3ef36 100644 --- a/server/main.go +++ b/server/main.go @@ -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() diff --git a/server/main_test.go b/server/main_test.go new file mode 100644 index 0000000..346b3b0 --- /dev/null +++ b/server/main_test.go @@ -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) + } + } +} diff --git a/server/open.go b/server/open.go index 25a436c..57cc62f 100644 --- a/server/open.go +++ b/server/open.go @@ -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 } diff --git a/server/open_test.go b/server/open_test.go new file mode 100644 index 0000000..46cd29d --- /dev/null +++ b/server/open_test.go @@ -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) +// } +//} diff --git a/settings/flimit_test.go b/settings/flimit_test.go new file mode 100644 index 0000000..4a50752 --- /dev/null +++ b/settings/flimit_test.go @@ -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) + } +} diff --git a/settings/io.go b/settings/io.go index d622ee8..2124117 100644 --- a/settings/io.go +++ b/settings/io.go @@ -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 } diff --git a/settings/io_test.go b/settings/io_test.go new file mode 100755 index 0000000..a45d6e0 --- /dev/null +++ b/settings/io_test.go @@ -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()) +} diff --git a/settings/settings.go b/settings/settings.go index 450b5c1..2812352 100644 --- a/settings/settings.go +++ b/settings/settings.go @@ -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 } diff --git a/settings/settings_test.go b/settings/settings_test.go new file mode 100644 index 0000000..17a2c77 --- /dev/null +++ b/settings/settings_test.go @@ -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)) +} diff --git a/settings/utils.go b/settings/utils.go index 88ee87a..bf2a41a 100644 --- a/settings/utils.go +++ b/settings/utils.go @@ -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) diff --git a/settings/utils_test.go b/settings/utils_test.go new file mode 100644 index 0000000..5a00f45 --- /dev/null +++ b/settings/utils_test.go @@ -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) + } + +} diff --git a/style/style_test.go b/style/style_test.go new file mode 100644 index 0000000..7c15d26 --- /dev/null +++ b/style/style_test.go @@ -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) + } +} diff --git a/watcher/cmd.go b/watcher/cmd.go index 673f040..276508d 100644 --- a/watcher/cmd.go +++ b/watcher/cmd.go @@ -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 { diff --git a/watcher/main.go b/watcher/main.go index 79eb154..fd57bd8 100644 --- a/watcher/main.go +++ b/watcher/main.go @@ -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"` diff --git a/watcher/utils.go b/watcher/utils.go index 4509080..714688e 100644 --- a/watcher/utils.go +++ b/watcher/utils.go @@ -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] } diff --git a/watcher/utils_test.go b/watcher/utils_test.go new file mode 100644 index 0000000..2158caa --- /dev/null +++ b/watcher/utils_test.go @@ -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) + } +}