spring 0.0.5 → 0.0.6
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/README.md +95 -55
- data/bin/spring +2 -3
- data/lib/spring/application.rb +7 -8
- data/lib/spring/application_manager.rb +12 -5
- data/lib/spring/application_watcher.rb +4 -1
- data/lib/spring/client.rb +31 -0
- data/lib/spring/client/binstub.rb +67 -0
- data/lib/spring/client/command.rb +18 -0
- data/lib/spring/client/help.rb +23 -0
- data/lib/spring/client/run.rb +101 -0
- data/lib/spring/client/stop.rb +11 -0
- data/lib/spring/commands.rb +84 -22
- data/lib/spring/configuration.rb +31 -0
- data/lib/spring/env.rb +43 -5
- data/lib/spring/errors.rb +33 -0
- data/lib/spring/server.rb +9 -2
- data/lib/spring/sid.rb +1 -1
- data/lib/spring/version.rb +2 -2
- data/test/acceptance/app_test.rb +80 -18
- data/test/apps/rails-3-2/.gitignore +3 -0
- data/test/apps/rails-3-2/Gemfile +1 -2
- data/test/helper.rb +0 -1
- data/test/unit/application_watcher_test.rb +15 -3
- data/test/unit/commands_test.rb +34 -0
- metadata +14 -3
- data/lib/spring.rb +0 -151
@@ -0,0 +1,23 @@
|
|
1
|
+
require "spring/version"
|
2
|
+
|
3
|
+
module Spring
|
4
|
+
module Client
|
5
|
+
class Help < Command
|
6
|
+
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
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require "rbconfig"
|
2
|
+
require "socket"
|
3
|
+
|
4
|
+
require "spring/commands"
|
5
|
+
|
6
|
+
module Spring
|
7
|
+
module Client
|
8
|
+
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
|
+
]
|
16
|
+
|
17
|
+
FORWARDED_SIGNALS = %w(INT QUIT USR1 USR2 INFO)
|
18
|
+
|
19
|
+
def call
|
20
|
+
Spring.verify_environment!
|
21
|
+
boot_server unless env.server_running?
|
22
|
+
|
23
|
+
application, client = UNIXSocket.pair
|
24
|
+
|
25
|
+
server = UNIXSocket.open(env.socket_name)
|
26
|
+
|
27
|
+
verify_server_version(server)
|
28
|
+
server.send_io client
|
29
|
+
server.puts rails_env_for(args.first)
|
30
|
+
|
31
|
+
application.send_io STDOUT
|
32
|
+
application.send_io STDERR
|
33
|
+
application.send_io STDIN
|
34
|
+
|
35
|
+
application.puts args.length
|
36
|
+
|
37
|
+
args.each do |arg|
|
38
|
+
application.puts arg.length
|
39
|
+
application.write arg
|
40
|
+
end
|
41
|
+
|
42
|
+
pid = server.gets.chomp
|
43
|
+
|
44
|
+
# We must not close the client socket until we are sure that the application has
|
45
|
+
# received the FD. Otherwise the FD can end up getting closed while it's in the server
|
46
|
+
# socket buffer on OS X. This doesn't happen on Linux.
|
47
|
+
client.close
|
48
|
+
|
49
|
+
if pid.empty?
|
50
|
+
exit 1
|
51
|
+
else
|
52
|
+
forward_signals(pid.to_i)
|
53
|
+
application.read # FIXME: receive exit status from server
|
54
|
+
end
|
55
|
+
rescue Errno::ECONNRESET
|
56
|
+
exit 1
|
57
|
+
ensure
|
58
|
+
application.close if application
|
59
|
+
server.close if server
|
60
|
+
end
|
61
|
+
|
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
|
+
def boot_server
|
66
|
+
env.socket_path.unlink if env.socket_path.exist?
|
67
|
+
Process.spawn(*SERVER_COMMAND, pgroup: SID.pgid)
|
68
|
+
sleep 0.1 until env.socket_path.exist?
|
69
|
+
end
|
70
|
+
|
71
|
+
def verify_server_version(server)
|
72
|
+
server_version = server.gets.chomp
|
73
|
+
if server_version != env.version
|
74
|
+
STDERR.puts <<-ERROR
|
75
|
+
There is a version mismatch beween the spring client and the server.
|
76
|
+
You should restart the server and make sure to use the same version.
|
77
|
+
|
78
|
+
CLIENT: #{env.version}, SERVER: #{server_version}
|
79
|
+
ERROR
|
80
|
+
exit 1
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def rails_env_for(command_name)
|
85
|
+
command = Spring.command(command_name)
|
86
|
+
|
87
|
+
if command.respond_to?(:env)
|
88
|
+
command.env
|
89
|
+
else
|
90
|
+
ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development'
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def forward_signals(pid)
|
95
|
+
(FORWARDED_SIGNALS & Signal.list.keys).each do |sig|
|
96
|
+
trap(sig) { Process.kill(sig, -Process.getpgid(pid)) }
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
data/lib/spring/commands.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
-
class
|
1
|
+
require 'active_support/core_ext/class/attribute'
|
2
|
+
|
3
|
+
module Spring
|
2
4
|
@commands = {}
|
3
5
|
|
4
6
|
class << self
|
@@ -13,56 +15,108 @@ class Spring
|
|
13
15
|
end
|
14
16
|
end
|
15
17
|
|
16
|
-
def self.
|
17
|
-
commands.
|
18
|
+
def self.command?(name)
|
19
|
+
commands.include? name
|
18
20
|
end
|
19
21
|
|
20
22
|
def self.command(name)
|
21
23
|
commands.fetch name
|
22
24
|
end
|
23
25
|
|
24
|
-
# Load custom commands, if any
|
25
|
-
begin
|
26
|
-
require "./config/spring"
|
27
|
-
rescue LoadError
|
28
|
-
end
|
29
|
-
|
30
26
|
module Commands
|
31
|
-
class
|
27
|
+
class Command
|
28
|
+
class_attribute :preloads
|
29
|
+
self.preloads = []
|
30
|
+
|
31
|
+
def setup
|
32
|
+
preload_files
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def preload_files
|
38
|
+
preloads.each do |file|
|
39
|
+
begin
|
40
|
+
require file
|
41
|
+
rescue LoadError => e
|
42
|
+
$stderr.puts <<-MESSAGE
|
43
|
+
The #{self.class} command tried to preload #{file} but could not find it.
|
44
|
+
You can configure what to preload in your `config/spring.rb` with:
|
45
|
+
#{self.class}.preloads = %w(files to preload)
|
46
|
+
MESSAGE
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
class Test < Command
|
53
|
+
self.preloads += %w(test_helper)
|
54
|
+
|
32
55
|
def env
|
33
56
|
"test"
|
34
57
|
end
|
35
58
|
|
36
59
|
def setup
|
37
60
|
$LOAD_PATH.unshift "test"
|
38
|
-
|
61
|
+
super
|
39
62
|
end
|
40
63
|
|
41
64
|
def call(args)
|
42
|
-
|
43
|
-
|
65
|
+
if args.size > 0
|
66
|
+
ARGV.replace args
|
67
|
+
path = File.expand_path(args.first)
|
68
|
+
|
69
|
+
if File.directory?(path)
|
70
|
+
Dir[File.join path, "**", "*_test.rb"].each { |f| require f }
|
71
|
+
else
|
72
|
+
require path
|
73
|
+
end
|
74
|
+
else
|
75
|
+
$stderr.puts "you need to specify what test to run: spring test TEST_NAME"
|
76
|
+
end
|
44
77
|
end
|
45
78
|
end
|
46
79
|
Spring.register_command "test", Test.new
|
47
80
|
|
48
|
-
class RSpec
|
81
|
+
class RSpec < Command
|
82
|
+
self.preloads += %w(spec_helper)
|
83
|
+
|
49
84
|
def env
|
50
85
|
"test"
|
51
86
|
end
|
52
87
|
|
53
88
|
def setup
|
54
89
|
$LOAD_PATH.unshift "spec"
|
55
|
-
|
90
|
+
super
|
91
|
+
require 'rspec/core'
|
56
92
|
end
|
57
93
|
|
58
94
|
def call(args)
|
95
|
+
$0 = "rspec"
|
59
96
|
::RSpec::Core::Runner.run(args)
|
60
97
|
end
|
61
98
|
end
|
62
99
|
Spring.register_command "rspec", RSpec.new
|
63
100
|
|
64
|
-
class
|
101
|
+
class Cucumber < Command
|
102
|
+
def env
|
103
|
+
"test"
|
104
|
+
end
|
105
|
+
|
106
|
+
def setup
|
107
|
+
super
|
108
|
+
require 'cucumber'
|
109
|
+
end
|
110
|
+
|
111
|
+
def call(args)
|
112
|
+
::Cucumber::Cli::Main.execute(args)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
Spring.register_command "cucumber", Cucumber.new
|
116
|
+
|
117
|
+
class Rake < Command
|
65
118
|
def setup
|
119
|
+
super
|
66
120
|
require "rake"
|
67
121
|
end
|
68
122
|
|
@@ -73,20 +127,22 @@ class Spring
|
|
73
127
|
end
|
74
128
|
Spring.register_command "rake", Rake.new
|
75
129
|
|
76
|
-
class Console
|
77
|
-
def setup
|
78
|
-
require "rails/commands/console"
|
79
|
-
end
|
80
130
|
|
131
|
+
class Console < Command
|
81
132
|
def call(args)
|
133
|
+
# This cannot be preloaded as it messes up the IRB prompt on OS X
|
134
|
+
# for unknown reasons. See discussion in issue #34.
|
135
|
+
require "rails/commands/console"
|
136
|
+
|
82
137
|
ARGV.replace args
|
83
138
|
::Rails::Console.start(::Rails.application)
|
84
139
|
end
|
85
140
|
end
|
86
141
|
Spring.register_command "console", Console.new, alias: "c"
|
87
142
|
|
88
|
-
class Generate
|
143
|
+
class Generate < Command
|
89
144
|
def setup
|
145
|
+
super
|
90
146
|
Rails.application.load_generators
|
91
147
|
end
|
92
148
|
|
@@ -97,7 +153,7 @@ class Spring
|
|
97
153
|
end
|
98
154
|
Spring.register_command "generate", Generate.new, alias: "g"
|
99
155
|
|
100
|
-
class Runner
|
156
|
+
class Runner < Command
|
101
157
|
def call(args)
|
102
158
|
Object.const_set(:APP_PATH, Rails.root.join('config/application'))
|
103
159
|
ARGV.replace args
|
@@ -106,4 +162,10 @@ class Spring
|
|
106
162
|
end
|
107
163
|
Spring.register_command "runner", Runner.new, alias: "r"
|
108
164
|
end
|
165
|
+
|
166
|
+
# Load custom commands, if any.
|
167
|
+
# needs to be at the end to allow modification of existing commands.
|
168
|
+
config = File.expand_path("./config/spring.rb")
|
169
|
+
require config if File.exist?(config)
|
170
|
+
|
109
171
|
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require "spring/errors"
|
2
|
+
|
3
|
+
module Spring
|
4
|
+
class << self
|
5
|
+
attr_accessor :application_root
|
6
|
+
|
7
|
+
def verify_environment!
|
8
|
+
application_root_path
|
9
|
+
end
|
10
|
+
|
11
|
+
def application_root_path
|
12
|
+
@application_root_path ||= begin
|
13
|
+
path = Pathname.new(File.expand_path(application_root || find_project_root))
|
14
|
+
raise MissingApplication.new(path) unless path.join("config/application.rb").exist?
|
15
|
+
path
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def find_project_root(current_dir = Pathname.new(Dir.pwd))
|
22
|
+
if current_dir.join("Gemfile").exist?
|
23
|
+
current_dir
|
24
|
+
elsif current_dir.root?
|
25
|
+
raise UnknownProject.new(Dir.pwd)
|
26
|
+
else
|
27
|
+
find_project_root(current_dir.parent)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/spring/env.rb
CHANGED
@@ -1,19 +1,25 @@
|
|
1
1
|
require "pathname"
|
2
|
-
require "spring/sid"
|
3
2
|
require "fileutils"
|
4
3
|
|
5
|
-
|
4
|
+
require "spring/version"
|
5
|
+
require "spring/sid"
|
6
|
+
|
7
|
+
module Spring
|
6
8
|
IGNORE_SIGNALS = %w(INT QUIT)
|
7
9
|
|
8
10
|
class Env
|
9
11
|
attr_reader :root
|
10
12
|
|
11
|
-
def initialize
|
12
|
-
@root = Pathname.new(File.expand_path('.'))
|
13
|
+
def initialize(root = nil)
|
14
|
+
@root = root || Pathname.new(File.expand_path('.'))
|
15
|
+
end
|
16
|
+
|
17
|
+
def version
|
18
|
+
Spring::VERSION
|
13
19
|
end
|
14
20
|
|
15
21
|
def tmp_path
|
16
|
-
path =
|
22
|
+
path = default_tmp_path
|
17
23
|
FileUtils.mkdir_p(path) unless path.exist?
|
18
24
|
path
|
19
25
|
end
|
@@ -29,5 +35,37 @@ class Spring
|
|
29
35
|
def pidfile_path
|
30
36
|
tmp_path.join("#{SID.sid}.pid")
|
31
37
|
end
|
38
|
+
|
39
|
+
def pid
|
40
|
+
pidfile_path.exist? ? pidfile_path.read.to_i : nil
|
41
|
+
end
|
42
|
+
|
43
|
+
def app_name
|
44
|
+
root.basename
|
45
|
+
end
|
46
|
+
|
47
|
+
def server_running?
|
48
|
+
if pidfile_path.exist?
|
49
|
+
pidfile = pidfile_path.open('r')
|
50
|
+
!pidfile.flock(File::LOCK_EX | File::LOCK_NB)
|
51
|
+
else
|
52
|
+
false
|
53
|
+
end
|
54
|
+
ensure
|
55
|
+
if pidfile
|
56
|
+
pidfile.flock(File::LOCK_UN)
|
57
|
+
pidfile.close
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def default_tmp_path
|
64
|
+
if ENV['SPRING_TMP_PATH']
|
65
|
+
Pathname.new(ENV['SPRING_TMP_PATH'])
|
66
|
+
else
|
67
|
+
root.join('tmp/spring')
|
68
|
+
end
|
69
|
+
end
|
32
70
|
end
|
33
71
|
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Spring
|
2
|
+
class ClientError < StandardError; end
|
3
|
+
|
4
|
+
class UnknownProject < ClientError
|
5
|
+
attr_reader :current_dir
|
6
|
+
|
7
|
+
def initialize(current_dir)
|
8
|
+
@current_dir = current_dir
|
9
|
+
end
|
10
|
+
|
11
|
+
def message
|
12
|
+
"Spring was unable to locate the root of your project. There was no Gemfile " \
|
13
|
+
"present in the current directory (#{current_dir}) or any of the parent " \
|
14
|
+
"directories."
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class MissingApplication < ClientError
|
19
|
+
attr_reader :project_root
|
20
|
+
|
21
|
+
def initialize(project_root)
|
22
|
+
@project_root = project_root
|
23
|
+
end
|
24
|
+
|
25
|
+
def message
|
26
|
+
"Spring was unable to find your config/application.rb file. " \
|
27
|
+
"Your project root was detected at #{project_root}, so spring " \
|
28
|
+
"looked for #{project_root}/config/application.rb but it doesn't exist. You can " \
|
29
|
+
"configure the root of your application by setting Spring.application_root in " \
|
30
|
+
"config/spring.rb."
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|