adamwiggins-sinatra 0.8.9 → 0.10.1

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