relaunch 0.0.1 → 0.16.0
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 +4 -4
- data/Changelog.md +32 -0
- data/Fork.md +59 -0
- data/LICENSE +34 -0
- data/README.md +491 -0
- data/Rakefile +75 -0
- data/bin/relaunch +3 -0
- data/bin/relaunch.bat +3 -0
- data/bin/rerun +22 -0
- data/bin/rerun.bat +3 -0
- data/icons/rails_grn_sml.png +0 -0
- data/icons/rails_red_sml.png +0 -0
- data/lib/relaunch.rb +1 -1
- data/lib/rerun/glob.rb +85 -0
- data/lib/rerun/notification.rb +83 -0
- data/lib/rerun/options.rb +148 -0
- data/lib/rerun/runner.rb +391 -0
- data/lib/rerun/system.rb +22 -0
- data/lib/rerun/watcher.rb +134 -0
- data/lib/rerun.rb +17 -0
- data/relaunch.gemspec +44 -0
- metadata +58 -12
data/lib/rerun/runner.rb
ADDED
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
require 'timeout'
|
|
2
|
+
require 'io/wait'
|
|
3
|
+
|
|
4
|
+
module Rerun
|
|
5
|
+
class Runner
|
|
6
|
+
|
|
7
|
+
# The watcher instance that wait for changes
|
|
8
|
+
attr_reader :watcher
|
|
9
|
+
|
|
10
|
+
def self.keep_running(cmd, options)
|
|
11
|
+
runner = new(cmd, options)
|
|
12
|
+
runner.start
|
|
13
|
+
runner.join
|
|
14
|
+
# apparently runner doesn't keep running anymore (as of Listen 2) so we have to sleep forever :-(
|
|
15
|
+
sleep 10000 while true # :-(
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
include System
|
|
19
|
+
include ::Timeout
|
|
20
|
+
|
|
21
|
+
def initialize(run_command, options = {})
|
|
22
|
+
@run_command, @options = run_command, options
|
|
23
|
+
@run_command = "ruby #{@run_command}" if @run_command.split(' ').first =~ /\.rb$/
|
|
24
|
+
@options[:directory] ||= options.delete(:dir) || '.'
|
|
25
|
+
@options[:ignore] ||= []
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def start_keypress_thread
|
|
29
|
+
return if @options[:background] || !interactive_terminal?
|
|
30
|
+
|
|
31
|
+
@keypress_thread = Thread.new do
|
|
32
|
+
while true
|
|
33
|
+
if c = key_pressed
|
|
34
|
+
case c.downcase
|
|
35
|
+
when 'c'
|
|
36
|
+
say "Clearing screen"
|
|
37
|
+
clear_screen
|
|
38
|
+
when 'r'
|
|
39
|
+
say "Restarting"
|
|
40
|
+
restart
|
|
41
|
+
when 'f'
|
|
42
|
+
say "Stopping and starting"
|
|
43
|
+
restart(false)
|
|
44
|
+
when 'p'
|
|
45
|
+
toggle_pause
|
|
46
|
+
when 'x', 'q'
|
|
47
|
+
die
|
|
48
|
+
break # the break will stop this thread, in case the 'die' doesn't
|
|
49
|
+
else
|
|
50
|
+
puts "\n#{c.inspect} pressed inside rerun"
|
|
51
|
+
puts [["c", "clear screen"],
|
|
52
|
+
["r", "restart"],
|
|
53
|
+
["f", "forced restart (stop and start)"],
|
|
54
|
+
["p", "toggle pause"],
|
|
55
|
+
["x or q", "stop and exit"]
|
|
56
|
+
].map {|key, description| " #{key} -- #{description}"}.join("\n")
|
|
57
|
+
puts
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
sleep 1 # todo: use select instead of polling somehow?
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
@keypress_thread.run
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def stop_keypress_thread
|
|
67
|
+
@keypress_thread.kill if @keypress_thread
|
|
68
|
+
@keypress_thread = nil
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def restart(with_signal = true)
|
|
72
|
+
@restarting = true
|
|
73
|
+
if @options[:restart] && with_signal
|
|
74
|
+
restart_with_signal(@options[:signal])
|
|
75
|
+
else
|
|
76
|
+
stop
|
|
77
|
+
start
|
|
78
|
+
end
|
|
79
|
+
@restarting = false
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def toggle_pause
|
|
83
|
+
unless @pausing
|
|
84
|
+
say "Pausing. Press 'p' again to resume."
|
|
85
|
+
@watcher.pause
|
|
86
|
+
@pausing = true
|
|
87
|
+
else
|
|
88
|
+
say "Resuming."
|
|
89
|
+
@watcher.unpause
|
|
90
|
+
@pausing = false
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def unpause
|
|
95
|
+
@watcher.unpause
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def dir
|
|
99
|
+
@options[:directory]
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def pattern
|
|
103
|
+
@options[:pattern]
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def clear?
|
|
107
|
+
@options[:clear]
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def quiet?
|
|
111
|
+
@options[:quiet]
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def verbose?
|
|
115
|
+
@options[:verbose]
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def exit?
|
|
119
|
+
@options[:exit]
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def app_name
|
|
123
|
+
@options[:name]
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def restart_with_signal(restart_signal)
|
|
127
|
+
if @pid && (@pid != 0)
|
|
128
|
+
notify "restarting", "We will be with you shortly."
|
|
129
|
+
send_signal(restart_signal)
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def force_polling
|
|
134
|
+
@options[:force_polling]
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def start
|
|
138
|
+
if @already_running
|
|
139
|
+
taglines = [
|
|
140
|
+
"Here we go again!",
|
|
141
|
+
"Keep on trucking.",
|
|
142
|
+
"Once more unto the breach, dear friends, once more!",
|
|
143
|
+
"The road goes ever on and on, down from the door where it began.",
|
|
144
|
+
]
|
|
145
|
+
notify "restarted", taglines[rand(taglines.size)]
|
|
146
|
+
else
|
|
147
|
+
taglines = [
|
|
148
|
+
"To infinity... and beyond!",
|
|
149
|
+
"Charge!",
|
|
150
|
+
]
|
|
151
|
+
notify "launched", taglines[rand(taglines.size)]
|
|
152
|
+
@already_running = true
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
clear_screen if clear?
|
|
156
|
+
start_keypress_thread unless @keypress_thread
|
|
157
|
+
|
|
158
|
+
begin
|
|
159
|
+
@pid = run @run_command
|
|
160
|
+
say "Rerun (#{$PID}) running #{app_name} (#{@pid})"
|
|
161
|
+
rescue => e
|
|
162
|
+
puts "#{e.class}: #{e.message}"
|
|
163
|
+
raise ExitException
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
status_thread = @status_thread = Process.detach(@pid)
|
|
167
|
+
|
|
168
|
+
Signal.trap("INT") do # INT = control-C -- allows user to stop the top-level rerun process
|
|
169
|
+
die
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
Signal.trap("TERM") do # TERM is the polite way of terminating a process
|
|
173
|
+
die
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
begin
|
|
177
|
+
sleep 2
|
|
178
|
+
rescue Interrupt => e
|
|
179
|
+
# in case someone hits control-C immediately ("oops!")
|
|
180
|
+
die
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
if exit?
|
|
184
|
+
status = status_thread.value
|
|
185
|
+
if status.success?
|
|
186
|
+
notify "succeeded", ""
|
|
187
|
+
else
|
|
188
|
+
notify "failed", "Exit status #{status.exitstatus}"
|
|
189
|
+
end
|
|
190
|
+
else
|
|
191
|
+
if !running?
|
|
192
|
+
notify "Launch Failed", "See console for error output"
|
|
193
|
+
@already_running = false
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
unless @watcher
|
|
198
|
+
watcher = Watcher.new(@options) do |changes|
|
|
199
|
+
message = change_message(changes)
|
|
200
|
+
say "Change detected: #{message}"
|
|
201
|
+
restart unless @restarting
|
|
202
|
+
end
|
|
203
|
+
watcher.start
|
|
204
|
+
@watcher = watcher
|
|
205
|
+
ignore = @options[:ignore]
|
|
206
|
+
say "Watching #{dir.join(', ')} for #{pattern}" +
|
|
207
|
+
(ignore.empty? ? "" : " (ignoring #{ignore.join(',')})") +
|
|
208
|
+
(watcher.adapter.nil? ? "" : " with #{watcher.adapter_name} adapter")
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def run command
|
|
213
|
+
Kernel.spawn command
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def change_message(changes)
|
|
217
|
+
message = [:modified, :added, :removed].map do |change|
|
|
218
|
+
count = changes[change] ? changes[change].size : 0
|
|
219
|
+
if count > 0
|
|
220
|
+
"#{count} #{change}"
|
|
221
|
+
end
|
|
222
|
+
end.compact.join(", ")
|
|
223
|
+
|
|
224
|
+
changed_files = changes.values.flatten
|
|
225
|
+
if changed_files.count > 0
|
|
226
|
+
message += ": "
|
|
227
|
+
message += changes.values.flatten[0..3].map {|path| path.split('/').last}.join(', ')
|
|
228
|
+
if changed_files.count > 3
|
|
229
|
+
message += ", ..."
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
message
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def die
|
|
236
|
+
#stop_keypress_thread # don't do this since we're probably *in* the keypress thread
|
|
237
|
+
stop # stop the child process if it exists
|
|
238
|
+
raise ExitException # todo: status code param
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def join
|
|
242
|
+
@watcher.join
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def running?
|
|
246
|
+
send_signal(0)
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
# Send the signal to process @pid and wait for it to die.
|
|
250
|
+
# @returns true if the process dies
|
|
251
|
+
# @returns false if either sending the signal fails or the process fails to die
|
|
252
|
+
def signal_and_wait(signal)
|
|
253
|
+
status_thread = @status_thread
|
|
254
|
+
|
|
255
|
+
signal_sent = if windows?
|
|
256
|
+
force_kill = (signal == 'KILL')
|
|
257
|
+
system("taskkill /T #{'/F' if force_kill} /PID #{@pid}")
|
|
258
|
+
else
|
|
259
|
+
send_signal(signal)
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
if signal_sent
|
|
263
|
+
# the signal was successfully sent, so wait for the process to die
|
|
264
|
+
begin
|
|
265
|
+
timeout(@options[:wait]) do
|
|
266
|
+
status_thread.join
|
|
267
|
+
end
|
|
268
|
+
process_status = status_thread.value
|
|
269
|
+
say "Process ended: #{process_status}" if verbose?
|
|
270
|
+
true
|
|
271
|
+
rescue Timeout::Error
|
|
272
|
+
false
|
|
273
|
+
end
|
|
274
|
+
else
|
|
275
|
+
false
|
|
276
|
+
end
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
# Send the signal to process @pid.
|
|
280
|
+
# @returns true if the signal is sent
|
|
281
|
+
# @returns false if sending the signal fails
|
|
282
|
+
# If sending the signal fails, the exception will be swallowed
|
|
283
|
+
# (and logged if verbose is true) and this method will return false.
|
|
284
|
+
#
|
|
285
|
+
def send_signal(signal)
|
|
286
|
+
say "Sending signal #{signal} to #{@pid}" unless signal == 0 if verbose?
|
|
287
|
+
Process.kill(signal, @pid)
|
|
288
|
+
true
|
|
289
|
+
rescue => e
|
|
290
|
+
say "Signal #{signal} failed: #{e.class}: #{e.message}" if verbose?
|
|
291
|
+
false
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
# todo: test escalation
|
|
295
|
+
def stop
|
|
296
|
+
if @pid && (@pid != 0)
|
|
297
|
+
notify "stopping", "All good things must come to an end." unless @restarting
|
|
298
|
+
@options[:signal].split(',').each do |signal|
|
|
299
|
+
success = signal_and_wait(signal)
|
|
300
|
+
return true if success
|
|
301
|
+
end
|
|
302
|
+
end
|
|
303
|
+
rescue
|
|
304
|
+
false
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
def git_head_changed?
|
|
308
|
+
old_git_head = @git_head
|
|
309
|
+
read_git_head
|
|
310
|
+
@git_head and old_git_head and @git_head != old_git_head
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
def read_git_head
|
|
314
|
+
git_head_file = File.join(dir, '.git', 'HEAD')
|
|
315
|
+
@git_head = File.exist?(git_head_file) && File.read(git_head_file)
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
def notify(title, body, background = true)
|
|
319
|
+
Notification.new(title, body, @options).send(background) if @options[:notify]
|
|
320
|
+
puts
|
|
321
|
+
say "#{app_name} #{title}"
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
def say msg
|
|
325
|
+
puts "#{Time.now.strftime("%T")} [rerun] #{msg}" unless quiet?
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
def stty(args)
|
|
329
|
+
system "stty #{args}"
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
# non-blocking stdin reader.
|
|
333
|
+
# returns a 1-char string if a key was pressed; otherwise nil
|
|
334
|
+
#
|
|
335
|
+
def key_pressed
|
|
336
|
+
return one_char if windows?
|
|
337
|
+
begin
|
|
338
|
+
# this "raw input" nonsense is because unix likes waiting for linefeeds before sending stdin
|
|
339
|
+
|
|
340
|
+
# 'raw' means turn raw input on
|
|
341
|
+
|
|
342
|
+
# restore proper output newline handling -- see "man stty" and /usr/include/sys/termios.h
|
|
343
|
+
# looks like "raw" flips off the OPOST bit 0x00000001 /* enable following output processing */
|
|
344
|
+
# which disables #define ONLCR 0x00000002 /* map NL to CR-NL (ala CRMOD) */
|
|
345
|
+
# so this sets it back on again since all we care about is raw input, not raw output
|
|
346
|
+
stty "raw opost"
|
|
347
|
+
one_char
|
|
348
|
+
ensure
|
|
349
|
+
stty "-raw" # turn raw input off
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
# note: according to 'man tty' the proper way restore the settings is
|
|
353
|
+
# tty_state=`stty -g`
|
|
354
|
+
# ensure
|
|
355
|
+
# system 'stty "#{tty_state}'
|
|
356
|
+
# end
|
|
357
|
+
# but this way seems fine and less confusing
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
def clear_screen
|
|
361
|
+
# see http://ascii-table.com/ansi-escape-sequences-vt-100.php
|
|
362
|
+
$stdout.print "\033[H\033[2J"
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
private
|
|
366
|
+
|
|
367
|
+
def interactive_terminal?
|
|
368
|
+
return false unless $stdin.tty?
|
|
369
|
+
return true if windows?
|
|
370
|
+
|
|
371
|
+
# A terminal can be inherited by a background group (e.g. under Overman).
|
|
372
|
+
# Querying ps avoids platform-specific terminal ioctl constants.
|
|
373
|
+
foreground_group = IO.popen(['ps', '-o', 'tpgid=', '-p', Process.pid.to_s],
|
|
374
|
+
err: File::NULL, &:read).to_i
|
|
375
|
+
foreground_group == Process.getpgrp
|
|
376
|
+
rescue IOError, SystemCallError
|
|
377
|
+
false
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
def one_char
|
|
381
|
+
c = nil
|
|
382
|
+
msg = $stdin.respond_to?(:wait_readable) ? :wait_readable : :ready?
|
|
383
|
+
|
|
384
|
+
if $stdin.send(msg)
|
|
385
|
+
c = $stdin.getc
|
|
386
|
+
end
|
|
387
|
+
c.chr if c
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
end
|
|
391
|
+
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|mingw32)/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.exist? rails_sig_file
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,134 @@
|
|
|
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, :ignore_dotfiles
|
|
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
|
+
:ignore_dotfiles => true,
|
|
37
|
+
}.merge(options)
|
|
38
|
+
|
|
39
|
+
@pattern = options[:pattern]
|
|
40
|
+
@directories = options[:directory]
|
|
41
|
+
@directories = sanitize_dirs(@directories)
|
|
42
|
+
@priority = options[:priority]
|
|
43
|
+
@force_polling = options[:force_polling]
|
|
44
|
+
@ignore = [options[:ignore]].flatten.compact
|
|
45
|
+
@ignore_dotfiles = options[:ignore_dotfiles]
|
|
46
|
+
@thread = nil
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def sanitize_dirs(dirs)
|
|
50
|
+
dirs = [*dirs]
|
|
51
|
+
dirs.map do |d|
|
|
52
|
+
d.chomp!("/")
|
|
53
|
+
unless FileTest.exist?(d) && FileTest.readable?(d) && FileTest.directory?(d)
|
|
54
|
+
raise InvalidDirectoryError, "Directory '#{d}' either doesnt exist or isn't readable"
|
|
55
|
+
end
|
|
56
|
+
File.expand_path(d)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def start
|
|
61
|
+
if @thread then
|
|
62
|
+
raise RuntimeError, "already started"
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
@thread = Thread.new do
|
|
66
|
+
@listener = Listen.to(*@directories, only: watching, ignore: ignoring, wait_for_delay: 1, force_polling: @force_polling) do |modified, added, removed|
|
|
67
|
+
count = modified.size + added.size + removed.size
|
|
68
|
+
if count > 0
|
|
69
|
+
@client_callback.call(:modified => modified, :added => added, :removed => removed)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
@listener.start
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
@thread.priority = @priority
|
|
76
|
+
|
|
77
|
+
sleep 0.1 until @listener
|
|
78
|
+
|
|
79
|
+
at_exit { stop } # try really hard to clean up after ourselves
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def watching
|
|
83
|
+
Rerun::Glob.new(@pattern).to_regexp
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def ignoring
|
|
87
|
+
patterns = []
|
|
88
|
+
if ignore_dotfiles
|
|
89
|
+
patterns << /^\.[^.]/ # at beginning of string, a real dot followed by any other character
|
|
90
|
+
end
|
|
91
|
+
patterns + @ignore.map { |x| Rerun::Glob.new(x).to_regexp }
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# kill the file watcher thread
|
|
95
|
+
def stop
|
|
96
|
+
@thread.wakeup rescue ThreadError
|
|
97
|
+
begin
|
|
98
|
+
@listener.stop
|
|
99
|
+
rescue Exception => e
|
|
100
|
+
puts "#{e.class}: #{e.message} stopping listener"
|
|
101
|
+
end
|
|
102
|
+
@thread.kill rescue ThreadError
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# wait for the file watcher to finish
|
|
106
|
+
def join
|
|
107
|
+
@thread.join if @thread
|
|
108
|
+
rescue Interrupt
|
|
109
|
+
# don't care
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def pause
|
|
113
|
+
@listener.pause if @listener
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def unpause
|
|
117
|
+
@listener.start if @listener
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def running?
|
|
121
|
+
@listener && @listener.processing?
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def adapter
|
|
125
|
+
@listener &&
|
|
126
|
+
(backend = @listener.instance_variable_get(:@backend)) &&
|
|
127
|
+
backend.instance_variable_get(:@adapter)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def adapter_name
|
|
131
|
+
adapter && adapter.class.name.split('::').last
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
data/lib/rerun.rb
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
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
|
+
# Raised when the runner wants to exit cleanly.
|
|
14
|
+
# This allows tests to catch the exit instead of terminating the process.
|
|
15
|
+
class ExitException < StandardError; end
|
|
16
|
+
end
|
|
17
|
+
|
data/relaunch.gemspec
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
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 = 'relaunch'
|
|
6
|
+
s.version = '0.16.0'
|
|
7
|
+
s.required_ruby_version = '>= 3.3'
|
|
8
|
+
|
|
9
|
+
s.description = "Restarts your app when a file changes. A no-frills, command-line alternative to Guard, Shotgun, Autotest, etc."
|
|
10
|
+
s.summary = "Launches an app, and restarts it whenever the filesystem changes. A no-frills, command-line alternative to Guard, Shotgun, Autotest, etc."
|
|
11
|
+
|
|
12
|
+
s.authors = ["Alex Chaffee", "Patrik Ragnarsson"]
|
|
13
|
+
s.email = ["alexch@gmail.com", "patrik@starkast.net"]
|
|
14
|
+
|
|
15
|
+
s.files = %w[
|
|
16
|
+
README.md
|
|
17
|
+
Changelog.md
|
|
18
|
+
Fork.md
|
|
19
|
+
LICENSE
|
|
20
|
+
Rakefile
|
|
21
|
+
relaunch.gemspec
|
|
22
|
+
bin/rerun
|
|
23
|
+
bin/rerun.bat
|
|
24
|
+
bin/relaunch
|
|
25
|
+
bin/relaunch.bat
|
|
26
|
+
icons/rails_grn_sml.png
|
|
27
|
+
icons/rails_red_sml.png] +
|
|
28
|
+
Dir['lib/**/*.rb']
|
|
29
|
+
s.executables = ['rerun', 'relaunch']
|
|
30
|
+
s.test_files = s.files.select {|path| path =~ /^spec\/.*_spec.rb/}
|
|
31
|
+
|
|
32
|
+
s.extra_rdoc_files = %w[README.md]
|
|
33
|
+
|
|
34
|
+
s.add_runtime_dependency 'listen', '~> 3.0'
|
|
35
|
+
|
|
36
|
+
s.homepage = "https://github.com/spinels/relaunch"
|
|
37
|
+
s.metadata["homepage_uri"] = s.homepage
|
|
38
|
+
s.metadata["changelog_uri"] = "#{s.homepage}/blob/main/Changelog.md"
|
|
39
|
+
s.metadata["source_code_uri"] = "#{s.homepage}/tree/main"
|
|
40
|
+
s.metadata["rubygems_mfa_required"] = "true"
|
|
41
|
+
s.require_paths = %w[lib]
|
|
42
|
+
|
|
43
|
+
s.license = 'MIT'
|
|
44
|
+
end
|
metadata
CHANGED
|
@@ -1,24 +1,69 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: relaunch
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0
|
|
4
|
+
version: 0.16.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
|
-
-
|
|
7
|
+
- Alex Chaffee
|
|
8
|
+
- Patrik Ragnarsson
|
|
8
9
|
bindir: bin
|
|
9
10
|
cert_chain: []
|
|
10
11
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
-
dependencies:
|
|
12
|
-
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: listen
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '3.0'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '3.0'
|
|
27
|
+
description: Restarts your app when a file changes. A no-frills, command-line alternative
|
|
28
|
+
to Guard, Shotgun, Autotest, etc.
|
|
29
|
+
email:
|
|
30
|
+
- alexch@gmail.com
|
|
31
|
+
- patrik@starkast.net
|
|
32
|
+
executables:
|
|
33
|
+
- relaunch
|
|
34
|
+
- rerun
|
|
13
35
|
extensions: []
|
|
14
|
-
extra_rdoc_files:
|
|
36
|
+
extra_rdoc_files:
|
|
37
|
+
- README.md
|
|
15
38
|
files:
|
|
39
|
+
- Changelog.md
|
|
40
|
+
- Fork.md
|
|
41
|
+
- LICENSE
|
|
42
|
+
- README.md
|
|
43
|
+
- Rakefile
|
|
44
|
+
- bin/relaunch
|
|
45
|
+
- bin/relaunch.bat
|
|
46
|
+
- bin/rerun
|
|
47
|
+
- bin/rerun.bat
|
|
48
|
+
- icons/rails_grn_sml.png
|
|
49
|
+
- icons/rails_red_sml.png
|
|
16
50
|
- lib/relaunch.rb
|
|
17
|
-
|
|
51
|
+
- lib/rerun.rb
|
|
52
|
+
- lib/rerun/glob.rb
|
|
53
|
+
- lib/rerun/notification.rb
|
|
54
|
+
- lib/rerun/options.rb
|
|
55
|
+
- lib/rerun/runner.rb
|
|
56
|
+
- lib/rerun/system.rb
|
|
57
|
+
- lib/rerun/watcher.rb
|
|
58
|
+
- relaunch.gemspec
|
|
59
|
+
homepage: https://github.com/spinels/relaunch
|
|
18
60
|
licenses:
|
|
19
61
|
- MIT
|
|
20
|
-
metadata:
|
|
21
|
-
|
|
62
|
+
metadata:
|
|
63
|
+
homepage_uri: https://github.com/spinels/relaunch
|
|
64
|
+
changelog_uri: https://github.com/spinels/relaunch/blob/main/Changelog.md
|
|
65
|
+
source_code_uri: https://github.com/spinels/relaunch/tree/main
|
|
66
|
+
rubygems_mfa_required: 'true'
|
|
22
67
|
rdoc_options: []
|
|
23
68
|
require_paths:
|
|
24
69
|
- lib
|
|
@@ -26,14 +71,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
26
71
|
requirements:
|
|
27
72
|
- - ">="
|
|
28
73
|
- !ruby/object:Gem::Version
|
|
29
|
-
version: '3.
|
|
74
|
+
version: '3.3'
|
|
30
75
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
31
76
|
requirements:
|
|
32
77
|
- - ">="
|
|
33
78
|
- !ruby/object:Gem::Version
|
|
34
79
|
version: '0'
|
|
35
80
|
requirements: []
|
|
36
|
-
rubygems_version: 4.0.
|
|
37
|
-
specification_version:
|
|
38
|
-
summary:
|
|
81
|
+
rubygems_version: 4.0.16
|
|
82
|
+
specification_version: 2
|
|
83
|
+
summary: Launches an app, and restarts it whenever the filesystem changes. A no-frills,
|
|
84
|
+
command-line alternative to Guard, Shotgun, Autotest, etc.
|
|
39
85
|
test_files: []
|