diff --git a/README.md b/README.md index 570cde1..6f35158 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,7 @@ A Go build system with file watchers, output streams and live reload. Run, build --name="Project Name" -> Name, if not specified takes the working directory name --path="server" -> Base Path, if not specified takes the working directory name --build -> Enables the build + --test -> Enables the tests --no-bin -> Disables the installation --no-run -> Disables the run --no-fmt -> Disables the fmt (go fmt) @@ -58,22 +59,20 @@ A Go build system with file watchers, output streams and live reload. Run, build ``` $ realize add - ``` - ``` + $ realize add --path="mypath" - ``` - ``` + $ realize add --name="My Project" --build - ``` - ``` + $ realize add --name="My Project" --path="/projects/package" --build - ``` - ``` + $ realize add --name="My Project" --path="projects/package" --build --no-run - ``` - - If you want, you can specify additional arguments for your project. + $ realize add --path="/Users/alessio/go/src/github.com/tockins/realize-examples/coin/" + ``` + + If you want, you can specify additional arguments for your project. + **The additional arguments must go after the options of "Realize"** ``` @@ -81,7 +80,7 @@ A Go build system with file watchers, output streams and live reload. Run, build $ realize add yourParams --yourFlags --path="/print/printer" --no-run // wrong ``` - + - Remove a project by its name ``` @@ -103,25 +102,28 @@ A Go build system with file watchers, output streams and live reload. Run, build ``` $ realize fast ``` - + The fast command supports the following custom parameters: ``` --build -> Enables the build + --test -> Enables the tests --no-bin -> Disables the installation --no-run -> Disables the run --no-fmt -> Disables the fmt (go fmt) --config -> Take the defined settings if exist a config file ``` - - The "fast" command supporst addittional arguments as the "add" command. + + The "fast" command supports addittional arguments as the "add" command. ``` $ realize fast --no-run yourParams --yourFlags // correct $ realize fast yourParams --yourFlags --no-run // wrong + + $ realize fast --path="/Users/alessio/go/src/github.com/tockins/realize-examples/coin/" ``` - + #### Color reference @@ -136,7 +138,6 @@ A Go build system with file watchers, output streams and live reload. Run, build - For more examples check [Realize Examples](https://github.com/tockins/realize-examples) ``` - version: "1.0" projects: - app_name: App One -> name app_path: one -> root path @@ -144,7 +145,8 @@ A Go build system with file watchers, output streams and live reload. Run, build app_bin: true -> enable/disable go install app_build: false -> enable/disable go build app_fmt: true -> enable/disable go fmt - app_params: + app_test: true -> enable/disable go test + app_params: -> the project will be launched with these parameters - --flag1 - param1 app_watcher: @@ -156,47 +158,20 @@ A Go build system with file watchers, output streams and live reload. Run, build - bin exts: -> file extensions to observe for live reload - .go - - app_name: App Two -> another project - app_path: two - app_run: true - app_build: true - app_bin: true - app_watcher: - paths: - - / - ignore_paths: - - vendor - - bin - exts: - - .go + output: -> enable/disable the output destinations + cli: true -> cli output + file: true -> creates an output file inside the project + ``` -#### Next releases - -#####Milestone 1.0 - -- [x] Cli start, remove, add, list, run -- [x] Remove duplicate projects -- [x] Support for multiple projects -- [x] Watcher files preview -- [x] Support for directories with duplicates names -- [ ] Go test support -- [x] Additional arguments -- [x] Go fmt support -- [x] Cli fast run -- [x] Execution times for build/install -- [x] Go doc -- [x] Support for server start/stop -- [x] Stream projects output -- [x] Cli feedback +#### Next release ##### Milestone 1.1 -- [ ] Test under windows -- [ ] Unit test -- [ ] Custom path on commands -- [ ] Output files -- [ ] Cli args -- [ ] Before/After command +- [ ] Testing on windows +- [x] Custom paths for the commands fast/add +- [x] Save output on a file +- [x] Enable the fields Before/After +- [ ] Web panel - **Maybe** #### Contacts @@ -204,4 +179,4 @@ A Go build system with file watchers, output streams and live reload. Run, build - Chat with us [Gitter](https://gitter.im/tockins/realize) - [Alessio Pracchia](https://www.linkedin.com/in/alessio-pracchia-38a70673) -- [Daniele Conventi](https://www.linkedin.com/in/daniele-conventi-b419b0a4) +- [Daniele Conventi](https://www.linkedin.com/in/conventi) diff --git a/cli/cmd.go b/cli/cmd.go new file mode 100644 index 0000000..3329caa --- /dev/null +++ b/cli/cmd.go @@ -0,0 +1,171 @@ +package cli + +import ( + "errors" + "fmt" + "gopkg.in/urfave/cli.v2" + "gopkg.in/yaml.v2" + "path/filepath" + "strings" +) + +// Watch method adds the given paths on the Watcher +func (h *Blueprint) Run() error { + err := h.Read() + if err == nil { + // loop projects + wg.Add(len(h.Projects)) + for k := range h.Projects { + go h.Projects[k].watching() + } + wg.Wait() + return nil + } + return err +} + +// Fast method run a project from his working directory without makes a config file +func (h *Blueprint) Fast(params *cli.Context) error { + fast := h.Projects[0] + // Takes the values from config if wd path match with someone else + if params.Bool("config") { + if err := h.Read(); err == nil { + for _, val := range h.Projects { + if fast.Path == val.Path { + fast = val + } + } + } + } + wg.Add(1) + go fast.watching() + wg.Wait() + return nil +} + +// Add a new project +func (h *Blueprint) Add(params *cli.Context) error { + p := Project{ + Name: nameFlag(params), + Path: filepath.Clean(params.String("path")), + Build: params.Bool("build"), + Bin: boolFlag(params.Bool("no-bin")), + Run: boolFlag(params.Bool("no-run")), + Fmt: boolFlag(params.Bool("no-fmt")), + Test: params.Bool("test"), + Params: argsParam(params), + Watcher: Watcher{ + Paths: []string{"/"}, + Ignore: []string{"vendor"}, + Exts: []string{".go"}, + Output: map[string]bool{ + "cli": true, + "file": false, + }, + }, + } + if _, err := duplicates(p, h.Projects); err != nil { + return err + } + h.Projects = append(h.Projects, p) + return nil +} + +// Clean duplicate projects +func (h *Blueprint) Clean() { + arr := h.Projects + for key, val := range arr { + if _, err := duplicates(val, arr[key+1:]); err != nil { + h.Projects = append(arr[:key], arr[key+1:]...) + break + } + } +} + +// Read, Check and remove duplicates from the config file +func (h *Blueprint) Read() error { + content, err := read(h.Files["config"]) + if err == nil { + err = yaml.Unmarshal(content, h) + if err == nil { + if len(h.Projects) > 0 { + h.Clean() + return nil + } + return errors.New("There are no projects!") + } + return err + } + return err +} + +// Create and unmarshal yaml config file +func (h *Blueprint) Create() error { + y, err := yaml.Marshal(h) + if err != nil { + return err + } + return write(h.Files["config"], y) +} + +// Inserts a new project in the list +func (h *Blueprint) Insert(params *cli.Context) error { + check := h.Read() + err := h.Add(params) + if err == nil { + err = h.Create() + if check == nil && err == nil { + fmt.Println(Green("Your project was successfully added")) + } else { + fmt.Println(Green("The config file was successfully created")) + } + } + return err +} + +// Remove a project +func (h *Blueprint) Remove(params *cli.Context) error { + err := h.Read() + if err == nil { + for key, val := range h.Projects { + if params.String("name") == val.Name { + h.Projects = append(h.Projects[:key], h.Projects[key+1:]...) + err = h.Create() + if err == nil { + fmt.Println(Green("Your project was successfully removed")) + } + return err + } + } + return errors.New("No project found") + } + return err +} + +// List of all the projects +func (h *Blueprint) List() error { + err := h.Read() + if err == nil { + for _, val := range h.Projects { + fmt.Println(Blue("|"), Blue(strings.ToUpper(val.Name))) + fmt.Println(MagentaS("|"), "\t", Yellow("Base Path"), ":", MagentaS(val.Path)) + fmt.Println(MagentaS("|"), "\t", Yellow("Run"), ":", MagentaS(val.Run)) + fmt.Println(MagentaS("|"), "\t", Yellow("Build"), ":", MagentaS(val.Build)) + fmt.Println(MagentaS("|"), "\t", Yellow("Install"), ":", MagentaS(val.Bin)) + fmt.Println(MagentaS("|"), "\t", Yellow("Fmt"), ":", MagentaS(val.Fmt)) + fmt.Println(MagentaS("|"), "\t", Yellow("Test"), ":", MagentaS(val.Test)) + fmt.Println(MagentaS("|"), "\t", Yellow("Params"), ":", MagentaS(val.Params)) + fmt.Println(MagentaS("|"), "\t", Yellow("Watcher"), ":") + fmt.Println(MagentaS("|"), "\t\t", Yellow("After"), ":", MagentaS(val.Watcher.After)) + fmt.Println(MagentaS("|"), "\t\t", Yellow("Before"), ":", MagentaS(val.Watcher.Before)) + fmt.Println(MagentaS("|"), "\t\t", Yellow("Extensions"), ":", MagentaS(val.Watcher.Exts)) + fmt.Println(MagentaS("|"), "\t\t", Yellow("Paths"), ":", MagentaS(val.Watcher.Paths)) + fmt.Println(MagentaS("|"), "\t\t", Yellow("Paths ignored"), ":", MagentaS(val.Watcher.Ignore)) + fmt.Println(MagentaS("|"), "\t\t", Yellow("Watch preview"), ":", MagentaS(val.Watcher.Preview)) + fmt.Println(MagentaS("|"), "\t\t", Yellow("Output"), ":") + fmt.Println(MagentaS("|"), "\t\t\t", Yellow("Cli"), ":", MagentaS(val.Watcher.Output["cli"])) + fmt.Println(MagentaS("|"), "\t\t\t", Yellow("File"), ":", MagentaS(val.Watcher.Output["file"])) + } + } + return err +} diff --git a/realize/project.go b/cli/exec.go similarity index 60% rename from realize/project.go rename to cli/exec.go index 43461c2..405af80 100644 --- a/realize/project.go +++ b/cli/exec.go @@ -1,4 +1,4 @@ -package realize +package cli import ( "bufio" @@ -8,24 +8,11 @@ import ( "os" "os/exec" "path/filepath" + "strings" "sync" "time" ) -// The Project struct defines the informations about a project -type Project struct { - reload time.Time - base string - Name string `yaml:"app_name,omitempty"` - Path string `yaml:"app_path,omitempty"` - Run bool `yaml:"app_run,omitempty"` - Bin bool `yaml:"app_bin,omitempty"` - Build bool `yaml:"app_build,omitempty"` - Fmt bool `yaml:"app_fmt,omitempty"` - Params []string `yaml:"app_params,omitempty"` - Watcher Watcher `yaml:"app_watcher,omitempty"` -} - // GoRun is an implementation of the bin execution func (p *Project) GoRun(channel chan bool, runner chan bool, wr *sync.WaitGroup) error { @@ -41,7 +28,7 @@ func (p *Project) GoRun(channel chan bool, runner chan bool, wr *sync.WaitGroup) if err := build.Process.Kill(); err != nil { log.Fatal(Red("Failed to stop: "), Red(err)) } - log.Println(pname(p.Name, 2), ":", RedS("Stopped")) + log.Println(pname(p.Name, 2), ":", RedS("Ended")) wr.Done() }() @@ -50,7 +37,6 @@ func (p *Project) GoRun(channel chan bool, runner chan bool, wr *sync.WaitGroup) // Read stdout and stderr in same var outputs := io.MultiReader(stdout, stderr) - if err != nil { log.Println(Red(err.Error())) return err @@ -66,7 +52,17 @@ func (p *Project) GoRun(channel chan bool, runner chan bool, wr *sync.WaitGroup) for in.Scan() { select { default: - log.Println(pname(p.Name, 3), ":", BlueS(in.Text())) + if p.Watcher.Output["cli"] { + log.Println(pname(p.Name, 3), ":", BlueS(in.Text())) + } + if p.Watcher.Output["file"] { + path := filepath.Join(p.base, Bp.Files["output"]) + f := create(path) + t := time.Now() + if _, err := f.WriteString(t.Format("2006-01-02 15:04:05") + " : " + in.Text() + "\r\n"); err != nil { + log.Fatal(err) + } + } } } close(stop) @@ -83,7 +79,7 @@ func (p *Project) GoRun(channel chan bool, runner chan bool, wr *sync.WaitGroup) } // GoBuild is an implementation of the "go build" -func (p *Project) GoBuild() (error, string) { +func (p *Project) GoBuild() (string, error) { var out bytes.Buffer var stderr bytes.Buffer build := exec.Command("go", "build") @@ -91,27 +87,27 @@ func (p *Project) GoBuild() (error, string) { build.Stdout = &out build.Stderr = &stderr if err := build.Run(); err != nil { - return err, stderr.String() + return stderr.String(), err } - return nil, "" + return "", nil } // GoInstall is an implementation of the "go install" -func (p *Project) GoInstall() (error, string) { +func (p *Project) GoInstall() (string, error) { var out bytes.Buffer var stderr bytes.Buffer err := os.Setenv("GOBIN", filepath.Join(os.Getenv("GOPATH"), "bin")) if err != nil { - return nil, "" + return "", nil } build := exec.Command("go", "install") build.Dir = p.base build.Stdout = &out build.Stderr = &stderr if err := build.Run(); err != nil { - return err, stderr.String() + return stderr.String(), err } - return nil, "" + return "", nil } // GoFmt is an implementation of the gofmt @@ -126,3 +122,30 @@ func (p *Project) GoFmt(path string) (io.Writer, error) { } return nil, nil } + +// GoTest is an implementation of the go test +func (p *Project) GoTest(path string) (io.Writer, error) { + var out bytes.Buffer + build := exec.Command("go", "test") + build.Dir = path + build.Stdout = &out + build.Stderr = &out + if err := build.Run(); err != nil { + return build.Stdout, err + } + return nil, nil +} + +// Cmd exec a list of defined commands +func (p *Project) Cmd(cmds []string) (errors []error) { + for _, cmd := range cmds { + cmd := strings.Replace(strings.Replace(cmd, "'", "", -1), "\"", "", -1) + c := strings.Split(cmd, " ") + build := exec.Command(c[0], c[1:]...) + build.Dir = p.base + if err := build.Run(); err != nil { + errors = append(errors, err) + } + } + return errors +} diff --git a/cli/main.go b/cli/main.go new file mode 100644 index 0000000..e1cf7f9 --- /dev/null +++ b/cli/main.go @@ -0,0 +1,62 @@ +package cli + +import ( + "github.com/fatih/color" + "log" + "sync" + "time" +) + +var Bp *Blueprint + +var wg sync.WaitGroup + +// Green, Red Bold, Red, Blue, Blue Bold, Yellow, Yellow Bold, Magenta, Magenta Bold colors +var Green, Red, RedS, Blue, BlueS, Yellow, YellowS, Magenta, MagentaS = color.New(color.FgGreen, color.Bold).SprintFunc(), + color.New(color.FgRed, color.Bold).SprintFunc(), + color.New(color.FgRed).SprintFunc(), + color.New(color.FgBlue, color.Bold).SprintFunc(), + color.New(color.FgBlue).SprintFunc(), + color.New(color.FgYellow, color.Bold).SprintFunc(), + color.New(color.FgYellow).SprintFunc(), + color.New(color.FgMagenta, color.Bold).SprintFunc(), + color.New(color.FgMagenta).SprintFunc() + +// Projects struct contains a projects list +type Blueprint struct { + Projects []Project `yaml:"Projects,omitempty"` + Files map[string]string `yaml:"-"` +} + +// Project defines the informations of a single project +type Project struct { + reload time.Time + base string + Name string `yaml:"app_name,omitempty"` + Path string `yaml:"app_path,omitempty"` + Run bool `yaml:"app_run,omitempty"` + Bin bool `yaml:"app_bin,omitempty"` + Build bool `yaml:"app_build,omitempty"` + Fmt bool `yaml:"app_fmt,omitempty"` + Test bool `yaml:"app_test,omitempty"` + Params []string `yaml:"app_params,omitempty"` + Watcher Watcher `yaml:"app_watcher,omitempty"` +} + +// Watcher struct defines the livereload's logic +type Watcher struct { + // different before and after on re-run? + Before []string `yaml:"before,omitempty"` + After []string `yaml:"after,omitempty"` + Paths []string `yaml:"paths,omitempty"` + Ignore []string `yaml:"ignore_paths,omitempty"` + Exts []string `yaml:"exts,omitempty"` + Preview bool `yaml:"preview,omitempty"` + Output map[string]bool `yaml:"output,omitempty"` +} + +// Initialize the application +func init() { + log.SetFlags(0) + log.SetOutput(new(logWriter)) +} diff --git a/cli/utils.go b/cli/utils.go new file mode 100644 index 0000000..ef3601a --- /dev/null +++ b/cli/utils.go @@ -0,0 +1,139 @@ +package cli + +import ( + "errors" + "fmt" + "gopkg.in/urfave/cli.v2" + "io/ioutil" + "log" + "os" + "path/filepath" + "strings" + "time" +) + +// Wdir returns the name last element of the working directory path +func Wdir() string { + dir, err := os.Getwd() + if err != nil { + log.Fatal(Red(err)) + } + return filepath.Base(dir) +} + +// Read a file given a name and return its byte stream +func read(file string) ([]byte, error) { + _, err := os.Stat(file) + if err == nil { + content, err := ioutil.ReadFile(file) + if err != nil { + return nil, err + } + return content, err + } + return nil, err + +} + +// Write a file given a name and a byte stream +func write(name string, data []byte) error { + err := ioutil.WriteFile(name, data, 0655) + if err != nil { + log.Fatal(Red(err)) + return err + } + return nil +} + +// Create a new file and return its pointer +func create(file string) *os.File { + out, err := os.OpenFile(file, os.O_APPEND|os.O_WRONLY|os.O_CREATE|os.O_SYNC, 0655) + if err != nil { + log.Fatal(err) + } + return out +} + +// argsParam parse one by one the given argumentes +func argsParam(params *cli.Context) []string { + argsN := params.NArg() + if argsN > 0 { + var args []string + for i := 0; i <= argsN-1; i++ { + args = append(args, params.Args().Get(i)) + } + return args + } + return nil +} + +// NameParam check the project name presence. If empty takes the working directory name +func nameFlag(params *cli.Context) string { + var name string + if params.String("name") == "" && params.String("path") == "" { + return Wdir() + } else if params.String("path") != "/" { + name = filepath.Base(params.String("path")) + } else { + name = params.String("name") + } + return name +} + +// BoolParam is used to check the presence of a bool flag +func boolFlag(b bool) bool { + if b { + return false + } + return true +} + +// Duplicates check projects with same name or same combinations of main/path +func duplicates(value Project, arr []Project) (Project, error) { + for _, val := range arr { + if value.Path == val.Path || value.Name == val.Name { + return val, errors.New("There is a duplicate of '" + val.Name + "'. Check your config file!") + } + } + return Project{}, nil +} + +// check if a string is inArray +func inArray(str string, list []string) bool { + for _, v := range list { + if v == str { + return true + } + } + return false +} + +// Defines the colors scheme for the project name +func pname(name string, color int) string { + switch color { + case 1: + name = Yellow("[") + strings.ToUpper(name) + Yellow("]") + break + case 2: + name = Yellow("[") + Red(strings.ToUpper(name)) + Yellow("]") + break + case 3: + name = Yellow("[") + Blue(strings.ToUpper(name)) + Yellow("]") + break + case 4: + name = Yellow("[") + Magenta(strings.ToUpper(name)) + Yellow("]") + break + case 5: + name = Yellow("[") + Green(strings.ToUpper(name)) + Yellow("]") + break + } + return name +} + +// Log struct +type logWriter struct{} + +// Cewrites the log timestamp +func (writer logWriter) Write(bytes []byte) (int, error) { + return fmt.Print(YellowS("[") + time.Now().Format("15:04:05") + YellowS("]") + string(bytes)) +} diff --git a/realize/watcher.go b/cli/watcher.go similarity index 64% rename from realize/watcher.go rename to cli/watcher.go index a962dea..334d06b 100644 --- a/realize/watcher.go +++ b/cli/watcher.go @@ -1,63 +1,20 @@ -package realize +package cli import ( + "errors" "fmt" "github.com/fsnotify/fsnotify" - "gopkg.in/urfave/cli.v2" "log" "math/big" "os" + "os/signal" "path/filepath" "strings" "sync" + "syscall" "time" ) -// The Watcher struct defines the livereload's logic -type Watcher struct { - // different before and after on re-run? - Before []string `yaml:"before,omitempty"` - After []string `yaml:"after,omitempty"` - Paths []string `yaml:"paths,omitempty"` - Ignore []string `yaml:"ignore_paths,omitempty"` - Exts []string `yaml:"exts,omitempty"` - Preview bool `yaml:"preview,omitempty"` -} - -// Watch method adds the given paths on the Watcher -func (h *Config) Watch() error { - err := h.Read() - if err == nil { - // loop projects - wg.Add(len(h.Projects)) - for k := range h.Projects { - go h.Projects[k].watching() - } - wg.Wait() - return nil - } - return err -} - -// Fast method run a project from his working directory without makes a config file -func (h *Config) Fast(params *cli.Context) error { - fast := h.Projects[0] - // Takes the values from config if wd path match with someone else - if params.Bool("config") { - if err := h.Read(); err == nil { - for _, val := range h.Projects { - if fast.Path == val.Path { - fast = val - } - } - } - } - wg.Add(1) - go fast.watching() - wg.Wait() - return nil -} - // Watching method is the main core. It manages the livereload and the watching func (p *Project) watching() { @@ -78,8 +35,14 @@ func (p *Project) watching() { } defer end() - p.walks(watcher) - go routines(p, channel, &wr) + p.cmd() + err = p.walks(watcher) + if err != nil { + fmt.Println(pname(p.Name, 1), ":", Red(err.Error())) + return + } + + go p.routines(channel, &wr) p.reload = time.Now().Truncate(time.Second) // waiting for an event @@ -101,7 +64,7 @@ func (p *Project) watching() { i := strings.Index(event.Name, filepath.Ext(event.Name)) if event.Name[:i] != "" && inArray(ext, p.Watcher.Exts) { - log.Println(pname(p.Name, 4), ":", Magenta(event.Name[:i]+ext)) + fmt.Println(pname(p.Name, 4), Magenta(strings.ToUpper(ext[1:])+" changed"), Magenta(event.Name[:i]+ext)) // stop and run again if p.Run { close(channel) @@ -112,7 +75,7 @@ func (p *Project) watching() { if err != nil { log.Fatal(Red(err)) } else { - go routines(p, channel, &wr) + go p.routines(channel, &wr) p.reload = time.Now().Truncate(time.Second) } } @@ -129,11 +92,11 @@ func (p *Project) install(channel chan bool, wr *sync.WaitGroup) { if p.Bin { log.Println(pname(p.Name, 1), ":", "Installing..") start := time.Now() - if err, std := p.GoInstall(); err != nil { + if std, err := p.GoInstall(); err != nil { log.Println(pname(p.Name, 1), ":", fmt.Sprint(Red(err)), std) wr.Done() } else { - log.Println(pname(p.Name, 5), ":", Green("Installed")+" after", MagentaS(big.NewFloat(float64(time.Since(start).Seconds())).Text('f', 3), "s")) + log.Println(pname(p.Name, 5), ":", Green("Installed")+" after", MagentaS(big.NewFloat(float64(time.Since(start).Seconds())).Text('f', 3), " s")) if p.Run { runner := make(chan bool, 1) log.Println(pname(p.Name, 1), ":", "Running..") @@ -142,7 +105,7 @@ func (p *Project) install(channel chan bool, wr *sync.WaitGroup) { for { select { case <-runner: - log.Println(pname(p.Name, 5), ":", Green("Has been run")+" after", MagentaS(big.NewFloat(float64(time.Since(start).Seconds())).Text('f', 3), "s")) + log.Println(pname(p.Name, 5), ":", Green("Has been run")+" after", MagentaS(big.NewFloat(float64(time.Since(start).Seconds())).Text('f', 3), " s")) return } } @@ -157,29 +120,67 @@ func (p *Project) build() { if p.Build { log.Println(pname(p.Name, 1), ":", "Building..") start := time.Now() - if err, std := p.GoBuild(); err != nil { + if std, err := p.GoBuild(); err != nil { log.Println(pname(p.Name, 1), ":", fmt.Sprint(Red(err)), std) } else { - log.Println(pname(p.Name, 5), ":", Green("Builded")+" after", MagentaS(big.NewFloat(float64(time.Since(start).Seconds())).Text('f', 3), "s")) + log.Println(pname(p.Name, 5), ":", Green("Builded")+" after", MagentaS(big.NewFloat(float64(time.Since(start).Seconds())).Text('f', 3), " s")) } return } return } -// Build calls an implementation of the "gofmt" +// Fmt calls an implementation of the "gofmt" func (p *Project) fmt(path string) error { if p.Fmt { if _, err := p.GoFmt(path); err != nil { log.Println(pname(p.Name, 1), Red("There are some GoFmt errors in "), ":", Magenta(path)) - //fmt.Println(msg) + } + } + return nil +} + +// Cmd calls an wrapper for execute the commands after/before +func (p *Project) cmd() { + c := make(chan os.Signal, 2) + signal.Notify(c, os.Interrupt, syscall.SIGTERM) + cast := func(commands []string) { + if errs := p.Cmd(commands); errs != nil { + for _, err := range errs { + log.Println(pname(p.Name, 2), Red(err)) + } + } + } + + if len(p.Watcher.Before) > 0 { + cast(p.Watcher.Before) + } + + go func() { + for { + select { + case <-c: + if len(p.Watcher.After) > 0 { + cast(p.Watcher.After) + } + os.Exit(1) + } + } + }() +} + +// Test calls an implementation of the "go test" +func (p *Project) test(path string) error { + if p.Test { + if _, err := p.GoTest(path); err != nil { + log.Println(pname(p.Name, 1), Red("Go Test fails in "), ":", Magenta(path)) } } return nil } // Walks the file tree of a project -func (p *Project) walks(watcher *fsnotify.Watcher) { +func (p *Project) walks(watcher *fsnotify.Watcher) error { var files, folders int64 wd, _ := os.Getwd() @@ -202,6 +203,11 @@ func (p *Project) walks(watcher *fsnotify.Watcher) { } else { folders++ + go func() { + if err := p.test(path); err != nil { + fmt.Println(err) + } + }() } } } @@ -210,10 +216,13 @@ func (p *Project) walks(watcher *fsnotify.Watcher) { if p.Path == "." || p.Path == "/" { p.base = wd - p.Path = WorkingDir() + p.Path = Wdir() + } else if filepath.IsAbs(p.Path) { + p.base = p.Path } else { p.base = filepath.Join(wd, p.Path) } + for _, dir := range p.Watcher.Paths { base := filepath.Join(p.base, dir) if _, err := os.Stat(base); err == nil { @@ -221,11 +230,11 @@ func (p *Project) walks(watcher *fsnotify.Watcher) { log.Println(Red(err.Error())) } } else { - fmt.Println(pname(p.Name, 1), ":\t", Red(base+" path doesn't exist")) + return errors.New(base + " path doesn't exist") } } - fmt.Println(Red("Watching: "), pname(p.Name, 1), Magenta(files), "file/s", Magenta(folders), "folder/s") - fmt.Println() + fmt.Println(pname(p.Name, 1), Red("Watching"), Magenta(files), "file/s", Magenta(folders), "folder/s") + return nil } // Ignore validates a path @@ -239,41 +248,9 @@ func (p *Project) ignore(str string) bool { } // Routines launches the following methods: run, build, fmt, install -func routines(p *Project, channel chan bool, wr *sync.WaitGroup) { +func (p *Project) routines(channel chan bool, wr *sync.WaitGroup) { wr.Add(1) go p.build() go p.install(channel, wr) wr.Wait() } - -// check if a string is inArray -func inArray(str string, list []string) bool { - for _, v := range list { - if v == str { - return true - } - } - return false -} - -// defines the colors scheme for the project name -func pname(name string, color int) string { - switch color { - case 1: - name = Yellow("[") + strings.ToUpper(name) + Yellow("]") - break - case 2: - name = Yellow("[") + Red(strings.ToUpper(name)) + Yellow("]") - break - case 3: - name = Yellow("[") + Blue(strings.ToUpper(name)) + Yellow("]") - break - case 4: - name = Yellow("[") + Magenta(strings.ToUpper(name)) + Yellow("]") - break - case 5: - name = Yellow("[") + Green(strings.ToUpper(name)) + Yellow("]") - break - } - return name -} diff --git a/main.go b/realize.go similarity index 51% rename from main.go rename to realize.go index 2a529c1..8c23614 100644 --- a/main.go +++ b/realize.go @@ -2,37 +2,79 @@ package main import ( "fmt" - r "github.com/tockins/realize/realize" + c "github.com/tockins/realize/cli" + s "github.com/tockins/realize/server" "gopkg.in/urfave/cli.v2" "log" "os" + "syscall" ) +var App Realize + +// Realize struct contains the general app informations +type Realize struct { + Name, Description, Author, Email string + Version string + Limit uint64 + Blueprint c.Blueprint + Server s.Server +} + +// Flimit defines the max number of watched files +func (r *Realize) Increases() { + // increases the files limit + var rLimit syscall.Rlimit + rLimit.Max = r.Limit + rLimit.Cur = r.Limit + err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rLimit) + if err != nil { + fmt.Println(c.Red("Error Setting Rlimit "), err) + } +} + +func init() { + App = Realize{ + Name: "Realize", + Version: "1.0", + Description: "A Go build system with file watchers, output streams and live reload. Run, build and watch file changes with custom paths", + Limit: 10000, + Blueprint: c.Blueprint{ + Files: map[string]string{ + "config": "r.config.yaml", + "output": "r.output.log", + }, + }, + } + App.Increases() + c.Bp = &App.Blueprint + s.Bp = &App.Blueprint + +} + func main() { - app := r.Info() - handle := func(err error) error { if err != nil { - fmt.Println(r.Red(err.Error())) + fmt.Println(c.Red(err.Error())) return nil } return nil } header := func() error { - fmt.Println(r.Blue(app.Name) + " - " + r.Blue(app.Version)) - fmt.Println(r.BlueS(app.Description) + "\n") + fmt.Println(c.Blue(App.Name) + " - " + c.Blue(App.Version)) + fmt.Println(c.BlueS(App.Description) + "\n") gopath := os.Getenv("GOPATH") if gopath == "" { - log.Fatal(r.Red("$GOPATH isn't set up properly")) + log.Fatal(c.Red("$GOPATH isn't set up properly")) } return nil } cli := &cli.App{ - Name: app.Name, - Version: app.Version, + Name: App.Name, + Version: App.Version, Authors: []*cli.Author{ { Name: "Alessio Pracchia", @@ -43,14 +85,13 @@ func main() { Email: "conventi@hastega.it", }, }, - Usage: app.Description, + Usage: App.Description, Commands: []*cli.Command{ { Name: "run", Usage: "Build and watch file changes", Action: func(p *cli.Context) error { - y := r.New(p) - return handle(y.Watch()) + return handle(App.Blueprint.Run()) }, Before: func(c *cli.Context) error { header() @@ -59,17 +100,20 @@ func main() { }, { Name: "fast", - Usage: "Build and watch file changes for a single project without any config file", + Usage: "Build and watch file changes for a single project without any Configuration file", Flags: []cli.Flag{ + &cli.StringFlag{Name: "path", Aliases: []string{"b"}, Value: "", Usage: "Project base path"}, &cli.BoolFlag{Name: "build", Value: false, Usage: "Enables the build"}, &cli.BoolFlag{Name: "no-run", Usage: "Disables the run"}, &cli.BoolFlag{Name: "no-bin", Usage: "Disables the installation"}, &cli.BoolFlag{Name: "no-fmt", Usage: "Disables the fmt (go fmt)"}, - &cli.BoolFlag{Name: "config", Value: false, Usage: "Take the defined settings if exist a config file."}, + &cli.BoolFlag{Name: "test", Value: false, Usage: "Enable the tests"}, + &cli.BoolFlag{Name: "Configuration", Value: false, Usage: "Take the defined settings if exist a Configuration file."}, }, Action: func(p *cli.Context) error { - y := r.New(p) - return handle(y.Fast(p)) + App.Blueprint.Add(p) + App.Server.Start() + return handle(App.Blueprint.Fast(p)) }, Before: func(c *cli.Context) error { header() @@ -78,20 +122,20 @@ func main() { }, { Name: "add", - Category: "config", + Category: "Configuration", Aliases: []string{"a"}, Usage: "Add another project", Flags: []cli.Flag{ - &cli.StringFlag{Name: "name", Aliases: []string{"n"}, Value: r.WorkingDir(), Usage: "Project name"}, + &cli.StringFlag{Name: "name", Aliases: []string{"n"}, Value: c.Wdir(), Usage: "Project name"}, &cli.StringFlag{Name: "path", Aliases: []string{"b"}, Value: "/", Usage: "Project base path"}, - &cli.BoolFlag{Name: "build", Value: false, Usage: "Enable go build"}, + &cli.BoolFlag{Name: "build", Value: false, Usage: "Enable the build"}, &cli.BoolFlag{Name: "no-run", Usage: "Disables the run"}, &cli.BoolFlag{Name: "no-bin", Usage: "Disables the installation"}, &cli.BoolFlag{Name: "no-fmt", Usage: "Disables the fmt (go fmt)"}, + &cli.BoolFlag{Name: "test", Value: false, Usage: "Enable the tests"}, }, Action: func(p *cli.Context) error { - y := r.New(p) - return handle(y.Add(p)) + return handle(App.Blueprint.Insert(p)) }, Before: func(c *cli.Context) error { header() @@ -100,15 +144,14 @@ func main() { }, { Name: "remove", - Category: "config", + Category: "Configuration", Aliases: []string{"r"}, Usage: "Remove a project", Flags: []cli.Flag{ &cli.StringFlag{Name: "name", Aliases: []string{"n"}, Value: ""}, }, Action: func(p *cli.Context) error { - y := r.New(p) - return handle(y.Remove(p)) + return handle(App.Blueprint.Remove(p)) }, Before: func(c *cli.Context) error { header() @@ -117,12 +160,11 @@ func main() { }, { Name: "list", - Category: "config", + Category: "Configuration", Aliases: []string{"l"}, Usage: "Projects list", Action: func(p *cli.Context) error { - y := r.New(p) - return handle(y.List()) + return handle(App.Blueprint.List()) }, Before: func(c *cli.Context) error { header() diff --git a/realize/app.go b/realize/app.go deleted file mode 100644 index b659f46..0000000 --- a/realize/app.go +++ /dev/null @@ -1,92 +0,0 @@ -package realize - -import ( - "fmt" - "github.com/fatih/color" - "log" - "sync" - "syscall" - "time" -) - -// Default values and info -const ( - AppName = "Realize" - AppVersion = "v1.0" - AppDescription = "A Go build system with file watchers, output streams and live reload. Run, build and watch file changes with custom paths" - AppFile = "realize.config.yaml" -) - -var wg sync.WaitGroup - -// Green color bold -var Green = color.New(color.FgGreen, color.Bold).SprintFunc() - -// Red color bold -var Red = color.New(color.FgRed, color.Bold).SprintFunc() - -// RedS color used for errors -var RedS = color.New(color.FgRed).SprintFunc() - -// Blue color bold used for project output -var Blue = color.New(color.FgBlue, color.Bold).SprintFunc() - -// BlueS color -var BlueS = color.New(color.FgBlue).SprintFunc() - -// Yellow color bold -var Yellow = color.New(color.FgYellow, color.Bold).SprintFunc() - -// YellowS color -var YellowS = color.New(color.FgYellow).SprintFunc() - -// MagentaS color -var MagentaS = color.New(color.FgMagenta).SprintFunc() - -// Magenta color bold -var Magenta = color.New(color.FgMagenta, color.Bold).SprintFunc() - -// WatcherIgnores is an array of default ignored paths -var watcherIgnores = []string{"vendor", "bin"} - -// WatcherExts is an array of default exts -var watcherExts = []string{".go"} - -// WatcherPaths is an array of default watched paths -var watcherPaths = []string{"/"} - -type logWriter struct{} - -// App struct contains the informations about realize -type App struct { - Name, Version, Description, Author, Email string -} - -// Custom log timestamp -func init() { - log.SetFlags(0) - log.SetOutput(new(logWriter)) - - // increases the files limit - var rLimit syscall.Rlimit - rLimit.Max = 10000 - rLimit.Cur = 10000 - err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rLimit) - if err != nil { - fmt.Println(Red("Error Setting Rlimit "), err) - } -} - -// Info returns the general informations about Realize -func Info() *App { - return &App{ - Name: AppName, - Version: AppVersion, - Description: AppDescription, - } -} - -// Cewrites the log timestamp -func (writer logWriter) Write(bytes []byte) (int, error) { - return fmt.Print(YellowS("[") + time.Now().Format("15:04:05") + YellowS("]") + string(bytes)) -} diff --git a/realize/config.go b/realize/config.go deleted file mode 100644 index 3166037..0000000 --- a/realize/config.go +++ /dev/null @@ -1,216 +0,0 @@ -package realize - -import ( - "errors" - "fmt" - "gopkg.in/urfave/cli.v2" - "gopkg.in/yaml.v2" - "io/ioutil" - "log" - "os" - "path/filepath" - "strings" -) - -// Config struct contains the general informations about a project -type Config struct { - file string - Version string `yaml:"version,omitempty"` - Projects []Project -} - -// New method puts the cli params in the struct -func New(params *cli.Context) *Config { - return &Config{ - file: AppFile, - Version: AppVersion, - Projects: []Project{ - { - Name: nameFlag(params), - Path: filepath.Clean(params.String("path")), - Build: params.Bool("build"), - Bin: boolFlag(params.Bool("no-bin")), - Run: boolFlag(params.Bool("no-run")), - Fmt: boolFlag(params.Bool("no-fmt")), - Params: argsParam(params), - Watcher: Watcher{ - Paths: watcherPaths, - Ignore: watcherIgnores, - Exts: watcherExts, - }, - }, - }, - } -} - -// argsParam parse one by one the given argumentes -func argsParam(params *cli.Context) []string { - argsN := params.NArg() - if argsN > 0 { - var args []string - for i := 0; i <= argsN-1; i++ { - args = append(args, params.Args().Get(i)) - } - return args - } - return nil -} - -// NameParam check the project name presence. If empty takes the working directory name -func nameFlag(params *cli.Context) string { - var name string - if params.String("name") == "" && params.String("path") == "" { - return WorkingDir() - } else if params.String("path") != "/" { - name = filepath.Base(params.String("path")) - } else { - name = params.String("name") - } - return name -} - -// BoolParam is used to check the presence of a bool flag -func boolFlag(b bool) bool { - if b { - return false - } - return true -} - -// WorkingDir returns the last element of the working dir path -func WorkingDir() string { - dir, err := os.Getwd() - if err != nil { - log.Fatal(Red(err)) - } - return filepath.Base(dir) -} - -// Duplicates check projects with same name or same combinations of main/path -func Duplicates(value Project, arr []Project) (error, Project) { - for _, val := range arr { - if value.Path == val.Path || value.Name == val.Name { - return errors.New("There is a duplicate of '" + val.Name + "'. Check your config file!"), val - } - } - return nil, Project{} -} - -// Clean duplicate projects -func (h *Config) Clean() { - arr := h.Projects - for key, val := range arr { - if err, _ := Duplicates(val, arr[key+1:]); err != nil { - h.Projects = append(arr[:key], arr[key+1:]...) - break - } - } -} - -// Read, Check and remove duplicates from the config file -func (h *Config) Read() error { - _, err := os.Stat(h.file) - if err == nil { - file, err := ioutil.ReadFile(h.file) - if err == nil { - if len(h.Projects) > 0 { - err = yaml.Unmarshal(file, h) - if err == nil { - h.Clean() - } - return err - } - return errors.New("There are no projects") - } - return h.Create() - } - return err -} - -// Create and unmarshal yaml config file -func (h *Config) Create() error { - y, err := yaml.Marshal(h) - if err != nil { - return err - } - return ioutil.WriteFile(h.file, y, 0655) -} - -// Add another project -func (h *Config) Add(params *cli.Context) error { - err := h.Read() - if err == nil { - new := Project{ - Name: nameFlag(params), - Path: filepath.Clean(params.String("path")), - Build: params.Bool("build"), - Bin: boolFlag(params.Bool("no-bin")), - Run: boolFlag(params.Bool("no-run")), - Fmt: boolFlag(params.Bool("no-fmt")), - Params: argsParam(params), - Watcher: Watcher{ - Paths: watcherPaths, - Exts: watcherExts, - Ignore: watcherIgnores, - }, - } - if err, _ := Duplicates(new, h.Projects); err != nil { - return err - } - h.Projects = append(h.Projects, new) - err = h.Create() - if err == nil { - fmt.Println(Green("Your project was successfully added")) - } - return err - } - err = h.Create() - if err == nil { - fmt.Println(Green("The config file was successfully created")) - } - return err -} - -// Remove a project in list -func (h *Config) Remove(params *cli.Context) error { - err := h.Read() - if err == nil { - for key, val := range h.Projects { - if params.String("name") == val.Name { - h.Projects = append(h.Projects[:key], h.Projects[key+1:]...) - err = h.Create() - if err == nil { - fmt.Println(Green("Your project was successfully removed")) - } - return err - } - } - return errors.New("No project found") - } - return err -} - -// List of projects -func (h *Config) List() error { - err := h.Read() - if err == nil { - for _, val := range h.Projects { - fmt.Println(Blue("|"), Blue(strings.ToUpper(val.Name))) - fmt.Println(MagentaS("|"), "\t", Yellow("Base Path"), ":", MagentaS(val.Path)) - fmt.Println(MagentaS("|"), "\t", Yellow("Run"), ":", MagentaS(val.Run)) - fmt.Println(MagentaS("|"), "\t", Yellow("Build"), ":", MagentaS(val.Build)) - fmt.Println(MagentaS("|"), "\t", Yellow("Install"), ":", MagentaS(val.Bin)) - fmt.Println(MagentaS("|"), "\t", Yellow("Fmt"), ":", MagentaS(val.Fmt)) - fmt.Println(MagentaS("|"), "\t", Yellow("Params"), ":", MagentaS(val.Params)) - fmt.Println(MagentaS("|"), "\t", Yellow("Watcher"), ":") - fmt.Println(MagentaS("|"), "\t\t", Yellow("After"), ":", MagentaS(val.Watcher.After)) - fmt.Println(MagentaS("|"), "\t\t", Yellow("Before"), ":", MagentaS(val.Watcher.Before)) - fmt.Println(MagentaS("|"), "\t\t", Yellow("Extensions"), ":", MagentaS(val.Watcher.Exts)) - fmt.Println(MagentaS("|"), "\t\t", Yellow("Paths"), ":", MagentaS(val.Watcher.Paths)) - fmt.Println(MagentaS("|"), "\t\t", Yellow("Paths ignored"), ":", MagentaS(val.Watcher.Ignore)) - fmt.Println(MagentaS("|"), "\t\t", Yellow("Watch preview"), ":", MagentaS(val.Watcher.Preview)) - } - return nil - } - return err -} diff --git a/server/assets/index.html b/server/assets/index.html new file mode 100644 index 0000000..0a90125 --- /dev/null +++ b/server/assets/index.html @@ -0,0 +1 @@ +Testing \ No newline at end of file diff --git a/server/data.go b/server/data.go new file mode 100644 index 0000000..3bc66f3 --- /dev/null +++ b/server/data.go @@ -0,0 +1,239 @@ +// Code generated by go-bindata. +// sources: +// server/assets/index.html +// DO NOT EDIT! + +package server + +import ( + "bytes" + "compress/gzip" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "strings" + "time" +) + +func bindataRead(data []byte, name string) ([]byte, error) { + gz, err := gzip.NewReader(bytes.NewBuffer(data)) + if err != nil { + return nil, fmt.Errorf("Read %q: %v", name, err) + } + + var buf bytes.Buffer + _, err = io.Copy(&buf, gz) + clErr := gz.Close() + + if err != nil { + return nil, fmt.Errorf("Read %q: %v", name, err) + } + if clErr != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +type asset struct { + bytes []byte + info os.FileInfo +} + +type bindataFileInfo struct { + name string + size int64 + mode os.FileMode + modTime time.Time +} + +func (fi bindataFileInfo) Name() string { + return fi.name +} +func (fi bindataFileInfo) Size() int64 { + return fi.size +} +func (fi bindataFileInfo) Mode() os.FileMode { + return fi.mode +} +func (fi bindataFileInfo) ModTime() time.Time { + return fi.modTime +} +func (fi bindataFileInfo) IsDir() bool { + return false +} +func (fi bindataFileInfo) Sys() interface{} { + return nil +} + +var _serverAssetsIndexHtml = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\x0a\x49\x2d\x2e\xc9\xcc\x4b\x07\x04\x00\x00\xff\xff\x9a\x63\x4e\x27\x07\x00\x00\x00") + +func serverAssetsIndexHtmlBytes() ([]byte, error) { + return bindataRead( + _serverAssetsIndexHtml, + "server/assets/index.html", + ) +} + +func serverAssetsIndexHtml() (*asset, error) { + bytes, err := serverAssetsIndexHtmlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "server/assets/index.html", size: 7, mode: os.FileMode(420), modTime: time.Unix(1472831748, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +// Asset loads and returns the asset for the given name. +// It returns an error if the asset could not be found or +// could not be loaded. +func Asset(name string) ([]byte, error) { + cannonicalName := strings.Replace(name, "\\", "/", -1) + if f, ok := _bindata[cannonicalName]; ok { + a, err := f() + if err != nil { + return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err) + } + return a.bytes, nil + } + return nil, fmt.Errorf("Asset %s not found", name) +} + +// MustAsset is like Asset but panics when Asset would return an error. +// It simplifies safe initialization of global variables. +func MustAsset(name string) []byte { + a, err := Asset(name) + if err != nil { + panic("asset: Asset(" + name + "): " + err.Error()) + } + + return a +} + +// AssetInfo loads and returns the asset info for the given name. +// It returns an error if the asset could not be found or +// could not be loaded. +func AssetInfo(name string) (os.FileInfo, error) { + cannonicalName := strings.Replace(name, "\\", "/", -1) + if f, ok := _bindata[cannonicalName]; ok { + a, err := f() + if err != nil { + return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err) + } + return a.info, nil + } + return nil, fmt.Errorf("AssetInfo %s not found", name) +} + +// AssetNames returns the names of the assets. +func AssetNames() []string { + names := make([]string, 0, len(_bindata)) + for name := range _bindata { + names = append(names, name) + } + return names +} + +// _bindata is a table, holding each asset generator, mapped to its name. +var _bindata = map[string]func() (*asset, error){ + "server/assets/index.html": serverAssetsIndexHtml, +} + +// AssetDir returns the file names below a certain +// directory embedded in the file by go-bindata. +// For example if you run go-bindata on data/... and data contains the +// following hierarchy: +// data/ +// foo.txt +// img/ +// a.png +// b.png +// then AssetDir("data") would return []string{"foo.txt", "img"} +// AssetDir("data/img") would return []string{"a.png", "b.png"} +// AssetDir("foo.txt") and AssetDir("notexist") would return an error +// AssetDir("") will return []string{"data"}. +func AssetDir(name string) ([]string, error) { + node := _bintree + if len(name) != 0 { + cannonicalName := strings.Replace(name, "\\", "/", -1) + pathList := strings.Split(cannonicalName, "/") + for _, p := range pathList { + node = node.Children[p] + if node == nil { + return nil, fmt.Errorf("Asset %s not found", name) + } + } + } + if node.Func != nil { + return nil, fmt.Errorf("Asset %s not found", name) + } + rv := make([]string, 0, len(node.Children)) + for childName := range node.Children { + rv = append(rv, childName) + } + return rv, nil +} + +type bintree struct { + Func func() (*asset, error) + Children map[string]*bintree +} + +var _bintree = &bintree{nil, map[string]*bintree{ + "server": {nil, map[string]*bintree{ + "assets": {nil, map[string]*bintree{ + "index.html": {serverAssetsIndexHtml, map[string]*bintree{}}, + }}, + }}, +}} + +// RestoreAsset restores an asset under the given directory +func RestoreAsset(dir, name string) error { + data, err := Asset(name) + if err != nil { + return err + } + info, err := AssetInfo(name) + if err != nil { + return err + } + err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755)) + if err != nil { + return err + } + err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode()) + if err != nil { + return err + } + err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime()) + if err != nil { + return err + } + return nil +} + +// RestoreAssets restores an asset under the given directory recursively +func RestoreAssets(dir, name string) error { + children, err := AssetDir(name) + // File + if err != nil { + return RestoreAsset(dir, name) + } + // Dir + for _, child := range children { + err = RestoreAssets(dir, filepath.Join(name, child)) + if err != nil { + return err + } + } + return nil +} + +func _filePath(dir, name string) string { + cannonicalName := strings.Replace(name, "\\", "/", -1) + return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...) +} diff --git a/server/main.go b/server/main.go new file mode 100644 index 0000000..c5d2d4c --- /dev/null +++ b/server/main.go @@ -0,0 +1,57 @@ +package server + +import ( + "github.com/labstack/echo" + "github.com/labstack/echo/engine/standard" + "github.com/labstack/echo/middleware" + c "github.com/tockins/realize/cli" + "golang.org/x/net/websocket" + "log" + "net/http" +) + +var Bp *c.Blueprint + +// Server struct contains server informations +type Server struct { +} + +func render(c echo.Context, path string) error { + data, err := Asset(path) + if err != nil { + return echo.NewHTTPError(http.StatusNotFound) + } + rs := c.Response() + rs.Header().Set(echo.HeaderContentType, echo.MIMETextHTMLCharsetUTF8) + rs.WriteHeader(http.StatusOK) + rs.Write(data) + return nil +} + +func (s *Server) Start() { + e := echo.New() + e.Use(middleware.Gzip()) + e.GET("/", func(c echo.Context) error { + return render(c, "server/assets/index.html") + }) + + e.GET("/projects", standard.WrapHandler(projects())) + go e.Run(standard.New(":5000")) +} + +// The WebSocket for projects list +func projects() websocket.Handler { + return websocket.Handler(func(ws *websocket.Conn) { + for { + err := websocket.Message.Send(ws, "Hello") + if err != nil { + log.Fatal(err) + } + msg := "" + err = websocket.Message.Receive(ws, &msg) + if err != nil { + log.Fatal(err) + } + } + }) +} diff --git a/server/open.go b/server/open.go new file mode 100644 index 0000000..5666b5a --- /dev/null +++ b/server/open.go @@ -0,0 +1,33 @@ +package server + +import ( + "bytes" + "errors" + "io" + "os/exec" + "runtime" +) + +var cli map[string]string +var stderr bytes.Buffer + +func init() { + cli = map[string]string{ + "windows": "start", + "darwin": "open", + "linux": "xdg-open", + } +} + +func Open(url string) (io.Writer, error) { + if open, err := cli[runtime.GOOS]; !err { + return nil, errors.New("This operating system is not supported.") + } else { + cmd := exec.Command(open, url) + cmd.Stderr = &stderr + if err := cmd.Run(); err != nil { + return cmd.Stderr, err + } + } + return nil, nil +}