Syd-sinatra 0.9.0.2 → 0.9.0.4

Sign up to get free protection for your applications and to get access to all the features.
data/AUTHORS CHANGED
@@ -1,13 +1,13 @@
1
1
  Sinatra was designed and developed by Blake Mizerany (bmizerany) in
2
2
  California. Continued development would not be possible without the ongoing
3
- financial support provided by Heroku <heroku.com> and the emotional support
4
- provided by Adam Wiggins (adamwiggins), Chris Wanstrath (defunkt), PJ Hyett (pjhyett), and
5
- the rest of the Github crew.
3
+ financial support provided by [Heroku](http://heroku.com) and the emotional
4
+ support provided by Adam Wiggins (adamwiggins), Chris Wanstrath (defunkt),
5
+ PJ Hyett (pjhyett), and the rest of the GitHub crew.
6
6
 
7
- Special thanks to the following extraordinary individuals, who-out which
7
+ Special thanks to the following extraordinary individuals, without whom
8
8
  Sinatra would not be possible:
9
9
 
10
- * Ryan Tomayko (rtomayko) for constantly fixing whitespace errors 60d5006
10
+ * Ryan Tomayko (rtomayko) for constantly fixing whitespace errors 60d5006
11
11
  * Ezra Zygmuntowicz (ezmobius) for initial help and letting Blake steal
12
12
  some of merbs internal code.
13
13
  * Christopher Schneid (cschneid) for The Book, the blog (gittr.com),
@@ -16,7 +16,7 @@ Sinatra would not be possible:
16
16
  the README, and hanging in there when times were rough.
17
17
  * Simon Rozet (sr) for a ton of doc patches, HAML options, and all that
18
18
  advocacy stuff he's going to do for 1.0.
19
- * Erik Kastner (kastner) for fixing MIME_TYPES under Rack 0.5.
19
+ * Erik Kastner (kastner) for fixing `MIME_TYPES` under Rack 0.5.
20
20
  * Ben Bleything (bleything) for caring about HTTP status codes and doc fixes.
21
21
  * Igal Koshevoy (igal) for root path detection under Thin/Passenger.
22
22
  * Jon Crosby (jcrosby) for coffee breaks, doc fixes, and just because, man.
@@ -27,12 +27,13 @@ Sinatra would not be possible:
27
27
  * Victor Hugo Borja (vic) for splat'n routes specs and doco.
28
28
  * Avdi Grimm (avdi) for basic RSpec support.
29
29
  * Jack Danger Canty for a more accurate root directory and for making me
30
- watch this just now: http://www.youtube.com/watch?v=ueaHLHgskkw.
30
+ watch [this](http://www.youtube.com/watch?v=ueaHLHgskkw) just now.
31
31
  * Mathew Walker for making escaped paths work with static files.
32
32
  * Millions of Us for having the problem that led to Sinatra's conception.
33
33
  * Songbird for the problems that helped Sinatra's future become realized.
34
- * Rick Olsen (technoweenie) for the killer plug at RailsConf '08.
34
+ * Rick Olson (technoweenie) for the killer plug at RailsConf '08.
35
35
  * Steven Garcia for the amazing custom artwork you see on 404's and 500's
36
+ * Pat Nakajima (nakajima) for fixing non-nested params in nested params Hash's.
36
37
 
37
38
  and last but not least:
38
39
 
data/README.rdoc CHANGED
@@ -10,12 +10,20 @@ effort:
10
10
  'Hello world!'
11
11
  end
12
12
 
13
- Run with <tt>ruby myapp.rb</tt> and view at <tt>http://localhost:4567</tt>
13
+ Install the gem and run with:
14
14
 
15
- == HTTP Methods
15
+ sudo gem install sinatra
16
+ ruby myapp.rb
17
+
18
+ View at: http://localhost:4567
19
+
20
+ == Routes
21
+
22
+ In Sinatra, a route is an HTTP method paired with an URL matching pattern.
23
+ Each route is associated with a block:
16
24
 
17
25
  get '/' do
18
- .. show things ..
26
+ .. show something ..
19
27
  end
20
28
 
21
29
  post '/' do
@@ -30,21 +38,13 @@ Run with <tt>ruby myapp.rb</tt> and view at <tt>http://localhost:4567</tt>
30
38
  .. annihilate something ..
31
39
  end
32
40
 
33
- == Routes
34
-
35
- Routes are matched based on the order of declaration. The first route that
41
+ Routes are matched in the order they are defined. The first route that
36
42
  matches the request is invoked.
37
43
 
38
- Basic routes:
39
-
40
- get '/hi' do
41
- ...
42
- end
43
-
44
44
  Route patterns may include named parameters, accessible via the
45
45
  <tt>params</tt> hash:
46
46
 
47
- get '/:name' do
47
+ get '/hello/:name' do
48
48
  # matches "GET /foo" and "GET /bar"
49
49
  # params[:name] is 'foo' or 'bar'
50
50
  "Hello #{params[:name]}!"
@@ -86,9 +86,13 @@ a different location by setting the <tt>:public</tt> option:
86
86
 
87
87
  set :public, File.dirname(__FILE__) + '/static'
88
88
 
89
+ Note that the public directory name is not included in the URL. A file
90
+ <tt>./public/css/style.css</tt> is made available as
91
+ <tt>http://example.com/css/style.css</tt>.
92
+
89
93
  == Views / Templates
90
94
 
91
- Templates are assumed to be located directly under a <tt>./views</tt>
95
+ Templates are assumed to be located directly under the <tt>./views</tt>
92
96
  directory. To use a different views directory:
93
97
 
94
98
  set :views, File.dirname(__FILE__) + '/templates'
@@ -141,7 +145,7 @@ Renders <tt>./views/stylesheet.sass</tt>.
141
145
 
142
146
  Renders the inlined template string.
143
147
 
144
- === Accessing Variables
148
+ === Accessing Variables in Templates
145
149
 
146
150
  Templates are evaluated within the same context as the route blocks. Instance
147
151
  variables set in route blocks are available in templates:
@@ -181,12 +185,13 @@ Templates may be defined at the end of the source file:
181
185
  @@ index
182
186
  %div.title Hello world!!!!!
183
187
 
184
- NOTE: Sinatra will automaticly load any in-file-templates in the
185
- source file that first required sinatra. If you have in-file-templates
186
- in another source file you will need to explicitly call
187
- +use_in_file_templates! on main in that file.
188
+ NOTE: In-file templates defined in the source file that requires sinatra
189
+ are automatically loaded. Call the <tt>use_in_file_templates!</tt>
190
+ method explicitly if you have in-file templates in another source file.
188
191
 
189
- It's also possible to define named templates using the top-level template
192
+ === Named Templates
193
+
194
+ It's possible to define named templates using the top-level <tt>template</tt>
190
195
  method:
191
196
 
192
197
  template :layout do
@@ -249,7 +254,7 @@ You can also specify a body when halting ...
249
254
 
250
255
  halt 'this will be the body'
251
256
 
252
- Set the status and body ...
257
+ Or set the status and body ...
253
258
 
254
259
  halt 401, 'go away!'
255
260
 
@@ -317,7 +322,7 @@ code is 404, the <tt>not_found</tt> handler is invoked:
317
322
 
318
323
  The +error+ handler is invoked any time an exception is raised from a route
319
324
  block or before filter. The exception object can be obtained from the
320
- 'sinatra.error' Rack variable:
325
+ <tt>sinatra.error</tt> Rack variable:
321
326
 
322
327
  error do
323
328
  'Sorry there was a nasty error - ' + env['sinatra.error'].name
@@ -339,8 +344,8 @@ You get this:
339
344
 
340
345
  So what happened was... something bad
341
346
 
342
- Sinatra installs special not_found and error handlers when running under
343
- the development environment.
347
+ Sinatra installs special <tt>not_found</tt> and <tt>error</tt> handlers when
348
+ running under the development environment.
344
349
 
345
350
  == Mime types
346
351
 
@@ -480,73 +485,39 @@ Options are:
480
485
  -s # specify rack server/handler (default is thin)
481
486
  -x # turn on the mutex lock (default is off)
482
487
 
483
- == Contributing
484
-
485
- === Tools
486
-
487
- Besides Ruby itself, you only need a text editor, preferably one that supports
488
- Ruby syntax hilighting. VIM and Emacs are a fine choice on any platform, but
489
- feel free to use whatever you're familiar with.
488
+ == The Bleeding Edge
490
489
 
491
- Sinatra uses the Git source code management system. If you're unfamiliar with
492
- Git, you can find more information and tutorials on http://git.or.cz/ as well
493
- as http://git-scm.com/. Scott Chacon created a great series of introductory
494
- screencasts about Git, which you can find here: http://www.gitcasts.com/
490
+ If you would like to use Sinatra's latest bleeding code, create a local
491
+ clone and run your app with the <tt>sinatra/lib</tt> directory on the
492
+ <tt>LOAD_PATH</tt>:
495
493
 
496
- === First Time: Cloning The Sinatra Repo
494
+ cd myapp
495
+ git clone git://github.com/sinatra/sinatra.git
496
+ ruby -Isinatra/lib myapp.rb
497
497
 
498
- cd where/you/keep/your/projects
499
- git clone git://github.com/bmizerany/sinatra.git
500
- cd sinatra
501
- cd path/to/your_project
502
- ln -s ../sinatra/
503
-
504
- === Updating Your Existing Sinatra Clone
505
-
506
- cd where/you/keep/sinatra
507
- git pull
508
-
509
- === Using Edge Sinatra in Your App
510
-
511
- at the top of your sinatra_app.rb file:
498
+ Alternatively, you can add the <tt>sinatra/lib<tt> directory to the
499
+ <tt>LOAD_PATH</tt> in your application:
512
500
 
513
501
  $LOAD_PATH.unshift File.dirname(__FILE__) + '/sinatra/lib'
502
+ require 'rubygems'
514
503
  require 'sinatra'
515
504
 
516
505
  get '/about' do
517
- "I'm running on Version " + Sinatra::VERSION
506
+ "I'm running version " + Sinatra::VERSION
518
507
  end
519
508
 
520
- === Contributing a Patch
521
-
522
- There are several ways to do this. Probably the easiest (and preferred) way is
523
- to fork Sinatra on GitHub (http://github.com/bmizerany/sinatra), push your
524
- changes to your Sinatra repo, and then send Blake Mizerany (bmizerany on
525
- GitHub) a pull request.
526
-
527
- You can also create a patch file and attach it to a feature request or bug fix
528
- on the issue tracker (see below) or send it to the mailing list (see Community
529
- section).
509
+ To update the Sinatra sources in the future:
530
510
 
531
- === Issue Tracking and Feature Requests
532
-
533
- http://sinatra.lighthouseapp.com/
534
-
535
- == Community
536
-
537
- === Mailing List
538
-
539
- http://groups.google.com/group/sinatrarb
540
-
541
- If you have a problem or question, please make sure to include all the
542
- relevant information in your mail, like the Sinatra version you're using, what
543
- version of Ruby you have, and so on.
544
-
545
- === IRC Channel
511
+ cd myproject/sinatra
512
+ git pull
546
513
 
547
- You can find us on the Freenode network in the channel #sinatra
548
- (irc://chat.freenode.net/#sinatra)
514
+ == More
549
515
 
550
- There's usually someone online at any given time, but we cannot pay attention
551
- to the channel all the time, so please stick around for a while after asking a
552
- question.
516
+ * {Project Website}[http://sinatra.github.com/] - Additional documentation,
517
+ news, and links to other resources.
518
+ * {Contributing}[http://sinatra.github.com/contribute.html] - Find a bug? Need
519
+ help? Have a patch?
520
+ * {Lighthouse}[http://sinatra.lighthouseapp.com] - Issue tracking and release
521
+ planning.
522
+ * {Mailing List}[http://groups.google.com/group/sinatrarb]
523
+ * {IRC: #sinatra}[irc://chat.freenode.net/#sinatra] on http://freenode.net
data/Rakefile CHANGED
@@ -1,23 +1,18 @@
1
- require 'rubygems'
2
1
  require 'rake/clean'
2
+ require 'rake/testtask'
3
3
  require 'fileutils'
4
4
 
5
- task :default => :test
5
+ task :default => [:compat, :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"
10
+ Rake::TestTask.new(:test) do |t|
11
+ t.test_files = FileList['test/*_test.rb']
12
+ t.ruby_opts = ['-rubygems'] if defined? Gem
18
13
  end
19
14
 
20
- desc 'Run compatibility specs'
15
+ desc 'Run compatibility specs (requires test/spec)'
21
16
  task :compat do |t|
22
17
  pattern = ENV['TEST'] || '.*'
23
18
  sh "specrb --testcase '#{pattern}' -Ilib:test compat/*_test.rb"
@@ -67,11 +62,6 @@ end
67
62
 
68
63
  # Rubyforge Release / Publish Tasks ==================================
69
64
 
70
- desc 'Publish website to rubyforge'
71
- task 'publish:doc' => 'doc/api/index.html' do
72
- sh 'scp -rp doc/* rubyforge.org:/var/www/gforge-projects/sinatra/'
73
- end
74
-
75
65
  desc 'Publish gem and tarball to rubyforge'
76
66
  task 'publish:gem' => [package('.gem'), package('.tar.gz')] do |t|
77
67
  sh <<-end
@@ -84,7 +74,7 @@ end
84
74
  # Building docs requires HAML and the hanna gem:
85
75
  # gem install mislav-hanna --source=http://gems.github.com
86
76
 
87
- task 'doc' => ['doc:api','doc:site']
77
+ task 'doc' => ['doc:api']
88
78
 
89
79
  desc 'Generate Hanna RDoc under doc/api'
90
80
  task 'doc:api' => ['doc/api/index.html']
@@ -110,47 +100,6 @@ def rdoc_to_html(file_name)
110
100
  rdoc.convert(File.read(file_name))
111
101
  end
112
102
 
113
- def haml(locals={})
114
- require 'haml'
115
- template = File.read('doc/template.haml')
116
- haml = Haml::Engine.new(template, :format => :html4, :attr_wrapper => '"')
117
- haml.render(Object.new, locals)
118
- end
119
-
120
- desc 'Build website HTML and stuff'
121
- task 'doc:site' => ['doc/index.html', 'doc/book.html']
122
-
123
- file 'doc/index.html' => %w[README.rdoc doc/template.haml] do |file|
124
- File.open(file.name, 'w') do |file|
125
- file << haml(:title => 'Sinatra', :content => rdoc_to_html('README.rdoc'))
126
- end
127
- end
128
- CLEAN.include 'doc/index.html'
129
-
130
- file 'doc/book.html' => ['book/output/sinatra-book.html'] do |file|
131
- File.open(file.name, 'w') do |file|
132
- book_content = File.read('book/output/sinatra-book.html')
133
- file << haml(:title => 'Sinatra Book', :content => book_content)
134
- end
135
- end
136
- CLEAN.include 'doc/book.html'
137
-
138
- file 'book/output/sinatra-book.html' => FileList['book/**'] do |f|
139
- unless File.directory?('book')
140
- sh 'git clone git://github.com/cschneid/sinatra-book.git book'
141
- end
142
- sh((<<-SH).strip.gsub(/\s+/, ' '))
143
- cd book &&
144
- git fetch origin &&
145
- git rebase origin/master &&
146
- thor book:build
147
- SH
148
- end
149
- CLEAN.include 'book/output/sinatra-book.html'
150
-
151
- desc 'Build the Sinatra book'
152
- task 'doc:book' => ['book/output/sinatra-book.html']
153
-
154
103
  # Gemspec Helpers ====================================================
155
104
 
156
105
  def source_version
data/compat/app_test.rb CHANGED
@@ -8,7 +8,8 @@ context "Sinatra" do
8
8
 
9
9
  specify "should put all DSL methods on (main)" do
10
10
  object = Object.new
11
- Sinatra::Application::FORWARD_METHODS.each do |method|
11
+ methods = %w[get put post head delete configure template helpers set]
12
+ methods.each do |method|
12
13
  object.private_methods.should.include(method)
13
14
  end
14
15
  end
@@ -105,7 +105,7 @@ context "SendData" do
105
105
  end
106
106
 
107
107
  # Deprecated. The Content-Disposition is no longer handled by sendfile.
108
- specify "should include a Content-Disposition header" do
108
+ xspecify "should include a Content-Disposition header" do
109
109
  get '/' do
110
110
  send_file File.dirname(__FILE__) + '/public/foo.xml'
111
111
  end
@@ -118,4 +118,16 @@ context "SendData" do
118
118
  headers['Content-Transfer-Encoding'].should.equal 'binary'
119
119
  end
120
120
 
121
+ specify "should include a Content-Disposition header when :disposition set to attachment" do
122
+ get '/' do
123
+ send_file File.dirname(__FILE__) + '/public/foo.xml',
124
+ :disposition => 'attachment'
125
+ end
126
+
127
+ get_it '/'
128
+
129
+ should.be.ok
130
+ headers['Content-Disposition'].should.not.be.nil
131
+ headers['Content-Disposition'].should.equal 'attachment; filename="foo.xml"'
132
+ end
121
133
  end
data/lib/sinatra/base.rb CHANGED
@@ -125,15 +125,24 @@ module Sinatra
125
125
  end
126
126
  end
127
127
 
128
- # Use the contents of the file as the response body and attempt to
128
+ # Use the contents of the file at +path+ as the response body.
129
129
  def send_file(path, opts={})
130
130
  stat = File.stat(path)
131
131
  last_modified stat.mtime
132
+
132
133
  content_type media_type(opts[:type]) ||
133
134
  media_type(File.extname(path)) ||
134
135
  response['Content-Type'] ||
135
136
  'application/octet-stream'
137
+
136
138
  response['Content-Length'] ||= (opts[:length] || stat.size).to_s
139
+
140
+ if opts[:disposition] == 'attachment' || opts[:filename]
141
+ attachment opts[:filename] || path
142
+ elsif opts[:disposition] == 'inline'
143
+ response['Content-Disposition'] = 'inline'
144
+ end
145
+
137
146
  halt StaticFile.open(path, 'rb')
138
147
  rescue Errno::ENOENT
139
148
  not_found
@@ -142,6 +151,7 @@ module Sinatra
142
151
  class StaticFile < ::File #:nodoc:
143
152
  alias_method :to_path, :path
144
153
  def each
154
+ rewind
145
155
  while buf = read(8192)
146
156
  yield buf
147
157
  end
@@ -243,7 +253,6 @@ module Sinatra
243
253
  locals_assigns = locals.to_a.collect { |k,v| "#{k} = locals[:#{k}]" }
244
254
  src = "#{locals_assigns.join("\n")}\n#{instance.src}"
245
255
  eval src, binding, '(__ERB__)', locals_assigns.length + 1
246
- instance.result(binding)
247
256
  end
248
257
 
249
258
  def haml(template, options={})
@@ -310,7 +319,11 @@ module Sinatra
310
319
  @request = Request.new(env)
311
320
  @response = Response.new
312
321
  @params = nil
313
- error_detection { dispatch! }
322
+
323
+ invoke { dispatch! }
324
+ invoke { error_block!(response.status) }
325
+
326
+ @response.body = [] if @env['REQUEST_METHOD'] == 'HEAD'
314
327
  @response.finish
315
328
  end
316
329
 
@@ -319,7 +332,8 @@ module Sinatra
319
332
  end
320
333
 
321
334
  def halt(*response)
322
- throw :halt, *response
335
+ response = response.first if response.length == 1
336
+ throw :halt, response
323
337
  end
324
338
 
325
339
  def pass
@@ -327,19 +341,21 @@ module Sinatra
327
341
  end
328
342
 
329
343
  private
330
- def dispatch!
331
- self.class.filters.each do |block|
332
- res = catch(:halt) { instance_eval(&block) ; :continue }
333
- return unless res == :continue
334
- end
344
+ # Run before filters and then locate and run a matching route.
345
+ def route!
346
+ @params = nested_params(@request.params)
347
+
348
+ # before filters
349
+ self.class.filters.each { |block| instance_eval(&block) }
335
350
 
351
+ # routes
336
352
  if routes = self.class.routes[@request.request_method]
353
+ original_params = @params
337
354
  path = @request.path_info
338
- original_params = nested_params(@request.params)
339
355
 
340
- routes.each do |pattern, keys, conditions, method_name|
341
- if pattern =~ path
342
- values = $~.captures.map{|val| val && unescape(val) }
356
+ routes.each do |pattern, keys, conditions, block|
357
+ if match = pattern.match(path)
358
+ values = match.captures.map{|val| val && unescape(val) }
343
359
  params =
344
360
  if keys.any?
345
361
  keys.zip(values).inject({}) do |hash,(k,v)|
@@ -357,24 +373,27 @@ module Sinatra
357
373
  end
358
374
  @params = original_params.merge(params)
359
375
 
360
- catch(:pass) {
376
+ catch(:pass) do
361
377
  conditions.each { |cond|
362
378
  throw :pass if instance_eval(&cond) == false }
363
- return invoke(method_name)
364
- }
379
+ throw :halt, instance_eval(&block)
380
+ end
365
381
  end
366
382
  end
367
383
  end
384
+
368
385
  raise NotFound
369
386
  end
370
387
 
371
388
  def nested_params(params)
372
389
  return indifferent_hash.merge(params) if !params.keys.join.include?('[')
373
390
  params.inject indifferent_hash do |res, (key,val)|
374
- if key =~ /\[.*\]/
375
- splat = key.scan(/(^[^\[]+)|\[([^\]]+)\]/).flatten.compact
376
- head, last = splat[0..-2], splat[-1]
377
- head.inject(res){ |s,v| s[v] ||= indifferent_hash }[last] = val
391
+ if key.include?('[')
392
+ head = key.split(/[\]\[]+/)
393
+ last = head.pop
394
+ head.inject(res){ |hash,k| hash[k] ||= indifferent_hash }[last] = val
395
+ else
396
+ res[key] = val
378
397
  end
379
398
  res
380
399
  end
@@ -384,7 +403,8 @@ module Sinatra
384
403
  Hash.new {|hash,key| hash[key.to_s] if Symbol === key }
385
404
  end
386
405
 
387
- def invoke(block)
406
+ # Run the block with 'throw :halt' support and apply result to the response.
407
+ def invoke(&block)
388
408
  res = catch(:halt) { instance_eval(&block) }
389
409
  return if res.nil?
390
410
 
@@ -416,30 +436,48 @@ module Sinatra
416
436
  res
417
437
  end
418
438
 
419
- def error_detection
420
- errmap = self.class.errors
421
- yield
439
+ # Dispatch a request with error handling.
440
+ def dispatch!
441
+ route!
422
442
  rescue NotFound => boom
423
443
  @env['sinatra.error'] = boom
424
444
  @response.status = 404
425
445
  @response.body = ['<h1>Not Found</h1>']
426
- handler = errmap[boom.class] || errmap[NotFound]
427
- invoke handler unless handler.nil?
446
+ error_block! boom.class, NotFound
447
+
428
448
  rescue ::Exception => boom
429
449
  @env['sinatra.error'] = boom
430
450
 
431
451
  if options.dump_errors?
432
- msg = ["#{boom.class} - #{boom.message}:", *boom.backtrace].join("\n ")
433
- @env['rack.errors'] << msg
452
+ backtrace = clean_backtrace(boom.backtrace)
453
+ msg = ["#{boom.class} - #{boom.message}:", *backtrace].join("\n ")
454
+ @env['rack.errors'].write(msg)
434
455
  end
435
456
 
436
457
  raise boom if options.raise_errors?
437
458
  @response.status = 500
438
- invoke errmap[boom.class] || errmap[Exception]
439
- ensure
440
- if @response.status >= 400 && errmap.key?(response.status)
441
- invoke errmap[response.status]
459
+ error_block! boom.class, Exception
460
+ end
461
+
462
+ # Find an custom error block for the key(s) specified.
463
+ def error_block!(*keys)
464
+ errmap = self.class.errors
465
+ keys.each do |key|
466
+ if block = errmap[key]
467
+ res = instance_eval(&block)
468
+ return res
469
+ end
442
470
  end
471
+ nil
472
+ end
473
+
474
+ def clean_backtrace(trace)
475
+ return trace unless options.clean_trace?
476
+
477
+ trace.reject { |line|
478
+ line =~ /lib\/sinatra.*\.rb/ ||
479
+ (defined?(Gem) && line.include?(Gem.dir))
480
+ }.map! { |line| line.gsub(/^\.\//, '') }
443
481
  end
444
482
 
445
483
  @routes = {}
@@ -569,7 +607,7 @@ module Sinatra
569
607
  route('GET', path, opts, &block)
570
608
 
571
609
  @conditions = conditions
572
- head(path, opts) { invoke(block) ; [] }
610
+ route('HEAD', path, opts, &block)
573
611
  end
574
612
 
575
613
  def put(path, opts={}, &bk); route 'PUT', path, opts, &bk; end
@@ -597,18 +635,22 @@ module Sinatra
597
635
  def compile(path)
598
636
  keys = []
599
637
  if path.respond_to? :to_str
638
+ special_chars = %w{. + ( )}
600
639
  pattern =
601
- URI.encode(path).gsub(/((:\w+)|\*)/) do |match|
602
- if match == "*"
640
+ URI.encode(path).gsub(/((:\w+)|[\*#{special_chars.join}])/) do |match|
641
+ case match
642
+ when "*"
603
643
  keys << 'splat'
604
644
  "(.*?)"
645
+ when *special_chars
646
+ Regexp.escape(match)
605
647
  else
606
648
  keys << $2[1..-1]
607
649
  "([^/?&#\.]+)"
608
650
  end
609
651
  end
610
652
  [/^#{pattern}$/, keys]
611
- elsif path.respond_to? :=~
653
+ elsif path.respond_to? :match
612
654
  [path, keys]
613
655
  else
614
656
  raise TypeError, path
@@ -616,6 +658,11 @@ module Sinatra
616
658
  end
617
659
 
618
660
  public
661
+ def helpers(*modules, &block)
662
+ include *modules unless modules.empty?
663
+ class_eval(&block) if block
664
+ end
665
+
619
666
  def development? ; environment == :development ; end
620
667
  def test? ; environment == :test ; end
621
668
  def production? ; environment == :production ; end
@@ -630,8 +677,8 @@ module Sinatra
630
677
  end
631
678
 
632
679
  def run!(options={})
633
- set(options)
634
- handler = Rack::Handler.get(server)
680
+ set options
681
+ handler = detect_rack_handler
635
682
  handler_name = handler.name.gsub(/.*::/, '')
636
683
  puts "== Sinatra/#{Sinatra::VERSION} has taken the stage " +
637
684
  "on #{port} for #{environment} with backup from #{handler_name}"
@@ -652,6 +699,18 @@ module Sinatra
652
699
  end
653
700
 
654
701
  private
702
+ def detect_rack_handler
703
+ servers = Array(self.server)
704
+ servers.each do |server_name|
705
+ begin
706
+ return Rack::Handler.get(server_name)
707
+ rescue LoadError
708
+ rescue NameError
709
+ end
710
+ end
711
+ fail "Server handler (#{servers.join(',')}) not found."
712
+ end
713
+
655
714
  def construct_middleware(builder=Rack::Builder.new)
656
715
  builder.use Rack::Session::Cookie if sessions?
657
716
  builder.use Rack::CommonLogger if logging?
@@ -691,6 +750,7 @@ module Sinatra
691
750
 
692
751
  set :raise_errors, true
693
752
  set :dump_errors, false
753
+ set :clean_trace, true
694
754
  set :sessions, false
695
755
  set :logging, false
696
756
  set :methodoverride, false
@@ -698,7 +758,7 @@ module Sinatra
698
758
  set :environment, (ENV['RACK_ENV'] || :development).to_sym
699
759
 
700
760
  set :run, false
701
- set :server, (defined?(Rack::Handler::Thin) ? "thin" : "mongrel")
761
+ set :server, %w[thin mongrel webrick]
702
762
  set :host, '0.0.0.0'
703
763
  set :port, 4567
704
764
 
@@ -771,7 +831,7 @@ module Sinatra
771
831
  <div id="c">
772
832
  <img src="/__sinatra__/500.png">
773
833
  <h1>#{escape_html(heading)}</h1>
774
- <pre class='trace'>#{escape_html(err.backtrace.join("\n"))}</pre>
834
+ <pre>#{escape_html(clean_backtrace(err.backtrace) * "\n")}</pre>
775
835
  <h2>Params</h2>
776
836
  <pre>#{escape_html(params.inspect)}</pre>
777
837
  </div>
@@ -790,7 +850,8 @@ module Sinatra
790
850
  set :methodoverride, true
791
851
  set :static, true
792
852
  set :run, false
793
- set :reload, Proc.new { app_file? && development? }
853
+ set :reload, Proc.new { app_file? && app_file !~ /\.ru$/i && development? }
854
+ set :lock, Proc.new { reload? }
794
855
 
795
856
  def self.reloading?
796
857
  @reloading ||= false
@@ -801,8 +862,10 @@ module Sinatra
801
862
  end
802
863
 
803
864
  def self.call(env)
804
- reload! if reload?
805
- super
865
+ synchronize do
866
+ reload! if reload?
867
+ super
868
+ end
806
869
  end
807
870
 
808
871
  def self.reload!
@@ -813,26 +876,36 @@ module Sinatra
813
876
  @reloading = false
814
877
  end
815
878
 
879
+ private
880
+ @@mutex = Mutex.new
881
+ def self.synchronize(&block)
882
+ if lock?
883
+ @@mutex.synchronize(&block)
884
+ else
885
+ yield
886
+ end
887
+ end
816
888
  end
817
889
 
818
890
  class Application < Default
819
891
  end
820
892
 
821
893
  module Delegator
822
- METHODS = %w[
823
- get put post delete head template layout before error not_found
824
- configures configure set set_option set_options enable disable use
825
- development? test? production? use_in_file_templates!
826
- ]
827
-
828
- METHODS.each do |method_name|
829
- eval <<-RUBY, binding, '(__DELEGATE__)', 1
830
- def #{method_name}(*args, &b)
831
- ::Sinatra::Application.#{method_name}(*args, &b)
832
- end
833
- private :#{method_name}
834
- RUBY
894
+ def self.delegate(*methods)
895
+ methods.each do |method_name|
896
+ eval <<-RUBY, binding, '(__DELEGATE__)', 1
897
+ def #{method_name}(*args, &b)
898
+ ::Sinatra::Application.#{method_name}(*args, &b)
899
+ end
900
+ private :#{method_name}
901
+ RUBY
902
+ end
835
903
  end
904
+
905
+ delegate :get, :put, :post, :delete, :head, :template, :layout, :before,
906
+ :error, :not_found, :configures, :configure, :set, :set_option,
907
+ :set_options, :enable, :disable, :use, :development?, :test?,
908
+ :production?, :use_in_file_templates!, :helpers
836
909
  end
837
910
 
838
911
  def self.new(base=Base, options={}, &block)