// wbr will run a build command and a run command on file system changes. This is so you can // build&run on save. // You specify a build command and run command as flags and optionally you can filter by extension. // TODO: // fix bug when only build command is provided that the output is like this: // [./build.sh [./build.sh] ./main.c ] package main import ( "flag" "fmt" "os/exec" "strings" "time" "github.com/fsnotify/fsnotify" ) // Colors in escape sequences var ( Reset = "\033[0m" BoldCyan = "\033[36;1m" Cyan = "\033[36m" // Red = "\033[31m" // Green = "\033[32m" // Yellow = "\033[33m" // Blue = "\033[34m" // Magenta = "\033[35m" // Cyan = "\033[36m" // Gray = "\033[37m" // White = "\033[97m" ) var ( // Path in which to look for commands Path string // Command run for building BuildCommand []string // Command run after build RunCommand []string // Command will only run on files that have the Extensions extension Extensions []string ) const ( DateLayout string = "15:04:05" ) func CompileRun(fileName string) { found := false for _, Ext := range Extensions { if strings.HasSuffix(fileName, Ext) { found = true break } } if !found { return } // Build the program, exit if return if there was an error if len(BuildCommand) > 0 && BuildCommand[0] != "" { var buildCmd *exec.Cmd if len(BuildCommand) > 1 { buildCmd = exec.Command(BuildCommand[0], append(BuildCommand[1:], fileName)...) } else { buildCmd = exec.Command(BuildCommand[0], fileName) } // run the build command out, err := buildCmd.CombinedOutput() date := time.Now().Format(DateLayout) if len(BuildCommand) > 1 { fmt.Printf("[%s %s%s %s%s]\n", date, Cyan, BuildCommand[0], strings.Join(append(BuildCommand[1:], fileName), " "), Reset) } else { fmt.Printf("[%s %s%s %s %s%s]\n", date, Cyan, BuildCommand[0], BuildCommand, fileName, Reset) } if len(out) > 0 { fmt.Printf("%s", out) } if err != nil { if _, ok := err.(*exec.ExitError); ok { return } else if !ok { panic(err) } } } if len(RunCommand) > 0 && RunCommand[0] != "" { // There was no error in the build so we can execute the run command var runCmd *exec.Cmd if len(RunCommand) > 1 { runCmd = exec.Command(RunCommand[0], RunCommand[1:]...) } else { runCmd = exec.Command(RunCommand[0]) } date := time.Now().Format(DateLayout) out, err := runCmd.CombinedOutput() if len(RunCommand) > 1 { fmt.Printf("[%s %s%s %s%s]\n", date, Cyan, RunCommand[0], strings.Join(RunCommand[1:], " "), Reset) } else { fmt.Printf("[%s %s%s%s]\n", date, Cyan, RunCommand[0], Reset) } if len(out) > 0 { fmt.Printf("%s", out) } if err != nil { if _, ok := err.(*exec.ExitError); ok { return } else if !ok { panic(err) } } } } func main() { watcher, err := fsnotify.NewWatcher() if err != nil { panic(err) } defer watcher.Close() bc := flag.String("b", "", "Command to run for building") rc := flag.String("r", "", "Command for running after build") e := flag.String("e", "", "Filter watched files by suffix") p := flag.String("p", ".", "Path to watch for files") flag.Parse() BuildCommand = strings.Split(*bc, " ") RunCommand = strings.Split(*rc, " ") Extensions = strings.Split(*e, ",") Path = *p fmt.Printf("[%s watching '%s'%s]\n", Cyan, Path, Reset) go func() { for { select { case event, ok := <-watcher.Events: if !ok { return } if event.Has(fsnotify.Write) { CompileRun(event.Name) } case err, ok := <-watcher.Errors: if !ok { return } fmt.Println("error:", err) } } }() err = watcher.Add(Path) if err != nil { panic(err) } <-make(chan struct{}) }