rerun 0.6.2 → 0.6.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (6) hide show
  1. data/README.md +60 -24
  2. data/Rakefile +15 -1
  3. data/bin/rerun +1 -3
  4. data/lib/rerun/runner.rb +109 -9
  5. data/rerun.gemspec +1 -1
  6. metadata +23 -40
data/README.md CHANGED
@@ -2,29 +2,37 @@
2
2
 
3
3
  <http://github.com/alexch/rerun>
4
4
 
5
- Launches your app, then watches the filesystem. If a relevant file
6
- changes, then it restarts your app.
5
+ Rerun launches your program, then watches the filesystem. If a relevant file
6
+ changes, then it restarts your program.
7
7
 
8
- By default only *.{rb,js,css,scss,sass,erb,html,haml,ru} files are watched. Use the
9
- `--pattern` option if you want to change this.
8
+ Rerun works for both long-running processes (e.g. apps) and short-running ones
9
+ (e.g. tests). So it works like shotgun and autotest (and guard and all the
10
+ rest).
10
11
 
11
- If you're on Mac OS X, and using the built-in ruby,
12
- it uses the built-in facilities for monitoring
13
- the filesystem, so CPU use is very light. And if you have `growlnotify`
14
- available on the `PATH`, it sends notifications to growl in addition to
15
- the console. Here's how to install
16
- [growlnotify](http://growl.info/extras.php#growlnotify):
12
+ Rerun's advantage is its simple design. Since it uses standard Unix "SIGINT"
13
+ and "SIGKILL" signals, you're sure the restarted app is really acting just
14
+ like it was when you ran it from the command line the first time.
17
15
 
18
- > The Installer package for growlnotify is in the growlnotify folder in the Extras folder on the Growl disk image. Simply open the Installer package and follow the on-screen instructions.
16
+ By default only *.{rb,js,css,scss,sass,erb,html,haml,ru} files are watched.
17
+ Use the `--pattern` option if you want to change this.
18
+
19
+ If you're on Mac OS X, and using the built-in ruby, it uses the built-in
20
+ facilities for monitoring the filesystem, so CPU use is very light.
19
21
 
20
22
  Rerun does not work on Windows. Sorry, but you can't do much relaunching
21
23
  without "fork".
22
24
 
23
25
  # Installation:
24
26
 
25
- sudo gem install rerun
27
+ gem install rerun
28
+
29
+ "sudo" may be required on older systems.
26
30
 
27
- # Usage:
31
+ If you are using RVM you might want to put this in your global gemset so it's available to all your apps. (There really should be a better way to distinguish gems-as-libraries from gems-as-tools.)
32
+
33
+ rvm @global do gem install rerun
34
+
35
+ # Usage:
28
36
 
29
37
  rerun [options] [--] cmd
30
38
 
@@ -32,23 +40,23 @@ For example, if you're running a Sinatra app whose main file is
32
40
  app.rb:
33
41
 
34
42
  rerun ruby app.rb
35
-
43
+
36
44
  If the first part of the command is a `.rb` filename, then `ruby` is
37
45
  optional, so the above can also be accomplished like this:
38
46
 
39
47
  rerun app.rb
40
-
48
+
41
49
  Or if you're using Thin to run a Rack app that's configured in config.ru
42
50
  but you want it on port 4000 and in debug mode, and only want to watch
43
51
  the `app` subdirectory:
44
52
 
45
53
  rerun --dir app -- thin start --debug --port=4000 -R config.ru
46
-
47
- The `--` is to separate rerun options from cmd options. You can also
54
+
55
+ The `--` is to separate rerun options from cmd options. You can also
48
56
  use a quoted string for the command, e.g.
49
57
 
50
58
  rerun --dir app "thin start --debug --port=4000 -R config.ru"
51
-
59
+
52
60
  Rackup can also be used to launch a Rack server, so let's try that:
53
61
 
54
62
  rerun -- rackup --port 4000 config.ru
@@ -65,18 +73,42 @@ How about regenerating your HTML files after every change to your [Erector](http
65
73
 
66
74
  rerun -x erector --to-html my_site.rb
67
75
 
76
+ Use Heroku Cedar? `rerun` is now compatible with `foreman`. Run all your
77
+ Procfile processes locally and restart them all when necessary.
78
+
79
+ rerun foreman start
80
+
68
81
  # Options:
69
82
 
70
- --dir directory to watch (default = ".")
83
+ `--dir` directory to watch (default = ".")
71
84
 
72
- --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.
85
+ `--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.
73
86
  By default it watches these files: `rb,js,css,scss,sass,erb,html,haml,ru`.
74
87
 
75
- --clear (or -c) clear the screen before each run
88
+ `--clear` (or -c) clear the screen before each run
76
89
 
77
- --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.
90
+ `--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.
78
91
 
79
- Also --version and --help.
92
+ Also --version and --help, naturally.
93
+
94
+ # Growl Notifications
95
+
96
+ If you have `growlnotify` available on the `PATH`, it sends notifications to
97
+ growl in addition to the console.
98
+
99
+ Here's how to install [growlnotify](http://growl.info/extras.php#growlnotify):
100
+
101
+ > The Installer package for growlnotify is in the growlnotify folder in the Extras folder on the Growl disk image. Simply open the Installer package and follow the on-screen instructions.
102
+
103
+ **NOTE**: Growl recently moved to the App Store. I upgraded, and it still works for me, but I'd love it if someone can confirm that `growlnotify` is still available in a clean App Store install and works as advertised.
104
+
105
+ # On-The-Fly Commands
106
+
107
+ While the app is (re)running, you can make things happen by pressing keys:
108
+
109
+ * **r** restart (as if a file had changed)
110
+ * **c** clear the screen
111
+ * **x** exit (just like control-C)
80
112
 
81
113
  # To Do:
82
114
 
@@ -85,7 +117,7 @@ Also --version and --help.
85
117
  * Allow arbitrary sets of directories and file types, possibly with "include" and "exclude" sets
86
118
  * ".rerun" file to specify options per project or in $HOME.
87
119
  * Test on Linux.
88
- * Merge with Kicker (using it as a library and writing a Rerun recipe) or Watchr
120
+ * Merge with Kicker or Watchr or Guard -- maybe by using it as a library and writing a Rerun recipe
89
121
  * On OS X, use a C library using growl's developer API <http://growl.info/developer/>
90
122
  * "Failed" icon
91
123
  * Get Rails icon working
@@ -99,6 +131,7 @@ Also --version and --help.
99
131
  * The Sinatra FAQ has a discussion at <http://www.sinatrarb.com/faq.html#reloading>
100
132
  * Kicker: <http://github.com/alloy/kicker/>
101
133
  * Watchr: <https://github.com/mynyml/watchr>
134
+ * Guard: <http://github.com/guard/guard>
102
135
 
103
136
  # Why would I use this instead of Shotgun?
104
137
 
@@ -120,6 +153,9 @@ pages all load other files (CSS, JS, media) and that adds up quickly.
120
153
  The developers of shotgun are probably using caching or a front web
121
154
  server so this doesn't affect them too much.
122
155
 
156
+ And hey, does Shotgun reload your Worker processes if you're using Foreman and
157
+ a Procfile? I'm pretty sure it doesn't.
158
+
123
159
  YMMV!
124
160
 
125
161
  # Why would I use this instead of Rack::Reloader?
data/Rakefile CHANGED
@@ -28,6 +28,16 @@ def package(ext='')
28
28
  "pkg/#{$spec.name}-#{$spec.version}" + ext
29
29
  end
30
30
 
31
+ desc 'Exit if git is dirty'
32
+ task :check_git do
33
+ state = `git status 2> /dev/null | tail -n1`
34
+ clean = (state =~ /working directory clean/)
35
+ unless clean
36
+ warn "can't do that on an unclean git dir"
37
+ exit 1
38
+ end
39
+ end
40
+
31
41
  desc 'Build packages'
32
42
  task :package => %w[.gem .tar.gz].map { |e| package(e) }
33
43
 
@@ -55,6 +65,10 @@ file package('.tar.gz') => %w[pkg/] + $spec.files do |f|
55
65
  end
56
66
 
57
67
  desc 'Publish gem and tarball to rubyforge'
58
- task 'release' => [package('.gem'), package('.tar.gz')] do |t|
68
+ task 'release' => [:check_git, package('.gem'), package('.tar.gz')] do |t|
69
+ puts "Releasing #{$spec.version}"
59
70
  sh "gem push #{package('.gem')}"
71
+ puts "Tagging and pushing"
72
+ sh "git tag -v#{$spec.version}"
73
+ sh "git push && git push --tags"
60
74
  end
data/bin/rerun CHANGED
@@ -64,6 +64,4 @@ if ARGV.empty?
64
64
  end
65
65
 
66
66
  cmd = ARGV.join(" ")
67
- runner = Rerun::Runner.new(cmd, options)
68
- runner.start
69
- runner.join
67
+ runner = Rerun::Runner.keep_running(cmd, options)
data/lib/rerun/runner.rb CHANGED
@@ -1,6 +1,15 @@
1
+ require 'timeout'
2
+ require 'io/wait'
3
+
1
4
  module Rerun
2
5
  class Runner
3
6
 
7
+ def self.keep_running(cmd, options)
8
+ runner = new(cmd, options)
9
+ runner.start
10
+ runner.join
11
+ end
12
+
4
13
  include System
5
14
 
6
15
  def initialize(run_command, options = {})
@@ -8,6 +17,44 @@ module Rerun
8
17
  @run_command = "ruby #{@run_command}" if @run_command.split(' ').first =~ /\.rb$/
9
18
  end
10
19
 
20
+ def start_keypress_thread
21
+ from = caller.first
22
+ @keypress_thread = Thread.new do
23
+ # puts "starting keypress thread #{Thread.current.object_id} from #{from}"
24
+ while true
25
+ if c = key_pressed
26
+ puts "\n#{c.inspect} pressed inside rerun"
27
+ case c.downcase
28
+ when 'c'
29
+ say "clearing screen"
30
+ clear_screen
31
+ when 'r'
32
+ say "'r' pressed - restarting"
33
+ restart
34
+ break # the break will stop this thread
35
+ when 'x'
36
+ die
37
+ break # the break will stop this thread, in case the 'die' doesn't
38
+ else
39
+ puts [["c", "clear screen"],
40
+ ["r", "restart"],
41
+ ["x", "stop and exit"]
42
+ ].map{|key, description| " #{key} -- #{description}"}.join("\n")
43
+ puts
44
+ end
45
+ end
46
+ sleep 1 # todo: use select instead of polling somehow?
47
+ end
48
+ # puts "keypress thread #{Thread.current.object_id} ending"
49
+ end
50
+ @keypress_thread.run
51
+ end
52
+
53
+ def kill_keypress_thread
54
+ @keypress_thread.kill if @keypress_thread
55
+ @keypress_thread = nil
56
+ end
57
+
11
58
  def restart
12
59
  @restarting = true
13
60
  stop
@@ -53,7 +100,8 @@ module Rerun
53
100
  notify "restarted", taglines[rand(taglines.size)]
54
101
  end
55
102
 
56
- print "\033[H\033[2J" if clear? # see http://ascii-table.com/ansi-escape-sequences-vt-100.php
103
+ clear_screen if clear?
104
+ start_keypress_thread
57
105
 
58
106
  @pid = Kernel.fork do
59
107
  begin
@@ -66,17 +114,19 @@ module Rerun
66
114
  end
67
115
  status_thread = Process.detach(@pid) # so if the child exits, it dies
68
116
 
69
- Signal.trap("INT") do # INT = control-C
70
- stop # first stop the child
71
- exit
117
+ Signal.trap("INT") do # INT = control-C -- allows user to stop the top-level rerun process
118
+ die
119
+ end
120
+
121
+ Signal.trap("TERM") do # TERM is the polite way of terminating a process
122
+ die
72
123
  end
73
124
 
74
125
  begin
75
126
  sleep 2
76
127
  rescue Interrupt => e
77
- # in case someone hits control-C immediately
78
- stop
79
- exit
128
+ # in case someone hits control-C immediately ("oops!")
129
+ die
80
130
  end
81
131
 
82
132
  if exit?
@@ -106,7 +156,11 @@ module Rerun
106
156
  watcher.start
107
157
  @watcher = watcher
108
158
  end
159
+ end
109
160
 
161
+ def die
162
+ stop # stop the child process if it exists
163
+ exit 0 # todo: status code param
110
164
  end
111
165
 
112
166
  def join
@@ -126,8 +180,23 @@ module Rerun
126
180
 
127
181
  def stop
128
182
  if @pid && (@pid != 0)
129
- notify "stopped", "All good things must come to an end." unless @restarting
130
- signal("KILL") && Process.wait(@pid)
183
+ notify "stopping", "All good things must come to an end." unless @restarting
184
+ begin
185
+ timeout(2) do
186
+ # start with a polite SIGTERM
187
+ signal("TERM") && Process.wait(@pid)
188
+ end
189
+ rescue Timeout::Error
190
+ begin
191
+ timeout(2) do
192
+ # escalate to SIGINT aka control-C since some foolish process may be ignoring SIGTERM
193
+ signal("INT") && Process.wait(@pid)
194
+ end
195
+ rescue Timeout::Error
196
+ # escalate to SIGKILL aka "kill -9" which cannot be ignored
197
+ signal("KILL") && Process.wait(@pid)
198
+ end
199
+ end
131
200
  end
132
201
  rescue => e
133
202
  false
@@ -154,5 +223,36 @@ module Rerun
154
223
  puts "#{Time.now.strftime("%T")} - #{msg}"
155
224
  end
156
225
 
226
+ # non-blocking stdin reader.
227
+ # returns a 1-char string if a key was pressed; otherwise nil
228
+ #
229
+ def key_pressed
230
+ begin
231
+ # this "raw input" nonsense is because unix likes waiting for linefeeds before sending stdin
232
+ # "stty -echo" would make it not clutter the console, but be sure (ensure) to "stty echo" before exit
233
+ system("stty raw") # turn raw input on
234
+ c = nil
235
+ if $stdin.ready?
236
+ c = $stdin.getc
237
+ end
238
+ c.chr if c
239
+ ensure
240
+ system "stty -raw" # turn raw input off
241
+ end
242
+
243
+ # note: according to 'man tty' the proper way restore the settings is
244
+ # tty_state=`stty -g`
245
+ # ensure
246
+ # system 'stty "#{tty_state}'
247
+ # end
248
+ # but this way seems fine and less confusing
249
+
250
+ end
251
+
252
+ def clear_screen
253
+ # see http://ascii-table.com/ansi-escape-sequences-vt-100.php
254
+ $stdout.print "\033[H\033[2J"
255
+ end
256
+
157
257
  end
158
258
  end
data/rerun.gemspec CHANGED
@@ -3,7 +3,7 @@ $spec = Gem::Specification.new do |s|
3
3
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
4
4
 
5
5
  s.name = 'rerun'
6
- s.version = '0.6.2'
6
+ s.version = '0.6.3'
7
7
 
8
8
  s.description = "Restarts your app when a file changes"
9
9
  s.summary = "Launches an app, and restarts it whenever the filesystem changes."
metadata CHANGED
@@ -1,33 +1,24 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: rerun
3
- version: !ruby/object:Gem::Version
4
- hash: 3
5
- prerelease: false
6
- segments:
7
- - 0
8
- - 6
9
- - 2
10
- version: 0.6.2
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.6.3
5
+ prerelease:
11
6
  platform: ruby
12
- authors:
7
+ authors:
13
8
  - Alex Chaffee
14
9
  autorequire:
15
10
  bindir: bin
16
11
  cert_chain: []
17
-
18
- date: 2011-05-18 00:00:00 -07:00
19
- default_executable:
12
+ date: 2011-11-27 00:00:00.000000000Z
20
13
  dependencies: []
21
-
22
14
  description: Restarts your app when a file changes
23
15
  email: alex@stinky.com
24
- executables:
16
+ executables:
25
17
  - rerun
26
18
  extensions: []
27
-
28
- extra_rdoc_files:
19
+ extra_rdoc_files:
29
20
  - README.md
30
- files:
21
+ files:
31
22
  - README.md
32
23
  - LICENSE
33
24
  - Rakefile
@@ -41,39 +32,31 @@ files:
41
32
  - lib/rerun/runner.rb
42
33
  - lib/rerun/system.rb
43
34
  - lib/rerun/watcher.rb
44
- has_rdoc: true
45
35
  homepage: http://github.com/alexch/rerun/
46
36
  licenses: []
47
-
48
37
  post_install_message:
49
38
  rdoc_options: []
50
-
51
- require_paths:
39
+ require_paths:
52
40
  - lib
53
- required_ruby_version: !ruby/object:Gem::Requirement
41
+ required_ruby_version: !ruby/object:Gem::Requirement
54
42
  none: false
55
- requirements:
56
- - - ">="
57
- - !ruby/object:Gem::Version
58
- hash: 3
59
- segments:
43
+ requirements:
44
+ - - ! '>='
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ segments:
60
48
  - 0
61
- version: "0"
62
- required_rubygems_version: !ruby/object:Gem::Requirement
49
+ hash: 569371154783151759
50
+ required_rubygems_version: !ruby/object:Gem::Requirement
63
51
  none: false
64
- requirements:
65
- - - ">="
66
- - !ruby/object:Gem::Version
67
- hash: 3
68
- segments:
69
- - 0
70
- version: "0"
52
+ requirements:
53
+ - - ! '>='
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
71
56
  requirements: []
72
-
73
57
  rubyforge_project: pivotalrb
74
- rubygems_version: 1.3.7
58
+ rubygems_version: 1.8.6
75
59
  signing_key:
76
60
  specification_version: 2
77
61
  summary: Launches an app, and restarts it whenever the filesystem changes.
78
62
  test_files: []
79
-