spring 0.0.5 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,18 @@
1
+ require "spring/env"
2
+
3
+ module Spring
4
+ module Client
5
+ class Command
6
+ def self.call(args)
7
+ new(args).call
8
+ end
9
+
10
+ attr_reader :args, :env
11
+
12
+ def initialize(args)
13
+ @args = args
14
+ @env = Env.new
15
+ end
16
+ end
17
+ end
18
+ end
@@ -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
@@ -0,0 +1,11 @@
1
+ require "spring/version"
2
+
3
+ module Spring
4
+ module Client
5
+ class Stop < Command
6
+ def call
7
+ Process.kill('SIGTERM', env.pid) if env.pid
8
+ end
9
+ end
10
+ end
11
+ end
@@ -1,4 +1,6 @@
1
- class Spring
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.command_registered?(name)
17
- commands.has_key?(name)
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 Test
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
- require "test_helper"
61
+ super
39
62
  end
40
63
 
41
64
  def call(args)
42
- ARGV.replace args
43
- require File.expand_path(args.first)
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
- require "spec_helper"
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 Rake
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
@@ -1,19 +1,25 @@
1
1
  require "pathname"
2
- require "spring/sid"
3
2
  require "fileutils"
4
3
 
5
- class Spring
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 = root.join('tmp/spring')
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