sinatra 1.0 → 1.1.a
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of sinatra might be problematic. Click here for more details.
- data/CHANGES +108 -1
- data/LICENSE +1 -1
- data/README.de.rdoc +1024 -0
- data/README.es.rdoc +1047 -0
- data/README.fr.rdoc +1038 -0
- data/README.hu.rdoc +607 -0
- data/README.jp.rdoc +473 -15
- data/README.rdoc +429 -41
- data/Rakefile +17 -6
- data/lib/sinatra/base.rb +357 -158
- data/lib/sinatra/showexceptions.rb +9 -1
- data/sinatra.gemspec +52 -9
- data/test/builder_test.rb +25 -1
- data/test/coffee_test.rb +88 -0
- data/test/encoding_test.rb +18 -0
- data/test/filter_test.rb +61 -2
- data/test/hello.mab +1 -0
- data/test/helper.rb +1 -0
- data/test/helpers_test.rb +141 -37
- data/test/less_test.rb +26 -2
- data/test/liquid_test.rb +58 -0
- data/test/markaby_test.rb +58 -0
- data/test/markdown_test.rb +35 -0
- data/test/nokogiri_test.rb +69 -0
- data/test/radius_test.rb +59 -0
- data/test/rdoc_test.rb +34 -0
- data/test/request_test.rb +12 -0
- data/test/routing_test.rb +35 -1
- data/test/sass_test.rb +46 -16
- data/test/scss_test.rb +88 -0
- data/test/settings_test.rb +32 -0
- data/test/sinatra_test.rb +4 -0
- data/test/static_test.rb +64 -0
- data/test/templates_test.rb +55 -1
- data/test/textile_test.rb +34 -0
- data/test/views/ascii.haml +2 -0
- data/test/views/explicitly_nested.str +1 -0
- data/test/views/hello.coffee +1 -0
- data/test/views/hello.liquid +1 -0
- data/test/views/hello.mab +1 -0
- data/test/views/hello.md +1 -0
- data/test/views/hello.nokogiri +1 -0
- data/test/views/hello.radius +1 -0
- data/test/views/hello.rdoc +1 -0
- data/test/views/hello.sass +1 -1
- data/test/views/hello.scss +3 -0
- data/test/views/hello.str +1 -0
- data/test/views/hello.textile +1 -0
- data/test/views/layout2.liquid +2 -0
- data/test/views/layout2.mab +2 -0
- data/test/views/layout2.nokogiri +3 -0
- data/test/views/layout2.radius +2 -0
- data/test/views/layout2.str +2 -0
- data/test/views/nested.str +1 -0
- data/test/views/utf8.haml +2 -0
- metadata +240 -33
- data/lib/sinatra/tilt.rb +0 -746
data/Rakefile
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'rake/clean'
|
2
2
|
require 'rake/testtask'
|
3
3
|
require 'fileutils'
|
4
|
+
require 'date'
|
4
5
|
|
5
6
|
task :default => :test
|
6
7
|
task :spec => :test
|
@@ -12,9 +13,19 @@ end
|
|
12
13
|
|
13
14
|
# SPECS ===============================================================
|
14
15
|
|
15
|
-
|
16
|
-
|
17
|
-
|
16
|
+
if !ENV['NO_TEST_FIX'] and RUBY_VERSION == '1.9.2' and RUBY_PATCHLEVEL == 0
|
17
|
+
# Avoids seg fault
|
18
|
+
task(:test) do
|
19
|
+
second_run = %w[settings rdoc markaby].map { |l| "test/#{l}_test.rb" }
|
20
|
+
first_run = Dir.glob('test/*_test.rb') - second_run
|
21
|
+
[first_run, second_run].each { |f| sh "testrb #{f.join ' '}" }
|
22
|
+
end
|
23
|
+
else
|
24
|
+
Rake::TestTask.new(:test) do |t|
|
25
|
+
t.test_files = FileList['test/*_test.rb']
|
26
|
+
t.ruby_opts = ['-rubygems'] if defined? Gem
|
27
|
+
t.ruby_opts << '-I.'
|
28
|
+
end
|
18
29
|
end
|
19
30
|
|
20
31
|
# Rcov ================================================================
|
@@ -22,7 +33,7 @@ namespace :test do
|
|
22
33
|
desc 'Mesures test coverage'
|
23
34
|
task :coverage do
|
24
35
|
rm_f "coverage"
|
25
|
-
rcov = "rcov --text-summary
|
36
|
+
rcov = "rcov --text-summary -Ilib"
|
26
37
|
system("#{rcov} --no-html --no-color test/*_test.rb")
|
27
38
|
end
|
28
39
|
end
|
@@ -36,11 +47,11 @@ task 'doc' => ['doc:api']
|
|
36
47
|
|
37
48
|
task 'doc:api' => ['doc/api/index.html']
|
38
49
|
|
39
|
-
file 'doc/api/index.html' => FileList['lib/**/*.rb','README
|
50
|
+
file 'doc/api/index.html' => FileList['lib/**/*.rb', 'README.*'] do |f|
|
40
51
|
require 'rbconfig'
|
41
52
|
hanna = RbConfig::CONFIG['ruby_install_name'].sub('ruby', 'hanna')
|
42
53
|
rb_files = f.prerequisites
|
43
|
-
sh(
|
54
|
+
sh(<<-end.gsub(/\s+/, ' '))
|
44
55
|
#{hanna}
|
45
56
|
--charset utf8
|
46
57
|
--fmt html
|
data/lib/sinatra/base.rb
CHANGED
@@ -4,48 +4,29 @@ require 'uri'
|
|
4
4
|
require 'rack'
|
5
5
|
require 'rack/builder'
|
6
6
|
require 'sinatra/showexceptions'
|
7
|
-
|
8
|
-
# require tilt if available; fall back on bundled version.
|
9
|
-
begin
|
10
|
-
require 'tilt'
|
11
|
-
if Tilt::VERSION < '0.8'
|
12
|
-
warn "WARN: sinatra requires tilt >= 0.8; you have #{Tilt::VERSION}. " +
|
13
|
-
"loading bundled version..."
|
14
|
-
Object.send :remove_const, :Tilt
|
15
|
-
raise LoadError
|
16
|
-
end
|
17
|
-
rescue LoadError
|
18
|
-
require 'sinatra/tilt'
|
19
|
-
end
|
7
|
+
require 'tilt'
|
20
8
|
|
21
9
|
module Sinatra
|
22
|
-
VERSION = '1.
|
10
|
+
VERSION = '1.1.a'
|
23
11
|
|
24
12
|
# The request object. See Rack::Request for more info:
|
25
13
|
# http://rack.rubyforge.org/doc/classes/Rack/Request.html
|
26
14
|
class Request < Rack::Request
|
27
15
|
# Returns an array of acceptable media types for the response
|
28
16
|
def accept
|
29
|
-
@env['HTTP_ACCEPT'].to_s.split(',').map { |a| a.strip }
|
17
|
+
@env['HTTP_ACCEPT'].to_s.split(',').map { |a| a.split(';')[0].strip }
|
30
18
|
end
|
31
19
|
|
32
|
-
|
33
|
-
(
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
if Rack.release < '1.1'
|
40
|
-
def params
|
41
|
-
self.GET.update(self.POST)
|
42
|
-
rescue EOFError, Errno::ESPIPE
|
43
|
-
self.GET
|
44
|
-
end
|
45
|
-
|
46
|
-
def user_agent
|
47
|
-
@env['HTTP_USER_AGENT']
|
20
|
+
if Rack.release <= "1.2"
|
21
|
+
# Whether or not the web server (or a reverse proxy in front of it) is
|
22
|
+
# using SSL to communicate with the client.
|
23
|
+
def secure?
|
24
|
+
@env['HTTPS'] == 'on' or
|
25
|
+
@env['HTTP_X_FORWARDED_PROTO'] == 'https' or
|
26
|
+
@env['rack.url_scheme'] == 'https'
|
48
27
|
end
|
28
|
+
else
|
29
|
+
alias secure? ssl?
|
49
30
|
end
|
50
31
|
end
|
51
32
|
|
@@ -87,15 +68,30 @@ module Sinatra
|
|
87
68
|
# evaluation is deferred until the body is read with #each.
|
88
69
|
def body(value=nil, &block)
|
89
70
|
if block_given?
|
90
|
-
def block.each
|
71
|
+
def block.each; yield(call) end
|
91
72
|
response.body = block
|
92
|
-
|
73
|
+
elsif value
|
93
74
|
response.body = value
|
75
|
+
else
|
76
|
+
response.body
|
94
77
|
end
|
95
78
|
end
|
96
79
|
|
97
80
|
# Halt processing and redirect to the URI provided.
|
98
81
|
def redirect(uri, *args)
|
82
|
+
if not uri =~ /^https?:\/\//
|
83
|
+
# According to RFC 2616 section 14.30, "the field value consists of a
|
84
|
+
# single absolute URI"
|
85
|
+
abs_uri = "#{request.scheme}://#{request.host}"
|
86
|
+
|
87
|
+
if request.scheme == 'https' && request.port != 443 ||
|
88
|
+
request.scheme == 'http' && request.port != 80
|
89
|
+
abs_uri << ":#{request.port}"
|
90
|
+
end
|
91
|
+
|
92
|
+
uri = (abs_uri << uri)
|
93
|
+
end
|
94
|
+
|
99
95
|
status 302
|
100
96
|
response['Location'] = uri
|
101
97
|
halt(*args)
|
@@ -121,7 +117,7 @@ module Sinatra
|
|
121
117
|
|
122
118
|
# Access the underlying Rack session.
|
123
119
|
def session
|
124
|
-
|
120
|
+
request.session
|
125
121
|
end
|
126
122
|
|
127
123
|
# Look up a media type by file extension in Rack's mime registry.
|
@@ -132,8 +128,9 @@ module Sinatra
|
|
132
128
|
# Set the Content-Type of the response body given a media type or file
|
133
129
|
# extension.
|
134
130
|
def content_type(type, params={})
|
135
|
-
mime_type =
|
131
|
+
mime_type = mime_type(type)
|
136
132
|
fail "Unknown media type: %p" % type if mime_type.nil?
|
133
|
+
params[:charset] ||= defined?(Encoding) ? Encoding.default_external.to_s.downcase : 'utf-8'
|
137
134
|
if params.any?
|
138
135
|
params = params.collect { |kv| "%s=%s" % kv }.join(', ')
|
139
136
|
response['Content-Type'] = [mime_type, params].join(";")
|
@@ -158,19 +155,30 @@ module Sinatra
|
|
158
155
|
last_modified stat.mtime
|
159
156
|
|
160
157
|
content_type mime_type(opts[:type]) ||
|
158
|
+
opts[:type] ||
|
161
159
|
mime_type(File.extname(path)) ||
|
162
160
|
response['Content-Type'] ||
|
163
161
|
'application/octet-stream'
|
164
162
|
|
165
|
-
response['Content-Length'] ||= (opts[:length] || stat.size).to_s
|
166
|
-
|
167
163
|
if opts[:disposition] == 'attachment' || opts[:filename]
|
168
164
|
attachment opts[:filename] || path
|
169
165
|
elsif opts[:disposition] == 'inline'
|
170
166
|
response['Content-Disposition'] = 'inline'
|
171
167
|
end
|
172
168
|
|
173
|
-
|
169
|
+
file_length = opts[:length] || stat.size
|
170
|
+
sf = StaticFile.open(path, 'rb')
|
171
|
+
if ! sf.parse_ranges(env, file_length)
|
172
|
+
response['Content-Range'] = "bytes */#{file_length}"
|
173
|
+
halt 416
|
174
|
+
elsif r=sf.range
|
175
|
+
response['Content-Range'] = "bytes #{r.begin}-#{r.end}/#{file_length}"
|
176
|
+
response['Content-Length'] = (r.end - r.begin + 1).to_s
|
177
|
+
halt 206, sf
|
178
|
+
else
|
179
|
+
response['Content-Length'] ||= file_length.to_s
|
180
|
+
halt sf
|
181
|
+
end
|
174
182
|
rescue Errno::ENOENT
|
175
183
|
not_found
|
176
184
|
end
|
@@ -179,10 +187,65 @@ module Sinatra
|
|
179
187
|
# generated iteratively in 8K chunks.
|
180
188
|
class StaticFile < ::File #:nodoc:
|
181
189
|
alias_method :to_path, :path
|
190
|
+
|
191
|
+
attr_accessor :range # a Range or nil
|
192
|
+
|
193
|
+
# Checks for byte-ranges in the request and sets self.range appropriately.
|
194
|
+
# Returns false if the ranges are unsatisfiable and the request should return 416.
|
195
|
+
def parse_ranges(env, size)
|
196
|
+
#r = Rack::Utils::byte_ranges(env, size) # TODO: not available yet in released Rack
|
197
|
+
r = byte_ranges(env, size)
|
198
|
+
return false if r == [] # Unsatisfiable; report error
|
199
|
+
@range = r[0] if r && r.length == 1 # Ignore multiple-range requests for now
|
200
|
+
return true
|
201
|
+
end
|
202
|
+
|
203
|
+
# TODO: Copied from the new method Rack::Utils::byte_ranges; this method can be removed once
|
204
|
+
# a version of Rack with that method is released and Sinatra can depend on it.
|
205
|
+
def byte_ranges(env, size)
|
206
|
+
# See <http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35>
|
207
|
+
http_range = env['HTTP_RANGE']
|
208
|
+
return nil unless http_range
|
209
|
+
ranges = []
|
210
|
+
http_range.split(/,\s*/).each do |range_spec|
|
211
|
+
matches = range_spec.match(/bytes=(\d*)-(\d*)/)
|
212
|
+
return nil unless matches
|
213
|
+
r0,r1 = matches[1], matches[2]
|
214
|
+
if r0.empty?
|
215
|
+
return nil if r1.empty?
|
216
|
+
# suffix-byte-range-spec, represents trailing suffix of file
|
217
|
+
r0 = [size - r1.to_i, 0].max
|
218
|
+
r1 = size - 1
|
219
|
+
else
|
220
|
+
r0 = r0.to_i
|
221
|
+
if r1.empty?
|
222
|
+
r1 = size - 1
|
223
|
+
else
|
224
|
+
r1 = r1.to_i
|
225
|
+
return nil if r1 < r0 # backwards range is syntactically invalid
|
226
|
+
r1 = size-1 if r1 >= size
|
227
|
+
end
|
228
|
+
end
|
229
|
+
ranges << (r0..r1) if r0 <= r1
|
230
|
+
end
|
231
|
+
ranges
|
232
|
+
end
|
233
|
+
|
234
|
+
CHUNK_SIZE = 8192
|
235
|
+
|
182
236
|
def each
|
183
|
-
|
184
|
-
|
185
|
-
|
237
|
+
if @range
|
238
|
+
self.pos = @range.begin
|
239
|
+
length = @range.end - @range.begin + 1
|
240
|
+
while length > 0 && (buf = read([CHUNK_SIZE,length].min))
|
241
|
+
yield buf
|
242
|
+
length -= buf.length
|
243
|
+
end
|
244
|
+
else
|
245
|
+
rewind
|
246
|
+
while buf = read(CHUNK_SIZE)
|
247
|
+
yield buf
|
248
|
+
end
|
186
249
|
end
|
187
250
|
end
|
188
251
|
end
|
@@ -242,16 +305,16 @@ module Sinatra
|
|
242
305
|
# and halt if conditional GET matches. The +time+ argument is a Time,
|
243
306
|
# DateTime, or other object that responds to +to_time+.
|
244
307
|
#
|
245
|
-
# When the current request includes an 'If-Modified-Since' header that
|
246
|
-
#
|
247
|
-
# '304 Not Modified' response.
|
308
|
+
# When the current request includes an 'If-Modified-Since' header that is
|
309
|
+
# equal or later than the time specified, execution is immediately halted
|
310
|
+
# with a '304 Not Modified' response.
|
248
311
|
def last_modified(time)
|
249
312
|
return unless time
|
250
313
|
time = time.to_time if time.respond_to?(:to_time)
|
251
|
-
time = time.
|
252
|
-
response['Last-Modified'] = time
|
253
|
-
halt 304 if
|
254
|
-
|
314
|
+
time = Time.parse time.strftime('%FT%T%:z') if time.respond_to?(:strftime)
|
315
|
+
response['Last-Modified'] = time.respond_to?(:httpdate) ? time.httpdate : time.to_s
|
316
|
+
halt 304 if Time.httpdate(request.env['HTTP_IF_MODIFIED_SINCE']) >= time
|
317
|
+
rescue ArgumentError
|
255
318
|
end
|
256
319
|
|
257
320
|
# Set the response entity tag (HTTP 'ETag' header) and halt if conditional
|
@@ -295,15 +358,17 @@ module Sinatra
|
|
295
358
|
# :locals A hash with local variables that should be available
|
296
359
|
# in the template
|
297
360
|
module Templates
|
361
|
+
module ContentTyped
|
362
|
+
attr_accessor :content_type
|
363
|
+
end
|
364
|
+
|
298
365
|
include Tilt::CompileSite
|
299
366
|
|
300
367
|
def erb(template, options={}, locals={})
|
301
|
-
options[:outvar] = '@_out_buf'
|
302
368
|
render :erb, template, options, locals
|
303
369
|
end
|
304
370
|
|
305
371
|
def erubis(template, options={}, locals={})
|
306
|
-
options[:outvar] = '@_out_buf'
|
307
372
|
render :erubis, template, options, locals
|
308
373
|
end
|
309
374
|
|
@@ -312,35 +377,86 @@ module Sinatra
|
|
312
377
|
end
|
313
378
|
|
314
379
|
def sass(template, options={}, locals={})
|
315
|
-
options
|
380
|
+
options.merge! :layout => false, :default_content_type => :css
|
316
381
|
render :sass, template, options, locals
|
317
382
|
end
|
318
383
|
|
384
|
+
def scss(template, options={}, locals={})
|
385
|
+
options.merge! :layout => false, :default_content_type => :css
|
386
|
+
render :scss, template, options, locals
|
387
|
+
end
|
388
|
+
|
319
389
|
def less(template, options={}, locals={})
|
320
|
-
options
|
390
|
+
options.merge! :layout => false, :default_content_type => :css
|
321
391
|
render :less, template, options, locals
|
322
392
|
end
|
323
393
|
|
324
394
|
def builder(template=nil, options={}, locals={}, &block)
|
395
|
+
render_xml(:builder, template, options, locals, &block)
|
396
|
+
end
|
397
|
+
|
398
|
+
def liquid(template, options={}, locals={})
|
399
|
+
render :liquid, template, options, locals
|
400
|
+
end
|
401
|
+
|
402
|
+
def markdown(template, options={}, locals={})
|
403
|
+
render :markdown, template, options, locals
|
404
|
+
end
|
405
|
+
|
406
|
+
def textile(template, options={}, locals={})
|
407
|
+
render :textile, template, options, locals
|
408
|
+
end
|
409
|
+
|
410
|
+
def rdoc(template, options={}, locals={})
|
411
|
+
render :rdoc, template, options, locals
|
412
|
+
end
|
413
|
+
|
414
|
+
def radius(template, options={}, locals={})
|
415
|
+
render :radius, template, options, locals
|
416
|
+
end
|
417
|
+
|
418
|
+
def markaby(template, options={}, locals={})
|
419
|
+
render :mab, template, options, locals
|
420
|
+
end
|
421
|
+
|
422
|
+
def coffee(template, options={}, locals={})
|
423
|
+
options.merge! :layout => false, :default_content_type => :js
|
424
|
+
render :coffee, template, options, locals
|
425
|
+
end
|
426
|
+
|
427
|
+
def nokogiri(template=nil, options={}, locals={}, &block)
|
428
|
+
options[:layout] = false if Tilt::VERSION <= "1.1"
|
429
|
+
render_xml(:nokogiri, template, options, locals, &block)
|
430
|
+
end
|
431
|
+
|
432
|
+
private
|
433
|
+
# logic shared between builder and nokogiri
|
434
|
+
def render_xml(engine, template, options={}, locals={}, &block)
|
435
|
+
options[:default_content_type] = :xml
|
325
436
|
options, template = template, nil if template.is_a?(Hash)
|
326
437
|
template = Proc.new { block } if template.nil?
|
327
|
-
render
|
438
|
+
render engine, template, options, locals
|
328
439
|
end
|
329
440
|
|
330
|
-
private
|
331
441
|
def render(engine, data, options={}, locals={}, &block)
|
332
442
|
# merge app-level options
|
333
443
|
options = settings.send(engine).merge(options) if settings.respond_to?(engine)
|
444
|
+
options[:outvar] ||= '@_out_buf'
|
334
445
|
|
335
446
|
# extract generic options
|
336
|
-
locals
|
337
|
-
views
|
338
|
-
|
339
|
-
layout
|
447
|
+
locals = options.delete(:locals) || locals || {}
|
448
|
+
views = options.delete(:views) || settings.views || "./views"
|
449
|
+
@default_layout = :layout if @default_layout.nil?
|
450
|
+
layout = options.delete(:layout)
|
451
|
+
layout = @default_layout if layout.nil? or layout == true
|
452
|
+
content_type = options.delete(:content_type) || options.delete(:default_content_type)
|
340
453
|
|
341
454
|
# compile and render template
|
342
|
-
|
343
|
-
|
455
|
+
layout_was = @default_layout
|
456
|
+
@default_layout = false if layout
|
457
|
+
template = compile_template(engine, data, options, views)
|
458
|
+
output = template.render(self, locals, &block)
|
459
|
+
@default_layout = layout_was
|
344
460
|
|
345
461
|
# render layout
|
346
462
|
if layout
|
@@ -351,11 +467,12 @@ module Sinatra
|
|
351
467
|
end
|
352
468
|
end
|
353
469
|
|
470
|
+
output.extend(ContentTyped).content_type = content_type if content_type
|
354
471
|
output
|
355
472
|
end
|
356
473
|
|
357
474
|
def compile_template(engine, data, options, views)
|
358
|
-
|
475
|
+
template_cache.fetch engine, data, options do
|
359
476
|
template = Tilt[engine]
|
360
477
|
raise "Template engine not found: #{engine}" if template.nil?
|
361
478
|
|
@@ -367,6 +484,11 @@ module Sinatra
|
|
367
484
|
template.new(path, line.to_i, options) { body }
|
368
485
|
else
|
369
486
|
path = ::File.join(views, "#{data}.#{engine}")
|
487
|
+
Tilt.mappings.each do |ext, klass|
|
488
|
+
break if File.exists?(path)
|
489
|
+
next unless klass == template
|
490
|
+
path = ::File.join(views, "#{data}.#{ext}")
|
491
|
+
end
|
370
492
|
template.new(path, 1, options)
|
371
493
|
end
|
372
494
|
when data.is_a?(Proc) || data.is_a?(String)
|
@@ -387,6 +509,7 @@ module Sinatra
|
|
387
509
|
include Templates
|
388
510
|
|
389
511
|
attr_accessor :app
|
512
|
+
attr_reader :template_cache
|
390
513
|
|
391
514
|
def initialize(app=nil)
|
392
515
|
@app = app
|
@@ -401,15 +524,24 @@ module Sinatra
|
|
401
524
|
|
402
525
|
attr_accessor :env, :request, :response, :params
|
403
526
|
|
404
|
-
def call!(env)
|
527
|
+
def call!(env) # :nodoc:
|
405
528
|
@env = env
|
406
529
|
@request = Request.new(env)
|
407
530
|
@response = Response.new
|
408
531
|
@params = indifferent_params(@request.params)
|
409
|
-
|
532
|
+
template_cache.clear if settings.reload_templates
|
533
|
+
force_encoding(@params)
|
410
534
|
|
535
|
+
@response['Content-Type'] = nil
|
411
536
|
invoke { dispatch! }
|
412
537
|
invoke { error_block!(response.status) }
|
538
|
+
unless @response['Content-Type']
|
539
|
+
if body.respond_to?(:to_ary) and body.first.respond_to? :content_type
|
540
|
+
content_type body.first.content_type
|
541
|
+
else
|
542
|
+
content_type :html
|
543
|
+
end
|
544
|
+
end
|
413
545
|
|
414
546
|
status, header, body = @response.finish
|
415
547
|
|
@@ -424,11 +556,20 @@ module Sinatra
|
|
424
556
|
[status, header, body]
|
425
557
|
end
|
426
558
|
|
559
|
+
# Access settings defined with Base.set.
|
560
|
+
def self.settings
|
561
|
+
self
|
562
|
+
end
|
563
|
+
|
427
564
|
# Access settings defined with Base.set.
|
428
565
|
def settings
|
429
|
-
self.class
|
566
|
+
self.class.settings
|
430
567
|
end
|
568
|
+
|
431
569
|
alias_method :options, :settings
|
570
|
+
class << self
|
571
|
+
alias_method :options, :settings
|
572
|
+
end
|
432
573
|
|
433
574
|
# Exit the current block, halts any further processing
|
434
575
|
# of the request, and returns the specified response.
|
@@ -455,64 +596,28 @@ module Sinatra
|
|
455
596
|
end
|
456
597
|
|
457
598
|
private
|
458
|
-
# Run
|
459
|
-
def
|
460
|
-
|
461
|
-
base.
|
462
|
-
end
|
463
|
-
|
464
|
-
# Run after filters defined on the class and all superclasses.
|
465
|
-
def after_filter!(base=self.class)
|
466
|
-
after_filter!(base.superclass) if base.superclass.respond_to?(:after_filters)
|
467
|
-
base.after_filters.each { |block| instance_eval(&block) }
|
599
|
+
# Run filters defined on the class and all superclasses.
|
600
|
+
def filter!(type, base = self.class)
|
601
|
+
filter! type, base.superclass if base.superclass.respond_to?(:filters)
|
602
|
+
base.filters[type].each { |block| instance_eval(&block) }
|
468
603
|
end
|
469
604
|
|
470
605
|
# Run routes defined on the class and all superclasses.
|
471
606
|
def route!(base=self.class, pass_block=nil)
|
472
607
|
if routes = base.routes[@request.request_method]
|
473
|
-
original_params = @params
|
474
|
-
path = unescape(@request.path_info)
|
475
|
-
|
476
608
|
routes.each do |pattern, keys, conditions, block|
|
477
|
-
|
478
|
-
|
479
|
-
params =
|
480
|
-
if keys.any?
|
481
|
-
keys.zip(values).inject({}) do |hash,(k,v)|
|
482
|
-
if k == 'splat'
|
483
|
-
(hash[k] ||= []) << v
|
484
|
-
else
|
485
|
-
hash[k] = v
|
486
|
-
end
|
487
|
-
hash
|
488
|
-
end
|
489
|
-
elsif values.any?
|
490
|
-
{'captures' => values}
|
491
|
-
else
|
492
|
-
{}
|
493
|
-
end
|
494
|
-
@params = original_params.merge(params)
|
495
|
-
@block_params = values
|
496
|
-
|
497
|
-
pass_block = catch(:pass) do
|
498
|
-
conditions.each { |cond|
|
499
|
-
throw :pass if instance_eval(&cond) == false }
|
500
|
-
route_eval(&block)
|
501
|
-
end
|
609
|
+
pass_block = process_route(pattern, keys, conditions) do
|
610
|
+
route_eval(&block)
|
502
611
|
end
|
503
612
|
end
|
504
|
-
|
505
|
-
@params = original_params
|
506
613
|
end
|
507
614
|
|
508
615
|
# Run routes defined in superclass.
|
509
616
|
if base.superclass.respond_to?(:routes)
|
510
|
-
route!
|
511
|
-
return
|
617
|
+
return route!(base.superclass, pass_block)
|
512
618
|
end
|
513
619
|
|
514
620
|
route_eval(&pass_block) if pass_block
|
515
|
-
|
516
621
|
route_missing
|
517
622
|
end
|
518
623
|
|
@@ -521,6 +626,46 @@ module Sinatra
|
|
521
626
|
throw :halt, instance_eval(&block)
|
522
627
|
end
|
523
628
|
|
629
|
+
# If the current request matches pattern and conditions, fill params
|
630
|
+
# with keys and call the given block.
|
631
|
+
# Revert params afterwards.
|
632
|
+
#
|
633
|
+
# Returns pass block.
|
634
|
+
def process_route(pattern, keys, conditions)
|
635
|
+
@original_params ||= @params
|
636
|
+
@path ||= begin
|
637
|
+
path = unescape(@request.path_info)
|
638
|
+
path.empty? ? "/" : path
|
639
|
+
end
|
640
|
+
if match = pattern.match(@path)
|
641
|
+
values = match.captures.to_a
|
642
|
+
params =
|
643
|
+
if keys.any?
|
644
|
+
keys.zip(values).inject({}) do |hash,(k,v)|
|
645
|
+
if k == 'splat'
|
646
|
+
(hash[k] ||= []) << v
|
647
|
+
else
|
648
|
+
hash[k] = v
|
649
|
+
end
|
650
|
+
hash
|
651
|
+
end
|
652
|
+
elsif values.any?
|
653
|
+
{'captures' => values}
|
654
|
+
else
|
655
|
+
{}
|
656
|
+
end
|
657
|
+
@params = @original_params.merge(params)
|
658
|
+
@block_params = values
|
659
|
+
catch(:pass) do
|
660
|
+
conditions.each { |cond|
|
661
|
+
throw :pass if instance_eval(&cond) == false }
|
662
|
+
yield
|
663
|
+
end
|
664
|
+
end
|
665
|
+
ensure
|
666
|
+
@params = @original_params
|
667
|
+
end
|
668
|
+
|
524
669
|
# No matching route was found or all routes passed. The default
|
525
670
|
# implementation is to forward the request downstream when running
|
526
671
|
# as middleware (@app is non-nil); when no downstream app is set, raise
|
@@ -557,6 +702,7 @@ module Sinatra
|
|
557
702
|
end
|
558
703
|
end
|
559
704
|
|
705
|
+
# Creates a Hash with indifferent access.
|
560
706
|
def indifferent_hash
|
561
707
|
Hash.new {|hash,key| hash[key.to_s] if Symbol === key }
|
562
708
|
end
|
@@ -587,7 +733,7 @@ module Sinatra
|
|
587
733
|
end
|
588
734
|
when res.respond_to?(:each)
|
589
735
|
@response.body = res
|
590
|
-
when (100
|
736
|
+
when (100..599) === res
|
591
737
|
@response.status = res
|
592
738
|
end
|
593
739
|
|
@@ -597,16 +743,17 @@ module Sinatra
|
|
597
743
|
# Dispatch a request with error handling.
|
598
744
|
def dispatch!
|
599
745
|
static! if settings.static? && (request.get? || request.head?)
|
600
|
-
|
746
|
+
filter! :before
|
601
747
|
route!
|
602
748
|
rescue NotFound => boom
|
603
749
|
handle_not_found!(boom)
|
604
750
|
rescue ::Exception => boom
|
605
751
|
handle_exception!(boom)
|
606
752
|
ensure
|
607
|
-
|
753
|
+
filter! :after unless env['sinatra.static_file']
|
608
754
|
end
|
609
755
|
|
756
|
+
# Special treatment for 404s in order to play nice with cascades.
|
610
757
|
def handle_not_found!(boom)
|
611
758
|
@env['sinatra.error'] = boom
|
612
759
|
@response.status = 404
|
@@ -615,11 +762,12 @@ module Sinatra
|
|
615
762
|
error_block! boom.class, NotFound
|
616
763
|
end
|
617
764
|
|
765
|
+
# Error handling during requests.
|
618
766
|
def handle_exception!(boom)
|
619
767
|
@env['sinatra.error'] = boom
|
620
768
|
|
621
769
|
dump_errors!(boom) if settings.dump_errors?
|
622
|
-
raise boom
|
770
|
+
raise boom if settings.show_exceptions? and settings.show_exceptions != :after_handler
|
623
771
|
|
624
772
|
@response.status = 500
|
625
773
|
if res = error_block!(boom.class)
|
@@ -644,6 +792,7 @@ module Sinatra
|
|
644
792
|
end
|
645
793
|
end
|
646
794
|
end
|
795
|
+
raise boom if settings.show_exceptions? and keys == Exception
|
647
796
|
nil
|
648
797
|
end
|
649
798
|
|
@@ -654,13 +803,14 @@ module Sinatra
|
|
654
803
|
end
|
655
804
|
|
656
805
|
class << self
|
657
|
-
attr_reader :routes, :
|
806
|
+
attr_reader :routes, :filters, :templates, :errors
|
658
807
|
|
808
|
+
# Removes all routes, filters, middleware and extension hooks from the
|
809
|
+
# current class (not routes/filters/... defined by its superclass).
|
659
810
|
def reset!
|
660
811
|
@conditions = []
|
661
812
|
@routes = {}
|
662
|
-
@
|
663
|
-
@after_filters = []
|
813
|
+
@filters = {:before => [], :after => []}
|
664
814
|
@errors = {}
|
665
815
|
@middleware = []
|
666
816
|
@prototype = nil
|
@@ -700,8 +850,8 @@ module Sinatra
|
|
700
850
|
metadef(option, &value)
|
701
851
|
metadef("#{option}?") { !!__send__(option) }
|
702
852
|
metadef("#{option}=") { |val| metadef(option, &Proc.new{val}) }
|
703
|
-
elsif value == self && option.respond_to?(:
|
704
|
-
option.
|
853
|
+
elsif value == self && option.respond_to?(:each)
|
854
|
+
option.each { |k,v| set(k, v) }
|
705
855
|
elsif respond_to?("#{option}=")
|
706
856
|
__send__ "#{option}=", value
|
707
857
|
else
|
@@ -746,7 +896,7 @@ module Sinatra
|
|
746
896
|
# Load embeded templates from the file; uses the caller's __FILE__
|
747
897
|
# when no file is specified.
|
748
898
|
def inline_templates=(file=nil)
|
749
|
-
file = (file.nil? || file == true) ? caller_files.first : file
|
899
|
+
file = (file.nil? || file == true) ? (caller_files.first || File.expand_path($0)) : file
|
750
900
|
|
751
901
|
begin
|
752
902
|
app, data =
|
@@ -760,7 +910,7 @@ module Sinatra
|
|
760
910
|
template = nil
|
761
911
|
data.each_line do |line|
|
762
912
|
lines += 1
|
763
|
-
if line =~ /^@@\s*(
|
913
|
+
if line =~ /^@@\s*(.*\S)\s*$/
|
764
914
|
template = ''
|
765
915
|
templates[$1.to_sym] = [template, file, lines]
|
766
916
|
elsif template
|
@@ -781,15 +931,24 @@ module Sinatra
|
|
781
931
|
# Define a before filter; runs before all requests within the same
|
782
932
|
# context as route handlers and may access/modify the request and
|
783
933
|
# response.
|
784
|
-
def before(&block)
|
785
|
-
|
934
|
+
def before(path = nil, &block)
|
935
|
+
add_filter(:before, path, &block)
|
786
936
|
end
|
787
937
|
|
788
938
|
# Define an after filter; runs after all requests within the same
|
789
939
|
# context as route handlers and may access/modify the request and
|
790
940
|
# response.
|
791
|
-
def after(&block)
|
792
|
-
|
941
|
+
def after(path = nil, &block)
|
942
|
+
add_filter(:after, path, &block)
|
943
|
+
end
|
944
|
+
|
945
|
+
# add a filter
|
946
|
+
def add_filter(type, path = nil, &block)
|
947
|
+
return filters[type] << block unless path
|
948
|
+
block, *arguments = compile!(type, path, block)
|
949
|
+
add_filter(type) do
|
950
|
+
process_route(*arguments) { instance_eval(&block) }
|
951
|
+
end
|
793
952
|
end
|
794
953
|
|
795
954
|
# Add a route condition. The route is considered non-matching when the
|
@@ -799,27 +958,30 @@ module Sinatra
|
|
799
958
|
end
|
800
959
|
|
801
960
|
private
|
961
|
+
# Condition for matching host name. Parameter might be String or Regexp.
|
802
962
|
def host_name(pattern)
|
803
963
|
condition { pattern === request.host }
|
804
964
|
end
|
805
965
|
|
966
|
+
# Condition for matching user agent. Parameter should be Regexp.
|
967
|
+
# Will set params[:agent].
|
806
968
|
def user_agent(pattern)
|
807
|
-
condition
|
969
|
+
condition do
|
808
970
|
if request.user_agent =~ pattern
|
809
971
|
@params[:agent] = $~[1..-1]
|
810
972
|
true
|
811
973
|
else
|
812
974
|
false
|
813
975
|
end
|
814
|
-
|
976
|
+
end
|
815
977
|
end
|
816
978
|
alias_method :agent, :user_agent
|
817
979
|
|
980
|
+
# Condition for matching mimetypes. Accepts file extensions.
|
818
981
|
def provides(*types)
|
819
|
-
types
|
820
|
-
types.map!{|t| mime_type(t)}
|
982
|
+
types.map! { |t| mime_type(t) }
|
821
983
|
|
822
|
-
condition
|
984
|
+
condition do
|
823
985
|
matching_types = (request.accept & types)
|
824
986
|
unless matching_types.empty?
|
825
987
|
response.headers['Content-Type'] = matching_types.first
|
@@ -827,7 +989,7 @@ module Sinatra
|
|
827
989
|
else
|
828
990
|
false
|
829
991
|
end
|
830
|
-
|
992
|
+
end
|
831
993
|
end
|
832
994
|
|
833
995
|
public
|
@@ -849,22 +1011,10 @@ module Sinatra
|
|
849
1011
|
private
|
850
1012
|
def route(verb, path, options={}, &block)
|
851
1013
|
# Because of self.options.host
|
852
|
-
host_name(options.delete(:
|
853
|
-
|
854
|
-
options.each {|option, args| send(option, *args)}
|
855
|
-
|
856
|
-
pattern, keys = compile(path)
|
857
|
-
conditions, @conditions = @conditions, []
|
858
|
-
|
859
|
-
define_method "#{verb} #{path}", &block
|
860
|
-
unbound_method = instance_method("#{verb} #{path}")
|
861
|
-
block =
|
862
|
-
if block.arity != 0
|
863
|
-
proc { unbound_method.bind(self).call(*@block_params) }
|
864
|
-
else
|
865
|
-
proc { unbound_method.bind(self).call }
|
866
|
-
end
|
1014
|
+
host_name(options.delete(:host)) if options.key?(:host)
|
1015
|
+
options.each { |option, args| send(option, *args) }
|
867
1016
|
|
1017
|
+
block, pattern, keys, conditions = compile! verb, path, block
|
868
1018
|
invoke_hook(:route_added, verb, path, block)
|
869
1019
|
|
870
1020
|
(@routes[verb] ||= []).
|
@@ -875,6 +1025,21 @@ module Sinatra
|
|
875
1025
|
extensions.each { |e| e.send(name, *args) if e.respond_to?(name) }
|
876
1026
|
end
|
877
1027
|
|
1028
|
+
def compile!(verb, path, block)
|
1029
|
+
method_name = "#{verb} #{path}"
|
1030
|
+
|
1031
|
+
define_method(method_name, &block)
|
1032
|
+
unbound_method = instance_method method_name
|
1033
|
+
pattern, keys = compile(path)
|
1034
|
+
conditions, @conditions = @conditions, []
|
1035
|
+
remove_method method_name
|
1036
|
+
|
1037
|
+
[ block.arity != 0 ?
|
1038
|
+
proc { unbound_method.bind(self).call(*@block_params) } :
|
1039
|
+
proc { unbound_method.bind(self).call },
|
1040
|
+
pattern, keys, conditions ]
|
1041
|
+
end
|
1042
|
+
|
878
1043
|
def compile(path)
|
879
1044
|
keys = []
|
880
1045
|
if path.respond_to? :to_str
|
@@ -889,7 +1054,7 @@ module Sinatra
|
|
889
1054
|
Regexp.escape(match)
|
890
1055
|
else
|
891
1056
|
keys << $2[1..-1]
|
892
|
-
"([
|
1057
|
+
"([^/?#]+)"
|
893
1058
|
end
|
894
1059
|
end
|
895
1060
|
[/^#{pattern}$/, keys]
|
@@ -910,6 +1075,8 @@ module Sinatra
|
|
910
1075
|
include(*extensions) if extensions.any?
|
911
1076
|
end
|
912
1077
|
|
1078
|
+
# Register an extension. Alternatively take a block from which an
|
1079
|
+
# extension will be created and registered on the fly.
|
913
1080
|
def register(*extensions, &block)
|
914
1081
|
extensions << Module.new(&block) if block_given?
|
915
1082
|
@extensions += extensions
|
@@ -935,6 +1102,12 @@ module Sinatra
|
|
935
1102
|
@middleware << [middleware, args, block]
|
936
1103
|
end
|
937
1104
|
|
1105
|
+
def quit!(server, handler_name)
|
1106
|
+
## Use thins' hard #stop! if available, otherwise just #stop
|
1107
|
+
server.respond_to?(:stop!) ? server.stop! : server.stop
|
1108
|
+
puts "\n== Sinatra has ended his set (crowd applauds)" unless handler_name =~/cgi/i
|
1109
|
+
end
|
1110
|
+
|
938
1111
|
# Run the Sinatra app as a self-hosted server using
|
939
1112
|
# Thin, Mongrel or WEBrick (in that order)
|
940
1113
|
def run!(options={})
|
@@ -944,11 +1117,7 @@ module Sinatra
|
|
944
1117
|
puts "== Sinatra/#{Sinatra::VERSION} has taken the stage " +
|
945
1118
|
"on #{port} for #{environment} with backup from #{handler_name}" unless handler_name =~/cgi/i
|
946
1119
|
handler.run self, :Host => bind, :Port => port do |server|
|
947
|
-
trap(
|
948
|
-
## Use thins' hard #stop! if available, otherwise just #stop
|
949
|
-
server.respond_to?(:stop!) ? server.stop! : server.stop
|
950
|
-
puts "\n== Sinatra has ended his set (crowd applauds)" unless handler_name =~/cgi/i
|
951
|
-
end
|
1120
|
+
[:INT, :TERM].each { |sig| trap(sig) { quit!(server, handler_name) } }
|
952
1121
|
set :running, true
|
953
1122
|
end
|
954
1123
|
rescue Errno::EADDRINUSE => e
|
@@ -981,7 +1150,7 @@ module Sinatra
|
|
981
1150
|
|
982
1151
|
private
|
983
1152
|
def detect_rack_handler
|
984
|
-
servers = Array(
|
1153
|
+
servers = Array(server)
|
985
1154
|
servers.each do |server_name|
|
986
1155
|
begin
|
987
1156
|
return Rack::Handler.get(server_name.downcase)
|
@@ -1012,12 +1181,13 @@ module Sinatra
|
|
1012
1181
|
end
|
1013
1182
|
|
1014
1183
|
public
|
1015
|
-
CALLERS_TO_IGNORE = [
|
1184
|
+
CALLERS_TO_IGNORE = [ # :nodoc:
|
1016
1185
|
/\/sinatra(\/(base|main|showexceptions))?\.rb$/, # all sinatra code
|
1017
|
-
/lib\/tilt.*\.rb$/,
|
1018
|
-
/\(.*\)/,
|
1019
|
-
/custom_require\.rb$/,
|
1020
|
-
/active_support/,
|
1186
|
+
/lib\/tilt.*\.rb$/, # all tilt code
|
1187
|
+
/\(.*\)/, # generated code
|
1188
|
+
/rubygems\/custom_require\.rb$/, # rubygems require hacks
|
1189
|
+
/active_support/, # active_support require hacks
|
1190
|
+
/<internal:/, # internal in ruby >= 1.9.2
|
1021
1191
|
]
|
1022
1192
|
|
1023
1193
|
# add rubinius (and hopefully other VM impls) ignore patterns ...
|
@@ -1030,6 +1200,8 @@ module Sinatra
|
|
1030
1200
|
map { |file,line| file }
|
1031
1201
|
end
|
1032
1202
|
|
1203
|
+
# Like caller_files, but containing Arrays rather than strings with the
|
1204
|
+
# first element being the file, and the second being the line.
|
1033
1205
|
def caller_locations
|
1034
1206
|
caller(1).
|
1035
1207
|
map { |line| line.split(/:(?=\d|in )/)[0,2] }.
|
@@ -1037,6 +1209,33 @@ module Sinatra
|
|
1037
1209
|
end
|
1038
1210
|
end
|
1039
1211
|
|
1212
|
+
# Fixes encoding issues by
|
1213
|
+
# * defaulting to UTF-8
|
1214
|
+
# * casting params to Encoding.default_external
|
1215
|
+
#
|
1216
|
+
# The latter might not be necessary if Rack handles it one day.
|
1217
|
+
# Keep an eye on Rack's LH #100.
|
1218
|
+
if defined? Encoding
|
1219
|
+
if Encoding.default_external.to_s =~ /^ASCII/
|
1220
|
+
Encoding.default_external = "UTF-8"
|
1221
|
+
end
|
1222
|
+
Encoding.default_internal ||= Encoding.default_external
|
1223
|
+
|
1224
|
+
def force_encoding(data)
|
1225
|
+
return if data == self || data.is_a?(Tempfile)
|
1226
|
+
if data.respond_to? :force_encoding
|
1227
|
+
data.force_encoding(Encoding.default_external)
|
1228
|
+
elsif data.respond_to? :each_value
|
1229
|
+
data.each_value { |v| force_encoding(v) }
|
1230
|
+
elsif data.respond_to? :each
|
1231
|
+
data.each { |v| force_encoding(v) }
|
1232
|
+
end
|
1233
|
+
end
|
1234
|
+
else
|
1235
|
+
def force_encoding(*) end
|
1236
|
+
end
|
1237
|
+
|
1238
|
+
|
1040
1239
|
reset!
|
1041
1240
|
|
1042
1241
|
set :environment, (ENV['RACK_ENV'] || :development).to_sym
|
@@ -1061,11 +1260,11 @@ module Sinatra
|
|
1061
1260
|
set :app_file, nil
|
1062
1261
|
set :root, Proc.new { app_file && File.expand_path(File.dirname(app_file)) }
|
1063
1262
|
set :views, Proc.new { root && File.join(root, 'views') }
|
1064
|
-
set :reload_templates, Proc.new { development? }
|
1263
|
+
set :reload_templates, Proc.new { development? or RUBY_VERSION < '1.8.7' }
|
1065
1264
|
set :lock, false
|
1066
1265
|
|
1067
1266
|
set :public, Proc.new { root && File.join(root, 'public') }
|
1068
|
-
set :static, Proc.new {
|
1267
|
+
set :static, Proc.new { public && File.exist?(public) }
|
1069
1268
|
|
1070
1269
|
error ::Exception do
|
1071
1270
|
response.status = 500
|
@@ -1151,7 +1350,7 @@ module Sinatra
|
|
1151
1350
|
# class scope.
|
1152
1351
|
def self.new(base=Base, options={}, &block)
|
1153
1352
|
base = Class.new(base)
|
1154
|
-
base.
|
1353
|
+
base.class_eval(&block) if block_given?
|
1155
1354
|
base
|
1156
1355
|
end
|
1157
1356
|
|