1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191
|
require 'set'
module Spring
module Client
class Binstub < Command
SHEBANG = /\#\!.*\n/
# If loading the bin/spring file works, it'll run spring which will
# eventually call Kernel.exit. This means that in the client process
# we will never execute the lines after this block. But if the spring
# client is not invoked for whatever reason, then the Kernel.exit won't
# happen, and so we'll fall back to the lines after this block, which
# should cause the "unsprung" version of the command to run.
LOADER = <<CODE
begin
load File.expand_path("../spring", __FILE__)
rescue LoadError
end
CODE
# The defined? check ensures these lines don't execute when we load the
# binstub from the application process. Which means that in the application
# process we'll execute the lines which come after the LOADER block, which
# is what we want.
#
# Parsing the lockfile in this way is pretty nasty but reliable enough
# The regex ensures that the match must be between a GEM line and an empty
# line, so it won't go on to the next section.
SPRING = <<'CODE'
#!/usr/bin/env ruby
# This file loads spring without using Bundler, in order to be fast
# It gets overwritten when you run the `spring binstub` command
unless defined?(Spring)
require "rubygems"
require "bundler"
if match = Bundler.default_lockfile.read.match(/^GEM$.*?^ spring \((.*?)\)$.*?^$/m)
ENV["GEM_PATH"] = ([Bundler.bundle_path.to_s] + Gem.path).join(File::PATH_SEPARATOR)
ENV["GEM_HOME"] = ""
Gem.paths = ENV
gem "spring", match[1]
require "spring/binstub"
end
end
CODE
OLD_BINSTUB = %{if !Process.respond_to?(:fork) || Gem::Specification.find_all_by_name("spring").empty?}
class Item
attr_reader :command, :existing
def initialize(command)
@command = command
if command.binstub.exist?
@existing = command.binstub.read
elsif command.name == "rails"
scriptfile = Spring.application_root_path.join("script/rails")
@existing = scriptfile.read if scriptfile.exist?
end
end
def status(text, stream = $stdout)
stream.puts "* #{command.binstub_name}: #{text}"
end
def add
if existing
if existing.include?(OLD_BINSTUB)
fallback = existing.match(/#{Regexp.escape OLD_BINSTUB}\n(.*)else/m)[1]
fallback.gsub!(/^ /, "")
fallback = nil if fallback.include?("exec")
generate(fallback)
status "upgraded"
elsif existing =~ /load .*spring/
status "spring already present"
else
head, shebang, tail = existing.partition(SHEBANG)
if shebang.include?("ruby")
unless command.binstub.exist?
FileUtils.touch command.binstub
command.binstub.chmod 0755
end
File.write(command.binstub, "#{head}#{shebang}#{LOADER}#{tail}")
status "spring inserted"
else
status "doesn't appear to be ruby, so cannot use spring", $stderr
exit 1
end
end
else
generate
status "generated with spring"
end
end
def generate(fallback = nil)
unless fallback
fallback = "require 'bundler/setup'\n" \
"load Gem.bin_path('#{command.gem_name}', '#{command.exec_name}')\n"
end
File.write(command.binstub, "#!/usr/bin/env ruby\n#{LOADER}#{fallback}")
command.binstub.chmod 0755
end
def remove
if existing
File.write(command.binstub, existing.sub(LOADER, ""))
status "spring removed"
end
end
end
attr_reader :bindir, :items
def self.description
"Generate spring based binstubs. Use --all to generate a binstub for all known commands."
end
def self.rails_command
@rails_command ||= CommandWrapper.new("rails")
end
def self.call(args)
require "spring/commands"
super
end
def initialize(args)
super
@bindir = env.root.join("bin")
@all = false
@mode = :add
@items = args.drop(1)
.map { |name| find_commands name }
.inject(Set.new, :|)
.map { |command| Item.new(command) }
end
def find_commands(name)
case name
when "--all"
@all = true
commands = Spring.commands.dup
commands.delete_if { |name, _| name.start_with?("rails_") }
commands.values + [self.class.rails_command]
when "--remove"
@mode = :remove
[]
when "rails"
[self.class.rails_command]
else
if command = Spring.commands[name]
[command]
else
$stderr.puts "The '#{name}' command is not known to spring."
exit 1
end
end
end
def call
case @mode
when :add
bindir.mkdir unless bindir.exist?
File.write(spring_binstub, SPRING)
spring_binstub.chmod 0755
items.each(&:add)
when :remove
spring_binstub.delete if @all
items.each(&:remove)
else
raise ArgumentError
end
end
def spring_binstub
bindir.join("spring")
end
end
end
end
|