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