shotgun 0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/COPYING +18 -0
- data/README +46 -0
- data/Rakefile +56 -0
- data/bin/shotgun +116 -0
- data/lib/shotgun.rb +82 -0
- data/shotgun.gemspec +33 -0
- metadata +70 -0
data/COPYING
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
Copyright (c) 2009 Ryan Tomayko <tomayko.com/about>
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to
|
5
|
+
deal in the Software without restriction, including without limitation the
|
6
|
+
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
7
|
+
sell copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
16
|
+
THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
17
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
18
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
Shotgun
|
2
|
+
|
3
|
+
This is an automatic reloading version of the rackup command that's shipped with
|
4
|
+
Rack. It can be used as an alternative to the complex reloading logic provided
|
5
|
+
by web frameworks or in environments that don't support application reloading.
|
6
|
+
|
7
|
+
The shotgun command starts one of Rack's supported servers (e.g., mongrel, thin,
|
8
|
+
webrick) and listens for requests but does not load any part of the actual
|
9
|
+
application. Each time a request is received, it forks, loads the application in
|
10
|
+
the child process, processes the request, and exits the child process. The
|
11
|
+
result is clean, application-wide reloading of all source files and templates on
|
12
|
+
each request.
|
13
|
+
|
14
|
+
Usage
|
15
|
+
-----
|
16
|
+
|
17
|
+
Installation:
|
18
|
+
|
19
|
+
gem install shotgun
|
20
|
+
|
21
|
+
Starting a server with a rackup file:
|
22
|
+
|
23
|
+
shotgun config.ru
|
24
|
+
|
25
|
+
Using Thin and starting on port 6000 instead of 9393 (default):
|
26
|
+
|
27
|
+
shotgun --server=thin --port=6000 config.ru
|
28
|
+
|
29
|
+
Running Sinatra apps:
|
30
|
+
|
31
|
+
shotgun hello.rb
|
32
|
+
|
33
|
+
See 'shotgun --help' for more advanced usage.
|
34
|
+
|
35
|
+
Links
|
36
|
+
-----
|
37
|
+
|
38
|
+
Shotgun: http://github.com/rtomayko/shotgun
|
39
|
+
Rack: http://rack.rubyforge.org/
|
40
|
+
Sinatra: http://www.sinatrarb.com/
|
41
|
+
|
42
|
+
The reloading system in Ian Bicking's webware framework served as inspiration
|
43
|
+
for the approach taken in Shotgun. Ian lays down the pros and cons of this
|
44
|
+
approach in the following article:
|
45
|
+
|
46
|
+
http://ianbicking.org/docs/Webware_reload.html
|
data/Rakefile
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'rake/clean'
|
2
|
+
require 'rake/testtask'
|
3
|
+
|
4
|
+
task :default => [:test]
|
5
|
+
task :spec => :test
|
6
|
+
|
7
|
+
Rake::TestTask.new(:test) do |t|
|
8
|
+
t.test_files = FileList['test/*_test.rb']
|
9
|
+
t.ruby_opts = ['-rubygems'] if defined? Gem
|
10
|
+
end
|
11
|
+
|
12
|
+
$spec =
|
13
|
+
begin
|
14
|
+
require 'rubygems/specification'
|
15
|
+
data = File.read('shotgun.gemspec')
|
16
|
+
spec = nil
|
17
|
+
Thread.new { spec = eval("$SAFE = 3\n#{data}") }.join
|
18
|
+
spec
|
19
|
+
end
|
20
|
+
|
21
|
+
def package(ext='')
|
22
|
+
"pkg/#{$spec.name}-#{$spec.version}" + ext
|
23
|
+
end
|
24
|
+
|
25
|
+
desc 'Build packages'
|
26
|
+
task :package => %w[.gem .tar.gz].map { |e| package(e) }
|
27
|
+
|
28
|
+
desc 'Build and install as local gem'
|
29
|
+
task :install => package('.gem') do
|
30
|
+
sh "gem install #{package('.gem')}"
|
31
|
+
end
|
32
|
+
|
33
|
+
directory 'pkg/'
|
34
|
+
CLOBBER.include('pkg')
|
35
|
+
|
36
|
+
file package('.gem') => %W[pkg/ #{$spec.name}.gemspec] + $spec.files do |f|
|
37
|
+
sh "gem build #{$spec.name}.gemspec"
|
38
|
+
mv File.basename(f.name), f.name
|
39
|
+
end
|
40
|
+
|
41
|
+
file package('.tar.gz') => %w[pkg/] + $spec.files do |f|
|
42
|
+
sh <<-SH
|
43
|
+
git archive \
|
44
|
+
--prefix=#{$spec.name}-#{$spec.version}/ \
|
45
|
+
--format=tar \
|
46
|
+
HEAD | gzip > #{f.name}
|
47
|
+
SH
|
48
|
+
end
|
49
|
+
|
50
|
+
desc 'Publish gem and tarball to rubyforge'
|
51
|
+
task 'publish:gem' => [package('.gem'), package('.tar.gz')] do |t|
|
52
|
+
sh <<-end
|
53
|
+
rubyforge add_release wink #{$spec.name} #{$spec.version} #{package('.gem')} &&
|
54
|
+
rubyforge add_file wink #{$spec.name} #{$spec.version} #{package('.tar.gz')}
|
55
|
+
end
|
56
|
+
end
|
data/bin/shotgun
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
libdir = "#{File.dirname(File.dirname(__FILE__))}/lib"
|
4
|
+
$LOAD_PATH.unshift libdir unless $LOAD_PATH.include?(libdir)
|
5
|
+
|
6
|
+
require 'optparse'
|
7
|
+
|
8
|
+
automatic = false
|
9
|
+
server = nil
|
10
|
+
env = ENV['RACK_ENV'] || 'development'
|
11
|
+
daemonize = false
|
12
|
+
pid = nil
|
13
|
+
options = {:Port => 9393, :Host => "0.0.0.0", :AccessLog => []}
|
14
|
+
|
15
|
+
opts = OptionParser.new("", 24, ' ') { |opts|
|
16
|
+
opts.banner = "Usage: shotgun [ruby options] [rack options] [rackup config]"
|
17
|
+
|
18
|
+
opts.separator ""
|
19
|
+
opts.separator "Ruby options:"
|
20
|
+
|
21
|
+
lineno = 1
|
22
|
+
opts.on("-e", "--eval LINE", "evaluate a LINE of code") { |line|
|
23
|
+
eval line, TOPLEVEL_BINDING, "-e", lineno
|
24
|
+
lineno += 1
|
25
|
+
}
|
26
|
+
|
27
|
+
opts.on("-d", "--debug", "set debugging flags (set $DEBUG to true)") {
|
28
|
+
$DEBUG = true
|
29
|
+
}
|
30
|
+
opts.on("-w", "--warn", "turn warnings on for your script") {
|
31
|
+
$-w = true
|
32
|
+
}
|
33
|
+
|
34
|
+
opts.on("-I", "--include PATH",
|
35
|
+
"specify $LOAD_PATH (may be used more than once)") { |path|
|
36
|
+
$LOAD_PATH.unshift(*path.split(":"))
|
37
|
+
}
|
38
|
+
|
39
|
+
opts.on("-r", "--require LIBRARY",
|
40
|
+
"require the library, before executing your script") { |library|
|
41
|
+
require library
|
42
|
+
}
|
43
|
+
|
44
|
+
opts.separator ""
|
45
|
+
opts.separator "Rack options:"
|
46
|
+
opts.on("-s", "--server SERVER", "server (webrick, mongrel, thin, etc.)") { |s|
|
47
|
+
server = s
|
48
|
+
}
|
49
|
+
|
50
|
+
opts.on("-o", "--host HOST", "listen on HOST (default: 0.0.0.0)") { |host|
|
51
|
+
options[:Host] = host
|
52
|
+
}
|
53
|
+
|
54
|
+
opts.on("-p", "--port PORT", "use PORT (default: 9393)") { |port|
|
55
|
+
options[:Port] = port
|
56
|
+
}
|
57
|
+
|
58
|
+
opts.on("-E", "--env ENVIRONMENT", "use ENVIRONMENT for defaults (default: development)") { |e|
|
59
|
+
env = e
|
60
|
+
}
|
61
|
+
|
62
|
+
opts.separator ""
|
63
|
+
opts.separator "Common options:"
|
64
|
+
|
65
|
+
opts.on_tail("-h", "--help", "Show this message") do
|
66
|
+
puts opts
|
67
|
+
exit
|
68
|
+
end
|
69
|
+
|
70
|
+
opts.on_tail("--version", "Show version") do
|
71
|
+
require 'rack'
|
72
|
+
puts "Rack #{Rack.version}"
|
73
|
+
exit
|
74
|
+
end
|
75
|
+
|
76
|
+
opts.parse! ARGV
|
77
|
+
}
|
78
|
+
|
79
|
+
config = ARGV[0] || "config.ru"
|
80
|
+
abort "configuration #{config} not found" unless File.exist? config
|
81
|
+
|
82
|
+
if config =~ /\.ru$/ && File.read(config)[/^#\\(.*)/]
|
83
|
+
opts.parse! $1.split(/\s+/)
|
84
|
+
end
|
85
|
+
|
86
|
+
require 'rack'
|
87
|
+
|
88
|
+
unless server = Rack::Handler.get(server)
|
89
|
+
begin
|
90
|
+
server = Rack::Handler::Mongrel
|
91
|
+
rescue LoadError => e
|
92
|
+
server = Rack::Handler::WEBrick
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
app_wrapper =
|
97
|
+
lambda do |inner_app|
|
98
|
+
if env == 'development'
|
99
|
+
Rack::Builder.new {
|
100
|
+
use Rack::ShowExceptions
|
101
|
+
use Rack::Lint
|
102
|
+
run inner_app
|
103
|
+
}.to_app
|
104
|
+
else
|
105
|
+
inner_app
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
ENV['RACK_ENV'] = env
|
110
|
+
|
111
|
+
require 'shotgun'
|
112
|
+
app = Shotgun.new(config, app_wrapper)
|
113
|
+
|
114
|
+
server.run app, options do |inst|
|
115
|
+
puts "== Shotgun starting #{server.to_s} on #{options[:Host]}:#{options[:Port]}"
|
116
|
+
end
|
data/lib/shotgun.rb
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'rack'
|
2
|
+
require 'thread'
|
3
|
+
|
4
|
+
class Shotgun
|
5
|
+
attr_reader :rackup_file
|
6
|
+
|
7
|
+
def initialize(rackup_file, wrapper=nil)
|
8
|
+
@rackup_file = rackup_file
|
9
|
+
@wrapper = wrapper || lambda { |inner_app| inner_app }
|
10
|
+
end
|
11
|
+
|
12
|
+
def call(env)
|
13
|
+
dup.call!(env)
|
14
|
+
end
|
15
|
+
|
16
|
+
def call!(env)
|
17
|
+
@env = env
|
18
|
+
@reader, @writer = IO.pipe
|
19
|
+
|
20
|
+
# Disable GC before forking in an attempt to get some advantage
|
21
|
+
# out of COW.
|
22
|
+
GC.disable
|
23
|
+
|
24
|
+
if fork
|
25
|
+
proceed_as_parent
|
26
|
+
else
|
27
|
+
proceed_as_child
|
28
|
+
end
|
29
|
+
|
30
|
+
ensure
|
31
|
+
GC.enable
|
32
|
+
end
|
33
|
+
|
34
|
+
# ==== Stuff that happens in the parent process
|
35
|
+
|
36
|
+
def proceed_as_parent
|
37
|
+
@writer.close
|
38
|
+
status, headers, body = Marshal.load(@reader)
|
39
|
+
@reader.close
|
40
|
+
Process.wait
|
41
|
+
[status, headers, body]
|
42
|
+
end
|
43
|
+
|
44
|
+
# ==== Stuff that happens in the forked child process.
|
45
|
+
|
46
|
+
def proceed_as_child
|
47
|
+
@reader.close
|
48
|
+
app = assemble_app
|
49
|
+
status, headers, body = app.call(@env)
|
50
|
+
Marshal.dump([status, headers.to_hash, slurp(body)], @writer)
|
51
|
+
@writer.close
|
52
|
+
exit! 0
|
53
|
+
end
|
54
|
+
|
55
|
+
def assemble_app
|
56
|
+
@wrapper.call(inner_app)
|
57
|
+
end
|
58
|
+
|
59
|
+
def inner_app
|
60
|
+
if rackup_file =~ /\.ru$/
|
61
|
+
config = File.read(rackup_file)
|
62
|
+
eval "Rack::Builder.new {( #{config}\n )}.to_app", nil, rackup_file
|
63
|
+
else
|
64
|
+
require rackup_file
|
65
|
+
if defined? Sinatra::Application
|
66
|
+
Sinatra::Application.set :reload, false
|
67
|
+
Sinatra::Application
|
68
|
+
else
|
69
|
+
Object.const_get(File.basename(rackup_file, '.rb').capitalize)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def slurp(body)
|
75
|
+
return body if body.respond_to? :to_ary
|
76
|
+
return [body] if body.respond_to? :to_str
|
77
|
+
|
78
|
+
buf = []
|
79
|
+
body.each { |part| buf << part }
|
80
|
+
buf
|
81
|
+
end
|
82
|
+
end
|
data/shotgun.gemspec
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.specification_version = 2 if s.respond_to? :specification_version=
|
3
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
4
|
+
|
5
|
+
s.name = 'shotgun'
|
6
|
+
s.version = '0.1'
|
7
|
+
s.date = '2009-02-20'
|
8
|
+
|
9
|
+
s.description = "Because reloading sucks."
|
10
|
+
s.summary = s.description
|
11
|
+
|
12
|
+
s.authors = ["Ryan Tomayko"]
|
13
|
+
s.email = "r@tomayko.com"
|
14
|
+
|
15
|
+
s.files = %w[
|
16
|
+
README
|
17
|
+
COPYING
|
18
|
+
Rakefile
|
19
|
+
shotgun.gemspec
|
20
|
+
lib/shotgun.rb
|
21
|
+
bin/shotgun
|
22
|
+
]
|
23
|
+
s.executables = ['shotgun']
|
24
|
+
s.test_files = s.files.select {|path| path =~ /^test\/.*_test.rb/}
|
25
|
+
|
26
|
+
s.extra_rdoc_files = %w[README]
|
27
|
+
s.add_dependency 'rack', '>= 0.9.1', '< 1.0'
|
28
|
+
|
29
|
+
s.homepage = "http://github.com/rtomayko/shotgun/"
|
30
|
+
s.require_paths = %w[lib]
|
31
|
+
s.rubyforge_project = 'wink'
|
32
|
+
s.rubygems_version = '1.1.1'
|
33
|
+
end
|
metadata
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: shotgun
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: "0.1"
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ryan Tomayko
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-02-20 00:00:00 -08:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: rack
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.9.1
|
24
|
+
- - <
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: "1.0"
|
27
|
+
version:
|
28
|
+
description: Because reloading sucks.
|
29
|
+
email: r@tomayko.com
|
30
|
+
executables:
|
31
|
+
- shotgun
|
32
|
+
extensions: []
|
33
|
+
|
34
|
+
extra_rdoc_files:
|
35
|
+
- README
|
36
|
+
files:
|
37
|
+
- README
|
38
|
+
- COPYING
|
39
|
+
- Rakefile
|
40
|
+
- shotgun.gemspec
|
41
|
+
- lib/shotgun.rb
|
42
|
+
- bin/shotgun
|
43
|
+
has_rdoc: false
|
44
|
+
homepage: http://github.com/rtomayko/shotgun/
|
45
|
+
post_install_message:
|
46
|
+
rdoc_options: []
|
47
|
+
|
48
|
+
require_paths:
|
49
|
+
- lib
|
50
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: "0"
|
55
|
+
version:
|
56
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: "0"
|
61
|
+
version:
|
62
|
+
requirements: []
|
63
|
+
|
64
|
+
rubyforge_project: wink
|
65
|
+
rubygems_version: 1.3.1
|
66
|
+
signing_key:
|
67
|
+
specification_version: 2
|
68
|
+
summary: Because reloading sucks.
|
69
|
+
test_files: []
|
70
|
+
|