sinatra 1.3.6 → 1.4.0.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.

Files changed (71) hide show
  1. data/CHANGES +96 -22
  2. data/Gemfile +11 -3
  3. data/README.de.md +2590 -0
  4. data/README.es.rdoc +66 -38
  5. data/README.fr.md +2630 -0
  6. data/README.hu.rdoc +3 -2
  7. data/README.jp.rdoc +16 -3
  8. data/README.ko.rdoc +11 -5
  9. data/README.md +2699 -0
  10. data/README.pt-br.rdoc +152 -21
  11. data/README.pt-pt.rdoc +3 -2
  12. data/README.ru.md +2724 -0
  13. data/README.zh.rdoc +3 -3
  14. data/Rakefile +3 -4
  15. data/examples/chat.rb +3 -3
  16. data/lib/sinatra/base.rb +433 -247
  17. data/lib/sinatra/main.rb +4 -2
  18. data/lib/sinatra/showexceptions.rb +6 -1
  19. data/lib/sinatra/version.rb +1 -1
  20. data/test/base_test.rb +21 -9
  21. data/test/builder_test.rb +15 -19
  22. data/test/coffee_test.rb +4 -6
  23. data/test/compile_test.rb +154 -0
  24. data/test/contest.rb +4 -6
  25. data/test/creole_test.rb +5 -5
  26. data/test/delegator_test.rb +1 -3
  27. data/test/erb_test.rb +32 -20
  28. data/test/extensions_test.rb +1 -3
  29. data/test/filter_test.rb +65 -56
  30. data/test/haml_test.rb +34 -26
  31. data/test/helpers_test.rb +331 -221
  32. data/test/integration_helper.rb +8 -0
  33. data/test/integration_test.rb +3 -1
  34. data/test/less_test.rb +10 -8
  35. data/test/liquid_test.rb +22 -4
  36. data/test/mapped_error_test.rb +122 -96
  37. data/test/markaby_test.rb +5 -5
  38. data/test/markdown_test.rb +5 -5
  39. data/test/middleware_test.rb +3 -3
  40. data/test/nokogiri_test.rb +4 -6
  41. data/test/rabl_test.rb +89 -0
  42. data/test/radius_test.rb +4 -4
  43. data/test/rdoc_test.rb +7 -7
  44. data/test/readme_test.rb +14 -30
  45. data/test/request_test.rb +15 -0
  46. data/test/response_test.rb +3 -4
  47. data/test/result_test.rb +11 -33
  48. data/test/route_added_hook_test.rb +10 -10
  49. data/test/routing_test.rb +123 -1
  50. data/test/sass_test.rb +26 -26
  51. data/test/scss_test.rb +16 -16
  52. data/test/server_test.rb +2 -2
  53. data/test/settings_test.rb +48 -4
  54. data/test/sinatra_test.rb +2 -7
  55. data/test/slim_test.rb +37 -23
  56. data/test/static_test.rb +56 -15
  57. data/test/streaming_test.rb +11 -2
  58. data/test/templates_test.rb +117 -45
  59. data/test/textile_test.rb +9 -9
  60. data/test/views/hello.rabl +2 -0
  61. data/test/views/hello.wlang +1 -0
  62. data/test/views/hello.yajl +1 -0
  63. data/test/views/layout2.rabl +3 -0
  64. data/test/views/layout2.wlang +2 -0
  65. data/test/wlang_test.rb +87 -0
  66. data/test/yajl_test.rb +86 -0
  67. metadata +27 -17
  68. data/README.de.rdoc +0 -2097
  69. data/README.fr.rdoc +0 -2036
  70. data/README.rdoc +0 -2017
  71. data/README.ru.rdoc +0 -1785
@@ -2,7 +2,7 @@
2
2
  <i>注:本文档仅仅是英文版的翻译,会出现内容没有及时更新的情况发生。
3
3
  如有不一致的地方,请以英文版为准。</i>
4
4
 
5
- Sinatra是一个基于Ruby语言,以最小精力为代价快速创建web应用为目的的DSL(
5
+ Sinatra是一个基于Ruby语言,以最小精力为代价快速创建web应用为目的的{DSL}[http://en.wikipedia.org/wiki/Domain-specific_language]
6
6
  领域专属语言):
7
7
 
8
8
  # myapp.rb
@@ -15,7 +15,7 @@ Sinatra是一个基于Ruby语言,以最小精力为代价快速创建web应用
15
15
  安装gem然后运行:
16
16
 
17
17
  gem install sinatra
18
- ruby -rubygems myapp.rb
18
+ ruby myapp.rb
19
19
 
20
20
  在该地址查看: http://localhost:4567
21
21
 
@@ -1016,7 +1016,7 @@ Sinatra并不理解。使用 +mime_type+ 通过文件扩展名来注册它们:
1016
1016
 
1017
1017
  这些辅助方法并不会为你做任何缓存,而是将必要的信息传送给你的缓存
1018
1018
  如果你在寻找缓存的快速解决方案,试试
1019
- {rack-cache}[http://rtomayko.github.com/rack-cache/]:
1019
+ {rack-cache}[https://github.com/rtomayko/rack-cache]:
1020
1020
 
1021
1021
  require "rack/cache"
1022
1022
  require "sinatra"
data/Rakefile CHANGED
@@ -1,4 +1,3 @@
1
- # encoding: UTF-8
2
1
  require 'rake/clean'
3
2
  require 'rake/testtask'
4
3
  require 'fileutils'
@@ -99,8 +98,8 @@ desc "list of contributors"
99
98
  task :thanks, [:release,:backports] do |t, a|
100
99
  a.with_defaults :release => "#{prev_version}..HEAD",
101
100
  :backports => "#{prev_feature}.0..#{prev_feature}.x"
102
- included = `git log --format=format:"%aN\t%s" #{a.release}`.lines.map { |l| l.force_encoding('binary') }
103
- excluded = `git log --format=format:"%aN\t%s" #{a.backports}`.lines.map { |l| l.force_encoding('binary') }
101
+ included = `git log --format=format:"%aN\t%s" #{a.release}`.lines.to_a
102
+ excluded = `git log --format=format:"%aN\t%s" #{a.backports}`.lines.to_a
104
103
  commits = (included - excluded).group_by { |c| c[/^[^\t]+/] }
105
104
  authors = commits.keys.sort_by { |n| - commits[n].size } - team
106
105
  puts authors[0..-2].join(', ') << " and " << authors.last,
@@ -167,7 +166,7 @@ if defined?(Gem)
167
166
 
168
167
  task 'release' => ['test', package('.gem')] do
169
168
  if File.binread("CHANGES") =~ /= \d\.\d\.\d . not yet released$/i
170
- fail 'please update changes first'
169
+ fail 'please update changes first' unless %x{git symbolic-ref HEAD} == "refs/heads/prerelease\n"
171
170
  end
172
171
 
173
172
  sh <<-SH
@@ -42,9 +42,6 @@ __END__
42
42
 
43
43
  @@ chat
44
44
  <pre id='chat'></pre>
45
- <form>
46
- <input id='msg' placeholder='type message here...' />
47
- </form>
48
45
 
49
46
  <script>
50
47
  // reading
@@ -59,3 +56,6 @@ __END__
59
56
  });
60
57
  </script>
61
58
 
59
+ <form>
60
+ <input id='msg' placeholder='type message here...' />
61
+ </form>
@@ -16,11 +16,14 @@ module Sinatra
16
16
  # The request object. See Rack::Request for more info:
17
17
  # http://rack.rubyforge.org/doc/classes/Rack/Request.html
18
18
  class Request < Rack::Request
19
+ HEADER_PARAM = /\s*[\w.]+=(?:[\w.]+|"(?:[^"\\]|\\.)*")?\s*/
20
+ HEADER_VALUE_WITH_PARAMS = /(?:(?:\w+|\*)\/(?:\w+|\*))\s*(?:;#{HEADER_PARAM})*/
21
+
19
22
  # Returns an array of acceptable media types for the response
20
23
  def accept
21
24
  @env['sinatra.accept'] ||= begin
22
- entries = @env['HTTP_ACCEPT'].to_s.split(',')
23
- entries.map { |e| accept_entry(e) }.sort_by(&:last).map(&:first)
25
+ entries = @env['HTTP_ACCEPT'].to_s.scan(HEADER_VALUE_WITH_PARAMS)
26
+ entries.map { |e| AcceptEntry.new(e) }.sort
24
27
  end
25
28
  end
26
29
 
@@ -50,11 +53,37 @@ module Sinatra
50
53
 
51
54
  private
52
55
 
53
- def accept_entry(entry)
54
- type, *options = entry.delete(' ').split(';')
55
- quality = 0 # we sort smallest 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]]
56
+ class AcceptEntry
57
+ attr_accessor :params
58
+
59
+ def initialize(entry)
60
+ params = entry.scan(HEADER_PARAM).map do |s|
61
+ key, value = s.strip.split('=', 2)
62
+ value = value[1..-2].gsub(/\\(.)/, '\1') if value.start_with?('"')
63
+ [key, value]
64
+ end
65
+
66
+ @type = entry[/[^;]+/].delete(' ')
67
+ @params = Hash[params]
68
+ @q = @params.delete('q') { "1.0" }.to_f
69
+ end
70
+
71
+ def <=>(other)
72
+ other.priority <=> self.priority
73
+ end
74
+
75
+ def priority
76
+ # We sort in descending order; better matches should be higher.
77
+ [ @q, -@type.count('*'), @params.size ]
78
+ end
79
+
80
+ def [](param)
81
+ @params[param]
82
+ end
83
+
84
+ def to_str
85
+ @type
86
+ end
58
87
  end
59
88
  end
60
89
 
@@ -96,7 +125,7 @@ module Sinatra
96
125
  headers["Content-Length"] = body.inject(0) { |l, p| l + Rack::Utils.bytesize(p) }.to_s
97
126
  end
98
127
 
99
- [status.to_i, headers, result]
128
+ [status.to_i, header, result]
100
129
  end
101
130
 
102
131
  private
@@ -130,7 +159,7 @@ module Sinatra
130
159
 
131
160
  private
132
161
 
133
- def setup_close(env, status, headers, body)
162
+ def setup_close(env, status, header, body)
134
163
  return unless body.respond_to? :close and env.include? 'async.close'
135
164
  env['async.close'].callback { body.close }
136
165
  env['async.close'].errback { body.close }
@@ -164,24 +193,25 @@ module Sinatra
164
193
  end
165
194
 
166
195
  class NotFound < NameError #:nodoc:
167
- def code ; 404 ; end
196
+ def http_status; 404 end
168
197
  end
169
198
 
170
199
  # Methods available to routes, before/after filters, and views.
171
200
  module Helpers
172
201
  # Set or retrieve the response status code.
173
- def status(value=nil)
202
+ def status(value = nil)
174
203
  response.status = value if value
175
204
  response.status
176
205
  end
177
206
 
178
207
  # Set or retrieve the response body. When a block is given,
179
208
  # evaluation is deferred until the body is read with #each.
180
- def body(value=nil, &block)
209
+ def body(value = nil, &block)
181
210
  if block_given?
182
211
  def block.each; yield(call) end
183
212
  response.body = block
184
213
  elsif value
214
+ headers.delete 'Content-Length' unless request.head?
185
215
  response.body = value
186
216
  else
187
217
  response.body
@@ -198,7 +228,7 @@ module Sinatra
198
228
 
199
229
  # According to RFC 2616 section 14.30, "the field value consists of a
200
230
  # single absolute URI"
201
- response['Location'] = uri(uri, settings.absolute_redirects?, settings.prefixed_redirects?)
231
+ response['Location'] = uri(uri.to_s, settings.absolute_redirects?, settings.prefixed_redirects?)
202
232
  halt(*args)
203
233
  end
204
234
 
@@ -224,19 +254,19 @@ module Sinatra
224
254
  alias to uri
225
255
 
226
256
  # Halt processing and return the error status provided.
227
- def error(code, body=nil)
257
+ def error(code, body = nil)
228
258
  code, body = 500, code.to_str if code.respond_to? :to_str
229
259
  response.body = body unless body.nil?
230
260
  halt code
231
261
  end
232
262
 
233
263
  # Halt processing and return a 404 Not Found.
234
- def not_found(body=nil)
264
+ def not_found(body = nil)
235
265
  error 404, body
236
266
  end
237
267
 
238
268
  # Set multiple response headers with Hash.
239
- def headers(hash=nil)
269
+ def headers(hash = nil)
240
270
  response.headers.merge! hash if hash
241
271
  response.headers
242
272
  end
@@ -258,7 +288,7 @@ module Sinatra
258
288
 
259
289
  # Set the Content-Type of the response body given a media type or file
260
290
  # extension.
261
- def content_type(type = nil, params={})
291
+ def content_type(type = nil, params = {})
262
292
  return response['Content-Type'] unless type
263
293
  default = params.delete :default
264
294
  mime_type = mime_type(type) || default
@@ -270,15 +300,18 @@ module Sinatra
270
300
  params.delete :charset if mime_type.include? 'charset'
271
301
  unless params.empty?
272
302
  mime_type << (mime_type.include?(';') ? ', ' : ';')
273
- mime_type << params.map { |kv| kv.join('=') }.join(', ')
303
+ mime_type << params.map do |key, val|
304
+ val = val.inspect if val =~ /[";,]/
305
+ "#{key}=#{val}"
306
+ end.join(', ')
274
307
  end
275
308
  response['Content-Type'] = mime_type
276
309
  end
277
310
 
278
311
  # Set the Content-Disposition to "attachment" with the specified filename,
279
312
  # instructing the user agents to prompt to save.
280
- def attachment(filename=nil)
281
- response['Content-Disposition'] = 'attachment'
313
+ def attachment(filename = nil, disposition = 'attachment')
314
+ response['Content-Disposition'] = disposition.to_s
282
315
  if filename
283
316
  params = '; filename="%s"' % File.basename(filename)
284
317
  response['Content-Disposition'] << params
@@ -288,16 +321,16 @@ module Sinatra
288
321
  end
289
322
 
290
323
  # Use the contents of the file at +path+ as the response body.
291
- def send_file(path, opts={})
324
+ def send_file(path, opts = {})
292
325
  if opts[:type] or not response['Content-Type']
293
326
  content_type opts[:type] || File.extname(path), :default => 'application/octet-stream'
294
327
  end
295
328
 
296
- if opts[:disposition] == 'attachment' || opts[:filename]
297
- attachment opts[:filename] || path
298
- elsif opts[:disposition] == 'inline'
299
- response['Content-Disposition'] = 'inline'
300
- end
329
+ disposition = opts[:disposition]
330
+ filename = opts[:filename]
331
+ disposition = 'attachment' if disposition.nil? and filename
332
+ filename = path if filename.nil?
333
+ attachment(filename, disposition) if disposition
301
334
 
302
335
  last_modified opts[:last_modified] if opts[:last_modified]
303
336
 
@@ -305,7 +338,8 @@ module Sinatra
305
338
  file.path = path
306
339
  result = file.serving env
307
340
  result[1].each { |k,v| headers[k] ||= v }
308
- halt result[0], result[2]
341
+ headers['Content-Length'] = result[1]['Content-Length']
342
+ halt opts[:status] || result[0], result[2]
309
343
  rescue Errno::ENOENT
310
344
  not_found
311
345
  end
@@ -356,6 +390,10 @@ module Sinatra
356
390
  end
357
391
 
358
392
  alias errback callback
393
+
394
+ def closed?
395
+ @closed
396
+ end
359
397
  end
360
398
 
361
399
  # Allows to start sending data to the client even though later parts of
@@ -575,7 +613,7 @@ module Sinatra
575
613
  #
576
614
  # Possible options are:
577
615
  # :content_type The content type to use, same arguments as content_type.
578
- # :layout If set to false, no layout is rendered, otherwise
616
+ # :layout If set to something falsy, no layout is rendered, otherwise
579
617
  # the specified layout is used (Ignored for `sass` and `less`)
580
618
  # :layout_engine Engine to use for rendering the layout.
581
619
  # :locals A hash with local variables that should be available
@@ -593,82 +631,96 @@ module Sinatra
593
631
  @default_layout = :layout
594
632
  end
595
633
 
596
- def erb(template, options={}, locals={})
597
- render :erb, template, options, locals
634
+ def erb(template, options = {}, locals = {}, &block)
635
+ render(:erb, template, options, locals, &block)
598
636
  end
599
637
 
600
- def erubis(template, options={}, locals={})
638
+ def erubis(template, options = {}, locals = {})
601
639
  warn "Sinatra::Templates#erubis is deprecated and will be removed, use #erb instead.\n" \
602
640
  "If you have Erubis installed, it will be used automatically."
603
641
  render :erubis, template, options, locals
604
642
  end
605
643
 
606
- def haml(template, options={}, locals={})
607
- render :haml, template, options, locals
644
+ def haml(template, options = {}, locals = {}, &block)
645
+ render(:haml, template, options, locals, &block)
608
646
  end
609
647
 
610
- def sass(template, options={}, locals={})
648
+ def sass(template, options = {}, locals = {})
611
649
  options.merge! :layout => false, :default_content_type => :css
612
650
  render :sass, template, options, locals
613
651
  end
614
652
 
615
- def scss(template, options={}, locals={})
653
+ def scss(template, options = {}, locals = {})
616
654
  options.merge! :layout => false, :default_content_type => :css
617
655
  render :scss, template, options, locals
618
656
  end
619
657
 
620
- def less(template, options={}, locals={})
658
+ def less(template, options = {}, locals = {})
621
659
  options.merge! :layout => false, :default_content_type => :css
622
660
  render :less, template, options, locals
623
661
  end
624
662
 
625
- def builder(template=nil, options={}, locals={}, &block)
663
+ def builder(template = nil, options = {}, locals = {}, &block)
626
664
  options[:default_content_type] = :xml
627
665
  render_ruby(:builder, template, options, locals, &block)
628
666
  end
629
667
 
630
- def liquid(template, options={}, locals={})
631
- render :liquid, template, options, locals
668
+ def liquid(template, options = {}, locals = {}, &block)
669
+ render(:liquid, template, options, locals, &block)
632
670
  end
633
671
 
634
- def markdown(template, options={}, locals={})
672
+ def markdown(template, options = {}, locals = {})
635
673
  render :markdown, template, options, locals
636
674
  end
637
675
 
638
- def textile(template, options={}, locals={})
676
+ def textile(template, options = {}, locals = {})
639
677
  render :textile, template, options, locals
640
678
  end
641
679
 
642
- def rdoc(template, options={}, locals={})
680
+ def rdoc(template, options = {}, locals = {})
643
681
  render :rdoc, template, options, locals
644
682
  end
645
683
 
646
- def radius(template, options={}, locals={})
684
+ def radius(template, options = {}, locals = {})
647
685
  render :radius, template, options, locals
648
686
  end
649
687
 
650
- def markaby(template=nil, options={}, locals={}, &block)
688
+ def markaby(template = nil, options = {}, locals = {}, &block)
651
689
  render_ruby(:mab, template, options, locals, &block)
652
690
  end
653
691
 
654
- def coffee(template, options={}, locals={})
692
+ def coffee(template, options = {}, locals = {})
655
693
  options.merge! :layout => false, :default_content_type => :js
656
694
  render :coffee, template, options, locals
657
695
  end
658
696
 
659
- def nokogiri(template=nil, options={}, locals={}, &block)
697
+ def nokogiri(template = nil, options = {}, locals = {}, &block)
660
698
  options[:default_content_type] = :xml
661
699
  render_ruby(:nokogiri, template, options, locals, &block)
662
700
  end
663
701
 
664
- def slim(template, options={}, locals={})
665
- render :slim, template, options, locals
702
+ def slim(template, options = {}, locals = {}, &block)
703
+ render(:slim, template, options, locals, &block)
666
704
  end
667
705
 
668
- def creole(template, options={}, locals={})
706
+ def creole(template, options = {}, locals = {})
669
707
  render :creole, template, options, locals
670
708
  end
671
709
 
710
+ def wlang(template, options = {}, locals = {}, &block)
711
+ render(:wlang, template, options, locals, &block)
712
+ end
713
+
714
+ def yajl(template, options = {}, locals = {})
715
+ options[:default_content_type] = :json
716
+ render :yajl, template, options, locals
717
+ end
718
+
719
+ def rabl(template, options = {}, locals = {})
720
+ Rabl.register!
721
+ render :rabl, template, options, locals
722
+ end
723
+
672
724
  # Calls the given block for every possible template file in views,
673
725
  # named name.ext, where ext is registered on engine.
674
726
  def find_template(views, name, engine)
@@ -679,29 +731,36 @@ module Sinatra
679
731
  end
680
732
  end
681
733
 
682
- private
734
+ private
683
735
  # logic shared between builder and nokogiri
684
- def render_ruby(engine, template, options={}, locals={}, &block)
736
+ def render_ruby(engine, template, options = {}, locals = {}, &block)
685
737
  options, template = template, nil if template.is_a?(Hash)
686
738
  template = Proc.new { block } if template.nil?
687
739
  render engine, template, options, locals
688
740
  end
689
741
 
690
- def render(engine, data, options={}, locals={}, &block)
742
+ def render(engine, data, options = {}, locals = {}, &block)
691
743
  # merge app-level options
692
- options = settings.send(engine).merge(options) if settings.respond_to?(engine)
693
- options[:outvar] ||= '@_out_buf'
694
- options[:default_encoding] ||= settings.default_encoding
744
+ engine_options = settings.respond_to?(engine) ? settings.send(engine) : {}
745
+ options = engine_options.merge(options)
695
746
 
696
747
  # extract generic options
697
748
  locals = options.delete(:locals) || locals || {}
698
749
  views = options.delete(:views) || settings.views || "./views"
699
- layout = options.delete(:layout)
750
+ layout = options[:layout]
751
+ layout = false if layout.nil? && options.include?(:layout)
700
752
  eat_errors = layout.nil?
701
- layout = @default_layout if layout.nil? or layout == true
702
- content_type = options.delete(:content_type) || options.delete(:default_content_type)
703
- layout_engine = options.delete(:layout_engine) || engine
704
- scope = options.delete(:scope) || self
753
+ layout = engine_options[:layout] if layout.nil? or layout == true
754
+ layout = @default_layout if layout.nil? or layout == true
755
+ layout_options = options.delete(:layout_options) || {}
756
+ content_type = options.delete(:content_type) || options.delete(:default_content_type)
757
+ layout_engine = options.delete(:layout_engine) || engine
758
+ scope = options.delete(:scope) || self
759
+ options.delete(:layout)
760
+
761
+ # set some defaults
762
+ options[:outvar] ||= '@_out_buf'
763
+ options[:default_encoding] ||= settings.default_encoding
705
764
 
706
765
  # compile and render template
707
766
  begin
@@ -716,6 +775,7 @@ module Sinatra
716
775
  # render layout
717
776
  if layout
718
777
  options = options.merge(:views => views, :layout => false, :eat_errors => eat_errors, :scope => scope)
778
+ options.merge! layout_options
719
779
  catch(:layout_missing) { return render(layout_engine, layout, options, locals) { output } }
720
780
  end
721
781
 
@@ -725,7 +785,7 @@ module Sinatra
725
785
 
726
786
  def compile_template(engine, data, options, views)
727
787
  eat_errors = options.delete :eat_errors
728
- template_cache.fetch engine, data, options do
788
+ template_cache.fetch engine, data, options, views do
729
789
  template = Tilt[engine]
730
790
  raise "Template engine not found: #{engine}" if template.nil?
731
791
 
@@ -768,7 +828,7 @@ module Sinatra
768
828
  attr_accessor :app
769
829
  attr_reader :template_cache
770
830
 
771
- def initialize(app=nil)
831
+ def initialize(app = nil)
772
832
  super()
773
833
  @app = app
774
834
  @template_cache = Tilt::Cache.new
@@ -792,7 +852,7 @@ module Sinatra
792
852
 
793
853
  @response['Content-Type'] = nil
794
854
  invoke { dispatch! }
795
- invoke { error_block!(response.status) }
855
+ invoke { error_block!(response.status) } unless @env['sinatra.error']
796
856
 
797
857
  unless @response['Content-Type']
798
858
  if Array === body and body[0].respond_to? :content_type
@@ -846,165 +906,184 @@ module Sinatra
846
906
  end
847
907
 
848
908
  private
849
- # Run filters defined on the class and all superclasses.
850
- def filter!(type, base = settings)
851
- filter! type, base.superclass if base.superclass.respond_to?(:filters)
852
- base.filters[type].each { |args| process_route(*args) }
853
- end
854
-
855
- # Run routes defined on the class and all superclasses.
856
- def route!(base = settings, pass_block=nil)
857
- if routes = base.routes[@request.request_method]
858
- routes.each do |pattern, keys, conditions, block|
859
- pass_block = process_route(pattern, keys, conditions) do |*args|
860
- route_eval { block[*args] }
861
- end
862
- end
863
- end
909
+ # Run filters defined on the class and all superclasses.
910
+ def filter!(type, base = settings)
911
+ filter! type, base.superclass if base.superclass.respond_to?(:filters)
912
+ base.filters[type].each { |args| process_route(*args) }
913
+ end
864
914
 
865
- # Run routes defined in superclass.
866
- if base.superclass.respond_to?(:routes)
867
- return route!(base.superclass, pass_block)
915
+ # Run routes defined on the class and all superclasses.
916
+ def route!(base = settings, pass_block = nil)
917
+ if routes = base.routes[@request.request_method]
918
+ routes.each do |pattern, keys, conditions, block|
919
+ pass_block = process_route(pattern, keys, conditions) do |*args|
920
+ route_eval { block[*args] }
921
+ end
868
922
  end
869
-
870
- route_eval(&pass_block) if pass_block
871
- route_missing
872
923
  end
873
924
 
874
- # Run a route block and throw :halt with the result.
875
- def route_eval
876
- throw :halt, yield
925
+ # Run routes defined in superclass.
926
+ if base.superclass.respond_to?(:routes)
927
+ return route!(base.superclass, pass_block)
877
928
  end
878
929
 
879
- # If the current request matches pattern and conditions, fill params
880
- # with keys and call the given block.
881
- # Revert params afterwards.
882
- #
883
- # Returns pass block.
884
- def process_route(pattern, keys, conditions, block = nil, values = [])
885
- route = @request.path_info
886
- route = '/' if route.empty? and not settings.empty_path_info?
887
- return unless match = pattern.match(route)
888
- values += match.captures.to_a.map { |v| force_encoding URI.decode_www_form_component(v) if v }
930
+ route_eval(&pass_block) if pass_block
931
+ route_missing
932
+ end
889
933
 
890
- if values.any?
891
- original, @params = params, params.merge('splat' => [], 'captures' => values)
892
- keys.zip(values) { |k,v| Array === @params[k] ? @params[k] << v : @params[k] = v if v }
893
- end
934
+ # Run a route block and throw :halt with the result.
935
+ def route_eval
936
+ throw :halt, yield
937
+ end
894
938
 
895
- catch(:pass) do
896
- conditions.each { |c| throw :pass if c.bind(self).call == false }
897
- block ? block[self, values] : yield(self, values)
898
- end
899
- ensure
900
- @params = original if original
939
+ # If the current request matches pattern and conditions, fill params
940
+ # with keys and call the given block.
941
+ # Revert params afterwards.
942
+ #
943
+ # Returns pass block.
944
+ def process_route(pattern, keys, conditions, block = nil, values = [])
945
+ route = @request.path_info
946
+ route = '/' if route.empty? and not settings.empty_path_info?
947
+ return unless match = pattern.match(route)
948
+ values += match.captures.to_a.map { |v| force_encoding URI.decode_www_form_component(v) if v }
949
+
950
+ if values.any?
951
+ original, @params = params, params.merge('splat' => [], 'captures' => values)
952
+ keys.zip(values) { |k,v| Array === @params[k] ? @params[k] << v : @params[k] = v if v }
901
953
  end
902
954
 
903
- # No matching route was found or all routes passed. The default
904
- # implementation is to forward the request downstream when running
905
- # as middleware (@app is non-nil); when no downstream app is set, raise
906
- # a NotFound exception. Subclasses can override this method to perform
907
- # custom route miss logic.
908
- def route_missing
909
- if @app
910
- forward
911
- else
912
- raise NotFound
913
- end
955
+ catch(:pass) do
956
+ conditions.each { |c| throw :pass if c.bind(self).call == false }
957
+ block ? block[self, values] : yield(self, values)
914
958
  end
959
+ ensure
960
+ @params = original if original
961
+ end
915
962
 
916
- # Attempt to serve static files from public directory. Throws :halt when
917
- # a matching file is found, returns nil otherwise.
918
- def static!
919
- return if (public_dir = settings.public_folder).nil?
920
- public_dir = File.expand_path(public_dir)
963
+ # No matching route was found or all routes passed. The default
964
+ # implementation is to forward the request downstream when running
965
+ # as middleware (@app is non-nil); when no downstream app is set, raise
966
+ # a NotFound exception. Subclasses can override this method to perform
967
+ # custom route miss logic.
968
+ def route_missing
969
+ if @app
970
+ forward
971
+ else
972
+ raise NotFound
973
+ end
974
+ end
921
975
 
922
- path = File.expand_path(public_dir + unescape(request.path_info))
923
- return unless path.start_with?(public_dir) and File.file?(path)
976
+ # Attempt to serve static files from public directory. Throws :halt when
977
+ # a matching file is found, returns nil otherwise.
978
+ def static!
979
+ return if (public_dir = settings.public_folder).nil?
980
+ public_dir = File.expand_path(public_dir)
924
981
 
925
- env['sinatra.static_file'] = path
926
- cache_control(*settings.static_cache_control) if settings.static_cache_control?
927
- send_file path, :disposition => nil
928
- end
982
+ path = File.expand_path(public_dir + unescape(request.path_info))
983
+ return unless path.start_with?(public_dir) and File.file?(path)
929
984
 
930
- # Enable string or symbol key access to the nested params hash.
931
- def indifferent_params(params)
932
- params = indifferent_hash.merge(params)
933
- params.each do |key, value|
934
- next unless value.is_a?(Hash)
935
- params[key] = indifferent_params(value)
936
- end
937
- end
985
+ env['sinatra.static_file'] = path
986
+ cache_control(*settings.static_cache_control) if settings.static_cache_control?
987
+ send_file path, :disposition => nil
988
+ end
938
989
 
939
- # Creates a Hash with indifferent access.
940
- def indifferent_hash
941
- Hash.new {|hash,key| hash[key.to_s] if Symbol === key }
990
+ # Enable string or symbol key access to the nested params hash.
991
+ def indifferent_params(object)
992
+ case object
993
+ when Hash
994
+ new_hash = indifferent_hash
995
+ object.each { |key, value| new_hash[key] = indifferent_params(value) }
996
+ new_hash
997
+ when Array
998
+ object.map { |item| indifferent_params(item) }
999
+ else
1000
+ object
942
1001
  end
1002
+ end
943
1003
 
944
- # Run the block with 'throw :halt' support and apply result to the response.
945
- def invoke
946
- res = catch(:halt) { yield }
947
- res = [res] if Fixnum === res or String === res
948
- if Array === res and Fixnum === res.first
949
- status(res.shift)
950
- body(res.pop)
951
- headers(*res)
952
- elsif res.respond_to? :each
953
- body res
954
- end
955
- nil # avoid double setting the same response tuple twice
956
- end
1004
+ # Creates a Hash with indifferent access.
1005
+ def indifferent_hash
1006
+ Hash.new {|hash,key| hash[key.to_s] if Symbol === key }
1007
+ end
957
1008
 
958
- # Dispatch a request with error handling.
959
- def dispatch!
960
- invoke do
961
- static! if settings.static? && (request.get? || request.head?)
962
- filter! :before
963
- route!
964
- end
965
- rescue ::Exception => boom
966
- invoke { handle_exception!(boom) }
967
- ensure
968
- filter! :after unless env['sinatra.static_file']
969
- end
1009
+ # Run the block with 'throw :halt' support and apply result to the response.
1010
+ def invoke
1011
+ res = catch(:halt) { yield }
1012
+ res = [res] if Fixnum === res or String === res
1013
+ if Array === res and Fixnum === res.first
1014
+ res = res.dup
1015
+ status(res.shift)
1016
+ body(res.pop)
1017
+ headers(*res)
1018
+ elsif res.respond_to? :each
1019
+ body res
1020
+ end
1021
+ nil # avoid double setting the same response tuple twice
1022
+ end
970
1023
 
971
- # Error handling during requests.
972
- def handle_exception!(boom)
973
- @env['sinatra.error'] = boom
974
- status boom.respond_to?(:code) ? Integer(boom.code) : 500
1024
+ # Dispatch a request with error handling.
1025
+ def dispatch!
1026
+ invoke do
1027
+ static! if settings.static? && (request.get? || request.head?)
1028
+ filter! :before
1029
+ route!
1030
+ end
1031
+ rescue ::Exception => boom
1032
+ invoke { handle_exception!(boom) }
1033
+ ensure
1034
+ filter! :after unless env['sinatra.static_file']
1035
+ end
975
1036
 
976
- if server_error?
977
- dump_errors! boom if settings.dump_errors?
978
- raise boom if settings.show_exceptions? and settings.show_exceptions != :after_handler
979
- end
1037
+ # Error handling during requests.
1038
+ def handle_exception!(boom)
1039
+ @env['sinatra.error'] = boom
980
1040
 
981
- if not_found?
982
- headers['X-Cascade'] = 'pass'
983
- body '<h1>Not Found</h1>'
984
- end
1041
+ if boom.respond_to? :http_status
1042
+ status(boom.http_status)
1043
+ elsif settings.use_code? and boom.respond_to? :code and boom.code.between? 400, 599
1044
+ status(boom.code)
1045
+ else
1046
+ status(500)
1047
+ end
985
1048
 
986
- res = error_block!(boom.class, boom) || error_block!(status, boom)
987
- return res if res or not server_error?
988
- raise boom if settings.raise_errors? or settings.show_exceptions?
989
- error_block! Exception, boom
1049
+ status(500) unless status.between? 400, 599
1050
+
1051
+ if server_error?
1052
+ dump_errors! boom if settings.dump_errors?
1053
+ raise boom if settings.show_exceptions? and settings.show_exceptions != :after_handler
1054
+ end
1055
+
1056
+ if not_found?
1057
+ headers['X-Cascade'] = 'pass' if settings.x_cascade?
1058
+ body '<h1>Not Found</h1>'
990
1059
  end
991
1060
 
992
- # Find an custom error block for the key(s) specified.
993
- def error_block!(key, *block_params)
994
- base = settings
995
- while base.respond_to?(:errors)
996
- next base = base.superclass unless args = base.errors[key]
1061
+ res = error_block!(boom.class, boom) || error_block!(status, boom)
1062
+ return res if res or not server_error?
1063
+ raise boom if settings.raise_errors? or settings.show_exceptions?
1064
+ error_block! Exception, boom
1065
+ end
1066
+
1067
+ # Find an custom error block for the key(s) specified.
1068
+ def error_block!(key, *block_params)
1069
+ base = settings
1070
+ while base.respond_to?(:errors)
1071
+ next base = base.superclass unless args_array = base.errors[key]
1072
+ args_array.reverse_each do |args|
1073
+ first = args == args_array.first
997
1074
  args += [block_params]
998
- return process_route(*args)
1075
+ resp = process_route(*args)
1076
+ return resp unless resp.nil? && !first
999
1077
  end
1000
- return false unless key.respond_to? :superclass and key.superclass < Exception
1001
- error_block!(key.superclass, *block_params)
1002
1078
  end
1079
+ return false unless key.respond_to? :superclass and key.superclass < Exception
1080
+ error_block!(key.superclass, *block_params)
1081
+ end
1003
1082
 
1004
- def dump_errors!(boom)
1005
- msg = ["#{boom.class} - #{boom.message}:", *boom.backtrace].join("\n\t")
1006
- @env['rack.errors'].puts(msg)
1007
- end
1083
+ def dump_errors!(boom)
1084
+ msg = ["#{boom.class} - #{boom.message}:", *boom.backtrace].join("\n\t")
1085
+ @env['rack.errors'].puts(msg)
1086
+ end
1008
1087
 
1009
1088
  class << self
1010
1089
  attr_reader :routes, :filters, :templates, :errors
@@ -1099,12 +1178,13 @@ module Sinatra
1099
1178
  args = compile! "ERROR", //, block
1100
1179
  codes = codes.map { |c| Array(c) }.flatten
1101
1180
  codes << Exception if codes.empty?
1102
- codes.each { |c| @errors[c] = args }
1181
+ codes.each { |c| (@errors[c] ||= []) << args }
1103
1182
  end
1104
1183
 
1105
1184
  # Sugar for `error(404) { ... }`
1106
1185
  def not_found(&block)
1107
- error 404, &block
1186
+ error(404, &block)
1187
+ error(Sinatra::NotFound, &block)
1108
1188
  end
1109
1189
 
1110
1190
  # Define a named template. The block must return the template source.
@@ -1114,13 +1194,13 @@ module Sinatra
1114
1194
  end
1115
1195
 
1116
1196
  # Define the layout template. The block must return the template source.
1117
- def layout(name=:layout, &block)
1197
+ def layout(name = :layout, &block)
1118
1198
  template name, &block
1119
1199
  end
1120
1200
 
1121
1201
  # Load embeded templates from the file; uses the caller's __FILE__
1122
1202
  # when no file is specified.
1123
- def inline_templates=(file=nil)
1203
+ def inline_templates=(file = nil)
1124
1204
  file = (file.nil? || file == true) ? (caller_files.first || File.expand_path($0)) : file
1125
1205
 
1126
1206
  begin
@@ -1152,7 +1232,7 @@ module Sinatra
1152
1232
  end
1153
1233
 
1154
1234
  # Lookup or register a mime type in Rack's mime registry.
1155
- def mime_type(type, value=nil)
1235
+ def mime_type(type, value = nil)
1156
1236
  return type if type.nil? || type.to_s.include?('/')
1157
1237
  type = ".#{type}" unless type.to_s[0] == ?.
1158
1238
  return Rack::Mime.mime_type(type, nil) unless value
@@ -1194,10 +1274,18 @@ module Sinatra
1194
1274
  end
1195
1275
 
1196
1276
  def public=(value)
1197
- warn ":public is no longer used to avoid overloading Module#public, use :public_folder instead"
1277
+ warn ":public is no longer used to avoid overloading Module#public, use :public_dir instead"
1198
1278
  set(:public_folder, value)
1199
1279
  end
1200
1280
 
1281
+ def public_dir=(value)
1282
+ self.public_folder = value
1283
+ end
1284
+
1285
+ def public_dir
1286
+ public_folder
1287
+ end
1288
+
1201
1289
  private
1202
1290
  # Dynamically defines a method on settings.
1203
1291
  def define_singleton(name, content = Proc.new)
@@ -1232,8 +1320,11 @@ module Sinatra
1232
1320
  types.map! { |t| mime_types(t) }
1233
1321
  types.flatten!
1234
1322
  condition do
1235
- if type = request.preferred_type(types)
1236
- content_type(type)
1323
+ if type = response['Content-Type']
1324
+ types.include? type or types.include? type[/^[^;]+/]
1325
+ elsif type = request.preferred_type(types)
1326
+ params = (type.respond_to?(:params) ? type.params : {})
1327
+ content_type(type, params)
1237
1328
  true
1238
1329
  else
1239
1330
  false
@@ -1244,7 +1335,7 @@ module Sinatra
1244
1335
  public
1245
1336
  # Defining a `GET` handler also automatically defines
1246
1337
  # a `HEAD` handler.
1247
- def get(path, opts={}, &block)
1338
+ def get(path, opts = {}, &block)
1248
1339
  conditions = @conditions.dup
1249
1340
  route('GET', path, opts, &block)
1250
1341
 
@@ -1252,15 +1343,15 @@ module Sinatra
1252
1343
  route('HEAD', path, opts, &block)
1253
1344
  end
1254
1345
 
1255
- def put(path, opts={}, &bk) route 'PUT', path, opts, &bk end
1256
- def post(path, opts={}, &bk) route 'POST', path, opts, &bk end
1257
- def delete(path, opts={}, &bk) route 'DELETE', path, opts, &bk end
1258
- def head(path, opts={}, &bk) route 'HEAD', path, opts, &bk end
1259
- def options(path, opts={}, &bk) route 'OPTIONS', path, opts, &bk end
1260
- def patch(path, opts={}, &bk) route 'PATCH', path, opts, &bk end
1346
+ def put(path, opts = {}, &bk) route 'PUT', path, opts, &bk end
1347
+ def post(path, opts = {}, &bk) route 'POST', path, opts, &bk end
1348
+ def delete(path, opts = {}, &bk) route 'DELETE', path, opts, &bk end
1349
+ def head(path, opts = {}, &bk) route 'HEAD', path, opts, &bk end
1350
+ def options(path, opts = {}, &bk) route 'OPTIONS', path, opts, &bk end
1351
+ def patch(path, opts = {}, &bk) route 'PATCH', path, opts, &bk end
1261
1352
 
1262
1353
  private
1263
- def route(verb, path, options={}, &block)
1354
+ def route(verb, path, options = {}, &block)
1264
1355
  # Because of self.options.host
1265
1356
  host_name(options.delete(:host)) if options.key?(:host)
1266
1357
  enable :empty_path_info if path == "" and empty_path_info.nil?
@@ -1296,17 +1387,26 @@ module Sinatra
1296
1387
  def compile(path)
1297
1388
  keys = []
1298
1389
  if path.respond_to? :to_str
1299
- pattern = path.to_str.gsub(/[^\?\%\\\/\:\*\w]/) { |c| encoded(c) }
1390
+ ignore = ""
1391
+ pattern = path.to_str.gsub(/[^\?\%\\\/\:\*\w]/) do |c|
1392
+ ignore << escaped(c).join if c.match(/[\.@]/)
1393
+ patt = encoded(c)
1394
+ patt.gsub(/%[\da-fA-F]{2}/) do |match|
1395
+ match.split(//).map {|char| char =~ /[A-Z]/ ? "[#{char}#{char.tr('A-Z', 'a-z')}]" : char}.join
1396
+ end
1397
+ end
1300
1398
  pattern.gsub!(/((:\w+)|\*)/) do |match|
1301
1399
  if match == "*"
1302
1400
  keys << 'splat'
1303
1401
  "(.*?)"
1304
1402
  else
1305
1403
  keys << $2[1..-1]
1306
- "([^/?#]+)"
1404
+ ignore_pattern = safe_ignore(ignore)
1405
+
1406
+ ignore_pattern
1307
1407
  end
1308
1408
  end
1309
- [/^#{pattern}$/, keys]
1409
+ [/\A#{pattern}\z/, keys]
1310
1410
  elsif path.respond_to?(:keys) && path.respond_to?(:match)
1311
1411
  [path, path.keys]
1312
1412
  elsif path.respond_to?(:names) && path.respond_to?(:match)
@@ -1322,11 +1422,38 @@ module Sinatra
1322
1422
 
1323
1423
  def encoded(char)
1324
1424
  enc = URI.escape(char)
1325
- enc = "(?:#{Regexp.escape enc}|#{URI.escape char, /./})" if enc == char
1425
+ enc = "(?:#{escaped(char, enc).join('|')})" if enc == char
1326
1426
  enc = "(?:#{enc}|#{encoded('+')})" if char == " "
1327
1427
  enc
1328
1428
  end
1329
1429
 
1430
+ def escaped(char, enc = URI.escape(char))
1431
+ [Regexp.escape(enc), URI.escape(char, /./)]
1432
+ end
1433
+
1434
+ def safe_ignore(ignore)
1435
+ unsafe_ignore = []
1436
+ ignore = ignore.gsub(/%[\da-fA-F]{2}/) do |hex|
1437
+ unsafe_ignore << hex[1..2]
1438
+ ''
1439
+ end
1440
+ unsafe_patterns = unsafe_ignore.map do |unsafe|
1441
+ chars = unsafe.split(//).map do |char|
1442
+ if char =~ /[A-Z]/
1443
+ char <<= char.tr('A-Z', 'a-z')
1444
+ end
1445
+ char
1446
+ end
1447
+
1448
+ "|(?:%[^#{chars[0]}].|%[#{chars[0]}][^#{chars[1]}])"
1449
+ end
1450
+ if unsafe_patterns.length > 0
1451
+ "((?:[^#{ignore}/?#%]#{unsafe_patterns.join()})+)"
1452
+ else
1453
+ "([^#{ignore}/?#]+)"
1454
+ end
1455
+ end
1456
+
1330
1457
  public
1331
1458
  # Makes the methods defined in the block and in the Modules given
1332
1459
  # in `extensions` available to the handlers and templates
@@ -1369,13 +1496,14 @@ module Sinatra
1369
1496
  end
1370
1497
 
1371
1498
  # Run the Sinatra app as a self-hosted server using
1372
- # Thin, Mongrel or WEBrick (in that order). If given a block, will call
1499
+ # Thin, Puma, Mongrel, or WEBrick (in that order). If given a block, will call
1373
1500
  # with the constructed handler once we have taken the stage.
1374
- def run!(options={})
1501
+ def run!(options = {})
1375
1502
  set options
1376
- handler = detect_rack_handler
1377
- handler_name = handler.name.gsub(/.*::/, '')
1378
- handler.run self, :Host => bind, :Port => port do |server|
1503
+ handler = detect_rack_handler
1504
+ handler_name = handler.name.gsub(/.*::/, '')
1505
+ server_settings = settings.respond_to?(:server_settings) ? settings.server_settings : {}
1506
+ handler.run self, server_settings.merge(:Port => port, :Host => bind) do |server|
1379
1507
  unless handler_name =~ /cgi/i
1380
1508
  $stderr.puts "== Sinatra/#{Sinatra::VERSION} has taken the stage " +
1381
1509
  "on #{port} for #{environment} with backup from #{handler_name}"
@@ -1401,15 +1529,17 @@ module Sinatra
1401
1529
  # pipeline. The object is guaranteed to respond to #call but may not be
1402
1530
  # an instance of the class new was called on.
1403
1531
  def new(*args, &bk)
1404
- build(Rack::Builder.new, *args, &bk).to_app
1532
+ instance = new!(*args, &bk)
1533
+ Wrapper.new(build(instance).to_app, instance)
1405
1534
  end
1406
1535
 
1407
1536
  # Creates a Rack::Builder instance with all the middleware set up and
1408
- # an instance of this class as end point.
1409
- def build(builder, *args, &bk)
1537
+ # the given +app+ as end point.
1538
+ def build(app)
1539
+ builder = Rack::Builder.new
1410
1540
  setup_default_middleware builder
1411
1541
  setup_middleware builder
1412
- builder.run new!(*args, &bk)
1542
+ builder.run app
1413
1543
  builder
1414
1544
  end
1415
1545
 
@@ -1460,8 +1590,9 @@ module Sinatra
1460
1590
  def setup_protection(builder)
1461
1591
  return unless protection?
1462
1592
  options = Hash === protection ? protection.dup : {}
1593
+ protect_session = options.fetch(:session) { sessions? }
1463
1594
  options[:except] = Array options[:except]
1464
- options[:except] += [:session_hijacking, :remote_token] unless sessions?
1595
+ options[:except] += [:session_hijacking, :remote_token] unless protect_session
1465
1596
  options[:reaction] ||= :drop_session
1466
1597
  builder.use Rack::Protection, options
1467
1598
  end
@@ -1577,7 +1708,9 @@ module Sinatra
1577
1708
  set :logging, false
1578
1709
  set :protection, true
1579
1710
  set :method_override, false
1711
+ set :use_code, false
1580
1712
  set :default_encoding, "utf-8"
1713
+ set :x_cascade, true
1581
1714
  set :add_charset, %w[javascript xml xhtml+xml json].map { |t| "application/#{t}" }
1582
1715
  settings.add_charset << /^text\//
1583
1716
 
@@ -1597,9 +1730,21 @@ module Sinatra
1597
1730
 
1598
1731
  set :run, false # start server via at-exit hook?
1599
1732
  set :running, false # is the built-in server running now?
1600
- set :server, %w[thin mongrel webrick]
1601
- set :bind, '0.0.0.0'
1602
- set :port, 4567
1733
+ set :server, %w[http webrick]
1734
+ set :bind, Proc.new { development? ? 'localhost' : '0.0.0.0' }
1735
+ set :port, Integer(ENV['PORT'] || 4567)
1736
+
1737
+ ruby_engine = defined?(RUBY_ENGINE) && RUBY_ENGINE
1738
+
1739
+ if ruby_engine == 'macruby'
1740
+ server.unshift 'control_tower'
1741
+ else
1742
+ server.unshift 'mongrel' if ruby_engine.nil?
1743
+ server.unshift 'puma' if ruby_engine != 'rbx'
1744
+ server.unshift 'thin' if ruby_engine != 'jruby'
1745
+ server.unshift 'puma' if ruby_engine == 'rbx'
1746
+ server.unshift 'trinidad' if ruby_engine =='jruby'
1747
+ end
1603
1748
 
1604
1749
  set :absolute_redirects, true
1605
1750
  set :prefixed_redirects, false
@@ -1632,25 +1777,44 @@ module Sinatra
1632
1777
  error NotFound do
1633
1778
  content_type 'text/html'
1634
1779
 
1635
- (<<-HTML).gsub(/^ {8}/, '')
1636
- <!DOCTYPE html>
1637
- <html>
1638
- <head>
1639
- <style type="text/css">
1640
- body { text-align:center;font-family:helvetica,arial;font-size:22px;
1641
- color:#888;margin:20px}
1642
- #c {margin:0 auto;width:500px;text-align:left}
1643
- </style>
1644
- </head>
1645
- <body>
1646
- <h2>Sinatra doesn&rsquo;t know this ditty.</h2>
1647
- <img src='#{uri "/__sinatra__/404.png"}'>
1648
- <div id="c">
1649
- Try this:
1650
- <pre>#{request.request_method.downcase} '#{request.path_info}' do\n "Hello World"\nend</pre>
1651
- </div>
1652
- </body>
1653
- </html>
1780
+ if self.class == Sinatra::Application
1781
+ code = <<-RUBY.gsub(/^ {12}/, '')
1782
+ #{request.request_method.downcase} '#{request.path_info}' do
1783
+ "Hello World"
1784
+ end
1785
+ RUBY
1786
+ else
1787
+ code = <<-RUBY.gsub(/^ {12}/, '')
1788
+ class #{self.class}
1789
+ #{request.request_method.downcase} '#{request.path_info}' do
1790
+ "Hello World"
1791
+ end
1792
+ end
1793
+ RUBY
1794
+
1795
+ file = settings.app_file.to_s.sub(settings.root.to_s, '').sub(/^\//, '')
1796
+ code = "# in #{file}\n#{code}" unless file.empty?
1797
+ end
1798
+
1799
+ (<<-HTML).gsub(/^ {10}/, '')
1800
+ <!DOCTYPE html>
1801
+ <html>
1802
+ <head>
1803
+ <style type="text/css">
1804
+ body { text-align:center;font-family:helvetica,arial;font-size:22px;
1805
+ color:#888;margin:20px}
1806
+ #c {margin:0 auto;width:500px;text-align:left}
1807
+ </style>
1808
+ </head>
1809
+ <body>
1810
+ <h2>Sinatra doesn&rsquo;t know this ditty.</h2>
1811
+ <img src='#{uri "/__sinatra__/404.png"}'>
1812
+ <div id="c">
1813
+ Try this:
1814
+ <pre>#{code}</pre>
1815
+ </div>
1816
+ </body>
1817
+ </html>
1654
1818
  HTML
1655
1819
  end
1656
1820
  end
@@ -1694,7 +1858,7 @@ module Sinatra
1694
1858
  delegate :get, :patch, :put, :post, :delete, :head, :options, :template, :layout,
1695
1859
  :before, :after, :error, :not_found, :configure, :set, :mime_type,
1696
1860
  :enable, :disable, :use, :development?, :test?, :production?,
1697
- :helpers, :settings
1861
+ :helpers, :settings, :register
1698
1862
 
1699
1863
  class << self
1700
1864
  attr_accessor :target
@@ -1703,9 +1867,31 @@ module Sinatra
1703
1867
  self.target = Application
1704
1868
  end
1705
1869
 
1870
+ class Wrapper
1871
+ def initialize(stack, instance)
1872
+ @stack, @instance = stack, instance
1873
+ end
1874
+
1875
+ def settings
1876
+ @instance.settings
1877
+ end
1878
+
1879
+ def helpers
1880
+ @instance
1881
+ end
1882
+
1883
+ def call(env)
1884
+ @stack.call(env)
1885
+ end
1886
+
1887
+ def inspect
1888
+ "#<#{@instance.class} app_file=#{settings.app_file.inspect}>"
1889
+ end
1890
+ end
1891
+
1706
1892
  # Create a new Sinatra application. The block is evaluated in the new app's
1707
1893
  # class scope.
1708
- def self.new(base=Base, options={}, &block)
1894
+ def self.new(base = Base, options = {}, &block)
1709
1895
  base = Class.new(base)
1710
1896
  base.class_eval(&block) if block_given?
1711
1897
  base