-
Notifications
You must be signed in to change notification settings - Fork 15
/
Copy pathjustrun.go
154 lines (139 loc) · 3.54 KB
/
justrun.go
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
package main
import (
"bufio"
"errors"
"flag"
"fmt"
"log"
"os"
"os/signal"
"strings"
"sync"
"syscall"
"time"
)
var (
help = flag.Bool("help", false, "print this help text")
h = flag.Bool("h", false, "print this help text")
command = flag.String("c", "", "command to run when files change in given directories")
shell = flag.String("s", "sh", "shell to run the command")
ignoreFlag pathsFlag
stdin = flag.Bool("stdin", false, "read list of files to track from stdin, not the command-line")
waitForCommand = flag.Bool("w", false, "wait for the command to finish and do not attempt to kill it")
delayDur = flag.Duration("delay", 750*time.Millisecond, "the time to wait between runs of the command if many fs events occur")
verbose = flag.Bool("v", false, "verbose output")
)
func usage() {
fmt.Fprintf(os.Stderr, "usage: justrun -c 'SOME BASH COMMAND' [FILEPATH]*\n")
flag.PrintDefaults()
os.Exit(1)
}
func argError(format string, obj ...interface{}) {
fmt.Fprintf(os.Stderr, "justrun: "+format+"\n", obj...)
usage()
}
func main() {
flag.Var(&ignoreFlag, "i", "a file path to ignore events from (may be given multiple times)")
flag.Usage = usage
flag.Parse()
if *help || *h {
argError("help requested")
}
if len(*command) == 0 {
argError("no command given with -c")
}
if *stdin && len(flag.Args()) != 0 {
argError("expected files to come in over stdin, but got paths '%s' in the commandline", strings.Join(flag.Args(), ", "))
}
var inputPaths []string
if *stdin {
sc := bufio.NewScanner(os.Stdin)
for sc.Scan() {
inputPaths = append(inputPaths, sc.Text())
}
if sc.Err() != nil {
argError("error reading from stdin: %s", sc.Err())
}
} else {
inputPaths = flag.Args()
}
if len(inputPaths) == 0 {
argError("no file paths provided to watch")
}
cmd := &cmdReloader{
cond: &sync.Cond{L: new(sync.Mutex)},
command: *command,
shell: *shell,
waitForCommand: *waitForCommand,
}
sigCh := make(chan os.Signal, 10)
signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM)
go waitForInterrupt(sigCh, cmd)
cmdCh := make(chan event, 100)
_, err := watch(inputPaths, ignoreFlag, cmdCh)
if err != nil {
log.Fatal(err)
}
wasDelayed := false
lastStartTime := time.Now()
cmd.Reload()
tick := time.NewTicker(*delayDur)
for {
select {
case ev, ok := <-cmdCh:
if !ok {
return
}
if lastStartTime.After(ev.Time) {
continue
}
// Using delayDur here and in NewTicker is slightly semantically
// incorrect, but it simplifies our config and prevents the
// egregious reloading.
if time.Now().Sub(ev.Time) < *delayDur {
wasDelayed = true
continue
}
wasDelayed = false
lastStartTime = time.Now()
cmd.Reload()
tick.Stop()
tick = time.NewTicker(*delayDur)
case <-tick.C:
if wasDelayed {
wasDelayed = false
lastStartTime = time.Now()
cmd.Reload()
}
}
}
}
func waitForInterrupt(sigCh chan os.Signal, cmd *cmdReloader) {
for range sigCh {
go func() {
cmd.Terminate()
os.Exit(0)
}()
}
}
type pathsFlag []string
func (pf *pathsFlag) String() string {
return fmt.Sprint(*pf)
}
func (pf *pathsFlag) Set(value string) error {
// TODO(jmhodges): remove comma Split in 2.0
// Only for backwards compatibilty with old -i
vals := strings.Split(value, ",")
before := len(*pf)
for _, p := range vals {
p = strings.TrimSpace(p)
if p == "" {
continue
}
*pf = append(*pf, p)
}
if before == len(*pf) {
return errors.New("a file path may not be blank")
}
return nil
}