rerun-cj 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -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
@@ -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: []