func main() { if err := globalFlags.Parse(os.Args[1:]); err != nil { Fatalln(err) } if verbose { printGlobals() } switch strings.ToLower(flagDecoration) { case "none": decoration = DecorationNone case "plain": decoration = DecorationPlain case "fancy": decoration = DecorationFancy default: Fatalln(fmt.Sprintf("Invalid decoration %s. Choices: none, plain, fancy.", flagDecoration)) } if flagConf == "" { reflex, err := NewReflex(globalConfig, globalFlags.Args()) if err != nil { Fatalln(err) } if verbose { reflex.PrintInfo("commandline") } reflexes = append(reflexes, reflex) if flagSequential { Fatalln("Cannot set --sequential without --config (because you cannot specify multiple commands).") } } else { if anyNonGlobalsRegistered() { Fatalln("Cannot set other flags along with --config other than --sequential, --verbose, and --decoration.") } // Now open the configuration file. // As a special case we read the config from stdin if --config is set to "-" var config io.ReadCloser if flagConf == "-" { config = os.Stdin } else { configFile, err := os.Open(flagConf) if err != nil { Fatalln(err) } config = configFile } scanner := bufio.NewScanner(config) lineNo := 0 for scanner.Scan() { lineNo++ errorMsg := fmt.Sprintf("Error on line %d of %s:", lineNo, flagConf) config := &Config{} flags := flag.NewFlagSet("", flag.ContinueOnError) registerFlags(flags, config) parts, err := shellquote.Split(scanner.Text()) if err != nil { Fatalln(errorMsg, err) } // Skip empty lines and comments (lines starting with #). if len(parts) == 0 || strings.HasPrefix(parts[0], "#") { continue } if err := flags.Parse(parts); err != nil { Fatalln(errorMsg, err) } reflex, err := NewReflex(config, flags.Args()) if err != nil { Fatalln(errorMsg, err) } if verbose { reflex.PrintInfo(fmt.Sprintf("%s, line %d", flagConf, lineNo)) } reflexes = append(reflexes, reflex) } if err := scanner.Err(); err != nil { Fatalln(err) } config.Close() } // Catch ctrl-c and make sure to kill off children. signals := make(chan os.Signal, 1) signal.Notify(signals, os.Interrupt) signal.Notify(signals, os.Signal(syscall.SIGTERM)) go func() { s := <-signals reason := fmt.Sprintf("Interrupted (%s). Cleaning up children...", s) cleanup(reason) }() defer cleanup("Cleaning up.") watcher, err := fsnotify.NewWatcher() if err != nil { Fatalln(err) } defer watcher.Close() rawChanges := make(chan string) allRawChanges := make([]chan<- string, len(reflexes)) done := make(chan error) for i, reflex := range reflexes { allRawChanges[i] = reflex.rawChanges } go watch(".", watcher, rawChanges, done) go broadcast(rawChanges, allRawChanges) go printOutput(stdout, os.Stdout) for _, reflex := range reflexes { go filterMatching(reflex.rawChanges, reflex.filtered, reflex) go batch(reflex.filtered, reflex.batched, reflex) go runEach(reflex.batched, reflex) if reflex.startService { // Easy hack to kick off the initial start. infoPrintln(reflex.id, "Starting service") runCommand(reflex, "", stdout) } } Fatalln(<-done) }
const ( DecorationNone = iota DecorationPlain DecorationFancy ) var ( reflexes []*Reflex matchAll = regexp.MustCompile(".*") flagConf string flagSequential bool flagDecoration string decoration Decoration verbose bool globalFlags = flag.NewFlagSet("", flag.ContinueOnError) globalConfig = &Config{} reflexID = 0 stdout = make(chan OutMsg, 100) cleanupMut = &sync.Mutex{} ) type Config struct { regex string glob string subSymbol string startService bool onlyFiles bool onlyDirs bool