poll-rerun 0.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +34 -0
- data/README.md +377 -0
- data/Rakefile +82 -0
- data/bin/rerun +12 -0
- data/icons/rails_grn_sml.png +0 -0
- data/icons/rails_red_sml.png +0 -0
- data/lib/goo.rb +3 -0
- data/lib/rerun/glob.rb +85 -0
- data/lib/rerun/notification.rb +64 -0
- data/lib/rerun/options.rb +124 -0
- data/lib/rerun/runner.rb +339 -0
- data/lib/rerun/system.rb +22 -0
- data/lib/rerun/watcher.rb +130 -0
- data/lib/rerun.rb +15 -0
- data/poll-rerun.gemspec +34 -0
- metadata +78 -0
@@ -0,0 +1,124 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'pathname'
|
3
|
+
require 'rerun/watcher'
|
4
|
+
|
5
|
+
libdir = "#{File.expand_path(File.dirname(File.dirname(__FILE__)))}"
|
6
|
+
|
7
|
+
$spec = Gem::Specification.load(File.join(libdir, "..", "rerun.gemspec"))
|
8
|
+
|
9
|
+
module Rerun
|
10
|
+
class Options
|
11
|
+
DEFAULT_PATTERN = "**/*.{rb,js,coffee,css,scss,sass,erb,html,haml,ru,yml,slim,md,feature}"
|
12
|
+
DEFAULT_DIRS = ["."]
|
13
|
+
|
14
|
+
DEFAULTS = {
|
15
|
+
:pattern => DEFAULT_PATTERN,
|
16
|
+
:signal => "TERM",
|
17
|
+
:notify => true,
|
18
|
+
:name => Pathname.getwd.basename.to_s.capitalize,
|
19
|
+
:ignore => [],
|
20
|
+
:dir => DEFAULT_DIRS,
|
21
|
+
:force_polling => false,
|
22
|
+
}
|
23
|
+
|
24
|
+
def self.parse args = ARGV
|
25
|
+
|
26
|
+
default_options = DEFAULTS.dup
|
27
|
+
options = {
|
28
|
+
ignore: []
|
29
|
+
}
|
30
|
+
|
31
|
+
opts = OptionParser.new("", 24, ' ') do |opts|
|
32
|
+
opts.banner = "Usage: rerun [options] [--] cmd"
|
33
|
+
|
34
|
+
opts.separator ""
|
35
|
+
opts.separator "Launches an app, and restarts it when the filesystem changes."
|
36
|
+
opts.separator "See http://github.com/alexch/rerun for more info."
|
37
|
+
opts.separator "Version: #{$spec.version}"
|
38
|
+
opts.separator ""
|
39
|
+
opts.separator "Options:"
|
40
|
+
|
41
|
+
opts.on("-d dir", "--dir dir", "directory to watch, default = \"#{DEFAULT_DIRS}\". Specify multiple paths with ',' or separate '-d dir' option pairs.") do |dir|
|
42
|
+
elements = dir.split(",")
|
43
|
+
options[:dir] = (options[:dir] || []) + elements
|
44
|
+
end
|
45
|
+
|
46
|
+
# todo: rename to "--watch"
|
47
|
+
opts.on("-p pattern", "--pattern pattern", "file glob to watch, default = \"#{DEFAULTS[:pattern]}\"") do |pattern|
|
48
|
+
options[:pattern] = pattern
|
49
|
+
end
|
50
|
+
|
51
|
+
opts.on("-i pattern", "--ignore pattern", "file glob to ignore (can be set many times). To ignore a directory, you must append '/*' e.g. --ignore 'coverage/*'") do |pattern|
|
52
|
+
options[:ignore] += [pattern]
|
53
|
+
end
|
54
|
+
|
55
|
+
opts.on("-s signal", "--signal signal", "terminate process using this signal, default = \"#{DEFAULTS[:signal]}\"") do |signal|
|
56
|
+
options[:signal] = signal
|
57
|
+
end
|
58
|
+
|
59
|
+
opts.on("-r", "--restart", "expect process to restart itself (uses the HUP signal unless overridden using --signal)") do |signal|
|
60
|
+
options[:restart] = true
|
61
|
+
default_options[:signal] = "HUP"
|
62
|
+
end
|
63
|
+
|
64
|
+
opts.on("-c", "--clear", "clear screen before each run") do
|
65
|
+
options[:clear] = true
|
66
|
+
end
|
67
|
+
|
68
|
+
opts.on("-x", "--exit", "expect the program to exit. With this option, rerun checks the return value; without it, rerun checks that the process is running.") do |dir|
|
69
|
+
options[:exit] = true
|
70
|
+
end
|
71
|
+
|
72
|
+
opts.on("-b", "--background", "disable on-the-fly commands, allowing the process to be backgrounded") do
|
73
|
+
options[:background] = true
|
74
|
+
end
|
75
|
+
|
76
|
+
opts.on("-n name", "--name name", "name of app used in logs and notifications, default = \"#{DEFAULTS[:name]}\"") do |name|
|
77
|
+
options[:name] = name
|
78
|
+
end
|
79
|
+
|
80
|
+
opts.on("--force-polling", "use polling instead of a native filesystem scan (useful for Vagrant)") do
|
81
|
+
options[:force_polling] = true
|
82
|
+
end
|
83
|
+
|
84
|
+
opts.on("--no-growl", "don't use growl [OBSOLETE]") do
|
85
|
+
options[:growl] = false
|
86
|
+
$stderr.puts "--no-growl is obsolete; use --no-notify instead"
|
87
|
+
return
|
88
|
+
end
|
89
|
+
|
90
|
+
opts.on("--[no-]notify [notifier]", "send messages through growl (requires growlnotify) or osx (requires terminal-notifier gem)") do |notifier|
|
91
|
+
notifier = true if notifier.nil?
|
92
|
+
options[:notify] = notifier
|
93
|
+
end
|
94
|
+
|
95
|
+
opts.on_tail("-h", "--help", "--usage", "show this message") do
|
96
|
+
puts opts
|
97
|
+
return
|
98
|
+
end
|
99
|
+
|
100
|
+
opts.on_tail("--version", "show version") do
|
101
|
+
puts $spec.version
|
102
|
+
return
|
103
|
+
end
|
104
|
+
|
105
|
+
opts.on_tail ""
|
106
|
+
opts.on_tail "On top of --pattern and --ignore, we ignore any changes to files and dirs starting with a dot."
|
107
|
+
|
108
|
+
end
|
109
|
+
|
110
|
+
if args.empty?
|
111
|
+
puts opts
|
112
|
+
nil
|
113
|
+
else
|
114
|
+
opts.parse! args
|
115
|
+
default_options[:cmd] = args.join(" ")
|
116
|
+
|
117
|
+
options = default_options.merge(options)
|
118
|
+
|
119
|
+
options
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
end
|
124
|
+
end
|
data/lib/rerun/runner.rb
ADDED
@@ -0,0 +1,339 @@
|
|
1
|
+
require 'timeout'
|
2
|
+
require 'io/wait'
|
3
|
+
|
4
|
+
module Rerun
|
5
|
+
class Runner
|
6
|
+
|
7
|
+
def self.keep_running(cmd, options)
|
8
|
+
runner = new(cmd, options)
|
9
|
+
runner.start
|
10
|
+
runner.join
|
11
|
+
# apparently runner doesn't keep running anymore (as of Listen 2) so we have to sleep forever :-(
|
12
|
+
sleep 10000 while true # :-(
|
13
|
+
end
|
14
|
+
|
15
|
+
include System
|
16
|
+
include ::Timeout
|
17
|
+
|
18
|
+
def initialize(run_command, options = {})
|
19
|
+
@run_command, @options = run_command, options
|
20
|
+
@run_command = "ruby #{@run_command}" if @run_command.split(' ').first =~ /\.rb$/
|
21
|
+
end
|
22
|
+
|
23
|
+
def start_keypress_thread
|
24
|
+
return if @options[:background]
|
25
|
+
|
26
|
+
@keypress_thread = Thread.new do
|
27
|
+
while true
|
28
|
+
if c = key_pressed
|
29
|
+
case c.downcase
|
30
|
+
when 'c'
|
31
|
+
say "Clearing screen"
|
32
|
+
clear_screen
|
33
|
+
when 'r'
|
34
|
+
say "Restarting"
|
35
|
+
restart
|
36
|
+
when 'p'
|
37
|
+
toggle_pause if watcher_running?
|
38
|
+
when 'x', 'q'
|
39
|
+
die
|
40
|
+
break # the break will stop this thread, in case the 'die' doesn't
|
41
|
+
else
|
42
|
+
puts "\n#{c.inspect} pressed inside rerun"
|
43
|
+
puts [["c", "clear screen"],
|
44
|
+
["r", "restart"],
|
45
|
+
["p", "toggle pause"],
|
46
|
+
["x or q", "stop and exit"]
|
47
|
+
].map { |key, description| " #{key} -- #{description}" }.join("\n")
|
48
|
+
puts
|
49
|
+
end
|
50
|
+
end
|
51
|
+
sleep 1 # todo: use select instead of polling somehow?
|
52
|
+
end
|
53
|
+
end
|
54
|
+
@keypress_thread.run
|
55
|
+
end
|
56
|
+
|
57
|
+
def stop_keypress_thread
|
58
|
+
@keypress_thread.kill if @keypress_thread
|
59
|
+
@keypress_thread = nil
|
60
|
+
end
|
61
|
+
|
62
|
+
def restart
|
63
|
+
@restarting = true
|
64
|
+
if @options[:restart]
|
65
|
+
restart_with_signal(@options[:signal])
|
66
|
+
else
|
67
|
+
stop
|
68
|
+
start
|
69
|
+
end
|
70
|
+
@restarting = false
|
71
|
+
end
|
72
|
+
|
73
|
+
def watcher_running?
|
74
|
+
@watcher && @watcher.running?
|
75
|
+
end
|
76
|
+
|
77
|
+
def toggle_pause
|
78
|
+
unless @pausing
|
79
|
+
say "Pausing. Press 'p' again to resume."
|
80
|
+
@watcher.pause
|
81
|
+
@pausing = true
|
82
|
+
else
|
83
|
+
say "Resuming."
|
84
|
+
@watcher.unpause
|
85
|
+
@pausing = false
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def unpause
|
90
|
+
@watcher.unpause
|
91
|
+
end
|
92
|
+
|
93
|
+
def dir
|
94
|
+
@options[:dir]
|
95
|
+
end
|
96
|
+
|
97
|
+
def dirs
|
98
|
+
@options[:dir] || "."
|
99
|
+
end
|
100
|
+
|
101
|
+
def pattern
|
102
|
+
@options[:pattern]
|
103
|
+
end
|
104
|
+
|
105
|
+
def ignore
|
106
|
+
@options[:ignore] || []
|
107
|
+
end
|
108
|
+
|
109
|
+
def clear?
|
110
|
+
@options[:clear]
|
111
|
+
end
|
112
|
+
|
113
|
+
def exit?
|
114
|
+
@options[:exit]
|
115
|
+
end
|
116
|
+
|
117
|
+
def app_name
|
118
|
+
@options[:name]
|
119
|
+
end
|
120
|
+
|
121
|
+
def restart_with_signal(restart_signal)
|
122
|
+
if @pid && (@pid != 0)
|
123
|
+
notify "restarting", "We will be with you shortly."
|
124
|
+
signal(restart_signal)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def force_polling
|
129
|
+
@options[:force_polling]
|
130
|
+
end
|
131
|
+
|
132
|
+
def start
|
133
|
+
if @already_running
|
134
|
+
taglines = [
|
135
|
+
"Here we go again!",
|
136
|
+
"Keep on trucking.",
|
137
|
+
"Once more unto the breach, dear friends, once more!",
|
138
|
+
"The road goes ever on and on, down from the door where it began.",
|
139
|
+
]
|
140
|
+
notify "restarted", taglines[rand(taglines.size)]
|
141
|
+
else
|
142
|
+
taglines = [
|
143
|
+
"To infinity... and beyond!",
|
144
|
+
"Charge!",
|
145
|
+
]
|
146
|
+
notify "launched", taglines[rand(taglines.size)]
|
147
|
+
@already_running = true
|
148
|
+
end
|
149
|
+
|
150
|
+
clear_screen if clear?
|
151
|
+
start_keypress_thread unless @keypress_thread
|
152
|
+
|
153
|
+
begin
|
154
|
+
@pid = run @run_command
|
155
|
+
rescue => e
|
156
|
+
puts "#{e.class}: #{e.message}"
|
157
|
+
exit
|
158
|
+
end
|
159
|
+
|
160
|
+
status_thread = Process.detach(@pid) # so if the child exits, it dies
|
161
|
+
|
162
|
+
Signal.trap("INT") do # INT = control-C -- allows user to stop the top-level rerun process
|
163
|
+
die
|
164
|
+
end
|
165
|
+
|
166
|
+
Signal.trap("TERM") do # TERM is the polite way of terminating a process
|
167
|
+
die
|
168
|
+
end
|
169
|
+
|
170
|
+
begin
|
171
|
+
sleep 2
|
172
|
+
rescue Interrupt => e
|
173
|
+
# in case someone hits control-C immediately ("oops!")
|
174
|
+
die
|
175
|
+
end
|
176
|
+
|
177
|
+
if exit?
|
178
|
+
status = status_thread.value
|
179
|
+
if status.success?
|
180
|
+
notify "succeeded", ""
|
181
|
+
else
|
182
|
+
notify "failed", "Exit status #{status.exitstatus}"
|
183
|
+
end
|
184
|
+
else
|
185
|
+
if !running?
|
186
|
+
notify "Launch Failed", "See console for error output"
|
187
|
+
@already_running = false
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
unless @watcher
|
192
|
+
|
193
|
+
watcher = Watcher.new(:directory => dirs, :pattern => pattern, :ignore => ignore, :force_polling => force_polling) do |changes|
|
194
|
+
|
195
|
+
message = change_message(changes)
|
196
|
+
|
197
|
+
say "Change detected: #{message}"
|
198
|
+
restart unless @restarting
|
199
|
+
end
|
200
|
+
watcher.start
|
201
|
+
@watcher = watcher
|
202
|
+
say "Watching #{dir.join(', ')} for #{pattern}" +
|
203
|
+
(ignore.empty? ? "" : " (ignoring #{ignore.join(',')})") +
|
204
|
+
(watcher.adapter.nil? ? "" : " with #{watcher.adapter_name} adapter")
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
def run command
|
209
|
+
Kernel.spawn command
|
210
|
+
end
|
211
|
+
|
212
|
+
def change_message(changes)
|
213
|
+
message = [:modified, :added, :removed].map do |change|
|
214
|
+
count = changes[change] ? changes[change].size : 0
|
215
|
+
if count > 0
|
216
|
+
"#{count} #{change}"
|
217
|
+
end
|
218
|
+
end.compact.join(", ")
|
219
|
+
|
220
|
+
changed_files = changes.values.flatten
|
221
|
+
if changed_files.count > 0
|
222
|
+
message += ": "
|
223
|
+
message += changes.values.flatten[0..3].map { |path| path.split('/').last }.join(', ')
|
224
|
+
if changed_files.count > 3
|
225
|
+
message += ", ..."
|
226
|
+
end
|
227
|
+
end
|
228
|
+
message
|
229
|
+
end
|
230
|
+
|
231
|
+
def die
|
232
|
+
#stop_keypress_thread # don't do this since we're probably *in* the keypress thread
|
233
|
+
stop # stop the child process if it exists
|
234
|
+
exit 0 # todo: status code param
|
235
|
+
end
|
236
|
+
|
237
|
+
def join
|
238
|
+
@watcher.join
|
239
|
+
end
|
240
|
+
|
241
|
+
def running?
|
242
|
+
signal(0)
|
243
|
+
end
|
244
|
+
|
245
|
+
def signal(signal)
|
246
|
+
say "Sending signal #{signal} to #{@pid}" unless signal == 0
|
247
|
+
Process.kill(signal, @pid)
|
248
|
+
true
|
249
|
+
rescue
|
250
|
+
false
|
251
|
+
end
|
252
|
+
|
253
|
+
# todo: test escalation
|
254
|
+
def stop
|
255
|
+
default_signal = @options[:signal] || "TERM"
|
256
|
+
if @pid && (@pid != 0)
|
257
|
+
notify "stopping", "All good things must come to an end." unless @restarting
|
258
|
+
begin
|
259
|
+
timeout(5) do # todo: escalation timeout setting
|
260
|
+
# start with a polite SIGTERM
|
261
|
+
signal(default_signal) && Process.wait(@pid)
|
262
|
+
end
|
263
|
+
rescue Timeout::Error
|
264
|
+
begin
|
265
|
+
timeout(5) do
|
266
|
+
# escalate to SIGINT aka control-C since some foolish process may be ignoring SIGTERM
|
267
|
+
signal("INT") && Process.wait(@pid)
|
268
|
+
end
|
269
|
+
rescue Timeout::Error
|
270
|
+
# escalate to SIGKILL aka "kill -9" which cannot be ignored
|
271
|
+
signal("KILL") && Process.wait(@pid)
|
272
|
+
end
|
273
|
+
end
|
274
|
+
end
|
275
|
+
rescue => e
|
276
|
+
false
|
277
|
+
end
|
278
|
+
|
279
|
+
def git_head_changed?
|
280
|
+
old_git_head = @git_head
|
281
|
+
read_git_head
|
282
|
+
@git_head and old_git_head and @git_head != old_git_head
|
283
|
+
end
|
284
|
+
|
285
|
+
def read_git_head
|
286
|
+
git_head_file = File.join(dir, '.git', 'HEAD')
|
287
|
+
@git_head = File.exists?(git_head_file) && File.read(git_head_file)
|
288
|
+
end
|
289
|
+
|
290
|
+
def notify(title, body, background = true)
|
291
|
+
Notification.new(title, body, @options).send(background) if @options[:notify]
|
292
|
+
puts
|
293
|
+
say "#{app_name} #{title}"
|
294
|
+
end
|
295
|
+
|
296
|
+
def say msg
|
297
|
+
puts "#{Time.now.strftime("%T")} [rerun] #{msg}"
|
298
|
+
end
|
299
|
+
|
300
|
+
# non-blocking stdin reader.
|
301
|
+
# returns a 1-char string if a key was pressed; otherwise nil
|
302
|
+
#
|
303
|
+
def key_pressed
|
304
|
+
begin
|
305
|
+
# this "raw input" nonsense is because unix likes waiting for linefeeds before sending stdin
|
306
|
+
|
307
|
+
# 'raw' means turn raw input on
|
308
|
+
|
309
|
+
# restore proper output newline handling -- see stty.rb and "man stty" and /usr/include/sys/termios.h
|
310
|
+
# looks like "raw" flips off the OPOST bit 0x00000001 /* enable following output processing */
|
311
|
+
# which disables #define ONLCR 0x00000002 /* map NL to CR-NL (ala CRMOD) */
|
312
|
+
# so this sets it back on again since all we care about is raw input, not raw output
|
313
|
+
system("stty raw opost")
|
314
|
+
|
315
|
+
c = nil
|
316
|
+
if $stdin.ready?
|
317
|
+
c = $stdin.getc
|
318
|
+
end
|
319
|
+
c.chr if c
|
320
|
+
ensure
|
321
|
+
system "stty -raw" # turn raw input off
|
322
|
+
end
|
323
|
+
|
324
|
+
# note: according to 'man tty' the proper way restore the settings is
|
325
|
+
# tty_state=`stty -g`
|
326
|
+
# ensure
|
327
|
+
# system 'stty "#{tty_state}'
|
328
|
+
# end
|
329
|
+
# but this way seems fine and less confusing
|
330
|
+
|
331
|
+
end
|
332
|
+
|
333
|
+
def clear_screen
|
334
|
+
# see http://ascii-table.com/ansi-escape-sequences-vt-100.php
|
335
|
+
$stdout.print "\033[H\033[2J"
|
336
|
+
end
|
337
|
+
|
338
|
+
end
|
339
|
+
end
|
data/lib/rerun/system.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
module Rerun
|
2
|
+
module System
|
3
|
+
|
4
|
+
def mac?
|
5
|
+
RUBY_PLATFORM =~ /darwin/i
|
6
|
+
end
|
7
|
+
|
8
|
+
def windows?
|
9
|
+
RUBY_PLATFORM =~ /mswin/i
|
10
|
+
end
|
11
|
+
|
12
|
+
def linux?
|
13
|
+
RUBY_PLATFORM =~ /linux/i
|
14
|
+
end
|
15
|
+
|
16
|
+
def rails?
|
17
|
+
rails_sig_file = File.expand_path(".")+"/config/boot.rb"
|
18
|
+
File.exists? rails_sig_file
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
require 'listen'
|
2
|
+
|
3
|
+
Thread.abort_on_exception = true
|
4
|
+
|
5
|
+
# This class will watch a directory and alert you of
|
6
|
+
# new files, modified files, deleted files.
|
7
|
+
#
|
8
|
+
# Now uses the Listen gem, but spawns its own thread on top.
|
9
|
+
# We should probably be accessing the Listen thread directly.
|
10
|
+
#
|
11
|
+
# Author: Alex Chaffee
|
12
|
+
#
|
13
|
+
module Rerun
|
14
|
+
class Watcher
|
15
|
+
InvalidDirectoryError = Class.new(RuntimeError)
|
16
|
+
|
17
|
+
#def self.default_ignore
|
18
|
+
# Listen::Silencer.new(Listen::Listener.new).send :_default_ignore_patterns
|
19
|
+
#end
|
20
|
+
|
21
|
+
attr_reader :directory, :pattern, :priority
|
22
|
+
|
23
|
+
# Create a file system watcher. Start it by calling #start.
|
24
|
+
#
|
25
|
+
# @param options[:directory] the directory to watch (default ".")
|
26
|
+
# @param options[:pattern] the glob pattern to search under the watched directory (default "**/*")
|
27
|
+
# @param options[:priority] the priority of the watcher thread (default 0)
|
28
|
+
#
|
29
|
+
def initialize(options = {}, &client_callback)
|
30
|
+
@client_callback = client_callback
|
31
|
+
|
32
|
+
options = {
|
33
|
+
:directory => ".",
|
34
|
+
:pattern => "**/*",
|
35
|
+
:priority => 0,
|
36
|
+
}.merge(options)
|
37
|
+
|
38
|
+
@pattern = options[:pattern]
|
39
|
+
@directories = options[:directory]
|
40
|
+
@directories = sanitize_dirs(@directories)
|
41
|
+
@priority = options[:priority]
|
42
|
+
@force_polling = options[:force_polling]
|
43
|
+
@ignore = [options[:ignore]].flatten.compact
|
44
|
+
@thread = nil
|
45
|
+
end
|
46
|
+
|
47
|
+
def sanitize_dirs(dirs)
|
48
|
+
dirs = [*dirs]
|
49
|
+
dirs.map do |d|
|
50
|
+
d.chomp!("/")
|
51
|
+
unless FileTest.exists?(d) && FileTest.readable?(d) && FileTest.directory?(d)
|
52
|
+
raise InvalidDirectoryError, "Directory '#{d}' either doesnt exist or isn't readable"
|
53
|
+
end
|
54
|
+
File.expand_path(d)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def start
|
59
|
+
if @thread then
|
60
|
+
raise RuntimeError, "already started"
|
61
|
+
end
|
62
|
+
|
63
|
+
@thread = Thread.new do
|
64
|
+
@listener = Listen.to(*@directories, only: watching, ignore: ignoring, wait_for_delay: 1, force_polling: @force_polling) do |modified, added, removed|
|
65
|
+
if((modified.size + added.size + removed.size) > 0)
|
66
|
+
@client_callback.call(:modified => modified, :added => added, :removed => removed)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
@listener.start
|
70
|
+
end
|
71
|
+
|
72
|
+
@thread.priority = @priority
|
73
|
+
|
74
|
+
sleep 0.1 until @listener
|
75
|
+
|
76
|
+
at_exit { stop } # try really hard to clean up after ourselves
|
77
|
+
end
|
78
|
+
|
79
|
+
def watching
|
80
|
+
Rerun::Glob.new(@pattern).to_regexp
|
81
|
+
end
|
82
|
+
|
83
|
+
def ignoring
|
84
|
+
# todo: --no-ignore-dotfiles
|
85
|
+
dotfiles = /^\.[^.]/ # at beginning of string, a real dot followed by any other character
|
86
|
+
[dotfiles] + @ignore.map { |x| Rerun::Glob.new(x).to_regexp }
|
87
|
+
end
|
88
|
+
|
89
|
+
# kill the file watcher thread
|
90
|
+
def stop
|
91
|
+
@thread.wakeup rescue ThreadError
|
92
|
+
begin
|
93
|
+
@listener.stop
|
94
|
+
rescue Exception => e
|
95
|
+
puts "#{e.class}: #{e.message} stopping listener"
|
96
|
+
end
|
97
|
+
@thread.kill rescue ThreadError
|
98
|
+
end
|
99
|
+
|
100
|
+
# wait for the file watcher to finish
|
101
|
+
def join
|
102
|
+
@thread.join if @thread
|
103
|
+
rescue Interrupt => e
|
104
|
+
# don't care
|
105
|
+
end
|
106
|
+
|
107
|
+
def pause
|
108
|
+
@listener.pause if @listener
|
109
|
+
end
|
110
|
+
|
111
|
+
def unpause
|
112
|
+
@listener.start if @listener
|
113
|
+
end
|
114
|
+
|
115
|
+
def running?
|
116
|
+
@listener && @listener.processing?
|
117
|
+
end
|
118
|
+
|
119
|
+
def adapter
|
120
|
+
@listener &&
|
121
|
+
(backend = @listener.instance_variable_get(:@backend)) &&
|
122
|
+
backend.instance_variable_get(:@adapter)
|
123
|
+
end
|
124
|
+
|
125
|
+
def adapter_name
|
126
|
+
adapter && adapter.class.name.split('::').last
|
127
|
+
end
|
128
|
+
|
129
|
+
end
|
130
|
+
end
|
data/lib/rerun.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
here = File.expand_path(File.dirname(__FILE__))
|
2
|
+
$: << here unless $:.include?(here)
|
3
|
+
|
4
|
+
require "listen" # pull in the Listen gem
|
5
|
+
require "rerun/options"
|
6
|
+
require "rerun/system"
|
7
|
+
require "rerun/notification"
|
8
|
+
require "rerun/runner"
|
9
|
+
require "rerun/watcher"
|
10
|
+
require "rerun/glob"
|
11
|
+
|
12
|
+
module Rerun
|
13
|
+
|
14
|
+
end
|
15
|
+
|
data/poll-rerun.gemspec
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
$spec = Gem::Specification.new do |s|
|
2
|
+
s.specification_version = 2 if s.respond_to? :specification_version=
|
3
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
4
|
+
|
5
|
+
s.name = 'poll-rerun'
|
6
|
+
s.version = '0.11.1'
|
7
|
+
|
8
|
+
s.description = "Fork of the original rerun gem but with a 'released' --force-polling flag available. Restarts your app when a file changes. A no-frills, command-line alternative to Guard, Shotgun, Autotest, etc."
|
9
|
+
s.summary = "Fork of original rerun, but with 'released' --force-polling flag. Launches an app, and restarts it whenever the filesystem changes. A no-frills, command-line alternative to Guard, Shotgun, Autotest, etc."
|
10
|
+
|
11
|
+
s.authors = ["Alex Chaffee"]
|
12
|
+
s.email = "alex@stinky.com"
|
13
|
+
|
14
|
+
s.files = %w[
|
15
|
+
README.md
|
16
|
+
LICENSE
|
17
|
+
Rakefile
|
18
|
+
poll-rerun.gemspec
|
19
|
+
bin/rerun
|
20
|
+
icons/rails_grn_sml.png
|
21
|
+
icons/rails_red_sml.png] +
|
22
|
+
Dir['lib/**/*.rb']
|
23
|
+
s.executables = ['rerun']
|
24
|
+
s.test_files = s.files.select {|path| path =~ /^spec\/.*_spec.rb/}
|
25
|
+
|
26
|
+
s.extra_rdoc_files = %w[README.md]
|
27
|
+
|
28
|
+
s.add_runtime_dependency 'listen', '~> 3.0'
|
29
|
+
|
30
|
+
s.homepage = "http://github.com/hawry/rerun"
|
31
|
+
s.require_paths = %w[lib]
|
32
|
+
|
33
|
+
s.license = 'MIT'
|
34
|
+
end
|