1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211
|
package main
import (
"fmt"
"log"
"os"
"os/signal"
"strings"
"sync"
"syscall"
"time"
"github.com/fsnotify/fsnotify"
flag "github.com/ogier/pflag"
)
const defaultSubSymbol = "{}"
var (
reflexes []*Reflex
flagConf string
flagSequential bool
flagDecoration string
decoration Decoration
verbose bool
globalFlags = flag.NewFlagSet("", flag.ContinueOnError)
globalConfig = &Config{}
reflexID = 0
stdout = make(chan OutMsg, 1)
cleanupMu = &sync.Mutex{}
)
func usage() {
fmt.Fprintf(os.Stderr, `Usage: %s [OPTIONS] [COMMAND]
COMMAND is any command you'd like to run. Any instance of {} will be replaced
with the filename of the changed file. (The symbol may be changed with the
--substitute flag.)
OPTIONS are given below:
`, os.Args[0])
globalFlags.PrintDefaults()
fmt.Fprint(os.Stderr, `
Examples:
# Print each .txt file if it changes
$ reflex -r '\.txt$' echo {}
# Run 'make' if any of the .c files in this directory change:
$ reflex -g '*.c' make
# Build and run a server; rebuild and restart when .java files change:
$ reflex -r '\.java$' -s -- sh -c 'make && java bin/Server'
`)
}
func init() {
globalFlags.Usage = usage
globalFlags.StringVarP(&flagConf, "config", "c", "", `
A configuration file that describes how to run reflex
(or '-' to read the configuration from stdin).`)
globalFlags.BoolVarP(&verbose, "verbose", "v", false, `
Verbose mode: print out more information about what reflex is doing.`)
globalFlags.BoolVarP(&flagSequential, "sequential", "e", false, `
Don't run multiple commands at the same time.`)
globalFlags.StringVarP(&flagDecoration, "decoration", "d", "plain", `
How to decorate command output. Choices: none, plain, fancy.`)
globalConfig.registerFlags(globalFlags)
}
func anyNonGlobalsRegistered() bool {
any := false
walkFn := func(f *flag.Flag) {
switch f.Name {
case "config", "verbose", "sequential", "decoration":
default:
any = true
}
}
globalFlags.Visit(walkFn)
return any
}
func printGlobals() {
fmt.Println("Globals set at commandline")
walkFn := func(f *flag.Flag) {
fmt.Printf("| --%s (-%s) '%s' (default: '%s')\n",
f.Name, f.Shorthand, f.Value, f.DefValue)
}
globalFlags.Visit(walkFn)
fmt.Println("+---------")
}
func cleanup(reason string) {
cleanupMu.Lock()
fmt.Println(reason)
wg := &sync.WaitGroup{}
for _, reflex := range reflexes {
if reflex.Running() {
wg.Add(1)
go func(reflex *Reflex) {
reflex.terminate()
wg.Done()
}(reflex)
}
}
wg.Wait()
// Give just a little time to finish printing output.
time.Sleep(10 * time.Millisecond)
os.Exit(0)
}
func main() {
log.SetFlags(0)
if err := globalFlags.Parse(os.Args[1:]); err != nil {
log.Fatal(err)
}
globalConfig.command = globalFlags.Args()
globalConfig.source = "[commandline]"
if verbose {
printGlobals()
}
switch strings.ToLower(flagDecoration) {
case "none":
decoration = DecorationNone
case "plain":
decoration = DecorationPlain
case "fancy":
decoration = DecorationFancy
default:
log.Fatalf("Invalid decoration %s. Choices: none, plain, fancy.", flagDecoration)
}
var configs []*Config
if flagConf == "" {
if flagSequential {
log.Fatal("Cannot set --sequential without --config (because you cannot specify multiple commands).")
}
configs = []*Config{globalConfig}
} else {
if anyNonGlobalsRegistered() {
log.Fatal("Cannot set other flags along with --config other than --sequential, --verbose, and --decoration.")
}
var err error
configs, err = ReadConfigs(flagConf)
if err != nil {
log.Fatalln("Could not parse configs:", err)
}
if len(configs) == 0 {
log.Fatal("No configurations found")
}
}
for _, config := range configs {
reflex, err := NewReflex(config)
if err != nil {
log.Fatalln("Could not make reflex for config:", err)
}
if verbose {
fmt.Println(reflex)
}
reflexes = append(reflexes, reflex)
}
// 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 {
log.Fatal(err)
}
defer watcher.Close()
changes := make(chan string)
broadcastChanges := make([]chan string, len(reflexes))
done := make(chan error)
for i := range reflexes {
broadcastChanges[i] = make(chan string)
}
go watch(".", watcher, changes, done, reflexes)
go broadcast(broadcastChanges, changes)
go printOutput(stdout, os.Stdout)
for i, reflex := range reflexes {
reflex.Start(broadcastChanges[i])
}
log.Fatal(<-done)
}
func broadcast(outs []chan string, in <-chan string) {
for e := range in {
for _, out := range outs {
out <- e
}
}
}
|