spring 0.0.7 → 0.0.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -9,11 +9,18 @@ module Spring
9
9
  "Print available commands."
10
10
  end
11
11
 
12
+ def self.call(args)
13
+ require "spring/commands"
14
+ super
15
+ end
16
+
12
17
  def initialize(args, spring_commands = nil, application_commands = nil)
13
18
  super args
14
19
 
15
- @spring_commands = spring_commands || Spring::Client::COMMANDS
16
- @application_commands = application_commands || Spring.commands
20
+ @spring_commands = spring_commands || Spring::Client::COMMANDS.dup
21
+ @application_commands = application_commands || Spring.commands.dup
22
+
23
+ @application_commands["rails"] = @spring_commands.delete("rails")
17
24
  end
18
25
 
19
26
  def call
@@ -22,41 +29,20 @@ module Spring
22
29
 
23
30
  def formatted_help
24
31
  ["Usage: spring COMMAND [ARGS]\n",
25
- *spring_command_help,
32
+ *command_help("spring itself", spring_commands),
26
33
  '',
27
- *application_command_help].join("\n")
34
+ *command_help("your application", application_commands)].join("\n")
28
35
  end
29
36
 
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) }]
37
+ def command_help(subject, commands)
38
+ ["Commands for #{subject}:\n",
39
+ *commands.sort_by(&:first).map { |name, command| display(name, command) }.compact]
38
40
  end
39
41
 
40
42
  private
41
43
 
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
44
  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
45
+ spring_commands.merge application_commands
60
46
  end
61
47
 
62
48
  def description_for_command(command)
@@ -67,12 +53,14 @@ module Spring
67
53
  end
68
54
  end
69
55
 
70
- def display_value(command, names)
71
- " #{ Array(names).join(', ').ljust(max_name_width) } #{ description_for_command(command) }"
56
+ def display(name, command)
57
+ if description = description_for_command(command)
58
+ " #{name.ljust(max_name_width)} #{description}"
59
+ end
72
60
  end
73
61
 
74
62
  def max_name_width
75
- @max_name_width ||= all_commands.collect { |_,n| Array(n).join(', ').length }.max
63
+ @max_name_width ||= all_commands.keys.map(&:length).max
76
64
  end
77
65
  end
78
66
  end
@@ -0,0 +1,29 @@
1
+ require "set"
2
+
3
+ module Spring
4
+ module Client
5
+ class Rails < Command
6
+ COMMANDS = Set.new %w(console runner generate)
7
+
8
+ ALIASES = {
9
+ "c" => "console",
10
+ "r" => "runner",
11
+ "g" => "generate"
12
+ }
13
+
14
+ def self.description
15
+ "Run a rails command. The following sub commands will use spring: #{COMMANDS.to_a.join ', '}."
16
+ end
17
+
18
+ def call
19
+ command_name = ALIASES[args[1]] || args[1]
20
+
21
+ if COMMANDS.include?(command_name)
22
+ Run.call(["rails_#{command_name}", *args.drop(2)])
23
+ else
24
+ exec "bundle", "exec", *args
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -1,43 +1,63 @@
1
1
  require "rbconfig"
2
2
  require "socket"
3
-
4
- require "spring/commands"
3
+ require "json"
5
4
 
6
5
  module Spring
7
6
  module Client
8
7
  class Run < Command
9
- SERVER_COMMAND = [
10
- File.join(*RbConfig::CONFIG.values_at('bindir', 'RUBY_INSTALL_NAME')),
11
- "-I", File.expand_path("../../..", __FILE__),
12
- "-r", "spring/server",
13
- "-r", "bundler/setup",
14
- "-e", "Spring::Server.boot"
15
- ]
8
+ FORWARDED_SIGNALS = %w(INT QUIT USR1 USR2 INFO) & Signal.list.keys
16
9
 
17
- FORWARDED_SIGNALS = %w(INT QUIT USR1 USR2 INFO)
10
+ def server
11
+ @server ||= UNIXSocket.open(env.socket_name)
12
+ end
18
13
 
19
14
  def call
20
15
  Spring.verify_environment!
16
+
21
17
  boot_server unless env.server_running?
18
+ verify_server_version
22
19
 
23
20
  application, client = UNIXSocket.pair
24
21
 
25
- server = UNIXSocket.open(env.socket_name)
22
+ connect_to_application(client)
23
+ run_command(client, application)
24
+ rescue Errno::ECONNRESET
25
+ exit 1
26
+ ensure
27
+ server.close if @server
28
+ end
29
+
30
+ def boot_server
31
+ env.socket_path.unlink if env.socket_path.exist?
32
+ Process.spawn("spring", "start")
33
+ sleep 0.1 until env.socket_path.exist?
34
+ end
35
+
36
+ def verify_server_version
37
+ server_version = server.gets.chomp
38
+ if server_version != env.version
39
+ $stderr.puts <<-ERROR
40
+ There is a version mismatch beween the spring client and the server.
41
+ You should restart the server and make sure to use the same version.
42
+
43
+ CLIENT: #{env.version}, SERVER: #{server_version}
44
+ ERROR
45
+ exit 1
46
+ end
47
+ end
26
48
 
27
- verify_server_version(server)
49
+ def connect_to_application(client)
28
50
  server.send_io client
29
- server.puts rails_env_for(args.first)
51
+ send_json server, args: args, env: ENV
52
+ server.gets or raise CommandNotFound
53
+ end
30
54
 
55
+ def run_command(client, application)
31
56
  application.send_io STDOUT
32
57
  application.send_io STDERR
33
58
  application.send_io STDIN
34
59
 
35
- application.puts args.length
36
-
37
- args.each do |arg|
38
- application.puts arg.length
39
- application.write arg
40
- end
60
+ send_json application, args
41
61
 
42
62
  pid = server.gets
43
63
  pid = pid.chomp if pid
@@ -53,46 +73,30 @@ module Spring
53
73
  else
54
74
  exit 1
55
75
  end
56
- rescue Errno::ECONNRESET
57
- exit 1
58
76
  ensure
59
- application.close if application
60
- server.close if server
61
- end
62
-
63
- def boot_server
64
- env.socket_path.unlink if env.socket_path.exist?
65
- Process.spawn(*SERVER_COMMAND)
66
- sleep 0.1 until env.socket_path.exist?
77
+ application.close
67
78
  end
68
79
 
69
- def verify_server_version(server)
70
- server_version = server.gets.chomp
71
- if server_version != env.version
72
- STDERR.puts <<-ERROR
73
- There is a version mismatch beween the spring client and the server.
74
- You should restart the server and make sure to use the same version.
75
-
76
- CLIENT: #{env.version}, SERVER: #{server_version}
77
- ERROR
78
- exit 1
80
+ def forward_signals(pid)
81
+ FORWARDED_SIGNALS.each do |sig|
82
+ trap(sig) { forward_signal sig, pid }
79
83
  end
80
84
  end
81
85
 
82
- def rails_env_for(command_name)
83
- command = Spring.command(command_name)
84
-
85
- if command.respond_to?(:env)
86
- command.env
87
- else
88
- ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development'
89
- end
86
+ def forward_signal(sig, pid)
87
+ Process.kill(sig, -Process.getpgid(pid))
88
+ rescue Errno::ESRCH
89
+ # If the application process is gone, then don't block the
90
+ # signal on this process.
91
+ trap(sig, 'DEFAULT')
92
+ Process.kill(sig, Process.pid)
90
93
  end
91
94
 
92
- def forward_signals(pid)
93
- (FORWARDED_SIGNALS & Signal.list.keys).each do |sig|
94
- trap(sig) { Process.kill(sig, -Process.getpgid(pid)) }
95
- end
95
+ def send_json(socket, data)
96
+ data = JSON.dump(data)
97
+
98
+ socket.puts data.length
99
+ socket.write data
96
100
  end
97
101
  end
98
102
  end
@@ -0,0 +1,17 @@
1
+ module Spring
2
+ module Client
3
+ class Start < Command
4
+ def self.description
5
+ "Boot the spring server (this happens automatically when you run a command)"
6
+ end
7
+
8
+ def call
9
+ # Require spring/server before bundler so that it doesn't have to be in
10
+ # the bundle
11
+ require "spring/server"
12
+ require "bundler/setup"
13
+ Spring::Server.boot
14
+ end
15
+ end
16
+ end
17
+ end
@@ -21,7 +21,7 @@ module Spring
21
21
  end
22
22
 
23
23
  def application_pids
24
- candidates = `ps -o ppid= -o pid=`.lines
24
+ candidates = `ps -a -o ppid= -o pid=`.lines
25
25
  candidates.select { |l| l =~ /^#{env.pid} / }
26
26
  .map { |l| l.split(" ").last }
27
27
  end
@@ -16,7 +16,7 @@ module Spring
16
16
  sleep 0.1 until !env.server_running? || Time.now >= timeout
17
17
 
18
18
  if env.server_running?
19
- STDERR.puts "Spring did not stop; killing forcibly."
19
+ $stderr.puts "Spring did not stop; killing forcibly."
20
20
  kill 'KILL'
21
21
  else
22
22
  puts "Spring stopped."
@@ -1,3 +1,7 @@
1
+ # If the config/spring.rb contains requires for commands from other gems,
2
+ # then we need to be under bundler.
3
+ require "bundler/setup"
4
+
1
5
  module Spring
2
6
  @commands = {}
3
7
 
@@ -5,12 +9,8 @@ module Spring
5
9
  attr_reader :commands
6
10
  end
7
11
 
8
- def self.register_command(name, klass, options = {})
12
+ def self.register_command(name, klass)
9
13
  commands[name] = klass
10
-
11
- if options[:alias]
12
- commands[options[:alias]] = klass
13
- end
14
14
  end
15
15
 
16
16
  def self.command?(name)
@@ -58,10 +58,10 @@ MESSAGE
58
58
  end
59
59
  end
60
60
 
61
- class Test < Command
61
+ class TestUnit < Command
62
62
  preloads << "test_helper"
63
63
 
64
- def env
64
+ def env(*)
65
65
  "test"
66
66
  end
67
67
 
@@ -89,19 +89,20 @@ MESSAGE
89
89
  "Execute a Test::Unit test."
90
90
  end
91
91
  end
92
- Spring.register_command "test", Test.new
92
+ Spring.register_command "testunit", TestUnit.new
93
93
 
94
94
  class RSpec < Command
95
95
  preloads << "spec_helper"
96
96
 
97
- def env
97
+ def env(*)
98
98
  "test"
99
99
  end
100
100
 
101
101
  def setup
102
102
  $LOAD_PATH.unshift "spec"
103
- super
104
103
  require 'rspec/core'
104
+ ::RSpec::Core::Runner.disable_autorun!
105
+ super
105
106
  end
106
107
 
107
108
  def call(args)
@@ -116,7 +117,7 @@ MESSAGE
116
117
  Spring.register_command "rspec", RSpec.new
117
118
 
118
119
  class Cucumber < Command
119
- def env
120
+ def env(*)
120
121
  "test"
121
122
  end
122
123
 
@@ -152,8 +153,11 @@ MESSAGE
152
153
  end
153
154
  Spring.register_command "rake", Rake.new
154
155
 
156
+ class RailsConsole < Command
157
+ def env(tail)
158
+ tail.first if tail.first && !tail.first.index("-")
159
+ end
155
160
 
156
- class Console < Command
157
161
  def setup
158
162
  require "rails/commands/console"
159
163
  end
@@ -164,12 +168,12 @@ MESSAGE
164
168
  end
165
169
 
166
170
  def description
167
- "Start the Rails console."
171
+ nil
168
172
  end
169
173
  end
170
- Spring.register_command "console", Console.new, alias: "c"
174
+ Spring.register_command "rails_console", RailsConsole.new
171
175
 
172
- class Generate < Command
176
+ class RailsGenerate < Command
173
177
  def setup
174
178
  super
175
179
  Rails.application.load_generators
@@ -181,12 +185,24 @@ MESSAGE
181
185
  end
182
186
 
183
187
  def description
184
- "Trigger a Rails generator."
188
+ nil
185
189
  end
186
190
  end
187
- Spring.register_command "generate", Generate.new, alias: "g"
191
+ Spring.register_command "rails_generate", RailsGenerate.new
192
+
193
+ class RailsRunner < Command
194
+ def env(tail)
195
+ previous_option = nil
196
+ tail.reverse.each do |option|
197
+ case option
198
+ when /--environment=(\w+)/ then return $1
199
+ when '-e' then return previous_option
200
+ end
201
+ previous_option = option
202
+ end
203
+ nil
204
+ end
188
205
 
189
- class Runner < Command
190
206
  def call(args)
191
207
  Object.const_set(:APP_PATH, Rails.root.join('config/application'))
192
208
  ARGV.replace args
@@ -194,10 +210,10 @@ MESSAGE
194
210
  end
195
211
 
196
212
  def description
197
- "Execute a command with the Rails runner."
213
+ nil
198
214
  end
199
215
  end
200
- Spring.register_command "runner", Runner.new, alias: "r"
216
+ Spring.register_command "rails_runner", RailsRunner.new
201
217
  end
202
218
 
203
219
  # Load custom commands, if any.
data/lib/spring/errors.rb CHANGED
@@ -30,4 +30,7 @@ module Spring
30
30
  "config/spring.rb."
31
31
  end
32
32
  end
33
+
34
+ class CommandNotFound < ClientError
35
+ end
33
36
  end
data/lib/spring/server.rb CHANGED
@@ -3,6 +3,7 @@ require "socket"
3
3
  require "spring/env"
4
4
  require "spring/application_manager"
5
5
  require "spring/process_title_updater"
6
+ require "spring/commands"
6
7
 
7
8
  # readline must be required before we setpgid, otherwise the require may hang,
8
9
  # if readline has been built against libedit. See issue #70.
@@ -23,15 +24,8 @@ module Spring
23
24
  end
24
25
 
25
26
  def boot
26
- # Boot the server into the process group of the current session.
27
- # This will cause it to be automatically killed once the session
28
- # ends (i.e. when the user closes their terminal).
29
- Process.setpgid(0, SID.pgid)
30
-
31
- # Ignore SIGINT and SIGQUIT otherwise the user typing ^C or ^\ on the command line
32
- # will kill the server/application.
33
- IGNORE_SIGNALS.each { |sig| trap(sig, "IGNORE") }
34
-
27
+ set_pgid
28
+ ignore_signals
35
29
  set_exit_hook
36
30
  write_pidfile
37
31
  redirect_output
@@ -43,14 +37,44 @@ module Spring
43
37
 
44
38
  def serve(client)
45
39
  client.puts env.version
46
- app_client = client.recv_io
47
- rails_env = client.gets.chomp
48
40
 
49
- client.puts @applications[rails_env].run(app_client)
41
+ app_client = client.recv_io
42
+ command = JSON.parse(client.read(client.gets.to_i))
43
+ args, client_env = command.values_at('args', 'env')
44
+
45
+ if Spring.command?(args.first)
46
+ client.puts
47
+ client.puts @applications[rails_env_for(args, client_env)].run(app_client)
48
+ else
49
+ client.close
50
+ end
50
51
  rescue SocketError => e
51
52
  raise e unless client.eof?
52
53
  end
53
54
 
55
+ def rails_env_for(args, client_env)
56
+ command = Spring.command(args.first)
57
+
58
+ if command.respond_to?(:env)
59
+ env = command.env(args.drop(1))
60
+ end
61
+
62
+ env || client_env['RAILS_ENV'] || client_env['RACK_ENV'] || 'development'
63
+ end
64
+
65
+ # Boot the server into the process group of the current session.
66
+ # This will cause it to be automatically killed once the session
67
+ # ends (i.e. when the user closes their terminal).
68
+ def set_pgid
69
+ Process.setpgid(0, SID.pgid)
70
+ end
71
+
72
+ # Ignore SIGINT and SIGQUIT otherwise the user typing ^C or ^\ on the command line
73
+ # will kill the server/application.
74
+ def ignore_signals
75
+ IGNORE_SIGNALS.each { |sig| trap(sig, "IGNORE") }
76
+ end
77
+
54
78
  def set_exit_hook
55
79
  server_pid = Process.pid
56
80