diff --git a/cmd/task/task.go b/cmd/task/task.go index 8a9b7d5e..c9f069e7 100644 --- a/cmd/task/task.go +++ b/cmd/task/task.go @@ -26,6 +26,7 @@ hello: pflag.PrintDefaults() } pflag.BoolVarP(&task.Force, "force", "f", false, "forces execution even when the task is up-to-date") + pflag.BoolVarP(&task.Watch, "watch", "w", false, "enables watch of the given task") pflag.Parse() task.Run() } diff --git a/errors.go b/errors.go index 0c6a1dcf..4b6c2dc1 100644 --- a/errors.go +++ b/errors.go @@ -36,3 +36,11 @@ type cyclicDepError struct { func (err *cyclicDepError) Error() string { return fmt.Sprintf(`task: Cyclic dependency of task "%s" detected`, err.taskName) } + +type cantWatchNoSourcesError struct { + taskName string +} + +func (err *cantWatchNoSourcesError) Error() string { + return fmt.Sprintf(`task: Can't watch task "%s" because it has no specified sources`, err.taskName) +} diff --git a/task.go b/task.go index 22d61ef7..73e4dbf6 100644 --- a/task.go +++ b/task.go @@ -18,6 +18,8 @@ var ( // Force (--force or -f flag) forces a task to run even when it's up-to-date Force bool + // Watch (--watch or -w flag) enables watch of a task + Watch bool // Tasks constains the tasks parsed from Taskfile Tasks = make(map[string]*Task) @@ -66,6 +68,13 @@ func Run() { } } + if Watch { + if err := WatchTasks(args); err != nil { + log.Fatal(err) + } + return + } + for _, a := range args { if err = RunTask(a); err != nil { log.Fatal(err) diff --git a/watch.go b/watch.go new file mode 100644 index 00000000..8117276c --- /dev/null +++ b/watch.go @@ -0,0 +1,98 @@ +package task + +import ( + "fmt" + "log" + "strings" + "time" + + "github.com/fsnotify/fsnotify" + "github.com/mattn/go-zglob" +) + +// WatchTasks start watching the given tasks +func WatchTasks(args []string) error { + log.Printf("task: Started watching for tasks: %s", strings.Join(args, ", ")) + + // run tasks on init + for _, a := range args { + if err := RunTask(a); err != nil { + fmt.Println(err) + break + } + } + + watcher, err := fsnotify.NewWatcher() + if err != nil { + return err + } + defer watcher.Close() + + go func() { + for { + if err := registerWatchedFiles(watcher, args); err != nil { + log.Printf("Error watching files: %v", err) + } + time.Sleep(time.Second * 2) + } + }() + +loop: + for { + select { + case <-watcher.Events: + for _, a := range args { + if err := RunTask(a); err != nil { + fmt.Println(err) + continue loop + } + } + case err := <-watcher.Errors: + fmt.Println(err) + continue loop + } + } +} + +var watchingFiles map[string]struct{} + +func registerWatchedFiles(w *fsnotify.Watcher, args []string) error { + oldWatchingFiles := watchingFiles + watchingFiles = make(map[string]struct{}, len(oldWatchingFiles)) + + for k := range oldWatchingFiles { + if err := w.Remove(k); err != nil { + return err + } + } + + for _, a := range args { + task, ok := Tasks[a] + if !ok { + return &taskNotFoundError{a} + } + if err := registerWatchedFiles(w, task.Deps); err != nil { + return err + } + for _, s := range task.Sources { + files, err := zglob.Glob(s) + if err != nil { + return err + } + for _, f := range files { + if err := w.Add(f); err != nil { + return err + } + watchingFiles[f] = struct{}{} + + // run if is new file + if oldWatchingFiles != nil { + if _, ok := oldWatchingFiles[f]; !ok { + w.Events <- fsnotify.Event{Name: f, Op: fsnotify.Create} + } + } + } + } + } + return nil +}