rerun-cj 0.10.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 +7 -0
- data/LICENSE +34 -0
- data/README.md +330 -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/rerun/glob.rb +85 -0
- data/lib/rerun/options.rb +99 -0
- data/lib/rerun/runner.rb +324 -0
- data/lib/rerun/system.rb +44 -0
- data/lib/rerun/watcher.rb +127 -0
- data/lib/rerun.rb +14 -0
- data/rerun.gemspec +34 -0
- metadata +80 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 27c6fa79f508fe873f0c236c273ea18346eadd9e
|
4
|
+
data.tar.gz: d79d590d3158eb9182b7790d040cb5598a58a6b4
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 4dddd98b58c3b26fcf6a3aebd2e7b965b2f827ad847f22ed5505e779355e08af7a4e5eb5ab79521fc7ff9a3dc1af7532ef6b8785eca95dd2c0bc38e10970f197
|
7
|
+
data.tar.gz: d437f2c29d6cba96f88d41aad99004b964b4e7f8c14b3c880cb77785ff39c4ed20410631cf464d1a9d607b6e843b2d37514514f762f60b0c479515c5a5dbe641
|
data/LICENSE
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
Rerun
|
2
|
+
Copyright (c) 2009 Alex Chaffee <alex@stinky.com>
|
3
|
+
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
5
|
+
of this software and associated documentation files (the "Software"), to
|
6
|
+
deal in the Software without restriction, including without limitation the
|
7
|
+
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
8
|
+
sell copies of the Software, and to permit persons to whom the Software is
|
9
|
+
furnished to do so, subject to the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be included in
|
12
|
+
all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
15
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
16
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
17
|
+
THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
18
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
19
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
20
|
+
|
21
|
+
---
|
22
|
+
|
23
|
+
rerun partially based on code from Rspactor
|
24
|
+
Copyright (c) 2009 Mislav Marohnić
|
25
|
+
License as above (MIT open source).
|
26
|
+
|
27
|
+
rerun partially based on code from FileSystemWatcher
|
28
|
+
http://paulhorman.com/filesystemwatcher/
|
29
|
+
No license provided; assumed public domain.
|
30
|
+
|
31
|
+
rerun partially based on code from Shotgun
|
32
|
+
Copyright (c) 2009 Ryan Tomayko <tomayko.com/about>
|
33
|
+
License as above (MIT open source).
|
34
|
+
|
data/README.md
ADDED
@@ -0,0 +1,330 @@
|
|
1
|
+
# Rerun
|
2
|
+
|
3
|
+
<http://github.com/alexch/rerun>
|
4
|
+
|
5
|
+
Rerun launches your program, then watches the filesystem. If a relevant file
|
6
|
+
changes, then it restarts your program.
|
7
|
+
|
8
|
+
Rerun works for both long-running processes (e.g. apps) and short-running ones
|
9
|
+
(e.g. tests). It's basically a no-frills command-line alternative to Guard,
|
10
|
+
Shotgun, Autotest, etc. that doesn't require config files and works on any
|
11
|
+
command, not just Ruby programs.
|
12
|
+
|
13
|
+
Rerun's advantage is its simple design. Since it uses `exec` and the standard
|
14
|
+
Unix `SIGINT` and `SIGKILL` signals, you're sure the restarted app is really
|
15
|
+
acting just like it was when you ran it from the command line the first time.
|
16
|
+
|
17
|
+
By default only `*.{rb,js,css,coffee,scss,sass,erb,html,haml,ru,slim,md}` files are watched.
|
18
|
+
Use the `--pattern` option if you want to change this.
|
19
|
+
|
20
|
+
As of version 0.7.0, we use the Listen gem, which tries to use your OS's
|
21
|
+
built-in facilities for monitoring the filesystem, so CPU use is very light.
|
22
|
+
|
23
|
+
Rerun does not work on Windows. Sorry, but you can't do much relaunching
|
24
|
+
without "fork".
|
25
|
+
|
26
|
+
# Installation:
|
27
|
+
|
28
|
+
gem install rerun
|
29
|
+
|
30
|
+
("sudo" may be required on older systems, but try it without sudo first.)
|
31
|
+
|
32
|
+
If you are using RVM you might want to put this in your global gemset so it's
|
33
|
+
available to all your apps. (There really should be a better way to distinguish
|
34
|
+
gems-as-libraries from gems-as-tools.)
|
35
|
+
|
36
|
+
rvm @global do gem install rerun
|
37
|
+
|
38
|
+
The Listen gem looks for certain platform-dependent gems, and will complain if
|
39
|
+
they're not available. Unfortunately, Rubygems doesn't understand optional
|
40
|
+
dependencies very well, so you may have to install extra gems (and/or put them
|
41
|
+
in your Gemfile) to make Rerun work more smoothly on your system.
|
42
|
+
(Learn more at <https://github.com/guard/listen#listen-adapters>.)
|
43
|
+
For example, on Mac OS X, use
|
44
|
+
|
45
|
+
gem install rb-fsevent
|
46
|
+
|
47
|
+
# Usage:
|
48
|
+
|
49
|
+
rerun [options] [--] cmd
|
50
|
+
|
51
|
+
For example, if you're running a Sinatra app whose main file is `app.rb`:
|
52
|
+
|
53
|
+
rerun ruby app.rb
|
54
|
+
|
55
|
+
If the first part of the command is a `.rb` filename, then `ruby` is
|
56
|
+
optional, so the above can also be accomplished like this:
|
57
|
+
|
58
|
+
rerun app.rb
|
59
|
+
|
60
|
+
Rails doesn't automatically notice all config file changes, so you can force it
|
61
|
+
to restart when you change a config file like this:
|
62
|
+
|
63
|
+
rerun --dir config rails s
|
64
|
+
|
65
|
+
Or if you're using Thin to run a Rack app that's configured in config.ru
|
66
|
+
but you want it on port 4000 and in debug mode, and only want to watch
|
67
|
+
the `app` and `web` subdirectories:
|
68
|
+
|
69
|
+
rerun --dir app,web -- thin start --debug --port=4000 -R config.ru
|
70
|
+
|
71
|
+
The `--` is to separate rerun options from cmd options. You can also
|
72
|
+
use a quoted string for the command, e.g.
|
73
|
+
|
74
|
+
rerun --dir app "thin start --debug --port=4000 -R config.ru"
|
75
|
+
|
76
|
+
Rackup can also be used to launch a Rack server, so let's try that:
|
77
|
+
|
78
|
+
rerun -- rackup --port 4000 config.ru
|
79
|
+
|
80
|
+
Want to mimic [autotest](https://github.com/grosser/autotest)? Try
|
81
|
+
|
82
|
+
rerun -x rake
|
83
|
+
|
84
|
+
or
|
85
|
+
|
86
|
+
rerun -cx rspec
|
87
|
+
|
88
|
+
And if you're using [Spork](https://github.com/sporkrb/spork) with Rails, you
|
89
|
+
need to [restart your spork server](https://github.com/sporkrb/spork/issues/201)
|
90
|
+
whenever certain Rails environment files change, so why not put this in your
|
91
|
+
Rakefile...
|
92
|
+
|
93
|
+
desc "run spork (via rerun)"
|
94
|
+
task :spork do
|
95
|
+
sh "rerun --pattern '{Gemfile,Gemfile.lock,spec/spec_helper.rb,.rspec,spec/factories/**,config/environment.rb,config/environments/test.rb,config/initializers/*.rb,lib/**/*.rb}' -- spork"
|
96
|
+
end
|
97
|
+
|
98
|
+
and start using `rake spork` to launch your spork server?
|
99
|
+
|
100
|
+
(If you're using Guard instead of Rerun, check out
|
101
|
+
[guard-spork](https://github.com/guard/guard-spork)
|
102
|
+
for a similar solution.)
|
103
|
+
|
104
|
+
How about regenerating your HTML files after every change to your
|
105
|
+
[Erector](http://erector.rubyforge.org) widgets?
|
106
|
+
|
107
|
+
rerun -x erector --to-html my_site.rb
|
108
|
+
|
109
|
+
Use Heroku Cedar? `rerun` is now compatible with `foreman`. Run all your
|
110
|
+
Procfile processes locally and restart them all when necessary.
|
111
|
+
|
112
|
+
rerun foreman start
|
113
|
+
|
114
|
+
# Options:
|
115
|
+
|
116
|
+
`--dir` directory (or directories) to watch (default = "."). Separate multiple paths with ',' and/or use multiple `-d` options.
|
117
|
+
|
118
|
+
`--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,coffee,scss,sass,erb,html,haml,ru,slim,md`.
|
120
|
+
On top of this, it also ignores dotfiles, `.tmp` files, and some other files and directories (like `.git` and `log`).
|
121
|
+
Run `rerun --help` to see the actual list.
|
122
|
+
|
123
|
+
`--ignore pattern` file glob to ignore (can be set many times). To ignore a directory, you must append '/*' e.g.
|
124
|
+
`--ignore 'coverage/*'`.
|
125
|
+
|
126
|
+
*On top of --pattern and --ignore, we ignore any changes to files and dirs starting with a dot.*
|
127
|
+
|
128
|
+
`--signal` (or -s) use specified signal (instead of the default SIGTERM) to terminate the previous process.
|
129
|
+
This may be useful for forcing the respective process to terminate as quickly as possible.
|
130
|
+
(`--signal KILL` is the equivalent of `kill -9`)
|
131
|
+
|
132
|
+
`--clear` (or -c) clear the screen before each run
|
133
|
+
|
134
|
+
`--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.
|
135
|
+
|
136
|
+
`--background` (or -b) disable on-the-fly commands, allowing the process to be backgrounded
|
137
|
+
|
138
|
+
`--no-growl` don't use growl
|
139
|
+
|
140
|
+
`--name` set the app name (for display)
|
141
|
+
|
142
|
+
Also `--version` and `--help`, naturally.
|
143
|
+
|
144
|
+
# Growl Notifications
|
145
|
+
|
146
|
+
If you have `growlnotify` available on the `PATH`, it sends notifications to
|
147
|
+
growl in addition to the console. If you have growl but don't want rerun to use it,
|
148
|
+
set the `--no-growl` option.
|
149
|
+
|
150
|
+
Download [growlnotify here](http://growl.info/downloads.php#generaldownloads)
|
151
|
+
now that Growl has moved to the App Store.
|
152
|
+
|
153
|
+
# On-The-Fly Commands
|
154
|
+
|
155
|
+
While the app is (re)running, you can make things happen by pressing keys:
|
156
|
+
|
157
|
+
* **r** -- restart (as if a file had changed)
|
158
|
+
* **c** -- clear the screen
|
159
|
+
* **x** or **q** -- exit (just like control-C)
|
160
|
+
* **p** -- pause/unpause filesystem watching
|
161
|
+
|
162
|
+
If you're backgrounding or using Pry or a debugger, you might not want these
|
163
|
+
keys to be trapped, so use the `--background` option.
|
164
|
+
|
165
|
+
# Signals
|
166
|
+
|
167
|
+
The current algorithm for killing the process is:
|
168
|
+
|
169
|
+
* send [SIGTERM](http://en.wikipedia.org/wiki/SIGTERM) (or the value of the `--signal` option)
|
170
|
+
* if that doesn't work after 4 seconds, send SIGINT (aka control-C)
|
171
|
+
* if that doesn't work after 2 more seconds, send SIGKILL (aka kill -9)
|
172
|
+
|
173
|
+
This seems like the most gentle and unixy way of doing things, but it does
|
174
|
+
mean that if your program ignores SIGTERM, it takes an extra 4 to 6 seconds to
|
175
|
+
restart.
|
176
|
+
|
177
|
+
# To Do:
|
178
|
+
|
179
|
+
## Must have for v1.0
|
180
|
+
* ".rerun" file to specify options per project or in $HOME.
|
181
|
+
* Make sure to pass through quoted options correctly to target process [bug]
|
182
|
+
* Optionally do "bundle install" before and "bundle exec" during launch
|
183
|
+
|
184
|
+
## Nice to have
|
185
|
+
* Smarter --signal option (specifying signal to try and a timeout to wait, repeated)
|
186
|
+
* If the last element of the command is a `.ru` file and there's no other command then use `rackup`
|
187
|
+
* Figure out an algorithm so "-x" is not needed (if possible) -- maybe by accepting a "--port" option or reading `config.ru`
|
188
|
+
* Specify (or deduce) port to listen for to determine success of a web server launch
|
189
|
+
|
190
|
+
## Wacky Ideas
|
191
|
+
* Make it work on Windows, like Guard now does. See
|
192
|
+
* https://github.com/guard/guard/issues/59
|
193
|
+
* https://github.com/guard/guard/issues/27
|
194
|
+
* On OS X:
|
195
|
+
* use a C library using growl's developer API <http://growl.info/developer/>
|
196
|
+
* Use growl's AppleScript or SDK instead of relying on growlnotify
|
197
|
+
* Use OS X notifications
|
198
|
+
* "Failed" icon for notifications
|
199
|
+
* On Linux:
|
200
|
+
* Test on Linux.
|
201
|
+
* Use libnotify or notify-send http://www.linuxjournal.com/content/tech-tip-get-notifications-your-scripts-notify-send
|
202
|
+
|
203
|
+
# Other projects that do similar things
|
204
|
+
|
205
|
+
* Restartomatic: <http://github.com/adammck/restartomatic>
|
206
|
+
* Shotgun: <http://github.com/rtomayko/shotgun>
|
207
|
+
* Rack::Reloader middleware: <http://github.com/rack/rack/blob/5ca8f82fb59f0bf0e8fd438e8e91c5acf3d98e44/lib/rack/reloader.rb>
|
208
|
+
* The Sinatra FAQ has a discussion at <http://www.sinatrarb.com/faq.html#reloading>
|
209
|
+
* Kicker: <http://github.com/alloy/kicker/>
|
210
|
+
* Watchr: <https://github.com/mynyml/watchr>
|
211
|
+
* Guard: <http://github.com/guard/guard>
|
212
|
+
* Autotest: <https://github.com/grosser/autotest>
|
213
|
+
|
214
|
+
# Why would I use this instead of Shotgun?
|
215
|
+
|
216
|
+
Shotgun does a "fork" after the web framework has loaded but before
|
217
|
+
your application is loaded. It then loads your app, processes a
|
218
|
+
single request in the child process, then exits the child process.
|
219
|
+
|
220
|
+
Rerun launches the whole app, then when it's time to restart, uses
|
221
|
+
"kill" to shut it down and starts the whole thing up again from
|
222
|
+
scratch.
|
223
|
+
|
224
|
+
So rerun takes somewhat longer than Shotgun to restart the app, but
|
225
|
+
does it much less frequently. And once it's running it behaves more
|
226
|
+
normally and consistently with your production app.
|
227
|
+
|
228
|
+
Also, Shotgun reloads the app on every request, even if it doesn't
|
229
|
+
need to. This is fine if you're loading a single file, but if your web
|
230
|
+
pages all load other files (CSS, JS, media) then that adds up quickly.
|
231
|
+
(I can only assume that the developers of shotgun are using caching or a
|
232
|
+
front web server so this isn't a pain point for them.)
|
233
|
+
|
234
|
+
And hey, does Shotgun reload your Worker processes if you're using Foreman and
|
235
|
+
a Procfile? I'm pretty sure it doesn't.
|
236
|
+
|
237
|
+
YMMV!
|
238
|
+
|
239
|
+
# Why would I use this instead of Rack::Reloader?
|
240
|
+
|
241
|
+
Rack::Reloader is certifiably beautiful code, and is a very elegant use
|
242
|
+
of Rack's middleware architecture. But because it relies on the
|
243
|
+
LOADED_FEATURES variable, it only reloads .rb files that were 'require'd,
|
244
|
+
not 'load'ed. That leaves out (non-Erector) template files, and also,
|
245
|
+
at least the way I was doing it, sub-actions (see
|
246
|
+
[this thread](http://groups.google.com/group/sinatrarb/browse_thread/thread/7329727a9296e96a#
|
247
|
+
)).
|
248
|
+
|
249
|
+
Rack::Reloader also doesn't reload configuration changes or redo other
|
250
|
+
things that happen during app startup. Rerun takes the attitude that if
|
251
|
+
you want to restart an app, you should just restart the whole app. You know?
|
252
|
+
|
253
|
+
# Why would I use this instead of Guard?
|
254
|
+
|
255
|
+
Guard is very powerful but requires some up-front configuration.
|
256
|
+
Rerun is meant as a no-frills command-line alternative requiring no knowledge
|
257
|
+
of Ruby nor config file syntax.
|
258
|
+
|
259
|
+
# Why did you write this?
|
260
|
+
|
261
|
+
I've been using [Sinatra](http://sinatrarb.com) and loving it. In order
|
262
|
+
to simplify their system, the Rat Pack removed auto-reloading from
|
263
|
+
Sinatra proper. I approve of this: a web application framework should be
|
264
|
+
focused on serving requests, not on munging Ruby ObjectSpace for
|
265
|
+
dev-time convenience. But I still wanted automatic reloading during
|
266
|
+
development. Shotgun wasn't working for me (see above) so I spliced
|
267
|
+
Rerun together out of code from Rspactor, FileSystemWatcher, and Shotgun
|
268
|
+
-- with a heavy amount of refactoring and rewriting. In late 2012 I
|
269
|
+
migrated the backend to the Listen gem, which was extracted from Guard,
|
270
|
+
so it should be more reliable and performant on multiple platforms.
|
271
|
+
|
272
|
+
# Credits
|
273
|
+
|
274
|
+
Rerun: [Alex Chaffee](http://alexchaffee.com), <mailto:alex@stinky.com>, <http://github.com/alexch/>
|
275
|
+
|
276
|
+
Based upon and/or inspired by:
|
277
|
+
|
278
|
+
* Shotgun: <http://github.com/rtomayko/shotgun>
|
279
|
+
* Rspactor: <http://github.com/mislav/rspactor>
|
280
|
+
* (In turn based on http://rails.aizatto.com/2007/11/28/taming-the-autotest-beast-with-fsevents/ )
|
281
|
+
* FileSystemWatcher: <http://paulhorman.com/filesystemwatcher/>
|
282
|
+
|
283
|
+
## Patches by:
|
284
|
+
|
285
|
+
* David Billskog <billskog@gmail.com>
|
286
|
+
* Jens B <https://github.com/dpree>
|
287
|
+
* Andrés Botero <https://github.com/anbotero>
|
288
|
+
* Dreamcat4
|
289
|
+
* <https://github.com/FND>
|
290
|
+
* Barry Sia <https://github.com/bsia>
|
291
|
+
* Paul Rangel <https://github.com/ismell>
|
292
|
+
|
293
|
+
# Version History
|
294
|
+
|
295
|
+
* ?
|
296
|
+
* better 'changed' message
|
297
|
+
|
298
|
+
* v0.10.0 4 May 2014
|
299
|
+
* add '.coffee,.slim,.md' to default pattern (thanks @xylinq)
|
300
|
+
* --ignore option
|
301
|
+
|
302
|
+
* v0.9.0 6 March 2014
|
303
|
+
* --dir (or -d) can be specified more than once, for multiple directories (thanks again Barry!)
|
304
|
+
* --name option
|
305
|
+
* press 'p' to pause/unpause filesystem watching (Barry is the man!)
|
306
|
+
* works with Listen 2 (note: needs 2.3 or higher)
|
307
|
+
* cooldown works, thanks to patches to underlying Listen gem
|
308
|
+
* ignore all dotfiles, and add actual list of ignored dirs and files
|
309
|
+
|
310
|
+
* v0.8.2
|
311
|
+
* bugfix, forcing Rerun to use Listen v1.0.3 while we work out the troubles we're having with Listen 1.3 and 2.1
|
312
|
+
|
313
|
+
* v0.8.1
|
314
|
+
* bugfix release (#30 and #34)
|
315
|
+
|
316
|
+
* v0.8.0
|
317
|
+
* --background option (thanks FND!) to disable the keyboard listener
|
318
|
+
* --signal option (thanks FND!)
|
319
|
+
* --no-growl option
|
320
|
+
* --dir supports multiple directories (thanks Barry!)
|
321
|
+
|
322
|
+
* v0.7.1
|
323
|
+
* bugfix: make rails icon work again
|
324
|
+
|
325
|
+
* v0.7.0
|
326
|
+
* uses Listen gem (which uses rb-fsevent for lightweight filesystem snooping)
|
327
|
+
|
328
|
+
# License
|
329
|
+
|
330
|
+
Open Source MIT License. See "LICENSE" file.
|
data/Rakefile
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/clean'
|
3
|
+
require 'rake/testtask'
|
4
|
+
require 'rspec/core/rake_task'
|
5
|
+
|
6
|
+
task :default => [:spec]
|
7
|
+
task :test => :spec
|
8
|
+
|
9
|
+
desc "Run all specs"
|
10
|
+
RSpec::Core::RakeTask.new('spec') do |t|
|
11
|
+
ENV['ENV'] = "test"
|
12
|
+
t.pattern = 'spec/**/*_spec.rb'
|
13
|
+
t.rspec_opts = ['--color']
|
14
|
+
end
|
15
|
+
|
16
|
+
$rubyforge_project = 'pivotalrb'
|
17
|
+
|
18
|
+
$spec =
|
19
|
+
begin
|
20
|
+
require 'rubygems/specification'
|
21
|
+
data = File.read('rerun.gemspec')
|
22
|
+
spec = nil
|
23
|
+
#Thread.new { spec = eval("$SAFE = 3\n#{data}") }.join
|
24
|
+
spec = eval data
|
25
|
+
spec
|
26
|
+
end
|
27
|
+
|
28
|
+
def package(ext='')
|
29
|
+
"pkg/#{$spec.name}-#{$spec.version}" + ext
|
30
|
+
end
|
31
|
+
|
32
|
+
desc 'Exit if git is dirty'
|
33
|
+
task :check_git do
|
34
|
+
state = `git status 2> /dev/null | tail -n1`
|
35
|
+
clean = (state =~ /working directory clean/)
|
36
|
+
unless clean
|
37
|
+
warn "can't do that on an unclean git dir"
|
38
|
+
exit 1
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
desc 'Build packages'
|
43
|
+
task :package => %w[.gem .tar.gz].map { |e| package(e) }
|
44
|
+
|
45
|
+
desc 'Build and install as local gem'
|
46
|
+
task :install => package('.gem') do
|
47
|
+
sh "gem install #{package('.gem')}"
|
48
|
+
end
|
49
|
+
|
50
|
+
directory 'pkg/'
|
51
|
+
CLOBBER.include('pkg')
|
52
|
+
|
53
|
+
file package('.gem') => %W[pkg/ #{$spec.name}.gemspec] + $spec.files do |f|
|
54
|
+
sh "gem build #{$spec.name}.gemspec"
|
55
|
+
mv File.basename(f.name), f.name
|
56
|
+
end
|
57
|
+
|
58
|
+
file package('.tar.gz') => %w[pkg/] + $spec.files do |f|
|
59
|
+
cmd = <<-SH
|
60
|
+
git archive \
|
61
|
+
--prefix=#{$spec.name}-#{$spec.version}/ \
|
62
|
+
--format=tar \
|
63
|
+
HEAD | gzip > #{f.name}
|
64
|
+
SH
|
65
|
+
sh cmd.gsub(/ +/, ' ')
|
66
|
+
end
|
67
|
+
|
68
|
+
desc 'Publish gem and tarball to rubyforge'
|
69
|
+
task 'release' => [:check_git, package('.gem'), package('.tar.gz')] do |t|
|
70
|
+
puts "Releasing #{$spec.version}"
|
71
|
+
sh "gem push #{package('.gem')}"
|
72
|
+
puts "Tagging and pushing"
|
73
|
+
sh "git tag v#{$spec.version}"
|
74
|
+
sh "git push && git push --tags"
|
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
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
libdir = "#{File.expand_path(File.dirname(File.dirname(__FILE__)))}/lib"
|
5
|
+
$LOAD_PATH.unshift libdir unless $LOAD_PATH.include?(libdir)
|
6
|
+
|
7
|
+
require 'rerun'
|
8
|
+
require 'optparse'
|
9
|
+
|
10
|
+
options = Rerun::Options.parse
|
11
|
+
exit if options.nil?
|
12
|
+
runner = Rerun::Runner.keep_running(options[:cmd], options)
|
Binary file
|
Binary file
|
data/lib/rerun/glob.rb
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
# based on http://cpan.uwinnipeg.ca/htdocs/Text-Glob/Text/Glob.pm.html#glob_to_regex_string-
|
2
|
+
|
3
|
+
# todo: release as separate gem
|
4
|
+
#
|
5
|
+
module Rerun
|
6
|
+
class Glob
|
7
|
+
NO_LEADING_DOT = '(?=[^\.])' # todo
|
8
|
+
START_OF_FILENAME = '(\A|\/)' # beginning of string or a slash
|
9
|
+
END_OF_STRING = '\z'
|
10
|
+
|
11
|
+
def initialize glob_string
|
12
|
+
@glob_string = glob_string
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_regexp_string
|
16
|
+
chars = @glob_string.split('')
|
17
|
+
|
18
|
+
chars = smoosh(chars)
|
19
|
+
|
20
|
+
curlies = 0
|
21
|
+
escaping = false
|
22
|
+
string = chars.map do |char|
|
23
|
+
if escaping
|
24
|
+
escaping = false
|
25
|
+
char
|
26
|
+
else
|
27
|
+
case char
|
28
|
+
when '**'
|
29
|
+
"([^/]+/)*"
|
30
|
+
when '*'
|
31
|
+
".*"
|
32
|
+
when "?"
|
33
|
+
"."
|
34
|
+
when "."
|
35
|
+
"\\."
|
36
|
+
|
37
|
+
when "{"
|
38
|
+
curlies += 1
|
39
|
+
"("
|
40
|
+
when "}"
|
41
|
+
if curlies > 0
|
42
|
+
curlies -= 1
|
43
|
+
")"
|
44
|
+
else
|
45
|
+
char
|
46
|
+
end
|
47
|
+
when ","
|
48
|
+
if curlies > 0
|
49
|
+
"|"
|
50
|
+
else
|
51
|
+
char
|
52
|
+
end
|
53
|
+
when "\\"
|
54
|
+
escaping = true
|
55
|
+
"\\"
|
56
|
+
|
57
|
+
else
|
58
|
+
char
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end.join
|
63
|
+
START_OF_FILENAME + string + END_OF_STRING
|
64
|
+
end
|
65
|
+
|
66
|
+
def to_regexp
|
67
|
+
Regexp.new(to_regexp_string)
|
68
|
+
end
|
69
|
+
|
70
|
+
def smoosh chars
|
71
|
+
out = []
|
72
|
+
until chars.empty?
|
73
|
+
char = chars.shift
|
74
|
+
if char == "*" and chars.first == "*"
|
75
|
+
chars.shift
|
76
|
+
chars.shift if chars.first == "/"
|
77
|
+
out.push("**")
|
78
|
+
else
|
79
|
+
out.push(char)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
out
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,99 @@
|
|
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}"
|
12
|
+
DEFAULT_DIRS = ["."]
|
13
|
+
|
14
|
+
DEFAULTS = {
|
15
|
+
:pattern => DEFAULT_PATTERN,
|
16
|
+
:signal => "TERM",
|
17
|
+
:growl => true,
|
18
|
+
:name => Pathname.getwd.basename.to_s.capitalize,
|
19
|
+
:ignore => []
|
20
|
+
}
|
21
|
+
|
22
|
+
def self.parse args = ARGV
|
23
|
+
options = DEFAULTS.dup
|
24
|
+
opts = OptionParser.new("", 24, ' ') do |opts|
|
25
|
+
opts.banner = "Usage: rerun [options] [--] cmd"
|
26
|
+
|
27
|
+
opts.separator ""
|
28
|
+
opts.separator "Launches an app, and restarts it when the filesystem changes."
|
29
|
+
opts.separator "See http://github.com/alexch/rerun for more info."
|
30
|
+
opts.separator "Version: #{$spec.version}"
|
31
|
+
opts.separator ""
|
32
|
+
opts.separator "Options:"
|
33
|
+
|
34
|
+
opts.on("-d dir", "--dir dir", "directory to watch, default = \"#{DEFAULT_DIRS}\". Specify multiple paths with ',' or separate '-d dir' option pairs.") do |dir|
|
35
|
+
elements = dir.split(",")
|
36
|
+
options[:dir] = (options[:dir] || []) + elements
|
37
|
+
end
|
38
|
+
|
39
|
+
# todo: rename to "--watch"
|
40
|
+
opts.on("-p pattern", "--pattern pattern", "file glob to watch, default = \"#{DEFAULTS[:pattern]}\"") do |pattern|
|
41
|
+
options[:pattern] = pattern
|
42
|
+
end
|
43
|
+
|
44
|
+
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|
|
45
|
+
options[:ignore] += [pattern]
|
46
|
+
end
|
47
|
+
|
48
|
+
opts.on("-s signal", "--signal signal", "terminate process using this signal, default = \"#{DEFAULTS[:signal]}\"") do |signal|
|
49
|
+
options[:signal] = signal
|
50
|
+
end
|
51
|
+
|
52
|
+
opts.on("-c", "--clear", "clear screen before each run") do
|
53
|
+
options[:clear] = true
|
54
|
+
end
|
55
|
+
|
56
|
+
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|
|
57
|
+
options[:exit] = true
|
58
|
+
end
|
59
|
+
|
60
|
+
opts.on("-b", "--background", "disable on-the-fly commands, allowing the process to be backgrounded") do
|
61
|
+
options[:background] = true
|
62
|
+
end
|
63
|
+
|
64
|
+
opts.on("-n name", "--name name", "name of app used in logs and notifications, default = \"#{DEFAULTS[:name]}\"") do |name|
|
65
|
+
options[:name] = name
|
66
|
+
end
|
67
|
+
|
68
|
+
opts.on("--no-growl", "don't use growl") do
|
69
|
+
options[:growl] = false
|
70
|
+
end
|
71
|
+
|
72
|
+
opts.on_tail("-h", "--help", "--usage", "show this message") do
|
73
|
+
puts opts
|
74
|
+
return
|
75
|
+
end
|
76
|
+
|
77
|
+
opts.on_tail("--version", "show version") do
|
78
|
+
puts $spec.version
|
79
|
+
return
|
80
|
+
end
|
81
|
+
|
82
|
+
opts.on_tail ""
|
83
|
+
opts.on_tail "On top of --pattern and --ignore, we ignore any changes to files and dirs starting with a dot."
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
if args.empty?
|
88
|
+
puts opts
|
89
|
+
nil
|
90
|
+
else
|
91
|
+
opts.parse! args
|
92
|
+
options[:cmd] = args.join(" ")
|
93
|
+
options[:dir] ||= DEFAULT_DIRS
|
94
|
+
options
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
end
|
data/lib/rerun/runner.rb
ADDED
@@ -0,0 +1,324 @@
|
|
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
|
+
|
17
|
+
def initialize(run_command, options = {})
|
18
|
+
@run_command, @options = run_command, options
|
19
|
+
@run_command = "ruby #{@run_command}" if @run_command.split(' ').first =~ /\.rb$/
|
20
|
+
end
|
21
|
+
|
22
|
+
def start_keypress_thread
|
23
|
+
return if @options[:background]
|
24
|
+
|
25
|
+
@keypress_thread = Thread.new do
|
26
|
+
while true
|
27
|
+
if c = key_pressed
|
28
|
+
case c.downcase
|
29
|
+
when 'c'
|
30
|
+
say "Clearing screen"
|
31
|
+
clear_screen
|
32
|
+
when 'r'
|
33
|
+
say "Restarting"
|
34
|
+
restart
|
35
|
+
when 'p'
|
36
|
+
toggle_pause if watcher_running?
|
37
|
+
when 'x', 'q'
|
38
|
+
die
|
39
|
+
break # the break will stop this thread, in case the 'die' doesn't
|
40
|
+
else
|
41
|
+
puts "\n#{c.inspect} pressed inside rerun"
|
42
|
+
puts [["c", "clear screen"],
|
43
|
+
["r", "restart"],
|
44
|
+
["p", "toggle pause"],
|
45
|
+
["x or q", "stop and exit"]
|
46
|
+
].map{|key, description| " #{key} -- #{description}"}.join("\n")
|
47
|
+
puts
|
48
|
+
end
|
49
|
+
end
|
50
|
+
sleep 1 # todo: use select instead of polling somehow?
|
51
|
+
end
|
52
|
+
end
|
53
|
+
@keypress_thread.run
|
54
|
+
end
|
55
|
+
|
56
|
+
def stop_keypress_thread
|
57
|
+
@keypress_thread.kill if @keypress_thread
|
58
|
+
@keypress_thread = nil
|
59
|
+
end
|
60
|
+
|
61
|
+
def restart
|
62
|
+
@restarting = true
|
63
|
+
stop
|
64
|
+
start
|
65
|
+
@restarting = false
|
66
|
+
end
|
67
|
+
|
68
|
+
def watcher_running?
|
69
|
+
@watcher && @watcher.running?
|
70
|
+
end
|
71
|
+
|
72
|
+
def toggle_pause
|
73
|
+
unless @pausing
|
74
|
+
say "Pausing. Press 'p' again to resume."
|
75
|
+
@watcher.pause
|
76
|
+
@pausing = true
|
77
|
+
else
|
78
|
+
say "Resuming"
|
79
|
+
@watcher.unpause
|
80
|
+
@pausing = false
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def unpause
|
85
|
+
@watcher.unpause
|
86
|
+
end
|
87
|
+
|
88
|
+
def dir
|
89
|
+
@options[:dir]
|
90
|
+
end
|
91
|
+
|
92
|
+
def dirs
|
93
|
+
@options[:dir] || "."
|
94
|
+
end
|
95
|
+
|
96
|
+
def pattern
|
97
|
+
@options[:pattern]
|
98
|
+
end
|
99
|
+
|
100
|
+
def ignore
|
101
|
+
@options[:ignore] || []
|
102
|
+
end
|
103
|
+
|
104
|
+
def clear?
|
105
|
+
@options[:clear]
|
106
|
+
end
|
107
|
+
|
108
|
+
def exit?
|
109
|
+
@options[:exit]
|
110
|
+
end
|
111
|
+
|
112
|
+
def app_name
|
113
|
+
@options[:name]
|
114
|
+
end
|
115
|
+
|
116
|
+
def start
|
117
|
+
if windows?
|
118
|
+
raise "Sorry, Rerun does not work on Windows."
|
119
|
+
end
|
120
|
+
|
121
|
+
if (!@already_running)
|
122
|
+
taglines = [
|
123
|
+
"To infinity... and beyond!",
|
124
|
+
"Charge!",
|
125
|
+
]
|
126
|
+
notify "launched", taglines[rand(taglines.size)]
|
127
|
+
@already_running = true
|
128
|
+
else
|
129
|
+
taglines = [
|
130
|
+
"Here we go again!",
|
131
|
+
"Keep on trucking.",
|
132
|
+
"Once more unto the breach, dear friends, once more!",
|
133
|
+
"The road goes ever on and on, down from the door where it began.",
|
134
|
+
]
|
135
|
+
notify "restarted", taglines[rand(taglines.size)]
|
136
|
+
end
|
137
|
+
|
138
|
+
clear_screen if clear?
|
139
|
+
start_keypress_thread unless @keypress_thread
|
140
|
+
|
141
|
+
@pid = Kernel.fork do
|
142
|
+
begin
|
143
|
+
exec(@run_command)
|
144
|
+
rescue => e
|
145
|
+
puts "#{e.class}: #{e.message}"
|
146
|
+
exit
|
147
|
+
end
|
148
|
+
end
|
149
|
+
status_thread = Process.detach(@pid) # so if the child exits, it dies
|
150
|
+
|
151
|
+
Signal.trap("INT") do # INT = control-C -- allows user to stop the top-level rerun process
|
152
|
+
die
|
153
|
+
end
|
154
|
+
|
155
|
+
Signal.trap("TERM") do # TERM is the polite way of terminating a process
|
156
|
+
die
|
157
|
+
end
|
158
|
+
|
159
|
+
begin
|
160
|
+
sleep 2
|
161
|
+
rescue Interrupt => e
|
162
|
+
# in case someone hits control-C immediately ("oops!")
|
163
|
+
die
|
164
|
+
end
|
165
|
+
|
166
|
+
if exit?
|
167
|
+
status = status_thread.value
|
168
|
+
if status.success?
|
169
|
+
notify "succeeded", ""
|
170
|
+
else
|
171
|
+
notify "failed", "Exit status #{status.exitstatus}"
|
172
|
+
end
|
173
|
+
else
|
174
|
+
if !running?
|
175
|
+
notify "Launch Failed", "See console for error output"
|
176
|
+
@already_running = false
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
unless @watcher
|
181
|
+
|
182
|
+
watcher = Watcher.new(:directory => dirs, :pattern => pattern, :ignore => ignore) do |changes|
|
183
|
+
|
184
|
+
message = change_message(changes)
|
185
|
+
|
186
|
+
say "Change detected: #{message}"
|
187
|
+
restart unless @restarting
|
188
|
+
end
|
189
|
+
watcher.start
|
190
|
+
@watcher = watcher
|
191
|
+
say "Watching #{dir.join(', ')} for #{pattern}" +
|
192
|
+
(ignore.empty? ? "" : " (ignoring #{ignore.join(',')})") +
|
193
|
+
" using #{watcher.adapter.class.name.split('::').last} adapter"
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
def change_message(changes)
|
198
|
+
message = [:modified, :added, :removed].map do |change|
|
199
|
+
count = changes[change] ? changes[change].size : 0
|
200
|
+
if count > 0
|
201
|
+
"#{count} #{change}"
|
202
|
+
end
|
203
|
+
end.compact.join(", ")
|
204
|
+
|
205
|
+
changed_files = changes.values.flatten
|
206
|
+
if changed_files.count > 0
|
207
|
+
message += ": "
|
208
|
+
message += changes.values.flatten[0..3].map { |path| path.split('/').last }.join(', ')
|
209
|
+
if changed_files.count > 3
|
210
|
+
message += ", ..."
|
211
|
+
end
|
212
|
+
end
|
213
|
+
message
|
214
|
+
end
|
215
|
+
|
216
|
+
def die
|
217
|
+
#stop_keypress_thread # don't do this since we're probably *in* the keypress thread
|
218
|
+
stop # stop the child process if it exists
|
219
|
+
exit 0 # todo: status code param
|
220
|
+
end
|
221
|
+
|
222
|
+
def join
|
223
|
+
@watcher.join
|
224
|
+
end
|
225
|
+
|
226
|
+
def running?
|
227
|
+
signal(0)
|
228
|
+
end
|
229
|
+
|
230
|
+
def signal(signal)
|
231
|
+
say "Sending signal #{signal} to #{@pid}" unless signal == 0
|
232
|
+
Process.kill(signal, @pid)
|
233
|
+
true
|
234
|
+
rescue
|
235
|
+
false
|
236
|
+
end
|
237
|
+
|
238
|
+
# todo: test escalation
|
239
|
+
def stop
|
240
|
+
default_signal = @options[:signal] || "TERM"
|
241
|
+
if @pid && (@pid != 0)
|
242
|
+
notify "stopping", "All good things must come to an end." unless @restarting
|
243
|
+
begin
|
244
|
+
timeout(5) do # todo: escalation timeout setting
|
245
|
+
# start with a polite SIGTERM
|
246
|
+
signal(default_signal) && Process.wait(@pid)
|
247
|
+
end
|
248
|
+
rescue Timeout::Error
|
249
|
+
begin
|
250
|
+
timeout(5) do
|
251
|
+
# escalate to SIGINT aka control-C since some foolish process may be ignoring SIGTERM
|
252
|
+
signal("INT") && Process.wait(@pid)
|
253
|
+
end
|
254
|
+
rescue Timeout::Error
|
255
|
+
# escalate to SIGKILL aka "kill -9" which cannot be ignored
|
256
|
+
signal("KILL") && Process.wait(@pid)
|
257
|
+
end
|
258
|
+
end
|
259
|
+
end
|
260
|
+
rescue => e
|
261
|
+
false
|
262
|
+
end
|
263
|
+
|
264
|
+
def git_head_changed?
|
265
|
+
old_git_head = @git_head
|
266
|
+
read_git_head
|
267
|
+
@git_head and old_git_head and @git_head != old_git_head
|
268
|
+
end
|
269
|
+
|
270
|
+
def read_git_head
|
271
|
+
git_head_file = File.join(dir, '.git', 'HEAD')
|
272
|
+
@git_head = File.exists?(git_head_file) && File.read(git_head_file)
|
273
|
+
end
|
274
|
+
|
275
|
+
def notify(title, body)
|
276
|
+
growl title, body if @options[:growl]
|
277
|
+
puts
|
278
|
+
say "#{app_name} #{title}"
|
279
|
+
end
|
280
|
+
|
281
|
+
def say msg
|
282
|
+
puts "#{Time.now.strftime("%T")} [rerun] #{msg}"
|
283
|
+
end
|
284
|
+
|
285
|
+
# non-blocking stdin reader.
|
286
|
+
# returns a 1-char string if a key was pressed; otherwise nil
|
287
|
+
#
|
288
|
+
def key_pressed
|
289
|
+
begin
|
290
|
+
# this "raw input" nonsense is because unix likes waiting for linefeeds before sending stdin
|
291
|
+
|
292
|
+
# 'raw' means turn raw input on
|
293
|
+
|
294
|
+
# restore proper output newline handling -- see stty.rb and "man stty" and /usr/include/sys/termios.h
|
295
|
+
# looks like "raw" flips off the OPOST bit 0x00000001 /* enable following output processing */
|
296
|
+
# which disables #define ONLCR 0x00000002 /* map NL to CR-NL (ala CRMOD) */
|
297
|
+
# so this sets it back on again since all we care about is raw input, not raw output
|
298
|
+
system("stty raw opost")
|
299
|
+
|
300
|
+
c = nil
|
301
|
+
if $stdin.ready?
|
302
|
+
c = $stdin.getc
|
303
|
+
end
|
304
|
+
c.chr if c
|
305
|
+
ensure
|
306
|
+
system "stty -raw" # turn raw input off
|
307
|
+
end
|
308
|
+
|
309
|
+
# note: according to 'man tty' the proper way restore the settings is
|
310
|
+
# tty_state=`stty -g`
|
311
|
+
# ensure
|
312
|
+
# system 'stty "#{tty_state}'
|
313
|
+
# end
|
314
|
+
# but this way seems fine and less confusing
|
315
|
+
|
316
|
+
end
|
317
|
+
|
318
|
+
def clear_screen
|
319
|
+
# see http://ascii-table.com/ansi-escape-sequences-vt-100.php
|
320
|
+
$stdout.print "\033[H\033[2J"
|
321
|
+
end
|
322
|
+
|
323
|
+
end
|
324
|
+
end
|
data/lib/rerun/system.rb
ADDED
@@ -0,0 +1,44 @@
|
|
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
|
+
# do we have growl or not?
|
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")
|
30
|
+
rails_sig_file = File.expand_path(".")+"/config/boot.rb"
|
31
|
+
"#{icondir}/rails_red_sml.png" if File.exists? rails_sig_file
|
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
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,127 @@
|
|
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
|
+
@ignore = [options[:ignore]].flatten.compact
|
43
|
+
@thread = nil
|
44
|
+
end
|
45
|
+
|
46
|
+
def sanitize_dirs(dirs)
|
47
|
+
dirs = [*dirs]
|
48
|
+
dirs.map do |d|
|
49
|
+
d.chomp!("/")
|
50
|
+
unless FileTest.exists?(d) && FileTest.readable?(d) && FileTest.directory?(d)
|
51
|
+
raise InvalidDirectoryError, "Directory '#{d}' either doesnt exist or isn't readable"
|
52
|
+
end
|
53
|
+
File.expand_path(d)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def start
|
58
|
+
if @thread then
|
59
|
+
raise RuntimeError, "already started"
|
60
|
+
end
|
61
|
+
|
62
|
+
@thread = Thread.new do
|
63
|
+
@listener = Listen.to(*@directories, only: watching, ignore: ignoring, wait_for_delay: 1) do |modified, added, removed|
|
64
|
+
if((modified.size + added.size + removed.size) > 0)
|
65
|
+
@client_callback.call(:modified => modified, :added => added, :removed => removed)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
@listener.start
|
69
|
+
end
|
70
|
+
|
71
|
+
@thread.priority = @priority
|
72
|
+
|
73
|
+
sleep 0.1 until @listener
|
74
|
+
|
75
|
+
at_exit { stop } # try really hard to clean up after ourselves
|
76
|
+
end
|
77
|
+
|
78
|
+
def watching
|
79
|
+
Rerun::Glob.new(@pattern).to_regexp
|
80
|
+
end
|
81
|
+
|
82
|
+
def ignoring
|
83
|
+
# todo: --no-ignore-dotfiles
|
84
|
+
# dotfiles = /^\.[^.]/ # at beginning of string, a real dot followed by any other character
|
85
|
+
# [dotfiles] + @ignore.map { |x| Rerun::Glob.new(x).to_regexp }
|
86
|
+
@ignore.map { |x| Rerun::Glob.new(x).to_regexp }
|
87
|
+
end
|
88
|
+
|
89
|
+
def adapter
|
90
|
+
@listener.registry[:adapter] || (timeout(4) do
|
91
|
+
sleep 1 until adapter = @listener.registry[:adapter]
|
92
|
+
adapter
|
93
|
+
end)
|
94
|
+
end
|
95
|
+
|
96
|
+
# kill the file watcher thread
|
97
|
+
def stop
|
98
|
+
@thread.wakeup rescue ThreadError
|
99
|
+
begin
|
100
|
+
@listener.stop
|
101
|
+
rescue Exception => e
|
102
|
+
puts "#{e.class}: #{e.message} stopping listener"
|
103
|
+
end
|
104
|
+
@thread.kill rescue ThreadError
|
105
|
+
end
|
106
|
+
|
107
|
+
# wait for the file watcher to finish
|
108
|
+
def join
|
109
|
+
@thread.join if @thread
|
110
|
+
rescue Interrupt => e
|
111
|
+
# don't care
|
112
|
+
end
|
113
|
+
|
114
|
+
def pause
|
115
|
+
@listener.pause if @listener
|
116
|
+
end
|
117
|
+
|
118
|
+
def unpause
|
119
|
+
@listener.unpause if @listener
|
120
|
+
end
|
121
|
+
|
122
|
+
def running?
|
123
|
+
@listener && @listener.instance_variable_get(:@adapter)
|
124
|
+
end
|
125
|
+
|
126
|
+
end
|
127
|
+
end
|
data/lib/rerun.rb
ADDED
@@ -0,0 +1,14 @@
|
|
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/runner"
|
8
|
+
require "rerun/watcher"
|
9
|
+
require "rerun/glob"
|
10
|
+
|
11
|
+
module Rerun
|
12
|
+
|
13
|
+
end
|
14
|
+
|
data/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 = 'rerun-cj'
|
6
|
+
s.version = '0.10.0'
|
7
|
+
|
8
|
+
s.description = "Restarts your app when a file changes. A no-frills, command-line alternative to Guard, Shotgun, Autotest, etc."
|
9
|
+
s.summary = "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
|
+
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', '~> 2.7', '>= 2.7.3'
|
29
|
+
|
30
|
+
s.homepage = "http://github.com/alexch/rerun/"
|
31
|
+
s.require_paths = %w[lib]
|
32
|
+
|
33
|
+
s.license = 'MIT'
|
34
|
+
end
|
metadata
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rerun-cj
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.10.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Alex Chaffee
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-09-23 00:00:00.000000000 Z
|
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: '2.7'
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 2.7.3
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - "~>"
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '2.7'
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 2.7.3
|
33
|
+
description: Restarts your app when a file changes. A no-frills, command-line alternative
|
34
|
+
to Guard, Shotgun, Autotest, etc.
|
35
|
+
email: alex@stinky.com
|
36
|
+
executables:
|
37
|
+
- rerun
|
38
|
+
extensions: []
|
39
|
+
extra_rdoc_files:
|
40
|
+
- README.md
|
41
|
+
files:
|
42
|
+
- LICENSE
|
43
|
+
- README.md
|
44
|
+
- Rakefile
|
45
|
+
- bin/rerun
|
46
|
+
- icons/rails_grn_sml.png
|
47
|
+
- icons/rails_red_sml.png
|
48
|
+
- lib/rerun.rb
|
49
|
+
- lib/rerun/glob.rb
|
50
|
+
- lib/rerun/options.rb
|
51
|
+
- lib/rerun/runner.rb
|
52
|
+
- lib/rerun/system.rb
|
53
|
+
- lib/rerun/watcher.rb
|
54
|
+
- rerun.gemspec
|
55
|
+
homepage: http://github.com/alexch/rerun/
|
56
|
+
licenses:
|
57
|
+
- MIT
|
58
|
+
metadata: {}
|
59
|
+
post_install_message:
|
60
|
+
rdoc_options: []
|
61
|
+
require_paths:
|
62
|
+
- lib
|
63
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '0'
|
68
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
69
|
+
requirements:
|
70
|
+
- - ">="
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: '0'
|
73
|
+
requirements: []
|
74
|
+
rubyforge_project:
|
75
|
+
rubygems_version: 2.2.2
|
76
|
+
signing_key:
|
77
|
+
specification_version: 2
|
78
|
+
summary: Launches an app, and restarts it whenever the filesystem changes. A no-frills,
|
79
|
+
command-line alternative to Guard, Shotgun, Autotest, etc.
|
80
|
+
test_files: []
|