shotgun 0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (7) hide show
  1. data/COPYING +18 -0
  2. data/README +46 -0
  3. data/Rakefile +56 -0
  4. data/bin/shotgun +116 -0
  5. data/lib/shotgun.rb +82 -0
  6. data/shotgun.gemspec +33 -0
  7. 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
+