tommygun 0.4
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/COPYING +18 -0
- data/README +46 -0
- data/Rakefile +56 -0
- data/bin/tommygun +143 -0
- data/lib/tommygun.rb +100 -0
- data/tommygun.gemspec +34 -0
- metadata +82 -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/spec_*.rb']
|
9
|
+
t.ruby_opts = ['-rubygems'] if defined? Gem
|
10
|
+
end
|
11
|
+
|
12
|
+
$spec =
|
13
|
+
begin
|
14
|
+
require 'rubygems/specification'
|
15
|
+
data = File.read('tommygun.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 'release' => [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/tommygun
ADDED
@@ -0,0 +1,143 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'optparse'
|
4
|
+
|
5
|
+
begin
|
6
|
+
require 'thin'
|
7
|
+
server = 'thin'
|
8
|
+
rescue LoadError
|
9
|
+
server = nil
|
10
|
+
end
|
11
|
+
|
12
|
+
env = ENV['RACK_ENV'] || 'development'
|
13
|
+
browse = false
|
14
|
+
options = {:Port => 4567, :Host => 'localhost', :AccessLog => [], :Path => '/'}
|
15
|
+
|
16
|
+
opts = OptionParser.new("", 24, ' ') { |opts|
|
17
|
+
opts.banner = "Usage: shotgun [ruby options] [rack options] [rackup config]"
|
18
|
+
|
19
|
+
opts.separator ""
|
20
|
+
opts.separator "Ruby options:"
|
21
|
+
|
22
|
+
lineno = 1
|
23
|
+
opts.on("-e", "--eval LINE", "evaluate a LINE of code") { |line|
|
24
|
+
eval line, TOPLEVEL_BINDING, "-e", lineno
|
25
|
+
lineno += 1
|
26
|
+
}
|
27
|
+
|
28
|
+
opts.on("-d", "--debug", "set debugging flags (set $DEBUG to true)") {
|
29
|
+
$DEBUG = true
|
30
|
+
}
|
31
|
+
opts.on("-w", "--warn", "turn warnings on for your script") {
|
32
|
+
$-w = true
|
33
|
+
}
|
34
|
+
|
35
|
+
opts.on("-I", "--include PATH",
|
36
|
+
"specify $LOAD_PATH (may be used more than once)") { |path|
|
37
|
+
$LOAD_PATH.unshift(*path.split(":"))
|
38
|
+
}
|
39
|
+
|
40
|
+
opts.on("-r", "--require LIBRARY",
|
41
|
+
"require the library, before executing your script") { |library|
|
42
|
+
require library
|
43
|
+
}
|
44
|
+
|
45
|
+
opts.separator ""
|
46
|
+
opts.separator "Rack options:"
|
47
|
+
opts.on("-s", "--server SERVER", "server (webrick, mongrel, thin, etc.)") { |s|
|
48
|
+
server = s
|
49
|
+
}
|
50
|
+
|
51
|
+
opts.on("-o", "--host HOST", "listen on HOST (default: 0.0.0.0)") { |host|
|
52
|
+
options[:Host] = host
|
53
|
+
}
|
54
|
+
|
55
|
+
opts.on("-p", "--port PORT", "use PORT (default: 4567)") { |port|
|
56
|
+
options[:Port] = port
|
57
|
+
}
|
58
|
+
|
59
|
+
opts.on("-E", "--env ENVIRONMENT", "use ENVIRONMENT for defaults (default: development)") { |e|
|
60
|
+
env = e
|
61
|
+
}
|
62
|
+
|
63
|
+
opts.separator ""
|
64
|
+
opts.separator "Tommygun options:"
|
65
|
+
|
66
|
+
opts.on("-u", "--url URL", "specify url path (default: /)") { |url|
|
67
|
+
options[:Path] = url
|
68
|
+
}
|
69
|
+
|
70
|
+
opts.on("-O", "--browse", "open browser immediately after starting") { |browse|
|
71
|
+
browse = true
|
72
|
+
}
|
73
|
+
|
74
|
+
opts.on_tail("-h", "--help", "show this message") do
|
75
|
+
puts opts
|
76
|
+
exit
|
77
|
+
end
|
78
|
+
|
79
|
+
opts.on_tail("--version", "show version") do
|
80
|
+
require 'rack'
|
81
|
+
puts "Rack #{Rack.version}"
|
82
|
+
exit
|
83
|
+
end
|
84
|
+
|
85
|
+
opts.parse! ARGV
|
86
|
+
}
|
87
|
+
|
88
|
+
config = ARGV[0] || "config.ru"
|
89
|
+
abort "configuration #{config} not found" unless File.exist? config
|
90
|
+
|
91
|
+
if config =~ /\.ru$/ && File.read(config)[/^#\\(.*)/]
|
92
|
+
opts.parse! $1.split(/\s+/)
|
93
|
+
end
|
94
|
+
|
95
|
+
require 'rack'
|
96
|
+
require 'rack/utils'
|
97
|
+
require 'rack/request'
|
98
|
+
require 'rack/response'
|
99
|
+
require 'rack/lint'
|
100
|
+
require 'rack/commonlogger'
|
101
|
+
require 'rack/showexceptions'
|
102
|
+
|
103
|
+
unless server = Rack::Handler.get(server)
|
104
|
+
begin
|
105
|
+
server = Rack::Handler::Mongrel
|
106
|
+
rescue LoadError => e
|
107
|
+
server = Rack::Handler::WEBrick
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
app_wrapper =
|
112
|
+
lambda do |inner_app|
|
113
|
+
case env
|
114
|
+
when 'development'
|
115
|
+
Rack::Builder.new {
|
116
|
+
use Rack::CommonLogger, STDERR unless server.name =~ /CGI/
|
117
|
+
use Rack::ShowExceptions
|
118
|
+
use Rack::Lint
|
119
|
+
run inner_app
|
120
|
+
}.to_app
|
121
|
+
when 'deployment', 'production'
|
122
|
+
Rack::Builder.new {
|
123
|
+
use Rack::CommonLogger, STDERR unless server.name =~ /CGI/
|
124
|
+
run inner_app
|
125
|
+
}.to_app
|
126
|
+
else
|
127
|
+
inner_app
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
ENV['RACK_ENV'] = env
|
132
|
+
|
133
|
+
require 'tommygun'
|
134
|
+
app = Tommygun.new(config, app_wrapper)
|
135
|
+
|
136
|
+
server.run app, options do |inst|
|
137
|
+
puts "== Tommygun starting #{server.to_s} on http://#{options[:Host]}:#{options[:Port]}"
|
138
|
+
|
139
|
+
if browse
|
140
|
+
require 'launchy'
|
141
|
+
Launchy.open("http://#{options[:Host]}:#{options[:Port]}#{options[:Path]}")
|
142
|
+
end
|
143
|
+
end
|
data/lib/tommygun.rb
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
require 'rack'
|
2
|
+
require 'rack/utils'
|
3
|
+
require 'thread'
|
4
|
+
|
5
|
+
class Tommygun
|
6
|
+
include Rack::Utils
|
7
|
+
attr_reader :rackup_file
|
8
|
+
|
9
|
+
def initialize(rackup_file, wrapper=nil)
|
10
|
+
@rackup_file = rackup_file
|
11
|
+
@wrapper = wrapper || lambda { |inner_app| inner_app }
|
12
|
+
end
|
13
|
+
|
14
|
+
def call(env)
|
15
|
+
dup.call!(env)
|
16
|
+
end
|
17
|
+
|
18
|
+
def call!(env)
|
19
|
+
@env = env
|
20
|
+
@reader, @writer = IO.pipe
|
21
|
+
|
22
|
+
# Disable GC before forking in an attempt to get some advantage
|
23
|
+
# out of COW.
|
24
|
+
GC.disable
|
25
|
+
|
26
|
+
if fork
|
27
|
+
proceed_as_parent
|
28
|
+
else
|
29
|
+
proceed_as_child
|
30
|
+
end
|
31
|
+
|
32
|
+
ensure
|
33
|
+
GC.enable
|
34
|
+
end
|
35
|
+
|
36
|
+
# ==== Stuff that happens in the parent process
|
37
|
+
|
38
|
+
def proceed_as_parent
|
39
|
+
rand # Reseeds
|
40
|
+
@writer.close
|
41
|
+
result = Marshal.load(@reader)
|
42
|
+
@reader.close
|
43
|
+
Process.wait
|
44
|
+
if result.length == 3
|
45
|
+
result
|
46
|
+
else
|
47
|
+
[500, {'Content-Type'=>'text/html;charset=utf-8'}, [format_error(result)]]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def format_error(result)
|
52
|
+
message, backtrace = result
|
53
|
+
"<h1>FAIL</h1><h3>#{escape_html(message)}</h3>" +
|
54
|
+
"<pre>#{escape_html(backtrace.join("\n"))}</pre>"
|
55
|
+
end
|
56
|
+
|
57
|
+
# ==== Stuff that happens in the forked child process.
|
58
|
+
|
59
|
+
def proceed_as_child
|
60
|
+
@reader.close
|
61
|
+
app = assemble_app
|
62
|
+
status, headers, body = app.call(@env)
|
63
|
+
Marshal.dump([status, headers.to_hash, slurp(body)], @writer)
|
64
|
+
@writer.close
|
65
|
+
rescue Object => boom
|
66
|
+
Marshal.dump(["#{boom.class.name}: #{boom.to_s}", boom.backtrace], @writer)
|
67
|
+
ensure
|
68
|
+
exit! 0
|
69
|
+
end
|
70
|
+
|
71
|
+
def assemble_app
|
72
|
+
@wrapper.call(inner_app)
|
73
|
+
end
|
74
|
+
|
75
|
+
def inner_app
|
76
|
+
if rackup_file =~ /\.ru$/
|
77
|
+
config = File.read(rackup_file)
|
78
|
+
eval "Rack::Builder.new {( #{config}\n )}.to_app", nil, rackup_file
|
79
|
+
else
|
80
|
+
require rackup_file
|
81
|
+
if defined? Sinatra::Application
|
82
|
+
Sinatra::Application.set :reload, false
|
83
|
+
Sinatra::Application.set :logging, false
|
84
|
+
Sinatra::Application.set :raise_errors, true
|
85
|
+
Sinatra::Application
|
86
|
+
else
|
87
|
+
Object.const_get(File.basename(rackup_file, '.rb').capitalize)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def slurp(body)
|
93
|
+
return body if body.respond_to? :to_ary
|
94
|
+
return [body] if body.respond_to? :to_str
|
95
|
+
|
96
|
+
buf = []
|
97
|
+
body.each { |part| buf << part }
|
98
|
+
buf
|
99
|
+
end
|
100
|
+
end
|
data/tommygun.gemspec
ADDED
@@ -0,0 +1,34 @@
|
|
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 = 'tommygun'
|
6
|
+
s.version = '0.4'
|
7
|
+
s.date = '2009-03-22'
|
8
|
+
|
9
|
+
s.description = "Shotgun with default port of 4567."
|
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
|
+
tommygun.gemspec
|
20
|
+
lib/tommygun.rb
|
21
|
+
bin/tommygun
|
22
|
+
]
|
23
|
+
s.executables = ['tommygun']
|
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'
|
28
|
+
s.add_dependency 'launchy', '>= 0.3.3', '< 1.0'
|
29
|
+
|
30
|
+
s.homepage = "http://github.com/rtomayko/shotgun/"
|
31
|
+
s.require_paths = %w[lib]
|
32
|
+
s.rubyforge_project = 'wink'
|
33
|
+
s.rubygems_version = '1.1.1'
|
34
|
+
end
|
metadata
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: tommygun
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: "0.4"
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ryan Tomayko
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-03-22 00:00:00 -07: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
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: launchy
|
27
|
+
type: :runtime
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.3.3
|
34
|
+
- - <
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: "1.0"
|
37
|
+
version:
|
38
|
+
description: Shotgun with default port of 4567.
|
39
|
+
email: r@tomayko.com
|
40
|
+
executables:
|
41
|
+
- tommygun
|
42
|
+
extensions: []
|
43
|
+
|
44
|
+
extra_rdoc_files:
|
45
|
+
- README
|
46
|
+
files:
|
47
|
+
- README
|
48
|
+
- COPYING
|
49
|
+
- Rakefile
|
50
|
+
- tommygun.gemspec
|
51
|
+
- lib/tommygun.rb
|
52
|
+
- bin/tommygun
|
53
|
+
has_rdoc: true
|
54
|
+
homepage: http://github.com/rtomayko/shotgun/
|
55
|
+
licenses: []
|
56
|
+
|
57
|
+
post_install_message:
|
58
|
+
rdoc_options: []
|
59
|
+
|
60
|
+
require_paths:
|
61
|
+
- lib
|
62
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: "0"
|
67
|
+
version:
|
68
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
69
|
+
requirements:
|
70
|
+
- - ">="
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: "0"
|
73
|
+
version:
|
74
|
+
requirements: []
|
75
|
+
|
76
|
+
rubyforge_project: wink
|
77
|
+
rubygems_version: 1.3.5
|
78
|
+
signing_key:
|
79
|
+
specification_version: 2
|
80
|
+
summary: Shotgun with default port of 4567.
|
81
|
+
test_files: []
|
82
|
+
|