rerun 0.9.0 → 0.13.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 +5 -5
- data/README.md +176 -29
- data/Rakefile +8 -1
- data/bin/rerun +9 -3
- data/lib/goo.rb +3 -0
- data/lib/rerun.rb +1 -2
- data/lib/rerun/notification.rb +82 -0
- data/lib/rerun/options.rb +96 -42
- data/lib/rerun/runner.rb +155 -84
- data/lib/rerun/system.rb +3 -25
- data/lib/rerun/watcher.rb +34 -13
- data/rerun.gemspec +2 -2
- metadata +10 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 12916c529d417f7435294064c61ab8bc9b3e4811e61ab162725b84dc42730212
|
4
|
+
data.tar.gz: 2d32573a380ae73f845564580eced58064271d3347a09da7c5e13ad72882a8c5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a3d9ed3d68053c23b3c9665abc33cde89d4fd5893fa1d9930fbcb93113459450810ced0d3d0cd7e83c2bc4dc9da27446228f1104dc2a46cdb01ef53dc8bc5f48
|
7
|
+
data.tar.gz: 1df642c255cd253271015b111f2b0fa4b9023c0fd697b1bd03405b2fccb9d716658f840a82884dfa7b042395c07664205f6427c08a9b432f8742ce58413792cd
|
data/README.md
CHANGED
@@ -14,14 +14,18 @@ Rerun's advantage is its simple design. Since it uses `exec` and the standard
|
|
14
14
|
Unix `SIGINT` and `SIGKILL` signals, you're sure the restarted app is really
|
15
15
|
acting just like it was when you ran it from the command line the first time.
|
16
16
|
|
17
|
-
By default
|
17
|
+
By default it watches files ending in: `rb,js,coffee,css,scss,sass,erb,html,haml,ru,yml,slim,md,feature,c,h`.
|
18
18
|
Use the `--pattern` option if you want to change this.
|
19
19
|
|
20
20
|
As of version 0.7.0, we use the Listen gem, which tries to use your OS's
|
21
21
|
built-in facilities for monitoring the filesystem, so CPU use is very light.
|
22
22
|
|
23
|
-
Rerun does
|
24
|
-
|
23
|
+
**UPDATE**: Now Rerun *does* work on Windows! Caveats:
|
24
|
+
* not well-tested
|
25
|
+
* you need to press Enter after keypress input
|
26
|
+
* you may need to install the `wdm` gem manually: `gem install wdm`
|
27
|
+
* You may see this persistent `INFO` error message; to remove it, use`--no-notify`:
|
28
|
+
* `INFO: Could not find files for the given pattern(s)`
|
25
29
|
|
26
30
|
# Installation:
|
27
31
|
|
@@ -40,10 +44,39 @@ they're not available. Unfortunately, Rubygems doesn't understand optional
|
|
40
44
|
dependencies very well, so you may have to install extra gems (and/or put them
|
41
45
|
in your Gemfile) to make Rerun work more smoothly on your system.
|
42
46
|
(Learn more at <https://github.com/guard/listen#listen-adapters>.)
|
43
|
-
|
47
|
+
|
48
|
+
On Mac OS X, use
|
44
49
|
|
45
50
|
gem install rb-fsevent
|
46
51
|
|
52
|
+
On Windows, use
|
53
|
+
|
54
|
+
gem install wdm
|
55
|
+
|
56
|
+
On *BSD, use
|
57
|
+
|
58
|
+
gem install rb-kqueue
|
59
|
+
|
60
|
+
## Installation via Gemfile / Bundler
|
61
|
+
|
62
|
+
If you are using rerun inside an existing Ruby application (like a Rails or Sinatra app), you can add it to your Gemfile:
|
63
|
+
|
64
|
+
``` ruby
|
65
|
+
group :development, :test do
|
66
|
+
gem "rerun"
|
67
|
+
end
|
68
|
+
```
|
69
|
+
|
70
|
+
Using a Gemfile is also an easy way to use the pre-release branch, which may have bugfixes or features you want:
|
71
|
+
|
72
|
+
``` ruby
|
73
|
+
group :development, :test do
|
74
|
+
gem "rerun", git: "https://github.com/alexch/rerun.git"
|
75
|
+
end
|
76
|
+
```
|
77
|
+
|
78
|
+
When using a Gemfile, install with `bundle install` or `bundle update`, and run using `bundle exec rerun`, to guarantee you are using the rerun version specified in the Gemfile, and not a different version in a system-wide gemset.
|
79
|
+
|
47
80
|
# Usage:
|
48
81
|
|
49
82
|
rerun [options] [--] cmd
|
@@ -113,42 +146,95 @@ Procfile processes locally and restart them all when necessary.
|
|
113
146
|
|
114
147
|
# Options:
|
115
148
|
|
149
|
+
These options can be specified on the command line and/or inside a `.rerun` config file (see below).
|
150
|
+
|
116
151
|
`--dir` directory (or directories) to watch (default = "."). Separate multiple paths with ',' and/or use multiple `-d` options.
|
117
152
|
|
118
153
|
`--pattern` glob to match inside directory. This uses the Ruby Dir glob style -- see <http://www.ruby-doc.org/core/classes/Dir.html#M002322> for details.
|
119
|
-
By default it watches files ending in: `rb,js,css,scss,sass,erb,html,haml,ru`.
|
120
|
-
On top of this, it also ignores dotfiles, `.tmp` files, and some other files and directories (like `.git
|
154
|
+
By default it watches files ending in: `rb,js,coffee,css,scss,sass,erb,html,haml,ru,yml,slim,md,feature,c,h`.
|
155
|
+
On top of this, it also ignores dotfiles, `.tmp` files, and some other files and directories (like `.git` and `log`).
|
156
|
+
Run `rerun --help` to see the actual list.
|
157
|
+
|
158
|
+
`--ignore pattern` file glob to ignore (can be set many times). To ignore a directory, you must append `'/*'` e.g.
|
159
|
+
`--ignore 'coverage/*'`.
|
121
160
|
|
122
|
-
`--
|
161
|
+
`--[no-]ignore-dotfiles` By default, on top of --pattern and --ignore, we ignore any changes to files and dirs starting with a dot. Setting `--no-ignore-dotfiles` allows you to monitor a relevant file like .env, but you may also have to explicitly --ignore more dotfiles and dotdirs.
|
162
|
+
|
163
|
+
`--signal` (or `-s`) use specified signal(s) (instead of the default `TERM,INT,KILL`) to terminate the previous process. You can use a comma-delimited list if you want to try a signal, wait up to 5 seconds for the process to die, then try again with a different signal, and so on.
|
123
164
|
This may be useful for forcing the respective process to terminate as quickly as possible.
|
124
165
|
(`--signal KILL` is the equivalent of `kill -9`)
|
125
166
|
|
126
|
-
`--
|
167
|
+
`--wait sec` (or `-w`) after asking the process to terminate, wait this long (in seconds) before either aborting, or trying the next signal in series. Default: 2 sec
|
168
|
+
|
169
|
+
`--restart` (or `-r`) expect process to restart itself, using signal HUP by default
|
170
|
+
(e.g. `-r -s INT` will send a INT and then resume watching for changes)
|
127
171
|
|
128
172
|
`--exit` (or -x) expect the program to exit. With this option, rerun checks the return value; without it, rerun checks that the launched process is still running.
|
129
173
|
|
174
|
+
`--clear` (or -c) clear the screen before each run
|
175
|
+
|
130
176
|
`--background` (or -b) disable on-the-fly commands, allowing the process to be backgrounded
|
131
177
|
|
132
|
-
`--
|
178
|
+
`--notify NOTIFIER` use `growl` or `osx` or `notify-send` for notifications (see below)
|
179
|
+
|
180
|
+
`--no-notify` disable notifications
|
133
181
|
|
134
182
|
`--name` set the app name (for display)
|
135
183
|
|
136
|
-
|
184
|
+
`--force-polling` use polling instead of a native filesystem scan (useful for Vagrant)
|
185
|
+
|
186
|
+
`--quiet` silences most messages
|
187
|
+
|
188
|
+
`--verbose` enables even more messages (unless you also specified `--quiet`, which overrides `--verbose`)
|
189
|
+
|
190
|
+
Also `--version` and `--help`, naturally.
|
191
|
+
|
192
|
+
## Config file
|
193
|
+
|
194
|
+
If the current directory contains a file named `.rerun`, it will be parsed with the same rules as command-line arguments. Newlines are the same as any other whitespace, so you can stack options vertically, like this:
|
195
|
+
|
196
|
+
```
|
197
|
+
--quiet
|
198
|
+
--pattern **/*.{rb,js,scss,sass,html,md}
|
199
|
+
```
|
137
200
|
|
138
|
-
|
201
|
+
Options specified on the command line will override those in the config file. You can negate boolean options with `--no-`, so for example, with the above config file, to re-enable logging, you could say:
|
202
|
+
|
203
|
+
```sh
|
204
|
+
rerun --no-quiet rackup
|
205
|
+
```
|
206
|
+
|
207
|
+
If you're not sure what options are being overwritten, use `--verbose` and rerun will show you the final result of the parsing.
|
208
|
+
|
209
|
+
# Notifications
|
139
210
|
|
140
211
|
If you have `growlnotify` available on the `PATH`, it sends notifications to
|
141
|
-
growl in addition to the console.
|
142
|
-
|
212
|
+
growl in addition to the console.
|
213
|
+
|
214
|
+
If you have `terminal-notifier`, it sends notifications to
|
215
|
+
the OS X notification center in addition to the console.
|
216
|
+
|
217
|
+
If you have `notify-send`, it sends notifications to Freedesktop-compatible
|
218
|
+
desktops in addition to the console.
|
219
|
+
|
220
|
+
If you have more than one available notification program, Rerun will pick one, or you can choose between them using `--notify growl`, `--notify osx`, `--notify notify-send`, etc.
|
221
|
+
|
222
|
+
If you have a notifier installed but don't want rerun to use it,
|
223
|
+
set the `--no-notify` option.
|
143
224
|
|
144
225
|
Download [growlnotify here](http://growl.info/downloads.php#generaldownloads)
|
145
226
|
now that Growl has moved to the App Store.
|
146
227
|
|
228
|
+
Install [terminal-notifier](https://github.com/julienXX/terminal-notifier) using `gem install terminal-notifier`. (You may have to put it in your system gemset and/or use `sudo` too.) Using Homebrew to install terminal-notifier is not recommended.
|
229
|
+
|
230
|
+
On Debian/Ubuntu, `notify-send` is availble in the `libnotify-bin` package. On other GNU/Linux systems, it might be in a package with a different name.
|
231
|
+
|
147
232
|
# On-The-Fly Commands
|
148
233
|
|
149
234
|
While the app is (re)running, you can make things happen by pressing keys:
|
150
235
|
|
151
236
|
* **r** -- restart (as if a file had changed)
|
237
|
+
* **f** -- force restart (stop and start)
|
152
238
|
* **c** -- clear the screen
|
153
239
|
* **x** or **q** -- exit (just like control-C)
|
154
240
|
* **p** -- pause/unpause filesystem watching
|
@@ -161,44 +247,66 @@ keys to be trapped, so use the `--background` option.
|
|
161
247
|
The current algorithm for killing the process is:
|
162
248
|
|
163
249
|
* send [SIGTERM](http://en.wikipedia.org/wiki/SIGTERM) (or the value of the `--signal` option)
|
164
|
-
* if that doesn't work after
|
250
|
+
* if that doesn't work after 2 seconds, send SIGINT (aka control-C)
|
165
251
|
* if that doesn't work after 2 more seconds, send SIGKILL (aka kill -9)
|
166
252
|
|
167
253
|
This seems like the most gentle and unixy way of doing things, but it does
|
168
|
-
mean that if your program ignores SIGTERM, it takes an extra
|
254
|
+
mean that if your program ignores SIGTERM, it takes an extra 2 to 4 seconds to
|
169
255
|
restart.
|
170
256
|
|
257
|
+
If you want to use your own series of signals, use the `--signal` option. If you want to change the delay before attempting the next signal, use the `--wait` option.
|
258
|
+
|
259
|
+
# Vagrant and VirtualBox
|
260
|
+
|
261
|
+
If running inside a shared directory using Vagrant and VirtualBox, you must pass the `--force-polling` option. You may also have to pass some extra `--ignore` options too; otherwise each scan can take 10 or more seconds on directories with a large number of files or subdirectories underneath it.
|
262
|
+
|
263
|
+
# Troubleshooting
|
264
|
+
|
265
|
+
## zsh ##
|
266
|
+
|
267
|
+
If you are using `zsh` as your shell, and you are specifying your `--pattern` as `**/*.rb`, you may face this error
|
268
|
+
```
|
269
|
+
Errno::EACCES: Permission denied - <filename>
|
270
|
+
```
|
271
|
+
This is because `**/*.rb` gets expanded into the command by `zsh` instead of passing it through to rerun. The solution is to simply quote ('' or "") the pattern.
|
272
|
+
i.e
|
273
|
+
```
|
274
|
+
rerun -p **/*.rb rake test
|
275
|
+
```
|
276
|
+
becomes
|
277
|
+
```
|
278
|
+
rerun -p "**/*.rb" rake test
|
279
|
+
```
|
280
|
+
|
171
281
|
# To Do:
|
172
282
|
|
173
|
-
|
283
|
+
## Must have for v1.0
|
284
|
+
* Make sure to pass through quoted options correctly to target process [bug]
|
285
|
+
* Optionally do "bundle install" before and "bundle exec" during launch
|
286
|
+
|
287
|
+
## Nice to have
|
288
|
+
* ".rerun" file in $HOME
|
174
289
|
* If the last element of the command is a `.ru` file and there's no other command then use `rackup`
|
175
|
-
* --ignore pattern (currently we're using Listen's default list plus dotfiles)
|
176
|
-
* ".rerun" file to specify options per project or in $HOME.
|
177
290
|
* Figure out an algorithm so "-x" is not needed (if possible) -- maybe by accepting a "--port" option or reading `config.ru`
|
178
291
|
* Specify (or deduce) port to listen for to determine success of a web server launch
|
179
|
-
*
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
* Optionally do "bundle install" before and "bundle exec" during launch
|
292
|
+
* see also [todo.md](todo.md)
|
293
|
+
|
294
|
+
## Wacky Ideas
|
295
|
+
|
184
296
|
* On OS X:
|
185
297
|
* use a C library using growl's developer API <http://growl.info/developer/>
|
186
298
|
* Use growl's AppleScript or SDK instead of relying on growlnotify
|
187
|
-
* Use OS X notifications
|
188
299
|
* "Failed" icon for notifications
|
189
|
-
* On Linux:
|
190
|
-
* Test on Linux.
|
191
|
-
* Use libnotify or notify-send http://www.linuxjournal.com/content/tech-tip-get-notifications-your-scripts-notify-send
|
192
300
|
|
193
301
|
# Other projects that do similar things
|
194
302
|
|
303
|
+
* Guard: <http://github.com/guard/guard>
|
195
304
|
* Restartomatic: <http://github.com/adammck/restartomatic>
|
196
305
|
* Shotgun: <http://github.com/rtomayko/shotgun>
|
197
306
|
* Rack::Reloader middleware: <http://github.com/rack/rack/blob/5ca8f82fb59f0bf0e8fd438e8e91c5acf3d98e44/lib/rack/reloader.rb>
|
198
307
|
* The Sinatra FAQ has a discussion at <http://www.sinatrarb.com/faq.html#reloading>
|
199
308
|
* Kicker: <http://github.com/alloy/kicker/>
|
200
309
|
* Watchr: <https://github.com/mynyml/watchr>
|
201
|
-
* Guard: <http://github.com/guard/guard>
|
202
310
|
* Autotest: <https://github.com/grosser/autotest>
|
203
311
|
|
204
312
|
# Why would I use this instead of Shotgun?
|
@@ -279,14 +387,53 @@ Based upon and/or inspired by:
|
|
279
387
|
* <https://github.com/FND>
|
280
388
|
* Barry Sia <https://github.com/bsia>
|
281
389
|
* Paul Rangel <https://github.com/ismell>
|
390
|
+
* James Edward Gray II <https://github.com/JEG2>
|
391
|
+
* Raul E Rangel <https://github.com/ismell> and Antonio Terceiro <https://github.com/terceiro>
|
392
|
+
* Mike Pastore <https://github.com/mwpastore>
|
393
|
+
* Andy Duncan <https://github.com/ajduncan>
|
394
|
+
* Brent Van Minnen
|
395
|
+
* Matthew O'Riordan <https://github.com/mattheworiordan>
|
396
|
+
* Antonio Terceiro <https://github.com/terceiro>
|
397
|
+
* <https://github.com/mattbrictson>
|
398
|
+
* <https://github.com/krissi>
|
282
399
|
|
283
400
|
# Version History
|
284
401
|
|
285
|
-
*
|
402
|
+
*
|
403
|
+
* --no-ignore-dotfiles option
|
404
|
+
|
405
|
+
* v0.13.0 26 January 2018
|
406
|
+
* bugfix: pause/unpause works again (thanks Barry!)
|
407
|
+
* `.rerun` config file
|
408
|
+
|
409
|
+
* v0.12.0 23 January 2018
|
410
|
+
* smarter `--signal` option, allowing you to specify a series of signals to try in order; also `--wait` to change how long between tries
|
411
|
+
* `--force-polling` option (thanks ajduncan)
|
412
|
+
* `f` key to force stop and start (thanks mwpastore)
|
413
|
+
* add `.c` and `.h` files to default ignore list
|
414
|
+
* support for Windows
|
415
|
+
* use `Kernel.spawn` instead of `fork`
|
416
|
+
* use `wdm` gem for Windows Directory Monitor
|
417
|
+
* support for notifications on GNU/Linux using [notify-send](http://www.linuxjournal.com/content/tech-tip-get-notifications-your-scripts-notify-send) (thanks terceiro)
|
418
|
+
* fix `Gem::LoadError - terminal-notifier is not part of the bundle` [bug](https://github.com/alexch/rerun/issues/108) (thanks mattheworiordan)
|
419
|
+
|
420
|
+
* 0.11.0 7 October 2015
|
421
|
+
* better 'changed' message
|
422
|
+
* `--notify osx` option
|
423
|
+
* `--restart` option (with bugfix by Mike Pastore)
|
424
|
+
* use Listen 3 gem
|
425
|
+
* add `.feature` files to default watchlist (thanks @jmuheim)
|
426
|
+
|
427
|
+
* v0.10.0 4 May 2014
|
428
|
+
* add '.coffee,.slim,.md' to default pattern (thanks @xylinq)
|
429
|
+
* --ignore option
|
430
|
+
|
431
|
+
* v0.9.0 6 March 2014
|
286
432
|
* --dir (or -d) can be specified more than once, for multiple directories (thanks again Barry!)
|
287
433
|
* --name option
|
288
434
|
* press 'p' to pause/unpause filesystem watching (Barry is the man!)
|
289
435
|
* works with Listen 2 (note: needs 2.3 or higher)
|
436
|
+
* cooldown works, thanks to patches to underlying Listen gem
|
290
437
|
* ignore all dotfiles, and add actual list of ignored dirs and files
|
291
438
|
|
292
439
|
* v0.8.2
|
data/Rakefile
CHANGED
@@ -32,7 +32,7 @@ end
|
|
32
32
|
desc 'Exit if git is dirty'
|
33
33
|
task :check_git do
|
34
34
|
state = `git status 2> /dev/null | tail -n1`
|
35
|
-
clean = (state =~ /working directory clean/)
|
35
|
+
clean = (state =~ /working (directory|tree) clean/)
|
36
36
|
unless clean
|
37
37
|
warn "can't do that on an unclean git dir"
|
38
38
|
exit 1
|
@@ -73,3 +73,10 @@ task 'release' => [:check_git, package('.gem'), package('.tar.gz')] do |t|
|
|
73
73
|
sh "git tag v#{$spec.version}"
|
74
74
|
sh "git push && git push --tags"
|
75
75
|
end
|
76
|
+
|
77
|
+
desc 'download github issues and pull requests'
|
78
|
+
task 'github' do
|
79
|
+
%w(issues pulls).each do |type|
|
80
|
+
sh "curl -o #{type}.json https://api.github.com/repos/alexch/rerun/#{type}"
|
81
|
+
end
|
82
|
+
end
|
data/bin/rerun
CHANGED
@@ -7,6 +7,12 @@ $LOAD_PATH.unshift libdir unless $LOAD_PATH.include?(libdir)
|
|
7
7
|
require 'rerun'
|
8
8
|
require 'optparse'
|
9
9
|
|
10
|
-
options = Rerun::Options.parse
|
11
|
-
|
12
|
-
|
10
|
+
options = Rerun::Options.parse config_file: ".rerun"
|
11
|
+
|
12
|
+
if options and options[:verbose]
|
13
|
+
puts "\nrerun options:\n\t#{options}"
|
14
|
+
end
|
15
|
+
|
16
|
+
exit if options.nil? or options[:cmd].nil? or options[:cmd].empty?
|
17
|
+
|
18
|
+
Rerun::Runner.keep_running(options[:cmd], options)
|
data/lib/goo.rb
ADDED
data/lib/rerun.rb
CHANGED
@@ -4,13 +4,12 @@ $: << here unless $:.include?(here)
|
|
4
4
|
require "listen" # pull in the Listen gem
|
5
5
|
require "rerun/options"
|
6
6
|
require "rerun/system"
|
7
|
+
require "rerun/notification"
|
7
8
|
require "rerun/runner"
|
8
9
|
require "rerun/watcher"
|
9
10
|
require "rerun/glob"
|
10
11
|
|
11
12
|
module Rerun
|
12
13
|
|
13
|
-
DEFAULT_PATTERN = "**/*.{rb,js,css,scss,sass,erb,html,haml,ru}"
|
14
|
-
|
15
14
|
end
|
16
15
|
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# todo: unit tests
|
2
|
+
|
3
|
+
module Rerun
|
4
|
+
class Notification
|
5
|
+
include System
|
6
|
+
|
7
|
+
attr_reader :title, :body, :options
|
8
|
+
|
9
|
+
def initialize(title, body, options = Options::DEFAULTS.dup)
|
10
|
+
@title = title
|
11
|
+
@body = body
|
12
|
+
@options = options
|
13
|
+
end
|
14
|
+
|
15
|
+
def command
|
16
|
+
# todo: strategy or subclass
|
17
|
+
|
18
|
+
s = nil
|
19
|
+
|
20
|
+
if options[:notify] == true or options[:notify] == "growl"
|
21
|
+
if (cmd = command_named("growlnotify"))
|
22
|
+
# todo: check version of growlnotify and warn if it's too old
|
23
|
+
icon_str = ("--image \"#{icon}\"" if icon)
|
24
|
+
s = "#{cmd} -n \"#{app_name}\" -m \"#{body}\" \"#{app_name} #{title}\" #{icon_str}"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
if s.nil? and options[:notify] == true or options[:notify] == "osx"
|
29
|
+
if (cmd = command_named("terminal-notifier"))
|
30
|
+
icon_str = ("-appIcon \"#{icon}\"" if icon)
|
31
|
+
s = "#{cmd} -title \"#{app_name}\" -message \"#{body}\" \"#{app_name} #{title}\" #{icon_str}"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
if s.nil? and options[:notify] == true or options[:notify] == "notify-send"
|
36
|
+
if (cmd = command_named('notify-send'))
|
37
|
+
icon_str = "--icon #{icon}" if icon
|
38
|
+
s = "#{cmd} -t 500 --hint=int:transient:1 #{icon_str} \"#{app_name}: #{title}\" \"#{body}\""
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
s
|
43
|
+
end
|
44
|
+
|
45
|
+
def command_named(name)
|
46
|
+
which_command = windows? ? 'where.exe %{cmd}' : 'which %{cmd} 2> /dev/null'
|
47
|
+
# TODO: remove 'INFO' error message
|
48
|
+
path = `#{which_command % {cmd: name}}`.chomp
|
49
|
+
path.empty? ? nil : path
|
50
|
+
end
|
51
|
+
|
52
|
+
def send(background = true)
|
53
|
+
return unless command
|
54
|
+
with_clean_env do
|
55
|
+
`#{command}#{" &" if background}`
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def app_name
|
60
|
+
options[:name]
|
61
|
+
end
|
62
|
+
|
63
|
+
def icon
|
64
|
+
"#{icon_dir}/rails_red_sml.png" if rails?
|
65
|
+
end
|
66
|
+
|
67
|
+
def icon_dir
|
68
|
+
here = File.expand_path(File.dirname(__FILE__))
|
69
|
+
File.expand_path("#{here}/../../icons")
|
70
|
+
end
|
71
|
+
|
72
|
+
def with_clean_env
|
73
|
+
if defined?(Bundler)
|
74
|
+
Bundler.with_clean_env do
|
75
|
+
yield
|
76
|
+
end
|
77
|
+
else
|
78
|
+
yield
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
data/lib/rerun/options.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
require 'optparse'
|
2
2
|
require 'pathname'
|
3
|
+
require 'rerun/watcher'
|
4
|
+
require 'rerun/system'
|
3
5
|
|
4
6
|
libdir = "#{File.expand_path(File.dirname(File.dirname(__FILE__)))}"
|
5
7
|
|
@@ -7,86 +9,138 @@ $spec = Gem::Specification.load(File.join(libdir, "..", "rerun.gemspec"))
|
|
7
9
|
|
8
10
|
module Rerun
|
9
11
|
class Options
|
10
|
-
|
12
|
+
|
13
|
+
extend Rerun::System
|
14
|
+
|
15
|
+
# If you change the default pattern, please update the README.md file -- the list appears twice therein, which at the time of this comment are lines 17 and 119
|
16
|
+
DEFAULT_PATTERN = "**/*.{rb,js,coffee,css,scss,sass,erb,html,haml,ru,yml,slim,md,feature,c,h}"
|
11
17
|
DEFAULT_DIRS = ["."]
|
12
18
|
|
13
19
|
DEFAULTS = {
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
20
|
+
:background => false,
|
21
|
+
:dir => DEFAULT_DIRS,
|
22
|
+
:force_polling => false,
|
23
|
+
:ignore => [],
|
24
|
+
:ignore_dotfiles => true,
|
25
|
+
:name => Pathname.getwd.basename.to_s.capitalize,
|
26
|
+
:notify => true,
|
27
|
+
:pattern => DEFAULT_PATTERN,
|
28
|
+
:quiet => false,
|
29
|
+
:signal => (windows? ? "TERM,KILL" : "TERM,INT,KILL"),
|
30
|
+
:verbose => false,
|
31
|
+
:wait => 2,
|
18
32
|
}
|
19
33
|
|
20
|
-
def self.parse args
|
21
|
-
options = DEFAULTS.dup
|
22
|
-
opts = OptionParser.new("", 24, ' ') do |opts|
|
23
|
-
opts.banner = "Usage: rerun [options] [--] cmd"
|
34
|
+
def self.parse args: ARGV, config_file: nil
|
24
35
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
36
|
+
default_options = DEFAULTS.dup
|
37
|
+
options = {
|
38
|
+
ignore: []
|
39
|
+
}
|
40
|
+
|
41
|
+
if config_file && File.exist?(config_file)
|
42
|
+
require 'shellwords'
|
43
|
+
config_args = File.read(config_file).shellsplit
|
44
|
+
args = config_args + args
|
45
|
+
end
|
31
46
|
|
32
|
-
|
47
|
+
option_parser = OptionParser.new("", 24, ' ') do |o|
|
48
|
+
o.banner = "Usage: rerun [options] [--] cmd"
|
49
|
+
|
50
|
+
o.separator ""
|
51
|
+
o.separator "Launches an app, and restarts it when the filesystem changes."
|
52
|
+
o.separator "See http://github.com/alexch/rerun for more info."
|
53
|
+
o.separator "Version: #{$spec.version}"
|
54
|
+
o.separator ""
|
55
|
+
o.separator "Options:"
|
56
|
+
|
57
|
+
o.on("-d dir", "--dir dir", "directory to watch, default = \"#{DEFAULT_DIRS}\". Specify multiple paths with ',' or separate '-d dir' option pairs.") do |dir|
|
33
58
|
elements = dir.split(",")
|
34
59
|
options[:dir] = (options[:dir] || []) + elements
|
35
60
|
end
|
36
61
|
|
37
|
-
|
62
|
+
# todo: rename to "--watch"
|
63
|
+
o.on("-p pattern", "--pattern pattern", "file glob to watch, default = \"#{DEFAULTS[:pattern]}\"") do |pattern|
|
38
64
|
options[:pattern] = pattern
|
39
65
|
end
|
40
66
|
|
41
|
-
|
67
|
+
o.on("-i pattern", "--ignore pattern", "file glob(s) to ignore. Can be set many times. To ignore a directory, you must append '/*' e.g. --ignore 'coverage/*' . Globs do not match dotfiles by default.") do |pattern|
|
68
|
+
options[:ignore] += [pattern]
|
69
|
+
end
|
70
|
+
|
71
|
+
o.on("--[no-]ignore-dotfiles", "by default, file globs do not match files that begin with a dot. Setting --no-ignore-dotfiles allows you to monitor a relevant file like .env, but you may also have to explicitly --ignore more dotfiles and dotdirs.") do |value|
|
72
|
+
options[:ignore_dotfiles] = value
|
73
|
+
end
|
74
|
+
|
75
|
+
o.on("-s signal", "--signal signal", "terminate process using this signal. To try several signals in series, use a comma-delimited list. Default: \"#{DEFAULTS[:signal]}\"") do |signal|
|
42
76
|
options[:signal] = signal
|
43
77
|
end
|
44
78
|
|
45
|
-
|
46
|
-
|
79
|
+
o.on("-w sec", "--wait sec", "after asking the process to terminate, wait this long (in seconds) before either aborting, or trying the next signal in series. Default: #{DEFAULTS[:wait]} sec")
|
80
|
+
|
81
|
+
o.on("-r", "--restart", "expect process to restart itself, so just send a signal and continue watching. Sends the HUP signal unless overridden using --signal") do |signal|
|
82
|
+
options[:restart] = true
|
83
|
+
default_options[:signal] = "HUP"
|
47
84
|
end
|
48
85
|
|
49
|
-
|
50
|
-
options[:exit] =
|
86
|
+
o.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 |value|
|
87
|
+
options[:exit] = value
|
51
88
|
end
|
52
89
|
|
53
|
-
|
54
|
-
options[:
|
90
|
+
o.on("-c", "--clear", "clear screen before each run") do |value|
|
91
|
+
options[:clear] = value
|
55
92
|
end
|
56
93
|
|
57
|
-
|
94
|
+
o.on("-b", "--background", "disable on-the-fly keypress commands, allowing the process to be backgrounded") do |value|
|
95
|
+
options[:background] = value
|
96
|
+
end
|
97
|
+
|
98
|
+
o.on("-n name", "--name name", "name of app used in logs and notifications, default = \"#{DEFAULTS[:name]}\"") do |name|
|
58
99
|
options[:name] = name
|
59
100
|
end
|
60
101
|
|
61
|
-
|
102
|
+
o.on("--[no-]force-polling", "use polling instead of a native filesystem scan (useful for Vagrant)") do |value|
|
103
|
+
options[:force_polling] = value
|
104
|
+
end
|
105
|
+
|
106
|
+
o.on("--no-growl", "don't use growl [OBSOLETE]") do
|
62
107
|
options[:growl] = false
|
108
|
+
$stderr.puts "--no-growl is obsolete; use --no-notify instead"
|
109
|
+
return
|
110
|
+
end
|
111
|
+
|
112
|
+
o.on("--[no-]notify [notifier]", "send messages through a desktop notification application. Supports growl (requires growlnotify), osx (requires terminal-notifier gem), and notify-send on GNU/Linux (notify-send must be installed)") do |notifier|
|
113
|
+
notifier = true if notifier.nil?
|
114
|
+
options[:notify] = notifier
|
63
115
|
end
|
64
116
|
|
65
|
-
|
66
|
-
|
117
|
+
o.on("-q", "--[no-]quiet", "don't output any logs") do |value|
|
118
|
+
options[:quiet] = value
|
119
|
+
end
|
120
|
+
|
121
|
+
o.on("--[no-]verbose", "log extra stuff like PIDs (unless you also specified `--quiet`") do |value|
|
122
|
+
options[:verbose] = value
|
123
|
+
end
|
124
|
+
|
125
|
+
o.on_tail("-h", "--help", "--usage", "show this message and immediately exit") do
|
126
|
+
puts o
|
67
127
|
return
|
68
128
|
end
|
69
129
|
|
70
|
-
|
130
|
+
o.on_tail("--version", "show version and immediately exit") do
|
71
131
|
puts $spec.version
|
72
132
|
return
|
73
133
|
end
|
74
134
|
|
75
|
-
opts.on_tail ""
|
76
|
-
opts.on_tail "On top of --pattern, we ignore any changes to files and dirs starting with a dot, ending with [#{Listen::Silencer::DEFAULT_IGNORED_EXTENSIONS.join(',')}], or named [#{Listen::Silencer::DEFAULT_IGNORED_DIRECTORIES.join(',')}]."
|
77
|
-
|
78
135
|
end
|
79
136
|
|
80
|
-
if args.empty?
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
opts.parse! args
|
85
|
-
options[:cmd] = args.join(" ")
|
86
|
-
options[:dir] ||= DEFAULT_DIRS
|
87
|
-
options
|
88
|
-
end
|
89
|
-
end
|
137
|
+
puts option_parser if args.empty?
|
138
|
+
option_parser.parse! args
|
139
|
+
options = default_options.merge(options)
|
140
|
+
options[:cmd] = args.join(" ").strip # todo: better arg word handling
|
90
141
|
|
142
|
+
options
|
143
|
+
end
|
91
144
|
end
|
145
|
+
|
92
146
|
end
|
data/lib/rerun/runner.rb
CHANGED
@@ -4,19 +4,25 @@ require 'io/wait'
|
|
4
4
|
module Rerun
|
5
5
|
class Runner
|
6
6
|
|
7
|
+
# The watcher instance that wait for changes
|
8
|
+
attr_reader :watcher
|
9
|
+
|
7
10
|
def self.keep_running(cmd, options)
|
8
11
|
runner = new(cmd, options)
|
9
12
|
runner.start
|
10
13
|
runner.join
|
11
14
|
# apparently runner doesn't keep running anymore (as of Listen 2) so we have to sleep forever :-(
|
12
|
-
sleep 10000 while true
|
15
|
+
sleep 10000 while true # :-(
|
13
16
|
end
|
14
17
|
|
15
18
|
include System
|
19
|
+
include ::Timeout
|
16
20
|
|
17
21
|
def initialize(run_command, options = {})
|
18
22
|
@run_command, @options = run_command, options
|
19
23
|
@run_command = "ruby #{@run_command}" if @run_command.split(' ').first =~ /\.rb$/
|
24
|
+
@options[:directory] ||= options.delete(:dir) || '.'
|
25
|
+
@options[:ignore] ||= []
|
20
26
|
end
|
21
27
|
|
22
28
|
def start_keypress_thread
|
@@ -32,22 +38,26 @@ module Rerun
|
|
32
38
|
when 'r'
|
33
39
|
say "Restarting"
|
34
40
|
restart
|
41
|
+
when 'f'
|
42
|
+
say "Stopping and starting"
|
43
|
+
restart(false)
|
35
44
|
when 'p'
|
36
|
-
toggle_pause
|
45
|
+
toggle_pause
|
37
46
|
when 'x', 'q'
|
38
47
|
die
|
39
|
-
break
|
48
|
+
break # the break will stop this thread, in case the 'die' doesn't
|
40
49
|
else
|
41
50
|
puts "\n#{c.inspect} pressed inside rerun"
|
42
51
|
puts [["c", "clear screen"],
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
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")
|
47
57
|
puts
|
48
58
|
end
|
49
59
|
end
|
50
|
-
sleep 1
|
60
|
+
sleep 1 # todo: use select instead of polling somehow?
|
51
61
|
end
|
52
62
|
end
|
53
63
|
@keypress_thread.run
|
@@ -58,24 +68,24 @@ module Rerun
|
|
58
68
|
@keypress_thread = nil
|
59
69
|
end
|
60
70
|
|
61
|
-
def restart
|
71
|
+
def restart(with_signal = true)
|
62
72
|
@restarting = true
|
63
|
-
|
64
|
-
|
73
|
+
if @options[:restart] && with_signal
|
74
|
+
restart_with_signal(@options[:signal])
|
75
|
+
else
|
76
|
+
stop
|
77
|
+
start
|
78
|
+
end
|
65
79
|
@restarting = false
|
66
80
|
end
|
67
81
|
|
68
|
-
def watcher_running?
|
69
|
-
@watcher && @watcher.running?
|
70
|
-
end
|
71
|
-
|
72
82
|
def toggle_pause
|
73
83
|
unless @pausing
|
74
84
|
say "Pausing. Press 'p' again to resume."
|
75
85
|
@watcher.pause
|
76
86
|
@pausing = true
|
77
87
|
else
|
78
|
-
say "Resuming"
|
88
|
+
say "Resuming."
|
79
89
|
@watcher.unpause
|
80
90
|
@pausing = false
|
81
91
|
end
|
@@ -86,11 +96,7 @@ module Rerun
|
|
86
96
|
end
|
87
97
|
|
88
98
|
def dir
|
89
|
-
@options[:
|
90
|
-
end
|
91
|
-
|
92
|
-
def dirs
|
93
|
-
@options[:dir] || "."
|
99
|
+
@options[:directory]
|
94
100
|
end
|
95
101
|
|
96
102
|
def pattern
|
@@ -101,6 +107,14 @@ module Rerun
|
|
101
107
|
@options[:clear]
|
102
108
|
end
|
103
109
|
|
110
|
+
def quiet?
|
111
|
+
@options[:quiet]
|
112
|
+
end
|
113
|
+
|
114
|
+
def verbose?
|
115
|
+
@options[:verbose]
|
116
|
+
end
|
117
|
+
|
104
118
|
def exit?
|
105
119
|
@options[:exit]
|
106
120
|
end
|
@@ -109,19 +123,19 @@ module Rerun
|
|
109
123
|
@options[:name]
|
110
124
|
end
|
111
125
|
|
112
|
-
def
|
113
|
-
if
|
114
|
-
|
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)
|
115
130
|
end
|
131
|
+
end
|
116
132
|
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
@already_running = true
|
124
|
-
else
|
133
|
+
def force_polling
|
134
|
+
@options[:force_polling]
|
135
|
+
end
|
136
|
+
|
137
|
+
def start
|
138
|
+
if @already_running
|
125
139
|
taglines = [
|
126
140
|
"Here we go again!",
|
127
141
|
"Keep on trucking.",
|
@@ -129,26 +143,33 @@ module Rerun
|
|
129
143
|
"The road goes ever on and on, down from the door where it began.",
|
130
144
|
]
|
131
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
|
132
153
|
end
|
133
154
|
|
134
155
|
clear_screen if clear?
|
135
156
|
start_keypress_thread unless @keypress_thread
|
136
157
|
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
end
|
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
|
+
exit
|
144
164
|
end
|
165
|
+
|
145
166
|
status_thread = Process.detach(@pid) # so if the child exits, it dies
|
146
167
|
|
147
168
|
Signal.trap("INT") do # INT = control-C -- allows user to stop the top-level rerun process
|
148
169
|
die
|
149
170
|
end
|
150
171
|
|
151
|
-
Signal.trap("TERM") do
|
172
|
+
Signal.trap("TERM") do # TERM is the polite way of terminating a process
|
152
173
|
die
|
153
174
|
end
|
154
175
|
|
@@ -174,28 +195,47 @@ module Rerun
|
|
174
195
|
end
|
175
196
|
|
176
197
|
unless @watcher
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
message = [:modified, :added, :removed].map do |change|
|
181
|
-
count = changes[change].size
|
182
|
-
if count and count > 0
|
183
|
-
"#{count} #{change}"
|
184
|
-
end
|
185
|
-
end.compact.join(", ")
|
198
|
+
watcher = Watcher.new(@options) do |changes|
|
199
|
+
message = change_message(changes)
|
186
200
|
say "Change detected: #{message}"
|
187
201
|
restart unless @restarting
|
188
202
|
end
|
189
203
|
watcher.start
|
190
204
|
@watcher = watcher
|
191
|
-
|
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")
|
192
209
|
end
|
193
210
|
end
|
194
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
|
+
|
195
235
|
def die
|
196
236
|
#stop_keypress_thread # don't do this since we're probably *in* the keypress thread
|
197
237
|
stop # stop the child process if it exists
|
198
|
-
exit 0
|
238
|
+
exit 0 # todo: status code param
|
199
239
|
end
|
200
240
|
|
201
241
|
def join
|
@@ -203,40 +243,63 @@ module Rerun
|
|
203
243
|
end
|
204
244
|
|
205
245
|
def running?
|
206
|
-
|
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
|
+
|
254
|
+
signal_sent = if windows?
|
255
|
+
force_kill = (signal == 'KILL')
|
256
|
+
system("taskkill /T #{'/F' if force_kill} /PID #{@pid}")
|
257
|
+
else
|
258
|
+
send_signal(signal)
|
259
|
+
end
|
260
|
+
|
261
|
+
if signal_sent
|
262
|
+
# the signal was successfully sent, so wait for the process to die
|
263
|
+
begin
|
264
|
+
timeout(@options[:wait]) do
|
265
|
+
Process.wait(@pid)
|
266
|
+
end
|
267
|
+
process_status = $?
|
268
|
+
say "Process ended: #{process_status}" if verbose?
|
269
|
+
true
|
270
|
+
rescue Timeout::Error
|
271
|
+
false
|
272
|
+
end
|
273
|
+
else
|
274
|
+
false
|
275
|
+
end
|
207
276
|
end
|
208
277
|
|
209
|
-
|
210
|
-
|
278
|
+
# Send the signal to process @pid.
|
279
|
+
# @returns true if the signal is sent
|
280
|
+
# @returns false if sending the signal fails
|
281
|
+
# If sending the signal fails, the exception will be swallowed
|
282
|
+
# (and logged if verbose is true) and this method will return false.
|
283
|
+
#
|
284
|
+
def send_signal(signal)
|
285
|
+
say "Sending signal #{signal} to #{@pid}" unless signal == 0 if verbose?
|
211
286
|
Process.kill(signal, @pid)
|
212
287
|
true
|
213
|
-
rescue
|
288
|
+
rescue => e
|
289
|
+
say "Signal #{signal} failed: #{e.class}: #{e.message}" if verbose?
|
214
290
|
false
|
215
291
|
end
|
216
292
|
|
217
293
|
# todo: test escalation
|
218
294
|
def stop
|
219
|
-
default_signal = @options[:signal] || "TERM"
|
220
295
|
if @pid && (@pid != 0)
|
221
296
|
notify "stopping", "All good things must come to an end." unless @restarting
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
signal(default_signal) && Process.wait(@pid)
|
226
|
-
end
|
227
|
-
rescue Timeout::Error
|
228
|
-
begin
|
229
|
-
timeout(2) do
|
230
|
-
# escalate to SIGINT aka control-C since some foolish process may be ignoring SIGTERM
|
231
|
-
signal("INT") && Process.wait(@pid)
|
232
|
-
end
|
233
|
-
rescue Timeout::Error
|
234
|
-
# escalate to SIGKILL aka "kill -9" which cannot be ignored
|
235
|
-
signal("KILL") && Process.wait(@pid)
|
236
|
-
end
|
297
|
+
@options[:signal].split(',').each do |signal|
|
298
|
+
success = signal_and_wait(signal)
|
299
|
+
return true if success
|
237
300
|
end
|
238
301
|
end
|
239
|
-
rescue
|
302
|
+
rescue
|
240
303
|
false
|
241
304
|
end
|
242
305
|
|
@@ -251,20 +314,25 @@ module Rerun
|
|
251
314
|
@git_head = File.exists?(git_head_file) && File.read(git_head_file)
|
252
315
|
end
|
253
316
|
|
254
|
-
def notify(title, body)
|
255
|
-
|
317
|
+
def notify(title, body, background = true)
|
318
|
+
Notification.new(title, body, @options).send(background) if @options[:notify]
|
256
319
|
puts
|
257
320
|
say "#{app_name} #{title}"
|
258
321
|
end
|
259
322
|
|
260
323
|
def say msg
|
261
|
-
puts "#{Time.now.strftime("%T")} [rerun] #{msg}"
|
324
|
+
puts "#{Time.now.strftime("%T")} [rerun] #{msg}" unless quiet?
|
325
|
+
end
|
326
|
+
|
327
|
+
def stty(args)
|
328
|
+
system "stty #{args}"
|
262
329
|
end
|
263
330
|
|
264
331
|
# non-blocking stdin reader.
|
265
332
|
# returns a 1-char string if a key was pressed; otherwise nil
|
266
333
|
#
|
267
334
|
def key_pressed
|
335
|
+
return one_char if windows?
|
268
336
|
begin
|
269
337
|
# this "raw input" nonsense is because unix likes waiting for linefeeds before sending stdin
|
270
338
|
|
@@ -274,15 +342,10 @@ module Rerun
|
|
274
342
|
# looks like "raw" flips off the OPOST bit 0x00000001 /* enable following output processing */
|
275
343
|
# which disables #define ONLCR 0x00000002 /* map NL to CR-NL (ala CRMOD) */
|
276
344
|
# so this sets it back on again since all we care about is raw input, not raw output
|
277
|
-
|
278
|
-
|
279
|
-
c = nil
|
280
|
-
if $stdin.ready?
|
281
|
-
c = $stdin.getc
|
282
|
-
end
|
283
|
-
c.chr if c
|
345
|
+
stty "raw opost"
|
346
|
+
one_char
|
284
347
|
ensure
|
285
|
-
|
348
|
+
stty "-raw" # turn raw input off
|
286
349
|
end
|
287
350
|
|
288
351
|
# note: according to 'man tty' the proper way restore the settings is
|
@@ -291,7 +354,6 @@ module Rerun
|
|
291
354
|
# system 'stty "#{tty_state}'
|
292
355
|
# end
|
293
356
|
# but this way seems fine and less confusing
|
294
|
-
|
295
357
|
end
|
296
358
|
|
297
359
|
def clear_screen
|
@@ -299,5 +361,14 @@ module Rerun
|
|
299
361
|
$stdout.print "\033[H\033[2J"
|
300
362
|
end
|
301
363
|
|
364
|
+
private
|
365
|
+
def one_char
|
366
|
+
c = nil
|
367
|
+
if $stdin.ready?
|
368
|
+
c = $stdin.getc
|
369
|
+
end
|
370
|
+
c.chr if c
|
371
|
+
end
|
372
|
+
|
302
373
|
end
|
303
374
|
end
|
data/lib/rerun/system.rb
CHANGED
@@ -6,38 +6,16 @@ module Rerun
|
|
6
6
|
end
|
7
7
|
|
8
8
|
def windows?
|
9
|
-
RUBY_PLATFORM =~ /mswin/i
|
9
|
+
RUBY_PLATFORM =~ /(mswin|mingw32)/i
|
10
10
|
end
|
11
11
|
|
12
12
|
def linux?
|
13
13
|
RUBY_PLATFORM =~ /linux/i
|
14
14
|
end
|
15
15
|
|
16
|
-
|
17
|
-
def growl_available?
|
18
|
-
mac? && (growlcmd != "")
|
19
|
-
end
|
20
|
-
|
21
|
-
def growlcmd
|
22
|
-
growlnotify = `which growlnotify`.chomp
|
23
|
-
# todo: check version of growlnotify and warn if it's too old
|
24
|
-
growlnotify
|
25
|
-
end
|
26
|
-
|
27
|
-
def icon
|
28
|
-
here = File.expand_path(File.dirname(__FILE__))
|
29
|
-
icondir = File.expand_path("#{here}/../../icons")
|
16
|
+
def rails?
|
30
17
|
rails_sig_file = File.expand_path(".")+"/config/boot.rb"
|
31
|
-
|
32
|
-
end
|
33
|
-
|
34
|
-
def growl(title, body, background = true)
|
35
|
-
if growl_available?
|
36
|
-
icon_str = ("--image \"#{icon}\"" if icon)
|
37
|
-
s = "#{growlcmd} -n \"#{app_name}\" -m \"#{body}\" \"#{app_name} #{title}\" #{icon_str}"
|
38
|
-
s += " &" if background
|
39
|
-
`#{s}`
|
40
|
-
end
|
18
|
+
File.exists? rails_sig_file
|
41
19
|
end
|
42
20
|
|
43
21
|
end
|
data/lib/rerun/watcher.rb
CHANGED
@@ -14,7 +14,11 @@ module Rerun
|
|
14
14
|
class Watcher
|
15
15
|
InvalidDirectoryError = Class.new(RuntimeError)
|
16
16
|
|
17
|
-
|
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
|
18
22
|
|
19
23
|
# Create a file system watcher. Start it by calling #start.
|
20
24
|
#
|
@@ -29,12 +33,16 @@ module Rerun
|
|
29
33
|
:directory => ".",
|
30
34
|
:pattern => "**/*",
|
31
35
|
:priority => 0,
|
36
|
+
:ignore_dotfiles => true,
|
32
37
|
}.merge(options)
|
33
38
|
|
34
39
|
@pattern = options[:pattern]
|
35
40
|
@directories = options[:directory]
|
36
41
|
@directories = sanitize_dirs(@directories)
|
37
42
|
@priority = options[:priority]
|
43
|
+
@force_polling = options[:force_polling]
|
44
|
+
@ignore = [options[:ignore]].flatten.compact
|
45
|
+
@ignore_dotfiles = options[:ignore_dotfiles]
|
38
46
|
@thread = nil
|
39
47
|
end
|
40
48
|
|
@@ -55,10 +63,9 @@ module Rerun
|
|
55
63
|
end
|
56
64
|
|
57
65
|
@thread = Thread.new do
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
if((modified.size + added.size + removed.size) > 0)
|
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
|
62
69
|
@client_callback.call(:modified => modified, :added => added, :removed => removed)
|
63
70
|
end
|
64
71
|
end
|
@@ -72,11 +79,16 @@ module Rerun
|
|
72
79
|
at_exit { stop } # try really hard to clean up after ourselves
|
73
80
|
end
|
74
81
|
|
75
|
-
def
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
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 }
|
80
92
|
end
|
81
93
|
|
82
94
|
# kill the file watcher thread
|
@@ -93,7 +105,7 @@ module Rerun
|
|
93
105
|
# wait for the file watcher to finish
|
94
106
|
def join
|
95
107
|
@thread.join if @thread
|
96
|
-
rescue Interrupt
|
108
|
+
rescue Interrupt
|
97
109
|
# don't care
|
98
110
|
end
|
99
111
|
|
@@ -102,12 +114,21 @@ module Rerun
|
|
102
114
|
end
|
103
115
|
|
104
116
|
def unpause
|
105
|
-
@listener.
|
117
|
+
@listener.start if @listener
|
106
118
|
end
|
107
119
|
|
108
120
|
def running?
|
109
|
-
@listener && @listener.
|
121
|
+
@listener && @listener.processing?
|
110
122
|
end
|
111
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
|
112
133
|
end
|
113
134
|
end
|
data/rerun.gemspec
CHANGED
@@ -3,7 +3,7 @@ $spec = Gem::Specification.new do |s|
|
|
3
3
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
4
4
|
|
5
5
|
s.name = 'rerun'
|
6
|
-
s.version = '0.
|
6
|
+
s.version = '0.13.1'
|
7
7
|
|
8
8
|
s.description = "Restarts your app when a file changes. A no-frills, command-line alternative to Guard, Shotgun, Autotest, etc."
|
9
9
|
s.summary = "Launches an app, and restarts it whenever the filesystem changes. A no-frills, command-line alternative to Guard, Shotgun, Autotest, etc."
|
@@ -25,7 +25,7 @@ $spec = Gem::Specification.new do |s|
|
|
25
25
|
|
26
26
|
s.extra_rdoc_files = %w[README.md]
|
27
27
|
|
28
|
-
s.
|
28
|
+
s.add_runtime_dependency 'listen', '~> 3.0'
|
29
29
|
|
30
30
|
s.homepage = "http://github.com/alexch/rerun/"
|
31
31
|
s.require_paths = %w[lib]
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rerun
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.13.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alex Chaffee
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-12-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: listen
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '3.0'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '3.0'
|
27
27
|
description: Restarts your app when a file changes. A no-frills, command-line alternative
|
28
28
|
to Guard, Shotgun, Autotest, etc.
|
29
29
|
email: alex@stinky.com
|
@@ -39,8 +39,10 @@ files:
|
|
39
39
|
- bin/rerun
|
40
40
|
- icons/rails_grn_sml.png
|
41
41
|
- icons/rails_red_sml.png
|
42
|
+
- lib/goo.rb
|
42
43
|
- lib/rerun.rb
|
43
44
|
- lib/rerun/glob.rb
|
45
|
+
- lib/rerun/notification.rb
|
44
46
|
- lib/rerun/options.rb
|
45
47
|
- lib/rerun/runner.rb
|
46
48
|
- lib/rerun/system.rb
|
@@ -50,7 +52,7 @@ homepage: http://github.com/alexch/rerun/
|
|
50
52
|
licenses:
|
51
53
|
- MIT
|
52
54
|
metadata: {}
|
53
|
-
post_install_message:
|
55
|
+
post_install_message:
|
54
56
|
rdoc_options: []
|
55
57
|
require_paths:
|
56
58
|
- lib
|
@@ -65,9 +67,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
65
67
|
- !ruby/object:Gem::Version
|
66
68
|
version: '0'
|
67
69
|
requirements: []
|
68
|
-
|
69
|
-
|
70
|
-
signing_key:
|
70
|
+
rubygems_version: 3.1.2
|
71
|
+
signing_key:
|
71
72
|
specification_version: 2
|
72
73
|
summary: Launches an app, and restarts it whenever the filesystem changes. A no-frills,
|
73
74
|
command-line alternative to Guard, Shotgun, Autotest, etc.
|