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 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: []