spring 0.0.6 → 0.0.7

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.
data/.travis.yml CHANGED
@@ -1,3 +1,4 @@
1
1
  language: ruby
2
2
  rvm:
3
3
  - 1.9.3
4
+ - 2.0.0
data/README.md CHANGED
@@ -40,7 +40,17 @@ real shell and a terminal running the rails "commands console".
40
40
 
41
41
  ## Compatibility
42
42
 
43
- At the moment only MRI 1.9.3 / Rails 3.2 is supported.
43
+ Ruby versions supported:
44
+
45
+ * MRI 1.9.3
46
+ * MRI 2.0.0
47
+
48
+ Rails versions supported:
49
+
50
+ * 3.2
51
+
52
+ Spring makes extensive use of `Process#fork`, so won't be able to run on
53
+ any platform which doesn't support that (Windows, JRuby).
44
54
 
45
55
  ## Usage
46
56
 
@@ -76,9 +86,11 @@ sys 0m0.066s
76
86
  That booted our app in the background:
77
87
 
78
88
  ```
79
- $ ps ax | grep spring
80
- 26150 pts/3 Sl 0:00 spring server | rails-3-2 | started 2013-02-01 20:16:40 +0000
81
- 26155 pts/3 Sl 0:02 spring app | rails-3-2 | started 2013-02-01 20:16:40 +0000 | test mode
89
+ $ spring status
90
+ Spring is running:
91
+
92
+ 26150 spring server | rails-3-2 | started 3 secs ago
93
+ 26155 spring app | rails-3-2 | started 3 secs ago | test mode
82
94
  ```
83
95
 
84
96
  We can see two processes, one is the Spring server, the other is the
@@ -147,9 +159,11 @@ automatically. Let's "edit" `config/application.rb`:
147
159
 
148
160
  ```
149
161
  $ touch config/application.rb
150
- $ ps ax | grep spring
151
- 26150 pts/3 Sl 0:00 spring server | rails-3-2 | started 2013-02-01 20:16:40 +0000
152
- 26556 pts/3 Sl 0:00 spring app | rails-3-2 | started 2013-02-01 20:20:07 +0000 | test mode
162
+ $ spring status
163
+ Spring is running:
164
+
165
+ 26150 spring server | rails-3-2 | started 36 secs ago
166
+ 26556 spring app | rails-3-2 | started 1 sec ago | test mode
153
167
  ```
154
168
 
155
169
  The application process detected the change and exited. The server process
@@ -178,10 +192,19 @@ We now have 3 processes: the server, and application in test mode and
178
192
  the application in development mode.
179
193
 
180
194
  ```
181
- $ ps ax | grep spring
182
- 26150 pts/3 Sl 0:00 spring server | rails-3-2 | started 2013-02-01 20:16:40 +0000
183
- 26556 pts/3 Sl 0:08 spring app | rails-3-2 | started 2013-02-01 20:20:07 +0000 | test mode
184
- 26707 pts/3 Sl 0:00 spring app | rails-3-2 | started 2013-02-01 20:22:41 +0000 | development mode
195
+ $ bin/spring status
196
+ Spring is running:
197
+
198
+ 26150 spring server | rails-3-2 | started 1 min ago
199
+ 26556 spring app | rails-3-2 | started 42 secs ago | test mode
200
+ 26707 spring app | rails-3-2 | started 2 secs ago | development mode
201
+ ```
202
+
203
+ To stop the background processes:
204
+
205
+ ```
206
+ $ bin/spring stop
207
+ Spring stopped.
185
208
  ```
186
209
 
187
210
  ## Commands
@@ -246,6 +269,41 @@ Spring where your app is located:
246
269
  Spring.application_root = './test/dummy'
247
270
  ```
248
271
 
272
+ ### preload files
273
+
274
+ Every Spring command has the ability to preload a set of files. The
275
+ `test` command for example preloads `test_helper` (it also adds the
276
+ `test/` directory to your load path). If the
277
+ defaults don't work for your application you can configure the
278
+ preloads for every command:
279
+
280
+ ```ruby
281
+ # if your test helper is called "helper"
282
+ Commands::Command::Test.preloads = %w(helper)
283
+
284
+ # if you don't want to preload spec_helper.rb
285
+ Commands::Command::RSpec.preloads = []
286
+
287
+ # if you want to preload additional files for the console
288
+ Commands::Command::Console.preloads << 'extenstions/console_helper'
289
+ ```
290
+
291
+ ### after fork callbacks
292
+
293
+ You might want to run code after Spring forked off the process but
294
+ before the actual command is run. You might want to use an
295
+ `after_fork` callback if you have to connect to an external service,
296
+ do some general cleanup or set up dynamic configuration.
297
+
298
+ ```ruby
299
+ Spring.after_fork do
300
+ # run arbitrary code
301
+ end
302
+ ```
303
+
304
+ If you want to register multiple callbacks you can simply call
305
+ `Spring.after_fork` multiple times with different blocks.
306
+
249
307
  ### tmp directory
250
308
 
251
309
  Spring needs a tmp directory. This will default to `Rails.root.join('tmp', 'spring')`.
data/Rakefile CHANGED
@@ -4,7 +4,7 @@ require "rake/testtask"
4
4
  namespace :test do
5
5
  Rake::TestTask.new(:unit) do |t|
6
6
  t.libs << "test"
7
- t.test_files = FileList["test/unit/*_test.rb"]
7
+ t.test_files = FileList["test/unit/**/*_test.rb"]
8
8
  t.verbose = true
9
9
  end
10
10
 
@@ -19,9 +19,6 @@ module Spring
19
19
  @manager = manager
20
20
  @watcher = watcher
21
21
  @setup = Set.new
22
-
23
- @stdout = IO.new(STDOUT.fileno)
24
- @stderr = IO.new(STDERR.fileno)
25
22
  end
26
23
 
27
24
  def start
@@ -57,30 +54,39 @@ module Spring
57
54
  end
58
55
 
59
56
  def serve(client)
60
- redirect_output(client) do
61
- stdin = client.recv_io
62
- args_length = client.gets.to_i
63
- args = args_length.times.map { client.read(client.gets.to_i) }
64
- command = Spring.command(args.shift)
65
-
66
- setup command
67
-
68
- ActionDispatch::Reloader.cleanup!
69
- ActionDispatch::Reloader.prepare!
70
-
71
- pid = fork {
72
- Process.setsid
73
- STDIN.reopen(stdin)
74
- IGNORE_SIGNALS.each { |sig| trap(sig, "DEFAULT") }
75
- command.call(args)
76
- }
77
-
78
- manager.puts pid
79
- Process.wait pid
80
- end
81
- ensure
82
- client.puts
57
+ streams = 3.times.map { client.recv_io }
58
+ args_length = client.gets.to_i
59
+ args = args_length.times.map { client.read(client.gets.to_i) }
60
+ command = Spring.command(args.shift)
61
+
62
+ setup command
63
+
64
+ ActionDispatch::Reloader.cleanup!
65
+ ActionDispatch::Reloader.prepare!
66
+
67
+ pid = fork {
68
+ Process.setsid
69
+ [STDOUT, STDERR, STDIN].zip(streams).each { |a, b| a.reopen(b) }
70
+ IGNORE_SIGNALS.each { |sig| trap(sig, "DEFAULT") }
71
+ invoke_after_fork_callbacks
72
+ command.call(args)
73
+ }
74
+
75
+ manager.puts pid
76
+
77
+ # Wait in a separate thread so we can run multiple commands at once
78
+ Thread.new {
79
+ _, status = Process.wait2 pid
80
+ streams.each(&:close)
81
+ client.puts(status.exitstatus)
82
+ client.close
83
+ }
84
+
85
+ rescue => e
86
+ streams.each(&:close) if streams
87
+ client.puts(1)
83
88
  client.close
89
+ raise
84
90
  end
85
91
 
86
92
  # The command might need to require some files in the
@@ -99,14 +105,10 @@ module Spring
99
105
  end
100
106
  end
101
107
 
102
- def redirect_output(socket)
103
- STDOUT.reopen socket.recv_io
104
- STDERR.reopen socket.recv_io
105
-
106
- yield
107
- ensure
108
- STDOUT.reopen @stdout
109
- STDERR.reopen @stderr
108
+ def invoke_after_fork_callbacks
109
+ Spring.after_fork_callbacks.each do |callback|
110
+ callback.call
111
+ end
110
112
  end
111
113
  end
112
114
  end
@@ -70,7 +70,9 @@ module Spring
70
70
  [STDOUT, STDERR].each { |s| s.reopen('/dev/null', 'w') } if silence
71
71
  @client.close if @client
72
72
  ENV['RAILS_ENV'] = ENV['RACK_ENV'] = app_env
73
- $0 = "spring app | #{spring_env.app_name} | started #{Time.now} | #{app_env} mode"
73
+ ProcessTitleUpdater.run { |distance|
74
+ "spring app | #{spring_env.app_name} | started #{distance} ago | #{app_env} mode"
75
+ }
74
76
  Application.new(child_socket).start
75
77
  }
76
78
  child_socket.close
data/lib/spring/client.rb CHANGED
@@ -4,13 +4,15 @@ require "spring/client/run"
4
4
  require "spring/client/help"
5
5
  require "spring/client/binstub"
6
6
  require "spring/client/stop"
7
+ require "spring/client/status"
7
8
 
8
9
  module Spring
9
10
  module Client
10
11
  COMMANDS = {
11
12
  "help" => Client::Help,
12
13
  "binstub" => Client::Binstub,
13
- "stop" => Client::Stop
14
+ "stop" => Client::Stop,
15
+ "status" => Client::Status
14
16
  }
15
17
 
16
18
  def self.run(args)
@@ -3,6 +3,10 @@ module Spring
3
3
  class Binstub < Command
4
4
  attr_reader :bindir, :name
5
5
 
6
+ def self.description
7
+ "Generate spring based binstubs."
8
+ end
9
+
6
10
  def initialize(args)
7
11
  super
8
12
 
@@ -3,20 +3,76 @@ require "spring/version"
3
3
  module Spring
4
4
  module Client
5
5
  class Help < Command
6
+ attr_reader :spring_commands, :application_commands
7
+
8
+ def self.description
9
+ "Print available commands."
10
+ end
11
+
12
+ def initialize(args, spring_commands = nil, application_commands = nil)
13
+ super args
14
+
15
+ @spring_commands = spring_commands || Spring::Client::COMMANDS
16
+ @application_commands = application_commands || Spring.commands
17
+ end
18
+
6
19
  def call
7
- puts <<-EOT
8
- Usage: spring COMMAND [ARGS]
9
-
10
- The most common spring commands are:
11
- rake Run a rake task
12
- console Start the Rails console
13
- runner Execute a command with the Rails runner
14
- generate Trigger a Rails generator
15
-
16
- test Execute a Test::Unit test
17
- rspec Execute an RSpec spec
18
- cucumber Execute a Cucumber feature
19
- EOT
20
+ puts formatted_help
21
+ end
22
+
23
+ def formatted_help
24
+ ["Usage: spring COMMAND [ARGS]\n",
25
+ *spring_command_help,
26
+ '',
27
+ *application_command_help].join("\n")
28
+ end
29
+
30
+ def spring_command_help
31
+ ["Commands for spring itself:\n",
32
+ *client_commands.map { |c,n| display_value(c,n) }]
33
+ end
34
+
35
+ def application_command_help
36
+ ["Commands for your application:\n",
37
+ *registered_commands.map { |c,n| display_value(c,n) }]
38
+ end
39
+
40
+ private
41
+
42
+ def client_commands
43
+ spring_commands.invert
44
+ end
45
+
46
+ def registered_commands
47
+ Hash[unique_commands.collect { |c| [c, command_aliases(c)] }]
48
+ end
49
+
50
+ def all_commands
51
+ @all_commands ||= client_commands.merge(registered_commands)
52
+ end
53
+
54
+ def unique_commands
55
+ application_commands.collect { |k,v| v }.uniq
56
+ end
57
+
58
+ def command_aliases(command)
59
+ spring_commands.merge(application_commands).select { |k,v| v == command }.keys
60
+ end
61
+
62
+ def description_for_command(command)
63
+ if command.respond_to?(:description)
64
+ command.description
65
+ else
66
+ "No description given."
67
+ end
68
+ end
69
+
70
+ def display_value(command, names)
71
+ " #{ Array(names).join(', ').ljust(max_name_width) } #{ description_for_command(command) }"
72
+ end
73
+
74
+ def max_name_width
75
+ @max_name_width ||= all_commands.collect { |_,n| Array(n).join(', ').length }.max
20
76
  end
21
77
  end
22
78
  end
@@ -39,18 +39,19 @@ module Spring
39
39
  application.write arg
40
40
  end
41
41
 
42
- pid = server.gets.chomp
42
+ pid = server.gets
43
+ pid = pid.chomp if pid
43
44
 
44
45
  # We must not close the client socket until we are sure that the application has
45
46
  # received the FD. Otherwise the FD can end up getting closed while it's in the server
46
47
  # socket buffer on OS X. This doesn't happen on Linux.
47
48
  client.close
48
49
 
49
- if pid.empty?
50
- exit 1
51
- else
50
+ if pid && !pid.empty?
52
51
  forward_signals(pid.to_i)
53
- application.read # FIXME: receive exit status from server
52
+ exit application.read.to_i
53
+ else
54
+ exit 1
54
55
  end
55
56
  rescue Errno::ECONNRESET
56
57
  exit 1
@@ -59,12 +60,9 @@ module Spring
59
60
  server.close if server
60
61
  end
61
62
 
62
- # Boot the server into the process group of the current session.
63
- # This will cause it to be automatically killed once the session
64
- # ends (i.e. when the user closes their terminal).
65
63
  def boot_server
66
64
  env.socket_path.unlink if env.socket_path.exist?
67
- Process.spawn(*SERVER_COMMAND, pgroup: SID.pgid)
65
+ Process.spawn(*SERVER_COMMAND)
68
66
  sleep 0.1 until env.socket_path.exist?
69
67
  end
70
68
 
@@ -0,0 +1,30 @@
1
+ module Spring
2
+ module Client
3
+ class Status < Command
4
+ def self.description
5
+ "Show current status."
6
+ end
7
+
8
+ def call
9
+ if env.server_running?
10
+ puts "Spring is running:"
11
+ puts
12
+ print_process env.pid
13
+ application_pids.each { |pid| print_process pid }
14
+ else
15
+ puts "Spring is not running."
16
+ end
17
+ end
18
+
19
+ def print_process(pid)
20
+ puts `ps -p #{pid} -o pid= -o args=`
21
+ end
22
+
23
+ def application_pids
24
+ candidates = `ps -o ppid= -o pid=`.lines
25
+ candidates.select { |l| l =~ /^#{env.pid} / }
26
+ .map { |l| l.split(" ").last }
27
+ end
28
+ end
29
+ end
30
+ end
@@ -3,8 +3,34 @@ require "spring/version"
3
3
  module Spring
4
4
  module Client
5
5
  class Stop < Command
6
+ TIMEOUT = 2 # seconds
7
+
8
+ def self.description
9
+ "Stop all spring processes for this project."
10
+ end
11
+
6
12
  def call
7
- Process.kill('SIGTERM', env.pid) if env.pid
13
+ if env.server_running?
14
+ timeout = Time.now + TIMEOUT
15
+ kill 'TERM'
16
+ sleep 0.1 until !env.server_running? || Time.now >= timeout
17
+
18
+ if env.server_running?
19
+ STDERR.puts "Spring did not stop; killing forcibly."
20
+ kill 'KILL'
21
+ else
22
+ puts "Spring stopped."
23
+ end
24
+ else
25
+ puts "Spring is not running"
26
+ end
27
+ end
28
+
29
+ def kill(sig)
30
+ pid = env.pid
31
+ Process.kill(sig, pid) if pid
32
+ rescue Errno::ESRCH
33
+ # already dead
8
34
  end
9
35
  end
10
36
  end