Watch File Changes with Godo
Godo is a make tool in Golang inspired by tools like rake and gulp in other languages. Similarly, it serves to execute tasks, watch changes, and can be called directly from command line. It's a new tool and is still growing. There might not be as many tools as in gulp but you can write Go code in it to take advantage to its performance.
Install
go get -u gopkg.in/godo.v2/cmd/godo
The Basics
This is how you can write a simple script that rebuilds the Go program every time the file has changed.
Create a
Gododir
ortasks
in your working directory.Create a
Godofile.go
ormain.go
in that directory.Write the following:
package main
import (
godo "gopkg.in/godo.v2"
)
func Tasks(p *godo.Project) {
p.Task("build", nil, func(c *godo.Context) {
c.Run("go build main.go")
}).Src("**/*.go")
}
func main() {
godo.Godo(Tasks)
}
So once any .go
file has changed in your working directory, Godo will run the build command against the main.go
. Then run in command line:
godo -w
which means compile the Gododir
and start watching the filesystem. Without -w
it will only compile and run the default
task once.
Separate Tasks in Different Files
If we have more than one task and want to separate them into different files, Godo automatically includes all files in Gododir
like we run go build *.go
. In this case, we can create different functions in different files and call them in the Tasks()
.
Here's an example.
- Gododir
- main.go
- foo.go
and we can write as follow:
// foo.go
package main
import (
godo "gopkg.in/godo.v2"
)
func Foo(p *godo.Project) {
p.Task("foo", nil, func(c *godo.Context) {
// Do something
})
}
and call the function in Tasks()
:
// main.go
func Tasks(p *godo.Project) {
Foo(p)
}
Therefore we have defined another task in another file.
Get FileEvent
Sometimes we only want to run commands against specific files, and we can take advantages of Godo's FileEvent
attribute under the context.
Supposedly we're testing a set of files under one directory when any .go
file is changed in this directory. The FileEvent
returns a FileEvent struct, which we can call Path
to retrieve the full path. For example:
p.Task("test", nil, func(c *godo.Context) {
if c.FileEvent != nil {
fmt.Println(c.FileEvent.Path) // => /path/to/this/file.go
}
}).Src("**/*.go")
}
Beware that in complie stage, there will be no FileEvent
because nothing is triggered at that time. In this case we only run the code if FileEvent
is not nil
.
A full example will be this:
package main
import (
"bytes"
godo "gopkg.in/godo.v2"
"io/ioutil"
"log"
"strings"
)
func Tasks(p *godo.Project) {
// If any file is changed in one directory, run go test against the directory
p.Task("test", nil, func(c *godo.Context) {
if c.FileEvent != nil {
c.Run("go test " + GoFiles(c.FileEvent.Path))
}
}).Src("**/*.go")
}
// GoFiles returns all files in the directory from the eventPath.
func GoFiles(eventPath string) string {
dir := DirFromPath(eventPath)
list, err := ioutil.ReadDir(dir)
if err != nil {
log.Fatal(err)
}
var files bytes.Buffer
for _, file := range list {
if strings.Contains(file.Name(), ".go") {
files.WriteString(dir + file.Name() + " ")
}
}
return files.String()
}
// DirFromPath removes the file from the path and returns the directory only.
func DirFromPath(path string) string {
slice := strings.Split(path, "/")
dir := "/" + strings.Join(slice[1:len(slice)-1], "/") + "/"
return dir
}
func main() {
godo.Godo(Tasks)
}
Run godo test -w
to watch it and we're done!