sinatra 1.3.0.a → 1.3.0.b
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 +26 -0
- data/Gemfile +1 -0
- data/README.de.rdoc +103 -39
- data/README.es.rdoc +69 -23
- data/README.fr.rdoc +128 -49
- data/README.rdoc +67 -23
- data/README.ru.rdoc +177 -158
- data/lib/sinatra/base.rb +76 -31
- data/lib/sinatra/showexceptions.rb +1 -1
- data/sinatra.gemspec +2 -3
- data/test/delegator_test.rb +20 -1
- data/test/helpers_test.rb +23 -1
- data/test/routing_test.rb +90 -3
- data/test/templates_test.rb +1 -1
- metadata +3 -4
- data/test/hello.mab +0 -1
data/lib/sinatra/base.rb
CHANGED
@@ -6,16 +6,33 @@ require 'sinatra/showexceptions'
|
|
6
6
|
require 'tilt'
|
7
7
|
|
8
8
|
module Sinatra
|
9
|
-
VERSION = '1.3.0.
|
9
|
+
VERSION = '1.3.0.b'
|
10
10
|
|
11
11
|
# The request object. See Rack::Request for more info:
|
12
12
|
# http://rack.rubyforge.org/doc/classes/Rack/Request.html
|
13
13
|
class Request < Rack::Request
|
14
|
+
def self.new(env)
|
15
|
+
env['sinatra.request'] ||= super
|
16
|
+
end
|
17
|
+
|
14
18
|
# Returns an array of acceptable media types for the response
|
15
19
|
def accept
|
16
|
-
@env['
|
20
|
+
@env['sinatra.accept'] ||= begin
|
21
|
+
entries = @env['HTTP_ACCEPT'].to_s.split(',')
|
22
|
+
entries.map { |e| accept_entry(e) }.sort_by(&:last).map(&:first)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def preferred_type(*types)
|
27
|
+
return accept.first if types.empty?
|
28
|
+
types.flatten!
|
29
|
+
accept.detect do |pattern|
|
30
|
+
type = types.detect { |t| File.fnmatch(pattern, t) }
|
31
|
+
return type if type
|
32
|
+
end
|
17
33
|
end
|
18
34
|
|
35
|
+
alias accept? preferred_type
|
19
36
|
alias secure? ssl?
|
20
37
|
|
21
38
|
def forwarded?
|
@@ -23,16 +40,22 @@ module Sinatra
|
|
23
40
|
end
|
24
41
|
|
25
42
|
def route
|
26
|
-
@route ||=
|
27
|
-
path = Rack::Utils.unescape(path_info)
|
28
|
-
path.empty? ? "/" : path
|
29
|
-
end
|
43
|
+
@route ||= Rack::Utils.unescape(path_info)
|
30
44
|
end
|
31
45
|
|
32
46
|
def path_info=(value)
|
33
47
|
@route = nil
|
34
48
|
super
|
35
49
|
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def accept_entry(entry)
|
54
|
+
type, *options = entry.gsub(/\s/, '').split(';')
|
55
|
+
quality = 0 # we sort smalles first
|
56
|
+
options.delete_if { |e| quality = 1 - e[2..-1].to_f if e.start_with? 'q=' }
|
57
|
+
[type, [quality, type.count('*'), 1 - options.size]]
|
58
|
+
end
|
36
59
|
end
|
37
60
|
|
38
61
|
# The response object. See Rack::Response and Rack::ResponseHelpers for
|
@@ -150,7 +173,8 @@ module Sinatra
|
|
150
173
|
|
151
174
|
# Set the Content-Type of the response body given a media type or file
|
152
175
|
# extension.
|
153
|
-
def content_type(type, params={})
|
176
|
+
def content_type(type = nil, params={})
|
177
|
+
return response['Content-Type'] unless type
|
154
178
|
default = params.delete :default
|
155
179
|
mime_type = mime_type(type) || default
|
156
180
|
fail "Unknown media type: %p" % type if mime_type.nil?
|
@@ -158,7 +182,11 @@ module Sinatra
|
|
158
182
|
unless params.include? :charset or settings.add_charset.all? { |p| not p === mime_type }
|
159
183
|
params[:charset] = params.delete('charset') || settings.default_encoding
|
160
184
|
end
|
161
|
-
|
185
|
+
params.delete :charset if mime_type.include? 'charset'
|
186
|
+
unless params.empty?
|
187
|
+
mime_type << (mime_type.include?(';') ? ', ' : ';')
|
188
|
+
mime_type << params.map { |kv| kv.join('=') }.join(', ')
|
189
|
+
end
|
162
190
|
response['Content-Type'] = mime_type
|
163
191
|
end
|
164
192
|
|
@@ -549,9 +577,9 @@ module Sinatra
|
|
549
577
|
template = Tilt[engine]
|
550
578
|
raise "Template engine not found: #{engine}" if template.nil?
|
551
579
|
|
552
|
-
case
|
553
|
-
when
|
554
|
-
body, path, line =
|
580
|
+
case data
|
581
|
+
when Symbol
|
582
|
+
body, path, line = settings.templates[data]
|
555
583
|
if body
|
556
584
|
body = body.call if body.respond_to?(:call)
|
557
585
|
template.new(path, line.to_i, options) { body }
|
@@ -567,9 +595,9 @@ module Sinatra
|
|
567
595
|
throw :layout_missing if eat_errors and not found
|
568
596
|
template.new(path, 1, options)
|
569
597
|
end
|
570
|
-
when
|
598
|
+
when Proc, String
|
571
599
|
body = data.is_a?(String) ? Proc.new { data } : data
|
572
|
-
path, line =
|
600
|
+
path, line = settings.caller_locations.first
|
573
601
|
template.new(path, line.to_i, options, &body)
|
574
602
|
else
|
575
603
|
raise ArgumentError
|
@@ -643,9 +671,10 @@ module Sinatra
|
|
643
671
|
self.class.settings
|
644
672
|
end
|
645
673
|
|
646
|
-
|
647
|
-
|
648
|
-
|
674
|
+
def options
|
675
|
+
warn "Sinatra::Base#options is deprecated and will be removed, " \
|
676
|
+
"use #settings insetad.\n\tfrom #{caller.first}"
|
677
|
+
settings
|
649
678
|
end
|
650
679
|
|
651
680
|
# Exit the current block, halts any further processing
|
@@ -674,13 +703,13 @@ module Sinatra
|
|
674
703
|
|
675
704
|
private
|
676
705
|
# Run filters defined on the class and all superclasses.
|
677
|
-
def filter!(type, base =
|
706
|
+
def filter!(type, base = settings)
|
678
707
|
filter! type, base.superclass if base.superclass.respond_to?(:filters)
|
679
708
|
base.filters[type].each { |block| instance_eval(&block) }
|
680
709
|
end
|
681
710
|
|
682
711
|
# Run routes defined on the class and all superclasses.
|
683
|
-
def route!(base=
|
712
|
+
def route!(base = settings, pass_block=nil)
|
684
713
|
if routes = base.routes[@request.request_method]
|
685
714
|
routes.each do |pattern, keys, conditions, block|
|
686
715
|
pass_block = process_route(pattern, keys, conditions) do
|
@@ -710,7 +739,9 @@ module Sinatra
|
|
710
739
|
# Returns pass block.
|
711
740
|
def process_route(pattern, keys, conditions)
|
712
741
|
@original_params ||= @params
|
713
|
-
|
742
|
+
route = @request.route
|
743
|
+
route = '/' if route.empty? and not settings.empty_path_info?
|
744
|
+
if match = pattern.match(route)
|
714
745
|
values = match.captures.to_a
|
715
746
|
params =
|
716
747
|
if keys.any?
|
@@ -854,7 +885,7 @@ module Sinatra
|
|
854
885
|
# Find an custom error block for the key(s) specified.
|
855
886
|
def error_block!(*keys)
|
856
887
|
keys.each do |key|
|
857
|
-
base =
|
888
|
+
base = settings
|
858
889
|
while base.respond_to?(:errors)
|
859
890
|
if block = base.errors[key]
|
860
891
|
# found a handler, eval and return result
|
@@ -1006,6 +1037,14 @@ module Sinatra
|
|
1006
1037
|
Rack::Mime::MIME_TYPES[type] = value
|
1007
1038
|
end
|
1008
1039
|
|
1040
|
+
# provides all mime types matching type, including deprecated types:
|
1041
|
+
# mime_types :html # => ['text/html']
|
1042
|
+
# mime_types :js # => ['application/javascript', 'text/javascript']
|
1043
|
+
def mime_types(type)
|
1044
|
+
type = mime_type type
|
1045
|
+
type =~ /^application\/(xml|javascript)$/ ? [type, "text/#$1"] : [type]
|
1046
|
+
end
|
1047
|
+
|
1009
1048
|
# Define a before filter; runs before all requests within the same
|
1010
1049
|
# context as route handlers and may access/modify the request and
|
1011
1050
|
# response.
|
@@ -1058,12 +1097,11 @@ module Sinatra
|
|
1058
1097
|
|
1059
1098
|
# Condition for matching mimetypes. Accepts file extensions.
|
1060
1099
|
def provides(*types)
|
1061
|
-
types.map! { |t|
|
1062
|
-
|
1100
|
+
types.map! { |t| mime_types(t) }
|
1101
|
+
types.flatten!
|
1063
1102
|
condition do
|
1064
|
-
|
1065
|
-
|
1066
|
-
content_type matching_types.first
|
1103
|
+
if type = request.preferred_type(types)
|
1104
|
+
content_type(type)
|
1067
1105
|
true
|
1068
1106
|
else
|
1069
1107
|
false
|
@@ -1093,6 +1131,7 @@ module Sinatra
|
|
1093
1131
|
def route(verb, path, options={}, &block)
|
1094
1132
|
# Because of self.options.host
|
1095
1133
|
host_name(options.delete(:host)) if options.key?(:host)
|
1134
|
+
enable :empty_path_info if path == "" and empty_path_info.nil?
|
1096
1135
|
|
1097
1136
|
block, pattern, keys, conditions = compile! verb, path, block, options
|
1098
1137
|
invoke_hook(:route_added, verb, path, block)
|
@@ -1226,10 +1265,10 @@ module Sinatra
|
|
1226
1265
|
# an instance of this class as end point.
|
1227
1266
|
def build(*args, &bk)
|
1228
1267
|
builder = Rack::Builder.new
|
1229
|
-
setup_logging builder
|
1230
|
-
setup_sessions builder
|
1231
1268
|
builder.use Rack::MethodOverride if method_override?
|
1232
1269
|
builder.use ShowExceptions if show_exceptions?
|
1270
|
+
setup_logging builder
|
1271
|
+
setup_sessions builder
|
1233
1272
|
middleware.each { |c,a,b| builder.use(c, *a, &b) }
|
1234
1273
|
builder.run new!(*args, &bk)
|
1235
1274
|
builder
|
@@ -1342,7 +1381,7 @@ module Sinatra
|
|
1342
1381
|
end
|
1343
1382
|
else
|
1344
1383
|
def self.force_encoding(data, *) data end
|
1345
|
-
|
1384
|
+
end
|
1346
1385
|
|
1347
1386
|
reset!
|
1348
1387
|
|
@@ -1372,6 +1411,7 @@ module Sinatra
|
|
1372
1411
|
|
1373
1412
|
set :absolute_redirects, true
|
1374
1413
|
set :prefixed_redirects, false
|
1414
|
+
set :empty_path_info, nil
|
1375
1415
|
|
1376
1416
|
set :app_file, nil
|
1377
1417
|
set :root, Proc.new { app_file && File.expand_path(File.dirname(app_file)) }
|
@@ -1410,7 +1450,7 @@ module Sinatra
|
|
1410
1450
|
</head>
|
1411
1451
|
<body>
|
1412
1452
|
<h2>Sinatra doesn't know this ditty.</h2>
|
1413
|
-
<img src='/__sinatra__/404.png'>
|
1453
|
+
<img src='#{uri "/__sinatra__/404.png"}'>
|
1414
1454
|
<div id="c">
|
1415
1455
|
Try this:
|
1416
1456
|
<pre>#{request.request_method.downcase} '#{request.path_info}' do\n "Hello World"\nend</pre>
|
@@ -1478,11 +1518,16 @@ module Sinatra
|
|
1478
1518
|
|
1479
1519
|
# Extend the top-level DSL with the modules provided.
|
1480
1520
|
def self.register(*extensions, &block)
|
1481
|
-
|
1521
|
+
Delegator.target.register(*extensions, &block)
|
1482
1522
|
end
|
1483
1523
|
|
1484
1524
|
# Include the helper modules provided in Sinatra's request context.
|
1485
1525
|
def self.helpers(*extensions, &block)
|
1486
|
-
|
1526
|
+
Delegator.target.helpers(*extensions, &block)
|
1527
|
+
end
|
1528
|
+
|
1529
|
+
# Use the middleware for classic applications.
|
1530
|
+
def self.use(*args, &block)
|
1531
|
+
Delegator.target.use(*args, &block)
|
1487
1532
|
end
|
1488
1533
|
end
|
@@ -174,7 +174,7 @@ TEMPLATE = <<-HTML # :nodoc:
|
|
174
174
|
<body>
|
175
175
|
<div id="wrap">
|
176
176
|
<div id="header">
|
177
|
-
<img src="
|
177
|
+
<img src="<%= env['SCRIPT_NAME'] %>/__sinatra__/500.png" alt="application error" height="161" width="313" />
|
178
178
|
<div id="summary">
|
179
179
|
<h1><strong><%=h exception.class %></strong> at <strong><%=h path %>
|
180
180
|
</strong></h1>
|
data/sinatra.gemspec
CHANGED
@@ -3,8 +3,8 @@ Gem::Specification.new do |s|
|
|
3
3
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
4
4
|
|
5
5
|
s.name = 'sinatra'
|
6
|
-
s.version = '1.3.0.
|
7
|
-
s.date = '2011-
|
6
|
+
s.version = '1.3.0.b'
|
7
|
+
s.date = '2011-04-08'
|
8
8
|
|
9
9
|
s.description = "Classy web-development dressed in a DSL"
|
10
10
|
s.summary = "Classy web-development dressed in a DSL"
|
@@ -48,7 +48,6 @@ Gem::Specification.new do |s|
|
|
48
48
|
test/extensions_test.rb
|
49
49
|
test/filter_test.rb
|
50
50
|
test/haml_test.rb
|
51
|
-
test/hello.mab
|
52
51
|
test/helper.rb
|
53
52
|
test/helpers_test.rb
|
54
53
|
test/less_test.rb
|
data/test/delegator_test.rb
CHANGED
@@ -46,7 +46,7 @@ class DelegatorTest < Test::Unit::TestCase
|
|
46
46
|
|
47
47
|
def delegate(&block)
|
48
48
|
assert Sinatra::Delegator.target != Sinatra::Application
|
49
|
-
Object.new.extend(Sinatra::Delegator).instance_eval(&block)
|
49
|
+
Object.new.extend(Sinatra::Delegator).instance_eval(&block) if block
|
50
50
|
Sinatra::Delegator.target
|
51
51
|
end
|
52
52
|
|
@@ -88,6 +88,25 @@ class DelegatorTest < Test::Unit::TestCase
|
|
88
88
|
assert_equal '', response.body
|
89
89
|
end
|
90
90
|
|
91
|
+
it "registers extensions with the delegation target" do
|
92
|
+
app, mixin = mirror, Module.new
|
93
|
+
Sinatra.register mixin
|
94
|
+
assert_equal app.last_call, ["register", mixin.to_s ]
|
95
|
+
end
|
96
|
+
|
97
|
+
it "registers helpers with the delegation target" do
|
98
|
+
app, mixin = mirror, Module.new
|
99
|
+
Sinatra.helpers mixin
|
100
|
+
assert_equal app.last_call, ["helpers", mixin.to_s ]
|
101
|
+
end
|
102
|
+
|
103
|
+
|
104
|
+
it "registers helpers with the delegation target" do
|
105
|
+
app, mixin = mirror, Module.new
|
106
|
+
Sinatra.use mixin
|
107
|
+
assert_equal app.last_call, ["use", mixin.to_s ]
|
108
|
+
end
|
109
|
+
|
91
110
|
delegates 'get'
|
92
111
|
delegates 'patch'
|
93
112
|
delegates 'put'
|
data/test/helpers_test.rb
CHANGED
@@ -442,6 +442,28 @@ class HelpersTest < Test::Unit::TestCase
|
|
442
442
|
get '/'
|
443
443
|
assert tests_ran
|
444
444
|
end
|
445
|
+
|
446
|
+
it 'handles already present params' do
|
447
|
+
mock_app do
|
448
|
+
get '/' do
|
449
|
+
content_type 'foo/bar;level=1', :charset => 'utf-8'
|
450
|
+
'ok'
|
451
|
+
end
|
452
|
+
end
|
453
|
+
get '/'
|
454
|
+
assert_equal 'foo/bar;level=1, charset=utf-8', response['Content-Type']
|
455
|
+
end
|
456
|
+
|
457
|
+
it 'does not add charset if present' do
|
458
|
+
mock_app do
|
459
|
+
get '/' do
|
460
|
+
content_type 'text/plain;charset=utf-16'
|
461
|
+
'ok'
|
462
|
+
end
|
463
|
+
end
|
464
|
+
get '/'
|
465
|
+
assert_equal 'text/plain;charset=utf-16', response['Content-Type']
|
466
|
+
end
|
445
467
|
end
|
446
468
|
|
447
469
|
describe 'send_file' do
|
@@ -716,7 +738,7 @@ class HelpersTest < Test::Unit::TestCase
|
|
716
738
|
get '/compare', {}, { 'HTTP_IF_MODIFIED_SINCE' => 'Sun, 26 Sep 2010 23:43:52 GMT' }
|
717
739
|
assert_equal 200, status
|
718
740
|
assert_equal 'foo', body
|
719
|
-
get '/compare', {}, { 'HTTP_IF_MODIFIED_SINCE' => 'Sun, 26 Sep
|
741
|
+
get '/compare', {}, { 'HTTP_IF_MODIFIED_SINCE' => 'Sun, 26 Sep 2030 23:43:52 GMT' }
|
720
742
|
assert_equal 304, status
|
721
743
|
assert_equal '', body
|
722
744
|
end
|
data/test/routing_test.rb
CHANGED
@@ -93,12 +93,28 @@ class RoutingTest < Test::Unit::TestCase
|
|
93
93
|
assert_equal "<h1>Not Found</h1>", response.body
|
94
94
|
end
|
95
95
|
|
96
|
-
it 'matches empty PATH_INFO to "/"' do
|
97
|
-
mock_app
|
96
|
+
it 'matches empty PATH_INFO to "/" if no route is defined for ""' do
|
97
|
+
mock_app do
|
98
98
|
get '/' do
|
99
99
|
'worked'
|
100
100
|
end
|
101
|
-
|
101
|
+
end
|
102
|
+
|
103
|
+
get '/', {}, "PATH_INFO" => ""
|
104
|
+
assert ok?
|
105
|
+
assert_equal 'worked', body
|
106
|
+
end
|
107
|
+
|
108
|
+
it 'matches empty PATH_INFO to "" if a route is defined for ""' do
|
109
|
+
mock_app do
|
110
|
+
get '/' do
|
111
|
+
'did not work'
|
112
|
+
end
|
113
|
+
|
114
|
+
get '' do
|
115
|
+
'worked'
|
116
|
+
end
|
117
|
+
end
|
102
118
|
|
103
119
|
get '/', {}, "PATH_INFO" => ""
|
104
120
|
assert ok?
|
@@ -724,6 +740,77 @@ class RoutingTest < Test::Unit::TestCase
|
|
724
740
|
assert_equal 'default', body
|
725
741
|
end
|
726
742
|
|
743
|
+
it 'respects user agent prefferences for the content type' do
|
744
|
+
mock_app { get('/', :provides => [:png, :html]) { content_type }}
|
745
|
+
get '/', {}, { 'HTTP_ACCEPT' => 'image/png;q=0.5,text/html;q=0.8' }
|
746
|
+
assert_body 'text/html;charset=utf-8'
|
747
|
+
get '/', {}, { 'HTTP_ACCEPT' => 'image/png;q=0.8,text/html;q=0.5' }
|
748
|
+
assert_body 'image/png'
|
749
|
+
end
|
750
|
+
|
751
|
+
it 'accepts generic types' do
|
752
|
+
mock_app do
|
753
|
+
get('/', :provides => :xml) { content_type }
|
754
|
+
get('/') { 'no match' }
|
755
|
+
end
|
756
|
+
get '/'
|
757
|
+
assert_body 'no match'
|
758
|
+
get '/', {}, { 'HTTP_ACCEPT' => 'foo/*' }
|
759
|
+
assert_body 'no match'
|
760
|
+
get '/', {}, { 'HTTP_ACCEPT' => 'application/*' }
|
761
|
+
assert_body 'application/xml;charset=utf-8'
|
762
|
+
get '/', {}, { 'HTTP_ACCEPT' => '*/*' }
|
763
|
+
assert_body 'application/xml;charset=utf-8'
|
764
|
+
end
|
765
|
+
|
766
|
+
it 'prefers concrete over partly generic types' do
|
767
|
+
mock_app { get('/', :provides => [:png, :html]) { content_type }}
|
768
|
+
get '/', {}, { 'HTTP_ACCEPT' => 'image/*, text/html' }
|
769
|
+
assert_body 'text/html;charset=utf-8'
|
770
|
+
get '/', {}, { 'HTTP_ACCEPT' => 'image/png, text/*' }
|
771
|
+
assert_body 'image/png'
|
772
|
+
end
|
773
|
+
|
774
|
+
it 'prefers concrete over fully generic types' do
|
775
|
+
mock_app { get('/', :provides => [:png, :html]) { content_type }}
|
776
|
+
get '/', {}, { 'HTTP_ACCEPT' => '*/*, text/html' }
|
777
|
+
assert_body 'text/html;charset=utf-8'
|
778
|
+
get '/', {}, { 'HTTP_ACCEPT' => 'image/png, */*' }
|
779
|
+
assert_body 'image/png'
|
780
|
+
end
|
781
|
+
|
782
|
+
it 'prefers partly generic over fully generic types' do
|
783
|
+
mock_app { get('/', :provides => [:png, :html]) { content_type }}
|
784
|
+
get '/', {}, { 'HTTP_ACCEPT' => '*/*, text/*' }
|
785
|
+
assert_body 'text/html;charset=utf-8'
|
786
|
+
get '/', {}, { 'HTTP_ACCEPT' => 'image/*, */*' }
|
787
|
+
assert_body 'image/png'
|
788
|
+
end
|
789
|
+
|
790
|
+
it 'respects quality with generic types' do
|
791
|
+
mock_app { get('/', :provides => [:png, :html]) { content_type }}
|
792
|
+
get '/', {}, { 'HTTP_ACCEPT' => 'image/*;q=1, text/html;q=0' }
|
793
|
+
assert_body 'image/png'
|
794
|
+
get '/', {}, { 'HTTP_ACCEPT' => 'image/png;q=0.5, text/*;q=0.7' }
|
795
|
+
assert_body 'text/html;charset=utf-8'
|
796
|
+
end
|
797
|
+
|
798
|
+
it 'accepts both text/javascript and application/javascript for js' do
|
799
|
+
mock_app { get('/', :provides => :js) { content_type }}
|
800
|
+
get '/', {}, { 'HTTP_ACCEPT' => 'application/javascript' }
|
801
|
+
assert_body 'application/javascript;charset=utf-8'
|
802
|
+
get '/', {}, { 'HTTP_ACCEPT' => 'text/javascript' }
|
803
|
+
assert_body 'text/javascript;charset=utf-8'
|
804
|
+
end
|
805
|
+
|
806
|
+
it 'accepts both text/xml and application/xml for xml' do
|
807
|
+
mock_app { get('/', :provides => :xml) { content_type }}
|
808
|
+
get '/', {}, { 'HTTP_ACCEPT' => 'application/xml' }
|
809
|
+
assert_body 'application/xml;charset=utf-8'
|
810
|
+
get '/', {}, { 'HTTP_ACCEPT' => 'text/xml' }
|
811
|
+
assert_body 'text/xml;charset=utf-8'
|
812
|
+
end
|
813
|
+
|
727
814
|
it 'passes a single url param as block parameters when one param is specified' do
|
728
815
|
mock_app {
|
729
816
|
get '/:foo' do |foo|
|