spring 0.0.7 → 0.0.8

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.
@@ -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