func main() { kingpin.Version(watch.Version) kingpin.Parse() log := termlog.NewLog() if *debug { log.Enable("debug") watch.Logger = log } ret, err := ioutil.ReadFile(*file) if err != nil { kingpin.Fatalf("%s", err) } cnf, err := conf.Parse(*file, string(ret)) if err != nil { kingpin.Fatalf("%s", err) } watchfile := *file if *noconf { watchfile = "" } for { cnf = run(log, cnf, watchfile) if cnf == nil { break } } }
func _testWatch(t *testing.T, modfunc func(), trigger string, expected []string) { defer utils.WithTempDir(t)() err := os.MkdirAll("a", 0777) if err != nil { t.Fatal(err) } err = os.MkdirAll("b", 0777) if err != nil { t.Fatal(err) } confTxt := ` ** { prep: echo ":all:" @mods } a/** { prep: echo ":a:" @mods } b/** { prep: echo ":b:" @mods } ` cnf, err := conf.Parse("test", confTxt) if err != nil { t.Fatal(err) } lt := termlog.NewLogTest() modchan := make(chan *watch.Mod, 1024) cback := func() { // There's some race condition in rjeczalik/notify. If we don't wait a // bit here, we sometimes don't receive notifications for our changes. time.Sleep(200 * time.Millisecond) start := time.Now() modfunc() for { if strings.Contains(lt.String(), trigger) { break } if time.Now().Sub(start) > timeout { fmt.Println("Timeout!") break } time.Sleep(50 * time.Millisecond) } modchan <- nil } _, err = runOnChan(modchan, cback, lt.Log, cnf, "", nil) if err != nil { t.Fatalf("runOnChan: %s", err) } ret := events(lt.String()) if !reflect.DeepEqual(ret, expected) { t.Errorf("Expected\n%#v\nGot\n%#v", expected, ret) } }
// ReadConfig parses the configuration file in ConfPath func (mr *ModRunner) ReadConfig() error { ret, err := ioutil.ReadFile(mr.ConfPath) if err != nil { return fmt.Errorf("Error reading config file %s: %s", mr.ConfPath, err) } newcnf, err := conf.Parse(mr.ConfPath, string(ret)) if err != nil { return fmt.Errorf("Error reading config file %s: %s", mr.ConfPath, err) } shellMethod := newcnf.GetVariables()[shellVarName] if !shell.Has(shellMethod) { return fmt.Errorf("No shell interface %q", shellMethod) } newcnf.CommonExcludes(CommonExcludes) mr.Config = newcnf return nil }
func main() { kingpin.Version(modd.Version) kingpin.Parse() if *ignores { for _, patt := range modd.CommonExcludes { fmt.Println(patt) } os.Exit(0) } log := termlog.NewLog() if *debug { log.Enable("debug") watch.Logger = log } ret, err := ioutil.ReadFile(*file) if err != nil { kingpin.Fatalf("%s", err) } cnf, err := conf.Parse(*file, string(ret)) if err != nil { kingpin.Fatalf("%s", err) } watchfile := *file if *noconf { watchfile = "" } notifiers := []notify.Notifier{} if *doNotify { n := notify.PlatformNotifier() if n == nil { log.Shout("Could not find a desktop notifier") } else { notifiers = append(notifiers, n) } } if *beep { notifiers = append(notifiers, ¬ify.BeepNotifier{}) } if *prep { err := modd.PrepOnly(log, cnf, notifiers) if err != nil { log.Shout("%s", err) } } else { for { cnf.CommonExcludes(modd.CommonExcludes) cnf, err = modd.Run(log, cnf, watchfile, notifiers) if err != nil { log.Shout("%s", err) break } if cnf == nil { break } } } }
// Gives control of chan to caller func runOnChan(modchan chan *watch.Mod, readyCallback func(), log termlog.TermLog, cnf *conf.Config, watchconf string, notifiers []notify.Notifier) (*conf.Config, error) { err := PrepOnly(log, cnf, notifiers) if err != nil { return nil, err } dworld, err := NewDaemonWorld(cnf, log) if err != nil { return nil, err } c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt, os.Kill) defer signal.Reset(os.Interrupt, os.Kill) defer dworld.Shutdown(os.Kill) go func() { dworld.Shutdown(<-c) os.Exit(0) }() dworld.Start() watchpaths := cnf.WatchPatterns() if watchconf != "" { watchpaths = append(watchpaths, filepath.Dir(watchconf)) } // FIXME: This takes a long time. We could start it in parallel with the // first process run in a goroutine watcher, err := watch.Watch(watchpaths, lullTime, modchan) if err != nil { return nil, fmt.Errorf("Error watching: %s", err) } defer watcher.Stop() go readyCallback() for mod := range modchan { if mod == nil { break } if watchconf != "" && mod.Has(watchconf) { ret, err := ioutil.ReadFile(watchconf) if err != nil { log.Warn("Reloading config - error reading %s: %s", watchconf, err) continue } newcnf, err := conf.Parse(watchconf, string(ret)) if err != nil { log.Warn("Reloading config - error reading %s: %s", watchconf, err) continue } log.Notice("Reloading config %s", watchconf) return newcnf, nil } log.SayAs("debug", "Delta: \n%s", mod.String()) for i, b := range cnf.Blocks { lmod, err := mod.Filter(b.Include, b.Exclude) if err != nil { log.Shout("Error filtering events: %s", err) continue } if lmod.Empty() { continue } err = RunPreps(b, cnf.GetVariables(), lmod, log, notifiers) if err != nil { if _, ok := err.(ProcError); ok { continue } else { return nil, err } } dworld.DaemonPens[i].Restart() } } return nil, nil }
func run(log termlog.TermLog, cnf *conf.Config, watchconf string) *conf.Config { modchan := make(chan *watch.Mod, 1024) if *ignores { for _, patt := range watch.CommonExcludes { fmt.Println(patt) } os.Exit(0) } daemonPens := make([]*modd.DaemonPen, len(cnf.Blocks)) for i, b := range cnf.Blocks { if !b.NoCommonFilter { b.Exclude = append(b.Exclude, watch.CommonExcludes...) } cnf.Blocks[i] = b _, err := prepsAndNotify(b, cnf.GetVariables(), nil, log) if err != nil { log.Shout("%s", err) return nil } d := modd.DaemonPen{} c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt, os.Kill) go func() { d.Shutdown(<-c) os.Exit(0) }() if !*prep { d.Start(b.Daemons, cnf.GetVariables(), log) } daemonPens[i] = &d } if *prep { os.Exit(0) } watchpaths := cnf.WatchPaths() if watchconf != "" { watchpaths = append(watchpaths, watchconf) } // FIXME: This takes a long time. We could start it in parallel with the // first process run in a goroutine watcher, err := watch.Watch(watchpaths, lullTime, modchan) defer watcher.Stop() if err != nil { kingpin.Fatalf("Fatal error: %s", err) } for mod := range modchan { if watchconf != "" && mod.Has(watchconf) { ret, err := ioutil.ReadFile(watchconf) if err != nil { log.Warn("Reloading config - error reading %s: %s", watchconf, err) continue } newcnf, err := conf.Parse(*file, string(ret)) if err != nil { log.Warn("Reloading config - error reading %s: %s", watchconf, err) continue } log.Notice("Reloading config %s", watchconf) return newcnf } if mod == nil { break } log.SayAs("debug", "Delta: \n%s", mod.String()) for i, b := range cnf.Blocks { lmod, err := mod.Filter(b.Include, b.Exclude) if err != nil { log.Shout("Error filtering events: %s", err) continue } if lmod.Empty() { continue } proceed, err := prepsAndNotify(b, cnf.GetVariables(), lmod, log) if err != nil { log.Shout("%s", err) return nil } if !proceed { continue } daemonPens[i].Restart() } } return nil }
func _testWatch(t *testing.T, modfunc func(), trigger string, expected []string) { defer utils.WithTempDir(t)() err := os.MkdirAll("a", 0777) if err != nil { t.Fatal(err) } err = os.MkdirAll("b", 0777) if err != nil { t.Fatal(err) } touch(t, "a/initial") // There's some race condition in rjeczalik/notify. If we don't wait a bit // here, we sometimes receive notifications for the change above even // though we haven't started the watcher. time.Sleep(200 * time.Millisecond) confTxt := ` ** { prep +onchange: echo ":skipit:" @mods prep: echo ":all:" @mods } a/** { prep: echo ":a:" @mods } b/** { prep: echo ":b:" @mods } ` cnf, err := conf.Parse("test", confTxt) if err != nil { t.Fatal(err) } lt := termlog.NewLogTest() modchan := make(chan *moddwatch.Mod, 1024) cback := func() { start := time.Now() modfunc() for { if strings.Contains(lt.String(), trigger) { break } if time.Now().Sub(start) > timeout { fmt.Println("Timeout!") break } time.Sleep(50 * time.Millisecond) } modchan <- nil } mr := ModRunner{ Log: lt.Log, Config: cnf, } err = mr.runOnChan(modchan, cback) if err != nil { t.Fatalf("runOnChan: %s", err) } ret := events(lt.String()) if !reflect.DeepEqual(ret, expected) { t.Errorf("Expected\n%#v\nGot\n%#v", expected, ret) } }