sinatra 0.9.1.1 → 0.9.2

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of sinatra might be problematic. Click here for more details.

data/CHANGES CHANGED
@@ -1,3 +1,84 @@
1
+ = 0.9.2 / unreleased
2
+
3
+ * This version is compatible with Rack 1.0. [Rein Henrichs]
4
+
5
+ * The development-mode unhandled exception / error page has been
6
+ greatly enhanced, functionally and aesthetically. The error
7
+ page is used when the :show_exceptions option is enabled and an
8
+ exception propagates outside of a route handler or before filter.
9
+ [Simon Rozet / Matte Noble / Ryan Tomayko]
10
+
11
+ * Backtraces that move through templates now include filenames and
12
+ line numbers where possible. [#51 / S. Brent Faulkner]
13
+
14
+ * All templates now have an app-level option for setting default
15
+ template options (:haml, :sass, :erb, :builder). The app-level
16
+ option value must be a Hash if set and is merged with the
17
+ template options specified to the render method (Base#haml,
18
+ Base#erb, Base#builder). [S. Brent Faulkner, Ryan Tomayko]
19
+
20
+ * The method signature for all template rendering methods has
21
+ been unified: "def engine(template, options={}, locals={})".
22
+ The options Hash now takes the generic :views, :layout, and
23
+ :locals options but also any template-specific options. The
24
+ generic options are removed before calling the template specific
25
+ render method. Locals may be specified using either the
26
+ :locals key in the options hash or a second Hash option to the
27
+ rendering method. [#191 / Ryan Tomayko]
28
+
29
+ * The receiver is now passed to "configure" blocks. This
30
+ allows for the following idiom in top-level apps:
31
+ configure { |app| set :foo, app.root + '/foo' }
32
+ [TJ Holowaychuck / Ryan Tomayko]
33
+
34
+ * The "sinatra/test" lib is deprecated and will be removed in
35
+ Sinatra 1.0. This includes the Sinatra::Test module and
36
+ Sinatra::TestHarness class in addition to all the framework
37
+ test helpers that were deprecated in 0.9.1. The Rack::Test
38
+ lib should be used instead: http://gitrdoc.com/brynary/rack-test
39
+ [#176 / Simon Rozet]
40
+
41
+ * Development mode source file reloading has been removed. The
42
+ "shotgun" (http://rtomayko.github.com/shotgun/) program can be
43
+ used to achieve the same basic functionality in most situations.
44
+ Passenger users should use the "tmp/always_restart.txt"
45
+ file (http://tinyurl.com/c67o4h). [#166 / Ryan Tomayko]
46
+
47
+ * Auto-requiring template libs in the erb, builder, haml, and
48
+ sass methods is deprecated due to thread-safety issues. You must
49
+ require the template libs explicitly in your app file. [Simon Rozet]
50
+
51
+ * A new Sinatra::Base#route_missing method was added. route_missing
52
+ is sent when no route matches the request or all route handlers
53
+ pass. The default implementation forwards the request to the
54
+ downstream app when running as middleware (i.e., "@app" is
55
+ non-nil), or raises a NotFound exception when no downstream app
56
+ is defined. Subclasses can override this method to perform custom
57
+ route miss logic. [Jon Crosby]
58
+
59
+ * A new Sinatra::Base#route_eval method was added. The method
60
+ yields to the block and throws :halt with the result. Subclasses
61
+ can override this method to tap into the route execution logic.
62
+ [TJ Holowaychuck]
63
+
64
+ * Fix the "-x" (enable request mutex / locking) command line
65
+ argument. Passing -x now properly sets the :lock option.
66
+ [S. Brent Faulkner, Ryan Tomayko]
67
+
68
+ * Fix writer ("foo=") and predicate ("foo?") methods in extension
69
+ modules not being added to the registering class.
70
+ [#172 / Pat Nakajima]
71
+
72
+ * Fix in-file templates when running alongside activesupport and
73
+ fatal errors when requiring activesupport before sinatra
74
+ [#178 / Brian Candler]
75
+
76
+ * Fix various issues running on Google AppEngine.
77
+ [Samuel Goebert, Simon Rozet]
78
+
79
+ * Fix in-file templates __END__ detection when __END__ exists with
80
+ other stuff on a line [Yoji Shidara]
81
+
1
82
  = 0.9.1.1 / 2009-03-09
2
83
 
3
84
  * Fix directory traversal vulnerability in default static files
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2007 Blake Mizerany
1
+ Copyright (c) 2007, 2008, 2009 Blake Mizerany
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person
4
4
  obtaining a copy of this software and associated documentation
@@ -109,18 +109,41 @@ directory. To use a different views directory:
109
109
 
110
110
  set :views, File.dirname(__FILE__) + '/templates'
111
111
 
112
+ One important thing to remember is that you always have to reference
113
+ templates with symbols, even if they're in a subdirectory (in this
114
+ case use <tt>:'subdir/template'</tt>). Rendering methods will render
115
+ any strings passed to them directly.
116
+
112
117
  === Haml Templates
113
118
 
114
119
  The haml gem/library is required to render HAML templates:
115
120
 
121
+ ## You'll need to require haml in your app
122
+ require 'haml'
123
+
116
124
  get '/' do
117
125
  haml :index
118
126
  end
119
127
 
120
128
  Renders <tt>./views/index.haml</tt>.
121
129
 
130
+ {Haml's options}[http://haml.hamptoncatlin.com/docs/rdoc/classes/Haml.html]
131
+ can be set globally through Sinatra's configurations,
132
+ see {Options and Configurations}[http://www.sinatrarb.com/configuration.html],
133
+ and overridden on an individual basis.
134
+
135
+ set :haml, {:format => :html5 } # default Haml format is :xhtml
136
+
137
+ get '/' do
138
+ haml :index, :haml_options => {:format => :html4 } # overridden
139
+ end
140
+
141
+
122
142
  === Erb Templates
123
143
 
144
+ ## You'll need to require erb in your app
145
+ require 'erb'
146
+
124
147
  get '/' do
125
148
  erb :index
126
149
  end
@@ -131,6 +154,9 @@ Renders <tt>./views/index.erb</tt>
131
154
 
132
155
  The builder gem/library is required to render builder templates:
133
156
 
157
+ ## You'll need to require builder in your app
158
+ require 'builder'
159
+
134
160
  get '/' do
135
161
  content_type 'application/xml', :charset => 'utf-8'
136
162
  builder :index
@@ -142,6 +168,9 @@ Renders <tt>./views/index.builder</tt>.
142
168
 
143
169
  The sass gem/library is required to render Sass templates:
144
170
 
171
+ ## You'll need to require haml or sass in your app
172
+ require 'sass'
173
+
145
174
  get '/stylesheet.css' do
146
175
  content_type 'text/css', :charset => 'utf-8'
147
176
  sass :stylesheet
@@ -149,6 +178,19 @@ The sass gem/library is required to render Sass templates:
149
178
 
150
179
  Renders <tt>./views/stylesheet.sass</tt>.
151
180
 
181
+ {Sass' options}[http://haml.hamptoncatlin.com/docs/rdoc/classes/Sass.html]
182
+ can be set globally through Sinatra's configurations,
183
+ see {Options and Configurations}[http://www.sinatrarb.com/configuration.html],
184
+ and overridden on an individual basis.
185
+
186
+ set :sass, {:style => :compact } # default Sass style is :nested
187
+
188
+ get '/stylesheet.css' do
189
+ content_type 'text/css', :charset => 'utf-8'
190
+ sass :stylesheet, :sass_options => {:style => :expanded } # overridden
191
+ end
192
+
193
+
152
194
  === Inline Templates
153
195
 
154
196
  get '/' do
@@ -285,13 +327,7 @@ A route can punt processing to the next matching route using <tt>pass</tt>:
285
327
  The route block is immediately exited and control continues with the next
286
328
  matching route. If no matching route is found, a 404 is returned.
287
329
 
288
- == Configuration and Reloading
289
-
290
- Sinatra supports multiple environments and reloading. Reloading happens
291
- before each request when running under the <tt>:development</tt>
292
- environment. Wrap your configurations (e.g., database connections, constants,
293
- etc.) in <tt>configure</tt> blocks to protect them from reloading or to
294
- target specific environments.
330
+ == Configuration
295
331
 
296
332
  Run once, at startup, in any environment:
297
333
 
@@ -300,14 +336,14 @@ Run once, at startup, in any environment:
300
336
  end
301
337
 
302
338
  Run only when the environment (RACK_ENV environment variable) is set to
303
- <tt>:production</tt>.
339
+ <tt>:production</tt>:
304
340
 
305
341
  configure :production do
306
342
  ...
307
343
  end
308
344
 
309
- Run when the environment (RACK_ENV environment variable) is set to
310
- either <tt>:production</tt> or <tt>:test</tt>.
345
+ Run when the environment is set to either <tt>:production</tt> or
346
+ <tt>:test</tt>:
311
347
 
312
348
  configure :production, :test do
313
349
  ...
@@ -401,34 +437,38 @@ typically don't have to +use+ them explicitly.
401
437
 
402
438
  == Testing
403
439
 
404
- The Sinatra::Test mixin and Sinatra::TestHarness class include a variety of
405
- helper methods for testing your Sinatra app:
440
+ Sinatra tests can be written using any Rack-based testing library
441
+ or framework. {Rack::Test}[http://gitrdoc.com/brynary/rack-test] is
442
+ recommended:
406
443
 
407
444
  require 'my_sinatra_app'
408
- require 'test/unit'
409
- require 'sinatra/test'
445
+ require 'rack/test'
410
446
 
411
447
  class MyAppTest < Test::Unit::TestCase
412
- include Sinatra::Test
448
+ include Rack::Test::Methods
449
+
450
+ def app
451
+ Sinatra::Application
452
+ end
413
453
 
414
454
  def test_my_default
415
455
  get '/'
416
- assert_equal 'Hello World!', @response.body
456
+ assert_equal 'Hello World!', last_response.body
417
457
  end
418
458
 
419
459
  def test_with_params
420
- get '/meet', {:name => 'Frank'}
421
- assert_equal 'Hello Frank!', @response.body
460
+ get '/meet', :name => 'Frank'
461
+ assert_equal 'Hello Frank!', last_response.body
422
462
  end
423
463
 
424
464
  def test_with_rack_env
425
- get '/', {}, :agent => 'Songbird'
426
- assert_equal "You're using Songbird!", @response.body
465
+ get '/', {}, 'HTTP_USER_AGENT' => 'Songbird'
466
+ assert_equal "You're using Songbird!", last_response.body
427
467
  end
428
468
  end
429
469
 
430
- See http://www.sinatrarb.com/testing.html for more on Sinatra::Test and using it
431
- with other test frameworks such as RSpec, Bacon, and test/spec.
470
+ NOTE: The built-in Sinatra::Test module and Sinatra::TestHarness class
471
+ are deprecated as of the 0.9.2 release.
432
472
 
433
473
  == Command line
434
474
 
@@ -454,7 +494,7 @@ clone and run your app with the <tt>sinatra/lib</tt> directory on the
454
494
  git clone git://github.com/sinatra/sinatra.git
455
495
  ruby -Isinatra/lib myapp.rb
456
496
 
457
- Alternatively, you can add the <tt>sinatra/lib<tt> directory to the
497
+ Alternatively, you can add the <tt>sinatra/lib</tt> directory to the
458
498
  <tt>LOAD_PATH</tt> in your application:
459
499
 
460
500
  $LOAD_PATH.unshift File.dirname(__FILE__) + '/sinatra/lib'
data/Rakefile CHANGED
@@ -2,20 +2,33 @@ require 'rake/clean'
2
2
  require 'rake/testtask'
3
3
  require 'fileutils'
4
4
 
5
- task :default => [:test]
5
+ task :default => [:test, :compat]
6
6
  task :spec => :test
7
7
 
8
8
  # SPECS ===============================================================
9
9
 
10
+ task(:test) { puts "==> Running main test suite" }
11
+
10
12
  Rake::TestTask.new(:test) do |t|
11
13
  t.test_files = FileList['test/*_test.rb']
12
14
  t.ruby_opts = ['-rubygems'] if defined? Gem
13
15
  end
14
16
 
15
- desc 'Run compatibility specs (requires test/spec)'
16
- task :compat do |t|
17
- pattern = ENV['TEST'] || '.*'
18
- sh "specrb --testcase '#{pattern}' -Ilib:test compat/*_test.rb"
17
+ desc "Run < 0.9.x compatibility specs"
18
+ task :compat do
19
+ begin
20
+ require 'mocha'
21
+ require 'test/spec'
22
+ at_exit { exit 0 } # disable test-spec at_exit runner
23
+
24
+ puts "==> Running compat test suite"
25
+ Rake::TestTask.new(:compat) do |t|
26
+ t.test_files = FileList['compat/*_test.rb']
27
+ t.ruby_opts = ['-rubygems'] if defined? Gem
28
+ end
29
+ rescue LoadError
30
+ warn 'Skipping compat tests. mocha and/or test-spec gems not installed.'
31
+ end
19
32
  end
20
33
 
21
34
  # PACKAGING ============================================================
@@ -33,7 +46,7 @@ def spec
33
46
  end
34
47
 
35
48
  def package(ext='')
36
- "dist/sinatra-#{spec.version}" + ext
49
+ "pkg/sinatra-#{spec.version}" + ext
37
50
  end
38
51
 
39
52
  desc 'Build packages'
@@ -44,15 +57,15 @@ task :install => package('.gem') do
44
57
  sh "gem install #{package('.gem')}"
45
58
  end
46
59
 
47
- directory 'dist/'
48
- CLOBBER.include('dist')
60
+ directory 'pkg/'
61
+ CLOBBER.include('pkg')
49
62
 
50
- file package('.gem') => %w[dist/ sinatra.gemspec] + spec.files do |f|
63
+ file package('.gem') => %w[pkg/ sinatra.gemspec] + spec.files do |f|
51
64
  sh "gem build sinatra.gemspec"
52
65
  mv File.basename(f.name), f.name
53
66
  end
54
67
 
55
- file package('.tar.gz') => %w[dist/] + spec.files do |f|
68
+ file package('.tar.gz') => %w[pkg/] + spec.files do |f|
56
69
  sh <<-SH
57
70
  git archive \
58
71
  --prefix=sinatra-#{source_version}/ \
@@ -64,7 +77,7 @@ end
64
77
  # Rubyforge Release / Publish Tasks ==================================
65
78
 
66
79
  desc 'Publish gem and tarball to rubyforge'
67
- task 'publish:gem' => [package('.gem'), package('.tar.gz')] do |t|
80
+ task 'release' => [package('.gem'), package('.tar.gz')] do |t|
68
81
  sh <<-end
69
82
  rubyforge add_release sinatra sinatra #{spec.version} #{package('.gem')} &&
70
83
  rubyforge add_file sinatra sinatra #{spec.version} #{package('.tar.gz')}
@@ -95,12 +108,6 @@ file 'doc/api/index.html' => FileList['lib/**/*.rb','README.rdoc'] do |f|
95
108
  end
96
109
  CLEAN.include 'doc/api'
97
110
 
98
- def rdoc_to_html(file_name)
99
- require 'rdoc/markup/to_html'
100
- rdoc = RDoc::Markup::ToHtml.new
101
- rdoc.convert(File.read(file_name))
102
- end
103
-
104
111
  # Gemspec Helpers ====================================================
105
112
 
106
113
  def source_version
@@ -108,12 +115,7 @@ def source_version
108
115
  line.match(/.*VERSION = '(.*)'/)[1]
109
116
  end
110
117
 
111
- project_files =
112
- FileList[
113
- '{lib,test,compat,images}/**',
114
- 'Rakefile', 'CHANGES', 'README.rdoc'
115
- ]
116
- file 'sinatra.gemspec' => project_files do |f|
118
+ task 'sinatra.gemspec' => FileList['{lib,test,compat}/**','Rakefile','CHANGES','*.rdoc'] do |f|
117
119
  # read spec file and split out manifest section
118
120
  spec = File.read(f.name)
119
121
  head, manifest, tail = spec.split(" # = MANIFEST =\n")
@@ -183,13 +183,14 @@ context "Haml" do
183
183
  Sinatra.application = nil
184
184
  end
185
185
 
186
- specify 'are empty be default' do
186
+ specify 'default to filename and line of caller' do
187
187
 
188
188
  get '/' do
189
189
  haml 'foo'
190
190
  end
191
191
 
192
- Haml::Engine.expects(:new).with('foo', {}).returns(stub(:render => 'foo'))
192
+ Haml::Engine.expects(:new).with('foo', {:filename => __FILE__,
193
+ :line => (__LINE__-4)}).returns(stub(:render => 'foo'))
193
194
 
194
195
  get_it '/'
195
196
  should.be.ok
@@ -202,7 +203,8 @@ context "Haml" do
202
203
  haml 'foo', :options => {:format => :html4}
203
204
  end
204
205
 
205
- Haml::Engine.expects(:new).with('foo', {:format => :html4}).returns(stub(:render => 'foo'))
206
+ Haml::Engine.expects(:new).with('foo', {:filename => __FILE__,
207
+ :line => (__LINE__-4), :format => :html4}).returns(stub(:render => 'foo'))
206
208
 
207
209
  get_it '/'
208
210
  should.be.ok
@@ -220,7 +222,8 @@ context "Haml" do
220
222
  haml 'foo'
221
223
  end
222
224
 
223
- Haml::Engine.expects(:new).with('foo', {:format => :html4,
225
+ Haml::Engine.expects(:new).with('foo', {:filename => __FILE__,
226
+ :line => (__LINE__-4), :format => :html4,
224
227
  :escape_html => true}).returns(stub(:render => 'foo'))
225
228
 
226
229
  get_it '/'
@@ -24,6 +24,9 @@ end
24
24
 
25
25
  class Test::Unit::TestCase
26
26
  include Sinatra::Test
27
+
28
+ PASSTHROUGH_EXCEPTIONS = [] unless const_defined?(:PASSTHROUGH_EXCEPTIONS)
29
+
27
30
  def setup
28
31
  @app = lambda { |env| Sinatra::Application.call(env) }
29
32
  end
@@ -52,6 +52,16 @@ context "Sass" do
52
52
  body.should.equal "#sass {\n background_color: #FFF; }\n"
53
53
  end
54
54
 
55
+ it "passes :sass option to the Sass engine" do
56
+ get '/' do
57
+ sass "#sass\n :background-color #FFF\n :color #000\n", :sass => {:style => :compact}
58
+ end
59
+
60
+ get_it '/'
61
+ should.be.ok
62
+ body.should.equal "#sass { background-color: #FFF; color: #000; }\n"
63
+ end
64
+
55
65
  end
56
66
 
57
67
  end
@@ -3,9 +3,10 @@ require 'time'
3
3
  require 'uri'
4
4
  require 'rack'
5
5
  require 'rack/builder'
6
+ require 'sinatra/showexceptions'
6
7
 
7
8
  module Sinatra
8
- VERSION = '0.9.1.1'
9
+ VERSION = '0.9.2'
9
10
 
10
11
  # The request object. See Rack::Request for more info:
11
12
  # http://rack.rubyforge.org/doc/classes/Rack/Request.html
@@ -14,6 +15,7 @@ module Sinatra
14
15
  @env['HTTP_USER_AGENT']
15
16
  end
16
17
 
18
+ # Returns an array of acceptable media types for the response
17
19
  def accept
18
20
  @env['HTTP_ACCEPT'].to_s.split(',').map { |a| a.strip }
19
21
  end
@@ -31,16 +33,6 @@ module Sinatra
31
33
  # http://rack.rubyforge.org/doc/classes/Rack/Response.html
32
34
  # http://rack.rubyforge.org/doc/classes/Rack/Response/Helpers.html
33
35
  class Response < Rack::Response
34
- def initialize
35
- @status, @body = 200, []
36
- @header = Rack::Utils::HeaderHash.new({'Content-Type' => 'text/html'})
37
- end
38
-
39
- def write(str)
40
- @body << str.to_s
41
- str
42
- end
43
-
44
36
  def finish
45
37
  @body = block if block_given?
46
38
  if [204, 304].include?(status.to_i)
@@ -162,6 +154,8 @@ module Sinatra
162
154
  not_found
163
155
  end
164
156
 
157
+ # Rack response body used to deliver static files. The file contents are
158
+ # generated iteratively in 8K chunks.
165
159
  class StaticFile < ::File #:nodoc:
166
160
  alias_method :to_path, :path
167
161
  def each
@@ -215,110 +209,141 @@ module Sinatra
215
209
  end
216
210
 
217
211
  # Template rendering methods. Each method takes a the name of a template
218
- # to render as a Symbol and returns a String with the rendered output.
212
+ # to render as a Symbol and returns a String with the rendered output,
213
+ # as well as an optional hash with additional options.
214
+ #
215
+ # `template` is either the name or path of the template as symbol
216
+ # (Use `:'subdir/myview'` for views in subdirectories), or a string
217
+ # that will be rendered.
218
+ #
219
+ # Possible options are:
220
+ # :layout If set to false, no layout is rendered, otherwise
221
+ # the specified layout is used (Ignored for `sass`)
222
+ # :locals A hash with local variables that should be available
223
+ # in the template
219
224
  module Templates
220
- def erb(template, options={})
221
- require 'erb' unless defined? ::ERB
222
- render :erb, template, options
225
+ def erb(template, options={}, locals={})
226
+ require_warn('ERB') unless defined?(::ERB)
227
+
228
+ render :erb, template, options, locals
223
229
  end
224
230
 
225
- def haml(template, options={})
226
- require 'haml' unless defined? ::Haml
227
- options[:options] ||= self.class.haml if self.class.respond_to? :haml
228
- render :haml, template, options
231
+ def haml(template, options={}, locals={})
232
+ require_warn('Haml') unless defined?(::Haml::Engine)
233
+
234
+ render :haml, template, options, locals
229
235
  end
230
236
 
231
- def sass(template, options={}, &block)
232
- require 'sass' unless defined? ::Sass
237
+ def sass(template, options={}, locals={})
238
+ require_warn('Sass') unless defined?(::Sass::Engine)
239
+
233
240
  options[:layout] = false
234
- render :sass, template, options
241
+ render :sass, template, options, locals
235
242
  end
236
243
 
237
- def builder(template=nil, options={}, &block)
238
- require 'builder' unless defined? ::Builder
244
+ def builder(template=nil, options={}, locals={}, &block)
245
+ require_warn('Builder') unless defined?(::Builder)
246
+
239
247
  options, template = template, nil if template.is_a?(Hash)
240
248
  template = lambda { block } if template.nil?
241
- render :builder, template, options
249
+ render :builder, template, options, locals
242
250
  end
243
251
 
244
252
  private
245
- def render(engine, template, options={}) #:nodoc:
246
- data = lookup_template(engine, template, options)
247
- output = __send__("render_#{engine}", template, data, options)
248
- layout, data = lookup_layout(engine, options)
253
+ def render(engine, template, options={}, locals={})
254
+ # merge app-level options
255
+ options = self.class.send(engine).merge(options) if self.class.respond_to?(engine)
256
+
257
+ # extract generic options
258
+ layout = options.delete(:layout)
259
+ layout = :layout if layout.nil? || layout == true
260
+ views = options.delete(:views) || self.class.views || "./views"
261
+ locals = options.delete(:locals) || locals || {}
262
+
263
+ # render template
264
+ data, options[:filename], options[:line] = lookup_template(engine, template, views)
265
+ output = __send__("render_#{engine}", template, data, options, locals)
266
+
267
+ # render layout
249
268
  if layout
250
- __send__("render_#{engine}", layout, data, options) { output }
251
- else
252
- output
269
+ data, options[:filename], options[:line] = lookup_layout(engine, layout, views)
270
+ if data
271
+ output = __send__("render_#{engine}", layout, data, options, {}) { output }
272
+ end
253
273
  end
274
+
275
+ output
254
276
  end
255
277
 
256
- def lookup_template(engine, template, options={})
278
+ def lookup_template(engine, template, views_dir, filename = nil, line = nil)
257
279
  case template
258
280
  when Symbol
259
281
  if cached = self.class.templates[template]
260
- lookup_template(engine, cached, options)
282
+ lookup_template(engine, cached[:template], views_dir, cached[:filename], cached[:line])
261
283
  else
262
- ::File.read(template_path(engine, template, options))
284
+ path = ::File.join(views_dir, "#{template}.#{engine}")
285
+ [ ::File.read(path), path, 1 ]
263
286
  end
264
287
  when Proc
265
- template.call
288
+ filename, line = self.class.caller_locations.first if filename.nil?
289
+ [ template.call, filename, line.to_i ]
266
290
  when String
267
- template
291
+ filename, line = self.class.caller_locations.first if filename.nil?
292
+ [ template, filename, line.to_i ]
268
293
  else
269
294
  raise ArgumentError
270
295
  end
271
296
  end
272
297
 
273
- def lookup_layout(engine, options)
274
- return if options[:layout] == false
275
- options.delete(:layout) if options[:layout] == true
276
- template = options[:layout] || :layout
277
- data = lookup_template(engine, template, options)
278
- [template, data]
298
+ def lookup_layout(engine, template, views_dir)
299
+ lookup_template(engine, template, views_dir)
279
300
  rescue Errno::ENOENT
280
301
  nil
281
302
  end
282
303
 
283
- def template_path(engine, template, options={})
284
- views_dir =
285
- options[:views_directory] || self.options.views || "./views"
286
- "#{views_dir}/#{template}.#{engine}"
287
- end
288
-
289
- def render_erb(template, data, options, &block)
290
- original_out_buf = @_out_buf
304
+ def render_erb(template, data, options, locals, &block)
305
+ original_out_buf = defined?(@_out_buf) && @_out_buf
291
306
  data = data.call if data.kind_of? Proc
292
307
 
293
308
  instance = ::ERB.new(data, nil, nil, '@_out_buf')
294
- locals = options[:locals] || {}
295
309
  locals_assigns = locals.to_a.collect { |k,v| "#{k} = locals[:#{k}]" }
296
310
 
297
- src = "#{locals_assigns.join("\n")}\n#{instance.src}"
298
- eval src, binding, '(__ERB__)', locals_assigns.length + 1
311
+ filename = options.delete(:filename) || '(__ERB__)'
312
+ line = options.delete(:line) || 1
313
+ line -= 1 if instance.src =~ /^#coding:/
314
+
315
+ render_binding = binding
316
+ eval locals_assigns.join("\n"), render_binding
317
+ eval instance.src, render_binding, filename, line
299
318
  @_out_buf, result = original_out_buf, @_out_buf
300
319
  result
301
320
  end
302
321
 
303
- def render_haml(template, data, options, &block)
304
- engine = ::Haml::Engine.new(data, options[:options] || {})
305
- engine.render(self, options[:locals] || {}, &block)
322
+ def render_haml(template, data, options, locals, &block)
323
+ ::Haml::Engine.new(data, options).render(self, locals, &block)
306
324
  end
307
325
 
308
- def render_sass(template, data, options, &block)
309
- engine = ::Sass::Engine.new(data, options[:sass] || {})
310
- engine.render
326
+ def render_sass(template, data, options, locals, &block)
327
+ ::Sass::Engine.new(data, options).render
311
328
  end
312
329
 
313
- def render_builder(template, data, options, &block)
314
- xml = ::Builder::XmlMarkup.new(:indent => 2)
330
+ def render_builder(template, data, options, locals, &block)
331
+ options = { :indent => 2 }.merge(options)
332
+ filename = options.delete(:filename) || '<BUILDER>'
333
+ line = options.delete(:line) || 1
334
+ xml = ::Builder::XmlMarkup.new(options)
315
335
  if data.respond_to?(:to_str)
316
- eval data.to_str, binding, '<BUILDER>', 1
336
+ eval data.to_str, binding, filename, line
317
337
  elsif data.kind_of?(Proc)
318
338
  data.call(xml)
319
339
  end
320
340
  xml.target!
321
341
  end
342
+
343
+ def require_warn(engine)
344
+ warn "Auto-require of #{engine} is deprecated; add require '#{engine}' to your app."
345
+ require engine.downcase
346
+ end
322
347
  end
323
348
 
324
349
  # Base class for all Sinatra applications and middleware.
@@ -368,13 +393,16 @@ module Sinatra
368
393
  self.class
369
394
  end
370
395
 
371
- # Exit the current block and halt the response.
396
+ # Exit the current block, halts any further processing
397
+ # of the request, and returns the specified response.
372
398
  def halt(*response)
373
399
  response = response.first if response.length == 1
374
400
  throw :halt, response
375
401
  end
376
402
 
377
403
  # Pass control to the next matching route.
404
+ # If there are no more matching routes, Sinatra will
405
+ # return a 404 response.
378
406
  def pass
379
407
  throw :pass
380
408
  end
@@ -392,7 +420,13 @@ module Sinatra
392
420
  private
393
421
  # Run before filters and then locate and run a matching route.
394
422
  def route!
395
- @params = nested_params(@request.params)
423
+ # enable nested params in Rack < 1.0; allow indifferent access
424
+ @params =
425
+ if Rack::Utils.respond_to?(:parse_nested_query)
426
+ indifferent_params(@request.params)
427
+ else
428
+ nested_params(@request.params)
429
+ end
396
430
 
397
431
  # before filters
398
432
  self.class.filters.each { |block| instance_eval(&block) }
@@ -426,14 +460,26 @@ module Sinatra
426
460
  catch(:pass) do
427
461
  conditions.each { |cond|
428
462
  throw :pass if instance_eval(&cond) == false }
429
- throw :halt, instance_eval(&block)
463
+ route_eval(&block)
430
464
  end
431
465
  end
432
466
  end
433
467
  end
434
468
 
435
- # No matching route found or all routes passed -- forward downstream
436
- # when running as middleware; 404 when running as normal app.
469
+ route_missing
470
+ end
471
+
472
+ # Run a route block and throw :halt with the result.
473
+ def route_eval(&block)
474
+ throw :halt, instance_eval(&block)
475
+ end
476
+
477
+ # No matching route was found or all routes passed. The default
478
+ # implementation is to forward the request downstream when running
479
+ # as middleware (@app is non-nil); when no downstream app is set, raise
480
+ # a NotFound exception. Subclasses can override this method to perform
481
+ # custom route miss logic.
482
+ def route_missing
437
483
  if @app
438
484
  forward
439
485
  else
@@ -441,6 +487,18 @@ module Sinatra
441
487
  end
442
488
  end
443
489
 
490
+ # Enable string or symbol key access to the nested params hash.
491
+ def indifferent_params(params)
492
+ params = indifferent_hash.merge(params)
493
+ params.each do |key, value|
494
+ next unless value.is_a?(Hash)
495
+ params[key] = indifferent_params(value)
496
+ end
497
+ end
498
+
499
+ # Recursively replace the params hash with a nested indifferent
500
+ # hash. Rack 1.0 has a built in implementation of this method - remove
501
+ # this once Rack 1.0 is required.
444
502
  def nested_params(params)
445
503
  return indifferent_hash.merge(params) if !params.keys.join.include?('[')
446
504
  params.inject indifferent_hash do |res, (key,val)|
@@ -512,7 +570,7 @@ module Sinatra
512
570
  @env['sinatra.error'] = boom
513
571
 
514
572
  dump_errors!(boom) if options.dump_errors?
515
- raise boom if options.raise_errors?
573
+ raise boom if options.raise_errors? || options.show_exceptions?
516
574
 
517
575
  @response.status = 500
518
576
  error_block! boom.class, Exception
@@ -553,12 +611,14 @@ module Sinatra
553
611
  @middleware = []
554
612
  @errors = {}
555
613
  @prototype = nil
614
+ @extensions = []
556
615
 
557
616
  class << self
558
617
  attr_accessor :routes, :filters, :conditions, :templates,
559
618
  :middleware, :errors
560
619
 
561
- public
620
+ # Sets an option to the given value. If the value is a proc,
621
+ # the proc will be called every time the option is accessed.
562
622
  def set(option, value=self)
563
623
  if value.kind_of?(Proc)
564
624
  metadef(option, &value)
@@ -574,14 +634,19 @@ module Sinatra
574
634
  self
575
635
  end
576
636
 
637
+ # Same as calling `set :option, true` for each of the given options.
577
638
  def enable(*opts)
578
639
  opts.each { |key| set(key, true) }
579
640
  end
580
641
 
642
+ # Same as calling `set :option, false` for each of the given options.
581
643
  def disable(*opts)
582
644
  opts.each { |key| set(key, false) }
583
645
  end
584
646
 
647
+ # Define a custom error handler. Optionally takes either an Exception
648
+ # class, or an HTTP status code to specify which errors should be
649
+ # handled.
585
650
  def error(codes=Exception, &block)
586
651
  if codes.respond_to? :each
587
652
  codes.each { |err| error(err, &block) }
@@ -590,29 +655,38 @@ module Sinatra
590
655
  end
591
656
  end
592
657
 
658
+ # Sugar for `error(404) { ... }`
593
659
  def not_found(&block)
594
660
  error 404, &block
595
661
  end
596
662
 
663
+ # Define a named template. The block must return the template source.
597
664
  def template(name, &block)
598
- templates[name] = block
665
+ filename, line = caller_locations.first
666
+ templates[name] = { :filename => filename, :line => line, :template => block }
599
667
  end
600
668
 
669
+ # Define the layout template. The block must return the template source.
601
670
  def layout(name=:layout, &block)
602
671
  template name, &block
603
672
  end
604
673
 
605
- def use_in_file_templates!
606
- ignore = [/lib\/sinatra.*\.rb/, /\(.*\)/, /rubygems\/custom_require\.rb/]
607
- file = caller.
608
- map { |line| line.sub(/:\d+.*$/, '') }.
609
- find { |line| ignore.all? { |pattern| line !~ pattern } }
610
- if data = ::IO.read(file).split('__END__')[1]
674
+ # Load embeded templates from the file; uses the caller's __FILE__
675
+ # when no file is specified.
676
+ def use_in_file_templates!(file=nil)
677
+ file ||= caller_files.first
678
+ app, data =
679
+ ::IO.read(file).split(/^__END__$/, 2) rescue nil
680
+
681
+ if data
611
682
  data.gsub!(/\r\n/, "\n")
683
+ lines = app.count("\n") + 1
612
684
  template = nil
613
685
  data.each_line do |line|
686
+ lines += 1
614
687
  if line =~ /^@@\s*(.*)/
615
- template = templates[$1.to_sym] = ''
688
+ template = ''
689
+ templates[$1.to_sym] = { :filename => file, :line => lines, :template => template }
616
690
  elsif template
617
691
  template << line
618
692
  end
@@ -627,10 +701,15 @@ module Sinatra
627
701
  Rack::Mime.mime_type(type, nil)
628
702
  end
629
703
 
704
+ # Define a before filter. Filters are run before all requests
705
+ # within the same context as route handlers and may access/modify the
706
+ # request and response.
630
707
  def before(&block)
631
708
  @filters << block
632
709
  end
633
710
 
711
+ # Add a route condition. The route is considered non-matching when the
712
+ # block returns false.
634
713
  def condition(&block)
635
714
  @conditions << block
636
715
  end
@@ -650,8 +729,9 @@ module Sinatra
650
729
  end
651
730
  }
652
731
  end
732
+ alias_method :agent, :user_agent
653
733
 
654
- def accept_mime_types(types)
734
+ def provides(*types)
655
735
  types = [types] unless types.kind_of? Array
656
736
  types.map!{|t| media_type(t)}
657
737
 
@@ -667,6 +747,8 @@ module Sinatra
667
747
  end
668
748
 
669
749
  public
750
+ # Defining a `GET` handler also automatically defines
751
+ # a `HEAD` handler.
670
752
  def get(path, opts={}, &block)
671
753
  conditions = @conditions.dup
672
754
  route('GET', path, opts, &block)
@@ -675,16 +757,17 @@ module Sinatra
675
757
  route('HEAD', path, opts, &block)
676
758
  end
677
759
 
678
- def put(path, opts={}, &bk); route 'PUT', path, opts, &bk; end
679
- def post(path, opts={}, &bk); route 'POST', path, opts, &bk; end
680
- def delete(path, opts={}, &bk); route 'DELETE', path, opts, &bk; end
681
- def head(path, opts={}, &bk); route 'HEAD', path, opts, &bk; end
760
+ def put(path, opts={}, &bk); route 'PUT', path, opts, &bk end
761
+ def post(path, opts={}, &bk); route 'POST', path, opts, &bk end
762
+ def delete(path, opts={}, &bk); route 'DELETE', path, opts, &bk end
763
+ def head(path, opts={}, &bk); route 'HEAD', path, opts, &bk end
682
764
 
683
765
  private
684
- def route(verb, path, opts={}, &block)
685
- host_name opts[:host] if opts.key?(:host)
686
- user_agent opts[:agent] if opts.key?(:agent)
687
- accept_mime_types opts[:provides] if opts.key?(:provides)
766
+ def route(verb, path, options={}, &block)
767
+ # Because of self.options.host
768
+ host_name(options.delete(:host)) if options.key?(:host)
769
+
770
+ options.each {|option, args| send(option, *args)}
688
771
 
689
772
  pattern, keys = compile(path)
690
773
  conditions, @conditions = @conditions, []
@@ -698,16 +781,22 @@ module Sinatra
698
781
  lambda { unbound_method.bind(self).call }
699
782
  end
700
783
 
784
+ invoke_hook(:route_added, verb, path, block)
785
+
701
786
  (routes[verb] ||= []).
702
787
  push([pattern, keys, conditions, block]).last
703
788
  end
704
789
 
790
+ def invoke_hook(name, *args)
791
+ extensions.each { |e| e.send(name, *args) if e.respond_to?(name) }
792
+ end
793
+
705
794
  def compile(path)
706
795
  keys = []
707
796
  if path.respond_to? :to_str
708
797
  special_chars = %w{. + ( )}
709
798
  pattern =
710
- path.gsub(/((:\w+)|[\*#{special_chars.join}])/) do |match|
799
+ path.to_str.gsub(/((:\w+)|[\*#{special_chars.join}])/) do |match|
711
800
  case match
712
801
  when "*"
713
802
  keys << 'splat'
@@ -720,6 +809,8 @@ module Sinatra
720
809
  end
721
810
  end
722
811
  [/^#{pattern}$/, keys]
812
+ elsif path.respond_to?(:keys) && path.respond_to?(:match)
813
+ [path, path.keys]
723
814
  elsif path.respond_to? :match
724
815
  [path, keys]
725
816
  else
@@ -728,33 +819,44 @@ module Sinatra
728
819
  end
729
820
 
730
821
  public
822
+ # Makes the methods defined in the block and in the Modules given
823
+ # in `extensions` available to the handlers and templates
731
824
  def helpers(*extensions, &block)
732
825
  class_eval(&block) if block_given?
733
- include *extensions if extensions.any?
826
+ include(*extensions) if extensions.any?
827
+ end
828
+
829
+ def extensions
830
+ (@extensions + (superclass.extensions rescue [])).uniq
734
831
  end
735
832
 
736
833
  def register(*extensions, &block)
737
834
  extensions << Module.new(&block) if block_given?
835
+ @extensions += extensions
738
836
  extensions.each do |extension|
739
837
  extend extension
740
838
  extension.registered(self) if extension.respond_to?(:registered)
741
839
  end
742
840
  end
743
841
 
744
- def development? ; environment == :development ; end
745
- def test? ; environment == :test ; end
746
- def production? ; environment == :production ; end
842
+ def development?; environment == :development end
843
+ def production?; environment == :production end
844
+ def test?; environment == :test end
747
845
 
846
+ # Set configuration options for Sinatra and/or the app.
847
+ # Allows scoping of settings for certain environments.
748
848
  def configure(*envs, &block)
749
- return if reloading?
750
- yield if envs.empty? || envs.include?(environment.to_sym)
849
+ yield self if envs.empty? || envs.include?(environment.to_sym)
751
850
  end
752
851
 
852
+ # Use the specified Rack middleware
753
853
  def use(middleware, *args, &block)
754
854
  @prototype = nil
755
855
  @middleware << [middleware, args, block]
756
856
  end
757
857
 
858
+ # Run the Sinatra app as a self-hosted server using
859
+ # Thin, Mongrel or WEBrick (in that order)
758
860
  def run!(options={})
759
861
  set options
760
862
  handler = detect_rack_handler
@@ -783,30 +885,17 @@ module Sinatra
783
885
  def new(*args, &bk)
784
886
  builder = Rack::Builder.new
785
887
  builder.use Rack::Session::Cookie if sessions? && !test?
786
- builder.use Rack::CommonLogger if logging?
787
- builder.use Rack::MethodOverride if methodoverride?
788
- @middleware.each { |c, args, bk| builder.use(c, *args, &bk) }
888
+ builder.use Rack::CommonLogger if logging?
889
+ builder.use Rack::MethodOverride if methodoverride?
890
+ builder.use ShowExceptions if show_exceptions?
891
+
892
+ @middleware.each { |c,a,b| builder.use(c, *a, &b) }
789
893
  builder.run super
790
894
  builder.to_app
791
895
  end
792
896
 
793
897
  def call(env)
794
- synchronize do
795
- reload! if reload?
796
- prototype.call(env)
797
- end
798
- end
799
-
800
- def reloading?
801
- @reloading
802
- end
803
-
804
- def reload!
805
- @reloading = true
806
- reset!
807
- $LOADED_FEATURES.delete("sinatra.rb")
808
- ::Kernel.load app_file
809
- @reloading = false
898
+ synchronize { prototype.call(env) }
810
899
  end
811
900
 
812
901
  def reset!(base=superclass)
@@ -817,6 +906,7 @@ module Sinatra
817
906
  @errors = base.errors.dup
818
907
  @middleware = base.middleware.dup
819
908
  @prototype = nil
909
+ @extensions = []
820
910
  end
821
911
 
822
912
  protected
@@ -832,7 +922,7 @@ module Sinatra
832
922
  servers = Array(self.server)
833
923
  servers.each do |server_name|
834
924
  begin
835
- return Rack::Handler.get(server_name)
925
+ return Rack::Handler.get(server_name.capitalize)
836
926
  rescue LoadError
837
927
  rescue NameError
838
928
  end
@@ -858,11 +948,36 @@ module Sinatra
858
948
  (class << self; self; end).
859
949
  send :define_method, message, &block
860
950
  end
951
+
952
+ public
953
+ CALLERS_TO_IGNORE = [
954
+ /lib\/sinatra.*\.rb$/, # all sinatra code
955
+ /\(.*\)/, # generated code
956
+ /custom_require\.rb$/, # rubygems require hacks
957
+ /active_support/, # active_support require hacks
958
+ ] unless self.const_defined?('CALLERS_TO_IGNORE')
959
+
960
+ # add rubinius (and hopefully other VM impls) ignore patterns ...
961
+ CALLERS_TO_IGNORE.concat(RUBY_IGNORE_CALLERS) if defined?(RUBY_IGNORE_CALLERS)
962
+
963
+ # Like Kernel#caller but excluding certain magic entries and without
964
+ # line / method information; the resulting array contains filenames only.
965
+ def caller_files
966
+ caller_locations.
967
+ map { |file,line| file }
968
+ end
969
+
970
+ def caller_locations
971
+ caller(1).
972
+ map { |line| line.split(/:(?=\d|in )/)[0,2] }.
973
+ reject { |file,line| CALLERS_TO_IGNORE.any? { |pattern| file =~ pattern } }
974
+ end
861
975
  end
862
976
 
863
977
  set :raise_errors, true
864
978
  set :dump_errors, false
865
979
  set :clean_trace, true
980
+ set :show_exceptions, Proc.new { development? }
866
981
  set :sessions, false
867
982
  set :logging, false
868
983
  set :methodoverride, false
@@ -878,8 +993,7 @@ module Sinatra
878
993
  set :root, Proc.new { app_file && File.expand_path(File.dirname(app_file)) }
879
994
  set :views, Proc.new { root && File.join(root, 'views') }
880
995
  set :public, Proc.new { root && File.join(root, 'public') }
881
- set :reload, Proc.new { app_file? && app_file !~ /\.ru$/i && development? }
882
- set :lock, Proc.new { reload? }
996
+ set :lock, false
883
997
 
884
998
  # static files route
885
999
  get(/.*[^\/]$/) do
@@ -905,6 +1019,8 @@ module Sinatra
905
1019
  end
906
1020
 
907
1021
  error NotFound do
1022
+ content_type 'text/html'
1023
+
908
1024
  (<<-HTML).gsub(/^ {8}/, '')
909
1025
  <!DOCTYPE html>
910
1026
  <html>
@@ -926,35 +1042,6 @@ module Sinatra
926
1042
  </html>
927
1043
  HTML
928
1044
  end
929
-
930
- error do
931
- next unless err = request.env['sinatra.error']
932
- heading = err.class.name + ' - ' + err.message.to_s
933
- (<<-HTML).gsub(/^ {8}/, '')
934
- <!DOCTYPE html>
935
- <html>
936
- <head>
937
- <style type="text/css">
938
- body {font-family:verdana;color:#333}
939
- #c {margin-left:20px}
940
- h1 {color:#1D6B8D;margin:0;margin-top:-30px}
941
- h2 {color:#1D6B8D;font-size:18px}
942
- pre {border-left:2px solid #ddd;padding-left:10px;color:#000}
943
- img {margin-top:10px}
944
- </style>
945
- </head>
946
- <body>
947
- <div id="c">
948
- <img src="/__sinatra__/500.png">
949
- <h1>#{escape_html(heading)}</h1>
950
- <pre>#{escape_html(clean_backtrace(err.backtrace) * "\n")}</pre>
951
- <h2>Params</h2>
952
- <pre>#{escape_html(params.inspect)}</pre>
953
- </div>
954
- </body>
955
- </html>
956
- HTML
957
- end
958
1045
  end
959
1046
  end
960
1047
 
@@ -970,7 +1057,7 @@ module Sinatra
970
1057
 
971
1058
  def self.register(*extensions, &block) #:nodoc:
972
1059
  added_methods = extensions.map {|m| m.public_instance_methods }.flatten
973
- Delegator.delegate *added_methods
1060
+ Delegator.delegate(*added_methods)
974
1061
  super(*extensions, &block)
975
1062
  end
976
1063
  end
@@ -980,14 +1067,17 @@ module Sinatra
980
1067
  class Application < Default
981
1068
  end
982
1069
 
1070
+ # Sinatra delegation mixin. Mixing this module into an object causes all
1071
+ # methods to be delegated to the Sinatra::Application class. Used primarily
1072
+ # at the top-level.
983
1073
  module Delegator #:nodoc:
984
1074
  def self.delegate(*methods)
985
1075
  methods.each do |method_name|
986
1076
  eval <<-RUBY, binding, '(__DELEGATE__)', 1
987
1077
  def #{method_name}(*args, &b)
988
- ::Sinatra::Application.#{method_name}(*args, &b)
1078
+ ::Sinatra::Application.send(#{method_name.inspect}, *args, &b)
989
1079
  end
990
- private :#{method_name}
1080
+ private #{method_name.inspect}
991
1081
  RUBY
992
1082
  end
993
1083
  end
@@ -998,6 +1088,8 @@ module Sinatra
998
1088
  :production?, :use_in_file_templates!, :helpers
999
1089
  end
1000
1090
 
1091
+ # Create a new Sinatra application. The block is evaluated in the new app's
1092
+ # class scope.
1001
1093
  def self.new(base=Base, options={}, &block)
1002
1094
  base = Class.new(base)
1003
1095
  base.send :class_eval, &block if block_given?