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.
- data/CHANGELOG.md +11 -0
- data/Gemfile +4 -0
- data/README.md +49 -24
- data/lib/spring/application.rb +30 -30
- data/lib/spring/application_manager.rb +34 -15
- data/lib/spring/client.rb +9 -7
- data/lib/spring/client/binstub.rb +8 -3
- data/lib/spring/client/help.rb +20 -32
- data/lib/spring/client/rails.rb +29 -0
- data/lib/spring/client/run.rb +55 -51
- data/lib/spring/client/start.rb +17 -0
- data/lib/spring/client/status.rb +1 -1
- data/lib/spring/client/stop.rb +1 -1
- data/lib/spring/commands.rb +36 -20
- data/lib/spring/errors.rb +3 -0
- data/lib/spring/server.rb +36 -12
- data/lib/spring/version.rb +1 -1
- data/lib/spring/watcher.rb +28 -0
- data/lib/spring/watcher/abstract.rb +83 -0
- data/lib/spring/watcher/listen.rb +54 -0
- data/lib/spring/watcher/polling.rb +59 -0
- data/test/acceptance/app_test.rb +62 -32
- data/test/apps/rails-3-2/Gemfile +5 -0
- data/test/unit/client/help_test.rb +27 -19
- data/test/unit/commands_test.rb +26 -1
- data/test/unit/watcher_test.rb +171 -0
- metadata +12 -6
- data/lib/spring/application_watcher.rb +0 -43
- data/test/unit/application_watcher_test.rb +0 -67
data/lib/spring/client/help.rb
CHANGED
@@ -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
|
16
|
-
@application_commands = application_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
|
-
*
|
32
|
+
*command_help("spring itself", spring_commands),
|
26
33
|
'',
|
27
|
-
*
|
34
|
+
*command_help("your application", application_commands)].join("\n")
|
28
35
|
end
|
29
36
|
|
30
|
-
def
|
31
|
-
["Commands for
|
32
|
-
*
|
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
|
-
|
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
|
71
|
-
|
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.
|
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
|
data/lib/spring/client/run.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
49
|
+
def connect_to_application(client)
|
28
50
|
server.send_io client
|
29
|
-
server
|
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
|
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
|
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
|
70
|
-
|
71
|
-
|
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
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
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
|
93
|
-
|
94
|
-
|
95
|
-
|
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
|
data/lib/spring/client/status.rb
CHANGED
data/lib/spring/client/stop.rb
CHANGED
@@ -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
|
-
|
19
|
+
$stderr.puts "Spring did not stop; killing forcibly."
|
20
20
|
kill 'KILL'
|
21
21
|
else
|
22
22
|
puts "Spring stopped."
|
data/lib/spring/commands.rb
CHANGED
@@ -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
|
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
|
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 "
|
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
|
-
|
171
|
+
nil
|
168
172
|
end
|
169
173
|
end
|
170
|
-
Spring.register_command "
|
174
|
+
Spring.register_command "rails_console", RailsConsole.new
|
171
175
|
|
172
|
-
class
|
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
|
-
|
188
|
+
nil
|
185
189
|
end
|
186
190
|
end
|
187
|
-
Spring.register_command "
|
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
|
-
|
213
|
+
nil
|
198
214
|
end
|
199
215
|
end
|
200
|
-
Spring.register_command "
|
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
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
|
-
|
27
|
-
|
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.
|
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
|
|