Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
…method didn't reload the app :+1
  • Loading branch information
kataras committed Feb 5, 2017
1 parent cf55c85 commit b91b5bb
Show file tree
Hide file tree
Showing 9 changed files with 400 additions and 133 deletions.
20 changes: 19 additions & 1 deletion HISTORY.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,22 @@
## 0.0.4 -> 0.0.5
## 0.0.6 -> 0.0.7

Rizla uses the operating system's signals to fire a change because it is the fastest way and it consumes the minimal CPU.
But as the [feature request](https://github.com/kataras/rizla/issues/6) explains, some IDEs overrides the Operating System's signals, so I needed to change the things a bit in order to allow
looping-every-while and compare the file(s) with its modtime for new changes while in the same time keep the default as it's.

- **NEW**: Add a common interface for file system watchers in order to accoblish select between two methods of scanning for file changes.
- file system's signals (default)
- `filepath.Walk` (using the `-walk` flag)

### When to enable `-walk`?
When the default method doesn't works for your IDE's save method.

### How to enable `-walk`?
- If you're command line user: `rizla -walk main.go` , just append the `-walk` flag.
- If you use rizla behind your source code then use the `rizla.RunWith(rizla.WatcherFromFlag("-flag"))` instead of `rizla.Run()`.


## 0.0.4 -> 0.0.5 and 0.0.6

- **Fix**: Reload more than one time on Mac

Expand Down
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
The MIT License (MIT)

Copyright (c) 2015-2016 Gerasimos Maropoulos
Copyright (c) 2015-2017 Gerasimos Maropoulos

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Rizla builds, runs and monitors your Go Applications with ease.
```bash
$ rizla main.go #single project monitoring
$ rizla C:/myprojects/project1/main.go C:/myprojects/project2/main.go #multi projects monitoring
$ rizla -walk main.go #prepend '-walk' only when the default file changes scanning method doesn't works for you.
```

Want to use it from your project's source code? easy
Expand All @@ -23,6 +24,7 @@ import (
func main() {
// Build, run & start monitoring the projects
rizla.Run("C:/iris-project/main.go", "C:/otherproject/main.go")
// rizla.RunWith(rizla.WatcherFromFlag("-walk"), "./main.go")
}
```

Expand Down Expand Up @@ -126,7 +128,7 @@ The author of rizla is [@kataras](https://github.com/kataras).
Versioning
------------

Current: **v0.0.6**
Current: **v0.0.7**

[HISTORY](https://github.com/kataras/rizla/blob/master/HISTORY.md) file is your best friend!

Expand Down Expand Up @@ -160,7 +162,7 @@ License can be found [here](LICENSE).
[Travis]: http://travis-ci.org/kataras/rizla
[License Widget]: https://img.shields.io/badge/license-MIT%20%20License%20-E91E63.svg?style=flat-square
[License]: https://github.com/kataras/rizla/blob/master/LICENSE
[Release Widget]: https://img.shields.io/badge/release-v0.0.6-blue.svg?style=flat-square
[Release Widget]: https://img.shields.io/badge/release-v0.0.7-blue.svg?style=flat-square
[Release]: https://github.com/kataras/rizla/releases
[Chat Widget]: https://img.shields.io/badge/community-chat-00BCD4.svg?style=flat-square
[Chat]: https://kataras.rocket.chat/channel/rizla
Expand Down
47 changes: 36 additions & 11 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
//
// rizla main.go
// rizla C:/myprojects/project1/main.go C:/myprojects/project2/main.go C:/myprojects/project3/main.go
// rizla -walk main.go [if -walk then rizla uses the stdlib's filepath.Walk method instead of file system's signals]
//
package main

Expand Down Expand Up @@ -30,7 +31,7 @@ var helpTmpl = fmt.Sprintf(`NAME:
USAGE:
rizla main.go
rizla C:/myprojects/project1/main.go C:/myprojects/project2/main.go C:/myprojects/project3/main.go
rizla -walk main.go [if -walk then rizla uses the stdlib's filepath.Walk method instead of file system's signals]
VERSION:
%s
`, Name, Description, Version)
Expand All @@ -45,21 +46,45 @@ func main() {
}

args := os.Args[1:]
for _, a := range args {
if !strings.HasSuffix(a, ".go") {
color.Red("Error: Please provide files with '.go' extension.\n")
help(-1)
} else if p, _ := filepath.Abs(a); !fileExists(p) {
programFiles := make([]string, 0)
var fsWatcher rizla.Watcher

for i, a := range args {
// The first argument must be the method type of the file system's watcher.
// if -w,-walk,walk then
// asks to use the stdlib's filepath.walk method instead of the operating system's signal.
// It's only usage is when the user's IDE overrides the os' signals.
// otherwise
// use the fsnotify's operating system's file system's signals.
if i == 0 {
fsWatcher = rizla.WatcherFromFlag(a)
}

// it's main.go or any go main program
if strings.HasSuffix(a, ".go") {
programFiles = append(programFiles, a)
continue
}
}

// no program files given
if len(programFiles) == 0 {
color.Red("Error: Please provide a *.go file.\n")
help(-1)
return
}

// check if given program files exist
for _, a := range programFiles {
// the argument is not the first given is *.go but doesn't exists on user's disk
if p, _ := filepath.Abs(a); !fileExists(p) {
color.Red("Error: File " + p + " does not exists.\n")
help(-1)
return
}
}

for _, a := range args {
p := rizla.NewProject(a)
rizla.Add(p)
}
rizla.Run()
rizla.RunWith(fsWatcher, programFiles...)
}

func help(code int) {
Expand Down
3 changes: 2 additions & 1 deletion rizla/project.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ func DefaultGoMatcher(fullname string) bool {
// allows all subdirs except .git, node_modules and vendor
func DefaultWatcher(abs string) bool {
base := filepath.Base(abs)
return !(base == ".git" || base == "node_modules" || base == "vendor")
// by-default ignore .git folder, node_modules, vendor and any hidden files.
return !(base == ".git" || base == "node_modules" || base == "vendor" || base == ".")
}

// DefaultOnReload fired when file has changed and reload going to happens
Expand Down
182 changes: 65 additions & 117 deletions rizla/rizla.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,10 @@ package rizla
import (
"os"
"os/exec"
"path/filepath"
"runtime"
"strconv"
"time"

"github.com/fsnotify/fsnotify"
"github.com/kataras/go-errors"
)

Expand All @@ -29,6 +27,8 @@ var (
pathSeparator = string(os.PathSeparator)

stopChan = make(chan bool, 1)

fsWatcher Watcher
)

// Add project(s) to the container
Expand All @@ -55,48 +55,29 @@ var (
errRun = errors.New("Failed to run the the program. Trace: %s\n")
)

// Run starts the repeat of the build-run-watch-reload task of all projects
// receives optional parameters which can be the main source file of the project(s) you want to add, they can work nice with .Add(project) also, so dont worry use it.
func Run(sources ...string) {
// RunWith starts the repeat of the build-run-watch-reload task of all projects
// receives optional parameters which can be the main source file
// of the project(s) you want to add, they can work nice with .Add(project) also, so dont worry use it.
//
// First receiver is the type of watcher
// second (optional) parameter(s) are the directories of the projects.
// it's optional because they can be added with the .Add(NewProject) before the RunWith.
//
func RunWith(watcher Watcher, sources ...string) {
// Author's notes: Because rizla's Run is not allowed to be called more than once
// the whole package works as it is, so the watcher here
// is CHANGING THE UNEXPORTED PACKGE VARIABLE 'fsWatcher'.
// We don't export the 'fsWatcher' directly because this may cause issues
// if user tries to change it while it runs.
fsWatcher = watcher

if len(sources) > 0 {
for _, s := range sources {
Add(NewProject(s))
}
}

watcher, werr := fsnotify.NewWatcher()
if werr != nil {
panic(werr)
}

for _, p := range projects {

// add to the watcher first in order to watch changes and re-builds if the first build has fallen

// add its root folder first
if werr = watcher.Add(p.dir); werr != nil {
p.Err.Dangerf("\n" + werr.Error() + "\n")
}

visitFn := func(path string, f os.FileInfo, err error) error {
if f.IsDir() {
// check if this subdir is allowed
if p.Watcher(path) {
if werr = watcher.Add(path); werr != nil {
p.Err.Dangerf("\n" + werr.Error() + "\n")
}
} else {
return filepath.SkipDir
}

}
return nil
}

if err := filepath.Walk(p.dir, visitFn); err != nil {
panic(err)
}

// go build
if err := buildProject(p); err != nil {
p.Err.Dangerf(errBuild.Error())
Expand All @@ -110,100 +91,67 @@ func Run(sources ...string) {
}

}
hasStoppedManually := false

defer func() {
watcher.Close()
for _, p := range projects {
killProcess(p.proc)
}
if !hasStoppedManually {
// if something bad happens and program exits, show an unexpected error message
Out.Dangerf(errUnexpected.Error())
}
}()
watcher.OnError(func(err error) {
Out.Dangerf("\n Error:" + err.Error())
})

stopChan <- false
watcher.OnChange(func(p *Project, filename string) {
if time.Now().After(p.lastChange.Add(p.AllowReloadAfter)) {
p.lastChange = time.Now()
match := p.Matcher(filename)

// run the watcher
for {
select {
case stop := <-stopChan:
if stop {
hasStoppedManually = true
return
}
if match {

case event := <-watcher.Events:
// ignore CHMOD events
if event.Op&fsnotify.Chmod == fsnotify.Chmod {
continue
}
p.OnReload(filename)

filename := event.Name
for _, p := range projects {
p.i++
// fix two-times reload on windows
if isWindows && p.i%2 != 0 {
continue
// kill previous running instance
err := killProcess(p.proc)
if err != nil {
p.Err.Dangerf(err.Error())
return
}

if time.Now().After(p.lastChange.Add(p.AllowReloadAfter)) {
p.lastChange = time.Now()

isDir := false
match := p.Matcher(filename)
if !p.DisableRuntimeDir { //we don't check if !match because the folder maybe be: myfolder.go
isDir = isDirectory(filename)
}

if match || isDir && p.Watcher(filename) {
if isDir {
if werr = watcher.Add(filename); werr != nil {
p.Err.Dangerf("\n" + werr.Error() + "\n")
}
}

p.OnReload(filename)

// kill previous running instance
err := killProcess(p.proc)
if err != nil {
p.Err.Dangerf(err.Error())
continue
}

// go build
err = buildProject(p)
if err != nil {
p.Err.Dangerf(errBuild.Error())
continue
}

// exec run the builded program
err = runProject(p)
if err != nil {
p.Err.Dangerf(errRun.Format(err.Error()).Error())
continue
}

p.OnReloaded(filename)

}
// go build
err = buildProject(p)
if err != nil {
p.Err.Dangerf(errBuild.Error())
return
}
}
case err := <-watcher.Errors:
if !hasStoppedManually {
Out.Dangerf("\n Error:" + err.Error())

// exec run the builded program
err = runProject(p)
if err != nil {
p.Err.Dangerf(errRun.Format(err.Error()).Error())
return
}

p.OnReloaded(filename)

}
}
}
})

watcher.Loop()
}

// Stop any projects are watched by the Run method, this function should be call when you call the Run inside a goroutine.
// Run same as RunWith but runs with the default file system watcher
// which is the fsnotify (watch over file system's signals) or the last used with RunWith.
func Run(sources ...string) {
if fsWatcher != nil {
// if user already called RunWith before, the watcher is saved on the 'fsWatcher' variable,
// use that instead.
RunWith(fsWatcher, sources...)
return
}
RunWith(WatcherFromFlag(""), sources...)
}

// Stop any projects are watched by the RunWith/Run method, this function should be call when you call the Run inside a goroutine.
func Stop() {
stopChan <- true
if fsWatcher != nil {
fsWatcher.Stop()
}
}

func isDirectory(fullname string) bool {
Expand Down
Loading

0 comments on commit b91b5bb

Please sign in to comment.