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.

@@ -6,16 +6,33 @@ require 'sinatra/showexceptions'
6
6
  require 'tilt'
7
7
 
8
8
  module Sinatra
9
- VERSION = '1.3.0.a'
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['HTTP_ACCEPT'].to_s.split(',').map { |a| a.split(';')[0].strip }
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 ||= begin
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
- mime_type << ";#{params.map { |kv| kv.join('=') }.join(', ')}" unless params.empty?
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 data.is_a?(Symbol)
554
- body, path, line = self.class.templates[data]
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 data.is_a?(Proc) || data.is_a?(String)
598
+ when Proc, String
571
599
  body = data.is_a?(String) ? Proc.new { data } : data
572
- path, line = self.class.caller_locations.first
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
- alias_method :options, :settings
647
- class << self
648
- alias_method :options, :settings
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 = self.class)
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=self.class, pass_block=nil)
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
- if match = pattern.match(@request.route)
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 = self.class
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| mime_type(t) }
1062
-
1100
+ types.map! { |t| mime_types(t) }
1101
+ types.flatten!
1063
1102
  condition do
1064
- matching_types = (request.accept & types)
1065
- unless matching_types.empty?
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
- end
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
- Application.register(*extensions, &block)
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
- Application.helpers(*extensions, &block)
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="/__sinatra__/500.png" alt="application error" height="161" width="313" />
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>
@@ -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.a'
7
- s.date = '2011-03-22'
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
@@ -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'
@@ -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 2100 23:43:52 GMT' }
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
@@ -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|