adamwiggins-sinatra 0.8.9 → 0.10.1
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/AUTHORS +8 -7
- data/CHANGES +211 -1
- data/LICENSE +1 -1
- data/README.rdoc +183 -139
- data/Rakefile +20 -81
- data/lib/sinatra.rb +5 -1
- data/lib/sinatra/base.rb +569 -278
- data/lib/sinatra/main.rb +12 -25
- data/lib/sinatra/showexceptions.rb +303 -0
- data/sinatra.gemspec +20 -44
- data/test/base_test.rb +140 -52
- data/test/builder_test.rb +14 -17
- data/test/contest.rb +64 -0
- data/test/erb_test.rb +42 -16
- data/test/extensions_test.rb +100 -0
- data/test/filter_test.rb +85 -13
- data/test/haml_test.rb +39 -21
- data/test/helper.rb +76 -0
- data/test/helpers_test.rb +219 -84
- data/test/mapped_error_test.rb +168 -146
- data/test/middleware_test.rb +22 -17
- data/test/options_test.rb +323 -54
- data/test/render_backtrace_test.rb +145 -0
- data/test/request_test.rb +28 -6
- data/test/response_test.rb +42 -0
- data/test/result_test.rb +27 -21
- data/test/route_added_hook_test.rb +59 -0
- data/test/routing_test.rb +558 -77
- data/test/sass_test.rb +52 -13
- data/test/server_test.rb +47 -0
- data/test/sinatra_test.rb +3 -5
- data/test/static_test.rb +57 -30
- data/test/templates_test.rb +74 -25
- data/test/views/error.builder +3 -0
- data/test/views/error.erb +3 -0
- data/test/views/error.haml +3 -0
- data/test/views/error.sass +2 -0
- data/test/views/foo/hello.test +1 -0
- metadata +50 -46
- data/compat/app_test.rb +0 -300
- data/compat/application_test.rb +0 -334
- data/compat/builder_test.rb +0 -101
- data/compat/custom_error_test.rb +0 -62
- data/compat/erb_test.rb +0 -136
- data/compat/events_test.rb +0 -75
- data/compat/filter_test.rb +0 -30
- data/compat/haml_test.rb +0 -233
- data/compat/helper.rb +0 -21
- data/compat/mapped_error_test.rb +0 -72
- data/compat/pipeline_test.rb +0 -71
- data/compat/public/foo.xml +0 -1
- data/compat/sass_test.rb +0 -57
- data/compat/sessions_test.rb +0 -39
- data/compat/streaming_test.rb +0 -121
- data/compat/sym_params_test.rb +0 -19
- data/compat/template_test.rb +0 -30
- data/compat/use_in_file_templates_test.rb +0 -47
- data/compat/views/foo.builder +0 -1
- data/compat/views/foo.erb +0 -1
- data/compat/views/foo.haml +0 -1
- data/compat/views/foo.sass +0 -2
- data/compat/views/foo_layout.erb +0 -2
- data/compat/views/foo_layout.haml +0 -2
- data/compat/views/layout_test/foo.builder +0 -1
- data/compat/views/layout_test/foo.erb +0 -1
- data/compat/views/layout_test/foo.haml +0 -1
- data/compat/views/layout_test/foo.sass +0 -2
- data/compat/views/layout_test/layout.builder +0 -3
- data/compat/views/layout_test/layout.erb +0 -1
- data/compat/views/layout_test/layout.haml +0 -1
- data/compat/views/layout_test/layout.sass +0 -2
- data/compat/views/no_layout/no_layout.builder +0 -1
- data/compat/views/no_layout/no_layout.haml +0 -1
- data/lib/sinatra/compat.rb +0 -239
- data/lib/sinatra/test.rb +0 -112
- data/lib/sinatra/test/rspec.rb +0 -2
- data/lib/sinatra/test/spec.rb +0 -2
- data/lib/sinatra/test/unit.rb +0 -11
- data/test/reload_test.rb +0 -65
data/Rakefile
CHANGED
@@ -1,26 +1,15 @@
|
|
1
|
-
require 'rubygems'
|
2
1
|
require 'rake/clean'
|
2
|
+
require 'rake/testtask'
|
3
3
|
require 'fileutils'
|
4
4
|
|
5
5
|
task :default => :test
|
6
|
+
task :spec => :test
|
6
7
|
|
7
8
|
# SPECS ===============================================================
|
8
9
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
sh "specrb --testcase '#{pattern}' --specdox -Ilib:test test/*_test.rb"
|
13
|
-
end
|
14
|
-
|
15
|
-
desc 'Run specs with unit test style output'
|
16
|
-
task :test do |t|
|
17
|
-
sh "specrb -Ilib:test test/*_test.rb"
|
18
|
-
end
|
19
|
-
|
20
|
-
desc 'Run compatibility specs'
|
21
|
-
task :compat do |t|
|
22
|
-
pattern = ENV['TEST'] || '.*'
|
23
|
-
sh "specrb --testcase '#{pattern}' -Ilib:test compat/*_test.rb"
|
10
|
+
Rake::TestTask.new(:test) do |t|
|
11
|
+
t.test_files = FileList['test/*_test.rb']
|
12
|
+
t.ruby_opts = ['-rubygems'] if defined? Gem
|
24
13
|
end
|
25
14
|
|
26
15
|
# PACKAGING ============================================================
|
@@ -38,7 +27,7 @@ def spec
|
|
38
27
|
end
|
39
28
|
|
40
29
|
def package(ext='')
|
41
|
-
"
|
30
|
+
"pkg/sinatra-#{spec.version}" + ext
|
42
31
|
end
|
43
32
|
|
44
33
|
desc 'Build packages'
|
@@ -49,25 +38,27 @@ task :install => package('.gem') do
|
|
49
38
|
sh "gem install #{package('.gem')}"
|
50
39
|
end
|
51
40
|
|
52
|
-
directory '
|
41
|
+
directory 'pkg/'
|
42
|
+
CLOBBER.include('pkg')
|
53
43
|
|
54
|
-
file package('.gem') => %w[
|
44
|
+
file package('.gem') => %w[pkg/ sinatra.gemspec] + spec.files do |f|
|
55
45
|
sh "gem build sinatra.gemspec"
|
56
46
|
mv File.basename(f.name), f.name
|
57
47
|
end
|
58
48
|
|
59
|
-
file package('.tar.gz') => %w[
|
60
|
-
sh
|
49
|
+
file package('.tar.gz') => %w[pkg/] + spec.files do |f|
|
50
|
+
sh <<-SH
|
51
|
+
git archive \
|
52
|
+
--prefix=sinatra-#{source_version}/ \
|
53
|
+
--format=tar \
|
54
|
+
HEAD | gzip > #{f.name}
|
55
|
+
SH
|
61
56
|
end
|
62
57
|
|
63
58
|
# Rubyforge Release / Publish Tasks ==================================
|
64
59
|
|
65
|
-
desc 'Publish
|
66
|
-
task '
|
67
|
-
sh 'scp -rp doc/* rubyforge.org:/var/www/gforge-projects/sinatra/'
|
68
|
-
end
|
69
|
-
|
70
|
-
task 'publish:gem' => [package('.gem'), package('.tar.gz')] do |t|
|
60
|
+
desc 'Publish gem and tarball to rubyforge'
|
61
|
+
task 'release' => [package('.gem'), package('.tar.gz')] do |t|
|
71
62
|
sh <<-end
|
72
63
|
rubyforge add_release sinatra sinatra #{spec.version} #{package('.gem')} &&
|
73
64
|
rubyforge add_file sinatra sinatra #{spec.version} #{package('.tar.gz')}
|
@@ -78,7 +69,7 @@ end
|
|
78
69
|
# Building docs requires HAML and the hanna gem:
|
79
70
|
# gem install mislav-hanna --source=http://gems.github.com
|
80
71
|
|
81
|
-
task 'doc' => ['doc:api'
|
72
|
+
task 'doc' => ['doc:api']
|
82
73
|
|
83
74
|
desc 'Generate Hanna RDoc under doc/api'
|
84
75
|
task 'doc:api' => ['doc/api/index.html']
|
@@ -98,53 +89,6 @@ file 'doc/api/index.html' => FileList['lib/**/*.rb','README.rdoc'] do |f|
|
|
98
89
|
end
|
99
90
|
CLEAN.include 'doc/api'
|
100
91
|
|
101
|
-
def rdoc_to_html(file_name)
|
102
|
-
require 'rdoc/markup/to_html'
|
103
|
-
rdoc = RDoc::Markup::ToHtml.new
|
104
|
-
rdoc.convert(File.read(file_name))
|
105
|
-
end
|
106
|
-
|
107
|
-
def haml(locals={})
|
108
|
-
require 'haml'
|
109
|
-
template = File.read('doc/template.haml')
|
110
|
-
haml = Haml::Engine.new(template, :format => :html4, :attr_wrapper => '"')
|
111
|
-
haml.render(Object.new, locals)
|
112
|
-
end
|
113
|
-
|
114
|
-
desc 'Build website HTML and stuff'
|
115
|
-
task 'doc:site' => ['doc/index.html', 'doc/book.html']
|
116
|
-
|
117
|
-
file 'doc/index.html' => %w[README.rdoc doc/template.haml] do |file|
|
118
|
-
File.open(file.name, 'w') do |file|
|
119
|
-
file << haml(:title => 'Sinatra', :content => rdoc_to_html('README.rdoc'))
|
120
|
-
end
|
121
|
-
end
|
122
|
-
CLEAN.include 'doc/index.html'
|
123
|
-
|
124
|
-
file 'doc/book.html' => ['book/output/sinatra-book.html'] do |file|
|
125
|
-
File.open(file.name, 'w') do |file|
|
126
|
-
book_content = File.read('book/output/sinatra-book.html')
|
127
|
-
file << haml(:title => 'Sinatra Book', :content => book_content)
|
128
|
-
end
|
129
|
-
end
|
130
|
-
CLEAN.include 'doc/book.html'
|
131
|
-
|
132
|
-
file 'book/output/sinatra-book.html' => FileList['book/**'] do |f|
|
133
|
-
unless File.directory?('book')
|
134
|
-
sh 'git clone git://github.com/cschneid/sinatra-book.git book'
|
135
|
-
end
|
136
|
-
sh((<<-SH).strip.gsub(/\s+/, ' '))
|
137
|
-
cd book &&
|
138
|
-
git fetch origin &&
|
139
|
-
git rebase origin/master &&
|
140
|
-
thor book:build
|
141
|
-
SH
|
142
|
-
end
|
143
|
-
CLEAN.include 'book/output/sinatra-book.html'
|
144
|
-
|
145
|
-
desc 'Build the Sinatra book'
|
146
|
-
task 'doc:book' => ['book/output/sinatra-book.html']
|
147
|
-
|
148
92
|
# Gemspec Helpers ====================================================
|
149
93
|
|
150
94
|
def source_version
|
@@ -152,12 +96,7 @@ def source_version
|
|
152
96
|
line.match(/.*VERSION = '(.*)'/)[1]
|
153
97
|
end
|
154
98
|
|
155
|
-
|
156
|
-
FileList[
|
157
|
-
'{lib,test,compat,images}/**',
|
158
|
-
'Rakefile', 'CHANGES', 'README.rdoc'
|
159
|
-
]
|
160
|
-
file 'sinatra.gemspec' => project_files do |f|
|
99
|
+
task 'sinatra.gemspec' => FileList['{lib,test,compat}/**','Rakefile','CHANGES','*.rdoc'] do |f|
|
161
100
|
# read spec file and split out manifest section
|
162
101
|
spec = File.read(f.name)
|
163
102
|
head, manifest, tail = spec.split(" # = MANIFEST =\n")
|
data/lib/sinatra.rb
CHANGED
data/lib/sinatra/base.rb
CHANGED
@@ -1,32 +1,42 @@
|
|
1
|
+
require 'thread'
|
1
2
|
require 'time'
|
2
3
|
require 'uri'
|
3
4
|
require 'rack'
|
4
5
|
require 'rack/builder'
|
6
|
+
require 'sinatra/showexceptions'
|
5
7
|
|
6
8
|
module Sinatra
|
7
|
-
VERSION = '0.
|
9
|
+
VERSION = '0.10.1'
|
8
10
|
|
11
|
+
# The request object. See Rack::Request for more info:
|
12
|
+
# http://rack.rubyforge.org/doc/classes/Rack/Request.html
|
9
13
|
class Request < Rack::Request
|
10
14
|
def user_agent
|
11
15
|
@env['HTTP_USER_AGENT']
|
12
16
|
end
|
13
17
|
|
18
|
+
# Returns an array of acceptable media types for the response
|
14
19
|
def accept
|
15
|
-
@env['HTTP_ACCEPT'].split(',').map { |a| a.strip }
|
20
|
+
@env['HTTP_ACCEPT'].to_s.split(',').map { |a| a.strip }
|
16
21
|
end
|
17
|
-
end
|
18
22
|
|
19
|
-
|
20
|
-
def
|
21
|
-
|
22
|
-
|
23
|
+
# Override Rack 0.9.x's #params implementation (see #72 in lighthouse)
|
24
|
+
def params
|
25
|
+
self.GET.update(self.POST)
|
26
|
+
rescue EOFError, Errno::ESPIPE
|
27
|
+
self.GET
|
23
28
|
end
|
24
29
|
|
25
|
-
def
|
26
|
-
@
|
27
|
-
str
|
30
|
+
def secure?
|
31
|
+
(@env['HTTP_X_FORWARDED_PROTO'] || @env['rack.url_scheme']) == 'https'
|
28
32
|
end
|
33
|
+
end
|
29
34
|
|
35
|
+
# The response object. See Rack::Response and Rack::ResponseHelpers for
|
36
|
+
# more info:
|
37
|
+
# http://rack.rubyforge.org/doc/classes/Rack/Response.html
|
38
|
+
# http://rack.rubyforge.org/doc/classes/Rack/Response/Helpers.html
|
39
|
+
class Response < Rack::Response
|
30
40
|
def finish
|
31
41
|
@body = block if block_given?
|
32
42
|
if [204, 304].include?(status.to_i)
|
@@ -35,19 +45,20 @@ module Sinatra
|
|
35
45
|
else
|
36
46
|
body = @body || []
|
37
47
|
body = [body] if body.respond_to? :to_str
|
38
|
-
if
|
48
|
+
if body.respond_to?(:to_ary)
|
39
49
|
header["Content-Length"] = body.to_ary.
|
40
|
-
inject(0) { |len, part| len + part
|
50
|
+
inject(0) { |len, part| len + Rack::Utils.bytesize(part) }.to_s
|
41
51
|
end
|
42
52
|
[status.to_i, header.to_hash, body]
|
43
53
|
end
|
44
54
|
end
|
45
55
|
end
|
46
56
|
|
47
|
-
class NotFound < NameError
|
57
|
+
class NotFound < NameError #:nodoc:
|
48
58
|
def code ; 404 ; end
|
49
59
|
end
|
50
60
|
|
61
|
+
# Methods available to routes, before filters, and views.
|
51
62
|
module Helpers
|
52
63
|
# Set or retrieve the response status code.
|
53
64
|
def status(value=nil)
|
@@ -75,7 +86,7 @@ module Sinatra
|
|
75
86
|
|
76
87
|
# Halt processing and return the error status provided.
|
77
88
|
def error(code, body=nil)
|
78
|
-
code, body
|
89
|
+
code, body = 500, code.to_str if code.respond_to? :to_str
|
79
90
|
response.body = body unless body.nil?
|
80
91
|
halt code
|
81
92
|
end
|
@@ -85,6 +96,12 @@ module Sinatra
|
|
85
96
|
error 404, body
|
86
97
|
end
|
87
98
|
|
99
|
+
# Set multiple response headers with Hash.
|
100
|
+
def headers(hash=nil)
|
101
|
+
response.headers.merge! hash if hash
|
102
|
+
response.headers
|
103
|
+
end
|
104
|
+
|
88
105
|
# Access the underlying Rack session.
|
89
106
|
def session
|
90
107
|
env['rack.session'] ||= {}
|
@@ -118,23 +135,35 @@ module Sinatra
|
|
118
135
|
end
|
119
136
|
end
|
120
137
|
|
121
|
-
# Use the contents of the file as the response body
|
138
|
+
# Use the contents of the file at +path+ as the response body.
|
122
139
|
def send_file(path, opts={})
|
123
140
|
stat = File.stat(path)
|
124
141
|
last_modified stat.mtime
|
142
|
+
|
125
143
|
content_type media_type(opts[:type]) ||
|
126
144
|
media_type(File.extname(path)) ||
|
127
145
|
response['Content-Type'] ||
|
128
146
|
'application/octet-stream'
|
147
|
+
|
129
148
|
response['Content-Length'] ||= (opts[:length] || stat.size).to_s
|
149
|
+
|
150
|
+
if opts[:disposition] == 'attachment' || opts[:filename]
|
151
|
+
attachment opts[:filename] || path
|
152
|
+
elsif opts[:disposition] == 'inline'
|
153
|
+
response['Content-Disposition'] = 'inline'
|
154
|
+
end
|
155
|
+
|
130
156
|
halt StaticFile.open(path, 'rb')
|
131
157
|
rescue Errno::ENOENT
|
132
158
|
not_found
|
133
159
|
end
|
134
160
|
|
161
|
+
# Rack response body used to deliver static files. The file contents are
|
162
|
+
# generated iteratively in 8K chunks.
|
135
163
|
class StaticFile < ::File #:nodoc:
|
136
164
|
alias_method :to_path, :path
|
137
165
|
def each
|
166
|
+
rewind
|
138
167
|
while buf = read(8192)
|
139
168
|
yield buf
|
140
169
|
end
|
@@ -177,109 +206,148 @@ module Sinatra
|
|
177
206
|
halt 304 if etags.include?(value) || etags.include?('*')
|
178
207
|
end
|
179
208
|
end
|
209
|
+
|
210
|
+
## Sugar for redirect (example: redirect back)
|
211
|
+
def back ; request.referer ; end
|
212
|
+
|
180
213
|
end
|
181
214
|
|
215
|
+
# Template rendering methods. Each method takes the name of a template
|
216
|
+
# to render as a Symbol and returns a String with the rendered output,
|
217
|
+
# as well as an optional hash with additional options.
|
218
|
+
#
|
219
|
+
# `template` is either the name or path of the template as symbol
|
220
|
+
# (Use `:'subdir/myview'` for views in subdirectories), or a string
|
221
|
+
# that will be rendered.
|
222
|
+
#
|
223
|
+
# Possible options are:
|
224
|
+
# :layout If set to false, no layout is rendered, otherwise
|
225
|
+
# the specified layout is used (Ignored for `sass`)
|
226
|
+
# :locals A hash with local variables that should be available
|
227
|
+
# in the template
|
182
228
|
module Templates
|
183
|
-
def
|
184
|
-
|
185
|
-
|
186
|
-
|
229
|
+
def erb(template, options={}, locals={})
|
230
|
+
render :erb, template, options, locals
|
231
|
+
end
|
232
|
+
|
233
|
+
def haml(template, options={}, locals={})
|
234
|
+
render :haml, template, options, locals
|
235
|
+
end
|
236
|
+
|
237
|
+
def sass(template, options={}, locals={})
|
238
|
+
options[:layout] = false
|
239
|
+
render :sass, template, options, locals
|
240
|
+
end
|
241
|
+
|
242
|
+
def builder(template=nil, options={}, locals={}, &block)
|
243
|
+
options, template = template, nil if template.is_a?(Hash)
|
244
|
+
template = lambda { block } if template.nil?
|
245
|
+
render :builder, template, options, locals
|
246
|
+
end
|
247
|
+
|
248
|
+
private
|
249
|
+
def render(engine, template, options={}, locals={})
|
250
|
+
# merge app-level options
|
251
|
+
options = self.class.send(engine).merge(options) if self.class.respond_to?(engine)
|
252
|
+
|
253
|
+
# extract generic options
|
254
|
+
layout = options.delete(:layout)
|
255
|
+
layout = :layout if layout.nil? || layout == true
|
256
|
+
views = options.delete(:views) || self.class.views || "./views"
|
257
|
+
locals = options.delete(:locals) || locals || {}
|
258
|
+
|
259
|
+
# render template
|
260
|
+
data, options[:filename], options[:line] = lookup_template(engine, template, views)
|
261
|
+
output = __send__("render_#{engine}", data, options, locals)
|
262
|
+
|
263
|
+
# render layout
|
187
264
|
if layout
|
188
|
-
|
189
|
-
|
190
|
-
|
265
|
+
data, options[:filename], options[:line] = lookup_layout(engine, layout, views)
|
266
|
+
if data
|
267
|
+
output = __send__("render_#{engine}", data, options, locals) { output }
|
268
|
+
end
|
191
269
|
end
|
270
|
+
|
271
|
+
output
|
192
272
|
end
|
193
273
|
|
194
|
-
def lookup_template(engine, template,
|
274
|
+
def lookup_template(engine, template, views_dir, filename = nil, line = nil)
|
195
275
|
case template
|
196
276
|
when Symbol
|
197
|
-
|
198
|
-
lookup_template(engine, cached, options)
|
199
|
-
else
|
200
|
-
::File.read(template_path(engine, template, options))
|
201
|
-
end
|
277
|
+
load_template(engine, template, views_dir, options)
|
202
278
|
when Proc
|
203
|
-
|
279
|
+
filename, line = self.class.caller_locations.first if filename.nil?
|
280
|
+
[template.call, filename, line.to_i]
|
204
281
|
when String
|
205
|
-
|
282
|
+
filename, line = self.class.caller_locations.first if filename.nil?
|
283
|
+
[template, filename, line.to_i]
|
206
284
|
else
|
207
285
|
raise ArgumentError
|
208
286
|
end
|
209
287
|
end
|
210
288
|
|
211
|
-
def
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
289
|
+
def load_template(engine, template, views_dir, options={})
|
290
|
+
base = self.class
|
291
|
+
while base.respond_to?(:templates)
|
292
|
+
if cached = base.templates[template]
|
293
|
+
return lookup_template(engine, cached[:template], views_dir, cached[:filename], cached[:line])
|
294
|
+
else
|
295
|
+
base = base.superclass
|
296
|
+
end
|
297
|
+
end
|
220
298
|
|
221
|
-
|
222
|
-
views_dir
|
223
|
-
|
224
|
-
"#{views_dir}/#{template}.#{engine}"
|
299
|
+
# If no template exists in the cache, try loading from disk.
|
300
|
+
path = ::File.join(views_dir, "#{template}.#{engine}")
|
301
|
+
[ ::File.read(path), path, 1 ]
|
225
302
|
end
|
226
303
|
|
227
|
-
def
|
228
|
-
|
229
|
-
|
304
|
+
def lookup_layout(engine, template, views_dir)
|
305
|
+
lookup_template(engine, template, views_dir)
|
306
|
+
rescue Errno::ENOENT
|
307
|
+
nil
|
230
308
|
end
|
231
309
|
|
232
|
-
def render_erb(
|
310
|
+
def render_erb(data, options, locals, &block)
|
311
|
+
original_out_buf = defined?(@_out_buf) && @_out_buf
|
233
312
|
data = data.call if data.kind_of? Proc
|
234
|
-
instance = ::ERB.new(data)
|
235
|
-
locals = options[:locals] || {}
|
236
|
-
locals_assigns = locals.to_a.collect { |k,v| "#{k} = locals[:#{k}]" }
|
237
|
-
src = "#{locals_assigns.join("\n")}\n#{instance.src}"
|
238
|
-
eval src, binding, '(__ERB__)', locals_assigns.length + 1
|
239
|
-
instance.result(binding)
|
240
|
-
end
|
241
313
|
|
242
|
-
|
243
|
-
|
244
|
-
options[:options] ||= self.class.haml if self.class.respond_to? :haml
|
245
|
-
render :haml, template, options
|
246
|
-
end
|
314
|
+
instance = ::ERB.new(data, nil, nil, '@_out_buf')
|
315
|
+
locals_assigns = locals.to_a.collect { |k,v| "#{k} = locals[:#{k}]" }
|
247
316
|
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
end
|
317
|
+
filename = options.delete(:filename) || '(__ERB__)'
|
318
|
+
line = options.delete(:line) || 1
|
319
|
+
line -= 1 if instance.src =~ /^#coding:/
|
252
320
|
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
321
|
+
render_binding = binding
|
322
|
+
eval locals_assigns.join("\n"), render_binding
|
323
|
+
eval instance.src, render_binding, filename, line
|
324
|
+
@_out_buf, result = original_out_buf, @_out_buf
|
325
|
+
result
|
257
326
|
end
|
258
327
|
|
259
|
-
def
|
260
|
-
|
261
|
-
engine.render
|
328
|
+
def render_haml(data, options, locals, &block)
|
329
|
+
::Haml::Engine.new(data, options).render(self, locals, &block)
|
262
330
|
end
|
263
331
|
|
264
|
-
def
|
265
|
-
|
266
|
-
options, template = template, nil if template.is_a?(Hash)
|
267
|
-
template = lambda { block } if template.nil?
|
268
|
-
render :builder, template, options
|
332
|
+
def render_sass(data, options, locals, &block)
|
333
|
+
::Sass::Engine.new(data, options).render
|
269
334
|
end
|
270
335
|
|
271
|
-
def render_builder(
|
272
|
-
|
336
|
+
def render_builder(data, options, locals, &block)
|
337
|
+
options = { :indent => 2 }.merge(options)
|
338
|
+
filename = options.delete(:filename) || '<BUILDER>'
|
339
|
+
line = options.delete(:line) || 1
|
340
|
+
xml = ::Builder::XmlMarkup.new(options)
|
273
341
|
if data.respond_to?(:to_str)
|
274
|
-
eval data.to_str, binding,
|
342
|
+
eval data.to_str, binding, filename, line
|
275
343
|
elsif data.kind_of?(Proc)
|
276
344
|
data.call(xml)
|
277
345
|
end
|
278
346
|
xml.target!
|
279
347
|
end
|
280
|
-
|
281
348
|
end
|
282
349
|
|
350
|
+
# Base class for all Sinatra applications and middleware.
|
283
351
|
class Base
|
284
352
|
include Rack::Utils
|
285
353
|
include Helpers
|
@@ -292,6 +360,7 @@ module Sinatra
|
|
292
360
|
yield self if block_given?
|
293
361
|
end
|
294
362
|
|
363
|
+
# Rack call interface.
|
295
364
|
def call(env)
|
296
365
|
dup.call!(env)
|
297
366
|
end
|
@@ -302,34 +371,69 @@ module Sinatra
|
|
302
371
|
@env = env
|
303
372
|
@request = Request.new(env)
|
304
373
|
@response = Response.new
|
305
|
-
@params =
|
306
|
-
|
307
|
-
|
374
|
+
@params = indifferent_params(@request.params)
|
375
|
+
|
376
|
+
invoke { dispatch! }
|
377
|
+
invoke { error_block!(response.status) }
|
378
|
+
|
379
|
+
status, header, body = @response.finish
|
380
|
+
|
381
|
+
# Never produce a body on HEAD requests. Do retain the Content-Length
|
382
|
+
# unless it's "0", in which case we assume it was calculated erroneously
|
383
|
+
# for a manual HEAD response and remove it entirely.
|
384
|
+
if @env['REQUEST_METHOD'] == 'HEAD'
|
385
|
+
body = []
|
386
|
+
header.delete('Content-Length') if header['Content-Length'] == '0'
|
387
|
+
end
|
388
|
+
|
389
|
+
[status, header, body]
|
308
390
|
end
|
309
391
|
|
392
|
+
# Access options defined with Base.set.
|
310
393
|
def options
|
311
394
|
self.class
|
312
395
|
end
|
313
396
|
|
397
|
+
# Exit the current block, halts any further processing
|
398
|
+
# of the request, and returns the specified response.
|
314
399
|
def halt(*response)
|
315
|
-
|
400
|
+
response = response.first if response.length == 1
|
401
|
+
throw :halt, response
|
316
402
|
end
|
317
403
|
|
404
|
+
# Pass control to the next matching route.
|
405
|
+
# If there are no more matching routes, Sinatra will
|
406
|
+
# return a 404 response.
|
318
407
|
def pass
|
319
408
|
throw :pass
|
320
409
|
end
|
321
410
|
|
411
|
+
# Forward the request to the downstream app -- middleware only.
|
412
|
+
def forward
|
413
|
+
fail "downstream app not set" unless @app.respond_to? :call
|
414
|
+
status, headers, body = @app.call(@request.env)
|
415
|
+
@response.status = status
|
416
|
+
@response.body = body
|
417
|
+
@response.headers.merge! headers
|
418
|
+
nil
|
419
|
+
end
|
420
|
+
|
322
421
|
private
|
323
|
-
|
324
|
-
|
325
|
-
if
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
422
|
+
# Run before filters defined on the class and all superclasses.
|
423
|
+
def filter!(base=self.class)
|
424
|
+
filter!(base.superclass) if base.superclass.respond_to?(:filters)
|
425
|
+
base.filters.each { |block| instance_eval(&block) }
|
426
|
+
end
|
427
|
+
|
428
|
+
# Run routes defined on the class and all superclasses.
|
429
|
+
def route!(base=self.class)
|
430
|
+
if routes = base.routes[@request.request_method]
|
431
|
+
original_params = @params
|
432
|
+
path = unescape(@request.path_info)
|
433
|
+
|
434
|
+
routes.each do |pattern, keys, conditions, block|
|
435
|
+
if match = pattern.match(path)
|
436
|
+
values = match.captures.to_a
|
333
437
|
params =
|
334
438
|
if keys.any?
|
335
439
|
keys.zip(values).inject({}) do |hash,(k,v)|
|
@@ -345,28 +449,78 @@ module Sinatra
|
|
345
449
|
else
|
346
450
|
{}
|
347
451
|
end
|
348
|
-
@params = original_params.
|
349
|
-
@
|
452
|
+
@params = original_params.merge(params)
|
453
|
+
@block_params = values
|
350
454
|
|
351
|
-
catch(:pass)
|
455
|
+
catch(:pass) do
|
352
456
|
conditions.each { |cond|
|
353
457
|
throw :pass if instance_eval(&cond) == false }
|
354
|
-
|
355
|
-
|
458
|
+
route_eval(&block)
|
459
|
+
end
|
356
460
|
end
|
357
461
|
end
|
462
|
+
|
463
|
+
@params = original_params
|
464
|
+
end
|
465
|
+
|
466
|
+
# Run routes defined in superclass.
|
467
|
+
if base.superclass.respond_to?(:routes)
|
468
|
+
route! base.superclass
|
469
|
+
return
|
358
470
|
end
|
359
|
-
|
471
|
+
|
472
|
+
route_missing
|
360
473
|
end
|
361
474
|
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
475
|
+
# Run a route block and throw :halt with the result.
|
476
|
+
def route_eval(&block)
|
477
|
+
throw :halt, instance_eval(&block)
|
478
|
+
end
|
479
|
+
|
480
|
+
# No matching route was found or all routes passed. The default
|
481
|
+
# implementation is to forward the request downstream when running
|
482
|
+
# as middleware (@app is non-nil); when no downstream app is set, raise
|
483
|
+
# a NotFound exception. Subclasses can override this method to perform
|
484
|
+
# custom route miss logic.
|
485
|
+
def route_missing
|
486
|
+
if @app
|
487
|
+
forward
|
488
|
+
else
|
489
|
+
raise NotFound
|
490
|
+
end
|
491
|
+
end
|
492
|
+
|
493
|
+
# Attempt to serve static files from public directory. Throws :halt when
|
494
|
+
# a matching file is found, returns nil otherwise.
|
495
|
+
def static!
|
496
|
+
return if (public_dir = options.public).nil?
|
497
|
+
public_dir = File.expand_path(public_dir)
|
498
|
+
|
499
|
+
path = File.expand_path(public_dir + unescape(request.path_info))
|
500
|
+
return if path[0, public_dir.length] != public_dir
|
501
|
+
return unless File.file?(path)
|
502
|
+
|
503
|
+
send_file path, :disposition => nil
|
504
|
+
end
|
505
|
+
|
506
|
+
# Enable string or symbol key access to the nested params hash.
|
507
|
+
def indifferent_params(params)
|
508
|
+
params = indifferent_hash.merge(params)
|
509
|
+
params.each do |key, value|
|
510
|
+
next unless value.is_a?(Hash)
|
511
|
+
params[key] = indifferent_params(value)
|
512
|
+
end
|
513
|
+
end
|
514
|
+
|
515
|
+
def indifferent_hash
|
516
|
+
Hash.new {|hash,key| hash[key.to_s] if Symbol === key }
|
517
|
+
end
|
518
|
+
|
519
|
+
# Run the block with 'throw :halt' support and apply result to the response.
|
520
|
+
def invoke(&block)
|
521
|
+
res = catch(:halt) { instance_eval(&block) }
|
522
|
+
return if res.nil?
|
523
|
+
|
370
524
|
case
|
371
525
|
when res.respond_to?(:to_str)
|
372
526
|
@response.body = [res]
|
@@ -379,7 +533,7 @@ module Sinatra
|
|
379
533
|
headers.each { |k, v| @response.headers[k] = v } if headers
|
380
534
|
elsif res.length == 2
|
381
535
|
@response.status = res.first
|
382
|
-
@response.body
|
536
|
+
@response.body = res.last
|
383
537
|
else
|
384
538
|
raise TypeError, "#{res.inspect} not supported"
|
385
539
|
end
|
@@ -390,57 +544,113 @@ module Sinatra
|
|
390
544
|
@response.body = res
|
391
545
|
when (100...599) === res
|
392
546
|
@response.status = res
|
393
|
-
when res.nil?
|
394
|
-
@response.body = []
|
395
547
|
end
|
548
|
+
|
396
549
|
res
|
397
550
|
end
|
398
551
|
|
399
|
-
|
400
|
-
|
401
|
-
|
552
|
+
# Dispatch a request with error handling.
|
553
|
+
def dispatch!
|
554
|
+
filter!
|
555
|
+
static! if options.static? && (request.get? || request.head?)
|
556
|
+
route!
|
402
557
|
rescue NotFound => boom
|
403
|
-
|
404
|
-
@response.status = 404
|
405
|
-
@response.body = ['<h1>Not Found</h1>']
|
406
|
-
handler = errmap[boom.class] || errmap[NotFound]
|
407
|
-
invoke handler unless handler.nil?
|
558
|
+
handle_not_found!(boom)
|
408
559
|
rescue ::Exception => boom
|
560
|
+
handle_exception!(boom)
|
561
|
+
end
|
562
|
+
|
563
|
+
def handle_not_found!(boom)
|
409
564
|
@env['sinatra.error'] = boom
|
565
|
+
@response.status = 404
|
566
|
+
@response.body = ['<h1>Not Found</h1>']
|
567
|
+
error_block! boom.class, NotFound
|
568
|
+
end
|
410
569
|
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
570
|
+
def handle_exception!(boom)
|
571
|
+
@env['sinatra.error'] = boom
|
572
|
+
|
573
|
+
dump_errors!(boom) if options.dump_errors?
|
574
|
+
raise boom if options.raise_errors? || options.show_exceptions?
|
415
575
|
|
416
|
-
raise boom if options.raise_errors?
|
417
576
|
@response.status = 500
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
577
|
+
error_block! boom.class, Exception
|
578
|
+
end
|
579
|
+
|
580
|
+
# Find an custom error block for the key(s) specified.
|
581
|
+
def error_block!(*keys)
|
582
|
+
keys.each do |key|
|
583
|
+
base = self.class
|
584
|
+
while base.respond_to?(:errors)
|
585
|
+
if block = base.errors[key]
|
586
|
+
# found a handler, eval and return result
|
587
|
+
res = instance_eval(&block)
|
588
|
+
return res
|
589
|
+
else
|
590
|
+
base = base.superclass
|
591
|
+
end
|
592
|
+
end
|
422
593
|
end
|
594
|
+
nil
|
423
595
|
end
|
424
596
|
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
597
|
+
def dump_errors!(boom)
|
598
|
+
backtrace = clean_backtrace(boom.backtrace)
|
599
|
+
msg = ["#{boom.class} - #{boom.message}:",
|
600
|
+
*backtrace].join("\n ")
|
601
|
+
@env['rack.errors'].write(msg)
|
602
|
+
end
|
603
|
+
|
604
|
+
def clean_backtrace(trace)
|
605
|
+
return trace unless options.clean_trace?
|
606
|
+
|
607
|
+
trace.reject { |line|
|
608
|
+
line =~ /lib\/sinatra.*\.rb/ ||
|
609
|
+
(defined?(Gem) && line.include?(Gem.dir))
|
610
|
+
}.map! { |line| line.gsub(/^\.\//, '') }
|
611
|
+
end
|
432
612
|
|
433
613
|
class << self
|
434
|
-
|
435
|
-
|
614
|
+
attr_reader :routes, :filters, :templates, :errors
|
615
|
+
|
616
|
+
def reset!
|
617
|
+
@conditions = []
|
618
|
+
@routes = {}
|
619
|
+
@filters = []
|
620
|
+
@templates = {}
|
621
|
+
@errors = {}
|
622
|
+
@middleware = []
|
623
|
+
@prototype = nil
|
624
|
+
@extensions = []
|
625
|
+
end
|
436
626
|
|
627
|
+
# Extension modules registered on this class and all superclasses.
|
628
|
+
def extensions
|
629
|
+
if superclass.respond_to?(:extensions)
|
630
|
+
(@extensions + superclass.extensions).uniq
|
631
|
+
else
|
632
|
+
@extensions
|
633
|
+
end
|
634
|
+
end
|
635
|
+
|
636
|
+
# Middleware used in this class and all superclasses.
|
637
|
+
def middleware
|
638
|
+
if superclass.respond_to?(:middleware)
|
639
|
+
superclass.middleware + @middleware
|
640
|
+
else
|
641
|
+
@middleware
|
642
|
+
end
|
643
|
+
end
|
644
|
+
|
645
|
+
# Sets an option to the given value. If the value is a proc,
|
646
|
+
# the proc will be called every time the option is accessed.
|
437
647
|
def set(option, value=self)
|
438
648
|
if value.kind_of?(Proc)
|
439
649
|
metadef(option, &value)
|
440
650
|
metadef("#{option}?") { !!__send__(option) }
|
441
651
|
metadef("#{option}=") { |val| set(option, Proc.new{val}) }
|
442
652
|
elsif value == self && option.respond_to?(:to_hash)
|
443
|
-
option.to_hash.each(
|
653
|
+
option.to_hash.each { |k,v| set(k, v) }
|
444
654
|
elsif respond_to?("#{option}=")
|
445
655
|
__send__ "#{option}=", value
|
446
656
|
else
|
@@ -449,14 +659,19 @@ module Sinatra
|
|
449
659
|
self
|
450
660
|
end
|
451
661
|
|
662
|
+
# Same as calling `set :option, true` for each of the given options.
|
452
663
|
def enable(*opts)
|
453
664
|
opts.each { |key| set(key, true) }
|
454
665
|
end
|
455
666
|
|
667
|
+
# Same as calling `set :option, false` for each of the given options.
|
456
668
|
def disable(*opts)
|
457
669
|
opts.each { |key| set(key, false) }
|
458
670
|
end
|
459
671
|
|
672
|
+
# Define a custom error handler. Optionally takes either an Exception
|
673
|
+
# class, or an HTTP status code to specify which errors should be
|
674
|
+
# handled.
|
460
675
|
def error(codes=Exception, &block)
|
461
676
|
if codes.respond_to? :each
|
462
677
|
codes.each { |err| error(err, &block) }
|
@@ -465,28 +680,43 @@ module Sinatra
|
|
465
680
|
end
|
466
681
|
end
|
467
682
|
|
683
|
+
# Sugar for `error(404) { ... }`
|
468
684
|
def not_found(&block)
|
469
685
|
error 404, &block
|
470
686
|
end
|
471
687
|
|
688
|
+
# Define a named template. The block must return the template source.
|
472
689
|
def template(name, &block)
|
473
|
-
|
690
|
+
filename, line = caller_locations.first
|
691
|
+
templates[name] = { :filename => filename, :line => line, :template => block }
|
474
692
|
end
|
475
693
|
|
694
|
+
# Define the layout template. The block must return the template source.
|
476
695
|
def layout(name=:layout, &block)
|
477
696
|
template name, &block
|
478
697
|
end
|
479
698
|
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
file
|
484
|
-
|
699
|
+
# Load embeded templates from the file; uses the caller's __FILE__
|
700
|
+
# when no file is specified.
|
701
|
+
def use_in_file_templates!(file=nil)
|
702
|
+
file ||= caller_files.first
|
703
|
+
|
704
|
+
begin
|
705
|
+
app, data =
|
706
|
+
::IO.read(file).split(/^__END__$/, 2)
|
707
|
+
rescue Errno::ENOENT
|
708
|
+
app, data = nil
|
709
|
+
end
|
710
|
+
|
711
|
+
if data
|
485
712
|
data.gsub!(/\r\n/, "\n")
|
713
|
+
lines = app.count("\n") + 1
|
486
714
|
template = nil
|
487
715
|
data.each_line do |line|
|
716
|
+
lines += 1
|
488
717
|
if line =~ /^@@\s*(.*)/
|
489
|
-
template =
|
718
|
+
template = ''
|
719
|
+
templates[$1.to_sym] = { :filename => file, :line => lines, :template => template }
|
490
720
|
elsif template
|
491
721
|
template << line
|
492
722
|
end
|
@@ -501,14 +731,20 @@ module Sinatra
|
|
501
731
|
Rack::Mime.mime_type(type, nil)
|
502
732
|
end
|
503
733
|
|
734
|
+
# Define a before filter. Filters are run before all requests
|
735
|
+
# within the same context as route handlers and may access/modify the
|
736
|
+
# request and response.
|
504
737
|
def before(&block)
|
505
738
|
@filters << block
|
506
739
|
end
|
507
740
|
|
741
|
+
# Add a route condition. The route is considered non-matching when the
|
742
|
+
# block returns false.
|
508
743
|
def condition(&block)
|
509
744
|
@conditions << block
|
510
745
|
end
|
511
746
|
|
747
|
+
private
|
512
748
|
def host_name(pattern)
|
513
749
|
condition { pattern === request.host }
|
514
750
|
end
|
@@ -523,8 +759,9 @@ module Sinatra
|
|
523
759
|
end
|
524
760
|
}
|
525
761
|
end
|
762
|
+
alias_method :agent, :user_agent
|
526
763
|
|
527
|
-
def
|
764
|
+
def provides(*types)
|
528
765
|
types = [types] unless types.kind_of? Array
|
529
766
|
types.map!{|t| media_type(t)}
|
530
767
|
|
@@ -539,52 +776,72 @@ module Sinatra
|
|
539
776
|
}
|
540
777
|
end
|
541
778
|
|
779
|
+
public
|
780
|
+
# Defining a `GET` handler also automatically defines
|
781
|
+
# a `HEAD` handler.
|
542
782
|
def get(path, opts={}, &block)
|
543
783
|
conditions = @conditions.dup
|
544
|
-
|
784
|
+
route('GET', path, opts, &block)
|
545
785
|
|
546
786
|
@conditions = conditions
|
547
|
-
|
787
|
+
route('HEAD', path, opts, &block)
|
548
788
|
end
|
549
789
|
|
550
|
-
def put(path, opts={}, &bk);
|
551
|
-
def post(path, opts={}, &bk);
|
552
|
-
def delete(path, opts={}, &bk); route 'DELETE', path, opts, &bk
|
553
|
-
def head(path, opts={}, &bk);
|
790
|
+
def put(path, opts={}, &bk); route 'PUT', path, opts, &bk end
|
791
|
+
def post(path, opts={}, &bk); route 'POST', path, opts, &bk end
|
792
|
+
def delete(path, opts={}, &bk); route 'DELETE', path, opts, &bk end
|
793
|
+
def head(path, opts={}, &bk); route 'HEAD', path, opts, &bk end
|
554
794
|
|
555
795
|
private
|
556
|
-
def route(
|
557
|
-
|
558
|
-
|
559
|
-
|
796
|
+
def route(verb, path, options={}, &block)
|
797
|
+
# Because of self.options.host
|
798
|
+
host_name(options.delete(:host)) if options.key?(:host)
|
799
|
+
|
800
|
+
options.each {|option, args| send(option, *args)}
|
560
801
|
|
561
802
|
pattern, keys = compile(path)
|
562
803
|
conditions, @conditions = @conditions, []
|
563
|
-
method_name = "route { #{method} #{path} }"
|
564
|
-
nmethods = instance_methods.grep(rx = /#{Regexp.escape(method_name)}/).size
|
565
|
-
method_name += " [#{nmethods}]"
|
566
804
|
|
567
|
-
define_method
|
805
|
+
define_method "#{verb} #{path}", &block
|
806
|
+
unbound_method = instance_method("#{verb} #{path}")
|
807
|
+
block =
|
808
|
+
if block.arity != 0
|
809
|
+
lambda { unbound_method.bind(self).call(*@block_params) }
|
810
|
+
else
|
811
|
+
lambda { unbound_method.bind(self).call }
|
812
|
+
end
|
813
|
+
|
814
|
+
invoke_hook(:route_added, verb, path, block)
|
815
|
+
|
816
|
+
(@routes[verb] ||= []).
|
817
|
+
push([pattern, keys, conditions, block]).last
|
818
|
+
end
|
568
819
|
|
569
|
-
|
570
|
-
|
820
|
+
def invoke_hook(name, *args)
|
821
|
+
extensions.each { |e| e.send(name, *args) if e.respond_to?(name) }
|
571
822
|
end
|
572
823
|
|
573
824
|
def compile(path)
|
574
825
|
keys = []
|
575
826
|
if path.respond_to? :to_str
|
827
|
+
special_chars = %w{. + ( )}
|
576
828
|
pattern =
|
577
|
-
|
578
|
-
|
829
|
+
path.to_str.gsub(/((:\w+)|[\*#{special_chars.join}])/) do |match|
|
830
|
+
case match
|
831
|
+
when "*"
|
579
832
|
keys << 'splat'
|
580
833
|
"(.*?)"
|
834
|
+
when *special_chars
|
835
|
+
Regexp.escape(match)
|
581
836
|
else
|
582
837
|
keys << $2[1..-1]
|
583
|
-
"([
|
838
|
+
"([^/?&#]+)"
|
584
839
|
end
|
585
840
|
end
|
586
841
|
[/^#{pattern}$/, keys]
|
587
|
-
elsif path.respond_to?
|
842
|
+
elsif path.respond_to?(:keys) && path.respond_to?(:match)
|
843
|
+
[path, path.keys]
|
844
|
+
elsif path.respond_to? :match
|
588
845
|
[path, keys]
|
589
846
|
else
|
590
847
|
raise TypeError, path
|
@@ -592,70 +849,106 @@ module Sinatra
|
|
592
849
|
end
|
593
850
|
|
594
851
|
public
|
595
|
-
|
596
|
-
|
597
|
-
def
|
852
|
+
# Makes the methods defined in the block and in the Modules given
|
853
|
+
# in `extensions` available to the handlers and templates
|
854
|
+
def helpers(*extensions, &block)
|
855
|
+
class_eval(&block) if block_given?
|
856
|
+
include(*extensions) if extensions.any?
|
857
|
+
end
|
858
|
+
|
859
|
+
def register(*extensions, &block)
|
860
|
+
extensions << Module.new(&block) if block_given?
|
861
|
+
@extensions += extensions
|
862
|
+
extensions.each do |extension|
|
863
|
+
extend extension
|
864
|
+
extension.registered(self) if extension.respond_to?(:registered)
|
865
|
+
end
|
866
|
+
end
|
867
|
+
|
868
|
+
def development?; environment == :development end
|
869
|
+
def production?; environment == :production end
|
870
|
+
def test?; environment == :test end
|
598
871
|
|
872
|
+
# Set configuration options for Sinatra and/or the app.
|
873
|
+
# Allows scoping of settings for certain environments.
|
599
874
|
def configure(*envs, &block)
|
600
|
-
yield if envs.empty? || envs.include?(environment.to_sym)
|
875
|
+
yield self if envs.empty? || envs.include?(environment.to_sym)
|
601
876
|
end
|
602
877
|
|
878
|
+
# Use the specified Rack middleware
|
603
879
|
def use(middleware, *args, &block)
|
604
|
-
|
880
|
+
@prototype = nil
|
605
881
|
@middleware << [middleware, args, block]
|
606
882
|
end
|
607
883
|
|
884
|
+
# Run the Sinatra app as a self-hosted server using
|
885
|
+
# Thin, Mongrel or WEBrick (in that order)
|
608
886
|
def run!(options={})
|
609
|
-
set
|
610
|
-
handler
|
887
|
+
set options
|
888
|
+
handler = detect_rack_handler
|
611
889
|
handler_name = handler.name.gsub(/.*::/, '')
|
612
890
|
puts "== Sinatra/#{Sinatra::VERSION} has taken the stage " +
|
613
|
-
"on #{port} for #{environment} with backup from #{handler_name}"
|
891
|
+
"on #{port} for #{environment} with backup from #{handler_name}" unless handler_name =~/cgi/i
|
614
892
|
handler.run self, :Host => host, :Port => port do |server|
|
615
893
|
trap(:INT) do
|
616
894
|
## Use thins' hard #stop! if available, otherwise just #stop
|
617
895
|
server.respond_to?(:stop!) ? server.stop! : server.stop
|
618
|
-
puts "\n== Sinatra has ended his set (crowd applauds)"
|
896
|
+
puts "\n== Sinatra has ended his set (crowd applauds)" unless handler_name =~/cgi/i
|
619
897
|
end
|
898
|
+
set :running, true
|
620
899
|
end
|
621
900
|
rescue Errno::EADDRINUSE => e
|
622
901
|
puts "== Someone is already performing on port #{port}!"
|
623
902
|
end
|
624
903
|
|
625
|
-
|
626
|
-
|
627
|
-
@
|
904
|
+
# The prototype instance used to process requests.
|
905
|
+
def prototype
|
906
|
+
@prototype ||= new
|
628
907
|
end
|
629
908
|
|
630
|
-
|
631
|
-
|
632
|
-
|
633
|
-
|
634
|
-
builder
|
635
|
-
|
636
|
-
builder.
|
637
|
-
|
909
|
+
# Create a new instance of the class fronted by its middleware
|
910
|
+
# pipeline. The object is guaranteed to respond to #call but may not be
|
911
|
+
# an instance of the class new was called on.
|
912
|
+
def new(*args, &bk)
|
913
|
+
builder = Rack::Builder.new
|
914
|
+
builder.use Rack::Session::Cookie if sessions? && !test?
|
915
|
+
builder.use Rack::CommonLogger if logging?
|
916
|
+
builder.use Rack::MethodOverride if methodoverride?
|
917
|
+
builder.use ShowExceptions if show_exceptions?
|
918
|
+
middleware.each { |c,a,b| builder.use(c, *a, &b) }
|
919
|
+
|
920
|
+
builder.run super
|
921
|
+
builder.to_app
|
638
922
|
end
|
639
923
|
|
640
|
-
def
|
641
|
-
|
924
|
+
def call(env)
|
925
|
+
synchronize { prototype.call(env) }
|
926
|
+
end
|
927
|
+
|
928
|
+
private
|
929
|
+
def detect_rack_handler
|
930
|
+
servers = Array(self.server)
|
931
|
+
servers.each do |server_name|
|
932
|
+
begin
|
933
|
+
return Rack::Handler.get(server_name.downcase)
|
934
|
+
rescue LoadError
|
935
|
+
rescue NameError
|
936
|
+
end
|
937
|
+
end
|
938
|
+
fail "Server handler (#{servers.join(',')}) not found."
|
642
939
|
end
|
643
940
|
|
644
941
|
def inherited(subclass)
|
645
|
-
subclass.
|
646
|
-
subclass.templates = templates.dup
|
647
|
-
subclass.conditions = []
|
648
|
-
subclass.filters = filters.dup
|
649
|
-
subclass.errors = errors.dup
|
650
|
-
subclass.middleware = middleware.dup
|
651
|
-
subclass.send :reset_middleware
|
942
|
+
subclass.reset!
|
652
943
|
super
|
653
944
|
end
|
654
945
|
|
655
|
-
|
656
|
-
|
657
|
-
|
658
|
-
|
946
|
+
@@mutex = Mutex.new
|
947
|
+
def synchronize(&block)
|
948
|
+
if lock?
|
949
|
+
@@mutex.synchronize(&block)
|
950
|
+
else
|
951
|
+
yield
|
659
952
|
end
|
660
953
|
end
|
661
954
|
|
@@ -663,18 +956,47 @@ module Sinatra
|
|
663
956
|
(class << self; self; end).
|
664
957
|
send :define_method, message, &block
|
665
958
|
end
|
959
|
+
|
960
|
+
public
|
961
|
+
CALLERS_TO_IGNORE = [
|
962
|
+
/\/sinatra(\/(base|main|showexceptions))?\.rb$/, # all sinatra code
|
963
|
+
/\(.*\)/, # generated code
|
964
|
+
/custom_require\.rb$/, # rubygems require hacks
|
965
|
+
/active_support/, # active_support require hacks
|
966
|
+
]
|
967
|
+
|
968
|
+
# add rubinius (and hopefully other VM impls) ignore patterns ...
|
969
|
+
CALLERS_TO_IGNORE.concat(RUBY_IGNORE_CALLERS) if defined?(RUBY_IGNORE_CALLERS)
|
970
|
+
|
971
|
+
# Like Kernel#caller but excluding certain magic entries and without
|
972
|
+
# line / method information; the resulting array contains filenames only.
|
973
|
+
def caller_files
|
974
|
+
caller_locations.
|
975
|
+
map { |file,line| file }
|
976
|
+
end
|
977
|
+
|
978
|
+
def caller_locations
|
979
|
+
caller(1).
|
980
|
+
map { |line| line.split(/:(?=\d|in )/)[0,2] }.
|
981
|
+
reject { |file,line| CALLERS_TO_IGNORE.any? { |pattern| file =~ pattern } }
|
982
|
+
end
|
666
983
|
end
|
667
984
|
|
985
|
+
reset!
|
986
|
+
|
668
987
|
set :raise_errors, true
|
669
988
|
set :dump_errors, false
|
989
|
+
set :clean_trace, true
|
990
|
+
set :show_exceptions, false
|
670
991
|
set :sessions, false
|
671
992
|
set :logging, false
|
672
993
|
set :methodoverride, false
|
673
994
|
set :static, false
|
674
995
|
set :environment, (ENV['RACK_ENV'] || :development).to_sym
|
675
996
|
|
676
|
-
set :run, false
|
677
|
-
set :
|
997
|
+
set :run, false # start server via at-exit hook?
|
998
|
+
set :running, false # is the built-in server running now?
|
999
|
+
set :server, %w[thin mongrel webrick]
|
678
1000
|
set :host, '0.0.0.0'
|
679
1001
|
set :port, 4567
|
680
1002
|
|
@@ -682,14 +1004,7 @@ module Sinatra
|
|
682
1004
|
set :root, Proc.new { app_file && File.expand_path(File.dirname(app_file)) }
|
683
1005
|
set :views, Proc.new { root && File.join(root, 'views') }
|
684
1006
|
set :public, Proc.new { root && File.join(root, 'public') }
|
685
|
-
|
686
|
-
# static files route
|
687
|
-
get(/.*[^\/]$/) do
|
688
|
-
pass unless options.static? && options.public?
|
689
|
-
path = options.public + unescape(request.path_info)
|
690
|
-
pass unless File.file?(path)
|
691
|
-
send_file path, :disposition => nil
|
692
|
-
end
|
1007
|
+
set :lock, false
|
693
1008
|
|
694
1009
|
error ::Exception do
|
695
1010
|
response.status = 500
|
@@ -705,6 +1020,8 @@ module Sinatra
|
|
705
1020
|
end
|
706
1021
|
|
707
1022
|
error NotFound do
|
1023
|
+
content_type 'text/html'
|
1024
|
+
|
708
1025
|
(<<-HTML).gsub(/^ {8}/, '')
|
709
1026
|
<!DOCTYPE html>
|
710
1027
|
<html>
|
@@ -726,93 +1043,67 @@ module Sinatra
|
|
726
1043
|
</html>
|
727
1044
|
HTML
|
728
1045
|
end
|
729
|
-
|
730
|
-
error do
|
731
|
-
next unless err = request.env['sinatra.error']
|
732
|
-
heading = err.class.name + ' - ' + err.message.to_s
|
733
|
-
(<<-HTML).gsub(/^ {8}/, '')
|
734
|
-
<!DOCTYPE html>
|
735
|
-
<html>
|
736
|
-
<head>
|
737
|
-
<style type="text/css">
|
738
|
-
body {font-family:verdana;color:#333}
|
739
|
-
#c {margin-left:20px}
|
740
|
-
h1 {color:#1D6B8D;margin:0;margin-top:-30px}
|
741
|
-
h2 {color:#1D6B8D;font-size:18px}
|
742
|
-
pre {border-left:2px solid #ddd;padding-left:10px;color:#000}
|
743
|
-
img {margin-top:10px}
|
744
|
-
</style>
|
745
|
-
</head>
|
746
|
-
<body>
|
747
|
-
<div id="c">
|
748
|
-
<img src="/__sinatra__/500.png">
|
749
|
-
<h1>#{escape_html(heading)}</h1>
|
750
|
-
<pre class='trace'>#{escape_html(err.backtrace.join("\n"))}</pre>
|
751
|
-
<h2>Params</h2>
|
752
|
-
<pre>#{escape_html(params.inspect)}</pre>
|
753
|
-
</div>
|
754
|
-
</body>
|
755
|
-
</html>
|
756
|
-
HTML
|
757
|
-
end
|
758
1046
|
end
|
759
1047
|
end
|
760
1048
|
|
761
|
-
|
762
|
-
|
1049
|
+
# The top-level Application. All DSL methods executed on main are delegated
|
1050
|
+
# to this class.
|
1051
|
+
class Application < Base
|
1052
|
+
set :raise_errors, Proc.new { test? }
|
1053
|
+
set :show_exceptions, Proc.new { development? }
|
763
1054
|
set :dump_errors, true
|
764
1055
|
set :sessions, false
|
765
|
-
set :logging,
|
1056
|
+
set :logging, Proc.new { ! test? }
|
766
1057
|
set :methodoverride, true
|
767
1058
|
set :static, true
|
768
|
-
set :run,
|
769
|
-
set :reload, Proc.new { app_file? && development? }
|
770
|
-
|
771
|
-
def self.reloading?
|
772
|
-
@reloading ||= false
|
773
|
-
end
|
774
|
-
|
775
|
-
def self.configure(*envs)
|
776
|
-
super unless reloading?
|
777
|
-
end
|
778
|
-
|
779
|
-
def self.call(env)
|
780
|
-
reload! if reload?
|
781
|
-
super
|
782
|
-
end
|
1059
|
+
set :run, Proc.new { ! test? }
|
783
1060
|
|
784
|
-
def self.
|
785
|
-
|
786
|
-
|
787
|
-
|
788
|
-
@reloading = false
|
1061
|
+
def self.register(*extensions, &block) #:nodoc:
|
1062
|
+
added_methods = extensions.map {|m| m.public_instance_methods }.flatten
|
1063
|
+
Delegator.delegate(*added_methods)
|
1064
|
+
super(*extensions, &block)
|
789
1065
|
end
|
790
|
-
|
791
|
-
end
|
792
|
-
|
793
|
-
class Application < Default
|
794
1066
|
end
|
795
1067
|
|
796
|
-
|
797
|
-
|
798
|
-
|
799
|
-
|
800
|
-
|
801
|
-
|
802
|
-
|
803
|
-
|
804
|
-
|
805
|
-
|
806
|
-
|
807
|
-
|
808
|
-
|
809
|
-
|
1068
|
+
# Deprecated.
|
1069
|
+
Default = Application
|
1070
|
+
|
1071
|
+
# Sinatra delegation mixin. Mixing this module into an object causes all
|
1072
|
+
# methods to be delegated to the Sinatra::Application class. Used primarily
|
1073
|
+
# at the top-level.
|
1074
|
+
module Delegator #:nodoc:
|
1075
|
+
def self.delegate(*methods)
|
1076
|
+
methods.each do |method_name|
|
1077
|
+
eval <<-RUBY, binding, '(__DELEGATE__)', 1
|
1078
|
+
def #{method_name}(*args, &b)
|
1079
|
+
::Sinatra::Application.send(#{method_name.inspect}, *args, &b)
|
1080
|
+
end
|
1081
|
+
private #{method_name.inspect}
|
1082
|
+
RUBY
|
1083
|
+
end
|
810
1084
|
end
|
1085
|
+
|
1086
|
+
delegate :get, :put, :post, :delete, :head, :template, :layout, :before,
|
1087
|
+
:error, :not_found, :configure, :set,
|
1088
|
+
:enable, :disable, :use, :development?, :test?,
|
1089
|
+
:production?, :use_in_file_templates!, :helpers
|
811
1090
|
end
|
812
1091
|
|
1092
|
+
# Create a new Sinatra application. The block is evaluated in the new app's
|
1093
|
+
# class scope.
|
813
1094
|
def self.new(base=Base, options={}, &block)
|
814
1095
|
base = Class.new(base)
|
815
1096
|
base.send :class_eval, &block if block_given?
|
816
1097
|
base
|
817
1098
|
end
|
1099
|
+
|
1100
|
+
# Extend the top-level DSL with the modules provided.
|
1101
|
+
def self.register(*extensions, &block)
|
1102
|
+
Application.register(*extensions, &block)
|
1103
|
+
end
|
1104
|
+
|
1105
|
+
# Include the helper modules provided in Sinatra's request context.
|
1106
|
+
def self.helpers(*extensions, &block)
|
1107
|
+
Application.helpers(*extensions, &block)
|
1108
|
+
end
|
818
1109
|
end
|